Skip to content

Commit 7c79f99

Browse files
Migrate IncrementalCompilationTests to Swift Testing
Migrate the incremental compilation tests from XCTest to Swift Testing, splitting the monolithic IncrementalCompilationTests.swift into: - IncrementalBasicTests: core incremental build tests - IncrementalExplicitBuildTests: explicit module / caching tests - IncrementalInputModificationTests: input add/remove/reorder tests Extract IncrementalTestHarness into its own file and introduce TestBuildConfig to parameterize explicit-module tests over implicit, explicit, caching, and prefix-mapped caching configurations. Test output is quiet by default; set SWIFT_DRIVER_TEST_VERBOSE=1 to re-enable diagnostic and debug printing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b4db0d9 commit 7c79f99

8 files changed

Lines changed: 2948 additions & 2571 deletions

Tests/SwiftDriverTests/AssertDiagnosticsTests.swift

Lines changed: 62 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import XCTest
1615
@_spi(Testing) import SwiftDriver
16+
import Testing
1717

1818
// Yes, these are meta-tests! `assertDiagnostics(do:)` and friends are
1919
// complicated enough to warrant a few tests of their own. To test that they
20-
// fail when they're supposed to, this test class has access to an
21-
// `assertFails(times:during:)` helper; see `FailableTestCase` below for the
22-
// implementation.
20+
// fail when they're supposed to, we use `withKnownIssue` to catch expected
21+
// failures.
2322

