Skip to content

Commit d056ae8

Browse files
committed
Test command output can be written asynchronously
1 parent c6d5146 commit d056ae8

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,
@@ -116,3 +118,34 @@ public func expectAsyncThrowsError<T>(
116118
errorHandler(error)
117119
}
118120
}
121+
122+
/// Checks if an output stream contains a specific string, with retry logic for asynchronous writes.
123+
/// - Parameters:
124+
/// - outputStream: The output stream to check
125+
/// - needle: The string to search for in the output stream
126+
/// - timeout: Maximum time to wait for the string to appear (default: 3 seconds)
127+
/// - retryInterval: Time to wait between checks (default: 50 milliseconds)
128+
/// - Returns: True if the string was found within the timeout period
129+
public func waitForOutputStreamToContain(
130+
_ outputStream: BufferedOutputByteStream,
131+
_ needle: String,
132+
timeout: TimeInterval = 3.0,
133+
retryInterval: TimeInterval = 0.05
134+
) async throws -> Bool {
135+
let description = outputStream.bytes.description
136+
if description.contains(needle) {
137+
return true
138+
}
139+
140+
let startTime = Date()
141+
while Date().timeIntervalSince(startTime) < timeout {
142+
let description = outputStream.bytes.description
143+
if description.contains(needle) {
144+
return true
145+
}
146+
147+
try await Task.sleep(nanoseconds: UInt64(retryInterval * 1_000_000_000))
148+
}
149+
150+
return outputStream.bytes.description.contains(needle)
151+
}

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
@@ -1194,10 +1194,12 @@ struct TestCommandTests {
11941194

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

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

@@ -1213,11 +1215,11 @@ struct TestCommandTests {
12131215

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

1216-
let errorDescription = outputStream.bytes.description
12171218
// Should hit the --parallel error first since validation is done in order
1219+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --parallel")
12181220
#expect(
1219-
errorDescription.contains("--debugger cannot be used with --parallel"),
1220-
"Expected error about incompatible flags, got: \(errorDescription)"
1221+
found,
1222+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12211223
)
12221224
}
12231225

@@ -1233,10 +1235,10 @@ struct TestCommandTests {
12331235

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

1236-
let errorDescription = outputStream.bytes.description
1238+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --num-workers")
12371239
#expect(
1238-
errorDescription.contains("--debugger cannot be used with --num-workers"),
1239-
"Expected error about incompatible flags, got: \(errorDescription)"
1240+
found,
1241+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12401242
)
12411243
}
12421244

@@ -1252,10 +1254,10 @@ struct TestCommandTests {
12521254

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

1255-
let errorDescription = outputStream.bytes.description
1257+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --list-tests")
12561258
#expect(
1257-
errorDescription.contains("--debugger cannot be used with --list-tests"),
1258-
"Expected error about incompatible flags, got: \(errorDescription)"
1259+
found,
1260+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12591261
)
12601262
}
12611263

@@ -1271,10 +1273,10 @@ struct TestCommandTests {
12711273

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

1274-
let errorDescription = outputStream.bytes.description
1276+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with --show-codecov-path")
12751277
#expect(
1276-
errorDescription.contains("--debugger cannot be used with --show-codecov-path"),
1277-
"Expected error about incompatible flags, got: \(errorDescription)"
1278+
found,
1279+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12781280
)
12791281
}
12801282

@@ -1290,10 +1292,10 @@ struct TestCommandTests {
12901292

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

1293-
let errorDescription = outputStream.bytes.description
1295+
let found = try await waitForOutputStreamToContain(outputStream, "--debugger cannot be used with release configuration")
12941296
#expect(
1295-
errorDescription.contains("--debugger cannot be used with release configuration"),
1296-
"Expected error about incompatible flags, got: \(errorDescription)"
1297+
found,
1298+
"Expected error about incompatible flags, got: \(outputStream.bytes.description)"
12971299
)
12981300
}
12991301

0 commit comments

Comments
 (0)