Skip to content

Commit bf9aacd

Browse files
committed
Removed executing: label from .run and made CollectedResults.standardOutput non-optional with fatalError
1 parent 9d0eeaa commit bf9aacd

File tree

4 files changed

+57
-31
lines changed

4 files changed

+57
-31
lines changed

Sources/FoundationEssentials/Subprocess/Platforms/Subprocess+Unix.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ internal func monitorProcessTermination(
281281
source.setEventHandler {
282282
source.cancel()
283283
var status: Int32 = -1
284-
waitpid(pid.value, &status, WNOHANG)
284+
waitpid(pid.value, &status, 0)
285285
if _was_process_exited(status) != 0 {
286286
continuation.resume(returning: .exit(_get_exit_code(status)))
287287
return

Sources/FoundationEssentials/Subprocess/Subprocess+API.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import SystemPackage
1313

1414
extension Subprocess {
1515
public static func run(
16-
executing executable: Executable,
16+
_ executable: Executable,
1717
arguments: Arguments = [],
1818
environment: Environment = .inherit,
1919
workingDirectory: FilePath? = nil,
@@ -23,7 +23,7 @@ extension Subprocess {
2323
error: CollectedOutputMethod = .collect
2424
) async throws -> CollectedResult {
2525
let result = try await self.run(
26-
executing: executable,
26+
executable,
2727
arguments: arguments,
2828
environment: environment,
2929
workingDirectory: workingDirectory,
@@ -48,7 +48,7 @@ extension Subprocess {
4848
}
4949

5050
public static func run(
51-
executing executable: Executable,
51+
_ executable: Executable,
5252
arguments: Arguments = [],
5353
environment: Environment = .inherit,
5454
workingDirectory: FilePath? = nil,
@@ -58,7 +58,7 @@ extension Subprocess {
5858
error: CollectedOutputMethod = .collect
5959
) async throws -> CollectedResult {
6060
let result = try await self.run(
61-
executing: executable,
61+
executable,
6262
arguments: arguments,
6363
environment: environment,
6464
workingDirectory: workingDirectory,
@@ -84,7 +84,7 @@ extension Subprocess {
8484
}
8585

8686
public static func run<S: AsyncSequence>(
87-
executing executable: Executable,
87+
_ executable: Executable,
8888
arguments: Arguments = [],
8989
environment: Environment = .inherit,
9090
workingDirectory: FilePath? = nil,
@@ -94,7 +94,7 @@ extension Subprocess {
9494
error: CollectedOutputMethod = .collect
9595
) async throws -> CollectedResult where S.Element == UInt8 {
9696
let result = try await self.run(
97-
executing: executable,
97+
executable,
9898
arguments: arguments,
9999
environment: environment,
100100
workingDirectory: workingDirectory,
@@ -123,7 +123,7 @@ extension Subprocess {
123123
// MARK: Custom Execution Body
124124
extension Subprocess {
125125
public static func run<R>(
126-
executing executable: Executable,
126+
_ executable: Executable,
127127
arguments: Arguments = [],
128128
environment: Environment = .inherit,
129129
workingDirectory: FilePath? = nil,
@@ -144,7 +144,7 @@ extension Subprocess {
144144
}
145145

146146
public static func run<R>(
147-
executing executable: Executable,
147+
_ executable: Executable,
148148
arguments: Arguments = [],
149149
environment: Environment = .inherit,
150150
workingDirectory: FilePath? = nil,
@@ -169,7 +169,7 @@ extension Subprocess {
169169
}
170170

171171
public static func run<R, S: AsyncSequence>(
172-
executing executable: Executable,
172+
_ executable: Executable,
173173
arguments: Arguments = [],
174174
environment: Environment = .inherit,
175175
workingDirectory: FilePath? = nil,
@@ -194,7 +194,7 @@ extension Subprocess {
194194
}
195195

196196
public static func run<R>(
197-
executing executable: Executable,
197+
_ executable: Executable,
198198
arguments: Arguments = [],
199199
environment: Environment = .inherit,
200200
workingDirectory: FilePath? = nil,

Sources/FoundationEssentials/Subprocess/Subprocess.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public struct Subprocess: Sendable {
4444
fatalError("The standard output was not redirected")
4545
}
4646
guard let fd = fd else {
47-
fatalError("The standard output has already been consumed")
47+
fatalError("The standard output has already been closed")
4848
}
4949
return AsyncBytes(fileDescriptor: fd)
5050
}
@@ -55,7 +55,7 @@ public struct Subprocess: Sendable {
5555
fatalError("The standard error was not redirected")
5656
}
5757
guard let fd = fd else {
58-
fatalError("The standard error has already been consumed")
58+
fatalError("The standard error has already been closed")
5959
}
6060
return AsyncBytes(fileDescriptor: fd)
6161
}
@@ -114,8 +114,20 @@ extension Subprocess {
114114
public struct CollectedResult: Sendable, Hashable {
115115
public let processIdentifier: ProcessIdentifier
116116
public let terminationStatus: TerminationStatus
117-
public let standardOutput: Data?
118-
public let standardError: Data?
117+
private let _standardOutput: Data?
118+
private let _standardError: Data?
119+
public var standardOutput: Data {
120+
guard let output = self._standardOutput else {
121+
fatalError("standardOutput is only available if the Subprocess was ran with .collect as output")
122+
}
123+
return output
124+
}
125+
public var standardError: Data {
126+
guard let output = self._standardError else {
127+
fatalError("standardError is only available if the Subprocess was ran with .collect as error ")
128+
}
129+
return output
130+
}
119131

120132
internal init(
121133
processIdentifier: ProcessIdentifier,
@@ -124,8 +136,8 @@ extension Subprocess {
124136
standardError: Data?) {
125137
self.processIdentifier = processIdentifier
126138
self.terminationStatus = terminationStatus
127-
self.standardOutput = standardOutput
128-
self.standardError = standardError
139+
self._standardOutput = standardOutput
140+
self._standardError = standardError
129141
}
130142
}
131143
}
@@ -144,7 +156,7 @@ extension Subprocess {
144156
private func capture(fileDescriptor: FileDescriptor, maxLength: Int) async throws -> Data{
145157
let chunkSize: Int = min(Subprocess.readBufferSize, maxLength)
146158
var buffer: [UInt8] = []
147-
while buffer.count < maxLength {
159+
while buffer.count <= maxLength {
148160
let captured = try await fileDescriptor.read(upToLength: chunkSize)
149161
buffer += captured
150162
if captured.count < chunkSize {
@@ -171,7 +183,7 @@ extension Subprocess {
171183
}
172184
return try await self.capture(fileDescriptor: readFd, maxLength: limit)
173185
}
174-
186+
175187
internal func captureIOs() async throws -> (standardOut: Data?, standardError: Data?) {
176188
return try await withThrowingTaskGroup(of: OutputCapturingState.self) { group in
177189
group.addTask {

Tests/FoundationEssentialsTests/SubprocessTests.swift

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,40 @@
1919
import TestSupport
2020
#endif
2121

22+
import SystemPackage
23+
2224
final class SubprocessTests: XCTestCase {
2325
func testSimple() async throws {
24-
let ls = try await Subprocess.run(executing: .named("ls"), output: .collect, error: .discard)
25-
let result = String(data: ls.standardOutput!, encoding: .utf8)!
26+
let ls = try await Subprocess.run(.named("ls"))
27+
let result = String(data: ls.standardOutput, encoding: .utf8)!
2628
XCTAssert(ls.terminationStatus.isSuccess)
2729
XCTAssert(!result.isEmpty)
2830
print(result)
2931
}
30-
32+
33+
func testInteractive() async throws {
34+
let su = try await Subprocess.run(.at("/Users/icharleshu/Developer/super.sh"), input: .readFrom(.standardInput, closeWhenDone: false))
35+
XCTAssert(!su.terminationStatus.isSuccess)
36+
}
37+
38+
func testChained() async throws {
39+
let (readFd, writeFd) = try FileDescriptor.pipe()
40+
try await Subprocess.run(
41+
.named("ls"), output: .writeTo(writeFd, closeWhenDone: true), error: .discard)
42+
let grep = try await Subprocess.run(.named("grep"), arguments: ["com"], input: .readFrom(readFd, closeWhenDone: true))
43+
var output = String(data: grep.standardOutput, encoding: .utf8) ?? "Failed to decode"
44+
print("Output: \(output)")
45+
}
46+
3147
func testLongText() async throws {
3248
let cat = try await Subprocess.run(
33-
executing: .named("cat"),
49+
.named("cat"),
3450
arguments: ["/Users/icharleshu/Downloads/PaP.txt"],
3551
output: .collect(limit: 1024 * 1024),
3652
error: .discard
3753
)
3854
print("after")
39-
print("Result: \(cat.standardOutput?.count ?? -1)")
55+
print("Result: \(cat.standardOutput.count)")
4056
}
4157

4258
func testComplex() async throws {
@@ -45,7 +61,7 @@ final class SubprocessTests: XCTestCase {
4561
}
4662

4763
let result = try await Subprocess.run(
48-
executing: .named("curl"),
64+
.named("curl"),
4965
arguments: ["http://ip.jsontest.com/"]
5066
) { execution in
5167
let output: [UInt8] = try await Array(execution.standardOutput)
@@ -57,8 +73,10 @@ final class SubprocessTests: XCTestCase {
5773
}
5874

5975
func testShell() async throws {
60-
let result = try await Subprocess.run(executing: .named("sh")) { subprocess, writer in
61-
// Stream all outputs
76+
let result = try await Subprocess.run(.named("sh")) { subprocess, writer in
77+
try await writer.write("ls | grep 'apple'\n".utf8)
78+
try await writer.finish()
79+
// Stream outputs
6280
try await withThrowingTaskGroup(of: Void.self) { group in
6381
group.addTask {
6482
// Stream output line by line
@@ -67,10 +85,6 @@ final class SubprocessTests: XCTestCase {
6785
print("> \(line)")
6886
}
6987
}
70-
group.addTask {
71-
try await writer.write("ls\n".utf8)
72-
try await writer.finish()
73-
}
7488
try await group.waitForAll()
7589
}
7690
}

0 commit comments

Comments
 (0)