diff --git a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Package.swift b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Package.swift index ac5587c1a32..c040212b25d 100644 --- a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Package.swift +++ b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Package.swift @@ -8,7 +8,13 @@ let package = Package( name: "PluginScriptProduct", targets: [ "PluginScriptTarget" - ] + ], + ), + .library( + name: "MyLib", + targets: [ + "MyLib", + ], ), ], targets: [ @@ -17,9 +23,10 @@ let package = Package( capability: .command( intent: .custom( verb: "do-something", - description: "Do something" - ) - ) + description: "Do something", + ), + ), ), + .target(name: "MyLib"), ] ) diff --git a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/ContainsMain.swift b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/ContainsMain.swift new file mode 100644 index 00000000000..62a53afc43e --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/ContainsMain.swift @@ -0,0 +1,9 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +@main +struct foo { + static func main() { + print("hello, snippets. File: \(#file)") + } +} diff --git a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/ImportsProductTarget.swift b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/ImportsProductTarget.swift new file mode 100644 index 00000000000..02b46f32b35 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/ImportsProductTarget.swift @@ -0,0 +1,3 @@ +import MyLib + +libraryCall() \ No newline at end of file diff --git a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/MySnippet.swift b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/MySnippet.swift index e69de29bb2d..601b98de34e 100644 --- a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/MySnippet.swift +++ b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/MySnippet.swift @@ -0,0 +1 @@ +print("hello, snippets. File: \(#file)") \ No newline at end of file diff --git a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/SubDirectory/main.swift b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/SubDirectory/main.swift new file mode 100644 index 00000000000..ab9439d5444 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets/SubDirectory/main.swift @@ -0,0 +1 @@ +print("hello, snippets! File: \(#file)") \ No newline at end of file diff --git a/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Sources/MyLib/MyLib.swift b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Sources/MyLib/MyLib.swift new file mode 100644 index 00000000000..57aa1a92179 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Sources/MyLib/MyLib.swift @@ -0,0 +1,4 @@ +public func libraryCall() { + print("From library") + print("hello, snippets. File: \(#file)") +} \ No newline at end of file diff --git a/Package.swift b/Package.swift index f39bb5399ff..c8f536d5948 100644 --- a/Package.swift +++ b/Package.swift @@ -534,6 +534,7 @@ let package = Package( .target( name: "SwiftBuildSupport", dependencies: [ + "Build", "SPMBuildCore", "PackageGraph", ], diff --git a/Sources/Basics/FileSystem/InMemoryFileSystem.swift b/Sources/Basics/FileSystem/InMemoryFileSystem.swift index 56e6450aa55..0853f607b31 100644 --- a/Sources/Basics/FileSystem/InMemoryFileSystem.swift +++ b/Sources/Basics/FileSystem/InMemoryFileSystem.swift @@ -226,12 +226,24 @@ public final class InMemoryFileSystem: FileSystem { } /// Virtualized current working directory. + private var _currentWorkingDirectory: TSCBasic.AbsolutePath = try! .init(validating: "/") + public var currentWorkingDirectory: TSCBasic.AbsolutePath? { - return try? .init(validating: "/") + return _currentWorkingDirectory } public func changeCurrentWorkingDirectory(to path: TSCBasic.AbsolutePath) throws { - throw FileSystemError(.unsupported, path) + return try lock.withLock { + // Verify the path exists and is a directory + guard let node = try getNode(path) else { + throw FileSystemError(.noEntry, path) + } + + guard case .directory = node.contents else { + throw FileSystemError(.notDirectory, path) + } + _currentWorkingDirectory = path + } } public var homeDirectory: TSCBasic.AbsolutePath { diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 19a7c3322f8..1d2bdb407a6 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -34,6 +34,34 @@ import struct TSCBasic.ByteString @available(*, deprecated, renamed: "SwiftModuleBuildDescription") public typealias SwiftTargetBuildDescription = SwiftModuleBuildDescription +// looking into the file content to see if it is using the @main annotation +// this is not bullet-proof since theoretically the file can contain the @main string for other reasons +// but it is the closest to accurate we can do at this point +package func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool { + let content: String = try fileSystem.readFileContents(path) + let lines = content.split(whereSeparator: { $0.isNewline }).map { $0.trimmingCharacters(in: .whitespaces) } + + var multilineComment = false + for line in lines { + if line.hasPrefix("//") { + continue + } + if line.hasPrefix("/*") { + multilineComment = true + } + if line.hasSuffix("*/") { + multilineComment = false + } + if multilineComment { + continue + } + if line.hasPrefix("@main") { + return true + } + } + return false +} + /// Build description for a Swift module. public final class SwiftModuleBuildDescription { /// The package this target belongs to. @@ -216,40 +244,12 @@ public final class SwiftModuleBuildDescription { return false } // looking into the file content to see if it is using the @main annotation which requires parse-as-library - return (try? self.containsAtMain(fileSystem: self.fileSystem, path: self.sources[0])) ?? false + return (try? containsAtMain(fileSystem: self.fileSystem, path: self.sources[0])) ?? false default: return false } } - // looking into the file content to see if it is using the @main annotation - // this is not bullet-proof since theoretically the file can contain the @main string for other reasons - // but it is the closest to accurate we can do at this point - func containsAtMain(fileSystem: FileSystem, path: AbsolutePath) throws -> Bool { - let content: String = try self.fileSystem.readFileContents(path) - let lines = content.split(whereSeparator: { $0.isNewline }).map { $0.trimmingCharacters(in: .whitespaces) } - - var multilineComment = false - for line in lines { - if line.hasPrefix("//") { - continue - } - if line.hasPrefix("/*") { - multilineComment = true - } - if line.hasSuffix("*/") { - multilineComment = false - } - if multilineComment { - continue - } - if line.hasPrefix("@main") { - return true - } - } - return false - } - /// The filesystem to operate on. let fileSystem: FileSystem diff --git a/Sources/Commands/PackageCommands/CompletionCommand.swift b/Sources/Commands/PackageCommands/CompletionCommand.swift index 49f68613943..14a61a08c2a 100644 --- a/Sources/Commands/PackageCommands/CompletionCommand.swift +++ b/Sources/Commands/PackageCommands/CompletionCommand.swift @@ -79,18 +79,18 @@ extension SwiftPackageCommand { case .listExecutables: let graph = try await swiftCommandState.loadPackageGraph() let package = graph.rootPackages[graph.rootPackages.startIndex].underlying - let executables = package.products.filter { $0.type == .executable } + let executables = package.products.filter { $0.type == .executable }.sorted() for executable in executables { print(executable.name) } case .listSnippets: let graph = try await swiftCommandState.loadPackageGraph() let package = graph.rootPackages[graph.rootPackages.startIndex].underlying - let executables = package.modules.filter { $0.type == .snippet } + let executables = package.modules.filter { $0.type == .snippet }.sorted() for executable in executables { print(executable.name) } } } } -} +} \ No newline at end of file diff --git a/Sources/PackageModel/Module/Module.swift b/Sources/PackageModel/Module/Module.swift index 34d6f72c8f8..7001c244212 100644 --- a/Sources/PackageModel/Module/Module.swift +++ b/Sources/PackageModel/Module/Module.swift @@ -292,6 +292,12 @@ extension Module: Hashable { } } +extension Module: Comparable { + public static func < (lhs: Module, rhs: Module) -> Bool { + return lhs.name < rhs.name + } +} + extension Module: CustomStringConvertible { public var description: String { return "<\(Swift.type(of: self)): \(name)>" diff --git a/Sources/PackageModel/Product.swift b/Sources/PackageModel/Product.swift index ac42bc458d6..4b5c79f696b 100644 --- a/Sources/PackageModel/Product.swift +++ b/Sources/PackageModel/Product.swift @@ -68,6 +68,12 @@ extension Product: Hashable { } } +extension Product: Comparable { + public static func < (lhs: Product, rhs: Product) -> Bool { + lhs.name < rhs.name + } +} + /// The type of product. public enum ProductType: Equatable, Hashable, Sendable { diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index 8964cb87072..65b303f68b4 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -451,13 +451,13 @@ public final class PackagePIFBuilder { try projectBuilder.makeLibraryProduct(product, type: libraryType) } - case .executable, .test: + case .executable, .test, .snippet: try projectBuilder.makeMainModuleProduct(product) case .plugin: try projectBuilder.makePluginProduct(product) - case .snippet, .macro: + case .macro: break // TODO: Double-check what's going on here as we skip snippet modules too (rdar://147705448) } } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index baba5819900..69f1d6d8e35 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -17,6 +17,7 @@ import TSCUtility import struct Basics.AbsolutePath import class Basics.ObservabilitySystem import struct Basics.SourceControlURL +import func Build.containsAtMain import class PackageModel.BinaryModule import class PackageModel.Manifest @@ -55,7 +56,7 @@ extension PackagePIFProjectBuilder { let synthesizedResourceGeneratingPluginInvocationResults: [PackagePIFBuilder.BuildToolPluginInvocationResult] = [] - if product.type == .executable { + if [.executable, .snippet].contains(product.type) { if let customPIFProductType = pifBuilder.delegate.customProductType(forExecutable: product.underlying) { pifProductType = customPIFProductType moduleOrProductType = PackagePIFBuilder.ModuleOrProductType(from: customPIFProductType) @@ -138,6 +139,17 @@ extension PackagePIFProjectBuilder { settings[.INSTALL_PATH] = "/usr/local/bin" settings[.LD_RUNPATH_SEARCH_PATHS] = ["$(inherited)", "@executable_path/../lib"] } + } else if mainModule.type == .snippet { + let hasMainModule: Bool + if let mainModule = product.mainModule { + // Check if any source file in the main module contains @main + hasMainModule = mainModule.sources.paths.contains { (sourcePath: AbsolutePath) in + (try? containsAtMain(fileSystem: pifBuilder.fileSystem, path: sourcePath)) ?? false + } + } else { + hasMainModule = false + } + settings[.SWIFT_DISABLE_PARSE_AS_LIBRARY] = hasMainModule ? "NO" : "YES" } let mainTargetDeploymentTargets = mainModule.deploymentTargets(using: pifBuilder.delegate) diff --git a/Sources/_InternalTestSupport/FileSystemHelpers.swift b/Sources/_InternalTestSupport/FileSystemHelpers.swift new file mode 100644 index 00000000000..5f08a6ff35e --- /dev/null +++ b/Sources/_InternalTestSupport/FileSystemHelpers.swift @@ -0,0 +1,118 @@ +import Foundation +import Basics + +func getFiles(atPath path: String, matchingExtension fileExtension: String) -> [URL] { + let fileManager = FileManager.default + var matchingFiles: [URL] = [] + + guard + let enumerator = fileManager.enumerator( + at: URL(fileURLWithPath: path), + includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles, .skipsPackageDescendants] + ) + else { + print("Error: Could not create enumerator for path: \(path)") + return [] + } + + for case let fileURL as URL in enumerator { + do { + let resourceValues = try fileURL.resourceValues(forKeys: [.isRegularFileKey]) + if let isRegularFile = resourceValues.isRegularFile, isRegularFile { + if fileURL.pathExtension.lowercased() == fileExtension.lowercased() { + matchingFiles.append(fileURL) + } + } + } catch { + print("Error retrieving resource values for \(fileURL.lastPathComponent): \(error.localizedDescription)") + } + } + return matchingFiles +} + +/// Returns all files that match the given extension in the specified directory. +/// +/// - Parameters: +/// - directory: The directory to search in (AbsolutePath) +/// - extension: The file extension to match (without the leading dot) +/// - recursive: Whether to search subdirectories recursively (default: true) +/// - fileSystem: The file system to use for operations (defaults to localFileSystem) +/// - Returns: An array of AbsolutePath objects +/// - Throws: FileSystemError if the directory cannot be accessed or enumerated +public func getFiles( + in directory: AbsolutePath, + matchingExtension extension: String, + recursive: Bool = true, + fileSystem: FileSystem = localFileSystem +) throws -> [AbsolutePath] { + var matchingFiles: [AbsolutePath] = [] + let normalizedExtension = `extension`.lowercased() + + guard fileSystem.exists(directory) else { + throw StringError("Directory does not exist: \(directory)") + } + + guard fileSystem.isDirectory(directory) else { + throw StringError("Path is not a directory: \(directory)") + } + + if recursive { + try fileSystem.enumerate(directory: directory) { filePath in + if fileSystem.isFile(filePath) { + if let fileExtension = filePath.extension?.lowercased(), + fileExtension == normalizedExtension { + matchingFiles.append(filePath) + } + } + } + } else { + // Non-recursive: only check direct children + let contents = try fileSystem.getDirectoryContents(directory) + for item in contents { + let itemPath = directory.appending(component: item) + if fileSystem.isFile(itemPath) { + if let fileExtension = itemPath.extension?.lowercased(), + fileExtension == normalizedExtension { + matchingFiles.append(itemPath) + } + } + } + } + + return matchingFiles +} + +/// Returns all files that match the given extension in the specified directory. +/// +/// - Parameters: +/// - directory: The directory to search in (RelativePath) +/// - extension: The file extension to match (without the leading dot) +/// - recursive: Whether to search subdirectories recursively (default: true) +/// - fileSystem: The file system to use for operations (defaults to localFileSystem) +/// - Returns: An array of RelativePath objects +/// - Throws: FileSystemError if the directory cannot be accessed or enumerated +public func getFiles( + in directory: RelativePath, + matchingExtension extension: String, + recursive: Bool = true, + fileSystem: FileSystem = localFileSystem +) throws -> [RelativePath] { + // Convert RelativePath to AbsolutePath for enumeration + guard let currentWorkingDirectory = fileSystem.currentWorkingDirectory else { + throw StringError("Cannot determine current working directory") + } + + let absoluteDirectory = currentWorkingDirectory.appending(directory) + let absoluteResults = try getFiles( + in: absoluteDirectory, + matchingExtension: `extension`, + recursive: recursive, + fileSystem: fileSystem + ) + + // Convert results back to RelativePath + return absoluteResults.map { absolutePath in + absolutePath.relative(to: currentWorkingDirectory) + } +} diff --git a/Sources/_InternalTestSupport/SwiftTesting+Tags.swift b/Sources/_InternalTestSupport/SwiftTesting+Tags.swift index d49995cd172..f8af94b5f30 100644 --- a/Sources/_InternalTestSupport/SwiftTesting+Tags.swift +++ b/Sources/_InternalTestSupport/SwiftTesting+Tags.swift @@ -33,6 +33,7 @@ extension Tag.Feature { @Tag public static var NetRc: Tag @Tag public static var Resource: Tag @Tag public static var SpecialCharacters: Tag + @Tag public static var Snippets: Tag @Tag public static var Traits: Tag } diff --git a/Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift b/Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift index a0132d824dc..b2f50811f42 100644 --- a/Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift +++ b/Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift @@ -222,6 +222,90 @@ struct InMemoryFileSystemTests { #expect(actualContents == expectedContents, "Actual is not as expected") } + @Suite( + .tags( + .TestSize.small, + ), + ) + struct ChangeCurrentWorkingDirectoryTests { + func errorOccursWhenChangingDirectoryToAFile() async throws { + // GIVEN we have a file + let fileUnderTest = AbsolutePath.root.appending(components: "Foo", "Bar", "baz.txt") + + // AND filesytstem + let fs = InMemoryFileSystem( + emptyFiles: [ + fileUnderTest.pathString + ] + ) + + // WHEN We change directory to this file + // THEN An error occurs + #expect(throws: FileSystemError(.notDirectory, fileUnderTest)) { + try fs.changeCurrentWorkingDirectory(to: fileUnderTest) + } + } + + func errorOccursWhenChangingDirectoryDoesNotExists() async throws { + + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem( + ) + let nonExistingDirectory = AbsolutePath.root.appending(components: "does-not-exists") + + // WHEN We change directory to this file + // THEN An error occurs + #expect(throws: FileSystemError(.noEntry, nonExistingDirectory)) { + try fs.changeCurrentWorkingDirectory(to: nonExistingDirectory) + } + } + + func changinDirectoryToTheParentOfAnExistingFileIsSuccessful() async throws { + // GIVEN we have a file + let fileUnderTest = AbsolutePath.root.appending(components: "Foo", "Bar", "baz.txt") + + // AND filesytstem + let fs = InMemoryFileSystem( + emptyFiles: [ + fileUnderTest.pathString + ] + ) + + // WHEN We change directory to this file + // THEN do not expect any errors + #expect(throws: Never.self) { + try fs.changeCurrentWorkingDirectory(to: fileUnderTest.parentDirectory) + } + } + } + + @Suite( + .tags( + .TestSize.small, + ), + ) + struct GetDirectoryContentsTests { + func returnsExpectedItemsWhenDirectoryHasASingleFile() async throws { + // GIVEN we have a file + let fileUnderTest = AbsolutePath.root.appending(components: "Foo", "Bar", "baz.txt") + + // AND filesytstem + let fs = InMemoryFileSystem( + emptyFiles: [ + fileUnderTest.pathString + ] + ) + + // WHEN We change directory to this file + // AND we retrieve the directory contents + try fs.changeCurrentWorkingDirectory(to: fileUnderTest.parentDirectory) + let contents = try fs.getDirectoryContents(fileUnderTest.parentDirectory) + + // THEN we expect the correct list of items + #expect(["baz.txt"] == contents) + } + } + @Test func readingADirectoryFailsWithAnError() async throws { // GIVEN we have a filesytstem diff --git a/Tests/BuildTests/BuildDescriptionTests.swift b/Tests/BuildTests/BuildDescriptionTests.swift new file mode 100644 index 00000000000..27f74d2c790 --- /dev/null +++ b/Tests/BuildTests/BuildDescriptionTests.swift @@ -0,0 +1,276 @@ +import Basics +import func Build.containsAtMain +import Testing + +struct ContainsAtMainReturnsExpectedValueTestData: CustomStringConvertible { + var description: String { + self.id + } + + let fileContent: String + let expected: Bool + let knownIssue: Bool + let id: String +} + +@Suite +struct BuildDescriptionTests { + @Test( + .tags( + .TestSize.small, + ), + arguments: [ + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + """, + expected: false, + knownIssue: false, + id: "Empty file", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: true, + knownIssue: false, + id: "Simple @main case", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: true, + knownIssue: false, + id: "@main with leading whitespace", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + // @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: false, + knownIssue: false, + id: "@main in single-line comment (should be ignored)", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + struct MyApp { + // This is @main but not at start + static func main() { + print("Hello, World!") + } + } + """, + expected: false, + knownIssue: false, + id: "@main not at beginning of line (should not match)", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + import Foundation + import SwiftUI + + @main + struct MyApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } + } + """, + expected: true, + knownIssue: false, + id: "@main with imports and other code", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + @main + struct FirstApp { + static func main() { + print("First") + } + } + + // @main (commented out) + struct SecondApp { + static func main() { + print("Second") + } + } + """, + expected: true, + knownIssue: false, + id: "Multiple @main occurrences (first one should be detected)", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + @main + struct MyApp { + static func main() { + let text = "@main is cool" + print(text) + } + } + """, + expected: true, + knownIssue: false, + id: "@main in string literal (should still match as it's at line start)", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: false, + knownIssue: false, + id: "No @main, just regular code", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + \t @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: true, + knownIssue: false, + id: "@main with tabs and spaces", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + /* + @main + */ + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: false, + knownIssue: false, + id: "@main in multi-line comment (should be ignored)", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + /* @main */ + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: false, + knownIssue: false, + id: "@main in multi-line comment on same line (should be ignored)", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + /* + Some comment + */ + @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: true, + knownIssue: false, + id: "@main after multi-line comment ends", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + // This is a comment + /* Multi-line + comment */ + @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: true, + knownIssue: false, + id: "@main with mixed comments", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + /* + This is a multi-line comment + that spans multiple lines + @main should be ignored here + */ + + @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: true, + knownIssue: false, + id: "Complex multi-line comment scenario", + ), + ContainsAtMainReturnsExpectedValueTestData( + fileContent: """ + /* + This is a multi-line comment + /* @main + struct MyApp { + static func main() { + print("Hello, World!") + } + } + """, + expected: true, + knownIssue: true, + id: "Multi-line comment end on a line containing @main", + ) + ], + ) + func containsAtMainReturnsExpectedValue( + data: ContainsAtMainReturnsExpectedValueTestData, + ) async throws { + let fileContent = data.fileContent + let expected = data.expected + let knownIssue = data.knownIssue + + let fileUnderTest = AbsolutePath.root.appending("myfile.swift") + let fs = InMemoryFileSystem() + try fs.createDirectory(fileUnderTest.parentDirectory, recursive: true) + try fs.writeFileContents(fileUnderTest, string: fileContent) + + let actual = try containsAtMain(fileSystem: fs, path: fileUnderTest) + + withKnownIssue { + #expect(actual == expected) + } when: { + knownIssue + } + + } +} diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 3f2079de23f..a885ab93631 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -1134,7 +1134,7 @@ struct PackageCommandTests { configuration: data.config, buildSystem: data.buildSystem, ) - #expect(result.stdout == "MySnippet\n") + #expect(result.stdout == "ContainsMain\nImportsProductTarget\nMySnippet\nmain\n") } } diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index 2646e3d3b16..03f4af842b8 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021-2024 Apple Inc. and the Swift project authors +// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -23,7 +23,11 @@ import Workspace import Testing import Foundation -@Suite(.serialized) +@Suite( + .tags( + .Feature.Command.Package.Plugin, + ) +) final class PluginTests { @Test( .bug("https://github.com/swiftlang/swift-package-manager/issues/8602"), @@ -1092,74 +1096,79 @@ final class PluginTests { toolchain: try UserToolchain.default ) let delegate = PluginDelegate(delegateQueue: delegateQueue) - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - // TODO: have invoke natively support task cancellation instead - try await withTaskCancellationHandler { - _ = try await plugin.invoke( - action: .performCommand(package: package, arguments: []), - buildEnvironment: BuildEnvironment(platform: .macOS, configuration: .debug), - scriptRunner: scriptRunner, - workingDirectory: package.path, - outputDirectory: pluginDir.appending("output"), - toolSearchDirectories: [try UserToolchain.default.swiftCompilerPath.parentDirectory], - accessibleTools: [:], - writableDirectories: [pluginDir.appending("output")], - readOnlyDirectories: [package.path], - allowNetworkConnections: [], - pkgConfigDirectories: [], - sdkRootPath: try UserToolchain.default.sdkRootPath, - fileSystem: localFileSystem, - modulesGraph: packageGraph, - observabilityScope: observability.topScope, - callbackQueue: delegateQueue, - delegate: delegate - ) - } onCancel: { - do { - try scriptRunner.cancel(deadline: .now() + .seconds(5)) - } catch { - Issue.record("Cancelling script runner should not fail: \(error)") - } - } - } - group.addTask { + // Use a task with timeout to test cancellation + let pluginTask = Task { + try await withTaskCancellationHandler { + _ = try await plugin.invoke( + action: .performCommand(package: package, arguments: []), + buildEnvironment: BuildEnvironment(platform: .macOS, configuration: .debug), + scriptRunner: scriptRunner, + workingDirectory: package.path, + outputDirectory: pluginDir.appending("output"), + toolSearchDirectories: [try UserToolchain.default.swiftCompilerPath.parentDirectory], + accessibleTools: [:], + writableDirectories: [pluginDir.appending("output")], + readOnlyDirectories: [package.path], + allowNetworkConnections: [], + pkgConfigDirectories: [], + sdkRootPath: try UserToolchain.default.sdkRootPath, + fileSystem: localFileSystem, + modulesGraph: packageGraph, + observabilityScope: observability.topScope, + callbackQueue: delegateQueue, + delegate: delegate + ) + } onCancel: { do { - try await Task.sleep(nanoseconds: UInt64(DispatchTimeInterval.seconds(3).nanoseconds()!)) + try scriptRunner.cancel(deadline: .now() + .seconds(5)) } catch { - Issue.record("The plugin should not finish within 3 seconds") + Issue.record("Cancelling script runner should not fail: \(error)") } } + } - try await group.next() + // Wait for the plugin to start and get its PID + try await Task.sleep(nanoseconds: UInt64(DispatchTimeInterval.seconds(3).nanoseconds()!)) + // At this point we should have parsed out the process identifier. But it's possible we don't always — this is being investigated in rdar://88792829. + var pid: Int? = .none + delegateQueue.sync { + pid = delegate.parsedProcessIdentifier + } + guard let pid = pid else { + pluginTask.cancel() + print("skipping test because no pid was received from the plugin; being investigated as rdar://88792829\n\(delegate.diagnostics.description)") + return + } - // At this point we should have parsed out the process identifier. But it's possible we don't always — this is being investigated in rdar://88792829. - var pid: Int? = .none - delegateQueue.sync { - pid = delegate.parsedProcessIdentifier - } - guard let pid = pid else { - print("skipping test because no pid was received from the plugin; being investigated as rdar://88792829\n\(delegate.diagnostics.description)") - return - } + // Check that it's running (we do this by asking for its priority — this only works on some platforms). + #if os(macOS) + errno = 0 + getpriority(Int32(PRIO_PROCESS), UInt32(pid)) + #expect(errno == 0, "unexpectedly got errno \(errno) when trying to check process \(pid)") + #endif + + // Cancel the plugin task + pluginTask.cancel() + + // Wait a bit for cancellation to propagate + try await Task.sleep(nanoseconds: UInt64(DispatchTimeInterval.milliseconds(500).nanoseconds()!)) + + // Check that it's no longer running (we do this by asking for its priority — this only works on some platforms). + #if os(macOS) + errno = 0 + getpriority(Int32(PRIO_PROCESS), UInt32(pid)) + #expect(errno == ESRCH, "unexpectedly got errno \(errno) when trying to check process \(pid)") + #endif - // Check that it's running (we do this by asking for its priority — this only works on some platforms). - #if os(macOS) - errno = 0 - getpriority(Int32(PRIO_PROCESS), UInt32(pid)) - #expect(errno == 0, "unexpectedly got errno \(errno) when trying to check process \(pid)") - #endif - - // Ask the plugin running to cancel all plugins. - group.cancelAll() - - // Check that it's no longer running (we do this by asking for its priority — this only works on some platforms). - #if os(macOS) - errno = 0 - getpriority(Int32(PRIO_PROCESS), UInt32(pid)) - #expect(errno == ESRCH, "unexpectedly got errno \(errno) when trying to check process \(pid)") - #endif + // Ensure the task was actually cancelled + do { + _ = try await pluginTask.value + Issue.record("Plugin task should have been cancelled") + } catch is CancellationError { + // Expected - task was cancelled + } catch { + // Also acceptable - plugin may have been terminated } @@ -1343,26 +1352,124 @@ final class PluginTests { } } - @Test( - .bug("https://github.com/swiftlang/swift-package-manager/issues/8774"), - .bug("https://github.com/swiftlang/swift-package-manager/issues/8602"), - .requiresSwiftConcurrencySupport, - arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), + @Suite( + .issue("https://github.com/swiftlang/swift-package-manager/issues/9040", relationship: .verifies), + .tags( + .Feature.Snippets, + ) ) - func testSnippetSupport( - buildData: BuildData, - ) async throws { - try await fixture(name: "Miscellaneous/Plugins") { path in - let (stdout, stderr) = try await executeSwiftPackage( - path.appending("PluginsAndSnippets"), - configuration: buildData.config, - extraArgs: ["do-something", "--skip-dump"], - buildSystem: buildData.buildSystem, - ) - #expect(stdout.contains("type of snippet target: snippet"), "output:\n\(stderr)\n\(stdout)") + struct SnippetTests { + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8774"), + .bug("https://github.com/swiftlang/swift-package-manager/issues/8602"), + .requiresSwiftConcurrencySupport, + arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), + ) + func testSnippetSupport( + buildData: BuildData, + ) async throws { + try await fixture(name: "Miscellaneous/Plugins/PluginsAndSnippets") { fixturePath in + let (stdout, stderr) = try await executeSwiftPackage( + fixturePath, + configuration: buildData.config, + extraArgs: ["do-something"], + buildSystem: buildData.buildSystem, + ) + #expect(stdout.contains("type of snippet target: snippet"), "stderr:\n\(stderr)") + } } - } + @Test( + .disabled(), + .requiresSwiftConcurrencySupport, + .tags( + .Feature.Command.Package.CompletionTool, + ), + arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), + ) + func testBasicBuildSnippets( + data: BuildData, + ) async throws { + try await fixture(name: "Miscellaneous/Plugins/PluginsAndSnippets") { fixturePath in + await #expect(throws: Never.self) { + let _ = try await executeSwiftBuild( + fixturePath, + configuration: data.config, + buildSystem: data.buildSystem, + ) + } + + let snippets = try await executeSwiftPackage( + fixturePath, + configuration: data.config, + extraArgs: ["completion-tool", "list-snippet"], + buildSystem: data.buildSystem, + ).stdout.split(whereSeparator: \.isNewline) + + for snippet in snippets { + try expectFileExists( + at: fixturePath.appending(components: data.buildSystem.binPath(for: data.config) + ["\(snippet)"]) + ) + } + } + } + + @Test( + .issue("https://github.com/swiftlang/swift-package-manager/issues/9040", relationship: .verifies), + .IssueWindowsCannotSaveAttachment, + .requiresSwiftConcurrencySupport, + arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), try getFiles(in: RelativePath(validating: "Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets"), matchingExtension: "swift",), + ) + func testBasicBuildIndividualSnippets( + data: BuildData, + targetPath: RelativePath, + ) async throws { + try await withKnownIssue(isIntermittent: true) { + try await fixture(name: "Miscellaneous/Plugins/PluginsAndSnippets") { fixturePath in + let targetName = targetPath.basenameWithoutExt + await #expect(throws: Never.self) { + let _ = try await executeSwiftBuild( + fixturePath, + configuration: data.config, + extraArgs: ["--product", targetName], + buildSystem: data.buildSystem, + ) + } + } + } when: { + ProcessInfo.hostOperatingSystem == .windows && data.buildSystem == .swiftbuild + } + } + + @Test( + .issue("https://github.com/swiftlang/swift-package-manager/issues/9040", relationship: .verifies), + .IssueWindowsCannotSaveAttachment, + .requiresSwiftConcurrencySupport, + .IssueSwiftBuildLinuxRunnable, + arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms), try getFiles(in: RelativePath(validating: "Fixtures/Miscellaneous/Plugins/PluginsAndSnippets/Snippets"), matchingExtension: "swift",), + ) + func testBasicRunSnippets( + data: BuildData, + targetPath: RelativePath, + ) async throws { + let targetName = targetPath.basenameWithoutExt + try await withKnownIssue(isIntermittent: true) { + try await fixture(name: "Miscellaneous/Plugins/PluginsAndSnippets") { fixturePath in + let (stdout, stderr) = try await executeSwiftRun( + fixturePath, + targetName, + configuration: data.config, + buildSystem: data.buildSystem, + ) + + #expect(stdout.contains("hello, snippets"), "stderr: \(stderr)") + } + } when: { + [.windows, .linux].contains(ProcessInfo.hostOperatingSystem) && data.buildSystem == .swiftbuild + } + } + } + @Test( .bug("https://github.com/swiftlang/swift-package-manager/issues/8774"), .requiresSwiftConcurrencySupport, diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index a00ad7eeeb2..c7c685ecab9 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -2,22 +2,28 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// - +import Foundation import Basics import PackageLoading import PackageModel import _InternalTestSupport -import XCTest +import Testing /// Tests for the handling of source layout conventions. -final class PackageBuilderTests: XCTestCase { +@Suite( + .tags( + .TestSize.medium + ) +) +struct PackageBuilderTests { + @Test func testDotFilesAreIgnored() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo/.Bar.swift", @@ -30,14 +36,15 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "foo"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { module in module.check(c99name: "foo", type: .library) module.checkSources(root: "/Sources/foo", paths: "Foo.swift") } } } + @Test func testXCPrivacyIgnored() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo/PrivacyInfo.xcprivacy", @@ -50,8 +57,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "foo"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { module in module.check(c99name: "foo", type: .library) module.checkSources(root: "/Sources/foo", paths: "Foo.swift") module.checkResources(resources: []) @@ -59,6 +66,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testMixedSources() throws { let foo: AbsolutePath = "/Sources/foo" @@ -74,11 +82,12 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "foo"), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "target at '\(foo)' contains mixed language source files; feature not supported", severity: .error) } } + @Test func testBrokenSymlink() throws { try testWithTemporaryDirectory { path in let fs = localFileSystem @@ -102,16 +111,17 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, path: path, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, path: path, in: fs) { package, diagnostics in diagnostics.check( diagnostic: "ignoring broken symlink \(linkPath)", severity: .warning ) - package.checkModule("foo") + try package.checkModule("foo") } } } + @Test func testSymlinkedSourcesDirectory() throws { try testWithTemporaryDirectory { path in let fs = localFileSystem @@ -132,12 +142,13 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, path: path, in: fs) { package, _ in - package.checkModule("bar") + try PackageBuilderTester(manifest, path: path, in: fs) { package, _ in + try package.checkModule("bar") } } } + @Test func testCInTests() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/MyPackage/main.swift", @@ -150,13 +161,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "MyPackageTests", dependencies: ["MyPackage"], type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("MyPackage") { module in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("MyPackage") { module in module.check(type: .executable) module.checkSources(root: "/Sources/MyPackage", paths: "main.swift") } - package.checkModule("MyPackageTests") { module in + try package.checkModule("MyPackageTests") { module in module.check(type: .test) module.checkSources(root: "/Tests/MyPackageTests", paths: "abc.c") } @@ -174,6 +185,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testValidSources() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/pkg/main.swift", @@ -191,8 +203,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "pkg"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("pkg") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("pkg") { module in module.check(type: .executable) module.checkSources(root: "/Sources/pkg", paths: "main.swift", "Package.swift") } @@ -200,6 +212,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testVersionSpecificManifests() throws { let fs = InMemoryFileSystem(emptyFiles: "/Package.swift", @@ -214,14 +227,15 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: name), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule(name) { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule(name) { module in module.check(c99name: name, type: .library) module.checkSources(root: "/Sources/Foo", paths: "Package.swift", "Package@swift-1.swift") } } } + @Test func testModuleMapLayout() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/clib/include/module.modulemap", @@ -235,8 +249,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "clib"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("clib") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("clib") { module in module.check(c99name: "clib", type: .library) module.checkSources(root: "/Sources/clib", paths: "clib.c") module.check(moduleMapType: .custom("/Sources/clib/include/module.modulemap")) @@ -244,6 +258,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testPublicIncludeDirMixedWithSources() throws { let Sources: AbsolutePath = "/Sources" @@ -268,12 +283,12 @@ final class PackageBuilderTests: XCTestCase { ), ] ) - PackageBuilderTester(manifest, in: fs) { package, diags in + try PackageBuilderTester(manifest, in: fs) { package, diags in diags.check( diagnostic: "found duplicate sources declaration in the package manifest: \(Sources.appending(components: "clib"))", severity: .warning ) - package.checkModule("clib") { module in + try package.checkModule("clib") { module in module.check(c99name: "clib", type: .library) module.checkSources(root: Sources.pathString, paths: RelativePath("clib").appending(components: "clib.c").pathString, RelativePath("clib").appending(components: "clib2.c").pathString, RelativePath("clib").appending(components: "nested", "nested.c").pathString) module.check(moduleMapType: .umbrellaHeader(Sources.appending(components: "clib", "clib.h"))) @@ -281,6 +296,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testDeclaredSourcesWithDot() throws { let swiftLib: RelativePath = RelativePath("swift.lib") @@ -314,22 +330,23 @@ final class PackageBuilderTests: XCTestCase { ), ] ) - PackageBuilderTester(manifest, in: fs) { result, _ in - result.checkModule("swift.lib") { module in + try PackageBuilderTester(manifest, in: fs) { result, _ in + try result.checkModule("swift.lib") { module in module.checkSources(sources: ["foo.swift"]) } - result.checkModule("swiftlib1") { module in + try result.checkModule("swiftlib1") { module in module.checkSources(sources: [swiftLib.appending(components: "foo.swift").pathString]) } - result.checkModule("swiftlib2") { module in + try result.checkModule("swiftlib2") { module in module.checkSources(sources: ["foo.swift"]) } - result.checkModule("swiftlib3") { module in + try result.checkModule("swiftlib3") { module in module.checkSources(sources: [RelativePath("foo.bar").appending(components: "bar.swift").pathString, "foo.swift"]) } } } + @Test func testOverlappingDeclaredSources() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/clib/subfolder/foo.h", @@ -349,13 +366,14 @@ final class PackageBuilderTests: XCTestCase { ), ] ) - PackageBuilderTester(manifest, in: fs) { result, _ in - result.checkModule("clib") { module in + try PackageBuilderTester(manifest, in: fs) { result, _ in + try result.checkModule("clib") { module in module.checkSources(sources: [RelativePath("clib").appending(components: "bar.c").pathString, RelativePath("clib").appending(components: "subfolder", "foo.c").pathString]) } } } + @Test func testDeclaredExecutableProducts() throws { // Check that declaring executable product doesn't collide with the // inferred products. @@ -374,9 +392,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "exec"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { _ in } - package.checkModule("exec") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { _ in } + try package.checkModule("exec") { _ in } package.checkProduct("exec") { product in product.check(type: .executable, targets: ["exec", "foo"]) } @@ -390,9 +408,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "exec"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { _ in } - package.checkModule("exec") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { _ in } + try package.checkModule("exec") { _ in } package.checkProduct("exec") { product in product.check(type: .executable, targets: ["exec"]) } @@ -410,15 +428,16 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "exec"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { _ in } - package.checkModule("exec") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { _ in } + try package.checkModule("exec") { _ in } package.checkProduct("exec1") { product in product.check(type: .executable, targets: ["exec"]) } } } + @Test func testExecutableTargets() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/exec1/exec.swift", @@ -438,9 +457,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "lib"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { _ in } - package.checkModule("exec1") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { _ in } + try package.checkModule("exec1") { _ in } package.checkProduct("exec1") { product in product.check(type: .executable, targets: ["exec1", "lib"]) } @@ -456,9 +475,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "lib"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { _ in } - package.checkModule("exec1") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { _ in } + try package.checkModule("exec1") { _ in } package.checkProduct("exec1") { product in product.check(type: .executable, targets: ["exec1"]) } @@ -476,9 +495,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "exec1", type: .executable), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { _ in } - package.checkModule("exec1") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { _ in } + try package.checkModule("exec1") { _ in } package.checkProduct("exec1") { product in product.check(type: .executable, targets: ["exec1"]) } @@ -496,9 +515,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "exec1", type: .executable), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { _ in } - package.checkModule("exec1") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { _ in } + try package.checkModule("exec1") { _ in } package.checkProduct("exec1") { product in product.check(type: .executable, targets: ["exec1"]) } @@ -516,19 +535,20 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "exec2"), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check( diagnostic: "'exec2' was identified as an executable target given the presence of a 'main' file. Starting with tools version 5.4.0 executable targets should be declared as 'executableTarget()'", severity: .warning ) - package.checkModule("lib") { _ in } - package.checkModule("exec2") { _ in } + try package.checkModule("lib") { _ in } + try package.checkModule("exec2") { _ in } package.checkProduct("exec2") { product in product.check(type: .executable, targets: ["exec2"]) } } } + @Test func testTestEntryPointFound() throws { try SwiftModule.testEntryPointNames.forEach { name in let fs = InMemoryFileSystem(emptyFiles: @@ -544,13 +564,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "tests", path: "swift/tests", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("exe") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("exe") { module in module.check(c99name: "exe", type: .library) module.checkSources(root: "/swift/exe", paths: "foo.swift") } - package.checkModule("tests") { module in + try package.checkModule("tests") { module in module.check(c99name: "tests", type: .test) module.checkSources(root: "/swift/tests", paths: "footests.swift") } @@ -563,9 +583,13 @@ final class PackageBuilderTests: XCTestCase { } } + @Test( + .IssueWindowsLongPath, + .IssueWindowsPathLastConponent, + .IssueWindowsRelativePathAssert + ) func testTestManifestSearch() throws { - try XCTSkipOnWindows(because: "possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") - + try withKnownIssue(isIntermittent: true) { let fs = InMemoryFileSystem(emptyFiles: "/pkg/foo.swift", "/pkg/footests.swift" @@ -587,17 +611,21 @@ final class PackageBuilderTests: XCTestCase { ), ] ) - PackageBuilderTester(manifest, path: "/pkg", in: fs) { package, _ in - package.checkModule("exe") { _ in } - package.checkModule("tests") { _ in } + try PackageBuilderTester(manifest, path: "/pkg", in: fs) { package, _ in + try package.checkModule("exe") { _ in } + try package.checkModule("tests") { _ in } package.checkProduct("pkgPackageTests") { product in product.check(type: .test, targets: ["tests"]) product.check(testEntryPointPath: nil) } } + } when: { + ProcessInfo.hostOperatingSystem == .windows + } } + @Test func testEmptyProductNameError() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/best/best.swift") @@ -611,11 +639,12 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check(diagnostic: "product names can not be empty", severity: .error) } } + @Test func testMultipleTestEntryPointsError() throws { let name = SwiftModule.defaultTestEntryPointName let swift: AbsolutePath = "/swift" @@ -636,11 +665,12 @@ final class PackageBuilderTests: XCTestCase { ), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check(diagnostic: "package '\(package.packageIdentity)' has multiple test entry point files: \(try! AbsolutePath(validating: "/\(name)")), \(swift.appending(components: name))", severity: .error) } } + @Test func testCustomTargetPaths() throws { let Sources: AbsolutePath = "/Sources" let swift: RelativePath = "swift" @@ -679,26 +709,26 @@ final class PackageBuilderTests: XCTestCase { sources: ["bar"]), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in + try PackageBuilderTester(manifest, in: fs) { package, _ in package.checkPredefinedPaths(target: Sources, testTarget: "/Tests") - package.checkModule("exe") { module in + try package.checkModule("exe") { module in module.check(c99name: "exe", type: .executable) module.checkSources(root: "/mah/target/exe", paths: swift.appending(components: "exe", "main.swift").pathString, swift.appending(components: "exe", "foo.swift").pathString, swift.appending(components: "bar.swift").pathString) } - package.checkModule("clib") { module in + try package.checkModule("clib") { module in module.check(c99name: "clib", type: .library) module.checkSources(root: "/mah/target/exe", paths: "foo.c") } - package.checkModule("foo") { module in + try package.checkModule("foo") { module in module.check(c99name: "foo", type: .library) module.checkSources(root: "/Sources/foo", paths: "foo.swift") } - package.checkModule("bar") { module in + try package.checkModule("bar") { module in module.check(c99name: "bar", type: .library) module.checkSources(root: bar.pathString, paths: RelativePath("bar").appending(components: "foo.swift").pathString) } @@ -707,6 +737,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testCustomTargetPathsOverlap() throws { let bar: AbsolutePath = "/target/bar" @@ -727,7 +758,7 @@ final class PackageBuilderTests: XCTestCase { type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check(diagnostic: "target 'barTests' has overlapping sources: \(bar.appending(components: "Tests", "barTests.swift"))", severity: .error) } @@ -744,15 +775,15 @@ final class PackageBuilderTests: XCTestCase { type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in + try PackageBuilderTester(manifest, in: fs) { package, _ in package.checkPredefinedPaths(target: "/Sources", testTarget: "/Tests") - package.checkModule("bar") { module in + try package.checkModule("bar") { module in module.check(c99name: "bar", type: .library) module.checkSources(root: "/target/bar", paths: "bar.swift") } - package.checkModule("barTests") { module in + try package.checkModule("barTests") { module in module.check(c99name: "barTests", type: .test) module.checkSources(root: bar.appending(components: "Tests").pathString, paths: "barTests.swift") } @@ -761,6 +792,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testPublicHeadersPath() throws { let Sources: AbsolutePath = "/Sources" let Tests: AbsolutePath = "/Tests" @@ -786,19 +818,19 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in + try PackageBuilderTester(manifest, in: fs) { package, _ in package.checkPredefinedPaths(target: Sources, testTarget: Tests) - package.checkModule("Foo") { module in - let clangTarget = module.target as? ClangModule - XCTAssertEqual(clangTarget?.headers.map{ $0.pathString }, [Sources.appending(components: "Foo", "Foo_private.h").pathString, Sources.appending(components: "Foo", "inc", "Foo.h").pathString]) + try package.checkModule("Foo") { module in + let clangTarget = try #require(module.target as? ClangModule) + #expect(clangTarget.headers.map{ $0.pathString } == [Sources.appending(components: "Foo", "Foo_private.h").pathString, Sources.appending(components: "Foo", "inc", "Foo.h").pathString]) module.check(c99name: "Foo", type: .library) module.checkSources(root: Sources.appending(components: "Foo").pathString, paths: "Foo.c") module.check(includeDir: Sources.appending(components: "Foo", "inc").pathString) module.check(moduleMapType: .custom(Sources.appending(components: "Foo", "inc", "module.modulemap"))) } - package.checkModule("Bar") { module in + try package.checkModule("Bar") { module in module.check(c99name: "Bar", type: .library) module.checkSources(root: Sources.appending(components: "Bar").pathString, paths: "Bar.c") module.check(includeDir: Sources.appending(components: "Bar", "include").pathString) @@ -807,6 +839,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testInvalidPublicHeadersPath() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/Foo/inc/module.modulemap", @@ -828,11 +861,12 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "invalid relative path \'/inc\'; relative path should not begin with \'\(AbsolutePath.root)\'", severity: .error) } } + @Test func testTestsLayoutsv4() throws { let Sources: AbsolutePath = "/Sources" @@ -852,27 +886,27 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "B", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in + try PackageBuilderTester(manifest, in: fs) { package, _ in package.checkPredefinedPaths(target: Sources, testTarget: "/Tests") - package.checkModule("A") { module in + try package.checkModule("A") { module in module.check(c99name: "A", type: .executable) module.checkSources(root: "/Sources/A", paths: "main.swift") } - package.checkModule("TheTestOfA") { module in + try package.checkModule("TheTestOfA") { module in module.check(c99name: "TheTestOfA", type: .test) module.checkSources(root: "/Tests/TheTestOfA", paths: "Foo.swift") module.check(targetDependencies: ["A"]) } - package.checkModule("B") { module in + try package.checkModule("B") { module in module.check(c99name: "B", type: .test) module.checkSources(root: "/Tests/B", paths: "Foo.swift") module.check(targetDependencies: []) } - package.checkModule("ATests") { module in + try package.checkModule("ATests") { module in module.check(c99name: "ATests", type: .test) module.checkSources(root: "/Tests/ATests", paths: "Foo.swift") module.check(targetDependencies: []) @@ -883,6 +917,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testMultipleTestProducts() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo/foo.swift", @@ -899,10 +934,10 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, shouldCreateMultipleTestProducts: true, in: fs) { package, _ in - package.checkModule("foo") { _ in } - package.checkModule("fooTests") { _ in } - package.checkModule("barTests") { _ in } + try PackageBuilderTester(manifest, shouldCreateMultipleTestProducts: true, in: fs) { package, _ in + try package.checkModule("foo") { _ in } + try package.checkModule("fooTests") { _ in } + try package.checkModule("barTests") { _ in } package.checkProduct("fooTests") { product in product.check(type: .test, targets: ["fooTests"]) } @@ -911,16 +946,17 @@ final class PackageBuilderTests: XCTestCase { } } - PackageBuilderTester(manifest, shouldCreateMultipleTestProducts: false, in: fs) { package, _ in - package.checkModule("foo") { _ in } - package.checkModule("fooTests") { _ in } - package.checkModule("barTests") { _ in } + try PackageBuilderTester(manifest, shouldCreateMultipleTestProducts: false, in: fs) { package, _ in + try package.checkModule("foo") { _ in } + try package.checkModule("fooTests") { _ in } + try package.checkModule("barTests") { _ in } package.checkProduct("pkgPackageTests") { product in product.check(type: .test, targets: ["barTests", "fooTests"]) } } } + @Test func testCustomTargetDependencies() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/Foo/Foo.swift", @@ -936,15 +972,15 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Baz"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("Foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("Foo") { module in module.check(c99name: "Foo", type: .library) module.checkSources(root: "/Sources/Foo", paths: "Foo.swift") module.check(targetDependencies: ["Bar"]) } for target in ["Bar", "Baz"] { - package.checkModule(target) { module in + try package.checkModule(target) { module in module.check(c99name: target, type: .library) module.checkSources(root: "/Sources/\(target)", paths: "\(target).swift") } @@ -960,26 +996,27 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Baz"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("Foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("Foo") { module in module.check(c99name: "Foo", type: .library) module.checkSources(root: "/Sources/Foo", paths: "Foo.swift") module.check(targetDependencies: ["Bar"]) } - package.checkModule("Bar") { module in + try package.checkModule("Bar") { module in module.check(c99name: "Bar", type: .library) module.checkSources(root: "/Sources/Bar", paths: "Bar.swift") module.check(targetDependencies: ["Baz"]) } - package.checkModule("Baz") { module in + try package.checkModule("Baz") { module in module.check(c99name: "Baz", type: .library) module.checkSources(root: "/Sources/Baz", paths: "Baz.swift") } } } + @Test func testTargetDependencies() throws { let Sources: AbsolutePath = "/Sources" @@ -999,11 +1036,11 @@ final class PackageBuilderTests: XCTestCase { dependencies: ["Bar", "Baz", "Bam"]), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in + try PackageBuilderTester(manifest, in: fs) { package, _ in package.checkPredefinedPaths(target: Sources, testTarget: "/Tests") - package.checkModule("Foo") { module in + try package.checkModule("Foo") { module in module.check(c99name: "Foo", type: .library) module.checkSources(root: Sources.appending(components: "Foo").pathString, paths: "Foo.swift") module.check(targetDependencies: ["Bar", "Baz"]) @@ -1011,7 +1048,7 @@ final class PackageBuilderTests: XCTestCase { } for target in ["Bar", "Baz"] { - package.checkModule(target) { module in + try package.checkModule(target) { module in module.check(c99name: target, type: .library) module.checkSources(root: "/Sources/\(target)", paths: "\(target).swift") } @@ -1021,6 +1058,7 @@ final class PackageBuilderTests: XCTestCase { /// Starting with tools version 5.9, packages are permitted to place /// sources anywhere in ./Sources when a package has a single target. + @Test func testRelaxedSourceLocationSingleTargetRegular() throws { let predefinedSourceDir = PackageBuilder.suggestedPredefinedSourceDirectory(type: .regular) do { @@ -1036,9 +1074,9 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") { result in - XCTAssertEqual("/\(predefinedSourceDir)", result.target.path) + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") { result in + #expect("/\(predefinedSourceDir)" == result.target.path) } } } @@ -1057,7 +1095,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random"), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target Random should be located under '\(predefinedSourceDir)/Random', '\(predefinedSourceDir)', or a custom sources path can be set with the 'path' property in Package.swift", severity: .warning) } } @@ -1075,8 +1113,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random"), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") } } do { @@ -1094,8 +1132,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random"), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") { result in result.checkSources(paths: "Random.swift") } } @@ -1114,12 +1152,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "TargetB"), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target TargetA should be located under '\(predefinedSourceDir)/TargetA', or a custom sources path can be set with the 'path' property in Package.swift", severity: .error) } } } + @Test func testRelaxedSourceLocationSingleTargetTest() throws { let predefinedSourceDir = PackageBuilder.suggestedPredefinedSourceDirectory(type: .test) do { @@ -1135,9 +1174,9 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("MyTests") { result in - XCTAssertEqual("/\(predefinedSourceDir)", result.target.path) + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("MyTests") { result in + #expect("/\(predefinedSourceDir)" == result.target.path) } package.checkProduct("pkgPackageTests") } @@ -1157,7 +1196,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "RandomTests", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target RandomTests should be located under '\(predefinedSourceDir)/RandomTests', '\(predefinedSourceDir)', or a custom sources path can be set with the 'path' property in Package.swift", severity: .warning) } } @@ -1175,8 +1214,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "RandomTests", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("RandomTests") + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("RandomTests") package.checkProduct("pkgPackageTests") } } @@ -1195,8 +1234,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "RandomTests", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("RandomTests") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("RandomTests") { result in result.checkSources(paths: "Random.swift") } package.checkProduct("pkgPackageTests") @@ -1216,12 +1255,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "TargetB", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target TargetA should be located under '\(predefinedSourceDir)/TargetA', or a custom sources path can be set with the 'path' property in Package.swift", severity: .error) } } } + @Test func testRelaxedSourceLocationSingleTargetPlugin() throws { let predefinedSourceDir = PackageBuilder.suggestedPredefinedSourceDirectory(type: .plugin) do { @@ -1237,8 +1277,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("MyPlugin") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("MyPlugin") { result in result.checkSources(root: result.target.path.appending(component: predefinedSourceDir).pathString, paths: "Foo.swift") } } @@ -1258,7 +1298,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .plugin, pluginCapability: .buildTool), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target Random should be located under '\(predefinedSourceDir)/Random', '\(predefinedSourceDir)', or a custom sources path can be set with the 'path' property in Package.swift", severity: .warning) } } @@ -1277,8 +1317,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .plugin, pluginCapability: .buildTool), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") } } do { @@ -1296,8 +1336,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .plugin, pluginCapability: .buildTool), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") { result in result.checkSources(paths: "Random.swift") } } @@ -1316,12 +1356,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "TargetB", type: .plugin, pluginCapability: .buildTool), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target TargetA should be located under '\(predefinedSourceDir)/TargetA', or a custom sources path can be set with the 'path' property in Package.swift", severity: .error) } } } + @Test func testRelaxedSourceLocationSingleTargetExecutable() throws { let predefinedSourceDir = PackageBuilder.suggestedPredefinedSourceDirectory(type: .executable) do { @@ -1337,9 +1378,9 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("MyExe") { result in - XCTAssertEqual("/\(predefinedSourceDir)", result.target.path) + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("MyExe") { result in + #expect("/\(predefinedSourceDir)" == result.target.path) } package.checkProduct("MyExe") } @@ -1359,7 +1400,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .executable), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target Random should be located under '\(predefinedSourceDir)/Random', '\(predefinedSourceDir)', or a custom sources path can be set with the 'path' property in Package.swift", severity: .warning) } } @@ -1377,8 +1418,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .executable) ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") package.checkProduct("Random") } } @@ -1397,8 +1438,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .executable) ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") { result in result.checkSources(paths: "Random.swift") } package.checkProduct("Random") @@ -1418,12 +1459,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "TargetB", type: .executable) ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target TargetA should be located under '\(predefinedSourceDir)/TargetA', or a custom sources path can be set with the 'path' property in Package.swift", severity: .error) } } } + @Test func testRelaxedSourceLocationSingleTargetSystem() throws { let predefinedSourceDir = PackageBuilder.suggestedPredefinedSourceDirectory(type: .system) do { @@ -1439,9 +1481,9 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Foo") { result in - XCTAssertEqual("/\(predefinedSourceDir)", result.target.path) + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Foo") { result in + #expect("/\(predefinedSourceDir)" == result.target.path) } } } @@ -1461,7 +1503,7 @@ final class PackageBuilderTests: XCTestCase { ] ) let map = "/\(predefinedSourceDir)/module.modulemap" - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in #if _runtime(_ObjC) diagnostics.check(diagnostic: "package has unsupported layout; missing system target module map at '\(map)'", severity: .error) #else @@ -1484,8 +1526,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .system) ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") } } do { @@ -1503,8 +1545,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .system) ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") { result in result.checkSources() } } @@ -1523,12 +1565,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "TargetB", type: .system) ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target TargetA should be located under '\(predefinedSourceDir)/TargetA', or a custom sources path can be set with the 'path' property in Package.swift", severity: .error) } } } + @Test func testRelaxedSourceLocationSingleTargetMacro() throws { let predefinedSourceDir = PackageBuilder.suggestedPredefinedSourceDirectory(type: .macro) do { @@ -1545,9 +1588,9 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Foo") { result in - XCTAssertEqual("/\(predefinedSourceDir)", result.target.path) + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Foo") { result in + #expect("/\(predefinedSourceDir)" == result.target.path) } package.checkProduct("Foo") } @@ -1567,7 +1610,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .macro), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target Random should be located under '\(predefinedSourceDir)/Random', '\(predefinedSourceDir)', or a custom sources path can be set with the 'path' property in Package.swift", severity: .warning) } } @@ -1585,8 +1628,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .macro) ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") { result in result.checkSources(root: "/\(predefinedSourceDir)", paths: "Random.swift") } package.checkProduct("Random") @@ -1607,8 +1650,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random", type: .macro) ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("Random") { result in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("Random") { result in result.checkSources(root: "/\(predefinedSourceDir)/Random", paths: "Random.swift") } package.checkProduct("Random") @@ -1628,12 +1671,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "TargetB", type: .macro) ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "Source files for target TargetA should be located under '\(predefinedSourceDir)/TargetA', or a custom sources path can be set with the 'path' property in Package.swift", severity: .error) } } } + @Test func testStrictSourceLocationPre5_9() throws { do { for fs in [ @@ -1651,13 +1695,14 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Random"), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: .contains("Source files for target Random should be located under 'Sources/Random'"), severity: .error) } } } } + @Test func testManifestTargetDeclErrors() throws { do { let fs = InMemoryFileSystem(emptyFiles: @@ -1669,7 +1714,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "pkg", dependencies: [.target(name: "Foo")]), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: .contains("Source files for target Foo should be located under 'Sources/Foo'"), severity: .error) } } @@ -1684,7 +1729,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "pkgTests", dependencies: [], type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: .contains("Source files for target pkgTests should be located under 'Tests/pkgTests'"), severity: .error) } } @@ -1700,7 +1745,7 @@ final class PackageBuilderTests: XCTestCase { ], traits: [] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "cyclic dependency declaration found: pkg -> pkg", severity: .error) } } @@ -1722,9 +1767,9 @@ final class PackageBuilderTests: XCTestCase { "foo": BinaryArtifact(kind: .xcframework, originURL: "https://foo.com/foo.zip", path: "/foo.xcframework"), "foo2": BinaryArtifact(kind: .xcframework, originURL: nil, path: "/foo2.xcframework") ] - PackageBuilderTester(manifest, binaryArtifacts: binaryArtifacts, in: fs) { package, _ in - package.checkModule("foo") - package.checkModule("foo2") + try PackageBuilderTester(manifest, binaryArtifacts: binaryArtifacts, in: fs) { package, _ in + try package.checkModule("foo") + try package.checkModule("foo2") } } @@ -1743,7 +1788,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "pkg3", dependencies: ["pkg1"]), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "cyclic dependency declaration found: pkg1 -> pkg2 -> pkg3 -> pkg1", severity: .error) } @@ -1755,7 +1800,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "pkg3", dependencies: ["pkg2"]), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "cyclic dependency declaration found: pkg1 -> pkg2 -> pkg3 -> pkg2", severity: .error) } } @@ -1776,12 +1821,12 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "pkg2"), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check( diagnostic: .contains("Source files for target pkg2 should be located under 'Sources/pkg2'"), severity: .warning ) - package.checkModule("pkg1") { module in + try package.checkModule("pkg1") { module in module.check(c99name: "pkg1", type: .library) module.checkSources(root: "/Sources/pkg1", paths: "Foo.swift") } @@ -1800,7 +1845,7 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "public headers (\"include\") directory path for 'Foo' is invalid or not contained in the target", severity: .error) } @@ -1810,7 +1855,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Bar", publicHeadersPath: "inc/../../../foo"), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "public headers (\"include\") directory path for 'Bar' is invalid or not contained in the target", severity: .error) } } @@ -1826,7 +1871,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Foo", path: "../foo"), ] ) - PackageBuilderTester(manifest, path: "/pkg", in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, path: "/pkg", in: fs) { package, diagnostics in diagnostics.check(diagnostic: "target 'Foo' in package '\(package.packageIdentity)' is outside the package root", severity: .error) } } @@ -1841,7 +1886,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Foo", path: "/foo"), ] ) - PackageBuilderTester(manifest, path: "/pkg", in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, path: "/pkg", in: fs) { _, diagnostics in diagnostics.check(diagnostic: "target path \'/foo\' is not supported; it should be relative to package root", severity: .error) } } @@ -1858,12 +1903,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Foo", path: "~/foo"), ] ) - PackageBuilderTester(manifest, path: "/pkg", in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, path: "/pkg", in: fs) { _, diagnostics in diagnostics.check(diagnostic: "target path \'~/foo\' is not supported; it should be relative to package root", severity: .error) } }*/ } + @Test func testExecutableAsADep() throws { // Executable as dependency. let fs = InMemoryFileSystem(emptyFiles: @@ -1877,13 +1923,13 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "exec"), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("exec") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("exec") { module in module.check(c99name: "exec", type: .executable) module.checkSources(root: "/Sources/exec", paths: "main.swift") } - package.checkModule("lib") { module in + try package.checkModule("lib") { module in module.check(c99name: "lib", type: .library) module.checkSources(root: "/Sources/lib", paths: "lib.swift") } @@ -1892,7 +1938,8 @@ final class PackageBuilderTests: XCTestCase { } } - func testInvalidManifestConfigForNonSystemModules() { + @Test + func testInvalidManifestConfigForNonSystemModules() throws { var fs = InMemoryFileSystem(emptyFiles: "/Sources/main.swift" ) @@ -1902,7 +1949,7 @@ final class PackageBuilderTests: XCTestCase { pkgConfig: "foo" ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check( diagnostic: "configuration of package '\(package.packageIdentity)' is invalid; the 'pkgConfig' property can only be used with a System Module Package", severity: .error) @@ -1916,26 +1963,28 @@ final class PackageBuilderTests: XCTestCase { providers: [.brew(["foo"])] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check( diagnostic: "configuration of package '\(package.packageIdentity)' is invalid; the 'providers' property can only be used with a System Module Package", severity: .error) } } + @Test func testResolvesSystemModulePackage() throws { let fs = InMemoryFileSystem(emptyFiles: "/module.modulemap") let manifest = Manifest.createRootManifest(displayName: "SystemModulePackage") - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("SystemModulePackage") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("SystemModulePackage") { module in module.check(c99name: "SystemModulePackage", type: .systemModule) module.checkSources(root: "/") } } } + @Test func testCompatibleSwiftVersions() throws { // Single swift executable target. let fs = InMemoryFileSystem(emptyFiles: @@ -1954,55 +2003,55 @@ final class PackageBuilderTests: XCTestCase { var manifest = try createManifest(swiftVersions: [.v3, .v4]) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { module in module.check(swiftVersion: "4") } package.checkProduct("foo") { _ in } } manifest = try createManifest(swiftVersions: [.v3]) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { module in module.check(swiftVersion: "3") } package.checkProduct("foo") { _ in } } manifest = try createManifest(swiftVersions: [.v4]) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { module in module.check(swiftVersion: "4") } package.checkProduct("foo") { _ in } } manifest = try createManifest(swiftVersions: nil) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { module in module.check(swiftVersion: "4") } package.checkProduct("foo") { _ in } } manifest = try createManifest(swiftVersions: [SwiftLanguageVersion(string: "5")!]) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("foo") { module in module.check(swiftVersion: "5") } package.checkProduct("foo") { _ in } } manifest = try createManifest(swiftVersions: [SwiftLanguageVersion(string: "6")!]) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("foo") { module in module.check(swiftVersion: "6") } package.checkProduct("foo") { _ in } } manifest = try createManifest(swiftVersions: []) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check( diagnostic: "package '\(package.packageIdentity)' supported Swift language versions is empty", severity: .error @@ -2010,7 +2059,7 @@ final class PackageBuilderTests: XCTestCase { } manifest = try createManifest(swiftVersions: [SwiftLanguageVersion(string: "7")!]) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in diagnostics.check( diagnostic: "package '\(package.packageIdentity)' requires minimum Swift language version 7 which is not supported by the current tools version (\(ToolsVersion.current))", severity: .error @@ -2018,6 +2067,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testPredefinedTargetSearchError() throws { do { @@ -2034,7 +2084,7 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: .contains("Source files for target Bar should be located under 'Sources/Bar'"), severity: .error) } } @@ -2053,7 +2103,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "FooTests", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: .contains("Source files for target BarTests should be located under 'Tests/BarTests'"), severity: .error) } @@ -2065,11 +2115,11 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "FooTests", type: .test), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("BarTests") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("BarTests") { module in module.check(c99name: "BarTests", type: .test) } - package.checkModule("FooTests") { module in + try package.checkModule("FooTests") { module in module.check(c99name: "FooTests", type: .test) } package.checkProduct("pkgPackageTests") { _ in } @@ -2077,6 +2127,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testSpecifiedCustomPathDoesNotExist() throws { let fs = InMemoryFileSystem(emptyFiles: "/Foo.swift") @@ -2087,11 +2138,12 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check(diagnostic: "invalid custom path './NotExist' for target 'Foo'", severity: .error) } } + @Test func testSpecialTargetDir() throws { let src: AbsolutePath = "/src" // Special directory should be src because both target and test target are under it. @@ -2108,13 +2160,13 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in + try PackageBuilderTester(manifest, in: fs) { package, _ in package.checkPredefinedPaths(target: src, testTarget: src) - package.checkModule("A") { module in + try package.checkModule("A") { module in module.check(c99name: "A", type: .library) } - package.checkModule("ATests") { module in + try package.checkModule("ATests") { module in module.check(c99name: "ATests", type: .test) } @@ -2122,6 +2174,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testExcludes() throws { // The exclude should win if a file is in exclude as well as sources. let fs = InMemoryFileSystem(emptyFiles: @@ -2139,14 +2192,15 @@ final class PackageBuilderTests: XCTestCase { ), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("bar") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("bar") { module in module.check(c99name: "bar", type: .library) module.checkSources(root: "/Sources/bar", paths: "bar.swift") } } } + @Test func testDuplicateProducts() throws { // Check that declaring executable product doesn't collide with the // inferred products. @@ -2166,8 +2220,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "foo"), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("foo") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("foo") { _ in } package.checkProduct("foo") { product in product.check(type: .library(.automatic), targets: ["foo"]) } @@ -2185,6 +2239,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testSystemPackageDeclaresTargetsDiagnostic() throws { let fs = InMemoryFileSystem(emptyFiles: "/module.modulemap", @@ -2199,8 +2254,8 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "bar"), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("SystemModulePackage") { module in + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("SystemModulePackage") { module in module.check(c99name: "SystemModulePackage", type: .systemModule) module.checkSources(root: "/") } @@ -2211,6 +2266,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testSystemLibraryTarget() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo/module.modulemap", @@ -2227,12 +2283,12 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "bar", dependencies: ["foo"]), ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { module in module.check(c99name: "foo", type: .systemModule) module.checkSources(root: "/Sources/foo") } - package.checkModule("bar") { module in + try package.checkModule("bar") { module in module.check(c99name: "bar", type: .library) module.checkSources(root: "/Sources/bar", paths: "bar.swift") module.check(targetDependencies: ["foo"]) @@ -2243,6 +2299,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testSystemLibraryTargetDiagnostics() throws { let Sources: AbsolutePath = "/Sources" @@ -2261,9 +2318,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "bar", dependencies: ["foo"]), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("foo") { _ in } - package.checkModule("bar") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("foo") { _ in } + try package.checkModule("bar") { _ in } diagnostics.check( diagnostic: "system library product foo shouldn't have a type and contain only one target", severity: .error @@ -2280,9 +2337,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "bar", dependencies: ["foo"]), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("foo") { _ in } - package.checkModule("bar") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("foo") { _ in } + try package.checkModule("bar") { _ in } diagnostics.check( diagnostic: "system library product foo shouldn't have a type and contain only one target", severity: .error @@ -2298,7 +2355,7 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "bar", type: .system) ] ) - PackageBuilderTester(manifest, in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, in: fs) { _, diagnostics in diagnostics.check( diagnostic: "package has unsupported layout; missing system target module map at '\(Sources.appending(components: "bar", "module.modulemap"))'", severity: .error @@ -2306,6 +2363,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testBadExecutableProductDecl() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo1/main.swift", @@ -2331,12 +2389,12 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Plugin1", type: .plugin, pluginCapability: .buildTool), ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("foo1") { _ in } - package.checkModule("foo2") { _ in } - package.checkModule("FooLib1") { _ in } - package.checkModule("FooLib2") { _ in } - package.checkModule("Plugin1") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("foo1") { _ in } + try package.checkModule("foo2") { _ in } + try package.checkModule("FooLib1") { _ in } + try package.checkModule("FooLib2") { _ in } + try package.checkModule("Plugin1") { _ in } diagnostics.check( diagnostic: """ executable product 'foo1' expects target 'FooLib1' to be executable; an executable target requires \ @@ -2362,6 +2420,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testLibraryProductDiagnostics() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/MyLibrary/library.swift", @@ -2378,9 +2437,9 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "MyPlugin", type: .plugin, pluginCapability: .buildTool) ] ) - PackageBuilderTester(manifest, in: fs) { package, diagnostics in - package.checkModule("MyLibrary") { _ in } - package.checkModule("MyPlugin") { _ in } + try PackageBuilderTester(manifest, in: fs) { package, diagnostics in + try package.checkModule("MyLibrary") { _ in } + try package.checkModule("MyPlugin") { _ in } diagnostics.check( diagnostic: """ library product 'MyLibrary' should not contain plugin targets (it has 'MyPlugin') @@ -2391,6 +2450,7 @@ final class PackageBuilderTests: XCTestCase { } + @Test func testBadREPLPackage() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/exe/main.swift" @@ -2403,8 +2463,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, createREPLProduct: true, in: fs) { package, diagnostics in - package.checkModule("exe") { _ in } + try PackageBuilderTester(manifest, createREPLProduct: true, in: fs) { package, diagnostics in + try package.checkModule("exe") { _ in } package.checkProduct("exe") { _ in } diagnostics.check( diagnostic: "unable to synthesize a REPL product as there are no library targets in the package", @@ -2413,6 +2473,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testAsmIsIgnoredInV4_2Manifest() throws { // .s is not considered a valid source in 4.2 manifest. let fs = InMemoryFileSystem(emptyFiles: @@ -2430,13 +2491,14 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { module in module.checkSources(root: "/Sources/lib", paths: "lib.c") } } } + @Test func testAsmInV5Manifest() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/lib/lib.s", @@ -2445,7 +2507,6 @@ final class PackageBuilderTests: XCTestCase { "/Sources/lib/include/lib.h" ) - //let observability = ObservabilitySystem.makeForTesting() let manifest = Manifest.createRootManifest( displayName: "Pkg", toolsVersion: .v5, @@ -2453,15 +2514,15 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "lib", dependencies: []), ] ) - //XCTAssertNoDiagnostics(observability.diagnostics) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { module in module.checkSources(root: "/Sources/lib", paths: "lib.c", "lib.s", "lib2.S") } } } + @Test func testUnknownSourceFilesUnderDeclaredSourcesIgnoredInV5_2Manifest() throws { let lib: AbsolutePath = "/Sources/lib" @@ -2480,8 +2541,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { module in module.checkSources(root: lib.pathString, paths: "lib.c") module.check(includeDir: lib.appending(components: "include").pathString) module.check(moduleMapType: .umbrellaHeader(lib.appending(components: "include", "lib.h"))) @@ -2489,6 +2550,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testUnknownSourceFilesUnderDeclaredSourcesCompiledInV5_3Manifest() throws { let lib: AbsolutePath = "/Sources/lib" @@ -2507,8 +2569,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("lib") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("lib") { module in module.checkSources(root: lib.pathString, paths: "movie.mkv", "lib.c") module.check(includeDir: lib.appending(components: "include").pathString) module.check(moduleMapType: .umbrellaHeader(lib.appending(components: "include", "lib.h"))) @@ -2516,6 +2578,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testBuildSettings() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/exe/main.swift", @@ -2564,63 +2627,63 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("cbar") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("cbar") { package in let scope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual(scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS), ["CCC=2", "CXX"]) - XCTAssertEqual(scope.evaluate(.HEADER_SEARCH_PATHS), ["Sources/headers", "Sources/cppheaders"]) - XCTAssertEqual(scope.evaluate(.OTHER_CFLAGS), ["-Icfoo", "-L", "cbar"]) - XCTAssertEqual(scope.evaluate(.OTHER_CPLUSPLUSFLAGS), ["-Icxxfoo", "-L", "cxxbar"]) + #expect(scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS) == ["CCC=2", "CXX"]) + #expect(scope.evaluate(.HEADER_SEARCH_PATHS) == ["Sources/headers", "Sources/cppheaders"]) + #expect(scope.evaluate(.OTHER_CFLAGS) == ["-Icfoo", "-L", "cbar"]) + #expect(scope.evaluate(.OTHER_CPLUSPLUSFLAGS) == ["-Icxxfoo", "-L", "cxxbar"]) let releaseScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual(releaseScope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS), ["CCC=2", "CXX", "RCXX"]) + #expect(releaseScope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS) == ["CCC=2", "CXX", "RCXX"]) } - package.checkModule("bar") { package in + try package.checkModule("bar") { package in let scope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .linux, configuration: .debug) ) - XCTAssertEqual(scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS), ["SOMETHING", "LINUX"]) - XCTAssertEqual(scope.evaluate(.OTHER_SWIFT_FLAGS), ["-Isfoo", "-L", "sbar"]) + #expect(scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS) == ["SOMETHING", "LINUX"]) + #expect(scope.evaluate(.OTHER_SWIFT_FLAGS) == ["-Isfoo", "-L", "sbar"]) let rscope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .linux, configuration: .release) ) - XCTAssertEqual(rscope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS), ["SOMETHING", "LINUX", "RLINUX"]) + #expect(rscope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS) == ["SOMETHING", "LINUX", "RLINUX"]) let mscope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual(mscope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS), ["SOMETHING", "DMACOS"]) + #expect(mscope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS) == ["SOMETHING", "DMACOS"]) } - package.checkModule("exe") { package in + try package.checkModule("exe") { package in let scope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .linux, configuration: .debug) ) - XCTAssertEqual(scope.evaluate(.LINK_LIBRARIES), ["sqlite3"]) - XCTAssertEqual(scope.evaluate(.OTHER_LDFLAGS), ["-Ilfoo", "-L", "lbar"]) - XCTAssertEqual(scope.evaluate(.LINK_FRAMEWORKS), []) - XCTAssertEqual(scope.evaluate(.OTHER_SWIFT_FLAGS), []) - XCTAssertEqual(scope.evaluate(.OTHER_CFLAGS), []) - XCTAssertEqual(scope.evaluate(.OTHER_CPLUSPLUSFLAGS), []) + #expect(scope.evaluate(.LINK_LIBRARIES) == ["sqlite3"]) + #expect(scope.evaluate(.OTHER_LDFLAGS) == ["-Ilfoo", "-L", "lbar"]) + #expect(scope.evaluate(.LINK_FRAMEWORKS) == []) + #expect(scope.evaluate(.OTHER_SWIFT_FLAGS) == []) + #expect(scope.evaluate(.OTHER_CFLAGS) == []) + #expect(scope.evaluate(.OTHER_CPLUSPLUSFLAGS) == []) let mscope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .iOS, configuration: .debug) ) - XCTAssertEqual(mscope.evaluate(.LINK_LIBRARIES), ["sqlite3"]) - XCTAssertEqual(mscope.evaluate(.LINK_FRAMEWORKS), ["CoreData"]) + #expect(mscope.evaluate(.LINK_LIBRARIES) == ["sqlite3"]) + #expect(mscope.evaluate(.LINK_FRAMEWORKS) == ["CoreData"]) } @@ -2628,6 +2691,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testEmptyUnsafeFlagsAreAllowed() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo/foo.swift", @@ -2660,50 +2724,51 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual(macosDebugScope.evaluate(.OTHER_CFLAGS), []) - XCTAssertEqual(macosDebugScope.evaluate(.OTHER_CPLUSPLUSFLAGS), []) - XCTAssertEqual(macosDebugScope.evaluate(.OTHER_LDFLAGS), []) + #expect(macosDebugScope.evaluate(.OTHER_CFLAGS) == []) + #expect(macosDebugScope.evaluate(.OTHER_CPLUSPLUSFLAGS) == []) + #expect(macosDebugScope.evaluate(.OTHER_LDFLAGS) == []) let macosReleaseScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual(macosReleaseScope.evaluate(.OTHER_CFLAGS), []) - XCTAssertEqual(macosReleaseScope.evaluate(.OTHER_CPLUSPLUSFLAGS), []) - XCTAssertEqual(macosReleaseScope.evaluate(.OTHER_LDFLAGS), []) + #expect(macosReleaseScope.evaluate(.OTHER_CFLAGS) == []) + #expect(macosReleaseScope.evaluate(.OTHER_CPLUSPLUSFLAGS) == []) + #expect(macosReleaseScope.evaluate(.OTHER_LDFLAGS) == []) } - package.checkModule("bar") { package in + try package.checkModule("bar") { package in let linuxDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .linux, configuration: .debug) ) - XCTAssertEqual(linuxDebugScope.evaluate(.OTHER_SWIFT_FLAGS), []) - XCTAssertEqual(linuxDebugScope.evaluate(.OTHER_LDFLAGS), []) + #expect(linuxDebugScope.evaluate(.OTHER_SWIFT_FLAGS) == []) + #expect(linuxDebugScope.evaluate(.OTHER_LDFLAGS) == []) let linuxReleaseScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .linux, configuration: .release) ) - XCTAssertEqual(linuxReleaseScope.evaluate(.OTHER_SWIFT_FLAGS), []) - XCTAssertEqual(linuxReleaseScope.evaluate(.OTHER_LDFLAGS), []) + #expect(linuxReleaseScope.evaluate(.OTHER_SWIFT_FLAGS) == []) + #expect(linuxReleaseScope.evaluate(.OTHER_LDFLAGS) == []) let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS), []) - XCTAssertEqual(macosDebugScope.evaluate(.OTHER_LDFLAGS), []) + #expect(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS) == []) + #expect(macosDebugScope.evaluate(.OTHER_LDFLAGS) == []) } } } + @Test func testInvalidHeaderSearchPath() throws { let fs = InMemoryFileSystem(emptyFiles: "/pkg/Sources/exe/main.swift" @@ -2722,7 +2787,7 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest1, path: "/pkg", in: fs) { package, diagnostics in + try PackageBuilderTester(manifest1, path: "/pkg", in: fs) { package, diagnostics in diagnostics.check(diagnostic: "invalid relative path '/Sources/headers'; relative path should not begin with '\(AbsolutePath.root)'", severity: .error) } @@ -2739,11 +2804,12 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest2, path: "/pkg", in: fs) { _, diagnostics in + try PackageBuilderTester(manifest2, path: "/pkg", in: fs) { _, diagnostics in diagnostics.check(diagnostic: "invalid header search path '../../..'; header search path should not be outside the package root", severity: .error) } } + @Test func testDuplicateTargetDependencies() throws { let fs = InMemoryFileSystem(emptyFiles: "/Foo/Sources/Foo/foo.swift", @@ -2792,11 +2858,11 @@ final class PackageBuilderTests: XCTestCase { traits: [] ) - PackageBuilderTester(manifest, path: "/Foo", in: fs) { package, diagnostics in - package.checkModule("Foo") - package.checkModule("Foo2") - package.checkModule("Foo3") - package.checkModule("Qux") + try PackageBuilderTester(manifest, path: "/Foo", in: fs) { package, diagnostics in + try package.checkModule("Foo") + try package.checkModule("Foo2") + try package.checkModule("Foo3") + try package.checkModule("Qux") diagnostics.checkUnordered( diagnostic: "invalid duplicate target dependency declaration 'Bar' in target 'Foo' from package '\(package.packageIdentity)'", severity: .warning @@ -2816,6 +2882,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testConditionalDependencies() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/Foo/main.swift", @@ -2853,11 +2920,11 @@ final class PackageBuilderTests: XCTestCase { traits: [] ) - PackageBuilderTester(manifest, in: fs) { package, _ in + try PackageBuilderTester(manifest, in: fs) { package, _ in package.checkProduct("Foo") - package.checkModule("Bar") - package.checkModule("Baz") - package.checkModule("Foo") { target in + try package.checkModule("Bar") + try package.checkModule("Baz") + try package.checkModule("Foo") { target in target.check(dependencies: ["Bar", "Baz", "Biz"]) target.checkDependency("Bar") { result in @@ -2882,6 +2949,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testMissingDefaultLocalization() throws { let fs = InMemoryFileSystem(emptyFiles: "/Foo/Sources/Foo/foo.swift", @@ -2898,11 +2966,12 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, path: "/Foo", in: fs) { _, diagnostics in + try PackageBuilderTester(manifest, path: "/Foo", in: fs) { _, diagnostics in diagnostics.check(diagnostic: "manifest property 'defaultLocalization' not set; it is required in the presence of localized resources", severity: .error) } } + @Test func testXcodeResources5_4AndEarlier() throws { // In SwiftTools 5.4 and earlier, supported xcbuild file types are supported by default. // Of course, modern file types such as xcstrings won't be supported here because those require a newer Swift tools version in general. @@ -2926,8 +2995,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, path: root, in: fs) { result, diagnostics in - result.checkModule("Foo") { result in + try PackageBuilderTester(manifest, path: root, in: fs) { result, diagnostics in + try result.checkModule("Foo") { result in result.checkSources(sources: ["foo.swift"]) result.checkResources(resources: [ foo.appending(components: "Foo.xib").pathString, @@ -2939,6 +3008,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testXcodeResources5_5AndLater() throws { // In SwiftTools 5.5 and later, xcbuild file types are only supported when explicitly passed via additionalFileRules. @@ -2962,8 +3032,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, path: root, supportXCBuildTypes: true, in: fs) { result, diagnostics in - result.checkModule("Foo") { result in + try PackageBuilderTester(manifest, path: root, supportXCBuildTypes: true, in: fs) { result, diagnostics in + try result.checkModule("Foo") { result in result.checkSources(sources: ["foo.swift"]) result.checkResources(resources: [ foo.appending(components: "Foo.xib").pathString, @@ -2976,6 +3046,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testXcodeResources6_0AndLater() throws { // In SwiftTools 6.0 and later, xcprivacy file types are only supported when explicitly passed via additionalFileRules. @@ -3000,8 +3071,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, path: root, supportXCBuildTypes: true, in: fs) { result, diagnostics in - result.checkModule("Foo") { result in + try PackageBuilderTester(manifest, path: root, supportXCBuildTypes: true, in: fs) { result, diagnostics in + try result.checkModule("Foo") { result in result.checkSources(sources: ["foo.swift"]) result.checkResources(resources: [ foo.appending(components: "Foo.xib").pathString, @@ -3015,6 +3086,7 @@ final class PackageBuilderTests: XCTestCase { } } + @Test func testXCPrivacyNoDiagnostics() throws { // In SwiftTools 6.0 and later, xcprivacy file types should not produce diagnostics messages when included // as resources and built with `swift build`. @@ -3038,8 +3110,8 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, path: root, supportXCBuildTypes: false, in: fs) { result, diagnostics in - result.checkModule("Foo") { result in + try PackageBuilderTester(manifest, path: root, supportXCBuildTypes: false, in: fs) { result, diagnostics in + try result.checkModule("Foo") { result in result.checkSources(sources: ["foo.swift"]) result.checkResources(resources: [ foo.appending(components: "PrivacyInfo.xcprivacy").pathString, @@ -3050,6 +3122,11 @@ final class PackageBuilderTests: XCTestCase { } } + @Test( + .tags( + .Feature.Snippets, + ), + ) func testSnippetsLinkProductLibraries() throws { let root = AbsolutePath("/Foo") let internalSourcesDir = root.appending(components: "Sources", "Internal") @@ -3070,26 +3147,27 @@ final class PackageBuilderTests: XCTestCase { try TargetDescription(name: "Product"), ]) - PackageBuilderTester(manifest, path: root, in: fs) { result, diagnostics in + try PackageBuilderTester(manifest, path: root, in: fs) { result, diagnostics in result.checkProduct("Product") { product in product.check(type: .library(.automatic), targets: ["Product"]) } result.checkProduct("ASnippet") { aSnippet in aSnippet.check(type: .snippet, targets: ["ASnippet"]) } - result.checkModule("Internal") { foo in + try result.checkModule("Internal") { foo in foo.checkSources(sources: ["Internal.swift"]) } - result.checkModule("Product") { foo in + try result.checkModule("Product") { foo in foo.checkSources(sources: ["Product.swift"]) } - result.checkModule("ASnippet") { aSnippet in + try result.checkModule("ASnippet") { aSnippet in aSnippet.checkSources(sources: ["ASnippet.swift"]) aSnippet.check(targetDependencies: ["Product"]) } } } + @Test func testCustomPlatformInConditionals() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/Foo/Best.swift") @@ -3117,14 +3195,15 @@ final class PackageBuilderTests: XCTestCase { settings.add(assignment, for: .SWIFT_ACTIVE_COMPILATION_CONDITIONS) settings.add(versionAssignment, for: .SWIFT_VERSION) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("Foo") { module in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("Foo") { module in module.check(c99name: "Foo", type: .library) module.check(buildSettings: settings) } } } + @Test func testSwiftLanguageVersionPerTarget() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo/foo.swift", @@ -3151,43 +3230,44 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual(macosDebugScope.evaluate(.SWIFT_VERSION), ["5"]) + #expect(macosDebugScope.evaluate(.SWIFT_VERSION) == ["5"]) let macosReleaseScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual(macosReleaseScope.evaluate(.SWIFT_VERSION), ["5"]) + #expect(macosReleaseScope.evaluate(.SWIFT_VERSION) == ["5"]) } - package.checkModule("bar") { package in + try package.checkModule("bar") { package in let linuxDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .linux, configuration: .debug) ) - XCTAssertEqual(linuxDebugScope.evaluate(.SWIFT_VERSION), ["3"]) + #expect(linuxDebugScope.evaluate(.SWIFT_VERSION) == ["3"]) let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual(macosDebugScope.evaluate(.SWIFT_VERSION), ["4"]) + #expect(macosDebugScope.evaluate(.SWIFT_VERSION) == ["4"]) let macosReleaseScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual(macosReleaseScope.evaluate(.SWIFT_VERSION), ["5"]) + #expect(macosReleaseScope.evaluate(.SWIFT_VERSION) == ["5"]) } } } + @Test func testSwiftWarningControlFlags() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/foo/foo.swift" @@ -3209,14 +3289,14 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("foo") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("foo") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual( - macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS), + #expect( + macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS) == ["-no-warnings-as-errors", "-Wwarning", "DeprecatedDeclaration"] ) @@ -3224,14 +3304,15 @@ final class PackageBuilderTests: XCTestCase { package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual( - macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS), + #expect( + macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS) == ["-warnings-as-errors", "-Werror", "DeprecatedDeclaration"] ) } } } + @Test func testCWarningControlFlags() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/cfoo/foo.c", @@ -3254,14 +3335,14 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("cfoo") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("cfoo") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual( - macosDebugScope.evaluate(.OTHER_CFLAGS), + #expect( + macosDebugScope.evaluate(.OTHER_CFLAGS) == ["-Wno-error", "-Wno-error=deprecated-declarations"] ) @@ -3269,14 +3350,15 @@ final class PackageBuilderTests: XCTestCase { package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual( - macosReleaseScope.evaluate(.OTHER_CFLAGS), + #expect( + macosReleaseScope.evaluate(.OTHER_CFLAGS) == ["-Werror", "-Werror=deprecated-declarations"] ) } } } + @Test func testCXXWarningControlFlags() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/cxxfoo/foo.cpp", @@ -3299,14 +3381,14 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("cxxfoo") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("cxxfoo") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual( - macosDebugScope.evaluate(.OTHER_CPLUSPLUSFLAGS), + #expect( + macosDebugScope.evaluate(.OTHER_CPLUSPLUSFLAGS) == ["-Wno-error", "-Wno-error=deprecated-declarations"] ) @@ -3314,14 +3396,15 @@ final class PackageBuilderTests: XCTestCase { package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual( - macosReleaseScope.evaluate(.OTHER_CPLUSPLUSFLAGS), + #expect( + macosReleaseScope.evaluate(.OTHER_CPLUSPLUSFLAGS) == ["-Werror", "-Werror=deprecated-declarations"] ) } } } + @Test func testCWarningEnableDisable() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/cfoo/foo.c", @@ -3342,14 +3425,14 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("cfoo") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("cfoo") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual( - macosDebugScope.evaluate(.OTHER_CFLAGS), + #expect( + macosDebugScope.evaluate(.OTHER_CFLAGS) == ["-Wimplicit-fallthrough"] ) @@ -3357,14 +3440,15 @@ final class PackageBuilderTests: XCTestCase { package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual( - macosReleaseScope.evaluate(.OTHER_CFLAGS), + #expect( + macosReleaseScope.evaluate(.OTHER_CFLAGS) == ["-Wno-unused-parameter"] ) } } } + @Test func testCXXWarningEnableDisable() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/cxxfoo/foo.cpp", @@ -3385,14 +3469,14 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("cxxfoo") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("cxxfoo") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertEqual( - macosDebugScope.evaluate(.OTHER_CPLUSPLUSFLAGS), + #expect( + macosDebugScope.evaluate(.OTHER_CPLUSPLUSFLAGS) == ["-Wimplicit-fallthrough"] ) @@ -3400,14 +3484,15 @@ final class PackageBuilderTests: XCTestCase { package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertEqual( - macosReleaseScope.evaluate(.OTHER_CPLUSPLUSFLAGS), + #expect( + macosReleaseScope.evaluate(.OTHER_CPLUSPLUSFLAGS) == ["-Wno-unused-parameter"] ) } } } + @Test func testDefaultIsolationPerTarget() throws { let fs = InMemoryFileSystem(emptyFiles: "/Sources/A/a.swift", @@ -3434,45 +3519,45 @@ final class PackageBuilderTests: XCTestCase { ] ) - PackageBuilderTester(manifest, in: fs) { package, _ in - package.checkModule("A") { package in + try PackageBuilderTester(manifest, in: fs) { package, _ in + try package.checkModule("A") { package in let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertMatch(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS), - [.anySequence, "-default-isolation", "MainActor", .anySequence]) + #expect(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS).contains("-default-isolation")) + #expect(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS).contains("MainActor")) let macosReleaseScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertMatch(macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS), - [.anySequence, "-default-isolation", "MainActor", .anySequence]) + #expect(macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS).contains("-default-isolation")) + #expect(macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS).contains("MainActor")) } - package.checkModule("B") { package in + try package.checkModule("B") { package in let linuxDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .linux, configuration: .debug) ) - XCTAssertMatch(linuxDebugScope.evaluate(.OTHER_SWIFT_FLAGS), - [.anySequence, "-default-isolation", "nonisolated", .anySequence]) + #expect(linuxDebugScope.evaluate(.OTHER_SWIFT_FLAGS).contains("-default-isolation")) + #expect(linuxDebugScope.evaluate(.OTHER_SWIFT_FLAGS).contains("nonisolated")) let macosDebugScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .debug) ) - XCTAssertMatch(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS), - [.anySequence, "-default-isolation", "MainActor", .anySequence]) + #expect(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS).contains("-default-isolation")) + #expect(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS).contains("MainActor")) let macosReleaseScope = BuildSettings.Scope( package.target.buildSettings, environment: BuildEnvironment(platform: .macOS, configuration: .release) ) - XCTAssertNoMatch(macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS), - [.anySequence, "-default-isolation", "MainActor", .anySequence]) + #expect(!macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS).contains("-default-isolation") || + !macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS).contains("MainActor")) } } @@ -3507,10 +3592,9 @@ final class PackageBuilderTester { createREPLProduct: Bool = false, supportXCBuildTypes: Bool = false, in fs: FileSystem, - file: StaticString = #file, - line: UInt = #line, - _ body: (PackageBuilderTester, DiagnosticsTestResult) -> Void - ) { + sourceLocation: SourceLocation = #_sourceLocation, + _ body: (PackageBuilderTester, DiagnosticsTestResult) throws -> Void + ) throws { self.packageIdentity = PackageIdentity(urlString: manifest.packageLocation) let observability = ObservabilitySystem.makeForTesting() do { @@ -3540,49 +3624,54 @@ final class PackageBuilderTester { observability.topScope.emit(error) } - testDiagnostics(observability.diagnostics, file: file, line: line) { diagnostics in - body(self, diagnostics) + try expectDiagnostics(observability.diagnostics, sourceLocation: sourceLocation) { diagnostics in + try body(self, diagnostics) } - validateCheckedModules(file: file, line: line) + validateCheckedModules(sourceLocation: sourceLocation) } - private func validateCheckedModules(file: StaticString, line: UInt) { + private func validateCheckedModules(sourceLocation: SourceLocation = #_sourceLocation) { if !uncheckedModules.isEmpty { - XCTFail("Unchecked targets: \(uncheckedModules)", file: file, line: line) + Issue.record("Unchecked targets: \(uncheckedModules)", sourceLocation: sourceLocation) } if !uncheckedProducts.isEmpty { - XCTFail("Unchecked products: \(uncheckedProducts)", file: file, line: line) + Issue.record("Unchecked products: \(uncheckedProducts)", sourceLocation: sourceLocation) } } - func checkPredefinedPaths(target: AbsolutePath, testTarget: AbsolutePath, file: StaticString = #file, line: UInt = #line) { + func checkPredefinedPaths(target: AbsolutePath, testTarget: AbsolutePath, sourceLocation: SourceLocation = #_sourceLocation) { guard case .package(let package) = result else { - return XCTFail("Expected package did not load \(self)", file: file, line: line) + Issue.record("Expected package did not load \(self)", sourceLocation: sourceLocation) + return } - XCTAssertEqual(target, package.targetSearchPath, file: file, line: line) - XCTAssertEqual(testTarget, package.testTargetSearchPath, file: file, line: line) + #expect(target == package.targetSearchPath, sourceLocation: sourceLocation) + #expect(testTarget == package.testTargetSearchPath, sourceLocation: sourceLocation) } - func checkModule(_ name: String, file: StaticString = #file, line: UInt = #line, _ body: ((ModuleResult) -> Void)? = nil) { + func checkModule(_ name: String, sourceLocation: SourceLocation = #_sourceLocation, _ body: ((ModuleResult) throws -> Void)? = nil) throws { guard case .package(let package) = result else { - return XCTFail("Expected package did not load \(self)", file: file, line: line) + Issue.record("Expected package did not load \(self)", sourceLocation: sourceLocation) + return } guard let target = package.modules.first(where: {$0.name == name}) else { - return XCTFail("Module: \(name) not found", file: file, line: line) + Issue.record("Module: \(name) not found", sourceLocation: sourceLocation) + return } uncheckedModules.remove(target) - body?(ModuleResult(target)) + try body?(ModuleResult(target)) } - func checkProduct(_ name: String, file: StaticString = #file, line: UInt = #line, _ body: ((ProductResult) -> Void)? = nil) { + func checkProduct(_ name: String, sourceLocation: SourceLocation = #_sourceLocation, _ body: ((ProductResult) -> Void)? = nil) { guard case .package(let package) = result else { - return XCTFail("Expected package did not load \(self)", file: file, line: line) + Issue.record("Expected package did not load \(self)", sourceLocation: sourceLocation) + return } let foundProducts = package.products.filter{$0.name == name} guard foundProducts.count == 1 else { - return XCTFail("Couldn't get the product: \(name). Found products \(foundProducts)", file: file, line: line) + Issue.record("Couldn't get the product: \(name). Found products \(foundProducts)", sourceLocation: sourceLocation) + return } uncheckedProducts.remove(foundProducts[0]) body?(ProductResult(foundProducts[0])) @@ -3595,13 +3684,14 @@ final class PackageBuilderTester { self.product = product } - func check(type: PackageModel.ProductType, targets: [String], file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual(product.type, type, file: file, line: line) - XCTAssertEqual(product.modules.map{$0.name}.sorted(), targets.sorted(), file: file, line: line) + func check(type: PackageModel.ProductType, targets: [String], sourceLocation: SourceLocation = #_sourceLocation) { + #expect(product.type == type, sourceLocation: sourceLocation) + #expect(product.modules.map{$0.name}.sorted() == targets.sorted(), sourceLocation: sourceLocation) } - func check(testEntryPointPath: String?, file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual(product.testEntryPointPath, testEntryPointPath.map({ try! AbsolutePath(validating: $0) }), file: file, line: line) + func check(testEntryPointPath: String?, sourceLocation: SourceLocation = #_sourceLocation) { + let expectedPath = testEntryPointPath.map({ try! AbsolutePath(validating: $0) }) + #expect(product.testEntryPointPath == expectedPath, sourceLocation: sourceLocation) } } @@ -3612,106 +3702,113 @@ final class PackageBuilderTester { self.target = target } - func check(includeDir: String, file: StaticString = #file, line: UInt = #line) { + func check(includeDir: String, sourceLocation: SourceLocation = #_sourceLocation) { guard case let target as ClangModule = target else { - return XCTFail("Include directory is being checked on a non clang target", file: file, line: line) + Issue.record("Include directory is being checked on a non clang target", sourceLocation: sourceLocation) + return } - XCTAssertEqual(target.includeDir.pathString, includeDir, file: file, line: line) + #expect(target.includeDir.pathString == includeDir, sourceLocation: sourceLocation) } - func check(moduleMapType: ModuleMapType, file: StaticString = #file, line: UInt = #line) { + func check(moduleMapType: ModuleMapType, sourceLocation: SourceLocation = #_sourceLocation) { guard case let target as ClangModule = target else { - return XCTFail("Module map type is being checked on a non-Clang target", file: file, line: line) + Issue.record("Module map type is being checked on a non-Clang target", sourceLocation: sourceLocation) + return } - XCTAssertEqual(target.moduleMapType, moduleMapType, file: file, line: line) + #expect(target.moduleMapType == moduleMapType, sourceLocation: sourceLocation) } - func check(c99name: String? = nil, type: PackageModel.Module.Kind? = nil, file: StaticString = #file, line: UInt = #line) { + func check(c99name: String? = nil, type: PackageModel.Module.Kind? = nil, sourceLocation: SourceLocation = #_sourceLocation) { if let c99name { - XCTAssertEqual(target.c99name, c99name, file: file, line: line) + #expect(target.c99name == c99name, sourceLocation: sourceLocation) } if let type { - XCTAssertEqual(target.type, type, file: file, line: line) + #expect(target.type == type, sourceLocation: sourceLocation) } } - func checkSources(root: String? = nil, sources paths: [String], file: StaticString = #file, line: UInt = #line) { + func checkSources(root: String? = nil, sources paths: [String], sourceLocation: SourceLocation = #_sourceLocation) { if let root { - XCTAssertEqual(target.sources.root, try! AbsolutePath(validating: root), file: file, line: line) + let expectedRoot = try! AbsolutePath(validating: root) + #expect(target.sources.root == expectedRoot, sourceLocation: sourceLocation) } let sources = Set(self.target.sources.relativePaths.map({ $0.pathString })) - XCTAssertEqual(sources, Set(paths), "unexpected source files in \(target.name)", file: file, line: line) + #expect(sources == Set(paths), "unexpected source files in \(target.name)", sourceLocation: sourceLocation) } - func checkSources(root: String? = nil, paths: String..., file: StaticString = #file, line: UInt = #line) { - checkSources(root: root, sources: paths, file: file, line: line) + func checkSources(root: String? = nil, paths: String..., sourceLocation: SourceLocation = #_sourceLocation) { + checkSources(root: root, sources: paths, sourceLocation: sourceLocation) } - func checkResources(resources: [String], file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual(Set(resources), Set(self.target.resources.map{ $0.path.pathString }), "unexpected resource files in \(target.name)", file: file, line: line) + func checkResources(resources: [String], sourceLocation: SourceLocation = #_sourceLocation) { + #expect(Set(resources) == Set(self.target.resources.map{ $0.path.pathString }), "unexpected resource files in \(target.name)", sourceLocation: sourceLocation) } - func check(targetDependencies depsToCheck: [String], file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual(Set(depsToCheck), Set(target.dependencies.compactMap { $0.module?.name }), "unexpected dependencies in \(target.name)", file: file, line: line) + func check(targetDependencies depsToCheck: [String], sourceLocation: SourceLocation = #_sourceLocation) { + #expect(Set(depsToCheck) == Set(target.dependencies.compactMap { $0.module?.name }), "unexpected dependencies in \(target.name)", sourceLocation: sourceLocation) } func check( productDependencies depsToCheck: [Module.ProductReference], - file: StaticString = #file, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) { let productDependencies = target.dependencies.compactMap { $0.product } guard depsToCheck.count == productDependencies.count else { - return XCTFail("Incorrect product dependencies", file: file, line: line) + Issue.record("Incorrect product dependencies", sourceLocation: sourceLocation) + return } for (idx, element) in depsToCheck.enumerated() { let rhs = productDependencies[idx] guard element.name == rhs.name && element.package == rhs.package else { - return XCTFail("Incorrect product dependencies", file: file, line: line) + Issue.record("Incorrect product dependencies", sourceLocation: sourceLocation) + return } } } - func check(dependencies: [String], file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual( - Set(dependencies), - Set(target.dependencies.map({ $0.name })), + func check(dependencies: [String], sourceLocation: SourceLocation = #_sourceLocation) { + #expect( + Set(dependencies) == Set(target.dependencies.map({ $0.name })), "unexpected dependencies in \(target.name)", - file: file, - line: line + sourceLocation: sourceLocation ) } func checkDependency( _ name: String, - file: StaticString = #file, - line: UInt = #line, + sourceLocation: SourceLocation = #_sourceLocation, _ body: (ModuleDependencyResult) -> Void ) { guard let dependency = target.dependencies.first(where: { $0.name == name }) else { - return XCTFail("Module: \(name) not found", file: file, line: line) + Issue.record("Module: \(name) not found", sourceLocation: sourceLocation) + return } body(ModuleDependencyResult(dependency)) } - func check(swiftVersion: String, file: StaticString = #file, line: UInt = #line) { + func check(swiftVersion: String, sourceLocation: SourceLocation = #_sourceLocation) { guard case let swiftTarget as SwiftModule = target else { - return XCTFail("\(target) is not a swift target", file: file, line: line) + Issue.record("\(target) is not a swift target", sourceLocation: sourceLocation) + return } - let versionAssignments = swiftTarget.buildSettings.assignments[.SWIFT_VERSION]? - .filter { $0.conditions.isEmpty }.flatMap(\.values) - XCTAssertNotNil(versionAssignments?.contains(swiftVersion), file: file, line: line) + guard let versionAssignments = swiftTarget.buildSettings.assignments[.SWIFT_VERSION]? + .filter { $0.conditions.isEmpty }.flatMap(\.values) else { + Issue.record("\(target) has no version assignments", sourceLocation: sourceLocation) + return + } + #expect(versionAssignments.contains(swiftVersion) != nil, sourceLocation: sourceLocation) } - func check(pluginCapability: PluginCapability, file: StaticString = #file, line: UInt = #line) { + func check(pluginCapability: PluginCapability, sourceLocation: SourceLocation = #_sourceLocation) { guard case let target as PluginModule = target else { - return XCTFail("Plugin capability is being checked on a target", file: file, line: line) + Issue.record("Plugin capability is being checked on a target", sourceLocation: sourceLocation) + return } - XCTAssertEqual(target.capability, pluginCapability, file: file, line: line) + #expect(target.capability == pluginCapability, sourceLocation: sourceLocation) } - func check(buildSettings: PackageModel.BuildSettings.AssignmentTable, file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual(target.buildSettings.assignments, buildSettings.assignments, file: file, line: line) + func check(buildSettings: PackageModel.BuildSettings.AssignmentTable, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(target.buildSettings.assignments == buildSettings.assignments, sourceLocation: sourceLocation) } } @@ -3722,16 +3819,15 @@ final class PackageBuilderTester { self.dependency = dependency } - func checkConditions(satisfy environment: BuildEnvironment, file: StaticString = #file, line: UInt = #line) { - XCTAssert(dependency.conditions.allSatisfy { $0.satisfies(environment) }, file: file, line: line) + func checkConditions(satisfy environment: BuildEnvironment, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(dependency.conditions.allSatisfy { $0.satisfies(environment) }, sourceLocation: sourceLocation) } func checkConditions( dontSatisfy environment: BuildEnvironment, - file: StaticString = #file, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) { - XCTAssert(!dependency.conditions.allSatisfy { $0.satisfies(environment) }, file: file, line: line) + #expect(!dependency.conditions.allSatisfy { $0.satisfies(environment) }, sourceLocation: sourceLocation) } } } diff --git a/Tests/PackageModelTests/SnippetTests.swift b/Tests/PackageModelTests/SnippetTests.swift index 634fe2e22c4..fa9af97da89 100644 --- a/Tests/PackageModelTests/SnippetTests.swift +++ b/Tests/PackageModelTests/SnippetTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -12,26 +12,34 @@ import Basics @testable import PackageModel -import XCTest - -class SnippetTests: XCTestCase { +import Testing + +@Suite( + .tags( + .TestSize.small, + .Feature.Snippets, + ), +) +struct SnippetTests { let fakeSourceFilePath = AbsolutePath("/fake/path/to/test.swift") /// Test the contents of the ``Snippet`` model when parsing an empty file. /// Currently, no errors are emitted and most things are either nil or empty. - func testEmptySourceFile() { + @Test + func testEmptySourceFile() async throws { let source = "" let snippet = Snippet(parsing: source, path: fakeSourceFilePath) - XCTAssertEqual(snippet.path, fakeSourceFilePath) - XCTAssertTrue(snippet.explanation.isEmpty) - XCTAssertTrue(snippet.presentationCode.isEmpty) - XCTAssertNil(snippet.groupName) - XCTAssertEqual("test", snippet.name) + #expect(snippet.path == fakeSourceFilePath) + #expect(snippet.explanation.isEmpty) + #expect(snippet.presentationCode.isEmpty) + #expect(snippet.groupName == nil) + #expect("test" == snippet.name) } /// Test the contents of the ``Snippet`` model when parsing a typical /// source file. - func testBasic() { + @Test + func testBasic() async throws { let explanation = "This snippet does a foo. Try it when XYZ." let presentationCode = """ import Module @@ -52,17 +60,18 @@ class SnippetTests: XCTestCase { let snippet = Snippet(parsing: source, path: fakeSourceFilePath) - XCTAssertEqual(snippet.path, fakeSourceFilePath) - XCTAssertEqual(explanation, snippet.explanation) - XCTAssertEqual(presentationCode, snippet.presentationCode) - XCTAssertNil(snippet.groupName) - XCTAssertEqual("test", snippet.name) + #expect(snippet.path == fakeSourceFilePath) + #expect(explanation == snippet.explanation) + #expect(presentationCode == snippet.presentationCode) + #expect(snippet.groupName == nil) + #expect("test" == snippet.name) } /// Test that multiple consecutive newlines in a snippet's /// presentation code is coalesced into no more than two newlines, /// and test that newlines at the beginning and end of are stripped. - func testMultiNewlineCoalescing() { + @Test + func testMultiNewlineCoalescing() async throws { let explanation = "This snippet does a foo. Try it when XYZ." let presentationCode = """ @@ -97,13 +106,13 @@ class SnippetTests: XCTestCase { """ let snippet = Snippet(parsing: source, path: fakeSourceFilePath) - XCTAssertEqual(explanation, snippet.explanation) - XCTAssertEqual(expectedPresentationCode, snippet.presentationCode) + #expect(explanation == snippet.explanation) + #expect(expectedPresentationCode == snippet.presentationCode) } /// Test that toggling back and forth with `mark: hide` and `mark: show` /// works as intended. - func testMarkHideShowToggle() { + func testMarkHideShowToggle() async throws { let source = """ shown1 @@ -129,14 +138,15 @@ class SnippetTests: XCTestCase { """ let snippet = Snippet(parsing: source, path: fakeSourceFilePath) - XCTAssertFalse(snippet.presentationCode.contains("hidden")) - XCTAssertFalse(snippet.explanation.contains("hidden")) - XCTAssertEqual(expectedPresentationCode, snippet.presentationCode) + #expect(!snippet.presentationCode.contains("hidden")) + #expect(!snippet.explanation.contains("hidden")) + #expect(expectedPresentationCode == snippet.presentationCode) } /// Tests that extra indentation is removed when extracting some inner /// part of nested code. - func testRemoveExtraIndentation() { + @Test + func testRemoveExtraIndentation() async throws { let source = """ // mark: hide struct Outer { @@ -154,6 +164,6 @@ class SnippetTests: XCTestCase { } """ let snippet = Snippet(parsing: source, path: fakeSourceFilePath) - XCTAssertEqual(expectedPresentationCode, snippet.presentationCode) + #expect(expectedPresentationCode == snippet.presentationCode) } } diff --git a/Tests/_InternalTestSupportTests/FileSystemHelpersTests.swift b/Tests/_InternalTestSupportTests/FileSystemHelpersTests.swift new file mode 100644 index 00000000000..754e0c9f2e0 --- /dev/null +++ b/Tests/_InternalTestSupportTests/FileSystemHelpersTests.swift @@ -0,0 +1,622 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import _InternalTestSupport +import Testing + +@Suite( + .tags( + .TestSize.small, + ), +) +struct FileSystemHelpersTests { + @Test + func testGetFilesAbsolutePathRecursive() throws { + // Create an in-memory file system for testing + let fileSystem = InMemoryFileSystem() + + // Create a test directory structure + let testDir = try AbsolutePath(validating: "/test") + try fileSystem.createDirectory(testDir, recursive: true) + + // Create some test files + let swiftFile1 = testDir.appending("file1.swift") + let swiftFile2 = testDir.appending("subdir").appending("file2.swift") + let txtFile = testDir.appending("readme.txt") + let swiftFile3 = testDir.appending("subdir").appending("nested").appending("file3.swift") + + try fileSystem.createDirectory(swiftFile2.parentDirectory, recursive: true) + try fileSystem.createDirectory(swiftFile3.parentDirectory, recursive: true) + + try fileSystem.writeFileContents(swiftFile1, string: "// Swift file 1") + try fileSystem.writeFileContents(swiftFile2, string: "// Swift file 2") + try fileSystem.writeFileContents(txtFile, string: "This is a text file") + try fileSystem.writeFileContents(swiftFile3, string: "// Swift file 3") + + // Test recursive search (default) + let swiftFiles = try getFiles( + in: testDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + + // Verify results + #expect(swiftFiles.count == 3) + #expect(swiftFiles.contains(swiftFile1)) + #expect(swiftFiles.contains(swiftFile2)) + #expect(swiftFiles.contains(swiftFile3)) + #expect(!swiftFiles.contains(txtFile)) + + // Test with different extension + let txtFiles = try getFiles( + in: testDir, + matchingExtension: "txt", + fileSystem: fileSystem + ) + + #expect(txtFiles.count == 1) + #expect(txtFiles.contains(txtFile)) + } + + @Test + func testGetFilesAbsolutePathNonRecursive() throws { + let fileSystem = InMemoryFileSystem() + + let testDir = try AbsolutePath(validating: "/test") + try fileSystem.createDirectory(testDir, recursive: true) + + // Create files at different levels + let swiftFile1 = testDir.appending("file1.swift") + let swiftFile2 = testDir.appending("subdir").appending("file2.swift") + + try fileSystem.createDirectory(swiftFile2.parentDirectory, recursive: true) + try fileSystem.writeFileContents(swiftFile1, string: "// Swift file 1") + try fileSystem.writeFileContents(swiftFile2, string: "// Swift file 2") + + // Test non-recursive search + let swiftFiles = try getFiles( + in: testDir, + matchingExtension: "swift", + recursive: false, + fileSystem: fileSystem + ) + + // Should only find the top-level file + #expect(swiftFiles.count == 1) + #expect(swiftFiles.contains(swiftFile1)) + #expect(!swiftFiles.contains(swiftFile2)) + } + + @Test + func testGetFilesWithCaseInsensitiveExtensionReturnsexpectedValue() throws { + let fileSystem = InMemoryFileSystem() + + let testDir = try AbsolutePath(validating: "/test") + try fileSystem.createDirectory(testDir, recursive: true) + + // Create files with different case extensions + let swiftFile = testDir.appending("file1.swift") + let SwiftFile = testDir.appending("file2.Swift") + let SWIFTFile = testDir.appending("file3.SWIFT") + + try fileSystem.writeFileContents(swiftFile, string: "// file1") + try fileSystem.writeFileContents(SwiftFile, string: "// file2") + try fileSystem.writeFileContents(SWIFTFile, string: "// file3") + + // Test with lowercase extension + let results = try getFiles( + in: testDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + + #expect(results.count == 3) + #expect(results.contains(swiftFile)) + #expect(results.contains(SwiftFile)) + #expect(results.contains(SWIFTFile)) + } + + @Test + func testGetFilesNonExistentDirectoryRasiesAnError() throws { + let fileSystem = InMemoryFileSystem() + let nonExistentDir = try AbsolutePath(validating: "/nonexistent") + + #expect(throws: StringError.self) { + try getFiles( + in: nonExistentDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + } + } + + @Test + func testGetFilesWithFileAsInputRaisesAnError() throws { + let fileSystem = InMemoryFileSystem() + + let testFile = try AbsolutePath(validating: "/test.swift") + try fileSystem.writeFileContents(testFile, string: "// test file") + + #expect(throws: StringError.self) { + try getFiles( + in: testFile, + matchingExtension: "swift", + fileSystem: fileSystem + ) + } + } + + @Test + func testGetFilesEmptyDirectoryReturnsEmptyList() throws { + let fileSystem = InMemoryFileSystem() + + let testDir = try AbsolutePath(validating: "/empty") + try fileSystem.createDirectory(testDir, recursive: true) + + let results = try getFiles( + in: testDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + + #expect(results.isEmpty) + } + + @Test + func testGetFilesRelativePathNonRecursive() throws { + let fileSystem = InMemoryFileSystem() + + // Set up current working directory + let cwd = try AbsolutePath(validating: "/current") + try fileSystem.createDirectory(cwd, recursive: true) + try fileSystem.changeCurrentWorkingDirectory(to: cwd) + + // Create test directory structure + let testDir = try RelativePath(validating: "test") + let absoluteTestDir = cwd.appending(testDir) + try fileSystem.createDirectory(absoluteTestDir, recursive: true) + + // Create files at different levels + let swiftFile1 = absoluteTestDir.appending("file1.swift") + let swiftFile2 = absoluteTestDir.appending("subdir").appending("file2.swift") + + try fileSystem.createDirectory(swiftFile2.parentDirectory, recursive: true) + try fileSystem.writeFileContents(swiftFile1, string: "// Swift file 1") + try fileSystem.writeFileContents(swiftFile2, string: "// Swift file 2") + + // Test non-recursive search + let swiftFiles = try getFiles( + in: testDir, + matchingExtension: "swift", + recursive: false, + fileSystem: fileSystem + ) + + // Should only find the top-level file + #expect(swiftFiles.count == 1) + + let expectedFile1 = swiftFile1.relative(to: cwd) + #expect(swiftFiles.contains(expectedFile1)) + + // Should not find the nested file + let expectedFile2 = swiftFile2.relative(to: cwd) + #expect(!swiftFiles.contains(expectedFile2)) + } + + @Test + func testGetFilesRelativePathInvalid() throws { + let fileSystem = InMemoryFileSystem() + + // Set up current working directory + let cwd = try AbsolutePath(validating: "/current") + try fileSystem.createDirectory(cwd, recursive: true) + try fileSystem.changeCurrentWorkingDirectory(to: cwd) + + // Try to access non-existent relative directory + let nonExistentDir = try RelativePath(validating: "nonexistent") + + #expect(throws: StringError.self) { + try getFiles( + in: nonExistentDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + } + } + + @Test + func testGetFilesRelativePathWithFileAsInputRaisesAnError() throws { + let fileSystem = InMemoryFileSystem() + + // Set up current working directory + let cwd = try AbsolutePath(validating: "/current") + try fileSystem.createDirectory(cwd, recursive: true) + try fileSystem.changeCurrentWorkingDirectory(to: cwd) + + // Create a file instead of directory + let testFile = cwd.appending("test.swift") + try fileSystem.writeFileContents(testFile, string: "// test file") + + let relativeFile = try RelativePath(validating: "test.swift") + + #expect(throws: StringError.self) { + try getFiles( + in: relativeFile, + matchingExtension: "swift", + fileSystem: fileSystem + ) + } + } + + @Test + func testGetFilesRelativePathNoCwdRaisesAnError() throws { + let fileSystem = InMemoryFileSystem() + // Don't set a current working directory + + let testDir = try RelativePath(validating: "test") + + #expect(throws: StringError.self) { + try getFiles( + in: testDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + } + } + + @Test + func testGetFilesRelativePathComplexStructureReturnsExpectedList() throws { + let fileSystem = InMemoryFileSystem() + + // Set up current working directory + let cwd = try AbsolutePath(validating: "/project") + try fileSystem.createDirectory(cwd, recursive: true) + try fileSystem.changeCurrentWorkingDirectory(to: cwd) + + // Create complex directory structure + let sourcesDir = try RelativePath(validating: "Sources") + let absoluteSourcesDir = cwd.appending(sourcesDir) + try fileSystem.createDirectory(absoluteSourcesDir, recursive: true) + + // Create files in various subdirectories + let files = [ + "Sources/App/main.swift", + "Sources/App/Models/User.swift", + "Sources/App/Controllers/UserController.swift", + "Sources/Shared/Utils.swift", + "Sources/Shared/Extensions/String+Extensions.swift", + "Sources/Tests/AppTests.swift", + "Sources/README.md", // Non-Swift file + ] + + for filePath in files { + let absolutePath = try AbsolutePath(validating: filePath, relativeTo: cwd) + try fileSystem.createDirectory(absolutePath.parentDirectory, recursive: true) + try fileSystem.writeFileContents(absolutePath, string: "// \(absolutePath.basename)") + } + + // Test recursive search + let allSwiftFiles = try getFiles( + in: sourcesDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + + #expect(allSwiftFiles.count == 6) // All .swift files, excluding README.md + + // Verify all expected files are found + let expectedSwiftFiles = files.filter { $0.hasSuffix(".swift") } + for expectedFile in expectedSwiftFiles { + let relativePath = try RelativePath(validating: expectedFile) + #expect(allSwiftFiles.contains(relativePath)) + } + + // Test non-recursive search (should find no files at Sources root level) + let topLevelSwiftFiles = try getFiles( + in: sourcesDir, + matchingExtension: "swift", + recursive: false, + fileSystem: fileSystem + ) + + #expect(topLevelSwiftFiles.isEmpty) + } + + @Test + func testGetFilesRelativePathCaseSensitivity() throws { + let fileSystem = InMemoryFileSystem() + + // Set up current working directory + let cwd = try AbsolutePath(validating: "/test") + try fileSystem.createDirectory(cwd, recursive: true) + try fileSystem.changeCurrentWorkingDirectory(to: cwd) + + // Create test directory + let testDir = try RelativePath(validating: "files") + let absoluteTestDir = cwd.appending(testDir) + try fileSystem.createDirectory(absoluteTestDir, recursive: true) + + // Create files with different case extensions + let files = [ + "file1.swift", + "file2.Swift", + "file3.SWIFT", + "file4.swiFT", + "file5.txt", // Different extension + ] + + for fileName in files { + let filePath = absoluteTestDir.appending(fileName) + try fileSystem.writeFileContents(filePath, string: "// \(fileName)") + } + + // Test case-insensitive matching + let swiftFiles = try getFiles( + in: testDir, + matchingExtension: "swift", + fileSystem: fileSystem + ) + + #expect(swiftFiles.count == 4) // All .swift variants, excluding .txt + + // Test with uppercase extension + let swiftFilesUpper = try getFiles( + in: testDir, + matchingExtension: "SWIFT", + fileSystem: fileSystem + ) + + #expect(swiftFilesUpper.count == 4) // Should match the same files + #expect(Set(swiftFiles) == Set(swiftFilesUpper)) + } + + // MARK: - Parameterized Tests + + @Test( + arguments: [ + ( + extension: "swift", + expectedFiles: ["file1.swift", "file2.Swift", "file3.SWIFT", "file4.swiFT"], + allFiles: ["file1.swift", "file2.Swift", "file3.SWIFT", "file4.swiFT", "file5.txt"], + ), + ( + extension: "SWIFT", + expectedFiles: ["file1.swift", "file2.Swift", "file3.SWIFT", "file4.swiFT"], + allFiles: ["file1.swift", "file2.Swift", "file3.SWIFT", "file4.swiFT", "file5.txt"], + + ), + ( + extension: "Swift", + expectedFiles: ["file1.swift", "file2.Swift", "file3.SWIFT", "file4.swiFT"], + allFiles: ["file1.swift", "file2.Swift", "file3.SWIFT", "file4.swiFT", "file5.txt"], + ), + ( + extension: "txt", + expectedFiles: ["file5.txt"], + allFiles: ["file1.swift", "file2.Swift", "file3.SWIFT", "file4.swiFT", "file5.txt"], + ), + ], + ) + func testCaseInsensitiveExtensionsParameterized( + extension: String, + expectedFiles: [String], + allFiles: [String], + ) throws { + let fileSystem = InMemoryFileSystem() + let testDir = try AbsolutePath(validating: "/test") + try fileSystem.createDirectory(testDir, recursive: true) + + // Create files with different case extensions + for fileName in allFiles { + let filePath = testDir.appending(fileName) + try fileSystem.writeFileContents(filePath, string: "// \(fileName)") + } + + let results = try getFiles( + in: testDir, + matchingExtension: `extension`, + fileSystem: fileSystem + ) + + #expect(results.count == expectedFiles.count, "Expected \(expectedFiles.count) files for extension '\(`extension`)'") + + for expectedFile in expectedFiles { + let expectedPath = testDir.appending(expectedFile) + #expect(results.contains(expectedPath), "Should contain \(expectedFile)") + } + } + + @Test( + arguments: [ + ("non-existent directory", "/nonexistent", false), + ("file instead of directory", "/test.swift", true), + ] + ) + func testErrorHandling( + description: String, + path: String, + createFile: Bool, + ) throws { + let fileSystem = InMemoryFileSystem() + + if createFile { + // Create a file for the "file instead of directory" test + let testFile = try AbsolutePath(validating: path) + try fileSystem.writeFileContents(testFile, string: "// test file") + } + + let testPath = try AbsolutePath(validating: path) + + #expect(throws: StringError.self) { + try getFiles( + in: testPath, + matchingExtension: "swift", + fileSystem: fileSystem + ) + } + } + + @Test( + arguments: [ + ("invalid path", true, false), + ("file instead of directory", true, true), + ("no current working directory", false, false), + ] + ) + func testRelativePathErrorConditions( + description: String, + setCwd: Bool, + createFile: Bool, + ) throws { + let fileSystem = InMemoryFileSystem() + + if setCwd { + let cwd = try AbsolutePath(validating: "/current") + try fileSystem.createDirectory(cwd, recursive: true) + try fileSystem.changeCurrentWorkingDirectory(to: cwd) + + if createFile { + // Create a file for the "file instead of directory" test + let testFile = cwd.appending("test.swift") + try fileSystem.writeFileContents(testFile, string: "// test file") + } + } + + let testPath = try RelativePath(validating: createFile ? "test.swift" : "nonexistent") + + #expect(throws: StringError.self) { + try getFiles( + in: testPath, + matchingExtension: "swift", + fileSystem: fileSystem + ) + } + } + + @Test( + arguments: [ + ( + recursive: true, + expectedCount: 3, + description: "recursive search should find all files" + ), + ( + recursive: false, + expectedCount: 1, + description: "non-recursive search should find only top-level files" + ), + ] + ) + func testgetFilesWithVariousRecursionModes( + recursive: Bool, + expectedCount: Int, + description: String, + ) throws { + let fileSystem = InMemoryFileSystem() + let testDir = try AbsolutePath(validating: "/test") + try fileSystem.createDirectory(testDir, recursive: true) + + // Create files at different levels + let swiftFile1 = testDir.appending("file1.swift") + let swiftFile2 = testDir.appending("subdir").appending("file2.swift") + let swiftFile3 = testDir.appending("subdir").appending("nested").appending("file3.swift") + + try fileSystem.createDirectory(swiftFile2.parentDirectory, recursive: true) + try fileSystem.createDirectory(swiftFile3.parentDirectory, recursive: true) + + try fileSystem.writeFileContents(swiftFile1, string: "// Swift file 1") + try fileSystem.writeFileContents(swiftFile2, string: "// Swift file 2") + try fileSystem.writeFileContents(swiftFile3, string: "// Swift file 3") + + let results = try getFiles( + in: testDir, + matchingExtension: "swift", + recursive: recursive, + fileSystem: fileSystem + ) + + #expect(results.count == expectedCount, "\(description): expected \(expectedCount), got \(results.count)") + + // Always should contain the top-level file + #expect(results.contains(swiftFile1), "Should always contain top-level file") + + if recursive { + // Should contain nested files + #expect(results.contains(swiftFile2), "Recursive search should contain nested files") + #expect(results.contains(swiftFile3), "Recursive search should contain deeply nested files") + } else { + // Should not contain nested files + #expect(!results.contains(swiftFile2), "Non-recursive search should not contain nested files") + #expect(!results.contains(swiftFile3), "Non-recursive search should not contain deeply nested files") + } + } + + @Test( + arguments: [ + ( + recursive: true, + expectedCount: 2, + description: "recursive RelativePath search", + ), + ( + recursive: false, + expectedCount: 1, + description: "non-recursive RelativePath search", + ), + ] + ) + func testRelativePathRecursion( + recursive: Bool, + expectedCount: Int, + description: String, + ) throws { + let fileSystem = InMemoryFileSystem() + + // Set up current working directory + let cwd = try AbsolutePath(validating: "/current") + try fileSystem.createDirectory(cwd, recursive: true) + try fileSystem.changeCurrentWorkingDirectory(to: cwd) + + // Create test directory structure + let testDir = try RelativePath(validating: "test") + let absoluteTestDir = cwd.appending(testDir) + try fileSystem.createDirectory(absoluteTestDir, recursive: true) + + // Create files at different levels + let swiftFile1 = absoluteTestDir.appending("file1.swift") + let swiftFile2 = absoluteTestDir.appending("subdir").appending("file2.swift") + + try fileSystem.createDirectory(swiftFile2.parentDirectory, recursive: true) + try fileSystem.writeFileContents(swiftFile1, string: "// Swift file 1") + try fileSystem.writeFileContents(swiftFile2, string: "// Swift file 2") + + let results = try getFiles( + in: testDir, + matchingExtension: "swift", + recursive: recursive, + fileSystem: fileSystem + ) + + #expect(results.count == expectedCount, "\(description): expected \(expectedCount), got \(results.count)") + + let expectedFile1 = swiftFile1.relative(to: cwd) + #expect(results.contains(expectedFile1), "Should contain top-level file") + + let expectedFile2 = swiftFile2.relative(to: cwd) + if recursive { + #expect(results.contains(expectedFile2), "Recursive search should contain nested file") + } else { + #expect(!results.contains(expectedFile2), "Non-recursive search should not contain nested file") + } + } +}