Skip to content

Commit e685bc0

Browse files
committed
Allow skipping tests with a flag, instead of the environment variable
1 parent 6f469cf commit e685bc0

File tree

4 files changed

+57
-46
lines changed

4 files changed

+57
-46
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: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -83,50 +83,25 @@ 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
88-
}
86+
var testCaseSpecifier: TestCaseSpecifier = .none
8987

90-
var _testCaseSpecifier: TestCaseSpecifier = .none
88+
var testCaseSkip: TestCaseSpecifier = .none
9189

9290
/// Path where the xUnit xml file should be generated.
9391
var xUnitOutput: AbsolutePath?
9492

9593
/// The test product to use. This is useful when there are multiple test products
9694
/// to choose from (usually in multiroot packages).
9795
public var testProduct: String?
98-
99-
/// Returns the test case specifier if overridden in the env.
100-
private func testCaseSpecifierOverride() -> TestCaseSpecifier? {
101-
guard let override = ProcessEnv.vars["_SWIFTPM_SKIP_TESTS_LIST"] else {
102-
return nil
103-
}
104-
105-
do {
106-
let skipTests: [String.SubSequence]
107-
// Read from the file if it exists.
108-
if let path = try? AbsolutePath(validating: override), localFileSystem.exists(path) {
109-
let contents = try localFileSystem.readFileContents(path).cString
110-
skipTests = contents.split(separator: "\n", omittingEmptySubsequences: true)
111-
} else {
112-
// Otherwise, read the env variable.
113-
skipTests = override.split(separator: ":", omittingEmptySubsequences: true)
114-
}
115-
116-
return .skip(skipTests.map(String.init))
117-
} catch {
118-
// FIXME: We should surface errors from here.
119-
}
120-
return nil
121-
}
12296
}
12397

12498
/// Tests filtering specifier
12599
///
126100
/// This is used to filter tests to run
127101
/// .none => No filtering
128102
/// .specific => Specify test with fully quantified name
129-
/// .regex => RegEx patterns
103+
/// .regex => RegEx patterns for tests to run
104+
/// .skip => RegEx patterns for tests to skip
130105
public enum TestCaseSpecifier {
131106
case none
132107
case specific(String)
@@ -168,7 +143,9 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
168143
case .listTests:
169144
let testProducts = try buildTestsIfNeeded()
170145
let testSuites = try getTestSuites(in: testProducts)
171-
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)
146+
let tests = testSuites
147+
.filteredTests(specifier: options.testCaseSpecifier)
148+
.skippedTests(specifier: options.testCaseSkip)
172149

173150
// Print the tests.
174151
for test in tests {
@@ -218,7 +195,9 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
218195

219196
// Find the tests we need to run.
220197
let testSuites = try getTestSuites(in: testProducts)
221-
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)
198+
let tests = testSuites
199+
.filteredTests(specifier: options.testCaseSpecifier)
200+
.skippedTests(specifier: options.testCaseSkip)
222201

223202
// If there were no matches, emit a warning.
224203
if tests.isEmpty {
@@ -252,7 +231,9 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
252231
let toolchain = try getToolchain()
253232
let testProducts = try buildTestsIfNeeded()
254233
let testSuites = try getTestSuites(in: testProducts)
255-
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)
234+
let tests = testSuites
235+
.filteredTests(specifier: options.testCaseSpecifier)
236+
.skippedTests(specifier: options.testCaseSkip)
256237
let buildParameters = try self.buildParameters()
257238

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

409390
binder.bind(
410391
option: parser.add(option: "--specifier", shortName: "-s", kind: String.self),
411-
to: { $0._testCaseSpecifier = .specific($1) })
392+
to: { $0.testCaseSpecifier = .specific($1) })
412393

413394
binder.bind(
414395
option: parser.add(option: "--xunit-output", kind: PathArgument.self),
@@ -418,7 +399,12 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
418399
option: parser.add(option: "--filter", kind: [String].self,
419400
usage: "Run test cases matching regular expression, Format: <test-target>.<test-case> or " +
420401
"<test-target>.<test-case>/<test>"),
421-
to: { $0._testCaseSpecifier = .regex($1) })
402+
to: { $0.testCaseSpecifier = .regex($1) })
403+
404+
binder.bind(
405+
option: parser.add(option: "--skip", kind: [String].self,
406+
usage: "Skip test cases matching regular expression, Example: --skip PerformanceTests"),
407+
to: { $0.testCaseSkip = .skip($1) })
422408

423409
binder.bind(
424410
option: parser.add(option: "--enable-code-coverage", kind: Bool.self,
@@ -967,14 +953,28 @@ fileprivate extension Dictionary where Key == AbsolutePath, Value == [TestSuite]
967953
})
968954
case .specific(let name):
969955
return allTests.filter{ $0.specifier == name }
956+
case .skip:
957+
fatalError("Tests to skip should never have been passed here.")
958+
}
959+
}
960+
}
961+
962+
fileprivate extension Array where Element == UnitTest {
963+
/// Skip tests matching the provided specifier
964+
func skippedTests(specifier: TestCaseSpecifier) -> [UnitTest] {
965+
switch specifier {
966+
case .none:
967+
return self
970968
case .skip(let skippedTests):
971-
var result = allTests
969+
var result = self
972970
for skippedTest in skippedTests {
973971
result = result.filter{
974972
$0.specifier.range(of: skippedTest, options: .regularExpression) == nil
975973
}
976974
}
977975
return result
976+
case .regex, .specific:
977+
fatalError("Tests to filter should never have been passed here.")
978978
}
979979
}
980980
}
@@ -1086,6 +1086,6 @@ final class XUnitGenerator {
10861086

10871087
private extension Diagnostic.Message {
10881088
static var noMatchingTests: Diagnostic.Message {
1089-
.warning("'--filter' predicate did not match any test case")
1089+
.warning("No matching test cases were run")
10901090
}
10911091
}

Tests/FunctionalTests/MiscellaneousTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,22 @@ class MiscellaneousTestCase: XCTestCase {
274274
#endif
275275
}
276276

277+
func testSwiftTestSkip() throws {
278+
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
279+
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--skip", "ParallelTestsTests", "-l", "--enable-test-discovery"], packagePath: prefix)
280+
XCTAssertNoMatch(stdout, .contains("testExample1"))
281+
XCTAssertNoMatch(stdout, .contains("testExample2"))
282+
XCTAssertMatch(stdout, .contains("testSureFailure"))
283+
}
284+
285+
fixture(name: "Miscellaneous/ParallelTestsPkg") { prefix in
286+
let (stdout, _) = try SwiftPMProduct.SwiftTest.execute(["--filter", "ParallelTestsTests", "--skip", ".*2", "--filter", "TestsFailure", "--skip", "testSureFailure", "-l", "--enable-test-discovery"], packagePath: prefix)
287+
XCTAssertMatch(stdout, .contains("testExample1"))
288+
XCTAssertNoMatch(stdout, .contains("testExample2"))
289+
XCTAssertNoMatch(stdout, .contains("testSureFailure"))
290+
}
291+
}
292+
277293
func testOverridingSwiftcArguments() throws {
278294
#if os(macOS)
279295
fixture(name: "Miscellaneous/OverrideSwiftcArgs") { prefix in

0 commit comments

Comments
 (0)