├── .github └── workflows │ ├── docs.yml │ ├── nightlies.yml │ └── tests.yml ├── .gitignore ├── .jazzy.yml ├── .ruby-version ├── ErrorAssertionExpectations.podspec ├── ErrorAssertions.podspec ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── ErrorAssertionExpectations │ ├── AssertExpectations.swift │ ├── FatalErrorExpectations.swift │ ├── PreconditionExpectations.swift │ └── Utilities │ │ ├── ClosureThread.swift │ │ └── Unreachable.swift └── ErrorAssertions │ ├── AssertWrapper.swift │ ├── FatalErrorWrapper.swift │ ├── PreconditionWrapper.swift │ └── Utilities │ ├── AnonymousError.swift │ └── RestorationHandler.swift ├── Tests ├── ErrorAssertionsTests │ ├── AssertTests.swift │ ├── FatalErrorTests.swift │ ├── PreconditionTests.swift │ ├── TestError.swift │ └── XCTestManifests.swift └── LinuxMain.swift └── muter.conf.json /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate Documentation 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | docs: 9 | runs-on: macOS-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Generate Xcode project 15 | run: swift package generate-xcodeproj 16 | 17 | - name: Install Homebrew Dependencies 18 | run: brew install sourcekitten jq 19 | 20 | - name: Parse Project 21 | run: | 22 | sourcekitten doc --module-name ErrorAssertions -- -project ErrorAssertions.xcodeproj > library_docs.json 23 | sourcekitten doc --module-name ErrorAssertionExpectations -- -project ErrorAssertions.xcodeproj > testhelpers_docs.json 24 | jq -s add library_docs.json testhelpers_docs.json > docs.json 25 | 26 | - name: Publish Jazzy Docs 27 | uses: Steven0351/publish-jazzy-docs@v1.1.1 28 | with: 29 | config: .jazzy.yml 30 | personal_access_token: ${{ secrets.ACCESS_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/nightlies.yml: -------------------------------------------------------------------------------- 1 | name: nightlies 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * * ' 6 | 7 | jobs: 8 | 9 | linux: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | image: [ 14 | "swift:nightly-5.2", 15 | "swift:nightly" 16 | ] 17 | name: Linux 18 | runs-on: ubuntu-latest 19 | container: 20 | image: swiftlang/${{ matrix.image }} 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v2 24 | - name: Swift Version 25 | run: swift --version 26 | - name: Debug Build 27 | run: swift build -v -c debug 28 | - name: Debug Test 29 | run: swift test -v -c debug 30 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | 8 | jobs: 9 | 10 | linux: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | image: 15 | - "swift:5.1" 16 | name: Linux 17 | runs-on: ubuntu-latest 18 | container: 19 | image: ${{ matrix.image }} 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | - name: Debug Build 24 | run: swift build -c debug 25 | - name: Debug Test 26 | run: swift test -c debug 27 | 28 | macos: 29 | strategy: 30 | matrix: 31 | swift-version: 32 | - "4" 33 | - "4.2" 34 | - "5" 35 | name: macOS (Swift ${{ matrix.swift-version }}) 36 | runs-on: macos-latest 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v2 40 | - name: Debug Build 41 | run: swift build -c debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-version }} 42 | - name: Debug Test 43 | run: swift test -c debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-version }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | /.build 4 | /Packages 5 | /*.xcodeproj 6 | *.xcuserdatad 7 | muter_logs/ 8 | docs/ 9 | fastlane/*_docs.json 10 | fastlane/report.xml 11 | .swiftpm 12 | -------------------------------------------------------------------------------- /.jazzy.yml: -------------------------------------------------------------------------------- 1 | sourcekitten_sourcefile: "docs.json" 2 | github_url: "https://github.com/SlaunchaMan/ErrorAssertions" 3 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 2 | -------------------------------------------------------------------------------- /ErrorAssertionExpectations.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "ErrorAssertionExpectations" 3 | spec.version = "0.4.0" 4 | spec.summary = "Testble Swift assertion functions" 5 | 6 | spec.description = <<-DESC 7 | ErrorAssertionsExpectations lets you write unit tests that cover cases you can’t 8 | cover with XCTest alone, such as using the Assert, Precondition, and FatalError 9 | APIs in Swift. By wrapping these calls, we can create test expectations and 10 | accurately test our error states. 11 | DESC 12 | 13 | spec.homepage = "https://github.com/SlaunchaMan/ErrorAssertions" 14 | spec.license = "MIT" 15 | spec.author = { "Jeff Kelley" => "SlaunchaMan@gmail.com" } 16 | spec.social_media_url = "https://twitter.com/SlaunchaMan" 17 | spec.ios.deployment_target = "8.0" 18 | spec.macos.deployment_target = "10.10" 19 | spec.tvos.deployment_target = "9.0" 20 | spec.swift_versions = ['4', '4.2', '5'] 21 | 22 | spec.source = { :git => "https://github.com/SlaunchaMan/ErrorAssertions.git", 23 | :tag => "#{spec.version}" } 24 | 25 | spec.dependency 'ErrorAssertions' 26 | spec.source_files = 'Sources/ErrorAssertionExpectations/**/*.swift' 27 | spec.frameworks = 'Foundation', 'XCTest' 28 | 29 | end 30 | 31 | -------------------------------------------------------------------------------- /ErrorAssertions.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "ErrorAssertions" 3 | spec.version = "0.4.0" 4 | spec.summary = "Versions of Swift assertion functions using Error types" 5 | 6 | spec.description = <<-DESC 7 | This package provides versions of Swift assertion methods—fatalError(), 8 | assert(), assertionFailure(), precondition(), and preconditionFailure()—that use 9 | Swift’s Error type instead of a simple String. 10 | DESC 11 | 12 | spec.homepage = "https://github.com/SlaunchaMan/ErrorAssertions" 13 | spec.license = "MIT" 14 | spec.author = { "Jeff Kelley" => "SlaunchaMan@gmail.com" } 15 | spec.social_media_url = "https://twitter.com/SlaunchaMan" 16 | spec.ios.deployment_target = "8.0" 17 | spec.macos.deployment_target = "10.10" 18 | spec.watchos.deployment_target = "2.0" 19 | spec.tvos.deployment_target = "9.0" 20 | spec.swift_versions = ['4', '4.2', '5'] 21 | 22 | spec.source = { :git => "https://github.com/SlaunchaMan/ErrorAssertions.git", 23 | :tag => "#{spec.version}" } 24 | 25 | spec.source_files = 'Sources/ErrorAssertions/**/*.swift' 26 | spec.frameworks = 'Foundation' 27 | 28 | spec.test_spec 'ErrorAssertionsTests' do |ts| 29 | ts.ios.deployment_target = "8.0" 30 | ts.macos.deployment_target = "10.10" 31 | ts.tvos.deployment_target = "9.0" 32 | 33 | ts.dependency 'ErrorAssertionExpectations' 34 | ts.source_files = 'Tests/ErrorAssertionsTests/**/*.swift' 35 | ts.frameworks = 'Foundation', 'XCTest' 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "cocoapods" 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (4.2.11.3) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | algoliasearch (1.27.3) 11 | httpclient (~> 2.8, >= 2.8.3) 12 | json (>= 1.5.1) 13 | atomos (0.1.3) 14 | claide (1.0.3) 15 | cocoapods (1.9.3) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.9.3) 19 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 20 | cocoapods-downloader (>= 1.2.2, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.4.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (>= 2.3.0, < 3.0) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.6) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.4) 33 | xcodeproj (>= 1.14.0, < 2.0) 34 | cocoapods-core (1.9.3) 35 | activesupport (>= 4.0.2, < 6) 36 | algoliasearch (~> 1.0) 37 | concurrent-ruby (~> 1.1) 38 | fuzzy_match (~> 2.0.4) 39 | nap (~> 1.0) 40 | netrc (~> 0.11) 41 | typhoeus (~> 1.0) 42 | cocoapods-deintegrate (1.0.4) 43 | cocoapods-downloader (1.6.3) 44 | cocoapods-plugins (1.0.0) 45 | nap 46 | cocoapods-search (1.0.0) 47 | cocoapods-stats (1.1.0) 48 | cocoapods-trunk (1.5.0) 49 | nap (>= 0.8, < 2.0) 50 | netrc (~> 0.11) 51 | cocoapods-try (1.2.0) 52 | colored2 (3.1.2) 53 | concurrent-ruby (1.1.6) 54 | escape (0.0.4) 55 | ethon (0.12.0) 56 | ffi (>= 1.3.0) 57 | ffi (1.13.0) 58 | fourflusher (2.3.1) 59 | fuzzy_match (2.0.4) 60 | gh_inspector (1.1.3) 61 | httpclient (2.8.3) 62 | i18n (0.9.5) 63 | concurrent-ruby (~> 1.0) 64 | json (2.3.0) 65 | minitest (5.14.1) 66 | molinillo (0.6.6) 67 | nanaimo (0.2.6) 68 | nap (1.1.0) 69 | netrc (0.11.0) 70 | ruby-macho (1.4.0) 71 | thread_safe (0.3.6) 72 | typhoeus (1.4.0) 73 | ethon (>= 0.9.0) 74 | tzinfo (1.2.7) 75 | thread_safe (~> 0.1) 76 | xcodeproj (1.16.0) 77 | CFPropertyList (>= 2.3.3, < 4.0) 78 | atomos (~> 0.1.3) 79 | claide (>= 1.0.2, < 2.0) 80 | colored2 (~> 3.1) 81 | nanaimo (~> 0.2.6) 82 | 83 | PLATFORMS 84 | ruby 85 | 86 | DEPENDENCIES 87 | cocoapods 88 | 89 | BUNDLED WITH 90 | 2.1.4 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Jeff Kelley 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "ErrorAssertions", 6 | products: [ 7 | .library(name: "ErrorAssertions", targets: ["ErrorAssertions"]), 8 | .library(name: "ErrorAssertionExpectations", 9 | targets: ["ErrorAssertionExpectations"]), 10 | ], 11 | targets: [ 12 | .target(name: "ErrorAssertions", dependencies: []), 13 | .target(name: "ErrorAssertionExpectations", 14 | dependencies: ["ErrorAssertions"]), 15 | .testTarget(name: "ErrorAssertionsTests", 16 | dependencies: [ 17 | "ErrorAssertions", 18 | "ErrorAssertionExpectations", 19 | ]), 20 | ], 21 | swiftLanguageVersions: [ 22 | .version("4"), 23 | .version("4.2"), 24 | .version("5") 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ErrorAssertions 2 | 3 | [![tests](https://github.com/SlaunchaMan/ErrorAssertions/workflows/tests/badge.svg)](https://github.com/SlaunchaMan/ErrorAssertions/actions?query=workflow%3Atests) 4 | [![Documentation](https://SlaunchaMan.github.io/ErrorAssertions/badge.svg)](https://SlaunchaMan.github.io/ErrorAssertions) 5 | [![Version](https://img.shields.io/cocoapods/v/ErrorAssertions.svg?style=flat)](https://cocoapods.org/pods/ErrorAssertions) 6 | [![Swift Package Manager](https://img.shields.io/badge/SPM-compatible-orange.svg)](https://swift.org/package-manager) 7 | 8 | Wrappers for Swift assertions that take `Error` instances instead of `String`s, 9 | and a suite of test functions to test these assertions. 10 | 11 | ## Getting Started 12 | 13 | To use ErrorAssertions, simply import it at the top of your Swift source file: 14 | 15 | ```swift 16 | import ErrorAssertions 17 | ``` 18 | 19 | Just by doing this, since the Swift compiler will prefer imported modules to the 20 | main Swift module, you’ll get the ErrorAssertion versions of functions like 21 | `fatalError(_:file:line:)`. 22 | 23 | ### Using `Error` Types 24 | 25 | To use an `Error` instead of a `String` when calling an assertion method, use 26 | the error version: 27 | 28 | ```swift 29 | import ErrorAssertions 30 | 31 | doSomething(completionHandler: { error in 32 | if let error = error { 33 | fatalError(error) 34 | } 35 | }) 36 | ``` 37 | 38 | You can use `Error` types with `fatalError()`, `assert()`, `assertionFailure()`, 39 | `precondition()`, and `preconditionFailure()`. 40 | 41 | ## Testing Assertions 42 | 43 | In your tests, import the `ErrorAssertionExpectations` module to test assertions 44 | made in your app (as long as you’ve imported `ErrorAssertions`). In your test 45 | cases, use the expectation methods: 46 | 47 | ```swift 48 | func testThatAnErrorHappens() { 49 | expectFatalError { 50 | doAThingThatProducesAFatalError() 51 | } 52 | } 53 | ``` 54 | 55 | There are also versions that take an `Error` or `String` and validate that the 56 | produced error is the one you’re expecting: 57 | 58 | ```swift 59 | func testThatASpecificErrorHappens() { 60 | expectFatalError(expectedError: URLError.badURL) { 61 | loadURL("thisisnotaurl") 62 | } 63 | } 64 | ``` 65 | 66 | ## Installation 67 | 68 | ### Swift Package Manager 69 | 70 | Swift Package Manager is the preferred way to install ErrorAssertions. Add the 71 | repository as a dependency: 72 | 73 | ```swift 74 | dependencies: [ 75 | .package(url: "https://github.com/SlaunchaMan/ErrorAssertions.git", 76 | from: "0.2.0") 77 | ] 78 | ``` 79 | 80 | In your targets, add `ErrorAssertions` as a dependency of your main target and, 81 | if you’re using the test support, add `ErrorAssertionExpectations` to the test 82 | target: 83 | 84 | ```swift 85 | targets: [ 86 | .target(name: "App", dependencies: ["ErrorAssertions")] 87 | .testTarget(name: "AppTests", dependencies: ["ErrorAssertionExpectations"]) 88 | ] 89 | ``` 90 | 91 | ### CocoaPods 92 | 93 | To use ErrorAssertions with CocoaPods, use the main pod as a dependency in your 94 | app and the ErrorAssertionExpectations pod in your tests: 95 | 96 | ```ruby 97 | target 'App' do 98 | pod 'ErrorAssertions' 99 | end 100 | 101 | target 'AppTests' do 102 | pod 'ErrorAssertionExpectatoins' 103 | end 104 | ``` 105 | -------------------------------------------------------------------------------- /Sources/ErrorAssertionExpectations/AssertExpectations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertExpectations.swift 3 | // ErrorAssertionExpectations 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import ErrorAssertions 9 | import XCTest 10 | 11 | extension XCTestCase { 12 | 13 | private func replaceAssert( 14 | _ handler: @escaping (Error) -> Void 15 | ) -> RestorationHandler { 16 | let assertRestorationHandler = AssertUtilities.replaceAssert { 17 | (condition, error, _, _) in 18 | if !condition { 19 | handler(error) 20 | unreachable() 21 | } 22 | } 23 | 24 | let assertionFailureRestorationHandler = 25 | AssertUtilities.replaceAssertionFailure { 26 | (error, _, _) in 27 | handler(error) 28 | unreachable() 29 | } 30 | 31 | return { 32 | assertRestorationHandler() 33 | assertionFailureRestorationHandler() 34 | } 35 | } 36 | 37 | private func wrapWithAssertions(_ testcase: @escaping () -> Void, 38 | timeout: TimeInterval, 39 | handler: ((Error) -> Void)? = nil) { 40 | let expectation = self.expectation( 41 | description: "Expecting an assertion failure to occur" 42 | ) 43 | 44 | let restoration = replaceAssert { error in 45 | handler?(error) 46 | expectation.fulfill() 47 | } 48 | 49 | defer { restoration() } 50 | 51 | let thread = ClosureThread(testcase) 52 | thread.start() 53 | defer { thread.cancel() } 54 | 55 | wait(for: [expectation], timeout: timeout) 56 | } 57 | 58 | /// Executes the `testcase` closure and expects it to produce a specific 59 | /// assertion failure. 60 | /// 61 | /// - Parameters: 62 | /// - expectedError: The `Error` you expect `testcase` to pass to 63 | /// `assert()` or `assertionFailure()`. 64 | /// - timeout: How long to wait for `testcase` to produce its error. 65 | /// Defaults to 2 seconds. 66 | /// - file: The test file. By default, this will be the file from which 67 | /// you’re calling this method. 68 | /// - line: The line number in `file` where this is called. 69 | /// - testcase: The closure to run that produces the error. 70 | public func expectAssertionFailure( 71 | expectedError: T, 72 | timeout: TimeInterval = 2, 73 | file: StaticString = #file, 74 | line: UInt = #line, 75 | testcase: @escaping () -> Void 76 | ) where T: Equatable { 77 | let equalityExpectation = expectation( 78 | description: "The error was equal to the expected value" 79 | ) 80 | 81 | wrapWithAssertions(testcase, timeout: timeout) { error in 82 | if let error = error as? T, error == expectedError { 83 | equalityExpectation.fulfill() 84 | } 85 | } 86 | 87 | wait(for: [equalityExpectation], timeout: timeout) 88 | } 89 | 90 | /// Executes the `testcase` closure and expects it to produce a specific 91 | /// assertion failure message. 92 | /// 93 | /// - Parameters: 94 | /// - message: The `String` you expect `testcase` to pass to 95 | /// `assert()`. 96 | /// - timeout: How long to wait for `testcase` to produce its error. 97 | /// Defaults to 2 seconds. 98 | /// - file: The test file. By default, this will be the file from which 99 | /// you’re calling this method. 100 | /// - line: The line number in `file` where this is called. 101 | /// - testcase: The closure to run that produces the error. 102 | public func expectAssertionFailure( 103 | expectedMessage message: String, 104 | timeout: TimeInterval = 2, 105 | file: StaticString = #file, 106 | line: UInt = #line, 107 | testcase: @escaping () -> Void 108 | ) { 109 | expectAssertionFailure( 110 | expectedError: AnonymousError(string: message), 111 | timeout: timeout, 112 | file: file, 113 | line: line, 114 | testcase: testcase) 115 | } 116 | 117 | /// Executes the `testcase` closure and expects it to produce any assertion 118 | /// failure. 119 | /// 120 | /// - Parameters: 121 | /// - timeout: How long to wait for `testcase` to produce its error. 122 | /// Defaults to 2 seconds. 123 | /// - file: The test file. By default, this will be the file from which 124 | /// you’re calling this method. 125 | /// - line: The line number in `file` where this is called. 126 | /// - testcase: The closure to run that produces the error. 127 | public func expectAssertionFailure( 128 | timeout: TimeInterval = 2, 129 | file: StaticString = #file, 130 | line: UInt = #line, 131 | testcase: @escaping () -> Void 132 | ) { 133 | wrapWithAssertions(testcase, timeout: timeout) 134 | } 135 | 136 | /// Executes the `testcase` closure and expects it finish without producing 137 | /// any assertion failures. 138 | /// 139 | /// - Parameters: 140 | /// - timeout: How long to wait for `testcase` to finish. Defaults to 2 141 | /// seconds. 142 | /// - file: The test file. By default, this will be the file from which 143 | /// you’re calling this method. 144 | /// - line: The line number in `file` where this is called. 145 | /// - testcase: The closure to run. 146 | public func expectNoAssertionFailure( 147 | timeout: TimeInterval = 2, 148 | file: StaticString = #file, 149 | line: UInt = #line, 150 | testcase: @escaping () -> Void 151 | ) { 152 | let expectation = self.expectation( 153 | description: "Expecting no assertion failure to occur" 154 | ) 155 | 156 | let restoration = replaceAssert { _ in 157 | XCTFail("Received an assertion failure when expecting none", 158 | file: file, 159 | line: line) 160 | 161 | expectation.fulfill() 162 | } 163 | 164 | defer { restoration() } 165 | 166 | let thread = ClosureThread { 167 | testcase() 168 | expectation.fulfill() 169 | } 170 | 171 | thread.start() 172 | defer { thread.cancel() } 173 | 174 | wait(for: [expectation], timeout: timeout) 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /Sources/ErrorAssertionExpectations/FatalErrorExpectations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FatalErrorExpectations.swift 3 | // ErrorAssertionExpectations 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import ErrorAssertions 9 | import XCTest 10 | 11 | extension XCTestCase { 12 | 13 | private func replaceFatalError( 14 | _ handler: @escaping (Error) -> Void 15 | ) -> RestorationHandler { 16 | return FatalErrorUtilities.replaceFatalError { error, _, _ in 17 | handler(error) 18 | unreachable() 19 | } 20 | } 21 | 22 | private func wrapWithAssertions(_ testcase: @escaping () -> Void, 23 | timeout: TimeInterval, 24 | handler: ((Error) -> Void)? = nil) { 25 | let expectation = self.expectation( 26 | description: "Expecting a fatal error to occur." 27 | ) 28 | 29 | let restoration = replaceFatalError { error in 30 | handler?(error) 31 | expectation.fulfill() 32 | } 33 | 34 | defer { restoration() } 35 | 36 | let thread = ClosureThread(testcase) 37 | thread.start() 38 | 39 | wait(for: [expectation], timeout: timeout) 40 | 41 | thread.cancel() 42 | } 43 | 44 | /// Executes the `testcase` closure and expects it to produce a specific 45 | /// fatal error. 46 | /// 47 | /// - Parameters: 48 | /// - expectedError: The `Error` you expect `testcase` to pass to 49 | /// `fatalError()`. 50 | /// - timeout: How long to wait for `testcase` to produce its error. 51 | /// defaults to 2 seconds. 52 | /// - file: The test file. By default, this will be the file from which 53 | /// you’re calling this method. 54 | /// - line: The line number in `file` where this is called. 55 | /// - testcase: The closure to run that produces the error. 56 | public func expectFatalError( 57 | expectedError: T, 58 | timeout: TimeInterval = 2, 59 | file: StaticString = #file, 60 | line: UInt = #line, 61 | testcase: @escaping () -> Void 62 | ) where T: Equatable { 63 | let equalityExpectation = expectation( 64 | description: "The error was equal to the expected value" 65 | ) 66 | 67 | wrapWithAssertions(testcase, timeout: timeout) { error in 68 | if let error = error as? T, error == expectedError { 69 | equalityExpectation.fulfill() 70 | } 71 | } 72 | 73 | wait(for: [equalityExpectation], timeout: timeout) 74 | } 75 | 76 | /// Executes the `testcase` closure and expects it to produce a specific 77 | /// fatal error message. 78 | /// 79 | /// - Parameters: 80 | /// - message: The `String` you expect `testcase` to pass to 81 | /// `fatalError()`. 82 | /// - timeout: How long to wait for `testcase` to produce its error. 83 | /// defaults to 2 seconds. 84 | /// - file: The test file. By default, this will be the file from which 85 | /// you’re calling this method. 86 | /// - line: The line number in `file` where this is called. 87 | /// - testcase: The closure to run that produces the error. 88 | public func expectFatalError( 89 | expectedMessage message: String, 90 | timeout: TimeInterval = 2, 91 | file: StaticString = #file, 92 | line: UInt = #line, 93 | testcase: @escaping () -> Void 94 | ) { 95 | expectFatalError(expectedError: AnonymousError(string: message), 96 | timeout: timeout, 97 | file: file, 98 | line: line, 99 | testcase: testcase) 100 | } 101 | 102 | /// Executes the `testcase` closure and expects it to produce any fatal 103 | /// error. 104 | /// 105 | /// - Parameters: 106 | /// - timeout: How long to wait for `testcase` to produce its error. 107 | /// defaults to 2 seconds. 108 | /// - file: The test file. By default, this will be the file from which 109 | /// you’re calling this method. 110 | /// - line: The line number in `file` where this is called. 111 | /// - testcase: The closure to run that produces the error. 112 | public func expectFatalError( 113 | timeout: TimeInterval = 2, 114 | in context: StaticString = #function, 115 | file: StaticString = #file, 116 | line: UInt = #line, 117 | testcase: @escaping () -> Void 118 | ) { 119 | wrapWithAssertions(testcase, timeout: timeout) 120 | } 121 | 122 | /// Executes the `testcase` closure and expects it execute without producing 123 | /// any fatal errors. 124 | /// 125 | /// - Parameters: 126 | /// - timeout: How long to wait for `testcase` to finish. Defaults to 2 127 | /// seconds. 128 | /// - file: The test file. By default, this will be the file from which 129 | /// you’re calling this method. 130 | /// - line: The line number in `file` where this is called. 131 | /// - testcase: The closure to run. 132 | public func expectNoFatalError( 133 | timeout: TimeInterval = 2, 134 | in context: StaticString = #function, 135 | file: StaticString = #file, 136 | line: UInt = #line, 137 | testcase: @escaping () -> Void 138 | ) { 139 | let expectation = self.expectation( 140 | description: "Expecting no fatal error to occur" 141 | ) 142 | 143 | let restoration = replaceFatalError { _ in 144 | XCTFail("Received a fatal error when expecting none", 145 | file: file, 146 | line: line) 147 | 148 | expectation.fulfill() 149 | } 150 | 151 | defer { restoration() } 152 | 153 | let thread = ClosureThread { 154 | testcase() 155 | expectation.fulfill() 156 | } 157 | 158 | thread.start() 159 | defer { thread.cancel() } 160 | 161 | wait(for: [expectation], timeout: timeout) 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /Sources/ErrorAssertionExpectations/PreconditionExpectations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreconditionExpectations.swift 3 | // ErrorAssertionExpectations 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import ErrorAssertions 9 | import XCTest 10 | 11 | extension XCTestCase { 12 | 13 | private func replacePrecondition( 14 | _ handler: @escaping (Error) -> Void 15 | ) -> RestorationHandler { 16 | let preconditionRestorationHandler = 17 | PreconditionUtilities.replacePrecondition { 18 | condition, error, _, _ in 19 | if !condition { 20 | handler(error) 21 | unreachable() 22 | } 23 | } 24 | 25 | let preconditionFailureRestorationHandler = 26 | PreconditionUtilities.replacePreconditionFailure { error, _, _ in 27 | handler(error) 28 | unreachable() 29 | } 30 | 31 | return { 32 | preconditionRestorationHandler() 33 | preconditionFailureRestorationHandler() 34 | } 35 | } 36 | 37 | private func wrapWithAssertions(_ testcase: @escaping () -> Void, 38 | timeout: TimeInterval, 39 | handler: ((Error) -> Void)? = nil) { 40 | let expectation = self.expectation( 41 | description: "Expecting a precondition failure to occur." 42 | ) 43 | 44 | let restoration = replacePrecondition { error in 45 | handler?(error) 46 | expectation.fulfill() 47 | } 48 | 49 | defer { restoration() } 50 | 51 | let thread = ClosureThread(testcase) 52 | thread.start() 53 | defer { thread.cancel() } 54 | 55 | wait(for: [expectation], timeout: timeout) 56 | } 57 | 58 | /// Executes the `testcase` closure and expects it to produce a specific 59 | /// precondition failure. 60 | /// 61 | /// - Parameters: 62 | /// - expectedError: The `Error` you expect `testcase` to pass to 63 | /// `precondition()`. 64 | /// - timeout: How long to wait for `testcase` to produce its error. 65 | /// defaults to 2 seconds. 66 | /// - file: The test file. By default, this will be the file from which 67 | /// you’re calling this method. 68 | /// - line: The line number in `file` where this is called. 69 | /// - testcase: The closure to run that produces the error. 70 | public func expectPreconditionFailure( 71 | expectedError: T, 72 | timeout: TimeInterval = 2, 73 | file: StaticString = #file, 74 | line: UInt = #line, 75 | testcase: @escaping () -> Void 76 | ) where T: Equatable { 77 | let equalityExpectation = expectation( 78 | description: "The error was equal to the expected value" 79 | ) 80 | 81 | wrapWithAssertions(testcase, timeout: timeout) { error in 82 | if let error = error as? T, error == expectedError { 83 | equalityExpectation.fulfill() 84 | } 85 | } 86 | 87 | wait(for: [equalityExpectation], timeout: timeout) 88 | } 89 | 90 | /// Executes the `testcase` closure and expects it to produce a specific 91 | /// precondition failure message. 92 | /// 93 | /// - Parameters: 94 | /// - message: The `String` you expect `testcase` to pass to 95 | /// `precondition()`. 96 | /// - timeout: How long to wait for `testcase` to produce its error. 97 | /// defaults to 2 seconds. 98 | /// - file: The test file. By default, this will be the file from which 99 | /// you’re calling this method. 100 | /// - line: The line number in `file` where this is called. 101 | /// - testcase: The closure to run that produces the error. 102 | public func expectPreconditionFailure( 103 | expectedMessage message: String, 104 | timeout: TimeInterval = 2, 105 | file: StaticString = #file, 106 | line: UInt = #line, 107 | testcase: @escaping () -> Void 108 | ) { 109 | expectPreconditionFailure( 110 | expectedError: AnonymousError(string: message), 111 | timeout: timeout, 112 | file: file, 113 | line: line, 114 | testcase: testcase) 115 | } 116 | 117 | /// Executes the `testcase` closure and expects it to produce any 118 | /// precondition failure. 119 | /// 120 | /// - Parameters: 121 | /// - timeout: How long to wait for `testcase` to produce its error. 122 | /// defaults to 2 seconds. 123 | /// - file: The test file. By default, this will be the file from which 124 | /// you’re calling this method. 125 | /// - line: The line number in `file` where this is called. 126 | /// - testcase: The closure to run that produces the error. 127 | public func expectPreconditionFailure( 128 | timeout: TimeInterval = 2, 129 | file: StaticString = #file, 130 | line: UInt = #line, 131 | testcase: @escaping () -> Void 132 | ) { 133 | wrapWithAssertions(testcase, timeout: timeout) 134 | } 135 | 136 | /// Executes the `testcase` closure and expects it finish without producing 137 | /// any precondition failures. 138 | /// 139 | /// - Parameters: 140 | /// - timeout: How long to wait for `testcase` to finish. Defaults to 2 141 | /// seconds. 142 | /// - file: The test file. By default, this will be the file from which 143 | /// you’re calling this method. 144 | /// - line: The line number in `file` where this is called. 145 | /// - testcase: The closure to run. 146 | public func expectNoPreconditionFailure( 147 | timeout: TimeInterval = 2, 148 | file: StaticString = #file, 149 | line: UInt = #line, 150 | testcase: @escaping () -> Void 151 | ) { 152 | let expectation = self.expectation( 153 | description: "Expecting no precondition failure to occur" 154 | ) 155 | 156 | let restoration = replacePrecondition { _ in 157 | XCTFail("Received a precondition failure when expecting none", 158 | file: file, 159 | line: line) 160 | 161 | expectation.fulfill() 162 | } 163 | 164 | defer { restoration() } 165 | 166 | let thread = ClosureThread { 167 | testcase() 168 | expectation.fulfill() 169 | } 170 | 171 | thread.start() 172 | defer { thread.cancel() } 173 | 174 | wait(for: [expectation], timeout: timeout) 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /Sources/ErrorAssertionExpectations/Utilities/ClosureThread.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClosureThread.swift 3 | // ErrorAssertionExpectations 4 | // 5 | // Created by Jeff Kelley on 8/1/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal final class ClosureThread: Thread { 11 | 12 | private let closure: () -> Void 13 | 14 | internal init(_ closure: @escaping () -> Void) { 15 | self.closure = closure 16 | super.init() 17 | } 18 | 19 | internal override func main() { 20 | closure() 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Sources/ErrorAssertionExpectations/Utilities/Unreachable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Unreachable.swift 3 | // ErrorAssertionExpectations 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal func unreachable() -> Never { 11 | repeat { 12 | RunLoop.current.run() 13 | } while (true) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/ErrorAssertions/AssertWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertWrapper.swift 3 | // ErrorAssertions 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Performs a traditional C-style assert with an `Error`. 11 | /// - Parameters: 12 | /// - condition: The condition to test. `condition` is only evaluated in 13 | /// playgrounds and `-Onone` builds. 14 | /// - error: The error that represents the assertion failure when `condition` 15 | /// evaluates to `false`. 16 | /// - file: The file name to print with the description of `error` if the 17 | /// assertion fails. The default is the file where 18 | /// `assert(_:error:file:line:)` is called. 19 | /// - line: The line number to print along with the description of `error` if 20 | /// the assertion fails. The default is the line number where 21 | /// `assert(_:error:file:line:)` is called. 22 | @inlinable 23 | public func assert(_ condition: @autoclosure () -> Bool, 24 | error: Error, 25 | file: StaticString = #file, 26 | line: UInt = #line) { 27 | AssertUtilities.assertClosure(condition(), error, file, line) 28 | } 29 | 30 | /// Performs a traditional C-style assert with an optional message. 31 | /// - Parameters: 32 | /// - condition: The condition to test. `condition` is only evaluated in 33 | /// playgrounds and `-Onone` builds. 34 | /// - message: A string to print if `condition` is evaluated to `false`. The 35 | /// default is an empty string. 36 | /// - file: The file name to print with `message` if the assertion fails. The 37 | /// default is the file where `assert(_:_:file:line:)` is called. 38 | /// - line: The line number to print along with message if the assertion 39 | /// fails. The default is the line number where 40 | /// `assert(_:_:file:line:)` is called. 41 | @inlinable 42 | public func assert(_ condition: @autoclosure () -> Bool, 43 | _ message: @autoclosure () -> String = String(), 44 | file: StaticString = #file, 45 | line: UInt = #line) { 46 | assert(condition(), 47 | error: AnonymousError(string: message()), 48 | file: file, 49 | line: line) 50 | } 51 | 52 | /// Indicates that an internal sanity check failed. 53 | /// - Parameters: 54 | /// - error: The error that represents the assertion failure. 55 | /// - file: The file name to print with the description of `error`. The 56 | /// default is the file where 57 | /// `assertionFailure(error:file:line:)` is called. 58 | /// - line: The line number to print along with the description of `error`. 59 | /// The default is the line number where 60 | /// `assertionFailure(error:file:line:)` is called. 61 | @inlinable 62 | public func assertionFailure(error: Error, 63 | file: StaticString = #file, 64 | line: UInt = #line) { 65 | AssertUtilities.assertionFailureClosure(error, file, line) 66 | } 67 | 68 | /// Indicates that an internal sanity check failed. 69 | /// - Parameters: 70 | /// - message: A string to print in a playground or `-Onone` build. The 71 | /// default is an empty string. 72 | /// - file: The file name to print with `message`. The default is the file 73 | /// where `assertionFailure(_:file:line:)` is called. 74 | /// - line: The line number to print along with `message`. The default is the 75 | /// line number where `assertionFailure(_:file:line:)` is called. 76 | @inlinable 77 | public func assertionFailure(_ message: @autoclosure () -> String = String(), 78 | file: StaticString = #file, 79 | line: UInt = #line) { 80 | assertionFailure(error: AnonymousError(string: message()), 81 | file: file, 82 | line: line) 83 | } 84 | 85 | /// A utility type that replaces the internal implementation of assertion 86 | /// functions. 87 | public enum AssertUtilities { 88 | 89 | /// A closure that handles an assertion. 90 | public typealias AssertClosure = (Bool, Error, StaticString, UInt) -> () 91 | 92 | /// A closure that handles an assertion failure. 93 | public typealias AssertionFailureClosure = 94 | (Error, StaticString, UInt) -> () 95 | 96 | internal static var _assertClosure: AssertClosure? 97 | 98 | @usableFromInline internal static var assertClosure: AssertClosure { 99 | return _assertClosure ?? defaultAssertClosure 100 | } 101 | 102 | internal static var _assertionFailureClosure: AssertionFailureClosure? 103 | 104 | @usableFromInline 105 | internal static var assertionFailureClosure: AssertionFailureClosure { 106 | return _assertionFailureClosure ?? defaultAssertionFailureClosure 107 | } 108 | 109 | private static let defaultAssertClosure = { 110 | (condition: Bool, error: Error, file: StaticString, line: UInt) in 111 | Swift.assert(condition, 112 | error.localizedDescription, 113 | file: file, 114 | line: line) 115 | } 116 | 117 | private static let defaultAssertionFailureClosure = { 118 | (error: Error, file: StaticString, line: UInt) in 119 | Swift.assertionFailure(error.localizedDescription, 120 | file: file, 121 | line: line) 122 | } 123 | 124 | /// Replaces the internal implementation of `assert(_:error:file:line:)` 125 | /// with the given closure. Returns a `RestorationHandler` to execute that 126 | /// restores the original implentation. 127 | /// - Parameter closure: The closure to execute when 128 | /// `assert(_:error:file:line:)` is called. 129 | static public func replaceAssert( 130 | with closure: @escaping AssertClosure 131 | ) -> RestorationHandler { 132 | _assertClosure = closure 133 | return restoreAssert 134 | } 135 | 136 | static private func restoreAssert() { 137 | _assertClosure = nil 138 | } 139 | 140 | /// Replaces the internal implementation of 141 | /// `assertionFailure(error:file:line:)` with the given closure. Returns a 142 | /// `RestorationHandler` to execute that restores the original implentation. 143 | /// - Parameter closure: The closure to execute when 144 | /// `assertionFailure(error:file:line:)` is called. 145 | static public func replaceAssertionFailure( 146 | with closure: @escaping AssertionFailureClosure 147 | ) -> RestorationHandler { 148 | _assertionFailureClosure = closure 149 | return restoreAssertionFailure 150 | } 151 | 152 | static private func restoreAssertionFailure() { 153 | _assertionFailureClosure = nil 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /Sources/ErrorAssertions/FatalErrorWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FatalErrorWrapper.swift 3 | // ErrorAssertions 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Unconditionally reports a given error and stops execution. 11 | /// - Parameters: 12 | /// - error: The error to report. 13 | /// - file: The file name to print with the description of `error`. The 14 | /// default is the file where `fatalError(_:file:line:)` is called. 15 | /// - line: The line number to print with the description of `error`. The 16 | /// default is the line number where `fatalError(_:file:line:)` is 17 | /// called. 18 | @inlinable 19 | public func fatalError(_ error: Error, 20 | file: StaticString = #file, 21 | line: UInt = #line) -> Never { 22 | FatalErrorUtilities.fatalErrorClosure(error, file, line) 23 | } 24 | 25 | /// Unconditionally prints a given message and stops execution. 26 | /// - Parameters: 27 | /// - message: The string to print. The default is an empty string. 28 | /// - file: The file name to print with `message`. The default is the file 29 | /// where `fatalError(_:file:line:)` is called. 30 | /// - line: The line number to print along with `message`. The default is the 31 | /// line number where `fatalError(_:file:line:)` is called. 32 | @inlinable 33 | public func fatalError(_ message: @autoclosure () -> String = String(), 34 | file: StaticString = #file, 35 | line: UInt = #line) -> Never { 36 | fatalError(AnonymousError(string: message()), 37 | file: file, 38 | line: line) 39 | } 40 | 41 | /// A utility type that replaces the internal implementation of `fatalError()`. 42 | public enum FatalErrorUtilities { 43 | 44 | /// A closure that handles a fatal error. 45 | public typealias FatalErrorClosure = (Error, StaticString, UInt) -> Never 46 | 47 | internal static var _fatalErrorClosure: FatalErrorClosure? 48 | 49 | @usableFromInline internal static var fatalErrorClosure: FatalErrorClosure { 50 | return _fatalErrorClosure ?? defaultFatalErrorClosure 51 | } 52 | 53 | private static let defaultFatalErrorClosure = { 54 | (error: Error, file: StaticString, line: UInt) -> Never in 55 | Swift.fatalError(error.localizedDescription, 56 | file: file, 57 | line: line) 58 | } 59 | 60 | /// Replaces the internal implementation of `fatalError(_:file:line:)` with 61 | /// the given closure. Returns a `RestorationHandler` to execute that 62 | /// restores the original implentation. 63 | /// - Parameter closure: The closure to execute when 64 | /// `fatalError(_:file:line:)` is called. 65 | static public func replaceFatalError( 66 | with closure: @escaping FatalErrorClosure 67 | ) -> RestorationHandler { 68 | _fatalErrorClosure = closure 69 | return restoreFatalError 70 | } 71 | 72 | static private func restoreFatalError() { 73 | _fatalErrorClosure = nil 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Sources/ErrorAssertions/PreconditionWrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreconditionWrapper.swift 3 | // ErrorAssertions 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Checks a necessary condition for making forward progress. 11 | /// - Parameters: 12 | /// - condition: The condition to test. `condition is not evaluated in 13 | /// `-Ounchecked` builds. 14 | /// - error: The error that represents the precondition failure when 15 | /// `condition` evaluates to `false`. 16 | /// - file: The file name to print with the description of `error` if the 17 | /// precondition fails. The default is the file where 18 | /// `precondition(_:error:file:line:)` is called. 19 | /// - line: The line number to print along with the description of `error` if 20 | /// the precondition fails. The default is the line number where 21 | /// `precondition(_:error:file:line:)` is called. 22 | @inlinable 23 | public func precondition(_ condition: @autoclosure () -> Bool, 24 | error: Error, 25 | file: StaticString = #file, 26 | line: UInt = #line) { 27 | PreconditionUtilities.preconditionClosure(condition(), error, file, line) 28 | } 29 | 30 | /// Checks a necessary condition for making forward progress. 31 | /// - Parameters: 32 | /// - condition: The condition to test. `condition` is not evaluated in 33 | /// `-Ounchecked` builds. 34 | /// - message: A string to print if `condition` is evaluated to `false` in a 35 | /// playground or `-Onone` build. The default is an empty string. 36 | /// - file: The file name to print with `message` if the precondition fails. 37 | /// The default is the file where `precondition(_:_:file:line:)` is 38 | /// called. 39 | /// - line: The line number to print along with `message` if the precondition 40 | /// fails. The default is the line number where 41 | /// `precondition(_:_:file:line:)` is called. 42 | @inlinable 43 | public func precondition(_ condition: @autoclosure () -> Bool, 44 | _ message: @autoclosure () -> String = String(), 45 | file: StaticString = #file, 46 | line: UInt = #line) { 47 | precondition(condition(), 48 | error: AnonymousError(string: message()), 49 | file: file, 50 | line: line) 51 | } 52 | 53 | /// Indicates that a precondition was violated. 54 | /// - Parameters: 55 | /// - error: The error that represents the precondition failure. 56 | /// - file: The file name to print with the description of `error`. The 57 | /// default is the file where `preconditionFailure(error:file:line:)` 58 | /// is called. 59 | /// - line: The line number to print along with the description of `error`. 60 | /// The default is the line number where 61 | /// `preconditionFailure(error:file:line:)` is called. 62 | @inlinable 63 | public func preconditionFailure(error: Error, 64 | file: StaticString = #file, 65 | line: UInt = #line) -> Never { 66 | PreconditionUtilities.preconditionFailureClosure(error, file, line) 67 | } 68 | 69 | /// Indicates that a precondition was violated. 70 | /// - Parameters: 71 | /// - message: A string to print in a playground or `-Onone` build. The 72 | /// default is an empty string. 73 | /// - file: The file name to print with `message`. The default is the file 74 | /// where `preconditionFailure(_:file:line:)` is called. 75 | /// - line: The line number to print along with `message`. The default is the 76 | /// line number where `preconditionFailure(_:file:line:)` is called. 77 | @inlinable 78 | public func preconditionFailure(_ message: @autoclosure () -> String = String(), 79 | file: StaticString = #file, 80 | line: UInt = #line) -> Never { 81 | preconditionFailure(error: AnonymousError(string: message()), 82 | file: file, 83 | line: line) 84 | } 85 | 86 | /// A utility type that replaces the internal implementation of precondition 87 | /// functions. 88 | public enum PreconditionUtilities { 89 | 90 | /// A closure that handles a precondition. 91 | public typealias PreconditionClosure = 92 | (Bool, Error, StaticString, UInt) -> () 93 | 94 | /// A closure that handles a precondition failure. 95 | public typealias PreconditionFailureClosure = 96 | (Error, StaticString, UInt) -> Never 97 | 98 | internal static var _preconditionClosure: PreconditionClosure? 99 | 100 | @usableFromInline 101 | internal static var preconditionClosure: PreconditionClosure { 102 | return _preconditionClosure ?? defaultPreconditionClosure 103 | } 104 | 105 | internal static var _preconditionFailureClosure: PreconditionFailureClosure? 106 | 107 | @usableFromInline 108 | internal static var preconditionFailureClosure: PreconditionFailureClosure { 109 | return _preconditionFailureClosure ?? defaultPreconditionFailureClosure 110 | } 111 | 112 | private static let defaultPreconditionClosure = { 113 | (condition: Bool, error: Error, file: StaticString, line: UInt) in 114 | Swift.precondition(condition, 115 | error.localizedDescription, 116 | file: file, 117 | line: line) 118 | } 119 | 120 | private static let defaultPreconditionFailureClosure = { 121 | (error: Error, file: StaticString, line: UInt) in 122 | Swift.preconditionFailure(error.localizedDescription, 123 | file: file, 124 | line: line) 125 | } 126 | 127 | /// Replaces the internal implementation of 128 | /// `precondition(_:error:file:line:)` with the given closure. Returns a 129 | /// `RestorationHandler` to execute that restores the original implentation. 130 | /// - Parameter closure: The closure to execute when 131 | /// `precondition(_:error:file:line:)` is called. 132 | static public func replacePrecondition( 133 | with closure: @escaping PreconditionClosure 134 | ) -> RestorationHandler { 135 | _preconditionClosure = closure 136 | return restorePrecondition 137 | } 138 | 139 | static private func restorePrecondition() { 140 | _preconditionClosure = nil 141 | } 142 | 143 | /// Replaces the internal implementation of 144 | /// `preconditionFailure(error:file:line:)` with the given closure. Returns 145 | /// a `RestorationHandler` to execute that restores the original 146 | /// implentation. 147 | /// - Parameter closure: The closure to execute when 148 | /// `preconditionFailure(error:file:line:)` is called. 149 | static public func replacePreconditionFailure( 150 | with closure: @escaping PreconditionFailureClosure 151 | ) -> RestorationHandler { 152 | _preconditionFailureClosure = closure 153 | return restorePreconditionFailure 154 | } 155 | 156 | static private func restorePreconditionFailure() { 157 | _preconditionFailureClosure = nil 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /Sources/ErrorAssertions/Utilities/AnonymousError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnonymousError.swift 3 | // ErrorAssertions 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import Foundation 9 | 10 | /// An error without context that optionally wraps a `String`. 11 | public enum AnonymousError: Error { 12 | 13 | /// An anonymous error with no message. 14 | case blank 15 | 16 | /// An anonymous error with a message. 17 | case withMessage(String) 18 | 19 | /// Creates an anonymous error 20 | /// - Parameter string: The string that the error wraps. If the string is 21 | /// empty, creates a `blank` error. 22 | public init(string: String) { 23 | self = string.isEmpty ? .blank : .withMessage(string) 24 | } 25 | 26 | } 27 | 28 | extension AnonymousError: Equatable {} 29 | 30 | extension AnonymousError: LocalizedError { 31 | 32 | public var errorDescription: String? { 33 | switch self { 34 | case .blank: return nil 35 | case .withMessage(let message): return message 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Sources/ErrorAssertions/Utilities/RestorationHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RestorationHandler.swift 3 | // ErrorAssertions 4 | // 5 | // Created by Jeff Kelley on 8/2/19. 6 | // 7 | 8 | /// A closure that, when executed, restores some state. 9 | public typealias RestorationHandler = () -> Void 10 | -------------------------------------------------------------------------------- /Tests/ErrorAssertionsTests/AssertTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertTests.swift 3 | // ErrorAssertionsTests 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import XCTest 9 | 10 | @testable import ErrorAssertions 11 | import ErrorAssertionExpectations 12 | 13 | final class AssertTests: XCTestCase { 14 | 15 | func testAssertionFailuresSendExpectedErrors() { 16 | var testcaseExecutionCount = 0 17 | 18 | expectAssertionFailure(expectedError: TestError.testErrorA) { 19 | testcaseExecutionCount += 1 20 | ErrorAssertions.assert(false, error: TestError.testErrorA) 21 | } 22 | 23 | expectAssertionFailure(expectedError: TestError.testErrorB) { 24 | testcaseExecutionCount += 1 25 | ErrorAssertions.assert(false, error: TestError.testErrorB) 26 | } 27 | 28 | expectAssertionFailure(expectedError: TestError.testErrorA) { 29 | testcaseExecutionCount += 1 30 | ErrorAssertions.assertionFailure(error: TestError.testErrorA) 31 | } 32 | 33 | expectAssertionFailure(expectedError: TestError.testErrorB) { 34 | testcaseExecutionCount += 1 35 | ErrorAssertions.assertionFailure(error: TestError.testErrorB) 36 | } 37 | 38 | XCTAssertEqual(testcaseExecutionCount, 4) 39 | } 40 | 41 | func testDefaultErrorIsABlankAnonymousError() { 42 | var testcaseExecutionCount = 0 43 | 44 | expectAssertionFailure(expectedError: AnonymousError.blank) { 45 | testcaseExecutionCount += 1 46 | ErrorAssertions.assert(false) 47 | } 48 | 49 | expectAssertionFailure(expectedError: AnonymousError.blank) { 50 | testcaseExecutionCount += 1 51 | ErrorAssertions.assertionFailure() 52 | } 53 | 54 | XCTAssertEqual(testcaseExecutionCount, 2) 55 | } 56 | 57 | func testDefaultErrorWithStringIsAnAnonymousError() { 58 | var testcaseExecutionCount = 0 59 | 60 | let expectedError = AnonymousError.withMessage("test") 61 | 62 | expectAssertionFailure(expectedError: expectedError) { 63 | testcaseExecutionCount += 1 64 | ErrorAssertions.assert(false, "test") 65 | } 66 | 67 | expectAssertionFailure(expectedError: expectedError) { 68 | testcaseExecutionCount += 1 69 | ErrorAssertions.assertionFailure("test") 70 | } 71 | 72 | XCTAssertEqual(testcaseExecutionCount, 2) 73 | } 74 | 75 | func testAssertionFailureWithoutCapturingError() { 76 | var testcaseExecutionCount = 0 77 | 78 | expectAssertionFailure { 79 | testcaseExecutionCount += 1 80 | ErrorAssertions.assert(false) 81 | } 82 | 83 | expectAssertionFailure { 84 | testcaseExecutionCount += 1 85 | ErrorAssertions.assertionFailure() 86 | } 87 | 88 | XCTAssertEqual(testcaseExecutionCount, 2) 89 | } 90 | 91 | func testAssertionFailureWithMessageWithoutCapturingError() { 92 | var testcaseExecutionCount = 0 93 | 94 | expectAssertionFailure(expectedMessage: "test") { 95 | testcaseExecutionCount += 1 96 | ErrorAssertions.assert(false, "test") 97 | } 98 | 99 | expectAssertionFailure(expectedMessage: "test") { 100 | testcaseExecutionCount += 1 101 | ErrorAssertions.assertionFailure("test") 102 | } 103 | 104 | XCTAssertEqual(testcaseExecutionCount, 2) 105 | } 106 | 107 | func testExpectingNoAssertionFailure() { 108 | var testcaseDidExecute = false 109 | 110 | expectNoAssertionFailure { 111 | testcaseDidExecute = true 112 | } 113 | 114 | XCTAssertTrue(testcaseDidExecute) 115 | } 116 | 117 | func testAssertionsDoNotContinueExecution() { 118 | let expectation = self.expectation(description: 119 | "The code after the assert should not execute" 120 | ) 121 | 122 | expectation.isInverted = true 123 | 124 | expectAssertionFailure { 125 | ErrorAssertions.assert(false) 126 | expectation.fulfill() 127 | } 128 | 129 | waitForExpectations(timeout: 1) 130 | } 131 | 132 | func testAssertionFailuresDoNotContinueExecution() { 133 | let expectation = self.expectation(description: 134 | "The code after the assertion failure should not execute" 135 | ) 136 | 137 | expectation.isInverted = true 138 | 139 | expectAssertionFailure { 140 | ErrorAssertions.assertionFailure() 141 | expectation.fulfill() 142 | } 143 | 144 | waitForExpectations(timeout: 1) 145 | } 146 | 147 | func testAssertionsDoContinueExecution() { 148 | let expectation = self.expectation(description: 149 | "The code after the assert executed" 150 | ) 151 | 152 | expectNoAssertionFailure { 153 | ErrorAssertions.assert(true) 154 | expectation.fulfill() 155 | } 156 | 157 | waitForExpectations(timeout: 1) 158 | } 159 | 160 | func testAssertExpectationThreadDies() { 161 | var thread: Thread? 162 | 163 | expectAssertionFailure { 164 | thread = Thread.current 165 | ErrorAssertions.assert(false) 166 | } 167 | 168 | if let receivedThread = thread { 169 | XCTAssertTrue(receivedThread.isCancelled) 170 | } 171 | else { 172 | XCTFail("did not receive a thread") 173 | } 174 | } 175 | 176 | func testAssertionFailureExpectationThreadDies() { 177 | var thread: Thread? 178 | 179 | expectAssertionFailure { 180 | thread = Thread.current 181 | ErrorAssertions.assertionFailure() 182 | } 183 | 184 | if let receivedThread = thread { 185 | XCTAssertTrue(receivedThread.isCancelled) 186 | } 187 | else { 188 | XCTFail("did not receive a thread") 189 | } 190 | } 191 | 192 | func testNoAssertionFailureExpectationThreadDies() { 193 | var thread: Thread? 194 | 195 | expectNoAssertionFailure { 196 | thread = Thread.current 197 | ErrorAssertions.assert(true) 198 | } 199 | 200 | if let receivedThread = thread { 201 | XCTAssertTrue(receivedThread.isCancelled) 202 | } 203 | else { 204 | XCTFail("did not receive a thread") 205 | } 206 | } 207 | 208 | func testAssertionMethodsAreReplacedAfterTestFinishes() { 209 | expectAssertionFailure { 210 | ErrorAssertions.assert(false) 211 | } 212 | 213 | XCTAssertNil(AssertUtilities._assertClosure) 214 | XCTAssertNil(AssertUtilities._assertionFailureClosure) 215 | 216 | expectAssertionFailure { 217 | ErrorAssertions.assertionFailure() 218 | } 219 | 220 | XCTAssertNil(AssertUtilities._assertClosure) 221 | XCTAssertNil(AssertUtilities._assertionFailureClosure) 222 | 223 | expectNoAssertionFailure { 224 | ErrorAssertions.assert(true) 225 | } 226 | 227 | XCTAssertNil(AssertUtilities._assertClosure) 228 | XCTAssertNil(AssertUtilities._assertionFailureClosure) 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /Tests/ErrorAssertionsTests/FatalErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FatalErrorTests.swift 3 | // ErrorAssertionsTests 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import XCTest 9 | 10 | @testable import ErrorAssertions 11 | import ErrorAssertionExpectations 12 | 13 | final class FatalErrorTests: XCTestCase { 14 | 15 | func testFatalErrorsSendExpectedErrors() { 16 | var testcaseExecutionCount = 0 17 | 18 | expectFatalError(expectedError: TestError.testErrorA) { 19 | testcaseExecutionCount += 1 20 | ErrorAssertions.fatalError(TestError.testErrorA) 21 | } 22 | 23 | expectFatalError(expectedError: TestError.testErrorB) { 24 | testcaseExecutionCount += 1 25 | ErrorAssertions.fatalError(TestError.testErrorB) 26 | } 27 | 28 | XCTAssertEqual(testcaseExecutionCount, 2) 29 | } 30 | 31 | func testDefaultErrorIsABlankAnonymousError() { 32 | var testcaseDidExecute = false 33 | 34 | expectFatalError(expectedError: AnonymousError.blank) { 35 | testcaseDidExecute = true 36 | ErrorAssertions.fatalError() 37 | } 38 | 39 | XCTAssertTrue(testcaseDidExecute) 40 | } 41 | 42 | func testDefaultErrorWithStringIsAnAnonymousError() { 43 | var testcaseDidExecute = false 44 | let expectedError = AnonymousError.withMessage("test") 45 | 46 | expectFatalError(expectedError: expectedError) { 47 | testcaseDidExecute = true 48 | ErrorAssertions.fatalError("test") 49 | } 50 | 51 | XCTAssertTrue(testcaseDidExecute) 52 | } 53 | 54 | func testFatalErrorWithoutCapturingError() { 55 | var testcaseDidExecute = false 56 | 57 | expectFatalError { 58 | testcaseDidExecute = true 59 | ErrorAssertions.fatalError() 60 | } 61 | 62 | XCTAssertTrue(testcaseDidExecute) 63 | } 64 | 65 | func testFatalErrorWithMessageWithoutCapturingError() { 66 | var testcaseDidExecute = false 67 | 68 | expectFatalError(expectedMessage: "test") { 69 | testcaseDidExecute = true 70 | ErrorAssertions.fatalError("test") 71 | } 72 | 73 | XCTAssertTrue(testcaseDidExecute) 74 | } 75 | 76 | func testExpectingNoFatalError() { 77 | var testcaseDidExecute = false 78 | 79 | expectNoFatalError { 80 | testcaseDidExecute = true 81 | } 82 | 83 | XCTAssertTrue(testcaseDidExecute) 84 | } 85 | 86 | func testFatalErrorsDoNotContinueExecution() { 87 | let expectation = self.expectation( 88 | description: "The code after the fatal error should not execute" 89 | ) 90 | 91 | expectation.isInverted = true 92 | 93 | expectFatalError { 94 | defer { expectation.fulfill() } 95 | ErrorAssertions.fatalError() 96 | } 97 | 98 | waitForExpectations(timeout: 1) 99 | } 100 | 101 | func testFatalErrorExpectationThreadDies() { 102 | var thread: Thread? 103 | 104 | expectFatalError { 105 | thread = Thread.current 106 | ErrorAssertions.fatalError() 107 | } 108 | 109 | if let receivedThread = thread { 110 | XCTAssertTrue(receivedThread.isCancelled) 111 | } 112 | else { 113 | XCTFail("did not receive a thread") 114 | } 115 | } 116 | 117 | func testNoFatalErrorExpectationThreadDies() { 118 | var thread: Thread? 119 | 120 | expectNoFatalError { 121 | thread = Thread.current 122 | } 123 | 124 | if let receivedThread = thread { 125 | XCTAssertTrue(receivedThread.isCancelled) 126 | } 127 | else { 128 | XCTFail("did not receive a thread") 129 | } 130 | } 131 | 132 | func testFatalErrorMethodsAreReplacedAfterTestFinishes() { 133 | expectFatalError { 134 | ErrorAssertions.fatalError() 135 | } 136 | 137 | XCTAssertNil(FatalErrorUtilities._fatalErrorClosure) 138 | 139 | expectNoFatalError { 140 | 141 | } 142 | 143 | XCTAssertNil(FatalErrorUtilities._fatalErrorClosure) 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /Tests/ErrorAssertionsTests/PreconditionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreconditionTests.swift 3 | // ErrorAssertionsTests 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | import XCTest 9 | 10 | @testable import ErrorAssertions 11 | import ErrorAssertionExpectations 12 | 13 | final class PreconditionTests: XCTestCase { 14 | 15 | func testPreconditionFailuresSendExpectedErrors() { 16 | var testcaseExecutionCount = 0 17 | 18 | expectPreconditionFailure(expectedError: TestError.testErrorA) { 19 | testcaseExecutionCount += 1 20 | ErrorAssertions.precondition(false, error: TestError.testErrorA) 21 | } 22 | 23 | expectPreconditionFailure(expectedError: TestError.testErrorB) { 24 | testcaseExecutionCount += 1 25 | ErrorAssertions.precondition(false, error: TestError.testErrorB) 26 | } 27 | 28 | expectPreconditionFailure(expectedError: TestError.testErrorA) { 29 | testcaseExecutionCount += 1 30 | ErrorAssertions.preconditionFailure(error: TestError.testErrorA) 31 | } 32 | 33 | expectPreconditionFailure(expectedError: TestError.testErrorB) { 34 | testcaseExecutionCount += 1 35 | ErrorAssertions.preconditionFailure(error: TestError.testErrorB) 36 | } 37 | 38 | XCTAssertEqual(testcaseExecutionCount, 4) 39 | } 40 | 41 | func testDefaultErrorIsABlankAnonymousError() { 42 | var testcaseExecutionCount = 0 43 | 44 | expectPreconditionFailure(expectedError: AnonymousError.blank) { 45 | testcaseExecutionCount += 1 46 | ErrorAssertions.precondition(false) 47 | } 48 | 49 | expectPreconditionFailure(expectedError: AnonymousError.blank) { 50 | testcaseExecutionCount += 1 51 | ErrorAssertions.preconditionFailure() 52 | } 53 | 54 | XCTAssertEqual(testcaseExecutionCount, 2) 55 | } 56 | 57 | func testDefaultErrorWithStringIsAnAnonymousError() { 58 | var testcaseExecutionCount = 0 59 | 60 | let expectedError = AnonymousError.withMessage("test") 61 | 62 | expectPreconditionFailure(expectedError: expectedError) { 63 | testcaseExecutionCount += 1 64 | ErrorAssertions.precondition(false, "test") 65 | } 66 | 67 | expectPreconditionFailure(expectedError: expectedError) { 68 | testcaseExecutionCount += 1 69 | ErrorAssertions.preconditionFailure("test") 70 | } 71 | 72 | XCTAssertEqual(testcaseExecutionCount, 2) 73 | } 74 | 75 | func testPreconditionFailureWithoutCapturingError() { 76 | var testcaseExecutionCount = 0 77 | 78 | expectPreconditionFailure { 79 | testcaseExecutionCount += 1 80 | ErrorAssertions.precondition(false) 81 | } 82 | 83 | expectPreconditionFailure { 84 | testcaseExecutionCount += 1 85 | ErrorAssertions.preconditionFailure() 86 | } 87 | 88 | XCTAssertEqual(testcaseExecutionCount, 2) 89 | } 90 | 91 | func testPreconditionFailureWithMessageWithoutCapturingError() { 92 | var testcaseExecutionCount = 0 93 | 94 | expectPreconditionFailure(expectedMessage: "test") { 95 | testcaseExecutionCount += 1 96 | ErrorAssertions.precondition(false, "test") 97 | } 98 | 99 | expectPreconditionFailure(expectedMessage: "test") { 100 | testcaseExecutionCount += 1 101 | ErrorAssertions.preconditionFailure("test") 102 | } 103 | 104 | XCTAssertEqual(testcaseExecutionCount, 2) 105 | } 106 | 107 | func testExpectingNoPreconditionFailure() { 108 | var testcaseDidExecute = false 109 | 110 | expectNoPreconditionFailure { 111 | testcaseDidExecute = true 112 | } 113 | 114 | XCTAssertTrue(testcaseDidExecute) 115 | } 116 | 117 | func testPreconditionsThatFailDoNotContinueExecution() { 118 | let expectation = self.expectation(description: 119 | "The code after the precondition should not execute" 120 | ) 121 | 122 | expectation.isInverted = true 123 | 124 | expectPreconditionFailure { 125 | ErrorAssertions.precondition(false) 126 | expectation.fulfill() 127 | } 128 | 129 | waitForExpectations(timeout: 1) 130 | } 131 | 132 | func testPreconditionFailuresDoNotContinueExecution() { 133 | let expectation = self.expectation(description: 134 | "The code after the precondition failure should not execute" 135 | ) 136 | 137 | expectation.isInverted = true 138 | 139 | expectPreconditionFailure { 140 | defer { 141 | // This will never actually execute, as `preconditionFailure()` 142 | // returns `Never`. But the code can’t go after that line, or 143 | // the compiler will (rightly) recognize that the code will 144 | // never execute. Putting it in a `defer` block seems to avoid 145 | // that inspection. 146 | expectation.fulfill() 147 | } 148 | ErrorAssertions.preconditionFailure() 149 | } 150 | 151 | waitForExpectations(timeout: 1) 152 | } 153 | 154 | func testPreconditionsThatSucceedDoContinueExecution() { 155 | let expectation = self.expectation(description: 156 | "The code after the assert executed" 157 | ) 158 | 159 | expectNoPreconditionFailure { 160 | ErrorAssertions.precondition(true) 161 | expectation.fulfill() 162 | } 163 | 164 | waitForExpectations(timeout: 1) 165 | } 166 | 167 | func testPreconditionExpectationThreadDies() { 168 | var thread: Thread? 169 | 170 | expectPreconditionFailure { 171 | thread = Thread.current 172 | ErrorAssertions.precondition(false) 173 | } 174 | 175 | if let receivedThread = thread { 176 | XCTAssertTrue(receivedThread.isCancelled) 177 | } 178 | else { 179 | XCTFail("did not receive a thread") 180 | } 181 | } 182 | 183 | func testPreconditionFailureExpectationThreadDies() { 184 | var thread: Thread? 185 | 186 | expectPreconditionFailure { 187 | thread = Thread.current 188 | ErrorAssertions.preconditionFailure() 189 | } 190 | 191 | if let receivedThread = thread { 192 | XCTAssertTrue(receivedThread.isCancelled) 193 | } 194 | else { 195 | XCTFail("did not receive a thread") 196 | } 197 | } 198 | 199 | func testNoPreconditionFailureExpectationThreadDies() { 200 | var thread: Thread? 201 | 202 | expectNoPreconditionFailure { 203 | thread = Thread.current 204 | ErrorAssertions.precondition(true) 205 | } 206 | 207 | if let receivedThread = thread { 208 | XCTAssertTrue(receivedThread.isCancelled) 209 | } 210 | else { 211 | XCTFail("did not receive a thread") 212 | } 213 | } 214 | 215 | func testPreconditionMethodsAreReplacedAfterTestFinishes() { 216 | expectPreconditionFailure { 217 | ErrorAssertions.precondition(false) 218 | } 219 | 220 | XCTAssertNil(PreconditionUtilities._preconditionClosure) 221 | XCTAssertNil(PreconditionUtilities._preconditionFailureClosure) 222 | 223 | expectPreconditionFailure { 224 | ErrorAssertions.preconditionFailure() 225 | } 226 | 227 | XCTAssertNil(PreconditionUtilities._preconditionClosure) 228 | XCTAssertNil(PreconditionUtilities._preconditionFailureClosure) 229 | 230 | expectNoPreconditionFailure { 231 | ErrorAssertions.precondition(true) 232 | } 233 | 234 | XCTAssertNil(PreconditionUtilities._preconditionClosure) 235 | XCTAssertNil(PreconditionUtilities._preconditionFailureClosure) 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /Tests/ErrorAssertionsTests/TestError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestError.swift 3 | // ErrorAssertionsTests 4 | // 5 | // Created by Jeff Kelley on 7/1/19. 6 | // 7 | 8 | enum TestError: Error, Equatable { 9 | case testErrorA 10 | case testErrorB 11 | } 12 | -------------------------------------------------------------------------------- /Tests/ErrorAssertionsTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | #if !canImport(ObjectiveC) 2 | import XCTest 3 | 4 | extension AssertTests { 5 | // DO NOT MODIFY: This is autogenerated, use: 6 | // `swift test --generate-linuxmain` 7 | // to regenerate. 8 | static let __allTests__AssertTests = [ 9 | ("testAssertExpectationThreadDies", testAssertExpectationThreadDies), 10 | ("testAssertionFailureExpectationThreadDies", testAssertionFailureExpectationThreadDies), 11 | ("testAssertionFailuresDoNotContinueExecution", testAssertionFailuresDoNotContinueExecution), 12 | ("testAssertionFailuresSendExpectedErrors", testAssertionFailuresSendExpectedErrors), 13 | ("testAssertionFailureWithMessageWithoutCapturingError", testAssertionFailureWithMessageWithoutCapturingError), 14 | ("testAssertionFailureWithoutCapturingError", testAssertionFailureWithoutCapturingError), 15 | ("testAssertionMethodsAreReplacedAfterTestFinishes", testAssertionMethodsAreReplacedAfterTestFinishes), 16 | ("testAssertionsDoContinueExecution", testAssertionsDoContinueExecution), 17 | ("testAssertionsDoNotContinueExecution", testAssertionsDoNotContinueExecution), 18 | ("testDefaultErrorIsABlankAnonymousError", testDefaultErrorIsABlankAnonymousError), 19 | ("testDefaultErrorWithStringIsAnAnonymousError", testDefaultErrorWithStringIsAnAnonymousError), 20 | ("testExpectingNoAssertionFailure", testExpectingNoAssertionFailure), 21 | ("testNoAssertionFailureExpectationThreadDies", testNoAssertionFailureExpectationThreadDies), 22 | ] 23 | } 24 | 25 | extension FatalErrorTests { 26 | // DO NOT MODIFY: This is autogenerated, use: 27 | // `swift test --generate-linuxmain` 28 | // to regenerate. 29 | static let __allTests__FatalErrorTests = [ 30 | ("testDefaultErrorIsABlankAnonymousError", testDefaultErrorIsABlankAnonymousError), 31 | ("testDefaultErrorWithStringIsAnAnonymousError", testDefaultErrorWithStringIsAnAnonymousError), 32 | ("testExpectingNoFatalError", testExpectingNoFatalError), 33 | ("testFatalErrorExpectationThreadDies", testFatalErrorExpectationThreadDies), 34 | ("testFatalErrorMethodsAreReplacedAfterTestFinishes", testFatalErrorMethodsAreReplacedAfterTestFinishes), 35 | ("testFatalErrorsDoNotContinueExecution", testFatalErrorsDoNotContinueExecution), 36 | ("testFatalErrorsSendExpectedErrors", testFatalErrorsSendExpectedErrors), 37 | ("testFatalErrorWithMessageWithoutCapturingError", testFatalErrorWithMessageWithoutCapturingError), 38 | ("testFatalErrorWithoutCapturingError", testFatalErrorWithoutCapturingError), 39 | ("testNoFatalErrorExpectationThreadDies", testNoFatalErrorExpectationThreadDies), 40 | ] 41 | } 42 | 43 | extension PreconditionTests { 44 | // DO NOT MODIFY: This is autogenerated, use: 45 | // `swift test --generate-linuxmain` 46 | // to regenerate. 47 | static let __allTests__PreconditionTests = [ 48 | ("testDefaultErrorIsABlankAnonymousError", testDefaultErrorIsABlankAnonymousError), 49 | ("testDefaultErrorWithStringIsAnAnonymousError", testDefaultErrorWithStringIsAnAnonymousError), 50 | ("testExpectingNoPreconditionFailure", testExpectingNoPreconditionFailure), 51 | ("testNoPreconditionFailureExpectationThreadDies", testNoPreconditionFailureExpectationThreadDies), 52 | ("testPreconditionExpectationThreadDies", testPreconditionExpectationThreadDies), 53 | ("testPreconditionFailureExpectationThreadDies", testPreconditionFailureExpectationThreadDies), 54 | ("testPreconditionFailuresDoNotContinueExecution", testPreconditionFailuresDoNotContinueExecution), 55 | ("testPreconditionFailuresSendExpectedErrors", testPreconditionFailuresSendExpectedErrors), 56 | ("testPreconditionFailureWithMessageWithoutCapturingError", testPreconditionFailureWithMessageWithoutCapturingError), 57 | ("testPreconditionFailureWithoutCapturingError", testPreconditionFailureWithoutCapturingError), 58 | ("testPreconditionMethodsAreReplacedAfterTestFinishes", testPreconditionMethodsAreReplacedAfterTestFinishes), 59 | ("testPreconditionsThatFailDoNotContinueExecution", testPreconditionsThatFailDoNotContinueExecution), 60 | ("testPreconditionsThatSucceedDoContinueExecution", testPreconditionsThatSucceedDoContinueExecution), 61 | ] 62 | } 63 | 64 | public func __allTests() -> [XCTestCaseEntry] { 65 | return [ 66 | testCase(AssertTests.__allTests__AssertTests), 67 | testCase(FatalErrorTests.__allTests__FatalErrorTests), 68 | testCase(PreconditionTests.__allTests__PreconditionTests), 69 | ] 70 | } 71 | #endif 72 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import ErrorAssertionsTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += ErrorAssertionsTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /muter.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude" : [ 3 | "Package.swift" 4 | ], 5 | "executable" : "/usr/bin/swift", 6 | "arguments": ["test"] 7 | } 8 | --------------------------------------------------------------------------------