Compare commits
17 Commits
master
...
feature/ge
Author | SHA1 | Date |
---|---|---|
![]() |
76f85110ac | |
![]() |
e3ac95f5f0 | |
![]() |
b73debd32b | |
![]() |
a2f9e68e72 | |
![]() |
807dc8953b | |
![]() |
f236c5e3f6 | |
![]() |
e078d4db98 | |
![]() |
9180694e93 | |
![]() |
335a2175c1 | |
![]() |
a7a779c9e2 | |
![]() |
52a69196dc | |
![]() |
ea9bd46c23 | |
![]() |
4b403883ea | |
![]() |
417075a94a | |
![]() |
ba434e6e91 | |
![]() |
9e965f55e2 | |
![]() |
0944bca05b |
|
@ -141,6 +141,16 @@
|
|||
58C54612D0DB47A284F29AD5 /* ExcludedStubTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C54B2B810BC9284205CB16 /* ExcludedStubTest.swift */; };
|
||||
58C54C0D4A65BCDC47704C0B /* ExcludedTestClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C54D12B64AF3169FC8A1A3 /* ExcludedTestClass.swift */; };
|
||||
58C54E2CB6689135915CFC3E /* ExcludedStubTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C54B2B810BC9284205CB16 /* ExcludedStubTest.swift */; };
|
||||
680894BF21ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */; };
|
||||
680894C021ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */; };
|
||||
680F09C92202F46A005F5C1A /* CollisionClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680F09C82202F46A005F5C1A /* CollisionClasses.swift */; };
|
||||
680F09CA2202F46A005F5C1A /* CollisionClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680F09C82202F46A005F5C1A /* CollisionClasses.swift */; };
|
||||
6861B32921A31DAC002EC2DA /* GenericClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B30B21A31DA2002EC2DA /* GenericClass.swift */; };
|
||||
6861B32A21A31DAD002EC2DA /* GenericClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B30B21A31DA2002EC2DA /* GenericClass.swift */; };
|
||||
6861B32C21A32279002EC2DA /* GenericProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B32B21A32279002EC2DA /* GenericProtocol.swift */; };
|
||||
6861B32D21A32279002EC2DA /* GenericProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6861B32B21A32279002EC2DA /* GenericProtocol.swift */; };
|
||||
6896352E21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */; };
|
||||
6896352F21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -242,6 +252,20 @@
|
|||
remoteGlobalIDString = "FileKit::FileKit::Product";
|
||||
remoteInfo = FileKit;
|
||||
};
|
||||
6861B32521A31DA3002EC2DA /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 18E2A3401FBB3FDD0058FEC5 /* CuckooGenerator.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = "SourceKitten::Clang_C::Product";
|
||||
remoteInfo = Clang_C;
|
||||
};
|
||||
6861B32721A31DA3002EC2DA /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 18E2A3401FBB3FDD0058FEC5 /* CuckooGenerator.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = "SourceKitten::SourceKit::Product";
|
||||
remoteInfo = SourceKit;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -331,6 +355,11 @@
|
|||
18E2A3971FBB43E60058FEC5 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = "<group>"; };
|
||||
58C54B2B810BC9284205CB16 /* ExcludedStubTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExcludedStubTest.swift; sourceTree = "<group>"; };
|
||||
58C54D12B64AF3169FC8A1A3 /* ExcludedTestClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExcludedTestClass.swift; sourceTree = "<group>"; };
|
||||
680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericClassTest.swift; sourceTree = "<group>"; };
|
||||
680F09C82202F46A005F5C1A /* CollisionClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollisionClasses.swift; sourceTree = "<group>"; };
|
||||
6861B30B21A31DA2002EC2DA /* GenericClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericClass.swift; sourceTree = "<group>"; };
|
||||
6861B32B21A32279002EC2DA /* GenericProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericProtocol.swift; sourceTree = "<group>"; };
|
||||
6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericProtocolTest.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -500,6 +529,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
18E2A2601FBB1C330058FEC5 /* ClassTest.swift */,
|
||||
680894BE21ABFDCB00C8D2EF /* GenericClassTest.swift */,
|
||||
18E2A2611FBB1C330058FEC5 /* CuckooFunctionsTest.swift */,
|
||||
18E2A2621FBB1C330058FEC5 /* DefaultValueRegistryTest.swift */,
|
||||
18E2A2631FBB1C330058FEC5 /* FailTest.swift */,
|
||||
|
@ -507,6 +537,7 @@
|
|||
18E2A2661FBB1C330058FEC5 /* Info.plist */,
|
||||
18E2A2671FBB1C330058FEC5 /* Matching */,
|
||||
18E2A26D1FBB1C330058FEC5 /* ProtocolTest.swift */,
|
||||
6896352D21AC5A4700B25D47 /* GenericProtocolTest.swift */,
|
||||
18E2A26E1FBB1C330058FEC5 /* Source */,
|
||||
18E2A2771FBB1C330058FEC5 /* Stubbing */,
|
||||
18E2A27D1FBB1C330058FEC5 /* StubTest.swift */,
|
||||
|
@ -550,6 +581,9 @@
|
|||
18E2A2751FBB1C330058FEC5 /* TestedSubProtocol.swift */,
|
||||
18E2A2761FBB1C330058FEC5 /* UnicodeTestProtocol.swift */,
|
||||
58C54D12B64AF3169FC8A1A3 /* ExcludedTestClass.swift */,
|
||||
6861B30B21A31DA2002EC2DA /* GenericClass.swift */,
|
||||
6861B32B21A32279002EC2DA /* GenericProtocol.swift */,
|
||||
680F09C82202F46A005F5C1A /* CollisionClasses.swift */,
|
||||
);
|
||||
path = Source;
|
||||
sourceTree = "<group>";
|
||||
|
@ -615,12 +649,14 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
18E2A36D1FBB3FDD0058FEC5 /* CYaml.framework */,
|
||||
6861B32621A31DA3002EC2DA /* Clang_C.framework */,
|
||||
18E2A35F1FBB3FDD0058FEC5 /* Commandant.framework */,
|
||||
18E2A35D1FBB3FDD0058FEC5 /* CuckooGeneratorFramework.framework */,
|
||||
18E2A3711FBB3FDD0058FEC5 /* FileKit.framework */,
|
||||
18E2A3651FBB3FDD0058FEC5 /* PathKit.framework */,
|
||||
18E2A3611FBB3FDD0058FEC5 /* Result.framework */,
|
||||
18E2A36F1FBB3FDD0058FEC5 /* SWXMLHash.framework */,
|
||||
6861B32821A31DA3002EC2DA /* SourceKit.framework */,
|
||||
18E2A3691FBB3FDD0058FEC5 /* SourceKittenFramework.framework */,
|
||||
18E2A3671FBB3FDD0058FEC5 /* Spectre.framework */,
|
||||
18E2A3631FBB3FDD0058FEC5 /* Stencil.framework */,
|
||||
|
@ -862,6 +898,20 @@
|
|||
remoteRef = 18E2A3701FBB3FDD0058FEC5 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
6861B32621A31DA3002EC2DA /* Clang_C.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = Clang_C.framework;
|
||||
remoteRef = 6861B32521A31DA3002EC2DA /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
6861B32821A31DA3002EC2DA /* SourceKit.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = SourceKit.framework;
|
||||
remoteRef = 6861B32721A31DA3002EC2DA /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
|
@ -908,7 +958,7 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "env -i PATH=$PATH PROJECT_DIR=$PROJECT_DIR USE_RUN=$USE_RUN swift \"$PROJECT_DIR/GenerateMocksForTests.swift\"";
|
||||
shellScript = "GENERATE=1\n\nif [ $GENERATE -ne 0 ]; then\nenv -i PATH=$PATH PROJECT_DIR=$PROJECT_DIR USE_RUN=$USE_RUN swift \"$PROJECT_DIR/GenerateMocksForTests.swift\"\nfi\n";
|
||||
};
|
||||
18E2A33F1FBB3FA80058FEC5 /* Generate Mocks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
|
@ -1030,23 +1080,28 @@
|
|||
18E2A2EF1FBB22CB0058FEC5 /* StubNoReturnThrowingFunctionTest.swift in Sources */,
|
||||
18E2A2E31FBB22C40058FEC5 /* TestedSubProtocol.swift in Sources */,
|
||||
18E2A2DE1FBB22C40058FEC5 /* ClassWithOptionals.swift in Sources */,
|
||||
6896352F21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */,
|
||||
18E2A2D41FBB22BC0058FEC5 /* ProtocolTest.swift in Sources */,
|
||||
18E2A2ED1FBB22CB0058FEC5 /* StubFunctionTest.swift in Sources */,
|
||||
18E2A2C51FBB22B60058FEC5 /* ClassTest.swift in Sources */,
|
||||
18E2A2C81FBB22B60058FEC5 /* FailTest.swift in Sources */,
|
||||
18E2A2E21FBB22C40058FEC5 /* TestedSubclass.swift in Sources */,
|
||||
680F09CA2202F46A005F5C1A /* CollisionClasses.swift in Sources */,
|
||||
18E2A2F11FBB22CB0058FEC5 /* StubTest.swift in Sources */,
|
||||
18E2A2D31FBB22BC0058FEC5 /* ParameterMatcherTest.swift in Sources */,
|
||||
18E2A2EE1FBB22CB0058FEC5 /* StubNoReturnFunctionTest.swift in Sources */,
|
||||
18E2A2C61FBB22B60058FEC5 /* CuckooFunctionsTest.swift in Sources */,
|
||||
18E2A2D21FBB22BC0058FEC5 /* ParameterMatcherFunctionsTest.swift in Sources */,
|
||||
18E2A2D11FBB22BC0058FEC5 /* MatchableTest.swift in Sources */,
|
||||
6861B32A21A31DAD002EC2DA /* GenericClass.swift in Sources */,
|
||||
18E2A2CF1FBB22BC0058FEC5 /* CallMatcherFunctionsTest.swift in Sources */,
|
||||
18E2A2C71FBB22B60058FEC5 /* DefaultValueRegistryTest.swift in Sources */,
|
||||
18E2A2E01FBB22C40058FEC5 /* TestedClass.swift in Sources */,
|
||||
18E2A2F51FBB22ED0058FEC5 /* ArgumentCaptorTest.swift in Sources */,
|
||||
680894C021ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */,
|
||||
58C540B5C5EF0037F77C90B5 /* ExcludedTestClass.swift in Sources */,
|
||||
58C54612D0DB47A284F29AD5 /* ExcludedStubTest.swift in Sources */,
|
||||
6861B32D21A32279002EC2DA /* GenericProtocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1067,23 +1122,28 @@
|
|||
18E2A2E81FBB22CA0058FEC5 /* StubNoReturnThrowingFunctionTest.swift in Sources */,
|
||||
18E2A2DB1FBB22C30058FEC5 /* TestedSubProtocol.swift in Sources */,
|
||||
18E2A2D61FBB22C30058FEC5 /* ClassWithOptionals.swift in Sources */,
|
||||
6896352E21AC5A4700B25D47 /* GenericProtocolTest.swift in Sources */,
|
||||
18E2A2CE1FBB22BC0058FEC5 /* ProtocolTest.swift in Sources */,
|
||||
18E2A2E61FBB22CA0058FEC5 /* StubFunctionTest.swift in Sources */,
|
||||
18E2A2C11FBB22B50058FEC5 /* ClassTest.swift in Sources */,
|
||||
18E2A2C41FBB22B50058FEC5 /* FailTest.swift in Sources */,
|
||||
18E2A2DA1FBB22C30058FEC5 /* TestedSubclass.swift in Sources */,
|
||||
680F09C92202F46A005F5C1A /* CollisionClasses.swift in Sources */,
|
||||
18E2A2EA1FBB22CA0058FEC5 /* StubTest.swift in Sources */,
|
||||
18E2A2CD1FBB22BC0058FEC5 /* ParameterMatcherTest.swift in Sources */,
|
||||
18E2A2E71FBB22CA0058FEC5 /* StubNoReturnFunctionTest.swift in Sources */,
|
||||
18E2A2C21FBB22B50058FEC5 /* CuckooFunctionsTest.swift in Sources */,
|
||||
18E2A2CC1FBB22BC0058FEC5 /* ParameterMatcherFunctionsTest.swift in Sources */,
|
||||
18E2A2CB1FBB22BC0058FEC5 /* MatchableTest.swift in Sources */,
|
||||
6861B32921A31DAC002EC2DA /* GenericClass.swift in Sources */,
|
||||
18E2A2C91FBB22BC0058FEC5 /* CallMatcherFunctionsTest.swift in Sources */,
|
||||
18E2A2C31FBB22B50058FEC5 /* DefaultValueRegistryTest.swift in Sources */,
|
||||
18E2A2D81FBB22C30058FEC5 /* TestedClass.swift in Sources */,
|
||||
18E2A2F31FBB22EC0058FEC5 /* ArgumentCaptorTest.swift in Sources */,
|
||||
680894BF21ABFDCB00C8D2EF /* GenericClassTest.swift in Sources */,
|
||||
58C54C0D4A65BCDC47704C0B /* ExcludedTestClass.swift in Sources */,
|
||||
58C54E2CB6689135915CFC3E /* ExcludedStubTest.swift in Sources */,
|
||||
6861B32C21A32279002EC2DA /* GenericProtocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -35,6 +35,8 @@ let generatorArguments = [
|
|||
"\(projectDir)/Tests/Source/TestedSubclass.swift",
|
||||
"\(projectDir)/Tests/Source/TestedSubProtocol.swift",
|
||||
"\(projectDir)/Tests/Source/ExcludedTestClass.swift",
|
||||
"\(projectDir)/Tests/Source/GenericClass.swift",
|
||||
"\(projectDir)/Tests/Source/GenericProtocol.swift",
|
||||
]
|
||||
|
||||
let useRun = Bool(ProcessInfo.processInfo.environment["USE_RUN", default: "false"]) ?? false
|
||||
|
|
|
@ -28,13 +28,13 @@ public struct Generator {
|
|||
}
|
||||
|
||||
ext.registerFilter("matchableGenericNames") { (value: Any?) in
|
||||
guard let parameters = value as? [MethodParameter] else { return value }
|
||||
return self.matchableGenerics(with: parameters)
|
||||
guard let method = value as? Method else { return value }
|
||||
return self.matchableGenericTypes(from: method)
|
||||
}
|
||||
|
||||
ext.registerFilter("matchableGenericWhere") { (value: Any?) in
|
||||
guard let parameters = value as? [MethodParameter] else { return value }
|
||||
return self.matchableGenerics(where: parameters)
|
||||
ext.registerFilter("matchableGenericWhereClause") { (value: Any?) in
|
||||
guard let method = value as? Method else { return value }
|
||||
return self.matchableGenericsWhereClause(from: method)
|
||||
}
|
||||
|
||||
ext.registerFilter("matchableParameterSignature") { (value: Any?) in
|
||||
|
@ -68,18 +68,26 @@ public struct Generator {
|
|||
return try environment.renderTemplate(string: Templates.mock, context: ["containers": containers, "debug": debug])
|
||||
}
|
||||
|
||||
private func matchableGenerics(with parameters: [MethodParameter]) -> String {
|
||||
guard parameters.isEmpty == false else { return "" }
|
||||
private func matchableGenericTypes(from method: Method) -> String {
|
||||
guard method.parameters.isEmpty == false else { return "" }
|
||||
|
||||
let genericParameters = (1...parameters.count).map { "M\($0): Cuckoo.Matchable" }.joined(separator: ", ")
|
||||
return "<\(genericParameters)>"
|
||||
let matchableGenericParameters = method.parameters.enumerated().map { index, parameter -> String in
|
||||
let type = parameter.isOptional ? "OptionalMatchable" : "Matchable"
|
||||
return "M\(index + 1): Cuckoo.\(type)"
|
||||
}
|
||||
let methodGenericParameters = method.genericParameters.map { $0.description }
|
||||
return "<\((matchableGenericParameters + methodGenericParameters).joined(separator: ", "))>"
|
||||
}
|
||||
|
||||
private func matchableGenerics(where parameters: [MethodParameter]) -> String {
|
||||
guard parameters.isEmpty == false else { return "" }
|
||||
private func matchableGenericsWhereClause(from method: Method) -> String {
|
||||
guard method.parameters.isEmpty == false else { return "" }
|
||||
|
||||
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.typeWithoutAttributes))" }.joined(separator: ", ")
|
||||
return " where \(whereClause)"
|
||||
let matchableWhereConstraints = method.parameters.enumerated().map { index, parameter -> String in
|
||||
let type = parameter.isOptional ? "OptionalMatchedType" : "MatchedType"
|
||||
return "M\(index + 1).\(type) == \(genericSafeType(from: parameter.type.withoutAttributes.unoptionaled.sugarized))"
|
||||
}
|
||||
let methodWhereConstraints = method.returnSignature.whereConstraints
|
||||
return " where \((matchableWhereConstraints + methodWhereConstraints).joined(separator: ", "))"
|
||||
}
|
||||
|
||||
private func matchableParameterSignature(with parameters: [MethodParameter]) -> String {
|
||||
|
@ -104,9 +112,9 @@ public struct Generator {
|
|||
var fullString = ""
|
||||
for (index, parameter) in parameters.enumerated() {
|
||||
if parameter.isClosure && !parameter.isEscaping {
|
||||
let indents = String(repeating: "\t", count: index + 1)
|
||||
let indents = String(repeating: "\t", count: index)
|
||||
let tries = (throwing ?? false) ? " try " : " "
|
||||
fullString += "\(indents)return\(tries)withoutActuallyEscaping(\(parameter.name), do: { (\(parameter.name)) in\n"
|
||||
fullString += "\(indents)return\(tries)withoutActuallyEscaping(\(parameter.name), do: { (\(parameter.name): @escaping \(parameter.type)) in\n"
|
||||
}
|
||||
}
|
||||
return fullString
|
||||
|
@ -116,7 +124,7 @@ public struct Generator {
|
|||
var fullString = ""
|
||||
for (index, parameter) in parameters.enumerated() {
|
||||
if parameter.isClosure && !parameter.isEscaping {
|
||||
let indents = String(repeating: "\t", count: index + 1)
|
||||
let indents = String(repeating: "\t", count: index)
|
||||
fullString += "\(indents)})\n"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,24 +8,47 @@
|
|||
import Foundation
|
||||
|
||||
extension Templates {
|
||||
static let staticGenericParameter = "_CUCKOO$$GENERIC"
|
||||
static let typeErasureClassName = "DefaultImplCaller"
|
||||
static let mock = """
|
||||
{% for container in containers %}
|
||||
{% for attribute in container.attributes %}
|
||||
{{ attribute.text }}
|
||||
{% endfor %}
|
||||
{{ container.accessibility }} class {{ container.mockName }}: {{ container.name }}, {% if container.isImplementation %}Cuckoo.ClassMock{% else %}Cuckoo.ProtocolMock{% endif %} {
|
||||
{{ container.accessibility }} typealias MocksType = {{ container.name }}
|
||||
{{ container.accessibility }} class {{ container.mockName }}{{ container.genericParameters }}: {{ container.name }}{% if container.isImplementation %}{{ container.genericArguments }}{% endif %}, {% if container.isImplementation %}Cuckoo.ClassMock{% else %}Cuckoo.ProtocolMock{% endif %} {
|
||||
{% if container.isGeneric and not container.isImplementation %}
|
||||
{{ container.accessibility }} typealias MocksType = \(typeErasureClassName){{ container.genericArguments }}
|
||||
{% else %}
|
||||
{{ container.accessibility }} typealias MocksType = {{ container.name }}{{ container.genericArguments }}
|
||||
{% endif %}
|
||||
{{ container.accessibility }} typealias Stubbing = __StubbingProxy_{{ container.name }}
|
||||
{{ container.accessibility }} typealias Verification = __VerificationProxy_{{ container.name }}
|
||||
|
||||
private var __defaultImplStub: {{ container.name }}?
|
||||
|
||||
{{ container.accessibility }} let cuckoo_manager = Cuckoo.MockManager.preconfiguredManager ?? Cuckoo.MockManager(hasParent: {{ container.isImplementation }})
|
||||
|
||||
{{ container.accessibility }} func enableDefaultImplementation(_ stub: {{ container.name }}) {
|
||||
{% if container.isGeneric and not container.isImplementation %}
|
||||
\(Templates.typeErasure.indented())
|
||||
|
||||
private var __defaultImplStub: \(typeErasureClassName){{ container.genericArguments }}?
|
||||
|
||||
{{ container.accessibility }} func enableDefaultImplementation<\(staticGenericParameter): {{ container.name }}>(_ stub: \(staticGenericParameter)) where {{ container.genericProtocolIdentity }} {
|
||||
var mutableStub = stub
|
||||
__defaultImplStub = \(typeErasureClassName)(from: &mutableStub, keeping: mutableStub)
|
||||
cuckoo_manager.enableDefaultStubImplementation()
|
||||
}
|
||||
|
||||
{{ container.accessibility }} func enableDefaultImplementation<\(staticGenericParameter): GenericProtocol>(mutating stub: UnsafeMutablePointer<\(staticGenericParameter)>) where {{ container.genericProtocolIdentity }} {
|
||||
__defaultImplStub = \(typeErasureClassName)(from: stub, keeping: nil)
|
||||
cuckoo_manager.enableDefaultStubImplementation()
|
||||
}
|
||||
{% else %}
|
||||
private var __defaultImplStub: {{ container.name }}{{ container.genericArguments }}?
|
||||
|
||||
{{ container.accessibility }} func enableDefaultImplementation(_ stub: {{ container.name }}{{ container.genericArguments }}) {
|
||||
__defaultImplStub = stub
|
||||
cuckoo_manager.enableDefaultStubImplementation()
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% for property in container.properties %}
|
||||
{% if debug %}
|
||||
|
@ -79,7 +102,7 @@ extension Templates {
|
|||
{% for attribute in method.attributes %}
|
||||
{{ attribute.text }}
|
||||
{% endfor %}
|
||||
{{ method.accessibility }}{% if container.isImplementation and method.isOverriding %} override{% endif %} func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
|
||||
{{ method.accessibility }}{% if container.isImplementation and method.isOverriding %} override{% endif %} func {{ method.name }}{{ method.genericParameters }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
|
||||
{{ method.parameters|openNestedClosure:method.isThrowing }}
|
||||
return{% if method.isThrowing %} try{% endif %} cuckoo_manager.call{% if method.isThrowing %}Throws{% endif %}("{{method.fullyQualifiedName}}",
|
||||
parameters: ({{method.parameterNames}}),
|
||||
|
@ -98,7 +121,6 @@ extension Templates {
|
|||
\(Templates.stubbingProxy.indented())
|
||||
|
||||
\(Templates.verificationProxy.indented())
|
||||
|
||||
}
|
||||
|
||||
\(Templates.noImplStub)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
extension Templates {
|
||||
static let noImplStub = """
|
||||
{{container.accessibility}} class {{ container.name }}Stub: {{ container.name }} {
|
||||
{{container.accessibility}} class {{ container.name }}Stub{{ container.genericParameters }}: {{ container.name }}{% if container.isImplementation %}{{ container.genericArguments }}{% endif %} {
|
||||
{% for property in container.properties %}
|
||||
{{ property.accessibility }}{% if container.@type == "ClassDeclaration" %} override{% endif %} var {{ property.name }}: {{ property.type }} {
|
||||
get {
|
||||
|
@ -28,7 +28,7 @@ extension Templates {
|
|||
{% endfor %}
|
||||
|
||||
{% for method in container.methods %}
|
||||
{{ method.accessibility }}{% if container.@type == "ClassDeclaration" and method.isOverriding %} override{% endif %} func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
|
||||
{{ method.accessibility }}{% if container.@type == "ClassDeclaration" and method.isOverriding %} override{% endif %} func {{ method.name }}{{ method.genericParameters }}({{ method.parameterSignature }}) {{ method.returnSignature }} {{ method.whereClause }} {
|
||||
return DefaultValueRegistry.defaultValue(for: {{method.returnType|genericSafe}}.self)
|
||||
}
|
||||
{% endfor %}
|
||||
|
|
|
@ -21,7 +21,7 @@ extension Templates {
|
|||
}
|
||||
{% endfor %}
|
||||
{% for method in container.methods %}
|
||||
func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.inputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}>{{method.parameters|matchableGenericWhere}} {
|
||||
func {{method.name}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.inputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}>{{method.self|matchableGenericWhereClause}} {
|
||||
{{method.parameters|parameterMatchers}}
|
||||
return .init(stub: cuckoo_manager.createStub(for: {{ container.mockName }}.self, method: "{{method.fullyQualifiedName}}", parameterMatchers: matchers))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// TypeErasureTemplate.swift
|
||||
// CuckooGeneratorFramework
|
||||
//
|
||||
// Created by Matyáš Kříž on 26/11/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Templates {
|
||||
static let typeErasure = """
|
||||
class \(typeErasureClassName){{ container.genericParameters }}: {{ container.name }} {
|
||||
private let reference: Any
|
||||
|
||||
{% for property in container.properties %}private let _getter_storage$${{ property.name }}: () -> {{ property.type }}{% if not property.isReadOnly %}
|
||||
private let _setter_storage$${{ property.name }}: ({{ property.type }}) -> Void{% endif %}
|
||||
var {{ property.name }}: {{ property.type }} {
|
||||
get { return _getter_storage$${{ property.name }}() }{% if not property.isReadOnly %}
|
||||
set { _setter_storage$${{ property.name }}(newValue) }{% endif %}
|
||||
}
|
||||
|
||||
{% endfor %}
|
||||
{# For developers: The `keeping reference: Any?` is necessary because when called from the `enableDefaultImplementation(stub:)` method
|
||||
instead of `enableDefaultImplementation(mutating:)`, we need to prevent the struct getting deallocated. #}
|
||||
init<\(staticGenericParameter): {{ container.name }}>(from defaultImpl: UnsafeMutablePointer<\(staticGenericParameter)>, keeping reference: @escaping @autoclosure () -> Any?) where {{ container.genericProtocolIdentity }} {
|
||||
self.reference = reference
|
||||
|
||||
{% for property in container.properties %}_getter_storage$${{ property.name }} = { defaultImpl.pointee.{{ property.name }} }
|
||||
{% if not property.isReadOnly %}_setter_storage$${{ property.name }} = { defaultImpl.pointee.{{ property.name }} = $0 }{% endif %}
|
||||
{% endfor %}
|
||||
{% for method in container.methods %}_storage$${{ method.name }} = defaultImpl.pointee.{{ method.name }}
|
||||
{% endfor %}
|
||||
}
|
||||
{% if container.initializers %}
|
||||
/// MARK:- ignored required initializers{% endif %}
|
||||
{% for initializer in container.initializers %}required init({{ initializer.parameterSignature }}) {
|
||||
fatalError("`DefaultImplCaller` class is only used for calling default implementation and can't be initialized on its own.")
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
{% for method in container.methods %}
|
||||
private let _storage$${{ method.name }}: ({{ method.inputTypes }}) -> {{ method.returnType }}
|
||||
func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} {
|
||||
return _storage$${{ method.name }}({{ method.parameterNames }})
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
"""
|
||||
}
|
|
@ -28,7 +28,7 @@ extension Templates {
|
|||
|
||||
{% for method in container.methods %}
|
||||
@discardableResult
|
||||
func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<{{method.returnType|genericSafe}}>{{method.parameters|matchableGenericWhere}} {
|
||||
func {{method.name}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<({{method.inputTypes|genericSafe}}), {{method.returnType|genericSafe}}>{{method.self|matchableGenericWhereClause}} {
|
||||
{{method.parameters|parameterMatchers}}
|
||||
return cuckoo_manager.verify("{{method.fullyQualifiedName}}", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation)
|
||||
}
|
||||
|
|
|
@ -77,74 +77,87 @@ public struct Tokenizer {
|
|||
return Attribute(kind: kind, text: text)
|
||||
}
|
||||
|
||||
let accessibility = (dictionary[Key.Accessibility.rawValue] as? String).flatMap { Accessibility(rawValue: $0) }
|
||||
let type = dictionary[Key.TypeName.rawValue] as? String
|
||||
guard !attributes.map({ $0.kind }).contains(.final) else {
|
||||
if debugMode {
|
||||
fputs("Cuckoo: Ignoring mocking of '\(name)' because it's marked `final`.\n", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let accessibility = (dictionary[Key.Accessibility.rawValue] as? String).flatMap { Accessibility(rawValue: $0) } ?? .Internal
|
||||
let type: WrappableType?
|
||||
if let stringType = dictionary[Key.TypeName.rawValue] as? String {
|
||||
type = WrappableType(parsing: stringType)
|
||||
} else {
|
||||
type = nil
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case Kinds.ProtocolDeclaration.rawValue:
|
||||
let subtokens = tokenize(dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? [])
|
||||
let initializers = subtokens.only(Initializer.self)
|
||||
let children = subtokens.noneOf(Initializer.self)
|
||||
let genericParameters = subtokens.only(GenericParameter.self)
|
||||
|
||||
return ProtocolDeclaration(
|
||||
name: name,
|
||||
accessibility: accessibility!,
|
||||
accessibility: accessibility,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
bodyRange: bodyRange!,
|
||||
initializers: initializers,
|
||||
children: children,
|
||||
inheritedTypes: tokenizedInheritedTypes,
|
||||
attributes: attributes)
|
||||
attributes: attributes,
|
||||
genericParameters: genericParameters)
|
||||
|
||||
case Kinds.ClassDeclaration.rawValue:
|
||||
guard !attributes.map({ $0.kind }).contains(.final) else {
|
||||
if debugMode {
|
||||
fputs("Cuckoo: Ignoring mocking of class \(name) because it's marked `final`.\n", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let subtokens = tokenize(dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? [])
|
||||
let initializers = subtokens.only(Initializer.self)
|
||||
let children = subtokens.noneOf(Initializer.self).map { child -> Token in
|
||||
if var property = child as? InstanceVariable {
|
||||
var accessibleChild = child as? HasAccessibility & Token
|
||||
if accessibleChild?.accessibility == .Internal {
|
||||
accessibleChild?.accessibility = accessibility
|
||||
}
|
||||
if var property = accessibleChild as? InstanceVariable {
|
||||
property.overriding = true
|
||||
return property
|
||||
} else {
|
||||
return child
|
||||
return accessibleChild ?? child
|
||||
}
|
||||
}
|
||||
let genericParameters = subtokens.only(GenericParameter.self)
|
||||
|
||||
return ClassDeclaration(
|
||||
name: name,
|
||||
accessibility: accessibility!,
|
||||
accessibility: accessibility,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
bodyRange: bodyRange!,
|
||||
initializers: initializers,
|
||||
children: children,
|
||||
inheritedTypes: tokenizedInheritedTypes,
|
||||
attributes: attributes)
|
||||
attributes: attributes,
|
||||
genericParameters: genericParameters)
|
||||
|
||||
case Kinds.ExtensionDeclaration.rawValue:
|
||||
return ExtensionDeclaration(range: range!)
|
||||
|
||||
case Kinds.InstanceVariable.rawValue:
|
||||
let setterAccessibility = (dictionary[Key.SetterAccessibility.rawValue] as? String).flatMap(Accessibility.init)
|
||||
|
||||
|
||||
if String(source.utf8.dropFirst(range!.startIndex))?.takeUntil(occurence: name)?.trimmed.hasPrefix("let") == true {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
if type == nil {
|
||||
stderrPrint("Type of instance variable \(name) could not be inferred. Please specify it explicitly. (\(file.path ?? ""))")
|
||||
}
|
||||
|
||||
return InstanceVariable(
|
||||
name: name,
|
||||
type: type ?? "__UnknownType",
|
||||
accessibility: accessibility!,
|
||||
type: type ?? .type("__UnknownType"),
|
||||
accessibility: accessibility,
|
||||
setterAccessibility: setterAccessibility,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
|
@ -152,48 +165,58 @@ public struct Tokenizer {
|
|||
attributes: attributes)
|
||||
|
||||
case Kinds.InstanceMethod.rawValue:
|
||||
let genericParameters = tokenize(dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? []).only(GenericParameter.self)
|
||||
let parameters = tokenize(methodName: name, parameters: dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? [])
|
||||
|
||||
var returnSignature: String
|
||||
let returnSignature: ReturnSignature
|
||||
if let bodyRange = bodyRange {
|
||||
returnSignature = source.utf8[nameRange!.endIndex..<bodyRange.startIndex].takeUntil(occurence: "{")?.trimmed ?? ""
|
||||
returnSignature = parseReturnSignature(source: source.utf8[nameRange!.endIndex..<bodyRange.startIndex].takeUntil(occurence: "{")?.trimmed ?? "")
|
||||
} else {
|
||||
returnSignature = source.utf8[nameRange!.endIndex..<range!.endIndex].trimmed
|
||||
if returnSignature.isEmpty {
|
||||
let untilThrows = String(source.utf8.dropFirst(nameRange!.endIndex))?
|
||||
.takeUntil(occurence: "throws").map { $0 + "throws" }?
|
||||
.trimmed
|
||||
if let untilThrows = untilThrows, untilThrows == "throws" || untilThrows == "rethrows" {
|
||||
returnSignature = "\(untilThrows)"
|
||||
}
|
||||
}
|
||||
}
|
||||
if returnSignature.isEmpty == false {
|
||||
returnSignature = " " + returnSignature
|
||||
returnSignature = parseReturnSignature(source: source.utf8[nameRange!.endIndex..<range!.endIndex].trimmed)
|
||||
}
|
||||
|
||||
// When bodyRange != nil, we need to create .ClassMethod instead of .ProtocolMethod
|
||||
if let bodyRange = bodyRange {
|
||||
return ClassMethod(
|
||||
name: name,
|
||||
accessibility: accessibility!,
|
||||
accessibility: accessibility,
|
||||
returnSignature: returnSignature,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
parameters: parameters,
|
||||
bodyRange: bodyRange,
|
||||
attributes: attributes)
|
||||
attributes: attributes,
|
||||
genericParameters: genericParameters)
|
||||
} else {
|
||||
return ProtocolMethod(
|
||||
name: name,
|
||||
accessibility: accessibility!,
|
||||
accessibility: accessibility,
|
||||
returnSignature: returnSignature,
|
||||
range: range!,
|
||||
nameRange: nameRange!,
|
||||
parameters: parameters,
|
||||
attributes: attributes)
|
||||
attributes: attributes,
|
||||
genericParameters: genericParameters)
|
||||
}
|
||||
|
||||
case Kinds.GenericParameter.rawValue:
|
||||
return tokenize(parameterLabel: nil, parameter: representable)
|
||||
|
||||
case Kinds.AssociatedType.rawValue:
|
||||
let regex = try! NSRegularExpression(pattern: "\\s*:\\s*([^\\s;\\/]+)")
|
||||
guard let nameRange = nameRange, let range = range else { return nil }
|
||||
guard let inheritanceMatch = regex.firstMatch(
|
||||
in: source,
|
||||
range: NSMakeRange(range.startIndex, range.endIndex - range.startIndex)) else {
|
||||
return GenericParameter(name: name, range: range, inheritedType: nil)
|
||||
}
|
||||
let inheritanceRange = inheritanceMatch.range(at: 1)
|
||||
let fromIndex = source.index(source.startIndex, offsetBy: inheritanceRange.location)
|
||||
let toIndex = source.index(fromIndex, offsetBy: inheritanceRange.length)
|
||||
let inheritance = String(source[fromIndex..<toIndex])
|
||||
let fullRange = range.lowerBound..<(range.upperBound + inheritanceMatch.range.length)
|
||||
return GenericParameter(name: name, range: fullRange, inheritedType: InheritanceDeclaration(name: inheritance))
|
||||
|
||||
default:
|
||||
// Do not log anything, until the parser contains all known cases.
|
||||
// stderrPrint("Unknown kind. Dictionary: \(dictionary) \(file.path ?? "")")
|
||||
|
@ -216,12 +239,12 @@ public struct Tokenizer {
|
|||
return kind == Kinds.MethodParameter.rawValue
|
||||
}
|
||||
|
||||
return zip(parameterLabels, filteredParameters).compactMap(tokenize)
|
||||
return zip(parameterLabels, filteredParameters).compactMap { tokenize(parameterLabel: $0, parameter: $1) as? MethodParameter }
|
||||
}
|
||||
|
||||
private func tokenize(parameterLabel: String?, parameter: SourceKitRepresentable) -> MethodParameter? {
|
||||
private func tokenize(parameterLabel: String?, parameter: SourceKitRepresentable) -> Token? {
|
||||
guard let dictionary = parameter as? [String: SourceKitRepresentable] else { return nil }
|
||||
|
||||
|
||||
let name = dictionary[Key.Name.rawValue] as? String ?? Tokenizer.nameNotSet
|
||||
let kind = dictionary[Key.Kind.rawValue] as? String ?? Tokenizer.unknownType
|
||||
let range = extractRange(from: dictionary, offset: .Offset, length: .Length)
|
||||
|
@ -230,7 +253,41 @@ public struct Tokenizer {
|
|||
|
||||
switch kind {
|
||||
case Kinds.MethodParameter.rawValue:
|
||||
return MethodParameter(label: parameterLabel, name: name, type: type!, range: range!, nameRange: nameRange!)
|
||||
// separate `inout` from the type and remember that the parameter is inout
|
||||
let type = type!
|
||||
|
||||
// we want to remove `inout` and remember it, but we don't want to affect a potential `inout` closure parameter
|
||||
let inoutSeparatedType: String
|
||||
let isInout: Bool
|
||||
if let inoutRange = type.range(of: "inout ") {
|
||||
if let closureParenIndex = type.firstIndex(of: "("), closureParenIndex < inoutRange.upperBound {
|
||||
inoutSeparatedType = type
|
||||
isInout = false
|
||||
} else {
|
||||
var mutableString = type
|
||||
mutableString.removeSubrange(inoutRange)
|
||||
inoutSeparatedType = mutableString
|
||||
isInout = true
|
||||
}
|
||||
} else {
|
||||
inoutSeparatedType = type
|
||||
isInout = false
|
||||
}
|
||||
|
||||
let wrappableType = WrappableType(parsing: inoutSeparatedType)
|
||||
|
||||
return MethodParameter(label: parameterLabel, name: name, type: wrappableType, range: range!, nameRange: nameRange!, isInout: isInout)
|
||||
|
||||
case Kinds.GenericParameter.rawValue:
|
||||
let inheritedTypeElement = (dictionary[Key.InheritedTypes.rawValue] as? [SourceKitRepresentable] ?? []).first
|
||||
let inheritedType = (inheritedTypeElement as? [String: SourceKitRepresentable] ?? [:])[Key.Name.rawValue] as? String
|
||||
let inheritanceDeclaration: InheritanceDeclaration?
|
||||
if let inheritedType = inheritedType {
|
||||
inheritanceDeclaration = .init(name: inheritedType)
|
||||
} else {
|
||||
inheritanceDeclaration = nil
|
||||
}
|
||||
return GenericParameter(name: name, range: range!, inheritedType: inheritanceDeclaration)
|
||||
|
||||
default:
|
||||
stderrPrint("Unknown method parameter. Dictionary: \(dictionary) \(file.path ?? "")")
|
||||
|
@ -263,12 +320,8 @@ public struct Tokenizer {
|
|||
rangesToIgnore.filter { $0 ~= result.range.location }.isEmpty
|
||||
}
|
||||
.map { result -> Import in
|
||||
let libraryRange = result.range(at: 1)
|
||||
let fromIndex = source.index(source.startIndex, offsetBy: libraryRange.location)
|
||||
let toIndex = source.index(fromIndex, offsetBy: libraryRange.length)
|
||||
let library = String(source[fromIndex..<toIndex])
|
||||
let range = result.range.location..<(result.range.location + result.range.length)
|
||||
print(library)
|
||||
let library = source.stringMatch(from: result, at: 1)
|
||||
return Import(range: range, importee: .library(name: library))
|
||||
}
|
||||
let components = componentRegex.matches(in: source, range: NSRange(location: 0, length: source.count))
|
||||
|
@ -281,7 +334,6 @@ public struct Tokenizer {
|
|||
let library = source[result.range(at: 2)]
|
||||
let component = source[result.range(at: 3)]
|
||||
let range = result.range.location..<(result.range.location + result.range.length)
|
||||
print(library, component)
|
||||
return Import(range: range, importee: .component(componentType: componentType, library: library, name: component))
|
||||
}
|
||||
|
||||
|
@ -290,6 +342,90 @@ public struct Tokenizer {
|
|||
fatalError("Invalid regex:" + error.description)
|
||||
}
|
||||
}
|
||||
|
||||
/// - parameter source: A trimmed string containing only the method return signature excluding the trailing brace
|
||||
/// - returns: tuple containing parsed throwString, returnType, and where constraints
|
||||
private func parseReturnSignature(source: String) -> ReturnSignature {
|
||||
var throwString = nil as String?
|
||||
var returnType = ""
|
||||
var whereConstraints = [] as [String]
|
||||
|
||||
var index = source.startIndex
|
||||
var parenLevel = 0
|
||||
var afterArrow = false
|
||||
parseLoop: while index != source.endIndex {
|
||||
let character = source[index]
|
||||
switch character {
|
||||
case "(", "<", "[":
|
||||
parenLevel += 1
|
||||
returnType.append(character)
|
||||
afterArrow = false
|
||||
case ")", ">", "]":
|
||||
parenLevel -= 1
|
||||
returnType.append(character)
|
||||
afterArrow = false
|
||||
case "r" where returnType.isEmpty:
|
||||
throwString = "rethrows"
|
||||
index = source.index(index, offsetBy: throwString!.count)
|
||||
continue
|
||||
case "t" where returnType.isEmpty:
|
||||
throwString = "throws"
|
||||
index = source.index(index, offsetBy: throwString!.count)
|
||||
continue
|
||||
case "w" where parenLevel == 0 && !afterArrow:
|
||||
index = source.index(index, offsetBy: "where".count)
|
||||
whereConstraints = parseWhereClause(source: source, index: &index)
|
||||
// the where clause is the last thing in method signature, so we'll just return our parsings
|
||||
break parseLoop
|
||||
case "-" where parenLevel == 0:
|
||||
index = source.index(after: index)
|
||||
guard source[index] == ">" else { fatalError("Uhh, what.") }
|
||||
// we will omit the first arrow, so that the generator doesn't have to remove it if it needs the type alone
|
||||
if !returnType.trimmed.isEmpty {
|
||||
returnType.append("->")
|
||||
}
|
||||
afterArrow = true
|
||||
default:
|
||||
returnType.append(character)
|
||||
afterArrow = false
|
||||
}
|
||||
index = source.index(after: index)
|
||||
}
|
||||
|
||||
let trimmedType = returnType.trimmed
|
||||
let typizedReturnType = trimmedType.isEmpty ? "Void" : trimmedType
|
||||
|
||||
return ReturnSignature(throwString: throwString, returnType: WrappableType(parsing: typizedReturnType), whereConstraints: whereConstraints)
|
||||
}
|
||||
|
||||
/// - returns: the where constraints parsed from the where clause
|
||||
private func parseWhereClause(source: String, index: inout String.Index) -> [String] {
|
||||
var whereConstraints = [] as [String]
|
||||
var currentConstraint = ""
|
||||
var parenLevel = 0
|
||||
while index != source.endIndex {
|
||||
let character = source[index]
|
||||
switch character {
|
||||
case "(", "<", "[":
|
||||
parenLevel += 1
|
||||
case ")", ">", "]":
|
||||
parenLevel -= 1
|
||||
case "," where parenLevel == 0:
|
||||
currentConstraint = currentConstraint.trimmed
|
||||
whereConstraints.append(currentConstraint)
|
||||
currentConstraint = ""
|
||||
default:
|
||||
currentConstraint.append(character)
|
||||
}
|
||||
|
||||
index = source.index(after: index)
|
||||
}
|
||||
if !currentConstraint.isEmpty {
|
||||
currentConstraint = currentConstraint.trimmed
|
||||
whereConstraints.append(currentConstraint)
|
||||
}
|
||||
return whereConstraints
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
@ -299,3 +435,22 @@ extension String {
|
|||
return String(self[fromIndex..<toIndex])
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func stringMatch(from match: NSTextCheckingResult, at range: Int = 0) -> String {
|
||||
let matchRange = match.range(at: range)
|
||||
let fromIndex = index(startIndex, offsetBy: matchRange.location)
|
||||
let toIndex = index(fromIndex, offsetBy: matchRange.length)
|
||||
return String(self[fromIndex..<toIndex])
|
||||
}
|
||||
|
||||
func removing(match: NSTextCheckingResult, at range: Int = 0) -> String {
|
||||
let matchRange = match.range(at: range)
|
||||
let fromIndex = index(startIndex, offsetBy: matchRange.location)
|
||||
let toIndex = index(fromIndex, offsetBy: matchRange.length)
|
||||
|
||||
var mutableString = self
|
||||
mutableString.removeSubrange(fromIndex..<toIndex)
|
||||
return mutableString
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,18 +6,18 @@
|
|||
// Copyright © 2016 Brightify. All rights reserved.
|
||||
//
|
||||
|
||||
public struct ClassDeclaration: ContainerToken {
|
||||
public let name: String
|
||||
public let accessibility: Accessibility
|
||||
public let range: CountableRange<Int>
|
||||
public let nameRange: CountableRange<Int>
|
||||
public let bodyRange: CountableRange<Int>
|
||||
public let initializers: [Initializer]
|
||||
public let children: [Token]
|
||||
public struct ClassDeclaration: ContainerToken, HasAccessibility {
|
||||
public let implementation: Bool = true
|
||||
public let inheritedTypes: [InheritanceDeclaration]
|
||||
public let attributes: [Attribute]
|
||||
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var bodyRange: CountableRange<Int>
|
||||
public var initializers: [Initializer]
|
||||
public var children: [Token]
|
||||
public var inheritedTypes: [InheritanceDeclaration]
|
||||
public var attributes: [Attribute]
|
||||
public var genericParameters: [GenericParameter]
|
||||
public var hasNoArgInit: Bool {
|
||||
return initializers.filter { $0.parameters.isEmpty }.isEmpty
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ public struct ClassDeclaration: ContainerToken {
|
|||
initializers: self.initializers,
|
||||
children: tokens,
|
||||
inheritedTypes: self.inheritedTypes,
|
||||
attributes: self.attributes)
|
||||
attributes: self.attributes,
|
||||
genericParameters: self.genericParameters)
|
||||
}
|
||||
|
||||
public func isEqual(to other: Token) -> Bool {
|
||||
|
|
|
@ -7,14 +7,15 @@
|
|||
//
|
||||
|
||||
public struct ClassMethod: Method {
|
||||
public let name: String
|
||||
public let accessibility: Accessibility
|
||||
public let returnSignature: String
|
||||
public let range: CountableRange<Int>
|
||||
public let nameRange: CountableRange<Int>
|
||||
public let parameters: [MethodParameter]
|
||||
public let bodyRange: CountableRange<Int>
|
||||
public let attributes: [Attribute]
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var returnSignature: ReturnSignature
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var parameters: [MethodParameter]
|
||||
public var bodyRange: CountableRange<Int>
|
||||
public var attributes: [Attribute]
|
||||
public var genericParameters: [GenericParameter]
|
||||
public var isOptional: Bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// Copyright © 2016 Brightify. All rights reserved.
|
||||
//
|
||||
|
||||
public protocol ContainerToken: Token {
|
||||
public protocol ContainerToken: Token, HasAccessibility {
|
||||
var name: String { get }
|
||||
var accessibility: Accessibility { get }
|
||||
var range: CountableRange<Int> { get }
|
||||
|
@ -17,6 +17,7 @@ public protocol ContainerToken: Token {
|
|||
var implementation: Bool { get }
|
||||
var inheritedTypes: [InheritanceDeclaration] { get }
|
||||
var attributes: [Attribute] { get }
|
||||
var genericParameters: [GenericParameter] { get }
|
||||
}
|
||||
|
||||
extension ContainerToken {
|
||||
|
@ -33,6 +34,11 @@ extension ContainerToken {
|
|||
.filter { $0.accessibility.isAccessible && $0.isInit && !$0.isDeinit }
|
||||
.map { $0.serializeWithType() }
|
||||
|
||||
let genericParametersString = genericParameters.map { $0.description }.joined(separator: ", ")
|
||||
let genericArgumentsString = genericParameters.map { $0.name }.joined(separator: ", ")
|
||||
let genericProtocolIdentity = genericParameters.map { "\(Templates.staticGenericParameter).\($0.name) == \($0.name)" }.joined(separator: ", ")
|
||||
let isGeneric = !genericParameters.isEmpty
|
||||
|
||||
return [
|
||||
"name": name,
|
||||
"accessibility": accessibility.sourceName,
|
||||
|
@ -45,6 +51,10 @@ extension ContainerToken {
|
|||
"mockName": "Mock\(name)",
|
||||
"inheritedTypes": inheritedTypes,
|
||||
"attributes": attributes.filter { $0.isSupported },
|
||||
"isGeneric": isGeneric,
|
||||
"genericParameters": isGeneric ? "<\(genericParametersString)>" : "",
|
||||
"genericArguments": isGeneric ? "<\(genericArgumentsString)>" : "",
|
||||
"genericProtocolIdentity": genericProtocolIdentity,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// GenericParameter.swift
|
||||
// CuckooGeneratorFramework
|
||||
//
|
||||
// Created by Matyáš Kříž on 19/11/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct GenericParameter: Token {
|
||||
public let name: String
|
||||
public let range: CountableRange<Int>
|
||||
public let inheritedType: InheritanceDeclaration?
|
||||
|
||||
public var description: String {
|
||||
let hasInheritedType = inheritedType != nil
|
||||
return "\(name)\(hasInheritedType ? ": " : "")\(inheritedType?.name ?? "")"
|
||||
}
|
||||
|
||||
public func isEqual(to other: Token) -> Bool {
|
||||
guard let other = other as? GenericParameter else { return false }
|
||||
return self.name == other.name && self.range == other.range && self.inheritedType?.name == other.inheritedType?.name
|
||||
}
|
||||
|
||||
public func serialize() -> [String : Any] {
|
||||
return [
|
||||
"name": name,
|
||||
"inheritedType": inheritedType,
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// HasAccessibility.swift
|
||||
// CuckooGenerator
|
||||
//
|
||||
// Created by Matyáš Kříž on 11/03/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol HasAccessibility {
|
||||
var accessibility: Accessibility { get set }
|
||||
}
|
|
@ -6,16 +6,18 @@
|
|||
// Copyright © 2016 Brightify. All rights reserved.
|
||||
//
|
||||
|
||||
public struct Initializer: Method {
|
||||
public let name: String
|
||||
public let accessibility: Accessibility
|
||||
public let returnSignature: String
|
||||
public let range: CountableRange<Int>
|
||||
public let nameRange: CountableRange<Int>
|
||||
public let parameters: [MethodParameter]
|
||||
public let isOverriding: Bool
|
||||
public let required: Bool
|
||||
public let attributes: [Attribute]
|
||||
public struct Initializer: Method, HasAccessibility {
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var returnType: WrappableType
|
||||
public var returnSignature: ReturnSignature
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var parameters: [MethodParameter]
|
||||
public var isOverriding: Bool
|
||||
public var required: Bool
|
||||
public var attributes: [Attribute]
|
||||
public var genericParameters: [GenericParameter]
|
||||
|
||||
public var isOptional: Bool {
|
||||
return false
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
// Copyright © 2016 Brightify. All rights reserved.
|
||||
//
|
||||
|
||||
public struct InstanceVariable: Token {
|
||||
public let name: String
|
||||
public let type: String
|
||||
public let accessibility: Accessibility
|
||||
public let setterAccessibility: Accessibility?
|
||||
public let range: CountableRange<Int>
|
||||
public let nameRange: CountableRange<Int>
|
||||
public struct InstanceVariable: Token, HasAccessibility {
|
||||
public var name: String
|
||||
public var type: WrappableType
|
||||
public var accessibility: Accessibility
|
||||
public var setterAccessibility: Accessibility?
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var overriding: Bool
|
||||
public let attributes: [Attribute]
|
||||
public var attributes: [Attribute]
|
||||
|
||||
public var readOnly: Bool {
|
||||
if let setterAccessibility = setterAccessibility {
|
||||
|
@ -33,7 +33,7 @@ public struct InstanceVariable: Token {
|
|||
let readOnlyString = readOnly ? "ReadOnly" : ""
|
||||
return [
|
||||
"name": name,
|
||||
"type": type,
|
||||
"type": type.sugarized,
|
||||
"accessibility": accessibility.sourceName,
|
||||
"isReadOnly": readOnly,
|
||||
"stubType": overriding ? "ClassToBeStubbed\(readOnlyString)Property" : "ProtocolToBeStubbed\(readOnlyString)Property",
|
||||
|
|
|
@ -13,4 +13,7 @@ public enum Kinds: String {
|
|||
case ClassDeclaration = "source.lang.swift.decl.class"
|
||||
case ExtensionDeclaration = "source.lang.swift.decl.extension"
|
||||
case InstanceVariable = "source.lang.swift.decl.var.instance"
|
||||
case GenericParameter = "source.lang.swift.decl.generic_type_param"
|
||||
case AssociatedType = "source.lang.swift.decl.associatedtype"
|
||||
case Optional = "source.decl.attribute.optional"
|
||||
}
|
||||
|
|
|
@ -6,60 +6,61 @@
|
|||
// Copyright © 2016 Brightify. All rights reserved.
|
||||
//
|
||||
|
||||
public protocol Method: Token {
|
||||
public protocol Method: Token, HasAccessibility {
|
||||
var name: String { get }
|
||||
var accessibility: Accessibility { get }
|
||||
var returnSignature: String { get }
|
||||
var returnSignature: ReturnSignature { get }
|
||||
var range: CountableRange<Int> { get }
|
||||
var nameRange: CountableRange<Int> { get }
|
||||
var parameters: [MethodParameter] { get }
|
||||
var isOptional: Bool { get }
|
||||
var isOverriding: Bool { get }
|
||||
var hasClosureParams: Bool { get }
|
||||
var hasOptionalParams: Bool { get }
|
||||
var attributes: [Attribute] { get }
|
||||
var genericParameters: [GenericParameter] { get }
|
||||
}
|
||||
|
||||
public extension Method {
|
||||
var rawName: String {
|
||||
return name.takeUntil(occurence: "(") ?? ""
|
||||
}
|
||||
|
||||
|
||||
var isInit: Bool {
|
||||
return rawName == "init"
|
||||
}
|
||||
|
||||
|
||||
var isDeinit: Bool {
|
||||
return rawName == "deinit"
|
||||
}
|
||||
|
||||
|
||||
var fullyQualifiedName: String {
|
||||
let parameterTypes = parameters.map { $0.type }
|
||||
let parameterTypes = parameters.map { ($0.isInout ? "inout " : "") + $0.type.sugarized }
|
||||
let nameParts = name.components(separatedBy: ":")
|
||||
let lastNamePart = nameParts.last ?? ""
|
||||
|
||||
|
||||
let returnSignatureDescription = returnSignature.description
|
||||
let returnSignatureString = returnSignatureDescription.isEmpty ? "" : " \(returnSignatureDescription)"
|
||||
|
||||
return zip(nameParts.dropLast(), parameterTypes)
|
||||
.map { $0 + ": " + $1 }
|
||||
.joined(separator: ", ") + lastNamePart + returnSignature
|
||||
.joined(separator: ", ") + lastNamePart + returnSignatureString
|
||||
}
|
||||
|
||||
|
||||
var isThrowing: Bool {
|
||||
return returnSignature.trimmed.hasPrefix("throws")
|
||||
return returnSignature.isThrowing
|
||||
}
|
||||
|
||||
var returnType: String {
|
||||
if let range = returnSignature.range(of: "->") {
|
||||
var type = String(returnSignature[range.upperBound...]).trimmed
|
||||
while type.hasSuffix("?") {
|
||||
type = "Optional<\(type[..<type.index(before: type.endIndex)])>"
|
||||
}
|
||||
return type
|
||||
} else {
|
||||
return "Void"
|
||||
}
|
||||
|
||||
var returnType: WrappableType {
|
||||
return returnSignature.returnType
|
||||
}
|
||||
|
||||
|
||||
var hasClosureParams: Bool {
|
||||
return parameters.filter { $0.isClosure }.count > 0
|
||||
return parameters.contains { $0.isClosure }
|
||||
}
|
||||
|
||||
var hasOptionalParams: Bool {
|
||||
return parameters.contains { $0.isOptional }
|
||||
}
|
||||
|
||||
public func isEqual(to other: Token) -> Bool {
|
||||
|
@ -69,23 +70,24 @@ public extension Method {
|
|||
|
||||
public func serialize() -> [String : Any] {
|
||||
let call = parameters.map {
|
||||
let referencedName = "\($0.isInout ? "&" : "")\($0.name)"
|
||||
if let label = $0.label {
|
||||
return "\(label): \($0.name)"
|
||||
return "\(label): \(referencedName)"
|
||||
} else {
|
||||
return $0.name
|
||||
return referencedName
|
||||
}
|
||||
}.joined(separator: ", ")
|
||||
|
||||
let stubFunctionPrefix = isOverriding ? "Class" : "Protocol"
|
||||
let stubFunction: String
|
||||
if isThrowing {
|
||||
if returnType == "Void" {
|
||||
if returnType.sugarized == "Void" {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubNoReturnThrowingFunction"
|
||||
} else {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubThrowingFunction"
|
||||
}
|
||||
} else {
|
||||
if returnType == "Void" {
|
||||
if returnType.sugarized == "Void" {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubNoReturnFunction"
|
||||
} else {
|
||||
stubFunction = "Cuckoo.\(stubFunctionPrefix)StubFunction"
|
||||
|
@ -100,26 +102,33 @@ public extension Method {
|
|||
}
|
||||
}.joined(separator: ", ")
|
||||
|
||||
let genericParametersString = genericParameters.map { $0.description }.joined(separator: ", ")
|
||||
let isGeneric = !genericParameters.isEmpty
|
||||
|
||||
return [
|
||||
"self": self,
|
||||
"name": rawName,
|
||||
"accessibility": accessibility.sourceName,
|
||||
"returnSignature": returnSignature,
|
||||
"returnSignature": returnSignature.description,
|
||||
"parameters": parameters,
|
||||
"parameterNames": parameters.map { $0.name }.joined(separator: ", "),
|
||||
"escapingParameterNames": escapingParameterNames,
|
||||
"isInit": isInit,
|
||||
"returnType": returnType,
|
||||
"returnType": returnType.sugarizedExplicitOnly,
|
||||
"isThrowing": isThrowing,
|
||||
"fullyQualifiedName": fullyQualifiedName,
|
||||
"call": call,
|
||||
"isOverriding": isOverriding,
|
||||
"parameterSignature": parameters.map { "\($0.labelAndName): \($0.type)" }.joined(separator: ", "),
|
||||
"parameterSignature": parameters.map { "\($0.labelAndName): \($0.isInout ? "inout " : "")\($0.type)" }.joined(separator: ", "),
|
||||
"parameterSignatureWithoutNames": parameters.map { "\($0.name): \($0.type)" }.joined(separator: ", "),
|
||||
"argumentSignature": parameters.map { $0.type.description }.joined(separator: ", "),
|
||||
"stubFunction": stubFunction,
|
||||
"inputTypes": parameters.map { $0.typeWithoutAttributes }.joined(separator: ", "),
|
||||
"isOptional": isOptional,
|
||||
"hasClosureParams": hasClosureParams,
|
||||
"hasOptionalParams": hasOptionalParams,
|
||||
"attributes": attributes.filter { $0.isSupported },
|
||||
"genericParameters": isGeneric ? "<\(genericParametersString)>" : "",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
//
|
||||
|
||||
public struct MethodParameter: Token, Equatable {
|
||||
public let label: String?
|
||||
public let name: String
|
||||
public let type: String
|
||||
public let range: CountableRange<Int>
|
||||
public let nameRange: CountableRange<Int>
|
||||
public var label: String?
|
||||
public var name: String
|
||||
public var type: WrappableType
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var isInout: Bool
|
||||
|
||||
public var labelAndName: String {
|
||||
if let label = label {
|
||||
|
@ -22,7 +23,7 @@ public struct MethodParameter: Token, Equatable {
|
|||
}
|
||||
|
||||
public var typeWithoutAttributes: String {
|
||||
return type.replacingOccurrences(of: "@escaping", with: "").replacingOccurrences(of: "@autoclosure", with: "").trimmed
|
||||
return type.withoutAttributes.sugarized.trimmed
|
||||
}
|
||||
|
||||
public func isEqual(to other: Token) -> Bool {
|
||||
|
@ -33,9 +34,13 @@ public struct MethodParameter: Token, Equatable {
|
|||
public var isClosure: Bool {
|
||||
return typeWithoutAttributes.hasPrefix("(") && typeWithoutAttributes.range(of: "->") != nil
|
||||
}
|
||||
|
||||
public var isOptional: Bool {
|
||||
return type.isOptional
|
||||
}
|
||||
|
||||
public var isEscaping: Bool {
|
||||
return isClosure && (type.hasPrefix("@escaping") || type.hasSuffix(")?"))
|
||||
return isClosure && (type.containsAttribute(named: "@escaping") || type.isOptional)
|
||||
}
|
||||
|
||||
public func serialize() -> [String : Any] {
|
||||
|
@ -46,6 +51,7 @@ public struct MethodParameter: Token, Equatable {
|
|||
"labelAndName": labelAndName,
|
||||
"typeWithoutAttributes": typeWithoutAttributes,
|
||||
"isClosure": isClosure,
|
||||
"isOptional": isOptional,
|
||||
"isEscaping": isEscaping
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,17 +6,18 @@
|
|||
// Copyright © 2016 Brightify. All rights reserved.
|
||||
//
|
||||
|
||||
public struct ProtocolDeclaration: ContainerToken {
|
||||
public let name: String
|
||||
public let accessibility: Accessibility
|
||||
public let range: CountableRange<Int>
|
||||
public let nameRange: CountableRange<Int>
|
||||
public let bodyRange: CountableRange<Int>
|
||||
public let initializers: [Initializer]
|
||||
public let children: [Token]
|
||||
public struct ProtocolDeclaration: ContainerToken, HasAccessibility {
|
||||
public let implementation: Bool = false
|
||||
public let inheritedTypes: [InheritanceDeclaration]
|
||||
public let attributes: [Attribute]
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var bodyRange: CountableRange<Int>
|
||||
public var initializers: [Initializer]
|
||||
public var children: [Token]
|
||||
public var inheritedTypes: [InheritanceDeclaration]
|
||||
public var attributes: [Attribute]
|
||||
public var genericParameters: [GenericParameter]
|
||||
|
||||
public func replace(children tokens: [Token]) -> ProtocolDeclaration {
|
||||
return ProtocolDeclaration(
|
||||
|
@ -28,7 +29,8 @@ public struct ProtocolDeclaration: ContainerToken {
|
|||
initializers: self.initializers,
|
||||
children: tokens,
|
||||
inheritedTypes: self.inheritedTypes,
|
||||
attributes: self.attributes)
|
||||
attributes: self.attributes,
|
||||
genericParameters: self.genericParameters)
|
||||
}
|
||||
|
||||
public func isEqual(to other: Token) -> Bool {
|
||||
|
|
|
@ -7,14 +7,15 @@
|
|||
//
|
||||
|
||||
public struct ProtocolMethod: Method {
|
||||
public let name: String
|
||||
public let accessibility: Accessibility
|
||||
public let returnSignature: String
|
||||
public let range: CountableRange<Int>
|
||||
public let nameRange: CountableRange<Int>
|
||||
public let parameters: [MethodParameter]
|
||||
public let attributes: [Attribute]
|
||||
|
||||
public var name: String
|
||||
public var accessibility: Accessibility
|
||||
public var returnSignature: ReturnSignature
|
||||
public var range: CountableRange<Int>
|
||||
public var nameRange: CountableRange<Int>
|
||||
public var parameters: [MethodParameter]
|
||||
public var attributes: [Attribute]
|
||||
public var genericParameters: [GenericParameter]
|
||||
|
||||
public var isOptional: Bool {
|
||||
return attributes.map { $0.kind }.contains(.optional)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// ReturnSignature.swift
|
||||
// CuckooGeneratorFramework
|
||||
//
|
||||
// Created by Matyáš Kříž on 25/03/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct ReturnSignature {
|
||||
var throwString: String?
|
||||
var returnType: WrappableType
|
||||
var whereConstraints: [String]
|
||||
|
||||
var isThrowing: Bool {
|
||||
guard let throwString = throwString else { return false }
|
||||
return throwString.trimmed.hasPrefix("throws")
|
||||
}
|
||||
}
|
||||
|
||||
extension ReturnSignature: CustomStringConvertible {
|
||||
public var description: String {
|
||||
let trimmedReturnType = returnType.sugarizedExplicitOnly.trimmed
|
||||
let returnString = trimmedReturnType.isEmpty || trimmedReturnType == "Void" ? nil : "-> \(returnType)"
|
||||
let whereString = whereConstraints.isEmpty ? nil : "where \(whereConstraints.joined(separator: ", "))"
|
||||
return [throwString, returnString, whereString].compactMap { $0 }.joined(separator: " ")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
//
|
||||
// WrappableType.swift
|
||||
// CuckooGeneratorFramework
|
||||
//
|
||||
// Created by Matyáš Kříž on 13/03/2019.
|
||||
//
|
||||
|
||||
public enum WrappableType {
|
||||
indirect case optional(WrappableType)
|
||||
indirect case implicitlyUnwrappedOptional(WrappableType)
|
||||
indirect case attributed(WrappableType, attributes: [String])
|
||||
case type(String)
|
||||
|
||||
public var sugarized: String {
|
||||
switch self {
|
||||
case .optional(let wrapped):
|
||||
return "\(wrapped.sugarized)?"
|
||||
case .implicitlyUnwrappedOptional(let wrapped):
|
||||
return "\(wrapped.sugarized)!"
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return "\(attributes.joined(separator: " ")) \(wrapped.sugarized)"
|
||||
case .type(let type):
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
public var sugarizedExplicitOnly: String {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return "\(wrapped.sugarizedExplicitOnly)?"
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return "\(attributes.joined(separator: " ")) \(wrapped.sugarizedExplicitOnly)"
|
||||
case .type(let type):
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
public var desugarized: String {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return "Optional<\(wrapped.desugarized)>"
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return "\(attributes.joined(separator: " ")) \(wrapped.desugarized)"
|
||||
case .type(let type):
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
public var unoptionaled: WrappableType {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return wrapped.unoptionaled
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return .attributed(wrapped.unoptionaled, attributes: attributes)
|
||||
case .type:
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public var unwrapped: WrappableType {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return wrapped
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return .attributed(wrapped.unwrapped, attributes: attributes)
|
||||
case .type:
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
public var withoutAttributes: WrappableType {
|
||||
switch self {
|
||||
case .optional(let wrapped):
|
||||
return .optional(wrapped.withoutAttributes)
|
||||
case .implicitlyUnwrappedOptional(let wrapped):
|
||||
return .implicitlyUnwrappedOptional(wrapped.withoutAttributes)
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return wrapped
|
||||
case .type(let typeString):
|
||||
return .type(typeString)
|
||||
}
|
||||
}
|
||||
|
||||
public var isOptional: Bool {
|
||||
switch self {
|
||||
case .optional, .implicitlyUnwrappedOptional:
|
||||
return true
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return wrapped.isOptional
|
||||
case .type:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public init(parsing value: String) {
|
||||
let trimmedValue = value.trimmed
|
||||
let optionalPrefix = "Optional<"
|
||||
if trimmedValue.hasPrefix("@") {
|
||||
let (attributes, resultString) = ["@autoclosure", "@escaping", "@noescape"]
|
||||
.reduce(([], trimmedValue)) { acc, next -> ([String], String) in
|
||||
var (attributes, resultString) = acc
|
||||
guard let range = resultString.range(of: next) else { return acc }
|
||||
resultString.removeSubrange(range)
|
||||
attributes.append(next)
|
||||
return (attributes, resultString)
|
||||
}
|
||||
self = .attributed(WrappableType(parsing: resultString), attributes: attributes)
|
||||
} else if trimmedValue.hasSuffix("?") {
|
||||
if trimmedValue.contains("->") && !trimmedValue.hasSuffix(")?") {
|
||||
self = .type(trimmedValue)
|
||||
} else {
|
||||
self = .optional(WrappableType(parsing: String(trimmedValue.dropLast())))
|
||||
}
|
||||
} else if trimmedValue.hasPrefix(optionalPrefix) {
|
||||
self = .optional(WrappableType(parsing: String(trimmedValue.dropFirst(optionalPrefix.count).dropLast())))
|
||||
} else if trimmedValue.hasSuffix("!") {
|
||||
self = .implicitlyUnwrappedOptional(WrappableType(parsing: String(trimmedValue.dropLast())))
|
||||
} else {
|
||||
self = .type(trimmedValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func containsAttribute(named attribute: String) -> Bool {
|
||||
switch self {
|
||||
case .optional(let wrapped), .implicitlyUnwrappedOptional(let wrapped):
|
||||
return wrapped.containsAttribute(named: attribute)
|
||||
case .attributed(let wrapped, let attributes):
|
||||
return attributes.contains(attribute.trimmed)
|
||||
case .type(let typeString):
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WrappableType: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return sugarized
|
||||
}
|
||||
}
|
||||
|
||||
extension WrappableType: Equatable {
|
||||
public static func ==(lhs: WrappableType, rhs: WrappableType) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.optional(let lhsWrapped), .optional(let rhsWrapped)),
|
||||
(.implicitlyUnwrappedOptional(let lhsWrapped), .implicitlyUnwrappedOptional(let rhsWrapped)):
|
||||
return lhsWrapped == rhsWrapped
|
||||
case (.attributed(let lhsWrapped, let lhsAttributes), .attributed(let rhsWrapped, let rhsAttributes)):
|
||||
return lhsWrapped == rhsWrapped && lhsAttributes == rhsAttributes
|
||||
case (.type(let lhsType), .type(let rhsType)):
|
||||
return lhsType == rhsType
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,8 +51,8 @@ extension Sequence {
|
|||
|
||||
internal func extractRange(from dictionary: [String: SourceKitRepresentable], offset: Key, length: Key) -> CountableRange<Int>? {
|
||||
guard let
|
||||
offset = (dictionary[offset.rawValue] as? Int64).map({ Int($0) }),
|
||||
let length = (dictionary[length.rawValue] as? Int64).map({ Int($0) })
|
||||
offset = (dictionary[offset.rawValue] as? Int64).map(Int.init),
|
||||
let length = (dictionary[length.rawValue] as? Int64).map(Int.init)
|
||||
else { return nil }
|
||||
|
||||
return offset..<offset.advanced(by: length)
|
||||
|
|
30
README.md
30
README.md
|
@ -31,8 +31,8 @@ List of all changes and new features can be found [here](CHANGELOG.md).
|
|||
We are still missing support for some important features like:
|
||||
|
||||
* <del>inheritance (grandparent methods)</del>
|
||||
* generics
|
||||
* type inference for instance variables (you need to write it explicitly, otherwise it will be replaced with "__UnknownType")
|
||||
* <del>generics</del>
|
||||
* type inference for instance variables (you need to write it explicitly, otherwise it will be replaced with `__UnknownType`)
|
||||
|
||||
## What will not be supported
|
||||
|
||||
|
@ -180,6 +180,32 @@ stub(mock) { stub in
|
|||
|
||||
Notice the `get` and `set` these will be used in verification later.
|
||||
|
||||
##### Enabling default implementation
|
||||
|
||||
In addition to stubbing, you can enable default implementation using an instance of the original class that's being mocked. Every method/variable that is not stubbed will behave according to original implementation.
|
||||
|
||||
Enabling default implementation is achieved by simply calling the provided method:
|
||||
|
||||
```Swift
|
||||
let original = OriginalClass<Int>(value: 12)
|
||||
mock.enableDefaultImplementation(original)
|
||||
```
|
||||
|
||||
For passing classes into the method, nothing changes whether you're mocking a class or a protocol. There however is a difference if you're using a `struct` to conform to the original protocol we are mocking:
|
||||
|
||||
```Swift
|
||||
let original = ConformingStruct<String>(value: "Hello, Cuckoo!")
|
||||
mock.enableDefaultImplementation(original)
|
||||
// or if you need to track changes:
|
||||
mock.enableDefaultImplementation(mutating: &original)
|
||||
```
|
||||
|
||||
Note that this only concerns `struct`s. `enableDefaultImplementation(_:)` and `enableDefaultImplementation(mutating:)` are different in state tracking.
|
||||
|
||||
The standard non-mutating method `enableDefaultImplementation(_:)` creates a copy of the `struct` for default implementation and works with that. However, the mutating method `enableDefaultImplementation(mutating:)` takes a reference to the struct and the changes of the `original` are reflected in the default implementation calls even after enabling default implementation.
|
||||
|
||||
We recommend using the non-mutating method for enabling default implementation unless you need to track the changes for consistency within your code.
|
||||
|
||||
##### Chain stubbing
|
||||
|
||||
It is possible to chain stubbing. This is useful if you need to set different behavior for multiple calls in order. The last behavior will last for all other calls. Syntax goes like this:
|
||||
|
|
|
@ -17,6 +17,12 @@ public protocol Matchable {
|
|||
var matcher: ParameterMatcher<MatchedType> { get }
|
||||
}
|
||||
|
||||
public protocol OptionalMatchable {
|
||||
associatedtype OptionalMatchedType
|
||||
|
||||
var optionalMatcher: ParameterMatcher<OptionalMatchedType?> { get }
|
||||
}
|
||||
|
||||
public extension Matchable {
|
||||
public func or<M>(_ otherMatchable: M) -> ParameterMatcher<MatchedType> where M: Matchable, M.MatchedType == MatchedType {
|
||||
return ParameterMatcher {
|
||||
|
@ -31,92 +37,125 @@ public extension Matchable {
|
|||
}
|
||||
}
|
||||
|
||||
extension Bool: Matchable {
|
||||
public var matcher: ParameterMatcher<Bool> {
|
||||
extension Optional: Matchable where Wrapped: Matchable, Wrapped.MatchedType == Wrapped {
|
||||
public typealias MatchedType = Wrapped?
|
||||
|
||||
public var matcher: ParameterMatcher<Wrapped?> {
|
||||
return ParameterMatcher<Wrapped?> { other in
|
||||
switch (self, other) {
|
||||
case (.none, .none):
|
||||
return true
|
||||
case (.some(let lhs), .some(let rhs)):
|
||||
return lhs.matcher.matches(rhs)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: OptionalMatchable where Wrapped: OptionalMatchable, Wrapped.OptionalMatchedType == Wrapped {
|
||||
public typealias OptionalMatchedType = Wrapped
|
||||
|
||||
public var optionalMatcher: ParameterMatcher<Wrapped?> {
|
||||
return ParameterMatcher<Wrapped?> { other in
|
||||
switch (self, other) {
|
||||
case (.none, .none):
|
||||
return true
|
||||
case (.some(let lhs), .some(let rhs)):
|
||||
return lhs.optionalMatcher.matches(rhs)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Matchable where Self: Equatable {
|
||||
public var matcher: ParameterMatcher<Self> {
|
||||
return equal(to: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension String: Matchable {
|
||||
public var matcher: ParameterMatcher<String> {
|
||||
return equal(to: self)
|
||||
extension OptionalMatchable where OptionalMatchedType == Self, Self: Equatable {
|
||||
public var optionalMatcher: ParameterMatcher<OptionalMatchedType?> {
|
||||
return ParameterMatcher { other in
|
||||
return Optional(self) == other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Float: Matchable {
|
||||
public var matcher: ParameterMatcher<Float> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Bool: Matchable {}
|
||||
extension Bool: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Bool
|
||||
}
|
||||
|
||||
extension Double: Matchable {
|
||||
public var matcher: ParameterMatcher<Double> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension String: Matchable {}
|
||||
extension String: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = String
|
||||
}
|
||||
|
||||
extension Character: Matchable {
|
||||
public var matcher: ParameterMatcher<Character> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Float: Matchable {}
|
||||
extension Float: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Float
|
||||
}
|
||||
|
||||
extension Int: Matchable {
|
||||
public var matcher: ParameterMatcher<Int> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Double: Matchable {}
|
||||
extension Double: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Double
|
||||
}
|
||||
|
||||
extension Int8: Matchable {
|
||||
public var matcher: ParameterMatcher<Int8> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Character: Matchable {}
|
||||
extension Character: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Character
|
||||
}
|
||||
|
||||
extension Int16: Matchable {
|
||||
public var matcher: ParameterMatcher<Int16> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Int: Matchable {}
|
||||
extension Int: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Int
|
||||
}
|
||||
|
||||
extension Int32: Matchable {
|
||||
public var matcher: ParameterMatcher<Int32> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Int8: Matchable {}
|
||||
extension Int8: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Int8
|
||||
}
|
||||
|
||||
extension Int64: Matchable {
|
||||
public var matcher: ParameterMatcher<Int64> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Int16: Matchable {}
|
||||
extension Int16: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Int16
|
||||
}
|
||||
|
||||
extension UInt: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Int32: Matchable {}
|
||||
extension Int32: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Int32
|
||||
}
|
||||
|
||||
extension UInt8: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt8> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension Int64: Matchable {}
|
||||
extension Int64: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = Int64
|
||||
}
|
||||
|
||||
extension UInt16: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt16> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension UInt: Matchable {}
|
||||
extension UInt: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = UInt
|
||||
}
|
||||
|
||||
extension UInt32: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt32> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension UInt8: Matchable {}
|
||||
extension UInt8: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = UInt8
|
||||
}
|
||||
|
||||
extension UInt64: Matchable {
|
||||
public var matcher: ParameterMatcher<UInt64> {
|
||||
return equal(to: self)
|
||||
}
|
||||
extension UInt16: Matchable {}
|
||||
extension UInt16: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = UInt16
|
||||
}
|
||||
|
||||
extension UInt32: Matchable {}
|
||||
extension UInt32: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = UInt32
|
||||
}
|
||||
|
||||
extension UInt64: Matchable {}
|
||||
extension UInt64: OptionalMatchable {
|
||||
public typealias OptionalMatchedType = UInt64
|
||||
}
|
||||
|
|
|
@ -22,3 +22,25 @@ public struct ParameterMatcher<T>: Matchable {
|
|||
return matchesFunction(input)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol CuckooOptionalType {
|
||||
associatedtype Wrapped
|
||||
|
||||
static func from(optional: Optional<Wrapped>) -> Self
|
||||
}
|
||||
|
||||
extension Optional: CuckooOptionalType {
|
||||
public static func from(optional: Optional<Wrapped>) -> Optional<Wrapped> {
|
||||
return optional
|
||||
}
|
||||
}
|
||||
|
||||
extension ParameterMatcher: OptionalMatchable where T: CuckooOptionalType {
|
||||
public typealias OptionalMatchedType = T.Wrapped
|
||||
|
||||
public var optionalMatcher: ParameterMatcher<T.Wrapped?> {
|
||||
return ParameterMatcher<T.Wrapped?> { other in
|
||||
self.matchesFunction(T.from(optional: other))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ public class MockManager {
|
|||
return stub
|
||||
}
|
||||
|
||||
public func verify<IN, OUT>(_ method: String, callMatcher: CallMatcher, parameterMatchers: [ParameterMatcher<IN>], sourceLocation: SourceLocation) -> __DoNotUse<OUT> {
|
||||
public func verify<IN, OUT>(_ method: String, callMatcher: CallMatcher, parameterMatchers: [ParameterMatcher<IN>], sourceLocation: SourceLocation) -> __DoNotUse<IN, OUT> {
|
||||
var calls: [StubCall] = []
|
||||
var indexesToRemove: [Int] = []
|
||||
for (i, stubCall) in stubCalls.enumerated() {
|
||||
|
|
|
@ -20,6 +20,12 @@ public func wrap<M: Matchable, IN>(matchable: M, mapping: @escaping (IN) -> M.Ma
|
|||
}
|
||||
}
|
||||
|
||||
public func wrap<M: OptionalMatchable, IN, O>(matchable: M, mapping: @escaping (IN) -> M.OptionalMatchedType?) -> ParameterMatcher<IN> where M.OptionalMatchedType == O {
|
||||
return ParameterMatcher {
|
||||
return matchable.optionalMatcher.matches(mapping($0))
|
||||
}
|
||||
}
|
||||
|
||||
public typealias SourceLocation = (file: StaticString, line: UInt)
|
||||
|
||||
public func escapingStub<IN, OUT>(for closure: (IN) -> OUT) -> (IN) -> OUT {
|
||||
|
@ -28,6 +34,12 @@ public func escapingStub<IN, OUT>(for closure: (IN) -> OUT) -> (IN) -> OUT {
|
|||
}
|
||||
}
|
||||
|
||||
public func escapingStub<IN, OUT>(for closure: (inout IN) -> OUT) -> (inout IN) -> OUT {
|
||||
return { _ in
|
||||
fatalError("This is a stub! It's not supposed to be called!")
|
||||
}
|
||||
}
|
||||
|
||||
public func escapingStub<IN, OUT>(for closure: (IN) throws -> OUT) -> (IN) throws -> OUT {
|
||||
return { _ in
|
||||
fatalError("This is a stub! It's not supposed to be called!")
|
||||
|
|
|
@ -13,12 +13,12 @@ public struct VerifyProperty<T> {
|
|||
private let sourceLocation: SourceLocation
|
||||
|
||||
@discardableResult
|
||||
public func get() -> __DoNotUse<T> {
|
||||
public func get() -> __DoNotUse<Void, T> {
|
||||
return manager.verify(getterName(name), callMatcher: callMatcher, parameterMatchers: [] as [ParameterMatcher<Void>], sourceLocation: sourceLocation)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func set<M: Matchable>(_ matcher: M) -> __DoNotUse<Void> where M.MatchedType == T {
|
||||
public func set<M: Matchable>(_ matcher: M) -> __DoNotUse<T, Void> where M.MatchedType == T {
|
||||
return manager.verify(setterName(name), callMatcher: callMatcher, parameterMatchers: [matcher.matcher], sourceLocation: sourceLocation)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ public struct VerifyReadOnlyProperty<T> {
|
|||
private let sourceLocation: SourceLocation
|
||||
|
||||
@discardableResult
|
||||
public func get() -> __DoNotUse<T> {
|
||||
public func get() -> __DoNotUse<Void, T> {
|
||||
return manager.verify(getterName(name), callMatcher: callMatcher, parameterMatchers: [] as [ParameterMatcher<Void>], sourceLocation: sourceLocation)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
//
|
||||
|
||||
/// Marker struct for use as a return type in verification.
|
||||
public struct __DoNotUse<T> { }
|
||||
public struct __DoNotUse<IN, OUT> { }
|
||||
|
|
|
@ -189,6 +189,40 @@ class ClassTest: XCTestCase {
|
|||
XCTAssertEqual(mock.callingCountCharactersMethodWithHello(), 0)
|
||||
verify(mock).callingCountCharactersMethodWithHello()
|
||||
}
|
||||
|
||||
func testInout() {
|
||||
let mock = MockInoutMethodClass()
|
||||
stub(mock) { mock in
|
||||
when(mock.inoutko(param: anyInt())).then { param in
|
||||
print(param)
|
||||
}
|
||||
}
|
||||
|
||||
var integer = 12
|
||||
mock.inoutko(param: &integer)
|
||||
}
|
||||
|
||||
func testOptionals() {
|
||||
let mock = MockOptionalParamsClass()
|
||||
|
||||
// stub(mock) { mock in
|
||||
// when(mock.clashingFunction(param1: Optional(1), param2: "Henlo Fren") as Cuckoo.ClassStubNoReturnFunction<(Int?, String)>).thenDoNothing()
|
||||
// when(mock.clashingFunction(param1: Optional.none, param2: "Henlo Fren") as Cuckoo.ClassStubNoReturnFunction<(Int?, String)>).thenDoNothing()
|
||||
// when(mock.clashingFunction(param1: anyInt(), param2: "What's the question?") as Cuckoo.ClassStubNoReturnFunction<(Int, String?)>).then {
|
||||
// print("What's 6 times 9? \($0.0)")
|
||||
// }
|
||||
// when(mock.clashingFunction(param1: anyInt(), param2: isNil()) as Cuckoo.ClassStubNoReturnFunction<(Int, String?)>).then {
|
||||
// print("What's 6 times 9? \($0.0)")
|
||||
// }
|
||||
// when(mock.function(param: "string")).thenDoNothing()
|
||||
// }
|
||||
//
|
||||
// mock.clashingFunction(param1: Optional(22), param2: "Henlo Fren")
|
||||
// mock.clashingFunction(param1: 42, param2: Optional("What's the question?"))
|
||||
//
|
||||
// _ = verify(mock).clashingFunction(param1: anyInt(), param2: "Henlo Fren") as Cuckoo.__DoNotUse<(Int?, String), Void>
|
||||
// _ = verify(mock).clashingFunction(param1: 42, param2: "What's the question?") as Cuckoo.__DoNotUse<(Int, String?), Void>
|
||||
}
|
||||
|
||||
private enum TestError: Error {
|
||||
case unknown
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
//
|
||||
// GenericClassTest.swift
|
||||
// Cuckoo
|
||||
//
|
||||
// Created by Matyáš Kříž on 26/11/2018.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Cuckoo
|
||||
|
||||
extension GenericClass: Mocked {
|
||||
typealias MockType = MockGenericClass<T, U, V>
|
||||
}
|
||||
|
||||
class GenericClassTest: XCTestCase {
|
||||
private var mock: MockGenericClass<Int, String, Bool>!
|
||||
private var original: GenericClass<Int, String, Bool>!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
mock = MockGenericClass(theT: 10, theU: "Hello, generics!", theV: false)
|
||||
original = GenericClass(theT: 42, theU: "Hello world!", theV: false)
|
||||
}
|
||||
|
||||
func testReadWritePropertyWithMockCreator() {
|
||||
let mock = createMock(for: GenericClass<Int, String, Bool>.self) { builder, stub in
|
||||
when(stub.readWritePropertyU.get).thenReturn("a")
|
||||
|
||||
return MockGenericClass(theT: 0, theU: "", theV: true)
|
||||
}
|
||||
|
||||
XCTAssertEqual(mock.readWritePropertyU, "a")
|
||||
_ = verify(mock).readWritePropertyU.get
|
||||
}
|
||||
|
||||
func testReadWriteProperty() {
|
||||
stub(mock) { mock in
|
||||
when(mock.readWritePropertyV.get).thenReturn(true)
|
||||
}
|
||||
|
||||
XCTAssertEqual(mock.readWritePropertyV, true)
|
||||
_ = verify(mock).readWritePropertyV.get
|
||||
}
|
||||
|
||||
func testConstantProperty() {
|
||||
mock.enableDefaultImplementation(original)
|
||||
XCTAssertEqual(mock.constant, 10.0)
|
||||
}
|
||||
|
||||
func testModification() {
|
||||
mock.enableDefaultImplementation(original)
|
||||
let numbers = [127, 0, 0, 1]
|
||||
for number in numbers {
|
||||
original.readWritePropertyT = number
|
||||
XCTAssertEqual(mock.readWritePropertyT, number)
|
||||
}
|
||||
|
||||
verify(mock, times(numbers.count)).readWritePropertyT.get
|
||||
}
|
||||
|
||||
func testReadWriteProperties() {
|
||||
var calledT = false
|
||||
var calledU = false
|
||||
var calledV = false
|
||||
stub(mock) { mock in
|
||||
when(mock.readWritePropertyT.get).thenReturn(1)
|
||||
when(mock.readWritePropertyT.set(anyInt())).then { _ in calledT = true }
|
||||
|
||||
when(mock.readWritePropertyU.get).thenReturn("Hello, mocker!")
|
||||
when(mock.readWritePropertyU.set(anyString())).then { _ in calledU = true }
|
||||
|
||||
when(mock.readWritePropertyV.get).thenReturn(false)
|
||||
when(mock.readWritePropertyV.set(any(Bool.self))).then { _ in calledV = true }
|
||||
}
|
||||
|
||||
mock.readWritePropertyT = 0
|
||||
XCTAssertEqual(mock.readWritePropertyT, 1)
|
||||
XCTAssertTrue(calledT)
|
||||
_ = verify(mock).readWritePropertyT.get
|
||||
verify(mock).readWritePropertyT.set(0)
|
||||
|
||||
mock.readWritePropertyU = "NO MOCKING FOR YOU"
|
||||
XCTAssertEqual(mock.readWritePropertyU, "Hello, mocker!")
|
||||
XCTAssertTrue(calledU)
|
||||
_ = verify(mock).readWritePropertyU.get
|
||||
verify(mock).readWritePropertyU.set("NO MOCKING FOR YOU")
|
||||
|
||||
mock.readWritePropertyV = true
|
||||
XCTAssertEqual(mock.readWritePropertyV, false)
|
||||
XCTAssertTrue(calledV)
|
||||
_ = verify(mock).readWritePropertyV.get
|
||||
verify(mock).readWritePropertyV.set(true)
|
||||
}
|
||||
|
||||
func testOptionalProperty() {
|
||||
var called = false
|
||||
stub(mock) { mock in
|
||||
when(mock.optionalProperty.get).thenReturn(nil)
|
||||
when(mock.optionalProperty.set(anyString())).then { _ in called = true }
|
||||
}
|
||||
|
||||
mock.optionalProperty = "tukabel"
|
||||
|
||||
XCTAssertNil(mock.optionalProperty)
|
||||
XCTAssertTrue(called)
|
||||
_ = verify(mock).optionalProperty.get
|
||||
verify(mock).optionalProperty.set(equal(to: "tukabel"))
|
||||
}
|
||||
|
||||
func testNoReturn() {
|
||||
var called = false
|
||||
stub(mock) { mock in
|
||||
when(mock.noReturn()).then { _ in called = true }
|
||||
}
|
||||
|
||||
mock.noReturn()
|
||||
|
||||
XCTAssertTrue(called)
|
||||
verify(mock).noReturn()
|
||||
}
|
||||
|
||||
func testUnequal() {
|
||||
stub(mock) { mock in
|
||||
when(mock.unequal(one: false, two: false)).thenReturn(false)
|
||||
when(mock.unequal(one: false, two: true)).thenReturn(true)
|
||||
when(mock.unequal(one: true, two: false)).thenReturn(true)
|
||||
when(mock.unequal(one: true, two: true)).thenReturn(false)
|
||||
}
|
||||
|
||||
XCTAssertFalse(mock.unequal(one: false, two: false))
|
||||
XCTAssertTrue(mock.unequal(one: false, two: true))
|
||||
XCTAssertTrue(mock.unequal(one: true, two: false))
|
||||
XCTAssertFalse(mock.unequal(one: true, two: true))
|
||||
}
|
||||
|
||||
func testGetThird() {
|
||||
stub(mock) { mock in
|
||||
when(mock.getThird(foo: anyInt(), bar: "gimme true", baz: any(Bool.self))).thenReturn(false)
|
||||
when(mock.getThird(foo: anyInt(), bar: "gimme false", baz: any(Bool.self))).thenReturn(true)
|
||||
}
|
||||
|
||||
XCTAssertFalse(mock.getThird(foo: 10, bar: "gimme true", baz: true))
|
||||
verify(mock).getThird(foo: 10, bar: "gimme true", baz: true)
|
||||
XCTAssertTrue(mock.getThird(foo: 1099, bar: "gimme false", baz: false))
|
||||
verify(mock).getThird(foo: 1099, bar: "gimme false", baz: false)
|
||||
}
|
||||
|
||||
func testPrint() {
|
||||
stub(mock) { mock in
|
||||
when(mock.print(theT: anyInt())).thenDoNothing()
|
||||
}
|
||||
|
||||
mock.print(theT: 555)
|
||||
verify(mock).print(theT: 555)
|
||||
}
|
||||
|
||||
func testEncode() {
|
||||
mock.enableDefaultImplementation(original)
|
||||
stub(mock) { mock in
|
||||
when(mock.encode(theU: anyString())).thenCallRealImplementation()
|
||||
}
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
let world = "Hello, world!"
|
||||
XCTAssertEqual(mock.encode(theU: world), try! encoder.encode(["root": world]))
|
||||
verify(mock).encode(theU: world)
|
||||
}
|
||||
|
||||
func testWithClosure() {
|
||||
stub(mock) { mock in
|
||||
when(mock.withClosure(anyClosure())).then { $0(666) }
|
||||
}
|
||||
|
||||
XCTAssertEqual(mock.withClosure { number in number * 2 + 5 }, 1337)
|
||||
verify(mock).withClosure(anyClosure())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
//
|
||||
// GenericProtocolTest.swift
|
||||
// Cuckoo
|
||||
//
|
||||
// Created by Matyáš Kříž on 26/11/2018.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Cuckoo
|
||||
|
||||
private class GenericProtocolConformerClass<C: AnyObject, V>: GenericProtocol {
|
||||
let readOnlyPropertyC: C
|
||||
var readWritePropertyV: V
|
||||
|
||||
let constant: Int = 0
|
||||
var optionalProperty: V?
|
||||
|
||||
required init(theC: C, theV: V) {
|
||||
readOnlyPropertyC = theC
|
||||
readWritePropertyV = theV
|
||||
}
|
||||
|
||||
func callSomeC(theC: C) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func callSomeV(theV: V) -> Int {
|
||||
switch theV {
|
||||
case let int as Int:
|
||||
return int
|
||||
case let string as String:
|
||||
return Int(string) ?? 8008135
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func compute(classy: C, value: V) -> C {
|
||||
guard let testyClassy = classy as? TestedClass else { return classy }
|
||||
switch value {
|
||||
case let int as Int:
|
||||
testyClassy.readWriteProperty = int
|
||||
case _ as String:
|
||||
testyClassy.optionalProperty = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
return testyClassy as! C
|
||||
}
|
||||
|
||||
func noReturn() {}
|
||||
}
|
||||
|
||||
private struct GenericProtocolConformerStruct<C: AnyObject, V>: GenericProtocol {
|
||||
let readOnlyPropertyC: C
|
||||
var readWritePropertyV: V
|
||||
|
||||
let constant: Int = 0
|
||||
var optionalProperty: V?
|
||||
|
||||
init(theC: C, theV: V) {
|
||||
readOnlyPropertyC = theC
|
||||
readWritePropertyV = theV
|
||||
}
|
||||
|
||||
func callSomeC(theC: C) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func callSomeV(theV: V) -> Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func compute(classy: C, value: V) -> C {
|
||||
return classy
|
||||
}
|
||||
|
||||
func noReturn() {}
|
||||
}
|
||||
|
||||
class GenericProtocolTest: XCTestCase {
|
||||
private func createMock<V>(value: V) -> MockGenericProtocol<MockTestedClass, V> {
|
||||
let classy = MockTestedClass()
|
||||
return MockGenericProtocol(theC: classy, theV: value)
|
||||
}
|
||||
|
||||
func testReadOnlyProperty() {
|
||||
let mock = createMock(value: 10)
|
||||
stub(mock) { mock in
|
||||
when(mock.readOnlyPropertyC.get).thenReturn(MockTestedClass())
|
||||
}
|
||||
|
||||
_ = verify(mock).readOnlyPropertyC.get
|
||||
}
|
||||
|
||||
func testReadWriteProperty() {
|
||||
let mock = createMock(value: 10)
|
||||
stub(mock) { mock in
|
||||
when(mock.readWritePropertyV.get).then { 11 }
|
||||
when(mock.readWritePropertyV.set(anyInt())).thenDoNothing()
|
||||
}
|
||||
|
||||
mock.readWritePropertyV = 42
|
||||
XCTAssertEqual(mock.readWritePropertyV, 11)
|
||||
_ = verify(mock).readWritePropertyV.get
|
||||
verify(mock).readWritePropertyV.set(42)
|
||||
}
|
||||
|
||||
func testOptionalProperty() {
|
||||
let mock = createMock(value: false)
|
||||
var called = false
|
||||
stub(mock) { mock in
|
||||
when(mock.optionalProperty.get).thenReturn(true)
|
||||
when(mock.optionalProperty.set(any(Bool?.self))).then { _ in called = true }
|
||||
}
|
||||
|
||||
mock.optionalProperty = false
|
||||
|
||||
XCTAssertTrue(mock.optionalProperty == true)
|
||||
XCTAssertTrue(called)
|
||||
_ = verify(mock).optionalProperty.get
|
||||
verify(mock).optionalProperty.set(equal(to: false))
|
||||
}
|
||||
|
||||
func testNoReturn() {
|
||||
let mock = createMock(value: "Hello. Sniffing through tests? If you're having trouble with Cuckoo, shoot us a message!")
|
||||
var called = false
|
||||
stub(mock) { mock in
|
||||
when(mock.noReturn()).then { _ in called = true }
|
||||
}
|
||||
|
||||
mock.noReturn()
|
||||
|
||||
XCTAssertTrue(called)
|
||||
verify(mock).noReturn()
|
||||
}
|
||||
|
||||
func testModification() {
|
||||
let mock = createMock(value: ["EXTERMINATE!": "EXTERMINATE!!", "EXTERMINATE!!!": "EXTERMINATE!!!!"])
|
||||
let original = GenericProtocolConformerClass(theC: MockTestedClass(), theV: ["Sir, may I help you?": "Nope, just lookin' 👀"])
|
||||
mock.enableDefaultImplementation(original)
|
||||
|
||||
original.readWritePropertyV["Are you sure?"] = "Yeah, I'm just waiting for my wife."
|
||||
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀", "Are you sure?": "Yeah, I'm just waiting for my wife."])
|
||||
|
||||
original.readWritePropertyV["Alright, have a nice weekend!"] = "Thanks, you too."
|
||||
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀",
|
||||
"Are you sure?": "Yeah, I'm just waiting for my wife.",
|
||||
"Alright, have a nice weekend!": "Thanks, you too."])
|
||||
|
||||
verify(mock, times(2)).readWritePropertyV.get
|
||||
}
|
||||
|
||||
// the next two test cases show using a struct as the default implementation and changing its state:
|
||||
// - NOTE: This only applies for `struct`s, not `class`es.
|
||||
// using: `enableDefaultImplementation(mutating:)` reflects the original's state at all times
|
||||
func testStructModification() {
|
||||
let mock = createMock(value: ["EXTERMINATE!": "EXTERMINATE!!", "EXTERMINATE!!!": "EXTERMINATE!!!!"])
|
||||
var original = GenericProtocolConformerStruct(theC: MockTestedClass(), theV: ["Sir, may I help you?": "Nope, just lookin' 👀"])
|
||||
mock.enableDefaultImplementation(mutating: &original)
|
||||
|
||||
original.readWritePropertyV["Are you sure?"] = "Yeah, I'm just waiting for my wife."
|
||||
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀", "Are you sure?": "Yeah, I'm just waiting for my wife."])
|
||||
|
||||
original.readWritePropertyV["Alright, have a nice weekend!"] = "Thanks, you too."
|
||||
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀",
|
||||
"Are you sure?": "Yeah, I'm just waiting for my wife.",
|
||||
"Alright, have a nice weekend!": "Thanks, you too."])
|
||||
|
||||
verify(mock, times(2)).readWritePropertyV.get
|
||||
}
|
||||
|
||||
// using: `enableDefaultImplementation(_:)` reflects the original's state at the time of enabling default implementation with the struct
|
||||
//
|
||||
func testStructNonModification() {
|
||||
let mock = createMock(value: ["EXTERMINATE!": "EXTERMINATE!!", "EXTERMINATE!!!": "EXTERMINATE!!!!"])
|
||||
var original = GenericProtocolConformerStruct(theC: MockTestedClass(), theV: ["Sir, may I help you?": "Nope, just lookin' 👀"])
|
||||
mock.enableDefaultImplementation(original)
|
||||
|
||||
original.readWritePropertyV["Are you sure?"] = "Yeah, I'm just waiting for my wife."
|
||||
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀"])
|
||||
XCTAssertEqual(original.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀", "Are you sure?": "Yeah, I'm just waiting for my wife."])
|
||||
|
||||
original.readWritePropertyV["Alright, have a nice weekend!"] = "Thanks, you too."
|
||||
XCTAssertEqual(mock.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀"])
|
||||
XCTAssertEqual(original.readWritePropertyV, ["Sir, may I help you?": "Nope, just lookin' 👀",
|
||||
"Are you sure?": "Yeah, I'm just waiting for my wife.",
|
||||
"Alright, have a nice weekend!": "Thanks, you too."])
|
||||
|
||||
verify(mock, times(2)).readWritePropertyV.get
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// CollisionClasses.swift
|
||||
// Cuckoo
|
||||
//
|
||||
// Created by Matyáš Kříž on 31/01/2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// should generate a compiler error if `FinalClass` isn't ignored and `MockFinalClass` is generated
|
||||
class MockFinalClass {}
|
||||
class FinalClassStub {}
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// GenericClass.swift
|
||||
// Cuckoo-iOS
|
||||
//
|
||||
// Created by Matyáš Kříž on 19/11/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class GenericClass<T: CustomStringConvertible, U: Codable & CustomStringConvertible, V: Hashable & Equatable> {
|
||||
let constant = 10.0
|
||||
|
||||
var readWritePropertyT: T
|
||||
var readWritePropertyU: U
|
||||
var readWritePropertyV: V
|
||||
|
||||
var optionalProperty: U?
|
||||
|
||||
init(theT: T, theU: U, theV: V) {
|
||||
readWritePropertyT = theT
|
||||
readWritePropertyU = theU
|
||||
readWritePropertyV = theV
|
||||
}
|
||||
|
||||
func unequal(one: V, two: V) -> Bool {
|
||||
return one != two
|
||||
}
|
||||
|
||||
func getThird(foo: T, bar: U, baz: V) -> V {
|
||||
return baz
|
||||
}
|
||||
|
||||
func print(theT: T) {
|
||||
Swift.print(theT)
|
||||
}
|
||||
|
||||
func encode(theU: U) -> Data {
|
||||
let encoder = JSONEncoder()
|
||||
return try! encoder.encode(["root": theU])
|
||||
}
|
||||
|
||||
func withClosure(_ closure: (T) -> Int) -> Int {
|
||||
return closure(readWritePropertyT)
|
||||
}
|
||||
|
||||
func noReturn() {}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// GenericProtocol.swift
|
||||
// Cuckoo
|
||||
//
|
||||
// Created by Matyáš Kříž on 19/11/2018.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol GenericProtocol {
|
||||
associatedtype C: AnyObject
|
||||
associatedtype V
|
||||
|
||||
var readOnlyPropertyC: C { get }
|
||||
var readWritePropertyV: V { get set }
|
||||
|
||||
var constant: Int { get }
|
||||
var optionalProperty: V? { get set }
|
||||
|
||||
init(theC: C, theV: V)
|
||||
|
||||
func callSomeC(theC: C) -> Int
|
||||
func callSomeV(theV: V) -> Int
|
||||
func compute(classy: C, value: V) -> C
|
||||
func noReturn()
|
||||
}
|
|
@ -12,7 +12,7 @@ import UIKit
|
|||
|
||||
@available(swift 4.0)
|
||||
class TestedClass {
|
||||
|
||||
|
||||
let constant: Float = 0.0
|
||||
|
||||
private(set) var privateSetProperty: Int = 0
|
||||
|
@ -20,33 +20,33 @@ class TestedClass {
|
|||
var readOnlyProperty: String {
|
||||
return "a"
|
||||
}
|
||||
|
||||
|
||||
lazy var readWriteProperty: Int = 0
|
||||
|
||||
|
||||
lazy var optionalProperty: Int? = 0
|
||||
|
||||
func noReturn() {
|
||||
}
|
||||
|
||||
|
||||
func count(characters: String) -> Int {
|
||||
return characters.count
|
||||
}
|
||||
|
||||
|
||||
func withThrows() throws -> Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
func withNoReturnThrows() throws {
|
||||
}
|
||||
|
||||
|
||||
func withClosure(_ closure: (String) -> Int) -> Int {
|
||||
return closure("hello")
|
||||
}
|
||||
|
||||
|
||||
func withEscape(_ a: String, action closure: @escaping (String) -> Void) {
|
||||
closure(a)
|
||||
}
|
||||
|
||||
|
||||
func withOptionalClosure(_ a: String, closure: ((String) -> Void)?) {
|
||||
closure?(a)
|
||||
}
|
||||
|
@ -201,6 +201,64 @@ final class FinalClass {
|
|||
var shouldBeIgnoredByCuckoo = true
|
||||
}
|
||||
|
||||
// should generate a compiler error if `FinalClass` isn't ignored and `MockFinalClass` is generated
|
||||
final class MockFinalClass {}
|
||||
final class FinalClassStub {}
|
||||
protocol GenericFunctionProtocol {
|
||||
func method<T>(param: T) where T: CustomStringConvertible, T: StringProtocol
|
||||
}
|
||||
|
||||
class GenericFunctionClass {
|
||||
func method<T>(param: T) where T: CustomStringConvertible {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class InternalFieldsInPublicClass {
|
||||
internal var field: Int? = nil
|
||||
|
||||
private(set) var privateSetField: Int? = nil
|
||||
|
||||
internal func function() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class FinalFields {
|
||||
final var field: Int? = nil
|
||||
|
||||
final func function() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class InoutMethodClass {
|
||||
func inoutko(param: inout Int) {
|
||||
|
||||
}
|
||||
|
||||
func inoutkoMultiple(param1: inout Int, param2: inout String, param3: Void) {
|
||||
|
||||
}
|
||||
|
||||
func inoutkoClosure(param: (inout Int) -> Void) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class OptionalParamsClass {
|
||||
func function(param: String?) {
|
||||
|
||||
}
|
||||
|
||||
// the next two methods are exactly the same except for parameter types
|
||||
// this is not ambiguous for Swift, however, mocking this in a way so
|
||||
// that the stubbing and verifying calls wouldn't be ambiguous would require
|
||||
// some serious amount of hacks, so we decided to postpone this feature for now
|
||||
// see `clashingFunction` calls in tests to see how to disambiguate if you're ever
|
||||
// in need of two almost identical methods
|
||||
func clashingFunction(param1: Int?, param2: String) {
|
||||
|
||||
}
|
||||
|
||||
func clashingFunction(param1: Int, param2: String?) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,6 +150,7 @@ class StubbingTest: XCTestCase {
|
|||
when(stub.withNamedTuple(tuple: any())).thenReturn(11)
|
||||
when(stub.subclassMethod()).thenReturn(12)
|
||||
when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2)
|
||||
when(stub.withOptionalClosureAndReturn(anyString(), closure: anyClosure())).thenReturn(3)
|
||||
when(stub.withClosureAndParam(anyString(), closure: anyClosure())).thenReturn(3)
|
||||
when(stub.withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())).thenReturn(4)
|
||||
when(stub.withThrowingClosure(closure: anyThrowingClosure())).thenReturn("throwing closure")
|
||||
|
@ -202,7 +203,8 @@ class StubbingTest: XCTestCase {
|
|||
XCTAssertEqual(try! mock.withThrows(), 10)
|
||||
XCTAssertEqual(mock.withNamedTuple(tuple: (a: "A", b: "B")), 11)
|
||||
XCTAssertEqual(mock.subclassMethod(), 12)
|
||||
XCTAssertEqual(mock.withOptionalClosureAndReturn("a", closure: nil), 2)
|
||||
XCTAssertEqual(mock.withOptionalClosureAndReturn("a", closure: Optional.none), 2)
|
||||
XCTAssertEqual(mock.withOptionalClosureAndReturn("a", closure: { _ in }), 3)
|
||||
XCTAssertEqual(mock.withClosureAndParam("a", closure: { _ in 0 }), 3)
|
||||
XCTAssertEqual(mock.withMultClosures(closure: { _ in 0 }, closureB: { _ in 1 }, closureC: { _ in 2 }), 4)
|
||||
XCTAssertEqual(mock.withThrowingClosure { p throws in
|
||||
|
|
Loading…
Reference in New Issue