Skip to content

Commit 221359c

Browse files
committed
Teach swift package add-target --type test about swift-testing
Introduce a command-line argument `--testing-library` to the add-target command to specify which test library to generate the test for. This can be 'xctest' (the prior XCTest behavior), 'swift-testing' (to use the new swift-testing library), or 'none' (for no test library at all). For the new swift-testing generation, also add the appropriate package and test target dependency, along with a stub testsuite to start from. Fixes #7478
1 parent d59191a commit 221359c

File tree

5 files changed

+187
-11
lines changed

5 files changed

+187
-11
lines changed

Sources/Commands/PackageCommands/AddTarget.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import TSCBasic
2121
import TSCUtility
2222
import Workspace
2323

24+
extension AddTarget.TestHarness: ExpressibleByArgument { }
25+
2426
extension SwiftPackageCommand {
2527
struct AddTarget: SwiftCommand {
2628
/// The type of target that can be specified on the command line.
@@ -58,6 +60,9 @@ extension SwiftPackageCommand {
5860
@Option(help: "The checksum for a remote binary target")
5961
var checksum: String?
6062

63+
@Option(help: "The testing library to use when generating test targets, which can be one of 'xctest', 'swift-testing', or 'none'")
64+
var testingLibrary: PackageModelSyntax.AddTarget.TestHarness = .default
65+
6166
func run(_ swiftCommandState: SwiftCommandState) throws {
6267
let workspace = try swiftCommandState.getActiveWorkspace()
6368

@@ -110,6 +115,7 @@ extension SwiftPackageCommand {
110115
let editResult = try PackageModelSyntax.AddTarget.addTarget(
111116
target,
112117
to: manifestSyntax,
118+
configuration: .init(testHarness: testingLibrary),
113119
installedSwiftPMConfiguration: swiftCommandState
114120
.getHostToolchain()
115121
.installedSwiftPMConfiguration

Sources/PackageModel/InstalledSwiftPMConfiguration.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public struct InstalledSwiftPMConfiguration: Codable {
3131

3232
let version: Int
3333
public let swiftSyntaxVersionForMacroTemplate: Version
34+
public let swiftTestingVersionForTestTemplate: Version
3435

3536
public static var `default`: InstalledSwiftPMConfiguration {
3637
return .init(
@@ -40,6 +41,12 @@ public struct InstalledSwiftPMConfiguration: Codable {
4041
minor: 0,
4142
patch: 0,
4243
prereleaseIdentifier: "latest"
44+
),
45+
swiftTestingVersionForTestTemplate: .init(
46+
major: 0,
47+
minor: 7,
48+
patch: 0,
49+
prereleaseIdentifier: nil
4350
)
4451
)
4552
}

Sources/PackageModelSyntax/AddTarget.swift

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,40 @@ public struct AddTarget {
3030
"cxxLanguageStandard"
3131
]
3232

33+
/// The kind of test harness to use. This isn't part of the manifest
34+
/// itself, but is used to guide the generation process.
35+
public enum TestHarness: String, Codable {
36+
/// Don't use any library
37+
case none
38+
39+
/// Create a test using the XCTest library.
40+
case xctest
41+
42+
/// Create a test using the swift-testing package.
43+
case swiftTesting = "swift-testing"
44+
45+
/// The default testing library to use.
46+
public static var `default`: TestHarness = .xctest
47+
}
48+
49+
/// Additional configuration information to guide the package editing
50+
/// process.
51+
public struct Configuration {
52+
/// The test harness to use.
53+
public var testHarness: TestHarness
54+
55+
public init(testHarness: TestHarness = .default) {
56+
self.testHarness = testHarness
57+
}
58+
}
59+
3360
/// Add the given target to the manifest, producing a set of edit results
3461
/// that updates the manifest and adds some source files to stub out the
3562
/// new target.
3663
public static func addTarget(
3764
_ target: TargetDescription,
3865
to manifest: SourceFileSyntax,
66+
configuration: Configuration = .init(),
3967
installedSwiftPMConfiguration: InstalledSwiftPMConfiguration = .default
4068
) throws -> PackageEditResult {
4169
// Make sure we have a suitable tools version in the manifest.
@@ -49,10 +77,20 @@ public struct AddTarget {
4977
// content when needed.
5078
var target = target
5179

52-
// Macro targets need to depend on a couple of libraries from
53-
// SwiftSyntax.
54-
if target.type == .macro {
80+
// Add dependencies needed for various targets.
81+
switch target.type {
82+
case .macro:
83+
// Macro targets need to depend on a couple of libraries from
84+
// SwiftSyntax.
5585
target.dependencies.append(contentsOf: macroTargetDependencies)
86+
87+
case .test where configuration.testHarness == .swiftTesting:
88+
// Testing targets using swift-testing need to depend on
89+
// SwiftTesting from the swift-testing package.
90+
target.dependencies.append(contentsOf: swiftTestingTestTargetDependencies)
91+
92+
default:
93+
break;
5694
}
5795

5896
var newPackageCall = try packageCall.appendingToArrayArgument(
@@ -84,6 +122,7 @@ public struct AddTarget {
84122
addPrimarySourceFile(
85123
outerPath: outerPath,
86124
target: target,
125+
configuration: configuration,
87126
to: &auxiliaryFiles
88127
)
89128

@@ -124,6 +163,17 @@ public struct AddTarget {
124163
}
125164
}
126165

166+
case .test where configuration.testHarness == .swiftTesting:
167+
if !manifest.description.contains("swift-testing") {
168+
newPackageCall = try AddPackageDependency
169+
.addPackageDependencyLocal(
170+
.swiftTesting(
171+
configuration: installedSwiftPMConfiguration
172+
),
173+
to: newPackageCall
174+
)
175+
}
176+
127177
default: break;
128178
}
129179

@@ -140,6 +190,7 @@ public struct AddTarget {
140190
fileprivate static func addPrimarySourceFile(
141191
outerPath: RelativePath,
142192
target: TargetDescription,
193+
configuration: Configuration,
143194
to auxiliaryFiles: inout AuxiliaryFiles
144195
) {
145196
let sourceFilePath = outerPath.appending(
@@ -153,7 +204,17 @@ public struct AddTarget {
153204

154205
// Add appropriate test module dependencies.
155206
if target.type == .test {
156-
importModuleNames.append("XCTest")
207+
switch configuration.testHarness {
208+
case .none:
209+
break
210+
211+
case .xctest:
212+
importModuleNames.append("XCTest")
213+
214+
case .swiftTesting:
215+
// Import is handled by the added dependency.
216+
break
217+
}
157218
}
158219

159220
let importDecls = importModuleNames.lazy.sorted().map { name in
@@ -184,14 +245,35 @@ public struct AddTarget {
184245
"""
185246

186247
case .test:
187-
"""
188-
\(imports)
189-
class \(raw: target.name): XCTestCase {
190-
func test\(raw: target.name)() {
191-
XCTAssertEqual(42, 17 + 25)
248+
switch configuration.testHarness {
249+
case .none:
250+
"""
251+
\(imports)
252+
// Test code here
253+
"""
254+
255+
case .xctest:
256+
"""
257+
\(imports)
258+
class \(raw: target.name): XCTestCase {
259+
func test\(raw: target.name)() {
260+
XCTAssertEqual(42, 17 + 25)
261+
}
262+
}
263+
"""
264+
265+
case .swiftTesting:
266+
"""
267+
\(imports)
268+
@Suite
269+
struct \(raw: target.name)Tests {
270+
@Test("\(raw: target.name) tests")
271+
func tests() {
272+
#expect(42 == 17 + 25)
273+
}
192274
}
275+
"""
193276
}
194-
"""
195277

196278
case .regular:
197279
"""
@@ -298,3 +380,35 @@ fileprivate extension PackageDependency {
298380
)
299381
}
300382
}
383+
384+
/// The set of dependencies we need to introduce to a newly-created macro
385+
/// target.
386+
fileprivate let swiftTestingTestTargetDependencies: [TargetDescription.Dependency] = [
387+
.product(name: "Testing", package: "swift-testing"),
388+
]
389+
390+
391+
/// The package dependency for swift-testing, for use in test files.
392+
fileprivate extension PackageDependency {
393+
/// Source control URL for the swift-syntax package.
394+
static var swiftTestingURL: SourceControlURL {
395+
"https://github.com/apple/swift-testing.git"
396+
}
397+
398+
/// Package dependency on the swift-testing package.
399+
static func swiftTesting(
400+
configuration: InstalledSwiftPMConfiguration
401+
) -> PackageDependency {
402+
let swiftTestingVersionDefault = configuration
403+
.swiftTestingVersionForTestTemplate
404+
let swiftTestingVersion = Version(swiftTestingVersionDefault.description)!
405+
406+
return .sourceControl(
407+
identity: PackageIdentity(url: swiftTestingURL),
408+
nameForTargetDependencyResolutionOnly: nil,
409+
location: .remote(swiftTestingURL),
410+
requirement: .range(.upToNextMajor(from: swiftTestingVersion)),
411+
productFilter: .everything
412+
)
413+
}
414+
}

Tests/PackageModelSyntaxTests/ManifestEditTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,53 @@ class ManifestEditTests: XCTestCase {
564564
}
565565
}
566566

567+
func testAddSwiftTestingTestTarget() throws {
568+
try assertManifestRefactor("""
569+
// swift-tools-version: 5.5
570+
let package = Package(
571+
name: "packages"
572+
)
573+
""",
574+
expectedManifest: """
575+
// swift-tools-version: 5.5
576+
let package = Package(
577+
name: "packages",
578+
dependencies: [
579+
.package(url: "https://github.com/apple/swift-testing.git", from: "0.7.0"),
580+
],
581+
targets: [
582+
.testTarget(
583+
name: "MyTest",
584+
dependencies: [ .product(name: "Testing", package: "swift-testing") ]
585+
),
586+
]
587+
)
588+
""",
589+
expectedAuxiliarySources: [
590+
RelativePath("Tests/MyTest/MyTest.swift") : """
591+
import Testing
592+
593+
@Suite
594+
struct MyTestTests {
595+
@Test("MyTest tests")
596+
func tests() {
597+
#expect(42 == 17 + 25)
598+
}
599+
}
600+
"""
601+
]) { manifest in
602+
try AddTarget.addTarget(
603+
TargetDescription(
604+
name: "MyTest",
605+
type: .test
606+
),
607+
to: manifest,
608+
configuration: .init(
609+
testHarness: .swiftTesting
610+
)
611+
)
612+
}
613+
}
567614
}
568615

569616

Utilities/config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
{"version":1,"swiftSyntaxVersionForMacroTemplate":{"major":600,"minor":0,"patch":0, "prereleaseIdentifier":"latest"}}
1+
{"version":1,
2+
"swiftSyntaxVersionForMacroTemplate":{"major":600,"minor":0,"patch":0, "prereleaseIdentifier":"latest"},
3+
"swiftTestingVersionForTestTemplate":{"major":0,"minor":7,"patch":0}}

0 commit comments

Comments
 (0)