Skip to content

Commit e775a91

Browse files
authored
Merge pull request #268 from egorzhdan/master
Allow running XCTest bundle with multiple comma-separated tests
2 parents 8af87e2 + e52fc65 commit e775a91

File tree

5 files changed

+39
-27
lines changed

5 files changed

+39
-27
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ The `XCTMain` function does not return, and will cause your test executable to e
7272
* A particular test or test case can be selected to execute. For example:
7373

7474
```
75-
$ ./FooTests Tests.FooTestCase/testFoo # Run a single test case
76-
$ ./FooTests Tests.FooTestCase # Run all the tests in FooTestCase
75+
$ ./FooTests Tests.FooTestCase/testFoo # Run a single test method
76+
$ ./FooTests Tests.FooTestCase # Run all the tests in FooTestCase
77+
$ ./FooTests Tests.FooTestCase/testFoo,Tests.FooTestCase/testBar # Run multiple test methods
7778
```
7879
* Tests can be listed, instead of executed.
7980

Sources/XCTest/Private/ArgumentParser.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ internal struct ArgumentParser {
1717

1818
/// The basic operations that can be performed by an XCTest runner executable
1919
enum ExecutionMode {
20-
/// Run a test or test suite, printing results to stdout and exiting with
21-
/// a non-0 return code if any tests failed. The name of a test or class
22-
/// may be provided to only run a subset of test cases.
23-
case run(selectedTestName: String?)
20+
/// Run tests or test cases, printing results to stdout and exiting with
21+
/// a non-0 return code if any tests failed. The names of tests or test cases
22+
/// may be provided to only run a subset of them.
23+
case run(selectedTestNames: [String]?)
2424

2525
/// The different ways that the tests can be represented when they are listed
2626
enum ListType {
@@ -39,9 +39,9 @@ internal struct ArgumentParser {
3939
/// Print Help
4040
case help(invalidOption: String?)
4141

42-
var selectedTestName: String? {
43-
if case .run(let name) = self {
44-
return name
42+
var selectedTestNames: [String]? {
43+
if case .run(let names) = self {
44+
return names
4545
} else {
4646
return nil
4747
}
@@ -56,7 +56,7 @@ internal struct ArgumentParser {
5656

5757
var executionMode: ExecutionMode {
5858
if arguments.count <= 1 {
59-
return .run(selectedTestName: nil)
59+
return .run(selectedTestNames: nil)
6060
} else if arguments[1] == "--list-tests" || arguments[1] == "-l" {
6161
return .list(type: .humanReadable)
6262
} else if arguments[1] == "--dump-tests-json" {
@@ -66,7 +66,7 @@ internal struct ArgumentParser {
6666
} else if let fst = arguments[1].first, fst == "-" {
6767
return .help(invalidOption: arguments[1])
6868
} else {
69-
return .run(selectedTestName: arguments[1])
69+
return .run(selectedTestNames: arguments[1].split(separator: ",").map(String.init))
7070
}
7171
}
7272
}

Sources/XCTest/Private/TestFiltering.swift

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,20 @@
1414
internal typealias TestFilter = (XCTestCase.Type, String) -> Bool
1515

1616
internal struct TestFiltering {
17-
private let selectedTestName: String?
17+
private let selectedTestNames: [String]?
1818

19-
init(selectedTestName: String?) {
20-
self.selectedTestName = selectedTestName
19+
init(selectedTestNames: [String]?) {
20+
self.selectedTestNames = selectedTestNames
2121
}
2222

2323
var selectedTestFilter: TestFilter {
24-
guard let selectedTestName = selectedTestName else { return includeAllFilter() }
25-
guard let selectedTest = SelectedTest(selectedTestName: selectedTestName) else { return excludeAllFilter() }
24+
guard let selectedTestNames = selectedTestNames else { return includeAllFilter() }
25+
let selectedTests = Set(selectedTestNames.compactMap { SelectedTest(selectedTestName: $0) })
2626

27-
return selectedTest.matches
28-
}
29-
30-
private func excludeAllFilter() -> TestFilter {
31-
return { _,_ in false }
27+
return { testCaseClass, testCaseMethodName in
28+
return selectedTests.contains(SelectedTest(testCaseClass: testCaseClass, testCaseMethodName: testCaseMethodName)) ||
29+
selectedTests.contains(SelectedTest(testCaseClass: testCaseClass, testCaseMethodName: nil))
30+
}
3231
}
3332

3433
private func includeAllFilter() -> TestFilter {
@@ -47,7 +46,7 @@ internal struct TestFiltering {
4746
}
4847

4948
/// A selected test can be a single test case, or an entire class of test cases
50-
private struct SelectedTest {
49+
private struct SelectedTest : Hashable {
5150
let testCaseClassName: String
5251
let testCaseMethodName: String?
5352
}
@@ -67,7 +66,7 @@ private extension SelectedTest {
6766
}
6867
}
6968

70-
func matches(testCaseClass: XCTestCase.Type, testCaseMethodName: String) -> Bool {
71-
return String(reflecting: testCaseClass) == testCaseClassName && (self.testCaseMethodName == nil || testCaseMethodName == self.testCaseMethodName)
69+
init(testCaseClass: XCTestCase.Type, testCaseMethodName: String?) {
70+
self.init(testCaseClassName: String(reflecting: testCaseClass), testCaseMethodName: testCaseMethodName)
7271
}
7372
}

Sources/XCTest/Public/XCTestMain.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public func XCTMain(_ testCases: [XCTestCaseEntry]) -> Never {
6666
// - An `XCTestSuite` representing the .xctest test bundle is not included.
6767
let rootTestSuite: XCTestSuite
6868
let currentTestSuite: XCTestSuite
69-
if executionMode.selectedTestName == nil {
69+
if executionMode.selectedTestNames == nil {
7070
rootTestSuite = XCTestSuite(name: "All tests")
7171
currentTestSuite = XCTestSuite(name: "\(testBundle.bundleURL.lastPathComponent).xctest")
7272
rootTestSuite.addTest(currentTestSuite)
@@ -75,7 +75,7 @@ public func XCTMain(_ testCases: [XCTestCaseEntry]) -> Never {
7575
currentTestSuite = rootTestSuite
7676
}
7777

78-
let filter = TestFiltering(selectedTestName: executionMode.selectedTestName)
78+
let filter = TestFiltering(selectedTestNames: executionMode.selectedTestNames)
7979
TestFiltering.filterTests(testCases, filter: filter.selectedTestFilter)
8080
.map(XCTestCaseSuite.init)
8181
.forEach(currentTestSuite.addTest)
@@ -118,7 +118,7 @@ public func XCTMain(_ testCases: [XCTestCaseEntry]) -> Never {
118118
> \(exeName) \(sampleTests)
119119
""")
120120
exit(invalidOption == nil ? EXIT_SUCCESS : EXIT_FAILURE)
121-
case .run(selectedTestName: _):
121+
case .run(selectedTestNames: _):
122122
// Add a test observer that prints test progress to stdout.
123123
let observationCenter = XCTestObservationCenter.shared
124124
observationCenter.addTestObserver(PrintObserver())

Tests/Functional/SelectedTest/main.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// RUN: %{swiftc} %s -o %T/SelectedTest
22
// RUN: %T/SelectedTest SelectedTest.ExecutedTestCase/test_foo > %T/one_test_case || true
33
// RUN: %T/SelectedTest SelectedTest.ExecutedTestCase > %T/one_test_case_class || true
4+
// RUN: %T/SelectedTest SelectedTest.ExecutedTestCase/test_foo,SelectedTest.ExecutedTestCase/test_bar > %T/two_test_cases || true
45
// RUN: %T/SelectedTest > %T/all || true
56
// RUN: %{xctest_checker} -p "// CHECK-METHOD:" %T/one_test_case %s
67
// RUN: %{xctest_checker} -p "// CHECK-CLASS:" %T/one_test_case_class %s
8+
// RUN: %{xctest_checker} -p "// CHECK-TWO-METHODS:" %T/two_test_cases %s
79
// RUN: %{xctest_checker} -p "// CHECK-ALL:" %T/all %s
810

911
#if os(macOS)
@@ -14,6 +16,7 @@
1416

1517
// CHECK-METHOD: Test Suite 'Selected tests' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
1618
// CHECK-CLASS: Test Suite 'Selected tests' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
19+
// CHECK-TWO-METHODS: Test Suite 'Selected tests' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
1720
// CHECK-ALL: Test Suite 'All tests' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
1821
// CHECK-ALL: Test Suite '.*\.xctest' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
1922

@@ -31,13 +34,18 @@ class ExecutedTestCase: XCTestCase {
3134
// CHECK-CLASS: Test Suite 'ExecutedTestCase' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
3235
// CHECK-CLASS: Test Case 'ExecutedTestCase.test_bar' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
3336
// CHECK-CLASS: Test Case 'ExecutedTestCase.test_bar' passed \(\d+\.\d+ seconds\)
37+
// CHECK-TWO-METHODS: Test Suite 'ExecutedTestCase' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
38+
// CHECK-TWO-METHODS: Test Case 'ExecutedTestCase.test_bar' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
39+
// CHECK-TWO-METHODS: Test Case 'ExecutedTestCase.test_bar' passed \(\d+\.\d+ seconds\)
3440
// CHECK-ALL: Test Suite 'ExecutedTestCase' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
3541
// CHECK-ALL: Test Case 'ExecutedTestCase.test_bar' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
3642
// CHECK-ALL: Test Case 'ExecutedTestCase.test_bar' passed \(\d+\.\d+ seconds\)
3743
func test_bar() {}
3844

3945
// CHECK-CLASS: Test Case 'ExecutedTestCase.test_foo' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
4046
// CHECK-CLASS: Test Case 'ExecutedTestCase.test_foo' passed \(\d+\.\d+ seconds\)
47+
// CHECK-TWO-METHODS: Test Case 'ExecutedTestCase.test_foo' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
48+
// CHECK-TWO-METHODS: Test Case 'ExecutedTestCase.test_foo' passed \(\d+\.\d+ seconds\)
4149
// CHECK-ALL: Test Case 'ExecutedTestCase.test_foo' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
4250
// CHECK-ALL: Test Case 'ExecutedTestCase.test_foo' passed \(\d+\.\d+ seconds\)
4351
func test_foo() {}
@@ -46,6 +54,8 @@ class ExecutedTestCase: XCTestCase {
4654
// CHECK-METHOD: \t Executed 1 test, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
4755
// CHECK-CLASS: Test Suite 'ExecutedTestCase' passed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
4856
// CHECK-CLASS: \t Executed 2 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
57+
// CHECK-TWO-METHODS: Test Suite 'ExecutedTestCase' passed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
58+
// CHECK-TWO-METHODS: \t Executed 2 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
4959
// CHECK-ALL: Test Suite 'ExecutedTestCase' passed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
5060
// CHECK-ALL: \t Executed 2 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
5161

@@ -72,6 +82,8 @@ XCTMain([
7282
// CHECK-METHOD: \t Executed 1 test, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
7383
// CHECK-CLASS: Test Suite 'Selected tests' passed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
7484
// CHECK-CLASS: \t Executed 2 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
85+
// CHECK-TWO-METHODS: Test Suite 'Selected tests' passed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
86+
// CHECK-TWO-METHODS: \t Executed 2 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
7587
// CHECK-ALL: Test Suite '.*\.xctest' passed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
7688
// CHECK-ALL: \t Executed 3 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
7789
// CHECK-ALL: Test Suite 'All tests' passed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+

0 commit comments

Comments
 (0)