Skip to content

XCTUnwrap API #279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ add_swift_library(XCTest
Sources/XCTest/Private/ArgumentParser.swift
Sources/XCTest/Private/SourceLocation.swift
Sources/XCTest/Private/WaiterManager.swift
Sources/XCTest/Private/IgnoredErrors.swift
Sources/XCTest/Public/XCTestRun.swift
Sources/XCTest/Public/XCTestMain.swift
Sources/XCTest/Public/XCTestCase.swift
Expand Down
25 changes: 25 additions & 0 deletions Sources/XCTest/Private/IgnoredErrors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//
// IgnoredErrors.swift
//

/// The user info key used by errors so that they are ignored by the XCTest library.
internal let XCTestErrorUserInfoKeyShouldIgnore = "XCTestErrorUserInfoKeyShouldIgnore"

/// The error type thrown by `XCTUnwrap` on assertion failure.
internal struct XCTestErrorWhileUnwrappingOptional: Error, CustomNSError {
static var errorDomain: String = XCTestErrorDomain

var errorCode: Int = 105

var errorUserInfo: [String : Any] {
return [XCTestErrorUserInfoKeyShouldIgnore: true]
}
}
43 changes: 43 additions & 0 deletions Sources/XCTest/Public/XCTAssert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ private enum _XCTAssertion {
case notEqualWithAccuracy
case `nil`
case notNil
case unwrap
case `true`
case `false`
case fail
Expand All @@ -39,6 +40,7 @@ private enum _XCTAssertion {
case .notEqualWithAccuracy: return "XCTAssertNotEqual"
case .`nil`: return "XCTAssertNil"
case .notNil: return "XCTAssertNotNil"
case .unwrap: return "XCTUnwrap"
case .`true`: return "XCTAssertTrue"
case .`false`: return "XCTAssertFalse"
case .throwsError: return "XCTAssertThrowsError"
Expand Down Expand Up @@ -289,6 +291,47 @@ public func XCTAssertNotNil(_ expression: @autoclosure () throws -> Any?, _ mess
}
}

/// Asserts that an expression is not `nil`, and returns its unwrapped value.
///
/// Generates a failure if `expression` returns `nil`.
///
/// - Parameters:
/// - expression: An expression of type `T?` to compare against `nil`. Its type will determine the type of the
/// returned value.
/// - message: An optional description of the failure.
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was
/// called.
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
/// - Returns: A value of type `T`, the result of evaluating and unwrapping the given `expression`.
/// - Throws: An error if `expression` returns `nil`. If `expression` throws an error, then that error will be rethrown instead.
public func XCTUnwrap<T>(_ expression: @autoclosure () throws -> T?, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) throws -> T {
var value: T?
var caughtErrorOptional: Swift.Error?

_XCTEvaluateAssertion(.unwrap, message: message(), file: file, line: line) {
do {
value = try expression()
} catch {
caughtErrorOptional = error
return .unexpectedFailure(error)
}

if value != nil {
return .success
} else {
return .expectedFailure("expected non-nil value of type \"\(T.self)\"")
}
}

if let unwrappedValue = value {
return unwrappedValue
} else if let error = caughtErrorOptional {
throw error
} else {
throw XCTestErrorWhileUnwrappingOptional()
}
}

public func XCTAssertTrue(_ expression: @autoclosure () throws -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
_XCTEvaluateAssertion(.`true`, message: message(), file: file, line: line) {
let value = try expression()
Expand Down
18 changes: 13 additions & 5 deletions Sources/XCTest/Public/XCTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,19 @@ open class XCTestCase: XCTest {
do {
try testClosure(self)
} catch {
recordFailure(
withDescription: "threw error \"\(error)\"",
inFile: "<EXPR>",
atLine: 0,
expected: false)
var shouldIgnore = false
if let userInfo = (error as? CustomNSError)?.errorUserInfo,
let shouldIgnoreValue = userInfo[XCTestErrorUserInfoKeyShouldIgnore] as? NSNumber {
shouldIgnore = shouldIgnoreValue.boolValue
}

if !shouldIgnore {
recordFailure(
withDescription: "threw error \"\(error)\"",
inFile: "<EXPR>",
atLine: 0,
expected: false)
}
}
tearDown()
}
Expand Down
160 changes: 157 additions & 3 deletions Tests/Functional/ErrorHandling/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ class ErrorHandling: XCTestCase {
// Tests for XCTAssertNoThrow
("test_shouldNotThrowErrorDefiningSuccess", test_shouldNotThrowErrorDefiningSuccess),
("test_shouldThrowErrorDefiningFailure", test_shouldThrowErrorDefiningFailure),

// Tests for XCTUnwrap
("test_shouldNotThrowErrorOnUnwrapSuccess", test_shouldNotThrowErrorOnUnwrapSuccess),
("test_shouldThrowErrorOnUnwrapFailure", test_shouldThrowErrorOnUnwrapFailure),
("test_shouldThrowErrorOnEvaluationFailure", test_shouldThrowErrorOnEvaluationFailure),
("test_implicitlyUnwrappedOptional_notNil", test_implicitlyUnwrappedOptional_notNil),
("test_implicitlyUnwrappedOptional_nil", test_implicitlyUnwrappedOptional_nil),
("test_unwrapAnyOptional_notNil", test_unwrapAnyOptional_notNil),
("test_unwrapAnyOptional_nil", test_unwrapAnyOptional_nil),
("test_shouldReportFailureOnUnwrapFailure_catchUnwrapFailure", test_shouldReportFailureOnUnwrapFailure_catchUnwrapFailure),
("test_shouldReportFailureOnUnwrapFailure_catchExpressionFailure", test_shouldReportFailureOnUnwrapFailure_catchExpressionFailure),
("test_shouldReportCorrectTypeOnUnwrapFailure", test_shouldReportCorrectTypeOnUnwrapFailure),
("test_shouldReportCustomFileLineLocation", test_shouldReportCustomFileLineLocation),
("test_shouldReportFailureNotOnMainThread", test_shouldReportFailureNotOnMainThread),
]
}()

Expand All @@ -38,6 +52,7 @@ class ErrorHandling: XCTestCase {

enum SomeError: Swift.Error {
case anError(String)
case shouldNotBeReached
}

func functionThatDoesThrowError() throws {
Expand All @@ -63,6 +78,8 @@ class ErrorHandling: XCTestCase {
switch thrownError {
case .anError(let message):
XCTAssertEqual(message, "an error message")
default:
XCTFail("Unexpected error: \(thrownError)")
}
}
}
Expand All @@ -80,6 +97,8 @@ class ErrorHandling: XCTestCase {
switch thrownError {
case .anError(let message):
XCTAssertEqual(message, "")
default:
XCTFail("Unexpected error: \(thrownError)")
}
}
}
Expand Down Expand Up @@ -121,14 +140,149 @@ class ErrorHandling: XCTestCase {
func test_shouldThrowErrorDefiningFailure() {
XCTAssertNoThrow(try functionThatDoesThrowError())
}

func functionShouldReturnOptionalButThrows() throws -> String? {
throw SomeError.anError("an error message")
}

// CHECK: Test Case 'ErrorHandling.test_shouldNotThrowErrorOnUnwrapSuccess' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: Test Case 'ErrorHandling.test_shouldNotThrowErrorOnUnwrapSuccess' passed \(\d+\.\d+ seconds\)
func test_shouldNotThrowErrorOnUnwrapSuccess() throws {
let optional: String? = "is not nil"

let unwrapped = try XCTUnwrap(optional)
XCTAssertEqual(unwrapped, optional)
}

// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorOnUnwrapFailure' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+4]]: error: ErrorHandling.test_shouldThrowErrorOnUnwrapFailure : XCTUnwrap failed: expected non-nil value of type "String" -
// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorOnUnwrapFailure' failed \(\d+\.\d+ seconds\)
func test_shouldThrowErrorOnUnwrapFailure() throws {
let optional: String? = nil
_ = try XCTUnwrap(optional)

// Should not be reached:
throw SomeError.shouldNotBeReached
}

// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorOnEvaluationFailure' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+4]]: error: ErrorHandling.test_shouldThrowErrorOnEvaluationFailure : XCTUnwrap threw error "anError\("an error message"\)" - Failure error message
// CHECK: \<EXPR\>:0: error: ErrorHandling.test_shouldThrowErrorOnEvaluationFailure : threw error "anError\("an error message"\)"
// CHECK: Test Case 'ErrorHandling.test_shouldThrowErrorOnEvaluationFailure' failed \(\d+\.\d+ seconds\)
func test_shouldThrowErrorOnEvaluationFailure() throws {
_ = try XCTUnwrap(functionShouldReturnOptionalButThrows(), "Failure error message")

// Should not be reached:
throw SomeError.shouldNotBeReached
}

// CHECK: Test Case 'ErrorHandling.test_implicitlyUnwrappedOptional_notNil' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: Test Case 'ErrorHandling.test_implicitlyUnwrappedOptional_notNil' passed \(\d+\.\d+ seconds\)
func test_implicitlyUnwrappedOptional_notNil() throws {
let implicitlyUnwrappedOptional: String! = "is not nil"

let unwrapped = try XCTUnwrap(implicitlyUnwrappedOptional)
XCTAssertEqual(unwrapped, implicitlyUnwrappedOptional)
}

// CHECK: Test Case 'ErrorHandling.test_implicitlyUnwrappedOptional_nil' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+4]]: error: ErrorHandling.test_implicitlyUnwrappedOptional_nil : XCTUnwrap failed: expected non-nil value of type "String" - Failure error message
// CHECK: Test Case 'ErrorHandling.test_implicitlyUnwrappedOptional_nil' failed \(\d+\.\d+ seconds\)
func test_implicitlyUnwrappedOptional_nil() throws {
let implicitlyUnwrappedOptional: String! = nil
_ = try XCTUnwrap(implicitlyUnwrappedOptional, "Failure error message")

// Should not be reached:
throw SomeError.shouldNotBeReached
}

// CHECK: Test Case 'ErrorHandling.test_unwrapAnyOptional_notNil' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: Test Case 'ErrorHandling.test_unwrapAnyOptional_notNil' passed \(\d+\.\d+ seconds\)
func test_unwrapAnyOptional_notNil() throws {
let anyOptional: Any? = "is not nil"

let unwrapped = try XCTUnwrap(anyOptional)
XCTAssertEqual(unwrapped as! String, anyOptional as! String)
}

// CHECK: Test Case 'ErrorHandling.test_unwrapAnyOptional_nil' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+4]]: error: ErrorHandling.test_unwrapAnyOptional_nil : XCTUnwrap failed: expected non-nil value of type "Any" - Failure error message
// CHECK: Test Case 'ErrorHandling.test_unwrapAnyOptional_nil' failed \(\d+\.\d+ seconds\)
func test_unwrapAnyOptional_nil() throws {
let anyOptional: Any? = nil
_ = try XCTUnwrap(anyOptional, "Failure error message")

// Should not be reached:
throw SomeError.shouldNotBeReached
}

// CHECK: Test Case 'ErrorHandling.test_shouldReportFailureOnUnwrapFailure_catchUnwrapFailure' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+5]]: error: ErrorHandling.test_shouldReportFailureOnUnwrapFailure_catchUnwrapFailure : XCTUnwrap failed: expected non-nil value of type "String" -
// CHECK: Test Case 'ErrorHandling.test_shouldReportFailureOnUnwrapFailure_catchUnwrapFailure' failed \(\d+\.\d+ seconds\)
func test_shouldReportFailureOnUnwrapFailure_catchUnwrapFailure() {
do {
let optional: String? = nil
_ = try XCTUnwrap(optional)
} catch {}
}

// CHECK: Test Case 'ErrorHandling.test_shouldReportFailureOnUnwrapFailure_catchExpressionFailure' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+4]]: error: ErrorHandling.test_shouldReportFailureOnUnwrapFailure_catchExpressionFailure : XCTUnwrap threw error "anError\("an error message"\)" -
// CHECK: Test Case 'ErrorHandling.test_shouldReportFailureOnUnwrapFailure_catchExpressionFailure' failed \(\d+\.\d+ seconds\)
func test_shouldReportFailureOnUnwrapFailure_catchExpressionFailure() {
do {
_ = try XCTUnwrap(functionShouldReturnOptionalButThrows())
} catch {}
}

struct CustomType {
var name: String
}

// CHECK: Test Case 'ErrorHandling.test_shouldReportCorrectTypeOnUnwrapFailure' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+4]]: error: ErrorHandling.test_shouldReportCorrectTypeOnUnwrapFailure : XCTUnwrap failed: expected non-nil value of type "CustomType" -
// CHECK: Test Case 'ErrorHandling.test_shouldReportCorrectTypeOnUnwrapFailure' failed \(\d+\.\d+ seconds\)
func test_shouldReportCorrectTypeOnUnwrapFailure() throws {
let customTypeOptional: CustomType? = nil
_ = try XCTUnwrap(customTypeOptional)

// Should not be reached:
throw SomeError.shouldNotBeReached
}

// CHECK: Test Case 'ErrorHandling.test_shouldReportCustomFileLineLocation' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: custom_file.swift:1234: error: ErrorHandling.test_shouldReportCustomFileLineLocation : XCTUnwrap failed: expected non-nil value of type "CustomType" -
// CHECK: Test Case 'ErrorHandling.test_shouldReportCustomFileLineLocation' failed \(\d+\.\d+ seconds\)
func test_shouldReportCustomFileLineLocation() throws {
let customTypeOptional: CustomType? = nil
_ = try XCTUnwrap(customTypeOptional, file: "custom_file.swift", line: 1234)

// Should not be reached:
throw SomeError.shouldNotBeReached
}

// CHECK: Test Case 'ErrorHandling.test_shouldReportFailureNotOnMainThread' started at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: .*[/\\]ErrorHandling[/\\]main.swift:[[@LINE+7]]: error: ErrorHandling.test_shouldReportFailureNotOnMainThread : XCTUnwrap failed: expected non-nil value of type "CustomType" -
// CHECK: Test Case 'ErrorHandling.test_shouldReportFailureNotOnMainThread' failed \(\d+\.\d+ seconds\)
func test_shouldReportFailureNotOnMainThread() throws {
let queue = DispatchQueue(label: "Test")
let semaphore = DispatchSemaphore(value: 0)
queue.async {
let customTypeOptional: CustomType? = nil
_ = try? XCTUnwrap(customTypeOptional)
semaphore.signal()
}

semaphore.wait()
}
}

// CHECK: Test Suite 'ErrorHandling' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: \t Executed \d+ tests, with \d+ failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: \t Executed \d+ tests, with \d+ failures \(5 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds

XCTMain([testCase(ErrorHandling.allTests)])

// CHECK: Test Suite '.*\.xctest' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: \t Executed \d+ tests, with \d+ failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: \t Executed \d+ tests, with \d+ failures \(5 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: Test Suite 'All tests' failed at \d+-\d+-\d+ \d+:\d+:\d+\.\d+
// CHECK: \t Executed \d+ tests, with \d+ failures \(2 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
// CHECK: \t Executed \d+ tests, with \d+ failures \(5 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
5 changes: 5 additions & 0 deletions XCTest.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
AE63767E1D01ED17002C0EA8 /* TestListing.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE63767D1D01ED17002C0EA8 /* TestListing.swift */; };
DA9D441B1D920A3500108768 /* XCTestCase+Asynchronous.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9D44161D920A3500108768 /* XCTestCase+Asynchronous.swift */; };
DA9D441C1D920A3500108768 /* XCTestExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9D44171D920A3500108768 /* XCTestExpectation.swift */; };
E1495C80224276A600CDEB7D /* IgnoredErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1495C7F224276A600CDEB7D /* IgnoredErrors.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -84,6 +85,7 @@
DA7805F91C6704A2003C6636 /* SwiftFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftFoundation.framework; path = "../swift-corelibs-foundation/build/Debug/SwiftFoundation.framework"; sourceTree = "<group>"; };
DA9D44161D920A3500108768 /* XCTestCase+Asynchronous.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Asynchronous.swift"; sourceTree = "<group>"; };
DA9D44171D920A3500108768 /* XCTestExpectation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestExpectation.swift; sourceTree = "<group>"; };
E1495C7F224276A600CDEB7D /* IgnoredErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IgnoredErrors.swift; sourceTree = "<group>"; };
EA3E74BB1BF2B6D500635A73 /* build_script.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = build_script.py; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -123,6 +125,7 @@
AE2FE0E81CFE86A5003EF0D7 /* Private */ = {
isa = PBXGroup;
children = (
E1495C7F224276A600CDEB7D /* IgnoredErrors.swift */,
AE2FE10C1CFE86E6003EF0D7 /* ArgumentParser.swift */,
AE2FE10D1CFE86E6003EF0D7 /* ObjectWrapper.swift */,
AE2FE10E1CFE86E6003EF0D7 /* PerformanceMeter.swift */,
Expand Down Expand Up @@ -296,6 +299,7 @@
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
Expand Down Expand Up @@ -326,6 +330,7 @@
buildActionMask = 2147483647;
files = (
AE2FE1071CFE86DB003EF0D7 /* XCTestObservationCenter.swift in Sources */,
E1495C80224276A600CDEB7D /* IgnoredErrors.swift in Sources */,
DA9D441C1D920A3500108768 /* XCTestExpectation.swift in Sources */,
AE2FE1011CFE86DB003EF0D7 /* XCTestCase+Performance.swift in Sources */,
DA9D441B1D920A3500108768 /* XCTestCase+Asynchronous.swift in Sources */,
Expand Down