Functions.swift 7.05 KB
Newer Older
Drew's avatar
Drew committed
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright (c) 2016 Drew Crawford.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Drew's avatar
Drew committed
14 15

//MARK: Functions
Drew's avatar
Drew committed
16
private func compare<T: Sequence>(_ a: T,  _ b: T) -> Bool where T.Iterator.Element: Equatable {
Drew's avatar
Drew committed
17 18 19 20 21 22 23 24 25 26 27
    var iteratorB = b.makeIterator()
    for itemA in a {
        let itemB = iteratorB.next()
        if itemB != itemA {
            return false
        }
    }
    //We've concluded iterating A.  Did B have more elements?
    if let _ = iteratorB.next() { return false }
    return true
}
Drew's avatar
Drew committed
28 29 30 31 32 33 34 35

private func compareDict<K: Equatable, V: Equatable> (_ a: [K:V], _ b: [K:V]) -> Bool {
    if a.count != b.count { return false } 
    for k in a.keys {
        if a[k] != b[k] { return false }
    }
    return true
}
Drew's avatar
Drew committed
36
extension CarolineTest {
Drew's avatar
Drew committed
37

Drew's avatar
Drew committed
38
    private final func setFailed() {
Drew's avatar
Drew committed
39 40 41 42 43 44 45 46 47 48
        var state = CarolineState(test: self)
        if self.expectFailure {
            state.outcome = .XFailed
        }
        else {
            state.outcome = .Failed
        }
        state.commit()
    }

Drew's avatar
Drew committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    private final func logFailureIfNecessary(_ message: @autoclosure() -> String, note: String? = nil, file: String?, line: Int?) {
        let state = CarolineState(test: self)
        if state.outcome == .Failed {
            if let f = file, let l = line {
                print("Test failed at \(f):\(l) - \(message())")
            }
            else {
                print("Test failed \(message())")
            }
            if let n = note {
                print("note: \(n)")
            }
        }
    }

    ///Fail the test.
    ///- parameter error: An error explaining the reason for failure
    internal final func fail(_ error: Error,  note: String?, file: String?, line: Int?) {
        self.setFailed()
        self.logFailureIfNecessary("\(error)", note: note, file: file, line: line)
    }

    ///Fail the test.
    ///- parameter message: A reason for failure
    internal final func fail(_ message: @autoclosure() -> String, note: String?, file: String?, line: Int?) {
        self.setFailed()
        self.logFailureIfNecessary(message, note: note, file: file, line: line)
    }

    ///Fail the test.
    ///- parameter error: An error explaining the reason for failure
    public final func fail(_ error: Error, file: String = #file, line: Int = #line) {
        self.fail(error, note: nil, file: file, line: line)
    }

    public final func fail(_ message: @autoclosure() -> String = "Test failed", file: String? = #file, line: Int? = #line) {
        self.fail(message, note: nil, file: file, line: line)
    }


Drew's avatar
Drew committed
89 90 91
    ///Assert a boolean expression
    ///- parameter condition: A condition to assert
    ///- message: A reason for failure
92
    public final func assert(_ condition: @autoclosure () -> Bool,  _ message: @autoclosure () -> String = "Assertion failed", file: String = #file, line: Int = #line) {
Drew's avatar
Drew committed
93
        if !condition() {
Drew's avatar
Drew committed
94
            self.fail(message(), file: file, line: line)
Drew's avatar
Drew committed
95 96
        }
    }
97 98 99

