aboutsummaryrefslogtreecommitdiff
path: root/Tests/barTests/phbarTests.swift
diff options
context:
space:
mode:
authorTommaso Negri <me@tommasongr.com>2026-10-15 11:01:01 +0200
committerTommaso Negri <me@tommasongr.com>2026-10-15 11:01:01 +0200
commit758f757cb9b60dddf10efd5d37becfec9f4b5196 (patch)
tree8c9a6ceea48a5981765e2150ea01890544bd231e /Tests/barTests/phbarTests.swift
parent498ac51307412adc29d6d4a6240ae7b98d55621e (diff)
downloadbar-758f757cb9b60dddf10efd5d37becfec9f4b5196.tar.gz
bar-758f757cb9b60dddf10efd5d37becfec9f4b5196.zip
drain command stdout and stderr concurrently
Diffstat (limited to 'Tests/barTests/phbarTests.swift')
-rw-r--r--Tests/barTests/phbarTests.swift53
1 files changed, 49 insertions, 4 deletions
diff --git a/Tests/barTests/phbarTests.swift b/Tests/barTests/phbarTests.swift
index 6a2c4d5..982e728 100644
--- a/Tests/barTests/phbarTests.swift
+++ b/Tests/barTests/phbarTests.swift
@@ -1,8 +1,53 @@
+import Foundation
+import Synchronization
import Testing
@testable import bar
-@Test func example() async throws {
- // Write your test here and use APIs like `#expect(...)` to check expected conditions.
- // Swift Testing Documentation
- // https://developer.apple.com/documentation/testing
+/// Blocking Runner work stays off the cooperative executor. The outer deadline
+/// also fails promptly if a regression prevents Runner from returning.
+private func runCommand(_ command: String, env: [String: String] = [:]) throws -> (lines: [String], diagnostics: Data) {
+ let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
+ FileManager.default.createFile(atPath: url.path, contents: nil)
+ let output = try FileHandle(forWritingTo: url)
+ defer { try? FileManager.default.removeItem(at: url) }
+ let result = Mutex<[String]?>(nil)
+ let finished = DispatchSemaphore(value: 0)
+ DispatchQueue.global(qos: .utility).async {
+ let lines = Runner.run(command, env: env, errorOutput: output)
+ try? output.close()
+ result.withLock { $0 = lines }
+ finished.signal()
+ }
+ let completed = finished.wait(timeout: .now() + 5) == .success
+ try #require(completed, "Runner exceeded the five-second outer deadline")
+ return (try #require(result.withLock { $0 }), try Data(contentsOf: url))
+}
+
+@Test func drainsLargeStderrBeforeStdout() throws {
+ let result = try runCommand("/usr/bin/head -c 1048576 /dev/zero >&2; printf ' ready \\n'")
+ #expect(result.lines == [" ready "])
+ #expect(result.diagnostics == Data(repeating: 0, count: 1048576))
+}
+
+@Test func drainsSimultaneousLargeStreams() throws {
+ let result = try runCommand("/usr/bin/head -c 1048576 /dev/zero >&2 & /usr/bin/head -c 1048576 /dev/zero; wait")
+ #expect(result.lines == [String(repeating: "\0", count: 1048576)])
+ #expect(result.diagnostics == Data(repeating: 0, count: 1048576))
+}
+
+@Test func emptyCommandOutput() throws {
+ let result = try runCommand(":")
+ #expect(result.lines.isEmpty)
+ #expect(result.diagnostics.isEmpty)
+}
+
+@Test func failedCommandForwardsDiagnostics() throws {
+ let result = try runCommand("printf 'ignored'; printf 'failure\\n' >&2; exit 7")
+ #expect(result.lines == ["ERROR"])
+ #expect(result.diagnostics == Data("failure\n".utf8))
+}
+
+@Test func shellEnvironmentAndWhitespaceArePreserved() throws {
+ let result = try runCommand("printf ' %s \\n\\nlast\\n' \"$BAR_RUNNER_TEST\"; test -n \"$PATH\"", env: ["BAR_RUNNER_TEST": "value"])
+ #expect(result.lines == [" value ", "", "last"])
}