Skip to content

Commit 1c9c9af

Browse files
authored
[6.2] Emit "barriers" into the stdout/stderr streams of an exit test. (#1220)
1 parent 93fd967 commit 1c9c9af

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,73 @@ extension ABI {
530530

531531
@_spi(ForToolsIntegrationOnly)
532532
extension ExitTest {
533+
/// A barrier value to insert into the standard output and standard error
534+
/// streams immediately before and after the body of an exit test runs in
535+
/// order to distinguish output produced by the host process.
536+
///
537+
/// The value of this property was randomly generated. It could conceivably
538+
/// show up in actual output from an exit test, but the statistical likelihood
539+
/// of that happening is negligible.
540+
static var barrierValue: [UInt8] {
541+
[
542+
0x39, 0x74, 0x87, 0x6d, 0x96, 0xdd, 0xf6, 0x17,
543+
0x7f, 0x05, 0x61, 0x5d, 0x46, 0xeb, 0x37, 0x0c,
544+
0x90, 0x07, 0xca, 0xe5, 0xed, 0x0b, 0xc4, 0xc4,
545+
0x46, 0x36, 0xc5, 0xb8, 0x9c, 0xc7, 0x86, 0x57,
546+
]
547+
}
548+
549+
/// Remove the leading and trailing barrier values from the given array of
550+
/// bytes along.
551+
///
552+
/// - Parameters:
553+
/// - buffer: The buffer to trim.
554+
///
555+
/// - Returns: A copy of `buffer`. If a barrier value (equal to
556+
/// ``barrierValue``) is present in `buffer`, it and everything before it
557+
/// are trimmed from the beginning of the copy. If there is more than one
558+
/// barrier value present, the last one and everything after it are trimmed
559+
/// from the end of the copy. If no barrier value is present, `buffer` is
560+
/// returned verbatim.
561+
private static func _trimToBarrierValues(_ buffer: [UInt8]) -> [UInt8] {
562+
let barrierValue = barrierValue
563+
let firstBarrierByte = barrierValue[0]
564+
565+
// If the buffer is too small to contain the barrier value, exit early.
566+
guard buffer.count > barrierValue.count else {
567+
return buffer
568+
}
569+
570+
// Find all the indices where the first byte of the barrier is present.
571+
let splits = buffer.indices.filter { buffer[$0] == firstBarrierByte }
572+
573+
// Trim off the leading barrier value. If we didn't find any barrier values,
574+
// we do nothing.
575+
let leadingIndex = splits.first { buffer[$0...].starts(with: barrierValue) }
576+
guard let leadingIndex else {
577+
return buffer
578+
}
579+
var trimmedBuffer = buffer[leadingIndex...].dropFirst(barrierValue.count)
580+
581+
// If there's a trailing barrier value, trim it too. If it's at the same
582+
// index as the leading barrier value, that means only one barrier value
583+
// was present and we should assume it's the leading one.
584+
let trailingIndex = splits.last { buffer[$0...].starts(with: barrierValue) }
585+
if let trailingIndex, trailingIndex > leadingIndex {
586+
trimmedBuffer = trimmedBuffer[..<trailingIndex]
587+
}
588+
589+
return Array(trimmedBuffer)
590+
}
591+
592+
/// Write barrier values (equal to ``barrierValue``) to the standard output
593+
/// and standard error streams of the current process.
594+
private static func _writeBarrierValues() {
595+
let barrierValue = Self.barrierValue
596+
try? FileHandle.stdout.write(barrierValue)
597+
try? FileHandle.stderr.write(barrierValue)
598+
}
599+
533600
/// A handler that is invoked when an exit test starts.
534601
///
535602
/// - Parameters:
@@ -682,6 +749,13 @@ extension ExitTest {
682749
}
683750

684751
result.body = { [configuration, body = result.body] exitTest in
752+
Self._writeBarrierValues()
753+
defer {
754+
// We will generally not end up writing these values if the process
755+
// exits abnormally.
756+
Self._writeBarrierValues()
757+
}
758+
685759
try await Configuration.withCurrent(configuration) {
686760
try exitTest._decodeCapturedValuesForEntryPoint()
687761
try await body(&exitTest)
@@ -862,14 +936,14 @@ extension ExitTest {
862936
if let stdoutReadEnd {
863937
stdoutWriteEnd?.close()
864938
taskGroup.addTask {
865-
let standardOutputContent = try stdoutReadEnd.readToEnd()
939+
let standardOutputContent = try Self._trimToBarrierValues(stdoutReadEnd.readToEnd())
866940
return { $0.standardOutputContent = standardOutputContent }
867941
}
868942
}
869943
if let stderrReadEnd {
870944
stderrWriteEnd?.close()
871945
taskGroup.addTask {
872-
let standardErrorContent = try stderrReadEnd.readToEnd()
946+
let standardErrorContent = try Self._trimToBarrierValues(stderrReadEnd.readToEnd())
873947
return { $0.standardErrorContent = standardErrorContent }
874948
}
875949
}

Tests/TestingTests/ExitTestTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ private import _TestingInternals
350350
}
351351
#expect(result.exitStatus == .exitCode(EXIT_SUCCESS))
352352
#expect(result.standardOutputContent.contains("STANDARD OUTPUT".utf8))
353+
#expect(!result.standardOutputContent.contains(ExitTest.barrierValue))
353354
#expect(result.standardErrorContent.isEmpty)
354355

355356
result = try await #require(processExitsWith: .success, observing: [\.standardErrorContent]) {
@@ -360,6 +361,7 @@ private import _TestingInternals
360361
#expect(result.exitStatus == .exitCode(EXIT_SUCCESS))
361362
#expect(result.standardOutputContent.isEmpty)
362363
#expect(result.standardErrorContent.contains("STANDARD ERROR".utf8.reversed()))
364+
#expect(!result.standardErrorContent.contains(ExitTest.barrierValue))
363365
}
364366

365367
@Test("Arguments to the macro are not captured during expansion (do not need to be literals/const)")

0 commit comments

Comments
 (0)