From 0f5aac026f3ca3b293acdccdb580ace3c8e840e4 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 | 44 +- Tests/WorkspaceTests/InitTests.swift | 714 ++++++++---------- 2 files changed, 361 insertions(+), 397 deletions(-) diff --git a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift index d7e3185b9a4..5a9ef7ae91f 100644 --- a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift +++ b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift @@ -11,30 +11,30 @@ 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, - _ 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)' does not exist. \(msgSuffix)", + "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 path: AbsolutePath, _ comment: Comment? = nil, @@ -59,6 +59,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 +83,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 +105,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, diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index c5a6cc61319..7fb4a20e3bb 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,370 +86,292 @@ 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")) - } - } + if !supportedTestingLibraries.isEmpty { + #expect(manifestContents.contains(".testTarget(")) + } - 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) + try customVerification?(path, packageName) - // Create the package - let initPackage = try InitPackage( - name: name, - packageType: .executable, - destinationPath: path, - fileSystem: localFileSystem - ) - try initPackage.writePackageStructure() + if let buildSystem = buildSystem { + await expectBuilds(path, buildSystem: buildSystem) - XCTAssertEqual(try fs.getDirectoryContents(path.appending("Sources").appending("main")), ["MainEntrypoint.swift"]) - await XCTAssertBuilds( - path, - buildSystem: .native, - ) + try 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 + ) throws { + let expectedPath = path.appending(components: try buildSystem.binPath(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 manifestContents: String = try localFileSystem.readFileContents(manifest) - XCTAssertMatch(manifestContents, .contains(".testTarget(")) - 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 manifestContents: String = try localFileSystem.readFileContents(manifest) - XCTAssertMatch(manifestContents, .contains(".testTarget(")) - 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, @@ -445,27 +379,32 @@ 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: try buildSystem.binPath(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", @@ -474,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")), @@ -496,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, @@ -505,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( @@ -518,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 dc936c4a84c6dcbc825627789560879635cdfabe 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 7fb4a20e3bb..6a1bed14891 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -262,10 +262,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") @@ -282,10 +288,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) @@ -300,10 +312,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) @@ -318,10 +336,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 9f2a73e9210c2c75ef3e818790aabb16f4b9b254 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 | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift index 5a9ef7ae91f..06bf53b76d0 100644 --- a/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift +++ b/Sources/_InternalTestSupport/SwiftTesting+Helpers.swift @@ -20,11 +20,16 @@ import Testing /// - sourceLocation: The source location where the expectation is made. public func expectFileExists( at path: AbsolutePath, + _ comment: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation, ) { #expect( localFileSystem.exists(path), - "Files '\(path)' does not exist.", + wrapMessage( + "Files '\(path)' does not exist.", + comment: comment, + directoryPath: path.parentDirectory + ), sourceLocation: sourceLocation, ) } @@ -32,7 +37,7 @@ 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( @@ -40,21 +45,13 @@ public func expectFileDoesNotExists( _ 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))", + wrapMessage( + "File: '\(path)' was not expected to exist, but does.", + comment: comment, + directoryPath: path.parentDirectory + ), sourceLocation: sourceLocation, ) } @@ -70,15 +67,9 @@ public func expectFileIsExecutable( _ comment: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation, ) { - let commentPrefix = - if let comment { - "\(comment): " - } else { - "" - } #expect( localFileSystem.isExecutableFile(fixturePath), - "\(commentPrefix)File '\(fixturePath)' expected to be executable, but is not.", + wrapMessage("File '\(fixturePath)' expected to be executable, but is not.", comment: comment), sourceLocation: sourceLocation, ) } @@ -90,17 +81,12 @@ public func expectFileIsExecutable( /// - sourceLocation: The source location where the expectation is made. public func expectDirectoryExists( at path: AbsolutePath, + _ comment: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation, ) { -let msgSuffix: String - do { - msgSuffix = try "Directory contents: \(localFileSystem.getDirectoryContents(path))" - } catch { - msgSuffix = "" - } #expect( localFileSystem.isDirectory(path), - "Expected directory doesn't exist: '\(path)'. \(msgSuffix)", + wrapMessage("Expected directory doesn't exist: '\(path)'", comment: comment, directoryPath: path), sourceLocation: sourceLocation, ) } @@ -112,21 +98,47 @@ let msgSuffix: String /// - sourceLocation: The source location where the expectation is made. public func expectDirectoryDoesNotExist( at path: AbsolutePath, + _ comment: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation, ) { - let msgSuffix: String - do { - msgSuffix = try "Directory contents: \(localFileSystem.getDirectoryContents(path))" - } catch { - msgSuffix = "" - } #expect( !localFileSystem.isDirectory(path), - "Directory exists unexpectedly: '\(path)'.\(msgSuffix)", + wrapMessage("Directory exists unexpectedly: '\(path)'", comment: comment, directoryPath: path), sourceLocation: sourceLocation, ) } +/// Wraps a message with an optional comment prefix and directory contents suffix. +/// +/// - Parameters: +/// - message: The base message to wrap. +/// - comment: An optional comment to prefix the message with. +/// - directoryPath: An optional path to a folder whose contents will be appended to the message. +/// - Returns: The formatted message with prefix and suffix. +private func wrapMessage( + _ message: Comment, + comment: Comment? = nil, + directoryPath: AbsolutePath? = nil +) -> Comment { + let commentPrefix = + if let comment { + "\(comment): " + } else { + "" + } + + var msgSuffix = "" + if let directoryPath { + do { + msgSuffix = try " Directory contents: \(localFileSystem.getDirectoryContents(directoryPath))" + } catch { + // Silently ignore errors when getting directory contents + } + } + + return "\(commentPrefix)\(message)\(msgSuffix)" +} + /// Expects that the expression throws a CommandExecutionError and passes it to the provided throwing error handler. /// - Parameters: /// - expression: The expression expected to throw