    //Equatable and SequenceType<Equatable>

Drew's avatar
Drew committed
100 101
    ///Assert that two objects are equal
    ///- parameter message: A reason for failure
Drew's avatar
Drew committed
102
    public final func assert<T: Equatable>(_ a: T?,  equals b: T?, _ message: @autoclosure () -> String = "Assertion failed", file: String = #file, line: Int = #line) {
103
        if a != b {
Drew's avatar
Drew committed
104
            self.fail("\(message()) - \(cdump(a)) != \(cdump(b))", file: file, line: line)
Drew's avatar
Drew committed
105 106
        }
    }
Drew's avatar
Drew committed
107 108
    ///Assert that two objects aren't equal
    ///- parameter message: A reason for failure
Drew's avatar
Drew committed
109
    public final func assert<T: Equatable>(_ a: T?,  notEqual b: T?, _ message: @autoclosure () -> String = "Assertion failed", file: String = #file, line: Int = #line) {
Drew's avatar
Drew committed
110
        if a == b {
Drew's avatar
Drew committed
111
            self.fail("\(message()) - \(cdump(a)) == \(cdump(b))", file: file, line: line)
112 113
        }
    }
Drew's avatar
Drew committed
114 115
    ///Assert that two sequences are equal
    ///- parameter message: A reason for failure
Drew's avatar
Drew committed
116
    public final func assert<T:Sequence>(_ a: T,  equals b: T, _ message: @autoclosure () -> String = "Assertion failed", file: String = #file, line: Int = #line) where T.Iterator.Element: Equatable {
Drew's avatar
Drew committed
117
        if !compare(a,b) {
Drew's avatar
Drew committed
118
            self.fail("\(message()) - \(cdump(a)) != \(cdump(b))", file: file, line: line)
Drew's avatar
Drew committed
119 120
        }
    }
Drew's avatar
Drew committed
121 122
    ///Assert that two sequences aren't equal
    ///- parameter message: A reason for failure
Drew's avatar
Drew committed
123
    public final func assert<T:Sequence>(_ a: T,  notEqual b: T, _ message: @autoclosure () -> String = "Assertion failed", file: String = #file, line: Int = #line) where T.Iterator.Element: Equatable {
Drew's avatar
Drew committed
124
        if compare(a,b) {
Drew's avatar
Drew committed
125
            self.fail("\(message()) - \(cdump(a)) == \(cdump(b))", file: file, line: line)
126 127
        }
    }
Drew's avatar
Drew committed
128 129 130

    ///Assert that two dictionaries are equal
    ///- parameter mesage: A reason for failure
Drew's avatar
Drew committed
131
    public final func assert<K,V>(_ a: [K:V],  equals b: [K:V], _ message: @autoclosure () -> String = "Assertion failed", file: String = #file, line: Int = #line) where K: Equatable, V: Equatable {
Drew's avatar
Drew committed
132 133 134 135 136 137 138
        if !compareDict(a,b) {
            self.fail("\(message()) - \(cdump(a)) != \(cdump(b))", file: file, line: line)
        }
    }

    ///Assert that two dictionaries aren't equal
    ///- parameter mesage: A reason for failure
Drew's avatar
Drew committed
139
    public final func assert<K,V>(_ a: [K:V],  notEqual b: [K:V], _ message: @autoclosure () -> String = "Assertion failed", file: String = #file, line: Int = #line) where K: Equatable, V: Equatable {
Drew's avatar
Drew committed
140 141 142 143 144
        if compareDict(a,b) {
            self.fail("\(message()) - \(cdump(a)) == \(cdump(b))", file: file, line: line)
        }
    }

145 146 147 148 149 150 151 152 153 154
    ///Check that a function does not return an error
    public final func check(_ message: @autoclosure() -> String = "Error thrown", file: String = #file, line: Int = #line, block: () throws -> ()) {
        do {
            try block()
        }
        catch {
            self.fail("\(message()) - \(error)", file: file, line: line)
        }
    }

155

Drew's avatar
Drew committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169
    ///Assert that the passed closure doesn't throw
    ///- returns: nil if the calling closure throws, otherwise the return value.
    ///It's not okay to force-unwrap this, use a construction like
    ///guard let a = self.dontThrow(closure: { /* */ }) else { return }
    public final func dontThrow<T>(_ message: @autoclosure () -> String = "Unexpectedly threw", file: String = #file, line: Int = #line, closure: () throws -> T) -> T? {
        do {
            return try closure()
        }
        catch {
            self.fail(message() + "\(error)", file: file, line: line)
            return nil
        }
    }

170 171
}

Drew's avatar
Drew committed
172 173 174 175 176 177 178 179 180
private func cdump(_ t: Any) -> String {
    if let t = t as? CustomDebugStringConvertible {
        return t.debugDescription
    }
    else if let t = t as? CustomStringConvertible {
        return t.description
    }
    return "\(t)"
}