forked from swiftlang/swift-build
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProcessTests.swift
More file actions
255 lines (224 loc) · 12.2 KB
/
Copy pathProcessTests.swift
File metadata and controls
255 lines (224 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import Testing
import SWBTestSupport
import SWBUtil
import SWBCore
@Suite(.requireProcessSpawning)
fileprivate struct ProcessTests {
@Test
func output() async throws {
let result = try await Process.getShellOutput("echo hello")
#expect(result.exitStatus == .exit(0))
#expect(result.stdout == Data(("hello" + .newline).utf8))
#expect(result.stderr == Data())
}
@Test
func outputRedirectingStderr() async throws {
let result = try await Process.getMergedShellOutput { host in
host == .windows ? "echo stdout & echo stderr 1>&2" : "echo stdout; echo stderr 1>&2"
}
#expect(result.exitStatus == .exit(0))
if try ProcessInfo.processInfo.hostOperatingSystem() == .windows {
#expect(result.output == Data("stdout \r\nstderr \r\n".utf8))
} else {
#expect(result.output == Data("stdout\nstderr\n".utf8))
}
}
@Test
func outputNotRedirectingStderr() async throws {
let result = try await Process.getShellOutput("echo stderr 1>&2")
#expect(result.exitStatus == .exit(0))
#expect(result.stdout == Data())
if try ProcessInfo.processInfo.hostOperatingSystem() == .windows {
#expect(result.stderr == Data("stderr \r\n".utf8))
} else {
#expect(result.stderr == Data("stderr\n".utf8))
}
}
@Test(.requireThreadSafeWorkingDirectory)
func workingDirectory() async throws {
let previous = Path.currentDirectory.str
let (_, output1, _) = try await Process.getShellOutput({ host in host == .windows ? "echo %CD%" : "echo $PWD" }, currentDirectoryURL: nil)
#expect(String(decoding: output1, as: UTF8.self) == previous + .newline)
try await withTemporaryDirectory { dir in
let (_, output2, _) = try await Process.getShellOutput({ host in host == .windows ? "echo %CD%" : "echo $PWD" }, currentDirectoryURL: URL(fileURLWithPath: dir.str))
#expect(String(decoding: output2, as: UTF8.self) == dir.str + .newline)
let (_, output3) = try await Process.getMergedShellOutput({ host in host == .windows ? "echo %CD%" : "echo $PWD" }, currentDirectoryURL: URL(fileURLWithPath: dir.str))
#expect(String(decoding: output3, as: UTF8.self) == dir.str + .newline)
}
let (_, output3, _) = try await Process.getShellOutput({ host in host == .windows ? "echo %CD%" : "echo $PWD" }, currentDirectoryURL: nil)
#expect(String(decoding: output3, as: UTF8.self) == previous + .newline)
// Failing on Windows due to https://github.com/apple/swift-corelibs-foundation/issues/5071
#if !os(Windows)
let (_, output4, _) = try await Process.getShellOutput({ host in host == .windows ? "echo %CD%" : "echo $PWD" }, currentDirectoryURL: URL(fileURLWithPath: Path.root.str))
#expect(String(decoding: output4, as: UTF8.self) == Path.root.str + .newline)
#endif
try await withThrowingTaskGroup(of: Void.self) { group in
for i in 0..<100 {
group.addTask {
if i % 2 == 0 {
let (_, output3, _) = try await Process.getShellOutput({ host in host == .windows ? "echo %CD%" : "echo $PWD" }, currentDirectoryURL: nil)
#expect(String(decoding: output3, as: UTF8.self) == previous + .newline)
} else {
// Failing on Windows due to https://github.com/apple/swift-corelibs-foundation/issues/5071
#if !os(Windows)
let (_, output4, _) = try await Process.getShellOutput({ host in host == .windows ? "echo %CD%" : "echo $PWD" }, currentDirectoryURL: URL(fileURLWithPath: Path.root.str))
#expect(String(decoding: output4, as: UTF8.self) == Path.root.str + .newline)
#endif
}
}
}
try await group.waitForAll()
}
let current = Path.currentDirectory.str
#expect(previous == current)
}
@Test
func exitCode() async throws {
let result = try await Process.getShellOutput { host in
host == .windows ? "exit /b 42" : "exit 42"
}
#expect(result.exitStatus == .exit(42))
}
@Test(.enabled(if: StackedSearchPath(environment: .current, fs: localFS).lookup(Path("clang")) != nil, "requires clang in PATH"))
func uncaughtSignal() async throws {
try await withTemporaryDirectory { tmpDir in
let mainCPath = tmpDir.join("main.c").str
let exePath = tmpDir.join("exe").str
let source =
"""
#ifdef _WIN32
#include <windows.h>
#include <processthreadsapi.h>
#else
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#endif
int main() {
#ifdef _WIN32
TerminateProcess(GetCurrentProcess(), 0xC0000009); // STATUS_BAD_INITIAL_STACK
#else
kill(getpid(), SIGKILL); // ignore-unacceptable-language; POSIX API
#endif
return 0;
}
"""
try source.write(to: URL(fileURLWithPath: mainCPath), atomically: true, encoding: .utf8)
var clangArgs = ["clang", "-o", exePath, mainCPath]
#if os(macOS)
// Pin arch + deployment target so the binary can exec on CI hosts.
let hostArch = try #require(Architecture.host.stringValue)
clangArgs += ["-target", "\(hostArch)-apple-macos26.0"]
#endif
let _ = try await runHostProcess(clangArgs)
do {
let result = try await Process.getOutput(url: URL(fileURLWithPath: exePath), arguments: [])
#expect(result.exitStatus == .uncaughtSignal(9))
}
do {
let result = try await Process.getOutput(url: URL(fileURLWithPath: exePath), arguments: [])
#expect(result.exitStatus != .exit(0))
}
}
}
#if !os(Windows)
@Test
func ensureFillingStderrBeforeWritingToStdoutDoesNotDeadlock() async throws {
let result = try await Process.getShellOutput(
"dd if=/dev/zero bs=131072 count=1 1>&2 2>/dev/null; echo done"
)
#expect(result.exitStatus == .exit(0))
#expect(result.stdout == Data("done\n".utf8))
#expect(result.stderr.count == 131072)
}
#endif
@Test
func exitStatus() throws {
#if !os(Windows)
#expect(Processes.ExitStatus(rawValue: -2) == .uncaughtSignal(126))
#expect(Processes.ExitStatus(rawValue: -1) == nil)
#expect(Processes.ExitStatus(rawValue: 0) == .exit(0))
#expect(Processes.ExitStatus(rawValue: 1) == .uncaughtSignal(SIGHUP))
#expect(Processes.ExitStatus(rawValue: 126) == .uncaughtSignal(126))
#expect(Processes.ExitStatus(rawValue: 127) == nil)
#expect(Processes.ExitStatus(rawValue: 128) == .exit(0))
// POSIX
#expect(Processes.ExitStatus(rawValue: 129) == .uncaughtSignal(SIGHUP)) // 1
#expect(Processes.ExitStatus(rawValue: 130) == .uncaughtSignal(SIGINT)) // 2
#expect(Processes.ExitStatus(rawValue: 131) == .uncaughtSignal(SIGQUIT)) // 3
#expect(Processes.ExitStatus(rawValue: 132) == .uncaughtSignal(SIGILL)) // 4
#expect(Processes.ExitStatus(rawValue: 133) == .uncaughtSignal(SIGTRAP)) // 5
#expect(Processes.ExitStatus(rawValue: 134) == .uncaughtSignal(SIGABRT)) // 6; aka SIGIOT
#expect(Processes.ExitStatus(rawValue: 136) == .uncaughtSignal(SIGFPE)) // 8
#expect(Processes.ExitStatus(rawValue: 137) == .uncaughtSignal(SIGKILL)) // 9
#expect(Processes.ExitStatus(rawValue: 139) == .uncaughtSignal(SIGSEGV)) // 11
#expect(Processes.ExitStatus(rawValue: 141) == .uncaughtSignal(SIGPIPE)) // 13
#expect(Processes.ExitStatus(rawValue: 142) == .uncaughtSignal(SIGALRM)) // 14
#expect(Processes.ExitStatus(rawValue: 143) == .uncaughtSignal(SIGTERM)) // 15
#expect(Processes.ExitStatus(rawValue: 160) == .uncaughtSignal(32))
#expect(Processes.ExitStatus(rawValue: 254) == .uncaughtSignal(126))
#expect(Processes.ExitStatus(rawValue: 255) == nil)
#expect(Processes.ExitStatus(rawValue: 256) == .exit(1))
#expect(Processes.ExitStatus(rawValue: 257) == .uncaughtSignal(1))
#expect(Processes.ExitStatus(rawValue: 510) == .uncaughtSignal(126))
#expect(Processes.ExitStatus(rawValue: 511) == nil)
#expect(Processes.ExitStatus(rawValue: 512) == .exit(2))
#expect(Processes.ExitStatus(rawValue: 513) == .uncaughtSignal(1))
#expect(Processes.ExitStatus(rawValue: 766) == .uncaughtSignal(126))
#expect(Processes.ExitStatus(rawValue: 767) == nil)
#expect(Processes.ExitStatus(rawValue: 768) == .exit(3))
#expect(Processes.ExitStatus(rawValue: 769) == .uncaughtSignal(1))
#expect(Processes.ExitStatus(rawValue: 1022) == .uncaughtSignal(126))
#expect(Processes.ExitStatus(rawValue: 1023) == nil)
#expect(Processes.ExitStatus(rawValue: 1024) == .exit(4))
#expect(Processes.ExitStatus(rawValue: 1025) == .uncaughtSignal(1))
#expect(Processes.ExitStatus(rawValue: 65278) == .uncaughtSignal(126))
#expect(Processes.ExitStatus(rawValue: 65279) == nil)
#expect(Processes.ExitStatus(rawValue: 65280) == .exit(255))
#expect(Processes.ExitStatus(rawValue: 65281) == .uncaughtSignal(1))
#endif
}
}
extension SWBUtil.Process {
fileprivate static func getMergedShellOutput(_ script: String, currentDirectoryURL: URL? = nil) async throws -> (exitStatus: Processes.ExitStatus, output: Data) {
try await getMergedShellOutput({ _ in script }, currentDirectoryURL: currentDirectoryURL)
}
fileprivate static func getShellOutput(_ script: String, currentDirectoryURL: URL? = nil) async throws -> (exitStatus: Processes.ExitStatus, stdout: Data, stderr: Data) {
try await getShellOutput({ _ in script }, currentDirectoryURL: currentDirectoryURL)
}
fileprivate static func getMergedShellOutput(_ script: (_ hostOS: OperatingSystem) async throws -> String, currentDirectoryURL: URL? = nil) async throws -> (exitStatus: Processes.ExitStatus, output: Data) {
let (commandShellPath, arguments) = try await _shellAndArgs(script)
return try await getMergedOutput(url: URL(fileURLWithPath: commandShellPath), arguments: arguments, currentDirectoryURL: currentDirectoryURL)
}
fileprivate static func getShellOutput(_ script: (_ hostOS: OperatingSystem) async throws -> String, currentDirectoryURL: URL? = nil) async throws -> (exitStatus: Processes.ExitStatus, stdout: Data, stderr: Data) {
let (commandShellPath, arguments) = try await _shellAndArgs(script)
let executionResult = try await getOutput(url: URL(fileURLWithPath: commandShellPath), arguments: arguments, currentDirectoryURL: currentDirectoryURL)
return (executionResult.exitStatus, executionResult.stdout, executionResult.stderr)
}
fileprivate static func _shellAndArgs(_ script: (_ hostOS: OperatingSystem) async throws -> String) async throws -> (String, [String]) {
let commandShellPath: String
let arguments: [String]
let hostOS = try ProcessInfo.processInfo.hostOperatingSystem()
let scriptString = try await script(hostOS)
if hostOS == .windows {
commandShellPath = try #require(getEnvironmentVariable("ComSpec"), "Can't determine path to cmd.exe because the ComSpec environment variable is not set")
arguments = ["/c", scriptString]
} else {
commandShellPath = "/bin/sh"
arguments = ["-c", scriptString]
}
return (commandShellPath, arguments)
}
}