24-
class AssertDiagnosticsTests: FailableTestCase {
25-
func testAssertNoDiagnostics() {
23+
@Suite(.suppressKnownIssues())
24+
struct AssertDiagnosticsTests {
25+
@Test func noDiagnostics() async throws {
2626
assertNoDiagnostics { _ in }
2727

28-
assertFails {
28+
try await expectedIssue {
2929
assertNoDiagnostics { diags in
3030
diags.emit(error: "something happened")
3131
}
3232
}
33-
assertFails {
33+
try await expectedIssue {
3434
assertNoDiagnostics { diags in
3535
diags.emit(warning: "hello")
3636
}
@@ -45,7 +45,7 @@ class AssertDiagnosticsTests: FailableTestCase {
4545
}
4646
}
4747

48-
func testAssertDiagnostics() {
48+
@Test func diagnostics() async throws {
4949
assertDiagnostics { diags, match in
5050
diags.emit(error: "yankees won again")
5151
match.expect(.error("won"))
@@ -55,21 +55,21 @@ class AssertDiagnosticsTests: FailableTestCase {
5555
diags.emit(error: "yankees won again")
5656
}
5757

58-
assertFails(times: 2) {
58+
try await expectedIssue(count: 2) {
5959
assertDiagnostics { diags, match in
6060
match.expect(.error("lost"))
6161
diags.emit(error: "yankees won again")
6262
}
6363
}
6464

65-
assertFails(times: 2) {
65+
try await expectedIssue(count: 2) {
6666
assertDiagnostics { diags, match in
6767
diags.emit(error: "yankees won again")
6868
diags.emit(error: "yankees won yet again")
6969
}
7070
}
7171

72-
assertFails(times: 2) {
72+
try await expectedIssue(count: 2) {
7373
assertDiagnostics { diags, match in
7474
match.expect(.error("won"))
7575
match.expect(.error("won"))
@@ -78,7 +78,7 @@ class AssertDiagnosticsTests: FailableTestCase {
7878

7979
// We should get two assertion failures: one for expecting the warning, one
8080
// for emitting the error.
81-
assertFails(times: 2) {
81+
try await expectedIssue(count: 2) {
8282
assertDiagnostics { diags, match in
8383
match.expect(.warning("won"))
8484
diags.emit(.error("yankees won again"))
@@ -87,15 +87,15 @@ class AssertDiagnosticsTests: FailableTestCase {
8787

8888
// We should get one assertion failure for the unexpected error. An
8989
// unexpected note is okay.
90-
assertFails(times: 1) {
90+
try await expectedIssue(count: 1) {
9191
assertDiagnostics { diags, match in
9292
diags.emit(error: "yankees won again")
9393
diags.emit(note: "investigate their star's doctor")
9494
}
9595
}
9696

9797
// ...unless we tighten things up.
98-
assertFails(times: 2) {
98+
try await expectedIssue(count: 2) {
9999
assertDiagnostics { diags, match in
100100
diags.emit(error: "yankees won again")
101101
diags.emit(note: "investigate their star's doctor")
@@ -111,27 +111,27 @@ class AssertDiagnosticsTests: FailableTestCase {
111111
}
112112
}
113113

114-
func testAssertDriverDiagosotics() throws {
114+
@Test func driverDiagnostics() async throws {
115115
try assertNoDriverDiagnostics(args: "swiftc", "test.swift")
116116

117117
try assertDriverDiagnostics(args: "swiftc", "test.swift") { driver, verify in
118118
driver.diagnosticEngine.emit(.error("this mode does not support emitting modules"))
119119
verify.expect(.error("this mode does not support emitting modules"))
120120
}
121121

122-
try assertFails {
122+
try await expectedIssue {
123123
try assertDriverDiagnostics(args: "swiftc", "test.swift") { driver, verify in
124124
verify.expect(.error("this mode does not support emitting modules"))
125125
}
126126
}
127127

128-
try assertFails {
128+
try await expectedIssue {
129129
try assertDriverDiagnostics(args: "swiftc", "test.swift") { driver, verify in
130130
driver.diagnosticEngine.emit(.error("this mode does not support emitting modules"))
131131
}
132132
}
133133

134-
try assertFails(times: 2) {
134+
try await expectedIssue(count: 2) {
135135
try assertDriverDiagnostics(args: "swiftc", "test.swift") { driver, verify in
136136
driver.diagnosticEngine.emit(.error("this mode does not support emitting modules"))
137137
verify.expect(.error("-static may not be used with -emit-executable"))
@@ -140,67 +140,53 @@ class AssertDiagnosticsTests: FailableTestCase {
140140
}
141141
}
142142

143-
// MARK: - Failure testing
144-
145-
/// Subclasses are considered to pass if exactly the right number of test assertions
146-
/// fail in each `assertFails(times:during:)` block. Failures are recorded
147-
/// if they fail too often or not often enough.
148-
class FailableTestCase: XCTestCase {
149-
fileprivate var anticipatedFailures = 0
150-
151-
func assertFails(
152-
times: Int = 1,
153-
_ message: String = "",
154-
file: String = #file,
155-
line: Int = #line,
156-
during body: () throws -> Void
157-
) rethrows {
158-
let outer = anticipatedFailures
159-
anticipatedFailures = times
160-
161-
defer {
162-
if anticipatedFailures > 0 {
163-
recordFailure(
164-
withDescription: "\(anticipatedFailures) failure(s) were supposed to occur, but did not: \(message)",
165-
inFile: file, atLine: line,
166-
expected: false
167-
)
168-
}
169-
anticipatedFailures = outer
143+
/// Invoke a function that is expected to record a specific number of issues.
144+
///
145+
/// - Parameters:
146+
/// - expectedCount: The exact number of issues expected.
147+
/// - comment: An optional comment describing the known issues.
148+
/// - file: The source file to attribute issues to.
149+
/// - line: The source line to attribute issues to.
150+
/// - body: The function to invoke.
151+
private func expectedIssue(
152+
count: Int = 1,
153+
_ comment: Comment? = nil,
154+
file: String = #file,
155+
line: UInt = #line,
156+
_ body: () async throws -> Void
157+
) async throws {
158+
let sourceLocation = SourceLocation(
159+
fileID: file,
160+
filePath: file,
161+
line: Int(line),
162+
column: 1
163+
)
164+
try await confirmation(
165+
comment,
166+
expectedCount: count,
167+
sourceLocation: sourceLocation
168+
) { issueConfirmation in
169+
try await withKnownIssue(
170+
comment,
171+
isIntermittent: false,
172+
sourceLocation: sourceLocation
173+
) {
174+
try await body()
175+
} matching: { issue in
176+
issueConfirmation.confirm()
177+
return true
170178
}
171-
172-
try body()
173179
}
180+
}
174181

175-
override func setUp() {
176-
super.setUp()
177-
anticipatedFailures = 0
178-
}
179-
180-
override func recordFailure(
181-
withDescription description: String,
182-
inFile filePath: String, atLine lineNumber: Int,
183-
expected: Bool
184-
) {
185-
guard anticipatedFailures == 0 else {
186-
anticipatedFailures -= 1
187-
return
188-
}
189-
190-
if #available(macOS 10.13, *) {
191-
192-
#if canImport(Darwin)
193-
super.record(XCTIssue(type: .assertionFailure,
194-
compactDescription: description,
195-
sourceCodeContext: XCTSourceCodeContext(location: XCTSourceCodeLocation(filePath: filePath,
196-
lineNumber: lineNumber))))
182+
private extension Trait where Self == IssueHandlingTrait {
183+
/// Filter out known issues, keeping only real failures (like confirmation miscounts).
184+
static func suppressKnownIssues() -> Self {
185+
#if compiler(>=6.3)
186+
.filterIssues { $0.isFailure }
197187
#else
198-
super.recordFailure(withDescription: description,
199-
inFile: filePath, atLine: lineNumber,
200-
expected: expected)
188+
// For compatibility, use `isKnown` which wasn't SPI on older versions.
189+
.filterIssues { $0.isKnown }
201190
#endif
202-
} else {
203-
fatalError(description, line: UInt(lineNumber))
204-
}
205191
}
206192
}

0 commit comments

Comments
 (0)