blob: 958ed7c896cbc01777c4c5801205593406166163 [file] [log] [blame]
// RUN: %empty-directory(%t)
// RUN: %target-build-swift -o %t/ErrorBridged -DPTR_SIZE_%target-ptrsize -module-name main %s
// RUN: %target-run %t/ErrorBridged
// REQUIRES: executable_test
// REQUIRES: objc_interop
import StdlibUnittest
import Foundation
import CoreLocation
import Darwin
var ErrorBridgingTests = TestSuite("ErrorBridging")
var NoisyErrorLifeCount = 0
var NoisyErrorDeathCount = 0
var CanaryHandle = 0
protocol OtherProtocol {
var otherProperty: String { get }
}
protocol OtherClassProtocol : class {
var otherClassProperty: String { get }
}
class NoisyError : Error, OtherProtocol, OtherClassProtocol {
init() { NoisyErrorLifeCount += 1 }
deinit { NoisyErrorDeathCount += 1 }
let _domain = "NoisyError"
let _code = 123
let otherProperty = "otherProperty"
let otherClassProperty = "otherClassProperty"
}
@objc enum EnumError : Int, Error {
case BadError = 9000
case ReallyBadError = 9001
}
ErrorBridgingTests.test("NSError") {
NoisyErrorLifeCount = 0
NoisyErrorDeathCount = 0
autoreleasepool {
let ns = NSError(domain: "SomeDomain", code: 321, userInfo: nil)
objc_setAssociatedObject(ns, &CanaryHandle, NoisyError(),
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
let e: Error = ns
expectEqual(e._domain, "SomeDomain")
expectEqual(e._code, 321)
let ns2 = e as NSError
expectTrue(ns === ns2)
expectEqual(ns2._domain, "SomeDomain")
expectEqual(ns2._code, 321)
}
expectEqual(NoisyErrorDeathCount, NoisyErrorLifeCount)
}
ErrorBridgingTests.test("NSCopying") {
autoreleasepool {
let orig = EnumError.ReallyBadError as NSError
let copy = orig.copy() as! NSError
expectEqual(orig, copy)
}
}
// Gated on the availability of NSKeyedArchiver.archivedData(withRootObject:).
@available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)
func archiveAndUnarchiveObject<T: NSCoding>(
_ object: T
) -> T?
where T: NSObject {
let unarchiver = NSKeyedUnarchiver(forReadingWith:
NSKeyedArchiver.archivedData(withRootObject: object)
)
unarchiver.requiresSecureCoding = true
return unarchiver.decodeObject(of: T.self, forKey: "root")
}
ErrorBridgingTests.test("NSCoding") {
if #available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) {
autoreleasepool {
let orig = EnumError.ReallyBadError as NSError
let unarchived = archiveAndUnarchiveObject(orig)!
expectEqual(orig, unarchived)
expectTrue(type(of: unarchived) == NSError.self)
}
}
}
ErrorBridgingTests.test("NSError-to-enum bridging") {
NoisyErrorLifeCount = 0
NoisyErrorDeathCount = 0
let testURL = URL(string: "https://swift.org")!
autoreleasepool {
let underlyingError = CocoaError(.fileLocking)
as Error as NSError
let ns = NSError(domain: NSCocoaErrorDomain,
code: NSFileNoSuchFileError,
userInfo: [
NSFilePathErrorKey: "/dev/null",
NSStringEncodingErrorKey: /*ASCII=*/1,
NSUnderlyingErrorKey: underlyingError,
NSURLErrorKey: testURL
])
objc_setAssociatedObject(ns, &CanaryHandle, NoisyError(),
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
let e: Error = ns
let cocoaCode: Int?
switch e {
case let x as CocoaError:
cocoaCode = x._code
expectTrue(x.isFileError)
expectEqual(x.code, .fileNoSuchFile)
default:
cocoaCode = nil
}
expectEqual(NSFileNoSuchFileError, cocoaCode)
let cocoaCode2: Int? = (ns as? CocoaError)?._code
expectEqual(NSFileNoSuchFileError, cocoaCode2)
let isNoSuchFileError: Bool
switch e {
case CocoaError.fileNoSuchFile:
isNoSuchFileError = true
default:
isNoSuchFileError = false
}
expectTrue(isNoSuchFileError)
// Check the contents of the error.
let cocoaError = e as! CocoaError
expectEqual("/dev/null", cocoaError.filePath)
expectEqual(String.Encoding.ascii, cocoaError.stringEncoding)
expectEqual(underlyingError, cocoaError.underlying.map { $0 as NSError })
expectEqual(testURL, cocoaError.url)
// URLError domain
let nsURL = NSError(domain: NSURLErrorDomain,
code: NSURLErrorBadURL,
userInfo: [NSURLErrorFailingURLErrorKey: testURL])
let eURL: Error = nsURL
let isBadURLError: Bool
switch eURL {
case URLError.badURL:
isBadURLError = true
default:
isBadURLError = false
}
expectTrue(isBadURLError)
let urlError = eURL as! URLError
expectEqual(testURL, urlError.failingURL)
expectNil(urlError.failureURLPeerTrust)
// CoreLocation error domain
let nsCL = NSError(domain: kCLErrorDomain,
code: CLError.headingFailure.rawValue,
userInfo: [NSURLErrorKey: testURL])
let eCL: Error = nsCL
let isHeadingFailure: Bool
switch eCL {
case CLError.headingFailure:
isHeadingFailure = true
default:
isHeadingFailure = false
}
let isCLError: Bool
switch eCL {
case let error as CLError:
isCLError = true
expectEqual(testURL, (error as NSError).userInfo[NSURLErrorKey] as? URL)
expectEqual(testURL, error.userInfo[NSURLErrorKey] as? URL)
default:
isCLError = false
}
expectTrue(isCLError)
// NSPOSIXError domain
let nsPOSIX = NSError(domain: NSPOSIXErrorDomain,
code: Int(EDEADLK),
userInfo: [:])
let ePOSIX: Error = nsPOSIX
let isDeadlock: Bool
switch ePOSIX {
case POSIXError.EDEADLK:
isDeadlock = true
default:
isDeadlock = false
}
expectTrue(isDeadlock)
// NSMachError domain
let nsMach = NSError(domain: NSMachErrorDomain,
code: Int(KERN_MEMORY_FAILURE),
userInfo: [:])
let eMach: Error = nsMach
let isMemoryFailure: Bool
switch eMach {
case MachError.memoryFailure:
isMemoryFailure = true
default:
isMemoryFailure = false
}
expectTrue(isMemoryFailure)
}
expectEqual(NoisyErrorDeathCount, NoisyErrorLifeCount)
}
func opaqueUpcastToAny<T>(_ x: T) -> Any {
return x
}
struct StructError: Error {
var _domain: String { return "StructError" }
var _code: Int { return 4812 }
}
ErrorBridgingTests.test("Error-to-NSError bridging") {
NoisyErrorLifeCount = 0
NoisyErrorDeathCount = 0
autoreleasepool {
let e: Error = NoisyError()
let ns = e as NSError
let ns2 = e as NSError
expectTrue(ns === ns2)
expectEqual(ns._domain, "NoisyError")
expectEqual(ns._code, 123)
let e3: Error = ns
expectEqual(e3._domain, "NoisyError")
expectEqual(e3._code, 123)
let ns3 = e3 as NSError
expectTrue(ns === ns3)
let nativeNS = NSError(domain: NSCocoaErrorDomain,
code: NSFileNoSuchFileError,
userInfo: nil)
objc_setAssociatedObject(ns, &CanaryHandle, NoisyError(),
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
let nativeE: Error = nativeNS
let nativeNS2 = nativeE as NSError
expectTrue(nativeNS === nativeNS2)
expectEqual(nativeNS2._domain, NSCocoaErrorDomain)
expectEqual(nativeNS2._code, NSFileNoSuchFileError)
let any: Any = NoisyError()
let ns4 = any as! NSError
expectEqual(ns4._domain, "NoisyError")
expectEqual(ns4._code, 123)
let ao: AnyObject = NoisyError()
let ns5 = ao as! NSError
expectEqual(ns5._domain, "NoisyError")
expectEqual(ns5._code, 123)
let any2: Any = StructError()
let ns6 = any2 as! NSError
expectEqual(ns6._domain, "StructError")
expectEqual(ns6._code, 4812)
}
expectEqual(NoisyErrorDeathCount, NoisyErrorLifeCount)
}
ErrorBridgingTests.test("NSError-to-error bridging in bridged container") {
autoreleasepool {
let error = NSError(domain: "domain", code: 42, userInfo: nil)
let nsdictionary = ["error": error] as NSDictionary
let dictionary = nsdictionary as? Dictionary<String, Error>
expectNotNil(dictionary)
expectEqual(error, dictionary?["error"] as NSError?)
}
}
ErrorBridgingTests.test("enum-to-NSError round trip") {
autoreleasepool {
// Emulate throwing an error from Objective-C.
func throwNSError(_ error: EnumError) throws {
throw NSError(domain: "main.EnumError", code: error.rawValue,
userInfo: [:])
}
var caughtError: Bool
caughtError = false
do {
try throwNSError(.BadError)
expectUnreachable()
} catch let error as EnumError {
expectEqual(.BadError, error)
caughtError = true
} catch _ {
expectUnreachable()
}
expectTrue(caughtError)
caughtError = false
do {
try throwNSError(.ReallyBadError)
expectUnreachable()
} catch EnumError.BadError {
expectUnreachable()
} catch EnumError.ReallyBadError {
caughtError = true
} catch _ {
expectUnreachable()
}
expectTrue(caughtError)
}
}
class SomeNSErrorSubclass: NSError {}
ErrorBridgingTests.test("Thrown NSError identity is preserved") {
do {
let e = NSError(domain: "ClericalError", code: 219,
userInfo: ["yeah": "yeah"])
do {
throw e
} catch let e2 as NSError {
expectTrue(e === e2)
expectTrue(e2.userInfo["yeah"] as? String == "yeah")
} catch {
expectUnreachable()
}
}
do {
let f = SomeNSErrorSubclass(domain: "ClericalError", code: 219,
userInfo: ["yeah": "yeah"])
do {
throw f
} catch let f2 as NSError {
expectTrue(f === f2)
expectTrue(f2.userInfo["yeah"] as? String == "yeah")
} catch {
expectUnreachable()
}
}
}
// Check errors customized via protocol.
struct MyCustomizedError : Error {
let code: Int
}
extension MyCustomizedError : LocalizedError {
var errorDescription: String? {
return NSLocalizedString("something went horribly wrong", comment: "")
}
var failureReason: String? {
return NSLocalizedString("because someone wrote 'throw'", comment: "")
}
var recoverySuggestion: String? {
return NSLocalizedString("delete the 'throw'", comment: "")
}
var helpAnchor: String? {
return NSLocalizedString("there is no help when writing tests", comment: "")
}
}
extension MyCustomizedError : CustomNSError {
static var errorDomain: String {
return "custom"
}
/// The error code within the given domain.
var errorCode: Int {
return code
}
/// The user-info dictionary.
var errorUserInfo: [String : Any] {
return [NSURLErrorKey : URL(string: "https://swift.org")!]
}
}
extension MyCustomizedError : RecoverableError {
var recoveryOptions: [String] {
return ["Delete 'throw'", "Disable the test" ]
}
func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool {
return recoveryOptionIndex == 0
}
}
/// An error type that provides localization and recovery, but doesn't
/// customize NSError directly.
enum MySwiftCustomizedError : Error {
case failed
static var errorDescriptionCount = 0
}
extension MySwiftCustomizedError : LocalizedError {
var errorDescription: String? {
MySwiftCustomizedError.errorDescriptionCount =
MySwiftCustomizedError.errorDescriptionCount + 1
return NSLocalizedString("something went horribly wrong", comment: "")
}
var failureReason: String? {
return NSLocalizedString("because someone wrote 'throw'", comment: "")
}
var recoverySuggestion: String? {
return NSLocalizedString("delete the 'throw'", comment: "")
}
var helpAnchor: String? {
return NSLocalizedString("there is no help when writing tests", comment: "")
}
}
extension MySwiftCustomizedError : RecoverableError {
var recoveryOptions: [String] {
return ["Delete 'throw'", "Disable the test" ]
}
func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool {
return recoveryOptionIndex == 0
}
}
/// Fake definition of the informal protocol
/// "NSErrorRecoveryAttempting" that we use to poke at the object
/// produced for a RecoverableError.
@objc protocol FakeNSErrorRecoveryAttempting {
@objc(attemptRecoveryFromError:optionIndex:delegate:didRecoverSelector:contextInfo:)
func attemptRecovery(fromError nsError: Error,
optionIndex recoveryOptionIndex: Int,
delegate: AnyObject?,
didRecoverSelector: Selector,
contextInfo: UnsafeMutableRawPointer?)
@objc(attemptRecoveryFromError:optionIndex:)
func attemptRecovery(fromError nsError: Error,
optionIndex recoveryOptionIndex: Int) -> Bool
}
class RecoveryDelegate {
let expectedSuccess: Bool
let expectedContextInfo: UnsafeMutableRawPointer?
var called = false
init(expectedSuccess: Bool,
expectedContextInfo: UnsafeMutableRawPointer?) {
self.expectedSuccess = expectedSuccess
self.expectedContextInfo = expectedContextInfo
}
@objc func recover(success: Bool, contextInfo: UnsafeMutableRawPointer?) {
expectEqual(expectedSuccess, success)
expectEqual(expectedContextInfo, contextInfo)
called = true
}
}
/// Helper for testing a customized error.
func testCustomizedError(error: Error, nsError: NSError) {
// LocalizedError
if #available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) {
expectNil(nsError.userInfo[NSLocalizedDescriptionKey])
expectNil(nsError.userInfo[NSLocalizedFailureReasonErrorKey])
expectNil(nsError.userInfo[NSLocalizedRecoverySuggestionErrorKey])
expectNil(nsError.userInfo[NSHelpAnchorErrorKey])
} else {
expectEqual("something went horribly wrong",
nsError.userInfo[NSLocalizedDescriptionKey] as? String)
expectEqual("because someone wrote 'throw'",
nsError.userInfo[NSLocalizedFailureReasonErrorKey] as? String)
expectEqual("delete the 'throw'",
nsError.userInfo[NSLocalizedRecoverySuggestionErrorKey] as? String)
expectEqual("there is no help when writing tests",
nsError.userInfo[NSHelpAnchorErrorKey] as? String)
}
expectEqual("something went horribly wrong", error.localizedDescription)
expectEqual("something went horribly wrong", nsError.localizedDescription)
expectEqual("because someone wrote 'throw'", nsError.localizedFailureReason)
expectEqual("delete the 'throw'", nsError.localizedRecoverySuggestion)
expectEqual("there is no help when writing tests", nsError.helpAnchor)
// RecoverableError
if #available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) {
expectNil(nsError.userInfo[NSLocalizedRecoveryOptionsErrorKey])
} else {
expectEqual(["Delete 'throw'", "Disable the test" ],
nsError.userInfo[NSLocalizedRecoveryOptionsErrorKey] as? [String])
}
expectEqual(["Delete 'throw'", "Disable the test" ],
nsError.localizedRecoveryOptions)
// Directly recover.
let ctx = UnsafeMutableRawPointer(bitPattern:0x1234567)
let attempter: AnyObject
if #available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) {
expectNil(nsError.userInfo[NSRecoveryAttempterErrorKey])
attempter = nsError.recoveryAttempter! as AnyObject
} else {
attempter = nsError.userInfo[NSRecoveryAttempterErrorKey]! as AnyObject
}
expectEqual(attempter.attemptRecovery(fromError: nsError, optionIndex: 0), true)
expectEqual(attempter.attemptRecovery(fromError: nsError, optionIndex: 1), false)
// Recover through delegate.
let rd1 = RecoveryDelegate(expectedSuccess: true, expectedContextInfo: ctx)
expectEqual(false, rd1.called)
attempter.attemptRecovery(
fromError: nsError,
optionIndex: 0,
delegate: rd1,
didRecoverSelector: #selector(RecoveryDelegate.recover(success:contextInfo:)),
contextInfo: ctx)
expectEqual(true, rd1.called)
let rd2 = RecoveryDelegate(expectedSuccess: false, expectedContextInfo: nil)
expectEqual(false, rd2.called)
attempter.attemptRecovery(
fromError: nsError,
optionIndex: 1,
delegate: rd2,
didRecoverSelector: #selector(RecoveryDelegate.recover(success:contextInfo:)),
contextInfo: nil)
expectEqual(true, rd2.called)
}
ErrorBridgingTests.test("Customizing NSError via protocols") {
let error = MyCustomizedError(code: 12345)
let nsError = error as NSError
// CustomNSError
expectEqual("custom", nsError.domain)
expectEqual(12345, nsError.code)
expectEqual(URL(string: "https://swift.org"),
nsError.userInfo[NSURLErrorKey] as? URL)
testCustomizedError(error: error, nsError: nsError)
}
ErrorBridgingTests.test("Customizing localization/recovery via protocols") {
let error = MySwiftCustomizedError.failed
let nsError = error as NSError
testCustomizedError(error: error, nsError: nsError)
}
ErrorBridgingTests.test("Customizing localization/recovery laziness") {
let countBefore = MySwiftCustomizedError.errorDescriptionCount
let error = MySwiftCustomizedError.failed
let nsError = error as NSError
// RecoverableError
if #available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) {
expectNil(nsError.userInfo[NSLocalizedRecoveryOptionsErrorKey])
} else {
expectEqual(["Delete 'throw'", "Disable the test" ],
nsError.userInfo[NSLocalizedRecoveryOptionsErrorKey] as? [String])
}
expectEqual(["Delete 'throw'", "Disable the test" ], nsError.localizedRecoveryOptions)
// None of the operations above should affect the count
if #available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) {
expectEqual(countBefore, MySwiftCustomizedError.errorDescriptionCount)
}
// This one does affect the count.
expectEqual("something went horribly wrong", error.localizedDescription)
// Check that we did get a call to errorDescription.
if #available(OSX 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *) {
expectEqual(countBefore+1, MySwiftCustomizedError.errorDescriptionCount)
}
}
enum DefaultCustomizedError1 : CustomNSError {
case bad
case worse
}
enum DefaultCustomizedError2 : Int, CustomNSError {
case bad = 7
case worse = 13
}
enum DefaultCustomizedError3 : UInt, CustomNSError {
case bad = 9
case worse = 115
#if PTR_SIZE_64
case dreadful = 0xFFFFFFFFFFFFFFFF
#elseif PTR_SIZE_32
case dreadful = 0xFFFFFFFF
#else
#error ("Unknown pointer size")
#endif
static var errorDomain: String {
return "customized3"
}
}
enum DefaultCustomizedParent {
enum ChildError: CustomNSError {
case foo
}
}
ErrorBridgingTests.test("Default-customized via CustomNSError") {
expectEqual(1, (DefaultCustomizedError1.worse as NSError).code)
expectEqual(13, (DefaultCustomizedError2.worse as NSError).code)
expectEqual(115, (DefaultCustomizedError3.worse as NSError).code)
expectEqual(-1, (DefaultCustomizedError3.dreadful as NSError).code)
expectEqual("main.DefaultCustomizedError1", (DefaultCustomizedError1.worse as NSError).domain)
expectEqual("customized3", (DefaultCustomizedError3.worse as NSError).domain)
expectEqual("main.DefaultCustomizedParent.ChildError", (DefaultCustomizedParent.ChildError.foo as NSError).domain)
}
class MyNSError : NSError { }
ErrorBridgingTests.test("NSError subclass identity") {
let myNSError: Error = MyNSError(domain: "MyNSError", code: 0, userInfo: [:])
let nsError = myNSError as NSError
expectTrue(type(of: nsError) == MyNSError.self)
}
ErrorBridgingTests.test("Wrapped NSError identity") {
let nsError = NSError(domain: NSCocoaErrorDomain,
code: NSFileNoSuchFileError,
userInfo: [
NSFilePathErrorKey : "/dev/null",
NSStringEncodingErrorKey : /*ASCII=*/1,
])
let error: Error = nsError
let nsError2: NSError = error as NSError
expectTrue(nsError === nsError2)
// Extracting the NSError via the runtime.
let cocoaErrorAny: Any = error as! CocoaError
let nsError3: NSError = cocoaErrorAny as! NSError
expectTrue(nsError === nsError3)
if let cocoaErrorAny2: Any = error as? CocoaError {
let nsError4: NSError = cocoaErrorAny2 as! NSError
expectTrue(nsError === nsError4)
} else {
expectUnreachable()
}
// Extracting the NSError via direct call.
let cocoaError = error as! CocoaError
let nsError5: NSError = cocoaError as NSError
expectTrue(nsError === nsError5)
if error is CocoaError {
let nsError6: NSError = cocoaError as NSError
expectTrue(nsError === nsError6)
} else {
expectUnreachable()
}
}
extension Error {
func asNSError() -> NSError {
return self as NSError
}
}
func unconditionalCast<T>(_ x: Any, to: T.Type) -> T {
return x as! T
}
func conditionalCast<T>(_ x: Any, to: T.Type) -> T? {
return x as? T
}
// SR-1562
ErrorBridgingTests.test("Error archetype identity") {
let myError = NSError(domain: "myErrorDomain", code: 0,
userInfo: [ "one" : 1 ])
expectTrue(myError === myError.asNSError())
expectTrue(unconditionalCast(myError, to: Error.self) as NSError
=== myError)
expectTrue(conditionalCast(myError, to: Error.self)! as NSError
=== myError)
let nsError = NSError(domain: NSCocoaErrorDomain,
code: NSFileNoSuchFileError,
userInfo: [
NSFilePathErrorKey : "/dev/null",
NSStringEncodingErrorKey : /*ASCII=*/1,
])
let cocoaError = nsError as Error as! CocoaError
expectTrue(cocoaError.asNSError() === nsError)
expectTrue(unconditionalCast(cocoaError, to: Error.self) as NSError
=== nsError)
expectTrue(conditionalCast(cocoaError, to: Error.self)! as NSError
=== nsError)
}
// SR-9389
class ParentA: NSObject {
@objc(ParentAError) enum Error: Int, Swift.Error {
case failed
}
}
class ParentB: NSObject {
@objc(ParentBError) enum Error: Int, Swift.Error {
case failed
}
}
private class NonPrintAsObjCClass: NSObject {
@objc enum Error: Int, Swift.Error {
case foo
}
}
@objc private enum NonPrintAsObjCError: Int, Error {
case bar
}
ErrorBridgingTests.test("@objc error domains for nested types") {
// Domain strings should correspond to Swift types, including parent types.
expectEqual(ParentA.Error.failed._domain, "main.ParentA.Error")
expectEqual(ParentB.Error.failed._domain, "main.ParentB.Error")
func makeNSError(like error: Error) -> NSError {
return NSError(domain: error._domain, code: error._code)
}
// NSErrors corresponding to Error types with the same name but nested in
// different enclosing types should not be castable to the wrong error type.
expectTrue(makeNSError(like: ParentA.Error.failed) is ParentA.Error)
expectFalse(makeNSError(like: ParentA.Error.failed) is ParentB.Error)
expectFalse(makeNSError(like: ParentB.Error.failed) is ParentA.Error)
expectTrue(makeNSError(like: ParentB.Error.failed) is ParentB.Error)
// If an @objc enum error is not eligible for PrintAsObjC, we should treat it
// as though it inherited the default implementation, which calls
// String(reflecting:).
expectEqual(NonPrintAsObjCClass.Error.foo._domain,
String(reflecting: NonPrintAsObjCClass.Error.self))
expectEqual(NonPrintAsObjCError.bar._domain,
String(reflecting: NonPrintAsObjCError.self))
}
runAllTests()