Skip to content

Commit

Permalink
Added Swift-Testing support
Browse files Browse the repository at this point in the history
  • Loading branch information
renep committed Sep 16, 2024
1 parent a8848f8 commit a8804b6
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 8 deletions.
6 changes: 3 additions & 3 deletions Hamcrest/Hamcrest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import XCTest
/// Reporter function that is called whenever a Hamcrest assertion fails.
/// By default this calls XCTFail, except in Playgrounds where it does nothing.
/// This is intended for testing Hamcrest itself.
public var HamcrestReportFunction: (_: String, _ file: StaticString, _ line: UInt) -> () = HamcrestDefaultReportFunction
public let HamcrestDefaultReportFunction =
nonisolated(unsafe) public var HamcrestReportFunction: (_: String, _ file: StaticString, _ line: UInt) -> () = HamcrestDefaultReportFunction
nonisolated(unsafe) public let HamcrestDefaultReportFunction =
isPlayground()
? {message, file, line in}
: {message, file, line in XCTFail(message, file: file, line: line)}
Expand Down Expand Up @@ -89,7 +89,7 @@ func isPlayground() -> Bool {
return reportResult(applyMatcher(matcher, toValue: value), message: message, file: file, line: line)
}

@discardableResult func applyMatcher<T>(_ matcher: Matcher<T>, toValue: () throws -> T) -> String? {
@discardableResult public func applyMatcher<T>(_ matcher: Matcher<T>, toValue: () throws -> T) -> String? {
do {
let value = try toValue()
let match = matcher.matches(value)
Expand Down
2 changes: 1 addition & 1 deletion Hamcrest/SequenceMatchers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public func containsInAnyOrder<T: Equatable, S: Sequence>(_ expectedValues: T...
return containsInAnyOrder(expectedValues.map {equalToWithoutDescription($0)})
}

func applyMatchers<T, S: Sequence>(_ matchers: [Matcher<T>], values: S) -> MatchResult where S.Iterator.Element == T {
public func applyMatchers<T, S: Sequence>(_ matchers: [Matcher<T>], values: S) -> MatchResult where S.Iterator.Element == T {
var mismatchDescriptions: [String?] = []

var i = 0
Expand Down
62 changes: 58 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,68 @@
// swift-tools-version:5.8
// swift-tools-version:6.0

import PackageDescription
import CompilerPluginSupport

let package = Package(
name: "Hamcrest",
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
products: [
.library(name: "Hamcrest", targets: ["Hamcrest"]),
.library(name: "Hamcrest", targets: ["Hamcrest"])
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest")
],
targets: [
.target(name: "Hamcrest", dependencies: [], path: "Hamcrest"),
.testTarget(name: "HamcrestTests", dependencies: ["Hamcrest"], path: "HamcrestTests"),
.target(
name: "Hamcrest",
dependencies: [],
path: "Hamcrest"),


.testTarget(
name: "HamcrestTests",
dependencies: [
"Hamcrest",
],
path: "HamcrestTests"),

.macro(
name: "HamcrestSwiftTestingMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
],
path: "SwiftTesting",
sources: [
"Source/Macros"
]
),

.target(
name: "HamcrestSwiftTesting",
dependencies: [
"HamcrestSwiftTestingMacros",
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
],
path: "SwiftTesting",
sources: [
"Source/Main"
]
),

.testTarget(
name: "HamcrestSwiftTestingTests",
dependencies: [
"Hamcrest",
"HamcrestSwiftTesting",
"HamcrestSwiftTestingMacros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax")
],
path: "SwiftTesting",
sources: [
"Source/Test"
]
)
]
)
43 changes: 43 additions & 0 deletions SwiftTesting/Source/Macros/AssertThatMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// AssesrtThatMacro.swift
// Hamcrest
//
// Created by René Pirringer on 13.09.24.
//

import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftCompilerPlugin

public struct AssertThatMacro: ExpressionMacro, Sendable {

public static func expansion(of node: some SwiftSyntax.FreestandingMacroExpansionSyntax, in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> SwiftSyntax.ExprSyntax {

let arguments = node.arguments
guard arguments.count == 2 else {
fatalError("the macro does not have proper arguments")
}

var iterator = arguments.makeIterator()
guard let firstArgument = iterator.next()?.expression else {
fatalError("the argument expression is missing")
}
guard let secondArgument = iterator.next()?.expression else {
fatalError("the argument expression is missing")
}
return "checkMatcher(\(firstArgument), \(secondArgument), comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()"
}


}

@main
struct HamcrestMacroPlugin: CompilerPlugin {
public init() {}

public let providingMacros: [Macro.Type] = [
AssertThatMacro.self
]

}
44 changes: 44 additions & 0 deletions SwiftTesting/Source/Main/SwiftTesting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// SwiftTesting.swift
// Hamcrest
//
// Created by René Pirringer on 13.09.24.
//
import Foundation
import Testing
import Hamcrest
import SwiftSyntax
import SwiftSyntaxMacros


@_disfavoredOverload public func checkMatcher<T>(
_ value: @autoclosure () throws -> T,
_ matcher: Matcher<T>,
comments: @autoclosure () -> [Comment],
isRequired: Bool,
sourceLocation: Testing.SourceLocation
) -> Result<Void, any Error> {


if let message = applyMatcher(matcher, toValue: value) {
let expression = Testing.__Expression.__fromSyntaxNode(message)
return __checkValue(
false,
expression: expression,
expressionWithCapturedRuntimeValues: expression,
comments: comments(),
isRequired: isRequired,
sourceLocation: sourceLocation
)
}
return .success(())
}

@freestanding(expression) public macro assertThat<T>(
_ value: @autoclosure () throws -> T,
_ matcher: Matcher<T>,
_ comment: @autoclosure () -> Comment? = nil,
sourceLocation: Testing.SourceLocation = #_sourceLocation
) = #externalMacro(module: "HamcrestSwiftTestingMacros", type: "AssertThatMacro")


57 changes: 57 additions & 0 deletions SwiftTesting/Source/Test/AssertThatMacroTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// AssertThatMacroTests.swift
// HamcrestTests
//
// Created by René Pirringer on 16.09.24.
//
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import HamcrestSwiftTestingMacros
import XCTest

#if canImport(HamcrestSwiftTestingMacros)
import HamcrestSwiftTestingMacros

let testMacros: [String: Macro.Type] = [
"assertThat": AssertThatMacro.self
]

#endif

final class AssertThatMacroTests: XCTestCase {

func test_macro_syntax_with_equal_int() throws {
#if canImport(HamcrestSwiftTestingMacros)

assertMacroExpansion(
"""
#assertThat(1, equalTo(1))
""", expandedSource:
"""
checkMatcher(1, equalTo(1), comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()
""",
macros: testMacros)
#else
throw XCTSkip("macros are only supported when running tests for the host platform")
#endif
}


func test_macro_syntax_with_equal_string() throws {
#if canImport(HamcrestSwiftTestingMacros)

assertMacroExpansion(
"""
#assertThat("1", equalTo("1"))
""", expandedSource:
"""
checkMatcher("1", equalTo("1"), comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()
""",
macros: testMacros)
#else
throw XCTSkip("macros are only supported when running tests for the host platform")
#endif
}
}
31 changes: 31 additions & 0 deletions SwiftTesting/Source/Test/SwiftTestIntegrationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// SwiftTestIntegrationTests.swift
// HamcrestTests
//
// Created by René Pirringer on 13.09.24.
//

import Testing
import Hamcrest
import HamcrestSwiftTesting

struct SwiftTestIntegrationTests {

@Test func test_checkMatcher() async throws {
checkMatcher("foo", equalTo("foo"), comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()
checkMatcher("foo", not(equalTo("bar")), comments: [], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected()
}


@Test func test_assertThat_macro() async throws {
#if canImport(HamcrestSwiftTestingMacros)
#assertThat("foo", equalTo("foo"))
#assertThat("foo", not(equalTo("bar")))
#else
throw XCTSkip("macros are only supported when running tests for the host platform")
#endif
}


}

0 comments on commit a8804b6

Please sign in to comment.