Skip to content

Commit e028ef3

Browse files
authored
[Commands] Allow skipping tests with a flag (swiftlang#2847)
* [Tests] Enable testSwiftTestParallel() and testSwiftTestFilter() on non-mac platforms by adding '--enable-test-discovery' flag * [Commands] Allow skipping tests with a flag Multiple '--skip' invocations can be used to skip multiple tests. Keep the environment variable around till its users switch over.
1 parent c3ec99a commit e028ef3

File tree

4 files changed

+96
-45
lines changed

4 files changed

+96
-45
lines changed

Documentation/Development.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ $ swift test --parallel
4747

4848
# Run a single test.
4949
$ swift test --filter PackageGraphTests.DependencyResolverTests/testBasics
50+
51+
# Run tests for the test targets BuildTests and WorkspaceTests, but skip some test cases.
52+
$ swift test --filter BuildTests --skip BuildPlanTests --filter WorkspaceTests --skip InitTests
5053
```
5154

5255
Note: PackageDescription v4 is not available when developing using this method.
@@ -184,14 +187,6 @@ absolute search paths. SwiftPM will choose the first
184187
path which exists on disk. If none of the paths are present on disk, it will fall
185188
back to built-in computation.
186189

187-
## Skipping SwiftPM tests
188-
189-
SwiftPM has a hidden env variable `_SWIFTPM_SKIP_TESTS_LIST` that can be used
190-
to skip a list of tests. This value of the variable is either a file path that contains a
191-
newline separated list of tests to skip, or a colon-separated list of tests.
192-
193-
This is only a development feature and should be considered _unsupported_.
194-
195190
## Making changes in TSC targets
196191

197192
All targets with the prefix TSC define the interface for the tools support core. Those APIs might be used in other projects as well and need to be updated in this repository by copying their sources directories to the TSC repository. The repository can be found [here](https://github.com/apple/swift-tools-support-core).

IntegrationTests/Tests/IntegrationTests/BasicTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,13 @@ final class BasicTests: XCTestCase {
222222
func testBaz() { }
223223
}
224224
"""))
225-
let testOutput = try sh(swiftTest, "--package-path", toolDir, "--filter", "MyTests.*").stderr
225+
let testOutput = try sh(swiftTest, "--package-path", toolDir, "--filter", "MyTests.*", "--skip", "testBaz").stderr
226226

227227
// Check the test log.
228228
XCTAssertContents(testOutput) { checker in
229229
checker.check(.contains("Test Suite 'MyTests' started"))
230230
checker.check(.contains("Test Suite 'MyTests' passed"))
231-
checker.check(.contains("Executed 3 tests, with 0 failures"))
231+
checker.check(.contains("Executed 2 tests, with 0 failures"))
232232
}
233233
}
234234
}

Sources/Commands/SwiftTestTool.swift

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,14 @@ public class TestToolOptions: ToolOptions {
8383
/// If the path of the exported code coverage JSON should be printed.
8484
var shouldPrintCodeCovPath = false
8585

86-
var testCaseSpecifier: TestCaseSpecifier {
87-
testCaseSpecifierOverride() ?? _testCaseSpecifier
86+
var testCaseSpecifier: TestCaseSpecifier = .none
87+
88+
var testCaseSkip: TestCaseSpecifier {
89+
// TODO: Remove this once the environment variable is no longer used.
90+
testCaseSkipOverride() ?? _testCaseSkip
8891
}
8992

90-
var _testCaseSpecifier: TestCaseSpecifier = .none
93+
var _testCaseSkip: TestCaseSpecifier = .none
9194

9295
/// Path where the xUnit xml file should be generated.
9396
var xUnitOutput: AbsolutePath?
@@ -97,7 +100,7 @@ public class TestToolOptions: ToolOptions {
97100
public var testProduct: String?
98101

99102
/// Returns the test case specifier if overridden in the env.
100-
private func testCaseSpecifierOverride() -> TestCaseSpecifier? {
103+
private func testCaseSkipOverride() -> TestCaseSpecifier? {
101104
guard let override = ProcessEnv.vars["_SWIFTPM_SKIP_TESTS_LIST"] else {
102105
return nil
103106
}
@@ -126,7 +129,8 @@ public class TestToolOptions: ToolOptions {
126129
/// This is used to filter tests to run
127130
/// .none => No filtering
128131
/// .specific => Specify test with fully quantified name
129-
/// .regex => RegEx patterns
132+
/// .regex => RegEx patterns for tests to run
133+
/// .skip => RegEx patterns for tests to skip
130134
public enum TestCaseSpecifier {
131135
case none
132136
case specific(String)
@@ -168,7 +172,9 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
168172
case .listTests:
169173
let testProducts = try buildTestsIfNeeded()
170174
let testSuites = try getTestSuites(in: testProducts)
171-
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)
175+
let tests = testSuites
176+
.filteredTests(specifier: options.testCaseSpecifier)
177+
.skippedTests(specifier: options.testCaseSkip)
172178

173179
// Print the tests.
174180
for test in tests {
@@ -208,7 +214,11 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
208214

209215
switch options.testCaseSpecifier {
210216
case .none:
211-
xctestArg = nil
217+
if case .skip = options.testCaseSkip {
218+
fallthrough
219+
} else {
220+
xctestArg = nil
221+
}
212222

213223
case .regex, .specific, .skip:
214224
// If old specifier `-s` option was used, emit deprecation notice.
@@ -218,7 +228,9 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
218228

219229
// Find the tests we need to run.
220230
let testSuites = try getTestSuites(in: testProducts)
221-
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)
231+
let tests = testSuites
232+
.filteredTests(specifier: options.testCaseSpecifier)
233+
.skippedTests(specifier: options.testCaseSkip)
222234

223235
// If there were no matches, emit a warning.
224236
if tests.isEmpty {
@@ -252,7 +264,9 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
252264
let toolchain = try getToolchain()
253265
let testProducts = try buildTestsIfNeeded()
254266
let testSuites = try getTestSuites(in: testProducts)
255-
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)
267+
let tests = testSuites
268+
.filteredTests(specifier: options.testCaseSpecifier)
269+
.skippedTests(specifier: options.testCaseSkip)
256270
let buildParameters = try self.buildParameters()
257271

258272
// If there were no matches, emit a warning and exit.
@@ -408,7 +422,7 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
408422

409423
binder.bind(
410424
option: parser.add(option: "--specifier", shortName: "-s", kind: String.self),
411-
to: { $0._testCaseSpecifier = .specific($1) })
425+
to: { $0.testCaseSpecifier = .specific($1) })
412426

413427
binder.bind(
414428
option: parser.add(option: "--xunit-output", kind: PathArgument.self),
@@ -418,7 +432,12 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
418432
option: parser.add(option: "--filter", kind: [String].self,
419433
usage: "Run test cases matching regular expression, Format: <test-target>.<test-case> or " +
420434
"<test-target>.<test-case>/<test>"),
421-
to: { $0._testCaseSpecifier = .regex($1) })
435+
to: { $0.testCaseSpecifier = .regex($1) })
436+
437+
binder.bind(
438+
option: parser.add(option: "--skip", kind: [String].self,
439+
usage: "Skip test cases matching regular expression, Example: --skip PerformanceTests"),
440+
to: { $0._testCaseSkip = .skip($1) })
422441

423442
binder.bind(
424443
option: parser.add(option: "--enable-code-coverage", kind: Bool.self,
@@ -967,14 +986,28 @@ fileprivate extension Dictionary where Key == AbsolutePath, Value == [TestSuite]
967986
})
968987
case .specific(let name):
969988
return allTests.filter{ $0.specifier == name }
989+
case .skip:
990+
fatalError("Tests to skip should never have been passed here.")
991+
}
992+
}
993+
}
994+
995+
fileprivate extension Array where Element == UnitTest {
996+
/// Skip tests matching the provided specifier
997+
func skippedTests(specifier: TestCaseSpecifier) -> [UnitTest] {
998+
switch specifier {
999+
case .none:
1000+
return self
9701001
case .skip(let skippedTests):
971-
var result = allTests
1002+
var result = self
9721003
for skippedTest in skippedTests {
9731004
result = result.filter{
9741005
$0.specifier.range(of: skippedTest, options: .regularExpression) == nil
9751006
}
9761007
}
9771008
return result
1009+
case .regex, .specific:
1010+
fatalError("Tests to filter should never have been passed here.")
9781011
}
9791012
}
9801013
}
@@ -1086,6 +1119,6 @@ final class XUnitGenerator {
10861119

10871120
private extension Diagnostic.Message {
10881121
static var noMatchingTests: Diagnostic.Message {
1089-
.warning("'--filter' predicate did not match any test case")
1122+
.warning("No matching test cases were run")
10901123
}
10911124
}

Tests/FunctionalTests/MiscellaneousTests.swift

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,21 @@ class MiscellaneousTestCase: XCTestCase {
213213
}
214214

215215
func testSwiftTestParallel() throws {
216-
// Running swift-test fixtures on linux is not yet possible.
217-
#if os(macOS)
218216
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
219217
// First try normal serial testing.
220218
do {
221-
_ = try SwiftPMProduct.SwiftTest.execute([], packagePath: prefix)
222-
} catch SwiftPMProductError.executionFailure(_, _, let stderr) {
223-
XCTAssertTrue(stderr.contains("Executed 2 tests"))
219+
_ = try SwiftPMProduct.SwiftTest.execute(["--enable-test-discovery"], packagePath: prefix)
220+
} catch SwiftPMProductError.executionFailure(_, let output, let stderr) {
221+
#if os(macOS)
222+
XCTAssertTrue(stderr.contains("Executed 2 tests"))
223+
#else
224+
XCTAssertTrue(output.contains("Executed 2 tests"))
225+
#endif
224226
}
225227

226228
do {
227229
// Run tests in parallel.
228-
_ = try SwiftPMProduct.SwiftTest.execute(["--parallel"], packagePath: prefix)
230+
_ = try SwiftPMProduct.SwiftTest.execute(["--parallel", "--enable-test-discovery"], packagePath: prefix)
229231
} catch SwiftPMProductError.executionFailure(_, let output, _) {
230232
XCTAssert(output.contains("testExample1"))
231233
XCTAssert(output.contains("testExample2"))
@@ -238,7 +240,7 @@ class MiscellaneousTestCase: XCTestCase {
238240
do {
239241
// Run tests in parallel with verbose output.
240242
_ = try SwiftPMProduct.SwiftTest.execute(
241-
["--parallel", "--verbose", "--xunit-output", xUnitOutput.pathString],
243+
["--parallel", "--verbose", "--xunit-output", xUnitOutput.pathString, "--enable-test-discovery"],
242244
packagePath: prefix)
243245
} catch SwiftPMProductError.executionFailure(_, let output, _) {
244246
XCTAssert(output.contains("testExample1"))
@@ -253,25 +255,46 @@ class MiscellaneousTestCase: XCTestCase {
253255
let contents = try localFileSystem.readFileContents(xUnitOutput).description
254256
XCTAssertTrue(contents.contains("tests=\"3\" failures=\"1\""))
255257
}
256-
#endif
257258
}
258259

259260
func testSwiftTestFilter() throws {
260-
#if os(macOS)
261-
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
262-
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--filter", ".*1", "-l"], packagePath: prefix)
263-
XCTAssertMatch(stdout, .contains("testExample1"))
264-
XCTAssertNoMatch(stdout, .contains("testExample2"))
265-
XCTAssertNoMatch(stdout, .contains("testSureFailure"))
266-
}
261+
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
262+
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--filter", ".*1", "-l", "--enable-test-discovery"], packagePath: prefix)
263+
XCTAssertMatch(stdout, .contains("testExample1"))
264+
XCTAssertNoMatch(stdout, .contains("testExample2"))
265+
XCTAssertNoMatch(stdout, .contains("testSureFailure"))
266+
}
267267

268-
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
269-
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--filter", ".*1", "--filter", "testSureFailure", "-l"], packagePath: prefix)
270-
XCTAssertMatch(stdout, .contains("testExample1"))
271-
XCTAssertNoMatch(stdout, .contains("testExample2"))
272-
XCTAssertMatch(stdout, .contains("testSureFailure"))
273-
}
274-
#endif
268+
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
269+
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--filter", "ParallelTestsTests", "--skip", ".*1", "--filter", "testSureFailure", "-l", "--enable-test-discovery"], packagePath: prefix)
270+
XCTAssertNoMatch(stdout, .contains("testExample1"))
271+
XCTAssertMatch(stdout, .contains("testExample2"))
272+
XCTAssertMatch(stdout, .contains("testSureFailure"))
273+
}
274+
}
275+
276+
func testSwiftTestSkip() throws {
277+
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
278+
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--skip", "ParallelTestsTests", "-l", "--enable-test-discovery"], packagePath: prefix)
279+
XCTAssertNoMatch(stdout, .contains("testExample1"))
280+
XCTAssertNoMatch(stdout, .contains("testExample2"))
281+
XCTAssertMatch(stdout, .contains("testSureFailure"))
282+
}
283+
284+
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
285+
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--filter", "ParallelTestsTests", "--skip", ".*2", "--filter", "TestsFailure", "--skip", "testSureFailure", "-l", "--enable-test-discovery"], packagePath: prefix)
286+
XCTAssertMatch(stdout, .contains("testExample1"))
287+
XCTAssertNoMatch(stdout, .contains("testExample2"))
288+
XCTAssertNoMatch(stdout, .contains("testSureFailure"))
289+
}
290+
291+
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
292+
let (stdout, stderr) = try SwiftPMProduct.SwiftTest.execute(["--skip", "Tests", "--enable-test-discovery"], packagePath: prefix)
293+
XCTAssertNoMatch(stdout, .contains("testExample1"))
294+
XCTAssertNoMatch(stdout, .contains("testExample2"))
295+
XCTAssertNoMatch(stdout, .contains("testSureFailure"))
296+
XCTAssertMatch(stderr, .contains("No matching test cases were run"))
297+
}
275298
}
276299

277300
func testOverridingSwiftcArguments() throws {

0 commit comments

Comments
 (0)