Skip to content
Open
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
22 changes: 14 additions & 8 deletions Sources/Nimble/Expectation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ internal func execute<T>(_ expression: Expression<T>, _ style: ExpectationStyle,
msg.to = to
do {
let result = try matcher.satisfies(expression)
result.message.update(failureMessage: msg)
if msg.actualValue == "" {
msg.actualValue = "<\(stringify(try expression.evaluate()))>"
let pass = result.toBoolean(expectation: style)
if !pass {
result.message.update(failureMessage: msg)
if msg.actualValue == "" {
msg.actualValue = "<\(stringify(try expression.evaluate()))>"
}
}
return (result.toBoolean(expectation: style), msg)
return (pass, msg)
} catch let error {
msg.stringValue = "unexpected error thrown: <\(error)>"
return (false, msg)
Expand Down Expand Up @@ -39,11 +42,14 @@ internal func execute<T>(_ expression: AsyncExpression<T>, _ style: ExpectationS
msg.to = to
do {
let result = try await matcher.satisfies(expression)
result.message.update(failureMessage: msg)
if msg.actualValue == "" {
msg.actualValue = "<\(stringify(try await expression.evaluate()))>"
let pass = result.toBoolean(expectation: style)
if !pass {
result.message.update(failureMessage: msg)
if msg.actualValue == "" {
msg.actualValue = "<\(stringify(try await expression.evaluate()))>"
}
}
return (result.toBoolean(expectation: style), msg)
return (pass, msg)
} catch let error {
msg.stringValue = "unexpected error thrown: <\(error)>"
return (false, msg)
Expand Down
34 changes: 21 additions & 13 deletions Sources/Nimble/Matchers/AllPass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,38 @@ private func createMatcher<S: Sequence>(_ elementMatcher: Matcher<S.Element>) ->
)
}

var failure: ExpectationMessage = .expectedTo("all pass")
var messageProvider: () -> ExpectationMessage = {
.expectedTo("all pass")
}
for currentElement in actualValue {
let exp = Expression(
expression: { currentElement },
location: actualExpression.location
)
let matcherResult = try elementMatcher.satisfies(exp)
if matcherResult.status == .matches {
failure = matcherResult.message.prepended(expectation: "all ")
messageProvider = {
matcherResult.message.prepended(expectation: "all ")
}
} else {
failure = matcherResult.message
.replacedExpectation({ .expectedTo($0.expectedMessage) })
.wrappedExpectation(
before: "all ",
after: ", but failed first at element <\(stringify(currentElement))>"
+ " in <\(stringify(actualValue))>"
return MatcherResult(
status: .doesNotMatch,
message: matcherResult.message
.replacedExpectation({ .expectedTo($0.expectedMessage) })
.wrappedExpectation(
before: "all ",
after: ", but failed first at element <\(stringify(currentElement))>"
+ " in <\(stringify(actualValue))>"
)
)
return MatcherResult(status: .doesNotMatch, message: failure)
}
}
failure = failure.replacedExpectation({ expectation in
return .expectedTo(expectation.expectedMessage)
})
return MatcherResult(status: .matches, message: failure)
messageProvider = { [messageProvider] in
messageProvider().replacedExpectation({ expectation in
return .expectedTo(expectation.expectedMessage)
})
}
return MatcherResult(status: .matches, message: messageProvider())
}
}

Expand Down
34 changes: 21 additions & 13 deletions Sources/Nimble/Matchers/AsyncAllPass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,37 @@ private func createMatcher<S: Sequence>(_ elementMatcher: AsyncMatcher<S.Element
)
}

var failure: ExpectationMessage = .expectedTo("all pass")
var messageProvider: () -> ExpectationMessage = {
.expectedTo("all pass")
}
for currentElement in actualValue {
let exp = AsyncExpression(
expression: { currentElement },
location: actualExpression.location
)
let matcherResult = try await elementMatcher.satisfies(exp)
if matcherResult.status == .matches {
failure = matcherResult.message.prepended(expectation: "all ")
messageProvider = {
matcherResult.message.prepended(expectation: "all ")
}
} else {
failure = matcherResult.message
.replacedExpectation({ .expectedTo($0.expectedMessage) })
.wrappedExpectation(
before: "all ",
after: ", but failed first at element <\(stringify(currentElement))>"
+ " in <\(stringify(actualValue))>"
return MatcherResult(
status: .doesNotMatch,
message: matcherResult.message
.replacedExpectation({ .expectedTo($0.expectedMessage) })
.wrappedExpectation(
before: "all ",
after: ", but failed first at element <\(stringify(currentElement))>"
+ " in <\(stringify(actualValue))>"
)
)
return MatcherResult(status: .doesNotMatch, message: failure)
}
}
failure = failure.replacedExpectation({ expectation in
return .expectedTo(expectation.expectedMessage)
})
return MatcherResult(status: .matches, message: failure)
messageProvider = { [messageProvider] in
messageProvider().replacedExpectation({ expectation in
return .expectedTo(expectation.expectedMessage)
})
}
return MatcherResult(status: .matches, message: messageProvider())
}
}
38 changes: 20 additions & 18 deletions Sources/Nimble/Matchers/BeAKindOf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,20 @@ private func matcherMessage(forClass expectedClass: AnyClass) -> String {
/// A Nimble matcher that succeeds when the actual value is an instance of the given class.
public func beAKindOf<T, U>(_ expectedType: T.Type) -> Matcher<U> {
return Matcher.define { actualExpression in
let message: ExpectationMessage

let instance = try actualExpression.evaluate()
guard let validInstance = instance else {
message = .expectedCustomValueTo(matcherMessage(forType: expectedType), actual: "<nil>")
return MatcherResult(status: .fail, message: message)
return MatcherResult(
status: .fail,
message: .expectedCustomValueTo(matcherMessage(forType: expectedType), actual: "<nil>")
)
}
message = .expectedCustomValueTo(
"be a kind of \(String(describing: expectedType))",
actual: "<\(String(describing: type(of: validInstance))) instance>"
)

return MatcherResult(
bool: validInstance is T,
message: message
message: .expectedCustomValueTo(
"be a kind of \(String(describing: expectedType))",
actual: "<\(String(describing: type(of: validInstance))) instance>"
)
)
}
}
Expand All @@ -34,25 +33,28 @@ import class Foundation.NSObject
/// @see beAnInstanceOf if you want to match against the exact class
public func beAKindOf(_ expectedClass: AnyClass) -> Matcher<NSObject> {
return Matcher.define { actualExpression in
let message: ExpectationMessage
let status: MatcherStatus

let instance = try actualExpression.evaluate()
if let validInstance = instance {
status = MatcherStatus(bool: instance != nil && instance!.isKind(of: expectedClass))
message = .expectedCustomValueTo(
matcherMessage(forClass: expectedClass),
actual: "<\(String(describing: type(of: validInstance))) instance>"
return MatcherResult(
status: status,
message: .expectedCustomValueTo(
matcherMessage(forClass: expectedClass),
actual: "<\(String(describing: type(of: validInstance))) instance>"
)
)
} else {
status = .fail
message = .expectedCustomValueTo(
matcherMessage(forClass: expectedClass),
actual: "<nil>"
return MatcherResult(
status: status,
message: .expectedCustomValueTo(
matcherMessage(forClass: expectedClass),
actual: "<nil>"
)
)
}

return MatcherResult(status: status, message: message)
}
}

Expand Down
22 changes: 9 additions & 13 deletions Sources/Nimble/Matchers/BeAnInstanceOf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,41 @@ import Foundation

/// A Nimble matcher that succeeds when the actual value is an _exact_ instance of the given class.
public func beAnInstanceOf<T, U>(_ expectedType: T.Type) -> Matcher<U> {
let errorMessage = "be an instance of \(String(describing: expectedType))"
return Matcher.define { actualExpression in
let instance = try actualExpression.evaluate()
guard let validInstance: Any = instance else {
return MatcherResult(
status: .doesNotMatch,
message: .expectedActualValueTo(errorMessage)
message: .expectedActualValueTo("be an instance of \(String(describing: expectedType))")
)
}

let actualString = "<\(String(describing: type(of: validInstance))) instance>"

return MatcherResult(
status: MatcherStatus(bool: type(of: validInstance) == expectedType),
message: .expectedCustomValueTo(errorMessage, actual: actualString)
message: .expectedCustomValueTo(
"be an instance of \(String(describing: expectedType))",
actual: "<\(String(describing: type(of: validInstance))) instance>"
)
)
}
}

/// A Nimble matcher that succeeds when the actual value is an instance of the given class.
/// @see beAKindOf if you want to match against subclasses
public func beAnInstanceOf(_ expectedClass: AnyClass) -> Matcher<NSObject> {
let errorMessage = "be an instance of \(String(describing: expectedClass))"
return Matcher.define { actualExpression in
let instance = try actualExpression.evaluate()
let actualString: String
if let validInstance = instance {
actualString = "<\(String(describing: type(of: validInstance))) instance>"
} else {
actualString = "<nil>"
}
#if canImport(Darwin)
let matches = instance != nil && instance!.isMember(of: expectedClass)
#else
let matches = instance != nil && type(of: instance!) == expectedClass
#endif
return MatcherResult(
status: MatcherStatus(bool: matches),
message: .expectedCustomValueTo(errorMessage, actual: actualString)
message: .expectedCustomValueTo(
"be an instance of \(String(describing: expectedClass))",
actual: instance.map { "<\(String(describing: type(of: $0))) instance>" } ?? "<nil>"
)
)
}
}
Expand Down
15 changes: 9 additions & 6 deletions Sources/Nimble/Matchers/BeCloseTo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ internal func isCloseTo<Value: FloatingPoint>(
expectedValue: Value,
delta: Value
) -> MatcherResult {
let errorMessage = "be close to <\(stringify(expectedValue))> (within \(stringify(delta)))"
return MatcherResult(
bool: actualValue != nil &&
abs(actualValue! - expectedValue) < delta,
message: .expectedCustomValueTo(errorMessage, actual: "<\(stringify(actualValue))>")
message: .expectedCustomValueTo(
"be close to <\(stringify(expectedValue))> (within \(stringify(delta)))",
actual: "<\(stringify(actualValue))>"
)
)
}

Expand All @@ -23,11 +25,13 @@ internal func isCloseTo(
expectedValue: NMBDoubleConvertible,
delta: Double
) -> MatcherResult {
let errorMessage = "be close to <\(stringify(expectedValue))> (within \(stringify(delta)))"
return MatcherResult(
bool: actualValue != nil &&
abs(actualValue!.doubleValue - expectedValue.doubleValue) < delta,
message: .expectedCustomValueTo(errorMessage, actual: "<\(stringify(actualValue))>")
message: .expectedCustomValueTo(
"be close to <\(stringify(expectedValue))> (within \(stringify(delta)))",
actual: "<\(stringify(actualValue))>"
)
)
}

Expand Down Expand Up @@ -100,8 +104,7 @@ public func beCloseTo<Value: FloatingPoint, Values: Collection>(
_ expectedValues: Values,
within delta: Value = defaultDelta()
) -> Matcher<Values> where Values.Element == Value {
let errorMessage = "be close to <\(stringify(expectedValues))> (each within \(stringify(delta)))"
return Matcher.simple(errorMessage) { actualExpression in
return Matcher.simple("be close to <\(stringify(expectedValues))> (each within \(stringify(delta)))") { actualExpression in
guard let actualValues = try actualExpression.evaluate() else {
return .doesNotMatch
}
Expand Down
6 changes: 2 additions & 4 deletions Sources/Nimble/Matchers/BeLessThan.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/// A Nimble matcher that succeeds when the actual value is less than the expected value.
public func beLessThan<T: Comparable>(_ expectedValue: T?) -> Matcher<T> {
let message = "be less than <\(stringify(expectedValue))>"
return Matcher.simple(message) { actualExpression in
return Matcher.simple("be less than <\(stringify(expectedValue))>") { actualExpression in
guard let actual = try actualExpression.evaluate(), let expected = expectedValue else { return .fail }

return MatcherStatus(bool: actual < expected)
Expand All @@ -21,8 +20,7 @@ import enum Foundation.ComparisonResult

/// A Nimble matcher that succeeds when the actual value is less than the expected value.
public func beLessThan<T: NMBComparable>(_ expectedValue: T?) -> Matcher<T> {
let message = "be less than <\(stringify(expectedValue))>"
return Matcher.simple(message) { actualExpression in
return Matcher.simple("be less than <\(stringify(expectedValue))>") { actualExpression in
let actualValue = try actualExpression.evaluate()
let matches = actualValue != nil && actualValue!.NMB_compare(expectedValue) == ComparisonResult.orderedAscending
return MatcherStatus(bool: matches)
Expand Down
36 changes: 28 additions & 8 deletions Sources/Nimble/Matchers/ElementsEqual.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@
public func elementsEqual<Seq1: Sequence, Seq2: Sequence>(
_ expectedValue: Seq2?
) -> Matcher<Seq1> where Seq1.Element: Equatable, Seq1.Element == Seq2.Element {
return Matcher.define("elementsEqual <\(stringify(expectedValue))>") { (actualExpression, msg) in
return Matcher.define { actualExpression in
let actualValue = try actualExpression.evaluate()
switch (expectedValue, actualValue) {
case (nil, _?):
return MatcherResult(status: .fail, message: msg.appendedBeNilHint())
return MatcherResult(
status: .fail,
message: .expectedActualValueTo("elementsEqual <\(stringify(expectedValue))>")
.appendedBeNilHint()
)
case (nil, nil), (_, nil):
return MatcherResult(status: .fail, message: msg)
return MatcherResult(
status: .fail,
message: .expectedActualValueTo("elementsEqual <\(stringify(expectedValue))>")
)
case (let expected?, let actual?):
let matches = expected.elementsEqual(actual)
return MatcherResult(bool: matches, message: msg)
return MatcherResult(
bool: matches,
message: .expectedActualValueTo("elementsEqual <\(stringify(expectedValue))>")
)
}
}
}
Expand All @@ -27,16 +37,26 @@ public func elementsEqual<Seq1: Sequence, Seq2: Sequence>(
_ expectedValue: Seq2?,
by areEquivalent: @escaping (Seq1.Element, Seq2.Element) -> Bool
) -> Matcher<Seq1> {
return Matcher.define("elementsEqual <\(stringify(expectedValue))>") { (actualExpression, msg) in
return Matcher.define { actualExpression in
let actualValue = try actualExpression.evaluate()
switch (expectedValue, actualValue) {
case (nil, _?):
return MatcherResult(status: .fail, message: msg.appendedBeNilHint())
return MatcherResult(
status: .fail,
message: .expectedActualValueTo("elementsEqual <\(stringify(expectedValue))>")
.appendedBeNilHint()
)
case (nil, nil), (_, nil):
return MatcherResult(status: .fail, message: msg)
return MatcherResult(
status: .fail,
message: .expectedActualValueTo("elementsEqual <\(stringify(expectedValue))>")
)
case (let expected?, let actual?):
let matches = actual.elementsEqual(expected, by: areEquivalent)
return MatcherResult(bool: matches, message: msg)
return MatcherResult(
bool: matches,
message: .expectedActualValueTo("elementsEqual <\(stringify(expectedValue))>")
)
}
}
}
Loading