Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions GeneratedTestingTarget/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#if canImport(Darwin)
@preconcurrency import Darwin
#elseif canImport(Glibc)
@preconcurrency import Glibc // disable concurrency safety checks for stdout/stderr flushing
#elseif canImport(CRT)
@preconcurrency import CRT
#endif

// Must be after the Glibc import, because the first occurency of a (transitive) import determines whether
// something is a @preconcurrency import.
import XCTest

// Type names not exported by the proprietary version of XCTest .
#if os(macOS) && !canImport(SwiftXCTest)
private typealias XCTestCaseClosure = (XCTestCase) throws -> Void
private typealias XCTestCaseEntry
= (testCaseClass: XCTestCase.Type, allTests: [(String, XCTestCaseClosure)])
#endif

/// The type of `T`'s generated static `allTests` property.
private typealias AllTests<T> = [(String, (T) -> () throws -> Void)]

private extension Array {

/// Transforms `self` into an `XCTestCaseEntry` by erasing `T` from the type and representing
/// it as a string, or returns an empty result if `T` is-not-a `XCTestCase`.
func eraseTestTypes<T>() -> XCTestCaseEntry where Self == AllTests<T> {
guard let t = T.self as? XCTestCase.Type else {
// Skip any methods scraped from non-XCTestCase types.
return (testCaseClass: XCTestCase.self, allTests: [])
}

func xcTestCaseClosure(_ f: @escaping (T) -> () throws -> Void) -> XCTestCaseClosure {
{ (x: XCTestCase) throws -> Void in try f(x as! T)() }
}

let allTests = self.map { name_f in (name_f.0, xcTestCaseClosure(name_f.1)) }
return (testCaseClass: t, allTests: allTests)
}

}

private extension TestCase {

static var allTests: AllTests<TestCase> {
return [
("test1", test1),
("testExtensionMethodsCanBeTests", testExtensionMethodsCanBeTests),
]
}

}

private extension StructsAreNotTestCases.NestedStructClassesCanBeTestCases {

static var allTests: AllTests<StructsAreNotTestCases.NestedStructClassesCanBeTestCases> {
return [
("testNestedClassMethodsCanBeTests", testNestedClassMethodsCanBeTests),
]
}

}

private extension StructsAreNotTestCases {

static var allTests: AllTests<StructsAreNotTestCases> {
return [
("testStructExtensionMethodsAreNotTests", testStructExtensionMethodsAreNotTests),
]
}

}

private extension EnumsAreNotTestCases.NestedEnumClassesCanBeTestCases {

static var allTests: AllTests<EnumsAreNotTestCases.NestedEnumClassesCanBeTestCases> {
return [
("testNestedClassMethodsCanBeTests", testNestedClassMethodsCanBeTests),
]
}

}

private extension NotAllClassesAreTestCases {

static var allTests: AllTests<NotAllClassesAreTestCases> {
return [
("testNonXCTestCaseMethodsAreNotTests", testNonXCTestCaseMethodsAreNotTests),
]
}

}

/// Feeds `allTestCases` to `XCTMain` if the latter is available.
func runAllTestCases() {
/// The full complement of test cases and their individual tests
let allTestCases: [XCTestCaseEntry] = [
TestCase.allTests.eraseTestTypes(),
StructsAreNotTestCases.NestedStructClassesCanBeTestCases.allTests.eraseTestTypes(),
StructsAreNotTestCases.allTests.eraseTestTypes(),
EnumsAreNotTestCases.NestedEnumClassesCanBeTestCases.allTests.eraseTestTypes(),
NotAllClassesAreTestCases.allTests.eraseTestTypes()
]
#if !os(Windows)
// ignore SIGPIPE which is sent when writing to closed file descriptors.
_ = signal(SIGPIPE, SIG_IGN)
#endif

atexit({
fflush(stdout)
fflush(stderr)
})
#if !os(macOS) || canImport(SwiftXCTest)
XCTMain(allTestCases)
#endif
}
runAllTestCases()
13 changes: 10 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.1

import PackageDescription

Expand Down Expand Up @@ -48,6 +48,13 @@ let package = Package(
dependencies: ["DummyTestee", "XCTestImporter"],
path: "Tests",
exclude: ["XCTestImporter.swift", "CMakeLists.txt", "Dummy.swift"],
sources: ["Tests.swift"])
]
sources: ["Tests.swift"]),
// Use this for testing the generated file.
// Generate the main.swift by running this: `swift run -Xswiftc -DBUILDING_WITH_SWIFT_PACKAGE_MANAGER GenerateSwiftXCTestMain -o GeneratedTestingTarget/main.swift /home/ambrus/GenerateSwiftXCTestMain/Tests/Tests.swift`
// Then copy Tests.swift into this directory and try to build the target. `swift run GeneratedTestingTarget`
.executableTarget(
name: "GeneratedTestingTarget",
dependencies: ["Tests"],
path: "GeneratedTestingTarget")
],
)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ the scope of test method bodies could produce unpredictable results.

There are two methods:

1. **Swift Package Manager**: `swift test`
1. **Swift Package Manager**: `swift test -Xswiftc -DBUILDING_WITH_SWIFT_PACKAGE_MANAGER`
2. **CMake**: `

```sh
Expand Down
22 changes: 12 additions & 10 deletions Sources/GenerateSwiftXCTestMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct GenerateSwiftXCTestMain: ParsableCommand {
///
/// - See also: `discoveredTests()` for more information.
func discoveredTests(in f: URL) throws -> TestCatalog {
let tree = try parseAndEmitDiagnostics(source: String(contentsOf: f))
let tree = try parseAndEmitDiagnostics(source: String(contentsOf: f, encoding: .utf8))
let scraper = TestScraper()
scraper.walk(tree)
return scraper.result
Expand Down Expand Up @@ -73,15 +73,18 @@ struct GenerateSwiftXCTestMain: ParsableCommand {

let output =
"""
import XCTest
#if canImport(Darwin)
import Darwin
@preconcurrency import Darwin
#elseif canImport(Glibc)
import Glibc
@preconcurrency import Glibc // disable concurrency safety checks for stdout/stderr flushing
#elseif canImport(CRT)
import CRT
@preconcurrency import CRT
#endif

// Must be after the Glibc import, because the first occurency of a (transitive) import determines whether
// something is a @preconcurrency import.
import XCTest

// Type names not exported by the proprietary version of XCTest .
#if os(macOS) && !canImport(SwiftXCTest)
private typealias XCTestCaseClosure = (XCTestCase) throws -> Void
Expand Down Expand Up @@ -114,13 +117,12 @@ struct GenerateSwiftXCTestMain: ParsableCommand {

\(tests.map(allTestsExtension).joined(separator: "\n\n"))

/// The full complement of test cases and their individual tests
private let allTestCases: [XCTestCaseEntry] = [
\(tests.keys.map {"\($0).allTests.eraseTestTypes()"}.joined(separator: ",\n "))
]

/// Feeds `allTestCases` to `XCTMain` if the latter is available.
func runAllTestCases() {
/// The full complement of test cases and their individual tests
let allTestCases: [XCTestCaseEntry] = [
\(tests.keys.map {"\($0).allTests.eraseTestTypes()"}.joined(separator: ",\n "))
]
#if !os(Windows)
// ignore SIGPIPE which is sent when writing to closed file descriptors.
_ = signal(SIGPIPE, SIG_IGN)
Expand Down
Loading