From d04439121a21333c7b9569a420b7b5f223371672 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 29 Aug 2025 16:33:27 -0400 Subject: [PATCH 1/3] [Tests] Convert InitTests to Swift Testing --- .../SwiftTesting+Helpers.swift | 57 +- Tests/WorkspaceTests/InitTests.swift | 712 ++++++++---------- 2 files changed, 388 insertions(+), 381 deletions(-) diff --git a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift index a3f96ff686c..a87ef1c3dae 100644 --- a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift +++ b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift @@ -11,8 +11,32 @@ import Basics import Testing +// MARK: File System Helpers + +/// Verifies that a file exists at the specified path. +/// +/// - Parameters: +/// - path: The absolute path to check for file existence. +/// - sourceLocation: The source location where the expectation is made. public func expectFileExists( at path: AbsolutePath, + sourceLocation: SourceLocation = #_sourceLocation, +) { + #expect( + localFileSystem.exists(path), + "Files '\(path)' does not exist.", + sourceLocation: sourceLocation, + ) +} + +/// Verifies that a file does not exist at the specified path. +/// +/// - Parameters: +/// - fixturePath: The absolute path to check for file non-existence. +/// - comment: An optional comment to include in the failure message. +/// - sourceLocation: The source location where the expectation is made. +public func expectFileDoesNotExists( + at fixturePath: AbsolutePath, _ comment: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation, ) { @@ -59,6 +83,12 @@ public func expectFileDoesNotExists( ) } +/// Verifies that a file exists and is executable at the specified path. +/// +/// - Parameters: +/// - fixturePath: The absolute path to check for executable file existence. +/// - comment: An optional comment to include in the failure message. +/// - sourceLocation: The source location where the expectation is made. public func expectFileIsExecutable( at fixturePath: AbsolutePath, _ comment: Comment? = nil, @@ -77,6 +107,11 @@ public func expectFileIsExecutable( ) } +/// Verifies that a directory exists at the specified path. +/// +/// - Parameters: +/// - path: The absolute path to check for directory existence. +/// - sourceLocation: The source location where the expectation is made. public func expectDirectoryExists( at path: AbsolutePath, sourceLocation: SourceLocation = #_sourceLocation, @@ -94,6 +129,11 @@ let msgSuffix: String ) } +/// Verifies that a directory does not exist at the specified path. +/// +/// - Parameters: +/// - path: The absolute path to check for directory non-existence. +/// - sourceLocation: The source location where the expectation is made. public func expectDirectoryDoesNotExist( at path: AbsolutePath, sourceLocation: SourceLocation = #_sourceLocation, @@ -111,6 +151,15 @@ public func expectDirectoryDoesNotExist( ) } +// MARK: Error Helpers + +/// Verifies that an expression throws a `CommandExecutionError`. +/// +/// - Parameters: +/// - expression: The expression to evaluate. +/// - message: An optional description of the failure. +/// - sourceLocation: The source location where the expectation is made. +/// - errorHandler: A closure that's called with the error if the expression throws. public func expectThrowsCommandExecutionError( _ expression: @autoclosure () async throws -> T, _ message: @autoclosure () -> Comment = "", @@ -129,7 +178,13 @@ public func expectThrowsCommandExecutionError( } } -/// An `async`-friendly replacement for `XCTAssertThrowsError`. +/// An async-friendly replacement for `XCTAssertThrowsError` that verifies an expression throws an error. +/// +/// - Parameters: +/// - expression: The expression to evaluate that should throw an error. +/// - message: An optional failure message to display if the expression doesn't throw. +/// - sourceLocation: The source location where the expectation is made. +/// - errorHandler: A closure that's called with the error if the expression throws. public func expectAsyncThrowsError( _ expression: @autoclosure () async throws -> T, _ message: @autoclosure () -> Comment? = nil, diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index 287077570b6..8c5d0325a19 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -14,59 +14,71 @@ import Basics import _InternalTestSupport import PackageModel import Workspace -import XCTest - -final class InitTests: XCTestCase { - - // MARK: TSCBasic package creation for each package type. - - func testInitPackageEmpty() throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename - try fs.createDirectory(path) - - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .empty, - destinationPath: path, - fileSystem: localFileSystem - ) - var progressMessages = [String]() - initPackage.progressReporter = { message in - progressMessages.append(message) +import Testing +import SPMBuildCore + +/// Tests for the `InitPackage` functionality, which creates new Swift packages with different configurations. +struct InitTests { + /// The target triple for the current platform, used to locate build products. + static let targetTriple: Triple = { + do { + return try UserToolchain.default.targetTriple + } catch { + fatalError("Failed to determine target triple: \(error)") + } + }() + + // MARK: - Helper Methods + + /// Asserts that the package under test builds successfully. + public func expectBuilds( + _ path: AbsolutePath, + buildSystem: BuildSystemProvider.Kind, + configurations: Set = [.debug, .release], + extraArgs: [String] = [], + Xcc: [String] = [], + Xld: [String] = [], + Xswiftc: [String] = [], + env: Environment? = nil, + sourceLocation: SourceLocation = #_sourceLocation, + ) async { + for conf in configurations { + await #expect(throws: Never.self, sourceLocation: sourceLocation) { + try await executeSwiftBuild( + path, + configuration: conf, + extraArgs: extraArgs, + Xcc: Xcc, + Xld: Xld, + Xswiftc: Xswiftc, + env: env, + buildSystem: buildSystem + ) } - try initPackage.writePackageStructure() - - // Not picky about the specific progress messages, just checking that we got some. - XCTAssertGreaterThan(progressMessages.count, 0) - - // 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) - let version = InitPackage.newPackageToolsVersion - let versionSpecifier = "\(version.major).\(version.minor)" - XCTAssertMatch(manifestContents, .prefix("// swift-tools-version:\(version < .v5_4 ? "" : " ")\(versionSpecifier)\n")) - XCTAssertMatch(manifestContents, .contains(packageWithNameOnly(named: name))) } } - func testInitPackageExecutable() async throws { - try await testWithTemporaryDirectory { tmpPath in + /// Creates a test package with the specified configuration and verifies its structure. + private func createAndVerifyPackage( + packageType: InitPackage.PackageType, + name: String = "Foo", + supportedTestingLibraries: Set = [.xctest], + buildSystem: BuildSystemProvider.Kind? = nil, + customVerification: ((AbsolutePath, String) throws -> Void)? = nil + ) async throws { + return try await testWithTemporaryDirectory { tmpPath in let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename + let path = tmpPath.appending(name) + let packageName = path.basename try fs.createDirectory(path) // Create the package let initPackage = try InitPackage( - name: name, - packageType: .executable, + name: packageName, + packageType: packageType, + supportedTestingLibraries: supportedTestingLibraries, destinationPath: path, - fileSystem: localFileSystem + fileSystem: fs ) var progressMessages = [String]() initPackage.progressReporter = { message in @@ -74,366 +86,290 @@ final class InitTests: XCTestCase { } try initPackage.writePackageStructure() - // Not picky about the specific progress messages, just checking that we got some. - XCTAssertGreaterThan(progressMessages.count, 0) - - // Verify basic file system content that we expect in the package + #expect(progressMessages.count > 0, "Expected progress messages during package creation") + let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - let manifestContents: String = try localFileSystem.readFileContents(manifest) + expectFileExists(at: manifest) + + let manifestContents: String = try fs.readFileContents(manifest) let version = InitPackage.newPackageToolsVersion let versionSpecifier = "\(version.major).\(version.minor)" - XCTAssertMatch(manifestContents, .prefix("// swift-tools-version:\(version < .v5_4 ? "" : " ")\(versionSpecifier)\n")) + #expect(manifestContents.hasPrefix("// swift-tools-version:\(version < .v5_4 ? "" : " ")\(versionSpecifier)\n")) - XCTAssertEqual(try fs.getDirectoryContents(path.appending("Sources").appending("Foo")), ["Foo.swift"]) - await XCTAssertBuilds( - path, - buildSystem: .native, - ) - let triple = try UserToolchain.default.targetTriple - let binPath = path.appending(components: ".build", triple.platformBuildPathComponent, "debug") -#if os(Windows) - XCTAssertFileExists(binPath.appending("Foo.exe")) -#else - XCTAssertFileExists(binPath.appending("Foo")) -#endif - XCTAssertFileExists(binPath.appending(components: "Modules", "Foo.swiftmodule")) - } - } + try customVerification?(path, packageName) - func testInitPackageExecutableCalledMain() async throws { - try await testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("main") - let name = path.basename - try fs.createDirectory(path) + if let buildSystem = buildSystem { + await expectBuilds(path, buildSystem: buildSystem) - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .executable, - destinationPath: path, - fileSystem: localFileSystem - ) - try initPackage.writePackageStructure() - - XCTAssertEqual(try fs.getDirectoryContents(path.appending("Sources").appending("main")), ["MainEntrypoint.swift"]) - await XCTAssertBuilds( - path, - buildSystem: .native, - ) + verifyBuildProducts(for: packageType, at: path, name: packageName, buildSystem: buildSystem) + } } } - func testInitPackageLibraryWithXCTestOnly() async throws { - try await testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename - try fs.createDirectory(path) - - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .library, - supportedTestingLibraries: [.xctest], - destinationPath: path, - fileSystem: localFileSystem - ) - var progressMessages = [String]() - initPackage.progressReporter = { message in - progressMessages.append(message) + /// Verifies that the expected build products exist for a package. + private func verifyBuildProducts( + for packageType: InitPackage.PackageType, + at path: AbsolutePath, + name: String, + buildSystem: BuildSystemProvider.Kind + ) { + let expectedPath = path + .appending(components: ".build", Self.targetTriple.platformBuildPathComponent) + .appending(components: buildSystem.binPathSuffixes(for: BuildConfiguration.debug)) + + switch packageType { + case .library: + if buildSystem == .native { + expectFileExists(at: expectedPath.appending("Modules", "\(name).swiftmodule")) + } else { + expectFileExists(at: expectedPath.appending("\(name).swiftmodule")) } - try initPackage.writePackageStructure() - - // Not picky about the specific progress messages, just checking that we got some. - XCTAssertGreaterThan(progressMessages.count, 0) - - // 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) - let version = InitPackage.newPackageToolsVersion - let versionSpecifier = "\(version.major).\(version.minor)" - XCTAssertMatch(manifestContents, .prefix("// swift-tools-version:\(version < .v5_4 ? "" : " ")\(versionSpecifier)\n")) - - XCTAssertEqual(try fs.getDirectoryContents(path.appending("Sources").appending("Foo")), ["Foo.swift"]) - - let tests = path.appending("Tests") - XCTAssertEqual(try fs.getDirectoryContents(tests).sorted(), ["FooTests"]) - - let testFile = tests.appending("FooTests").appending("FooTests.swift") - let testFileContents: String = try localFileSystem.readFileContents(testFile) - XCTAssertTrue(testFileContents.hasPrefix("import XCTest"), """ - Validates formatting of XCTest source file, in particular that it does not contain leading whitespace: - \(testFileContents) - """) - XCTAssertMatch(testFileContents, .contains("func testExample() throws")) - - // Try building it - await XCTAssertBuilds( - path, - buildSystem: .native, - ) - let triple = try UserToolchain.default.targetTriple - XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) + case .executable, .tool: + expectFileExists(at: expectedPath.appending(executableName(name))) + case .empty, .buildToolPlugin, .commandPlugin, .macro: + // These types don't have specific build products to verify or are verified separately + break } } - func testInitPackageLibraryWithSwiftTestingOnly() async throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename - try fs.createDirectory(path) - - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .library, - supportedTestingLibraries: [.swiftTesting], - destinationPath: path, - fileSystem: localFileSystem - ) - try initPackage.writePackageStructure() + /// Verifies the test file contents for a package. + private func verifyTestFileContents( + at path: AbsolutePath, + name: String, + hasSwiftTesting: Bool, + hasXCTest: Bool + ) throws { + let testFile = path.appending("Tests").appending("\(name)Tests").appending("\(name)Tests.swift") + let testFileContents: String = try localFileSystem.readFileContents(testFile) + + if hasSwiftTesting { + #expect(testFileContents.contains(#"import Testing"#)) + #expect(testFileContents.contains(#"@Test func example() async throws"#)) + } else { + #expect(!testFileContents.contains(#"import Testing"#)) + #expect(!testFileContents.contains(#"@Test func example() async throws"#)) + } - // Verify basic file system content that we expect in the package - let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - - 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() async throws"#)) - XCTAssertNoMatch(testFileContents, .contains("func testExample() throws")) - -#if canImport(TestingDisabled) - // Try building it - await XCTAssertBuilds( - path, - buildSystem: .native, - ) - let triple = try UserToolchain.default.targetTriple - XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) -#endif + if hasXCTest { + #expect(testFileContents.contains(#"import XCTest"#)) + #expect(testFileContents.contains("func testExample() throws")) + + if hasSwiftTesting { + // When both are present, ensure XCTest content is properly formatted + #expect(testFileContents.contains("import XCTest"), "XCTest import should be present") + } else { + // When only XCTest is present, ensure it's at the beginning of the file + #expect(testFileContents.hasPrefix("import XCTest"), """ + Validates formatting of XCTest source file, in particular that it does not contain leading whitespace: + \(testFileContents) + """) + } + } else { + #expect(!testFileContents.contains(#"import XCTest"#)) + #expect(!testFileContents.contains("func testExample() throws")) } } - func testInitPackageLibraryWithBothSwiftTestingAndXCTest() async throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename - try fs.createDirectory(path) + /// Verifies plugin package contents. + private func verifyPluginPackage( + at path: AbsolutePath, + name: String, + isCommandPlugin: Bool + ) throws { + let manifest = path.appending("Package.swift") + expectFileExists(at: manifest) + let manifestContents: String = try localFileSystem.readFileContents(manifest) + + // Verify manifest contents + #expect(manifestContents.contains(".plugin(") && manifestContents.contains("targets: [\"\(name)\"]")) + + if isCommandPlugin { + #expect(manifestContents.contains(".plugin(") && + manifestContents.contains("capability: .command(intent: .custom(") && + manifestContents.contains("verb: \"\(name)\"")) + } else { + #expect(manifestContents.contains(".plugin(") && manifestContents.contains("capability: .buildTool()")) + } - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .library, - supportedTestingLibraries: [.swiftTesting, .xctest], - destinationPath: path, - fileSystem: localFileSystem - ) - try initPackage.writePackageStructure() + // Verify source file + let source = path.appending("Plugins", "\(name).swift") + expectFileExists(at: source) + let sourceContents: String = try localFileSystem.readFileContents(source) + + if isCommandPlugin { + #expect(sourceContents.contains("struct \(name): CommandPlugin")) + #expect(sourceContents.contains("performCommand(context: PluginContext")) + } else { + #expect(sourceContents.contains("struct \(name): BuildToolPlugin")) + #expect(sourceContents.contains("createBuildCommands(context: PluginContext")) + } - // Verify basic file system content that we expect in the package - let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - - 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() async throws"#)) - XCTAssertMatch(testFileContents, .contains("func testExample() throws")) - -#if canImport(TestingDisabled) - // Try building it - await XCTAssertBuilds( - path, - buildSystem: .native, - ) - let triple = try UserToolchain.default.targetTriple - XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) -#endif + // Both plugin types should have Xcode extensions + #expect(sourceContents.contains("import XcodeProjectPlugin")) + if isCommandPlugin { + #expect(sourceContents.contains("extension \(name): XcodeCommandPlugin")) + #expect(sourceContents.contains("performCommand(context: XcodePluginContext")) + } else { + #expect(sourceContents.contains("extension \(name): XcodeBuildToolPlugin")) + #expect(sourceContents.contains("createBuildCommands(context: XcodePluginContext")) } } - func testInitPackageLibraryWithNoTests() async throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename - try fs.createDirectory(path) - - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .library, - supportedTestingLibraries: [], - destinationPath: path, - fileSystem: localFileSystem - ) - try initPackage.writePackageStructure() - - // 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) - XCTAssertNoMatch(manifestContents, .contains(#".testTarget"#)) - - XCTAssertNoSuchPath(path.appending("Tests")) - -#if canImport(TestingDisabled) - // Try building it - await XCTAssertBuilds( - path, - buildSystem: .native, - ) - let triple = try UserToolchain.default.targetTriple - XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) -#endif + // MARK: - Package Type Tests + + /// Tests creating an empty package. + @Test func initPackageEmpty() throws { + Task { + try await createAndVerifyPackage( + packageType: .empty, + supportedTestingLibraries: [], + customVerification: { path, name in + let manifestContents: String = try localFileSystem.readFileContents(path.appending("Package.swift")) + #expect(manifestContents.contains(packageWithNameOnly(named: name))) + }) } } - func testInitPackageExecutableWithSwiftTesting() async throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename - try fs.createDirectory(path) - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .executable, - supportedTestingLibraries: [.swiftTesting], - destinationPath: path, - fileSystem: localFileSystem - ) - - try initPackage.writePackageStructure() - // Verify basic file system content that we expect in the package - let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - 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() async throws"#)) - XCTAssertNoMatch(testFileContents, .contains("func testExample() throws")) - } + /// Tests creating an executable package with different build systems. + @Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild]) + func initPackageExecutable(buildSystem: BuildSystemProvider.Kind) async throws { + try await createAndVerifyPackage( + packageType: .executable, + buildSystem: buildSystem, + customVerification: { path, name in + let directoryContents = try localFileSystem.getDirectoryContents(path.appending("Sources").appending(name)) + #expect(directoryContents == ["\(name).swift"]) + } + ) } - func testInitPackageToolWithSwiftTesting() async throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("Foo") - let name = path.basename - try fs.createDirectory(path) - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .tool, - supportedTestingLibraries: [.swiftTesting], - destinationPath: path, - fileSystem: localFileSystem - ) + /// Tests creating an executable package named "main". + @Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild]) + func initPackageExecutableCalledMain(buildSystem: BuildSystemProvider.Kind) async throws { + try await createAndVerifyPackage( + packageType: .executable, + name: "main", + buildSystem: buildSystem, + customVerification: { path, _ in + let directoryContents = try localFileSystem.getDirectoryContents(path.appending("Sources").appending("main")) + #expect(directoryContents == ["MainEntrypoint.swift"]) + } + ) + } - try initPackage.writePackageStructure() - // Verify basic file system content that we expect in the package - let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - 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() async throws"#)) - XCTAssertNoMatch(testFileContents, .contains("func testExample() throws")) - } + /// Tests creating packages with XCTest only. + @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) + func initPackageLibraryWithXCTestOnly(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + try await createAndVerifyPackage( + packageType: packageType, + supportedTestingLibraries: [.xctest], + buildSystem: buildSystem, + customVerification: { path, name in + #expect(try localFileSystem.getDirectoryContents(path.appending("Sources").appending(name)) == ["\(name).swift"], + "Expected single source file in Sources/\(name) directory") + + let tests = path.appending("Tests") + #expect(try localFileSystem.getDirectoryContents(tests).sorted() == ["\(name)Tests"], + "Expected single test directory") + + try verifyTestFileContents(at: path, name: name, hasSwiftTesting: false, hasXCTest: true) + } + ) } - func testInitPackageCommandPlugin() throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("MyCommandPlugin") - let name = path.basename - try fs.createDirectory(path) + /// Tests creating packages with Swift Testing only. + @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) + func initPackagesWithSwiftTestingOnly(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + try await createAndVerifyPackage( + packageType: packageType, + supportedTestingLibraries: [.swiftTesting], + buildSystem: buildSystem, + customVerification: { path, name in + try verifyTestFileContents(at: path, name: name, hasSwiftTesting: true, hasXCTest: false) + + #if canImport(TestingDisabled) + let expectedPath = path.appending(components: ".build", Self.targetTriple.platformBuildPathComponent, "debug", "Modules", "\(name).swiftmodule") + expectFileExists(at: expectedPath) + #endif + } + ) + } - // Create the package - try InitPackage( - name: name, - packageType: .commandPlugin, - destinationPath: path, - fileSystem: localFileSystem - ).writePackageStructure() + /// Tests creating packages with both Swift Testing and XCTest. + @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) + func initPackageWithBothSwiftTestingAndXCTest(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + try await createAndVerifyPackage( + packageType: packageType, + supportedTestingLibraries: [.swiftTesting, .xctest], + buildSystem: buildSystem, + customVerification: { path, name in + try verifyTestFileContents(at: path, name: name, hasSwiftTesting: true, hasXCTest: true) + + #if canImport(TestingDisabled) + let expectedPath = path.appending(components: ".build", Self.targetTriple.platformBuildPathComponent, "debug", "Modules", "\(name).swiftmodule") + expectFileExists(at: expectedPath) + #endif + } + ) + } - // 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, .and(.contains(".plugin("), .contains("targets: [\"MyCommandPlugin\"]"))) - XCTAssertMatch(manifestContents, .and(.contains(".plugin("), - .and(.contains("capability: .command(intent: .custom("), .contains("verb: \"MyCommandPlugin\"")))) - - // Check basic content that we expect in the plugin source file - let source = path.appending("Plugins", "MyCommandPlugin.swift") - XCTAssertFileExists(source) - let sourceContents: String = try localFileSystem.readFileContents(source) - XCTAssertMatch(sourceContents, .contains("struct MyCommandPlugin: CommandPlugin")) - XCTAssertMatch(sourceContents, .contains("performCommand(context: PluginContext")) - XCTAssertMatch(sourceContents, .contains("import XcodeProjectPlugin")) - XCTAssertMatch(sourceContents, .contains("extension MyCommandPlugin: XcodeCommandPlugin")) - XCTAssertMatch(sourceContents, .contains("performCommand(context: XcodePluginContext")) - } + /// Tests creating packages with no testing libraries. + @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) + func initPackageWithNoTests(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + try await createAndVerifyPackage( + packageType: packageType, + supportedTestingLibraries: [], + buildSystem: buildSystem, + customVerification: { path, name in + let manifestContents: String = try localFileSystem.readFileContents(path.appending("Package.swift")) + #expect(!manifestContents.contains(#".testTarget"#)) + + expectDirectoryDoesNotExist(at: path.appending("Tests")) + + #if canImport(TestingDisabled) + let expectedPath = path.appending(components: ".build", Self.targetTriple.platformBuildPathComponent, "debug", "Modules", "\(name).swiftmodule") + expectFileExists(at: expectedPath) + #endif + } + ) } - - func testInitPackageBuildToolPlugin() throws { - try testWithTemporaryDirectory { tmpPath in - let fs = localFileSystem - let path = tmpPath.appending("MyBuildToolPlugin") - let name = path.basename - try fs.createDirectory(path) - // Create the package - try InitPackage( - name: name, - packageType: .buildToolPlugin, - destinationPath: path, - fileSystem: localFileSystem - ).writePackageStructure() + /// Tests creating a command plugin package. + @Test func initPackageCommandPlugin() async throws { + try await createAndVerifyPackage( + packageType: .commandPlugin, + name: "MyCommandPlugin", + supportedTestingLibraries: [], + customVerification: { path, name in + try verifyPluginPackage(at: path, name: name, isCommandPlugin: true) + } + ) + } - // 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, .and(.contains(".plugin("), .contains("targets: [\"MyBuildToolPlugin\"]"))) - XCTAssertMatch(manifestContents, .and(.contains(".plugin("), .contains("capability: .buildTool()"))) - - // Check basic content that we expect in the plugin source file - let source = path.appending("Plugins", "MyBuildToolPlugin.swift") - XCTAssertFileExists(source) - let sourceContents: String = try localFileSystem.readFileContents(source) - XCTAssertMatch(sourceContents, .contains("struct MyBuildToolPlugin: BuildToolPlugin")) - XCTAssertMatch(sourceContents, .contains("createBuildCommands(context: PluginContext")) - XCTAssertMatch(sourceContents, .contains("import XcodeProjectPlugin")) - XCTAssertMatch(sourceContents, .contains("extension MyBuildToolPlugin: XcodeBuildToolPlugin")) - XCTAssertMatch(sourceContents, .contains("createBuildCommands(context: XcodePluginContext")) - } + /// Tests creating a build tool plugin package. + @Test func initPackageBuildToolPlugin() async throws { + try await createAndVerifyPackage( + packageType: .buildToolPlugin, + name: "MyBuildToolPlugin", + supportedTestingLibraries: [], + customVerification: { path, name in + try verifyPluginPackage(at: path, name: name, isCommandPlugin: false) + } + ) } - // MARK: Special case testing + // MARK: - Special Case Tests - func testInitPackageNonc99Directory() async throws { + /// Tests creating a package in a directory with a non-C99 compliant name. + @Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild]) + func initPackageNonc99Directory(buildSystem: BuildSystemProvider.Kind) async throws { try await withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in - XCTAssertDirectoryExists(tempDirPath) - // Create a directory with non c99name. let packageRoot = tempDirPath.appending("some-package") let packageName = packageRoot.basename try localFileSystem.createDirectory(packageRoot) - XCTAssertDirectoryExists(packageRoot) - + expectDirectoryExists(at: packageRoot) + // Create the package let initPackage = try InitPackage( name: packageName, @@ -441,27 +377,34 @@ final class InitTests: XCTestCase { destinationPath: packageRoot, fileSystem: localFileSystem ) - initPackage.progressReporter = { message in } + initPackage.progressReporter = { _ in } try initPackage.writePackageStructure() // Try building it. - await XCTAssertBuilds( - packageRoot, - buildSystem: .native, - ) - let triple = try UserToolchain.default.targetTriple - XCTAssertFileExists(packageRoot.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "some_package.swiftmodule")) + await expectBuilds(packageRoot, buildSystem: buildSystem) + + // Assert that the expected build products exist + let expectedPath = packageRoot + .appending(components: ".build", Self.targetTriple.platformBuildPathComponent) + .appending(components: buildSystem.binPathSuffixes(for: BuildConfiguration.debug)) + + // Verify the module name is properly mangled + if buildSystem == .native { + expectFileExists(at: expectedPath.appending("Modules", "some_package.swiftmodule")) + } else { + expectFileExists(at: expectedPath.appending("some_package.swiftmodule")) + } } } - - func testNonC99NameExecutablePackage() async throws { + + /// Tests creating a package with a non-C99 compliant name. + @Test(arguments: [BuildSystemProvider.Kind.native, .swiftbuild]) + func nonC99NameExecutablePackage(buildSystem: BuildSystemProvider.Kind) async throws { try await withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in - XCTAssertDirectoryExists(tempDirPath) - let packageRoot = tempDirPath.appending("Foo") try localFileSystem.createDirectory(packageRoot) - XCTAssertDirectoryExists(packageRoot) - + expectDirectoryExists(at: packageRoot) + // Create package with non c99name. let initPackage = try InitPackage( name: "package-name", @@ -470,16 +413,15 @@ final class InitTests: XCTestCase { fileSystem: localFileSystem ) try initPackage.writePackageStructure() - - await XCTAssertBuilds( - packageRoot, - buildSystem: .native, - ) + + await expectBuilds(packageRoot, buildSystem: buildSystem) } } - func testPlatforms() throws { + /// Tests creating a package with custom platform requirements. + @Test func platforms() throws { try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in + // Define custom platform requirements var options = InitPackage.InitPackageOptions(packageType: .library, supportedTestingLibraries: []) options.platforms = [ .init(platform: .macOS, version: PlatformVersion("10.15")), @@ -492,6 +434,7 @@ final class InitTests: XCTestCase { try localFileSystem.removeFileTree(packageRoot) try localFileSystem.createDirectory(packageRoot) + // Create the package with custom options let initPackage = try InitPackage( name: "Foo", options: options, @@ -501,11 +444,17 @@ final class InitTests: XCTestCase { ) try initPackage.writePackageStructure() + // Verify platform requirements are correctly included in the manifest let contents: String = try localFileSystem.readFileContents(packageRoot.appending("Package.swift")) - XCTAssertMatch(contents, .contains(#"platforms: [.macOS(.v10_15), .iOS(.v12), .watchOS("2.1"), .tvOS("999.0")],"#)) + #expect(contents.contains(#"platforms: [.macOS(.v10_15), .iOS(.v12), .watchOS("2.1"), .tvOS("999.0")],"#)) } } + // MARK: - Helper Methods for Package Content + + /// Creates a simple package manifest with just the name. + /// - Parameter name: The name of the package + /// - Returns: A string containing the package manifest private func packageWithNameOnly(named name: String) -> String { return """ let package = Package( @@ -514,6 +463,9 @@ final class InitTests: XCTestCase { """ } + /// Creates a package manifest with name and dependencies section. + /// - Parameter name: The name of the package + /// - Returns: A string containing the package manifest private func packageWithNameAndDependencies(with name: String) -> String { return """ let package = Package( From ce0cf11dc5903bc2c868cf6e816378e58d530a3e Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Sat, 30 Aug 2025 13:48:31 -0400 Subject: [PATCH 2/3] Fixup tests --- Tests/WorkspaceTests/InitTests.swift | 32 ++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index 8c5d0325a19..90375281b39 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -260,10 +260,16 @@ struct InitTests { /// Tests creating packages with XCTest only. @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) func initPackageLibraryWithXCTestOnly(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + #if canImport(TestingDisabled) + let buildSys = buildSystem + #else + let buildSys: BuildSystemProvider.Kind? = nil + #endif + try await createAndVerifyPackage( packageType: packageType, supportedTestingLibraries: [.xctest], - buildSystem: buildSystem, + buildSystem: buildSys, customVerification: { path, name in #expect(try localFileSystem.getDirectoryContents(path.appending("Sources").appending(name)) == ["\(name).swift"], "Expected single source file in Sources/\(name) directory") @@ -280,10 +286,16 @@ struct InitTests { /// Tests creating packages with Swift Testing only. @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) func initPackagesWithSwiftTestingOnly(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + #if canImport(TestingDisabled) + let buildSys = buildSystem + #else + let buildSys: BuildSystemProvider.Kind? = nil + #endif + try await createAndVerifyPackage( packageType: packageType, supportedTestingLibraries: [.swiftTesting], - buildSystem: buildSystem, + buildSystem: buildSys, customVerification: { path, name in try verifyTestFileContents(at: path, name: name, hasSwiftTesting: true, hasXCTest: false) @@ -298,10 +310,16 @@ struct InitTests { /// Tests creating packages with both Swift Testing and XCTest. @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) func initPackageWithBothSwiftTestingAndXCTest(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + #if canImport(TestingDisabled) + let buildSys = buildSystem + #else + let buildSys: BuildSystemProvider.Kind? = nil + #endif + try await createAndVerifyPackage( packageType: packageType, supportedTestingLibraries: [.swiftTesting, .xctest], - buildSystem: buildSystem, + buildSystem: buildSys, customVerification: { path, name in try verifyTestFileContents(at: path, name: name, hasSwiftTesting: true, hasXCTest: true) @@ -316,10 +334,16 @@ struct InitTests { /// Tests creating packages with no testing libraries. @Test(arguments: [InitPackage.PackageType.library, .executable, .tool], [BuildSystemProvider.Kind.native, .swiftbuild]) func initPackageWithNoTests(packageType: InitPackage.PackageType, buildSystem: BuildSystemProvider.Kind) async throws { + #if canImport(TestingDisabled) + let buildSys = buildSystem + #else + let buildSys: BuildSystemProvider.Kind? = nil + #endif + try await createAndVerifyPackage( packageType: packageType, supportedTestingLibraries: [], - buildSystem: buildSystem, + buildSystem: buildSys, customVerification: { path, name in let manifestContents: String = try localFileSystem.readFileContents(path.appending("Package.swift")) #expect(!manifestContents.contains(#".testTarget"#)) From 196f5e1ed11da15ce2b349aaac9a7000d2a6db81 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 12 Sep 2025 08:50:44 -0400 Subject: [PATCH 3/3] Fixup rebase --- .../SwiftTesting+Helpers.swift | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift index a87ef1c3dae..5fc76b0578e 100644 --- a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift +++ b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift @@ -32,11 +32,11 @@ public func expectFileExists( /// Verifies that a file does not exist at the specified path. /// /// - Parameters: -/// - fixturePath: The absolute path to check for file non-existence. +/// - path: The absolute path to check for file non-existence. /// - comment: An optional comment to include in the failure message. /// - sourceLocation: The source location where the expectation is made. public func expectFileDoesNotExists( - at fixturePath: AbsolutePath, + at path: AbsolutePath, _ comment: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation, ) { @@ -59,30 +59,6 @@ public func expectFileDoesNotExists( ) } -public func expectFileDoesNotExists( - at path: AbsolutePath, - _ comment: Comment? = nil, - sourceLocation: SourceLocation = #_sourceLocation, -) { - let commentPrefix = - if let comment { - "\(comment): " - } else { - "" - } - let msgSuffix: String - do { - msgSuffix = try "Directory contents: \(localFileSystem.getDirectoryContents(path.parentDirectory))" - } catch { - msgSuffix = "" - } - #expect( - !localFileSystem.exists(path), - "\(commentPrefix)File: '\(path)' was not expected to exist, but does.\(msgSuffix))", - sourceLocation: sourceLocation, - ) -} - /// Verifies that a file exists and is executable at the specified path. /// /// - Parameters: