Skip to content

Commit

Permalink
macro-testing
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Sep 25, 2023
1 parent e8f0299 commit 3883fea
Show file tree
Hide file tree
Showing 92 changed files with 6,670 additions and 0 deletions.
8 changes: 8 additions & 0 deletions 0250-macro-testing-pt1/Experimentation/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
54 changes: 54 additions & 0 deletions 0250-macro-testing-pt1/Experimentation/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
import CompilerPluginSupport

let package = Package(
name: "Experimentation",
platforms: [.macOS(.v10_15), .iOS(.v17), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "Experimentation",
targets: ["Experimentation"]
),
.executable(
name: "ExperimentationClient",
targets: ["ExperimentationClient"]
),
],
dependencies: [
// Depend on the latest Swift 5.9 prerelease of SwiftSyntax
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.1.0")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
// Macro implementation that performs the source transformation of a macro.
.macro(
name: "ExperimentationMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),

// Library that exposes a macro as part of its API, which is used in client programs.
.target(name: "Experimentation", dependencies: ["ExperimentationMacros"]),

// A client of the library, which is able to use the macro in its own code.
.executableTarget(name: "ExperimentationClient", dependencies: ["Experimentation"]),

// A test target used to develop the macro implementation.
.testTarget(
name: "ExperimentationTests",
dependencies: [
"ExperimentationMacros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
.product(name: "MacroTesting", package: "swift-macro-testing")
]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book

/// A macro that produces both a value and a string containing the
/// source code that generated the value. For example,
///
/// #stringify(x + y)
///
/// produces a tuple `(x + y, "x + y")`.
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "ExperimentationMacros", type: "StringifyMacro")
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Experimentation

let a = 17
let b = 25

let (result, code) = #stringify(a + b)

//let (result1, code1) = #stringify()

print("The value \(result) was produced by the code \"\(code)\"")
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

/// Implementation of the `stringify` macro, which takes an expression
/// of any type and produces a tuple containing the value of that expression
/// and the source code that produced the value. For example
///
/// #stringify(x + y)
///
/// will expand to
///
/// (x + y, "x + y")
public struct StringifyMacro: ExpressionMacro {
public static func expansion<F: FreestandingMacroExpansionSyntax, C: MacroExpansionContext>(
of node: F,
in context: C
) throws -> ExprSyntax {
guard let argument = node.argumentList.first?.expression else {
fatalError("compiler bug: the macro does not have any arguments")
}

guard argument.description.count < 20
else {
throw SomeError()
}

return "(\(argument), \(literal: argument.description))"
}
}

struct SomeError: Error {}

@main
struct ExperimentationPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
StringifyMacro.self,
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import MacroTesting
import InlineSnapshotTesting
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import SwiftUI
import XCTest

// Macro implementations build for the host, so the corresponding module is not available when cross-compiling. Cross-compiled tests may still make use of the macro itself in end-to-end tests.
#if canImport(ExperimentationMacros)
import ExperimentationMacros

let testMacros: [String: Macro.Type] = [
"stringify": StringifyMacro.self,
]
#endif

final class ExperimentationTests: XCTestCase {
override func invokeTest() {
withMacroTesting(
// isRecording: true,
macros: [StringifyMacro.self]
) {
super.invokeTest()
}
}

func testMacro() throws {
#if canImport(ExperimentationMacros)
assertMacroExpansion(
"""
#stringify(a + b)
""",
expandedSource: """
(a + b, "a + b")
""",
macros: testMacros
)
#else
throw XCTSkip("macros are only supported when running tests for the host platform")
#endif
}

func testMacro_Improved() {
assertMacro {
"#stringify(a + b)"
} matches: {
"""
(a + b, "a + b")
"""
}
SnapshotTesting.diffTool = "ksdiff"
assertSnapshot(
of: Text("Bye")
.frame(width: 200, height: 200)
.background {
Color.yellow.opacity(0.2)
},
as: .image
)
struct User: Codable {
let id: Int
var name: String
}
assertInlineSnapshot(of: User(id: 42, name: "Blob"), as: .json) {
"""
{
"id" : 42,
"name" : "Blob"
}
"""
}
}

func testMacro_LongArgumentDiagnostic() throws {
assertMacroExpansion(
"""
#stringify((a + b) * (a - b) * (b - a))
""",
expandedSource: """
#stringify((a + b) * (a - b) * (b - a))
""",
diagnostics: [
DiagnosticSpec(message: "SomeError()", line: 1, column: 1),
DiagnosticSpec(message: "SomeError()", line: 1, column: 1),
],
macros: testMacros
)
}

func testMacro_LongArgumentDiagnostic_Improved() throws {
assertMacro {
"""
let (result, string) = #stringify((a + b) * (a - b) * (b - a))
"""
} matches: {
"""
let (result, string) = #stringify((a + b) * (a - b) * (b - a))
┬──────────────────────────────────────
╰─ 🛑 SomeError()
"""
}
}

func testMacroWithStringLiteral() throws {
#if canImport(ExperimentationMacros)
assertMacroExpansion(
#"""
#stringify("Hello, \(name)")
"""#,
expandedSource: #"""
("Hello, \(name)", #""Hello, \(name)""#)
"""#,
macros: testMacros
)
#else
throw XCTSkip("macros are only supported when running tests for the host platform")
#endif
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id" : 42,
"name" : "Blob"
}
5 changes: 5 additions & 0 deletions 0250-macro-testing-pt1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## [Point-Free](https://www.pointfree.co)

> #### This directory contains code from Point-Free Episode: [Testing & Debugging Macros: Part 1](https://www.pointfree.co/episodes/ep250-testing-debugging-macros-part-1)
>
> Macros are here! To celebrate, we are releasing a brand new tool to aid in writing tests for them. First, let’s explore testing macros using the tools that Apple provides, evaluate their shortcomings, and see how we can address them.
Loading

0 comments on commit 3883fea

Please sign in to comment.