From 977fa2450706626cdb5c90a1786884fe6968a1ef Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Fri, 4 Apr 2025 11:23:46 -0400 Subject: [PATCH] Emit "barriers" into the stdout/stderr streams of an exit test. This PR causes Swift Testing to write "barriers" (known sequences of bytes) to `stdout` and `stderr` in the child process created by an exit test. Then, in the parent, these values are used to splice off any leading or trailing output that wasn't generated by the exit test's body (such as content generated by the host process, XCTest/Xcode, etc.) This reduces the amount of extraneous data reported back to the exit test's parent process. Thanks to @briancroom for the suggestion. --- Sources/Testing/ExitTests/ExitTest.swift | 78 +++++++++++++++++++++++- Tests/TestingTests/ExitTestTests.swift | 2 + 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/Sources/Testing/ExitTests/ExitTest.swift b/Sources/Testing/ExitTests/ExitTest.swift index a38c7592e..b7332ea7d 100644 --- a/Sources/Testing/ExitTests/ExitTest.swift +++ b/Sources/Testing/ExitTests/ExitTest.swift @@ -429,6 +429,73 @@ extension ABI { @_spi(Experimental) @_spi(ForToolsIntegrationOnly) extension ExitTest { + /// A barrier value to insert into the standard output and standard error + /// streams immediately before and after the body of an exit test runs in + /// order to distinguish output produced by the host process. + /// + /// The value of this property was randomly generated. It could conceivably + /// show up in actual output from an exit test, but the statistical likelihood + /// of that happening is negligible. + static var barrierValue: [UInt8] { + [ + 0x39, 0x74, 0x87, 0x6d, 0x96, 0xdd, 0xf6, 0x17, + 0x7f, 0x05, 0x61, 0x5d, 0x46, 0xeb, 0x37, 0x0c, + 0x90, 0x07, 0xca, 0xe5, 0xed, 0x0b, 0xc4, 0xc4, + 0x46, 0x36, 0xc5, 0xb8, 0x9c, 0xc7, 0x86, 0x57, + ] + } + + /// Remove the leading and trailing barrier values from the given array of + /// bytes along. + /// + /// - Parameters: + /// - buffer: The buffer to trim. + /// + /// - Returns: A copy of `buffer`. If a barrier value (equal to + /// ``barrierValue``) is present in `buffer`, it and everything before it + /// are trimmed from the beginning of the copy. If there is more than one + /// barrier value present, the last one and everything after it are trimmed + /// from the end of the copy. If no barrier value is present, `buffer` is + /// returned verbatim. + private static func _trimToBarrierValues(_ buffer: [UInt8]) -> [UInt8] { + let barrierValue = barrierValue + let firstBarrierByte = barrierValue[0] + + // If the buffer is too small to contain the barrier value, exit early. + guard buffer.count > barrierValue.count else { + return buffer + } + + // Find all the indices where the first byte of the barrier is present. + let splits = buffer.indices.filter { buffer[$0] == firstBarrierByte } + + // Trim off the leading barrier value. If we didn't find any barrier values, + // we do nothing. + let leadingIndex = splits.first { buffer[$0...].starts(with: barrierValue) } + guard let leadingIndex else { + return buffer + } + var trimmedBuffer = buffer[leadingIndex...].dropFirst(barrierValue.count) + + // If there's a trailing barrier value, trim it too. If it's at the same + // index as the leading barrier value, that means only one barrier value + // was present and we should assume it's the leading one. + let trailingIndex = splits.last { buffer[$0...].starts(with: barrierValue) } + if let trailingIndex, trailingIndex > leadingIndex { + trimmedBuffer = trimmedBuffer[..