@@ -17,8 +17,8 @@ import { reduceTestItemChildren } from "./TestUtils";
1717
1818type ProcessResult = {
1919 testItems : vscode . TestItem [ ] ;
20- xcTestArgs : string [ ] ;
21- swiftTestArgs : string [ ] ;
20+ xcTestArgs : vscode . TestItem [ ] ;
21+ swiftTestArgs : vscode . TestItem [ ] ;
2222} ;
2323
2424/**
@@ -30,14 +30,11 @@ export class TestRunArguments {
3030 public xcTestArgs : string [ ] ;
3131 public swiftTestArgs : string [ ] ;
3232
33- constructor (
34- request : vscode . TestRunRequest ,
35- private isDebug : boolean
36- ) {
37- const { testItems, xcTestArgs, swiftTestArgs } = this . createTestLists ( request ) ;
33+ constructor ( request : vscode . TestRunRequest , isDebug : boolean ) {
34+ const { testItems, xcTestArgs, swiftTestArgs } = this . createTestLists ( request , isDebug ) ;
3835 this . testItems = testItems ;
39- this . xcTestArgs = xcTestArgs ;
40- this . swiftTestArgs = swiftTestArgs ;
36+ this . xcTestArgs = this . annotateTestArgs ( xcTestArgs , isDebug ) ;
37+ this . swiftTestArgs = this . annotateTestArgs ( swiftTestArgs , isDebug ) ;
4138 }
4239
4340 public get hasXCTests ( ) : boolean {
@@ -52,9 +49,10 @@ export class TestRunArguments {
5249 * Construct test item list from TestRequest
5350 * @returns list of test items to run and list of test for XCTest arguments
5451 */
55- private createTestLists ( request : vscode . TestRunRequest ) : ProcessResult {
52+ private createTestLists ( request : vscode . TestRunRequest , isDebug : boolean ) : ProcessResult {
5653 const includes = request . include ?? [ ] ;
57- return includes . reduce ( this . createTestItemReducer ( request . include , request . exclude ) , {
54+ const excludes = request . exclude ?? [ ] ;
55+ return includes . reduce ( this . createTestItemReducer ( includes , excludes , isDebug ) , {
5856 testItems : this . createIncludeParentList ( includes ) ,
5957 xcTestArgs : [ ] ,
6058 swiftTestArgs : [ ] ,
@@ -78,15 +76,42 @@ export class TestRunArguments {
7876 return Array . from ( parents . values ( ) ) ;
7977 }
8078
79+ /**
80+ * Converts a list of TestItems to a regex test item ID. Depending on the TestItem's
81+ * tags and whether it is a debug run the ID is converted to a regex pattern that will
82+ * match the correct tests when passed to the `--filter` argument of `swift test`.
83+ */
84+ private annotateTestArgs ( testArgs : vscode . TestItem [ ] , isDebug : boolean ) : string [ ] {
85+ return testArgs . map ( arg => {
86+ const isTestTarget = ! ! arg . tags . find ( tag => tag . id === "test-target" ) ;
87+ if ( isTestTarget ) {
88+ return `${ arg . id } .*` ;
89+ }
90+ const isXCTest = ! ! arg . tags . find ( tag => tag . id === "XCTest" ) ;
91+ const hasChildren = arg . children . size > 0 ;
92+ if ( isXCTest ) {
93+ const terminator = hasChildren ? "/" : "$" ;
94+ // Debugging XCTests requires exact matches, so we don't need a trailing terminator.
95+ return isDebug ? arg . id : `${ arg . id } ${ terminator } ` ;
96+ } else {
97+ // Append a trailing slash to match a suite name exactly.
98+ // This prevents TestTarget.MySuite matching TestTarget.MySuite2.
99+ return `${ arg . id } /` ;
100+ }
101+ } ) ;
102+ }
103+
81104 private createTestItemReducer (
82- include : readonly vscode . TestItem [ ] | undefined ,
83- exclude : readonly vscode . TestItem [ ] | undefined
105+ include : readonly vscode . TestItem [ ] ,
106+ exclude : readonly vscode . TestItem [ ] ,
107+ isDebug : boolean
84108 ) : ( previousValue : ProcessResult , testItem : vscode . TestItem ) => ProcessResult {
85109 return ( previousValue , testItem ) => {
86110 const { testItems, swiftTestArgs, xcTestArgs } = this . processTestItem (
87111 testItem ,
88112 include ,
89- exclude
113+ exclude ,
114+ isDebug
90115 ) ;
91116
92117 // If no children were added we can skip adding this parent.
@@ -119,63 +144,53 @@ export class TestRunArguments {
119144
120145 private itemContainsAllArgs (
121146 testItem : vscode . TestItem ,
122- xcTestArgs : string [ ] ,
123- swiftTestArgs : string [ ]
147+ xcTestArgs : vscode . TestItem [ ] ,
148+ swiftTestArgs : vscode . TestItem [ ]
124149 ) : boolean {
125- return xcTestArgs . length + swiftTestArgs . length === testItem . children . size ;
150+ return (
151+ testItem . children . size > 0 &&
152+ xcTestArgs . length + swiftTestArgs . length === testItem . children . size
153+ ) ;
126154 }
127155
128156 private simplifyTestArgs (
129157 testItem : vscode . TestItem ,
130- xcTestArgs : string [ ] ,
131- swiftTestArgs : string [ ]
132- ) : { xcTestResult : string [ ] ; swiftTestResult : string [ ] } {
133- if (
134- testItem . parent &&
135- this . itemContainsAllArgs ( testItem . parent , xcTestArgs , swiftTestArgs )
136- ) {
137- return this . simplifyTestArgs ( testItem . parent , xcTestArgs , swiftTestArgs ) ;
138- } else {
139- // If we've worked all the way up to a test target, it may have both swift-testing
140- // and XCTests.
141- const isTestTarget = ! ! testItem . tags . find ( tag => tag . id === "test-target" ) ;
142- if ( isTestTarget ) {
143- return {
144- // Add a trailing .* to match a test target name exactly.
145- // This prevents TestTarget matching TestTarget2.
146- xcTestResult : xcTestArgs . length > 0 ? [ `${ testItem . id } .*` ] : [ ] ,
147- swiftTestResult : swiftTestArgs . length > 0 ? [ `${ testItem . id } .*` ] : [ ] ,
148- } ;
149- }
150-
151- // If we've added all the children to the list of arguments, just add
152- // the parent instead of each individual child. This crafts a minimal set
153- // of test/suites that run all the test cases requested with the smallest list
154- // of arguments. The testItem has to have a parent to perform this optimization.
155- // If it does not we break the ability to run both swift testing tests and XCTests
156- // in the same run, since test targets can have both types of tests in them.
157- const isXCTest = ! ! testItem . tags . find ( tag => tag . id === "XCTest" ) ;
158+ xcTestArgs : vscode . TestItem [ ] ,
159+ swiftTestArgs : vscode . TestItem [ ]
160+ ) : { xcTestResult : vscode . TestItem [ ] ; swiftTestResult : vscode . TestItem [ ] } {
161+ // If we've worked all the way up to a test target, it may have both swift-testing
162+ // and XCTests.
163+ const isTestTarget = ! ! testItem . tags . find ( tag => tag . id === "test-target" ) ;
164+ if ( isTestTarget ) {
158165 return {
159- swiftTestResult : [
160- // Append a trailing slash to match a suite name exactly.
161- // This prevents TestTarget.MySuite matching TestTarget.MySuite2.
162- ...( ! isXCTest ? [ `${ testItem . id } /` ] : [ ] ) ,
163- ] ,
164- xcTestResult : [
165- // Debugging XCTests require exact matches, so we don't ned a trailing slash.
166- ...( isXCTest ? [ this . isDebug ? testItem . id : `${ testItem . id } /` ] : [ ] ) ,
167- ] ,
166+ // Add a trailing .* to match a test target name exactly.
167+ // This prevents TestTarget matching TestTarget2.
168+ xcTestResult : xcTestArgs . length > 0 ? [ testItem ] : [ ] ,
169+ swiftTestResult : swiftTestArgs . length > 0 ? [ testItem ] : [ ] ,
168170 } ;
169171 }
172+
173+ // If we've added all the children to the list of arguments, just add
174+ // the parent instead of each individual child. This crafts a minimal set
175+ // of test/suites that run all the test cases requested with the smallest list
176+ // of arguments. The testItem has to have a parent to perform this optimization.
177+ // If it does not we break the ability to run both swift testing tests and XCTests
178+ // in the same run, since test targets can have both types of tests in them.
179+ const isXCTest = ! ! testItem . tags . find ( tag => tag . id === "XCTest" ) ;
180+ return {
181+ xcTestResult : isXCTest ? [ testItem ] : [ ] ,
182+ swiftTestResult : ! isXCTest ? [ testItem ] : [ ] ,
183+ } ;
170184 }
171185
172186 private processTestItem (
173187 testItem : vscode . TestItem ,
174- include ?: readonly vscode . TestItem [ ] ,
175- exclude ?: readonly vscode . TestItem [ ]
188+ include : readonly vscode . TestItem [ ] ,
189+ exclude : readonly vscode . TestItem [ ] ,
190+ isDebug : boolean
176191 ) : ProcessResult {
177192 // Skip tests the user asked to exclude
178- if ( exclude ? .includes ( testItem ) ) {
193+ if ( exclude . includes ( testItem ) ) {
179194 return {
180195 testItems : [ ] ,
181196 xcTestArgs : [ ] ,
@@ -184,12 +199,13 @@ export class TestRunArguments {
184199 }
185200
186201 const testItems : vscode . TestItem [ ] = [ ] ;
187- const xcTestArgs : string [ ] = [ ] ;
188- const swiftTestArgs : string [ ] = [ ] ;
202+ const xcTestArgs : vscode . TestItem [ ] = [ ] ;
203+ const swiftTestArgs : vscode . TestItem [ ] = [ ] ;
189204
190205 // If this test item is included or we are including everything
191- if ( include ? .includes ( testItem ) || ! include ) {
206+ if ( include . includes ( testItem ) || include . length === 0 ) {
192207 const isXCTest = testItem . tags . find ( tag => tag . id === "XCTest" ) ;
208+ const isSwiftTestingTest = testItem . tags . find ( tag => tag . id === "swift-testing" ) ;
193209
194210 // Collect up a list of all the test items involved in the run
195211 // from the TestExplorer tree and store them in `testItems`. Exclude
@@ -200,20 +216,17 @@ export class TestRunArguments {
200216 // Only add leaf items to the list of arguments to pass to the test runner.
201217 if ( this . isLeafTestItem ( testItem , ! ! isXCTest ) ) {
202218 if ( isXCTest ) {
203- // Tests in debug are specified exactly, otherwise
204- // add the regex line ending character to avoid capturing
205- // unwanted tests.
206- xcTestArgs . push ( this . isDebug ? testItem . id : `${ testItem . id } $` ) ;
207- } else {
208- swiftTestArgs . push ( testItem . id ) ;
219+ xcTestArgs . push ( testItem ) ;
220+ } else if ( isSwiftTestingTest ) {
221+ swiftTestArgs . push ( testItem ) ;
209222 }
210223 }
211224 }
212225 }
213226
214227 return reduceTestItemChildren (
215228 testItem . children ,
216- this . createTestItemReducer ( undefined , exclude ) ,
229+ this . createTestItemReducer ( [ ] , exclude , isDebug ) ,
217230 {
218231 testItems,
219232 xcTestArgs,
0 commit comments