Skip to content

Commit 4f49efe

Browse files
authored
feat(integ-runner): support config file (#22937)
Enables support for yarg's config file, replacing the current unused `IntegrationTests.fromFile()` method. This also fixes a bug where `--list` would ignore test provided as args and via `--from-file` and just list all tests. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 5b3d06d commit 4f49efe

File tree

6 files changed

+197
-162
lines changed

6 files changed

+197
-162
lines changed

packages/@aws-cdk/integ-runner/README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ If you are providing a list of tests to execute, either as CLI arguments or from
8585
For example, if there is a test `aws-iam/test/integ.policy.js` and the current working directory is `aws-iam` you would provide `integ.policy.js`
8686

8787
```bash
88-
yarn integ integ.policy.js
88+
integ-runner integ.policy.js
8989
```
9090

9191
### Common Workflow
@@ -121,7 +121,7 @@ Snapshot Results:
121121

122122
Tests: 1 failed, 9 total
123123
Error: Some snapshot tests failed!
124-
To re-run failed tests run: yarn integ-runner --update-on-failed
124+
To re-run failed tests run: integ-runner --update-on-failed
125125
at main (packages/@aws-cdk/integ-runner/lib/cli.js:90:15)
126126
error Command failed with exit code 1.
127127
```
@@ -197,7 +197,7 @@ If you are adding a new test which creates a new snapshot then you should run th
197197
For example, if you are working on a new test `integ.new-test.js` then you would run:
198198

199199
```bash
200-
yarn integ --update-on-failed --disable-update-workflow integ.new-test.js
200+
integ-runner --update-on-failed --disable-update-workflow integ.new-test.js
201201
```
202202

203203
This is because for a new test we do not need to test the update workflow (there is nothing to update).
@@ -210,3 +210,25 @@ See [@aws-cdk/cloud-assembly-schema/lib/integ-tests/schema.ts](../cloud-assembly
210210

211211
See the `@aws-cdk/integ-tests` module for information on how to define
212212
integration tests for the runner to exercise.
213+
214+
### Config file
215+
216+
All options can be configured via the `integ.config.json` configuration file in the current working directory.
217+
218+
```json
219+
{
220+
"maxWorkers": 10,
221+
"parallelRegions": [
222+
"eu-west-1",
223+
"ap-southeast-2"
224+
]
225+
}
226+
```
227+
228+
Available options can be listed by running the following command:
229+
230+
```sh
231+
integ-runner --help
232+
```
233+
234+
To use a different config file, provide the `--config` command-line option.

packages/@aws-cdk/integ-runner/lib/cli.ts

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
// Exercise all integ stacks and if they deploy, update the expected synth files
2-
import { promises as fs } from 'fs';
2+
import * as fs from 'fs';
33
import * as path from 'path';
44
import * as chalk from 'chalk';
55
import * as workerpool from 'workerpool';
66
import * as logger from './logger';
7-
import { IntegrationTests, IntegTestInfo, IntegTest } from './runner/integration-tests';
7+
import { IntegrationTests, IntegTestInfo } from './runner/integration-tests';
88
import { runSnapshotTests, runIntegrationTests, IntegRunnerMetrics, IntegTestWorkerConfig, DestructiveChange } from './workers';
99

1010
// https://github.com/yargs/yargs/issues/1929
1111
// https://github.com/evanw/esbuild/issues/1492
1212
// eslint-disable-next-line @typescript-eslint/no-require-imports
1313
const yargs = require('yargs');
1414

15-
16-
export async function main(args: string[]) {
15+
export function parseCliArgs(args: string[] = []) {
1716
const argv = yargs
1817
.usage('Usage: integ-runner [TEST...]')
18+
.option('config', {
19+
config: true,
20+
configParser: IntegrationTests.configFromFile,
21+
default: 'integ.config.json',
22+
desc: 'Load options from a JSON config file. Options provided as CLI arguments take precedent.',
23+
})
1924
.option('list', { type: 'boolean', default: false, desc: 'List tests instead of running them' })
2025
.option('clean', { type: 'boolean', default: true, desc: 'Skips stack clean up after test is completed (use --no-clean to negate)' })
2126
.option('verbose', { type: 'boolean', default: false, alias: 'v', count: true, desc: 'Verbose logs and metrics on integration tests durations (specify multiple times to increase verbosity)' })
@@ -35,61 +40,87 @@ export async function main(args: string[]) {
3540
.strict()
3641
.parse(args);
3742

38-
const pool = workerpool.pool(path.join(__dirname, '../lib/workers/extract/index.js'), {
39-
maxWorkers: argv['max-workers'],
40-
});
41-
42-
// list of integration tests that will be executed
43-
const testRegex = arrayFromYargs(argv['test-regex']);
44-
const testsToRun: IntegTestWorkerConfig[] = [];
45-
const destructiveChanges: DestructiveChange[] = [];
46-
const testsFromArgs: IntegTest[] = [];
43+
const tests: string[] = argv._;
4744
const parallelRegions = arrayFromYargs(argv['parallel-regions']);
4845
const testRegions: string[] = parallelRegions ?? ['us-east-1', 'us-east-2', 'us-west-2'];
4946
const profiles = arrayFromYargs(argv.profiles);
50-
const runUpdateOnFailed = argv['update-on-failed'] ?? false;
5147
const fromFile: string | undefined = argv['from-file'];
52-
const exclude: boolean = argv.exclude;
53-
const app: string | undefined = argv.app;
48+
const maxWorkers: number = argv['max-workers'];
49+
const verbosity: number = argv.verbose;
50+
const verbose: boolean = verbosity >= 1;
5451

55-
let failedSnapshots: IntegTestWorkerConfig[] = [];
56-
if (argv['max-workers'] < testRegions.length * (profiles ?? [1]).length) {
57-
logger.warning('You are attempting to run %s tests in parallel, but only have %s workers. Not all of your profiles+regions will be utilized', argv.profiles * argv['parallel-regions'], argv['max-workers']);
52+
const numTests = testRegions.length * (profiles ?? [1]).length;
53+
if (maxWorkers < numTests) {
54+
logger.warning('You are attempting to run %s tests in parallel, but only have %s workers. Not all of your profiles+regions will be utilized', numTests, maxWorkers);
5855
}
5956

60-
let testsSucceeded = false;
61-
try {
62-
if (argv.list) {
63-
const tests = await new IntegrationTests(argv.directory).fromCliArgs({ testRegex, app });
64-
process.stdout.write(tests.map(t => t.discoveryRelativeFileName).join('\n') + '\n');
65-
return;
66-
}
57+
if (tests.length > 0 && fromFile) {
58+
throw new Error('A list of tests cannot be provided if "--from-file" is provided');
59+
}
60+
const requestedTests = fromFile
61+
? (fs.readFileSync(fromFile, { encoding: 'utf8' })).split('\n').filter(x => x)
62+
: (tests.length > 0 ? tests : undefined); // 'undefined' means no request
63+
64+
return {
65+
tests: requestedTests,
66+
app: argv.app as (string | undefined),
67+
testRegex: arrayFromYargs(argv['test-regex']),
68+
testRegions,
69+
profiles,
70+
runUpdateOnFailed: (argv['update-on-failed'] ?? false) as boolean,
71+
fromFile,
72+
exclude: argv.exclude as boolean,
73+
maxWorkers,
74+
list: argv.list as boolean,
75+
directory: argv.directory as string,
76+
inspectFailures: argv['inspect-failures'] as boolean,
77+
verbosity,
78+
verbose,
79+
clean: argv.clean as boolean,
80+
force: argv.force as boolean,
81+
dryRun: argv['dry-run'] as boolean,
82+
disableUpdateWorkflow: argv['disable-update-workflow'] as boolean,
83+
};
84+
}
6785

68-
if (argv._.length > 0 && fromFile) {
69-
throw new Error('A list of tests cannot be provided if "--from-file" is provided');
70-
}
71-
const requestedTests = fromFile
72-
? (await fs.readFile(fromFile, { encoding: 'utf8' })).split('\n').filter(x => x)
73-
: (argv._.length > 0 ? argv._ : undefined); // 'undefined' means no request
7486

75-
testsFromArgs.push(...(await new IntegrationTests(path.resolve(argv.directory)).fromCliArgs({
76-
app,
77-
testRegex,
78-
tests: requestedTests,
79-
exclude,
80-
})));
87+
export async function main(args: string[]) {
88+
const options = parseCliArgs(args);
89+
90+
const testsFromArgs = await new IntegrationTests(path.resolve(options.directory)).fromCliArgs({
91+
app: options.app,
92+
testRegex: options.testRegex,
93+
tests: options.tests,
94+
exclude: options.exclude,
95+
});
96+
97+
// List only prints the discoverd tests
98+
if (options.list) {
99+
process.stdout.write(testsFromArgs.map(t => t.discoveryRelativeFileName).join('\n') + '\n');
100+
return;
101+
}
102+
103+
const pool = workerpool.pool(path.join(__dirname, '../lib/workers/extract/index.js'), {
104+
maxWorkers: options.maxWorkers,
105+
});
81106

107+
const testsToRun: IntegTestWorkerConfig[] = [];
108+
const destructiveChanges: DestructiveChange[] = [];
109+
let failedSnapshots: IntegTestWorkerConfig[] = [];
110+
let testsSucceeded = false;
111+
112+
try {
82113
// always run snapshot tests, but if '--force' is passed then
83114
// run integration tests on all failed tests, not just those that
84115
// failed snapshot tests
85116
failedSnapshots = await runSnapshotTests(pool, testsFromArgs, {
86-
retain: argv['inspect-failures'],
87-
verbose: Boolean(argv.verbose),
117+
retain: options.inspectFailures,
118+
verbose: options.verbose,
88119
});
89120
for (const failure of failedSnapshots) {
90121
destructiveChanges.push(...failure.destructiveChanges ?? []);
91122
}
92-
if (!argv.force) {
123+
if (!options.force) {
93124
testsToRun.push(...failedSnapshots);
94125
} else {
95126
// if any of the test failed snapshot tests, keep those results
@@ -98,25 +129,25 @@ export async function main(args: string[]) {
98129
}
99130

100131
// run integration tests if `--update-on-failed` OR `--force` is used
101-
if (runUpdateOnFailed || argv.force) {
132+
if (options.runUpdateOnFailed || options.force) {
102133
const { success, metrics } = await runIntegrationTests({
103134
pool,
104135
tests: testsToRun,
105-
regions: testRegions,
106-
profiles,
107-
clean: argv.clean,
108-
dryRun: argv['dry-run'],
109-
verbosity: argv.verbose,
110-
updateWorkflow: !argv['disable-update-workflow'],
136+
regions: options.testRegions,
137+
profiles: options.profiles,
138+
clean: options.clean,
139+
dryRun: options.dryRun,
140+
verbosity: options.verbosity,
141+
updateWorkflow: !options.disableUpdateWorkflow,
111142
});
112143
testsSucceeded = success;
113144

114145

115-
if (argv.clean === false) {
146+
if (options.clean === false) {
116147
logger.warning('Not cleaning up stacks since "--no-clean" was used');
117148
}
118149

119-
if (Boolean(argv.verbose)) {
150+
if (Boolean(options.verbose)) {
120151
printMetrics(metrics);
121152
}
122153

@@ -134,8 +165,8 @@ export async function main(args: string[]) {
134165
}
135166
if (failedSnapshots.length > 0) {
136167
let message = '';
137-
if (!runUpdateOnFailed) {
138-
message = 'To re-run failed tests run: yarn integ-runner --update-on-failed';
168+
if (!options.runUpdateOnFailed) {
169+
message = 'To re-run failed tests run: integ-runner --update-on-failed';
139170
}
140171
if (!testsSucceeded) {
141172
throw new Error(`Some tests failed!\n${message}`);

packages/@aws-cdk/integ-runner/lib/runner/integration-tests.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -175,32 +175,26 @@ export interface IntegrationTestsDiscoveryOptions {
175175
}
176176

177177

178-
/**
179-
* The list of tests to run can be provided in a file
180-
* instead of as command line arguments.
181-
*/
182-
export interface IntegrationTestFileConfig extends IntegrationTestsDiscoveryOptions {
183-
/**
184-
* List of tests to include (or exclude if `exclude=true`)
185-
*/
186-
readonly tests: string[];
187-
}
188-
189178
/**
190179
* Discover integration tests
191180
*/
192181
export class IntegrationTests {
193-
constructor(private readonly directory: string) {
194-
}
195-
196182
/**
197-
* Takes a file name of a file that contains a list of test
198-
* to either run or exclude and returns a list of Integration Tests to run
183+
* Return configuration options from a file
199184
*/
200-
public async fromFile(fileName: string): Promise<IntegTest[]> {
201-
const file: IntegrationTestFileConfig = JSON.parse(fs.readFileSync(fileName, { encoding: 'utf-8' }));
185+
public static configFromFile(fileName?: string): Record<string, any> {
186+
if (!fileName) {
187+
return {};
188+
}
202189

203-
return this.discover(file);
190+
try {
191+
return JSON.parse(fs.readFileSync(fileName, { encoding: 'utf-8' }));
192+
} catch {
193+
return {};
194+
}
195+
}
196+
197+
constructor(private readonly directory: string) {
204198
}
205199

206200
/**

packages/@aws-cdk/integ-runner/lib/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class WorkList<A> {
7575

7676
public done() {
7777
this.remaining.clear();
78+
this.stopTimer();
7879
}
7980

8081
private stopTimer() {

0 commit comments

Comments
 (0)