@@ -530,6 +530,73 @@ extension ABI {
530
530
531
531
@_spi ( ForToolsIntegrationOnly)
532
532
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
+
533
600
/// A handler that is invoked when an exit test starts.
534
601
///
535
602
/// - Parameters:
@@ -682,6 +749,13 @@ extension ExitTest {
682
749
}
683
750
684
751
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
+
685
759
try await Configuration . withCurrent ( configuration) {
686
760
try exitTest. _decodeCapturedValuesForEntryPoint ( )
687
761
try await body ( & exitTest)
@@ -862,14 +936,14 @@ extension ExitTest {
862
936
if let stdoutReadEnd {
863
937
stdoutWriteEnd? . close ( )
864
938
taskGroup. addTask {
865
- let standardOutputContent = try stdoutReadEnd. readToEnd ( )
939
+ let standardOutputContent = try Self . _trimToBarrierValues ( stdoutReadEnd. readToEnd ( ) )
866
940
return { $0. standardOutputContent = standardOutputContent }
867
941
}
868
942
}
869
943
if let stderrReadEnd {
870
944
stderrWriteEnd? . close ( )
871
945
taskGroup. addTask {
872
- let standardErrorContent = try stderrReadEnd. readToEnd ( )
946
+ let standardErrorContent = try Self . _trimToBarrierValues ( stderrReadEnd. readToEnd ( ) )
873
947
return { $0. standardErrorContent = standardErrorContent }
874
948
}
875
949
}
0 commit comments