Skip to content

Commit

Permalink
added support for labels (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkowalski87 authored Nov 14, 2023
1 parent 8c0719b commit ad6e977
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 31 deletions.
13 changes: 10 additions & 3 deletions ExampleApp/ExampleAppTests/APIEndpointsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import XCTestParametrizedMacro

final class APIEndpointsTests: XCTestCase {

@Parametrize(input: [APIEndpoint.profile, APIEndpoint.transactions, APIEndpoint.order("2345")])
func testEndpointURL(input endpoint: APIEndpoint) throws {
XCTAssertNotNil(endpoint.buildURL)
@Parametrize(
input: [APIEndpoint.profile, APIEndpoint.transactions, APIEndpoint.order("2345")],
output: ["https://example.com/api/me",
"https://example.com/api/transactions",
"https://example.com/api/order/2345"],
labels: ["profile",
"transactions",
"order"])
func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws {
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
}
6 changes: 6 additions & 0 deletions Sources/XCTestParametrizedMacro/XCTestParametrizedMacro.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
@attached(peer, names: arbitrary)
public macro Parametrize<I>(input: [I]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro")

@attached(peer, names: arbitrary)
public macro Parametrize<I>(input: [I], labels: [String]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro")

@attached(peer, names: arbitrary)
public macro Parametrize<I, O>(input: [I], output: [O]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro")

@attached(peer, names: arbitrary)
public macro Parametrize<I, O>(input: [I], output: [O], labels: [String]) = #externalMacro(module: "XCTestParametrizedMacroMacros", type: "ParametrizeMacro")
18 changes: 18 additions & 0 deletions Sources/XCTestParametrizedMacroMacros/MacroDeclarationHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,22 @@ struct MacroDeclarationHelper {
return arrayOfValues
}
}

var labels: ArrayElementListSyntax? {
get throws {
guard let firstMacroArgument = firstAttribute?.arguments?.as(LabeledExprListSyntax.self) else {
throw ParametrizeMacroError.macroAttributeNotAnArray
}

guard let labelsArgument = firstMacroArgument.first(where: { $0.label?.text == "labels" }) else {
return nil
}

guard let arrayOfValues = labelsArgument.as(LabeledExprSyntax.self)?.expression.as(ArrayExprSyntax.self)?.elements else {
throw ParametrizeMacroError.macroAttributeNotAnArray
}

return arrayOfValues
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum ParametrizeMacroError: Error, CustomStringConvertible {
case .macroAttributeNotAnArray:
return "Parametrize macro requires at least one attribute as array of input/output values."
case .macroAttributeArraysMismatchSize:
return "Size of the input array and output array should be the same."
return "Arrays passed as an argument should be same size."
}
}
}
42 changes: 17 additions & 25 deletions Sources/XCTestParametrizedMacroMacros/TestMethodsFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,17 @@ struct TestMethodsFactory {
let outputParamName = macroDeclarationHelper.outputParamName?.text
let outputParamType = macroDeclarationHelper.outputParamType

let outputValues = try macroDeclarationHelper.outputValues
let inputValues = try macroDeclarationHelper.inputValues
if let outputValues = outputValues,
let outputParamName = outputParamName,
let outputParamType = outputParamType {
let input = inputValues.map { $0 }
let output = outputValues.map { $0 }
guard input.count == output.count else {
throw ParametrizeMacroError.macroAttributeArraysMismatchSize
}
return try zip(input, output).map { input, output in
"""
\(raw: buildTestMethodSignature(funcName: funcName, inputParamName: inputParamName, inputObject: input, outputParamName: outputParamName, outputObject: output))
let input = try macroDeclarationHelper.inputValues.map { $0 }
let output: [ArrayElementListSyntax.Element?] = try macroDeclarationHelper.outputValues?.map { $0 } ?? .init(repeating: nil, count: input.count)
let labels: [ArrayElementListSyntax.Element?] = try macroDeclarationHelper.labels?.map { $0 } ?? .init(repeating: nil, count: input.count)

guard input.count == output.count, output.count == labels.count else {
throw ParametrizeMacroError.macroAttributeArraysMismatchSize
}
return try zip(input, zip(output, labels)).map { (input, arg) in
let (output, label) = arg
return """
\(raw: buildTestMethodSignature(funcName: funcName, inputParamName: inputParamName, inputObject: input, outputParamName: outputParamName, outputObject: output, label: label))
\(raw: buildLocalVariables(inputParamName: inputParamName,
inputParamType: inputParamType,
inputObject: input,
Expand All @@ -51,25 +49,19 @@ struct TestMethodsFactory {
\(raw: try bodyFunc)
}
"""
}
} else {
return try inputValues
.map {
"""
\(raw: buildTestMethodSignature(funcName: funcName, inputParamName: inputParamName, inputObject: $0))
\(raw: buildLocalVariables(inputParamName: inputParamName, inputParamType: inputParamType, inputObject: $0))
\(raw: try bodyFunc)
}
"""
}
}

}

func buildTestMethodSignature(funcName: TokenSyntax,
inputParamName: String,
inputObject: ArrayElementListSyntax.Element,
outputParamName: String? = nil,
outputObject: ArrayElementListSyntax.Element? = nil) -> String {
outputObject: ArrayElementListSyntax.Element? = nil,
label: ArrayElementListSyntax.Element? = nil ) -> String {
guard label == nil else {
return "func \(funcName)_\(label!.asFunctionName)() throws {"
}
if let outputParamName = outputParamName, let outputObject = outputObject {
return "func \(funcName)_\(inputParamName.capitalizedFirst)_\(inputObject.asFunctionName)_\(outputParamName.capitalizedFirst)_\(outputObject.asFunctionName)() throws {"
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension ArrayElementSyntax {

/// returns content as string representation that can be used in function name
var asFunctionName: String {
let value = self.expression.description
let value = self.expression.trimmed.description
return ParamValueTransformer.transform(value: value)
}
}
Expand Down
26 changes: 25 additions & 1 deletion Tests/XCTestParametrizedMacroTests/AttachmentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,31 @@ final class AttachmentTests: XCTestCase {
}
""",
diagnostics: [
DiagnosticSpec(message: "Size of the input array and output array should be the same.", line: 2, column: 5)
DiagnosticSpec(message: "Arrays passed as an argument should be same size.", line: 2, column: 5)
],
macros: testMacros
)
}

func testParametrizeInputOutputLabels_DifferentSizeOfArrays_ShouldFail() throws {
assertMacroExpansion(
"""
struct TestStruct {
@Parametrize(input: [1,2,3], output: [1,4,3], labels: ["first", "second"])
func testPow2(input n: Int, output result: Int) {
XCTAssertEqual(pow2(n), result)
}
}
""",
expandedSource: """
struct TestStruct {
func testPow2(input n: Int, output result: Int) {
XCTAssertEqual(pow2(n), result)
}
}
""",
diagnostics: [
DiagnosticSpec(message: "Arrays passed as an argument should be same size.", line: 2, column: 5)
],
macros: testMacros
)
Expand Down
148 changes: 148 additions & 0 deletions Tests/XCTestParametrizedMacroTests/LabelsParameterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest

import XCTestParametrizedMacroMacros

final class LabelsParameterTests: XCTestCase {

let testMacros: [String: Macro.Type] = [
"Parametrize": ParametrizeMacro.self,
]

func testParametrizeInputLabels_OneLabel() throws {
assertMacroExpansion(
"""
struct TestStruct {
@Parametrize(input: [3.1415], labels: ["Pi"])
func testValidateDouble(input n: Double) {
XCTAssertNotNil(validate_number(n))
}
}
""",
expandedSource: """
struct TestStruct {
func testValidateDouble(input n: Double) {
XCTAssertNotNil(validate_number(n))
}
func testValidateDouble_Pi() throws {
let n: Double = 3.1415
XCTAssertNotNil(validate_number(n))
}
}
""",
macros: testMacros
)
}

func testParametrizeInputOutputLabels_OneLabel() throws {
assertMacroExpansion(
"""
struct TestStruct {
@Parametrize(input: [3], output: [9], labels: ["ThreePowerOfTheTwo"])
func testPow2(input n: Int, output result: Int) {
XCTAssertEqual(pow2(n),result)
}
}
""",
expandedSource: """
struct TestStruct {
func testPow2(input n: Int, output result: Int) {
XCTAssertEqual(pow2(n),result)
}
func testPow2_ThreePowerOfTheTwo() throws {
let n: Int = 3
let result: Int = 9
XCTAssertEqual(pow2(n), result)
}
}
""",
macros: testMacros
)
}

func testParametrizeInputOutputLabels_TwoLabels() throws {
assertMacroExpansion(
"""
struct TestStruct {
@Parametrize(input: [3, 4], output: [9, 16], labels: ["ThreePowerOfTheTwo", "FourPowerOfTheTwo"])
func testPow2(input n: Int, output result: Int) {
XCTAssertEqual(pow2(n),result)
}
}
""",
expandedSource: """
struct TestStruct {
func testPow2(input n: Int, output result: Int) {
XCTAssertEqual(pow2(n),result)
}
func testPow2_ThreePowerOfTheTwo() throws {
let n: Int = 3
let result: Int = 9
XCTAssertEqual(pow2(n), result)
}
func testPow2_FourPowerOfTheTwo() throws {
let n: Int = 4
let result: Int = 16
XCTAssertEqual(pow2(n), result)
}
}
""",
macros: testMacros
)
}

func testParametrizeInputOutputLabels_CustomObjects() throws {
assertMacroExpansion(
"""
struct TestStruct {
@Parametrize(
input: [APIEndpoint.profile, APIEndpoint.transactions, APIEndpoint.order("2345")],
output: ["https://example.com/api/me",
"https://example.com/api/transactions",
"https://example.com/api/order/2345"],
labels: ["profile",
"transactions",
"order"])
func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws {
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
}
""",
expandedSource: """
struct TestStruct {
func testEndpointURL(input endpoint: APIEndpoint, output expectedUrl: String) throws {
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
func testEndpointURL_profile() throws {
let endpoint: APIEndpoint = APIEndpoint.profile
let expectedUrl: String = "https://example.com/api/me"
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
func testEndpointURL_transactions() throws {
let endpoint: APIEndpoint = APIEndpoint.transactions
let expectedUrl: String =
"https://example.com/api/transactions"
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
func testEndpointURL_order() throws {
let endpoint: APIEndpoint = APIEndpoint.order("2345")
let expectedUrl: String =
"https://example.com/api/order/2345"
XCTAssertEqual(endpoint.buildURL?.absoluteString, expectedUrl)
}
}
""",
macros: testMacros
)
}
}


0 comments on commit ad6e977

Please sign in to comment.