11import * as path from 'path' ;
22import * as fs from 'fs-extra' ;
33
4+ const CDK_OUTDIR_PREFIX = 'cdk-integ.out' ;
5+
46/**
57 * Represents a single integration test
8+ *
9+ * This type is a data-only structure, so it can trivially be passed to workers.
10+ * Derived attributes are calculated using the `IntegTest` class.
611 */
7- export interface IntegTestConfig {
12+ export interface IntegTestInfo {
813 /**
9- * The name of the file that contains the
10- * integration tests. This will be in the format
11- * of integ.{test-name}.js
14+ * Path to the file to run
15+ *
16+ * Path is relative to the current working directory.
1217 */
1318 readonly fileName : string ;
1419
1520 /**
16- * The base directory where the tests are
17- * discovered from
21+ * The root directory we discovered this test from
22+ *
23+ * Path is relative to the current working directory.
1824 */
19- readonly directory : string ;
25+ readonly discoveryRoot : string ;
26+ }
27+
28+ /**
29+ * Derived information for IntegTests
30+ */
31+ export class IntegTest {
32+ /**
33+ * The name of the file to run
34+ *
35+ * Path is relative to the current working directory.
36+ */
37+ public readonly fileName : string ;
38+
39+ /**
40+ * Relative path to the file to run
41+ *
42+ * Relative from the "discovery root".
43+ */
44+ public readonly discoveryRelativeFileName : string ;
45+
46+ /**
47+ * The absolute path to the file
48+ */
49+ public readonly absoluteFileName : string ;
50+
51+ /**
52+ * Directory the test is in
53+ */
54+ public readonly directory : string ;
55+
56+ /**
57+ * Display name for the test
58+ *
59+ * Depends on the discovery directory.
60+ *
61+ * Looks like `integ.mytest` or `package/test/integ.mytest`.
62+ */
63+ public readonly testName : string ;
64+
65+ /**
66+ * Path of the snapshot directory for this test
67+ */
68+ public readonly snapshotDir : string ;
69+
70+ /**
71+ * Path to the temporary output directory for this test
72+ */
73+ public readonly temporaryOutputDir : string ;
74+
75+ constructor ( public readonly info : IntegTestInfo ) {
76+ this . absoluteFileName = path . resolve ( info . fileName ) ;
77+ this . fileName = path . relative ( process . cwd ( ) , info . fileName ) ;
78+
79+ const parsed = path . parse ( this . fileName ) ;
80+ this . discoveryRelativeFileName = path . relative ( info . discoveryRoot , info . fileName ) ;
81+ this . directory = parsed . dir ;
82+
83+ // if we are running in a package directory then just use the fileName
84+ // as the testname, but if we are running in a parent directory with
85+ // multiple packages then use the directory/filename as the testname
86+ //
87+ // Looks either like `integ.mytest` or `package/test/integ.mytest`.
88+ const relDiscoveryRoot = path . relative ( process . cwd ( ) , info . discoveryRoot ) ;
89+ this . testName = this . directory === path . join ( relDiscoveryRoot , 'test' ) || this . directory === path . join ( relDiscoveryRoot )
90+ ? parsed . name
91+ : path . join ( path . relative ( this . info . discoveryRoot , parsed . dir ) , parsed . name ) ;
92+
93+ const nakedTestName = parsed . name . slice ( 6 ) ; // Leave name without 'integ.' and '.ts'
94+ this . snapshotDir = path . join ( this . directory , `${ nakedTestName } .integ.snapshot` ) ;
95+ this . temporaryOutputDir = path . join ( this . directory , `${ CDK_OUTDIR_PREFIX } .${ nakedTestName } ` ) ;
96+ }
97+
98+ /**
99+ * Whether this test matches the user-given name
100+ *
101+ * We are very lenient here. A name matches if it matches:
102+ *
103+ * - The CWD-relative filename
104+ * - The discovery root-relative filename
105+ * - The suite name
106+ * - The absolute filename
107+ */
108+ public matches ( name : string ) {
109+ return [
110+ this . fileName ,
111+ this . discoveryRelativeFileName ,
112+ this . testName ,
113+ this . absoluteFileName ,
114+ ] . includes ( name ) ;
115+ }
20116}
21117
22118/**
@@ -49,7 +145,7 @@ export class IntegrationTests {
49145 * Takes a file name of a file that contains a list of test
50146 * to either run or exclude and returns a list of Integration Tests to run
51147 */
52- public async fromFile ( fileName : string ) : Promise < IntegTestConfig [ ] > {
148+ public async fromFile ( fileName : string ) : Promise < IntegTest [ ] > {
53149 const file : IntegrationTestFileConfig = JSON . parse ( fs . readFileSync ( fileName , { encoding : 'utf-8' } ) ) ;
54150 const foundTests = await this . discover ( ) ;
55151
@@ -65,32 +161,27 @@ export class IntegrationTests {
65161 * If they have provided a test name that we don't find, then we write out that error message.
66162 * - If it is a list of tests to exclude, then we discover all available tests and filter out the tests that were provided by the user.
67163 */
68- private filterTests ( discoveredTests : IntegTestConfig [ ] , requestedTests ?: string [ ] , exclude ?: boolean ) : IntegTestConfig [ ] {
69- if ( ! requestedTests || requestedTests . length === 0 ) {
164+ private filterTests ( discoveredTests : IntegTest [ ] , requestedTests ?: string [ ] , exclude ?: boolean ) : IntegTest [ ] {
165+ if ( ! requestedTests ) {
70166 return discoveredTests ;
71167 }
72- const all = discoveredTests . map ( x => {
73- return path . relative ( x . directory , x . fileName ) ;
74- } ) ;
75- let foundAll = true ;
76- // Pare down found tests to filter
168+
169+
77170 const allTests = discoveredTests . filter ( t => {
78- if ( exclude ) {
79- return ( ! requestedTests . includes ( path . relative ( t . directory , t . fileName ) ) ) ;
80- }
81- return ( requestedTests . includes ( path . relative ( t . directory , t . fileName ) ) ) ;
171+ const matches = requestedTests . some ( pattern => t . matches ( pattern ) ) ;
172+ return matches !== ! ! exclude ; // Looks weird but is equal to (matches && !exclude) || (!matches && exclude)
82173 } ) ;
83174
175+ // If not excluding, all patterns must have matched at least one test
84176 if ( ! exclude ) {
85- const selectedNames = allTests . map ( t => path . relative ( t . directory , t . fileName ) ) ;
86- for ( const unmatched of requestedTests . filter ( t => ! selectedNames . includes ( t ) ) ) {
177+ const unmatchedPatterns = requestedTests . filter ( pattern => ! discoveredTests . some ( t => t . matches ( pattern ) ) ) ;
178+ for ( const unmatched of unmatchedPatterns ) {
87179 process . stderr . write ( `No such integ test: ${ unmatched } \n` ) ;
88- foundAll = false ;
89180 }
90- }
91- if ( ! foundAll ) {
92- process . stderr . write ( `Available tests: ${ all . join ( ' ' ) } \n` ) ;
93- return [ ] ;
181+ if ( unmatchedPatterns . length > 0 ) {
182+ process . stderr . write ( `Available tests: ${ discoveredTests . map ( t => t . discoveryRelativeFileName ) . join ( ' ' ) } \n` ) ;
183+ return [ ] ;
184+ }
94185 }
95186
96187 return allTests ;
@@ -99,23 +190,26 @@ export class IntegrationTests {
99190 /**
100191 * Takes an optional list of tests to look for, otherwise
101192 * it will look for all tests from the directory
193+ *
194+ * @param tests Tests to include or exclude, undefined means include all tests.
195+ * @param exclude Whether the 'tests' list is inclusive or exclusive (inclusive by default).
102196 */
103- public async fromCliArgs ( tests ?: string [ ] , exclude ?: boolean ) : Promise < IntegTestConfig [ ] > {
197+ public async fromCliArgs ( tests ?: string [ ] , exclude ?: boolean ) : Promise < IntegTest [ ] > {
104198 const discoveredTests = await this . discover ( ) ;
105199
106200 const allTests = this . filterTests ( discoveredTests , tests , exclude ) ;
107201
108202 return allTests ;
109203 }
110204
111- private async discover ( ) : Promise < IntegTestConfig [ ] > {
205+ private async discover ( ) : Promise < IntegTest [ ] > {
112206 const files = await this . readTree ( ) ;
113207 const integs = files . filter ( fileName => path . basename ( fileName ) . startsWith ( 'integ.' ) && path . basename ( fileName ) . endsWith ( '.js' ) ) ;
114208 return this . request ( integs ) ;
115209 }
116210
117- private request ( files : string [ ] ) : IntegTestConfig [ ] {
118- return files . map ( fileName => { return { directory : this . directory , fileName } ; } ) ;
211+ private request ( files : string [ ] ) : IntegTest [ ] {
212+ return files . map ( fileName => new IntegTest ( { discoveryRoot : this . directory , fileName } ) ) ;
119213 }
120214
121215 private async readTree ( ) : Promise < string [ ] > {
0 commit comments