Skip to content

Commit e6a519f

Browse files
committed
[StdlibUnittest] Install our own handler for uncaught ObjC exceptions.
We don't want to be at the whims of the system on what to do with an uncaught exception; we need to make sure its message gets printed to stderr so that the parent process can check it. (There's a bit of trickery here to see if the class looks like an NSException; otherwise we lose the name of the exception and just get the reason.)
1 parent e2c43cf commit e6a519f

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

stdlib/private/StdlibUnittest/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ set(swift_stdlib_unittest_module_depends
33
SwiftPrivate SwiftPrivatePthreadExtras SwiftPrivateLibcExtras)
44
set(swift_stdlib_unittest_framework_depends)
55
set(swift_stdlib_unittest_private_link_libraries)
6-
set(swift_stdlib_unittest_compile_flags)
6+
set(swift_stdlib_unittest_compile_flags
7+
"-Xfrontend" "-disable-objc-attr-requires-foundation-module")
78

89
if(SWIFT_HOST_VARIANT MATCHES "${SWIFT_DARWIN_VARIANTS}")
910
list(APPEND swift_stdlib_unittest_platform_sources

stdlib/private/StdlibUnittest/StdlibUnittest.swift.gyb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,24 @@ let _crashedPrefix = "CRASHED:"
448448
@_silgen_name("swift_stdlib_installTrapInterceptor")
449449
func _stdlib_installTrapInterceptor()
450450

451+
@objc protocol _StdlibUnittestNSException {
452+
optional var name: String { get }
453+
}
454+
451455
func _childProcess() {
452456
_stdlib_installTrapInterceptor()
457+
objc_setUncaughtExceptionHandler {
458+
var stderr = _Stderr()
459+
let maybeNSException = unsafeBitCast($0, to:_StdlibUnittestNSException.self)
460+
if let name = maybeNSException.name {
461+
print("*** [StdlibUnittest] Terminating due to uncaught exception " +
462+
"\(name): \($0)",
463+
to: &stderr)
464+
} else {
465+
print("*** [StdlibUnittest] Terminating due to uncaught exception: \($0)",
466+
to: &stderr)
467+
}
468+
}
453469
while let line = _stdlib_getline() {
454470
let parts = line._split(separator: ";")
455471
let testSuiteName = parts[0]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// RUN: %target-run-simple-swift 2>&1 | FileCheck %s
2+
// REQUIRES: executable_test
3+
// REQUIRES: objc_interop
4+
5+
import StdlibUnittest
6+
import ObjectiveC
7+
import Foundation
8+
9+
// Don't actually exit with a non-zero status, just say we're going to do it.
10+
_setTestSuiteFailedCallback() { print("abort()") }
11+
12+
13+
func raiseNSException() {
14+
NSException(name: "Trogdor", reason: "Burnination", userInfo: nil).raise()
15+
}
16+
17+
var TestSuiteCrashes = TestSuite("NSExceptionCrashes")
18+
19+
TestSuiteCrashes.test("uncaught") {
20+
print("uncaught")
21+
raiseNSException()
22+
}
23+
// CHECK-LABEL: stdout>>> uncaught
24+
// CHECK: stderr>>> *** [StdlibUnittest] Terminating due to uncaught exception Trogdor: Burnination
25+
// CHECK: stderr>>> CRASHED: SIG
26+
// CHECK: the test crashed unexpectedly
27+
// CHECK: [ FAIL ] NSExceptionCrashes.uncaught
28+
29+
TestSuiteCrashes.test("crashesAsExpected") {
30+
print("crashesAsExpected")
31+
expectCrashLater()
32+
raiseNSException()
33+
}
34+
// CHECK-LABEL: stdout>>> crashesAsExpected
35+
// CHECK: stderr>>> *** [StdlibUnittest] Terminating due to uncaught exception Trogdor: Burnination
36+
// CHECK: stderr>>> OK: saw expected "crashed: sig
37+
// CHECK: [ OK ] NSExceptionCrashes.crashesAsExpected
38+
39+
TestSuiteCrashes.test("crashesWithMessage")
40+
.crashOutputMatches("libUnittest]")
41+
.crashOutputMatches("Trogdor")
42+
.crashOutputMatches("Burnination").code {
43+
print("crashesWithMessage")
44+
expectCrashLater()
45+
raiseNSException()
46+
}
47+
// CHECK-LABEL: stdout>>> crashesWithMessage
48+
// CHECK: stderr>>> *** [StdlibUnittest] Terminating due to uncaught exception Trogdor: Burnination
49+
// CHECK: stderr>>> OK: saw expected "crashed: sig
50+
// CHECK: [ OK ] NSExceptionCrashes.crashesWithMessage
51+
52+
TestSuiteCrashes.test("nonNSException")
53+
.crashOutputMatches("countryside").code {
54+
print("nonNSException")
55+
expectCrashLater()
56+
objc_exception_throw("countryside")
57+
}
58+
// CHECK-LABEL: stdout>>> nonNSException
59+
// CHECK: stderr>>> *** [StdlibUnittest] Terminating due to uncaught exception: countryside
60+
// CHECK: stderr>>> OK: saw expected "crashed: sig
61+
// CHECK: [ OK ] NSExceptionCrashes.nonNSException
62+
63+
// CHECK: NSExceptionCrashes: Some tests failed, aborting
64+
// CHECK: abort()
65+
66+
runAllTests()
67+

0 commit comments

Comments
 (0)