Skip to content

Commit 5bb5783

Browse files
committed
Test command output can be written asynchronously
1 parent c31454e commit 5bb5783

File tree

3 files changed

+55
-20
lines changed

3 files changed

+55
-20
lines changed

Sources/_InternalTestSupport/SwiftTesting+Helpers.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import Basics
1212
import Testing
13+
import Foundation
14+
import class TSCBasic.BufferedOutputByteStream
1315

1416
public func expectFileExists(
1517
at path: AbsolutePath,
@@ -146,3 +148,34 @@ public func expectAsyncThrowsError<T>(
146148
errorHandler(error)
147149
}
148150
}
151+
152+
/// Checks if an output stream contains a specific string, with retry logic for asynchronous writes.
153+
/// - Parameters:
154+
/// - outputStream: The output stream to check
155+
/// - needle: The string to search for in the output stream
156+
/// - timeout: Maximum time to wait for the string to appear (default: 3 seconds)
157+
/// - retryInterval: Time to wait between checks (default: 50 milliseconds)
158+
/// - Returns: True if the string was found within the timeout period
159+
public func waitForOutputStreamToContain(
160+
_ outputStream: BufferedOutputByteStream,
161+
_ needle: String,
162+
timeout: TimeInterval = 3.0,
163+
retryInterval: TimeInterval = 0.05
164+
) async throws -> Bool {
165+
let description = outputStream.bytes.description
166+
if description.contains(needle) {
167+
return true
168+
}
169+
170+
let startTime = Date()
171+
while Date().timeIntervalSince(startTime) < timeout {
172+
let description = outputStream.bytes.description
173+
if description.contains(needle) {
174+
return true
175+
}
176+
177+
try await Task.sleep(nanoseconds: UInt64(retryInterval * 1_000_000_000))
178+
}
179+
180+
return outputStream.bytes.description.contains(needle)
181+
}

Tests/CommandsTests/PackageCommandTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ fileprivate func execute(
4444
try localFileSystem.writeFileContents(packagePath.appending("Package.swift"), string: manifest)
4545
}
4646

47-
environment["SWIFTPM_TESTS_LLDB"] = "1"
48-
47+
// don't ignore local packages when caching
48+
environment["SWIFTPM_TESTS_PACKAGECACHE"] = "1"
4949
return try await executeSwiftPackage(
5050
packagePath,
5151
configuration: configuration,

Tests/CommandsTests/TestCommandTests.swift

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,10 +1190,12 @@ struct TestCommandTests {
11901190

11911191
#expect(error == ExitCode.failure, "Expected ExitCode.failure, got \(String(describing: error))")
11921192

1193-
let errorDescription = outputStream.bytes.description
1193+
// The output stream is written to asynchronously on a DispatchQueue and can
1194+
// receive output after the command has thrown.
1195+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --parallel")
11941196
#expect(
1195-
errorDescription.contains("--debugger cannot be used with --parallel"),
1196-
"Expected error about incompatible flags, got: \(errorDescription)"
1197+
found,
1198+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
11971199
)
11981200
}
11991201

@@ -1209,11 +1211,11 @@ struct TestCommandTests {
12091211

12101212
#expect(error == ExitCode.failure, "Expected ExitCode.failure, got \(String(describing: error))")
12111213

1212-
let errorDescription = outputStream.bytes.description
12131214
// Should hit the --parallel error first since validation is done in order
1215+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --parallel")
12141216
#expect(
1215-
errorDescription.contains("--debugger cannot be used with --parallel"),
1216-
"Expected error about incompatible flags, got: \(errorDescription)"
1217+
found,
1218+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12171219
)
12181220
}
12191221

@@ -1229,10 +1231,10 @@ struct TestCommandTests {
12291231

12301232
#expect(error == ExitCode.failure, "Expected ExitCode.failure, got \(String(describing: error))")
12311233

1232-
let errorDescription = outputStream.bytes.description
1234+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --num-workers")
12331235
#expect(
1234-
errorDescription.contains("--debugger cannot be used with --num-workers"),
1235-
"Expected error about incompatible flags, got: \(errorDescription)"
1236+
found,
1237+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12361238
)
12371239
}
12381240

@@ -1248,10 +1250,10 @@ struct TestCommandTests {
12481250

12491251
#expect(error == ExitCode.failure, "Expected ExitCode.failure, got \(String(describing: error))")
12501252

1251-
let errorDescription = outputStream.bytes.description
1253+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --list-tests")
12521254
#expect(
1253-
errorDescription.contains("--debugger cannot be used with --list-tests"),
1254-
"Expected error about incompatible flags, got: \(errorDescription)"
1255+
found,
1256+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12551257
)
12561258
}
12571259

@@ -1267,10 +1269,10 @@ struct TestCommandTests {
12671269

12681270
#expect(error == ExitCode.failure, "Expected ExitCode.failure, got \(String(describing: error))")
12691271

1270-
let errorDescription = outputStream.bytes.description
1272+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --show-codecov-path")
12711273
#expect(
1272-
errorDescription.contains("--debugger cannot be used with --show-codecov-path"),
1273-
"Expected error about incompatible flags, got: \(errorDescription)"
1274+
found,
1275+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12741276
)
12751277
}
12761278

@@ -1286,10 +1288,10 @@ struct TestCommandTests {
12861288

12871289
#expect(error == ExitCode.failure, "Expected ExitCode.failure, got \(String(describing: error))")
12881290

1289-
let errorDescription = outputStream.bytes.description
1291+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with release configuration")
12901292
#expect(
1291-
errorDescription.contains("--debugger cannot be used with release configuration"),
1292-
"Expected error about incompatible flags, got: \(errorDescription)"
1293+
found,
1294+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12931295
)
12941296
}
12951297

0 commit comments

Comments
 (0)