@@ -16,6 +16,19 @@ import Utility
16
16
17
17
import func POSIX. exit
18
18
19
+
20
+ /// Diagnostics info for deprecated `--specifier` option
21
+ struct SpecifierDeprecatedDiagnostic : DiagnosticData {
22
+ static let id = DiagnosticID (
23
+ type: AnyDiagnostic . self,
24
+ name: " org.swift.diags.specifier-deprecated " ,
25
+ defaultBehavior: . warning,
26
+ description: {
27
+ $0 <<< " '--specifier' option is deprecated, use '--filter' instead. "
28
+ }
29
+ )
30
+ }
31
+
19
32
private enum TestError : Swift . Error {
20
33
case invalidListTestJSONData
21
34
case multipleTestProducts
@@ -42,15 +55,16 @@ public class TestToolOptions: ToolOptions {
42
55
if shouldPrintVersion {
43
56
return . version
44
57
}
45
- // List the test cases.
58
+
59
+ if shouldRunInParallel {
60
+ return . runParallel
61
+ }
62
+
46
63
if shouldListTests {
47
64
return . listTests
48
65
}
49
- // Run tests in parallel.
50
- if shouldRunInParallel {
51
- return . runInParallel
52
- }
53
- return . run( specifier)
66
+
67
+ return . runSerial
54
68
}
55
69
56
70
/// If the test target should be built before testing.
@@ -65,15 +79,26 @@ public class TestToolOptions: ToolOptions {
65
79
/// List the tests and exit.
66
80
var shouldListTests = false
67
81
68
- /// Run only these specified tests.
69
- var specifier : String ?
82
+ var testCaseSpecifier : TestCaseSpecifier = . none
83
+ }
84
+
85
+ /// Tests filtering specifier
86
+ ///
87
+ /// This is used to filter tests to run
88
+ /// .none => No filtering
89
+ /// .specific => Specify test with fully quantified name
90
+ /// .regex => RegEx pattern
91
+ public enum TestCaseSpecifier {
92
+ case none
93
+ case specific( String )
94
+ case regex( String )
70
95
}
71
96
72
97
public enum TestMode {
73
98
case version
74
99
case listTests
75
- case run ( String ? )
76
- case runInParallel
100
+ case runSerial
101
+ case runParallel
77
102
}
78
103
79
104
/// swift-test tool namespace
@@ -96,25 +121,47 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
96
121
case . listTests:
97
122
let testPath = try buildTestsIfNeeded ( options)
98
123
let testSuites = try getTestSuites ( path: testPath)
124
+ let tests = testSuites. filteredTests ( specifier: options. testCaseSpecifier)
125
+
99
126
// Print the tests.
100
- for testSuite in testSuites {
101
- for testCase in testSuite. tests {
102
- for test in testCase. tests {
103
- print ( testCase. name + " / " + test)
104
- }
105
- }
127
+ for test in tests {
128
+ print ( test. specifier)
106
129
}
107
130
108
- case . run ( let specifier ) :
131
+ case . runSerial :
109
132
let testPath = try buildTestsIfNeeded ( options)
110
- let success : Bool = TestRunner ( path: testPath, xctestArg: specifier, processSet: processSet) . test ( )
111
- exit ( success ? 0 : 1 )
133
+ let testSuites = try getTestSuites ( path: testPath)
134
+ var ranSuccessfully = true
135
+
136
+ switch options. testCaseSpecifier {
137
+ case . none:
138
+ let runner = TestRunner (
139
+ path: testPath,
140
+ xctestArg: nil ,
141
+ processSet: processSet
142
+ )
143
+ ranSuccessfully = runner. test ( )
144
+ case . regex, . specific:
145
+ if case . specific = options. testCaseSpecifier {
146
+ diagnostics. emit ( data: SpecifierDeprecatedDiagnostic ( ) )
147
+ }
148
+ let tests = testSuites. filteredTests ( specifier: options. testCaseSpecifier)
149
+ for test in tests {
150
+ let runner = TestRunner ( path: testPath,
151
+ xctestArg: test. specifier,
152
+ processSet: processSet)
153
+ ranSuccessfully = ranSuccessfully && runner. test ( )
154
+ }
155
+ }
112
156
113
- case . runInParallel:
157
+ exit ( ranSuccessfully ? 0 : 1 )
158
+
159
+ case . runParallel:
114
160
let testPath = try buildTestsIfNeeded ( options)
115
161
let testSuites = try getTestSuites ( path: testPath)
162
+ let tests = testSuites. filteredTests ( specifier: options. testCaseSpecifier)
116
163
let runner = ParallelTestRunner ( testPath: testPath, processSet: processSet)
117
- try runner. run ( testSuites )
164
+ try runner. run ( tests )
118
165
exit ( runner. ranSuccesfully ? 0 : 1 )
119
166
}
120
167
}
@@ -173,10 +220,14 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
173
220
to: { $0. shouldRunInParallel = $1 } )
174
221
175
222
binder. bind (
176
- option: parser. add ( option: " --specifier " , shortName: " -s " , kind: String . self,
177
- usage: " Run a specific test class or method, Format: <test-target>.<test-case> or " +
223
+ option: parser. add ( option: " --specifier " , shortName: " -s " , kind: String . self) ,
224
+ to: { $0. testCaseSpecifier = . specific( $1) } )
225
+
226
+ binder. bind (
227
+ option: parser. add ( option: " --filter " , kind: String . self,
228
+ usage: " Run test cases matching regular expression, Format: <test-target>.<test-case> or " +
178
229
" <test-target>.<test-case>/<test> " ) ,
179
- to: { $0. specifier = $1 } )
230
+ to: { $0. testCaseSpecifier = . regex ( $1 ) } )
180
231
}
181
232
182
233
/// Locates XCTestHelper tool inside the libexec directory and bin directory.
@@ -234,6 +285,20 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
234
285
}
235
286
}
236
287
288
+ /// A structure representing an individual unit test.
289
+ struct UnitTest {
290
+ /// The name of the unit test.
291
+ let name : String
292
+
293
+ /// The name of the test case.
294
+ let testCase : String
295
+
296
+ /// The specifier argument which can be passed to XCTest.
297
+ var specifier : String {
298
+ return testCase + " / " + name
299
+ }
300
+ }
301
+
237
302
/// A class to run tests on a XCTest binary.
238
303
///
239
304
/// Note: Executes the XCTest with inherited environment as it is convenient to pass senstive
@@ -311,20 +376,6 @@ final class TestRunner {
311
376
312
377
/// A class to run tests in parallel.
313
378
final class ParallelTestRunner {
314
- /// A structure representing an individual unit test.
315
- struct UnitTest {
316
- /// The name of the unit test.
317
- let name : String
318
-
319
- /// The name of the test case.
320
- let testCase : String
321
-
322
- /// The specifier argument which can be passed to XCTest.
323
- var specifier : String {
324
- return testCase + " / " + name
325
- }
326
- }
327
-
328
379
/// An enum representing result of a unit test execution.
329
380
enum TestResult {
330
381
case success( UnitTest )
@@ -372,17 +423,13 @@ final class ParallelTestRunner {
372
423
progressBar. update ( percent: 100 * numCurrentTest/ numTests, text: test. specifier)
373
424
}
374
425
375
- func enqueueTests( _ testSuites : [ TestSuite ] ) throws {
426
+ func enqueueTests( _ tests : [ UnitTest ] ) throws {
376
427
// FIXME: Add a count property in SynchronizedQueue.
377
428
var numTests = 0
378
429
// Enqueue all the tests.
379
- for testSuite in testSuites {
380
- for testCase in testSuite. tests {
381
- for test in testCase. tests {
382
- numTests += 1
383
- pendingTests. enqueue ( UnitTest ( name: test, testCase: testCase. name) )
384
- }
385
- }
430
+ for test in tests {
431
+ numTests += 1
432
+ pendingTests. enqueue ( test)
386
433
}
387
434
self . numTests = numTests
388
435
self . numCurrentTest = 0
@@ -393,9 +440,9 @@ final class ParallelTestRunner {
393
440
}
394
441
395
442
/// Executes the tests spawning parallel workers. Blocks calling thread until all workers are finished.
396
- func run( _ testSuites : [ TestSuite ] ) throws {
443
+ func run( _ tests : [ UnitTest ] ) throws {
397
444
// Enqueue all the tests.
398
- try enqueueTests ( testSuites )
445
+ try enqueueTests ( tests )
399
446
400
447
// Create the worker threads.
401
448
let workers : [ Thread ] = ( 0 ..< numJobs) . map ( { _ in
@@ -521,3 +568,28 @@ struct TestSuite {
521
568
} )
522
569
}
523
570
}
571
+
572
+
573
+ fileprivate extension Sequence where Iterator. Element == TestSuite {
574
+ /// Returns all the unit tests of the test suites.
575
+ var allTests : [ UnitTest ] {
576
+ return flatMap { $0. tests } . flatMap ( { testCase in
577
+ testCase. tests. map { UnitTest ( name: $0, testCase: testCase. name) }
578
+ } )
579
+ }
580
+
581
+ /// Return tests matching the provided specifier
582
+ func filteredTests( specifier: TestCaseSpecifier ) -> [ UnitTest ] {
583
+ switch specifier {
584
+ case . none:
585
+ return allTests
586
+ case . regex( let pattern) :
587
+ return allTests. filter ( { test in
588
+ test. specifier. range ( of: pattern,
589
+ options: . regularExpression) != nil
590
+ } )
591
+ case . specific( let name) :
592
+ return allTests. filter { $0. specifier == name }
593
+ }
594
+ }
595
+ }
0 commit comments