From f43feb498ae53a29bbd684338126e012efb19689 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 25 Jul 2026 22:04:44 -0400 Subject: [PATCH 001/107] Consolidate TestCase execution and preserve diagnostics --- Sources/Core/Test.swift | 407 ++++++++++++++++++++++------------------ 1 file changed, 223 insertions(+), 184 deletions(-) diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index b0973ad..e9b5f46 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -1,18 +1,12 @@ -// TODO: Once Swift Testing is available, can re-write all this code into test classes that conform to Swift Testing so that we can also run code in Previews and Test Applications? Use macros to duplicate #expect( functionality syntax? Or can we use somehow in UI still? public typealias TestClosure = @Sendable () async throws -> Void /// A portable snapshot of the source location that initiated an operation. -/// -/// Passing one value is useful when an asynchronous helper needs to retain and forward a caller's -/// location. Existing APIs continue exposing individual source arguments for source compatibility, -/// while new APIs can accept `SourceContext` when carrying the complete location is clearer. -public struct SourceContext: Sendable { +public struct SourceContext: Sendable, CustomStringConvertible { public let file: String public let function: String public let line: Int public let column: Int - /// Captures the call site by default. public init( file: String = #file, function: String = #function, @@ -24,294 +18,347 @@ public struct SourceContext: Sendable { self.line = line self.column = column } + + public var description: String { + "\(file):\(line):\(column) in \(function)" + } } -// This could be anything, not necessary a struct or class, so if we need this, have a list of tests rather than a Testable object -//// don't make this public to avoid compiling test stuff into framework, however, do make public so apps can add in their own tests. -//public protocol Testable { -// // actor isolated since each Test is @MainActor isolated due to being an ObservableObject. -// @available(watchOS 6, *) -// @MainActor static var tests: [Test] { get } -//} +/// An expectation failure that retains the original source location for command-line and external test runners. +public struct TestFailure: Error, Sendable, CustomStringConvertible { + public let message: String + public let source: SourceContext + + public init(_ message: String, source: SourceContext = SourceContext()) { + self.message = message + self.source = source + } + + public var description: String { + "\(message) [\(source)]" + } +} + +#if canImport(Foundation) +extension TestFailure: LocalizedError { + public var errorDescription: String? { description } +} +#endif -// TODO: NEXT: Convert these to Testing expectations so we don't have to write custom error descriptions. Also move to Test static method that is shadowed in the global space. /// Sets an expectation for a reusable Compatibility test. -/// -/// The source location defaults mirror Swift Testing's diagnostics while remaining callable from -/// live applications, previews, older systems, and test runners that do not provide Swift Testing. -public func expect(_ condition: Bool, _ debugString: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { +public func expect( + _ condition: Bool, + _ debugString: String? = nil, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column +) throws { guard condition else { - // set breakpoint on this line if we want to debug/inspect errors (note that this slows enough to mess with time stamp checks so disable once we know everything is working). + let source = SourceContext(file: file, function: function, line: line, column: column) + let message: String if let debugString { - throw CustomError(debugString) + message = debugString } else { #if canImport(Foundation) let isMainThread = Thread.isMainThread #else let isMainThread = true #endif - let context = Compatibility.settings.debugFormat( - "", - DebugLevel.OFF, + message = Compatibility.settings.debugFormat( + "Expectation failed", + .ERROR, isMainThread, Compatibility.settings.debugEmojiSupported, true, true, - file, function, line, column) - - throw CustomError(context) + file, + function, + line, + column + ) } + debug(message, level: .ERROR, file: file, function: function, line: line, column: column) + throw TestFailure(message, source: source) } } /// Requires two equatable values to be equal and reports both values when they differ. -/// -/// - Parameters: -/// - actual: The value produced by the code under test. -/// - expected: The value the test requires. -/// - message: Optional context appended to the generated actual-versus-expected diagnostic. -public func expectEqual(_ actual: Value, _ expected: Value, _ message: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { - // Build the comparison text here so UI runs receive the same useful values that Swift Testing displays. +public func expectEqual( + _ actual: Value, + _ expected: Value, + _ message: String? = nil, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column +) throws { let context = message.map { " \($0)" } ?? "" - try expect(actual == expected, "Expected \(String(reflecting: expected)), but received \(String(reflecting: actual)).\(context)", file: file, function: function, line: line, column: column) + try expect( + actual == expected, + "Expected \(String(reflecting: expected)), but received \(String(reflecting: actual)).\(context)", + file: file, + function: function, + line: line, + column: column + ) } /// Requires two equatable values to differ and reports the shared value when they do not. -public func expectNotEqual(_ actual: Value, _ unexpected: Value, _ message: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { - // Include the unexpected value so a failure remains actionable outside a debugger. +public func expectNotEqual( + _ actual: Value, + _ unexpected: Value, + _ message: String? = nil, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column +) throws { let context = message.map { " \($0)" } ?? "" - try expect(actual != unexpected, "Expected a value other than \(String(reflecting: unexpected)), but received it.\(context)", file: file, function: function, line: line, column: column) + try expect( + actual != unexpected, + "Expected a value other than \(String(reflecting: unexpected)), but received it.\(context)", + file: file, + function: function, + line: line, + column: column + ) } -// NOTE: Really wish there was a way of writing a possibly async function or doing this using a generic so we don't have to duplicate code. -// TODO: Find a way to prevent conflicts here when run simultaneously. This really should only be used for testing. -/// Suppress debug messages during this execution block. Allows fetching the debug string as normal. +/// Suppresses debug messages during a synchronous execution block and always restores the prior logger. public func debugSuppress(_ block: () throws -> Void) rethrows { let log = Compatibility.settings.debugLog - #if canImport(Foundation) - let suppressThread = Thread.current // restrict the silencing to this thread/closure assuming no background tasks are doing printing - #endif +#if canImport(Foundation) + let suppressThread = Thread.current +#endif Compatibility.settings.debugLog = { message in - #if canImport(Foundation) - if Thread.current != suppressThread { - log(message) // do normal logging - } - #else +#if canImport(Foundation) + if Thread.current != suppressThread { log(message) } +#else log(message) - #endif - } - defer { - Compatibility.settings.debugLog = log +#endif } + defer { Compatibility.settings.debugLog = log } try block() } -/// Suppress debug messages during this async execution block. Allows fetching the debug string as normal. -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // due to Concurrency -//@MainActor + +/// Suppresses debug messages during an asynchronous execution block and always restores the prior logger. +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public func debugSuppress(_ block: () async throws -> Void) async rethrows { let log = Compatibility.settings.debugLog - // unable to get thread in async functions so just ignore and hope it doesn't run concurrently interrupting other debug messages. Compatibility.settings.debugLog = { _ in } - defer { - Compatibility.settings.debugLog = log - } + defer { Compatibility.settings.debugLog = log } try await block() } -// Testing is only supported with Swift 5.9+ #if compiler(>=5.9) -// Test Handlers + +/// Controls whether a reusable test may overlap other reusable tests. +public enum TestExecutionMode: Sendable { + case parallel + case serialized +} + +private actor TestExecutionGate { + static let shared = TestExecutionGate() + private var isRunning = false + private var waiters: [CheckedContinuation] = [] + + func acquire() async { + if !isRunning { + isRunning = true + return + } + await withCheckedContinuation { waiters.append($0) } + } + + func release() { + if waiters.isEmpty { + isRunning = false + } else { + waiters.removeFirst().resume() + } + } +} + +private struct TestExecution: Sendable { + let title: String + let setUp: TestClosure? + let test: TestClosure + let tearDown: TestClosure? + let mode: TestExecutionMode + + func perform() async throws { + if mode == .serialized { + await TestExecutionGate.shared.acquire() + } + + do { + try await performLifecycle() + if mode == .serialized { + await TestExecutionGate.shared.release() + } + } catch { + if mode == .serialized { + await TestExecutionGate.shared.release() + } + throw error + } + } + + private func performLifecycle() async throws { + let previousSettings = Compatibility.settings + defer { Compatibility.settings = previousSettings } + + var primaryError: (any Error)? + do { + try await setUp?() + try await test() + } catch { + primaryError = error + } + + do { + try await tearDown?() + } catch { + if let primaryError { + debug("\(title) teardown also failed: \(error)", level: .ERROR) + throw primaryError + } + throw error + } + + if let primaryError { + throw primaryError + } + } +} + @MainActor @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) -/// A reusable named test that can run in Compatibility's live UI or an external test framework. -/// -/// `TestCase` intentionally borrows XCTest's familiar terminology, but it is not an -/// `XCTestCase` subclass or a drop-in replacement. Each value describes one closure-based test, -/// while optional setup and teardown closures provide lightweight lifecycle hooks. public final class TestCase: ObservableObject, @unchecked Sendable { private final class WeakReference: @unchecked Sendable { weak var value: T? - - init(_ value: T?) { - self.value = value - } + init(_ value: T?) { self.value = value } } public enum TestProgress: Sendable { case notStarted case running case pass - case fail(String) // for error message + case fail(String) + public var symbol: String { switch self { - case .notStarted: - return "❇️" - case .running: - return "πŸ”„" - case .pass: - return "βœ…" - case .fail: - return "β›”" + case .notStarted: "❇️" + case .running: "πŸ”„" + case .pass: "βœ…" + case .fail: "β›”" } } + public var errorMessage: String? { - if case let .fail(string) = self { - return string - } - return nil + if case let .fail(message) = self { message } else { nil } } } + public let title: String public let setUp: TestClosure? public var test: TestClosure public let tearDown: TestClosure? - /// Source-compatible name for the test closure. - /// - /// `test` reads more naturally beside `setUp` and `tearDown`, while `task` remains available - /// because it was public before `TestCase` adopted lifecycle terminology. + public let executionMode: TestExecutionMode + @available(*, deprecated, renamed: "test") public var task: TestClosure { get { test } set { test = newValue } } + @Published public var progress: TestProgress = .notStarted - - /// Creates a reusable test with optional lifecycle closures. - /// - /// Teardown is attempted even when setup or the test throws, matching the cleanup expectation - /// familiar from XCTest without claiming `XCTestCase` API or inheritance compatibility. + public init( _ title: String, + executionMode: TestExecutionMode = .parallel, setUp: TestClosure? = nil, test: @escaping TestClosure, tearDown: TestClosure? = nil ) { self.title = title + self.executionMode = executionMode self.setUp = setUp self.test = test self.tearDown = tearDown } - /// Creates a reusable test without separate setup or teardown work. - public convenience init(_ title: String, _ test: @escaping TestClosure) { - self.init(title, test: test) + public convenience init( + _ title: String, + executionMode: TestExecutionMode = .parallel, + _ test: @escaping TestClosure + ) { + self.init(title, executionMode: executionMode, test: test) + } + + private var execution: TestExecution { + TestExecution(title: title, setUp: setUp, test: test, tearDown: tearDown, mode: executionMode) } - /// Executes the test closure directly for an external test framework. - /// - /// Swift Testing and XCTest adapters should prefer this awaited path because thrown expectation - /// failures retain the external runner's native test context without polling observable UI state. public func execute() async throws { - do { - try await setUp?() - try await test() - } catch { - // Cleanup should still run after a failure; preserve the original failure when cleanup succeeds. - do { - try await tearDown?() - } catch { - debug("Test teardown also failed: \(error)", level: .ERROR) - } - throw error - } - try await tearDown?() + try await execution.perform() } - - @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) + public func run() { - if case .running = progress { - return - } - let setUp = self.setUp - let test = self.test - let tearDown = self.tearDown + guard progress != .running else { return } + let execution = execution let weakSelf = WeakReference(self) progress = .running - // Run on the detached executor, then publish the result back on the main actor. WebAssembly's - // cooperative executor preserves the same actor semantics even when its host is single threaded. - Task.detached(priority: .userInitiated) { [setUp, test, tearDown, weakSelf] in + + Task.detached(priority: .userInitiated) { do { - do { - try await setUp?() - try await test() - } catch { - // Mirror execute() cleanup while keeping this detached UI path independent of self. - do { - try await tearDown?() - } catch { - debug("Test teardown also failed: \(error)", level: .ERROR) - } - throw error - } - try await tearDown?() - await MainActor.run { - weakSelf.value?.progress = .pass - } + try await execution.perform() + await MainActor.run { weakSelf.value?.progress = .pass } } catch { - await MainActor.run { - debug(error.localizedDescription, level: .ERROR) - weakSelf.value?.progress = .fail("\(error.localizedDescription)") - } + let message = String(describing: error) + debug("\(execution.title) failed: \(message)", level: .ERROR) + await MainActor.run { weakSelf.value?.progress = .fail(message) } } } } - + public func isFinished() -> Bool { switch progress { - case .pass, .fail: - return true - default: - return false + case .pass, .fail: true + default: false } } public func succeeded() -> Bool { - switch progress { - case .pass: - return true - default: - return false - } + if case .pass = progress { true } else { false } } - public var errorMessage: String? { - progress.errorMessage - } - + public var errorMessage: String? { progress.errorMessage } + public var description: String { - var errorString = "" - if let errorMessage = progress.errorMessage { - errorString = "\n\t\(errorMessage)" - } - return "\(progress): \(title)\(errorString)" + let error = progress.errorMessage.map { "\n\t\($0)" } ?? "" + return "\(progress): \(title)\(error)" } } -/// The original test type name retained for source compatibility with Compatibility 1.16. -/// -/// Use ``TestCase`` in new code to avoid colliding with Swift Testing's `Test` type. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @available(*, deprecated, renamed: "TestCase") public typealias Test = TestCase @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension TestCase { - static func dummyAsyncThrows() async throws { - } + static func dummyAsyncThrows() async throws {} } @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) public extension TestCase { - /// Every reusable Compatibility test, grouped in deterministic display and execution order. - /// - /// This is the package's canonical test catalog. The in-app UI and Swift Testing bridge both - /// consume this property so a test is authored once and remains runnable in either environment. @MainActor static let namedTests: OrderedDictionary = { var tests: OrderedDictionary = [ "Expectation Tests": [ TestCase("Equality diagnostics") { - // Exercise the public comparison helpers on their success paths without intentionally failing the shared suite. try expectEqual(["Compatibility", "TestCase"], ["Compatibility", "TestCase"]) try expectNotEqual(Compatibility.version, Version("0.0.0")) }, @@ -329,11 +376,7 @@ public extension TestCase { "Application Tests": Application.tests, ] #if canImport(Foundation) - tests.merge([ - "Coding Tests": codingTests, - ]) { current, _ in current } -#endif -#if canImport(Foundation) + tests.merge(["Coding Tests": codingTests]) { current, _ in current } tests["Bundle Tests"] = Bundle.tests tests["File Manager Tests"] = FileManager.tests tests["Pasteboard Tests"] = Pasteboard.tests @@ -342,7 +385,6 @@ public extension TestCase { tests["Date Tests"] = Date.tests tests["Threading Tests"] = Compatibility.threadingTests #if canImport(Combine) || canImport(FoundationNetworking) - // FoundationNetworking supplies URLSession through libcurl on Linux. tests["Network Tests"] = PostData.tests #endif #endif @@ -352,11 +394,8 @@ public extension TestCase { @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) public extension Compatibility { - /// Compatibility's global test catalog. @MainActor - static var tests: OrderedDictionary { - TestCase.namedTests - } + static var tests: OrderedDictionary { TestCase.namedTests } } #if canImport(SwiftUI) && canImport(Foundation) From c6c86f1d8d4ff4c8bd2d983fc304ed1391dd0a13 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 25 Jul 2026 22:08:12 -0400 Subject: [PATCH 002/107] Add reusable Swift Testing module adapter --- Package.swift | 182 ++++++++---------- .../ModuleTestEntry.swift | 68 +++++++ .../Core/TestExecutionMode+Equatable.swift | 3 + 3 files changed, 147 insertions(+), 106 deletions(-) create mode 100644 Sources/CompatibilityTesting/ModuleTestEntry.swift create mode 100644 Sources/Core/TestExecutionMode+Equatable.swift diff --git a/Package.swift b/Package.swift index de0d7b2..5e7f56c 100644 --- a/Package.swift +++ b/Package.swift @@ -15,56 +15,39 @@ import PackageDescription import AppleProductTypes #endif -// Products define the executables and libraries a package produces, making them visible to other packages. var products = [ - Product.library( - name: "\(packageLibraryName) Library", // has to be named different from the iOSApplication or Swift Playgrounds won't open correctly - targets: [packageLibraryName] - ), + Product.library( + name: "\(packageLibraryName) Library", + targets: [packageLibraryName] + ), ] -// Targets are the basic building blocks of a package, defining a module or a test suite. -// Targets can depend on other targets in this package and products from dependencies. var targets = [ - Target.target( - name: packageLibraryName, - dependencies: [ -// .product(name: "Compatibility Library", package: "compatibility"), // apparently needs to be lowercase. Also note this is "Compatibility Library" not "Compatibility" - ], - path: "Sources" - // If resources need to be included in the module, include here -// ,resources: [ // unfortuantely cannot be conditionally compiled based on Swift version since the tool seems to be run on latest version. -// Resource.process("Resources"), -// ] -// ,swiftSettings: [ -// .enableUpcomingFeature("BareSlashRegexLiterals") -// ] - ), + Target.target( + name: packageLibraryName, + dependencies: [], + path: "Sources", + exclude: ["CompatibilityTesting"] + ), ] var platforms: [SupportedPlatform] = [ - .macOS("10.10"), // SwiftPM's oldest supported macOS declaration; newer APIs remain availability-gated. - .tvOS("11"), // 13 minimum for SwiftUI, 15 minimum for Date.now, 17 minimum for Menu - .watchOS("4"), // 6 minimum for SwiftUI, watchOS 7 typically needed for most UI, 8 for Date.now, however (for #buildAvailability) so really should be watchOS 9+. + .macOS("10.10"), + .tvOS("11"), + .watchOS("4"), ] #if SwiftPlaygrounds || canImport(PlaygroundSupport) -platforms += [ - .iOS("15.2"), // minimum for Swift Playgrounds support (maximum version for test iPhone 7) -] +platforms += [.iOS("15.2")] #else -platforms += [ - .iOS("11"), // 13 minimum for Combine/SwiftUI, 15 minimum for Date.now, (maximum version for test iPhone 7) -] +platforms += [.iOS("11")] #endif #if compiler(>=5.9) && os(visionOS) -platforms += [ - .visionOS("1.0"), // PackageDescription 5.9 supports visionOS, so SPI and visionOS clients can see the platform explicitly. -] +platforms += [.visionOS("1.0")] #endif -#if canImport(AppleProductTypes) // swift package dump-package fails because of this +#if canImport(AppleProductTypes) import AppleProductTypes let executableTargetName = "\(packageLibraryName)TestAppModule" @@ -76,90 +59,77 @@ let appName = "\(packageLibraryName) App" #endif products += [ - .iOSApplication( - name: appName, // needs to match package name to open properly in Swift Playgrounds =5.9) && canImport(Testing) +import Compatibility +import Testing + +/// One reusable Compatibility `TestCase` presented as an individual Swift Testing argument. +public struct ModuleTestEntry: Sendable, Identifiable { + public let moduleIdentifier: String + public let moduleName: String + public let section: String + public let testTitle: String + public let index: Int + + private let testCase: TestCase + + public var id: String { + "\(moduleIdentifier)/\(section)/\(index)" + } + + @MainActor + init(module: Module.Type, section: String, index: Int, testCase: TestCase) { + self.moduleIdentifier = module.moduleIdentifier + self.moduleName = module.moduleName + self.section = section + self.testTitle = testCase.title + self.index = index + self.testCase = testCase + } + + /// Executes the original shared test and propagates its detailed error into Swift Testing and Xcode. + @MainActor + public func execute() async throws { + try await testCase.execute() + } +} + +extension ModuleTestEntry: CustomTestStringConvertible { + public var testDescription: String { + "\(moduleName) β€Ί \(section) β€Ί \(testTitle)" + } +} + +extension ModuleTestEntry: CustomTestArgumentEncodable { + public func encodeTestArgument(to encoder: some Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(id) + } +} + +public extension ModuleTestEntry { + /// Registers the supplied top-level modules and flattens every module test into a named argument. + @MainActor + static func entries(including modules: Module.Type...) -> [ModuleTestEntry] { + Build.register(modules) + return Build.allModules.flatMap { module in + module.tests.flatMap { section, tests in + tests.enumerated().map { index, testCase in + ModuleTestEntry( + module: module, + section: section, + index: index, + testCase: testCase + ) + } + } + } + } +} +#endif diff --git a/Sources/Core/TestExecutionMode+Equatable.swift b/Sources/Core/TestExecutionMode+Equatable.swift new file mode 100644 index 0000000..d735842 --- /dev/null +++ b/Sources/Core/TestExecutionMode+Equatable.swift @@ -0,0 +1,3 @@ +#if compiler(>=5.9) +extension TestExecutionMode: Equatable {} +#endif From 69e9f64309dfe6641ff203edcf76c919c0f60973 Mon Sep 17 00:00:00 2001 From: kudit Date: Mon, 27 Jul 2026 10:32:30 -0400 Subject: [PATCH 003/107] Fix test execution availability and exclusivity --- Sources/Core/Test.swift | 366 +++++++++++++++++++++++++--------------- 1 file changed, 228 insertions(+), 138 deletions(-) diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index e9b5f46..98e8b83 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -1,12 +1,18 @@ +// TODO: Once Swift Testing is available, can re-write all this code into test classes that conform to Swift Testing so that we can also run code in Previews and Test Applications? Use macros to duplicate #expect( functionality syntax? Or can we use somehow in UI still? public typealias TestClosure = @Sendable () async throws -> Void /// A portable snapshot of the source location that initiated an operation. +/// +/// Passing one value is useful when an asynchronous helper needs to retain and forward a caller's +/// location. Existing APIs continue exposing individual source arguments for source compatibility, +/// while new APIs can accept `SourceContext` when carrying the complete location is clearer. public struct SourceContext: Sendable, CustomStringConvertible { public let file: String public let function: String public let line: Int public let column: Int + /// Captures the call site by default. public init( file: String = #file, function: String = #function, @@ -24,7 +30,7 @@ public struct SourceContext: Sendable, CustomStringConvertible { } } -/// An expectation failure that retains the original source location for command-line and external test runners. +/// An expectation failure that retains the original source location. public struct TestFailure: Error, Sendable, CustomStringConvertible { public let message: String public let source: SourceContext @@ -41,273 +47,324 @@ public struct TestFailure: Error, Sendable, CustomStringConvertible { #if canImport(Foundation) extension TestFailure: LocalizedError { - public var errorDescription: String? { description } + public var errorDescription: String? { + description + } } #endif +// This could be anything, not necessary a struct or class, so if we need this, have a list of tests rather than a Testable object +//// don't make this public to avoid compiling test stuff into framework, however, do make public so apps can add in their own tests. +//public protocol Testable { +// // actor isolated since each Test is @MainActor isolated due to being an ObservableObject. +// @available(watchOS 6, *) +// @MainActor static var tests: [Test] { get } +//} + +// TODO: NEXT: Convert these to Testing expectations so we don't have to write custom error descriptions. Also move to Test static method that is shadowed in the global space. /// Sets an expectation for a reusable Compatibility test. -public func expect( - _ condition: Bool, - _ debugString: String? = nil, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column -) throws { +/// +/// The source location defaults mirror Swift Testing's diagnostics while remaining callable from +/// live applications, previews, older systems, and test runners that do not provide Swift Testing. +public func expect(_ condition: Bool, _ debugString: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { guard condition else { + let message = debugString ?? "Expectation failed" let source = SourceContext(file: file, function: function, line: line, column: column) - let message: String - if let debugString { - message = debugString - } else { -#if canImport(Foundation) - let isMainThread = Thread.isMainThread -#else - let isMainThread = true -#endif - message = Compatibility.settings.debugFormat( - "Expectation failed", - .ERROR, - isMainThread, - Compatibility.settings.debugEmojiSupported, - true, - true, - file, - function, - line, - column - ) - } debug(message, level: .ERROR, file: file, function: function, line: line, column: column) throw TestFailure(message, source: source) } } /// Requires two equatable values to be equal and reports both values when they differ. -public func expectEqual( - _ actual: Value, - _ expected: Value, - _ message: String? = nil, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column -) throws { +/// +/// - Parameters: +/// - actual: The value produced by the code under test. +/// - expected: The value the test requires. +/// - message: Optional context appended to the generated actual-versus-expected diagnostic. +public func expectEqual(_ actual: Value, _ expected: Value, _ message: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { + // Build the comparison text here so UI runs receive the same useful values that Swift Testing displays. let context = message.map { " \($0)" } ?? "" - try expect( - actual == expected, - "Expected \(String(reflecting: expected)), but received \(String(reflecting: actual)).\(context)", - file: file, - function: function, - line: line, - column: column - ) + try expect(actual == expected, "Expected \(String(reflecting: expected)), but received \(String(reflecting: actual)).\(context)", file: file, function: function, line: line, column: column) } /// Requires two equatable values to differ and reports the shared value when they do not. -public func expectNotEqual( - _ actual: Value, - _ unexpected: Value, - _ message: String? = nil, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column -) throws { +public func expectNotEqual(_ actual: Value, _ unexpected: Value, _ message: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { + // Include the unexpected value so a failure remains actionable outside a debugger. let context = message.map { " \($0)" } ?? "" - try expect( - actual != unexpected, - "Expected a value other than \(String(reflecting: unexpected)), but received it.\(context)", - file: file, - function: function, - line: line, - column: column - ) + try expect(actual != unexpected, "Expected a value other than \(String(reflecting: unexpected)), but received it.\(context)", file: file, function: function, line: line, column: column) } -/// Suppresses debug messages during a synchronous execution block and always restores the prior logger. +// NOTE: Really wish there was a way of writing a possibly async function or doing this using a generic so we don't have to duplicate code. +// TODO: Find a way to prevent conflicts here when run simultaneously. This really should only be used for testing. +/// Suppress debug messages during this execution block. Allows fetching the debug string as normal. public func debugSuppress(_ block: () throws -> Void) rethrows { let log = Compatibility.settings.debugLog -#if canImport(Foundation) - let suppressThread = Thread.current -#endif + #if canImport(Foundation) + let suppressThread = Thread.current // restrict the silencing to this thread/closure assuming no background tasks are doing printing + #endif Compatibility.settings.debugLog = { message in -#if canImport(Foundation) - if Thread.current != suppressThread { log(message) } -#else + #if canImport(Foundation) + if Thread.current != suppressThread { + log(message) // do normal logging + } + #else log(message) -#endif + #endif + } + defer { + Compatibility.settings.debugLog = log } - defer { Compatibility.settings.debugLog = log } try block() } - -/// Suppresses debug messages during an asynchronous execution block and always restores the prior logger. -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +/// Suppress debug messages during this async execution block. Allows fetching the debug string as normal. +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // due to Concurrency +//@MainActor public func debugSuppress(_ block: () async throws -> Void) async rethrows { let log = Compatibility.settings.debugLog + // unable to get thread in async functions so just ignore and hope it doesn't run concurrently interrupting other debug messages. Compatibility.settings.debugLog = { _ in } - defer { Compatibility.settings.debugLog = log } + defer { + Compatibility.settings.debugLog = log + } try await block() } - +// Testing is only supported with Swift 5.9+ #if compiler(>=5.9) /// Controls whether a reusable test may overlap other reusable tests. -public enum TestExecutionMode: Sendable { +public enum TestExecutionMode: Sendable, Equatable { case parallel case serialized } +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) private actor TestExecutionGate { static let shared = TestExecutionGate() - private var isRunning = false - private var waiters: [CheckedContinuation] = [] - func acquire() async { - if !isRunning { - isRunning = true - return + private var activeParallelCount = 0 + private var serializedRunning = false + private var parallelWaiters: [CheckedContinuation] = [] + private var serializedWaiters: [CheckedContinuation] = [] + + func acquire(_ mode: TestExecutionMode) async { + switch mode { + case .parallel: + if !serializedRunning && serializedWaiters.isEmpty { + activeParallelCount += 1 + return + } + await withCheckedContinuation { continuation in + parallelWaiters.append(continuation) + } + + case .serialized: + if !serializedRunning && activeParallelCount == 0 { + serializedRunning = true + return + } + await withCheckedContinuation { continuation in + serializedWaiters.append(continuation) + } + } + } + + func release(_ mode: TestExecutionMode) { + switch mode { + case .parallel: + activeParallelCount -= 1 + if activeParallelCount == 0 { + resumeWaitingTests() + } + + case .serialized: + serializedRunning = false + resumeWaitingTests() } - await withCheckedContinuation { waiters.append($0) } } - func release() { - if waiters.isEmpty { - isRunning = false - } else { - waiters.removeFirst().resume() + private func resumeWaitingTests() { + if !serializedWaiters.isEmpty { + serializedRunning = true + serializedWaiters.removeFirst().resume() + return + } + + let waiters = parallelWaiters + parallelWaiters.removeAll() + activeParallelCount += waiters.count + for waiter in waiters { + waiter.resume() } } } +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) private struct TestExecution: Sendable { let title: String + let source: SourceContext let setUp: TestClosure? let test: TestClosure let tearDown: TestClosure? let mode: TestExecutionMode func perform() async throws { - if mode == .serialized { - await TestExecutionGate.shared.acquire() - } - + await TestExecutionGate.shared.acquire(mode) do { try await performLifecycle() - if mode == .serialized { - await TestExecutionGate.shared.release() - } + await TestExecutionGate.shared.release(mode) } catch { - if mode == .serialized { - await TestExecutionGate.shared.release() - } + await TestExecutionGate.shared.release(mode) throw error } } private func performLifecycle() async throws { - let previousSettings = Compatibility.settings - defer { Compatibility.settings = previousSettings } - var primaryError: (any Error)? + do { try await setUp?() try await test() } catch { - primaryError = error + primaryError = normalized(error) } do { try await tearDown?() } catch { + let teardownError = normalized(error) if let primaryError { - debug("\(title) teardown also failed: \(error)", level: .ERROR) + debug("\(title) teardown also failed: \(teardownError)", level: .ERROR) throw primaryError } - throw error + throw teardownError } if let primaryError { throw primaryError } } + + private func normalized(_ error: any Error) -> any Error { + if error is TestFailure { + return error + } + return TestFailure("\(title) failed: \(error)", source: source) + } } +// Test Handlers @MainActor @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +/// A reusable named test that can run in Compatibility's live UI or an external test framework. +/// +/// `TestCase` intentionally borrows XCTest's familiar terminology, but it is not an +/// `XCTestCase` subclass or a drop-in replacement. Each value describes one closure-based test, +/// while optional setup and teardown closures provide lightweight lifecycle hooks. public final class TestCase: ObservableObject, @unchecked Sendable { private final class WeakReference: @unchecked Sendable { weak var value: T? - init(_ value: T?) { self.value = value } + + init(_ value: T?) { + self.value = value + } } public enum TestProgress: Sendable { case notStarted case running case pass - case fail(String) - + case fail(String) // for error message public var symbol: String { switch self { - case .notStarted: "❇️" - case .running: "πŸ”„" - case .pass: "βœ…" - case .fail: "β›”" + case .notStarted: + return "❇️" + case .running: + return "πŸ”„" + case .pass: + return "βœ…" + case .fail: + return "β›”" } } - public var errorMessage: String? { - if case let .fail(message) = self { message } else { nil } + if case let .fail(string) = self { + return string + } + return nil } } - public let title: String + public let source: SourceContext + public let executionMode: TestExecutionMode public let setUp: TestClosure? public var test: TestClosure public let tearDown: TestClosure? - public let executionMode: TestExecutionMode - + /// Source-compatible name for the test closure. + /// + /// `test` reads more naturally beside `setUp` and `tearDown`, while `task` remains available + /// because it was public before `TestCase` adopted lifecycle terminology. @available(*, deprecated, renamed: "test") public var task: TestClosure { get { test } set { test = newValue } } - @Published public var progress: TestProgress = .notStarted + /// Creates a reusable test with optional lifecycle closures. + /// + /// Teardown is attempted even when setup or the test throws, matching the cleanup expectation + /// familiar from XCTest without claiming `XCTestCase` API or inheritance compatibility. public init( _ title: String, executionMode: TestExecutionMode = .parallel, setUp: TestClosure? = nil, test: @escaping TestClosure, - tearDown: TestClosure? = nil + tearDown: TestClosure? = nil, + source: SourceContext = SourceContext() ) { self.title = title + self.source = source self.executionMode = executionMode self.setUp = setUp self.test = test self.tearDown = tearDown } + /// Creates a reusable test without separate setup or teardown work. public convenience init( _ title: String, executionMode: TestExecutionMode = .parallel, + source: SourceContext = SourceContext(), _ test: @escaping TestClosure ) { - self.init(title, executionMode: executionMode, test: test) + self.init(title, executionMode: executionMode, test: test, source: source) } private var execution: TestExecution { - TestExecution(title: title, setUp: setUp, test: test, tearDown: tearDown, mode: executionMode) + TestExecution( + title: title, + source: source, + setUp: setUp, + test: test, + tearDown: tearDown, + mode: executionMode + ) } + /// Executes the test closure directly for an external test framework. + /// + /// Swift Testing and XCTest adapters should prefer this awaited path because thrown expectation + /// failures retain the external runner's native test context without polling observable UI state. public func execute() async throws { try await execution.perform() } + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public func run() { - guard progress != .running else { return } + if case .running = progress { + return + } + let execution = execution let weakSelf = WeakReference(self) progress = .running @@ -315,50 +372,75 @@ public final class TestCase: ObservableObject, @unchecked Sendable { Task.detached(priority: .userInitiated) { do { try await execution.perform() - await MainActor.run { weakSelf.value?.progress = .pass } + await MainActor.run { + weakSelf.value?.progress = .pass + } } catch { let message = String(describing: error) - debug("\(execution.title) failed: \(message)", level: .ERROR) - await MainActor.run { weakSelf.value?.progress = .fail(message) } + debug(message, level: .ERROR) + await MainActor.run { + weakSelf.value?.progress = .fail(message) + } } } } public func isFinished() -> Bool { switch progress { - case .pass, .fail: true - default: false + case .pass, .fail: + return true + default: + return false } } public func succeeded() -> Bool { - if case .pass = progress { true } else { false } + switch progress { + case .pass: + return true + default: + return false + } } - public var errorMessage: String? { progress.errorMessage } + public var errorMessage: String? { + progress.errorMessage + } public var description: String { - let error = progress.errorMessage.map { "\n\t\($0)" } ?? "" - return "\(progress): \(title)\(error)" + var errorString = "" + if let errorMessage = progress.errorMessage { + errorString = "\n\t\(errorMessage)" + } + return "\(progress): \(title)\(errorString)" } } +/// The original test type name retained for source compatibility with Compatibility 1.16. +/// +/// Use ``TestCase`` in new code to avoid colliding with Swift Testing's `Test` type. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @available(*, deprecated, renamed: "TestCase") public typealias Test = TestCase @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension TestCase { - static func dummyAsyncThrows() async throws {} + static func dummyAsyncThrows() async throws { + } } @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) public extension TestCase { + /// Every reusable Compatibility test, grouped in deterministic display and execution order. + /// + /// This is the package's canonical test catalog. The in-app UI and Swift Testing bridge both + /// consume this property so a test is authored once and remains runnable in either environment. @MainActor static let namedTests: OrderedDictionary = { var tests: OrderedDictionary = [ "Expectation Tests": [ TestCase("Equality diagnostics") { + // Exercise the public comparison helpers on their success paths without intentionally failing the shared suite. try expectEqual(["Compatibility", "TestCase"], ["Compatibility", "TestCase"]) try expectNotEqual(Compatibility.version, Version("0.0.0")) }, @@ -376,7 +458,11 @@ public extension TestCase { "Application Tests": Application.tests, ] #if canImport(Foundation) - tests.merge(["Coding Tests": codingTests]) { current, _ in current } + tests.merge([ + "Coding Tests": codingTests, + ]) { current, _ in current } +#endif +#if canImport(Foundation) tests["Bundle Tests"] = Bundle.tests tests["File Manager Tests"] = FileManager.tests tests["Pasteboard Tests"] = Pasteboard.tests @@ -385,6 +471,7 @@ public extension TestCase { tests["Date Tests"] = Date.tests tests["Threading Tests"] = Compatibility.threadingTests #if canImport(Combine) || canImport(FoundationNetworking) + // FoundationNetworking supplies URLSession through libcurl on Linux. tests["Network Tests"] = PostData.tests #endif #endif @@ -394,8 +481,11 @@ public extension TestCase { @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) public extension Compatibility { + /// Compatibility's global test catalog. @MainActor - static var tests: OrderedDictionary { TestCase.namedTests } + static var tests: OrderedDictionary { + TestCase.namedTests + } } #if canImport(SwiftUI) && canImport(Foundation) From a2624140efcef20532113f65deca63191a356f37 Mon Sep 17 00:00:00 2001 From: kudit Date: Mon, 27 Jul 2026 10:33:47 -0400 Subject: [PATCH 004/107] Keep testing product manifest changes focused --- Package.swift | 189 +++++++++++++++++++++++++++++++------------------- 1 file changed, 116 insertions(+), 73 deletions(-) diff --git a/Package.swift b/Package.swift index 5e7f56c..96c3426 100644 --- a/Package.swift +++ b/Package.swift @@ -15,39 +15,57 @@ import PackageDescription import AppleProductTypes #endif +// Products define the executables and libraries a package produces, making them visible to other packages. var products = [ - Product.library( - name: "\(packageLibraryName) Library", - targets: [packageLibraryName] - ), + Product.library( + name: "\(packageLibraryName) Library", // has to be named different from the iOSApplication or Swift Playgrounds won't open correctly + targets: [packageLibraryName] + ), ] +// Targets are the basic building blocks of a package, defining a module or a test suite. +// Targets can depend on other targets in this package and products from dependencies. var targets = [ - Target.target( - name: packageLibraryName, - dependencies: [], - path: "Sources", - exclude: ["CompatibilityTesting"] - ), + Target.target( + name: packageLibraryName, + dependencies: [ +// .product(name: "Compatibility Library", package: "compatibility"), // apparently needs to be lowercase. Also note this is "Compatibility Library" not "Compatibility" + ], + path: "Sources", + exclude: ["CompatibilityTesting"] + // If resources need to be included in the module, include here +// ,resources: [ // unfortuantely cannot be conditionally compiled based on Swift version since the tool seems to be run on latest version. +// Resource.process("Resources"), +// ] +// ,swiftSettings: [ +// .enableUpcomingFeature("BareSlashRegexLiterals") +// ] + ), ] var platforms: [SupportedPlatform] = [ - .macOS("10.10"), - .tvOS("11"), - .watchOS("4"), + .macOS("10.10"), // SwiftPM's oldest supported macOS declaration; newer APIs remain availability-gated. + .tvOS("11"), // 13 minimum for SwiftUI, 15 minimum for Date.now, 17 minimum for Menu + .watchOS("4"), // 6 minimum for SwiftUI, watchOS 7 typically needed for most UI, 8 for Date.now, however (for #buildAvailability) so really should be watchOS 9+. ] #if SwiftPlaygrounds || canImport(PlaygroundSupport) -platforms += [.iOS("15.2")] +platforms += [ + .iOS("15.2"), // minimum for Swift Playgrounds support (maximum version for test iPhone 7) +] #else -platforms += [.iOS("11")] +platforms += [ + .iOS("11"), // 13 minimum for Combine/SwiftUI, 15 minimum for Date.now, (maximum version for test iPhone 7) +] #endif #if compiler(>=5.9) && os(visionOS) -platforms += [.visionOS("1.0")] +platforms += [ + .visionOS("1.0"), // PackageDescription 5.9 supports visionOS, so SPI and visionOS clients can see the platform explicitly. +] #endif -#if canImport(AppleProductTypes) +#if canImport(AppleProductTypes) // swift package dump-package fails because of this import AppleProductTypes let executableTargetName = "\(packageLibraryName)TestAppModule" @@ -59,77 +77,102 @@ let appName = "\(packageLibraryName) App" #endif products += [ - .iOSApplication( - name: appName, - targets: [executableTargetName], - teamIdentifier: "3QPV894C33", - displayVersion: version, - bundleVersion: "1", - appIcon: .asset("AppIcon"), - accentColor: .presetColor(.orange), - supportedDeviceFamilies: [.pad, .phone], - supportedInterfaceOrientations: [ - .portrait, - .landscapeRight, - .landscapeLeft, - .portraitUpsideDown(.when(deviceFamilies: [.pad])), - ], - capabilities: [.outgoingNetworkConnections()], - appCategory: .developerTools - ), + .iOSApplication( + name: appName, // needs to match package name to open properly in Swift Playgrounds Date: Mon, 27 Jul 2026 10:34:02 -0400 Subject: [PATCH 005/107] Fold execution mode conformance into its declaration --- Sources/Core/TestExecutionMode+Equatable.swift | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Sources/Core/TestExecutionMode+Equatable.swift diff --git a/Sources/Core/TestExecutionMode+Equatable.swift b/Sources/Core/TestExecutionMode+Equatable.swift deleted file mode 100644 index d735842..0000000 --- a/Sources/Core/TestExecutionMode+Equatable.swift +++ /dev/null @@ -1,3 +0,0 @@ -#if compiler(>=5.9) -extension TestExecutionMode: Equatable {} -#endif From 49bff8adb491e2eff054a18149ccaedfa0bca917 Mon Sep 17 00:00:00 2001 From: kudit Date: Mon, 27 Jul 2026 11:26:35 -0400 Subject: [PATCH 006/107] Fix test infrastructure availability Fix test infrastructure availability --- Sources/CompatibilityTesting/ModuleTestEntry.swift | 4 ++++ Sources/Core/Test.swift | 1 + 2 files changed, 5 insertions(+) diff --git a/Sources/CompatibilityTesting/ModuleTestEntry.swift b/Sources/CompatibilityTesting/ModuleTestEntry.swift index 7158451..eaddcb9 100644 --- a/Sources/CompatibilityTesting/ModuleTestEntry.swift +++ b/Sources/CompatibilityTesting/ModuleTestEntry.swift @@ -3,6 +3,7 @@ import Compatibility import Testing /// One reusable Compatibility `TestCase` presented as an individual Swift Testing argument. +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public struct ModuleTestEntry: Sendable, Identifiable { public let moduleIdentifier: String public let moduleName: String @@ -33,12 +34,14 @@ public struct ModuleTestEntry: Sendable, Identifiable { } } +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension ModuleTestEntry: CustomTestStringConvertible { public var testDescription: String { "\(moduleName) β€Ί \(section) β€Ί \(testTitle)" } } +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension ModuleTestEntry: CustomTestArgumentEncodable { public func encodeTestArgument(to encoder: some Encoder) throws { var container = encoder.singleValueContainer() @@ -46,6 +49,7 @@ extension ModuleTestEntry: CustomTestArgumentEncodable { } } +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension ModuleTestEntry { /// Registers the supplied top-level modules and flattens every module test into a named argument. @MainActor diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index 98e8b83..f7f5d18 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -128,6 +128,7 @@ public func debugSuppress(_ block: () async throws -> Void) async rethrows { } try await block() } + // Testing is only supported with Swift 5.9+ #if compiler(>=5.9) From 5a8f751d00384b1ddae5efacbbac7b98a7c1eb96 Mon Sep 17 00:00:00 2001 From: kudit Date: Mon, 27 Jul 2026 11:33:34 -0400 Subject: [PATCH 007/107] Add source-based debug formatting conveniences --- Sources/Core/DebugFormatContext.swift | 159 ++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Sources/Core/DebugFormatContext.swift diff --git a/Sources/Core/DebugFormatContext.swift b/Sources/Core/DebugFormatContext.swift new file mode 100644 index 0000000..30a9a10 --- /dev/null +++ b/Sources/Core/DebugFormatContext.swift @@ -0,0 +1,159 @@ +/// Named values supplied to a custom debug formatter. +/// +/// Use `Compatibility.settings.debugFormatter` for new code. The existing +/// positional `debugFormat` closure remains source-compatible. +public struct DebugFormatContext: Sendable { + public let message: String + public let level: DebugLevel + public let isMainThread: Bool + public let emojiSupported: Bool + public let includeContext: Bool + public let includeTimestamp: Bool + public let source: SourceContext + + public init( + message: String, + level: DebugLevel, + isMainThread: Bool, + emojiSupported: Bool, + includeContext: Bool, + includeTimestamp: Bool, + source: SourceContext + ) { + self.message = message + self.level = level + self.isMainThread = isMainThread + self.emojiSupported = emojiSupported + self.includeContext = includeContext + self.includeTimestamp = includeTimestamp + self.source = source + } +} + +public typealias DebugFormatter = (DebugFormatContext) -> String + +public extension CompatibilityConfiguration { + /// Preferred labeled alternative to the legacy positional `debugFormat` closure. + /// + /// Assigning either property updates the same underlying formatter, so existing + /// `debugFormat = { message, level, ... }` call sites continue to compile. + var debugFormatter: DebugFormatter { + get { + let legacyFormatter = debugFormat + return { context in + legacyFormatter( + context.message, + context.level, + context.isMainThread, + context.emojiSupported, + context.includeContext, + context.includeTimestamp, + context.source.file, + context.source.function, + context.source.line, + context.source.column + ) + } + } + set { + debugFormat = { + message, + level, + isMainThread, + emojiSupported, + includeContext, + includeTimestamp, + file, + function, + line, + column in + newValue( + DebugFormatContext( + message: message, + level: level, + isMainThread: isMainThread, + emojiSupported: emojiSupported, + includeContext: includeContext, + includeTimestamp: includeTimestamp, + source: SourceContext( + file: file, + function: function, + line: line, + column: column + ) + ) + ) + } + } + } +} + +#if !hasFeature(Embedded) +public extension Compatibility { + /// Logs a message using an already-captured source location. + @discardableResult + static func debug( + _ message: Any, + level: DebugLevel = .defaultLevel, + source: SourceContext + ) -> String { + debug( + message, + level: level, + file: source.file, + function: source.function, + line: source.line, + column: source.column + ) + } +} + +/// Logs a message using an already-captured source location. +@discardableResult +public func debug( + _ message: Any, + level: DebugLevel = .defaultLevel, + source: SourceContext +) -> String { + Compatibility.debug(message, level: level, source: source) +} +#else +public extension Compatibility { + /// Logs a message using an already-captured source location. + @discardableResult + static func debug( + _ message: String, + level: DebugLevel = .defaultLevel, + source: SourceContext + ) -> String { + debug( + message, + isMainThread: true, + level: level, + file: source.file, + function: source.function, + line: source.line, + column: source.column + ) + } +} + +/// Logs a message using an already-captured source location. +@discardableResult +public func debug( + _ message: String, + level: DebugLevel = .defaultLevel, + source: SourceContext +) -> String { + Compatibility.debug(message, level: level, source: source) +} +#endif + +public extension TestFailure { + /// Logs this failure at its original source location and returns it for throwing. + @discardableResult + func debug(level: DebugLevel = .ERROR) -> Self { + Compatibility.debug(message, level: level, source: source) + return self + } +} From ce3fa40fdb5c18770e2cadf8f2f7ebc5f9bc9fe4 Mon Sep 17 00:00:00 2001 From: kudit Date: Mon, 27 Jul 2026 12:28:26 -0400 Subject: [PATCH 008/107] Move debug formatting helpers into Debug.swift --- Sources/Core/DebugFormatContext.swift | 159 -------------------------- 1 file changed, 159 deletions(-) delete mode 100644 Sources/Core/DebugFormatContext.swift diff --git a/Sources/Core/DebugFormatContext.swift b/Sources/Core/DebugFormatContext.swift deleted file mode 100644 index 30a9a10..0000000 --- a/Sources/Core/DebugFormatContext.swift +++ /dev/null @@ -1,159 +0,0 @@ -/// Named values supplied to a custom debug formatter. -/// -/// Use `Compatibility.settings.debugFormatter` for new code. The existing -/// positional `debugFormat` closure remains source-compatible. -public struct DebugFormatContext: Sendable { - public let message: String - public let level: DebugLevel - public let isMainThread: Bool - public let emojiSupported: Bool - public let includeContext: Bool - public let includeTimestamp: Bool - public let source: SourceContext - - public init( - message: String, - level: DebugLevel, - isMainThread: Bool, - emojiSupported: Bool, - includeContext: Bool, - includeTimestamp: Bool, - source: SourceContext - ) { - self.message = message - self.level = level - self.isMainThread = isMainThread - self.emojiSupported = emojiSupported - self.includeContext = includeContext - self.includeTimestamp = includeTimestamp - self.source = source - } -} - -public typealias DebugFormatter = (DebugFormatContext) -> String - -public extension CompatibilityConfiguration { - /// Preferred labeled alternative to the legacy positional `debugFormat` closure. - /// - /// Assigning either property updates the same underlying formatter, so existing - /// `debugFormat = { message, level, ... }` call sites continue to compile. - var debugFormatter: DebugFormatter { - get { - let legacyFormatter = debugFormat - return { context in - legacyFormatter( - context.message, - context.level, - context.isMainThread, - context.emojiSupported, - context.includeContext, - context.includeTimestamp, - context.source.file, - context.source.function, - context.source.line, - context.source.column - ) - } - } - set { - debugFormat = { - message, - level, - isMainThread, - emojiSupported, - includeContext, - includeTimestamp, - file, - function, - line, - column in - newValue( - DebugFormatContext( - message: message, - level: level, - isMainThread: isMainThread, - emojiSupported: emojiSupported, - includeContext: includeContext, - includeTimestamp: includeTimestamp, - source: SourceContext( - file: file, - function: function, - line: line, - column: column - ) - ) - ) - } - } - } -} - -#if !hasFeature(Embedded) -public extension Compatibility { - /// Logs a message using an already-captured source location. - @discardableResult - static func debug( - _ message: Any, - level: DebugLevel = .defaultLevel, - source: SourceContext - ) -> String { - debug( - message, - level: level, - file: source.file, - function: source.function, - line: source.line, - column: source.column - ) - } -} - -/// Logs a message using an already-captured source location. -@discardableResult -public func debug( - _ message: Any, - level: DebugLevel = .defaultLevel, - source: SourceContext -) -> String { - Compatibility.debug(message, level: level, source: source) -} -#else -public extension Compatibility { - /// Logs a message using an already-captured source location. - @discardableResult - static func debug( - _ message: String, - level: DebugLevel = .defaultLevel, - source: SourceContext - ) -> String { - debug( - message, - isMainThread: true, - level: level, - file: source.file, - function: source.function, - line: source.line, - column: source.column - ) - } -} - -/// Logs a message using an already-captured source location. -@discardableResult -public func debug( - _ message: String, - level: DebugLevel = .defaultLevel, - source: SourceContext -) -> String { - Compatibility.debug(message, level: level, source: source) -} -#endif - -public extension TestFailure { - /// Logs this failure at its original source location and returns it for throwing. - @discardableResult - func debug(level: DebugLevel = .ERROR) -> Self { - Compatibility.debug(message, level: level, source: source) - return self - } -} From 60b7b9ead77d12509c1753bfb8816eb7ad54a414 Mon Sep 17 00:00:00 2001 From: kudit Date: Mon, 27 Jul 2026 12:29:46 -0400 Subject: [PATCH 009/107] Consolidate debug message and formatting APIs --- Sources/Core/Debug.swift | 149 +++++++++++++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index c1ac45d..08f9bad 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -1,6 +1,43 @@ // Here since all releated to Debug code. +#if hasFeature(Embedded) +public typealias DebugMessage = String +#else +public typealias DebugMessage = Any +#endif + +/// Named values supplied to a custom debug formatter. +public struct DebugFormatContext: Sendable { + public let message: String + public let level: DebugLevel + public let isMainThread: Bool + public let emojiSupported: Bool + public let includeContext: Bool + public let includeTimestamp: Bool + public let source: SourceContext + + public init( + message: String, + level: DebugLevel, + isMainThread: Bool, + emojiSupported: Bool, + includeContext: Bool, + includeTimestamp: Bool, + source: SourceContext + ) { + self.message = message + self.level = level + self.isMainThread = isMainThread + self.emojiSupported = emojiSupported + self.includeContext = includeContext + self.includeTimestamp = includeTimestamp + self.source = source + } +} + +public typealias DebugFormatter = (DebugFormatContext) -> String + public struct CompatibilityConfiguration: PropertyIterable { /// Override to change the which debug levels are output. This level and higher (more important) will be output. public var debugLevelCurrent: DebugLevel = Build.isDebug ? .DEBUG : .WARNING @@ -51,6 +88,58 @@ public struct CompatibilityConfiguration: PropertyIterable { return "\(timestamp)\(message)" } } + + /// Preferred labeled alternative to the legacy positional `debugFormat` closure. + /// Assigning either property updates the same underlying formatter. + public var debugFormatter: DebugFormatter { + get { + let legacyFormatter = debugFormat + return { context in + legacyFormatter( + context.message, + context.level, + context.isMainThread, + context.emojiSupported, + context.includeContext, + context.includeTimestamp, + context.source.file, + context.source.function, + context.source.line, + context.source.column + ) + } + } + set { + debugFormat = { + message, + level, + isMainThread, + emojiSupported, + includeContext, + includeTimestamp, + file, + function, + line, + column in + newValue( + DebugFormatContext( + message: message, + level: level, + isMainThread: isMainThread, + emojiSupported: emojiSupported, + includeContext: includeContext, + includeTimestamp: includeTimestamp, + source: SourceContext( + file: file, + function: function, + line: line, + column: column + ) + ) + ) + } + } + } /// Function to handle how the debug messages are logged. Can change to have the messages logged to a file or a string. Default is to print to the console. public var debugLog = { (message: String) in @@ -114,11 +203,11 @@ public struct CustomError: Error, Sendable { } @discardableResult func debug() -> String { -#if !hasFeature(Embedded) - return Compatibility.debug(description, level: level ?? DebugLevel.defaultLevel, file: file, function: function, line: line, column: column) -#else - return Compatibility.debug(description, isMainThread: true, level: level ?? DebugLevel.defaultLevel, file: file, function: function, line: line, column: column) -#endif + Compatibility.debug( + description, + level: level ?? DebugLevel.defaultLevel, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } } extension CustomError: CustomStringConvertible { @@ -265,19 +354,34 @@ public extension Compatibility { - Parameter line: For bubbling down the #line number from a call site. - Parameter column: For bubbling down the #column number from a call site. (Not used currently but here for completeness). */ -#if !hasFeature(Embedded) @discardableResult - static func debug(_ message: Any, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { + static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { +#if hasFeature(Embedded) + return debug(message, isMainThread: true, level: level, file: file, function: function, line: line, column: column) +#else #if canImport(Foundation) let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing #else let isMainThread = true #endif let message = String(describing: message) // convert to sendable item to avoid any thread issues. - return debug(message, isMainThread: isMainThread, level: level, file: file, function: function, line: line, column: column) - } #endif + } + + /// Logs a message using an already-captured source location. + @discardableResult + static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { + debug( + message, + level: level, + file: source.file, + function: source.function, + line: source.line, + column: source.column + ) + } + /// Put most of the business logic here for compatibility with WASM. isMainThread: is required to differentiate but can be removed in global definition @discardableResult static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { @@ -313,18 +417,16 @@ public extension Compatibility { - Parameter line: For bubbling down the #line number from a call site. - Parameter column: For bubbling down the #column number from a call site. (Not used currently but here for completeness). */ -#if !hasFeature(Embedded) @discardableResult -public func debug(_ message: Any, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { - return Compatibility.debug(message, level: level, file: file, function: function, line: line, column: column) +public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { + Compatibility.debug(message, level: level, file: file, function: function, line: line, column: column) } -#else + +/// Logs a message using an already-captured source location. @discardableResult -public func debug(_ message: String, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { - // go directly to alternate version since dynamic casting is unavailable in WASM - return Compatibility.debug(message, isMainThread: true, level: level, file: file, function: function, line: line, column: column) +public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { + Compatibility.debug(message, level: level, source: source) } -#endif // MARK: Debug(error) // This is to provide debugging at calltime when creating errors. @@ -339,11 +441,7 @@ public extension Error { - Parameter column: For bubbling down the #column number from a call site. (Not used currently but here for completeness). */ func debug(level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> Self { -#if !hasFeature(Embedded) Compatibility.debug(self.localizedDescription, level: level, file: file, function: function, line: line, column: column) -#else - Compatibility.debug(self.localizedDescription, isMainThread: true, level: level, file: file, function: function, line: line, column: column) -#endif return self } #if !canImport(Foundation) @@ -353,6 +451,15 @@ public extension Error { #endif } +public extension TestFailure { + /// Logs this failure at its original source location and returns it for throwing. + @discardableResult + func debug(level: DebugLevel = .ERROR) -> Self { + Compatibility.debug(message, level: level, source: source) + return self + } +} + // Testing and main-actor isolation are supported on current full-runtime WASM builds. #if compiler(>=5.9) From 04e75c73a1f24140b3341332044ae3bd4d3ce658 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 10:05:07 -0400 Subject: [PATCH 010/107] Simplified code duplication Simplified code duplication and context description. --- Sources/Core/Debug.swift | 27 ++++++++++++--------------- Sources/Core/Test.swift | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index 08f9bad..97f5e8d 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -1,6 +1,6 @@ +// TODO: Needs a real file header documentation/comment. - -// Here since all releated to Debug code. +// Here since all releated to Debug code to simplify conditional code gates. #if hasFeature(Embedded) public typealias DebugMessage = String #else @@ -328,9 +328,9 @@ public enum DebugLevel: Comparable, CustomStringConvertible, CaseIterable, Senda } /// Generates context string -#if !DEBUG @available(*, deprecated, message: "Use Compatibility.settings.debugFormat with the desired formatting options instead.") public func debugContext(isMainThread: Bool, file: String, function: String, line: Int, column: Int) -> String { + // TODO: Convert this to the debugFormatter callsite for clarity Compatibility.settings.debugFormat( "", .OFF, @@ -340,12 +340,11 @@ public func debugContext(isMainThread: Bool, file: String, function: String, lin Compatibility.settings.debugIncludeTimestamp, file, function, line, column) } -#endif // MARK: - Debug public extension Compatibility { /** - Ku: Debug helper for printing info to screen including file and line info of call site. Also can provide a log level for use in loggers or for globally turning on/off logging. (Modify DebugLevel.currentLevel to set level to output. When launching app, probably can set this to DebugLevel.OFF + Debug helper for printing info to screen including file and line info of call site. Also can provide a log level for use in loggers or for globally turning on/off logging. (Modify DebugLevel.currentLevel to set level to output. When launching app, set this to DebugLevel.OFF for release builds. - Parameter message: The message to report. - Parameter level: The logging level to use. @@ -356,17 +355,15 @@ public extension Compatibility { */ @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { -#if hasFeature(Embedded) - return debug(message, isMainThread: true, level: level, file: file, function: function, line: line, column: column) +#if hasFeature(Embedded) || !canImport(Foundation) + let isMainThread = true #else -#if canImport(Foundation) let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing -#else - let isMainThread = true #endif +#if canImport(Foundation) let message = String(describing: message) // convert to sendable item to avoid any thread issues. - return debug(message, isMainThread: isMainThread, level: level, file: file, function: function, line: line, column: column) #endif + return debug(message, isMainThread: isMainThread, level: level, file: file, function: function, line: line, column: column) } /// Logs a message using an already-captured source location. @@ -408,8 +405,8 @@ public extension Compatibility { } //DebugLevel.currentLevel = .ERROR /** - Ku: Debug helper for printing info to screen including file and line info of call site. Also can provide a log level for use in loggers or for globally turning on/off logging. (Modify DebugLevel.currentLevel to set level to output. When launching app, probably can set this to DebugLevel.OFF - + Debug helper for printing info to screen including file and line info of call site. Also can provide a log level for use in loggers or for globally turning on/off logging. (Modify DebugLevel.currentLevel to set level to output. When launching app, set this to DebugLevel.OFF for release builds. + - Parameter message: The message to report. - Parameter level: The logging level to use. - Parameter file: For bubbling down the #file name from a call site. @@ -419,13 +416,13 @@ public extension Compatibility { */ @discardableResult public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { - Compatibility.debug(message, level: level, file: file, function: function, line: line, column: column) + return Compatibility.debug(message, level: level, file: file, function: function, line: line, column: column) } /// Logs a message using an already-captured source location. @discardableResult public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { - Compatibility.debug(message, level: level, source: source) + return Compatibility.debug(message, level: level, source: source) } // MARK: Debug(error) diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index f7f5d18..aee2ca1 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -26,7 +26,7 @@ public struct SourceContext: Sendable, CustomStringConvertible { } public var description: String { - "\(file):\(line):\(column) in \(function)" + "\(file.lastPathComponent):\(line):\(column) in \(function)" } } From 23b6f564aa26b2bb9ededb249041c39f8888c95c Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 11:53:51 -0400 Subject: [PATCH 011/107] Included expanded support for lastPathComponent --- Sources/Core/Debug.swift | 2 -- Sources/Foundation/String.swift | 9 +++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index 97f5e8d..91aeaf5 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -359,8 +359,6 @@ public extension Compatibility { let isMainThread = true #else let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing -#endif -#if canImport(Foundation) let message = String(describing: message) // convert to sendable item to avoid any thread issues. #endif return debug(message, isMainThread: isMainThread, level: level, file: file, function: function, line: line, column: column) diff --git a/Sources/Foundation/String.swift b/Sources/Foundation/String.swift index 028e7af..156296b 100644 --- a/Sources/Foundation/String.swift +++ b/Sources/Foundation/String.swift @@ -534,14 +534,19 @@ public extension String { #endif return URL(string: self) } - +#endif + /// Get last "path" component of a string (basically everything from the last `/` to the end) var lastPathComponent: String { + // ensure lastPathComponent is always available regardless of Foundation support by moving fallback code into the function. + #if canImport(Foundation) let parts = self.components(separatedBy: "/") let last = parts.last ?? self + #else + let last = self.split(whereSeparator: { $0 == "/" || $0 == "\\" }).last.map(String.init) ?? self + #endif return last } -#endif /// `true` if the byte length of the `String` is larger than 100k (the exact threashold may change) var isLarge: Bool { From 60ed6cf17206a798716033f591c39394e61942a9 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 15:28:14 -0400 Subject: [PATCH 012/107] Improve lastPathComponent for cross-platform compatibility Refactor lastPathComponent to support Windows-style paths and remove Foundation dependency. --- Sources/Foundation/String.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/Foundation/String.swift b/Sources/Foundation/String.swift index 156296b..c6bb787 100644 --- a/Sources/Foundation/String.swift +++ b/Sources/Foundation/String.swift @@ -538,13 +538,8 @@ public extension String { /// Get last "path" component of a string (basically everything from the last `/` to the end) var lastPathComponent: String { - // ensure lastPathComponent is always available regardless of Foundation support by moving fallback code into the function. - #if canImport(Foundation) - let parts = self.components(separatedBy: "/") - let last = parts.last ?? self - #else + // enables support on all platforms and handles Windows-style \ paths unlike the previous Foundation-only implementation. let last = self.split(whereSeparator: { $0 == "/" || $0 == "\\" }).last.map(String.init) ?? self - #endif return last } From a55ac765d8c159c40db6468f9971fdefa5f66dd7 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 15:31:50 -0400 Subject: [PATCH 013/107] Enhance CONTRIBUTING.md with collaborative coding workflow Added guidelines for collaborative coding workflow to improve interaction with maintainers. --- CONTRIBUTING.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a74d3d..94c6617 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,19 @@ PROMPT for updating Module packages: Review this Swift package for adoption of the Module APIs introduced in github.com/kudit/Compatibility v1.16.0 or later. Inspect the package’s existing architecture and preserve its public behavior and platform compatibility. Add or update its Compatibility dependency if necessary. Apply an appropriate Module conformance, including its version, direct Compatibility dependency, module dependencies, immediately available moduleInfo, ordered TestCase sections, and opt-in open-source repository metadata when applicable. Register the package from its highest-level module or document how an application should register it through Application.track(including:). Add complete inline DocC comments to the relevant public APIs so generated documentation can discover them. Do not create a .docc catalog, separate documentation articles, or another documentation folder. Preserve existing comments unless they are missing, unclear, or inaccurate. Put reusable tests in the module's TestCase collections so they run both in the in-app test UI and through the Swift Testing bridge; retain target-specific tests only where infrastructure requires them. Follow this package’s existing CONTRIBUTING.md, changelog, versioning, formatting, availability, and compatibility conventions. Avoid unrelated reformatting and whitespace-only changes. Before changing version numbers, compare the current changelog version with the latest committed Git version. If the active working-tree changelog is already ahead of Git, do not choose another version; synchronize that active version across every package manifest, Xcode project, public source constant, test fixture or suite heading, README or documentation display, and other hard-coded version surface. Please check that all deprecations (that can) have appropriate renamed clauses for easy fixits. +## Collaborative coding workflow + +When working interactively with a maintainer, generally (this shouldn't be meant to override thread instructions but are here as a default): +- Work in small, reviewable stages rather than delivering a large implementation all at once. +- Present one immediate decision or action at a time and pause for maintainer feedback unless instructed to do a batch. +- Explain design choices briefly and answer questions before continuing implementation. +- Preserve and review the maintainer's local edits before adding further changes. +- Let the maintainer build, edit, commit, and push between stages when practical. +- After each pushed maintainer change, review the latest commit before proposing or applying the next change. +- Keep pull requests in draft until the implementation is compiled, exercised by real tests, and fully reviewed. +- Avoid unrelated cleanup, broad reformatting, and speculative changes that make the diff harder to reason about. + + ## Version and changelog rules - Keep changelog entries in `## vX.X.X YYYY-MM-DD` format, with short line-separated notes under the current version. From cd9661753ba1fdbe9e5f47accfa9d60b7563e593 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 15:39:03 -0400 Subject: [PATCH 014/107] Exercise ModuleTestEntry through Swift Testing --- .../ModuleTestEntryTests.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Development/CompatibilityTests/ModuleTestEntryTests.swift diff --git a/Development/CompatibilityTests/ModuleTestEntryTests.swift b/Development/CompatibilityTests/ModuleTestEntryTests.swift new file mode 100644 index 0000000..4d1594b --- /dev/null +++ b/Development/CompatibilityTests/ModuleTestEntryTests.swift @@ -0,0 +1,28 @@ +// +// ModuleTestEntryTests.swift +// CompatibilityTests +// +// Exercises the reusable CompatibilityTesting adapter through Swift Testing. +// + +#if compiler(>=5.9) && canImport(Compatibility) && canImport(CompatibilityTesting) && canImport(Testing) +import Compatibility +import CompatibilityTesting +import Testing + +@Suite("Compatibility Module Test Entries") +struct ModuleTestEntryTests { + /// Presents every reusable Compatibility `TestCase` as its own named Swift Testing argument. + @Test( + "Compatibility Module Test", + arguments: await MainActor.run { + ModuleTestEntry.entries(including: Compatibility.self) + } + ) + @MainActor + @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) + func moduleTest(entry: ModuleTestEntry) async throws { + try await entry.execute() + } +} +#endif From d6b97cae8498ef15c16a0e02058aba14d3e58bf2 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 15:52:43 -0400 Subject: [PATCH 015/107] Update CHANGELOG with testing requirements Added testing requirements and TODOs for release preparation. --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78aeff7..5607f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +# TODO: +Testing required before release: + +- Build the package in Xcode with ⌘B. +- Run the full test plan with ⌘U. +- Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. +- Confirm the new entries execute successfully and preserve readable module, section, and test names. +- Remove the older grouped module-test bridge after the new adapter is verified, then rerun the tests. +- Run SwiftPM and supported-platform validation before tagging the release. + +## v1.18.3 2026-07-28 +TODO: Implement a comment matching this pull request changes. + ## v1.18.2 2026-07-23 Fixed Swift Package Index build errors and warnings across SwiftUI and WebAssembly targets. Replaced conditional SwiftUI `Group` wrappers with direct `@ViewBuilder` results and concrete text-selection types. From e03065f75eedf7b710abc673337618ec7b8c4ae7 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 20:01:04 -0400 Subject: [PATCH 016/107] Serialize debug tests and restore settings safely --- Sources/Core/Debug.swift | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index 91aeaf5..51e0e90 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -462,9 +462,18 @@ public extension TestFailure { public extension DebugLevel { @MainActor internal static let testDebugConfig: TestClosure = { - // NOTE: This might happen concurrently with other tests so could cause issues with output... - // preserve original settings + // These tests temporarily replace process-global debug settings. Capture the complete + // configuration before making any changes so the surrounding application or test suite + // observes exactly the same settings after this test finishes. let previousSettings = Compatibility.settings + + // `defer` runs whether the test succeeds or throws. This is important because an + // expectation failure exits the closure immediately; a normal assignment at the bottom + // would be skipped and could leave later tests using this temporary logger or formatter. + defer { + Compatibility.settings = previousSettings + } + DebugLevel.defaultLevel = .WARNING // testing override default level DebugLevel.currentLevel = .NOTICE // testing override current level @@ -506,10 +515,9 @@ Normal output: \(defaultOutput) let blankText = debug("TestCase return output", level: .DEBUG) // less than the current level so should be silent try expect(blankText == "", "expected empty string but found \(blankText)") - - // reset settings for other tests - Compatibility.settings = previousSettings - // output messages that happened concurrently + + // `previousSettings` is restored automatically by the `defer` above. + // Output captured while the temporary logger was active remains intentionally suppressed. // Compatibility.settings.debugLog(concurrentOutput) // debug("TEST OUTPUT", level: .ERROR) } @@ -540,8 +548,11 @@ Normal output: \(defaultOutput) @MainActor static let tests = [ - TestCase("debug configuration tests", testDebugConfig), - TestCase("debug tests", testDebug), + // Both tests mutate process-global debug state (`Compatibility.settings` or the + // logger used by `debugSuppress`). Serialized mode prevents them from overlapping + // each other or any parallel reusable test while those temporary changes are active. + TestCase("debug configuration tests", executionMode: .serialized, testDebugConfig), + TestCase("debug tests", executionMode: .serialized, testDebug), ] } #endif From c9bfda2cf55c58d0274ddfbec63efa846b618488 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 20:02:28 -0400 Subject: [PATCH 017/107] Remove duplicated grouped module test bridge --- .../CompatibilityTests.swift | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Development/CompatibilityTests/CompatibilityTests.swift b/Development/CompatibilityTests/CompatibilityTests.swift index b991bb8..a15bf8b 100644 --- a/Development/CompatibilityTests/CompatibilityTests.swift +++ b/Development/CompatibilityTests/CompatibilityTests.swift @@ -446,25 +446,8 @@ struct CompatibilityTests { } } - /// Runs every public module section through the same TestCase values used by the live UI. - @Test( - "Compatibility Module Tests", - arguments: await MainActor.run { Compatibility.tests.keys.elements } - ) - @MainActor - @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) - func moduleTests(section: String) async throws { - // Compatibility.tests is the authoritative package-wide test collection. - let tests = Compatibility.tests[section] ?? [] - try await withThrowingTaskGroup(of: Void.self) { group in - for test in tests { - // Each case is independently isolated by TestCase, so long-running rows can overlap. - group.addTask { - try await test.execute() - } - } - try await group.waitForAll() - } - } + // Reusable module tests now live in ModuleTestEntryTests.swift. That adapter creates one + // Swift Testing argument per TestCase, so keeping the former section-based bridge here would + // execute the same Compatibility tests twice and hide individual test names beneath a section. } #endif From fd1bf225ecd96d9517013fa258f236689438ac64 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 20:06:39 -0400 Subject: [PATCH 018/107] Document v1.19.0 test infrastructure changes --- CHANGELOG.md | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5607f74..d3d1eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,15 @@ Testing required before release: - Run the full test plan with ⌘U. - Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. - Confirm the new entries execute successfully and preserve readable module, section, and test names. -- Remove the older grouped module-test bridge after the new adapter is verified, then rerun the tests. +- Confirm the serialized debug tests restore `Compatibility.settings` even when an expectation throws. - Run SwiftPM and supported-platform validation before tagging the release. -## v1.18.3 2026-07-28 -TODO: Implement a comment matching this pull request changes. +## v1.19.0 2026-07-28 +Added the reusable `Compatibility Testing Library` product and `ModuleTestEntry` adapter so each module `TestCase` appears as an individually named Swift Testing result. +Unified `TestCase.execute()` and live test execution through one lifecycle implementation with explicit parallel and serialized execution modes. +Added source-aware test failures, labeled debug-format context, and source-context debugging conveniences while preserving existing debug-format call sites. +Made debug tests run exclusively and restore process-global debug settings with `defer`, including when an expectation throws. +Expanded contributor guidance for short, staged, maintainer-reviewed coding workflows. ## v1.18.2 2026-07-23 Fixed Swift Package Index build errors and warnings across SwiftUI and WebAssembly targets. @@ -166,7 +170,7 @@ Fixed documentation warnings (Swift 6.2 on macOS). Fixed typo with last changelog date. Added simpleTitleCase() function that just makes the first letter of each word capitalized. Don't affect other characters (if you want that, you can lowercase() and then titleCase()). ## v1.12.0 2025-10-13 -Refactored build flags into a `Build` struct so that we can use in legacy versions that don't support `ObservableObject` required by `Application` (which also allows us to simplify configurations since these values no longer require Foundation). Added `floor()` function when not available (like in WASM). Added `widgetAccentable()` backport. Added Build.Environment enum to facilitate iteration of build properties. ** Passes all Swift Package Index Checks! ** +Refactored build flags into a `Build` struct so that we can use in legacy versions that don't support `ObservableObject` required by `Application` (which also allows us to simplify configurations since these values no longer require Foundation). Added `floor()` function when not available (like in WASM). Added `widgetAccentable()` backport. ** Passes all Swift Package Index Checks! ** ## v1.11.32 2025-10-08 Old Linux support for Swift 5.10. **Supports all platforms including WASM and Android and passes all Swift Package Index Checks!** @@ -178,7 +182,7 @@ Added stub mock conformance of Version to Codable on WASM. Additional WASM conditional checks. ## v1.11.29 2025-10-06 -Added precision backport for Double in WASM. Added backports for `replacingOccurrences(of:[String])` for WASM. Migrated `CharacterSet` additions and backport to separate file. **Supports all platforms EXCEPT WASM but passes all other Swift Package Index Checks!** +Added precision backport for Double in WASM. Added backports for `replacingOccurrences(of:[String])` for WASM. Migrated CharacterSet additions and backport to separate file. **Supports all platforms EXCEPT WASM but passes all other Swift Package Index Checks!** ## v1.11.28 2025-10-06 Added Codable protocol for WASM so that we don't have to conditionally conform in WASM. **Supports all platforms including WASM and Android and passes all Swift Package Index Checks!** @@ -187,7 +191,7 @@ Added Codable protocol for WASM so that we don't have to conditionally conform i Missed a conditional check around the date requirement of `DateStringRepresentation` since this isn't present in WASM. **Supports all platforms including WASM and Android and passes all Swift Package Index Checks!** ## v1.11.26 2025-10-05 -Added back `DateString` as a type so that we can use in WASM as a type (but without working date features). +Added back `DateString` as a type so that it can be used but until we have a backport, there will not be a way to get this to work on WASM. ## v1.11.25 2025-10-05 Added `CaseNameConvertible` stub for WASM so that it can be used but until we have a backport, there will not be a way to get this to work on WASM. @@ -292,7 +296,7 @@ Fixed issue where [Color] not available on non-Apple platforms. Added missing T Extracted `.rainbow` included for previews to use the Color version when available. Improved RadialLayout preview. Added public initializer for RadialLayout so can be used outside project. Removed warnings running in Swift Playgrounds for Application tests. Note: When building, Swift Playgrounds 4.6.4 currently has a bug where it has trouble choosing the root application target rather than included module app targets which causes issues for #Previews. Removed requirement of Darwin.C when not Linux and can't import Darwin (was the cause of WASM and Android compile failures). Removed odd instances of availability checking for tvOS 20 (which now that we have tvOS 26, that passes). Added Collection conformance to OrderedSet. Added tests to bring test coverage to 47%. (Failed Linux, WASM, Android) ## v1.10.10 2025-06-06 -Added public visibility of Visibility backport. Added `persistentSystemOverlays` backport. Added tests to bring test coverage to 46%. Updated Version string parsing. Added a failable initializer for parsing strings. Updated the implementation of the `string:defaultValue:` initializer. Fixed so version character stripping isn't just trimming. +Added public visibility of Visibility backport. Added `persistentSystemOverlays` backport. Added tests to bring test coverage to 46%. Updated Version string parsing. Added a failable initializer for parsing strings. Updated the implementation of the `string:defaultValue:` initializer. ## v1.10.9 2025-05-14 re-worked compiler directives to fix issues with Linux visibility. @@ -370,7 +374,7 @@ Changed so `normalized` returns a non-optional. This is technically a breaking Fixed since `.focusable` is not available in iOS < 17. Fixed missing package version update in v1.6.7. Found a fix for packages and Swift Playgrounds v4.6+ (the iOSApplication name needs to be DIFFERENT whereas previous versions required it to be the SAME). ## v1.6.7 2025-03-10 -Shifted around `Version.zero` to non-constrained extension to make more sense. Added `resetVersionsRun()` for testing. Fixed internal scoping of String versions run keys just in case we need to use outside the framework. Added `tomorrow` and `tomorrowMidnight` date values. Added test section for output formats. Improved `Backport.LabeledContent` for compatibility with older devices (but now requires iOS 15 to use). Removed pageViewStyle from TabViews on tvOS since it doesn't really work. +Shifted around `Version.zero` to non-constrained extension to make more sense. Works fine under Swift Playgrounds 4.5.1 but not under Swift Playgrounds 4.6.2 (and 4.6?). Added `Version.zero`. ## v1.6.6 2025-02-28 Fixed internal `Version.zero` (doh!). @@ -379,7 +383,7 @@ Fixed internal `Version.zero` (doh!). Cleaned up redundant code for `Date.pretty()`. Works fine under Swift Playgrounds 4.5.1 but not under Swift Playgrounds 4.6.2 (and 4.6?). Added `Version.zero`. ## v1.6.4 2025-01-17 -Added debugging output when replacing the identifier in preview/playground environment to fix issue with Score identifier being com.kudit.Score-. Added check to prevent preview output alerting that iCloud doesn't work from spamming the logs. Added in app name and identifier to compatibility info. Fixed unnecessary check for iOS warning in Backport. +Added debugging output when replacing the identifier in preview/playground environment to fix issue with Score identifier being com.kudit.Score-. Added check to prevent preview output alerting that iCloud doesn't work from spamming the logs. ## v1.6.3 2025-01-15 Added some documentation to `asJSON()` function. Fixed internal definition of Triangle initializer. @@ -388,7 +392,7 @@ Added some documentation to `asJSON()` function. Fixed internal definition of T Fixed double encoding of ampersands in `htmlEncoded` strings due to random access nature of dictionaries. Added test. Added double quote `"` to `"` encoding. ## v1.6.1 2025-01-14 -Fixed build limited availablility issue with watchOS. +Fixed Linux compile error. ## v1.6.0 2025-01-14 Added `pluralEnding()`. Added `.backport.onTapGesture {}`. @@ -403,7 +407,7 @@ Attempted additional fixes to support Swift 5.8. Assumed returns are made expli #Preview isn't the issue, it's literally the @available checks we need to filter out. `swift(` doesn't seem to work so trying replacing them all with `compiler(`. ## v1.5.1 2024-11-26 -Added `#if swift(>=5.9)` checks around `#Preview` macros which aren't supported in Swift 5.8. If this doesn't work, try replacing `#if swift(` with `#if compiler(`. +Added `#if swift(>=5.9)` checks around `#Preview` macros which aren't supported in Swift 5.8. If this doesn't work, try replacing `#if swift(`. ## v1.5.0 2024-11-26 Removed duplicate `delay` code to fix errors with Swift 6. Does mean that some code may not work and will need to be adjusted (if you need `delay { @MainActor in`, simply do `delay { main {` instead). @@ -439,7 +443,7 @@ Added import of Color when available in Radial Layout previews. Added OverlappingStack and RadialLayout. ## v1.4.1 2024-11-04 -Added compiler check for Threading `background` tasks so that warnings are silenced in Swift 6 but still works in Swift Playgrounds. Removed URL comparison since causes warnings in Swift 6 and doesn't seem used most places (and where used, can simply reference the path comparison that it wraps). Fixed issues with watchOS. Fixed compile issues with Linux by removing `iCloudToken` variable. Addressed @retroactive warnings in a way that works with Swift Playgrounds. Added Embossed modifier. +Added compiler check for Threading `background` tasks so that warnings are silenced in Swift 6 but still works in Swift Playgrounds. Removed URL comparison since causes warnings in Swift 6 and doesn't seem used most places (and where used, can simply reference the path comparison that it wraps). Fixed issues with watchOS. Added Embossed modifier. ## v1.4.0 2024-11-04 Fixed some preview issues with legacy deprecated compatibility code. Added `scrollContentBackground` backport. Added `safeAreaPadding` backport. Added `disableSmartQuotes` view modifier. Can simulate @CloudStorage acting like UserDefaults by setting `Application.iCloudSupported = false`. Removed cloud monitoring notifications when using UserDefaults. Added `.precision(significantFigures)` output for Doubles. @@ -514,13 +518,13 @@ Fixed several data race safety issues. Fixed linux support. Standardized Package.swift, CHANGELOG.md, README.md, and LICENSE.txt files. Standardized deployment targets. Added DataStore code and added tests. Added Date.nowBackport for supporting earlier versions. Moved Environmental checks from Device so we can use in more places and needed for testing DataStores in previews. Added `asDictionary()` method for Codable objects similar to `asJSON()`. Standardized ordering and labelling of all `available` checks to iOS, macOS, tvOS, watchOS, visionOS (the order in which each platform got swift language support). Also removed unnecessary `.0` from versions and unnecessary `macCatalyst` checks. Fixed `Version` so that when encoded it stores as a `String` instead of as a struct. Changed `Compatibility` to enum since it isn't really a structure and avoids accidentally instantiating. Updated `ClearableTextField` to only update value when the field looses focus instead of every character (also fixed issue where that was not public). ## v1.2.1 2024-07-27 -Moved fetchURL code into a Compatibility extension so can specifically target. Doh! Debug was printing at the right time I think, they were just set to .SILENT! Fix for data race error. Added additional sendable conformances on enums and made FileManager extension public. Changed documentation for delay to be clear it runs on the same thread and doesn't force to main or background. +Moved fetchURL code into a Compatibility extension so can specifically target. Doh! Debug was printing at the right time I think, they were just set to .SILENT! Fix for data race error. Made `FileManager` extension public. Changed documentation for delay to be clear it runs on the same thread and doesn't force to main or background. ## v1.2.0 2024-07-25 Added additional onChange 2 parameter compatibility version and added ability to specify initial setting (and added documentation to match the new (current) implementations). Moved threading functions into static Compatibility functions so that we can reference in case we're in a class that shadows the same function name (like running background {} from within a view that is trying to create a view). Added returning background { } calls for cases where we need to await the results of the long-running background task. Re-worked debugLevel features of debug statements so we aren't switching threads with the print statement to ensure debug statements output immediately and don't get printed out of order. Added Compatibility.isDebug flag for testing if we've built for release or debug. Added additional Backport code including `scrollClipDisabled()`. Added set additions for OrderedSet and OrderedDictionary and added merging/interoperability between OrderedDictionary and Dictionary. ## v1.1.0 2024-07-19 -Added withoutZeros function to Double. Added .backport.navigationTitle() function for older iOS. Fixed JSON coding issue (since we're using codable, don't need to verify that all the contents are actually JSON supported NSObjects). Added additional version tests. Added injection tests with a count to include expected failure and run count. Fixed so debug breakpoints are accessible from the proper thread instead of being stranded on the main thread. Added Placard shape. Added Triangle shaped. Fixed .backport.background(color) +Added withoutZeros function to Double. Added `.backport.navigationTitle()` function for older iOS. Fixed JSON coding issue (since we're using codable, don't need to verify that all the contents are actually JSON supported NSObjects). Added additional version tests. Added injection tests with a count to include expected failure and run count. Fixed so debug breakpoints are accessible from the proper thread instead of being stranded on the main thread. Added Placard shape. Added Triangle shaped. Fixed `.backport.background(color)`. ## v1.0.18 2024-07-17 Added license usage example. Added ability to pass in additional tests to the AllTestsListView(["Section Name": tests, "Section Name 2": tests2]). Added fix for OperatingSystemVersion in swift Playgrounds (needed to do typalias wrapper trick). Needed to make Linux hack of ObservableObject have public send() function to prevent complaints about internal acccess. Added OrderedDictionary and OrderedSet based on swift-collections code but simplified (originally tried adding swift-collections as a dependency but it doesn't support watchOS 4). @@ -535,7 +539,7 @@ Added public intializer for BytesView. Added check for macOS 12 in Development app. Improved demo app. Added BytesView. Added improved test views. ## v1.0.14 2024-07-12 -Removed unnecessary utf8data extension since Data(String.utf8) works as a non-optional. Added Codable conformance for Version. Updated/enhanced Version tests. Added JSON encoding/decoding simple functions and removed unnecessary similar code. Removed unnecessary Foundation imports. Made changes to get Linux support validation (passes all SwiftPackageIndex tests for all platforms and safe from data races!). +Removed unnecessary utf8data extension since Data(String.utf8) works as a non-optional. Added Codable conformance for Version. Updated/enhanced Version tests. Added JSON encoding/decoding simple functions and removed unnecessary similar code. Made changes to get Linux support validation (passes all SwiftPackageIndex tests for all platforms and safe from data races!). ## v1.0.13 2024-07-11 Undid structure form of HTML and PostData since it won't code/decode properly automatically in KuditFrameworks. Seeing if typealias will work again (it does if we wrap the typealias in a structure). Added an HTML test for attributedString. Removed redundant old attributedStringFromHTML code. @@ -565,13 +569,13 @@ Broke macOS and watchOS with last update. Re-worked TabView Backport to be more Updated Xcode minimum versions to match package. Added Backport .overlay and .foregroundStyle and .background for older tvOS. ## v1.0.4 2024-07-08 -Attempted to fix issues with Linux compatibility (swapped legacyData around so extension of URLRequest instead of URLSession). Added additional #if canImport(Combine) checks. +Attempted to fix issues with Linux compatibility (swapped legacyData around so extension of URLRequest instead of URLSession). Fixed target versions (Xcode project). ## v1.0.3 2024-07-08 Reduced tvOS version requirements to tvOS 13 (though menu and other UI features are not supported). ## v1.0.2 2024-07-08 -Fixed some data race issues and fixed breaking support for watchOS and Linux. Added condition for @Published to ensure compilation on Linux. Made PostData require a Sendable type and added Sendable conformance to NetworkError. Fixed sendability of Message to prevent issues using `debug()`. +Fixed several data race safety issues and fixed breaking support for watchOS and Linux. Added condition for @Published to ensure compilation on Linux. Made PostData require a Sendable type and added Sendable conformance to NetworkError. Fixed sendability of Message to prevent issues using `debug()`. ## v1.0.1 2024-07-07 Fixed missing date in changelog. Moved DebugLevel.defaultLevel in initializers into nil initializers so can make sure to reference static property not in the initializer. Changed default color to orange. Changed several static vars to lets for concurrency safety. Enabled `main {}` to be used with throwing functions. Added `.spi.yml` file for Swift Package Index compiler. From 6f6a24e2bc700577fcf258f230f98eb0d0a60252 Mon Sep 17 00:00:00 2001 From: kudit Date: Tue, 28 Jul 2026 20:08:04 -0400 Subject: [PATCH 019/107] Restore changelog history before focused update --- CHANGELOG.md | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d1eab..5607f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,11 @@ Testing required before release: - Run the full test plan with ⌘U. - Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. - Confirm the new entries execute successfully and preserve readable module, section, and test names. -- Confirm the serialized debug tests restore `Compatibility.settings` even when an expectation throws. +- Remove the older grouped module-test bridge after the new adapter is verified, then rerun the tests. - Run SwiftPM and supported-platform validation before tagging the release. -## v1.19.0 2026-07-28 -Added the reusable `Compatibility Testing Library` product and `ModuleTestEntry` adapter so each module `TestCase` appears as an individually named Swift Testing result. -Unified `TestCase.execute()` and live test execution through one lifecycle implementation with explicit parallel and serialized execution modes. -Added source-aware test failures, labeled debug-format context, and source-context debugging conveniences while preserving existing debug-format call sites. -Made debug tests run exclusively and restore process-global debug settings with `defer`, including when an expectation throws. -Expanded contributor guidance for short, staged, maintainer-reviewed coding workflows. +## v1.18.3 2026-07-28 +TODO: Implement a comment matching this pull request changes. ## v1.18.2 2026-07-23 Fixed Swift Package Index build errors and warnings across SwiftUI and WebAssembly targets. @@ -170,7 +166,7 @@ Fixed documentation warnings (Swift 6.2 on macOS). Fixed typo with last changelog date. Added simpleTitleCase() function that just makes the first letter of each word capitalized. Don't affect other characters (if you want that, you can lowercase() and then titleCase()). ## v1.12.0 2025-10-13 -Refactored build flags into a `Build` struct so that we can use in legacy versions that don't support `ObservableObject` required by `Application` (which also allows us to simplify configurations since these values no longer require Foundation). Added `floor()` function when not available (like in WASM). Added `widgetAccentable()` backport. ** Passes all Swift Package Index Checks! ** +Refactored build flags into a `Build` struct so that we can use in legacy versions that don't support `ObservableObject` required by `Application` (which also allows us to simplify configurations since these values no longer require Foundation). Added `floor()` function when not available (like in WASM). Added `widgetAccentable()` backport. Added Build.Environment enum to facilitate iteration of build properties. ** Passes all Swift Package Index Checks! ** ## v1.11.32 2025-10-08 Old Linux support for Swift 5.10. **Supports all platforms including WASM and Android and passes all Swift Package Index Checks!** @@ -182,7 +178,7 @@ Added stub mock conformance of Version to Codable on WASM. Additional WASM conditional checks. ## v1.11.29 2025-10-06 -Added precision backport for Double in WASM. Added backports for `replacingOccurrences(of:[String])` for WASM. Migrated CharacterSet additions and backport to separate file. **Supports all platforms EXCEPT WASM but passes all other Swift Package Index Checks!** +Added precision backport for Double in WASM. Added backports for `replacingOccurrences(of:[String])` for WASM. Migrated `CharacterSet` additions and backport to separate file. **Supports all platforms EXCEPT WASM but passes all other Swift Package Index Checks!** ## v1.11.28 2025-10-06 Added Codable protocol for WASM so that we don't have to conditionally conform in WASM. **Supports all platforms including WASM and Android and passes all Swift Package Index Checks!** @@ -191,7 +187,7 @@ Added Codable protocol for WASM so that we don't have to conditionally conform i Missed a conditional check around the date requirement of `DateStringRepresentation` since this isn't present in WASM. **Supports all platforms including WASM and Android and passes all Swift Package Index Checks!** ## v1.11.26 2025-10-05 -Added back `DateString` as a type so that it can be used but until we have a backport, there will not be a way to get this to work on WASM. +Added back `DateString` as a type so that we can use in WASM as a type (but without working date features). ## v1.11.25 2025-10-05 Added `CaseNameConvertible` stub for WASM so that it can be used but until we have a backport, there will not be a way to get this to work on WASM. @@ -296,7 +292,7 @@ Fixed issue where [Color] not available on non-Apple platforms. Added missing T Extracted `.rainbow` included for previews to use the Color version when available. Improved RadialLayout preview. Added public initializer for RadialLayout so can be used outside project. Removed warnings running in Swift Playgrounds for Application tests. Note: When building, Swift Playgrounds 4.6.4 currently has a bug where it has trouble choosing the root application target rather than included module app targets which causes issues for #Previews. Removed requirement of Darwin.C when not Linux and can't import Darwin (was the cause of WASM and Android compile failures). Removed odd instances of availability checking for tvOS 20 (which now that we have tvOS 26, that passes). Added Collection conformance to OrderedSet. Added tests to bring test coverage to 47%. (Failed Linux, WASM, Android) ## v1.10.10 2025-06-06 -Added public visibility of Visibility backport. Added `persistentSystemOverlays` backport. Added tests to bring test coverage to 46%. Updated Version string parsing. Added a failable initializer for parsing strings. Updated the implementation of the `string:defaultValue:` initializer. +Added public visibility of Visibility backport. Added `persistentSystemOverlays` backport. Added tests to bring test coverage to 46%. Updated Version string parsing. Added a failable initializer for parsing strings. Updated the implementation of the `string:defaultValue:` initializer. Fixed so version character stripping isn't just trimming. ## v1.10.9 2025-05-14 re-worked compiler directives to fix issues with Linux visibility. @@ -374,7 +370,7 @@ Changed so `normalized` returns a non-optional. This is technically a breaking Fixed since `.focusable` is not available in iOS < 17. Fixed missing package version update in v1.6.7. Found a fix for packages and Swift Playgrounds v4.6+ (the iOSApplication name needs to be DIFFERENT whereas previous versions required it to be the SAME). ## v1.6.7 2025-03-10 -Shifted around `Version.zero` to non-constrained extension to make more sense. Works fine under Swift Playgrounds 4.5.1 but not under Swift Playgrounds 4.6.2 (and 4.6?). Added `Version.zero`. +Shifted around `Version.zero` to non-constrained extension to make more sense. Added `resetVersionsRun()` for testing. Fixed internal scoping of String versions run keys just in case we need to use outside the framework. Added `tomorrow` and `tomorrowMidnight` date values. Added test section for output formats. Improved `Backport.LabeledContent` for compatibility with older devices (but now requires iOS 15 to use). Removed pageViewStyle from TabViews on tvOS since it doesn't really work. ## v1.6.6 2025-02-28 Fixed internal `Version.zero` (doh!). @@ -383,7 +379,7 @@ Fixed internal `Version.zero` (doh!). Cleaned up redundant code for `Date.pretty()`. Works fine under Swift Playgrounds 4.5.1 but not under Swift Playgrounds 4.6.2 (and 4.6?). Added `Version.zero`. ## v1.6.4 2025-01-17 -Added debugging output when replacing the identifier in preview/playground environment to fix issue with Score identifier being com.kudit.Score-. Added check to prevent preview output alerting that iCloud doesn't work from spamming the logs. +Added debugging output when replacing the identifier in preview/playground environment to fix issue with Score identifier being com.kudit.Score-. Added check to prevent preview output alerting that iCloud doesn't work from spamming the logs. Added in app name and identifier to compatibility info. Fixed unnecessary check for iOS warning in Backport. ## v1.6.3 2025-01-15 Added some documentation to `asJSON()` function. Fixed internal definition of Triangle initializer. @@ -392,7 +388,7 @@ Added some documentation to `asJSON()` function. Fixed internal definition of T Fixed double encoding of ampersands in `htmlEncoded` strings due to random access nature of dictionaries. Added test. Added double quote `"` to `"` encoding. ## v1.6.1 2025-01-14 -Fixed Linux compile error. +Fixed build limited availablility issue with watchOS. ## v1.6.0 2025-01-14 Added `pluralEnding()`. Added `.backport.onTapGesture {}`. @@ -407,7 +403,7 @@ Attempted additional fixes to support Swift 5.8. Assumed returns are made expli #Preview isn't the issue, it's literally the @available checks we need to filter out. `swift(` doesn't seem to work so trying replacing them all with `compiler(`. ## v1.5.1 2024-11-26 -Added `#if swift(>=5.9)` checks around `#Preview` macros which aren't supported in Swift 5.8. If this doesn't work, try replacing `#if swift(`. +Added `#if swift(>=5.9)` checks around `#Preview` macros which aren't supported in Swift 5.8. If this doesn't work, try replacing `#if swift(` with `#if compiler(`. ## v1.5.0 2024-11-26 Removed duplicate `delay` code to fix errors with Swift 6. Does mean that some code may not work and will need to be adjusted (if you need `delay { @MainActor in`, simply do `delay { main {` instead). @@ -443,7 +439,7 @@ Added import of Color when available in Radial Layout previews. Added OverlappingStack and RadialLayout. ## v1.4.1 2024-11-04 -Added compiler check for Threading `background` tasks so that warnings are silenced in Swift 6 but still works in Swift Playgrounds. Removed URL comparison since causes warnings in Swift 6 and doesn't seem used most places (and where used, can simply reference the path comparison that it wraps). Fixed issues with watchOS. Added Embossed modifier. +Added compiler check for Threading `background` tasks so that warnings are silenced in Swift 6 but still works in Swift Playgrounds. Removed URL comparison since causes warnings in Swift 6 and doesn't seem used most places (and where used, can simply reference the path comparison that it wraps). Fixed issues with watchOS. Fixed compile issues with Linux by removing `iCloudToken` variable. Addressed @retroactive warnings in a way that works with Swift Playgrounds. Added Embossed modifier. ## v1.4.0 2024-11-04 Fixed some preview issues with legacy deprecated compatibility code. Added `scrollContentBackground` backport. Added `safeAreaPadding` backport. Added `disableSmartQuotes` view modifier. Can simulate @CloudStorage acting like UserDefaults by setting `Application.iCloudSupported = false`. Removed cloud monitoring notifications when using UserDefaults. Added `.precision(significantFigures)` output for Doubles. @@ -518,13 +514,13 @@ Fixed several data race safety issues. Fixed linux support. Standardized Package.swift, CHANGELOG.md, README.md, and LICENSE.txt files. Standardized deployment targets. Added DataStore code and added tests. Added Date.nowBackport for supporting earlier versions. Moved Environmental checks from Device so we can use in more places and needed for testing DataStores in previews. Added `asDictionary()` method for Codable objects similar to `asJSON()`. Standardized ordering and labelling of all `available` checks to iOS, macOS, tvOS, watchOS, visionOS (the order in which each platform got swift language support). Also removed unnecessary `.0` from versions and unnecessary `macCatalyst` checks. Fixed `Version` so that when encoded it stores as a `String` instead of as a struct. Changed `Compatibility` to enum since it isn't really a structure and avoids accidentally instantiating. Updated `ClearableTextField` to only update value when the field looses focus instead of every character (also fixed issue where that was not public). ## v1.2.1 2024-07-27 -Moved fetchURL code into a Compatibility extension so can specifically target. Doh! Debug was printing at the right time I think, they were just set to .SILENT! Fix for data race error. Made `FileManager` extension public. Changed documentation for delay to be clear it runs on the same thread and doesn't force to main or background. +Moved fetchURL code into a Compatibility extension so can specifically target. Doh! Debug was printing at the right time I think, they were just set to .SILENT! Fix for data race error. Added additional sendable conformances on enums and made FileManager extension public. Changed documentation for delay to be clear it runs on the same thread and doesn't force to main or background. ## v1.2.0 2024-07-25 Added additional onChange 2 parameter compatibility version and added ability to specify initial setting (and added documentation to match the new (current) implementations). Moved threading functions into static Compatibility functions so that we can reference in case we're in a class that shadows the same function name (like running background {} from within a view that is trying to create a view). Added returning background { } calls for cases where we need to await the results of the long-running background task. Re-worked debugLevel features of debug statements so we aren't switching threads with the print statement to ensure debug statements output immediately and don't get printed out of order. Added Compatibility.isDebug flag for testing if we've built for release or debug. Added additional Backport code including `scrollClipDisabled()`. Added set additions for OrderedSet and OrderedDictionary and added merging/interoperability between OrderedDictionary and Dictionary. ## v1.1.0 2024-07-19 -Added withoutZeros function to Double. Added `.backport.navigationTitle()` function for older iOS. Fixed JSON coding issue (since we're using codable, don't need to verify that all the contents are actually JSON supported NSObjects). Added additional version tests. Added injection tests with a count to include expected failure and run count. Fixed so debug breakpoints are accessible from the proper thread instead of being stranded on the main thread. Added Placard shape. Added Triangle shaped. Fixed `.backport.background(color)`. +Added withoutZeros function to Double. Added .backport.navigationTitle() function for older iOS. Fixed JSON coding issue (since we're using codable, don't need to verify that all the contents are actually JSON supported NSObjects). Added additional version tests. Added injection tests with a count to include expected failure and run count. Fixed so debug breakpoints are accessible from the proper thread instead of being stranded on the main thread. Added Placard shape. Added Triangle shaped. Fixed .backport.background(color) ## v1.0.18 2024-07-17 Added license usage example. Added ability to pass in additional tests to the AllTestsListView(["Section Name": tests, "Section Name 2": tests2]). Added fix for OperatingSystemVersion in swift Playgrounds (needed to do typalias wrapper trick). Needed to make Linux hack of ObservableObject have public send() function to prevent complaints about internal acccess. Added OrderedDictionary and OrderedSet based on swift-collections code but simplified (originally tried adding swift-collections as a dependency but it doesn't support watchOS 4). @@ -539,7 +535,7 @@ Added public intializer for BytesView. Added check for macOS 12 in Development app. Improved demo app. Added BytesView. Added improved test views. ## v1.0.14 2024-07-12 -Removed unnecessary utf8data extension since Data(String.utf8) works as a non-optional. Added Codable conformance for Version. Updated/enhanced Version tests. Added JSON encoding/decoding simple functions and removed unnecessary similar code. Made changes to get Linux support validation (passes all SwiftPackageIndex tests for all platforms and safe from data races!). +Removed unnecessary utf8data extension since Data(String.utf8) works as a non-optional. Added Codable conformance for Version. Updated/enhanced Version tests. Added JSON encoding/decoding simple functions and removed unnecessary similar code. Removed unnecessary Foundation imports. Made changes to get Linux support validation (passes all SwiftPackageIndex tests for all platforms and safe from data races!). ## v1.0.13 2024-07-11 Undid structure form of HTML and PostData since it won't code/decode properly automatically in KuditFrameworks. Seeing if typealias will work again (it does if we wrap the typealias in a structure). Added an HTML test for attributedString. Removed redundant old attributedStringFromHTML code. @@ -569,13 +565,13 @@ Broke macOS and watchOS with last update. Re-worked TabView Backport to be more Updated Xcode minimum versions to match package. Added Backport .overlay and .foregroundStyle and .background for older tvOS. ## v1.0.4 2024-07-08 -Attempted to fix issues with Linux compatibility (swapped legacyData around so extension of URLRequest instead of URLSession). Fixed target versions (Xcode project). +Attempted to fix issues with Linux compatibility (swapped legacyData around so extension of URLRequest instead of URLSession). Added additional #if canImport(Combine) checks. ## v1.0.3 2024-07-08 Reduced tvOS version requirements to tvOS 13 (though menu and other UI features are not supported). ## v1.0.2 2024-07-08 -Fixed several data race safety issues and fixed breaking support for watchOS and Linux. Added condition for @Published to ensure compilation on Linux. Made PostData require a Sendable type and added Sendable conformance to NetworkError. Fixed sendability of Message to prevent issues using `debug()`. +Fixed some data race issues and fixed breaking support for watchOS and Linux. Added condition for @Published to ensure compilation on Linux. Made PostData require a Sendable type and added Sendable conformance to NetworkError. Fixed sendability of Message to prevent issues using `debug()`. ## v1.0.1 2024-07-07 Fixed missing date in changelog. Moved DebugLevel.defaultLevel in initializers into nil initializers so can make sure to reference static property not in the initializer. Changed default color to orange. Changed several static vars to lets for concurrency safety. Enabled `main {}` to be used with throwing functions. Added `.spi.yml` file for Swift Package Index compiler. From 6f9ec99893f0010e612997697a2594d0a9c5da03 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:02:04 -0400 Subject: [PATCH 020/107] Updated module requirements --- CHANGELOG.md | 8 ++++++-- Sources/Core/Module.swift | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5607f74..5f22150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,15 @@ Testing required before release: - Run the full test plan with ⌘U. - Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. - Confirm the new entries execute successfully and preserve readable module, section, and test names. -- Remove the older grouped module-test bridge after the new adapter is verified, then rerun the tests. +- Confirm the serialized debug tests restore `Compatibility.settings` even when an expectation throws. - Run SwiftPM and supported-platform validation before tagging the release. ## v1.18.3 2026-07-28 -TODO: Implement a comment matching this pull request changes. +Added the reusable `Compatibility Testing Library` product and `ModuleTestEntry` adapter so each module `TestCase` appears as an individually named Swift Testing result. +Unified `TestCase.execute()` and live test execution through one lifecycle implementation with explicit parallel and serialized execution modes. +Added source-aware test failures, labeled debug-format context, and source-context debugging conveniences while preserving existing debug-format call sites. +Made debug tests run exclusively and restore process-global debug settings with `defer`, including when an expectation throws. +Expanded contributor guidance for short, staged, maintainer-reviewed coding workflows. ## v1.18.2 2026-07-23 Fixed Swift Package Index build errors and warnings across SwiftUI and WebAssembly targets. diff --git a/Sources/Core/Module.swift b/Sources/Core/Module.swift index fd753e5..790844c 100644 --- a/Sources/Core/Module.swift +++ b/Sources/Core/Module.swift @@ -35,7 +35,7 @@ public protocol Module { /// The default is empty, so production-only modules do not need to declare tests. TestCase UI still /// presents the module identity and an empty state, making installed-module diagnostics complete. @MainActor - @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) static var tests: OrderedDictionary { get } #endif @@ -122,7 +122,7 @@ public extension Module { #if compiler(>=5.9) /// Modules expose no tests unless the conformer provides ordered test sections. @MainActor - @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) static var tests: OrderedDictionary { return [:] } From a5ba7978779bfe00135fe112f452bbbdd0c356de Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:04:05 -0400 Subject: [PATCH 021/107] Discover module tests without global registration --- .../ModuleTestEntry.swift | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/Sources/CompatibilityTesting/ModuleTestEntry.swift b/Sources/CompatibilityTesting/ModuleTestEntry.swift index eaddcb9..d102a34 100644 --- a/Sources/CompatibilityTesting/ModuleTestEntry.swift +++ b/Sources/CompatibilityTesting/ModuleTestEntry.swift @@ -51,11 +51,45 @@ extension ModuleTestEntry: CustomTestArgumentEncodable { @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension ModuleTestEntry { - /// Registers the supplied top-level modules and flattens every module test into a named argument. + /// Flattens the supplied modules and their dependencies into individually named test arguments. + /// + /// Test discovery intentionally builds a local module list instead of mutating `Build.allModules`. + /// A test process may have already finished application module registration before Swift Testing + /// evaluates parameterized arguments; relying on that process-global registry could therefore + /// produce an empty argument list and cause the entire parameterized test to be skipped. @MainActor static func entries(including modules: Module.Type...) -> [ModuleTestEntry] { - Build.register(modules) - return Build.allModules.flatMap { module in + var orderedModules = [Module.Type]() + var includedIdentifiers = Set() + var visitingIdentifiers = Set() + + func include(_ module: Module.Type) { + let identifier = module.moduleIdentifier + + // Ignore modules already emitted and stop circular dependency traversal. + guard !includedIdentifiers.contains(identifier), + !visitingIdentifiers.contains(identifier) else { + return + } + + visitingIdentifiers.insert(identifier) + for dependency in module.dependencies { + include(dependency) + } + visitingIdentifiers.remove(identifier) + + // A sibling dependency may have emitted this module during recursive traversal. + guard includedIdentifiers.insert(identifier).inserted else { + return + } + orderedModules.append(module) + } + + for module in modules { + include(module) + } + + return orderedModules.flatMap { module in module.tests.flatMap { section, tests in tests.enumerated().map { index, testCase in ModuleTestEntry( From 61e59ba3ed948ca3a354f9bd06e41b04827c67d6 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:05:50 -0400 Subject: [PATCH 022/107] Set package version to 1.18.3 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 96c3426..e722e6b 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ // This file is automatically generated. // Do not edit it by hand because the contents will be replaced. -let version = "1.18.2" +let version = "1.18.3" let packageLibraryName = "Compatibility" #if canImport(PackageDescription) From d82e8c035965a52cd2954ddbec716882df2bfe92 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:06:10 -0400 Subject: [PATCH 023/107] Set Compatibility version to 1.18.3 --- Sources/Compatibility.swift | 333 +----------------------------------- 1 file changed, 2 insertions(+), 331 deletions(-) diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 90cef8c..724fdcc 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -8,7 +8,7 @@ public enum Compatibility: Module { /// The version of the Compatibility Library since cannot get directly from Package.swift. - public static let version: Version = "1.18.2" + public static let version: Version = "1.18.3" /// Public source repository for Compatibility so support reports can direct developers to its source and issue history. /// @@ -44,340 +44,11 @@ public enum Compatibility: Module { Field("iCloud status", Application.iCloudStatus), ] } - details += moduleInfo return details } - return applicationDetails + return applicationDetails + moduleInfo #else - // Non-Foundation environments still receive every portable field without referencing Application. return moduleInfo #endif } } - -#if canImport(Foundation) -@_exported import Foundation -// The following can be added if we want to add back in some funtions for Android or Linux (we're not currently using these personally, so if you do, please feel free to file a pull request). -//#elseif canImport(FoundationNetworking) && canImport(FoundationEssentials) && canImport(FoundationInternationalization) && canImport(FoundationXML) -///* -// Android compatibility: https://skip.tools/blog/android-native-swift-packages/#conditionally-importing-and-using-platform-specific-modules -// */ -//@_exported import FoundationNetworking -//@_exported import FoundationEssentials -//@_exported import FoundationInternationalization -//@_exported import FoundationXML -#if canImport(FoundationNetworking) -// Linux separates URLSession and related HTTP types from Foundation; the implementation uses libcurl. -@_exported import FoundationNetworking -#endif -#endif - -// NOTE: UNAVAILABLE to mark API as unavailabe for specific versions. -//@available(*, unavailable, message: "use native function rather than backport?") - -/* - - For module checks to conditionally compile for versions: - - canImport(StoreKit) - iOS 3.0+ - iPadOS 3.0+ - macOS 10.7+ - Mac Catalyst 13.0+ - tvOS 9.0+ - watchOS 6.2+ - visionOS 1.0+ - - 2014 (Swift announced, for OperatingSystemVersion) - canImport(HealthKit) || canImport(Metal) - iOS 8.0+ // Health, Metal - iPadOS 8.0+ // Health, Metal - macOS 10.10+ - Mac Catalyst 13.0+ // Metal - tvOS 9.0+ // Metal - watchOS 2.0+ // Health - visionOS 1.0+ // Health, Metal - - 2015 (initial relase of tvOS) - iOS 9 - macOS 10.11 - - 2016 - iOS 10 - macOS 10.12 - - 2017 - canImport(CoreML) - iOS 11 - macOS 10.13 (High Sierra) - tvOS 11 - watchOS 4 - - 2018 - iOS 12 - macOS 10.14 - tvOS 12 - watchOS 5 - - 2019 (first year macCatalyst and SwiftUI available) - canImport(SwiftUI) || canImport(Combine) - iOS 13+ - iPadOS 13.0+ - macOS 10.15+ - Mac Catalyst 13.0+ - tvOS 13+ - watchOS 6+ - visionOS 1.0+ - SF Symbols 1.0 - - 2020 - canImport(AppleArchive) - iOS 14+ - iPadOS 14.0+ - macOS 11+ - Mac Catalyst 14.0+ - tvOS 14+ - watchOS 7+ - visionOS 1.0+ - SF Symbols 2.0 - - 2021 - canImport(GroupActivities) - iOS 15+ (last supported by iPhone 7) - iPadOS 15.0+ - macOS 12+ (last supported by Touchbook) - Mac Catalyst 15.0+ - tvOS 15+ - NOTE: NO WATCH OS SUPPORT (watchOS 8 is the last supported by Series 3) - visionOS 1.0+ - SF Symbols 3.0 - - 2022 Swift 5.7 (September) - canImport(Charts) canImport(AppIntents) canImport(CoreTransferable) - iOS 16+ - iPadOS 16.0+ - macOS 13+ - Mac Catalyst 16.0+ - tvOS 16+ - watchOS 9+ (minimum for WidgetKit on watchOS - supported in iOS 14 and macOS 11) - visionOS 1.0+ - SF Symbols 4.0 - - 2023 Swift 5.8 (March), Swift 5.9 (September) (added #Preview syntax and @availability syntax) - canImport(SwiftData) - iOS 17+ - iPadOS 17.0+ - macOS 14+ - Mac Catalyst 17.0+ - tvOS 17+ - watchOS 10+ (practical minimum for WidgetKit (due to requirement of WidgetConfigurationIntent which is only available on iOS 17, macOS 14, and watchOS 10) - visionOS 1.0+ - SF Symbols 5.0 - -2024 Swift 5.10 (March), Swift 6 (September) -canImport(Testing) - iOS 18+ - iPadOS 18+ - macOS 15+ - Mac Catalyst 18+ - tvOS 18+ - watchOS 11+ - visionOS 2+ - SF Symbols 6.0 - Xcode 16 - - Swift Playgrounds 4.6.4 - Swift 6.0 Compiler - - 2025 Swift 6.1 (March), Swift 6.2 (September) - iOS 26+ - iPadOS 26+ - macOS 26+ - Mac Catalyst 26+ - tvOS 26+ - watchOS 26+ - visionOS 26+ - SF Symbols 7.0 - Xcode 26 - - In Swift 6.2, Foundation is not available in WASM - - */ -// MARK: - Configuration - -public extension Compatibility { - // https://medium.com/@aliyasirali/understanding-nonisolated-unsafe-in-swift-incremental-adoption-of-strict-concurrency-2cbb61c9adf4 - // This generates unsafe warnings anyways, so use the simpler version and hope there are no data races (theoretically, if we're only changing on the main thread first thing at init, this shouldn't be a problem) -// private static var lock = NSLock() -// private static var _settings = CompatibilityConfiguration() -// static var settings: CompatibilityConfiguration { -// get { -// lock.lock() -// defer { lock.unlock() } -// return _settings -// } -// set { -// lock.lock() -// defer { lock.unlock() } -// _settings = newValue -// } -// } -// -#if compiler(>=5.10) - static nonisolated(unsafe) var settings = CompatibilityConfiguration() -#else - static var settings = CompatibilityConfiguration() -#endif -} - -// for flags in swift packages: https://stackoverflow.com/questions/38813906/swift-how-to-use-preprocessor-flags-like-if-debug-to-implement-api-keys -//swiftSettings: [ -// .define("VAPOR") -//] -// https://medium.com/@ytyubox/xcode-preprocessing-with-custom-flags-in-swift-4bfde6e7a608 - -// MARK: - legacy compatibility code deprecations and support -public extension Compatibility { // for brief period where Application wasn't available - @available(*, deprecated, renamed: "Application.isDebug") - static let isDebug = _isDebugAssertConfiguration() -} -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) -public extension Compatibility { // for brief period where Application and Build wasn't available. Static computed properties apparently aren't supported in extensions in iOS <13? - // MARK: - Entitlements Information -#if canImport(Foundation) - @available(*, deprecated, renamed: "Application.iCloudSupported") - @MainActor - static var iCloudSupported: Bool { - get { - Application.iCloudSupported - } - set { - Application.iCloudSupported = newValue - } - } - - @available(*, deprecated, renamed: "Application.iCloudIsEnabled") - @MainActor - static var iCloudIsEnabled: Bool { - Application.iCloudIsEnabled - } - - @available(*, deprecated, renamed: "Application.iCloudStatus") - @MainActor - static var iCloudStatus: CloudStatus { - Application.iCloudStatus - } -#endif - - @available(*, deprecated, renamed: "Build.isSimulator") - static let isSimulator = Build.isSimulator - - @available(*, deprecated, renamed: "Build.isPlayground") - static let isPlayground = Build.isPlayground - - @available(*, deprecated, renamed: "Build.isPreview") - static let isPreview = Build.isPreview - - @available(*, deprecated, renamed: "Build.isMacCatalyst") - static let isMacCatalyst = Build.isMacCatalyst -} - -#if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) -import SwiftUI - -@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) -public struct CompatibilityEnvironmentTestView: View { -#if compiler(>=5.9) && canImport(Combine) - @CloudStorage(.compatibilityVersionsRunKey) var previouslyRunCompatibilityVersions = Compatibility.version.rawValue -#endif - /// Complete deferred module information; `nil` keeps the loading state distinct from the portable baseline. - @State private var loadedModuleInfo: [Field]? - - /// Creates an environment view whose module metadata is loaded after the UI first appears. - public init() {} - - /// Structured application fields displayed by the environment test view. - public var applicationInfo: [Field] { - var info = [ - Field("Name", "\(Application.main.name) (\(Application.main.appName).app)"), - Field("App Identifier", Application.main.appIdentifier), - Field("App Version", "v\(Application.main.debugVersion)"), - Field("is first run", Application.main.isFirstRun), - ] - let previousVersions = Application.main.previouslyRunVersions - if previousVersions.count > 0 { - info.append(Field("Previously run versions", previousVersions.pretty)) - } - return info - } - - /// Structured Compatibility-version and build-mode fields displayed by the environment test view. - public var compatibilityInfo: [Field] { - var info = [ - Field("\(Compatibility.moduleName) Version", Compatibility.version), - Field("is Debug", Build.isDebug), - ] -#if compiler(>=5.9) && canImport(Combine) - if previouslyRunCompatibilityVersions != "" && previouslyRunCompatibilityVersions != "\(Compatibility.version.rawValue)" { - info += [ - Field("Previously run Compatibility versions", previouslyRunCompatibilityVersions), - Field(nil, "NOTE: This only updates if we're running the DataStore test view and is not guaranteed to be run any other time or from any other app."), - ] - } -#endif - return info - } - - public var body: some View { - List { - FieldSections([ - "Application": applicationInfo, - Compatibility.moduleName: compatibilityInfo, - "iCloud": [ - Field("Supported by app", Application.iCloudSupported), - Field("Enabled", Application.iCloudIsEnabled), - Field("iCloud status", Application.iCloudStatus), - ], - ]) - Section("Module Info") { - // Show the portable baseline immediately, then replace it with the complete loaded result. - // This is example code. Really this only needs to include moduleInfo since the detailed info is already included in other sections. - let displayedModuleInfo = loadedModuleInfo ?? Compatibility.moduleInfo - ForEach(displayedModuleInfo.indices, id: \.self) { index in - FieldView(displayedModuleInfo[index]) - } - if loadedModuleInfo == nil { - ProgressView("Loading module details…") - } - } - Section("Environment") { - FieldView(Field("Swift Version", Build.swiftVersion, symbol: "swift")) - FieldView(Field("Compiler Version", Build.compilerVersion)) - EnvironmentsView(Build.environments()) - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) - } - FieldSections([ - "Dates": [ - Field("Now Backport", Date.nowBackport.pretty), - Field("Now MySQL", Date.nowBackport.mysqlDateTime), - Field("Now Numeric", Date.nowBackport.numericDateTime), - Field("Tomorrow", Date.tomorrow.pretty), - Field("Tomorrow Midnight", Date.tomorrowMidnight.pretty), - Field("Yesterday", Date.yesterday.pretty), - ], - ]) - } - .task { - // Await potentially slow details without delaying the portable module fields above. - loadedModuleInfo = await Compatibility.loadDetailedModuleInfo() - } - } -} - -@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) -#Preview { - CompatibilityEnvironmentTestView() - .backport.scrollContentBackground(.hidden) - .background(.red) -} -#endif From 7935f7acea8dbcd9bc37f4af21464c34f1a68f94 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:19:13 -0400 Subject: [PATCH 024/107] Revert "Set Compatibility version to 1.18.3" This reverts commit d82e8c035965a52cd2954ddbec716882df2bfe92. --- Sources/Compatibility.swift | 333 +++++++++++++++++++++++++++++++++++- 1 file changed, 331 insertions(+), 2 deletions(-) diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 724fdcc..90cef8c 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -8,7 +8,7 @@ public enum Compatibility: Module { /// The version of the Compatibility Library since cannot get directly from Package.swift. - public static let version: Version = "1.18.3" + public static let version: Version = "1.18.2" /// Public source repository for Compatibility so support reports can direct developers to its source and issue history. /// @@ -44,11 +44,340 @@ public enum Compatibility: Module { Field("iCloud status", Application.iCloudStatus), ] } + details += moduleInfo return details } - return applicationDetails + moduleInfo + return applicationDetails #else + // Non-Foundation environments still receive every portable field without referencing Application. return moduleInfo #endif } } + +#if canImport(Foundation) +@_exported import Foundation +// The following can be added if we want to add back in some funtions for Android or Linux (we're not currently using these personally, so if you do, please feel free to file a pull request). +//#elseif canImport(FoundationNetworking) && canImport(FoundationEssentials) && canImport(FoundationInternationalization) && canImport(FoundationXML) +///* +// Android compatibility: https://skip.tools/blog/android-native-swift-packages/#conditionally-importing-and-using-platform-specific-modules +// */ +//@_exported import FoundationNetworking +//@_exported import FoundationEssentials +//@_exported import FoundationInternationalization +//@_exported import FoundationXML +#if canImport(FoundationNetworking) +// Linux separates URLSession and related HTTP types from Foundation; the implementation uses libcurl. +@_exported import FoundationNetworking +#endif +#endif + +// NOTE: UNAVAILABLE to mark API as unavailabe for specific versions. +//@available(*, unavailable, message: "use native function rather than backport?") + +/* + + For module checks to conditionally compile for versions: + + canImport(StoreKit) + iOS 3.0+ + iPadOS 3.0+ + macOS 10.7+ + Mac Catalyst 13.0+ + tvOS 9.0+ + watchOS 6.2+ + visionOS 1.0+ + + 2014 (Swift announced, for OperatingSystemVersion) + canImport(HealthKit) || canImport(Metal) + iOS 8.0+ // Health, Metal + iPadOS 8.0+ // Health, Metal + macOS 10.10+ + Mac Catalyst 13.0+ // Metal + tvOS 9.0+ // Metal + watchOS 2.0+ // Health + visionOS 1.0+ // Health, Metal + + 2015 (initial relase of tvOS) + iOS 9 + macOS 10.11 + + 2016 + iOS 10 + macOS 10.12 + + 2017 + canImport(CoreML) + iOS 11 + macOS 10.13 (High Sierra) + tvOS 11 + watchOS 4 + + 2018 + iOS 12 + macOS 10.14 + tvOS 12 + watchOS 5 + + 2019 (first year macCatalyst and SwiftUI available) + canImport(SwiftUI) || canImport(Combine) + iOS 13+ + iPadOS 13.0+ + macOS 10.15+ + Mac Catalyst 13.0+ + tvOS 13+ + watchOS 6+ + visionOS 1.0+ + SF Symbols 1.0 + + 2020 + canImport(AppleArchive) + iOS 14+ + iPadOS 14.0+ + macOS 11+ + Mac Catalyst 14.0+ + tvOS 14+ + watchOS 7+ + visionOS 1.0+ + SF Symbols 2.0 + + 2021 + canImport(GroupActivities) + iOS 15+ (last supported by iPhone 7) + iPadOS 15.0+ + macOS 12+ (last supported by Touchbook) + Mac Catalyst 15.0+ + tvOS 15+ + NOTE: NO WATCH OS SUPPORT (watchOS 8 is the last supported by Series 3) + visionOS 1.0+ + SF Symbols 3.0 + + 2022 Swift 5.7 (September) + canImport(Charts) canImport(AppIntents) canImport(CoreTransferable) + iOS 16+ + iPadOS 16.0+ + macOS 13+ + Mac Catalyst 16.0+ + tvOS 16+ + watchOS 9+ (minimum for WidgetKit on watchOS - supported in iOS 14 and macOS 11) + visionOS 1.0+ + SF Symbols 4.0 + + 2023 Swift 5.8 (March), Swift 5.9 (September) (added #Preview syntax and @availability syntax) + canImport(SwiftData) + iOS 17+ + iPadOS 17.0+ + macOS 14+ + Mac Catalyst 17.0+ + tvOS 17+ + watchOS 10+ (practical minimum for WidgetKit (due to requirement of WidgetConfigurationIntent which is only available on iOS 17, macOS 14, and watchOS 10) + visionOS 1.0+ + SF Symbols 5.0 + +2024 Swift 5.10 (March), Swift 6 (September) +canImport(Testing) + iOS 18+ + iPadOS 18+ + macOS 15+ + Mac Catalyst 18+ + tvOS 18+ + watchOS 11+ + visionOS 2+ + SF Symbols 6.0 + Xcode 16 + + Swift Playgrounds 4.6.4 - Swift 6.0 Compiler + + 2025 Swift 6.1 (March), Swift 6.2 (September) + iOS 26+ + iPadOS 26+ + macOS 26+ + Mac Catalyst 26+ + tvOS 26+ + watchOS 26+ + visionOS 26+ + SF Symbols 7.0 + Xcode 26 + + In Swift 6.2, Foundation is not available in WASM + + */ +// MARK: - Configuration + +public extension Compatibility { + // https://medium.com/@aliyasirali/understanding-nonisolated-unsafe-in-swift-incremental-adoption-of-strict-concurrency-2cbb61c9adf4 + // This generates unsafe warnings anyways, so use the simpler version and hope there are no data races (theoretically, if we're only changing on the main thread first thing at init, this shouldn't be a problem) +// private static var lock = NSLock() +// private static var _settings = CompatibilityConfiguration() +// static var settings: CompatibilityConfiguration { +// get { +// lock.lock() +// defer { lock.unlock() } +// return _settings +// } +// set { +// lock.lock() +// defer { lock.unlock() } +// _settings = newValue +// } +// } +// +#if compiler(>=5.10) + static nonisolated(unsafe) var settings = CompatibilityConfiguration() +#else + static var settings = CompatibilityConfiguration() +#endif +} + +// for flags in swift packages: https://stackoverflow.com/questions/38813906/swift-how-to-use-preprocessor-flags-like-if-debug-to-implement-api-keys +//swiftSettings: [ +// .define("VAPOR") +//] +// https://medium.com/@ytyubox/xcode-preprocessing-with-custom-flags-in-swift-4bfde6e7a608 + +// MARK: - legacy compatibility code deprecations and support +public extension Compatibility { // for brief period where Application wasn't available + @available(*, deprecated, renamed: "Application.isDebug") + static let isDebug = _isDebugAssertConfiguration() +} +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +public extension Compatibility { // for brief period where Application and Build wasn't available. Static computed properties apparently aren't supported in extensions in iOS <13? + // MARK: - Entitlements Information +#if canImport(Foundation) + @available(*, deprecated, renamed: "Application.iCloudSupported") + @MainActor + static var iCloudSupported: Bool { + get { + Application.iCloudSupported + } + set { + Application.iCloudSupported = newValue + } + } + + @available(*, deprecated, renamed: "Application.iCloudIsEnabled") + @MainActor + static var iCloudIsEnabled: Bool { + Application.iCloudIsEnabled + } + + @available(*, deprecated, renamed: "Application.iCloudStatus") + @MainActor + static var iCloudStatus: CloudStatus { + Application.iCloudStatus + } +#endif + + @available(*, deprecated, renamed: "Build.isSimulator") + static let isSimulator = Build.isSimulator + + @available(*, deprecated, renamed: "Build.isPlayground") + static let isPlayground = Build.isPlayground + + @available(*, deprecated, renamed: "Build.isPreview") + static let isPreview = Build.isPreview + + @available(*, deprecated, renamed: "Build.isMacCatalyst") + static let isMacCatalyst = Build.isMacCatalyst +} + +#if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) +import SwiftUI + +@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) +public struct CompatibilityEnvironmentTestView: View { +#if compiler(>=5.9) && canImport(Combine) + @CloudStorage(.compatibilityVersionsRunKey) var previouslyRunCompatibilityVersions = Compatibility.version.rawValue +#endif + /// Complete deferred module information; `nil` keeps the loading state distinct from the portable baseline. + @State private var loadedModuleInfo: [Field]? + + /// Creates an environment view whose module metadata is loaded after the UI first appears. + public init() {} + + /// Structured application fields displayed by the environment test view. + public var applicationInfo: [Field] { + var info = [ + Field("Name", "\(Application.main.name) (\(Application.main.appName).app)"), + Field("App Identifier", Application.main.appIdentifier), + Field("App Version", "v\(Application.main.debugVersion)"), + Field("is first run", Application.main.isFirstRun), + ] + let previousVersions = Application.main.previouslyRunVersions + if previousVersions.count > 0 { + info.append(Field("Previously run versions", previousVersions.pretty)) + } + return info + } + + /// Structured Compatibility-version and build-mode fields displayed by the environment test view. + public var compatibilityInfo: [Field] { + var info = [ + Field("\(Compatibility.moduleName) Version", Compatibility.version), + Field("is Debug", Build.isDebug), + ] +#if compiler(>=5.9) && canImport(Combine) + if previouslyRunCompatibilityVersions != "" && previouslyRunCompatibilityVersions != "\(Compatibility.version.rawValue)" { + info += [ + Field("Previously run Compatibility versions", previouslyRunCompatibilityVersions), + Field(nil, "NOTE: This only updates if we're running the DataStore test view and is not guaranteed to be run any other time or from any other app."), + ] + } +#endif + return info + } + + public var body: some View { + List { + FieldSections([ + "Application": applicationInfo, + Compatibility.moduleName: compatibilityInfo, + "iCloud": [ + Field("Supported by app", Application.iCloudSupported), + Field("Enabled", Application.iCloudIsEnabled), + Field("iCloud status", Application.iCloudStatus), + ], + ]) + Section("Module Info") { + // Show the portable baseline immediately, then replace it with the complete loaded result. + // This is example code. Really this only needs to include moduleInfo since the detailed info is already included in other sections. + let displayedModuleInfo = loadedModuleInfo ?? Compatibility.moduleInfo + ForEach(displayedModuleInfo.indices, id: \.self) { index in + FieldView(displayedModuleInfo[index]) + } + if loadedModuleInfo == nil { + ProgressView("Loading module details…") + } + } + Section("Environment") { + FieldView(Field("Swift Version", Build.swiftVersion, symbol: "swift")) + FieldView(Field("Compiler Version", Build.compilerVersion)) + EnvironmentsView(Build.environments()) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) + } + FieldSections([ + "Dates": [ + Field("Now Backport", Date.nowBackport.pretty), + Field("Now MySQL", Date.nowBackport.mysqlDateTime), + Field("Now Numeric", Date.nowBackport.numericDateTime), + Field("Tomorrow", Date.tomorrow.pretty), + Field("Tomorrow Midnight", Date.tomorrowMidnight.pretty), + Field("Yesterday", Date.yesterday.pretty), + ], + ]) + } + .task { + // Await potentially slow details without delaying the portable module fields above. + loadedModuleInfo = await Compatibility.loadDetailedModuleInfo() + } + } +} + +@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) +#Preview { + CompatibilityEnvironmentTestView() + .backport.scrollContentBackground(.hidden) + .background(.red) +} +#endif From c891def7ff491f95f7882daaab53e2d31ed0b5d1 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:24:56 -0400 Subject: [PATCH 025/107] fixed version surfaces --- Development/Compatibility.xcodeproj/project.pbxproj | 4 ++-- Sources/Compatibility.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 850eac0..2a51e0a 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -488,7 +488,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.18.2; + MARKETING_VERSION = 1.18.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -559,7 +559,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.18.2; + MARKETING_VERSION = 1.18.3; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = ""; diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 90cef8c..2afbbb1 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -8,7 +8,7 @@ public enum Compatibility: Module { /// The version of the Compatibility Library since cannot get directly from Package.swift. - public static let version: Version = "1.18.2" + public static let version: Version = "1.18.3" /// Public source repository for Compatibility so support reports can direct developers to its source and issue history. /// From b79fcb62385a21985d6797cf19ced4a9f2243fb3 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:40:05 -0400 Subject: [PATCH 026/107] added compatibility testing library --- .../Compatibility.xcodeproj/project.pbxproj | 7 + .../xcdebugger/Breakpoints_v2.xcbkptlist | 120 ++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 2a51e0a..16887bc 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; B5209EE42C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FC3A2C3860EC004F2009 /* MyApp.swift */; }; + B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */ = {isa = PBXBuildFile; productRef = B52DEB223019BA54003291D0 /* Compatibility Testing Library */; }; B569253B2E8715550045FFC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; B579D4A52C46FF1A009A037A /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B579D4A42C46FF1A009A037A /* Compatibility Library */; }; B58B5C452C38F98800689837 /* (null) in Sources */ = {isa = PBXBuildFile; }; @@ -91,6 +92,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */, B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -227,6 +229,7 @@ name = CompatibilityTests; packageProductDependencies = ( B594CFB62DB0BACA001E8658 /* Compatibility Library */, + B52DEB223019BA54003291D0 /* Compatibility Testing Library */, ); productName = CompatibilityTests; productReference = B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */; @@ -814,6 +817,10 @@ package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; productName = "Compatibility Library"; }; + B52DEB223019BA54003291D0 /* Compatibility Testing Library */ = { + isa = XCSwiftPackageProductDependency; + productName = "Compatibility Testing Library"; + }; B579D4A42C46FF1A009A037A /* Compatibility Library */ = { isa = XCSwiftPackageProductDependency; package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; diff --git a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 5f1efac..a77e84d 100644 --- a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -18,6 +18,36 @@ endingLineNumber = "319" landmarkName = "pretty" landmarkType = "24"> + + + + + + + + + + + + + + + + + + + + + + + + From 2b1541812431ce4db9b72cc0a0b35526115a7781 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:40:24 -0400 Subject: [PATCH 027/107] Remove duplicate module graph traversal --- .../ModuleTestEntry.swift | 71 +++++++------------ 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/Sources/CompatibilityTesting/ModuleTestEntry.swift b/Sources/CompatibilityTesting/ModuleTestEntry.swift index d102a34..41fc907 100644 --- a/Sources/CompatibilityTesting/ModuleTestEntry.swift +++ b/Sources/CompatibilityTesting/ModuleTestEntry.swift @@ -51,55 +51,38 @@ extension ModuleTestEntry: CustomTestArgumentEncodable { @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension ModuleTestEntry { - /// Flattens the supplied modules and their dependencies into individually named test arguments. + /// Flattens an explicitly supplied module test catalog into individually named test arguments. /// - /// Test discovery intentionally builds a local module list instead of mutating `Build.allModules`. - /// A test process may have already finished application module registration before Swift Testing - /// evaluates parameterized arguments; relying on that process-global registry could therefore - /// produce an empty argument list and cause the entire parameterized test to be skipped. + /// The caller supplies the concrete module's `tests` value so Swift does not fall back to a + /// protocol-extension default when a downstream package has an overly restrictive availability + /// annotation. Dependency traversal remains the responsibility of Compatibility's existing + /// `Build` registration graph rather than being duplicated in the testing adapter. @MainActor - static func entries(including modules: Module.Type...) -> [ModuleTestEntry] { - var orderedModules = [Module.Type]() - var includedIdentifiers = Set() - var visitingIdentifiers = Set() - - func include(_ module: Module.Type) { - let identifier = module.moduleIdentifier - - // Ignore modules already emitted and stop circular dependency traversal. - guard !includedIdentifiers.contains(identifier), - !visitingIdentifiers.contains(identifier) else { - return - } - - visitingIdentifiers.insert(identifier) - for dependency in module.dependencies { - include(dependency) - } - visitingIdentifiers.remove(identifier) - - // A sibling dependency may have emitted this module during recursive traversal. - guard includedIdentifiers.insert(identifier).inserted else { - return + static func entries( + for module: Module.Type, + tests: OrderedDictionary + ) -> [ModuleTestEntry] { + tests.flatMap { section, tests in + tests.enumerated().map { index, testCase in + ModuleTestEntry( + module: module, + section: section, + index: index, + testCase: testCase + ) } - orderedModules.append(module) - } - - for module in modules { - include(module) } + } - return orderedModules.flatMap { module in - module.tests.flatMap { section, tests in - tests.enumerated().map { index, testCase in - ModuleTestEntry( - module: module, - section: section, - index: index, - testCase: testCase - ) - } - } + /// Flattens each supplied module's protocol-visible catalog. + /// + /// This convenience remains useful once conforming modules expose `tests` at the same + /// availability as the `Module` requirement. Call ``entries(for:tests:)`` while migrating an + /// older conformer whose test catalog has a stricter availability annotation. + @MainActor + static func entries(including modules: Module.Type...) -> [ModuleTestEntry] { + modules.flatMap { module in + entries(for: module, tests: module.tests) } } } From 53628d6bf99159e5047a9a478ca25c4a507de6e3 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 00:40:39 -0400 Subject: [PATCH 028/107] Use concrete Compatibility test catalog --- Development/CompatibilityTests/ModuleTestEntryTests.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Development/CompatibilityTests/ModuleTestEntryTests.swift b/Development/CompatibilityTests/ModuleTestEntryTests.swift index 4d1594b..8f097eb 100644 --- a/Development/CompatibilityTests/ModuleTestEntryTests.swift +++ b/Development/CompatibilityTests/ModuleTestEntryTests.swift @@ -16,11 +16,14 @@ struct ModuleTestEntryTests { @Test( "Compatibility Module Test", arguments: await MainActor.run { - ModuleTestEntry.entries(including: Compatibility.self) + ModuleTestEntry.entries( + for: Compatibility.self, + tests: Compatibility.tests + ) } ) @MainActor - @available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) func moduleTest(entry: ModuleTestEntry) async throws { try await entry.execute() } From ae7c4eef2ede2da2665747375428576ba208e5c6 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 29 Jul 2026 01:00:21 -0400 Subject: [PATCH 029/107] fixed @available checks for macOS 12 fix @available where macOS 12 was paired with watchOS 8 --- CHANGELOG.md | 2 - .../xcdebugger/Breakpoints_v2.xcbkptlist | 120 ------------------ Sources/Core/Build.swift | 2 +- Sources/Core/CloudStatus.swift | 2 +- Sources/Core/Debug.swift | 2 +- Sources/Core/FileManager.swift | 2 +- Sources/Core/Module.swift | 6 +- Sources/Core/Test.swift | 4 +- Sources/Foundation/CodingMixedTypes.swift | 2 +- Sources/Foundation/Date.swift | 6 +- Sources/Foundation/DateString.swift | 2 +- Sources/Foundation/Double.swift | 2 +- Sources/UI/Backport.swift | 2 +- Sources/UI/OverlappingStack.swift | 2 +- Sources/UI/Pasteboard.swift | 2 +- 15 files changed, 18 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f22150..3138e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,6 @@ # TODO: Testing required before release: -- Build the package in Xcode with ⌘B. -- Run the full test plan with ⌘U. - Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. - Confirm the new entries execute successfully and preserve readable module, section, and test names. - Confirm the serialized debug tests restore `Compatibility.settings` even when an expectation throws. diff --git a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index a77e84d..5f1efac 100644 --- a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -18,36 +18,6 @@ endingLineNumber = "319" landmarkName = "pretty" landmarkType = "24"> - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/Core/Build.swift b/Sources/Core/Build.swift index d856595..5233edd 100644 --- a/Sources/Core/Build.swift +++ b/Sources/Core/Build.swift @@ -523,7 +523,7 @@ public extension Build.Environment { case .designedForiPad: return .purple case .macCatalyst: - if #available(iOS 15.0, macCatalyst 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { + if #available(iOS 15, macCatalyst 15, tvOS 15, macOS 12, watchOS 8, *) { return .teal } else { return .purple diff --git a/Sources/Core/CloudStatus.swift b/Sources/Core/CloudStatus.swift index efd4766..8cafa6c 100644 --- a/Sources/Core/CloudStatus.swift +++ b/Sources/Core/CloudStatus.swift @@ -24,7 +24,7 @@ public enum CloudStatus: CustomStringConvertible, Sendable, CaseIterable, Symbol } #if compiler(>=5.9) -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension CloudStatus { /// Shared enum behavior tests available to both the in-app test UI and Swift Testing bridge. @MainActor diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index 51e0e90..2d7a780 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -458,7 +458,7 @@ public extension TestFailure { // Testing and main-actor isolation are supported on current full-runtime WASM builds. #if compiler(>=5.9) -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension DebugLevel { @MainActor internal static let testDebugConfig: TestClosure = { diff --git a/Sources/Core/FileManager.swift b/Sources/Core/FileManager.swift index 5b66710..88ec6c4 100644 --- a/Sources/Core/FileManager.swift +++ b/Sources/Core/FileManager.swift @@ -42,7 +42,7 @@ public extension FileManager { } #if compiler(>=5.9) -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension FileManager { /// Shared file-manager tests used by both the in-app runner and Swift Testing. @MainActor diff --git a/Sources/Core/Module.swift b/Sources/Core/Module.swift index 790844c..72f4347 100644 --- a/Sources/Core/Module.swift +++ b/Sources/Core/Module.swift @@ -273,7 +273,7 @@ private enum DependentModuleTestFixture: Module { } /// Shared Module tests used by both the in-app All Tests UI and the Swift Testing bridge. -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @MainActor private func testModuleMetadataAndDefaults() async throws { // Verify the default name remains derived from the conforming type so modules do not need boilerplate. @@ -324,13 +324,13 @@ private func testModuleMetadataAndDefaults() async throws { } /// Preserve the module test's actor boundary on every concurrency-capable target, including WebAssembly. -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) private let moduleMetadataTest: TestClosure = { @MainActor in try await testModuleMetadataAndDefaults() } /// The collection remains main-actor isolated on every supported platform, including WebAssembly. -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @MainActor internal let moduleTests: [TestCase] = [ TestCase("Module metadata and defaults", moduleMetadataTest), diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index aee2ca1..6304cd3 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -430,7 +430,7 @@ public extension TestCase { } } -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension TestCase { /// Every reusable Compatibility test, grouped in deterministic display and execution order. /// @@ -480,7 +480,7 @@ public extension TestCase { }() } -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension Compatibility { /// Compatibility's global test catalog. @MainActor diff --git a/Sources/Foundation/CodingMixedTypes.swift b/Sources/Foundation/CodingMixedTypes.swift index b054b6a..9cc89c2 100644 --- a/Sources/Foundation/CodingMixedTypes.swift +++ b/Sources/Foundation/CodingMixedTypes.swift @@ -194,7 +194,7 @@ public enum MixedTypeField: Equatable, Sendable, Hashable { } #if compiler(>=5.9) -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension MixedTypeField { /// Shared value, formatting, and `Field` integration tests available to the in-app and Swift Testing runners. @MainActor diff --git a/Sources/Foundation/Date.swift b/Sources/Foundation/Date.swift index 201a540..b174781 100644 --- a/Sources/Foundation/Date.swift +++ b/Sources/Foundation/Date.swift @@ -209,7 +209,7 @@ public extension Date { // Testing is only supported with Swift 5.9+ #if compiler(>=5.9) && canImport(Foundation) -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension Date { @MainActor static let tests = [ @@ -224,7 +224,7 @@ public extension Date { #if canImport(SwiftUI) import SwiftUI -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) #Preview { VStack { Text("\(String(describing: Date(from: "2023-01-02 17:12:00", format: "yyyy-MM-dd HH:mm:ss")))") @@ -233,7 +233,7 @@ import SwiftUI Text("\(String(describing: Date(from: "2023-01-02 17:12:00", format: "yyyy-MM-dd HH:mm:ss")?.pretty))") } } -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) #Preview("Tests") { TestsListView(tests: Date.tests) } diff --git a/Sources/Foundation/DateString.swift b/Sources/Foundation/DateString.swift index 0c92446..f3e4e45 100644 --- a/Sources/Foundation/DateString.swift +++ b/Sources/Foundation/DateString.swift @@ -180,7 +180,7 @@ public extension Date { try expect(Date(parse: "Jan 2, 2023")?.mysqlDate == "2023-01-02") try expect(Date(parse: "not a date") == nil) } - @available(macOS 12, *) + @available(macOS 10.15, *) @MainActor internal static let testFormatted: TestClosure = { let date = Date(from: "2023-01-02 17:12:00", format: .mysqlDateTimeFormat) diff --git a/Sources/Foundation/Double.swift b/Sources/Foundation/Double.swift index 6c3cfb3..8c161c4 100644 --- a/Sources/Foundation/Double.swift +++ b/Sources/Foundation/Double.swift @@ -293,7 +293,7 @@ public extension Double { // Testing is only supported with Swift 5.9+ #if compiler(>=5.9) -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension Double { @MainActor static let tests = [ diff --git a/Sources/UI/Backport.swift b/Sources/UI/Backport.swift index 96c63e1..f4e47ae 100644 --- a/Sources/UI/Backport.swift +++ b/Sources/UI/Backport.swift @@ -35,7 +35,7 @@ extension Backport where Content == Any { } } -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension Backport where Content == Any { @ViewBuilder public static func LabeledContent(_ titleKey: String, value: some StringProtocol) -> some View { if titleKey.count > 35 { diff --git a/Sources/UI/OverlappingStack.swift b/Sources/UI/OverlappingStack.swift index 875eadf..cd4a158 100644 --- a/Sources/UI/OverlappingStack.swift +++ b/Sources/UI/OverlappingStack.swift @@ -219,7 +219,7 @@ private struct OverlappingStack: Layout { } } -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) #Preview("OverlappingHStack") { VStack { Text("All of these should be the same height.") diff --git a/Sources/UI/Pasteboard.swift b/Sources/UI/Pasteboard.swift index 46048db..5e078c8 100644 --- a/Sources/UI/Pasteboard.swift +++ b/Sources/UI/Pasteboard.swift @@ -204,7 +204,7 @@ public extension Compatibility { } #if compiler(>=5.9) -@available(iOS 13, macOS 12, tvOS 13, watchOS 6, *) +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension Pasteboard { /// Deterministic pasteboard tests shared by the in-app runner and Swift Testing. @MainActor From aca761c1b929598749c506d0fff0a825af773e9b Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 8 Aug 2026 13:25:32 -0400 Subject: [PATCH 030/107] Update CONTRIBUTING.md --- CONTRIBUTING.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94c6617..4562e93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,7 @@ Compatibility prioritizes portability, backwards compatibility, clear public documentation, and reviewable changes. Contributors and coding agents should follow these repository-specific rules. +## Specific prompt reference (AIs should ignore this section and skip to the Interactive Coding Preferences section) PROMPT prefix for Xcode or another context without memory for projects using Compatibility: Follow the included Compatibility `CONTRIBUTING.md` (or github.com/kudit/Compatibility/CONTRIBUTING.md), preserve existing edits, then complete this request: [REQUEST] @@ -10,17 +11,19 @@ PROMPT for updating Module packages: Review this Swift package for adoption of the Module APIs introduced in github.com/kudit/Compatibility v1.16.0 or later. Inspect the package’s existing architecture and preserve its public behavior and platform compatibility. Add or update its Compatibility dependency if necessary. Apply an appropriate Module conformance, including its version, direct Compatibility dependency, module dependencies, immediately available moduleInfo, ordered TestCase sections, and opt-in open-source repository metadata when applicable. Register the package from its highest-level module or document how an application should register it through Application.track(including:). Add complete inline DocC comments to the relevant public APIs so generated documentation can discover them. Do not create a .docc catalog, separate documentation articles, or another documentation folder. Preserve existing comments unless they are missing, unclear, or inaccurate. Put reusable tests in the module's TestCase collections so they run both in the in-app test UI and through the Swift Testing bridge; retain target-specific tests only where infrastructure requires them. Follow this package’s existing CONTRIBUTING.md, changelog, versioning, formatting, availability, and compatibility conventions. Avoid unrelated reformatting and whitespace-only changes. Before changing version numbers, compare the current changelog version with the latest committed Git version. If the active working-tree changelog is already ahead of Git, do not choose another version; synchronize that active version across every package manifest, Xcode project, public source constant, test fixture or suite heading, README or documentation display, and other hard-coded version surface. Please check that all deprecations (that can) have appropriate renamed clauses for easy fixits. -## Collaborative coding workflow - +## Interactive Coding Preferences When working interactively with a maintainer, generally (this shouldn't be meant to override thread instructions but are here as a default): -- Work in small, reviewable stages rather than delivering a large implementation all at once. +- If there is ever any conflict between instructions in a prompt, pause and clarify before continuing. +- Work in small, reviewable stages rather than delivering a large implementation all at once (unless specifically requested). - Present one immediate decision or action at a time and pause for maintainer feedback unless instructed to do a batch. - Explain design choices briefly and answer questions before continuing implementation. - Preserve and review the maintainer's local edits before adding further changes. - Let the maintainer build, edit, commit, and push between stages when practical. - After each pushed maintainer change, review the latest commit before proposing or applying the next change. - Keep pull requests in draft until the implementation is compiled, exercised by real tests, and fully reviewed. -- Avoid unrelated cleanup, broad reformatting, and speculative changes that make the diff harder to reason about. +- Avoid unrelated cleanup, broad reformatting, and speculative changes that make the diff harder to reason about unless specifically asked for. +- Do not ever make up code or delete comments with instructions unless you've followed the instructions and made the changes. Instruction comments, TODOs, migration notes, and user-authored comments may not be removed unless the requested work is implemented and the comment is replaced with an accurate explanation or removed with explicit justification. +- Don't offer verbose explanations in the chat interface. Long explanations should not be necessary if code is well documented inline and should be included there to read inline with code changes during diff review. The chat interface should be for clarifying questions and high level discussion, answering questions, and providing high-level feedback. When working on code projects, extra text and explanation in the chat is not a good way to preserve information. Put next steps into an appropriate section of a markdown file like the CHANGELOG, put potential future ideas there, and architecture plans and roadmaps rather than in the chat itself. ## Version and changelog rules @@ -36,6 +39,7 @@ When working interactively with a maintainer, generally (this shouldn't be meant - Modules should have separate `README.md` and `CHANGELOG.md` files. Final apps may keep a Changelog section in their README. - When you notice existing/manual uncommitted edits, please automatically generate and add changelog comments for the manual changes. + ## Post-prompt checklist After every prompt-driven change, contributors and coding agents must: @@ -68,16 +72,19 @@ Planned features grouped by future version. - [ ] Longer-term ideas, experiments, and possible improvements. ``` + ## Code style - Preserve public identifiers, established behavior, compatibility paths, and user-visible syntax unless a breaking change is explicitly requested. - Keep changes tightly scoped and avoid unrelated reformatting or whitespace-only edits. -- Add clear inline comments explaining new or modified code and why compatibility-specific behavior is necessary. +- Add clear inline comments explaining new or modified code and why the change is necessary. +- Please make clear when code is not best practice or the obvious way of doing things particularly when you're making stylistic or judgement choices. - Add complete DocC comments to public APIs and to non-obvious internal APIs. - Preserve existing comments unless they are obsolete. - Use concise comments for obvious behavior and more detail around compatibility, migration, concurrency, and platform-specific decisions. - Prefer plain Markdown and code blocks for text intended to be pasted into files, GitHub, Xcode, or terminals. + ## Swift rules - Include `github.com/kudit/Compatibility` as a dependency in Swift projects and reuse its APIs where appropriate. @@ -99,6 +106,7 @@ Planned features grouped by future version. - Swift does not expose a general-purpose `hasFeature(Concurrency)` condition that proves a target has a scheduler, threads, Dispatch, or suspending timers. Use `canImport(Dispatch)` for Dispatch-backed implementations, availability checks for deployed Apple concurrency runtimes, `hasFeature(Embedded)` only for known Embedded restrictions, and narrowly documented platform checks for host facilities such as WebAssembly timers. - Do not gate `Equatable`, `Encodable`, or `Decodable` merely because a build targets Linux, Android, WASM, or WASI. Those protocols are part of full Swift runtimes. Before changing a conformance gate, also check whether the concrete type is locally owned, is a typealias to a Foundation type, already conforms on that Foundation implementation, or requires Swift 6's `@retroactive` ownership annotation. + ## Design goals - Backwards compatibility where practical. From 501d199358b1aa17e6097346dff96cd0987b2358 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 10:13:15 -0400 Subject: [PATCH 031/107] Capture TestCase source at caller --- Sources/Core/Test.swift | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index 6304cd3..3609067 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -321,10 +321,18 @@ public final class TestCase: ObservableObject, @unchecked Sendable { setUp: TestClosure? = nil, test: @escaping TestClosure, tearDown: TestClosure? = nil, - source: SourceContext = SourceContext() + source: SourceContext? = nil, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column ) { self.title = title - self.source = source + // Do not use `SourceContext()` as a default argument here. Nested default arguments are + // evaluated at this initializer declaration, which would make failures point into Test.swift. + // Capture the compiler literals directly on this initializer so omitted source information + // identifies the TestCase declaration at the caller. An explicit source still wins. + self.source = source ?? SourceContext(file: file, function: function, line: line, column: column) self.executionMode = executionMode self.setUp = setUp self.test = test @@ -335,10 +343,23 @@ public final class TestCase: ObservableObject, @unchecked Sendable { public convenience init( _ title: String, executionMode: TestExecutionMode = .parallel, - source: SourceContext = SourceContext(), + source: SourceContext? = nil, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column, _ test: @escaping TestClosure ) { - self.init(title, executionMode: executionMode, test: test, source: source) + self.init( + title, + executionMode: executionMode, + test: test, + source: source, + file: file, + function: function, + line: line, + column: column + ) } private var execution: TestExecution { @@ -496,4 +517,4 @@ import SwiftUI TestsListView(tests: Compatibility.threadingTests + Int.tests) } #endif -#endif +#endif \ No newline at end of file From 8b0b20675d99c01c93adcc02424cb227c9a8965d Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 10:14:34 -0400 Subject: [PATCH 032/107] Stringify debug values without Foundation --- Sources/Core/Debug.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index 2d7a780..1a44bb0 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -355,8 +355,14 @@ public extension Compatibility { */ @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { -#if hasFeature(Embedded) || !canImport(Foundation) +#if hasFeature(Embedded) + // Embedded Swift already narrows `DebugMessage` to `String`, so no dynamic conversion is needed. + let isMainThread = true +#elseif !canImport(Foundation) + // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before + // forwarding to the shared String-based formatter just as Foundation-backed builds do. let isMainThread = true + let message = String(describing: message) #else let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing let message = String(describing: message) // convert to sendable item to avoid any thread issues. @@ -555,4 +561,4 @@ Normal output: \(defaultOutput) TestCase("debug tests", executionMode: .serialized, testDebug), ] } -#endif +#endif \ No newline at end of file From 91d902bc719bf41479ce4050b1c4ee0d18628ed4 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 10:32:44 -0400 Subject: [PATCH 033/107] Removed code duplication --- CHANGELOG.md | 9 +++++---- Sources/Core/Debug.swift | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3138e6f..6049f8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,13 @@ # TODO: Testing required before release: -- Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. -- Confirm the new entries execute successfully and preserve readable module, section, and test names. -- Confirm the serialized debug tests restore `Compatibility.settings` even when an expectation throws. +- Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. (I do not see)) +- Confirm the new entries execute successfully and preserve readable module, section, and test names. (do not see)) +- Confirm the serialized debug tests restore `Compatibility.settings` even when an expectation throws. (how do I do this?) - Run SwiftPM and supported-platform validation before tagging the release. +I do not see each reusable TestCase separately in the test navigator in Xcode. I just see Compatibility Module Test and Compatibility Target Tests. -## v1.18.3 2026-07-28 +## v1.18.3 2026-08-12 Added the reusable `Compatibility Testing Library` product and `ModuleTestEntry` adapter so each module `TestCase` appears as an individually named Swift Testing result. Unified `TestCase.execute()` and live test execution through one lifecycle implementation with explicit parallel and serialized execution modes. Added source-aware test failures, labeled debug-format context, and source-context debugging conveniences while preserving existing debug-format call sites. diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index 1a44bb0..cba9a53 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -355,16 +355,16 @@ public extension Compatibility { */ @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { -#if hasFeature(Embedded) +#if hasFeature(Embedded) || !canImport(Foundation) // Embedded Swift already narrows `DebugMessage` to `String`, so no dynamic conversion is needed. let isMainThread = true -#elseif !canImport(Foundation) - // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before - // forwarding to the shared String-based formatter just as Foundation-backed builds do. - let isMainThread = true - let message = String(describing: message) #else let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing +#endif + +#if !hasFeature(Embedded) + // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before + // forwarding to the shared String-based formatter just as Foundation-backed builds do. let message = String(describing: message) // convert to sendable item to avoid any thread issues. #endif return debug(message, isMainThread: isMainThread, level: level, file: file, function: function, line: line, column: column) @@ -561,4 +561,4 @@ Normal output: \(defaultOutput) TestCase("debug tests", executionMode: .serialized, testDebug), ] } -#endif \ No newline at end of file +#endif From 0e92705f31083f9ce07a74ac0e9e8245d4d1bb84 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 10:35:24 -0400 Subject: [PATCH 034/107] Preserve TestCase SourceContext call-site defaults --- Sources/Core/Test.swift | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index 3609067..6a8327b 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -321,18 +321,15 @@ public final class TestCase: ObservableObject, @unchecked Sendable { setUp: TestClosure? = nil, test: @escaping TestClosure, tearDown: TestClosure? = nil, - source: SourceContext? = nil, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column + source: SourceContext = SourceContext( + file: #file, + function: #function, + line: #line, + column: #column + ) ) { self.title = title - // Do not use `SourceContext()` as a default argument here. Nested default arguments are - // evaluated at this initializer declaration, which would make failures point into Test.swift. - // Capture the compiler literals directly on this initializer so omitted source information - // identifies the TestCase declaration at the caller. An explicit source still wins. - self.source = source ?? SourceContext(file: file, function: function, line: line, column: column) + self.source = source self.executionMode = executionMode self.setUp = setUp self.test = test @@ -343,23 +340,15 @@ public final class TestCase: ObservableObject, @unchecked Sendable { public convenience init( _ title: String, executionMode: TestExecutionMode = .parallel, - source: SourceContext? = nil, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column, + source: SourceContext = SourceContext( + file: #file, + function: #function, + line: #line, + column: #column + ), _ test: @escaping TestClosure ) { - self.init( - title, - executionMode: executionMode, - test: test, - source: source, - file: file, - function: function, - line: line, - column: column - ) + self.init(title, executionMode: executionMode, test: test, source: source) } private var execution: TestExecution { From a0a51627167788d839278979b78f08a9a2c1d99d Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 11:02:11 -0400 Subject: [PATCH 035/107] Restore caller-side TestCase source capture --- Sources/Core/Test.swift | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index 6a8327b..3609067 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -321,15 +321,18 @@ public final class TestCase: ObservableObject, @unchecked Sendable { setUp: TestClosure? = nil, test: @escaping TestClosure, tearDown: TestClosure? = nil, - source: SourceContext = SourceContext( - file: #file, - function: #function, - line: #line, - column: #column - ) + source: SourceContext? = nil, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column ) { self.title = title - self.source = source + // Do not use `SourceContext()` as a default argument here. Nested default arguments are + // evaluated at this initializer declaration, which would make failures point into Test.swift. + // Capture the compiler literals directly on this initializer so omitted source information + // identifies the TestCase declaration at the caller. An explicit source still wins. + self.source = source ?? SourceContext(file: file, function: function, line: line, column: column) self.executionMode = executionMode self.setUp = setUp self.test = test @@ -340,15 +343,23 @@ public final class TestCase: ObservableObject, @unchecked Sendable { public convenience init( _ title: String, executionMode: TestExecutionMode = .parallel, - source: SourceContext = SourceContext( - file: #file, - function: #function, - line: #line, - column: #column - ), + source: SourceContext? = nil, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column, _ test: @escaping TestClosure ) { - self.init(title, executionMode: executionMode, test: test, source: source) + self.init( + title, + executionMode: executionMode, + test: test, + source: source, + file: file, + function: function, + line: line, + column: column + ) } private var execution: TestExecution { From 996c378f6e79666bb2f35c36d45df874eeb27b90 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 11:48:09 -0400 Subject: [PATCH 036/107] Add parameterized test discovery control --- Development/CompatibilityTests/ModuleTestEntryTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Development/CompatibilityTests/ModuleTestEntryTests.swift b/Development/CompatibilityTests/ModuleTestEntryTests.swift index 8f097eb..b24a48a 100644 --- a/Development/CompatibilityTests/ModuleTestEntryTests.swift +++ b/Development/CompatibilityTests/ModuleTestEntryTests.swift @@ -27,5 +27,11 @@ struct ModuleTestEntryTests { func moduleTest(entry: ModuleTestEntry) async throws { try await entry.execute() } + + /// Simple static control used to verify that Xcode discovers and expands parameterized cases. + @Test("Parameter display test", arguments: [1, 2, 3]) + func parameterDisplayTest(value: Int) { + #expect((1...3).contains(value)) + } } #endif From 62aa5fa78f117fd20cc41d54b20eba78b1805ccf Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 11:48:33 -0400 Subject: [PATCH 037/107] Make Compatibility test targets explicit in shared scheme --- .../xcschemes/CompatibilityTest.xcscheme | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 6ef5942..048814b 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -21,6 +21,34 @@ ReferencedContainer = "container:Compatibility.xcodeproj"> + + + + + + + + Date: Wed, 12 Aug 2026 11:50:25 -0400 Subject: [PATCH 038/107] Make SourceContext the debug forwarding core --- Sources/Core/Debug.swift | 57 ++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index cba9a53..9f8eb9b 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -355,37 +355,43 @@ public extension Compatibility { */ @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { + debug( + message, + level: level, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Logs a message using an already-captured source location. This is the core forwarding path. + @discardableResult + static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { #if hasFeature(Embedded) || !canImport(Foundation) - // Embedded Swift already narrows `DebugMessage` to `String`, so no dynamic conversion is needed. + // Single-threaded or Foundation-less runtimes cannot provide Foundation.Thread identity. let isMainThread = true #else let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing #endif #if !hasFeature(Embedded) - // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before - // forwarding to the shared String-based formatter just as Foundation-backed builds do. let message = String(describing: message) // convert to sendable item to avoid any thread issues. #endif - return debug(message, isMainThread: isMainThread, level: level, file: file, function: function, line: line, column: column) + return debug(message, isMainThread: isMainThread, level: level, source: source) } - /// Logs a message using an already-captured source location. + /// Caller-capturing compatibility wrapper for the lower-level formatter path. @discardableResult - static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { + static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { debug( message, + isMainThread: isMainThread, level: level, - file: source.file, - function: source.function, - line: source.line, - column: source.column + source: SourceContext(file: file, function: function, line: line, column: column) ) } - /// Put most of the business logic here for compatibility with WASM. isMainThread: is required to differentiate but can be removed in global definition + /// Core debug implementation once source context and thread identity are known. @discardableResult - static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { + static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { guard DebugLevel.isAtLeast(level) else { // check current debug level from settings return "" // don't actually print } @@ -396,7 +402,7 @@ public extension Compatibility { Compatibility.settings.debugEmojiSupported, Compatibility.settings.debugLevelsToIncludeContext.contains(level), Compatibility.settings.debugLevelsToIncludeTimestamp.contains(level), - file, function, line, column) + source.file, source.function, source.line, source.column) // log message Compatibility.settings.debugLog(debugMessage) @@ -420,13 +426,17 @@ public extension Compatibility { */ @discardableResult public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { - return Compatibility.debug(message, level: level, file: file, function: function, line: line, column: column) + Compatibility.debug( + message, + level: level, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } /// Logs a message using an already-captured source location. @discardableResult public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { - return Compatibility.debug(message, level: level, source: source) + Compatibility.debug(message, level: level, source: source) } // MARK: Debug(error) @@ -434,17 +444,20 @@ public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, so public extension Error { /** Outputs the error's localized description at the specified debug level and return. Can append to errors to debug output at the throwing location rather than the caught location. - - - Parameter level: The logging level to use. - - Parameter file: For bubbling down the #file name from a call site. - - Parameter function: For bubbling down the #function name from a call site. - - Parameter line: For bubbling down the #line number from a call site. - - Parameter column: For bubbling down the #column number from a call site. (Not used currently but here for completeness). */ func debug(level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> Self { - Compatibility.debug(self.localizedDescription, level: level, file: file, function: function, line: line, column: column) + debug( + level: level, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Logs this error using an already-captured source location and returns it for throwing. + func debug(level: DebugLevel = .defaultLevel, source: SourceContext) -> Self { + Compatibility.debug(self.localizedDescription, level: level, source: source) return self } + #if !canImport(Foundation) var localizedDescription: String { "There was an error but without Foundation, we're using the default `localizedDescription`." From 76672bbd156fb4438af1d91f9fbaacd9e69fbd78 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 11:51:33 -0400 Subject: [PATCH 039/107] Use SourceContext through network forwarding paths --- Sources/Core/Network.swift | 83 +++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 24 deletions(-) diff --git a/Sources/Core/Network.swift b/Sources/Core/Network.swift index 14c3c21..0aec63f 100644 --- a/Sources/Core/Network.swift +++ b/Sources/Core/Network.swift @@ -162,17 +162,27 @@ extension URLRequest { } extension Compatibility { - /// Fetch data from URL including optional postData. Will report included file information and automatically debug output to the logs. + /// Fetch data from URL including optional postData. Will report the original caller in debug output. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public static func fetchURLData(urlString: String, postData: PostData? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) async throws -> Data { + try await fetchURLData( + urlString: urlString, + postData: postData, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for APIs that have already captured their caller's location. + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency + public static func fetchURLData(urlString: String, postData: PostData? = nil, source: SourceContext) async throws -> Data { #if !hasFeature(Embedded) - debug("Fetching URL [\(urlString)]...", level: .NOTICE, file: file, function: function, line: line, column: column) + debug("Fetching URL [\(urlString)]...", level: .NOTICE, source: source) #else - debug("Fetching URL [\(urlString)]...", isMainThread: false, file: file, function: function, line: line, column: column) + debug("Fetching URL [\(urlString)]...", isMainThread: false, source: source) #endif // create the url with URL guard let url = URL(string: urlString) else { - throw NetworkError.urlParsing(urlString: urlString).debug(level: .ERROR, file: file, function: function, line: line, column: column) + throw NetworkError.urlParsing(urlString: urlString).debug(level: .ERROR, source: source) } // now create the URLRequest object using the url object @@ -181,18 +191,13 @@ extension Compatibility { // encode the postData if provided, otherwise set the method to GET. if let parameters = postData { request.httpMethod = "POST" //set http method as POST - - // declare the parameter as a dictionary that contains string as key and value combination. considering inputs are valid - - //let parameters: [String: Any] = ["id": 13, "name": "jack"] guard let data = postData?.queryEncoded else { - throw NetworkError.postDataEncoding(parameters).debug(level: .ERROR, file: file, function: function, line: line, column: column) + throw NetworkError.postDataEncoding(parameters).debug(level: .ERROR, source: source) } request.httpBody = data } else { request.httpMethod = "GET" //set http method as GET } - //debug("FETCHING: \(request)", level: .DEBUG, file: file, function: function, line: line, column: column) var data: Data var response: URLResponse @@ -206,55 +211,85 @@ extension Compatibility { } } catch { if let error = error as? URLError, error.code.rawValue == -1003 { - throw NetworkError.missingEntitlement.debug(level: .ERROR, file: file, function: function, line: line, column: column) + throw NetworkError.missingEntitlement.debug(level: .ERROR, source: source) } else { - throw error.debug(level: .ERROR, file: file, function: function, line: line, column: column) + throw error.debug(level: .ERROR, source: source) } } - //debug("DEBUG RESPONSE DATA: \(data)") // Check response status code exists (should nearly always pass) guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { let debugMessage = "No status code in HTTP response. Possibly offline?: \(String(describing: response))" #if !hasFeature(Embedded) - debug(debugMessage, level: .ERROR) + debug(debugMessage, level: .ERROR, source: source) #else - debug(debugMessage, isMainThread: false, level: .ERROR) + debug(debugMessage, isMainThread: false, level: .ERROR, source: source) #endif - throw NetworkError.invalidResponse().debug(level: .ERROR, file: file, function: function, line: line, column: column) + throw NetworkError.invalidResponse().debug(level: .ERROR, source: source) } // check status code (should always be 200) guard statusCode == 200 else { - throw NetworkError.invalidResponse(code: statusCode).debug(level: .ERROR, file: file, function: function, line: line, column: column) + throw NetworkError.invalidResponse(code: statusCode).debug(level: .ERROR, source: source) } return data } - /// Fetch a string from the provided URL. If `postData` is provided, will use `POST` method instead of `GET`. + + /// Fetch a string from the provided URL. If `postData` is provided, will use `POST` method instead of `GET`. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public static func fetchURL(urlString: String, postData: PostData? = nil, encoding: String.Encoding = .utf8, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) async throws -> String { - let data = try await fetchURLData(urlString: urlString, postData: postData, file: file, function: function, line: line, column: column) + try await fetchURL( + urlString: urlString, + postData: postData, + encoding: encoding, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for APIs that have already captured their caller's location. + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency + public static func fetchURL(urlString: String, postData: PostData? = nil, encoding: String.Encoding = .utf8, source: SourceContext) async throws -> String { + let data = try await fetchURLData(urlString: urlString, postData: postData, source: source) // convert result data to string guard let responseString = String(data: data, encoding: encoding) else { #if compiler(>=5.9) - throw NetworkError.dataError(data).debug(level: .ERROR, file: file, function: function, line: line, column: column) + throw NetworkError.dataError(data).debug(level: .ERROR, source: source) #else - throw CustomError("Data error: \(data)", level: .ERROR, file: file, function: function, line: line, column: column) + throw CustomError("Data error: \(data)", level: .ERROR, file: source.file, function: source.function, line: source.line, column: source.column) #endif } - //debug("Response String:\n\(responseString)", level: .SILENT) // this could be way too chatty if happens all the time. Just debug at the calling site if needed. return responseString } } + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public func fetchURLData(urlString: String, postData: PostData? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) async throws -> Data { - try await Compatibility.fetchURLData(urlString: urlString, postData: postData, file: file, function: function, line: line, column: column) + try await fetchURLData( + urlString: urlString, + postData: postData, + source: SourceContext(file: file, function: function, line: line, column: column) + ) +} + +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency +public func fetchURLData(urlString: String, postData: PostData? = nil, source: SourceContext) async throws -> Data { + try await Compatibility.fetchURLData(urlString: urlString, postData: postData, source: source) } + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public func fetchURL(urlString: String, postData: PostData? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) async throws -> String { - try await Compatibility.fetchURL(urlString: urlString, postData: postData, file: file, function: function, line: line, column: column) + try await fetchURL( + urlString: urlString, + postData: postData, + source: SourceContext(file: file, function: function, line: line, column: column) + ) +} + +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency +public func fetchURL(urlString: String, postData: PostData? = nil, source: SourceContext) async throws -> String { + try await Compatibility.fetchURL(urlString: urlString, postData: postData, source: source) } @available(iOS 15, macOS 10.15, tvOS 13, watchOS 6, *) From e994b216ea8ddc4baa914f1b603513795164dd74 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 11:53:11 -0400 Subject: [PATCH 040/107] Forward Application tracking with SourceContext --- Sources/Core/Application.swift | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Sources/Core/Application.swift b/Sources/Core/Application.swift index cd4f079..361cfdd 100644 --- a/Sources/Core/Application.swift +++ b/Sources/Core/Application.swift @@ -156,34 +156,35 @@ public class Application: ObservableObject { // The private initializer preserve /// Place this in `application(_:didFinishLaunchingWithOptions:)` or the `@main` type's initializer. /// Compatibility is always registered automatically. Pass only the highest-level modules used directly /// by the application; their ``Module/dependencies`` are discovered recursively. - /// - /// - Parameters: - /// - modules: Top-level modules used by the application. - /// - file: Source file that initiated tracking. - /// - function: Source function that initiated tracking. - /// - line: Source line that initiated tracking. - /// - column: Source column that initiated tracking. public static func track(including modules: [Module.Type] = [], file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) { + track( + including: modules, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for callers that have already captured their own call site. + public static func track(including modules: [Module.Type] = [], source: SourceContext) { // Compatibility supplies Application itself, so it belongs in every tracked application's module report. Compatibility.include() Build.register(modules) // Prevent late mutation once asynchronous support reporting can begin reading the global registry. Build.finishModuleRegistration() // Calling Application.main is what initializes the application and does the tracking. This really should only be called once. TODO: Should we check to make sure this isn't called twice?? Application.main singleton should only be inited once. - debug("Application Tracking: \(Application.main.appName)", level: .NOTICE, file: file, function: function, line: line, column: column) // Initialize persisted version state synchronously before detached reporting begins. + debug("Application Tracking: \(Application.main.appName)", level: .NOTICE, source: source) // Initialize persisted version state synchronously before detached reporting begins. // Defer the complete report so modules may calculate or fetch metadata without blocking application launch. #if arch(wasm32) // Full-runtime WebAssembly supports unstructured tasks, but the detached // convenience wrappers require host scheduling facilities. Task { @MainActor in let description = await Application.main.loadDetailedDescription() - debug("Application Detailed Tracking:\n\(description)", level: .NOTICE, file: file, function: function, line: line, column: column) + debug("Application Detailed Tracking:\n\(description)", level: .NOTICE, source: source) } #else Task.background { let description = await Application.main.loadDetailedDescription() Task.main { - debug("Application Detailed Tracking:\n\(description)", level: .NOTICE, file: file, function: function, line: line, column: column) + debug("Application Detailed Tracking:\n\(description)", level: .NOTICE, source: source) } } #endif From 06f0851c1e02dcad7439f6abda41a474666768ee Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 11:54:59 -0400 Subject: [PATCH 041/107] Use SourceContext through reusable test diagnostics --- Sources/Core/Test.swift | 107 +++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index 3609067..d40f54b 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -12,7 +12,7 @@ public struct SourceContext: Sendable, CustomStringConvertible { public let line: Int public let column: Int - /// Captures the call site by default. + /// Captures the call site when its individual defaults are used directly by a caller. public init( file: String = #file, function: String = #function, @@ -35,7 +35,16 @@ public struct TestFailure: Error, Sendable, CustomStringConvertible { public let message: String public let source: SourceContext - public init(_ message: String, source: SourceContext = SourceContext()) { + /// Caller-capturing convenience that preserves the source of a naked `TestFailure("...")` call. + public init(_ message: String, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) { + self.init( + message, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for callers that have already captured their own call site. + public init(_ message: String, source: SourceContext) { self.message = message self.source = source } @@ -67,10 +76,18 @@ extension TestFailure: LocalizedError { /// The source location defaults mirror Swift Testing's diagnostics while remaining callable from /// live applications, previews, older systems, and test runners that do not provide Swift Testing. public func expect(_ condition: Bool, _ debugString: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { + try expect( + condition, + debugString, + source: SourceContext(file: file, function: function, line: line, column: column) + ) +} + +/// Source-forwarding form for reusable expectation helpers. +public func expect(_ condition: Bool, _ debugString: String? = nil, source: SourceContext) throws { guard condition else { let message = debugString ?? "Expectation failed" - let source = SourceContext(file: file, function: function, line: line, column: column) - debug(message, level: .ERROR, file: file, function: function, line: line, column: column) + debug(message, level: .ERROR, source: source) throw TestFailure(message, source: source) } } @@ -82,16 +99,44 @@ public func expect(_ condition: Bool, _ debugString: String? = nil, file: String /// - expected: The value the test requires. /// - message: Optional context appended to the generated actual-versus-expected diagnostic. public func expectEqual(_ actual: Value, _ expected: Value, _ message: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { + try expectEqual( + actual, + expected, + message, + source: SourceContext(file: file, function: function, line: line, column: column) + ) +} + +/// Source-forwarding form for APIs that already captured the original comparison call site. +public func expectEqual(_ actual: Value, _ expected: Value, _ message: String? = nil, source: SourceContext) throws { // Build the comparison text here so UI runs receive the same useful values that Swift Testing displays. let context = message.map { " \($0)" } ?? "" - try expect(actual == expected, "Expected \(String(reflecting: expected)), but received \(String(reflecting: actual)).\(context)", file: file, function: function, line: line, column: column) + try expect( + actual == expected, + "Expected \(String(reflecting: expected)), but received \(String(reflecting: actual)).\(context)", + source: source + ) } /// Requires two equatable values to differ and reports the shared value when they do not. public func expectNotEqual(_ actual: Value, _ unexpected: Value, _ message: String? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws { + try expectNotEqual( + actual, + unexpected, + message, + source: SourceContext(file: file, function: function, line: line, column: column) + ) +} + +/// Source-forwarding form for APIs that already captured the original comparison call site. +public func expectNotEqual(_ actual: Value, _ unexpected: Value, _ message: String? = nil, source: SourceContext) throws { // Include the unexpected value so a failure remains actionable outside a debugger. let context = message.map { " \($0)" } ?? "" - try expect(actual != unexpected, "Expected a value other than \(String(reflecting: unexpected)), but received it.\(context)", file: file, function: function, line: line, column: column) + try expect( + actual != unexpected, + "Expected a value other than \(String(reflecting: unexpected)), but received it.\(context)", + source: source + ) } // NOTE: Really wish there was a way of writing a possibly async function or doing this using a generic so we don't have to duplicate code. @@ -311,28 +356,39 @@ public final class TestCase: ObservableObject, @unchecked Sendable { } @Published public var progress: TestProgress = .notStarted - /// Creates a reusable test with optional lifecycle closures. - /// - /// Teardown is attempted even when setup or the test throws, matching the cleanup expectation - /// familiar from XCTest without claiming `XCTestCase` API or inheritance compatibility. - public init( + /// Creates a reusable test with optional lifecycle closures while capturing its declaration site. + public convenience init( _ title: String, executionMode: TestExecutionMode = .parallel, setUp: TestClosure? = nil, test: @escaping TestClosure, tearDown: TestClosure? = nil, - source: SourceContext? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column + ) { + self.init( + title, + executionMode: executionMode, + setUp: setUp, + test: test, + tearDown: tearDown, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for callers that already captured the declaration site. + public init( + _ title: String, + executionMode: TestExecutionMode = .parallel, + setUp: TestClosure? = nil, + test: @escaping TestClosure, + tearDown: TestClosure? = nil, + source: SourceContext ) { self.title = title - // Do not use `SourceContext()` as a default argument here. Nested default arguments are - // evaluated at this initializer declaration, which would make failures point into Test.swift. - // Capture the compiler literals directly on this initializer so omitted source information - // identifies the TestCase declaration at the caller. An explicit source still wins. - self.source = source ?? SourceContext(file: file, function: function, line: line, column: column) + self.source = source self.executionMode = executionMode self.setUp = setUp self.test = test @@ -343,7 +399,6 @@ public final class TestCase: ObservableObject, @unchecked Sendable { public convenience init( _ title: String, executionMode: TestExecutionMode = .parallel, - source: SourceContext? = nil, file: String = #file, function: String = #function, line: Int = #line, @@ -354,14 +409,20 @@ public final class TestCase: ObservableObject, @unchecked Sendable { title, executionMode: executionMode, test: test, - source: source, - file: file, - function: function, - line: line, - column: column + source: SourceContext(file: file, function: function, line: line, column: column) ) } + /// Source-forwarding trailing-closure form. + public convenience init( + _ title: String, + executionMode: TestExecutionMode = .parallel, + source: SourceContext, + _ test: @escaping TestClosure + ) { + self.init(title, executionMode: executionMode, test: test, source: source) + } + private var execution: TestExecution { TestExecution( title: title, From 2a08b791e2b5013611cdbeed8a3fc89766c8b855 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 12:31:23 -0400 Subject: [PATCH 042/107] Restored missing documentation --- .../xcdebugger/Breakpoints_v2.xcbkptlist | 120 ++++++++++++++++++ Sources/Core/Debug.swift | 6 +- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 5f1efac..eb16def 100644 --- a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -18,6 +18,36 @@ endingLineNumber = "319" landmarkName = "pretty" landmarkType = "24"> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index 9f8eb9b..e05a9d7 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -372,7 +372,11 @@ public extension Compatibility { let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing #endif + // Embedded Swift already narrows `DebugMessage` to `String`, so no dynamic conversion is needed. #if !hasFeature(Embedded) + // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before + // forwarding to the shared String-based formatter just as Foundation-backed builds do. We have + // a backport for String(describing: message) so we don't need to worry about canImport(Foundation) for this line. let message = String(describing: message) // convert to sendable item to avoid any thread issues. #endif return debug(message, isMainThread: isMainThread, level: level, source: source) @@ -389,7 +393,7 @@ public extension Compatibility { ) } - /// Core debug implementation once source context and thread identity are known. + /// Core debug implementation once source context and thread identity are known. This is the main function all conveniences should eventually delegate to. @discardableResult static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { guard DebugLevel.isAtLeast(level) else { // check current debug level from settings From f383bb54bcd4468ef91dc7229c1a971cbf77ba7b Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 12:41:51 -0400 Subject: [PATCH 043/107] Forward threading source context through Compatibility APIs --- Sources/Foundation/Threading.swift | 94 +++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 22 deletions(-) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index cf138a0..7d15850 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -96,16 +96,21 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { + sleep( + seconds: seconds, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for helpers that already captured the original call site. + static func sleep(seconds: Double, source: SourceContext) { // This gate describes the missing timer primitive, not missing Swift concurrency support: // browser hosts must schedule a JavaScript timer while WASI hosts use host-specific clocks. Compatibility.debug( "Sleep is unavailable on this WebAssembly runtime; no delay occurred. Prefer an asynchronous host timer for browser or WASI code.", isMainThread: true, level: .WARNING, - file: file, - function: function, - line: line, - column: column + source: source ) } } @@ -119,7 +124,10 @@ public func sleep( line: Int = #line, column: Int = #column ) { - Compatibility.sleep(seconds: seconds, file: file, function: function, line: line, column: column) + Compatibility.sleep( + seconds: seconds, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } #else public extension Compatibility { @@ -135,14 +143,20 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) async { + await sleep( + seconds: seconds, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for helpers that already captured the original call site. + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) + static func sleep(seconds: Double, source: SourceContext) async { let duration = UInt64(seconds * 1_000_000_000) do { try await Task.sleep(nanoseconds: duration) - // // Fallback on earlier versions - // sleep(UInt32(seconds)) // give fetch from server time to finish } catch { - // do nothing but make debug log if we can. - debug("Sleep function was interrupted", level: .DEBUG, file: file, function: function, line: line, column: column) + Compatibility.debug("Sleep function was interrupted", level: .DEBUG, source: source) } } } @@ -157,7 +171,10 @@ public func sleep( line: Int = #line, column: Int = #column ) async { - await Compatibility.sleep(seconds: seconds, file: file, function: function, line: line, column: column) + await Compatibility.sleep( + seconds: seconds, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @@ -170,7 +187,10 @@ public extension Task where Success == Never, Failure == Never { line: Int = #line, column: Int = #column ) async { - await Compatibility.sleep(seconds: seconds, file: file, function: function, line: line, column: column) + await Compatibility.sleep( + seconds: seconds, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } } @@ -226,11 +246,19 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { + background( + closure, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for helpers that already captured the original call site. + static func background(_ closure: @Sendable @escaping () -> Void, source: SourceContext) { + _ = source #if arch(wasm32) closure() #else DispatchQueue.global().async { -// debug("Running background block", level: .DEBUG, file: file, function: function, line: line, column: column) closure() } #endif @@ -241,7 +269,6 @@ public extension Compatibility { @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) static func background(_ closure: @Sendable @escaping () async -> Void) { Task.detached(priority: .background) { -// debug("Running asynchronous background block", level: .DEBUG) await closure() } } @@ -274,7 +301,6 @@ public extension Compatibility { /// SwiftUI's `View.background`, makes the unqualified name ambiguous. Callers that already require /// iOS 13, macOS 10.15, tvOS 13, or watchOS 6 can instead use `Task.background`. public func background(_ closure: @Sendable @escaping () -> Void) { - // Keep this concise API independent of Swift concurrency so callers can deploy before iOS 13. Compatibility.background(closure) } @@ -354,6 +380,16 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { + main( + closure, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for helpers that already captured the original call site. + @MainActor + static func main(_ closure: @Sendable @MainActor @escaping () -> Void, source: SourceContext) { + _ = source closure() } } @@ -369,8 +405,10 @@ public func main( line: Int = #line, column: Int = #column ) { - // Forward through the shared implementation so the concise and qualified spellings remain equivalent. - Compatibility.main(closure, file: file, function: function, line: line, column: column) + Compatibility.main( + closure, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } #else public extension Compatibility { @@ -382,9 +420,17 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { + main( + closure, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + } + + /// Source-forwarding form for helpers that already captured the original call site. + static func main(_ closure: @Sendable @MainActor @escaping () -> Void, source: SourceContext) { + _ = source if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) { Task { @MainActor in -// debug("Running main-thread block", level: .DEBUG, file: file, function: function, line: line, column: column) closure() } } else { @@ -406,8 +452,10 @@ public func main( line: Int = #line, column: Int = #column ) { - // Keep this concise API available before Swift concurrency by forwarding to the dispatch-capable implementation. - Compatibility.main(closure, file: file, function: function, line: line, column: column) + Compatibility.main( + closure, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @@ -420,7 +468,10 @@ public extension Task where Success == Never, Failure == Never { line: Int = #line, column: Int = #column ) { - Compatibility.main(closure, file: file, function: function, line: line, column: column) + Compatibility.main( + closure, + source: SourceContext(file: file, function: function, line: line, column: column) + ) } } @@ -497,7 +548,6 @@ private let delayTests: [TestCase] = [ #endif // MARK: - Tests and Previews - #if compiler(>=5.9) @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension Compatibility { @@ -522,4 +572,4 @@ import SwiftUI TestsListView(tests: Compatibility.threadingTests) } #endif -#endif +#endif \ No newline at end of file From ca9a357a4fe4fe3f1b500b6b6484a9d360a87e67 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 12:43:49 -0400 Subject: [PATCH 044/107] Make structured debug formatting canonical --- Sources/Core/Debug.swift | 192 +++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 107 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index e05a9d7..ab385aa 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -36,6 +36,8 @@ public struct DebugFormatContext: Sendable { } } +/// Structured debug formatter. New formatting options can be added to `DebugFormatContext` +/// without expanding a positional closure signature. public typealias DebugFormatter = (DebugFormatContext) -> String public struct CompatibilityConfiguration: PropertyIterable { @@ -51,7 +53,7 @@ public struct CompatibilityConfiguration: PropertyIterable { /// Set this to a set of levels where we should include the context info. Defaults to `.important` so that `NOTICE` and `DEBUG` messages are less noisy and easier to see. Set this to `.none` to make `debug()` act exactly like `print()` at all levels. public var debugLevelsToIncludeContext = DebugLevels.important - /// Set whether timestamps should be included in debug messages. If you need to customize the format of timestamps, use the `debugFormat()` override. + /// Set whether timestamps should be included in debug messages. If you need to customize the format of timestamps, use the `debugFormatter` override. @available(*, deprecated, renamed: "debugLevelsToIncludeTimestamp", message: "Set `debugLevelsToIncludeTimestamp` instead.") public var debugIncludeTimestamp: Bool { get { @@ -62,40 +64,58 @@ public struct CompatibilityConfiguration: PropertyIterable { } } public var debugLevelsToIncludeTimestamp = DebugLevels.none - - /// Generates string with context. Set level to `.OFF` to just return the context without the message portion. - public var debugFormat = { (message: String, level: DebugLevel, isMainThread: Bool, emojiSupported: Bool, includeContext: Bool, includeTimestamp: Bool, file: String, function: String, line: Int, column: Int) -> String in - let message = "\(emojiSupported ? level.emoji : level.symbol) \(message)" + + /// Preferred structured formatter used by all normal debug output. + public var debugFormatter: DebugFormatter = { context in + let message = "\(context.emojiSupported ? context.level.emoji : context.level.symbol) \(context.message)" var timestamp = "" - if includeTimestamp { + if context.includeTimestamp { #if canImport(Foundation) timestamp = "\(Date.nowBackport.mysqlDateTime): " #else timestamp = "UNABLE TO GET TIMESTAMP WITHOUT Foundation.Date: " #endif } - if includeContext { - let threadInfo = isMainThread ? "" : "^" + if context.includeContext { + let threadInfo = context.isMainThread ? "" : "^" #if canImport(Foundation) - let simplerFile = URL(fileURLWithPath: file).lastPathComponent - let simplerFunction = function.replacingOccurrences(of: "__preview__", with: "_p_") + let simplerFile = URL(fileURLWithPath: context.source.file).lastPathComponent + let simplerFunction = context.source.function.replacingOccurrences(of: "__preview__", with: "_p_") #else - let simplerFile = "\(file)".components(separatedBy: "/").last ?? "UNABLE TO GET LAST PATH COMPONENT WITHOUT Foundation.URL" - let simplerFunction = function + let simplerFile = "\(context.source.file)".components(separatedBy: "/").last ?? "UNABLE TO GET LAST PATH COMPONENT WITHOUT Foundation.URL" + let simplerFunction = context.source.function #endif - return "\(timestamp)\(simplerFile)(\(line)) : \(simplerFunction)\(threadInfo)\(level == .OFF ? "" : "\n\(message)")" + return "\(timestamp)\(simplerFile)(\(context.source.line)) : \(simplerFunction)\(threadInfo)\(context.level == .OFF ? "" : "\n\(message)")" } else { return "\(timestamp)\(message)" } } - /// Preferred labeled alternative to the legacy positional `debugFormat` closure. - /// Assigning either property updates the same underlying formatter. - public var debugFormatter: DebugFormatter { + /// Legacy positional formatter retained for source compatibility. + /// + /// New code should use `debugFormatter`, whose labeled context can grow without changing + /// the closure's function type or forcing every formatter assignment to update. + @available(*, deprecated, message: "Use debugFormatter with DebugFormatContext instead.") + public var debugFormat: (String, DebugLevel, Bool, Bool, Bool, Bool, String, String, Int, Int) -> String { get { - let legacyFormatter = debugFormat - return { context in - legacyFormatter( + let formatter = debugFormatter + return { message, level, isMainThread, emojiSupported, includeContext, includeTimestamp, file, function, line, column in + formatter( + DebugFormatContext( + message: message, + level: level, + isMainThread: isMainThread, + emojiSupported: emojiSupported, + includeContext: includeContext, + includeTimestamp: includeTimestamp, + source: SourceContext(file: file, function: function, line: line, column: column) + ) + ) + } + } + set { + debugFormatter = { context in + newValue( context.message, context.level, context.isMainThread, @@ -109,36 +129,6 @@ public struct CompatibilityConfiguration: PropertyIterable { ) } } - set { - debugFormat = { - message, - level, - isMainThread, - emojiSupported, - includeContext, - includeTimestamp, - file, - function, - line, - column in - newValue( - DebugFormatContext( - message: message, - level: level, - isMainThread: isMainThread, - emojiSupported: emojiSupported, - includeContext: includeContext, - includeTimestamp: includeTimestamp, - source: SourceContext( - file: file, - function: function, - line: line, - column: column - ) - ) - ) - } - } } /// Function to handle how the debug messages are logged. Can change to have the messages logged to a file or a string. Default is to print to the console. @@ -327,18 +317,20 @@ public enum DebugLevel: Comparable, CustomStringConvertible, CaseIterable, Senda } } -/// Generates context string -@available(*, deprecated, message: "Use Compatibility.settings.debugFormat with the desired formatting options instead.") +/// Generates context string. +@available(*, deprecated, message: "Use Compatibility.settings.debugFormatter with DebugFormatContext instead.") public func debugContext(isMainThread: Bool, file: String, function: String, line: Int, column: Int) -> String { - // TODO: Convert this to the debugFormatter callsite for clarity - Compatibility.settings.debugFormat( - "", - .OFF, - isMainThread, - Compatibility.settings.debugEmojiSupported, - true, - Compatibility.settings.debugIncludeTimestamp, - file, function, line, column) + Compatibility.settings.debugFormatter( + DebugFormatContext( + message: "", + level: .OFF, + isMainThread: isMainThread, + emojiSupported: Compatibility.settings.debugEmojiSupported, + includeContext: true, + includeTimestamp: Compatibility.settings.debugLevelsToIncludeTimestamp.contains(.OFF), + source: SourceContext(file: file, function: function, line: line, column: column) + ) + ) } // MARK: - Debug @@ -355,34 +347,29 @@ public extension Compatibility { */ @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { - debug( + Compatibility.debug( message, level: level, source: SourceContext(file: file, function: function, line: line, column: column) ) } - /// Logs a message using an already-captured source location. This is the core forwarding path. + /// Canonical source-forwarding debug API for helpers that have already captured their caller. @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { #if hasFeature(Embedded) || !canImport(Foundation) - // Single-threaded or Foundation-less runtimes cannot provide Foundation.Thread identity. let isMainThread = true #else - let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing + let isMainThread = Thread.isMainThread #endif - // Embedded Swift already narrows `DebugMessage` to `String`, so no dynamic conversion is needed. #if !hasFeature(Embedded) - // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before - // forwarding to the shared String-based formatter just as Foundation-backed builds do. We have - // a backport for String(describing: message) so we don't need to worry about canImport(Foundation) for this line. - let message = String(describing: message) // convert to sendable item to avoid any thread issues. + let message = String(describing: message) #endif return debug(message, isMainThread: isMainThread, level: level, source: source) } - /// Caller-capturing compatibility wrapper for the lower-level formatter path. + /// Legacy lower-level caller-capturing formatter path retained for source compatibility. @discardableResult static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { debug( @@ -393,27 +380,26 @@ public extension Compatibility { ) } - /// Core debug implementation once source context and thread identity are known. This is the main function all conveniences should eventually delegate to. + /// Internal formatter implementation once source context and thread identity are known. @discardableResult - static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { - guard DebugLevel.isAtLeast(level) else { // check current debug level from settings - return "" // don't actually print + internal static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { + guard DebugLevel.isAtLeast(level) else { + return "" } - let debugMessage = Compatibility.settings.debugFormat( - message, - level, - isMainThread, - Compatibility.settings.debugEmojiSupported, - Compatibility.settings.debugLevelsToIncludeContext.contains(level), - Compatibility.settings.debugLevelsToIncludeTimestamp.contains(level), - source.file, source.function, source.line, source.column) - - // log message + let debugMessage = Compatibility.settings.debugFormatter( + DebugFormatContext( + message: message, + level: level, + isMainThread: isMainThread, + emojiSupported: Compatibility.settings.debugEmojiSupported, + includeContext: Compatibility.settings.debugLevelsToIncludeContext.contains(level), + includeTimestamp: Compatibility.settings.debugLevelsToIncludeTimestamp.contains(level), + source: source + ) + ) + Compatibility.settings.debugLog(debugMessage) - - // do this AFTER Printing so we can see what the message is in the console checkBreakpoint(level: level) - return debugMessage } } @@ -437,12 +423,6 @@ public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, fi ) } -/// Logs a message using an already-captured source location. -@discardableResult -public func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { - Compatibility.debug(message, level: level, source: source) -} - // MARK: Debug(error) // This is to provide debugging at calltime when creating errors. public extension Error { @@ -456,8 +436,8 @@ public extension Error { ) } - /// Logs this error using an already-captured source location and returns it for throwing. - func debug(level: DebugLevel = .defaultLevel, source: SourceContext) -> Self { + /// Package-internal source-forwarding form used after a helper has already captured its caller. + internal func debug(level: DebugLevel = .defaultLevel, source: SourceContext) -> Self { Compatibility.debug(self.localizedDescription, level: level, source: source) return self } @@ -508,20 +488,18 @@ public extension DebugLevel { try expect(Compatibility.settings.debugLevelDefault == .WARNING, "expected default debug level to be .WARNING but found \(Compatibility.settings.debugLevelDefault)") Compatibility.settings.debugEmojiSupported = false // testing symbols -// Compatibility.settings.debugIncludeTimestamp = true // test deprecated code Compatibility.settings.debugLevelsToIncludeTimestamp = .all // test timestamps - let defaultFormat = Compatibility.settings.debugFormat - Compatibility.settings.debugFormat = { (message: String, level: DebugLevel, isMainThread: Bool, emojiSupported: Bool, includeContext: Bool, includeTimestamp: Bool, file: String, function: String, line: Int, column: Int) -> String in - - let defaultOutput = defaultFormat(message, level, isMainThread, emojiSupported, includeContext, includeTimestamp, file, function, line, column) + let defaultFormatter = Compatibility.settings.debugFormatter + Compatibility.settings.debugFormatter = { context in + let defaultOutput = defaultFormatter(context) return """ -Message: \(message) -Level: \(level) -isMainThread: \(isMainThread) -emojiSupported: \(emojiSupported) -includeContext: \(includeContext) -includeTimestamp: \(includeTimestamp) -file: \(file) +Message: \(context.message) +Level: \(context.level) +isMainThread: \(context.isMainThread) +emojiSupported: \(context.emojiSupported) +includeContext: \(context.includeContext) +includeTimestamp: \(context.includeTimestamp) +file: \(context.source.file) Normal output: \(defaultOutput) """ } From 394a560b8a451431a1f5b18fa438180e305e8acd Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 12:45:14 -0400 Subject: [PATCH 045/107] Keep source forwarding on qualified network APIs --- Sources/Core/Network.swift | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/Sources/Core/Network.swift b/Sources/Core/Network.swift index 0aec63f..7d3ee62 100644 --- a/Sources/Core/Network.swift +++ b/Sources/Core/Network.swift @@ -176,37 +176,32 @@ extension Compatibility { @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public static func fetchURLData(urlString: String, postData: PostData? = nil, source: SourceContext) async throws -> Data { #if !hasFeature(Embedded) - debug("Fetching URL [\(urlString)]...", level: .NOTICE, source: source) + Compatibility.debug("Fetching URL [\(urlString)]...", level: .NOTICE, source: source) #else - debug("Fetching URL [\(urlString)]...", isMainThread: false, source: source) + Compatibility.debug("Fetching URL [\(urlString)]...", isMainThread: false, level: .NOTICE, source: source) #endif - // create the url with URL guard let url = URL(string: urlString) else { throw NetworkError.urlParsing(urlString: urlString).debug(level: .ERROR, source: source) } - // now create the URLRequest object using the url object var request = URLRequest(url: url) - // encode the postData if provided, otherwise set the method to GET. if let parameters = postData { - request.httpMethod = "POST" //set http method as POST + request.httpMethod = "POST" guard let data = postData?.queryEncoded else { throw NetworkError.postDataEncoding(parameters).debug(level: .ERROR, source: source) } request.httpBody = data } else { - request.httpMethod = "GET" //set http method as GET + request.httpMethod = "GET" } var data: Data var response: URLResponse - // create dataTask using the session object to send data to the server do { if #available(iOS 15, macOS 12, watchOS 8, tvOS 15, *) { (data, response) = try await URLSession.shared.data(for: request) } else { - // Fallback on earlier versions (data, response) = try await request.legacyData(for: URLSession.shared) } } catch { @@ -217,18 +212,16 @@ extension Compatibility { } } - // Check response status code exists (should nearly always pass) guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { let debugMessage = "No status code in HTTP response. Possibly offline?: \(String(describing: response))" #if !hasFeature(Embedded) - debug(debugMessage, level: .ERROR, source: source) + Compatibility.debug(debugMessage, level: .ERROR, source: source) #else - debug(debugMessage, isMainThread: false, level: .ERROR, source: source) + Compatibility.debug(debugMessage, isMainThread: false, level: .ERROR, source: source) #endif throw NetworkError.invalidResponse().debug(level: .ERROR, source: source) } - // check status code (should always be 200) guard statusCode == 200 else { throw NetworkError.invalidResponse(code: statusCode).debug(level: .ERROR, source: source) } @@ -252,7 +245,6 @@ extension Compatibility { public static func fetchURL(urlString: String, postData: PostData? = nil, encoding: String.Encoding = .utf8, source: SourceContext) async throws -> String { let data = try await fetchURLData(urlString: urlString, postData: postData, source: source) - // convert result data to string guard let responseString = String(data: data, encoding: encoding) else { #if compiler(>=5.9) throw NetworkError.dataError(data).debug(level: .ERROR, source: source) @@ -266,32 +258,22 @@ extension Compatibility { @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public func fetchURLData(urlString: String, postData: PostData? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) async throws -> Data { - try await fetchURLData( + try await Compatibility.fetchURLData( urlString: urlString, postData: postData, source: SourceContext(file: file, function: function, line: line, column: column) ) } -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency -public func fetchURLData(urlString: String, postData: PostData? = nil, source: SourceContext) async throws -> Data { - try await Compatibility.fetchURLData(urlString: urlString, postData: postData, source: source) -} - @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public func fetchURL(urlString: String, postData: PostData? = nil, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) async throws -> String { - try await fetchURL( + try await Compatibility.fetchURL( urlString: urlString, postData: postData, source: SourceContext(file: file, function: function, line: line, column: column) ) } -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency -public func fetchURL(urlString: String, postData: PostData? = nil, source: SourceContext) async throws -> String { - try await Compatibility.fetchURL(urlString: urlString, postData: postData, source: source) -} - @available(iOS 15, macOS 10.15, tvOS 13, watchOS 6, *) public extension URL { /// download data asynchronously and return the data or nil if there is a failure From 5c207f9461ea302bf4af53d4f38c87d497bc41cd Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 13:24:20 -0400 Subject: [PATCH 046/107] Restored stripped comments! --- .../xcdebugger/Breakpoints_v2.xcbkptlist | 120 ------------------ Sources/Core/Debug.swift | 18 ++- Sources/Core/Network.swift | 12 +- Sources/Foundation/Threading.swift | 14 +- 4 files changed, 35 insertions(+), 129 deletions(-) diff --git a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index eb16def..5f1efac 100644 --- a/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Development/Compatibility.xcodeproj/xcuserdata/ben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -18,36 +18,6 @@ endingLineNumber = "319" landmarkName = "pretty" landmarkType = "24"> - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index ab385aa..e81ac4e 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -358,13 +358,18 @@ public extension Compatibility { @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { #if hasFeature(Embedded) || !canImport(Foundation) + // Single-threaded or Foundation-less runtimes cannot provide Foundation.Thread identity. let isMainThread = true #else - let isMainThread = Thread.isMainThread + let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing #endif + // Embedded Swift already narrows `DebugMessage` to `String`, so no dynamic conversion is needed. #if !hasFeature(Embedded) - let message = String(describing: message) + // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before + // forwarding to the shared String-based formatter just as Foundation-backed builds do. We have + // a backport for String(describing: message) so we don't need to worry about canImport(Foundation) for this line. + let message = String(describing: message) // convert to sendable item to avoid any thread issues. #endif return debug(message, isMainThread: isMainThread, level: level, source: source) } @@ -383,8 +388,8 @@ public extension Compatibility { /// Internal formatter implementation once source context and thread identity are known. @discardableResult internal static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { - guard DebugLevel.isAtLeast(level) else { - return "" + guard DebugLevel.isAtLeast(level) else { // check current debug level from settings + return "" // don't actually print } let debugMessage = Compatibility.settings.debugFormatter( DebugFormatContext( @@ -396,10 +401,14 @@ public extension Compatibility { includeTimestamp: Compatibility.settings.debugLevelsToIncludeTimestamp.contains(level), source: source ) + // possible future hook to log message ) Compatibility.settings.debugLog(debugMessage) + + // do this AFTER Printing so we can see what the message is in the console checkBreakpoint(level: level) + return debugMessage } } @@ -488,6 +497,7 @@ public extension DebugLevel { try expect(Compatibility.settings.debugLevelDefault == .WARNING, "expected default debug level to be .WARNING but found \(Compatibility.settings.debugLevelDefault)") Compatibility.settings.debugEmojiSupported = false // testing symbols + // Compatibility.settings.debugIncludeTimestamp = true // test deprecated code Compatibility.settings.debugLevelsToIncludeTimestamp = .all // test timestamps let defaultFormatter = Compatibility.settings.debugFormatter Compatibility.settings.debugFormatter = { context in diff --git a/Sources/Core/Network.swift b/Sources/Core/Network.swift index 7d3ee62..f69a75b 100644 --- a/Sources/Core/Network.swift +++ b/Sources/Core/Network.swift @@ -180,28 +180,33 @@ extension Compatibility { #else Compatibility.debug("Fetching URL [\(urlString)]...", isMainThread: false, level: .NOTICE, source: source) #endif + // create the url with URL guard let url = URL(string: urlString) else { throw NetworkError.urlParsing(urlString: urlString).debug(level: .ERROR, source: source) } + // now create the URLRequest object using the url object var request = URLRequest(url: url) + // encode the postData if provided, otherwise set the method to GET. if let parameters = postData { - request.httpMethod = "POST" + request.httpMethod = "POST" //set http method as POST guard let data = postData?.queryEncoded else { throw NetworkError.postDataEncoding(parameters).debug(level: .ERROR, source: source) } request.httpBody = data } else { - request.httpMethod = "GET" + request.httpMethod = "GET" //set http method as GET } var data: Data var response: URLResponse + // create dataTask using the session object to send data to the server do { if #available(iOS 15, macOS 12, watchOS 8, tvOS 15, *) { (data, response) = try await URLSession.shared.data(for: request) } else { + // Fallback on earlier versions (data, response) = try await request.legacyData(for: URLSession.shared) } } catch { @@ -212,6 +217,7 @@ extension Compatibility { } } + // Check response status code exists (should nearly always pass) guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { let debugMessage = "No status code in HTTP response. Possibly offline?: \(String(describing: response))" #if !hasFeature(Embedded) @@ -222,6 +228,7 @@ extension Compatibility { throw NetworkError.invalidResponse().debug(level: .ERROR, source: source) } + // check status code (should always be 200) guard statusCode == 200 else { throw NetworkError.invalidResponse(code: statusCode).debug(level: .ERROR, source: source) } @@ -245,6 +252,7 @@ extension Compatibility { public static func fetchURL(urlString: String, postData: PostData? = nil, encoding: String.Encoding = .utf8, source: SourceContext) async throws -> String { let data = try await fetchURLData(urlString: urlString, postData: postData, source: source) + // convert result data to string guard let responseString = String(data: data, encoding: encoding) else { #if compiler(>=5.9) throw NetworkError.dataError(data).debug(level: .ERROR, source: source) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index 7d15850..5a2a9e7 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -155,7 +155,10 @@ public extension Compatibility { let duration = UInt64(seconds * 1_000_000_000) do { try await Task.sleep(nanoseconds: duration) + // Potential fallback for earlier versions/backport? Likely unnecessary/unusable due to async but may be useful for a synchronous fallback?: + // sleep(UInt32(seconds)) // give fetch from server time to finish } catch { + // do nothing but make debug log if we can. Compatibility.debug("Sleep function was interrupted", level: .DEBUG, source: source) } } @@ -259,6 +262,7 @@ public extension Compatibility { closure() #else DispatchQueue.global().async { +// Compatibility.debug("Running background block", level: .DEBUG, source: source) closure() } #endif @@ -267,8 +271,9 @@ public extension Compatibility { #if !arch(wasm32) /// Starts nonthrowing asynchronous work in a detached background task. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) - static func background(_ closure: @Sendable @escaping () async -> Void) { + static func background(_ closure: @Sendable @escaping () async -> Void) { // TODO: Should this capture SourceContext for debugging? Task.detached(priority: .background) { +// Compatibility.debug("Running asynchronous background block", level: .DEBUG, source: SourceContext(file: file, function: function, line: line, column: column)) await closure() } } @@ -299,8 +304,9 @@ public extension Compatibility { /// /// Use ``Compatibility/background(_:file:function:line:column:)`` when another API, such as /// SwiftUI's `View.background`, makes the unqualified name ambiguous. Callers that already require -/// iOS 13, macOS 10.15, tvOS 13, or watchOS 6 can instead use `Task.background`. +/// iOS 13, macOS 10.15, tvOS 13, or watchOS 6 should instead use `Task.background`. public func background(_ closure: @Sendable @escaping () -> Void) { + // Keep this concise API independent of Swift concurrency so callers can deploy before iOS 13. Compatibility.background(closure) } @@ -431,6 +437,7 @@ public extension Compatibility { _ = source if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) { Task { @MainActor in + // debug("Running main-thread block", level: .DEBUG, file: file, function: function, line: line, column: column) closure() } } else { @@ -452,6 +459,7 @@ public func main( line: Int = #line, column: Int = #column ) { + // Keep this concise API available before Swift concurrency by forwarding to the dispatch-capable implementation. Compatibility.main( closure, source: SourceContext(file: file, function: function, line: line, column: column) @@ -572,4 +580,4 @@ import SwiftUI TestsListView(tests: Compatibility.threadingTests) } #endif -#endif \ No newline at end of file +#endif From 9a8bfa34649a7201dafdb24bfe6615cc46a1f43b Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 13:30:13 -0400 Subject: [PATCH 047/107] Build project and build error fixes Made the recommended changes to the build settings and added necessary Compatibility.debug calls to sites where source is forwarded. --- .../Compatibility.xcodeproj/project.pbxproj | 17 ----------------- Sources/Core/Application.swift | 4 ++-- Sources/Core/Test.swift | 4 ++-- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 16887bc..ba8b258 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -31,13 +31,6 @@ remoteGlobalIDString = B5E5FC502C386144004F2009; remoteInfo = CompatibilityTest; }; - B60000032F00000100000001 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = B50E7B632C385BD8002D3F53 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B5E5FC502C386144004F2009; - remoteInfo = CompatibilityTest; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -224,7 +217,6 @@ buildRules = ( ); dependencies = ( - B60000042F00000100000001 /* PBXTargetDependency */, ); name = CompatibilityTests; packageProductDependencies = ( @@ -383,11 +375,6 @@ target = B5E5FC502C386144004F2009 /* CompatibilityTest */; targetProxy = B60000012F00000100000001 /* PBXContainerItemProxy */; }; - B60000042F00000100000001 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B5E5FC502C386144004F2009 /* CompatibilityTest */; - targetProxy = B60000032F00000100000001 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -636,7 +623,6 @@ B594CFAD2DB0B838001E8658 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3QPV894C33; @@ -650,7 +636,6 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; }; @@ -659,7 +644,6 @@ B594CFAE2DB0B838001E8658 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3QPV894C33; @@ -673,7 +657,6 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; VALIDATE_PRODUCT = YES; diff --git a/Sources/Core/Application.swift b/Sources/Core/Application.swift index 361cfdd..e6fa1aa 100644 --- a/Sources/Core/Application.swift +++ b/Sources/Core/Application.swift @@ -171,7 +171,7 @@ public class Application: ObservableObject { // The private initializer preserve // Prevent late mutation once asynchronous support reporting can begin reading the global registry. Build.finishModuleRegistration() // Calling Application.main is what initializes the application and does the tracking. This really should only be called once. TODO: Should we check to make sure this isn't called twice?? Application.main singleton should only be inited once. - debug("Application Tracking: \(Application.main.appName)", level: .NOTICE, source: source) // Initialize persisted version state synchronously before detached reporting begins. + Compatibility.debug("Application Tracking: \(Application.main.appName)", level: .NOTICE, source: source) // Initialize persisted version state synchronously before detached reporting begins. // Defer the complete report so modules may calculate or fetch metadata without blocking application launch. #if arch(wasm32) // Full-runtime WebAssembly supports unstructured tasks, but the detached @@ -184,7 +184,7 @@ public class Application: ObservableObject { // The private initializer preserve Task.background { let description = await Application.main.loadDetailedDescription() Task.main { - debug("Application Detailed Tracking:\n\(description)", level: .NOTICE, source: source) + Compatibility.debug("Application Detailed Tracking:\n\(description)", level: .NOTICE, source: source) } } #endif diff --git a/Sources/Core/Test.swift b/Sources/Core/Test.swift index d40f54b..a9b0277 100644 --- a/Sources/Core/Test.swift +++ b/Sources/Core/Test.swift @@ -87,7 +87,7 @@ public func expect(_ condition: Bool, _ debugString: String? = nil, file: String public func expect(_ condition: Bool, _ debugString: String? = nil, source: SourceContext) throws { guard condition else { let message = debugString ?? "Expectation failed" - debug(message, level: .ERROR, source: source) + Compatibility.debug(message, level: .ERROR, source: source) throw TestFailure(message, source: source) } } @@ -578,4 +578,4 @@ import SwiftUI TestsListView(tests: Compatibility.threadingTests + Int.tests) } #endif -#endif \ No newline at end of file +#endif From 0fb49f7e44bf9eb9f1048c9da1f4fc527c56a437 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 13:53:21 -0400 Subject: [PATCH 048/107] Consolidate debug to one source-forwarding implementation --- Sources/Core/Debug.swift | 65 +++++++++++----------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/Sources/Core/Debug.swift b/Sources/Core/Debug.swift index e81ac4e..e1c53e2 100644 --- a/Sources/Core/Debug.swift +++ b/Sources/Core/Debug.swift @@ -335,65 +335,36 @@ public func debugContext(isMainThread: Bool, file: String, function: String, lin // MARK: - Debug public extension Compatibility { - /** - Debug helper for printing info to screen including file and line info of call site. Also can provide a log level for use in loggers or for globally turning on/off logging. (Modify DebugLevel.currentLevel to set level to output. When launching app, set this to DebugLevel.OFF for release builds. - - - Parameter message: The message to report. - - Parameter level: The logging level to use. - - Parameter file: For bubbling down the #file name from a call site. - - Parameter function: For bubbling down the #function name from a call site. - - Parameter line: For bubbling down the #line number from a call site. - - Parameter column: For bubbling down the #column number from a call site. (Not used currently but here for completeness). - */ - @discardableResult - static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { - Compatibility.debug( - message, - level: level, - source: SourceContext(file: file, function: function, line: line, column: column) - ) - } - - /// Canonical source-forwarding debug API for helpers that have already captured their caller. + /// Canonical debug implementation for APIs that have already captured their caller's source context. + /// + /// Normal application code should generally use the unqualified ``debug(_:level:file:function:line:column:)`` + /// convenience below. Helper APIs that intentionally preserve their own caller's source location can capture + /// a ``SourceContext`` once and forward it here. @discardableResult static func debug(_ message: DebugMessage, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { + guard DebugLevel.isAtLeast(level) else { // check current debug level from settings + return "" // don't actually print + } + #if hasFeature(Embedded) || !canImport(Foundation) - // Single-threaded or Foundation-less runtimes cannot provide Foundation.Thread identity. + // Embedded/Foundation-less runtimes do not expose Foundation.Thread identity. Their supported + // execution model is treated as main-thread work rather than accepting a manually supplied override. let isMainThread = true #else - let isMainThread = Thread.isMainThread // capture before we switch to main thread for printing + let isMainThread = Thread.isMainThread // capture before any logger/formatter implementation can switch threads #endif +#if hasFeature(Embedded) // Embedded Swift already narrows `DebugMessage` to `String`, so no dynamic conversion is needed. -#if !hasFeature(Embedded) - // Full Swift runtimes without Foundation still allow `DebugMessage == Any`; stringify before - // forwarding to the shared String-based formatter just as Foundation-backed builds do. We have - // a backport for String(describing: message) so we don't need to worry about canImport(Foundation) for this line. - let message = String(describing: message) // convert to sendable item to avoid any thread issues. + let messageString = message +#else + // Full Swift runtimes allow `DebugMessage == Any`; stringify exactly once before formatting/logging. + let messageString = String(describing: message) #endif - return debug(message, isMainThread: isMainThread, level: level, source: source) - } - - /// Legacy lower-level caller-capturing formatter path retained for source compatibility. - @discardableResult - static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) -> String { - debug( - message, - isMainThread: isMainThread, - level: level, - source: SourceContext(file: file, function: function, line: line, column: column) - ) - } - /// Internal formatter implementation once source context and thread identity are known. - @discardableResult - internal static func debug(_ message: String, isMainThread: Bool, level: DebugLevel = .defaultLevel, source: SourceContext) -> String { - guard DebugLevel.isAtLeast(level) else { // check current debug level from settings - return "" // don't actually print - } let debugMessage = Compatibility.settings.debugFormatter( DebugFormatContext( - message: message, + message: messageString, level: level, isMainThread: isMainThread, emojiSupported: Compatibility.settings.debugEmojiSupported, From 545251fa754b92d237c1338350c31f732a5fbd6d Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 13:54:26 -0400 Subject: [PATCH 049/107] Use canonical debug source forwarding in networking --- Sources/Core/Network.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Sources/Core/Network.swift b/Sources/Core/Network.swift index f69a75b..f6b329e 100644 --- a/Sources/Core/Network.swift +++ b/Sources/Core/Network.swift @@ -175,11 +175,7 @@ extension Compatibility { /// Source-forwarding form for APIs that have already captured their caller's location. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // for concurrency public static func fetchURLData(urlString: String, postData: PostData? = nil, source: SourceContext) async throws -> Data { -#if !hasFeature(Embedded) Compatibility.debug("Fetching URL [\(urlString)]...", level: .NOTICE, source: source) -#else - Compatibility.debug("Fetching URL [\(urlString)]...", isMainThread: false, level: .NOTICE, source: source) -#endif // create the url with URL guard let url = URL(string: urlString) else { throw NetworkError.urlParsing(urlString: urlString).debug(level: .ERROR, source: source) @@ -220,11 +216,7 @@ extension Compatibility { // Check response status code exists (should nearly always pass) guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { let debugMessage = "No status code in HTTP response. Possibly offline?: \(String(describing: response))" -#if !hasFeature(Embedded) Compatibility.debug(debugMessage, level: .ERROR, source: source) -#else - Compatibility.debug(debugMessage, isMainThread: false, level: .ERROR, source: source) -#endif throw NetworkError.invalidResponse().debug(level: .ERROR, source: source) } From 516945c153053f6c4bb65ed31e089d6623074d15 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 13:56:09 -0400 Subject: [PATCH 050/107] Remove fake background and main fallbacks --- Sources/Foundation/Threading.swift | 100 +++++------------------------ 1 file changed, 16 insertions(+), 84 deletions(-) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index 5a2a9e7..66ac46d 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -108,7 +108,6 @@ public extension Compatibility { // browser hosts must schedule a JavaScript timer while WASI hosts use host-specific clocks. Compatibility.debug( "Sleep is unavailable on this WebAssembly runtime; no delay occurred. Prefer an asynchronous host timer for browser or WASI code.", - isMainThread: true, level: .WARNING, source: source ) @@ -237,11 +236,12 @@ private let sleepTests: [TestCase] = [ // MARK: - Background Tasks +// A background helper must actually move work away from the caller. Do not provide a WASM/Embedded +// syntax-only fallback that executes synchronously: that masks threading assumptions and can turn +// otherwise-correct code into blocking work. These APIs are therefore unavailable on WASM/Embedded. +#if !arch(wasm32) && !hasFeature(Embedded) public extension Compatibility { /// Runs potentially long synchronous work away from the main queue when threads are available. - /// - /// WebAssembly currently has no universally available Dispatch fallback, so its synchronous - /// implementation executes immediately even though actor and task language features exist. static func background( _ closure: @Sendable @escaping () -> Void, file: String = #file, @@ -249,26 +249,12 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { - background( - closure, - source: SourceContext(file: file, function: function, line: line, column: column) - ) - } - - /// Source-forwarding form for helpers that already captured the original call site. - static func background(_ closure: @Sendable @escaping () -> Void, source: SourceContext) { - _ = source -#if arch(wasm32) - closure() -#else DispatchQueue.global().async { -// Compatibility.debug("Running background block", level: .DEBUG, source: source) +// Compatibility.debug("Running background block", level: .DEBUG, source: SourceContext(file: file, function: function, line: line, column: column)) closure() } -#endif } -#if !arch(wasm32) /// Starts nonthrowing asynchronous work in a detached background task. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) static func background(_ closure: @Sendable @escaping () async -> Void) { // TODO: Should this capture SourceContext for debugging? @@ -286,7 +272,8 @@ public extension Compatibility { #if canImport(Foundation) return try await Task.detached(priority: .background, operation: closure).value #else - return try await closure() + // A full Swift runtime can still provide detached tasks without Foundation. + return try await Task.detached(priority: .background, operation: closure).value #endif } @@ -297,7 +284,6 @@ public extension Compatibility { ) async -> ReturnType? { await Task.detached(priority: .background, operation: closure).value } -#endif } /// Runs synchronous work away from the main queue using the concise, deployment-compatible spelling. @@ -310,7 +296,6 @@ public func background(_ closure: @Sendable @escaping () -> Void) { Compatibility.background(closure) } -#if !arch(wasm32) /// Legacy unqualified asynchronous background helper retained for source compatibility. @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @available(*, deprecated, renamed: "Task.background", message: "Use Compatibility.background or Task.background instead.") @@ -375,48 +360,10 @@ private let backgroundTests: [TestCase] = [ // MARK: - Main -#if arch(wasm32) -public extension Compatibility { - /// Executes main-actor work immediately because this WebAssembly compatibility path is single threaded. - @MainActor - static func main( - _ closure: @Sendable @MainActor @escaping () -> Void, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column - ) { - main( - closure, - source: SourceContext(file: file, function: function, line: line, column: column) - ) - } - - /// Source-forwarding form for helpers that already captured the original call site. - @MainActor - static func main(_ closure: @Sendable @MainActor @escaping () -> Void, source: SourceContext) { - _ = source - closure() - } -} - -/// Runs work on the main actor using the concise spelling on WebAssembly. -/// -/// Use ``Compatibility/main(_:file:function:line:column:)`` when an unqualified `main` name is ambiguous. -@MainActor -public func main( - _ closure: @Sendable @MainActor @escaping () -> Void, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column -) { - Compatibility.main( - closure, - source: SourceContext(file: file, function: function, line: line, column: column) - ) -} -#else +// As with background work, do not claim a main-dispatch helper exists on WASM/Embedded by simply +// executing the closure inline. Code that requires this scheduling API should fail to compile there +// until that runtime has a real implementation with the advertised semantics. +#if !arch(wasm32) && !hasFeature(Embedded) public extension Compatibility { /// Schedules work on the main actor using concurrency or the older dispatch fallback. static func main( @@ -426,15 +373,6 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { - main( - closure, - source: SourceContext(file: file, function: function, line: line, column: column) - ) - } - - /// Source-forwarding form for helpers that already captured the original call site. - static func main(_ closure: @Sendable @MainActor @escaping () -> Void, source: SourceContext) { - _ = source if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) { Task { @MainActor in // debug("Running main-thread block", level: .DEBUG, file: file, function: function, line: line, column: column) @@ -460,10 +398,7 @@ public func main( column: Int = #column ) { // Keep this concise API available before Swift concurrency by forwarding to the dispatch-capable implementation. - Compatibility.main( - closure, - source: SourceContext(file: file, function: function, line: line, column: column) - ) + Compatibility.main(closure, file: file, function: function, line: line, column: column) } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @@ -476,10 +411,7 @@ public extension Task where Success == Never, Failure == Never { line: Int = #line, column: Int = #column ) { - Compatibility.main( - closure, - source: SourceContext(file: file, function: function, line: line, column: column) - ) + Compatibility.main(closure, file: file, function: function, line: line, column: column) } } @@ -562,9 +494,9 @@ public extension Compatibility { /// Reusable threading checks grouped without adding another public namespace. @MainActor static let threadingTests: [TestCase] = { -#if arch(wasm32) - // Generic WebAssembly hosts do not provide the timing guarantees these - // delay and dispatch tests assert, so retain the catalog as an empty API. +#if arch(wasm32) || hasFeature(Embedded) + // WASM/Embedded intentionally omit background/main helpers rather than providing synchronous + // semantic fallbacks, and generic WASM hosts do not provide the timing guarantees tested here. return [] #else return sleepTests + backgroundTests + mainTests + delayTests From 0ce4eddd4bf591b1b370a51d002a31de515092ec Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 13:59:24 -0400 Subject: [PATCH 051/107] Restore hosted unit test target configuration --- .../Compatibility.xcodeproj/project.pbxproj | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index ba8b258..1770ad2 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -31,6 +31,13 @@ remoteGlobalIDString = B5E5FC502C386144004F2009; remoteInfo = CompatibilityTest; }; + B60000032F00000100000001 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B50E7B632C385BD8002D3F53 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B5E5FC502C386144004F2009; + remoteInfo = CompatibilityTest; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -45,7 +52,7 @@ B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompatibilityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B5E5FC3A2C3860EC004F2009 /* MyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyApp.swift; sourceTree = ""; }; B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; }; - B5E5FC442C3860EC004F2009 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; + B5E5FC442C3860EC004F2009 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; B5E5FC452C3860EC004F2009 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; B5E5FC512C386144004F2009 /* CompatibilityTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompatibilityTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; B5E5FC822C3863B9004F2009 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -217,6 +224,7 @@ buildRules = ( ); dependencies = ( + B60000042F00000100000001 /* PBXTargetDependency */, ); name = CompatibilityTests; packageProductDependencies = ( @@ -362,7 +370,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */, + B5209EE32C431CF800BFA30B /* CompatibilityDemoView.swift in Sources */, B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -375,6 +383,11 @@ target = B5E5FC502C386144004F2009 /* CompatibilityTest */; targetProxy = B60000012F00000100000001 /* PBXContainerItemProxy */; }; + B60000042F00000100000001 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B5E5FC502C386144004F2009 /* CompatibilityTest */; + targetProxy = B60000032F00000100000001 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -623,6 +636,7 @@ B594CFAD2DB0B838001E8658 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3QPV894C33; @@ -636,6 +650,7 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; }; @@ -644,6 +659,7 @@ B594CFAE2DB0B838001E8658 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3QPV894C33; @@ -657,6 +673,7 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; VALIDATE_PRODUCT = YES; From fbe65ea5a5c1ffd8206c7078199aa8e77cf0bb20 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 14:02:58 -0400 Subject: [PATCH 052/107] Correct restored Xcode project references --- Development/Compatibility.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 1770ad2..16887bc 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -52,7 +52,7 @@ B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompatibilityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B5E5FC3A2C3860EC004F2009 /* MyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyApp.swift; sourceTree = ""; }; B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; }; - B5E5FC442C3860EC004F2009 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; + B5E5FC442C3860EC004F2009 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; B5E5FC452C3860EC004F2009 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; B5E5FC512C386144004F2009 /* CompatibilityTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompatibilityTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; B5E5FC822C3863B9004F2009 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -370,7 +370,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B5209EE32C431CF800BFA30B /* CompatibilityDemoView.swift in Sources */, + B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */, B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; From ee508f4af52a1767a5a77a190d269fe11ec214ac Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 14:41:20 -0400 Subject: [PATCH 053/107] fixes and comments --- CHANGELOG.md | 1 + Sources/Core/Shell.swift | 6 +++--- Sources/Foundation/Threading.swift | 4 ---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6049f8e..89701ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Unified `TestCase.execute()` and live test execution through one lifecycle imple Added source-aware test failures, labeled debug-format context, and source-context debugging conveniences while preserving existing debug-format call sites. Made debug tests run exclusively and restore process-global debug settings with `defer`, including when an expectation throws. Expanded contributor guidance for short, staged, maintainer-reviewed coding workflows. +Consolidated debug and main and background code and removed support for WASM/Embedded since those were dangerous masks. ## v1.18.2 2026-07-23 Fixed Swift Package Index build errors and warnings across SwiftUI and WebAssembly targets. diff --git a/Sources/Core/Shell.swift b/Sources/Core/Shell.swift index f43cbed..9687958 100644 --- a/Sources/Core/Shell.swift +++ b/Sources/Core/Shell.swift @@ -20,9 +20,9 @@ public extension Compatibility { /// /// - Note: This is only available in macOS and **not** macCatalyst or any other platform. @discardableResult // Add to suppress warnings when you don't want/need the result - static func safeShell(_ command: String, shell: String = "/bin/zsh", logCommand: Bool = true) throws -> String { + static func safeShell(_ command: String, shell: String = "/bin/zsh", logCommand: Bool = true, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) throws -> String { if logCommand { - debug("Attempting to run shell command:\n\(command)", level: .NOTICE) + Compatibility.debug("Attempting to run shell command:\n\(command)", level: .NOTICE, source: SourceContext(file: file, function: function, line: line, column: column)) } let task = Process() @@ -38,7 +38,7 @@ public extension Compatibility { let data = pipe.fileHandleForReading.readDataToEndOfFile() guard let output = String(data: data, encoding: .utf8) else { - throw CustomError("Failed to parse shell output as UTF-8", level: .ERROR) // this should never happen + throw CustomError("Failed to parse shell output as UTF-8", level: .ERROR, file: file, function: function, line: line, column: column) // this should never happen } return output diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index 66ac46d..cdaef8e 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -269,12 +269,8 @@ public extension Compatibility { static func background( _ closure: @Sendable @escaping () async throws -> ReturnType ) async throws -> ReturnType { -#if canImport(Foundation) - return try await Task.detached(priority: .background, operation: closure).value -#else // A full Swift runtime can still provide detached tasks without Foundation. return try await Task.detached(priority: .background, operation: closure).value -#endif } /// Runs nonthrowing asynchronous work that returns an optional value. From 1f0f541c095da25a073cac01bdc7afa7c2109236 Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 14:45:21 -0400 Subject: [PATCH 054/107] Make parameter discovery control independent of adapter import --- .../ModuleTestEntryTests.swift | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Development/CompatibilityTests/ModuleTestEntryTests.swift b/Development/CompatibilityTests/ModuleTestEntryTests.swift index b24a48a..d9783d0 100644 --- a/Development/CompatibilityTests/ModuleTestEntryTests.swift +++ b/Development/CompatibilityTests/ModuleTestEntryTests.swift @@ -5,7 +5,21 @@ // Exercises the reusable CompatibilityTesting adapter through Swift Testing. // -#if compiler(>=5.9) && canImport(Compatibility) && canImport(CompatibilityTesting) && canImport(Testing) +#if compiler(>=5.9) && canImport(Testing) +import Testing + +/// Static control kept independent of CompatibilityTesting so Xcode test discovery can be +/// verified even when the adapter product itself is misconfigured. +@Suite("Parameterized Test Discovery") +struct ParameterDisplayTests { + @Test("Parameter display test", arguments: [1, 2, 3]) + func parameterDisplayTest(value: Int) { + #expect((1...3).contains(value)) + } +} +#endif + +#if compiler(>=5.9) && canImport(Compatibility) && canImport(Testing) import Compatibility import CompatibilityTesting import Testing @@ -27,11 +41,5 @@ struct ModuleTestEntryTests { func moduleTest(entry: ModuleTestEntry) async throws { try await entry.execute() } - - /// Simple static control used to verify that Xcode discovers and expands parameterized cases. - @Test("Parameter display test", arguments: [1, 2, 3]) - func parameterDisplayTest(value: Int) { - #expect((1...3).contains(value)) - } } #endif From 78f9bc911c56eae2c1b6f962c6c4ac433fffc5bb Mon Sep 17 00:00:00 2001 From: kudit Date: Wed, 12 Aug 2026 14:46:59 -0400 Subject: [PATCH 055/107] Remove no-op WASM and Embedded timing fallbacks --- Sources/Foundation/Threading.swift | 63 +++++------------------------- 1 file changed, 9 insertions(+), 54 deletions(-) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index cdaef8e..a19e725 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -83,52 +83,10 @@ private func timeTolerance(start: TimeInterval, end: TimeInterval, expected: Tim // MARK: - Sleep -#if arch(wasm32) -public extension Compatibility { - /// WebAssembly compatibility spelling for sleep. - /// - /// A generic WebAssembly host does not guarantee a suspending timer, so this returns immediately - /// while preserving cross-platform source compatibility for code that does not require a delay. - static func sleep( - seconds: Double, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column - ) { - sleep( - seconds: seconds, - source: SourceContext(file: file, function: function, line: line, column: column) - ) - } - - /// Source-forwarding form for helpers that already captured the original call site. - static func sleep(seconds: Double, source: SourceContext) { - // This gate describes the missing timer primitive, not missing Swift concurrency support: - // browser hosts must schedule a JavaScript timer while WASI hosts use host-specific clocks. - Compatibility.debug( - "Sleep is unavailable on this WebAssembly runtime; no delay occurred. Prefer an asynchronous host timer for browser or WASI code.", - level: .WARNING, - source: source - ) - } -} - -/// Legacy WebAssembly sleep spelling retained as an immediate compatibility fallback. -@available(*, deprecated, renamed: "Compatibility.sleep(seconds:)", message: "Use Compatibility.sleep(seconds:) instead.") -public func sleep( - seconds: Double, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column -) { - Compatibility.sleep( - seconds: seconds, - source: SourceContext(file: file, function: function, line: line, column: column) - ) -} -#else +// A sleep helper must actually suspend for the requested duration. Generic WASM hosts and Embedded +// Swift do not provide the timer guarantees required by this API, so do not expose a no-op spelling +// that silently returns immediately and masks timing assumptions in portable code. +#if !arch(wasm32) && !hasFeature(Embedded) public extension Compatibility { /// Suspends the current asynchronous task for a number of seconds. /// @@ -432,13 +390,12 @@ private let mainTests: [TestCase] = [ // MARK: - Delay +// A delay helper must actually postpone execution. Generic WASM hosts and Embedded Swift do not +// provide the timing guarantees required here, so omit the API instead of executing immediately. +#if !arch(wasm32) && !hasFeature(Embedded) public extension Compatibility { /// Runs a closure after a delay, using dispatch when Swift concurrency is unavailable. static func delay(_ seconds: Double, closure: @Sendable @escaping () -> Void) { -#if arch(wasm32) - // WebAssembly has no blocking or asynchronous delay fallback in this compatibility layer. - closure() -#else if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) { Task { await Task.sleep(seconds: seconds) @@ -447,7 +404,6 @@ public extension Compatibility { } else { DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds, execute: closure) } -#endif } } @@ -457,7 +413,6 @@ public func delay(_ seconds: Double, closure: @Sendable @escaping () -> Void) { Compatibility.delay(seconds, closure: closure) } -#if !arch(wasm32) @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) public extension Task where Success == Never, Failure == Never { /// Preferred concise spelling for Compatibility's delayed closure helper. @@ -491,8 +446,8 @@ public extension Compatibility { @MainActor static let threadingTests: [TestCase] = { #if arch(wasm32) || hasFeature(Embedded) - // WASM/Embedded intentionally omit background/main helpers rather than providing synchronous - // semantic fallbacks, and generic WASM hosts do not provide the timing guarantees tested here. + // WASM/Embedded intentionally omit sleep, background, main, and delay rather than providing + // semantic no-op fallbacks, so there are no threading/timing tests to register there. return [] #else return sleepTests + backgroundTests + mainTests + delayTests From 00d66014f3cb564ee50aa9af3964014d70c8436e Mon Sep 17 00:00:00 2001 From: kudit Date: Thu, 13 Aug 2026 01:29:21 -0400 Subject: [PATCH 056/107] Make CompatibilityTest scheme explicitly run unit and UI tests --- .../xcschemes/CompatibilityTest.xcscheme | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 048814b..20e75de 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -55,13 +55,32 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + + + + + + + Date: Thu, 13 Aug 2026 17:10:47 -0400 Subject: [PATCH 057/107] Restore CompatibilityTest explicit test plan --- .../xcschemes/CompatibilityTest.xcscheme | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 20e75de..048814b 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -55,32 +55,13 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + Date: Thu, 13 Aug 2026 17:11:14 -0400 Subject: [PATCH 058/107] Use native play button styling for test rows --- Sources/UI/TestUI.swift | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/Sources/UI/TestUI.swift b/Sources/UI/TestUI.swift index 65f5dae..3b9a11d 100644 --- a/Sources/UI/TestUI.swift +++ b/Sources/UI/TestUI.swift @@ -17,9 +17,42 @@ public struct TestRow: View { Text(test.progress.symbol) Text(test.title) Spacer() - Button("▢️") { - test.run() + Group { + if #available(iOS 26, macOS 26, tvOS 26, watchOS 26, *) { + Button { + test.run() + } label: { + Image(systemName: "play.fill") + .font(.system(size: 12, weight: .semibold)) + .frame(width: 28, height: 28) + } + .buttonStyle(.glass) + .buttonBorderShape(.circle) + } else if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { + Button { + test.run() + } label: { + Image(systemName: "play.fill") + .font(.system(size: 12, weight: .semibold)) + .frame(width: 28, height: 28) + .background(.regularMaterial, in: Circle()) + .contentShape(Circle()) + } + .buttonStyle(.plain) + } else { + Button { + test.run() + } label: { + Image(systemName: "play.fill") + .font(.system(size: 12, weight: .semibold)) + .frame(width: 28, height: 28) + .background(Circle().fill(Color.secondary.opacity(0.15))) + .contentShape(Circle()) + } + .buttonStyle(.plain) + } } + .accessibilityLabel("Run test") } if let errorMessage = test.errorMessage { Text(errorMessage) From cc40d07e6113675d7acfaf6d8aa8b67d7bc21603 Mon Sep 17 00:00:00 2001 From: kudit Date: Thu, 13 Aug 2026 17:12:33 -0400 Subject: [PATCH 059/107] Restore real MainActor scheduling helper on WASM --- Sources/Foundation/ThreadingWASMMain.swift | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Sources/Foundation/ThreadingWASMMain.swift diff --git a/Sources/Foundation/ThreadingWASMMain.swift b/Sources/Foundation/ThreadingWASMMain.swift new file mode 100644 index 0000000..fdb5615 --- /dev/null +++ b/Sources/Foundation/ThreadingWASMMain.swift @@ -0,0 +1,55 @@ +// +// ThreadingWASMMain.swift +// Compatibility +// +// Full-runtime WebAssembly has Swift concurrency but not Dispatch-backed threading. +// Keep the main-actor scheduling convenience there without pretending that work can +// be moved to a background thread or that host timer services exist. +// + +#if arch(wasm32) && !hasFeature(Embedded) + +public extension Compatibility { + /// Schedules work onto Swift's main actor on full-runtime WebAssembly. + /// + /// Unlike the former synchronous fallback, this uses Swift concurrency and does not + /// claim that an arbitrary caller is already executing in the main-actor isolation domain. + static func main( + _ closure: @Sendable @MainActor @escaping () -> Void, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column + ) { + Task { @MainActor in + closure() + } + } +} + +/// Schedules work onto Swift's main actor using the concise cross-platform spelling. +public func main( + _ closure: @Sendable @MainActor @escaping () -> Void, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column +) { + Compatibility.main(closure, file: file, function: function, line: line, column: column) +} + +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +public extension Task where Success == Never, Failure == Never { + /// WebAssembly counterpart to the main-actor scheduling convenience on threaded hosts. + static func main( + _ closure: @Sendable @MainActor @escaping () -> Void, + file: String = #file, + function: String = #function, + line: Int = #line, + column: Int = #column + ) { + Compatibility.main(closure, file: file, function: function, line: line, column: column) + } +} + +#endif From 9bf3daf8871816cfcc2dda3ee1893e9155a7de01 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 11:44:34 -0400 Subject: [PATCH 060/107] Backport Liquid Glass button styling --- Sources/UI/BackportButtonStyle.swift | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Sources/UI/BackportButtonStyle.swift diff --git a/Sources/UI/BackportButtonStyle.swift b/Sources/UI/BackportButtonStyle.swift new file mode 100644 index 0000000..235df68 --- /dev/null +++ b/Sources/UI/BackportButtonStyle.swift @@ -0,0 +1,39 @@ +#if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) +import SwiftUI + +/// Button styles whose newest system appearance can be used through Compatibility's backport surface. +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +public enum BackportButtonStyle: Sendable { + /// Uses the system Liquid Glass button style when available and a material-backed circular fallback otherwise. + case glass +} + +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +@MainActor +public extension Backport where Content: View { + /// Applies a Compatibility-managed button style whose appearance degrades gracefully on older systems. + /// + /// Use `.backport.buttonStyle(.glass)` instead of repeating availability checks at each call site. + @ViewBuilder + func buttonStyle(_ style: BackportButtonStyle) -> some View { + switch style { + case .glass: + if #available(iOS 26, macOS 26, tvOS 26, watchOS 26, *) { + content + .buttonStyle(.glass) + .buttonBorderShape(.circle) + } else if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { + content + .buttonStyle(.plain) + .background(.regularMaterial, in: Circle()) + .contentShape(Circle()) + } else { + content + .buttonStyle(.plain) + .background(Circle().fill(Color.secondary.opacity(0.15))) + .contentShape(Circle()) + } + } + } +} +#endif From fefa39d1f78de15c9556ab82b27493fb10410774 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 11:45:05 -0400 Subject: [PATCH 061/107] Use backported glass style for test run button --- Sources/UI/TestUI.swift | 43 ++++++++--------------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/Sources/UI/TestUI.swift b/Sources/UI/TestUI.swift index 3b9a11d..5bf87b4 100644 --- a/Sources/UI/TestUI.swift +++ b/Sources/UI/TestUI.swift @@ -17,42 +17,15 @@ public struct TestRow: View { Text(test.progress.symbol) Text(test.title) Spacer() - Group { - if #available(iOS 26, macOS 26, tvOS 26, watchOS 26, *) { - Button { - test.run() - } label: { - Image(systemName: "play.fill") - .font(.system(size: 12, weight: .semibold)) - .frame(width: 28, height: 28) - } - .buttonStyle(.glass) - .buttonBorderShape(.circle) - } else if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { - Button { - test.run() - } label: { - Image(systemName: "play.fill") - .font(.system(size: 12, weight: .semibold)) - .frame(width: 28, height: 28) - .background(.regularMaterial, in: Circle()) - .contentShape(Circle()) - } - .buttonStyle(.plain) - } else { - Button { - test.run() - } label: { - Image(systemName: "play.fill") - .font(.system(size: 12, weight: .semibold)) - .frame(width: 28, height: 28) - .background(Circle().fill(Color.secondary.opacity(0.15))) - .contentShape(Circle()) - } - .buttonStyle(.plain) - } + Button { + test.run() + } label: { + Backport.Image(systemName: "play.fill") + .font(.system(size: 12, weight: .semibold)) + .frame(width: 28, height: 28) } - .accessibilityLabel("Run test") + .backport.buttonStyle(.glass) + .accessibility(label: Text("Run test")) } if let errorMessage = test.errorMessage { Text(errorMessage) From 24fc7886243b64de93cbce4d393e4cc29ad52be8 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 11:45:50 -0400 Subject: [PATCH 062/107] Set Compatibility version to 2.0.0 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index e722e6b..02baba6 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ // This file is automatically generated. // Do not edit it by hand because the contents will be replaced. -let version = "1.18.3" +let version = "2.0.0" let packageLibraryName = "Compatibility" #if canImport(PackageDescription) From cf4c95b57645a28d4a578e77b8ecf952c63c77d5 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 11:47:20 -0400 Subject: [PATCH 063/107] Move shared test plan outside test source target --- Development/CompatibilityTest.xctestplan | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Development/CompatibilityTest.xctestplan diff --git a/Development/CompatibilityTest.xctestplan b/Development/CompatibilityTest.xctestplan new file mode 100644 index 0000000..a66f152 --- /dev/null +++ b/Development/CompatibilityTest.xctestplan @@ -0,0 +1,36 @@ +{ + "configurations" : [ + { + "id" : "4A4C83C6-63F1-4F6E-B0E6-2B4002000001", + "name" : "Default", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : true, + "targetForVariableExpansion" : { + "containerPath" : "container:Compatibility.xcodeproj", + "identifier" : "B5E5FC502C386144004F2009", + "name" : "CompatibilityTest" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:Compatibility.xcodeproj", + "identifier" : "B594CFA82DB0B838001E8658", + "name" : "CompatibilityTests" + } + }, + { + "target" : { + "containerPath" : "container:Compatibility.xcodeproj", + "identifier" : "B50707012C60A00100000001", + "name" : "CompatibilityUITests" + } + } + ], + "version" : 1 +} From 9408d8b0446840ca4ab77bf49565a32c31c2e39d Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 11:47:36 -0400 Subject: [PATCH 064/107] Point app scheme at project-level shared test plan --- .../xcshareddata/xcschemes/CompatibilityTest.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 048814b..474207c 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -58,7 +58,7 @@ shouldUseLaunchSchemeArgsEnv = "YES"> From 779361ec317885a4049a20837a45a1b179862d79 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 11:47:58 -0400 Subject: [PATCH 065/107] Correct shared test plan path --- .../xcshareddata/xcschemes/CompatibilityTest.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 474207c..1629748 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -58,7 +58,7 @@ shouldUseLaunchSchemeArgsEnv = "YES"> From d12871a3c8818a61d1480cad976bcb306e512283 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 11:48:43 -0400 Subject: [PATCH 066/107] Set public Compatibility version to 2.0.0 --- Sources/Compatibility.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 2afbbb1..35eb3b2 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -8,7 +8,7 @@ public enum Compatibility: Module { /// The version of the Compatibility Library since cannot get directly from Package.swift. - public static let version: Version = "1.18.3" + public static let version: Version = "2.0.0" /// Public source repository for Compatibility so support reports can direct developers to its source and issue history. /// From e1a1ee67be82cbee2a74aced96e0d7395a47e4dc Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:29:29 -0400 Subject: [PATCH 067/107] contributing tweaks and 1.18.3 reversion --- CONTRIBUTING.md | 2 ++ Package.swift | 2 +- Sources/Compatibility.swift | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4562e93..fcefc0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,6 +51,8 @@ After every prompt-driven change, contributors and coding agents must: 5. Refresh the active unpushed changelog date when work continues on a later date. 6. Append the complete prompt as `PROMPT: [PROMPT TEXT]`. 7. Review both the normal diff and an ignore-whitespace diff, remove unrelated or whitespace-only changes, run `git diff --check`, and run the repository's real build and tests. +8. For multi-file edits, patch each repository or external file separately. After every patch, verify the tool result, inspect the exact diff, run syntax checks, and search for the removed symbol or dependency. Never report the overall change as complete when any hunk failed or remains unverified. Always list changed files and show all deltas using a diff editor (if in Codex). + A full changelog outline may include: diff --git a/Package.swift b/Package.swift index 02baba6..e722e6b 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ // This file is automatically generated. // Do not edit it by hand because the contents will be replaced. -let version = "2.0.0" +let version = "1.18.3" let packageLibraryName = "Compatibility" #if canImport(PackageDescription) diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 35eb3b2..2afbbb1 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -8,7 +8,7 @@ public enum Compatibility: Module { /// The version of the Compatibility Library since cannot get directly from Package.swift. - public static let version: Version = "2.0.0" + public static let version: Version = "1.18.3" /// Public source repository for Compatibility so support reports can direct developers to its source and issue history. /// From 91a2dc82c9cfe078cf595b45279cac47ae50ca0d Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:33:06 -0400 Subject: [PATCH 068/107] Use scheme testables for combined app tests --- .../xcschemes/CompatibilityTest.xcscheme | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 1629748..e6d6c99 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -55,13 +55,31 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + + + + + + + Date: Fri, 14 Aug 2026 20:33:14 -0400 Subject: [PATCH 069/107] Use autocreated scheme plan instead of checked-in plan --- Development/CompatibilityTest.xctestplan | 36 ------------------------ 1 file changed, 36 deletions(-) delete mode 100644 Development/CompatibilityTest.xctestplan diff --git a/Development/CompatibilityTest.xctestplan b/Development/CompatibilityTest.xctestplan deleted file mode 100644 index a66f152..0000000 --- a/Development/CompatibilityTest.xctestplan +++ /dev/null @@ -1,36 +0,0 @@ -{ - "configurations" : [ - { - "id" : "4A4C83C6-63F1-4F6E-B0E6-2B4002000001", - "name" : "Default", - "options" : { - - } - } - ], - "defaultOptions" : { - "codeCoverage" : true, - "targetForVariableExpansion" : { - "containerPath" : "container:Compatibility.xcodeproj", - "identifier" : "B5E5FC502C386144004F2009", - "name" : "CompatibilityTest" - } - }, - "testTargets" : [ - { - "target" : { - "containerPath" : "container:Compatibility.xcodeproj", - "identifier" : "B594CFA82DB0B838001E8658", - "name" : "CompatibilityTests" - } - }, - { - "target" : { - "containerPath" : "container:Compatibility.xcodeproj", - "identifier" : "B50707012C60A00100000001", - "name" : "CompatibilityUITests" - } - } - ], - "version" : 1 -} From de469f1f7488fecffb8dadedd364744cf33804c4 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:34:10 -0400 Subject: [PATCH 070/107] Consolidate WebAssembly main implementation --- Sources/Foundation/Threading.swift | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index a19e725..462d2c0 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -314,12 +314,12 @@ private let backgroundTests: [TestCase] = [ // MARK: - Main -// As with background work, do not claim a main-dispatch helper exists on WASM/Embedded by simply -// executing the closure inline. Code that requires this scheduling API should fail to compile there -// until that runtime has a real implementation with the advertised semantics. -#if !arch(wasm32) && !hasFeature(Embedded) +// Embedded Swift cannot provide the scheduling semantics promised here, so omit the API there. +// Full-runtime WebAssembly does have Swift concurrency, so it can use the same MainActor scheduling +// implementation while simply skipping the Dispatch fallback that is unavailable on wasm32. +#if !hasFeature(Embedded) public extension Compatibility { - /// Schedules work on the main actor using concurrency or the older dispatch fallback. + /// Schedules work on the main actor using Swift concurrency or the older dispatch fallback. static func main( _ closure: @Sendable @MainActor @escaping () -> Void, file: String = #file, @@ -327,6 +327,11 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { +#if arch(wasm32) + Task { @MainActor in + closure() + } +#else if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) { Task { @MainActor in // debug("Running main-thread block", level: .DEBUG, file: file, function: function, line: line, column: column) @@ -337,6 +342,7 @@ public extension Compatibility { closure() } } +#endif } } @@ -351,7 +357,6 @@ public func main( line: Int = #line, column: Int = #column ) { - // Keep this concise API available before Swift concurrency by forwarding to the dispatch-capable implementation. Compatibility.main(closure, file: file, function: function, line: line, column: column) } @@ -446,8 +451,8 @@ public extension Compatibility { @MainActor static let threadingTests: [TestCase] = { #if arch(wasm32) || hasFeature(Embedded) - // WASM/Embedded intentionally omit sleep, background, main, and delay rather than providing - // semantic no-op fallbacks, so there are no threading/timing tests to register there. + // WASM/Embedded omit sleep, background, and delay rather than providing semantic no-op fallbacks. + // WebAssembly still exposes real MainActor scheduling, but its host-independent test catalog remains empty. return [] #else return sleepTests + backgroundTests + mainTests + delayTests From 4af709711864c783c2b156a90d57ff88b4d98143 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:34:16 -0400 Subject: [PATCH 071/107] Remove redundant WebAssembly threading file --- Sources/Foundation/ThreadingWASMMain.swift | 55 ---------------------- 1 file changed, 55 deletions(-) delete mode 100644 Sources/Foundation/ThreadingWASMMain.swift diff --git a/Sources/Foundation/ThreadingWASMMain.swift b/Sources/Foundation/ThreadingWASMMain.swift deleted file mode 100644 index fdb5615..0000000 --- a/Sources/Foundation/ThreadingWASMMain.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// ThreadingWASMMain.swift -// Compatibility -// -// Full-runtime WebAssembly has Swift concurrency but not Dispatch-backed threading. -// Keep the main-actor scheduling convenience there without pretending that work can -// be moved to a background thread or that host timer services exist. -// - -#if arch(wasm32) && !hasFeature(Embedded) - -public extension Compatibility { - /// Schedules work onto Swift's main actor on full-runtime WebAssembly. - /// - /// Unlike the former synchronous fallback, this uses Swift concurrency and does not - /// claim that an arbitrary caller is already executing in the main-actor isolation domain. - static func main( - _ closure: @Sendable @MainActor @escaping () -> Void, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column - ) { - Task { @MainActor in - closure() - } - } -} - -/// Schedules work onto Swift's main actor using the concise cross-platform spelling. -public func main( - _ closure: @Sendable @MainActor @escaping () -> Void, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column -) { - Compatibility.main(closure, file: file, function: function, line: line, column: column) -} - -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) -public extension Task where Success == Never, Failure == Never { - /// WebAssembly counterpart to the main-actor scheduling convenience on threaded hosts. - static func main( - _ closure: @Sendable @MainActor @escaping () -> Void, - file: String = #file, - function: String = #function, - line: Int = #line, - column: Int = #column - ) { - Compatibility.main(closure, file: file, function: function, line: line, column: column) - } -} - -#endif From 6a3ff808f4db19e0fd17cae4963fe9eb0ea89d2c Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:34:39 -0400 Subject: [PATCH 072/107] Keep Compatibility release at 1.18.3 --- Package.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index e722e6b..6614750 100644 --- a/Package.swift +++ b/Package.swift @@ -157,10 +157,7 @@ targets += [ .init(stringLiteral: packageLibraryName), "CompatibilityTesting", ],// have to use init since normally would be assignable by string literal but we're not using a string literal - path: "Development/CompatibilityTests", - // The Xcode project consumes this test plan directly, while SwiftPM has no - // declaration for it and otherwise warns that the file is unhandled. - exclude: ["CompatibilityTest.xctestplan"] + path: "Development/CompatibilityTests" ), ] #endif From fa3cedc23d7246c104f7c6ca5bc3198dc9982314 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:35:09 -0400 Subject: [PATCH 073/107] Keep public Compatibility version at 1.18.3 From 47ce53fdedd857e4e7364091382ee397d97c9683 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:35:28 -0400 Subject: [PATCH 074/107] Keep Xcode marketing version at 1.18.3 --- .../Compatibility.xcodeproj/project.pbxproj | 838 +----------------- 1 file changed, 1 insertion(+), 837 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 16887bc..311c8dd 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -1,837 +1 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 70; - objects = { - -/* Begin PBXBuildFile section */ - B50707092C60A00100000001 /* CompatibilityUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50707082C60A00100000001 /* CompatibilityUITests.swift */; }; - B5198A0E2C38FAD300CEA720 /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FC3A2C3860EC004F2009 /* MyApp.swift */; }; - B5198A102C38FAEB00CEA720 /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B5198A0F2C38FAEB00CEA720 /* Compatibility Library */; }; - B51B70C62C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */; }; - B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; - B5209EE42C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; - B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FC3A2C3860EC004F2009 /* MyApp.swift */; }; - B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */ = {isa = PBXBuildFile; productRef = B52DEB223019BA54003291D0 /* Compatibility Testing Library */; }; - B569253B2E8715550045FFC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; - B579D4A52C46FF1A009A037A /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B579D4A42C46FF1A009A037A /* Compatibility Library */; }; - B58B5C452C38F98800689837 /* (null) in Sources */ = {isa = PBXBuildFile; }; - B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B594CFB62DB0BACA001E8658 /* Compatibility Library */; }; - B5CB1E3C2C6BB1D300CF542B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */; }; - B5E5FC832C3863B9004F2009 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - B60000012F00000100000001 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = B50E7B632C385BD8002D3F53 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B5E5FC502C386144004F2009; - remoteInfo = CompatibilityTest; - }; - B60000032F00000100000001 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = B50E7B632C385BD8002D3F53 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B5E5FC502C386144004F2009; - remoteInfo = CompatibilityTest; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - B50707072C60A00100000001 /* CompatibilityUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompatibilityUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B50707082C60A00100000001 /* CompatibilityUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityUITests.swift; sourceTree = ""; }; - B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - B51B70C52C5D6DBF001F7DCF /* Entitlements.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Entitlements.entitlements; sourceTree = ""; }; - B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityDemoView.swift; sourceTree = ""; }; - B52C8E082C386F2D008EBD2D /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; - B52C8E0C2C3886E6008EBD2D /* Compatibility.swiftpm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Compatibility.swiftpm; path = ..; sourceTree = ""; }; - B58B5C3F2C38F98800689837 /* CompatibilityTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompatibilityTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; - B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompatibilityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - B5E5FC3A2C3860EC004F2009 /* MyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyApp.swift; sourceTree = ""; }; - B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; }; - B5E5FC442C3860EC004F2009 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; - B5E5FC452C3860EC004F2009 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; - B5E5FC512C386144004F2009 /* CompatibilityTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompatibilityTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; - B5E5FC822C3863B9004F2009 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - B594CFB92DB0BBC4001E8658 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - CompatibilityTest.xctestplan, - ); - target = B594CFA82DB0B838001E8658 /* CompatibilityTests */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - B5965FE52DB0B4FD00784140 /* CompatibilityTests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B594CFB92DB0BBC4001E8658 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = CompatibilityTests; sourceTree = ""; }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - B50707032C60A00100000001 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B58B5C3C2C38F98800689837 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B5198A102C38FAEB00CEA720 /* Compatibility Library in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B594CFA62DB0B838001E8658 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */, - B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B5E5FC4E2C386144004F2009 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B579D4A52C46FF1A009A037A /* Compatibility Library in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - B50707062C60A00100000001 /* CompatibilityUITests */ = { - isa = PBXGroup; - children = ( - B50707082C60A00100000001 /* CompatibilityUITests.swift */, - ); - path = CompatibilityUITests; - sourceTree = ""; - }; - B50E7B622C385BD8002D3F53 = { - isa = PBXGroup; - children = ( - B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */, - B5E5FC452C3860EC004F2009 /* README.md */, - B52C8E082C386F2D008EBD2D /* Package.swift */, - B5E5FC3B2C3860EC004F2009 /* Development */, - B52C8E0C2C3886E6008EBD2D /* Compatibility.swiftpm */, - B5E5FC442C3860EC004F2009 /* LICENSE.txt */, - B50E7B6D2C385BD8002D3F53 /* Products */, - B52C8E092C387BE9008EBD2D /* Frameworks */, - ); - sourceTree = ""; - }; - B50E7B6D2C385BD8002D3F53 /* Products */ = { - isa = PBXGroup; - children = ( - B5E5FC512C386144004F2009 /* CompatibilityTest.app */, - B58B5C3F2C38F98800689837 /* CompatibilityTest.app */, - B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */, - B50707072C60A00100000001 /* CompatibilityUITests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - B52C8E092C387BE9008EBD2D /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; - B5E5FC3B2C3860EC004F2009 /* Development */ = { - isa = PBXGroup; - children = ( - B5965FE52DB0B4FD00784140 /* CompatibilityTests */, - B50707062C60A00100000001 /* CompatibilityUITests */, - B5E5FC812C38638B004F2009 /* Resources */, - B5E5FC3A2C3860EC004F2009 /* MyApp.swift */, - B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */, - ); - name = Development; - sourceTree = ""; - }; - B5E5FC812C38638B004F2009 /* Resources */ = { - isa = PBXGroup; - children = ( - B51B70C52C5D6DBF001F7DCF /* Entitlements.entitlements */, - B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */, - B5E5FC822C3863B9004F2009 /* Assets.xcassets */, - ); - path = Resources; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - B50707012C60A00100000001 /* CompatibilityUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = B50707052C60A00100000001 /* Build configuration list for PBXNativeTarget "CompatibilityUITests" */; - buildPhases = ( - B50707022C60A00100000001 /* Sources */, - B50707032C60A00100000001 /* Frameworks */, - B50707042C60A00100000001 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - B60000022F00000100000001 /* PBXTargetDependency */, - ); - name = CompatibilityUITests; - packageProductDependencies = ( - ); - productName = CompatibilityUITests; - productReference = B50707072C60A00100000001 /* CompatibilityUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; - B58B5C3E2C38F98800689837 /* CompatibilityTest Watch App */ = { - isa = PBXNativeTarget; - buildConfigurationList = B58B5C522C38F98900689837 /* Build configuration list for PBXNativeTarget "CompatibilityTest Watch App" */; - buildPhases = ( - B58B5C3B2C38F98800689837 /* Sources */, - B58B5C3C2C38F98800689837 /* Frameworks */, - B58B5C3D2C38F98800689837 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "CompatibilityTest Watch App"; - packageProductDependencies = ( - B5198A0F2C38FAEB00CEA720 /* Compatibility Library */, - ); - productName = "CompatibilityTest2 Watch App"; - productReference = B58B5C3F2C38F98800689837 /* CompatibilityTest.app */; - productType = "com.apple.product-type.application"; - }; - B594CFA82DB0B838001E8658 /* CompatibilityTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = B594CFAF2DB0B838001E8658 /* Build configuration list for PBXNativeTarget "CompatibilityTests" */; - buildPhases = ( - B594CFA52DB0B838001E8658 /* Sources */, - B594CFA62DB0B838001E8658 /* Frameworks */, - B594CFA72DB0B838001E8658 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - B60000042F00000100000001 /* PBXTargetDependency */, - ); - name = CompatibilityTests; - packageProductDependencies = ( - B594CFB62DB0BACA001E8658 /* Compatibility Library */, - B52DEB223019BA54003291D0 /* Compatibility Testing Library */, - ); - productName = CompatibilityTests; - productReference = B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - B5E5FC502C386144004F2009 /* CompatibilityTest */ = { - isa = PBXNativeTarget; - buildConfigurationList = B5E5FC5D2C386145004F2009 /* Build configuration list for PBXNativeTarget "CompatibilityTest" */; - buildPhases = ( - B5E5FC4D2C386144004F2009 /* Sources */, - B5E5FC4E2C386144004F2009 /* Frameworks */, - B5E5FC4F2C386144004F2009 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = CompatibilityTest; - packageProductDependencies = ( - B579D4A42C46FF1A009A037A /* Compatibility Library */, - ); - productName = ColorTest; - productReference = B5E5FC512C386144004F2009 /* CompatibilityTest.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - B50E7B632C385BD8002D3F53 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1630; - LastUpgradeCheck = 2600; - TargetAttributes = { - B50707012C60A00100000001 = { - CreatedOnToolsVersion = 26.0; - TestTargetID = B5E5FC502C386144004F2009; - }; - B58B5C3E2C38F98800689837 = { - CreatedOnToolsVersion = 15.4; - }; - B594CFA82DB0B838001E8658 = { - CreatedOnToolsVersion = 16.3; - TestTargetID = B5E5FC502C386144004F2009; - }; - B5E5FC502C386144004F2009 = { - CreatedOnToolsVersion = 15.4; - }; - }; - }; - buildConfigurationList = B50E7B662C385BD8002D3F53 /* Build configuration list for PBXProject "Compatibility" */; - compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = B50E7B622C385BD8002D3F53; - packageReferences = ( - B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */, - ); - productRefGroup = B50E7B6D2C385BD8002D3F53 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - B5E5FC502C386144004F2009 /* CompatibilityTest */, - B58B5C3E2C38F98800689837 /* CompatibilityTest Watch App */, - B594CFA82DB0B838001E8658 /* CompatibilityTests */, - B50707012C60A00100000001 /* CompatibilityUITests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - B50707042C60A00100000001 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B58B5C3D2C38F98800689837 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B569253B2E8715550045FFC6 /* Assets.xcassets in Resources */, - B5CB1E3C2C6BB1D300CF542B /* PrivacyInfo.xcprivacy in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B594CFA72DB0B838001E8658 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B5E5FC4F2C386144004F2009 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B51B70C62C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy in Resources */, - B5E5FC832C3863B9004F2009 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - B50707022C60A00100000001 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B50707092C60A00100000001 /* CompatibilityUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B58B5C3B2C38F98800689837 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B58B5C452C38F98800689837 /* (null) in Sources */, - B5198A0E2C38FAD300CEA720 /* MyApp.swift in Sources */, - B5209EE42C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B594CFA52DB0B838001E8658 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - B5E5FC4D2C386144004F2009 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */, - B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - B60000022F00000100000001 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B5E5FC502C386144004F2009 /* CompatibilityTest */; - targetProxy = B60000012F00000100000001 /* PBXContainerItemProxy */; - }; - B60000042F00000100000001 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B5E5FC502C386144004F2009 /* CompatibilityTest */; - targetProxy = B60000032F00000100000001 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - B507070A2C60A00100000001 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 3QPV894C33; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.UITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_TARGET_NAME = CompatibilityTest; - }; - name = Debug; - }; - B507070B2C60A00100000001 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 3QPV894C33; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.UITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_TARGET_NAME = CompatibilityTest; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - B50E7B7E2C385BD8002D3F53 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_ENTITLEMENTS = Resources/Entitlements.entitlements; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = "${MARKETING_VERSION}"; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 3QPV894C33; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.18.3; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - OTHER_SWIFT_FLAGS = ""; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = ""; - TVOS_DEPLOYMENT_TARGET = 12.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 10.0; - XROS_DEPLOYMENT_TARGET = 1.0; - }; - name = Debug; - }; - B50E7B7F2C385BD8002D3F53 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_ENTITLEMENTS = Resources/Entitlements.entitlements; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = "${MARKETING_VERSION}"; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = 3QPV894C33; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.18.3; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - OTHER_SWIFT_FLAGS = ""; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_STRICT_CONCURRENCY = complete; - SWIFT_VERSION = ""; - TVOS_DEPLOYMENT_TARGET = 12.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - WATCHOS_DEPLOYMENT_TARGET = 10.0; - XROS_DEPLOYMENT_TARGET = 1.0; - }; - name = Release; - }; - B58B5C502C38F98900689837 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.kudit.CompatibilityTest; - INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.watchkitapp; - PRODUCT_NAME = CompatibilityTest; - SDKROOT = watchos; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 10.0; - }; - name = Debug; - }; - B58B5C512C38F98900689837 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.kudit.CompatibilityTest; - INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.watchkitapp; - PRODUCT_NAME = CompatibilityTest; - SDKROOT = watchos; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 4; - VALIDATE_PRODUCT = YES; - WATCHOS_DEPLOYMENT_TARGET = 10.0; - }; - name = Release; - }; - B594CFAD2DB0B838001E8658 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_ENTITLEMENTS = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 3QPV894C33; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; - TEST_TARGET_NAME = CompatibilityTest; - TVOS_DEPLOYMENT_TARGET = 13.0; - }; - name = Debug; - }; - B594CFAE2DB0B838001E8658 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_ENTITLEMENTS = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 3QPV894C33; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; - TEST_TARGET_NAME = CompatibilityTest; - TVOS_DEPLOYMENT_TARGET = 13.0; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - B5E5FC5E2C386145004F2009 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - DEAD_CODE_STRIPPING = YES; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; - INFOPLIST_KEY_UIRequiresFullScreen = YES; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; - "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3,7"; - }; - name = Debug; - }; - B5E5FC5F2C386145004F2009 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - DEAD_CODE_STRIPPING = YES; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; - "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; - INFOPLIST_KEY_UIRequiresFullScreen = YES; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; - "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; - "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SUPPORTS_MACCATALYST = YES; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3,7"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - B50707052C60A00100000001 /* Build configuration list for PBXNativeTarget "CompatibilityUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B507070A2C60A00100000001 /* Debug */, - B507070B2C60A00100000001 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - B50E7B662C385BD8002D3F53 /* Build configuration list for PBXProject "Compatibility" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B50E7B7E2C385BD8002D3F53 /* Debug */, - B50E7B7F2C385BD8002D3F53 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - B58B5C522C38F98900689837 /* Build configuration list for PBXNativeTarget "CompatibilityTest Watch App" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B58B5C502C38F98900689837 /* Debug */, - B58B5C512C38F98900689837 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - B594CFAF2DB0B838001E8658 /* Build configuration list for PBXNativeTarget "CompatibilityTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B594CFAD2DB0B838001E8658 /* Debug */, - B594CFAE2DB0B838001E8658 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - B5E5FC5D2C386145004F2009 /* Build configuration list for PBXNativeTarget "CompatibilityTest" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B5E5FC5E2C386145004F2009 /* Debug */, - B5E5FC5F2C386145004F2009 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */ = { - isa = XCLocalSwiftPackageReference; - relativePath = ..; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - B5198A0F2C38FAEB00CEA720 /* Compatibility Library */ = { - isa = XCSwiftPackageProductDependency; - package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; - productName = "Compatibility Library"; - }; - B52DEB223019BA54003291D0 /* Compatibility Testing Library */ = { - isa = XCSwiftPackageProductDependency; - productName = "Compatibility Testing Library"; - }; - B579D4A42C46FF1A009A037A /* Compatibility Library */ = { - isa = XCSwiftPackageProductDependency; - package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; - productName = "Compatibility Library"; - }; - B594CFB62DB0BACA001E8658 /* Compatibility Library */ = { - isa = XCSwiftPackageProductDependency; - package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; - productName = "Compatibility Library"; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = B50E7B632C385BD8002D3F53 /* Project object */; -} +PLACEHOLDER \ No newline at end of file From 2be2b642045f31a726c0d4a51f347d3e3f7f7b4c Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 20:37:16 -0400 Subject: [PATCH 075/107] Restore Xcode project after connector write --- .../Compatibility.xcodeproj/project.pbxproj | 838 +++++++++++++++++- 1 file changed, 837 insertions(+), 1 deletion(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 311c8dd..16887bc 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -1 +1,837 @@ -PLACEHOLDER \ No newline at end of file +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 70; + objects = { + +/* Begin PBXBuildFile section */ + B50707092C60A00100000001 /* CompatibilityUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50707082C60A00100000001 /* CompatibilityUITests.swift */; }; + B5198A0E2C38FAD300CEA720 /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FC3A2C3860EC004F2009 /* MyApp.swift */; }; + B5198A102C38FAEB00CEA720 /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B5198A0F2C38FAEB00CEA720 /* Compatibility Library */; }; + B51B70C62C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */; }; + B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; + B5209EE42C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; + B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FC3A2C3860EC004F2009 /* MyApp.swift */; }; + B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */ = {isa = PBXBuildFile; productRef = B52DEB223019BA54003291D0 /* Compatibility Testing Library */; }; + B569253B2E8715550045FFC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; + B579D4A52C46FF1A009A037A /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B579D4A42C46FF1A009A037A /* Compatibility Library */; }; + B58B5C452C38F98800689837 /* (null) in Sources */ = {isa = PBXBuildFile; }; + B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B594CFB62DB0BACA001E8658 /* Compatibility Library */; }; + B5CB1E3C2C6BB1D300CF542B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */; }; + B5E5FC832C3863B9004F2009 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + B60000012F00000100000001 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B50E7B632C385BD8002D3F53 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B5E5FC502C386144004F2009; + remoteInfo = CompatibilityTest; + }; + B60000032F00000100000001 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B50E7B632C385BD8002D3F53 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B5E5FC502C386144004F2009; + remoteInfo = CompatibilityTest; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + B50707072C60A00100000001 /* CompatibilityUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompatibilityUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B50707082C60A00100000001 /* CompatibilityUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityUITests.swift; sourceTree = ""; }; + B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + B51B70C52C5D6DBF001F7DCF /* Entitlements.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Entitlements.entitlements; sourceTree = ""; }; + B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityDemoView.swift; sourceTree = ""; }; + B52C8E082C386F2D008EBD2D /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; + B52C8E0C2C3886E6008EBD2D /* Compatibility.swiftpm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Compatibility.swiftpm; path = ..; sourceTree = ""; }; + B58B5C3F2C38F98800689837 /* CompatibilityTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompatibilityTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompatibilityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B5E5FC3A2C3860EC004F2009 /* MyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyApp.swift; sourceTree = ""; }; + B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; }; + B5E5FC442C3860EC004F2009 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; + B5E5FC452C3860EC004F2009 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + B5E5FC512C386144004F2009 /* CompatibilityTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompatibilityTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B5E5FC822C3863B9004F2009 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + B594CFB92DB0BBC4001E8658 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + CompatibilityTest.xctestplan, + ); + target = B594CFA82DB0B838001E8658 /* CompatibilityTests */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + B5965FE52DB0B4FD00784140 /* CompatibilityTests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B594CFB92DB0BBC4001E8658 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = CompatibilityTests; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + B50707032C60A00100000001 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B58B5C3C2C38F98800689837 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B5198A102C38FAEB00CEA720 /* Compatibility Library in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B594CFA62DB0B838001E8658 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */, + B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B5E5FC4E2C386144004F2009 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B579D4A52C46FF1A009A037A /* Compatibility Library in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + B50707062C60A00100000001 /* CompatibilityUITests */ = { + isa = PBXGroup; + children = ( + B50707082C60A00100000001 /* CompatibilityUITests.swift */, + ); + path = CompatibilityUITests; + sourceTree = ""; + }; + B50E7B622C385BD8002D3F53 = { + isa = PBXGroup; + children = ( + B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */, + B5E5FC452C3860EC004F2009 /* README.md */, + B52C8E082C386F2D008EBD2D /* Package.swift */, + B5E5FC3B2C3860EC004F2009 /* Development */, + B52C8E0C2C3886E6008EBD2D /* Compatibility.swiftpm */, + B5E5FC442C3860EC004F2009 /* LICENSE.txt */, + B50E7B6D2C385BD8002D3F53 /* Products */, + B52C8E092C387BE9008EBD2D /* Frameworks */, + ); + sourceTree = ""; + }; + B50E7B6D2C385BD8002D3F53 /* Products */ = { + isa = PBXGroup; + children = ( + B5E5FC512C386144004F2009 /* CompatibilityTest.app */, + B58B5C3F2C38F98800689837 /* CompatibilityTest.app */, + B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */, + B50707072C60A00100000001 /* CompatibilityUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + B52C8E092C387BE9008EBD2D /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + B5E5FC3B2C3860EC004F2009 /* Development */ = { + isa = PBXGroup; + children = ( + B5965FE52DB0B4FD00784140 /* CompatibilityTests */, + B50707062C60A00100000001 /* CompatibilityUITests */, + B5E5FC812C38638B004F2009 /* Resources */, + B5E5FC3A2C3860EC004F2009 /* MyApp.swift */, + B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */, + ); + name = Development; + sourceTree = ""; + }; + B5E5FC812C38638B004F2009 /* Resources */ = { + isa = PBXGroup; + children = ( + B51B70C52C5D6DBF001F7DCF /* Entitlements.entitlements */, + B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */, + B5E5FC822C3863B9004F2009 /* Assets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + B50707012C60A00100000001 /* CompatibilityUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = B50707052C60A00100000001 /* Build configuration list for PBXNativeTarget "CompatibilityUITests" */; + buildPhases = ( + B50707022C60A00100000001 /* Sources */, + B50707032C60A00100000001 /* Frameworks */, + B50707042C60A00100000001 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + B60000022F00000100000001 /* PBXTargetDependency */, + ); + name = CompatibilityUITests; + packageProductDependencies = ( + ); + productName = CompatibilityUITests; + productReference = B50707072C60A00100000001 /* CompatibilityUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; + B58B5C3E2C38F98800689837 /* CompatibilityTest Watch App */ = { + isa = PBXNativeTarget; + buildConfigurationList = B58B5C522C38F98900689837 /* Build configuration list for PBXNativeTarget "CompatibilityTest Watch App" */; + buildPhases = ( + B58B5C3B2C38F98800689837 /* Sources */, + B58B5C3C2C38F98800689837 /* Frameworks */, + B58B5C3D2C38F98800689837 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "CompatibilityTest Watch App"; + packageProductDependencies = ( + B5198A0F2C38FAEB00CEA720 /* Compatibility Library */, + ); + productName = "CompatibilityTest2 Watch App"; + productReference = B58B5C3F2C38F98800689837 /* CompatibilityTest.app */; + productType = "com.apple.product-type.application"; + }; + B594CFA82DB0B838001E8658 /* CompatibilityTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = B594CFAF2DB0B838001E8658 /* Build configuration list for PBXNativeTarget "CompatibilityTests" */; + buildPhases = ( + B594CFA52DB0B838001E8658 /* Sources */, + B594CFA62DB0B838001E8658 /* Frameworks */, + B594CFA72DB0B838001E8658 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + B60000042F00000100000001 /* PBXTargetDependency */, + ); + name = CompatibilityTests; + packageProductDependencies = ( + B594CFB62DB0BACA001E8658 /* Compatibility Library */, + B52DEB223019BA54003291D0 /* Compatibility Testing Library */, + ); + productName = CompatibilityTests; + productReference = B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + B5E5FC502C386144004F2009 /* CompatibilityTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = B5E5FC5D2C386145004F2009 /* Build configuration list for PBXNativeTarget "CompatibilityTest" */; + buildPhases = ( + B5E5FC4D2C386144004F2009 /* Sources */, + B5E5FC4E2C386144004F2009 /* Frameworks */, + B5E5FC4F2C386144004F2009 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CompatibilityTest; + packageProductDependencies = ( + B579D4A42C46FF1A009A037A /* Compatibility Library */, + ); + productName = ColorTest; + productReference = B5E5FC512C386144004F2009 /* CompatibilityTest.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B50E7B632C385BD8002D3F53 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1630; + LastUpgradeCheck = 2600; + TargetAttributes = { + B50707012C60A00100000001 = { + CreatedOnToolsVersion = 26.0; + TestTargetID = B5E5FC502C386144004F2009; + }; + B58B5C3E2C38F98800689837 = { + CreatedOnToolsVersion = 15.4; + }; + B594CFA82DB0B838001E8658 = { + CreatedOnToolsVersion = 16.3; + TestTargetID = B5E5FC502C386144004F2009; + }; + B5E5FC502C386144004F2009 = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = B50E7B662C385BD8002D3F53 /* Build configuration list for PBXProject "Compatibility" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = B50E7B622C385BD8002D3F53; + packageReferences = ( + B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */, + ); + productRefGroup = B50E7B6D2C385BD8002D3F53 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B5E5FC502C386144004F2009 /* CompatibilityTest */, + B58B5C3E2C38F98800689837 /* CompatibilityTest Watch App */, + B594CFA82DB0B838001E8658 /* CompatibilityTests */, + B50707012C60A00100000001 /* CompatibilityUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + B50707042C60A00100000001 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B58B5C3D2C38F98800689837 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B569253B2E8715550045FFC6 /* Assets.xcassets in Resources */, + B5CB1E3C2C6BB1D300CF542B /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B594CFA72DB0B838001E8658 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B5E5FC4F2C386144004F2009 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B51B70C62C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy in Resources */, + B5E5FC832C3863B9004F2009 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + B50707022C60A00100000001 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B50707092C60A00100000001 /* CompatibilityUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B58B5C3B2C38F98800689837 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B58B5C452C38F98800689837 /* (null) in Sources */, + B5198A0E2C38FAD300CEA720 /* MyApp.swift in Sources */, + B5209EE42C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B594CFA52DB0B838001E8658 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B5E5FC4D2C386144004F2009 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */, + B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + B60000022F00000100000001 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B5E5FC502C386144004F2009 /* CompatibilityTest */; + targetProxy = B60000012F00000100000001 /* PBXContainerItemProxy */; + }; + B60000042F00000100000001 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B5E5FC502C386144004F2009 /* CompatibilityTest */; + targetProxy = B60000032F00000100000001 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + B507070A2C60A00100000001 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 3QPV894C33; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.UITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TEST_TARGET_NAME = CompatibilityTest; + }; + name = Debug; + }; + B507070B2C60A00100000001 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 3QPV894C33; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.UITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TEST_TARGET_NAME = CompatibilityTest; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + B50E7B7E2C385BD8002D3F53 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = Resources/Entitlements.entitlements; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "${MARKETING_VERSION}"; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 3QPV894C33; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.18.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = ""; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = ""; + TVOS_DEPLOYMENT_TARGET = 12.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Debug; + }; + B50E7B7F2C385BD8002D3F53 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = Resources/Entitlements.entitlements; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = "${MARKETING_VERSION}"; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 3QPV894C33; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.18.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = ""; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = ""; + TVOS_DEPLOYMENT_TARGET = 12.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + XROS_DEPLOYMENT_TARGET = 1.0; + }; + name = Release; + }; + B58B5C502C38F98900689837 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.kudit.CompatibilityTest; + INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.watchkitapp; + PRODUCT_NAME = CompatibilityTest; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + B58B5C512C38F98900689837 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconWatch; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.kudit.CompatibilityTest; + INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.watchkitapp; + PRODUCT_NAME = CompatibilityTest; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; + B594CFAD2DB0B838001E8658 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 3QPV894C33; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; + TEST_TARGET_NAME = CompatibilityTest; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = Debug; + }; + B594CFAE2DB0B838001E8658 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 3QPV894C33; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; + TEST_TARGET_NAME = CompatibilityTest; + TVOS_DEPLOYMENT_TARGET = 13.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + B5E5FC5E2C386145004F2009 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UIRequiresFullScreen = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,7"; + }; + name = Debug; + }; + B5E5FC5F2C386145004F2009 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEAD_CODE_STRIPPING = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + INFOPLIST_KEY_UIRequiresFullScreen = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 11.0; + PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,7"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + B50707052C60A00100000001 /* Build configuration list for PBXNativeTarget "CompatibilityUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B507070A2C60A00100000001 /* Debug */, + B507070B2C60A00100000001 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B50E7B662C385BD8002D3F53 /* Build configuration list for PBXProject "Compatibility" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B50E7B7E2C385BD8002D3F53 /* Debug */, + B50E7B7F2C385BD8002D3F53 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B58B5C522C38F98900689837 /* Build configuration list for PBXNativeTarget "CompatibilityTest Watch App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B58B5C502C38F98900689837 /* Debug */, + B58B5C512C38F98900689837 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B594CFAF2DB0B838001E8658 /* Build configuration list for PBXNativeTarget "CompatibilityTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B594CFAD2DB0B838001E8658 /* Debug */, + B594CFAE2DB0B838001E8658 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B5E5FC5D2C386145004F2009 /* Build configuration list for PBXNativeTarget "CompatibilityTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5E5FC5E2C386145004F2009 /* Debug */, + B5E5FC5F2C386145004F2009 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ..; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + B5198A0F2C38FAEB00CEA720 /* Compatibility Library */ = { + isa = XCSwiftPackageProductDependency; + package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; + productName = "Compatibility Library"; + }; + B52DEB223019BA54003291D0 /* Compatibility Testing Library */ = { + isa = XCSwiftPackageProductDependency; + productName = "Compatibility Testing Library"; + }; + B579D4A42C46FF1A009A037A /* Compatibility Library */ = { + isa = XCSwiftPackageProductDependency; + package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; + productName = "Compatibility Library"; + }; + B594CFB62DB0BACA001E8658 /* Compatibility Library */ = { + isa = XCSwiftPackageProductDependency; + package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; + productName = "Compatibility Library"; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = B50E7B632C385BD8002D3F53 /* Project object */; +} From 55cde7ad91729d91eb698c2603b238f72ad2d17a Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 21:34:06 -0400 Subject: [PATCH 076/107] Restore test-plan-based CompatibilityTest scheme --- .../xcschemes/CompatibilityTest.xcscheme | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index e6d6c99..048814b 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -55,31 +55,13 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + Date: Fri, 14 Aug 2026 21:55:30 -0400 Subject: [PATCH 077/107] Simplify main availability handling --- Sources/Foundation/Threading.swift | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index 462d2c0..7ba2636 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -315,8 +315,8 @@ private let backgroundTests: [TestCase] = [ // MARK: - Main // Embedded Swift cannot provide the scheduling semantics promised here, so omit the API there. -// Full-runtime WebAssembly does have Swift concurrency, so it can use the same MainActor scheduling -// implementation while simply skipping the Dispatch fallback that is unavailable on wasm32. +// Full-runtime WebAssembly has Swift concurrency and follows the same modern availability branch; +// only the legacy Dispatch fallback is conditionally compiled where Dispatch is actually available. #if !hasFeature(Embedded) public extension Compatibility { /// Schedules work on the main actor using Swift concurrency or the older dispatch fallback. @@ -327,17 +327,14 @@ public extension Compatibility { line: Int = #line, column: Int = #column ) { -#if arch(wasm32) - Task { @MainActor in - closure() - } -#else if #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) { Task { @MainActor in // debug("Running main-thread block", level: .DEBUG, file: file, function: function, line: line, column: column) closure() } - } else { + } +#if canImport(Dispatch) + else { DispatchQueue.main.async { @MainActor in closure() } From 8d0a5d9bacaa4eeeafd8f0646db26df255b33802 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 22:17:08 -0400 Subject: [PATCH 078/107] Updated threading to fix else split bug. --- Sources/Foundation/Threading.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index 7ba2636..ac4afd8 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -332,14 +332,15 @@ public extension Compatibility { // debug("Running main-thread block", level: .DEBUG, file: file, function: function, line: line, column: column) closure() } - } -#if canImport(Dispatch) - else { + } else { +#if !canImport(Dispatch) DispatchQueue.main.async { @MainActor in closure() } - } +#else + // CODEX TODO: Implement reasonable fallback without Dispatch on older iOS. Would it be possible to just run the closure? #endif + } } } From 20d08fd7e07433b2f2eab6f363c38b9242edc896 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 22:37:49 -0400 Subject: [PATCH 079/107] removed unnecessary conditional --- Sources/Foundation/Threading.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/Foundation/Threading.swift b/Sources/Foundation/Threading.swift index ac4afd8..5d93b19 100644 --- a/Sources/Foundation/Threading.swift +++ b/Sources/Foundation/Threading.swift @@ -333,13 +333,9 @@ public extension Compatibility { closure() } } else { -#if !canImport(Dispatch) DispatchQueue.main.async { @MainActor in closure() } -#else - // CODEX TODO: Implement reasonable fallback without Dispatch on older iOS. Would it be possible to just run the closure? -#endif } } } From 1bfe74f048ca9687cfdf6a7b1e5815a6d7ef6ca1 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 22:50:43 -0400 Subject: [PATCH 080/107] fix for project-file defect --- Development/Compatibility.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 16887bc..cdd47ee 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -13,8 +13,8 @@ B51B70C62C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */; }; B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; B5209EE42C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; + B523074630300B5C00036874 /* Compatibility Testing Library in Frameworks */ = {isa = PBXBuildFile; productRef = B523074530300B5C00036874 /* Compatibility Testing Library */; }; B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FC3A2C3860EC004F2009 /* MyApp.swift */; }; - B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */ = {isa = PBXBuildFile; productRef = B52DEB223019BA54003291D0 /* Compatibility Testing Library */; }; B569253B2E8715550045FFC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; B579D4A52C46FF1A009A037A /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B579D4A42C46FF1A009A037A /* Compatibility Library */; }; B58B5C452C38F98800689837 /* (null) in Sources */ = {isa = PBXBuildFile; }; @@ -92,7 +92,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B52DEB233019BA54003291D0 /* Compatibility Testing Library in Frameworks */, + B523074630300B5C00036874 /* Compatibility Testing Library in Frameworks */, B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -229,7 +229,7 @@ name = CompatibilityTests; packageProductDependencies = ( B594CFB62DB0BACA001E8658 /* Compatibility Library */, - B52DEB223019BA54003291D0 /* Compatibility Testing Library */, + B523074530300B5C00036874 /* Compatibility Testing Library */, ); productName = CompatibilityTests; productReference = B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */; @@ -817,7 +817,7 @@ package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; productName = "Compatibility Library"; }; - B52DEB223019BA54003291D0 /* Compatibility Testing Library */ = { + B523074530300B5C00036874 /* Compatibility Testing Library */ = { isa = XCSwiftPackageProductDependency; productName = "Compatibility Testing Library"; }; From 5eddb534d69870610dfa0868ee69112713931558 Mon Sep 17 00:00:00 2001 From: kudit Date: Fri, 14 Aug 2026 22:59:40 -0400 Subject: [PATCH 081/107] remove the hosted-test settings --- Development/Compatibility.xcodeproj/project.pbxproj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index cdd47ee..2c9affb 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -274,7 +274,6 @@ }; B594CFA82DB0B838001E8658 = { CreatedOnToolsVersion = 16.3; - TestTargetID = B5E5FC502C386144004F2009; }; B5E5FC502C386144004F2009 = { CreatedOnToolsVersion = 15.4; @@ -636,7 +635,6 @@ B594CFAD2DB0B838001E8658 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3QPV894C33; @@ -650,7 +648,6 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; }; @@ -659,7 +656,6 @@ B594CFAE2DB0B838001E8658 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 3QPV894C33; @@ -673,7 +669,6 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CompatibilityTest.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CompatibilityTest"; TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; VALIDATE_PRODUCT = YES; From 4e886cad62a01cfa57e4f608e1d3ec7b5dc47564 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 13:49:44 -0400 Subject: [PATCH 082/107] removed user defined TEST_TARGET_NAME --- Development/Compatibility.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 2c9affb..a8daac5 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -648,7 +648,6 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Debug; @@ -669,7 +668,6 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; - TEST_TARGET_NAME = CompatibilityTest; TVOS_DEPLOYMENT_TARGET = 13.0; VALIDATE_PRODUCT = YES; }; From a92d96b879b3392eb63a1513aac1199412b2b06a Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 18:21:19 -0400 Subject: [PATCH 083/107] Fixed test frameworks and silenced version warnings. --- .../Compatibility.xcodeproj/project.pbxproj | 142 +----------------- .../xcschemes/CompatibilityTest.xcscheme | 2 +- .../CompatibilityTest.xctestplan | 13 +- 3 files changed, 13 insertions(+), 144 deletions(-) rename Development/{CompatibilityTests => }/CompatibilityTest.xctestplan (77%) diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index a8daac5..93935df 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -13,12 +13,10 @@ B51B70C62C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */; }; B5209EE32C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; B5209EE42C431CF800FBA30B /* CompatibilityDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5209EE22C431CF800FBA30B /* CompatibilityDemoView.swift */; }; - B523074630300B5C00036874 /* Compatibility Testing Library in Frameworks */ = {isa = PBXBuildFile; productRef = B523074530300B5C00036874 /* Compatibility Testing Library */; }; B52C8E0F2C38CA76008EBD2D /* MyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FC3A2C3860EC004F2009 /* MyApp.swift */; }; B569253B2E8715550045FFC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; B579D4A52C46FF1A009A037A /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B579D4A42C46FF1A009A037A /* Compatibility Library */; }; B58B5C452C38F98800689837 /* (null) in Sources */ = {isa = PBXBuildFile; }; - B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */ = {isa = PBXBuildFile; productRef = B594CFB62DB0BACA001E8658 /* Compatibility Library */; }; B5CB1E3C2C6BB1D300CF542B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B51B70C42C5D6DBF001F7DCF /* PrivacyInfo.xcprivacy */; }; B5E5FC832C3863B9004F2009 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E5FC822C3863B9004F2009 /* Assets.xcassets */; }; /* End PBXBuildFile section */ @@ -31,13 +29,6 @@ remoteGlobalIDString = B5E5FC502C386144004F2009; remoteInfo = CompatibilityTest; }; - B60000032F00000100000001 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = B50E7B632C385BD8002D3F53 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B5E5FC502C386144004F2009; - remoteInfo = CompatibilityTest; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -49,7 +40,7 @@ B52C8E082C386F2D008EBD2D /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; B52C8E0C2C3886E6008EBD2D /* Compatibility.swiftpm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Compatibility.swiftpm; path = ..; sourceTree = ""; }; B58B5C3F2C38F98800689837 /* CompatibilityTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CompatibilityTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; - B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CompatibilityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B5DC66AB30310F3900FAFC9E /* CompatibilityTest.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CompatibilityTest.xctestplan; sourceTree = ""; }; B5E5FC3A2C3860EC004F2009 /* MyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyApp.swift; sourceTree = ""; }; B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; }; B5E5FC442C3860EC004F2009 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; @@ -58,18 +49,8 @@ B5E5FC822C3863B9004F2009 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - B594CFB92DB0BBC4001E8658 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - CompatibilityTest.xctestplan, - ); - target = B594CFA82DB0B838001E8658 /* CompatibilityTests */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - /* Begin PBXFileSystemSynchronizedRootGroup section */ - B5965FE52DB0B4FD00784140 /* CompatibilityTests */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (B594CFB92DB0BBC4001E8658 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = CompatibilityTests; sourceTree = ""; }; + B5965FE52DB0B4FD00784140 /* CompatibilityTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = CompatibilityTests; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -88,15 +69,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B594CFA62DB0B838001E8658 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B523074630300B5C00036874 /* Compatibility Testing Library in Frameworks */, - B594CFB72DB0BACA001E8658 /* Compatibility Library in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; B5E5FC4E2C386144004F2009 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -119,6 +91,7 @@ B50E7B622C385BD8002D3F53 = { isa = PBXGroup; children = ( + B5DC66AB30310F3900FAFC9E /* CompatibilityTest.xctestplan */, B5E5FC3E2C3860EC004F2009 /* CHANGELOG.md */, B5E5FC452C3860EC004F2009 /* README.md */, B52C8E082C386F2D008EBD2D /* Package.swift */, @@ -135,7 +108,6 @@ children = ( B5E5FC512C386144004F2009 /* CompatibilityTest.app */, B58B5C3F2C38F98800689837 /* CompatibilityTest.app */, - B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */, B50707072C60A00100000001 /* CompatibilityUITests.xctest */, ); name = Products; @@ -213,28 +185,6 @@ productReference = B58B5C3F2C38F98800689837 /* CompatibilityTest.app */; productType = "com.apple.product-type.application"; }; - B594CFA82DB0B838001E8658 /* CompatibilityTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = B594CFAF2DB0B838001E8658 /* Build configuration list for PBXNativeTarget "CompatibilityTests" */; - buildPhases = ( - B594CFA52DB0B838001E8658 /* Sources */, - B594CFA62DB0B838001E8658 /* Frameworks */, - B594CFA72DB0B838001E8658 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - B60000042F00000100000001 /* PBXTargetDependency */, - ); - name = CompatibilityTests; - packageProductDependencies = ( - B594CFB62DB0BACA001E8658 /* Compatibility Library */, - B523074530300B5C00036874 /* Compatibility Testing Library */, - ); - productName = CompatibilityTests; - productReference = B594CFA92DB0B838001E8658 /* CompatibilityTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; B5E5FC502C386144004F2009 /* CompatibilityTest */ = { isa = PBXNativeTarget; buildConfigurationList = B5E5FC5D2C386145004F2009 /* Build configuration list for PBXNativeTarget "CompatibilityTest" */; @@ -272,9 +222,6 @@ B58B5C3E2C38F98800689837 = { CreatedOnToolsVersion = 15.4; }; - B594CFA82DB0B838001E8658 = { - CreatedOnToolsVersion = 16.3; - }; B5E5FC502C386144004F2009 = { CreatedOnToolsVersion = 15.4; }; @@ -298,7 +245,6 @@ targets = ( B5E5FC502C386144004F2009 /* CompatibilityTest */, B58B5C3E2C38F98800689837 /* CompatibilityTest Watch App */, - B594CFA82DB0B838001E8658 /* CompatibilityTests */, B50707012C60A00100000001 /* CompatibilityUITests */, ); }; @@ -321,13 +267,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B594CFA72DB0B838001E8658 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; B5E5FC4F2C386144004F2009 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -358,13 +297,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - B594CFA52DB0B838001E8658 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; B5E5FC4D2C386144004F2009 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -382,11 +314,6 @@ target = B5E5FC502C386144004F2009 /* CompatibilityTest */; targetProxy = B60000012F00000100000001 /* PBXContainerItemProxy */; }; - B60000042F00000100000001 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B5E5FC502C386144004F2009 /* CompatibilityTest */; - targetProxy = B60000032F00000100000001 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -398,7 +325,7 @@ DEVELOPMENT_TEAM = 3QPV894C33; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 14.6; PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.UITests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -418,7 +345,7 @@ DEVELOPMENT_TEAM = 3QPV894C33; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 14.6; PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.UITests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -632,47 +559,6 @@ }; name = Release; }; - B594CFAD2DB0B838001E8658 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 3QPV894C33; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 13.0; - }; - name = Debug; - }; - B594CFAE2DB0B838001E8658 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_ENTITLEMENTS = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 3QPV894C33; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_BUNDLE_IDENTIFIER = com.kudit.CompatibilityTest.Tests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = auto; - SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 13.0; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; B5E5FC5E2C386145004F2009 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -777,15 +663,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B594CFAF2DB0B838001E8658 /* Build configuration list for PBXNativeTarget "CompatibilityTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B594CFAD2DB0B838001E8658 /* Debug */, - B594CFAE2DB0B838001E8658 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; B5E5FC5D2C386145004F2009 /* Build configuration list for PBXNativeTarget "CompatibilityTest" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -810,20 +687,11 @@ package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; productName = "Compatibility Library"; }; - B523074530300B5C00036874 /* Compatibility Testing Library */ = { - isa = XCSwiftPackageProductDependency; - productName = "Compatibility Testing Library"; - }; B579D4A42C46FF1A009A037A /* Compatibility Library */ = { isa = XCSwiftPackageProductDependency; package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; productName = "Compatibility Library"; }; - B594CFB62DB0BACA001E8658 /* Compatibility Library */ = { - isa = XCSwiftPackageProductDependency; - package = B52C8E0D2C3886E6008EBD2D /* XCLocalSwiftPackageReference ".." */; - productName = "Compatibility Library"; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B50E7B632C385BD8002D3F53 /* Project object */; diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 048814b..1629748 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -58,7 +58,7 @@ shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/Development/CompatibilityTests/CompatibilityTest.xctestplan b/Development/CompatibilityTest.xctestplan similarity index 77% rename from Development/CompatibilityTests/CompatibilityTest.xctestplan rename to Development/CompatibilityTest.xctestplan index affa57b..db16326 100644 --- a/Development/CompatibilityTests/CompatibilityTest.xctestplan +++ b/Development/CompatibilityTest.xctestplan @@ -1,7 +1,7 @@ { "configurations" : [ { - "id" : "2E2FC559-E6BB-4797-9D40-0CD8EC7E9991", + "id" : "45A88BEA-0855-421E-9A28-2A4812BED761", "name" : "Test Scheme Action", "options" : { @@ -10,6 +10,7 @@ ], "defaultOptions" : { "codeCoverage" : true, + "performanceAntipatternCheckerEnabled" : true, "targetForVariableExpansion" : { "containerPath" : "container:Compatibility.xcodeproj", "identifier" : "B5E5FC502C386144004F2009", @@ -20,15 +21,15 @@ { "target" : { "containerPath" : "container:Compatibility.xcodeproj", - "identifier" : "B594CFA82DB0B838001E8658", - "name" : "CompatibilityTests" + "identifier" : "B50707012C60A00100000001", + "name" : "CompatibilityUITests" } }, { "target" : { - "containerPath" : "container:Compatibility.xcodeproj", - "identifier" : "B50707012C60A00100000001", - "name" : "CompatibilityUITests" + "containerPath" : "container:..", + "identifier" : "CompatibilityTests", + "name" : "CompatibilityTests" } } ], From 7756966684930b16937558bfee92639be74b9917 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 18:24:04 -0400 Subject: [PATCH 084/107] removed fixed issues from changelog --- CHANGELOG.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89701ba..663c9f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,5 @@ # Changelog -# TODO: -Testing required before release: - -- Confirm `Compatibility Module Test Entries` displays each reusable `TestCase` separately. (I do not see)) -- Confirm the new entries execute successfully and preserve readable module, section, and test names. (do not see)) -- Confirm the serialized debug tests restore `Compatibility.settings` even when an expectation throws. (how do I do this?) -- Run SwiftPM and supported-platform validation before tagging the release. -I do not see each reusable TestCase separately in the test navigator in Xcode. I just see Compatibility Module Test and Compatibility Target Tests. - ## v1.18.3 2026-08-12 Added the reusable `Compatibility Testing Library` product and `ModuleTestEntry` adapter so each module `TestCase` appears as an individually named Swift Testing result. Unified `TestCase.execute()` and live test execution through one lifecycle implementation with explicit parallel and serialized execution modes. From c1614eb85caaa030d1298e8c4b65bfed5c9ce1a8 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 18:27:35 -0400 Subject: [PATCH 085/107] Remove parameterized discovery diagnostic test --- .../CompatibilityTests/ModuleTestEntryTests.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Development/CompatibilityTests/ModuleTestEntryTests.swift b/Development/CompatibilityTests/ModuleTestEntryTests.swift index d9783d0..1593084 100644 --- a/Development/CompatibilityTests/ModuleTestEntryTests.swift +++ b/Development/CompatibilityTests/ModuleTestEntryTests.swift @@ -5,20 +5,6 @@ // Exercises the reusable CompatibilityTesting adapter through Swift Testing. // -#if compiler(>=5.9) && canImport(Testing) -import Testing - -/// Static control kept independent of CompatibilityTesting so Xcode test discovery can be -/// verified even when the adapter product itself is misconfigured. -@Suite("Parameterized Test Discovery") -struct ParameterDisplayTests { - @Test("Parameter display test", arguments: [1, 2, 3]) - func parameterDisplayTest(value: Int) { - #expect((1...3).contains(value)) - } -} -#endif - #if compiler(>=5.9) && canImport(Compatibility) && canImport(Testing) import Compatibility import CompatibilityTesting From 4ff724fe9236fcfea060a5a4b3a563a309712d94 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 22:59:01 -0400 Subject: [PATCH 086/107] fixed version to 1.19.0 --- CHANGELOG.md | 19 +++++++++++++------ .../Compatibility.xcodeproj/project.pbxproj | 4 ++-- Package.swift | 2 +- Sources/Compatibility.swift | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 663c9f1..8901704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,19 @@ # Changelog -## v1.18.3 2026-08-12 -Added the reusable `Compatibility Testing Library` product and `ModuleTestEntry` adapter so each module `TestCase` appears as an individually named Swift Testing result. -Unified `TestCase.execute()` and live test execution through one lifecycle implementation with explicit parallel and serialized execution modes. -Added source-aware test failures, labeled debug-format context, and source-context debugging conveniences while preserving existing debug-format call sites. -Made debug tests run exclusively and restore process-global debug settings with `defer`, including when an expectation throws. +I would like the UI tests to visit every screen and be sure to scroll down on the tests screen. It can also exercise buttons where appropriate to actually test the interactive features. + +I also decided we can compromise by setting the version to 1.19 so that code can still update but it is more than just a bug fix patch. + + +## v1.19.0 2026-08-15 +Added `Compatibility Testing Library` and `ModuleTestEntry` so reusable module `TestCase`s run as named parameterized Swift Testing cases in SwiftPM and Xcode. +Unified reusable test execution through one lifecycle with parallel/serialized modes, source-aware failures, and reliable cleanup of mutable debug settings. +Consolidated debug formatting and source-context handling, and removed unnecessary main-actor isolation from debug logging. +Corrected `main` so it can be called from any thread while only its closure is main-actor isolated; full-runtime WebAssembly now uses real Swift concurrency for main-actor scheduling. +Removed misleading WASM/Embedded fallbacks for `sleep`, `background`, and `delay`; these APIs are now unavailable there rather than silently providing incorrect semantics. `main` remains available on full-runtime WASM but is unavailable in Embedded Swift. +Improved the reusable test UI and Xcode/SwiftPM test integration, including parameterized Test Navigator results and unified unit/UI test execution. Expanded contributor guidance for short, staged, maintainer-reviewed coding workflows. -Consolidated debug and main and background code and removed support for WASM/Embedded since those were dangerous masks. +Increased automated code coverage to XX%. ## v1.18.2 2026-07-23 Fixed Swift Package Index build errors and warnings across SwiftUI and WebAssembly targets. diff --git a/Development/Compatibility.xcodeproj/project.pbxproj b/Development/Compatibility.xcodeproj/project.pbxproj index 93935df..5ddcda1 100644 --- a/Development/Compatibility.xcodeproj/project.pbxproj +++ b/Development/Compatibility.xcodeproj/project.pbxproj @@ -417,7 +417,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.18.3; + MARKETING_VERSION = 1.19.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -488,7 +488,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 12.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.18.3; + MARKETING_VERSION = 1.19.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = ""; diff --git a/Package.swift b/Package.swift index 6614750..e0c7c0c 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ // This file is automatically generated. // Do not edit it by hand because the contents will be replaced. -let version = "1.18.3" +let version = "1.19.0" let packageLibraryName = "Compatibility" #if canImport(PackageDescription) diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 2afbbb1..81021f6 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -8,7 +8,7 @@ public enum Compatibility: Module { /// The version of the Compatibility Library since cannot get directly from Package.swift. - public static let version: Version = "1.18.3" + public static let version: Version = "1.19.0" /// Public source repository for Compatibility so support reports can direct developers to its source and issue history. /// From d0dfc157527062d58e0634c4e3c09fe5f57a258d Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 23:02:26 -0400 Subject: [PATCH 087/107] Make every demo screen directly testable for UI coverage --- Development/CompatibilityDemoView.swift | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Development/CompatibilityDemoView.swift b/Development/CompatibilityDemoView.swift index 8a18677..daf5ef4 100644 --- a/Development/CompatibilityDemoView.swift +++ b/Development/CompatibilityDemoView.swift @@ -7,6 +7,7 @@ #if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) import SwiftUI +import Foundation import Compatibility final class DemoFailureCounter: @unchecked Sendable { @@ -48,51 +49,75 @@ struct CompatibilityDemoView: View { ] ] + // UI coverage tests can request a specific page directly. This avoids depending on + // platform-specific TabView accessibility while still rendering the real demo screens. + @State private var selectedTab = Int(ProcessInfo.processInfo.environment["COMPATIBILITY_DEMO_TAB"] ?? "") ?? 0 + var body: some View { - TabView { + TabView(selection: $selectedTab) { if #available(watchOS 9, *) { CompatibilityEnvironmentTestView() + .accessibilityIdentifier("demo.compatibility") .tabItem { Text("Compatibility") } + .tag(0) DataStoreTestView() + .accessibilityIdentifier("demo.datastore") .tabItem { Text("DataStore") } + .tag(1) } // Application tracking has already registered the complete ordered module graph consumed here. AllTestsListView(additionalTests: Self.additionalTests) + .accessibilityIdentifier("demo.allTests") .tabItem { Text("All Tests") } + .tag(2) ClosureTestView() + .accessibilityIdentifier("demo.closure") .tabItem { Text("Closure") } + .tag(3) RandomBytesTestView() + .accessibilityIdentifier("demo.randomBytes") .tabItem { Text("Random Bytes") } + .tag(4) ConvertTestView() + .accessibilityIdentifier("demo.convert") .tabItem { Text("Convert") } + .tag(5) TriangleShowcaseView() + .accessibilityIdentifier("demo.triangle") .tabItem { Text("Triangle Showcase") } + .tag(6) FillAndStrokeTest() + .accessibilityIdentifier("demo.fillAndStroke") .tabItem { Text("Fill & Stroke") } + .tag(7) PlacardShowcaseView() + .accessibilityIdentifier("demo.placard") .tabItem { Text("Placard Showcase") } + .tag(8) MaterialTestView() + .accessibilityIdentifier("demo.material") .tabItem { Text("Material") } + .tag(9) } .backport.tabViewStyle(.page) } From a69224021ef4b4f5f695d94c8f9decf957ed2103 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 23:02:43 -0400 Subject: [PATCH 088/107] Exercise every demo screen in UI coverage --- .../CompatibilityUITests.swift | 137 ++++++++++++++---- 1 file changed, 106 insertions(+), 31 deletions(-) diff --git a/Development/CompatibilityUITests/CompatibilityUITests.swift b/Development/CompatibilityUITests/CompatibilityUITests.swift index 768658a..c360db8 100644 --- a/Development/CompatibilityUITests/CompatibilityUITests.swift +++ b/Development/CompatibilityUITests/CompatibilityUITests.swift @@ -30,61 +30,136 @@ private extension XCUIElement { var backport: XCUIElementBackport { XCUIElementBackport(element: self) } } -/// Smoke tests for the Compatibility demo application. +/// UI coverage for the Compatibility demo application. /// -/// These tests intentionally launch the real demo app instead of constructing -/// views directly so Xcode coverage sees the SwiftUI app, scene, tab container, -/// and first visible content path as user-facing code. +/// Each launch renders one real demo page so coverage does not depend on how a platform exposes +/// page-style TabView controls to XCTest. Interactive pages also exercise representative controls. final class CompatibilityUITests: XCTestCase { + private struct DemoScreen { + let index: Int + let name: String + let identifier: String + } + + private let screens = [ + DemoScreen(index: 0, name: "Compatibility", identifier: "demo.compatibility"), + DemoScreen(index: 1, name: "DataStore", identifier: "demo.datastore"), + DemoScreen(index: 2, name: "All Tests", identifier: "demo.allTests"), + DemoScreen(index: 3, name: "Closure", identifier: "demo.closure"), + DemoScreen(index: 4, name: "Random Bytes", identifier: "demo.randomBytes"), + DemoScreen(index: 5, name: "Convert", identifier: "demo.convert"), + DemoScreen(index: 6, name: "Triangle Showcase", identifier: "demo.triangle"), + DemoScreen(index: 7, name: "Fill & Stroke", identifier: "demo.fillAndStroke"), + DemoScreen(index: 8, name: "Placard Showcase", identifier: "demo.placard"), + DemoScreen(index: 9, name: "Material", identifier: "demo.material"), + ] + override func setUpWithError() throws { continueAfterFailure = false } @MainActor - func testDemoAppLaunchesAndShowsCompatibilityContent() throws { + func testEveryDemoScreenAndRepresentativeInteractions() throws { let app = XCUIApplication() - - // Ignore saved state so the smoke test starts from the same first tab - // even when Xcode or a previous manual run restored another demo page. app.launchArguments += ["-ApplePersistenceIgnoreState", "YES"] - // UI tests run out of process, so explicitly pass the generic testing environment to the app under test. app.launchEnvironment["TESTING"] = "1" - app.launch() - XCTAssertTrue(app.wait(for: .runningForeground, timeout: 15), "CompatibilityTest app should launch into the foreground.") + for screen in screens { + app.launchEnvironment["COMPATIBILITY_DEMO_TAB"] = String(screen.index) + app.launch() - // The first tab renders environment/application sections, which proves - // the app scene and core Compatibility SwiftUI demo path are visible. - XCTAssertTrue(waitForAnyText(["Application", "Compatibility", "iCloud"], in: app), "The Compatibility demo should show its first-page sections.") + XCTAssertTrue(app.wait(for: .runningForeground, timeout: 15), "\(screen.name) should launch into the foreground.") + XCTAssertTrue( + app.descendants(matching: .any)[screen.identifier].waitForExistence(timeout: 10), + "\(screen.name) should render its demo screen." + ) - // Tapping exposed tab labels exercises additional demo pages on - // platforms where SwiftUI exposes the page/tab controls to UI testing. - for tabName in ["DataStore", "All Tests", "Closure", "Random Bytes", "Convert"] { - tapIfPresent(tabName, in: app) + exercise(screen: screen, in: app) + app.terminate() } } @MainActor - private func waitForAnyText(_ labels: [String], in app: XCUIApplication, timeout: TimeInterval = 10) -> Bool { - for label in labels { - if app.staticTexts[label].waitForExistence(timeout: timeout) { - return true + private func exercise(screen: DemoScreen, in app: XCUIApplication) { + switch screen.index { + case 0: + // Exercise the expandable environment presentation when XCTest exposes it as an actionable element. + tapFirstHittableElement(in: app) + + case 1: + // DataStore is a long form. Scrolling forces lazy rows and their bindings to render. + scrollThroughCurrentScreen(in: app, passes: 5) + + case 2: + // The complete test list is deliberately long; traverse it so off-screen test rows are rendered. + scrollThroughCurrentScreen(in: app, passes: 12) + + case 3: + // Closure/Menu contains a radial layout plus menu controls; render the complete page and + // activate the first safe exposed button when one is available. + tapFirstHittableElement(in: app) + + case 4: + // Random Bytes is a List, so scrolling renders the full range of BytesView rows. + scrollThroughCurrentScreen(in: app, passes: 6) + + case 5: + // Exercise Binding.convert through the Convert screen's slider. + let slider = app.sliders.firstMatch + if slider.waitForExistence(timeout: 2) { + slider.adjust(toNormalizedSliderPosition: 0.75) } + + case 6: + // Exercise Triangle drawing and navigationDestination, then return to the showcase. + let button = app.buttons.firstMatch + if button.waitForExistence(timeout: 2) && button.isHittable { + button.backport.tap() + let destination = app.buttons["Navigation Destination TestCase"] + if destination.waitForExistence(timeout: 2) { + destination.backport.tap() + } + } + + case 7, 8, 9: + // These pages are primarily visual; rendering them is the behavior under test. + break + + default: + XCTFail("Unexpected Compatibility demo screen index: \(screen.index)") } - return false } @MainActor - private func tapIfPresent(_ label: String, in app: XCUIApplication) { - let button = app.buttons[label] - if button.waitForExistence(timeout: 1) { - button.backport.tap() - return + private func scrollThroughCurrentScreen(in app: XCUIApplication, passes: Int) { + let scrollView = app.scrollViews.firstMatch + let table = app.tables.firstMatch + let collection = app.collectionViews.firstMatch + + let scrollable: XCUIElement + if scrollView.exists { + scrollable = scrollView + } else if table.exists { + scrollable = table + } else if collection.exists { + scrollable = collection + } else { + scrollable = app } - let text = app.staticTexts[label] - if text.waitForExistence(timeout: 1) { - text.backport.tap() + for _ in 0.. Date: Sat, 15 Aug 2026 23:03:08 -0400 Subject: [PATCH 089/107] Exercise safe controls in UI coverage tour --- .../CompatibilityUITests.swift | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Development/CompatibilityUITests/CompatibilityUITests.swift b/Development/CompatibilityUITests/CompatibilityUITests.swift index c360db8..32d0da6 100644 --- a/Development/CompatibilityUITests/CompatibilityUITests.swift +++ b/Development/CompatibilityUITests/CompatibilityUITests.swift @@ -83,8 +83,8 @@ final class CompatibilityUITests: XCTestCase { private func exercise(screen: DemoScreen, in app: XCUIApplication) { switch screen.index { case 0: - // Exercise the expandable environment presentation when XCTest exposes it as an actionable element. - tapFirstHittableElement(in: app) + // Rendering the environment page exercises its application/module fields and environment presentation. + break case 1: // DataStore is a long form. Scrolling forces lazy rows and their bindings to render. @@ -95,9 +95,15 @@ final class CompatibilityUITests: XCTestCase { scrollThroughCurrentScreen(in: app, passes: 12) case 3: - // Closure/Menu contains a radial layout plus menu controls; render the complete page and - // activate the first safe exposed button when one is available. - tapFirstHittableElement(in: app) + // Open the real menu when exposed so Menu callbacks and menu-item construction are covered. + let symbols = app.buttons["Symbols"] + if symbols.waitForExistence(timeout: 2) && symbols.isHittable { + symbols.backport.tap() + let star = app.buttons["star"] + if star.waitForExistence(timeout: 2) && star.isHittable { + star.backport.tap() + } + } case 4: // Random Bytes is a List, so scrolling renders the full range of BytesView rows. @@ -154,13 +160,5 @@ final class CompatibilityUITests: XCTestCase { scrollable.swipeDown() } } - - @MainActor - private func tapFirstHittableElement(in app: XCUIApplication) { - for button in app.buttons.allElementsBoundByIndex where button.isHittable { - button.backport.tap() - return - } - } } #endif From e4e1d18daf330eadf8095f1bfb804479b2b698ca Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 23:03:39 -0400 Subject: [PATCH 090/107] Set Compatibility version to 1.19.0 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 6614750..e0c7c0c 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ // This file is automatically generated. // Do not edit it by hand because the contents will be replaced. -let version = "1.18.3" +let version = "1.19.0" let packageLibraryName = "Compatibility" #if canImport(PackageDescription) From ecc7fe4c7b65afd4e8dd5c9248b339aa4831cfaf Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 23:03:57 -0400 Subject: [PATCH 091/107] Set public Compatibility version to 1.19.0 --- Sources/Compatibility.swift | 312 +++++------------------------------- 1 file changed, 42 insertions(+), 270 deletions(-) diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 2afbbb1..b98bb2c 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -8,7 +8,7 @@ public enum Compatibility: Module { /// The version of the Compatibility Library since cannot get directly from Package.swift. - public static let version: Version = "1.18.3" + public static let version: Version = "1.19.0" /// Public source repository for Compatibility so support reports can direct developers to its source and issue history. /// @@ -78,306 +78,78 @@ public enum Compatibility: Module { /* For module checks to conditionally compile for versions: - - canImport(StoreKit) - iOS 3.0+ - iPadOS 3.0+ - macOS 10.7+ - Mac Catalyst 13.0+ - tvOS 9.0+ - watchOS 6.2+ - visionOS 1.0+ - - 2014 (Swift announced, for OperatingSystemVersion) - canImport(HealthKit) || canImport(Metal) - iOS 8.0+ // Health, Metal - iPadOS 8.0+ // Health, Metal - macOS 10.10+ - Mac Catalyst 13.0+ // Metal - tvOS 9.0+ // Metal - watchOS 2.0+ // Health - visionOS 1.0+ // Health, Metal - 2015 (initial relase of tvOS) - iOS 9 - macOS 10.11 - - 2016 - iOS 10 - macOS 10.12 - - 2017 - canImport(CoreML) - iOS 11 - macOS 10.13 (High Sierra) - tvOS 11 - watchOS 4 - - 2018 - iOS 12 - macOS 10.14 - tvOS 12 - watchOS 5 - - 2019 (first year macCatalyst and SwiftUI available) - canImport(SwiftUI) || canImport(Combine) - iOS 13+ - iPadOS 13.0+ - macOS 10.15+ - Mac Catalyst 13.0+ - tvOS 13+ - watchOS 6+ - visionOS 1.0+ - SF Symbols 1.0 + #if canImport(Compatibility) + import Compatibility + #endif - 2020 - canImport(AppleArchive) - iOS 14+ - iPadOS 14.0+ - macOS 11+ - Mac Catalyst 14.0+ - tvOS 14+ - watchOS 7+ - visionOS 1.0+ - SF Symbols 2.0 + #if canImport(Compatibility) && compiler(>=5.8) + // Compatibility is imported and the Swift compiler is new enough for the feature being used. + #endif - 2021 - canImport(GroupActivities) - iOS 15+ (last supported by iPhone 7) - iPadOS 15.0+ - macOS 12+ (last supported by Touchbook) - Mac Catalyst 15.0+ - tvOS 15+ - NOTE: NO WATCH OS SUPPORT (watchOS 8 is the last supported by Series 3) - visionOS 1.0+ - SF Symbols 3.0 - - 2022 Swift 5.7 (September) - canImport(Charts) canImport(AppIntents) canImport(CoreTransferable) - iOS 16+ - iPadOS 16.0+ - macOS 13+ - Mac Catalyst 16.0+ - tvOS 16+ - watchOS 9+ (minimum for WidgetKit on watchOS - supported in iOS 14 and macOS 11) - visionOS 1.0+ - SF Symbols 4.0 - - 2023 Swift 5.8 (March), Swift 5.9 (September) (added #Preview syntax and @availability syntax) - canImport(SwiftData) - iOS 17+ - iPadOS 17.0+ - macOS 14+ - Mac Catalyst 17.0+ - tvOS 17+ - watchOS 10+ (practical minimum for WidgetKit (due to requirement of WidgetConfigurationIntent which is only available on iOS 17, macOS 14, and watchOS 10) - visionOS 1.0+ - SF Symbols 5.0 - -2024 Swift 5.10 (March), Swift 6 (September) -canImport(Testing) - iOS 18+ - iPadOS 18+ - macOS 15+ - Mac Catalyst 18+ - tvOS 18+ - watchOS 11+ - visionOS 2+ - SF Symbols 6.0 - Xcode 16 - - Swift Playgrounds 4.6.4 - Swift 6.0 Compiler - - 2025 Swift 6.1 (March), Swift 6.2 (September) - iOS 26+ - iPadOS 26+ - macOS 26+ - Mac Catalyst 26+ - tvOS 26+ - watchOS 26+ - visionOS 26+ - SF Symbols 7.0 - Xcode 26 - - In Swift 6.2, Foundation is not available in WASM - */ -// MARK: - Configuration - -public extension Compatibility { - // https://medium.com/@aliyasirali/understanding-nonisolated-unsafe-in-swift-incremental-adoption-of-strict-concurrency-2cbb61c9adf4 - // This generates unsafe warnings anyways, so use the simpler version and hope there are no data races (theoretically, if we're only changing on the main thread first thing at init, this shouldn't be a problem) -// private static var lock = NSLock() -// private static var _settings = CompatibilityConfiguration() -// static var settings: CompatibilityConfiguration { -// get { -// lock.lock() -// defer { lock.unlock() } -// return _settings -// } -// set { -// lock.lock() -// defer { lock.unlock() } -// _settings = newValue -// } -// } -// -#if compiler(>=5.10) - static nonisolated(unsafe) var settings = CompatibilityConfiguration() -#else - static var settings = CompatibilityConfiguration() -#endif -} - -// for flags in swift packages: https://stackoverflow.com/questions/38813906/swift-how-to-use-preprocessor-flags-like-if-debug-to-implement-api-keys -//swiftSettings: [ -// .define("VAPOR") -//] -// https://medium.com/@ytyubox/xcode-preprocessing-with-custom-flags-in-swift-4bfde6e7a608 - -// MARK: - legacy compatibility code deprecations and support -public extension Compatibility { // for brief period where Application wasn't available - @available(*, deprecated, renamed: "Application.isDebug") - static let isDebug = _isDebugAssertConfiguration() -} -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) -public extension Compatibility { // for brief period where Application and Build wasn't available. Static computed properties apparently aren't supported in extensions in iOS <13? - // MARK: - Entitlements Information -#if canImport(Foundation) - @available(*, deprecated, renamed: "Application.iCloudSupported") - @MainActor - static var iCloudSupported: Bool { - get { - Application.iCloudSupported - } - set { - Application.iCloudSupported = newValue - } - } - - @available(*, deprecated, renamed: "Application.iCloudIsEnabled") - @MainActor - static var iCloudIsEnabled: Bool { - Application.iCloudIsEnabled - } - - @available(*, deprecated, renamed: "Application.iCloudStatus") - @MainActor - static var iCloudStatus: CloudStatus { - Application.iCloudStatus - } -#endif - - @available(*, deprecated, renamed: "Build.isSimulator") - static let isSimulator = Build.isSimulator - - @available(*, deprecated, renamed: "Build.isPlayground") - static let isPlayground = Build.isPlayground - - @available(*, deprecated, renamed: "Build.isPreview") - static let isPreview = Build.isPreview - - @available(*, deprecated, renamed: "Build.isMacCatalyst") - static let isMacCatalyst = Build.isMacCatalyst -} #if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) import SwiftUI -@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) +@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) public struct CompatibilityEnvironmentTestView: View { -#if compiler(>=5.9) && canImport(Combine) - @CloudStorage(.compatibilityVersionsRunKey) var previouslyRunCompatibilityVersions = Compatibility.version.rawValue -#endif - /// Complete deferred module information; `nil` keeps the loading state distinct from the portable baseline. - @State private var loadedModuleInfo: [Field]? - - /// Creates an environment view whose module metadata is loaded after the UI first appears. + @State private var previouslyRunCompatibilityVersions: [Version] = [] + public init() {} - - /// Structured application fields displayed by the environment test view. - public var applicationInfo: [Field] { - var info = [ - Field("Name", "\(Application.main.name) (\(Application.main.appName).app)"), - Field("App Identifier", Application.main.appIdentifier), - Field("App Version", "v\(Application.main.debugVersion)"), - Field("is first run", Application.main.isFirstRun), + + @MainActor + private var applicationInfo: [Field] { + var info: [Field] = [ + Field("Application", Application.main.name), + Field("Version", Application.main.version), + Field("Build", Bundle.main.build), + Field("Bundle ID", Application.main.appIdentifier), ] - let previousVersions = Application.main.previouslyRunVersions - if previousVersions.count > 0 { - info.append(Field("Previously run versions", previousVersions.pretty)) + if Application.iCloudSupported { + info.append(Field("iCloud", Application.iCloudStatus)) } return info } - /// Structured Compatibility-version and build-mode fields displayed by the environment test view. - public var compatibilityInfo: [Field] { + @MainActor + private var compatibilityInfo: [Field] { var info = [ - Field("\(Compatibility.moduleName) Version", Compatibility.version), - Field("is Debug", Build.isDebug), + Field("Compatibility", Compatibility.version), ] -#if compiler(>=5.9) && canImport(Combine) - if previouslyRunCompatibilityVersions != "" && previouslyRunCompatibilityVersions != "\(Compatibility.version.rawValue)" { - info += [ - Field("Previously run Compatibility versions", previouslyRunCompatibilityVersions), - Field(nil, "NOTE: This only updates if we're running the DataStore test view and is not guaranteed to be run any other time or from any other app."), - ] + info += Compatibility.moduleInfo + info += Build.environments().map { environment in + Field(environment.label, environment.test, symbol: environment.symbolName) } -#endif return info } public var body: some View { List { - FieldSections([ - "Application": applicationInfo, - Compatibility.moduleName: compatibilityInfo, - "iCloud": [ - Field("Supported by app", Application.iCloudSupported), - Field("Enabled", Application.iCloudIsEnabled), - Field("iCloud status", Application.iCloudStatus), - ], - ]) - Section("Module Info") { - // Show the portable baseline immediately, then replace it with the complete loaded result. - // This is example code. Really this only needs to include moduleInfo since the detailed info is already included in other sections. - let displayedModuleInfo = loadedModuleInfo ?? Compatibility.moduleInfo - ForEach(displayedModuleInfo.indices, id: \.self) { index in - FieldView(displayedModuleInfo[index]) - } - if loadedModuleInfo == nil { - ProgressView("Loading module details…") - } + FieldSections(applicationInfo) + FieldSections(compatibilityInfo) + Section("Environments") { + EnvironmentsView() } - Section("Environment") { - FieldView(Field("Swift Version", Build.swiftVersion, symbol: "swift")) - FieldView(Field("Compiler Version", Build.compilerVersion)) - EnvironmentsView(Build.environments()) - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) + Section("Previously Run Compatibility Versions") { + if previouslyRunCompatibilityVersions.isEmpty { + Text("None") + } else { + ForEach(previouslyRunCompatibilityVersions, id: \.self) { version in + Text(version.description) + } + } } - FieldSections([ - "Dates": [ - Field("Now Backport", Date.nowBackport.pretty), - Field("Now MySQL", Date.nowBackport.mysqlDateTime), - Field("Now Numeric", Date.nowBackport.numericDateTime), - Field("Tomorrow", Date.tomorrow.pretty), - Field("Tomorrow Midnight", Date.tomorrowMidnight.pretty), - Field("Yesterday", Date.yesterday.pretty), - ], - ]) } .task { - // Await potentially slow details without delaying the portable module fields above. - loadedModuleInfo = await Compatibility.loadDetailedModuleInfo() + previouslyRunCompatibilityVersions = Application.main.previouslyRunVersions } + .backport.navigationTitle("Compatibility") } } -@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) -#Preview { +@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) +#Preview("Compatibility") { CompatibilityEnvironmentTestView() - .backport.scrollContentBackground(.hidden) - .background(.red) } #endif From 300a4417d630295b091392b61d6237be32e80daa Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 23:04:36 -0400 Subject: [PATCH 092/107] Restore Compatibility.swift and set version to 1.19.0 --- Sources/Compatibility.swift | 310 +++++++++++++++++++++++++++++++----- 1 file changed, 269 insertions(+), 41 deletions(-) diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index b98bb2c..81021f6 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -78,78 +78,306 @@ public enum Compatibility: Module { /* For module checks to conditionally compile for versions: + + canImport(StoreKit) + iOS 3.0+ + iPadOS 3.0+ + macOS 10.7+ + Mac Catalyst 13.0+ + tvOS 9.0+ + watchOS 6.2+ + visionOS 1.0+ + + 2014 (Swift announced, for OperatingSystemVersion) + canImport(HealthKit) || canImport(Metal) + iOS 8.0+ // Health, Metal + iPadOS 8.0+ // Health, Metal + macOS 10.10+ + Mac Catalyst 13.0+ // Metal + tvOS 9.0+ // Metal + watchOS 2.0+ // Health + visionOS 1.0+ // Health, Metal - #if canImport(Compatibility) - import Compatibility - #endif + 2015 (initial relase of tvOS) + iOS 9 + macOS 10.11 + + 2016 + iOS 10 + macOS 10.12 + + 2017 + canImport(CoreML) + iOS 11 + macOS 10.13 (High Sierra) + tvOS 11 + watchOS 4 + + 2018 + iOS 12 + macOS 10.14 + tvOS 12 + watchOS 5 + + 2019 (first year macCatalyst and SwiftUI available) + canImport(SwiftUI) || canImport(Combine) + iOS 13+ + iPadOS 13.0+ + macOS 10.15+ + Mac Catalyst 13.0+ + tvOS 13+ + watchOS 6+ + visionOS 1.0+ + SF Symbols 1.0 - #if canImport(Compatibility) && compiler(>=5.8) - // Compatibility is imported and the Swift compiler is new enough for the feature being used. - #endif + 2020 + canImport(AppleArchive) + iOS 14+ + iPadOS 14.0+ + macOS 11+ + Mac Catalyst 14.0+ + tvOS 14+ + watchOS 7+ + visionOS 1.0+ + SF Symbols 2.0 + 2021 + canImport(GroupActivities) + iOS 15+ (last supported by iPhone 7) + iPadOS 15.0+ + macOS 12+ (last supported by Touchbook) + Mac Catalyst 15.0+ + tvOS 15+ + NOTE: NO WATCH OS SUPPORT (watchOS 8 is the last supported by Series 3) + visionOS 1.0+ + SF Symbols 3.0 + + 2022 Swift 5.7 (September) + canImport(Charts) canImport(AppIntents) canImport(CoreTransferable) + iOS 16+ + iPadOS 16.0+ + macOS 13+ + Mac Catalyst 16.0+ + tvOS 16+ + watchOS 9+ (minimum for WidgetKit on watchOS - supported in iOS 14 and macOS 11) + visionOS 1.0+ + SF Symbols 4.0 + + 2023 Swift 5.8 (March), Swift 5.9 (September) (added #Preview syntax and @availability syntax) + canImport(SwiftData) + iOS 17+ + iPadOS 17.0+ + macOS 14+ + Mac Catalyst 17.0+ + tvOS 17+ + watchOS 10+ (practical minimum for WidgetKit (due to requirement of WidgetConfigurationIntent which is only available on iOS 17, macOS 14, and watchOS 10) + visionOS 1.0+ + SF Symbols 5.0 + +2024 Swift 5.10 (March), Swift 6 (September) +canImport(Testing) + iOS 18+ + iPadOS 18+ + macOS 15+ + Mac Catalyst 18+ + tvOS 18+ + watchOS 11+ + visionOS 2+ + SF Symbols 6.0 + Xcode 16 + + Swift Playgrounds 4.6.4 - Swift 6.0 Compiler + + 2025 Swift 6.1 (March), Swift 6.2 (September) + iOS 26+ + iPadOS 26+ + macOS 26+ + Mac Catalyst 26+ + tvOS 26+ + watchOS 26+ + visionOS 26+ + SF Symbols 7.0 + Xcode 26 + + In Swift 6.2, Foundation is not available in WASM + */ +// MARK: - Configuration + +public extension Compatibility { + // https://medium.com/@aliyasirali/understanding-nonisolated-unsafe-in-swift-incremental-adoption-of-strict-concurrency-2cbb61c9adf4 + // This generates unsafe warnings anyways, so use the simpler version and hope there are no data races (theoretically, if we're only changing on the main thread first thing at init, this shouldn't be a problem) +// private static var lock = NSLock() +// private static var _settings = CompatibilityConfiguration() +// static var settings: CompatibilityConfiguration { +// get { +// lock.lock() +// defer { lock.unlock() } +// return _settings +// } +// set { +// lock.lock() +// defer { lock.unlock() } +// _settings = newValue +// } +// } +// +#if compiler(>=5.10) + static nonisolated(unsafe) var settings = CompatibilityConfiguration() +#else + static var settings = CompatibilityConfiguration() +#endif +} + +// for flags in swift packages: https://stackoverflow.com/questions/38813906/swift-how-to-use-preprocessor-flags-like-if-debug-to-implement-api-keys +//swiftSettings: [ +// .define("VAPOR") +//] +// https://medium.com/@ytyubox/xcode-preprocessing-with-custom-flags-in-swift-4bfde6e7a608 + +// MARK: - legacy compatibility code deprecations and support +public extension Compatibility { // for brief period where Application wasn't available + @available(*, deprecated, renamed: "Application.isDebug") + static let isDebug = _isDebugAssertConfiguration() +} +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +public extension Compatibility { // for brief period where Application and Build wasn't available. Static computed properties apparently aren't supported in extensions in iOS <13? + // MARK: - Entitlements Information +#if canImport(Foundation) + @available(*, deprecated, renamed: "Application.iCloudSupported") + @MainActor + static var iCloudSupported: Bool { + get { + Application.iCloudSupported + } + set { + Application.iCloudSupported = newValue + } + } + + @available(*, deprecated, renamed: "Application.iCloudIsEnabled") + @MainActor + static var iCloudIsEnabled: Bool { + Application.iCloudIsEnabled + } + + @available(*, deprecated, renamed: "Application.iCloudStatus") + @MainActor + static var iCloudStatus: CloudStatus { + Application.iCloudStatus + } +#endif + + @available(*, deprecated, renamed: "Build.isSimulator") + static let isSimulator = Build.isSimulator + + @available(*, deprecated, renamed: "Build.isPlayground") + static let isPlayground = Build.isPlayground + + @available(*, deprecated, renamed: "Build.isPreview") + static let isPreview = Build.isPreview + + @available(*, deprecated, renamed: "Build.isMacCatalyst") + static let isMacCatalyst = Build.isMacCatalyst +} #if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) import SwiftUI -@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) +@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) public struct CompatibilityEnvironmentTestView: View { - @State private var previouslyRunCompatibilityVersions: [Version] = [] - +#if compiler(>=5.9) && canImport(Combine) + @CloudStorage(.compatibilityVersionsRunKey) var previouslyRunCompatibilityVersions = Compatibility.version.rawValue +#endif + /// Complete deferred module information; `nil` keeps the loading state distinct from the portable baseline. + @State private var loadedModuleInfo: [Field]? + + /// Creates an environment view whose module metadata is loaded after the UI first appears. public init() {} - - @MainActor - private var applicationInfo: [Field] { - var info: [Field] = [ - Field("Application", Application.main.name), - Field("Version", Application.main.version), - Field("Build", Bundle.main.build), - Field("Bundle ID", Application.main.appIdentifier), + + /// Structured application fields displayed by the environment test view. + public var applicationInfo: [Field] { + var info = [ + Field("Name", "\(Application.main.name) (\(Application.main.appName).app)"), + Field("App Identifier", Application.main.appIdentifier), + Field("App Version", "v\(Application.main.debugVersion)"), + Field("is first run", Application.main.isFirstRun), ] - if Application.iCloudSupported { - info.append(Field("iCloud", Application.iCloudStatus)) + let previousVersions = Application.main.previouslyRunVersions + if previousVersions.count > 0 { + info.append(Field("Previously run versions", previousVersions.pretty)) } return info } - @MainActor - private var compatibilityInfo: [Field] { + /// Structured Compatibility-version and build-mode fields displayed by the environment test view. + public var compatibilityInfo: [Field] { var info = [ - Field("Compatibility", Compatibility.version), + Field("\(Compatibility.moduleName) Version", Compatibility.version), + Field("is Debug", Build.isDebug), ] - info += Compatibility.moduleInfo - info += Build.environments().map { environment in - Field(environment.label, environment.test, symbol: environment.symbolName) +#if compiler(>=5.9) && canImport(Combine) + if previouslyRunCompatibilityVersions != "" && previouslyRunCompatibilityVersions != "\(Compatibility.version.rawValue)" { + info += [ + Field("Previously run Compatibility versions", previouslyRunCompatibilityVersions), + Field(nil, "NOTE: This only updates if we're running the DataStore test view and is not guaranteed to be run any other time or from any other app."), + ] } +#endif return info } public var body: some View { List { - FieldSections(applicationInfo) - FieldSections(compatibilityInfo) - Section("Environments") { - EnvironmentsView() - } - Section("Previously Run Compatibility Versions") { - if previouslyRunCompatibilityVersions.isEmpty { - Text("None") - } else { - ForEach(previouslyRunCompatibilityVersions, id: \.self) { version in - Text(version.description) - } + FieldSections([ + "Application": applicationInfo, + Compatibility.moduleName: compatibilityInfo, + "iCloud": [ + Field("Supported by app", Application.iCloudSupported), + Field("Enabled", Application.iCloudIsEnabled), + Field("iCloud status", Application.iCloudStatus), + ], + ]) + Section("Module Info") { + // Show the portable baseline immediately, then replace it with the complete loaded result. + // This is example code. Really this only needs to include moduleInfo since the detailed info is already included in other sections. + let displayedModuleInfo = loadedModuleInfo ?? Compatibility.moduleInfo + ForEach(displayedModuleInfo.indices, id: \.self) { index in + FieldView(displayedModuleInfo[index]) } + if loadedModuleInfo == nil { + ProgressView("Loading module details…") + } + } + Section("Environment") { + FieldView(Field("Swift Version", Build.swiftVersion, symbol: "swift")) + FieldView(Field("Compiler Version", Build.compilerVersion)) + EnvironmentsView(Build.environments()) + .frame(maxWidth: .infinity, alignment: .leading) + .contentShape(Rectangle()) } + FieldSections([ + "Dates": [ + Field("Now Backport", Date.nowBackport.pretty), + Field("Now MySQL", Date.nowBackport.mysqlDateTime), + Field("Now Numeric", Date.nowBackport.numericDateTime), + Field("Tomorrow", Date.tomorrow.pretty), + Field("Tomorrow Midnight", Date.tomorrowMidnight.pretty), + Field("Yesterday", Date.yesterday.pretty), + ], + ]) } .task { - previouslyRunCompatibilityVersions = Application.main.previouslyRunVersions + // Await potentially slow details without delaying the portable module fields above. + loadedModuleInfo = await Compatibility.loadDetailedModuleInfo() } - .backport.navigationTitle("Compatibility") } } -@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) -#Preview("Compatibility") { +@available(iOS 15, macOS 12, tvOS 15, watchOS 9, *) +#Preview { CompatibilityEnvironmentTestView() + .backport.scrollContentBackground(.hidden) + .background(.red) } #endif From 4e1c50d55107b9fc16e7781d33fde23d722b653b Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 23:52:14 -0400 Subject: [PATCH 093/107] Use natural TabView ordering for UI coverage --- Development/CompatibilityDemoView.swift | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Development/CompatibilityDemoView.swift b/Development/CompatibilityDemoView.swift index daf5ef4..6d7216b 100644 --- a/Development/CompatibilityDemoView.swift +++ b/Development/CompatibilityDemoView.swift @@ -49,25 +49,19 @@ struct CompatibilityDemoView: View { ] ] - // UI coverage tests can request a specific page directly. This avoids depending on - // platform-specific TabView accessibility while still rendering the real demo screens. - @State private var selectedTab = Int(ProcessInfo.processInfo.environment["COMPATIBILITY_DEMO_TAB"] ?? "") ?? 0 - var body: some View { - TabView(selection: $selectedTab) { + TabView { if #available(watchOS 9, *) { CompatibilityEnvironmentTestView() .accessibilityIdentifier("demo.compatibility") .tabItem { Text("Compatibility") } - .tag(0) DataStoreTestView() .accessibilityIdentifier("demo.datastore") .tabItem { Text("DataStore") } - .tag(1) } // Application tracking has already registered the complete ordered module graph consumed here. AllTestsListView(additionalTests: Self.additionalTests) @@ -75,49 +69,41 @@ struct CompatibilityDemoView: View { .tabItem { Text("All Tests") } - .tag(2) ClosureTestView() .accessibilityIdentifier("demo.closure") .tabItem { Text("Closure") } - .tag(3) RandomBytesTestView() .accessibilityIdentifier("demo.randomBytes") .tabItem { Text("Random Bytes") } - .tag(4) ConvertTestView() .accessibilityIdentifier("demo.convert") .tabItem { Text("Convert") } - .tag(5) TriangleShowcaseView() .accessibilityIdentifier("demo.triangle") .tabItem { Text("Triangle Showcase") } - .tag(6) FillAndStrokeTest() .accessibilityIdentifier("demo.fillAndStroke") .tabItem { Text("Fill & Stroke") } - .tag(7) PlacardShowcaseView() .accessibilityIdentifier("demo.placard") .tabItem { Text("Placard Showcase") } - .tag(8) MaterialTestView() .accessibilityIdentifier("demo.material") .tabItem { Text("Material") } - .tag(9) } .backport.tabViewStyle(.page) } From 7c8c40b4de72d776f89a06bdd83f70d8861a4715 Mon Sep 17 00:00:00 2001 From: kudit Date: Sat, 15 Aug 2026 23:52:31 -0400 Subject: [PATCH 094/107] Run demo UI coverage in one launch --- .../CompatibilityUITests.swift | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/Development/CompatibilityUITests/CompatibilityUITests.swift b/Development/CompatibilityUITests/CompatibilityUITests.swift index 32d0da6..0d040b7 100644 --- a/Development/CompatibilityUITests/CompatibilityUITests.swift +++ b/Development/CompatibilityUITests/CompatibilityUITests.swift @@ -32,26 +32,26 @@ private extension XCUIElement { /// UI coverage for the Compatibility demo application. /// -/// Each launch renders one real demo page so coverage does not depend on how a platform exposes -/// page-style TabView controls to XCTest. Interactive pages also exercise representative controls. +/// The app launches once and the test advances through the real page-style `TabView` in declaration order. +/// This deliberately avoids numeric selection tags so inserting or rearranging demo tabs does not require +/// keeping a second set of tab indices synchronized. final class CompatibilityUITests: XCTestCase { private struct DemoScreen { - let index: Int let name: String let identifier: String } private let screens = [ - DemoScreen(index: 0, name: "Compatibility", identifier: "demo.compatibility"), - DemoScreen(index: 1, name: "DataStore", identifier: "demo.datastore"), - DemoScreen(index: 2, name: "All Tests", identifier: "demo.allTests"), - DemoScreen(index: 3, name: "Closure", identifier: "demo.closure"), - DemoScreen(index: 4, name: "Random Bytes", identifier: "demo.randomBytes"), - DemoScreen(index: 5, name: "Convert", identifier: "demo.convert"), - DemoScreen(index: 6, name: "Triangle Showcase", identifier: "demo.triangle"), - DemoScreen(index: 7, name: "Fill & Stroke", identifier: "demo.fillAndStroke"), - DemoScreen(index: 8, name: "Placard Showcase", identifier: "demo.placard"), - DemoScreen(index: 9, name: "Material", identifier: "demo.material"), + DemoScreen(name: "Compatibility", identifier: "demo.compatibility"), + DemoScreen(name: "DataStore", identifier: "demo.datastore"), + DemoScreen(name: "All Tests", identifier: "demo.allTests"), + DemoScreen(name: "Closure", identifier: "demo.closure"), + DemoScreen(name: "Random Bytes", identifier: "demo.randomBytes"), + DemoScreen(name: "Convert", identifier: "demo.convert"), + DemoScreen(name: "Triangle Showcase", identifier: "demo.triangle"), + DemoScreen(name: "Fill & Stroke", identifier: "demo.fillAndStroke"), + DemoScreen(name: "Placard Showcase", identifier: "demo.placard"), + DemoScreen(name: "Material", identifier: "demo.material"), ] override func setUpWithError() throws { @@ -63,36 +63,36 @@ final class CompatibilityUITests: XCTestCase { let app = XCUIApplication() app.launchArguments += ["-ApplePersistenceIgnoreState", "YES"] app.launchEnvironment["TESTING"] = "1" + app.launch() - for screen in screens { - app.launchEnvironment["COMPATIBILITY_DEMO_TAB"] = String(screen.index) - app.launch() - - XCTAssertTrue(app.wait(for: .runningForeground, timeout: 15), "\(screen.name) should launch into the foreground.") + // `launch()` does not return until the application is running in the foreground, so an + // additional blocking application-state wait is unnecessary and can trigger a performance diagnostic. + for (index, screen) in screens.enumerated() { + let screenElement = app.descendants(matching: .any)[screen.identifier] XCTAssertTrue( - app.descendants(matching: .any)[screen.identifier].waitForExistence(timeout: 10), + screenElement.waitForExistence(timeout: 10), "\(screen.name) should render its demo screen." ) - exercise(screen: screen, in: app) - app.terminate() + exercise(screenAt: index, in: app) + + if index < screens.count - 1 { + advanceToNextPage(in: app) + } } } @MainActor - private func exercise(screen: DemoScreen, in app: XCUIApplication) { - switch screen.index { - case 0: - // Rendering the environment page exercises its application/module fields and environment presentation. + private func exercise(screenAt index: Int, in app: XCUIApplication) { + switch index { + case 0, 1, 4, 7, 8, 9: + // These pages are primarily exercised by rendering. Keep the UI tour fast and avoid + // synthetic scrolling where there is no behavior we specifically need to validate. break - case 1: - // DataStore is a long form. Scrolling forces lazy rows and their bindings to render. - scrollThroughCurrentScreen(in: app, passes: 5) - case 2: // The complete test list is deliberately long; traverse it so off-screen test rows are rendered. - scrollThroughCurrentScreen(in: app, passes: 12) + scrollThroughAllTests(in: app) case 3: // Open the real menu when exposed so Menu callbacks and menu-item construction are covered. @@ -105,10 +105,6 @@ final class CompatibilityUITests: XCTestCase { } } - case 4: - // Random Bytes is a List, so scrolling renders the full range of BytesView rows. - scrollThroughCurrentScreen(in: app, passes: 6) - case 5: // Exercise Binding.convert through the Convert screen's slider. let slider = app.sliders.firstMatch @@ -127,17 +123,22 @@ final class CompatibilityUITests: XCTestCase { } } - case 7, 8, 9: - // These pages are primarily visual; rendering them is the behavior under test. - break - default: - XCTFail("Unexpected Compatibility demo screen index: \(screen.index)") + XCTFail("Unexpected Compatibility demo screen index: \(index)") } } @MainActor - private func scrollThroughCurrentScreen(in app: XCUIApplication, passes: Int) { + private func advanceToNextPage(in app: XCUIApplication) { +#if os(tvOS) + XCUIRemote.shared.press(.right) +#else + app.swipeLeft() +#endif + } + + @MainActor + private func scrollThroughAllTests(in app: XCUIApplication) { let scrollView = app.scrollViews.firstMatch let table = app.tables.firstMatch let collection = app.collectionViews.firstMatch @@ -153,10 +154,10 @@ final class CompatibilityUITests: XCTestCase { scrollable = app } - for _ in 0.. Date: Sat, 15 Aug 2026 23:53:07 -0400 Subject: [PATCH 095/107] Add high-yield coverage gap tests --- .../CompatibilityTests/CoverageGapTests.swift | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 Development/CompatibilityTests/CoverageGapTests.swift diff --git a/Development/CompatibilityTests/CoverageGapTests.swift b/Development/CompatibilityTests/CoverageGapTests.swift new file mode 100644 index 0000000..507caf6 --- /dev/null +++ b/Development/CompatibilityTests/CoverageGapTests.swift @@ -0,0 +1,214 @@ +// +// CoverageGapTests.swift +// CompatibilityTests +// +// Focused tests for public code paths that are expensive to reach through the demo UI. +// + +#if compiler(>=5.9) && canImport(Compatibility) && canImport(Testing) +import Compatibility +import Testing +#if canImport(Foundation) +import Foundation +#endif +#if canImport(SwiftUI) +import SwiftUI +#endif + +#if !hasFeature(Embedded) +private struct IntrospectionFixture: PropertyIterable { + let name: String + let count: Int +} + +private enum IntrospectionNonObjectFixture: PropertyIterable { + case value +} + +private enum RawValueFixture: String { + case alpha + case beta +} + +private struct RawSequenceFixture: RawRepresentableSequence { + typealias Element = RawValueFixture + typealias RawValue = [String] + + private var storage: [Element] + + init(_ s: S) where S: Sequence, Element == S.Element { + storage = Array(s) + } + + init(arrayLiteral elements: Element...) { + storage = elements + } + + func makeIterator() -> Array.Iterator { + storage.makeIterator() + } +} + +private struct IdentifiableFixture: Identifiable, Equatable { + let id: Int + var value: String +} +#endif + +#if canImport(Foundation) +private struct FoundationCodingFixture: Codable, Equatable { + let eventDate: Date + let payload: Data + let score: Double + let camelCaseValue: String +} + +private enum ExpectedEncodingError: Error { + case expected +} + +private struct ThrowingEncodableFixture: Encodable { + func encode(to encoder: Encoder) throws { + throw ExpectedEncodingError.expected + } +} +#endif + +@Suite("Coverage Gap Tests") +struct CoverageGapTests { +#if !hasFeature(Embedded) + @Test("Property introspection and dynamic equality") + func propertyIntrospectionAndEquality() throws { + let fixture = IntrospectionFixture(name: "Compatibility", count: 19) + let properties = fixture.allProperties + + #expect(properties.count == 2) + #expect(properties["name"] as? String == "Compatibility") + #expect(properties["count"] as? Int == 19) + + let keyPaths = fixture.allKeyPaths + #expect(keyPaths.count == 2) + let nameKeyPath = try #require(keyPaths["name"]) + #expect(fixture[keyPath: nameKeyPath] as? String == "Compatibility") + + // Exercise the non-struct/class guard as well as matching, mismatched, and non-Equatable values. + #expect(IntrospectionNonObjectFixture.value.allProperties.isEmpty) + #expect(42.isEqual(42)) + #expect(!42.isEqual("42")) + #expect(areEqual(42, 42)) + #expect(!areEqual(42, "42")) + #expect(!areEqual(nil, nil)) + } + + @Test("RawRepresentable sequence conversion and identifiable array mutation") + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) + func collectionGapCoverage() { + let raw = RawSequenceFixture(rawValue: ["alpha", "invalid", "beta"]) + #expect(Array(raw) == [.alpha, .beta]) + #expect(raw.rawValue == ["alpha", "beta"]) + + var values = [ + IdentifiableFixture(id: 1, value: "one"), + IdentifiableFixture(id: 2, value: "two"), + ] + #expect(values[id: 2]?.value == "two") + values[id: 2] = IdentifiableFixture(id: 2, value: "updated") + #expect(values[id: 2]?.value == "updated") + + // These intentionally leave the array unchanged while exercising the guarded setter paths. + values[id: 99] = IdentifiableFixture(id: 99, value: "missing") + values[id: 1] = nil + #expect(values.count == 2) + #expect(values[id: 1]?.value == "one") + } +#endif + +#if canImport(Foundation) + @Test("Foundation dictionary coder strategies and failure path") + func foundationDictionaryCoderStrategies() throws { + let encoder = DictionaryEncoder() + encoder.dateEncodingStrategy = .secondsSince1970 + encoder.dataEncodingStrategy = .base64 + encoder.nonConformingFloatEncodingStrategy = .convertToString( + positiveInfinity: "INF", + negativeInfinity: "-INF", + nan: "NaN" + ) + encoder.keyEncodingStrategy = .convertToSnakeCase + + // Read each public strategy back as well as setting it; these accessors are part of the wrapper API. + _ = encoder.dateEncodingStrategy + _ = encoder.dataEncodingStrategy + _ = encoder.nonConformingFloatEncodingStrategy + _ = encoder.keyEncodingStrategy + + let fixture = FoundationCodingFixture( + eventDate: Date(timeIntervalSince1970: 12_345), + payload: Data([0, 1, 2, 3]), + score: .infinity, + camelCaseValue: "value" + ) + let encoded = try #require(try encoder.encode(fixture) as? [String: Any]) + #expect(encoded["event_date"] != nil) + #expect(encoded["camel_case_value"] as? String == "value") + #expect(encoded["score"] as? String == "INF") + + let decoder = DictionaryDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.dataDecodingStrategy = .base64 + decoder.nonConformingFloatDecodingStrategy = .convertFromString( + positiveInfinity: "INF", + negativeInfinity: "-INF", + nan: "NaN" + ) + decoder.keyDecodingStrategy = .convertFromSnakeCase + + _ = decoder.dateDecodingStrategy + _ = decoder.dataDecodingStrategy + _ = decoder.nonConformingFloatDecodingStrategy + _ = decoder.keyDecodingStrategy + + let decoded = try decoder.decode(FoundationCodingFixture.self, from: encoded) + #expect(decoded == fixture) + + // Verify the convenience API's documented failure behavior for an Encodable that throws. + #expect(ThrowingEncodableFixture().asDictionary() == nil) + } + + @Test("OrderedSet Codable, hashing, filtering, and reflection") + func orderedSetGapCoverage() throws { + let original: OrderedSet = [3, 1, 3, 2] + #expect(Array(original) == [3, 1, 2]) + + let data = try JSONEncoder().encode(original) + let decoded = try JSONDecoder().decode(OrderedSet.self, from: data) + #expect(decoded == original) + + var hasher = Hasher() + original.hash(into: &hasher) + _ = hasher.finalize() + + #expect(Array(original.filter { $0 > 1 }) == [3, 2]) + _ = original.customMirror + } +#endif + +#if canImport(SwiftUI) && canImport(Foundation) + @Test("Shape path generation") + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) + func shapePathGeneration() { + let rect = CGRect(x: 10, y: 20, width: 200, height: 120) + + for edge in Edge.allCases { + let bounds = Triangle(flatEdge: edge).path(in: rect).boundingRect + #expect(bounds.width > 0) + #expect(bounds.height > 0) + } + + let placardBounds = Placard().path(in: rect).boundingRect + #expect(placardBounds.width > 0) + #expect(placardBounds.height > 0) + } +#endif +} +#endif From 4f7a17168926d4f017c78f3c7d92e5bcf6aacb58 Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 08:57:58 -0400 Subject: [PATCH 096/107] Fix identifiable array subscript test syntax --- .../CompatibilityTests/CoverageGapTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Development/CompatibilityTests/CoverageGapTests.swift b/Development/CompatibilityTests/CoverageGapTests.swift index 507caf6..0aa66a6 100644 --- a/Development/CompatibilityTests/CoverageGapTests.swift +++ b/Development/CompatibilityTests/CoverageGapTests.swift @@ -111,15 +111,15 @@ struct CoverageGapTests { IdentifiableFixture(id: 1, value: "one"), IdentifiableFixture(id: 2, value: "two"), ] - #expect(values[id: 2]?.value == "two") - values[id: 2] = IdentifiableFixture(id: 2, value: "updated") - #expect(values[id: 2]?.value == "updated") + #expect(values[2]?.value == "two") + values[2] = IdentifiableFixture(id: 2, value: "updated") + #expect(values[2]?.value == "updated") // These intentionally leave the array unchanged while exercising the guarded setter paths. - values[id: 99] = IdentifiableFixture(id: 99, value: "missing") - values[id: 1] = nil + values[99] = IdentifiableFixture(id: 99, value: "missing") + values[1] = nil #expect(values.count == 2) - #expect(values[id: 1]?.value == "one") + #expect(values[1]?.value == "one") } #endif From 9e4dfce4f70b62849ee5b213d39916956cb0b036 Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 09:39:29 -0400 Subject: [PATCH 097/107] Add reusable introspection coverage tests --- Sources/Foundation/Introspection.swift | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Sources/Foundation/Introspection.swift b/Sources/Foundation/Introspection.swift index cef4e8d..6e910e4 100644 --- a/Sources/Foundation/Introspection.swift +++ b/Sources/Foundation/Introspection.swift @@ -78,3 +78,41 @@ public func areEqual(_ left: Any?, _ right: Any?) -> Bool { return false #endif } + +#if compiler(>=5.9) && !hasFeature(Embedded) +private struct IntrospectionTestFixture: PropertyIterable { + let name: String + let count: Int +} + +private enum IntrospectionNonObjectTestFixture: PropertyIterable { + case value +} + +@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +@MainActor +let introspectionTests: [TestCase] = [ + TestCase("Property iteration and dynamic equality") { + let fixture = IntrospectionTestFixture(name: "Compatibility", count: 19) + let properties = fixture.allProperties + try expect(properties.count == 2) + try expect(properties["name"] as? String == "Compatibility") + try expect(properties["count"] as? Int == 19) + + let keyPaths = fixture.allKeyPaths + try expect(keyPaths.count == 2) + if let nameKeyPath = keyPaths["name"] { + try expect(fixture[keyPath: nameKeyPath] as? String == "Compatibility") + } else { + try expect(false, "Expected name key path") + } + + try expect(IntrospectionNonObjectTestFixture.value.allProperties.isEmpty) + try expect(42.isEqual(42)) + try expect(!42.isEqual("42")) + try expect(areEqual(42, 42)) + try expect(!areEqual(42, "42")) + try expect(!areEqual(nil, nil)) + }, +] +#endif From 6e00dc44292f43d6f487766ed8d691bcb495db1f Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 09:39:52 -0400 Subject: [PATCH 098/107] Remove standalone coverage gap test file --- .../CompatibilityTests/CoverageGapTests.swift | 214 ------------------ 1 file changed, 214 deletions(-) delete mode 100644 Development/CompatibilityTests/CoverageGapTests.swift diff --git a/Development/CompatibilityTests/CoverageGapTests.swift b/Development/CompatibilityTests/CoverageGapTests.swift deleted file mode 100644 index 0aa66a6..0000000 --- a/Development/CompatibilityTests/CoverageGapTests.swift +++ /dev/null @@ -1,214 +0,0 @@ -// -// CoverageGapTests.swift -// CompatibilityTests -// -// Focused tests for public code paths that are expensive to reach through the demo UI. -// - -#if compiler(>=5.9) && canImport(Compatibility) && canImport(Testing) -import Compatibility -import Testing -#if canImport(Foundation) -import Foundation -#endif -#if canImport(SwiftUI) -import SwiftUI -#endif - -#if !hasFeature(Embedded) -private struct IntrospectionFixture: PropertyIterable { - let name: String - let count: Int -} - -private enum IntrospectionNonObjectFixture: PropertyIterable { - case value -} - -private enum RawValueFixture: String { - case alpha - case beta -} - -private struct RawSequenceFixture: RawRepresentableSequence { - typealias Element = RawValueFixture - typealias RawValue = [String] - - private var storage: [Element] - - init(_ s: S) where S: Sequence, Element == S.Element { - storage = Array(s) - } - - init(arrayLiteral elements: Element...) { - storage = elements - } - - func makeIterator() -> Array.Iterator { - storage.makeIterator() - } -} - -private struct IdentifiableFixture: Identifiable, Equatable { - let id: Int - var value: String -} -#endif - -#if canImport(Foundation) -private struct FoundationCodingFixture: Codable, Equatable { - let eventDate: Date - let payload: Data - let score: Double - let camelCaseValue: String -} - -private enum ExpectedEncodingError: Error { - case expected -} - -private struct ThrowingEncodableFixture: Encodable { - func encode(to encoder: Encoder) throws { - throw ExpectedEncodingError.expected - } -} -#endif - -@Suite("Coverage Gap Tests") -struct CoverageGapTests { -#if !hasFeature(Embedded) - @Test("Property introspection and dynamic equality") - func propertyIntrospectionAndEquality() throws { - let fixture = IntrospectionFixture(name: "Compatibility", count: 19) - let properties = fixture.allProperties - - #expect(properties.count == 2) - #expect(properties["name"] as? String == "Compatibility") - #expect(properties["count"] as? Int == 19) - - let keyPaths = fixture.allKeyPaths - #expect(keyPaths.count == 2) - let nameKeyPath = try #require(keyPaths["name"]) - #expect(fixture[keyPath: nameKeyPath] as? String == "Compatibility") - - // Exercise the non-struct/class guard as well as matching, mismatched, and non-Equatable values. - #expect(IntrospectionNonObjectFixture.value.allProperties.isEmpty) - #expect(42.isEqual(42)) - #expect(!42.isEqual("42")) - #expect(areEqual(42, 42)) - #expect(!areEqual(42, "42")) - #expect(!areEqual(nil, nil)) - } - - @Test("RawRepresentable sequence conversion and identifiable array mutation") - @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) - func collectionGapCoverage() { - let raw = RawSequenceFixture(rawValue: ["alpha", "invalid", "beta"]) - #expect(Array(raw) == [.alpha, .beta]) - #expect(raw.rawValue == ["alpha", "beta"]) - - var values = [ - IdentifiableFixture(id: 1, value: "one"), - IdentifiableFixture(id: 2, value: "two"), - ] - #expect(values[2]?.value == "two") - values[2] = IdentifiableFixture(id: 2, value: "updated") - #expect(values[2]?.value == "updated") - - // These intentionally leave the array unchanged while exercising the guarded setter paths. - values[99] = IdentifiableFixture(id: 99, value: "missing") - values[1] = nil - #expect(values.count == 2) - #expect(values[1]?.value == "one") - } -#endif - -#if canImport(Foundation) - @Test("Foundation dictionary coder strategies and failure path") - func foundationDictionaryCoderStrategies() throws { - let encoder = DictionaryEncoder() - encoder.dateEncodingStrategy = .secondsSince1970 - encoder.dataEncodingStrategy = .base64 - encoder.nonConformingFloatEncodingStrategy = .convertToString( - positiveInfinity: "INF", - negativeInfinity: "-INF", - nan: "NaN" - ) - encoder.keyEncodingStrategy = .convertToSnakeCase - - // Read each public strategy back as well as setting it; these accessors are part of the wrapper API. - _ = encoder.dateEncodingStrategy - _ = encoder.dataEncodingStrategy - _ = encoder.nonConformingFloatEncodingStrategy - _ = encoder.keyEncodingStrategy - - let fixture = FoundationCodingFixture( - eventDate: Date(timeIntervalSince1970: 12_345), - payload: Data([0, 1, 2, 3]), - score: .infinity, - camelCaseValue: "value" - ) - let encoded = try #require(try encoder.encode(fixture) as? [String: Any]) - #expect(encoded["event_date"] != nil) - #expect(encoded["camel_case_value"] as? String == "value") - #expect(encoded["score"] as? String == "INF") - - let decoder = DictionaryDecoder() - decoder.dateDecodingStrategy = .secondsSince1970 - decoder.dataDecodingStrategy = .base64 - decoder.nonConformingFloatDecodingStrategy = .convertFromString( - positiveInfinity: "INF", - negativeInfinity: "-INF", - nan: "NaN" - ) - decoder.keyDecodingStrategy = .convertFromSnakeCase - - _ = decoder.dateDecodingStrategy - _ = decoder.dataDecodingStrategy - _ = decoder.nonConformingFloatDecodingStrategy - _ = decoder.keyDecodingStrategy - - let decoded = try decoder.decode(FoundationCodingFixture.self, from: encoded) - #expect(decoded == fixture) - - // Verify the convenience API's documented failure behavior for an Encodable that throws. - #expect(ThrowingEncodableFixture().asDictionary() == nil) - } - - @Test("OrderedSet Codable, hashing, filtering, and reflection") - func orderedSetGapCoverage() throws { - let original: OrderedSet = [3, 1, 3, 2] - #expect(Array(original) == [3, 1, 2]) - - let data = try JSONEncoder().encode(original) - let decoded = try JSONDecoder().decode(OrderedSet.self, from: data) - #expect(decoded == original) - - var hasher = Hasher() - original.hash(into: &hasher) - _ = hasher.finalize() - - #expect(Array(original.filter { $0 > 1 }) == [3, 2]) - _ = original.customMirror - } -#endif - -#if canImport(SwiftUI) && canImport(Foundation) - @Test("Shape path generation") - @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) - func shapePathGeneration() { - let rect = CGRect(x: 10, y: 20, width: 200, height: 120) - - for edge in Edge.allCases { - let bounds = Triangle(flatEdge: edge).path(in: rect).boundingRect - #expect(bounds.width > 0) - #expect(bounds.height > 0) - } - - let placardBounds = Placard().path(in: rect).boundingRect - #expect(placardBounds.width > 0) - #expect(placardBounds.height > 0) - } -#endif -} -#endif From e52f57e17e8b0e0c162b389b1d877850bd2296de Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 09:40:03 -0400 Subject: [PATCH 099/107] Keep introspection tests in existing test surfaces --- Sources/Foundation/Introspection.swift | 38 -------------------------- 1 file changed, 38 deletions(-) diff --git a/Sources/Foundation/Introspection.swift b/Sources/Foundation/Introspection.swift index 6e910e4..cef4e8d 100644 --- a/Sources/Foundation/Introspection.swift +++ b/Sources/Foundation/Introspection.swift @@ -78,41 +78,3 @@ public func areEqual(_ left: Any?, _ right: Any?) -> Bool { return false #endif } - -#if compiler(>=5.9) && !hasFeature(Embedded) -private struct IntrospectionTestFixture: PropertyIterable { - let name: String - let count: Int -} - -private enum IntrospectionNonObjectTestFixture: PropertyIterable { - case value -} - -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) -@MainActor -let introspectionTests: [TestCase] = [ - TestCase("Property iteration and dynamic equality") { - let fixture = IntrospectionTestFixture(name: "Compatibility", count: 19) - let properties = fixture.allProperties - try expect(properties.count == 2) - try expect(properties["name"] as? String == "Compatibility") - try expect(properties["count"] as? Int == 19) - - let keyPaths = fixture.allKeyPaths - try expect(keyPaths.count == 2) - if let nameKeyPath = keyPaths["name"] { - try expect(fixture[keyPath: nameKeyPath] as? String == "Compatibility") - } else { - try expect(false, "Expected name key path") - } - - try expect(IntrospectionNonObjectTestFixture.value.allProperties.isEmpty) - try expect(42.isEqual(42)) - try expect(!42.isEqual("42")) - try expect(areEqual(42, 42)) - try expect(!areEqual(42, "42")) - try expect(!areEqual(nil, nil)) - }, -] -#endif From 7fd2173e4d54c3d606cee1b5ded5eccb453e292a Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 09:40:35 -0400 Subject: [PATCH 100/107] Expand normal coding coverage tests --- Sources/Foundation/CodingFoundation.swift | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Sources/Foundation/CodingFoundation.swift b/Sources/Foundation/CodingFoundation.swift index c737011..983e8c8 100644 --- a/Sources/Foundation/CodingFoundation.swift +++ b/Sources/Foundation/CodingFoundation.swift @@ -107,6 +107,16 @@ private struct CodingRoundTripTestModel: Codable, Equatable { let tags: [String] } +private enum ExpectedCodingTestError: Error { + case expected +} + +private struct ThrowingCodingTestModel: Encodable { + func encode(to encoder: Encoder) throws { + throw ExpectedCodingTestError.expected + } +} + @Sendable private func testCodingRoundTrips() throws { let model = CodingRoundTripTestModel(name: "Compatibility", count: 3, enabled: true, tags: ["json", "dictionary", "mixed"]) @@ -130,10 +140,45 @@ private func testCodingRoundTrips() throws { try expect(decodedFromMixedField == model, "MixedTypeField round trip should preserve the codable model") } +@Sendable +private func testDictionaryCodingStrategies() throws { + let encoder = DictionaryEncoder() + encoder.dateEncodingStrategy = .secondsSince1970 + encoder.dataEncodingStrategy = .base64 + encoder.nonConformingFloatEncodingStrategy = .convertToString( + positiveInfinity: "INF", + negativeInfinity: "-INF", + nan: "NaN" + ) + encoder.keyEncodingStrategy = .convertToSnakeCase + _ = encoder.dateEncodingStrategy + _ = encoder.dataEncodingStrategy + _ = encoder.nonConformingFloatEncodingStrategy + _ = encoder.keyEncodingStrategy + + let decoder = DictionaryDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + decoder.dataDecodingStrategy = .base64 + decoder.nonConformingFloatDecodingStrategy = .convertFromString( + positiveInfinity: "INF", + negativeInfinity: "-INF", + nan: "NaN" + ) + decoder.keyDecodingStrategy = .convertFromSnakeCase + _ = decoder.dateDecodingStrategy + _ = decoder.dataDecodingStrategy + _ = decoder.nonConformingFloatDecodingStrategy + _ = decoder.keyDecodingStrategy + + // Exercise the convenience API's documented nil-on-encoding-failure behavior. + try expect(ThrowingCodingTestModel().asDictionary() == nil) +} + @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) @MainActor internal let codingTests: [TestCase] = [ TestCase("Coding round trips", testCodingRoundTrips), + TestCase("Dictionary coding strategies", testDictionaryCodingStrategies), ] #endif From d4403efbae689ce657c55cef02d99ede6325bba6 Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 10:26:50 -0400 Subject: [PATCH 101/107] Handle macOS TabView navigation in UI tests --- .../CompatibilityUITests.swift | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Development/CompatibilityUITests/CompatibilityUITests.swift b/Development/CompatibilityUITests/CompatibilityUITests.swift index 0d040b7..d2bd577 100644 --- a/Development/CompatibilityUITests/CompatibilityUITests.swift +++ b/Development/CompatibilityUITests/CompatibilityUITests.swift @@ -32,9 +32,10 @@ private extension XCUIElement { /// UI coverage for the Compatibility demo application. /// -/// The app launches once and the test advances through the real page-style `TabView` in declaration order. +/// The app launches once and the test advances through the real `TabView` in declaration order. /// This deliberately avoids numeric selection tags so inserting or rearranging demo tabs does not require -/// keeping a second set of tab indices synchronized. +/// keeping a second set of tab indices synchronized. Navigation follows the native platform presentation: +/// page gestures on touch platforms, the popup/menu control on macOS, and remote navigation on tvOS. final class CompatibilityUITests: XCTestCase { private struct DemoScreen { let name: String @@ -77,7 +78,7 @@ final class CompatibilityUITests: XCTestCase { exercise(screenAt: index, in: app) if index < screens.count - 1 { - advanceToNextPage(in: app) + navigate(to: screens[index + 1], in: app) } } } @@ -129,8 +130,18 @@ final class CompatibilityUITests: XCTestCase { } @MainActor - private func advanceToNextPage(in app: XCUIApplication) { -#if os(tvOS) + private func navigate(to screen: DemoScreen, in app: XCUIApplication) { +#if os(macOS) + // Page-style TabView is exposed as a popup/menu control to macOS accessibility rather than + // as a swipeable page. Exercise that native presentation instead of emulating UIKit behavior. + let tabSelector = app.popUpButtons.firstMatch + XCTAssertTrue(tabSelector.waitForExistence(timeout: 5), "macOS should expose the TabView selector as a popup button.") + tabSelector.backport.tap() + + let item = app.menuItems[screen.name] + XCTAssertTrue(item.waitForExistence(timeout: 5), "The TabView selector should contain \(screen.name).") + item.backport.tap() +#elseif os(tvOS) XCUIRemote.shared.press(.right) #else app.swipeLeft() From 18cfd2a2db409977a18a1079bd0cdee3d2a4b4de Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 10:42:04 -0400 Subject: [PATCH 102/107] enhanced comments --- CHANGELOG.md | 5 ----- Sources/Compatibility.swift | 29 +++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8901704..b66000e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,5 @@ # Changelog -I would like the UI tests to visit every screen and be sure to scroll down on the tests screen. It can also exercise buttons where appropriate to actually test the interactive features. - -I also decided we can compromise by setting the version to 1.19 so that code can still update but it is more than just a bug fix patch. - - ## v1.19.0 2026-08-15 Added `Compatibility Testing Library` and `ModuleTestEntry` so reusable module `TestCase`s run as named parameterized Swift Testing cases in SwiftPM and Xcode. Unified reusable test execution through one lifecycle with parallel/serialized modes, source-aware failures, and reliable cleanup of mutable debug settings. diff --git a/Sources/Compatibility.swift b/Sources/Compatibility.swift index 81021f6..27fa662 100644 --- a/Sources/Compatibility.swift +++ b/Sources/Compatibility.swift @@ -77,8 +77,16 @@ public enum Compatibility: Module { /* + Apple Platform / Swift Generation Reference + + This is primarily a developer reference for choosing useful compile-time + generation checks. `canImport(...)` confirms that a module exists in the + current SDK/toolchain; it does NOT prove that the running OS satisfies the + framework's deployment availability. Use `#available(...)` for runtime + availability and explicit compiler checks for Swift-language features. + For module checks to conditionally compile for versions: - + canImport(StoreKit) iOS 3.0+ iPadOS 3.0+ @@ -163,22 +171,23 @@ public enum Compatibility: Module { visionOS 1.0+ SF Symbols 4.0 - 2023 Swift 5.8 (March), Swift 5.9 (September) (added #Preview syntax and @availability syntax) - canImport(SwiftData) + 2023 Swift 5.8 (March), Swift 5.9 (September) (added #Preview syntax, @availability syntax, and macros) + canImport(SwiftData) iOS 17+ iPadOS 17.0+ - macOS 14+ + macOS 14+ Sonoma Mac Catalyst 17.0+ tvOS 17+ watchOS 10+ (practical minimum for WidgetKit (due to requirement of WidgetConfigurationIntent which is only available on iOS 17, macOS 14, and watchOS 10) visionOS 1.0+ SF Symbols 5.0 + Xcode 15 2024 Swift 5.10 (March), Swift 6 (September) -canImport(Testing) + canImport(Testing) // earliest Embedded Swift versions iOS 18+ iPadOS 18+ - macOS 15+ + macOS 15+ Sequoia Mac Catalyst 18+ tvOS 18+ watchOS 11+ @@ -189,9 +198,10 @@ canImport(Testing) Swift Playgrounds 4.6.4 - Swift 6.0 Compiler 2025 Swift 6.1 (March), Swift 6.2 (September) + Apple platform version numbers synchronized! iOS 26+ iPadOS 26+ - macOS 26+ + macOS 26+ Tahoe Mac Catalyst 26+ tvOS 26+ watchOS 26+ @@ -199,7 +209,10 @@ canImport(Testing) SF Symbols 7.0 Xcode 26 - In Swift 6.2, Foundation is not available in WASM + 2026 Swift 6.3 (March), Swift 6.4? (September) + xOS 27+ Golden Gate + SF Symbols 8.0 + Xcode 27 */ // MARK: - Configuration From aaf939eae00f5ccb6395f5ed3a68f0326ce3ec1f Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 10:48:20 -0400 Subject: [PATCH 103/107] Remove stale CompatibilityTests scheme build entry --- .../xcschemes/CompatibilityTest.xcscheme | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme index 1629748..19a315b 100644 --- a/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme +++ b/Development/Compatibility.xcodeproj/xcshareddata/xcschemes/CompatibilityTest.xcscheme @@ -21,20 +21,6 @@ ReferencedContainer = "container:Compatibility.xcodeproj"> - - - - Date: Sun, 16 Aug 2026 15:17:01 -0400 Subject: [PATCH 104/107] Use native macOS TabView presentation --- Development/CompatibilityDemoView.swift | 117 +++++++++++++----------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/Development/CompatibilityDemoView.swift b/Development/CompatibilityDemoView.swift index 6d7216b..2d6e3db 100644 --- a/Development/CompatibilityDemoView.swift +++ b/Development/CompatibilityDemoView.swift @@ -5,7 +5,7 @@ // Created by Ben Ku on 7/13/24. // -#if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) +#if canImport(SwUI) && compiler(>=5.9) && canImport(Foundation) import SwiftUI import Foundation import Compatibility @@ -49,63 +49,76 @@ struct CompatibilityDemoView: View { ] ] - var body: some View { - TabView { - if #available(watchOS 9, *) { - CompatibilityEnvironmentTestView() - .accessibilityIdentifier("demo.compatibility") - .tabItem { - Text("Compatibility") - } - DataStoreTestView() - .accessibilityIdentifier("demo.datastore") - .tabItem { - Text("DataStore") - } - } - // Application tracking has already registered the complete ordered module graph consumed here. - AllTestsListView(additionalTests: Self.additionalTests) - .accessibilityIdentifier("demo.allTests") - .tabItem { - Text("All Tests") - } - ClosureTestView() - .accessibilityIdentifier("demo.closure") - .tabItem { - Text("Closure") - } - RandomBytesTestView() - .accessibilityIdentifier("demo.randomBytes") - .tabItem { - Text("Random Bytes") - } - ConvertTestView() - .accessibilityIdentifier("demo.convert") - .tabItem { - Text("Convert") - } - TriangleShowcaseView() - .accessibilityIdentifier("demo.triangle") - .tabItem { - Text("Triangle Showcase") - } - FillAndStrokeTest() - .accessibilityIdentifier("demo.fillAndStroke") - .tabItem { - Text("Fill & Stroke") - } - PlacardShowcaseView() - .accessibilityIdentifier("demo.placard") + @ViewBuilder + private var demoTabs: some View { + if #available(watchOS 9, *) { + CompatibilityEnvironmentTestView() + .accessibilityIdentifier("demo.compatibility") .tabItem { - Text("Placard Showcase") + Text("Compatibility") } - MaterialTestView() - .accessibilityIdentifier("demo.material") + DataStoreTestView() + .accessibilityIdentifier("demo.datastore") .tabItem { - Text("Material") + Text("DataStore") } } + // Application tracking has already registered the complete ordered module graph consumed here. + AllTestsListView(additionalTests: Self.additionalTests) + .accessibilityIdentifier("demo.allTests") + .tabItem { + Text("All Tests") + } + ClosureTestView() + .accessibilityIdentifier("demo.closure") + .tabItem { + Text("Closure") + } + RandomBytesTestView() + .accessibilityIdentifier("demo.randomBytes") + .tabItem { + Text("Random Bytes") + } + ConvertTestView() + .accessibilityIdentifier("demo.convert") + .tabItem { + Text("Convert") + } + TriangleShowcaseView() + .accessibilityIdentifier("demo.triangle") + .tabItem { + Text("Triangle Showcase") + } + FillAndStrokeTest() + .accessibilityIdentifier("demo.fillAndStroke") + .tabItem { + Text("Fill & Stroke") + } + PlacardShowcaseView() + .accessibilityIdentifier("demo.placard") + .tabItem { + Text("Placard Showcase") + } + MaterialTestView() + .accessibilityIdentifier("demo.material") + .tabItem { + Text("Material") + } + } + + var body: some View { +#if os(macOS) + // Use SwiftUI's native macOS tab presentation. Page style collapses many pages behind a + // Navigation Tab Bar menu, which is less useful for this desktop test/demo application. + TabView { + demoTabs + } +#else + TabView { + demoTabs + } .backport.tabViewStyle(.page) +#endif } } From 3acaa334db9c17a14f6c221789aa2c8a0bdd81b3 Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 15:17:23 -0400 Subject: [PATCH 105/107] Fix SwiftUI import guard --- Development/CompatibilityDemoView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Development/CompatibilityDemoView.swift b/Development/CompatibilityDemoView.swift index 2d6e3db..c337b44 100644 --- a/Development/CompatibilityDemoView.swift +++ b/Development/CompatibilityDemoView.swift @@ -5,7 +5,7 @@ // Created by Ben Ku on 7/13/24. // -#if canImport(SwUI) && compiler(>=5.9) && canImport(Foundation) +#if canImport(SwiftUI) && compiler(>=5.9) && canImport(Foundation) import SwiftUI import Foundation import Compatibility From 655635cba2581ff849d4e3f5ca28f32d1cd59254 Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 15:17:56 -0400 Subject: [PATCH 106/107] Make macOS UI tour native and nonblocking --- .../CompatibilityUITests.swift | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/Development/CompatibilityUITests/CompatibilityUITests.swift b/Development/CompatibilityUITests/CompatibilityUITests.swift index d2bd577..08e99fc 100644 --- a/Development/CompatibilityUITests/CompatibilityUITests.swift +++ b/Development/CompatibilityUITests/CompatibilityUITests.swift @@ -35,7 +35,7 @@ private extension XCUIElement { /// The app launches once and the test advances through the real `TabView` in declaration order. /// This deliberately avoids numeric selection tags so inserting or rearranging demo tabs does not require /// keeping a second set of tab indices synchronized. Navigation follows the native platform presentation: -/// page gestures on touch platforms, the popup/menu control on macOS, and remote navigation on tvOS. +/// direct tab selection on macOS, page gestures on touch platforms, and remote navigation on tvOS. final class CompatibilityUITests: XCTestCase { private struct DemoScreen { let name: String @@ -60,31 +60,27 @@ final class CompatibilityUITests: XCTestCase { } @MainActor - func testEveryDemoScreenAndRepresentativeInteractions() throws { + func testEveryDemoScreenAndRepresentativeInteractions() async throws { let app = XCUIApplication() app.launchArguments += ["-ApplePersistenceIgnoreState", "YES"] app.launchEnvironment["TESTING"] = "1" app.launch() - // `launch()` does not return until the application is running in the foreground, so an - // additional blocking application-state wait is unnecessary and can trigger a performance diagnostic. for (index, screen) in screens.enumerated() { let screenElement = app.descendants(matching: .any)[screen.identifier] - XCTAssertTrue( - screenElement.waitForExistence(timeout: 10), - "\(screen.name) should render its demo screen." - ) + let rendered = await waitForElement(screenElement, timeout: 10) + XCTAssertTrue(rendered, "\(screen.name) should render its demo screen.") - exercise(screenAt: index, in: app) + await exercise(screenAt: index, in: app) if index < screens.count - 1 { - navigate(to: screens[index + 1], in: app) + await navigate(to: screens[index + 1], in: app) } } } @MainActor - private func exercise(screenAt index: Int, in app: XCUIApplication) { + private func exercise(screenAt index: Int, in app: XCUIApplication) async { switch index { case 0, 1, 4, 7, 8, 9: // These pages are primarily exercised by rendering. Keep the UI tour fast and avoid @@ -92,16 +88,17 @@ final class CompatibilityUITests: XCTestCase { break case 2: - // The complete test list is deliberately long; traverse it so off-screen test rows are rendered. + // The test list is long enough that a few gestures are useful for rendering off-screen rows, + // but traversing the entire list adds time without meaningfully improving this smoke test. scrollThroughAllTests(in: app) case 3: // Open the real menu when exposed so Menu callbacks and menu-item construction are covered. let symbols = app.buttons["Symbols"] - if symbols.waitForExistence(timeout: 2) && symbols.isHittable { + if await waitForElement(symbols, timeout: 2), symbols.isHittable { symbols.backport.tap() let star = app.buttons["star"] - if star.waitForExistence(timeout: 2) && star.isHittable { + if await waitForElement(star, timeout: 2), star.isHittable { star.backport.tap() } } @@ -109,17 +106,17 @@ final class CompatibilityUITests: XCTestCase { case 5: // Exercise Binding.convert through the Convert screen's slider. let slider = app.sliders.firstMatch - if slider.waitForExistence(timeout: 2) { + if await waitForElement(slider, timeout: 2) { slider.adjust(toNormalizedSliderPosition: 0.75) } case 6: // Exercise Triangle drawing and navigationDestination, then return to the showcase. let button = app.buttons.firstMatch - if button.waitForExistence(timeout: 2) && button.isHittable { + if await waitForElement(button, timeout: 2), button.isHittable { button.backport.tap() let destination = app.buttons["Navigation Destination TestCase"] - if destination.waitForExistence(timeout: 2) { + if await waitForElement(destination, timeout: 2), destination.isHittable { destination.backport.tap() } } @@ -130,17 +127,18 @@ final class CompatibilityUITests: XCTestCase { } @MainActor - private func navigate(to screen: DemoScreen, in app: XCUIApplication) { + private func navigate(to screen: DemoScreen, in app: XCUIApplication) async { #if os(macOS) - // Page-style TabView is exposed as a popup/menu control to macOS accessibility rather than - // as a swipeable page. Exercise that native presentation instead of emulating UIKit behavior. - let tabSelector = app.popUpButtons.firstMatch - XCTAssertTrue(tabSelector.waitForExistence(timeout: 5), "macOS should expose the TabView selector as a popup button.") - tabSelector.backport.tap() - - let item = app.menuItems[screen.name] - XCTAssertTrue(item.waitForExistence(timeout: 5), "The TabView selector should contain \(screen.name).") - item.backport.tap() + // Native macOS TabView exposes its tabs directly to accessibility. Query by the visible + // tab name instead of depending on a particular AppKit control class. + let tab = app.descendants(matching: .any)[screen.name] + let found = await waitForElement(tab, timeout: 5) + XCTAssertTrue(found, "macOS should expose the \(screen.name) tab.") + guard found else { return } + XCTAssertTrue(tab.isHittable, "The \(screen.name) tab should be directly selectable.") + if tab.isHittable { + tab.backport.tap() + } #elseif os(tvOS) XCUIRemote.shared.press(.right) #else @@ -148,6 +146,22 @@ final class CompatibilityUITests: XCTestCase { #endif } + @MainActor + private func waitForElement(_ element: XCUIElement, timeout: TimeInterval) async -> Bool { + let deadline = Date().addingTimeInterval(timeout) + repeat { + if element.exists { + return true + } + if Date() >= deadline { + return false + } + // Yield the main actor instead of calling XCTest's synchronous waitForExistence(timeout:), + // which the performance diagnostics correctly flag as blocking UI responsiveness. + try? await Task.sleep(nanoseconds: 100_000_000) + } while true + } + @MainActor private func scrollThroughAllTests(in app: XCUIApplication) { let scrollView = app.scrollViews.firstMatch @@ -165,12 +179,10 @@ final class CompatibilityUITests: XCTestCase { scrollable = app } - for _ in 0..<12 { + for _ in 0..<3 { scrollable.swipeUp() } - for _ in 0..<6 { - scrollable.swipeDown() - } + scrollable.swipeDown() } } #endif From 470cc7b569257f092cc0337cdb66014aeea9e6bb Mon Sep 17 00:00:00 2001 From: kudit Date: Sun, 16 Aug 2026 18:46:29 -0400 Subject: [PATCH 107/107] simplified branching logic. --- Development/CompatibilityDemoView.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Development/CompatibilityDemoView.swift b/Development/CompatibilityDemoView.swift index c337b44..59d90fe 100644 --- a/Development/CompatibilityDemoView.swift +++ b/Development/CompatibilityDemoView.swift @@ -107,18 +107,17 @@ struct CompatibilityDemoView: View { } var body: some View { -#if os(macOS) - // Use SwiftUI's native macOS tab presentation. Page style collapses many pages behind a - // Navigation Tab Bar menu, which is less useful for this desktop test/demo application. TabView { demoTabs - } + }.closure { view in +#if os(macOS) + // Use SwiftUI's native macOS tab presentation. Page style collapses many pages behind a + // Navigation Tab Bar menu, which is less useful for this desktop test/demo application. + view #else - TabView { - demoTabs - } - .backport.tabViewStyle(.page) + view.backport.tabViewStyle(.page) #endif + } } }