diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..588c3a6 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,58 @@ +name: "NikSativa CI" + +on: + push: + branches: + - main + paths: + - ".github/workflows/**" + - "Package.swift" + - "Source/**" + - "Tests/**" + pull_request: + paths: + - ".github/workflows/**" + - "Package.swift" + - "Source/**" + - "Tests/**" + +concurrency: + group: ${{ github.ref_name }} + cancel-in-progress: true +jobs: + macOS: + name: "macOS ${{ matrix.xcode }} ${{ matrix.swift }}" + runs-on: ${{ matrix.runsOn }} + env: + DEVELOPER_DIR: "/Applications/${{ matrix.xcode }}.app/Contents/Developer" + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: +# unsupported yet +# - xcode: "Xcode_16" +# runsOn: firebreak +# swift: "6.0" +# outputFilter: xcbeautify --renderer github-actions + - xcode: "Xcode_15.4" + runsOn: macOS-14 + swift: "5.10" + outputFilter: xcbeautify --renderer github-actions + - xcode: "Xcode_15.2" + runsOn: macOS-14 + swift: "5.9" + outputFilter: xcbeautify --renderer github-actions + - xcode: "Xcode_14.3" + runsOn: macOS-13 + swift: "5.8" + outputFilter: xcbeautify --renderer github-actions + steps: + - uses: NeedleInAJayStack/setup-swift + with: + swift-version: ${{ matrix.swift }} + - uses: actions/checkout@v4 + - name: "build ${{ matrix.xcode }} ${{ matrix.swift }}" + run: swift build -v | ${{ matrix.outputFilter }} + - name: "test ${{ matrix.xcode }} ${{ matrix.swift }}" + run: swift test -v | ${{ matrix.outputFilter }} diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift deleted file mode 100644 index 78e665a..0000000 --- a/Package@swift-5.5.swift +++ /dev/null @@ -1,36 +0,0 @@ -// swift-tools-version:5.5 -// swiftformat:disable all -import PackageDescription - -let package = Package( - name: "SpryKit", - platforms: [ - .iOS(.v13), - .macOS(.v11), - .macCatalyst(.v13), - .tvOS(.v13), - .watchOS(.v6) - ], - products: [ - .library(name: "SpryKit", targets: ["SpryKit"]), - ], - dependencies: [ - .package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", .upToNextMinor(from: "2.2.2")) - ], - targets: [ - .target(name: "SpryKit", - dependencies: [ - "CwlPreconditionTesting" - ], - path: "Source", - resources: [ - .copy("../PrivacyInfo.xcprivacy") - ]), - .testTarget(name: "SpryTests", - dependencies: [ - "SpryKit" - ], - path: "Tests") - ], - swiftLanguageVersions: [.v5] -) diff --git a/Package@swift-5.6.swift b/Package@swift-5.6.swift deleted file mode 100644 index c5e0d7b..0000000 --- a/Package@swift-5.6.swift +++ /dev/null @@ -1,36 +0,0 @@ -// swift-tools-version:5.6 -// swiftformat:disable all -import PackageDescription - -let package = Package( - name: "SpryKit", - platforms: [ - .iOS(.v13), - .macOS(.v11), - .macCatalyst(.v13), - .tvOS(.v13), - .watchOS(.v6) - ], - products: [ - .library(name: "SpryKit", targets: ["SpryKit"]), - ], - dependencies: [ - .package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", .upToNextMinor(from: "2.2.2")) - ], - targets: [ - .target(name: "SpryKit", - dependencies: [ - "CwlPreconditionTesting" - ], - path: "Source", - resources: [ - .copy("../PrivacyInfo.xcprivacy") - ]), - .testTarget(name: "SpryTests", - dependencies: [ - "SpryKit" - ], - path: "Tests") - ], - swiftLanguageVersions: [.v5] -) diff --git a/Package@swift-5.7.swift b/Package@swift-5.7.swift deleted file mode 100644 index 10db1de..0000000 --- a/Package@swift-5.7.swift +++ /dev/null @@ -1,36 +0,0 @@ -// swift-tools-version:5.7 -// swiftformat:disable all -import PackageDescription - -let package = Package( - name: "SpryKit", - platforms: [ - .iOS(.v13), - .macOS(.v11), - .macCatalyst(.v13), - .tvOS(.v13), - .watchOS(.v6) - ], - products: [ - .library(name: "SpryKit", targets: ["SpryKit"]), - ], - dependencies: [ - .package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", .upToNextMinor(from: "2.2.2")) - ], - targets: [ - .target(name: "SpryKit", - dependencies: [ - "CwlPreconditionTesting" - ], - path: "Source", - resources: [ - .copy("../PrivacyInfo.xcprivacy") - ]), - .testTarget(name: "SpryTests", - dependencies: [ - "SpryKit" - ], - path: "Tests") - ], - swiftLanguageVersions: [.v5] -) diff --git a/Source/Helpers/Image+Helpers.swift b/Source/Helpers/Image+Helpers.swift index ec82420..7e6e77b 100644 --- a/Source/Helpers/Image+Helpers.swift +++ b/Source/Helpers/Image+Helpers.swift @@ -27,7 +27,7 @@ public extension Image { } public extension Image.spry { - #if os(macOS) && swift(>=6.0) + #if os(macOS) && swift(>=5.10) nonisolated(unsafe) static let testImage: Image = .init(systemSymbolName: "circle", accessibilityDescription: nil)! nonisolated(unsafe) static let testImage1: Image = .init(systemSymbolName: "square", accessibilityDescription: nil)! nonisolated(unsafe) static let testImage2: Image = .init(systemSymbolName: "diamond", accessibilityDescription: nil)! @@ -59,7 +59,7 @@ public extension Image.spry { color.setFill() context.fill(rect) } - #elseif (swift(>=5.9) && os(visionOS)) + #elseif(swift(>=5.9) && os(visionOS)) let data = UIGraphicsImageRenderer(bounds: rect).pngData { context in color.setFill() context.fill(rect) diff --git a/Source/Matcher/XCTAssertThrowsAssertion.swift b/Source/Matcher/XCTAssertThrowsAssertion.swift index 94b92d7..16603ab 100644 --- a/Source/Matcher/XCTAssertThrowsAssertion.swift +++ b/Source/Matcher/XCTAssertThrowsAssertion.swift @@ -1,4 +1,3 @@ -#if (os(macOS) || os(iOS) || (swift(>=5.9) && os(visionOS))) && (arch(x86_64) || arch(arm64)) import CwlPreconditionTesting import Foundation import XCTest @@ -8,6 +7,7 @@ public func XCTAssertThrowsAssertion(_ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, _ expression: @escaping () throws -> some Any) { + #if (os(macOS) || os(iOS) || (swift(>=5.9) && os(visionOS))) && (arch(x86_64) || arch(arm64)) print(" --- ⚠️ ignore this assertion in console! this is a result of XCTAssertThrowsAssertion ⚠️ --- ") XCTAssertNotNil(catchBadInstruction(in: { do { @@ -16,6 +16,9 @@ public func XCTAssertThrowsAssertion(_ message: @autoclosure () -> String = "", XCTFail("catch error: " + error.localizedDescription, file: file, line: line) } }), message(), file: file, line: line) + #else + print(" --- ⚠️ this is a result of XCTAssertThrowsAssertion. it is not supported on this platform ⚠️ --- ") + #endif } @inline(__always) @@ -25,4 +28,3 @@ public func XCTAssertThrowsAssertion(_ expression: @autoclosure @escaping () thr line: UInt = #line) { XCTAssertThrowsAssertion(message(), file: file, line: line, expression) } -#endif diff --git a/Source/Spy/SpyableImpl.swift b/Source/Spy/SpyableImpl.swift index e3d89c6..cfb6862 100644 --- a/Source/Spy/SpyableImpl.swift +++ b/Source/Spy/SpyableImpl.swift @@ -3,7 +3,7 @@ import Foundation // A global NSMapTable to hold onto calls for types conforming to Spyable. This map table has "weak to strong objects" options. // // - Important: Do NOT use this object. -#if swift(>=6.0) +#if swift(>=5.10) private nonisolated(unsafe) var callsMapTable: NSMapTable> = NSMapTable.weakToStrongObjects() #else private var callsMapTable: NSMapTable> = NSMapTable.weakToStrongObjects() diff --git a/Source/Stub/StubbableImpl.swift b/Source/Stub/StubbableImpl.swift index 6371568..b7fc85a 100644 --- a/Source/Stub/StubbableImpl.swift +++ b/Source/Stub/StubbableImpl.swift @@ -3,7 +3,7 @@ import Foundation // A global NSMapTable to hold onto stubs for types conforming to Stubbable. This map table has "weak to strong objects" options. // // - Important: Do NOT use this object. -#if swift(>=6.0) +#if swift(>=5.10) private nonisolated(unsafe) var stubsMapTable: NSMapTable> = NSMapTable.weakToStrongObjects() #else private var stubsMapTable: NSMapTable> = NSMapTable.weakToStrongObjects() diff --git a/Tests/ArgumentTests.swift b/Tests/ArgumentTests.swift index edb3d8b..f2c6f05 100644 --- a/Tests/ArgumentTests.swift +++ b/Tests/ArgumentTests.swift @@ -4,11 +4,12 @@ import XCTest final class ArgumentTests: XCTestCase { func test_CustomStringConvertible() { - XCTAssertEqual(Argument.anything.description, "Argument.anything") - XCTAssertEqual(Argument.nonNil.description, "Argument.nonNil") - XCTAssertEqual(Argument.nil.description, "Argument.nil") - XCTAssertEqual(Argument.validator { _ in true }.description, "Argument.validator") - XCTAssertEqual(Argument.closure.description, "Argument.closure") + XCTAssertEqual(Argument.anything.description, "Argument.anything", "Argument.anything") + XCTAssertEqual(Argument.nonNil.description, "Argument.nonNil", "Argument.nonNil") + XCTAssertEqual(Argument.nil.description, "Argument.nil", "Argument.nil") + XCTAssertEqual(Argument.validator { _ in true }.description, "Argument.validator", "Argument.validator") + XCTAssertEqual(Argument.closure.description, "Argument.closure", "Argument.closure") + XCTAssertEqual(Argument.skipped.description, "Argument.skipped", "Argument.skipped") } func test_is_equal_args_list() { diff --git a/Tests/CrossPlatformTests.swift b/Tests/CrossPlatformTests.swift index 337383f..0484df8 100644 --- a/Tests/CrossPlatformTests.swift +++ b/Tests/CrossPlatformTests.swift @@ -21,25 +21,43 @@ final class CrossPlatformTests: XCTestCase { import UIKit private enum Screen { - @MainActor static var scale: CGFloat { + #if swift(>=5.10) + nonisolated(unsafe) static var scale: CGFloat { + return syncMainThread { UIScreen.main.scale } + } + #else + static var scale: CGFloat { return UIScreen.main.scale } + #endif } #elseif os(watchOS) import WatchKit private enum Screen { - @MainActor static var scale: CGFloat { + #if swift(>=5.10) + nonisolated(unsafe) static var scale: CGFloat { return WKInterfaceDevice.current().screenScale } + #else + static var scale: CGFloat { + return WKInterfaceDevice.current().screenScale + } + #endif } -#elseif (swift(>=5.9) && os(visionOS)) +#elseif(swift(>=5.9) && os(visionOS)) public enum Screen { + #if swift(>=5.10) /// visionOS doesn't have a screen scale, so we'll just use 2x for Tests. /// override it on your own risk. - @MainActor public static var scale: CGFloat? + public nonisolated(unsafe) static var scale: CGFloat? + #else + /// visionOS doesn't have a screen scale, so we'll just use 2x for Tests. + /// override it on your own risk. + public static var scale: CGFloat? + #endif } #endif @@ -63,11 +81,9 @@ private struct PlatformImage { return sdk.png } - #elseif (swift(>=5.9) && os(visionOS)) + #elseif(swift(>=5.9) && os(visionOS)) init?(data: Data) { - let scale = syncMainThread { Screen.scale } - - if let scale, + if let scale = Screen.scale, let image = UIImage(data: data, scale: scale) { self.init(image) } else if let image = UIImage(data: data) { @@ -83,7 +99,7 @@ private struct PlatformImage { #elseif os(iOS) || os(tvOS) || os(watchOS) init?(data: Data) { - let scale = syncMainThread { Screen.scale } + let scale = Screen.scale if let image = UIImage(data: data, scale: scale) { self.init(image) @@ -118,25 +134,25 @@ private extension NSImage { #if swift(>=5.10) @inline(__always) -private func syncMainThread(_ callback: @MainActor () -> T) -> T { +private func syncMainThread(_ callback: @MainActor @Sendable () -> T) -> T { if Thread.isMainThread { - MainActor.assumeIsolated { - callback() + return MainActor.assumeIsolated { + return callback() } } else { - DispatchQueue.main.sync { - callback() + return DispatchQueue.main.sync { + return callback() } } } #else @inline(__always) -private func syncMainThread(_ callback: @MainActor () -> T) -> T { +private func syncMainThread(_ callback: @Sendable () -> T) -> T { if Thread.isMainThread { - callback() + return callback() } else { - DispatchQueue.main.sync { - callback() + return DispatchQueue.main.sync { + return callback() } } } diff --git a/Tests/StubbableTests.swift b/Tests/StubbableTests.swift index 4e6e383..fc9fc76 100644 --- a/Tests/StubbableTests.swift +++ b/Tests/StubbableTests.swift @@ -339,9 +339,8 @@ final class StubbableTests: XCTestCase { func test_on_a_class() { StubbableTestHelper.stub(.classFunction).andReturn("fallbackValue") StubbableTestHelper.resetStubs() - #if (os(macOS) || os(iOS) || (swift(>=5.9) && os(visionOS))) && (arch(x86_64) || arch(arm64)) + XCTAssertThrowsAssertion(StubbableTestHelper.classFunction()) - #endif StubbableTestHelper.stub(.classFunction).andReturn("fallbackValue") XCTAssertEqual(StubbableTestHelper.classFunction(), "fallbackValue") @@ -377,9 +376,8 @@ final class StubbableTests: XCTestCase { func test_class_version_spot_check() { StubbableTestHelper.stub(.classFunction).andReturn("original return value") - #if (os(macOS) || os(iOS) || (swift(>=5.9) && os(visionOS))) && (arch(x86_64) || arch(arm64)) + XCTAssertThrowsAssertion(StubbableTestHelper.stub(.classFunction).andReturn("original return value")) - #endif let newReturnValue = "new return value" StubbableTestHelper.stubAgain(.classFunction).andReturn(newReturnValue)