Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate aws-cdk from nodeunit to jest #5659

Merged
merged 1 commit into from
Jan 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ cdk.context.json
.cdk.staging/
cdk.out/
*.tabl.json

# Yarn error log
yarn-error.log
18 changes: 15 additions & 3 deletions packages/aws-cdk/lib/api/util/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export interface SDKOptions {
* @default No certificate bundle
*/
caBundlePath?: string;

/**
* The custom suer agent to use.
*
* @default - <package-name>/<package-version>
*/
userAgent?: string;
}

/**
Expand Down Expand Up @@ -191,9 +198,14 @@ export class SDK implements ISDK {
private async configureSDKHttpOptions(options: SDKOptions) {
const config: {[k: string]: any} = {};
const httpOptions: {[k: string]: any} = {};
// Find the package.json from the main toolkit
const pkg = (require.main as any).require('../package.json');
config.customUserAgent = `${pkg.name}/${pkg.version}`;

let userAgent = options.userAgent;
if (userAgent == null) {
// Find the package.json from the main toolkit
const pkg = (require.main as any).require('../package.json');
userAgent = `${pkg.name}/${pkg.version}`;
}
config.customUserAgent = userAgent;

// https://aws.amazon.com/blogs/developer/using-the-aws-sdk-for-javascript-from-behind-a-proxy/
options.proxyAddress = options.proxyAddress || httpsProxyFromEnvironment();
Expand Down
58 changes: 29 additions & 29 deletions packages/aws-cdk/lib/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const CDK_HOME = process.env.CDK_HOME ? path.resolve(process.env.CDK_HOME) : pat
/**
* Initialize a CDK package in the current directory
*/
export async function cliInit(type?: string, language?: string, canUseNetwork = true, generateOnly = false) {
export async function cliInit(type?: string, language?: string, canUseNetwork = true, generateOnly = false, workDir = process.cwd()) {
if (!type && !language) {
await printAvailableTemplates();
return;
Expand All @@ -43,7 +43,7 @@ export async function cliInit(type?: string, language?: string, canUseNetwork =
throw new Error('No language was selected');
}

await initializeProject(template, language, canUseNetwork, generateOnly);
await initializeProject(template, language, canUseNetwork, generateOnly, workDir);
}

/**
Expand Down Expand Up @@ -237,13 +237,13 @@ export async function printAvailableTemplates(language?: string) {
}
}

async function initializeProject(template: InitTemplate, language: string, canUseNetwork: boolean, generateOnly: boolean) {
await assertIsEmptyDirectory();
async function initializeProject(template: InitTemplate, language: string, canUseNetwork: boolean, generateOnly: boolean, workDir: string) {
await assertIsEmptyDirectory(workDir);
print(`Applying project template ${colors.green(template.name)} for ${colors.blue(language)}`);
await template.install(language, process.cwd());
await template.install(language, workDir);
if (!generateOnly) {
await initializeGitRepository();
await postInstall(language, canUseNetwork);
await initializeGitRepository(workDir);
await postInstall(language, canUseNetwork, workDir);
}
if (await fs.pathExists('README.md')) {
print(colors.green(await fs.readFile('README.md', { encoding: 'utf-8' })));
Expand All @@ -252,43 +252,43 @@ async function initializeProject(template: InitTemplate, language: string, canUs
}
}

async function assertIsEmptyDirectory() {
const files = await fs.readdir(process.cwd());
async function assertIsEmptyDirectory(workDir: string) {
const files = await fs.readdir(workDir);
if (files.filter(f => !f.startsWith('.')).length !== 0) {
throw new Error('`cdk init` cannot be run in a non-empty directory!');
}
}

async function initializeGitRepository() {
if (await isInGitRepository(process.cwd())) { return; }
async function initializeGitRepository(workDir: string) {
if (await isInGitRepository(workDir)) { return; }
print('Initializing a new git repository...');
try {
await execute('git', 'init');
await execute('git', 'add', '.');
await execute('git', 'commit', '--message="Initial commit"', '--no-gpg-sign');
await execute('git', ['init'], { cwd: workDir });
await execute('git', ['add', '.'], { cwd: workDir });
await execute('git', ['commit', '--message="Initial commit"', '--no-gpg-sign'], { cwd: workDir });
} catch (e) {
warning('Unable to initialize git repository for your project.');
}
}

async function postInstall(language: string, canUseNetwork: boolean) {
async function postInstall(language: string, canUseNetwork: boolean, workDir: string) {
switch (language) {
case 'javascript':
return await postInstallJavascript(canUseNetwork);
return await postInstallJavascript(canUseNetwork, workDir);
case 'typescript':
return await postInstallTypescript(canUseNetwork);
return await postInstallTypescript(canUseNetwork, workDir);
case 'java':
return await postInstallJava(canUseNetwork);
return await postInstallJava(canUseNetwork, workDir);
case 'python':
return await postInstallPython();
return await postInstallPython(workDir);
}
}

async function postInstallJavascript(canUseNetwork: boolean) {
return postInstallTypescript(canUseNetwork);
async function postInstallJavascript(canUseNetwork: boolean, cwd: string) {
return postInstallTypescript(canUseNetwork, cwd);
}

async function postInstallTypescript(canUseNetwork: boolean) {
async function postInstallTypescript(canUseNetwork: boolean, cwd: string) {
const command = 'npm';

if (!canUseNetwork) {
Expand All @@ -298,27 +298,27 @@ async function postInstallTypescript(canUseNetwork: boolean) {

print(`Executing ${colors.green(`${command} install`)}...`);
try {
await execute(command, 'install');
await execute(command, ['install'], { cwd });
} catch (e) {
throw new Error(`${colors.green(`${command} install`)} failed: ` + e.message);
}
}

async function postInstallJava(canUseNetwork: boolean) {
async function postInstallJava(canUseNetwork: boolean, cwd: string) {
if (!canUseNetwork) {
print(`Please run ${colors.green(`mvn package`)}!`);
return;
}

print(`Executing ${colors.green('mvn package')}...`);
await execute('mvn', 'package');
await execute('mvn', ['package'], { cwd });
}

async function postInstallPython() {
async function postInstallPython(cwd: string) {
const python = pythonExecutable();
print(`Executing ${colors.green('Creating virtualenv...')}`);
try {
await execute(python, '-m venv', '.env');
await execute(python, ['-m venv', '.env'], { cwd });
} catch (e) {
print('Unable to create virtualenv automatically');
print(`Please run ${colors.green(python + ' -m venv .env')}!`);
Expand Down Expand Up @@ -353,8 +353,8 @@ function isRoot(dir: string) {
*
* @returns STDOUT (if successful).
*/
async function execute(cmd: string, ...args: string[]) {
const child = childProcess.spawn(cmd, args, { shell: true, stdio: [ 'ignore', 'pipe', 'inherit' ] });
async function execute(cmd: string, args: string[], { cwd }: { cwd: string }) {
const child = childProcess.spawn(cmd, args, { cwd, shell: true, stdio: [ 'ignore', 'pipe', 'inherit' ] });
let stdout = '';
child.stdout.on('data', chunk => stdout += chunk.toString());
return new Promise<string>((ok, fail) => {
Expand Down
36 changes: 25 additions & 11 deletions packages/aws-cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@
]
}
},
"nyc": {
"statements": 8,
"lines": 8,
"branches": 3
},
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
Expand All @@ -47,11 +42,11 @@
"@types/archiver": "^3.0.0",
"@types/fs-extra": "^8.0.1",
"@types/glob": "^7.1.1",
"@types/jest": "^24.0.25",
"@types/jszip": "^3.1.6",
"@types/minimatch": "^3.0.3",
"@types/mockery": "^1.4.29",
"@types/node": "^10.17.13",
"@types/nodeunit": "^0.0.30",
"@types/promptly": "^3.0.0",
"@types/request": "^2.48.4",
"@types/semver": "^6.2.0",
Expand All @@ -62,18 +57,19 @@
"@types/yargs": "^13.0.4",
"aws-sdk-mock": "^5.0.0",
"cdk-build-tools": "1.19.0",
"jest": "^24.9.0",
"jszip": "^3.2.2",
"mockery": "^2.1.0",
"nodeunit": "^0.11.3",
"pkglint": "1.19.0",
"sinon": "^8.0.2"
"sinon": "^8.0.2",
"ts-jest": "^24.2.0"
},
"dependencies": {
"@aws-cdk/cloudformation-diff": "1.19.0",
"@aws-cdk/cx-api": "1.19.0",
"@aws-cdk/region-info": "1.19.0",
"archiver": "^3.1.1",
"aws-sdk": "^2.597.0",
"aws-sdk": "^2.596.0",
"camelcase": "^5.3.1",
"colors": "^1.4.0",
"decamelize": "^3.2.0",
Expand All @@ -88,7 +84,7 @@
"table": "^5.4.6",
"uuid": "^3.3.3",
"yaml": "^1.7.2",
"yargs": "^15.1.0"
"yargs": "^15.0.2"
},
"repository": {
"url": "https://github.com/aws/aws-cdk.git",
Expand All @@ -103,5 +99,23 @@
"engines": {
"node": ">= 10.3.0"
},
"stability": "stable"
"stability": "stable",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this duplicated configuration everywhere? For example the reporter types should be the same for all modules!

"jest": {
"collectCoverage": true,
"coverageReporters": [
"lcov",
"html",
"text-summary"
],
"coverageThreshold": {
"global": {
"branches": 45,
"statements": 60
}
},
"preset": "ts-jest",
"testMatch": [
"**/?(*.)+(spec|test).ts?(x)"
]
}
}
91 changes: 91 additions & 0 deletions packages/aws-cdk/test/account-cache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { AccountAccessKeyCache } from '../lib/api/util/account-cache';

async function makeCache() {
const dir = await fs.mkdtemp('/tmp/account-cache-test');
const file = path.join(dir, 'cache.json');
return {
cacheDir: dir,
cacheFile: file,
cache: new AccountAccessKeyCache(file),
};
}

async function nukeCache(cacheDir: string) {
await fs.remove(cacheDir);
}

test('get(k) when cache is empty', async () => {
const { cacheDir, cacheFile, cache } = await makeCache();
try {
expect(await cache.get('foo')).toBeUndefined();
expect(await fs.pathExists(cacheFile)).toBeFalsy();
} finally {
await nukeCache(cacheDir);
}
});

test('put(k,v) and then get(k)', async () => {
const { cacheDir, cacheFile, cache } = await makeCache();

try {
await cache.put('key', 'value');
await cache.put('boo', 'bar');
expect(await cache.get('key')).toBe('value');

// create another cache instance on the same file, should still work
const cache2 = new AccountAccessKeyCache(cacheFile);
expect(await cache2.get('boo')).toBe('bar');

// whitebox: read the file
expect(await fs.readJson(cacheFile)).toEqual({
key: 'value',
boo: 'bar'
});
} finally {
await nukeCache(cacheDir);
}
});

test('fetch(k, resolver) can be used to "atomically" get + resolve + put', async () => {
const { cacheDir, cache } = await makeCache();

try {
expect(await cache.get('foo')).toBeUndefined();
expect(await cache.fetch('foo', async () => 'bar')).toBe('bar');
expect(await cache.get('foo')).toBe('bar');
} finally {
await nukeCache(cacheDir);
}
});

test(`cache is nuked if it exceeds ${AccountAccessKeyCache.MAX_ENTRIES} entries`, async () => {
// This makes a lot of promises, so it can queue for a while...
jest.setTimeout(30_000);

const { cacheDir, cacheFile, cache } = await makeCache();

try {
for (let i = 0; i < AccountAccessKeyCache.MAX_ENTRIES; ++i) {
await cache.put(`key${i}`, `value${i}`);
}

// verify all values are on disk
const otherCache = new AccountAccessKeyCache(cacheFile);
for (let i = 0; i < AccountAccessKeyCache.MAX_ENTRIES; ++i) {
expect(await otherCache.get(`key${i}`)).toBe(`value${i}`);
}

// add another value
await cache.put('nuke-me', 'genesis');

// now, we expect only `nuke-me` to exist on disk
expect(await otherCache.get('nuke-me')).toBe('genesis');
for (let i = 0; i < AccountAccessKeyCache.MAX_ENTRIES; ++i) {
expect(await otherCache.get(`key${i}`)).toBeUndefined();
}
} finally {
await nukeCache(cacheDir);
}
});
Loading