From 5bd155f053b23664a8bb586f625aa9f8fa83ed86 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 26 Aug 2024 21:20:47 -0400 Subject: [PATCH] [6.0.0] Update package templates to use Swift Testing in the toolchain rather than as a package dependency. (#7911) --- .../TestDiscovery/SwiftTesting/Package.swift | 13 +---- Sources/Commands/PackageCommands/Init.swift | 12 ++-- Sources/PackageModelSyntax/AddTarget.swift | 51 +---------------- Sources/SPMTestSupport/misc.swift | 2 +- Sources/Workspace/InitPackage.swift | 51 +++++++---------- Tests/CommandsTests/TestCommandTests.swift | 18 ++++++ .../ManifestEditTests.swift | 16 ++---- Tests/WorkspaceTests/InitTests.swift | 57 ++++++++----------- 8 files changed, 78 insertions(+), 142 deletions(-) diff --git a/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift index 2588f440a55..927603bf003 100644 --- a/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift +++ b/Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift @@ -1,18 +1,9 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.0 import PackageDescription let package = Package( name: "SwiftTesting", - platforms: [ - .macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16), .visionOS(.v1) - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-testing.git", branch: "main"), - ], targets: [ - .testTarget( - name: "SwiftTestingTests", - dependencies: [.product(name: "Testing", package: "swift-testing"),] - ), + .testTarget(name: "SwiftTestingTests"), ] ) diff --git a/Sources/Commands/PackageCommands/Init.swift b/Sources/Commands/PackageCommands/Init.swift index 38463a1f79b..6f40bcab180 100644 --- a/Sources/Commands/PackageCommands/Init.swift +++ b/Sources/Commands/PackageCommands/Init.swift @@ -56,14 +56,16 @@ extension SwiftPackageCommand { let packageName = self.packageName ?? cwd.basename - // Which testing libraries should be used? XCTest is on by default, - // but Swift Testing must remain off by default until it is present - // in the Swift toolchain. + // Testing is on by default, with XCTest only enabled explicitly. + // For macros this is reversed, since we don't support testing + // macros with Swift Testing yet. var supportedTestingLibraries = Set() - if testLibraryOptions.isEnabled(.xctest) { + if testLibraryOptions.isExplicitlyEnabled(.xctest) || + (initMode == .macro && testLibraryOptions.isEnabled(.xctest)) { supportedTestingLibraries.insert(.xctest) } - if testLibraryOptions.isExplicitlyEnabled(.swiftTesting) { + if testLibraryOptions.isExplicitlyEnabled(.swiftTesting) || + (initMode != .macro && testLibraryOptions.isEnabled(.swiftTesting)) { supportedTestingLibraries.insert(.swiftTesting) } diff --git a/Sources/PackageModelSyntax/AddTarget.swift b/Sources/PackageModelSyntax/AddTarget.swift index b32f705e990..53bdb3b5271 100644 --- a/Sources/PackageModelSyntax/AddTarget.swift +++ b/Sources/PackageModelSyntax/AddTarget.swift @@ -84,11 +84,6 @@ public struct AddTarget { // SwiftSyntax. target.dependencies.append(contentsOf: macroTargetDependencies) - case .test where configuration.testHarness == .swiftTesting: - // Testing targets using swift-testing need to depend on - // SwiftTesting from the swift-testing package. - target.dependencies.append(contentsOf: swiftTestingTestTargetDependencies) - default: break; } @@ -163,17 +158,6 @@ public struct AddTarget { } } - case .test where configuration.testHarness == .swiftTesting: - if !manifest.description.contains("swift-testing") { - newPackageCall = try AddPackageDependency - .addPackageDependencyLocal( - .swiftTesting( - configuration: installedSwiftPMConfiguration - ), - to: newPackageCall - ) - } - default: break; } @@ -212,8 +196,7 @@ public struct AddTarget { importModuleNames.append("XCTest") case .swiftTesting: - // Import is handled by the added dependency. - break + importModuleNames.append("Testing") } } @@ -380,35 +363,3 @@ fileprivate extension PackageDependency { ) } } - -/// The set of dependencies we need to introduce to a newly-created macro -/// target. -fileprivate let swiftTestingTestTargetDependencies: [TargetDescription.Dependency] = [ - .product(name: "Testing", package: "swift-testing"), -] - - -/// The package dependency for swift-testing, for use in test files. -fileprivate extension PackageDependency { - /// Source control URL for the swift-syntax package. - static var swiftTestingURL: SourceControlURL { - "https://github.com/apple/swift-testing.git" - } - - /// Package dependency on the swift-testing package. - static func swiftTesting( - configuration: InstalledSwiftPMConfiguration - ) -> PackageDependency { - let swiftTestingVersionDefault = - configuration.swiftTestingVersionForTestTemplate - let swiftTestingVersion = Version(swiftTestingVersionDefault.description)! - - return .sourceControl( - identity: PackageIdentity(url: swiftTestingURL), - nameForTargetDependencyResolutionOnly: nil, - location: .remote(swiftTestingURL), - requirement: .range(.upToNextMajor(from: swiftTestingVersion)), - productFilter: .everything - ) - } -} diff --git a/Sources/SPMTestSupport/misc.swift b/Sources/SPMTestSupport/misc.swift index f003bf21c93..d88da13bb32 100644 --- a/Sources/SPMTestSupport/misc.swift +++ b/Sources/SPMTestSupport/misc.swift @@ -474,7 +474,7 @@ extension InitPackage { public convenience init( name: String, packageType: PackageType, - supportedTestingLibraries: Set = [.xctest], + supportedTestingLibraries: Set = [.swiftTesting], destinationPath: AbsolutePath, fileSystem: FileSystem ) throws { diff --git a/Sources/Workspace/InitPackage.swift b/Sources/Workspace/InitPackage.swift index 866fb292835..6d28d67ed44 100644 --- a/Sources/Workspace/InitPackage.swift +++ b/Sources/Workspace/InitPackage.swift @@ -36,7 +36,7 @@ public final class InitPackage { public init( packageType: PackageType, - supportedTestingLibraries: Set = [.xctest], + supportedTestingLibraries: Set, platforms: [SupportedPlatform] = [] ) { self.packageType = packageType @@ -186,8 +186,8 @@ public final class InitPackage { var platforms = options.platforms - // Macros and swift-testing require macOS 10.15, iOS 13, etc. - if packageType == .macro || options.supportedTestingLibraries.contains(.swiftTesting) { + // Macros require macOS 10.15, iOS 13, etc. + if packageType == .macro { func addIfMissing(_ newPlatform: SupportedPlatform) { if platforms.contains(where: { platform in platform.platform == newPlatform.platform @@ -275,9 +275,6 @@ public final class InitPackage { } else if packageType == .macro { dependencies.append(#".package(url: "https://github.com/swiftlang/swift-syntax.git", from: "\#(self.installedSwiftPMConfiguration.swiftSyntaxVersionForMacroTemplate.description)")"#) } - if options.supportedTestingLibraries.contains(.swiftTesting) { - dependencies.append(#".package(url: "https://github.com/apple/swift-testing.git", from: "0.11.0")"#) - } if !dependencies.isEmpty { let dependencies = dependencies.map { dependency in " \(dependency)," @@ -384,17 +381,7 @@ public final class InitPackage { """ } else { let testTarget: String - if options.supportedTestingLibraries.contains(.swiftTesting) { - testTarget = """ - .testTarget( - name: "\(pkgname)Tests", - dependencies: [ - "\(pkgname)", - .product(name: "Testing", package: "swift-testing"), - ] - ), - """ - } else if options.supportedTestingLibraries.contains(.xctest) { + if !options.supportedTestingLibraries.isEmpty { testTarget = """ .testTarget( name: "\(pkgname)Tests", @@ -687,6 +674,10 @@ public final class InitPackage { private func writeLibraryTestsFile(_ path: AbsolutePath) throws { var content = "" + // XCTest is only added if it was explicitly asked for, so add tests + // for it *and* Testing if it is enabled (or just XCTest if Testing + // is explicitly disabled). + if options.supportedTestingLibraries.contains(.swiftTesting) { content += "import Testing\n" } @@ -695,20 +686,18 @@ public final class InitPackage { } content += "@testable import \(moduleName)\n" - // Prefer swift-testing if specified, otherwise XCTest. If both are - // specified, the developer is free to write tests using both - // libraries, but we still only want to present a single library's - // example tests. + if options.supportedTestingLibraries.contains(.swiftTesting) { content += """ - @Test func example() throws { - // swift-testing Documentation - // https://swiftpackageindex.com/apple/swift-testing/main/documentation/testing + @Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. } """ - } else if options.supportedTestingLibraries.contains(.xctest) { + } + + if options.supportedTestingLibraries.contains(.xctest) { content += """ final class \(moduleName)Tests: XCTestCase { @@ -761,13 +750,15 @@ public final class InitPackage { """## - // Prefer swift-testing if specified, otherwise XCTest. If both are - // specified, the developer is free to write tests using both - // libraries, but we still only want to present a single library's - // example tests. + + // XCTest is only added if it was explicitly asked for, so add tests + // for it *and* Testing if it is enabled. + if options.supportedTestingLibraries.contains(.swiftTesting) { // FIXME: https://github.com/swiftlang/swift-syntax/issues/2400 - } else if options.supportedTestingLibraries.contains(.xctest) { + } + + if options.supportedTestingLibraries.contains(.xctest) { content += ##""" final class \##(moduleName)Tests: XCTestCase { func testMacro() throws { diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index 72d3d59d9e7..328885bcc8f 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -276,10 +276,28 @@ final class TestCommandTests: CommandsTestCase { } func testBasicSwiftTestingIntegration() async throws { +#if !canImport(TestingDisabled) try XCTSkipUnless( nil != Environment.current["SWIFT_PM_SWIFT_TESTING_TESTS_ENABLED"], "Skipping \(#function) because swift-testing tests are not explicitly enabled" ) +#endif + + try await fixture(name: "Miscellaneous/TestDiscovery/SwiftTesting") { fixturePath in + do { + let (stdout, _) = try await SwiftPM.Test.execute(["--enable-swift-testing", "--disable-xctest"], packagePath: fixturePath) + XCTAssertMatch(stdout, .contains(#"Test "SOME TEST FUNCTION" started"#)) + } + } + } + + func testBasicSwiftTestingIntegration_ExperimentalFlag() async throws { +#if !canImport(TestingDisabled) + try XCTSkipUnless( + nil != Environment.current["SWIFT_PM_SWIFT_TESTING_TESTS_ENABLED"], + "Skipping \(#function) because swift-testing tests are not explicitly enabled" + ) +#endif try await fixture(name: "Miscellaneous/TestDiscovery/SwiftTesting") { fixturePath in do { diff --git a/Tests/PackageModelSyntaxTests/ManifestEditTests.swift b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift index b2c6254ce67..1294e2b1792 100644 --- a/Tests/PackageModelSyntaxTests/ManifestEditTests.swift +++ b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift @@ -575,14 +575,8 @@ class ManifestEditTests: XCTestCase { // swift-tools-version: 5.5 let package = Package( name: "packages", - dependencies: [ - .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), - ], targets: [ - .testTarget( - name: "MyTest", - dependencies: [ .product(name: "Testing", package: "swift-testing") ] - ), + .testTarget(name: "MyTest"), ] ) """, @@ -618,7 +612,7 @@ class ManifestEditTests: XCTestCase { let package = Package( name: "packages", dependencies: [ - .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), + .package(url: "https://github.com/swiftlang/swift-example.git", from: "1.2.3"), ], targets: [ .testTarget( @@ -632,20 +626,20 @@ class ManifestEditTests: XCTestCase { let package = Package( name: "packages", dependencies: [ - .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), + .package(url: "https://github.com/swiftlang/swift-example.git", from: "1.2.3"), ], targets: [ .testTarget( name: "MyTest", dependencies: [ - .product(name: "Testing", package: "swift-testing"), + .product(name: "SomethingOrOther", package: "swift-example"), ] ), ] ) """) { manifest in try AddTargetDependency.addTargetDependency( - .product(name: "Testing", package: "swift-testing"), + .product(name: "SomethingOrOther", package: "swift-example"), targetName: "MyTest", to: manifest ) diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index 96958148cfc..731b800dc20 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -113,6 +113,7 @@ final class InitTests: XCTestCase { let initPackage = try InitPackage( name: name, packageType: .library, + supportedTestingLibraries: [.xctest], destinationPath: path, fileSystem: localFileSystem ) @@ -153,8 +154,8 @@ final class InitTests: XCTestCase { } } - func testInitPackageLibraryWithSwiftTestingOnly() throws { - try testWithTemporaryDirectory { tmpPath in + func testInitPackageLibraryWithSwiftTestingOnly() async throws { + try await testWithTemporaryDirectory { tmpPath in let fs = localFileSystem let path = tmpPath.appending("Foo") let name = path.basename @@ -173,31 +174,25 @@ final class InitTests: XCTestCase { // Verify basic file system content that we expect in the package let manifest = path.appending("Package.swift") XCTAssertFileExists(manifest) - let manifestContents: String = try localFileSystem.readFileContents(manifest) - XCTAssertMatch(manifestContents, .contains(#".macOS(.v10_15)"#)) - XCTAssertMatch(manifestContents, .contains(#".iOS(.v13)"#)) - XCTAssertMatch(manifestContents, .contains(#".tvOS(.v13)"#)) - XCTAssertMatch(manifestContents, .contains(#".watchOS(.v6)"#)) - XCTAssertMatch(manifestContents, .contains(#".macCatalyst(.v13)"#)) - XCTAssertMatch(manifestContents, .contains(#"swift-testing.git", from: "0.11.0""#)) - XCTAssertMatch(manifestContents, .contains(#".product(name: "Testing", package: "swift-testing")"#)) let testFile = path.appending("Tests").appending("FooTests").appending("FooTests.swift") let testFileContents: String = try localFileSystem.readFileContents(testFile) XCTAssertMatch(testFileContents, .contains(#"import Testing"#)) XCTAssertNoMatch(testFileContents, .contains(#"import XCTest"#)) - XCTAssertMatch(testFileContents, .contains(#"@Test func example() throws"#)) + XCTAssertMatch(testFileContents, .contains(#"@Test func example() async throws"#)) XCTAssertNoMatch(testFileContents, .contains("func testExample() throws")) - // Try building it -- DISABLED because we cannot pull the swift-testing repository from CI. -// XCTAssertBuilds(path) -// let triple = try UserToolchain.default.targetTriple -// XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) +#if canImport(TestingDisabled) + // Try building it + await XCTAssertBuilds(path) + let triple = try UserToolchain.default.targetTriple + XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) +#endif } } - func testInitPackageLibraryWithBothSwiftTestingAndXCTest() throws { - try testWithTemporaryDirectory { tmpPath in + func testInitPackageLibraryWithBothSwiftTestingAndXCTest() async throws { + try await testWithTemporaryDirectory { tmpPath in let fs = localFileSystem let path = tmpPath.appending("Foo") let name = path.basename @@ -216,26 +211,20 @@ final class InitTests: XCTestCase { // Verify basic file system content that we expect in the package let manifest = path.appending("Package.swift") XCTAssertFileExists(manifest) - let manifestContents: String = try localFileSystem.readFileContents(manifest) - XCTAssertMatch(manifestContents, .contains(#".macOS(.v10_15)"#)) - XCTAssertMatch(manifestContents, .contains(#".iOS(.v13)"#)) - XCTAssertMatch(manifestContents, .contains(#".tvOS(.v13)"#)) - XCTAssertMatch(manifestContents, .contains(#".watchOS(.v6)"#)) - XCTAssertMatch(manifestContents, .contains(#".macCatalyst(.v13)"#)) - XCTAssertMatch(manifestContents, .contains(#"swift-testing.git", from: "0.11.0""#)) - XCTAssertMatch(manifestContents, .contains(#".product(name: "Testing", package: "swift-testing")"#)) let testFile = path.appending("Tests").appending("FooTests").appending("FooTests.swift") let testFileContents: String = try localFileSystem.readFileContents(testFile) XCTAssertMatch(testFileContents, .contains(#"import Testing"#)) XCTAssertMatch(testFileContents, .contains(#"import XCTest"#)) - XCTAssertMatch(testFileContents, .contains(#"@Test func example() throws"#)) - XCTAssertNoMatch(testFileContents, .contains("func testExample() throws")) + XCTAssertMatch(testFileContents, .contains(#"@Test func example() async throws"#)) + XCTAssertMatch(testFileContents, .contains("func testExample() throws")) - // Try building it -- DISABLED because we cannot pull the swift-testing repository from CI. - // XCTAssertBuilds(path) - // let triple = try UserToolchain.default.targetTriple - // XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) +#if canImport(TestingDisabled) + // Try building it + await XCTAssertBuilds(path) + let triple = try UserToolchain.default.targetTriple + XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) +#endif } } @@ -262,16 +251,16 @@ final class InitTests: XCTestCase { let manifest = path.appending("Package.swift") XCTAssertFileExists(manifest) let manifestContents: String = try localFileSystem.readFileContents(manifest) - XCTAssertNoMatch(manifestContents, .contains(#"swift-testing.git", from: "0.11.0""#)) - XCTAssertNoMatch(manifestContents, .contains(#".product(name: "Testing", package: "swift-testing")"#)) XCTAssertNoMatch(manifestContents, .contains(#".testTarget"#)) XCTAssertNoSuchPath(path.appending("Tests")) +#if canImport(TestingDisabled) // Try building it await XCTAssertBuilds(path) let triple = try UserToolchain.default.targetTriple XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) +#endif } } @@ -399,7 +388,7 @@ final class InitTests: XCTestCase { func testPlatforms() throws { try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in - var options = InitPackage.InitPackageOptions(packageType: .library) + var options = InitPackage.InitPackageOptions(packageType: .library, supportedTestingLibraries: []) options.platforms = [ .init(platform: .macOS, version: PlatformVersion("10.15")), .init(platform: .iOS, version: PlatformVersion("12")),