-
Notifications
You must be signed in to change notification settings - Fork 145
Expand file tree
/
Copy pathExpectation+Process.swift
More file actions
159 lines (149 loc) · 5.52 KB
/
Expectation+Process.swift
File metadata and controls
159 lines (149 loc) · 5.52 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
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//
#if canImport(Foundation)
@_spi(ForToolsIntegrationOnly) public import Testing
public import Foundation
private import _TestingInternals
@_spi(Experimental)
@freestanding(expression)
@discardableResult
#if !SWT_NO_EXIT_TESTS
@available(macOS 10.15.4, *)
#else
@_unavailableInEmbedded
@available(*, unavailable, message: "Exit tests are not available on this platform.")
#endif
public macro expect(
_ process: Process,
exitsWith expectedExitCondition: ExitTest.Condition,
observing observedValues: [any PartialKeyPath<ExitTest.Result> & Sendable] = [],
_ comment: @autoclosure () -> Comment? = nil,
sourceLocation: SourceLocation = #_sourceLocation
) -> ExitTest.Result? = #externalMacro(module: "TestingMacros", type: "ExpectNSTaskExitsWithMacro")
@_spi(Experimental)
@freestanding(expression)
@discardableResult
#if !SWT_NO_EXIT_TESTS
@available(macOS 10.15.4, *)
#else
@_unavailableInEmbedded
@available(*, unavailable, message: "Exit tests are not available on this platform.")
#endif
public macro require(
_ process: Process,
exitsWith expectedExitCondition: ExitTest.Condition,
observing observedValues: [any PartialKeyPath<ExitTest.Result> & Sendable] = [],
_ comment: @autoclosure () -> Comment? = nil,
sourceLocation: SourceLocation = #_sourceLocation
) -> ExitTest.Result = #externalMacro(module: "TestingMacros", type: "RequireNSTaskExitsWithMacro")
// MARK: -
@_spi(Experimental)
@discardableResult
#if !SWT_NO_EXIT_TESTS
@available(macOS 10.15.4, *)
#else
@_unavailableInEmbedded
@available(*, unavailable, message: "Exit tests are not available on this platform.")
#endif
public func __check(
_ process: Process,
exitsWith expectedExitCondition: ExitTest.Condition,
observing observedValues: [any PartialKeyPath<ExitTest.Result> & Sendable] = [],
comments: @autoclosure () -> [Comment],
isRequired: Bool,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation
) async -> Result<ExitTest.Result?, any Error> {
#if !SWT_NO_EXIT_TESTS
// The process may have already started and may already have a termination
// handler set, so it's not possible for us to asynchronously wait for it.
// As such, we'll have to block _some_ thread.
var result: ExitTest.Result
do {
try await withCheckedThrowingContinuation { continuation in
Thread.detachNewThread {
do {
// There's an obvious race condition here, but that's a limitation of
// the Process/NSTask API and we'll just have to accept it.
if !process.isRunning {
try process.run()
}
process.waitUntilExit()
continuation.resume()
} catch {
continuation.resume(throwing: error)
}
}
}
let reason = process.terminationReason
let exitStatus: ExitStatus = switch reason {
case .exit:
.exitCode(process.terminationStatus)
case .uncaughtSignal:
#if os(Windows)
// On Windows, Foundation tries to map exit codes that look like HRESULT
// values to signals, which is not the model Swift Testing uses. The
// conversion is lossy, so there's not much we can do here other than treat
// it as an exit code too.
.exitCode(process.terminationStatus)
#else
.signal(process.terminationStatus)
#endif
@unknown default:
fatalError("Unexpected termination reason '\(reason)' from process \(process). Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new")
}
result = ExitTest.Result(exitStatus: exitStatus)
func makeContent(from streamObject: Any?) -> [UInt8] {
if let fileHandle = streamObject as? FileHandle {
if let content = try? fileHandle.readToEnd() {
return Array(content)
}
} else if let pipe = streamObject as? Pipe {
return makeContent(from: pipe.fileHandleForReading)
}
return []
}
if observedValues.contains(\.standardOutputContent) {
result.standardOutputContent = makeContent(from: process.standardOutput)
}
if observedValues.contains(\.standardErrorContent) {
result.standardErrorContent = makeContent(from: process.standardError)
}
} catch {
// As with the main exit test implementation, if an error occurs while
// trying to run the exit test, treat it as a system error and treat the
// condition as a mismatch.
let issue = Issue(
kind: .system,
comments: comments() + CollectionOfOne(Comment(rawValue: String(describingForTest: error))),
sourceContext: SourceContext(backtrace: nil, sourceLocation: sourceLocation)
)
issue.record()
let exitStatus: ExitStatus = if expectedExitCondition.isApproximatelyEqual(to: .exitCode(EXIT_FAILURE)) {
.exitCode(EXIT_SUCCESS)
} else {
.exitCode(EXIT_FAILURE)
}
result = ExitTest.Result(exitStatus: exitStatus)
}
let expression = Expression("expectedExitCondition")
return __checkValue(
expectedExitCondition.isApproximatelyEqual(to: result.exitStatus),
expression: expression,
expressionWithCapturedRuntimeValues: expression.capturingRuntimeValues(result.exitStatus),
comments: comments(),
isRequired: isRequired,
sourceLocation: sourceLocation
).map { _ in result }
#else
swt_unreachable()
#endif
}
#endif