// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // RUN: %target-run-simple-swift // REQUIRES: executable_test // REQUIRES: objc_interop import Foundation import StdlibUnittest // Since we test various apis with and without errors define these conveniences func expectError(_ verb: () throws -> ()) { do { try verb() expectUnreachable("Expected to throw error!") } catch { // expected } } func expectNoError(_ verb: () throws -> ()) { do { try verb() } catch let error { expectUnreachable("Did not expect to throw error! \(error)") } } // Utilities for minimizing boiler plate in the tests @available(OSX 10.11, iOS 9.0, *) func archive(_ archival: (_: NSKeyedArchiver) throws -> Void) rethrows -> Data { let data = NSMutableData() let ka = NSKeyedArchiver(forWritingWith: data) do { defer { ka.finishEncoding() } try archival(ka) } expectNotEqual(data.length, 0) return data as Data } @available(OSX 10.11, iOS 9.0, *) func unarchive(_ data: Data, _ unarchival: (_: NSKeyedUnarchiver) throws -> Void) rethrows -> Void { let ku = NSKeyedUnarchiver(forReadingWith: data as Data) ku.requiresSecureCoding = true ku.decodingFailurePolicy = .setErrorAndReturn try unarchival(ku) ku.finishDecoding() } // MARK: - Data // simple struct comprised of entirely plist types struct Simple : Codable, Equatable { var number: Int = 5 var string: String = "hello" var data: Data = Data(bytes: [0, 1, 2, 3]) var array: [String] = ["stay", "a while", "and listen"] var dictionary: [String:String] = ["Deckard": "Cain"] static func ==(lhs: Simple, rhs: Simple) -> Bool { return lhs.number == rhs.number && lhs.string == rhs.string && lhs.data == rhs.data && lhs.array == rhs.array && lhs.dictionary == rhs.dictionary } } // simple struct comprised of entirely plist types struct ThrowingCodable : Codable { public func encode(to encoder: Encoder) throws { throw NSError(domain: "sad", code: 3) } } // MARK - Tests @available(OSX 10.11, iOS 9.0, *) func test_simpleCodableSupport() { let s = Simple() var data: Data? = nil expectNoError { data = try archive { archiver in try archiver.encodeEncodable(s, forKey: "valid") try archiver.encodeEncodable(s, forKey: "wrong-type") } } unarchive(data!) { unarchiver in // confirm we can roundtrip our data let roundtrip = unarchiver.decodeDecodable(Simple.self, forKey: "valid") expectNotNil(roundtrip) if let rt = roundtrip { expectEqual(rt, s) } // also ask for something that is not there let notThere = unarchiver.decodeDecodable(Simple.self, forKey: "not-there") expectNil(notThere) expectNil(unarchiver.error) // String != Simple so this should fail at the type level let wrongType = unarchiver.decodeDecodable(String.self, forKey: "wrong-type") expectNil(wrongType) expectNotNil(unarchiver.error) } } @available(OSX 10.11, iOS 9.0, *) func test_encodableErrorHandling() { expectError { _ = try archive { archiver in try archiver.encodeEncodable(ThrowingCodable(), forKey: "non-codable") } } } @available(OSX 10.11, iOS 9.0, *) func test_readingNonCodableFromDecodeDecodable() { var data: Data? = nil expectNoError { data = archive { archiver in archiver.encode(NSDate(), forKey: "non-codable") } } unarchive(data!) { unarchiver in let nonCodable = unarchiver.decodeDecodable(Simple.self, forKey: "non-codable") expectNil(nonCodable) expectNotNil(unarchiver.error) } } @available(OSX 10.11, iOS 9.0, *) func test_toplevelAPIVariants() { let s = Simple() var data: Data? = nil expectNoError { data = try archive { archiver in try archiver.encodeEncodable(s, forKey: "valid") try archiver.encodeEncodable(Date(), forKey: "non-codable") } } unarchive(data!) { unarchiver in var caught = false do { _ = try unarchiver.decodeTopLevelDecodable(Simple.self, forKey: "non-codable") } catch { caught = true } expectTrue(caught) } expectNoError { _ = try unarchive(data!) { unarchiver in let roundtrip = try unarchiver.decodeTopLevelDecodable(Simple.self, forKey: "valid") expectNotNil(roundtrip) if let rt = roundtrip { expectEqual(rt, s) } } } } @available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) func test_unarchiveObjectOfClass() { let topLevel = NSArray() let data = NSKeyedArchiver.archivedData(withRootObject: topLevel) // Should be able to decode an NSArray back out. expectNoError { guard let result = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: data) else { expectUnreachable("Unable to decode top-level array.") return } expectEqual(result, topLevel) } // Shouldn't be able to decode an NSString out of an NSArray. expectError { let _ = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSString.self, from: data) } } @available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) func test_unarchiveObjectOfClasses() { let topLevel = NSArray() let data = NSKeyedArchiver.archivedData(withRootObject: topLevel) // Should be able to unarchive an array back out. expectNoError { guard let result = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self], from: data) as? NSArray else { expectUnreachable("Unable to decode top-level array.") return } expectEqual(result, topLevel) } // Shouldn't be able to decode an NSString out of an NSArray. expectError { let _ = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSString.self], from: data) } } // MARK: - Run Tests #if !FOUNDATION_XCTEST if #available(OSX 10.11, iOS 9.0, *) { let NSKeyedArchiverTest = TestSuite("TestNSKeyedArchiver") var tests = [ "NSKeyedArchival.simpleCodableSupportInNSKeyedArchival": test_simpleCodableSupport, "NSKeyedArchival.encodableErrorHandling": test_encodableErrorHandling, "NSKeyedArchival.readingNonCodableFromDecodeDecodable": test_readingNonCodableFromDecodeDecodable, "NSKeyedArchival.toplevelAPIVariants": test_toplevelAPIVariants ] if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) { tests["NSKeyedArchival.unarchiveObjectOfClass"] = test_unarchiveObjectOfClass tests["NSKeyedArchival.unarchiveObjectOfClasses"] = test_unarchiveObjectOfClasses } for (name, test) in tests { NSKeyedArchiverTest.test(name) { test() } } runAllTests() } #endif