Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,27 @@ extension Templates {
{{ container.accessibility }} struct __VerificationProxy_{{ container.name }}: Cuckoo.VerificationProxy {
private let cuckoo_manager: Cuckoo.MockManager
private let callMatcher: Cuckoo.CallMatcher
private let continuation: Cuckoo.Continuation
private let sourceLocation: Cuckoo.SourceLocation

{{ container.accessibility }} init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) {
{{ container.accessibility }} init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, continuation: Cuckoo.Continuation, sourceLocation: Cuckoo.SourceLocation) {
self.cuckoo_manager = manager
self.callMatcher = callMatcher
self.continuation = continuation
self.sourceLocation = sourceLocation
}

{{ container.accessibility }} init(manager: Cuckoo.MockManager, callMatcher: Cuckoo.CallMatcher, sourceLocation: Cuckoo.SourceLocation) {
self.init(manager: manager, callMatcher: callMatcher, continuation: Cuckoo.ContinuationOnlyOnce(), sourceLocation: sourceLocation)
}

{% for property in container.properties %}
{{ property.unavailablePlatformsCheck }}
{% for attribute in property.attributes %}
{{ attribute.text }}
{% endfor %}
var {{property.name}}: Cuckoo.{{property.verifyType}}<{% if property.isReadOnly %}{{property.type|genericSafe}}{% else %}{{property.nonOptionalType|genericSafe}}{% endif %}> {
return .init(manager: cuckoo_manager, name: "{{property.name}}", callMatcher: callMatcher, sourceLocation: sourceLocation)
return .init(manager: cuckoo_manager, name: "{{property.name}}", callMatcher: callMatcher, continuation: continuation, sourceLocation: sourceLocation)
}
{% if property.hasUnavailablePlatforms %}
#endif
Expand All @@ -44,7 +50,7 @@ extension Templates {
return cuckoo_manager.verify(
\"\"\"
{{method.fullyQualifiedName}}
\"\"\", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
\"\"\", callMatcher: callMatcher, parameterMatchers: matchers, continuation: continuation, sourceLocation: sourceLocation)
}
{% if method.hasUnavailablePlatforms %}
#endif
Expand Down
54 changes: 54 additions & 0 deletions Source/Continuation/Continuation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Continuation.swift
// Cuckoo
//
// Created by Shoto Kobayashi on 03/09/2022.
//

import Foundation

public protocol Continuation {
var exitOnSuccess: Bool { get }

func check() -> Bool

func wait()

func times(_ count: Int) -> VerificationSpec

func never() -> VerificationSpec

func atLeastOnce() -> VerificationSpec

func atLeast(_ count: Int) -> VerificationSpec

func atMost(_ count: Int) -> VerificationSpec

func with(_ callMatcher: CallMatcher) -> VerificationSpec
}

public extension Continuation {
func times(_ count: Int) -> VerificationSpec {
VerificationSpec(callMatcher: Cuckoo.times(count), continuation: self)
}

func never() -> VerificationSpec {
VerificationSpec(callMatcher: Cuckoo.times(0), continuation: self)
}

func atLeastOnce() -> VerificationSpec {
VerificationSpec(callMatcher: Cuckoo.atLeast(1), continuation: self)
}

func atLeast(_ count: Int) -> VerificationSpec {
VerificationSpec(callMatcher: Cuckoo.atLeast(count), continuation: self)
}

func atMost(_ count: Int) -> VerificationSpec {
VerificationSpec(callMatcher: Cuckoo.atMost(count), continuation: self)
}

func with(_ callMatcher: CallMatcher) -> VerificationSpec {
VerificationSpec(callMatcher: callMatcher, continuation: self)
}
}
17 changes: 17 additions & 0 deletions Source/Continuation/ContinuationAfterDelay.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// ContinuationAfterDelay.swift
// Cuckoo
//
// Created by Shoto Kobayashi on 03/09/2022.
//


import Foundation

public class ContinueationAfterDelay: NSObject, ContinuationWrapper {
public let wrappedContinuation: ContinuationOverTime

public init(delayDuration: TimeInterval, waitingDuration: TimeInterval) {
wrappedContinuation = ContinuationOverTime(duration: delayDuration, waitingDuration: waitingDuration, exitOnSuccess: false)
}
}
22 changes: 22 additions & 0 deletions Source/Continuation/ContinuationFunctions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// ContinuationFunctions.swift
// Cuckoo
//
// Created by Shoto Kobayashi on 03/09/2022.
//

import Foundation

public func timeout(_ timeoutDuration: TimeInterval, waitingDuration: TimeInterval = 0.01) -> ContinuationWithTimeout {
ContinuationWithTimeout(
timeoutDuration: timeoutDuration,
waitingDuration: waitingDuration
)
}

public func after(_ delayDuration: TimeInterval, waitingDuration: TimeInterval = 0.01) -> ContinueationAfterDelay {
ContinueationAfterDelay(
delayDuration: delayDuration,
waitingDuration: waitingDuration
)
}
29 changes: 29 additions & 0 deletions Source/Continuation/ContinuationOnlyOnce.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// ContinuationOnlyOnce.swift
// Cuckoo
//
// Created by Shoto Kobayashi on 03/09/2022.
//

import Foundation

public class ContinuationOnlyOnce: NSObject, Continuation {
public let exitOnSuccess = true

private var isAlreadyChecked = false

public override init() {
super.init()
}

public func check() -> Bool {
guard !isAlreadyChecked else {
return false
}
isAlreadyChecked = true
return true
}

public func wait() {
}
}
38 changes: 38 additions & 0 deletions Source/Continuation/ContinuationOverTime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// ContinuationOverTime.swift
// Cuckoo
//
// Created by Shoto Kobayashi on 03/09/2022.
//

import Foundation

public class ContinuationOverTime: NSObject, Continuation {
public let duration: TimeInterval
public let waitingDuration: TimeInterval
public let exitOnSuccess: Bool

private var start: Date?

public init(duration: TimeInterval, waitingDuration: TimeInterval, exitOnSuccess: Bool) {
self.duration = duration
self.waitingDuration = waitingDuration
self.exitOnSuccess = exitOnSuccess
super.init()
}

public func check() -> Bool {
if start == nil {
start = Date()
}
return -start!.timeIntervalSinceNow <= duration
}

public func wait() {
Thread.sleep(forTimeInterval: waitingDuration)
}

public func times(_ count: Int) -> VerificationSpec {
VerificationSpec(callMatcher: Cuckoo.times(count), continuation: self)
}
}
16 changes: 16 additions & 0 deletions Source/Continuation/ContinuationWithTimeout.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// ContinuationWithTimeout.swift
// Cuckoo
//
// Created by Shoto Kobayashi on 03/09/2022.
//

import Foundation

public class ContinuationWithTimeout: NSObject, ContinuationWrapper {
public let wrappedContinuation: ContinuationOverTime

public init(timeoutDuration: TimeInterval, waitingDuration: TimeInterval) {
wrappedContinuation = ContinuationOverTime(duration: timeoutDuration, waitingDuration: waitingDuration, exitOnSuccess: true)
}
}
28 changes: 28 additions & 0 deletions Source/Continuation/ContinuationWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// ContinuationWrapper.swift
// Cuckoo
//
// Created by Shoto Kobayashi on 03/09/2022.
//

import Foundation

public protocol ContinuationWrapper: Continuation {
associatedtype WrappedContinuation: Continuation

var wrappedContinuation: WrappedContinuation { get }
}

public extension ContinuationWrapper {
var exitOnSuccess: Bool {
wrappedContinuation.exitOnSuccess
}

func check() -> Bool {
wrappedContinuation.check()
}

func wait() {
wrappedContinuation.wait()
}
}
14 changes: 13 additions & 1 deletion Source/CuckooFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@ public func when<F/*: BaseStubFunctionTrait*/>(_ function: F) -> F {
return function
}

public func verify<M: Mock>(_ mock: M, _ callMatcher: CallMatcher, _ continuation: Continuation, file: StaticString = #file, line: UInt = #line) -> M.Verification {
return mock.getVerificationProxy(callMatcher, continuation, sourceLocation: (file, line))
}

/// Creates object used for verification of calls.
public func verify<M: Mock>(_ mock: M, _ callMatcher: CallMatcher = times(1), file: StaticString = #file, line: UInt = #line) -> M.Verification {
return mock.getVerificationProxy(callMatcher, sourceLocation: (file, line))
return verify(mock, callMatcher, ContinuationOnlyOnce(), file: file, line: line)
}

public func verify<M: Mock>(_ mock: M, _ continuation: Continuation, file: StaticString = #file, line: UInt = #line) -> M.Verification {
return verify(mock, times(1), continuation, file: file, line: line)
}

public func verify<M: Mock>(_ mock: M, _ verificationSpec: VerificationSpec, file: StaticString = #file, line: UInt = #line) -> M.Verification {
return verify(mock, verificationSpec.callMatcher, verificationSpec.continuation, file: file, line: line)
}

/// Clears all invocations and stubs of mocks.
Expand Down
29 changes: 20 additions & 9 deletions Source/Matching/CallMatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,44 @@ public struct CallMatcher {
public let name: String

private let matchesFunction: ([StubCall]) -> Bool
private let canRecoverFromFailureFunction: ([StubCall]) -> Bool

public init(name: String, matchesFunction: @escaping ([StubCall]) -> Bool) {
public init(name: String, matchesFunction: @escaping ([StubCall]) -> Bool, canRecoverFromFailureFunction: @escaping ([StubCall]) -> Bool) {
self.name = name
self.matchesFunction = matchesFunction
self.canRecoverFromFailureFunction = canRecoverFromFailureFunction
}

public init(name: String, numberOfExpectedCalls: Int, compareCallsFunction: @escaping (_ expected: Int, _ actual: Int) -> Bool) {
self.init(name: name) {
return compareCallsFunction(numberOfExpectedCalls, $0.count)

public init(
name: String,
numberOfExpectedCalls: Int,
compareCallsFunction: @escaping (_ expected: Int, _ actual: Int) -> Bool,
canRecoverFromFailureFunction: @escaping (_ expected: Int, _ actual: Int) -> Bool
) {
self.init(name: name, matchesFunction: { compareCallsFunction(numberOfExpectedCalls, $0.count) }) {
canRecoverFromFailureFunction(numberOfExpectedCalls, $0.count)
}
}

public func matches(_ calls: [StubCall]) -> Bool {
return matchesFunction(calls)
}

public func canRecoverFromFailure(_ calls: [StubCall]) -> Bool {
canRecoverFromFailureFunction(calls)
}

public func or(_ otherMatcher: CallMatcher) -> CallMatcher {
let name = "either \(self.name) or \(otherMatcher.name)"
return CallMatcher(name: name) {
return self.matches($0) || otherMatcher.matches($0)
return CallMatcher(name: name, matchesFunction: { self.matches($0) || otherMatcher.matches($0) }) {
self.canRecoverFromFailure($0) || otherMatcher.canRecoverFromFailureFunction($0)
}
}

public func and(_ otherMatcher: CallMatcher) -> CallMatcher {
let name = "both \(self.name) and \(otherMatcher.name)"
return CallMatcher(name: name) {
return self.matches($0) && otherMatcher.matches($0)
return CallMatcher(name: name, matchesFunction: { self.matches($0) && otherMatcher.matches($0) }) {
self.canRecoverFromFailure($0) && otherMatcher.canRecoverFromFailureFunction($0)
}
}
}
6 changes: 3 additions & 3 deletions Source/Matching/CallMatcherFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/// Returns a matcher ensuring a call was made **`count`** times.
public func times(_ count: Int) -> CallMatcher {
let name = count == 0 ? "never" : "\(count) times"
return CallMatcher(name: name, numberOfExpectedCalls: count, compareCallsFunction: ==)
return CallMatcher(name: name, numberOfExpectedCalls: count, compareCallsFunction: ==, canRecoverFromFailureFunction: >=)
}

/// Returns a matcher ensuring no call was made.
Expand All @@ -24,10 +24,10 @@ public func atLeastOnce() -> CallMatcher {

/// Returns a matcher ensuring call was made at least `count` times.
public func atLeast(_ count: Int) -> CallMatcher {
return CallMatcher(name: "at least \(count) times", numberOfExpectedCalls: count, compareCallsFunction: <=)
return CallMatcher(name: "at least \(count) times", numberOfExpectedCalls: count, compareCallsFunction: <=, canRecoverFromFailureFunction: <=)
}

/// Returns a matcher ensuring call was made at most `count` times.
public func atMost(_ count: Int) -> CallMatcher {
return CallMatcher(name: "at most \(count) times",numberOfExpectedCalls: count, compareCallsFunction: >=)
return CallMatcher(name: "at most \(count) times",numberOfExpectedCalls: count, compareCallsFunction: >=, canRecoverFromFailureFunction: >=)
}
6 changes: 6 additions & 0 deletions Source/Mock/Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public protocol Mock: HasMockManager, HasSuperclass {

func getVerificationProxy(_ callMatcher: CallMatcher, sourceLocation: SourceLocation) -> Verification

func getVerificationProxy(_ callMatcher: CallMatcher, _ continuation: Continuation, sourceLocation: SourceLocation) -> Verification

func enableDefaultImplementation(_ stub: MocksType)
}

Expand All @@ -35,6 +37,10 @@ public extension Mock {
return Verification(manager: cuckoo_manager, callMatcher: callMatcher, sourceLocation: sourceLocation)
}

func getVerificationProxy(_ callMatcher: CallMatcher, _ continuation: Continuation, sourceLocation: SourceLocation) -> Verification {
return Verification(manager: cuckoo_manager, callMatcher: callMatcher, continuation: continuation, sourceLocation: sourceLocation)
}

func withEnabledDefaultImplementation(_ stub: MocksType) -> Self {
enableDefaultImplementation(stub)
return self
Expand Down
2 changes: 2 additions & 0 deletions Source/Mock/VerificationProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@

public protocol VerificationProxy {
init(manager: MockManager, callMatcher: CallMatcher, sourceLocation: SourceLocation)

init(manager: MockManager, callMatcher: CallMatcher, continuation: Continuation, sourceLocation: SourceLocation)
}
Loading