[Backtracing][Tests] add unit tests for coloured/uncoloured output

rdar://164624364
This commit is contained in:
Carl Peto
2025-11-14 13:26:10 +00:00
parent 95d6d4384c
commit 4d57a809e8
4 changed files with 284 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
#if os(macOS)
internal import Darwin
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
func level1() {
level2()
}
func level2() {
level3()
}
func level3() {
level4()
}
func level4() {
level5()
}
func level5() {
print("About to crash")
let ptr = UnsafeMutablePointer<Int>(bitPattern: 4)!
ptr.pointee = 42
}
@main
struct Crash {
static func main() {
print("subprocess stdout is a tty: \(isatty(1))")
print("subprocess stderr is a tty: \(isatty(2))")
level1()
}
}

View File

@@ -0,0 +1,73 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/TTYDetection
// RUN: %target-codesign %t/TTYDetection
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/TTYDetection 2> %t/default-output || true) && cat %t/default-output | %FileCheck %s
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no,output-to=stderr %target-run %t/TTYDetection 2> %t/stderr-output || true) && cat %t/stderr-output | %FileCheck %s --check-prefix STDERR
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no,output-to=stdout %target-run %t/TTYDetection > %t/stdout-output || true) && cat %t/stdout-output | %FileCheck %s --check-prefix STDOUT
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: asan
// REQUIRES: executable_test
// REQUIRES: backtracing
// REQUIRES: OS=macosx || OS=linux-gnu
// COM: we should be able to add Windows to this test
func level1() {
level2()
}
func level2() {
level3()
}
func level3() {
level4()
}
func level4() {
level5()
}
func level5() {
print("About to crash")
let ptr = UnsafeMutablePointer<Int>(bitPattern: 4)!
ptr.pointee = 42
}
@main
struct TTYDetection {
static func main() {
level1()
}
}
// CHECK: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 ***
// CHECK: Thread 0 {{(".*" )?}}crashed:
// CHECK: 0 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in TTYDetection
// CHECK-NEXT: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in TTYDetection
// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in TTYDetection
// CHECK-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in TTYDetection
// STDERR: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 ***
// STDERR: Thread 0 {{(".*" )?}}crashed:
// STDERR: 0 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in TTYDetection
// STDERR-NEXT: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in TTYDetection
// STDERR-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in TTYDetection
// STDERR-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in TTYDetection
// STDOUT: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 ***
// STDOUT: Thread 0 {{(".*" )?}}crashed:
// STDOUT: 0 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in TTYDetection
// STDOUT-NEXT: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in TTYDetection
// STDOUT-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in TTYDetection
// STDOUT-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in TTYDetection

View File

@@ -0,0 +1,127 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %S/Inputs/CrashSubprocess.swift -parse-as-library -Onone -g -o %t/crash
// RUN: %target-codesign %t/crash
// RUN: %target-build-swift %s -o %t/crash-host
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no,interactive=no %target-run %t/crash-host %t/crash)| %FileCheck %s
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: asan
// REQUIRES: executable_test
// REQUIRES: backtracing
// REQUIRES: OS=linux-gnu || OS=macosx
// COM: this test would need significant rework on Windows/WinSDK, but is not critical function so probably not worth porting the test to that platform
#if os(macOS)
internal import Darwin
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
guard CommandLine.arguments.count > 1 else {
fputs("Usage: \(CommandLine.arguments[0]) <path-to-crash>\n", stderr)
exit(1)
}
let crashPath = CommandLine.arguments[1]
// CHECK: host stdout is a tty: 0
print("host stdout is a tty: \(isatty(1))")
// (piped output on linux is fully buffered by default)
fflush(stdout)
// CHECK: host stderr is a tty: 0
print("host stderr is a tty: \(isatty(2))")
fflush(stdout)
var masterFD: Int32 = 0
var slaveFD: Int32 = 0
let slaveName = UnsafeMutablePointer<CChar>.allocate(capacity: 1024)
guard openpty(&masterFD, &slaveFD, slaveName, nil, nil) == 0 else {
perror("openpty")
exit(1)
}
// CHECK: slave is: /dev/{{.+}}
print("slave is: \(String(cString: slaveName))")
slaveName.deallocate()
fflush(stdout)
// try to avoid using Foundation, use Posix primitives instead
var childPid: pid_t = 0
#if os(macOS)
var childFileActions: posix_spawn_file_actions_t?
posix_spawn_file_actions_init(&childFileActions)
posix_spawn_file_actions_adddup2(&childFileActions, slaveFD, STDOUT_FILENO)
posix_spawn_file_actions_adddup2(&childFileActions, slaveFD, STDERR_FILENO)
guard posix_spawn(&childPid, Array<CChar>(crashPath.utf8CString), &childFileActions, nil, nil, environ) == 0 else {
perror("unable to spawn child")
exit(1)
}
posix_spawn_file_actions_destroy(&childFileActions)
#elseif os(Linux)
var childFileActions = posix_spawn_file_actions_t()
posix_spawn_file_actions_init(&childFileActions)
posix_spawn_file_actions_adddup2(&childFileActions, slaveFD, STDOUT_FILENO)
posix_spawn_file_actions_adddup2(&childFileActions, slaveFD, STDERR_FILENO)
var argv: [UnsafeMutablePointer<CChar>?] = [nil]
guard posix_spawn(&childPid, Array<CChar>(crashPath.utf8CString), &childFileActions, nil, argv, environ) == 0 else {
perror("unable to spawn child")
exit(1)
}
posix_spawn_file_actions_destroy(&childFileActions)
#endif
// CHECK: child pid is {{[0-9]+}}
print("child pid is \(childPid)")
fflush(stdout)
// the parent process should now close this FD as it is used by the child for stdout/stderr
close(slaveFD)
// CHECK: subprocess stdout is a tty: 1
// CHECK: subprocess stderr is a tty: 1
// CHECK: About to crash
// CHECK: 💣{{.*}}Program crashed: Bad pointer dereference at 0x{{0+}}4
// CHECK: Thread 0 {{(".*" )?}}crashed:
// CHECK: Backtrace took {{[0-9.]+}}s
// allow the child process time to start, and crash...
sleep(1)
// now read the standard output and error from our master pty
// and write it to our own standard output for scanning by FileCheck
let bufferSize = 4096
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
var bytesRead = read(masterFD, buffer, bufferSize)
while bytesRead > 0 {
write(1, buffer, bytesRead)
bytesRead = read(masterFD, buffer, bufferSize)
}
close(masterFD)
buffer.deallocate()
// CHECK-NOT: child exited with status: 0
var childExitStatus: Int32 = 0
waitpid(childPid, &childExitStatus, 0)
print("child exited with status: \(childExitStatus)")

View File

@@ -0,0 +1,46 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/TTYDetection
// RUN: %target-codesign %t/TTYDetection
// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no,interactive=no %target-run script -reF %t/script-output %t/TTYDetection || true) && cat %t/script-output | %FileCheck %s
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: asan
// REQUIRES: executable_test
// REQUIRES: backtracing
// REQUIRES: OS=macosx
// COM: this won't work on Windows, but is not critical function
func level1() {
level2()
}
func level2() {
level3()
}
func level3() {
level4()
}
func level4() {
level5()
}
func level5() {
print("About to crash")
let ptr = UnsafeMutablePointer<Int>(bitPattern: 4)!
ptr.pointee = 42
}
@main
struct TTYDetection {
static func main() {
level1()
}
}
// CHECK: 💣{{.*}}Program crashed: Bad pointer dereference at 0x{{0+}}4
// CHECK: Thread 0 {{(".*" )?}}crashed: