Skip to content

Commit

Permalink
feat: multi language api
Browse files Browse the repository at this point in the history
  • Loading branch information
mrgrain committed Sep 23, 2022
1 parent 14d538c commit be513c7
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 28 deletions.
25 changes: 20 additions & 5 deletions packages/@aws-cdk/integ-runner/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as path from 'path';
import * as chalk from 'chalk';
import * as workerpool from 'workerpool';
import * as logger from './logger';
import { IntegrationTests, IntegTestInfo, IntegTest, IntegrationTestFileType } from './runner/integration-tests';
import { IntegrationTests, IntegTestInfo, IntegTest, Language } from './runner/integration-tests';
import { runSnapshotTests, runIntegrationTests, IntegRunnerMetrics, IntegTestWorkerConfig, DestructiveChange } from './workers';

// https://github.com/yargs/yargs/issues/1929
Expand All @@ -30,7 +30,14 @@ async function main() {
.options('from-file', { type: 'string', desc: 'Read TEST names from a file (one TEST per line)' })
.option('inspect-failures', { type: 'boolean', desc: 'Keep the integ test cloud assembly if a failure occurs for inspection', default: false })
.option('disable-update-workflow', { type: 'boolean', default: false, desc: 'If this is "true" then the stack update workflow will be disabled' })
.option('t', { alias: 'typescript', type: 'boolean', default: false, desc: 'Discover and run TypeScript files with ts-node instead of JavaScript files' })
.option('language', {
alias: 'l',
default: 'javascript',
choices: ['javascript', 'typescript', 'python'],
desc: 'Use this preset to run integration tests for the selected language',
})
.option('app', { alias: 'a', defaultDescription: 'node {filePath}', type: 'string', desc: 'The app to execute integration tests with. Use {filePath} to mark the path of the app under test.' })
.option('test-match', { alias: 'm', defaultDescription: 'integ.*.js', type: 'array', desc: 'The glob patterns used to detect integration test files. By default it will match files starting with \'integ.\'.' })
.strict()
.argv;

Expand All @@ -48,7 +55,13 @@ async function main() {
const runUpdateOnFailed = argv['update-on-failed'] ?? false;
const fromFile: string | undefined = argv['from-file'];
const exclude: boolean = argv.exclude;
const fileType: IntegrationTestFileType = argv.t ? IntegrationTestFileType.TYPESCRIPT : IntegrationTestFileType.JAVASCRIPT;
const language: Language = argv.language;
const app: string = argv.app;
const testMatch: string[] = argv.testMatch;
const discoveryOptions = {
app,
testMatch,
};

let failedSnapshots: IntegTestWorkerConfig[] = [];
if (argv['max-workers'] < testRegions.length * (profiles ?? [1]).length) {
Expand All @@ -58,7 +71,7 @@ async function main() {
let testsSucceeded = false;
try {
if (argv.list) {
const tests = await new IntegrationTests(argv.directory).fromCliArgs(undefined, undefined, fileType);
const tests = await new IntegrationTests(argv.directory).fromCliArgs(undefined, undefined, language, discoveryOptions);
process.stdout.write(tests.map(t => t.discoveryRelativeFileName).join('\n') + '\n');
return;
}
Expand All @@ -70,7 +83,9 @@ async function main() {
? (await fs.readFile(fromFile, { encoding: 'utf8' })).split('\n').filter(x => x)
: (argv._.length > 0 ? argv._ : undefined); // 'undefined' means no request

testsFromArgs.push(...(await new IntegrationTests(path.resolve(argv.directory)).fromCliArgs(requestedTests, exclude, fileType)));
testsFromArgs.push(
...(await new IntegrationTests(path.resolve(argv.directory)).fromCliArgs(requestedTests, exclude, language, discoveryOptions)),
);

// always run snapshot tests, but if '--force' is passed then
// run integration tests on all failed tests, not just those that
Expand Down
87 changes: 64 additions & 23 deletions packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export interface IntegTestInfo {
* Path is relative to the current working directory.
*/
readonly discoveryRoot: string;

/**
* The app use the run the integration test.
*/
readonly app?: string;
}

/**
Expand Down Expand Up @@ -107,7 +112,7 @@ export class IntegTest {
this.snapshotDir = path.join(this.directory, `${nakedTestName}.integ.snapshot`);
this.temporaryOutputDir = path.join(this.directory, `${CDK_OUTDIR_PREFIX}.${nakedTestName}`);

this.app = `${this.getExec()} ${path.relative(this.directory, this.fileName)}`;
this.app = info.app ?? `node ${path.relative(this.directory, this.fileName)}`;
}


Expand All @@ -129,22 +134,33 @@ export class IntegTest {
this.absoluteFileName,
].includes(name);
}
}
class IntegrationTestPreset {
public constructor(readonly app: string, readonly testMatch: string[]) {}
}

const PRESETS = {
javascript: new IntegrationTestPreset('node {filePath}', ['integ.*.js']),
typescript: new IntegrationTestPreset('node -r ts-node/register {filePath}', ['integ.*.ts']),
python: new IntegrationTestPreset('python {filePath}', ['integ.*.py']),
};

/**
* Find the executable used to run this test
*/
private getExec(): string {
if (this.fileName.endsWith(IntegrationTestFileType.TYPESCRIPT)) {
return 'node -r ts-node/register';
}
return 'node';
}
export enum Language {
JAVASCRIPT = 'javascript',
TYPESCRIPT = 'typescript',
PYTHON = 'python',
}

export enum IntegrationTestFileType {
TYPESCRIPT = '.ts',
JAVASCRIPT = '.js'
export interface IntegrationTestDiscoveryOptions {
/**
* Language preset used for integration test
*/
readonly app: string;

/**
* Glob patterns used to match integration test files.
*/
readonly testMatch: string[];
}

/**
Expand All @@ -166,9 +182,19 @@ export interface IntegrationTestFileConfig {
readonly tests: string[];

/**
* Type of files used for integration test
* Language preset used for integration test
*/
readonly fileTypes?: IntegrationTestFileType;
readonly language?: Language;

/**
* Language preset used for integration test
*/
readonly app?: string;

/**
* Glob patterns used to match integration test files.
*/
readonly testMatch?: string[];
}

/**
Expand All @@ -184,7 +210,14 @@ export class IntegrationTests {
*/
public async fromFile(fileName: string): Promise<IntegTest[]> {
const file: IntegrationTestFileConfig = JSON.parse(fs.readFileSync(fileName, { encoding: 'utf-8' }));
const foundTests = await this.discover(file.fileTypes);
const discoveryOptions: Partial<IntegrationTestDiscoveryOptions> = {
app: file.app,
testMatch: file.testMatch,
};
const foundTests = await this.discover({
...PRESETS[file.language ?? 'javascript'],
...discoveryOptions,
});

const allTests = this.filterTests(foundTests, file.tests, file.exclude);

Expand Down Expand Up @@ -234,23 +267,31 @@ export class IntegrationTests {
public async fromCliArgs(
tests?: string[],
exclude?: boolean,
fileType: IntegrationTestFileType = IntegrationTestFileType.JAVASCRIPT,
language: Language = Language.JAVASCRIPT,
discoveryOptions: Partial<IntegrationTestDiscoveryOptions> = {},
): Promise<IntegTest[]> {
const discoveredTests = await this.discover(fileType);
const discoveredTests = await this.discover({
...PRESETS[language],
...discoveryOptions,
});

const allTests = this.filterTests(discoveredTests, tests, exclude);

return allTests;
}

private async discover(ext: IntegrationTestFileType = IntegrationTestFileType.JAVASCRIPT): Promise<IntegTest[]> {
private async discover(options: IntegrationTestDiscoveryOptions): Promise<IntegTest[]> {
const files = await this.readTree();
const integs = files.filter(fileName => path.basename(fileName).startsWith('integ.') && path.basename(fileName).endsWith(ext) && !path.basename(fileName).endsWith('.d.ts'));
return this.request(integs);
const integs = files.filter(fileName => path.basename(fileName).startsWith('integ.'));
return this.request(integs, options.app);
}

private request(files: string[]): IntegTest[] {
return files.map(fileName => new IntegTest({ discoveryRoot: this.directory, fileName }));
private request(files: string[], app: string): IntegTest[] {
return files.map(fileName => new IntegTest({
discoveryRoot: this.directory,
fileName,
app: app.replace('{filePath}', path.relative(this.directory, fileName)),
}));
}

private async readTree(): Promise<string[]> {
Expand Down

0 comments on commit be513c7

Please sign in to comment.