diff --git a/Package.swift b/Package.swift index 2e68dae34de..34e6b5a5b94 100644 --- a/Package.swift +++ b/Package.swift @@ -568,7 +568,10 @@ let package = Package( "SourceKitLSPAPI", "SwiftBuildSupport", "Workspace" - ] + swiftTSCBasicsDeps + swiftToolsProtocolsDeps + ] + swiftTSCBasicsDeps + swiftToolsProtocolsDeps, + exclude: [ + "CMakeLists.txt", + ], ), // MARK: Commands diff --git a/Sources/Basics/Cancellator.swift b/Sources/Basics/Cancellator.swift index 7925e1297e8..b8ad51fea62 100644 --- a/Sources/Basics/Cancellator.swift +++ b/Sources/Basics/Cancellator.swift @@ -99,7 +99,7 @@ public final class Cancellator: Cancellable, Sendable { @discardableResult public func register(name: String, handler: @escaping CancellationHandler) -> RegistrationKey? { - if self.cancelling.get(default: false) { + if self.cancelling.get() { self.observabilityScope?.emit(debug: "not registering '\(name)' with terminator, termination in progress") return .none } diff --git a/Sources/Basics/Concurrency/AsyncProcess.swift b/Sources/Basics/Concurrency/AsyncProcess.swift index 3afc34a4d57..f18dff34719 100644 --- a/Sources/Basics/Concurrency/AsyncProcess.swift +++ b/Sources/Basics/Concurrency/AsyncProcess.swift @@ -509,8 +509,7 @@ package final class AsyncProcess { group.enter() stdoutPipe.fileHandleForReading.readabilityHandler = { (fh: FileHandle) in - // 4096 is default pipe buffer size so reading in that size seems most efficient and still get output as it available - let data = (try? fh.read(upToCount: 4096)) ?? Data() + let data = (try? fh.read(upToCount: Int.max)) ?? Data() if data.count == 0 { stdoutPipe.fileHandleForReading.readabilityHandler = nil group.leave() @@ -525,8 +524,7 @@ package final class AsyncProcess { group.enter() stderrPipe.fileHandleForReading.readabilityHandler = { (fh: FileHandle) in - // 4096 is default pipe buffer size so reading in that size seems most efficient and still get output as it available - let data = (try? fh.read(upToCount: 4096)) ?? Data() + let data = (try? fh.read(upToCount: Int.max)) ?? Data() if data.count == 0 { stderrPipe.fileHandleForReading.readabilityHandler = nil group.leave() @@ -806,7 +804,7 @@ package final class AsyncProcess { package func waitUntilExit() throws -> AsyncProcessResult { let group = DispatchGroup() group.enter() - let resultBox = ThreadSafeBox>() + let resultBox = ThreadSafeBox?>() self.waitUntilExit { result in resultBox.put(result) group.leave() diff --git a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift index 1007c598162..1057becfaa9 100644 --- a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift +++ b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift @@ -30,7 +30,7 @@ public enum Concurrency { public func unsafe_await(_ body: @Sendable @escaping () async -> T) -> T { let semaphore = DispatchSemaphore(value: 0) - let box = ThreadSafeBox() + let box = ThreadSafeBox() Task { let localValue: T = await body() box.mutate { _ in localValue } diff --git a/Sources/Basics/Concurrency/ThreadSafeBox.swift b/Sources/Basics/Concurrency/ThreadSafeBox.swift index 272415bcaaa..c5b04ad6dca 100644 --- a/Sources/Basics/Concurrency/ThreadSafeBox.swift +++ b/Sources/Basics/Concurrency/ThreadSafeBox.swift @@ -12,135 +12,211 @@ import class Foundation.NSLock -/// Thread-safe value boxing structure +/// Thread-safe value boxing structure that provides synchronized access to a wrapped value. @dynamicMemberLookup public final class ThreadSafeBox { - private var underlying: Value? + private var underlying: Value private let lock = NSLock() - public init() {} - + /// Creates a new thread-safe box with the given initial value. + /// + /// - Parameter seed: The initial value to store in the box. public init(_ seed: Value) { self.underlying = seed } - public func mutate(body: (Value?) throws -> Value?) rethrows { + /// Atomically mutates the stored value by applying a transformation function. + /// + /// The transformation function receives the current value and returns a new value + /// to replace it. The entire operation is performed under a lock to ensure atomicity. + /// + /// - Parameter body: A closure that takes the current value and returns a new value. + /// - Throws: Any error thrown by the transformation function. + public func mutate(body: (Value) throws -> Value) rethrows { try self.lock.withLock { let value = try body(self.underlying) self.underlying = value } } - - public func mutate(body: (inout Value?) throws -> ()) rethrows { + + /// Atomically mutates the stored value by applying an in-place transformation. + /// + /// The transformation function receives an inout reference to the current value, + /// allowing direct modification. The entire operation is performed under a lock + /// to ensure atomicity. + /// + /// - Parameter body: A closure that receives an inout reference to the current value. + /// - Throws: Any error thrown by the transformation function. + public func mutate(body: (inout Value) throws -> Void) rethrows { try self.lock.withLock { try body(&self.underlying) } } - @discardableResult - public func memoize(body: () throws -> Value) rethrows -> Value { - if let value = self.get() { - return value - } - let value = try body() + /// Atomically retrieves the current value from the box. + /// + /// - Returns: A copy of the current value stored in the box. + public func get() -> Value { self.lock.withLock { - self.underlying = value + self.underlying } - return value } - @discardableResult - public func memoize(body: () async throws -> Value) async rethrows -> Value { - if let value = self.get() { - return value - } - let value = try await body() + /// Atomically replaces the current value with a new value. + /// + /// - Parameter newValue: The new value to store in the box. + public func put(_ newValue: Value) { self.lock.withLock { - self.underlying = value + self.underlying = newValue } - return value } - public func clear() { + /// Provides thread-safe read-only access to properties of the wrapped value. + /// + /// This subscript allows you to access properties of the wrapped value using + /// dot notation while maintaining thread safety. + /// + /// - Parameter keyPath: A key path to a property of the wrapped value. + /// - Returns: The value of the specified property. + public subscript(dynamicMember keyPath: KeyPath) -> T { self.lock.withLock { - self.underlying = nil + self.underlying[keyPath: keyPath] } } - public func get() -> Value? { - self.lock.withLock { - self.underlying + /// Provides thread-safe read-write access to properties of the wrapped value. + /// + /// - Parameter keyPath: A writable key path to a property of the wrapped value. + /// - Returns: The value of the specified property when getting. + public subscript(dynamicMember keyPath: WritableKeyPath) -> T { + get { + self.lock.withLock { + self.underlying[keyPath: keyPath] + } + } + set { + self.lock.withLock { + self.underlying[keyPath: keyPath] = newValue + } } } +} - public func get(default: Value) -> Value { - self.lock.withLock { - self.underlying ?? `default` - } +// Extension for optional values to support empty initialization +extension ThreadSafeBox { + /// Creates a new thread-safe box initialized with nil for optional value types. + /// + /// This convenience initializer is only available when the wrapped value type is optional. + public convenience init() where Value == Wrapped? { + self.init(nil) } - public func put(_ newValue: Value) { + /// Takes the stored optional value, setting it to nil. + /// - Returns: The previously stored value, or nil if none was present. + public func takeValue() -> Value where Value == Wrapped? { self.lock.withLock { - self.underlying = newValue + guard let value = self.underlying else { return nil } + self.underlying = nil + return value } } - public func takeValue() -> Value where U? == Value { + /// Atomically sets the stored optional value to nil. + /// + /// This method is only available when the wrapped value type is optional. + public func clear() where Value == Wrapped? { self.lock.withLock { - guard let value = self.underlying else { return nil } self.underlying = nil - return value } } - public subscript(dynamicMember keyPath: KeyPath) -> T? { + /// Atomically retrieves the stored value, returning a default if nil. + /// + /// This method is only available when the wrapped value type is optional. + /// + /// - Parameter defaultValue: The value to return if the stored value is nil. + /// - Returns: The stored value if not nil, otherwise the default value. + public func get(default defaultValue: Wrapped) -> Wrapped where Value == Wrapped? { self.lock.withLock { - self.underlying?[keyPath: keyPath] + self.underlying ?? defaultValue } } - public subscript(dynamicMember keyPath: WritableKeyPath) -> T? { - get { - self.lock.withLock { - self.underlying?[keyPath: keyPath] + /// Atomically computes and caches a value if not already present. + /// + /// If the box already contains a non-nil value, that value is returned immediately. + /// Otherwise, the provided closure is executed to compute the value, which is then + /// stored and returned. This method is only available when the wrapped value type is optional. + /// + /// - Parameter body: A closure that computes the value to store if none exists. + /// - Returns: The cached value or the newly computed value. + /// - Throws: Any error thrown by the computation closure. + @discardableResult + public func memoize(body: () throws -> Wrapped) rethrows -> Wrapped where Value == Wrapped? { + try self.lock.withLock { + if let value = self.underlying { + return value } + let value = try body() + self.underlying = value + return value } - set { - self.lock.withLock { - if var value = self.underlying { - value[keyPath: keyPath] = newValue - } + } + + /// Atomically computes and caches an optional value if not already present. + /// + /// If the box already contains a non-nil value, that value is returned immediately. + /// Otherwise, the provided closure is executed to compute the value, which is then + /// stored and returned. This method is only available when the wrapped value type is optional. + /// + /// If the returned value is `nil` subsequent calls to `memoize` or `memoizeOptional` will + /// re-execute the closure. + /// + /// - Parameter body: A closure that computes the optional value to store if none exists. + /// - Returns: The cached value or the newly computed value (which may be nil). + /// - Throws: Any error thrown by the computation closure. + @discardableResult + public func memoizeOptional(body: () throws -> Wrapped?) rethrows -> Wrapped? where Value == Wrapped? { + try self.lock.withLock { + if let value = self.underlying { + return value } + let value = try body() + self.underlying = value + return value } } } extension ThreadSafeBox where Value == Int { + /// Atomically increments the stored integer value by 1. + /// + /// This method is only available when the wrapped value type is Int. public func increment() { self.lock.withLock { - if let value = self.underlying { - self.underlying = value + 1 - } + self.underlying = self.underlying + 1 } } + /// Atomically decrements the stored integer value by 1. + /// + /// This method is only available when the wrapped value type is Int. public func decrement() { self.lock.withLock { - if let value = self.underlying { - self.underlying = value - 1 - } + self.underlying = self.underlying - 1 } } } extension ThreadSafeBox where Value == String { + /// Atomically appends a string to the stored string value. + /// + /// This method is only available when the wrapped value type is String. + /// + /// - Parameter value: The string to append to the current stored value. public func append(_ value: String) { self.mutate { existingValue in - if let existingValue { - return existingValue + value - } else { - return value - } + existingValue + value } } } diff --git a/Sources/Basics/Observability.swift b/Sources/Basics/Observability.swift index 7d915fd1d1f..136aa5d0c09 100644 --- a/Sources/Basics/Observability.swift +++ b/Sources/Basics/Observability.swift @@ -163,7 +163,7 @@ public final class ObservabilityScope: DiagnosticsEmitterProtocol, Sendable, Cus } var errorsReported: Bool { - self._errorsReported.get() ?? false + self._errorsReported.get() } } } diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 831689605a4..5eb99dce8bf 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -171,10 +171,10 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } /// The build description resulting from planing. - private let buildDescription = ThreadSafeBox() + private let buildDescription = AsyncThrowingValueMemoizer() /// The loaded package graph. - private let packageGraph = ThreadSafeBox() + private let packageGraph = AsyncThrowingValueMemoizer() /// File system to operate on. private var fileSystem: Basics.FileSystem { diff --git a/Sources/Build/ClangSupport.swift b/Sources/Build/ClangSupport.swift index af5fd8581a5..fe4fc3bc687 100644 --- a/Sources/Build/ClangSupport.swift +++ b/Sources/Build/ClangSupport.swift @@ -24,7 +24,7 @@ public enum ClangSupport { let features: [Feature] } - private static var cachedFeatures = ThreadSafeBox() + private static var cachedFeatures = ThreadSafeBox() public static func supportsFeature(name: String, toolchain: PackageModel.Toolchain) throws -> Bool { let features = try cachedFeatures.memoize { diff --git a/Sources/Build/LLBuildProgressTracker.swift b/Sources/Build/LLBuildProgressTracker.swift index 63a6a6caace..ec5d85fb06d 100644 --- a/Sources/Build/LLBuildProgressTracker.swift +++ b/Sources/Build/LLBuildProgressTracker.swift @@ -96,7 +96,7 @@ public final class BuildExecutionContext { // MARK: - Private - private var indexStoreAPICache = ThreadSafeBox>() + private var indexStoreAPICache = ThreadSafeBox?>() /// Reference to the index store API. var indexStoreAPI: Result { diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index 5c0a2cb9412..cb8d0497c00 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -1223,7 +1223,7 @@ final class ParallelTestRunner { } self.finishedTests.enqueue(TestResult( unitTest: test, - output: output.get() ?? "", + output: output.get(), success: result != .failure, duration: duration )) diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index 81b32961376..2d6d12bebf8 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -242,10 +242,10 @@ enum TestingSupport { // Since XCTestHelper targets macOS, we need the macOS platform paths here. if let sdkPlatformPaths = try? SwiftSDK.sdkPlatformPaths(for: .macOS) { // appending since we prefer the user setting (if set) to the one we inject - for frameworkPath in sdkPlatformPaths.frameworks { + for frameworkPath in sdkPlatformPaths.runtimeFrameworkSearchPaths { env.appendPath(key: "DYLD_FRAMEWORK_PATH", value: frameworkPath.pathString) } - for libraryPath in sdkPlatformPaths.libraries { + for libraryPath in sdkPlatformPaths.runtimeLibrarySearchPaths { env.appendPath(key: "DYLD_LIBRARY_PATH", value: libraryPath.pathString) } } diff --git a/Sources/DriverSupport/DriverSupportUtils.swift b/Sources/DriverSupport/DriverSupportUtils.swift index 1497ec1b94a..1f7ccfadd19 100644 --- a/Sources/DriverSupport/DriverSupportUtils.swift +++ b/Sources/DriverSupport/DriverSupportUtils.swift @@ -19,7 +19,7 @@ import class TSCBasic.Process import struct TSCBasic.ProcessResult public enum DriverSupport { - private static var flagsMap = ThreadSafeBox<[String: Set]>() + private static var flagsMap = ThreadSafeBox<[String: Set]>([:]) /// This checks _frontend_ supported flags, which are not necessarily supported in the driver. public static func checkSupportedFrontendFlags( @@ -29,8 +29,9 @@ public enum DriverSupport { ) -> Bool { let trimmedFlagSet = Set(flags.map { $0.trimmingCharacters(in: ["-"]) }) let swiftcPathString = toolchain.swiftCompilerPath.pathString + let entry = flagsMap.get() - if let entry = flagsMap.get(), let cachedSupportedFlagSet = entry[swiftcPathString + "-frontend"] { + if let cachedSupportedFlagSet = entry[swiftcPathString + "-frontend"] { return cachedSupportedFlagSet.intersection(trimmedFlagSet) == trimmedFlagSet } do { @@ -63,8 +64,9 @@ public enum DriverSupport { ) -> Bool { let trimmedFlagSet = Set(flags.map { $0.trimmingCharacters(in: ["-"]) }) let swiftcPathString = toolchain.swiftCompilerPath.pathString + let entry = flagsMap.get() - if let entry = flagsMap.get(), let cachedSupportedFlagSet = entry[swiftcPathString + "-driver"] { + if let cachedSupportedFlagSet = entry[swiftcPathString + "-driver"] { return cachedSupportedFlagSet.intersection(trimmedFlagSet) == trimmedFlagSet } do { diff --git a/Sources/LLBuildManifest/LLBuildManifestWriter.swift b/Sources/LLBuildManifest/LLBuildManifestWriter.swift index d902aca8ca0..f4a6375683d 100644 --- a/Sources/LLBuildManifest/LLBuildManifestWriter.swift +++ b/Sources/LLBuildManifest/LLBuildManifestWriter.swift @@ -129,63 +129,63 @@ public struct ManifestToolStream { fileprivate var buffer = "" public subscript(key: String) -> Int { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return an Int") } set { self.buffer += " \(key): \(newValue.description.asJSON)\n" } } public subscript(key: String) -> String { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return a String") } set { self.buffer += " \(key): \(newValue.asJSON)\n" } } public subscript(key: String) -> ToolProtocol { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return a ToolProtocol") } set { self.buffer += " \(key): \(type(of: newValue).name)\n" } } public subscript(key: String) -> AbsolutePath { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return an AbsolutePath") } set { self.buffer += " \(key): \(newValue.pathString.asJSON)\n" } } public subscript(key: String) -> [AbsolutePath] { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return an array of AbsolutePath") } set { self.buffer += " \(key): \(newValue.map(\.pathString).asJSON)\n" } } public subscript(key: String) -> [Node] { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return an array of Node") } set { self.buffer += " \(key): \(newValue.map(\.encodingName).asJSON)\n" } } public subscript(key: String) -> Bool { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return a Bool") } set { self.buffer += " \(key): \(newValue.description)\n" } } public subscript(key: String) -> [String] { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return an array of String") } set { self.buffer += " \(key): \(newValue.asJSON)\n" } } public subscript(key: String) -> [String: String] { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return a [String: String]") } set { self.buffer += " \(key):\n" for (key, value) in newValue.sorted(by: { $0.key < $1.key }) { @@ -195,7 +195,7 @@ public struct ManifestToolStream { } package subscript(key: String) -> Environment { - get { fatalError() } + get { fatalError("\(#file):\(#line) at function \(#function) - Cannot get subscript that return an Environment") } set { self.buffer += " \(key):\n" for (key, value) in newValue.sorted(by: { $0.key < $1.key }) { diff --git a/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift b/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift index 295a496deb7..f499a831858 100644 --- a/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift +++ b/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift @@ -50,7 +50,7 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable private let ftsLock = NSLock() // FTS not supported on some platforms; the code falls back to "slow path" in that case // marked internal for testing - internal let useSearchIndices = ThreadSafeBox() + internal let useSearchIndices = ThreadSafeBox(false) // Targets have in-memory trie in addition to SQLite FTS as optimization private let targetTrie = Trie() @@ -725,9 +725,10 @@ final class SQLitePackageCollectionsStorage: PackageCollectionsStorage, Closable private func shouldUseSearchIndices() throws -> Bool { // Make sure createSchemaIfNecessary is called and useSearchIndices is set before reading it try self.withDB { _ in - self.useSearchIndices.get() ?? false + self.useSearchIndices.get() } } + internal func populateTargetTrie() async throws { try await withCheckedThrowingContinuation { continuation in self.populateTargetTrie(callback: { diff --git a/Sources/PackageGraph/VersionSetSpecifier.swift b/Sources/PackageGraph/VersionSetSpecifier.swift index e32a556ed65..6a20f9c81f3 100644 --- a/Sources/PackageGraph/VersionSetSpecifier.swift +++ b/Sources/PackageGraph/VersionSetSpecifier.swift @@ -254,7 +254,7 @@ extension VersionSetSpecifier { case (_, .any): return .empty case (.any, _): - fatalError() + fatalError("\(#file):\(#line) - Illegal call of \(#function) on left hand side value of `.any`") case (.empty, _): return .empty case (_, .empty): diff --git a/Sources/PackageModel/EnabledTrait.swift b/Sources/PackageModel/EnabledTrait.swift index 103c1042a6c..ae798b5b273 100644 --- a/Sources/PackageModel/EnabledTrait.swift +++ b/Sources/PackageModel/EnabledTrait.swift @@ -103,12 +103,10 @@ public struct EnabledTraitsMap { } public subscript(key: PackageIdentity) -> EnabledTraits { - get { storage.get()?.traits[key] ?? ["default"] } + get { storage.get().traits[key] ?? ["default"] } set { - storage.mutate { state -> Storage? in - guard var state = state else { - return Storage() - } + storage.mutate { (state: Storage) -> Storage in + var state = state // Omit adding "default" explicitly, since the map returns "default" // if there are no explicit traits enabled. This will allow us to check @@ -157,7 +155,7 @@ public struct EnabledTraitsMap { /// - Parameter key: The package identity to query. /// - Returns: The set of setters that disabled default traits, or `nil` if no disablers exist. public subscript(disablersFor key: PackageIdentity) -> Set? { - storage.get()?._disablers[key] + storage.get()._disablers[key] } /// Returns the set of setters that explicitly disabled default traits for a package identified by a string. @@ -178,7 +176,7 @@ public struct EnabledTraitsMap { /// - Parameter key: The package identity to query. /// - Returns: The set of setters that requested default traits, or `nil` if no default setters exist. public subscript(defaultSettersFor key: PackageIdentity) -> Set? { - storage.get()?._defaultSetters[key] + storage.get()._defaultSetters[key] } /// Returns the set of setters that requested default traits for a package identified by a string. @@ -196,7 +194,7 @@ public struct EnabledTraitsMap { /// - Parameter key: The package identity to query. /// - Returns: The explicitly enabled traits, or `nil` if no traits were explicitly set (meaning the package uses defaults). public subscript(explicitlyEnabledTraitsFor key: PackageIdentity) -> EnabledTraits? { - storage.get()?.traits[key] + storage.get().traits[key] } /// Returns a list of traits that were explicitly enabled for a given package. @@ -211,7 +209,7 @@ public struct EnabledTraitsMap { /// Returns a dictionary literal representation of the enabled traits map. public var dictionaryLiteral: [PackageIdentity: EnabledTraits] { - return storage.get()?.traits ?? [:] + return storage.get().traits } } diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index bef655063af..5e5b95908cf 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -593,10 +593,10 @@ public struct SwiftSDK: Equatable { #if os(macOS) do { let sdkPaths = try SwiftSDK.sdkPlatformPaths(for: darwinPlatform, environment: environment) - extraCCFlags.append(contentsOf: sdkPaths.frameworks.flatMap { ["-F", $0.pathString] }) - extraSwiftCFlags.append(contentsOf: sdkPaths.frameworks.flatMap { ["-F", $0.pathString] }) - extraSwiftCFlags.append(contentsOf: sdkPaths.libraries.flatMap { ["-I", $0.pathString] }) - extraSwiftCFlags.append(contentsOf: sdkPaths.libraries.flatMap { ["-L", $0.pathString] }) + extraCCFlags.append(contentsOf: sdkPaths.buildTimeFrameworkSearchPaths.flatMap { ["-F", $0.pathString] }) + extraSwiftCFlags.append(contentsOf: sdkPaths.buildTimeFrameworkSearchPaths.flatMap { ["-F", $0.pathString] }) + extraSwiftCFlags.append(contentsOf: sdkPaths.buildTimeLibrarySearchPaths.flatMap { ["-I", $0.pathString] }) + extraSwiftCFlags.append(contentsOf: sdkPaths.buildTimeLibrarySearchPaths.flatMap { ["-L", $0.pathString] }) xctestSupport = .supported } catch { xctestSupport = .unsupported(reason: String(describing: error)) @@ -628,11 +628,21 @@ public struct SwiftSDK: Equatable { /// /// - SeeAlso: ``sdkPlatformPaths(for:environment:)`` public struct PlatformPaths { - /// Paths of directories containing auxiliary platform frameworks. - public var frameworks: [Basics.AbsolutePath] + /// Paths of directories containing auxiliary platform frameworks which + /// should be included as framework search paths at build time. + public var buildTimeFrameworkSearchPaths: [Basics.AbsolutePath] - /// Paths of directories containing auxiliary platform libraries. - public var libraries: [Basics.AbsolutePath] + /// Paths of directories containing auxiliary platform libraries which + /// should be included as library search paths at build time. + public var buildTimeLibrarySearchPaths: [Basics.AbsolutePath] + + /// Paths of directories containing auxiliary platform frameworks which + /// should be included as framework search paths at runtime. + public var runtimeFrameworkSearchPaths: [Basics.AbsolutePath] + + /// Paths of directories containing auxiliary platform libraries which + /// should be included as library search paths at runtime. + public var runtimeLibrarySearchPaths: [Basics.AbsolutePath] } /// Returns `macosx` sdk platform framework path. @@ -641,10 +651,10 @@ public struct SwiftSDK: Equatable { environment: Environment = .current ) throws -> (fwk: Basics.AbsolutePath, lib: Basics.AbsolutePath) { let paths = try sdkPlatformPaths(for: .macOS, environment: environment) - guard let frameworkPath = paths.frameworks.first else { + guard let frameworkPath = paths.buildTimeFrameworkSearchPaths.first else { throw StringError("could not determine SDK platform framework path") } - guard let libraryPath = paths.libraries.first else { + guard let libraryPath = paths.buildTimeLibrarySearchPaths.first else { throw StringError("could not determine SDK platform library path") } return (fwk: frameworkPath, lib: libraryPath) @@ -682,7 +692,12 @@ public struct SwiftSDK: Equatable { components: "Developer", "usr", "lib" ) - let sdkPlatformFrameworkPath = PlatformPaths(frameworks: [frameworksPath, privateFrameworksPath], libraries: [librariesPath]) + let sdkPlatformFrameworkPath = PlatformPaths( + buildTimeFrameworkSearchPaths: [frameworksPath /* omit privateFrameworksPath */], + buildTimeLibrarySearchPaths: [librariesPath], + runtimeFrameworkSearchPaths: [frameworksPath, privateFrameworksPath], + runtimeLibrarySearchPaths: [librariesPath] + ) _sdkPlatformFrameworkPath[darwinPlatform] = sdkPlatformFrameworkPath return sdkPlatformFrameworkPath } diff --git a/Sources/PackageModel/UserToolchain.swift b/Sources/PackageModel/UserToolchain.swift index 36c27c03389..4bb37aa6965 100644 --- a/Sources/PackageModel/UserToolchain.swift +++ b/Sources/PackageModel/UserToolchain.swift @@ -43,7 +43,7 @@ public final class UserToolchain: Toolchain { public let librarySearchPaths: [AbsolutePath] /// Thread-safe cached runtime library paths - private let _runtimeLibraryPaths = ThreadSafeBox<[AbsolutePath]>() + private let _runtimeLibraryPaths = ThreadSafeBox<[AbsolutePath]?>() /// An array of paths to use with binaries produced by this toolchain at run time. public var runtimeLibraryPaths: [AbsolutePath] { @@ -94,7 +94,7 @@ public final class UserToolchain: Toolchain { private let _cachedTargetInfo = ThreadSafeBox() private var targetInfo: JSON? { - return _cachedTargetInfo.memoize { + return _cachedTargetInfo.memoizeOptional { // Only call out to the swift compiler to fetch the target info when necessary return try? _targetInfo ?? Self.getTargetInfo(swiftCompiler: swiftCompilerPath) } @@ -105,7 +105,7 @@ public final class UserToolchain: Toolchain { // A version string that can be used to identify the swift compiler version public var swiftCompilerVersion: String? { - return _swiftCompilerVersion.memoize { + return _swiftCompilerVersion.memoizeOptional { guard let targetInfo = self.targetInfo else { return nil } diff --git a/Sources/QueryEngine/Query.swift b/Sources/QueryEngine/Query.swift index d4f300c8d7f..de4e9e91087 100644 --- a/Sources/QueryEngine/Query.swift +++ b/Sources/QueryEngine/Query.swift @@ -156,7 +156,7 @@ extension HashEncoder: UnkeyedEncodingContainer { } func superEncoder() -> any Encoder { - fatalError() + fatalError("\(#file):\(#line) - Illegal call of function \(#function)") } } @@ -272,11 +272,11 @@ extension HashEncoder { } mutating func superEncoder() -> any Encoder { - fatalError() + fatalError("\(#file):\(#line) - Illegal call of function \(#function)") } mutating func superEncoder(forKey key: K) -> any Encoder { - fatalError() + fatalError("\(#file):\(#line) - Illegal call of function \(#function)") } typealias Key = K diff --git a/Sources/Runtimes/PackageDescription/PackageDependency.swift b/Sources/Runtimes/PackageDescription/PackageDependency.swift index 06cf9912343..c61729bc397 100644 --- a/Sources/Runtimes/PackageDescription/PackageDependency.swift +++ b/Sources/Runtimes/PackageDescription/PackageDependency.swift @@ -165,7 +165,7 @@ extension Package { traits: traits ) } - + convenience init( name: String?, location: String, @@ -181,7 +181,7 @@ extension Package { traits: traits ) } - + convenience init( id: String, requirement: RegistryRequirement, @@ -1067,7 +1067,7 @@ extension Package.Dependency { /// ```swift /// .package(id: "scope.name", "1.2.3"..."1.2.6"), /// ``` - /// + /// /// If the package you depend on defines traits, the package manager uses the dependency with its default set of traits. /// /// - Parameters: @@ -1140,11 +1140,11 @@ extension Package.Dependency { extension Package.Dependency { @available(*, unavailable, message: "use package(url:exact:) instead") public static func package(url: String, version: Version) -> Package.Dependency { - fatalError() + fatalError("\(#file):\(#line) - Illegal call of deprecated function \(#function)") } @available(*, unavailable, message: "use package(url:_:) instead") public static func package(url: String, range: Range) -> Package.Dependency { - fatalError() + fatalError("\(#file):\(#line) - Illegal call of deprecated function \(#function)") } } diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index a9fe2744794..27b65aaa7ab 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -332,7 +332,7 @@ public struct BuildParameters: Encodable { case .library(.dynamic): return try dynamicLibraryPath(for: product.name) case .library(.automatic), .plugin: - fatalError() + fatalError("\(#file):\(#line) - Illegal call of function \(#function) with automatica library and plugin") case .test: switch buildSystemKind { case .native, .xcode: diff --git a/Sources/SPMBuildCore/Plugins/DefaultPluginScriptRunner.swift b/Sources/SPMBuildCore/Plugins/DefaultPluginScriptRunner.swift index a5b450a6f5a..f20e7ed822a 100644 --- a/Sources/SPMBuildCore/Plugins/DefaultPluginScriptRunner.swift +++ b/Sources/SPMBuildCore/Plugins/DefaultPluginScriptRunner.swift @@ -37,7 +37,7 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable { private let cancellator: Cancellator private let verboseOutput: Bool - private let sdkRootCache = ThreadSafeBox() + private let sdkRootCache = ThreadSafeBox() public init( fileSystem: Basics.FileSystem, @@ -402,26 +402,23 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable { /// Returns path to the sdk, if possible. // FIXME: This is copied from ManifestLoader. This should be consolidated when ManifestLoader is cleaned up. private func sdkRoot() -> Basics.AbsolutePath? { - if let sdkRoot = self.sdkRootCache.get() { - return sdkRoot - } - - var sdkRootPath: Basics.AbsolutePath? - // Find SDKROOT on macOS using xcrun. - #if os(macOS) - let foundPath = try? AsyncProcess.checkNonZeroExit( - args: "/usr/bin/xcrun", "--sdk", "macosx", "--show-sdk-path" - ) - guard let sdkRoot = foundPath?.spm_chomp(), !sdkRoot.isEmpty else { - return nil - } - if let path = try? Basics.AbsolutePath(validating: sdkRoot) { - sdkRootPath = path - self.sdkRootCache.put(path) - } - #endif + return self.sdkRootCache.memoizeOptional(body: { + var sdkRootPath: Basics.AbsolutePath? + // Find SDKROOT on macOS using xcrun. + #if os(macOS) + let foundPath = try? AsyncProcess.checkNonZeroExit( + args: "/usr/bin/xcrun", "--sdk", "macosx", "--show-sdk-path" + ) + guard let sdkRoot = foundPath?.spm_chomp(), !sdkRoot.isEmpty else { + return nil + } + if let path = try? Basics.AbsolutePath(validating: sdkRoot) { + sdkRootPath = path + } + #endif - return sdkRootPath + return sdkRootPath + }) } /// Private function that invokes a compiled plugin executable and communicates with it until it finishes. diff --git a/Sources/SourceControl/GitRepository.swift b/Sources/SourceControl/GitRepository.swift index 6dbdf063275..33674c623fd 100644 --- a/Sources/SourceControl/GitRepository.swift +++ b/Sources/SourceControl/GitRepository.swift @@ -410,10 +410,10 @@ public final class GitRepository: Repository, WorkingCheckout { private var cachedHashes = ThreadSafeKeyValueStore() private var cachedBlobs = ThreadSafeKeyValueStore() private var cachedTrees = ThreadSafeKeyValueStore() - private var cachedTags = ThreadSafeBox<[String]>() - private var cachedBranches = ThreadSafeBox<[String]>() - private var cachedIsBareRepo = ThreadSafeBox() - private var cachedHasSubmodules = ThreadSafeBox() + private var cachedTags = ThreadSafeBox<[String]?>() + private var cachedBranches = ThreadSafeBox<[String]?>() + private var cachedIsBareRepo = ThreadSafeBox() + private var cachedHasSubmodules = ThreadSafeBox() public convenience init(path: AbsolutePath, isWorkingRepo: Bool = true, cancellator: Cancellator? = .none) { // used in one-off operations on git repo, as such the terminator is not ver important diff --git a/Sources/SourceControl/Repository.swift b/Sources/SourceControl/Repository.swift index 71a438681c1..043ef427abd 100644 --- a/Sources/SourceControl/Repository.swift +++ b/Sources/SourceControl/Repository.swift @@ -47,7 +47,7 @@ public struct RepositorySpecifier: Hashable, Sendable { if basename.hasSuffix(".git") { basename = String(basename.dropLast(4)) } - if basename == "/" { + if basename == "/" || basename == "\\" { return "" } return basename diff --git a/Sources/SourceControl/RepositoryManager.swift b/Sources/SourceControl/RepositoryManager.swift index 248a2be308a..104f89a64c6 100644 --- a/Sources/SourceControl/RepositoryManager.swift +++ b/Sources/SourceControl/RepositoryManager.swift @@ -343,12 +343,12 @@ public class RepositoryManager: Cancellable { // If we are offline and have a valid cached repository, use the cache anyway. if try isOffline(error) && self.provider.isValidDirectory(cachedRepositoryPath, for: handle.repository) { // For the first offline use in the lifetime of this repository manager, emit a warning. - var warningState = self.emitNoConnectivityWarning.get(default: [:]) - if !(warningState[handle.repository.url] ?? false) { - warningState[handle.repository.url] = true - self.emitNoConnectivityWarning.put(warningState) - observabilityScope.emit(warning: "no connectivity to \(handle.repository.url), using previously cached repository state") - } + self.emitNoConnectivityWarning.mutate(body: { + if !$0[handle.repository.url, default: false] { + $0[handle.repository.url] = true + observabilityScope.emit(warning: "no connectivity to \(handle.repository.url), using previously cached repository state") + } + }) observabilityScope.emit(info: "using previously cached repository state for \(package)") cacheUsed = true diff --git a/Sources/SwiftBuildSupport/CMakeLists.txt b/Sources/SwiftBuildSupport/CMakeLists.txt index 43956e93f4b..8b6a335b34b 100644 --- a/Sources/SwiftBuildSupport/CMakeLists.txt +++ b/Sources/SwiftBuildSupport/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(SwiftBuildSupport STATIC BuildSystem.swift + Diagnostics+Extensions.swift DotPIFSerializer.swift PackagePIFBuilder.swift PackagePIFBuilder+Helpers.swift diff --git a/Sources/SwiftBuildSupport/Diagnostics+Extensions.swift b/Sources/SwiftBuildSupport/Diagnostics+Extensions.swift new file mode 100644 index 00000000000..06d112ae5d5 --- /dev/null +++ b/Sources/SwiftBuildSupport/Diagnostics+Extensions.swift @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +extension Basics.Diagnostic { + package static var swiftBackDeployWarning: Self { + .warning( + """ + Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by \ + default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an \ + optional "Swift 5 Runtime Support for Command Line Tools" package that can be downloaded from \"More Downloads\" \ + for Apple Developers at https://developer.apple.com/download/more/ + """ + ) + } +} diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index 6e1abe5ac9d..269a941787b 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -403,7 +403,7 @@ public final class PackagePIFBuilder { case .packageProduct: .packageProduct case .hostBuildTool: fatalError("Unexpected hostBuildTool type") @unknown default: - fatalError() + fatalError("Unknown product type: \(pifProductType)") } } } diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index b2a9ee05ffb..e5f4204aebd 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -120,7 +120,7 @@ func withSession( package final class SwiftBuildSystemPlanningOperationDelegate: SWBPlanningOperationDelegate, SWBIndexingDelegate, Sendable { package init() {} - + public func provisioningTaskInputs( targetGUID: String, provisioningSourceData: SWBProvisioningTaskInputsSourceData @@ -762,7 +762,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { // native build system. settings["SWIFT_EXEC"] = buildParameters.toolchain.swiftCompilerPath.pathString } - + let overrideToolchains = [buildParameters.toolchain.metalToolchainId, toolchainID?.rawValue].compactMap { $0 } if !overrideToolchains.isEmpty { settings["TOOLCHAINS"] = (overrideToolchains + ["$(inherited)"]).joined(separator: " ") @@ -920,7 +920,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { } try settings.merge(Self.constructDebuggingSettingsOverrides(from: buildParameters.debuggingParameters), uniquingKeysWith: reportConflict) try settings.merge(Self.constructDriverSettingsOverrides(from: buildParameters.driverParameters), uniquingKeysWith: reportConflict) - try settings.merge(Self.constructLinkerSettingsOverrides(from: buildParameters.linkingParameters), uniquingKeysWith: reportConflict) + try settings.merge(self.constructLinkerSettingsOverrides(from: buildParameters.linkingParameters, triple: buildParameters.triple), uniquingKeysWith: reportConflict) try settings.merge(Self.constructTestingSettingsOverrides(from: buildParameters.testingParameters), uniquingKeysWith: reportConflict) try settings.merge(Self.constructAPIDigesterSettingsOverrides(from: buildParameters.apiDigesterMode), uniquingKeysWith: reportConflict) @@ -937,9 +937,19 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { return params } - public func makeBuildRequest(session: SWBBuildServiceSession, configuredTargets: [SWBTargetGUID], derivedDataPath: Basics.AbsolutePath, symbolGraphOptions: BuildOutput.SymbolGraphOptions?) async throws -> SWBBuildRequest { + public func makeBuildRequest( + session: SWBBuildServiceSession, + configuredTargets: [SWBTargetGUID], + derivedDataPath: Basics.AbsolutePath, + symbolGraphOptions: BuildOutput.SymbolGraphOptions?, + setToolchainSetting: Bool = true, + ) async throws -> SWBBuildRequest { var request = SWBBuildRequest() - request.parameters = try await makeBuildParameters(session: session, symbolGraphOptions: symbolGraphOptions) + request.parameters = try await makeBuildParameters( + session: session, + symbolGraphOptions: symbolGraphOptions, + setToolchainSetting: setToolchainSetting, + ) request.configuredTargets = configuredTargets.map { SWBConfiguredTarget(guid: $0.rawValue, parameters: request.parameters) } request.useParallelTargets = true request.useImplicitDependencies = false @@ -947,6 +957,7 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { request.hideShellScriptEnvironment = true request.showNonLoggedProgress = true request.recordBuildBacktraces = buildParameters.outputParameters.enableTaskBacktraces + request.schedulerLaneWidthOverride = buildParameters.workers // Override the arena. We need to apply the arena info to both the request-global build // parameters as well as the target-specific build parameters, since they may have been @@ -1007,7 +1018,10 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { return settings } - private static func constructLinkerSettingsOverrides(from parameters: BuildParameters.Linking) -> [String: String] { + private func constructLinkerSettingsOverrides( + from parameters: BuildParameters.Linking, + triple: Triple, + ) -> [String: String] { var settings: [String: String] = [:] if parameters.linkerDeadStrip { @@ -1025,7 +1039,19 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { break } - // TODO: shouldLinkStaticSwiftStdlib + if triple.isDarwin() && parameters.shouldLinkStaticSwiftStdlib { + self.observabilityScope.emit(Basics.Diagnostic.swiftBackDeployWarning) + } else { + if parameters.shouldLinkStaticSwiftStdlib { + settings["SWIFT_FORCE_STATIC_LINK_STDLIB"] = "YES" + } else { + settings["SWIFT_FORCE_STATIC_LINK_STDLIB"] = "NO" + } + } + + if let resourcesPath = self.buildParameters.toolchain.swiftResourcesPath(isStatic: parameters.shouldLinkStaticSwiftStdlib) { + settings["SWIFT_RESOURCE_DIR"] = resourcesPath.pathString + } return settings } diff --git a/Sources/SwiftPMBuildServer/SwiftPMBuildServer.swift b/Sources/SwiftPMBuildServer/SwiftPMBuildServer.swift index 8997851d5af..a92c75d9858 100644 --- a/Sources/SwiftPMBuildServer/SwiftPMBuildServer.swift +++ b/Sources/SwiftPMBuildServer/SwiftPMBuildServer.swift @@ -165,6 +165,9 @@ public actor SwiftPMBuildServer: QueueBasedMessageHandler { case is OnBuildLogMessageNotification: // If we receive a build log message notification, forward it on to the client connectionToClient.send(notification) + case is OnBuildTargetDidChangeNotification: + // If the underlying server notifies us of target updates, forward the notification to the client + connectionToClient.send(notification) default: logToClient(.warning, "SwiftPM build server received unknown notification type: \(notification)") } diff --git a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift index 2517c3d7f9c..4bbf8333ddc 100644 --- a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift @@ -39,7 +39,7 @@ public struct FileSystemPackageContainer: PackageContainer { private let observabilityScope: ObservabilityScope /// cached version of the manifest - private let manifest = ThreadSafeBox() + private let manifest = AsyncThrowingValueMemoizer() public init( package: PackageReference, @@ -68,7 +68,7 @@ public struct FileSystemPackageContainer: PackageContainer { } private func loadManifest() async throws -> Manifest { - try await manifest.memoize() { + try await manifest.memoize { let packagePath: AbsolutePath switch self.package.kind { case .root(let path), .fileSystem(let path): diff --git a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift index 1279e0d7074..7b42c5c6907 100644 --- a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift @@ -31,7 +31,7 @@ public class RegistryPackageContainer: PackageContainer { private let currentToolsVersion: ToolsVersion private let observabilityScope: ObservabilityScope - private var knownVersionsCache = ThreadSafeBox<[Version]>() + private var knownVersionsCache = AsyncThrowingValueMemoizer<[Version]>() private var toolsVersionsCache = ThrowingAsyncKeyValueMemoizer() private var validToolsVersionsCache = AsyncKeyValueMemoizer() private var manifestsCache = ThrowingAsyncKeyValueMemoizer() diff --git a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index 89d05743699..70b8affa0a9 100644 --- a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -69,7 +69,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri private var dependenciesCache = [String: [ProductFilter: (Manifest, [Constraint])]]() private var dependenciesCacheLock = NSLock() - private var knownVersionsCache = ThreadSafeBox<[Version: String]>() + private var knownVersionsCache = ThreadSafeBox<[Version: String]?>() private var manifestsCache = ThrowingAsyncKeyValueMemoizer() private var toolsVersionsCache = ThreadSafeKeyValueStore() diff --git a/Sources/_InternalTestSupport/MockBuildTestHelper.swift b/Sources/_InternalTestSupport/MockBuildTestHelper.swift index ecbdf5db37d..0648ff1b89a 100644 --- a/Sources/_InternalTestSupport/MockBuildTestHelper.swift +++ b/Sources/_InternalTestSupport/MockBuildTestHelper.swift @@ -98,6 +98,7 @@ public func mockBuildParameters( enableXCFrameworksOnLinux: Bool = false, prepareForIndexing: BuildParameters.PrepareForIndexingMode = .off, sanitizers: [Sanitizer] = [], + numberOfWorkers: UInt32 = 3, ) -> BuildParameters { try! BuildParameters( destination: destination, @@ -108,7 +109,7 @@ public func mockBuildParameters( flags: flags, buildSystemKind: buildSystemKind, pkgConfigDirectories: [], - workers: 3, + workers: numberOfWorkers, sanitizers: EnabledSanitizers(Set(sanitizers)), indexStoreMode: indexStoreMode, prepareForIndexing: prepareForIndexing, diff --git a/Sources/_InternalTestSupport/SwiftTesting+Tags.swift b/Sources/_InternalTestSupport/SwiftTesting+Tags.swift index 66d6c441ccc..1801424cfbd 100644 --- a/Sources/_InternalTestSupport/SwiftTesting+Tags.swift +++ b/Sources/_InternalTestSupport/SwiftTesting+Tags.swift @@ -34,6 +34,7 @@ extension Tag.FunctionalArea { @Tag public static var PIF: Tag @Tag public static var IndexMode: Tag @Tag public static var Sanitizer: Tag + @Tag public static var LinkSwiftStaticStdlib: Tag } extension Tag.Feature { diff --git a/Sources/_InternalTestSupport/Toolchain.swift b/Sources/_InternalTestSupport/Toolchain.swift index 3bbb62cb620..5746ae53dd4 100644 --- a/Sources/_InternalTestSupport/Toolchain.swift +++ b/Sources/_InternalTestSupport/Toolchain.swift @@ -37,7 +37,10 @@ package func resolveBinDir() throws -> AbsolutePath { #if os(macOS) return try macOSBundleRoot() #else - return try AbsolutePath(validating: CommandLine.arguments[0], relativeTo: localFileSystem.currentWorkingDirectory!).parentDirectory + guard let cwd = localFileSystem.currentWorkingDirectory else { + fatalError("Current working directory unavailable!") + } + return try AbsolutePath(validating: CommandLine.arguments[0], relativeTo: cwd).parentDirectory #endif } diff --git a/Tests/BasicsTests/AsyncProcessTests.swift b/Tests/BasicsTests/AsyncProcessTests.swift index 0bc3b94cb32..283c218a450 100644 --- a/Tests/BasicsTests/AsyncProcessTests.swift +++ b/Tests/BasicsTests/AsyncProcessTests.swift @@ -54,16 +54,14 @@ final class AsyncProcessTests: XCTestCase { } func testPopenWithBufferLargerThanAllocated() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/9031: test fails on windows.") - // Test buffer larger than that allocated. try withTemporaryFile { file in let count = 10000 let stream = BufferedOutputByteStream() stream.send(Format.asRepeating(string: "a", count: count)) - try localFileSystem.writeFileContents(file.path, bytes: stream.bytes) + file.fileHandle.write(Data(stream.bytes.contents)) let actualStreamCount = stream.bytes.count - XCTAssertTrue(actualStreamCount == count, "Actual stream count (\(actualStreamCount)) is not as exxpected (\(count))") + XCTAssertTrue(actualStreamCount == count, "Actual stream count (\(actualStreamCount)) is not as expected (\(count))") let outputCount = try AsyncProcess.popen(arguments: catExecutableArgs + [file.path.pathString]).utf8Output().count XCTAssert(outputCount == count, "Actual count (\(outputCount)) is not as expected (\(count))") } @@ -77,7 +75,7 @@ final class AsyncProcessTests: XCTestCase { let args = ["whoami"] let answer = NSUserName() #endif - let popenResult = ThreadSafeBox>() + let popenResult = ThreadSafeBox?>() let group = DispatchGroup() group.enter() AsyncProcess.popen(arguments: args) { result in @@ -246,7 +244,7 @@ final class AsyncProcessTests: XCTestCase { let stdout = ThreadSafeBox<[UInt8]>([]) let process = AsyncProcess(scriptName: "in-to-out\(ProcessInfo.batSuffix)", outputRedirection: .stream(stdout: { stdoutBytes in stdout.mutate { - $0?.append(contentsOf: stdoutBytes) + $0.append(contentsOf: stdoutBytes) } }, stderr: { _ in })) let stdinStream = try process.launch() @@ -258,7 +256,7 @@ final class AsyncProcessTests: XCTestCase { try process.waitUntilExit() - XCTAssertEqual(String(decoding: stdout.get(default: []), as: UTF8.self), "hello\(ProcessInfo.EOL)") + XCTAssertEqual(String(decoding: stdout.get(), as: UTF8.self), "hello\(ProcessInfo.EOL)") } func testStdoutStdErr() throws { @@ -359,19 +357,19 @@ final class AsyncProcessTests: XCTestCase { let stderr = ThreadSafeBox<[UInt8]>([]) let process = AsyncProcess(scriptName: "long-stdout-stderr\(ProcessInfo.batSuffix)", outputRedirection: .stream(stdout: { stdoutBytes in stdout.mutate { - $0?.append(contentsOf: stdoutBytes) + $0.append(contentsOf: stdoutBytes) } }, stderr: { stderrBytes in stderr.mutate { - $0?.append(contentsOf: stderrBytes) + $0.append(contentsOf: stderrBytes) } })) try process.launch() try process.waitUntilExit() let count = 16 * 1024 - XCTAssertEqual(String(bytes: stdout.get(default: []), encoding: .utf8), String(repeating: "1", count: count)) - XCTAssertEqual(String(bytes: stderr.get(default: []), encoding: .utf8), String(repeating: "2", count: count)) + XCTAssertEqual(String(bytes: stdout.get(), encoding: .utf8), String(repeating: "1", count: count)) + XCTAssertEqual(String(bytes: stderr.get(), encoding: .utf8), String(repeating: "2", count: count)) } func testStdoutStdErrStreamingRedirected() throws { @@ -380,11 +378,11 @@ final class AsyncProcessTests: XCTestCase { let process = AsyncProcess(scriptName: "long-stdout-stderr\(ProcessInfo.batSuffix)", outputRedirection: .stream(stdout: { stdoutBytes in stdout.mutate { - $0?.append(contentsOf: stdoutBytes) + $0.append(contentsOf: stdoutBytes) } }, stderr: { stderrBytes in stderr.mutate { - $0?.append(contentsOf: stderrBytes) + $0.append(contentsOf: stderrBytes) } }, redirectStderr: true)) try process.launch() @@ -398,8 +396,8 @@ final class AsyncProcessTests: XCTestCase { let expectedStdout = String(repeating: "12", count: count) let expectedStderr = "" #endif - XCTAssertEqual(String(bytes: stdout.get(default: []), encoding: .utf8), expectedStdout) - XCTAssertEqual(String(bytes: stderr.get(default: []), encoding: .utf8), expectedStderr) + XCTAssertEqual(String(bytes: stdout.get(), encoding: .utf8), expectedStdout) + XCTAssertEqual(String(bytes: stderr.get(), encoding: .utf8), expectedStderr) } func testWorkingDirectory() throws { diff --git a/Tests/BasicsTests/ConcurrencyHelpersTests.swift b/Tests/BasicsTests/ConcurrencyHelpersTests.swift index 7350a7e06c7..5104d02fabc 100644 --- a/Tests/BasicsTests/ConcurrencyHelpersTests.swift +++ b/Tests/BasicsTests/ConcurrencyHelpersTests.swift @@ -85,45 +85,6 @@ struct ConcurrencyHelpersTest { } } - @Test( - .bug("https://github.com/swiftlang/swift-package-manager/issues/8770"), - ) - func threadSafeBox() async throws { - // Actor to serialize the critical section that was previously handled by the serial queue - actor SerialCoordinator { - func processTask(_ index: Int, winner: inout Int?, cache: ThreadSafeBox) { - // This simulates the serial queue behavior - both winner determination - // and cache memoization happen atomically in the same serial context - if winner == nil { - winner = index - } - cache.memoize { - index - } - } - } - - for num in 0 ..< 100 { - var winner: Int? - let cache = ThreadSafeBox() - let coordinator = SerialCoordinator() - - try await withThrowingTaskGroup(of: Void.self) { group in - for index in 0 ..< 1000 { - group.addTask { - // Random sleep to simulate concurrent access timing - try await Task.sleep(nanoseconds: UInt64(Double.random(in: 100 ... 300) * 1000)) - - // Process both winner determination and cache memoization serially - await coordinator.processTask(index, winner: &winner, cache: cache) - } - } - try await group.waitForAll() - } - #expect(cache.get() == winner, "Iteration \(num) failed") - } - } - @Suite struct AsyncOperationQueueTests { fileprivate actor ResultsTracker { @@ -278,3 +239,930 @@ struct ConcurrencyHelpersTest { } } } + +extension ConcurrencyHelpersTest { + @Suite struct ThreadSafeBoxTests { + // MARK: - Basic Functionality Tests + + @Test + func basicGetAndPut() { + let box = ThreadSafeBox(42) + #expect(box.get() == 42) + + box.put(100) + #expect(box.get() == 100) + } + + @Test + func mutateReturningNewValue() { + let box = ThreadSafeBox(10) + box.mutate { value in + value * 2 + } + #expect(box.get() == 20) + } + + @Test + func mutateInPlace() { + let box = ThreadSafeBox([1, 2, 3]) + box.mutate { value in + value.append(4) + } + #expect(box.get() == [1, 2, 3, 4]) + } + + // MARK: - Optional Value Tests + + @Test + func optionalInitEmpty() { + let box = ThreadSafeBox() + #expect(box.get() == nil) + } + + @Test + func optionalClear() { + let box = ThreadSafeBox(42) + #expect(box.get() == 42) + + box.clear() + #expect(box.get() == nil) + } + + @Test + func optionalGetWithDefault() { + let emptyBox = ThreadSafeBox() + #expect(emptyBox.get(default: 999) == 999) + + let filledBox = ThreadSafeBox(42) + #expect(filledBox.get(default: 999) == 42) + } + + @Test + func memoizeComputesOnce() { + let box = ThreadSafeBox() + var computeCount = 0 + + let result1 = box.memoize { + computeCount += 1 + return 42 + } + #expect(result1 == 42) + #expect(computeCount == 1) + + let result2 = box.memoize { + computeCount += 1 + return 99 + } + #expect(result2 == 42) + #expect(computeCount == 1) + } + + @Test + func memoizeOptionalNilValue() { + let box = ThreadSafeBox() + var computeCount = 0 + + let result1 = box.memoizeOptional { + computeCount += 1 + return nil + } + #expect(result1 == nil) + #expect(computeCount == 1) + + // Should recompute since result was nil + let result2 = box.memoizeOptional { + computeCount += 1 + return 42 + } + #expect(result2 == 42) + #expect(computeCount == 2) + + // Now should use cached value + let result3 = box.memoizeOptional { + computeCount += 1 + return 99 + } + #expect(result3 == 42) + #expect(computeCount == 2) + } + + // MARK: - Int Extension Tests + + @Test + func intIncrement() { + let box = ThreadSafeBox(0) + box.increment() + #expect(box.get() == 1) + box.increment() + #expect(box.get() == 2) + } + + @Test + func intDecrement() { + let box = ThreadSafeBox(10) + box.decrement() + #expect(box.get() == 9) + box.decrement() + #expect(box.get() == 8) + } + + // MARK: - String Extension Tests + + @Test + func stringAppend() { + let box = ThreadSafeBox("Hello") + box.append(" World") + #expect(box.get() == "Hello World") + box.append("!") + #expect(box.get() == "Hello World!") + } + + // MARK: - Dynamic Member Lookup Tests + + @Test + func dynamicMemberReadOnly() { + struct Person { + let name: String + let age: Int + } + + let box = ThreadSafeBox(Person(name: "Alice", age: 30)) + #expect(box.name == "Alice") + #expect(box.age == 30) + } + + @Test + func dynamicMemberWritable() { + struct Counter { + var count: Int + var label: String + } + + let box = ThreadSafeBox(Counter(count: 0, label: "Test")) + #expect(box.count == 0) + #expect(box.label == "Test") + + box.count = 42 + #expect(box.count == 42) + #expect(box.label == "Test") + + box.label = "Updated" + #expect(box.count == 42) + #expect(box.label == "Updated") + } + + // MARK: - Thread Safety Tests + + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8770"), + ) + func concurrentMemoization() async throws { + actor SerialCoordinator { + func processTask(_ index: Int, winner: inout Int?, cache: ThreadSafeBox) { + if winner == nil { + winner = index + } + cache.memoize { + index + } + } + } + + for num in 0 ..< 100 { + var winner: Int? + let cache = ThreadSafeBox() + let coordinator = SerialCoordinator() + + try await withThrowingTaskGroup(of: Void.self) { group in + for index in 0 ..< 1000 { + group.addTask { + try await Task.sleep(nanoseconds: UInt64(Double.random(in: 100 ... 300) * 1000)) + await coordinator.processTask(index, winner: &winner, cache: cache) + } + } + try await group.waitForAll() + } + #expect(cache.get() == winner, "Iteration \(num) failed") + } + } + + @Test + func concurrentIncrements() async throws { + let box = ThreadSafeBox(0) + let iterations = 1000 + + try await withThrowingTaskGroup(of: Void.self) { group in + for _ in 0..= 0 && finalValue < 1000) + } + } + + @Suite struct AsyncThrowingValueMemoizerTests { + // MARK: - Basic Functionality Tests + + @Test + func memoizeComputesOnlyOnce() async throws { + let memoizer = AsyncThrowingValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + + let result1 = try await memoizer.memoize { + computeCount += 1 + return 42 + } + #expect(result1 == 42) + #expect(computeCount == 1) + + let result2 = try await memoizer.memoize { + computeCount += 1 + return 99 + } + #expect(result2 == 42) + #expect(computeCount == 1) + } + + @Test + func memoizeWithAsyncWork() async throws { + let memoizer = AsyncThrowingValueMemoizer() + + let result = try await memoizer.memoize { + try await Task.sleep(nanoseconds: 1_000_000) + return "computed" + } + + #expect(result == "computed") + } + + @Test + func memoizeCachesError() async throws { + struct TestError: Error, Equatable {} + let memoizer = AsyncThrowingValueMemoizer() + + await #expect(throws: TestError.self) { + try await memoizer.memoize { + throw TestError() + } + } + + // After error, subsequent calls should return the cached error + await #expect(throws: TestError.self) { + try await memoizer.memoize { + 100 + } + } + } + + // MARK: - Concurrency Tests + + @Test + func concurrentMemoizationSharesWork() async throws { + let memoizer = AsyncThrowingValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + let lock = NSLock() + + try await withThrowingTaskGroup(of: Int.self) { group in + for _ in 0..<100 { + group.addTask { + try await memoizer.memoize { + lock.withLock { + computeCount += 1 + } + try await Task.sleep(nanoseconds: 10_000_000) + return 42 + } + } + } + + var results = [Int]() + for try await result in group { + results.append(result) + } + + #expect(results.count == 100) + #expect(results.allSatisfy { $0 == 42 }) + } + + // Should only compute once despite 100 concurrent calls + #expect(computeCount == 1) + } + + @Test + func concurrentMemoizationWithQuickCompletion() async throws { + let memoizer = AsyncThrowingValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + let lock = NSLock() + + try await withThrowingTaskGroup(of: String.self) { group in + for i in 0..<50 { + group.addTask { + try await memoizer.memoize { + lock.withLock { + computeCount += 1 + } + return "value-\(i)" + } + } + } + + var results = [String]() + for try await result in group { + results.append(result) + } + + #expect(results.count == 50) + // All results should be the same (from the first caller) + #expect(Set(results).count == 1) + } + + #expect(computeCount == 1) + } + + @Test + func concurrentErrorPropagation() async throws { + struct TestError: Error {} + let memoizer = AsyncThrowingValueMemoizer() + var errorCount = 0 + let lock = NSLock() + + await withThrowingTaskGroup(of: Void.self) { group in + for _ in 0..<20 { + group.addTask { + do { + _ = try await memoizer.memoize { + try await Task.sleep(nanoseconds: 5_000_000) + throw TestError() + } + } catch { + lock.withLock { + errorCount += 1 + } + } + } + } + + // Consume all results (ignoring errors) + while let _ = try? await group.next() {} + } + + // All concurrent calls should receive the error + #expect(errorCount == 20) + } + + @Test + func sequentialMemoizationAfterSuccess() async throws { + let memoizer = AsyncThrowingValueMemoizer() + + let first = try await memoizer.memoize { + try await Task.sleep(nanoseconds: 1_000_000) + return 42 + } + #expect(first == 42) + + let second = try await memoizer.memoize { + try await Task.sleep(nanoseconds: 1_000_000) + return 99 + } + #expect(second == 42) + } + + @Test + func complexValueType() async throws { + struct ComplexValue: Sendable, Equatable { + let id: Int + let name: String + let tags: [String] + } + + let memoizer = AsyncThrowingValueMemoizer() + + let result = try await memoizer.memoize { + try await Task.sleep(nanoseconds: 1_000_000) + return ComplexValue(id: 1, name: "Test", tags: ["a", "b", "c"]) + } + + #expect(result.id == 1) + #expect(result.name == "Test") + #expect(result.tags == ["a", "b", "c"]) + } + + @Test + func memoizeWithVariableDelay() async throws { + let memoizer = AsyncThrowingValueMemoizer() + nonisolated(unsafe) var firstCallComplete = false + let lock = NSLock() + + try await withThrowingTaskGroup(of: Int.self) { group in + // First task with delay + group.addTask { + try await memoizer.memoize { + try await Task.sleep(nanoseconds: 20_000_000) + lock.withLock { + firstCallComplete = true + } + return 100 + } + } + + // Wait a bit then add more tasks + try await Task.sleep(nanoseconds: 5_000_000) + + for _ in 0..<10 { + group.addTask { + try await memoizer.memoize { + return 999 + } + } + } + + var results = [Int]() + for try await result in group { + results.append(result) + } + + #expect(results.count == 11) + #expect(results.allSatisfy { $0 == 100 }) + } + + let wasFirstCallComplete = lock.withLock { firstCallComplete } + #expect(wasFirstCallComplete == true) + } + } + + @Suite struct AsyncKeyValueMemoizerTests { + // MARK: - Basic Functionality Tests + + @Test + func memoizeComputesOncePerKey() async { + let memoizer = AsyncKeyValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + + let result1 = await memoizer.memoize("key1") { + computeCount += 1 + return 42 + } + #expect(result1 == 42) + #expect(computeCount == 1) + + let result2 = await memoizer.memoize("key1") { + computeCount += 1 + return 99 + } + #expect(result2 == 42) + #expect(computeCount == 1) + + let result3 = await memoizer.memoize("key2") { + computeCount += 1 + return 100 + } + #expect(result3 == 100) + #expect(computeCount == 2) + } + + @Test + func memoizeWithAsyncWork() async { + let memoizer = AsyncKeyValueMemoizer() + + let result = await memoizer.memoize(1) { + await Task.yield() + return "computed" + } + + #expect(result == "computed") + } + + @Test + func memoizeMultipleKeys() async { + let memoizer = AsyncKeyValueMemoizer() + + let result1 = await memoizer.memoize(1) { "value1" } + let result2 = await memoizer.memoize(2) { "value2" } + let result3 = await memoizer.memoize(3) { "value3" } + + #expect(result1 == "value1") + #expect(result2 == "value2") + #expect(result3 == "value3") + + // Verify cached values + let cached1 = await memoizer.memoize(1) { "different" } + let cached2 = await memoizer.memoize(2) { "different" } + let cached3 = await memoizer.memoize(3) { "different" } + + #expect(cached1 == "value1") + #expect(cached2 == "value2") + #expect(cached3 == "value3") + } + + // MARK: - Concurrency Tests + + @Test + func concurrentMemoizationSharesWorkPerKey() async { + let memoizer = AsyncKeyValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + let lock = NSLock() + + await withTaskGroup(of: Int.self) { group in + for _ in 0..<100 { + group.addTask { + await memoizer.memoize("shared-key") { + lock.withLock { + computeCount += 1 + } + try? await Task.sleep(nanoseconds: 10_000_000) + return 42 + } + } + } + + var results = [Int]() + for await result in group { + results.append(result) + } + + #expect(results.count == 100) + #expect(results.allSatisfy { $0 == 42 }) + } + + // Should only compute once despite 100 concurrent calls + #expect(computeCount == 1) + } + + @Test + func concurrentMemoizationDifferentKeys() async { + let memoizer = AsyncKeyValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + let lock = NSLock() + + await withTaskGroup(of: String.self) { group in + for i in 0..<50 { + group.addTask { + await memoizer.memoize(i) { + lock.withLock { + computeCount += 1 + } + return "value-\(i)" + } + } + } + + var results = [String]() + for await result in group { + results.append(result) + } + + #expect(results.count == 50) + #expect(Set(results).count == 50) + } + + #expect(computeCount == 50) + } + + @Test + func complexKeyAndValueTypes() async { + struct Key: Hashable, Sendable { + let id: Int + let category: String + } + + struct Value: Sendable, Equatable { + let data: [String] + } + + let memoizer = AsyncKeyValueMemoizer() + + let key = Key(id: 1, category: "test") + let result = await memoizer.memoize(key) { + try? await Task.sleep(nanoseconds: 1_000_000) + return Value(data: ["a", "b", "c"]) + } + + #expect(result.data == ["a", "b", "c"]) + + let cached = await memoizer.memoize(key) { + Value(data: ["different"]) + } + #expect(cached.data == ["a", "b", "c"]) + } + } + + @Suite struct ThrowingAsyncKeyValueMemoizerTests { + // MARK: - Basic Functionality Tests + + @Test + func memoizeComputesOncePerKey() async throws { + let memoizer = ThrowingAsyncKeyValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + + let result1 = try await memoizer.memoize("key1") { + computeCount += 1 + return 42 + } + #expect(result1 == 42) + #expect(computeCount == 1) + + let result2 = try await memoizer.memoize("key1") { + computeCount += 1 + return 99 + } + #expect(result2 == 42) + #expect(computeCount == 1) + + let result3 = try await memoizer.memoize("key2") { + computeCount += 1 + return 100 + } + #expect(result3 == 100) + #expect(computeCount == 2) + } + + @Test + func memoizeWithAsyncWork() async throws { + let memoizer = ThrowingAsyncKeyValueMemoizer() + + let result = try await memoizer.memoize(1) { + try await Task.sleep(nanoseconds: 1_000_000) + return "computed" + } + + #expect(result == "computed") + } + + @Test + func memoizeCachesErrorPerKey() async throws { + struct TestError: Error, Equatable {} + let memoizer = ThrowingAsyncKeyValueMemoizer() + + await #expect(throws: TestError.self) { + try await memoizer.memoize("error-key") { + throw TestError() + } + } + + // Subsequent calls to same key should return cached error + await #expect(throws: TestError.self) { + try await memoizer.memoize("error-key") { + 100 + } + } + + // Different key should work fine + let result = try await memoizer.memoize("success-key") { + 42 + } + #expect(result == 42) + } + + @Test + func memoizeMultipleKeysWithMixedResults() async throws { + struct TestError: Error {} + let memoizer = ThrowingAsyncKeyValueMemoizer() + + let result1 = try await memoizer.memoize(1) { "value1" } + #expect(result1 == "value1") + + await #expect(throws: TestError.self) { + try await memoizer.memoize(2) { + throw TestError() + } + } + + let result3 = try await memoizer.memoize(3) { "value3" } + #expect(result3 == "value3") + + // Verify cached values + let cached1 = try await memoizer.memoize(1) { "different" } + #expect(cached1 == "value1") + + await #expect(throws: TestError.self) { + try await memoizer.memoize(2) { "different" } + } + + let cached3 = try await memoizer.memoize(3) { "different" } + #expect(cached3 == "value3") + } + + // MARK: - Concurrency Tests + + @Test + func concurrentMemoizationSharesWorkPerKey() async throws { + let memoizer = ThrowingAsyncKeyValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + let lock = NSLock() + + try await withThrowingTaskGroup(of: Int.self) { group in + for _ in 0..<100 { + group.addTask { + try await memoizer.memoize("shared-key") { + lock.withLock { + computeCount += 1 + } + try await Task.sleep(nanoseconds: 10_000_000) + return 42 + } + } + } + + var results = [Int]() + for try await result in group { + results.append(result) + } + + #expect(results.count == 100) + #expect(results.allSatisfy { $0 == 42 }) + } + + // Should only compute once despite 100 concurrent calls + #expect(computeCount == 1) + } + + @Test + func concurrentErrorPropagationPerKey() async throws { + struct TestError: Error {} + let memoizer = ThrowingAsyncKeyValueMemoizer() + var errorCount = 0 + let lock = NSLock() + + await withThrowingTaskGroup(of: Void.self) { group in + for _ in 0..<20 { + group.addTask { + do { + _ = try await memoizer.memoize("error-key") { + try await Task.sleep(nanoseconds: 5_000_000) + throw TestError() + } + } catch { + lock.withLock { + errorCount += 1 + } + } + } + } + + // Consume all results (ignoring errors) + while let _ = try? await group.next() {} + } + + // All concurrent calls should receive the error + #expect(errorCount == 20) + } + + @Test + func concurrentMemoizationDifferentKeys() async throws { + let memoizer = ThrowingAsyncKeyValueMemoizer() + nonisolated(unsafe) var computeCount = 0 + let lock = NSLock() + + try await withThrowingTaskGroup(of: String.self) { group in + for i in 0..<50 { + group.addTask { + try await memoizer.memoize(i) { + lock.withLock { + computeCount += 1 + } + return "value-\(i)" + } + } + } + + var results = [String]() + for try await result in group { + results.append(result) + } + + #expect(results.count == 50) + #expect(Set(results).count == 50) + } + + #expect(computeCount == 50) + } + + @Test + func complexKeyAndValueTypes() async throws { + struct Key: Hashable, Sendable { + let id: Int + let category: String + } + + struct Value: Sendable, Equatable { + let data: [String] + } + + let memoizer = ThrowingAsyncKeyValueMemoizer() + + let key = Key(id: 1, category: "test") + let result = try await memoizer.memoize(key) { + try await Task.sleep(nanoseconds: 1_000_000) + return Value(data: ["a", "b", "c"]) + } + + #expect(result.data == ["a", "b", "c"]) + + let cached = try await memoizer.memoize(key) { + Value(data: ["different"]) + } + #expect(cached.data == ["a", "b", "c"]) + } + + @Test + func memoizeWithVariableDelayMultipleKeys() async throws { + let memoizer = ThrowingAsyncKeyValueMemoizer() + nonisolated(unsafe) var firstCallComplete = false + let lock = NSLock() + + try await withThrowingTaskGroup(of: (String, Int).self) { group in + // First task with delay for key1 + group.addTask { + let result = try await memoizer.memoize("key1") { + try await Task.sleep(nanoseconds: 20_000_000) + lock.withLock { + firstCallComplete = true + } + return 100 + } + return ("key1", result) + } + + // Wait a bit then add more tasks for both keys + try await Task.sleep(nanoseconds: 5_000_000) + + for _ in 0..<10 { + group.addTask { + let result = try await memoizer.memoize("key1") { + return 999 + } + return ("key1", result) + } + } + + for _ in 0..<10 { + group.addTask { + let result = try await memoizer.memoize("key2") { + return 200 + } + return ("key2", result) + } + } + + var results: [String: [Int]] = [:] + for try await (key, value) in group { + results[key, default: []].append(value) + } + + #expect(results["key1"]?.count == 11) + #expect(results["key1"]?.allSatisfy { $0 == 100 } == true) + #expect(results["key2"]?.count == 10) + #expect(results["key2"]?.allSatisfy { $0 == 200 } == true) + } + + let wasFirstCallComplete = lock.withLock { firstCallComplete } + #expect(wasFirstCallComplete == true) + } + } +} diff --git a/Tests/BasicsTests/LegacyHTTPClientTests.swift b/Tests/BasicsTests/LegacyHTTPClientTests.swift index b074c6ec9c8..25996730e13 100644 --- a/Tests/BasicsTests/LegacyHTTPClientTests.swift +++ b/Tests/BasicsTests/LegacyHTTPClientTests.swift @@ -353,13 +353,13 @@ final class LegacyHTTPClientTests: XCTestCase { try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") let count = ThreadSafeBox(0) - let lastCall = ThreadSafeBox() + let lastCall = ThreadSafeBox() let maxAttempts = 5 let errorCode = Int.random(in: 500 ..< 600) let delay = SendableTimeInterval.milliseconds(100) let brokenHandler: LegacyHTTPClient.Handler = { _, _, completion in - let expectedDelta = pow(2.0, Double(count.get(default: 0) - 1)) * delay.timeInterval()! + let expectedDelta = pow(2.0, Double(count.get() - 1)) * delay.timeInterval()! let delta = lastCall.get().flatMap { Date().timeIntervalSince($0) } ?? 0 XCTAssertEqual(delta, expectedDelta, accuracy: 0.1) diff --git a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift index 858abce2cad..3cf2e3a8562 100644 --- a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift +++ b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift @@ -34,18 +34,16 @@ final class SerializedJSONTests: XCTestCase { } func testPathInterpolationFailsOnWindows() throws { - try XCTSkipOnWindows(because: "Expectations are not met. Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") - #if os(Windows) var path = try AbsolutePath(validating: #"\\?\C:\Users"#) var json: SerializedJSON = "\(path)" - XCTAssertEqual(json.underlying, #"C:\\Users"#) + XCTAssertEqual(json.underlying, #"\\\\?\\C:\\Users"#) path = try AbsolutePath(validating: #"\\.\UNC\server\share\"#) json = "\(path)" - XCTAssertEqual(json.underlying, #"\\.\\UNC\\server\\share"#) + XCTAssertEqual(json.underlying, #"\\\\.\\UNC\\server\\share"#) #endif } } diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 50b7f70de46..18c88d11bbd 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -782,7 +782,7 @@ struct BuildCommandTestCases { ) async throws { let buildSystem = data.buildSystem try await fixture(name: "Miscellaneous/ParseableInterfaces") { fixturePath in - try await withKnownIssue(isIntermittent: ProcessInfo.hostOperatingSystem == .windows) { + try await withKnownIssue(isIntermittent: true) { let result = try await build( ["--enable-parseable-module-interfaces"], packagePath: fixturePath, @@ -864,6 +864,7 @@ struct BuildCommandTestCases { } @Test( + .IssueWindowsLongPath, .tags( .Feature.BuildCache, ), @@ -874,43 +875,47 @@ struct BuildCommandTestCases { data: BuildData, ) async throws { let buildSystem = data.buildSystem - try await fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in - let buildCompleteRegex = try Regex(#"Build complete!\s?(\([0-9]*\.[0-9]*\s*s(econds)?\))?"#) - do { - let result = try await execute( - packagePath: fixturePath, - configuration: data.config, - buildSystem: buildSystem, - ) - // This test fails to match the 'Compiling' regex; rdar://101815761 - // XCTAssertMatch(result.stdout, .regex("\\[[1-9][0-9]*\\/[1-9][0-9]*\\] Compiling")) - let lines = result.stdout.split(whereSeparator: { $0.isNewline }) - let lastLine = try #require(lines.last) - #expect(lastLine.contains(buildCompleteRegex)) - } + try await withKnownIssue(isIntermittent: true) { + try await fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in + let buildCompleteRegex = try Regex(#"Build complete!\s?(\([0-9]*\.[0-9]*\s*s(econds)?\))?"#) + do { + let result = try await execute( + packagePath: fixturePath, + configuration: data.config, + buildSystem: buildSystem, + ) + // This test fails to match the 'Compiling' regex; rdar://101815761 + // XCTAssertMatch(result.stdout, .regex("\\[[1-9][0-9]*\\/[1-9][0-9]*\\] Compiling")) + let lines = result.stdout.split(whereSeparator: { $0.isNewline }) + let lastLine = try #require(lines.last) + #expect(lastLine.contains(buildCompleteRegex)) + } - do { - // test second time, to stabilize the cache - try await execute( - packagePath: fixturePath, - configuration: data.config, - buildSystem: buildSystem, - ) - } + do { + // test second time, to stabilize the cache + try await execute( + packagePath: fixturePath, + configuration: data.config, + buildSystem: buildSystem, + ) + } - do { - // test third time, to make sure message is presented even when nothing to build (cached) - let result = try await execute( - packagePath: fixturePath, - configuration: data.config, - buildSystem: buildSystem, - ) - // This test fails to match the 'Compiling' regex; rdar://101815761 - // XCTAssertNoMatch(result.stdout, .regex("\\[[1-9][0-9]*\\/[1-9][0-9]*\\] Compiling")) - let lines = result.stdout.split(whereSeparator: { $0.isNewline }) - let lastLine = try #require(lines.last) - #expect(lastLine.contains(buildCompleteRegex)) + do { + // test third time, to make sure message is presented even when nothing to build (cached) + let result = try await execute( + packagePath: fixturePath, + configuration: data.config, + buildSystem: buildSystem, + ) + // This test fails to match the 'Compiling' regex; rdar://101815761 + // XCTAssertNoMatch(result.stdout, .regex("\\[[1-9][0-9]*\\/[1-9][0-9]*\\] Compiling")) + let lines = result.stdout.split(whereSeparator: { $0.isNewline }) + let lastLine = try #require(lines.last) + #expect(lastLine.contains(buildCompleteRegex)) + } } + } when: { + (buildSystem == .swiftbuild && ProcessInfo.hostOperatingSystem == .windows) } } @@ -1148,24 +1153,31 @@ struct BuildCommandTestCases { func swiftDriverRawOutputGetsNewlines( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in - // Building with `-wmo` should result in a `remark: Incremental compilation has been disabled: it is not - // compatible with whole module optimization` message, which should have a trailing newline. Since that - // message won't be there at all when the legacy compiler driver is used, we gate this check on whether the - // remark is there in the first place. - let result = try await execute( - ["-Xswiftc", "-wmo"], - packagePath: fixturePath, - configuration: .release, - buildSystem: buildSystem, - ) - if result.stdout.contains( - "remark: Incremental compilation has been disabled: it is not compatible with whole module optimization" - ) { - #expect(result.stdout.contains("optimization\n")) - #expect(!result.stdout.contains("optimization[")) - #expect(!result.stdout.contains("optimizationremark")) + try await withKnownIssue( + "error produced for this fixture", + isIntermittent: true, + ) { + try await fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in + // Building with `-wmo` should result in a `remark: Incremental compilation has been disabled: it is not + // compatible with whole module optimization` message, which should have a trailing newline. Since that + // message won't be there at all when the legacy compiler driver is used, we gate this check on whether the + // remark is there in the first place. + let result = try await execute( + ["-Xswiftc", "-wmo"], + packagePath: fixturePath, + configuration: .release, + buildSystem: buildSystem, + ) + if result.stdout.contains( + "remark: Incremental compilation has been disabled: it is not compatible with whole module optimization" + ) { + #expect(result.stdout.contains("optimization\n")) + #expect(!result.stdout.contains("optimization[")) + #expect(!result.stdout.contains("optimizationremark")) + } } + } when: { + ProcessInfo.hostOperatingSystem == .windows && buildSystem == .swiftbuild } } @@ -1205,7 +1217,7 @@ struct BuildCommandTestCases { try await withKnownIssue( "https://github.com/swiftlang/swift-package-manager/issues/8659, SWIFT_EXEC override is not working", - isIntermittent: (buildSystem == .native && config == .release) + isIntermittent: true ){ // Build with a swiftc that returns version 1.0, we expect a successful build which compiles our one source // file. @@ -1300,7 +1312,7 @@ struct BuildCommandTestCases { func getTaskAllowEntitlement( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue(isIntermittent: (ProcessInfo.hostOperatingSystem == .linux)) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in #if os(macOS) // try await building with default parameters. This should succeed. We build verbosely so we get full command @@ -1512,7 +1524,7 @@ struct BuildCommandTestCases { func parseAsLibraryCriteria( buildData: BuildData, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/ParseAsLibrary") { fixturePath in _ = try await executeSwiftBuild( fixturePath, diff --git a/Tests/CommandsTests/CoverageTests.swift b/Tests/CommandsTests/CoverageTests.swift index 4872691725e..280d1ba7ab6 100644 --- a/Tests/CommandsTests/CoverageTests.swift +++ b/Tests/CommandsTests/CoverageTests.swift @@ -41,7 +41,7 @@ struct CoverageTests { buildSystem: BuildSystemProvider.Kind, ) async throws { let config = BuildConfiguration.debug - try await withKnownIssue(isIntermittent: (ProcessInfo.hostOperatingSystem == .linux && buildSystem == .swiftbuild)) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in _ = try await executeSwiftBuild( path, @@ -96,7 +96,7 @@ struct CoverageTests { let codeCovPath = try AbsolutePath(validating: codeCovPathString) // WHEN we build with coverage enabled - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await executeSwiftBuild( path, configuration: config, @@ -157,7 +157,7 @@ struct CoverageTests { try #require(!localFileSystem.exists(coveragePath)) // WHEN we test with coverage enabled - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await executeSwiftTest( path, configuration: buildData.config, diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 6aa4315a857..e5c7d2655ec 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -90,7 +90,7 @@ private func executeAddURLDependencyAndAssert( } @Suite( - .serializedIfOnWindows, + .serialized, .tags( .TestSize.large, .Feature.Command.Package.General, @@ -1044,7 +1044,7 @@ struct PackageCommandTests { func describeJson( data: BuildData, ) async throws { - try await withKnownIssue(isIntermittent: ProcessInfo.hostOperatingSystem == .windows) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "DependencyResolution/External/Simple/Bar") { fixturePath in // Generate the JSON description. let (jsonOutput, _) = try await execute( @@ -1189,7 +1189,7 @@ struct PackageCommandTests { withPrettyPrinting: Bool, ) async throws { // try XCTSkipIf(buildSystemProvider == .native && (try? UserToolchain.default.getSymbolGraphExtract()) == nil, "skipping test because the `swift-symbolgraph-extract` tools isn't available") - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture( name: "DependencyResolution/Internal/Simple", removeFixturePathOnDeinit: true @@ -3069,9 +3069,7 @@ struct PackageCommandTests { func purgeCacheWithoutPackage( data: BuildData, ) async throws { - try await withKnownIssue( - isIntermittent: ProcessInfo.isHostAmazonLinux2() // rdar://134238535 - ) { + try await withKnownIssue(isIntermittent: true) { // Create a temporary directory without Package.swift try await fixture(name: "Miscellaneous") { fixturePath in let tempDir = fixturePath.appending("empty-dir-for-purge-test") @@ -3090,7 +3088,7 @@ struct PackageCommandTests { } } } when: { - ProcessInfo.isHostAmazonLinux2() + ProcessInfo.isHostAmazonLinux2() //rdar://134238535 } } @@ -3434,7 +3432,7 @@ struct PackageCommandTests { """ ) } - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await testWithTemporaryDirectory { tmpPath in let packageDir = tmpPath.appending(components: "library") try localFileSystem.writeFileContents( @@ -4421,7 +4419,7 @@ struct PackageCommandTests { func buildToolPlugin( data: BuildData, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await testBuildToolPlugin(data: data, staticStdlib: false) } when: { ProcessInfo.hostOperatingSystem == .windows && data.buildSystem == .swiftbuild @@ -4565,7 +4563,7 @@ struct PackageCommandTests { buildSystem: data.buildSystem, ) ) { error in - withKnownIssue { + withKnownIssue(isIntermittent: true) { #expect(error.stderr.contains("This is text from the plugin")) #expect(error.stderr.contains("error: This is an error from the plugin")) } when: { @@ -5495,7 +5493,7 @@ struct PackageCommandTests { ) async throws { let debugTarget = try buildData.buildSystem.binPath(for: .debug) + [executableName("placeholder")] let releaseTarget = try buildData.buildSystem.binPath(for: .release) + [executableName("placeholder")] - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { // By default, a plugin-requested build produces a debug binary try await fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in let _ = try await execute( @@ -5530,7 +5528,7 @@ struct PackageCommandTests { ) async throws { let debugTarget = try buildData.buildSystem.binPath(for: .debug) + [executableName("placeholder")] let releaseTarget = try buildData.buildSystem.binPath(for: .release) + [executableName("placeholder")] - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { // If the plugin specifies a debug binary, that is what will be built, regardless of overall configuration try await fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in let _ = try await execute( @@ -5569,7 +5567,7 @@ struct PackageCommandTests { ) async throws { let debugTarget = try buildData.buildSystem.binPath(for: .debug) + [executableName("placeholder")] let releaseTarget = try buildData.buildSystem.binPath(for: .release) + [executableName("placeholder")] - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { // If the plugin requests a release binary, that is what will be built, regardless of overall configuration try await fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in let _ = try await execute( @@ -5607,7 +5605,7 @@ struct PackageCommandTests { ) async throws { let debugTarget = try buildData.buildSystem.binPath(for: .debug) + [executableName("placeholder")] let releaseTarget = try buildData.buildSystem.binPath(for: .release) + [executableName("placeholder")] - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { // If the plugin inherits the overall build configuration, that is what will be built try await fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in let _ = try await execute( @@ -5705,7 +5703,7 @@ struct PackageCommandTests { func commandPluginBuildTestabilityAllWithTests_Release_True( data: BuildData, ) async throws { - try await withKnownIssue(isIntermittent: (ProcessInfo.hostOperatingSystem == .linux)) { + try await withKnownIssue(isIntermittent: true) { // Overall configuration: release, plugin build request: release including tests -> with testability try await fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in let _ = await #expect(throws: Never.self) { @@ -5754,7 +5752,7 @@ struct PackageCommandTests { // otherwise the logs may be different in subsequent tests. // Check than nothing is echoed when echoLogs is false - try await withKnownIssue(isIntermittent: ProcessInfo.hostOperatingSystem == .windows) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in let (stdout, stderr) = try await execute( //got here ["print-diagnostics", "build"], @@ -6502,7 +6500,7 @@ struct PackageCommandTests { func commandPluginBuildingCallbacks( data: BuildData, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await testWithTemporaryDirectory { tmpPath in let buildSystemProvider = data.buildSystem // Create a sample package with a library, an executable, and a command plugin. @@ -6678,7 +6676,7 @@ struct PackageCommandTests { } // SwiftBuild is currently not producing a static archive for static products unless they are linked into some other binary. - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { // Invoke the plugin with parameters choosing a verbose build of MyStaticLibrary for release. do { let (stdout, _) = try await execute( @@ -6757,7 +6755,7 @@ struct PackageCommandTests { arguments: [BuildSystemProvider.Kind.native, .swiftbuild], ) func commandPluginBuildingCallbacksExcludeUnbuiltArtifacts(buildSystem: BuildSystemProvider.Kind) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "PartiallyUnusedDependency") { fixturePath in let (stdout, _) = try await execute( ["dump-artifacts-plugin"], @@ -6796,7 +6794,7 @@ struct PackageCommandTests { func commandPluginTestingCallbacks( data: BuildData, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await testWithTemporaryDirectory { tmpPath in // Create a sample package with a library, a command plugin, and a couple of tests. let packageDir = tmpPath.appending(components: "MyPackage") @@ -7498,7 +7496,7 @@ struct PackageCommandTests { func commandPluginDynamicDependencies( buildData: BuildData ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await testWithTemporaryDirectory { tmpPath in // Create a sample package with a command plugin that has a dynamic dependency. let packageDir = tmpPath.appending(components: "MyPackage") diff --git a/Tests/CommandsTests/RunCommandTests.swift b/Tests/CommandsTests/RunCommandTests.swift index b363ad32d41..b93d4daaf0f 100644 --- a/Tests/CommandsTests/RunCommandTests.swift +++ b/Tests/CommandsTests/RunCommandTests.swift @@ -210,26 +210,30 @@ struct RunCommandTests { func multipleExecutableAndExplicitExecutable( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await fixture(name: "Miscellaneous/MultipleExecutables") { fixturePath in + try await withKnownIssue(isIntermittent: true) { + try await fixture(name: "Miscellaneous/MultipleExecutables") { fixturePath in - let error = await #expect(throws: SwiftPMError.self ) { - try await execute(packagePath: fixturePath, buildSystem: buildSystem) - } - guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = try #require(error) else { - Issue.record("Incorrect error was raised.") - return - } + let error = await #expect(throws: SwiftPMError.self ) { + try await execute(packagePath: fixturePath, buildSystem: buildSystem) + } + guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = try #require(error) else { + Issue.record("Incorrect error was raised.") + return + } - #expect( - stderr.contains("error: multiple executable products available: exec1, exec2"), - "got stdout: \(stdout), stderr: \(stderr)", - ) + #expect( + stderr.contains("error: multiple executable products available: exec1, exec2"), + "got stdout: \(stdout), stderr: \(stderr)", + ) - var (runOutput, _) = try await execute(["exec1"], packagePath: fixturePath, buildSystem: buildSystem) - #expect(runOutput.contains("1")) + var (runOutput, _) = try await execute(["exec1"], packagePath: fixturePath, buildSystem: buildSystem) + #expect(runOutput.contains("1")) - (runOutput, _) = try await execute(["exec2"], packagePath: fixturePath, buildSystem: buildSystem) - #expect(runOutput.contains("2")) + (runOutput, _) = try await execute(["exec2"], packagePath: fixturePath, buildSystem: buildSystem) + #expect(runOutput.contains("2")) + } + } when: { + ProcessInfo.hostOperatingSystem == .windows && buildSystem == .swiftbuild } } @@ -245,7 +249,7 @@ struct RunCommandTests { func unreachableExecutable( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/UnreachableTargets") { fixturePath in let (output, _) = try await execute(["bexec"], packagePath: fixturePath.appending("A"), buildSystem: buildSystem) let outputLines = output.split(whereSeparator: { $0.isNewline }) @@ -265,7 +269,7 @@ struct RunCommandTests { func fileDeprecation( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/EchoExecutable") { fixturePath in let filePath = AbsolutePath(fixturePath, "Sources/secho/main.swift").pathString let cwd = try #require(localFileSystem.currentWorkingDirectory, "Current working directory should not be nil") @@ -422,7 +426,7 @@ struct RunCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { // GIVEN we have a simple test package try await fixture(name: "Miscellaneous/SwiftRun") { fixturePath in //WHEN we run with the --quiet option diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index 757d764b8b4..11cb401a470 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -237,7 +237,7 @@ struct TestCommandTests { ) async throws { try await withKnownIssue( "fails to build the package", - isIntermittent: (CiEnvironment.runningInSmokeTestPipeline), + isIntermittent: true, ) { // default should run with testability try await fixture(name: "Miscellaneous/TestableExe") { fixturePath in @@ -250,8 +250,7 @@ struct TestCommandTests { #expect(result.stderr.contains("-enable-testing")) } } when: { - // || (buildSystem == .swiftbuild && .windows == ProcessInfo.hostOperatingSystem && CiEnvironment.runningInSelfHostedPipeline) - (buildSystem == .swiftbuild && .windows == ProcessInfo.hostOperatingSystem ) + buildSystem == .swiftbuild && .windows == ProcessInfo.hostOperatingSystem } } @@ -267,7 +266,7 @@ struct TestCommandTests { configuration: BuildConfiguration, ) async throws { // disabled - try await withKnownIssue("fails to build", isIntermittent: ProcessInfo.hostOperatingSystem == .windows) { + try await withKnownIssue("fails to build", isIntermittent: true) { try await fixture(name: "Miscellaneous/TestableExe") { fixturePath in let error = await #expect(throws: SwiftPMError.self) { try await execute( @@ -302,7 +301,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue("failes to build the package", isIntermittent: (ProcessInfo.hostOperatingSystem == .windows)) { + try await withKnownIssue("failes to build the package", isIntermittent: true) { try await fixture(name: "Miscellaneous/TestableExe") { fixturePath in let result = try await execute( ["--enable-testable-imports", "--vv"], @@ -354,7 +353,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/ParallelTestsPkg") { fixturePath in // First try normal serial testing. let error = await #expect(throws: SwiftPMError.self) { @@ -374,7 +373,7 @@ struct TestCommandTests { #expect(!stdout.contains("[3/3]")) } } when: { - buildSystem == .swiftbuild && [.windows].contains(ProcessInfo.hostOperatingSystem) + buildSystem == .swiftbuild && ProcessInfo.hostOperatingSystem == .windows } } @@ -392,7 +391,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/ParallelTestsPkg") { fixturePath in // Try --no-parallel. let error = await #expect(throws: SwiftPMError.self) { @@ -428,7 +427,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/ParallelTestsPkg") { fixturePath in // Run tests in parallel. let error = await #expect(throws: SwiftPMError.self) { @@ -467,7 +466,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/ParallelTestsPkg") { fixturePath in let xUnitOutput = fixturePath.appending("result.xml") // Run tests in parallel with verbose output. @@ -521,7 +520,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/EmptyTestsPkg") { fixturePath in let xUnitOutput = fixturePath.appending("result.xml") // Run tests in parallel with verbose output. @@ -693,7 +692,7 @@ struct TestCommandTests { // windows issue not recorded for: // - native, single, XCTest, experimental true // - native, single, XCTest, experimental false - try await withKnownIssue( isIntermittent: (ProcessInfo.hostOperatingSystem == .windows)) { + try await withKnownIssue( isIntermittent: true) { try await fixture(name: tcdata.fixtureName) { fixturePath in // GIVEN we have a Package with a failing \(testRunner) test cases let xUnitOutput = fixturePath.appending("result.xml") @@ -748,7 +747,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/SkipTests") { fixturePath in let (stdout, _) = try await execute( ["--filter", ".*1"], @@ -794,7 +793,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/SkipTests") { fixturePath in let (stdout, _) = try await execute( ["--skip", "SomeTests"], @@ -812,7 +811,7 @@ struct TestCommandTests { [.windows].contains(ProcessInfo.hostOperatingSystem) && buildSystem == .swiftbuild } - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/SkipTests") { fixturePath in let (stdout, _) = try await execute( [ @@ -838,7 +837,7 @@ struct TestCommandTests { [.windows].contains(ProcessInfo.hostOperatingSystem) && buildSystem == .swiftbuild } - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/SkipTests") { fixturePath in let (stdout, _) = try await execute( ["--skip", "Tests"], @@ -959,7 +958,7 @@ struct TestCommandTests { ) async throws { try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in // build first - try await withKnownIssue("Fails to save attachment") { + try await withKnownIssue("Fails to save attachment", isIntermittent: true) { // This might be intermittently failing on windows let (buildStdout, _) = try await executeSwiftBuild( fixturePath, @@ -975,7 +974,7 @@ struct TestCommandTests { } // list - try await withKnownIssue("Fails to find test executable", isIntermittent: ([.linux, .windows].contains(ProcessInfo.hostOperatingSystem))) { // windows; issue not recorded + try await withKnownIssue("Fails to find test executable", isIntermittent: true) { // windows; issue not recorded let (listStdout, listStderr) = try await execute( ["list"], packagePath: fixturePath, @@ -1012,10 +1011,10 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue("Failes to find test executable") { + try await withKnownIssue("Failes to find test executable, or getting error: module 'Simple' was not compiled for testing, onMacOS", isIntermittent: true) { try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in // build first - try await withKnownIssue("Failed to save attachment", isIntermittent: (.windows == ProcessInfo.hostOperatingSystem)) { // windows: native, debug did not record issue + try await withKnownIssue("Failed to save attachment", isIntermittent: true) { // This might be intermittently failing on windows let (buildStdout, _) = try await executeSwiftBuild( fixturePath, @@ -1029,25 +1028,14 @@ struct TestCommandTests { } // list while skipping build - try await withKnownIssue( - """ - Windows: error: Test build artifacts were not found in the build folder. - """ - ) { - let (listStdout, listStderr) = try await execute(["list", "--skip-build"], packagePath: fixturePath, buildSystem: buildSystem) - // build was not run - #expect(!listStderr.contains("Build complete!")) - // getting the lists - #expect(listStdout.contains("SimpleTests.SimpleTests/testExample1")) - #expect(listStdout.contains("SimpleTests.SimpleTests/test_Example2")) - #expect(listStdout.contains("SimpleTests.SimpleTests/testThrowing")) - } when: { - (buildSystem == .swiftbuild && configuration == .debug && ProcessInfo.hostOperatingSystem == .windows) - } + let (listStdout, listStderr) = try await execute(["list", "--skip-build"], packagePath: fixturePath, buildSystem: buildSystem) + // build was not run + #expect(!listStderr.contains("Build complete!")) + // getting the lists + #expect(listStdout.contains("SimpleTests.SimpleTests/testExample1")) + #expect(listStdout.contains("SimpleTests.SimpleTests/test_Example2")) + #expect(listStdout.contains("SimpleTests.SimpleTests/testThrowing")) } - } when: { - (configuration == .release) - || (buildSystem == .swiftbuild && .linux == ProcessInfo.hostOperatingSystem && configuration == .release) } } @@ -1120,7 +1108,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { let strictConcurrencyFlags = ["-Xswiftc", "-strict-concurrency=complete"] try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in let (_, stderr) = try await execute( @@ -1147,7 +1135,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { let existentialAnyFlags = ["-Xswiftc", "-enable-upcoming-feature", "-Xswiftc", "ExistentialAny"] try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in let (_, stderr) = try await execute( @@ -1208,7 +1196,7 @@ struct TestCommandTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue("Fails to find test executable") { + try await withKnownIssue("Fails to find test executable", isIntermittent: true) { try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in let (_, stderr) = try await execute( ["--disable-swift-testing"], @@ -1219,7 +1207,7 @@ struct TestCommandTests { #expect(!stderr.contains("No matching test cases were run")) } } when: { - buildSystem == .swiftbuild && [.windows].contains(ProcessInfo.hostOperatingSystem) + buildSystem == .swiftbuild && ProcessInfo.hostOperatingSystem == .windows } } @@ -1298,6 +1286,7 @@ struct TestCommandTests { } } when: { .windows == ProcessInfo.hostOperatingSystem + || ProcessInfo.processInfo.environment["SWIFTCI_EXHIBITS_GH_9524"] != nil } } diff --git a/Tests/FunctionalTests/DependencyResolutionTests.swift b/Tests/FunctionalTests/DependencyResolutionTests.swift index 887a3adbcb6..51bee76f332 100644 --- a/Tests/FunctionalTests/DependencyResolutionTests.swift +++ b/Tests/FunctionalTests/DependencyResolutionTests.swift @@ -43,7 +43,7 @@ struct DependencyResolutionTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue(isIntermittent: (ProcessInfo.hostOperatingSystem == .windows) ) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "DependencyResolution/Internal/Simple") { fixturePath in try await executeSwiftBuild( fixturePath, @@ -74,7 +74,7 @@ struct DependencyResolutionTests { configuration: BuildConfiguration, ) async throws { try await fixture(name: "DependencyResolution/Internal/InternalExecutableAsDependency") { fixturePath in - await withKnownIssue { + await withKnownIssue(isIntermittent: true) { await #expect(throws: (any Error).self) { try await executeSwiftBuild( fixturePath, @@ -83,7 +83,7 @@ struct DependencyResolutionTests { ) } } when: { - configuration == .release && buildSystem == .swiftbuild && ProcessInfo.hostOperatingSystem != .windows // an error is not raised. + configuration == .release && buildSystem == .swiftbuild // an error is not raised. } } } @@ -101,7 +101,7 @@ struct DependencyResolutionTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue(isIntermittent: ProcessInfo.hostOperatingSystem == .windows) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "DependencyResolution/Internal/Complex") { fixturePath in try await executeSwiftBuild( fixturePath, diff --git a/Tests/FunctionalTests/MiscellaneousTests.swift b/Tests/FunctionalTests/MiscellaneousTests.swift index f10c6b65872..dc2e2c9fa90 100644 --- a/Tests/FunctionalTests/MiscellaneousTests.swift +++ b/Tests/FunctionalTests/MiscellaneousTests.swift @@ -448,7 +448,7 @@ final class MiscellaneousTestCase: XCTestCase { } func testUnicode() async throws { - try XCTSkipOnWindows(because: "Filepath too long error") + try XCTSkipOnWindows(because: "fixture copy does not like the unicode files, needs investigation.") #if !os(Linux) && !os(Android) // TODO: - Linux has trouble with this and needs investigation. try await fixtureXCTest(name: "Miscellaneous/Unicode") { fixturePath in // See the fixture manifest for an explanation of this string. @@ -877,9 +877,8 @@ final class MiscellaneousTestCase: XCTestCase { try await fixtureXCTest(name: "Miscellaneous/RootPackageWithConditionals") { path in _ = try await executeSwiftBuild( path, - extraArgs: ["--build-system=swiftbuild"], env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"], - buildSystem: .native, + buildSystem: .swiftbuild, ) } } diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index a6354a3e2eb..e9a8162e650 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -46,7 +46,7 @@ struct PluginTests { func testUseOfBuildToolPluginTargetByExecutableInSamePackage( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("MySourceGenPlugin"), @@ -84,7 +84,7 @@ struct PluginTests { func testUseOfBuildToolPluginTargetNoPreBuildCommands( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (_, stderr) = try await executeSwiftTest( fixturePath.appending("MySourceGenPluginNoPreBuildCommands"), @@ -112,7 +112,7 @@ struct PluginTests { func testUseOfBuildToolPluginProductByExecutableAcrossPackages( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("MySourceGenClient"), @@ -151,7 +151,7 @@ struct PluginTests { func testUseOfPrebuildPluginTargetByExecutableAcrossPackages( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("MySourceGenPlugin"), @@ -185,7 +185,7 @@ struct PluginTests { func testUseOfPluginWithInternalExecutable( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("ClientOfPluginWithInternalExecutable"), @@ -252,7 +252,7 @@ struct PluginTests { func testLocalBuildToolPluginUsingRemoteExecutable( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("LibraryWithLocalBuildToolPluginUsingRemoteTool"), @@ -286,7 +286,7 @@ struct PluginTests { func testBuildToolPluginDependencies( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("MyBuildToolPluginDependencies"), @@ -319,7 +319,7 @@ struct PluginTests { func testContrivedTestCases( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let buildSpecificArgs: [String] = switch buildSystem { case .native, .xcode: @@ -385,7 +385,7 @@ struct PluginTests { ) func testUseOfVendedBinaryTool(buildSystem: BuildSystemProvider.Kind) async throws { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in - try await withKnownIssue (isIntermittent: true) { + try await withKnownIssue(isIntermittent: true) { let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("MyBinaryToolPlugin"), configuration: .debug, @@ -487,7 +487,7 @@ struct PluginTests { """) } - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await testWithTemporaryDirectory { tmpPath in let packageDir = tmpPath.appending(components: "MyPackage") let pathOfGeneratedFile = packageDir.appending(components: [".build", "plugins", "outputs", "mypackage", "SomeTarget", "destination", "Plugin", "best.txt"]) @@ -877,7 +877,7 @@ struct PluginTests { arguments: SupportedBuildSystemOnAllPlatforms, ) func testLocalAndRemoteToolDependencies(buildSystem: BuildSystemProvider.Kind) async throws { - try await withKnownIssue (isIntermittent: true) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins/PluginUsingLocalAndRemoteTool") { path in let (stdout, stderr) = try await executeSwiftPackage( path.appending("MyLibrary"), @@ -1491,7 +1491,7 @@ struct PluginTests { func testIncorrectDependencies( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue (isIntermittent: true) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { path in let (stdout, stderr) = try await executeSwiftBuild( path.appending("IncorrectDependencies"), @@ -1658,7 +1658,7 @@ struct PluginTests { func testPluginCanBeReferencedByProductName( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath.appending("PluginCanBeReferencedByProductName"), @@ -1696,7 +1696,7 @@ struct PluginTests { func testPluginCanBeAffectedByXBuildToolsParameters( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins") { fixturePath in let buildArgs: [String] = switch buildSystem { case .native, .xcode: [] @@ -1738,7 +1738,7 @@ struct PluginTests { func testURLBasedPluginAPI( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath, @@ -1760,7 +1760,7 @@ struct PluginTests { func testDependentPlugins( buildSystem: BuildSystemProvider.Kind, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Miscellaneous/Plugins/DependentPlugins") { fixturePath in let (stdout, _) = try await executeSwiftBuild( fixturePath, diff --git a/Tests/FunctionalTests/StaticBinaryLibrary.swift b/Tests/FunctionalTests/StaticBinaryLibrary.swift index 4806fd08113..329438f24d2 100644 --- a/Tests/FunctionalTests/StaticBinaryLibrary.swift +++ b/Tests/FunctionalTests/StaticBinaryLibrary.swift @@ -32,7 +32,7 @@ struct StaticBinaryLibraryTests { func staticLibrary( buildData: BuildData, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "BinaryLibraries") { fixturePath in let (stdout, _) = try await executeSwiftRun( fixturePath.appending("Static").appending("Package1"), diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index b5db4e797f0..83a5d174066 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -40,7 +40,7 @@ struct TraitTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue(isIntermittent: (ProcessInfo.hostOperatingSystem == .windows && buildSystem == .swiftbuild)) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), @@ -63,8 +63,7 @@ struct TraitTests { """) } } when: { - (ProcessInfo.hostOperatingSystem == .windows && (CiEnvironment.runningInSmokeTestPipeline || buildSystem == .swiftbuild)) - || (buildSystem == .swiftbuild && [.windows].contains(ProcessInfo.hostOperatingSystem)) + (ProcessInfo.hostOperatingSystem == .windows && (CiEnvironment.runningInSmokeTestPipeline || buildSystem == .swiftbuild)) } } @@ -85,7 +84,7 @@ struct TraitTests { """ Windows: "https://github.com/swiftlang/swift-build/issues/609" """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows), + isIntermittent: true, ) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( @@ -136,7 +135,7 @@ struct TraitTests { """ Windows: https://github.com/swiftlang/swift-build/issues/609 """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows), + isIntermittent: true, ) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( @@ -184,7 +183,7 @@ struct TraitTests { """ Windows: https://github.com/swiftlang/swift-build/issues/609 """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows), + isIntermittent: true, ) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( @@ -232,7 +231,7 @@ struct TraitTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue(isIntermittent: (ProcessInfo.hostOperatingSystem == .windows && buildSystem == .swiftbuild)) { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( fixturePath.appending("Example"), @@ -272,7 +271,7 @@ struct TraitTests { try await withKnownIssue(""" Windows: https://github.com/swiftlang/swift-build/issues/609 """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows && buildSystem == .swiftbuild), + isIntermittent: true, ) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( @@ -317,7 +316,7 @@ struct TraitTests { """ Windows: https://github.com/swiftlang/swift-build/issues/609 """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows), + isIntermittent: true, ) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( @@ -372,7 +371,7 @@ struct TraitTests { """ Windows: https://github.com/swiftlang/swift-build/issues/609 """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows) + isIntermittent: true ) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftRun( @@ -450,7 +449,7 @@ struct TraitTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Traits") { fixturePath in let (stdout, _) = try await executeSwiftTest( fixturePath.appending("Example"), @@ -489,7 +488,7 @@ struct TraitTests { """ Windows: "https://github.com/swiftlang/swift-build/issues/609" """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows), + isIntermittent: true, ) { try await fixture(name: "Traits") { fixturePath in let (stdout, stderr) = try await executeSwiftTest( @@ -535,7 +534,7 @@ struct TraitTests { buildSystem: BuildSystemProvider.Kind, configuration: BuildConfiguration, ) async throws { - try await withKnownIssue(isIntermittent: true, { + try await withKnownIssue(isIntermittent: true) { try await fixture(name: "Traits") { fixturePath in let (stdout, _) = try await executeSwiftPackage( fixturePath.appending("Package10"), @@ -554,9 +553,9 @@ struct TraitTests { #expect(symbolGraph.contains("TypeGatedByPackage10Trait1")) #expect(symbolGraph.contains("TypeGatedByPackage10Trait2")) } - }, when: { + } when: { ProcessInfo.hostOperatingSystem == .windows - }) + } } @Test( @@ -686,7 +685,7 @@ struct TraitTests { """ Windows: https://github.com/swiftlang/swift-build/issues/609 """, - isIntermittent: (ProcessInfo.hostOperatingSystem == .windows), + isIntermittent: true, ) { try await fixture(name: "Traits") { fixturePath in // We expect no warnings to be produced. Specifically no unused dependency warnings. diff --git a/Tests/IntegrationTests/SwiftPMTests.swift b/Tests/IntegrationTests/SwiftPMTests.swift index 773c2612d75..fba06201478 100644 --- a/Tests/IntegrationTests/SwiftPMTests.swift +++ b/Tests/IntegrationTests/SwiftPMTests.swift @@ -316,7 +316,7 @@ private struct SwiftPMTests { let coveragePath = try AbsolutePath(validating: expectedCoveragePath) // Check the coverage path exists. - try withKnownIssue(isIntermittent: ProcessInfo.hostOperatingSystem == .windows) { + try withKnownIssue(isIntermittent: true) { // the CoveragePath file does not exists in Linux platform build expectFileExists(at: coveragePath) diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index ec2e153d31d..88d512269df 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -114,6 +114,7 @@ struct ModulesGraphTests { @Test( .IssueWindowsPathTestsFailures, + .disabled("Requires swift-testing bug fix https://github.com/swiftlang/swift-testing/pull/1441"), ) func basic() throws { try withKnownIssue { diff --git a/Tests/PackageLoadingTests/PDLoadingTests.swift b/Tests/PackageLoadingTests/PDLoadingTests.swift index 0ceab38b4e0..9116e9472e8 100644 --- a/Tests/PackageLoadingTests/PDLoadingTests.swift +++ b/Tests/PackageLoadingTests/PDLoadingTests.swift @@ -18,7 +18,7 @@ import XCTest class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { lazy var manifestLoader = ManifestLoader(toolchain: try! UserToolchain.default, delegate: self) - var parsedManifest = ThreadSafeBox() + var parsedManifest = ThreadSafeBox(.root) func willLoad(packageIdentity: PackageModel.PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { // noop diff --git a/Tests/PackageLoadingTests/PD_6_2_LoadingTests.swift b/Tests/PackageLoadingTests/PD_6_2_LoadingTests.swift index 54849d49146..d0b24502be6 100644 --- a/Tests/PackageLoadingTests/PD_6_2_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_6_2_LoadingTests.swift @@ -72,21 +72,25 @@ struct PackageDescription6_2LoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try await PackageDescriptionLoadingTests - .loadAndValidateManifest( - content, - toolsVersion: .v6_2, - packageKind: .fileSystem(.root), - manifestLoader: ManifestLoader( - toolchain: try! UserToolchain.default - ), - observabilityScope: observability.topScope - ) - try expectDiagnostics(validationDiagnostics) { results in - results.checkIsEmpty() - } - try expectDiagnostics(observability.diagnostics) { results in - results.checkIsEmpty() + try await withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8543: there are compilation errors on Windows", isIntermittent: true) { + let (_, validationDiagnostics) = try await PackageDescriptionLoadingTests + .loadAndValidateManifest( + content, + toolsVersion: .v6_2, + packageKind: .fileSystem(.root), + manifestLoader: ManifestLoader( + toolchain: try! UserToolchain.default + ), + observabilityScope: observability.topScope + ) + try expectDiagnostics(validationDiagnostics) { results in + results.checkIsEmpty() + } + try expectDiagnostics(observability.diagnostics) { results in + results.checkIsEmpty() + } + } when: { + isWindows && !CiEnvironment.runningInSmokeTestPipeline } } } diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index 3d577aaa805..462b35ea05e 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -584,9 +584,6 @@ struct PackageBuilderTests { } @Test( - .IssueWindowsLongPath, - .IssueWindowsPathLastComponent, - .IssueWindowsRelativePathAssert, .tags( Tag.Feature.PackageType.Executable, Tag.Feature.PackageType.Library, @@ -594,36 +591,36 @@ struct PackageBuilderTests { ) func testTestManifestSearch() throws { try withKnownIssue(isIntermittent: true) { - let fs = InMemoryFileSystem(emptyFiles: - "/pkg/foo.swift", - "/pkg/footests.swift" - ) + let fs = InMemoryFileSystem(emptyFiles: + "/pkg/foo.swift", + "/pkg/footests.swift" + ) - let manifest = Manifest.createRootManifest( - displayName: "pkg", - targets: [ - try TargetDescription( - name: "exe", - path: "./", - sources: ["foo.swift"] - ), - try TargetDescription( - name: "tests", - path: "./", - sources: ["footests.swift"], - type: .test - ), - ] - ) - try PackageBuilderTester(manifest, path: "/pkg", in: fs) { package, _ in - try package.checkModule("exe") { _ in } - try package.checkModule("tests") { _ in } + let manifest = Manifest.createRootManifest( + displayName: "pkg", + targets: [ + try TargetDescription( + name: "exe", + path: "./", + sources: ["foo.swift"] + ), + try TargetDescription( + name: "tests", + path: "./", + sources: ["footests.swift"], + type: .test + ), + ] + ) + 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) + package.checkProduct("pkgPackageTests") { product in + product.check(type: .test, targets: ["tests"]) + product.check(testEntryPointPath: nil) + } } - } } when: { ProcessInfo.hostOperatingSystem == .windows } diff --git a/Tests/QueryEngineTests/QueryEngineTests.swift b/Tests/QueryEngineTests/QueryEngineTests.swift index 6e9d9d2ad7f..4a5eae1c7b2 100644 --- a/Tests/QueryEngineTests/QueryEngineTests.swift +++ b/Tests/QueryEngineTests/QueryEngineTests.swift @@ -100,12 +100,13 @@ private struct Expression: CachingQuery { } struct QueryEngineTests { - @Test( - .bug("https://github.com/swiftlang/swift-package-manager/issues/8541"), - .disabled(if: ProcessInfo.hostOperatingSystem == .windows), - ) + @Test func filePathHashing() throws { +#if os(Windows) + let path = "C:\\root" +#else let path = "/root" +#endif let hashEncoder1 = HashEncoder() try hashEncoder1.encode(FilePath(path)) diff --git a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift index f8a6c243bde..04e662f151a 100644 --- a/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift +++ b/Tests/SwiftBuildSupportTests/SwiftBuildSystemTests.swift @@ -197,6 +197,87 @@ struct SwiftBuildSystemTests { } } + @Suite( + .tags( + .FunctionalArea.LinkSwiftStaticStdlib, + ), + ) + struct SwiftStaticStdlibSettingTests { + @Test + func makingBuildParametersRaisesAWarningWhenRunOnDarwin() async throws { + // GIVEN we have a Darwin triple + let triple = try Triple("x86_64-apple-macosx") + // AND we want to statically link Swift sdtlib + let shouldLinkStaticSwiftStdlib = true + try await withInstantiatedSwiftBuildSystem( + fromFixture: "PIFBuilder/Simple", + buildParameters: mockBuildParameters( + destination: .host, + shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib, + triple: triple, + ), + ) { swiftBuild, session, observabilityScope, buildParameters in + // WHEN we make the build parameter + let _: SWBBuildParameters = try await swiftBuild.makeBuildParameters( + session: session, + symbolGraphOptions: nil, + setToolchainSetting: false, // Set this to false as SwiftBuild checks the toolchain path + ) + + // THEN we expect a warning to be emitted + let warnings = observabilityScope.diagnostics.filter { + $0.severity == .warning + } + #expect(warnings.count == 1) + + let diagnostic = try #require(warnings.first) + // AND we expect the diagnostic message, severity and description to be as expected + #expect(diagnostic.message == Basics.Diagnostic.swiftBackDeployWarning.message) + #expect(diagnostic.severity == Basics.Diagnostic.swiftBackDeployWarning.severity) + #expect(diagnostic.description == Basics.Diagnostic.swiftBackDeployWarning.description) + } + } + + @Test( + arguments: [ + (shouldLinkStaticSwiftStdlib: true, expectedValue: "YES"), + (shouldLinkStaticSwiftStdlib: false, expectedValue: "NO"), + ] + ) + func swiftStaticStdLibSettingIsSetCorrectly( + shouldLinkStaticSwiftStdlib: Bool, + expectedValue: String + ) async throws { + // GIVEN we have a non-darwin triple AND we want statically link Swift sdtlib or not + let nonDarwinTriple = try Triple("i686-pc-windows-cygnus") + try await withInstantiatedSwiftBuildSystem( + fromFixture: "PIFBuilder/Simple", + buildParameters: mockBuildParameters( + destination: .host, + shouldLinkStaticSwiftStdlib: shouldLinkStaticSwiftStdlib, + triple: nonDarwinTriple, + ), + ) { swiftBuild, session, observabilityScope, buildParameters in + // WHEN we make the build parameter + let buildSettings = try await swiftBuild.makeBuildParameters( + session: session, + symbolGraphOptions: nil, + setToolchainSetting: false, // Set this to false as SwiftBuild checks the toolchain path + ) + + // THEN we don't expect any warnings to be emitted + let warnings = observabilityScope.diagnostics.filter { + $0.severity == .warning + } + #expect(warnings.isEmpty) + + // AND we expect the build setting to be set correctly + let synthesizedArgs = try #require(buildSettings.overrides.synthesized) + #expect(synthesizedArgs.table["SWIFT_FORCE_STATIC_LINK_STDLIB"] == expectedValue) + } + } + } + @Test( arguments: BuildParameters.IndexStoreMode.allCases, // arguments: [BuildParameters.IndexStoreMode.on], @@ -269,4 +350,37 @@ struct SwiftBuildSystemTests { ) } } + + @Test( + .issue("https://github.com/swiftlang/swift-package-manager/issues/9321", relationship: .verifies), + arguments: [ + 0, + 1, + 2, + 10, + ], + ) + func numberOfWorkersBuildParameterSetsTheExpectedSwiftBuildRequest( + expectedNumberOfWorkers: UInt32, + ) async throws { + try await withTemporaryDirectory { tempDir in + try await withInstantiatedSwiftBuildSystem( + fromFixture: "PIFBuilder/Simple", + buildParameters: mockBuildParameters( + destination: .host, + numberOfWorkers: expectedNumberOfWorkers, + ), + ) { swiftBuild, session, observabilityScope, buildParameters in + let buildRequest = try await swiftBuild.makeBuildRequest( + session: session, + configuredTargets: [], + derivedDataPath: tempDir, + symbolGraphOptions: nil, + setToolchainSetting: false + ) + + #expect(buildRequest.schedulerLaneWidthOverride == expectedNumberOfWorkers) + } + } + } } diff --git a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift index 3f956e0913a..1b27b65ed04 100644 --- a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift +++ b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift @@ -234,14 +234,18 @@ final class ManifestSourceGenerationTests: XCTestCase { } func testAdvancedFeatures() async throws { - try XCTSkipOnWindows() - let manifestContents = """ // swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription + #if os(Windows) + let absolutePath = "c:/a/b/c" + #else + let absolutePath = "/a/b/c" + #endif + let package = Package( name: "MyPackage", products: [ @@ -252,8 +256,8 @@ final class ManifestSourceGenerationTests: XCTestCase { ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(path: "/a/b/c"), - .package(name: "abc", path: "/a/b/d"), + .package(path: absolutePath), + .package(name: "abc", path: absolutePath), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 73db78f47c6..24faac24443 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -28,20 +28,6 @@ import struct TSCBasic.ByteString import struct TSCUtility.Version final class WorkspaceTests: XCTestCase { - // override func setUpWithError() throws { - // let windowsPassingTests = [ - // #selector(self.testBinaryArtifactsInvalidPath), - // #selector(self.testManifestLoaderDiagnostics), - // #selector(self.testInterpreterFlags), - // #selector(self.testManifestParseError), - // #selector(self.testSimpleAPI) - // ] - // let matches = windowsPassingTests.filter { $0 == self.invocation?.selector} - // if matches.count == 0 { - // try XCTSkipOnWindows() - // } - // } - func testBasics() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8313,7 +8299,7 @@ final class WorkspaceTests: XCTestCase { guard case .download(let fileSystem, let destination) = request.kind else { throw StringError("invalid request \(request.kind)") } - acceptHeaders.mutate { $0?.append(request.headers.get("accept").first!) } + acceptHeaders.mutate { $0.append(request.headers.get("accept").first!) } let contents: [UInt8] switch request.url.lastPathComponent { @@ -8873,7 +8859,7 @@ final class WorkspaceTests: XCTestCase { } concurrentRequests.increment() - if concurrentRequests.get()! > maxConcurrentRequests { + if concurrentRequests.get() > maxConcurrentRequests { XCTFail("too many concurrent requests \(concurrentRequests), expected \(maxConcurrentRequests)") } diff --git a/Tests/_InternalTestSupportTests/FileSystemHelpersTests.swift b/Tests/_InternalTestSupportTests/FileSystemHelpersTests.swift index 754e0c9f2e0..b3acb14bc83 100644 --- a/Tests/_InternalTestSupportTests/FileSystemHelpersTests.swift +++ b/Tests/_InternalTestSupportTests/FileSystemHelpersTests.swift @@ -15,6 +15,7 @@ import _InternalTestSupport import Testing @Suite( + .serialized, // these change CWD .tags( .TestSize.small, ), diff --git a/Utilities/build-using-self b/Utilities/build-using-self index 3370a64637c..cc3d04efd6d 100755 --- a/Utilities/build-using-self +++ b/Utilities/build-using-self @@ -283,7 +283,6 @@ def main() -> None: "build", *global_args, *BUILD_OVERRIDES, - "--build-tests", *ignore_args, *args.additional_build_args.split(" ") ] @@ -301,7 +300,6 @@ def main() -> None: *args.additional_run_args.split(" "), "swift-test", *global_args, - "--vv", "--force-resolved-versions", "--parallel", "--scratch-path",