From 4fade92a7112d2b7ae0667cf92195bb3d2b0b1e5 Mon Sep 17 00:00:00 2001 From: Charles Srstka Date: Mon, 16 Mar 2026 18:30:01 -0400 Subject: [PATCH] Fix build errors on Linux --- .github/workflows/ci.yml | 48 +++++ Package.resolved | 15 ++ Package.swift | 10 +- Sources/CSErrors/CocoaError+CSErrors.swift | 38 +++- Sources/CSErrors/Error+CSErrors.swift | 22 ++- Sources/CSErrors/ErrorMetadata.swift | 40 ++++- Sources/CSErrors/HTTPError.swift | 2 +- Sources/CSErrors/NSError+CSErrors.swift | 5 +- Sources/CSErrors/OSStatus Errors.swift | 89 +++++---- Sources/CSErrors/POSIX Errors.swift | 49 +++-- Sources/CSErrors/Recoverability.swift | 3 +- Sources/CSErrors/Stderr.swift | 6 + Sources/CSErrors/URLError+CSErrors.swift | 3 +- Tests/CSErrorsTests/CocoaErrorTests.swift | 25 ++- .../ErrnoCocoaMappingTests.swift | 27 ++- .../CSErrorsTests/ErrnoURLSupportTests.swift | 83 ++++++--- Tests/CSErrorsTests/ErrorMetadataTests.swift | 13 +- Tests/CSErrorsTests/HTTPErrorTests.swift | 14 +- Tests/CSErrorsTests/NSErrorTests.swift | 2 +- Tests/CSErrorsTests/OSStatusErrorTests.swift | 22 ++- Tests/CSErrorsTests/POSIXErrorTests.swift | 170 +++++++----------- Tests/CSErrorsTests/RecoverabilityTests.swift | 14 +- Tests/CSErrorsTests/StderrTests.swift | 18 +- Tests/CSErrorsTests/URLErrorTests.swift | 19 +- 24 files changed, 512 insertions(+), 225 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Package.resolved diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2f00557 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,48 @@ +name: CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + unit_tests: + strategy: + fail-fast: false + matrix: + os: [macos-latest, macos-15-intel, ubuntu-latest, ubuntu-24.04-arm] + traits: ["none", "Foundation"] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v6 + - uses: SwiftyLab/setup-swift@latest + - name: Determine traits flags + id: traits + run: | + if [[ ${{ matrix.traits }} = 'all' ]]; then + echo "flags=--enable-all-traits" >> $GITHUB_OUTPUT + elif [[ ${{ matrix.traits }} = 'none' ]]; then + echo "flags=--disable-default-traits" >> $GITHUB_OUTPUT + else + echo "flags=--traits ${{ matrix.traits }}" >> $GITHUB_OUTPUT + fi + - name: Build + run: swift build --build-tests --enable-code-coverage -v ${{ steps.traits.outputs.flags }} + - name: Run tests + run: swift test --enable-code-coverage -v ${{ steps.traits.outputs.flags }} --xunit-output testresults.xml + - name: Collect artifacts + if: always() + run: | + mkdir -p artifacts + cp testresults*.xml artifacts/ 2>/dev/null || true + find .build -path "*/codecov/*.json" | xargs -I{} cp {} artifacts/ 2>/dev/null || true + + - name: Upload test results + uses: actions/upload-artifact@v7 + if: always() + with: + name: test-results-${{ matrix.os }}-${{ matrix.traits }} + path: artifacts/ + diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..4b4371e --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "b4bf9c49d7348a285fb3847929caad86cc556e07c6f31c7eb721e40d69d00d8e", + "pins" : [ + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system", + "state" : { + "revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df", + "version" : "1.6.4" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift index 0d3df02..ea5235e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.1 +// swift-tools-version:6.2 import PackageDescription @@ -20,11 +20,15 @@ let package = Package( traits: [ "Foundation" ], - dependencies: [], + dependencies: [ + .package(url: "https://github.com/apple/swift-system", from: "1.6.1"), + ], targets: [ .target( name: "CSErrors", - dependencies: [] + dependencies: [ + .product(name: "SystemPackage", package: "swift-system", condition: .when(platforms: [.linux])) + ] ), .testTarget( name: "CSErrorsTests", diff --git a/Sources/CSErrors/CocoaError+CSErrors.swift b/Sources/CSErrors/CocoaError+CSErrors.swift index 3a3789e..a494cfd 100644 --- a/Sources/CSErrors/CocoaError+CSErrors.swift +++ b/Sources/CSErrors/CocoaError+CSErrors.swift @@ -5,7 +5,12 @@ // Created by Charles Srstka on 4/17/20. // -import System + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#endif #if Foundation #if canImport(FoundationEssentials) @@ -14,6 +19,12 @@ import FoundationEssentials import Foundation #endif +#if canImport(SystemPackage) +import SystemPackage +#else +import System +#endif + internal func cocoaCode(posixCode: Int32, isWrite: Bool) -> CocoaError.Code? { switch posixCode { case EPERM, EACCES: @@ -28,8 +39,10 @@ internal func cocoaCode(posixCode: Int32, isWrite: Bool) -> CocoaError.Code? { return .fileWriteOutOfSpace case EROFS: return .fileWriteVolumeReadOnly +#if canImport(Darwin) case EFTYPE: return .fileReadCorruptFile +#endif case ECANCELED: return .userCancelled default: @@ -94,7 +107,15 @@ extension CocoaError { var userInfo = metadata.toUserInfo() if let stringEncoding { - userInfo[NSStringEncodingErrorKey] = stringEncoding.rawValue + // The Darwin version of Foundation stores this as a UInt, other platforms store it as an NSNumber + // Also, the string keys they use are different. + // https://github.com/swiftlang/swift-foundation/blob/b011018acca72a38179bd4ac9d0377d2f90b4cff/Sources/FoundationEssentials/Error/CocoaError.swift#L108-L114 + +#if Foundation && canImport(Darwin) + userInfo[NSStringEncodingErrorKey] = NSNumber(value: stringEncoding.rawValue) +#endif + + userInfo[NSStringEncodingErrorKeyNonDarwin] = Int(stringEncoding.rawValue) } self.init(code, userInfo: userInfo) @@ -159,11 +180,20 @@ extension CocoaError { extension CocoaError: CSErrorProtocol { public var isFileNotFoundError: Bool { - [.fileNoSuchFile, .fileReadNoSuchFile, .ubiquitousFileUnavailable].contains(self.code) + switch self.code { + case .fileNoSuchFile, .fileReadNoSuchFile: true +#if canImport(Darwin) + case .ubiquitousFileUnavailable: true +#endif + default: false + } } public var isPermissionError: Bool { - [.fileReadNoPermission, .fileWriteNoPermission].contains(self.code) + switch self.code { + case .fileReadNoPermission, .fileWriteNoPermission: true + default: false + } } public var isCancelledError: Bool { diff --git a/Sources/CSErrors/Error+CSErrors.swift b/Sources/CSErrors/Error+CSErrors.swift index 7620a9d..a234d9a 100644 --- a/Sources/CSErrors/Error+CSErrors.swift +++ b/Sources/CSErrors/Error+CSErrors.swift @@ -5,7 +5,11 @@ // Created by Charles Srstka on 12/27/15. // +#if canImport(SystemPackage) +import SystemPackage +#else import System +#endif #if canImport(Darwin) import Darwin @@ -25,7 +29,7 @@ extension Error { /// True if the error represents a “File Not Found” error, regardless of domain. public var isFileNotFoundError: Bool { if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, macCatalyst 14.0, *), versionCheck(11), - let errno = self as? System.Errno, errno == .noSuchFileOrDirectory { + let errno = self as? Errno, errno == .noSuchFileOrDirectory { return true } @@ -33,17 +37,15 @@ extension Error { return true } -#if canImport(Darwin) if let osStatus = self.toOSStatus(), OSStatusError.Codes.fileNotFoundErrors.contains(osStatus) { return true } -#endif if let err = self as? any CSErrorProtocol, err.isFileNotFoundError { return true } -#if Foundation +#if Foundation && canImport(Darwin) if let err = (self as NSError).toCSErrorProtocol(), err.isFileNotFoundError { return true } @@ -55,7 +57,7 @@ extension Error { /// True if the error represents a permission or access error, regardless of domain. public var isPermissionError: Bool { if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, macCatalyst 14.0, *), versionCheck(11), - let errno = self as? System.Errno, [.permissionDenied, .notPermitted].contains(errno) + let errno = self as? Errno, [.permissionDenied, .notPermitted].contains(errno) { return true } @@ -64,17 +66,15 @@ extension Error { return true } -#if canImport(Darwin) if let osStatus = self.toOSStatus(), OSStatusError.Codes.permissionErrors.contains(osStatus) { return true } -#endif if let err = self as? any CSErrorProtocol, err.isPermissionError { return true } -#if Foundation +#if Foundation && canImport(Darwin) if let err = (self as NSError).toCSErrorProtocol(), err.isPermissionError { return true } @@ -86,7 +86,7 @@ extension Error { /// True if the error results from a user cancellation, regardless of domain. public var isCancelledError: Bool { if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, macCatalyst 14.0, *), versionCheck(11), - let errno = self as? System.Errno, errno == .canceled + let errno = self as? Errno, errno == .canceled { return true } @@ -95,17 +95,15 @@ extension Error { return true } -#if canImport(Darwin) if let err = self.toOSStatus(), OSStatusError.Codes.cancelErrors.contains(err) { return true } -#endif if let err = self as? any CSErrorProtocol, err.isCancelledError { return true } -#if Foundation +#if Foundation && canImport(Darwin) if let err = (self as NSError).toCSErrorProtocol(), err.isCancelledError { return true } diff --git a/Sources/CSErrors/ErrorMetadata.swift b/Sources/CSErrors/ErrorMetadata.swift index cfc1749..d84e10d 100644 --- a/Sources/CSErrors/ErrorMetadata.swift +++ b/Sources/CSErrors/ErrorMetadata.swift @@ -5,16 +5,32 @@ // Created by Charles Srstka on 1/11/23. // +#if canImport(SystemPackage) +import SystemPackage +#else import System +#endif #if Foundation #if canImport(FoundationEssentials) import FoundationEssentials +let NSFilePathErrorKey = "NSFilePath" +let NSHelpAnchorErrorKey = "NSHelpAnchor" +let NSLocalizedDescriptionKey = "NSLocalizedDescription" +let NSLocalizedFailureReasonErrorKey = "NSLocalizedFailureReason" +let NSRecoveryAttempterErrorKey = "NSRecoveryAttempter" +let NSLocalizedRecoveryOptionsErrorKey = "NSLocalizedRecoveryOptions" +let NSLocalizedRecoverySuggestionErrorKey = "NSLocalizedRecoverySuggestion" +let NSStringEncodingErrorKey = "NSStringEncoding" +let NSUnderlyingErrorKey = "NSUnderlyingError" +let NSURLErrorKey = "NSURL" #else import Foundation #endif #endif +let NSStringEncodingErrorKeyNonDarwin = "NSStringEncodingErrorKey" + public struct ErrorMetadata: Sendable { public let description: String? public internal(set) var failureReason: String? @@ -159,7 +175,15 @@ public struct ErrorMetadata: Sendable { } if let stringEncoding { - custom[NSStringEncodingErrorKey] = stringEncoding.rawValue + // The Darwin version of Foundation stores this as a UInt, other platforms store it as an NSNumber + // Also, the string keys they use are different. + // https://github.com/swiftlang/swift-foundation/blob/b011018acca72a38179bd4ac9d0377d2f90b4cff/Sources/FoundationEssentials/Error/CocoaError.swift#L108-L114 + +#if Foundation && canImport(Darwin) + custom[NSStringEncodingErrorKey] = NSNumber(value: stringEncoding.rawValue) +#endif + + custom[NSStringEncodingErrorKeyNonDarwin] = Int(stringEncoding.rawValue) } self.init( @@ -233,20 +257,30 @@ public struct ErrorMetadata: Sendable { return url } +#if canImport(Darwin) if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, macCatalyst 16.1, *), versionCheck(13), let path = self.path, let url = URL(filePath: path) { return url } else if let path = self.pathString { return URL(fileURLWithPath: path) } +#else + if let path = self.path { + return URL(filePath: path.string) + } +#endif return nil } public var stringEncoding: String.Encoding? { - guard let rawEncoding = self.custom?[NSStringEncodingErrorKey] as? UInt else { return nil } + if let rawEncoding = self.custom?[NSStringEncodingErrorKey] as? UInt { + return String.Encoding(rawValue: rawEncoding) + } else if let rawEncoding = self.custom?[NSStringEncodingErrorKeyNonDarwin] as? Int { + return String.Encoding(rawValue: UInt(rawEncoding)) + } - return String.Encoding(rawValue: rawEncoding) + return nil } #endif } diff --git a/Sources/CSErrors/HTTPError.swift b/Sources/CSErrors/HTTPError.swift index 68f34ba..dff4b64 100644 --- a/Sources/CSErrors/HTTPError.swift +++ b/Sources/CSErrors/HTTPError.swift @@ -16,7 +16,7 @@ public struct HTTPError: CSErrorProtocol { public var failureReason: String? { var reason = "HTTP \(self.statusCode)" -#if Foundation +#if Foundation && canImport(Darwin) reason += " (\(HTTPURLResponse.localizedString(forStatusCode: self.statusCode)))" #endif diff --git a/Sources/CSErrors/NSError+CSErrors.swift b/Sources/CSErrors/NSError+CSErrors.swift index 7fc6e96..e4e149c 100644 --- a/Sources/CSErrors/NSError+CSErrors.swift +++ b/Sources/CSErrors/NSError+CSErrors.swift @@ -5,15 +5,16 @@ // Created by Charles Srstka on 11/11/23. // -import System +#if Foundation && canImport(Darwin) -#if Foundation #if canImport(FoundationEssentials) import FoundationEssentials #else import Foundation #endif +import System + extension NSError { internal func toCSErrorProtocol() -> (any CSErrorProtocol)? { switch self.domain { diff --git a/Sources/CSErrors/OSStatus Errors.swift b/Sources/CSErrors/OSStatus Errors.swift index 500dfb5..30a09a3 100644 --- a/Sources/CSErrors/OSStatus Errors.swift +++ b/Sources/CSErrors/OSStatus Errors.swift @@ -5,14 +5,22 @@ // Created by Charles Srstka on 1/10/23. // +#if canImport(SystemPackage) +import SystemPackage +#else import System +#endif #if canImport(Darwin) import Darwin +#else +public typealias OSStatus = Int32 +#endif #if Foundation #if canImport(FoundationEssentials) import FoundationEssentials +private let NSOSStatusErrorDomain = "NSOSStatusErrorDomain" #else import Foundation #endif @@ -36,32 +44,33 @@ extension Error { public struct OSStatusError: Error { internal struct Codes { // Some OSStatus codes that we use elsewhere in this package. - internal static let kPOSIXErrorBase: Int32 = 100000 - internal static let unimpErr: Int32 = -4 - internal static let ioErr: Int32 = -36 - internal static let eofErr: Int32 = -39 - internal static let fnfErr: Int32 = -43 - internal static let userCanceledErr: Int32 = -128 - internal static let errAEWaitCanceled: Int32 = -1711 - internal static let kernelCanceledErr: Int32 = -2402 - internal static let kOTCanceledErr: Int32 = -3180 - internal static let kEPERMErr: Int32 = -3200 - internal static let kENOENTErr: Int32 = -3201 - internal static let kEACCESErr: Int32 = -3212 - internal static let kEINVALErr: Int32 = -3221 - internal static let kECANCELErr: Int32 = -3273 + internal static let noErr: OSStatus = 0 + internal static let kPOSIXErrorBase: OSStatus = 100000 + internal static let unimpErr: OSStatus = -4 + internal static let ioErr: OSStatus = -36 + internal static let eofErr: OSStatus = -39 + internal static let fnfErr: OSStatus = -43 + internal static let userCanceledErr: OSStatus = -128 + internal static let errAEWaitCanceled: OSStatus = -1711 + internal static let kernelCanceledErr: OSStatus = -2402 + internal static let kOTCanceledErr: OSStatus = -3180 + internal static let kEPERMErr: OSStatus = -3200 + internal static let kENOENTErr: OSStatus = -3201 + internal static let kEACCESErr: OSStatus = -3212 + internal static let kEINVALErr: OSStatus = -3221 + internal static let kECANCELErr: OSStatus = -3273 internal static let coreFoundationUnknownErr = -4960 - internal static let afpAccessDenied: Int32 = -5000 - internal static let errIACanceled: Int32 = -5385 - internal static let kRAConnectionCanceled: Int32 = -7109 - internal static let kTXNUserCanceledOperationErr: Int32 = -22004 - internal static let kFBCindexingCanceled: Int32 = -30520 - internal static let kFBCaccessCanceled: Int32 = -30521 - internal static let kFBCsummarizationCanceled: Int32 = -30529 - - internal static let fileNotFoundErrors: [Int32] = [Self.fnfErr, Self.kENOENTErr] - internal static let permissionErrors: [Int32] = [Self.afpAccessDenied, Self.kEPERMErr, Self.kEACCESErr] - internal static let cancelErrors: [Int32] = [ + internal static let afpAccessDenied: OSStatus = -5000 + internal static let errIACanceled: OSStatus = -5385 + internal static let kRAConnectionCanceled: OSStatus = -7109 + internal static let kTXNUserCanceledOperationErr: OSStatus = -22004 + internal static let kFBCindexingCanceled: OSStatus = -30520 + internal static let kFBCaccessCanceled: OSStatus = -30521 + internal static let kFBCsummarizationCanceled: OSStatus = -30529 + + internal static let fileNotFoundErrors: [OSStatus] = [Self.fnfErr, Self.kENOENTErr] + internal static let permissionErrors: [OSStatus] = [Self.afpAccessDenied, Self.kEPERMErr, Self.kEACCESErr] + internal static let cancelErrors: [OSStatus] = [ Self.userCanceledErr, Self.errAEWaitCanceled, Self.kernelCanceledErr, @@ -288,7 +297,7 @@ public func callOSStatusAPI( ) throws { let err = closure() - guard err == noErr else { + guard err == OSStatusError.Codes.noErr else { throw osStatusError(err, description: errorDescription, path: path, custom: customErrorUserInfo) } } @@ -307,7 +316,7 @@ public func callOSStatusAPI( ) throws { let err = closure() - guard err == noErr else { + guard err == OSStatusError.Codes.noErr else { throw osStatusError(err, description: errorDescription, path: path, custom: customErrorUserInfo) } } @@ -328,7 +337,7 @@ public func callOSStatusAPI( var ret = 0 as T let err = closure(&ret) - guard err == noErr else { + guard err == OSStatusError.Codes.noErr else { throw osStatusError(err, description: errorDescription, path: path, custom: customErrorUserInfo) } @@ -350,7 +359,7 @@ public func callOSStatusAPI( var ret = 0 as T let err = closure(&ret) - guard err == noErr else { + guard err == OSStatusError.Codes.noErr else { throw osStatusError(err, description: errorDescription, path: path, custom: customErrorUserInfo) } @@ -373,9 +382,9 @@ public func callOSStatusAPI( var ret: T? = nil let err = closure(&ret) - guard err == noErr, let ret = ret else { + guard err == OSStatusError.Codes.noErr, let ret = ret else { throw osStatusError( - err != noErr ? err : OSStatus(OSStatusError.Codes.coreFoundationUnknownErr), + err != OSStatusError.Codes.noErr ? err : OSStatus(OSStatusError.Codes.coreFoundationUnknownErr), description: errorDescription, path: path, custom: customErrorUserInfo @@ -399,9 +408,9 @@ public func callOSStatusAPI( var ret: T? = nil let err = closure(&ret) - guard err == noErr, let ret = ret else { + guard err == OSStatusError.Codes.noErr, let ret = ret else { throw osStatusError( - err != noErr ? err : OSStatus(OSStatusError.Codes.coreFoundationUnknownErr), + err != OSStatusError.Codes.noErr ? err : OSStatus(OSStatusError.Codes.coreFoundationUnknownErr), description: errorDescription, path: path ) @@ -427,7 +436,7 @@ public func callOSStatusAPI( ) throws { let err = closure() - guard err == noErr else { + guard err == OSStatusError.Codes.noErr else { throw osStatusError(err, description: errorDescription, url: url, custom: customErrorUserInfo) } } @@ -450,7 +459,7 @@ public func callOSStatusAPI( let err = closure(ptr) - guard err == noErr else { + guard err == OSStatusError.Codes.noErr else { throw osStatusError(err, description: errorDescription, url: url, custom: customErrorUserInfo) } @@ -473,9 +482,9 @@ public func callOSStatusAPI( var ret: T? = nil let err = closure(&ret) - guard err == noErr, let ret = ret else { + guard err == OSStatusError.Codes.noErr, let ret = ret else { throw osStatusError( - err != noErr ? err : OSStatus(OSStatusError.Codes.coreFoundationUnknownErr), + err != OSStatusError.Codes.noErr ? err : OSStatus(OSStatusError.Codes.coreFoundationUnknownErr), description: errorDescription, url: url, custom: customErrorUserInfo @@ -487,7 +496,11 @@ public func callOSStatusAPI( extension OSStatusError { internal static func getFailureReason(_ osStatus: OSStatus) -> String? { +#if canImport(Darwin) SecCopyErrorMessageString(osStatus, nil) as String? +#else + nil +#endif } public static var errorDomain: String { NSOSStatusErrorDomain } @@ -495,8 +508,8 @@ extension OSStatusError { public var errorUserInfo: [String : Any] { self.metadata.toUserInfo() } } +#if canImport(Darwin) extension OSStatusError: CustomNSError {} - #endif #endif diff --git a/Sources/CSErrors/POSIX Errors.swift b/Sources/CSErrors/POSIX Errors.swift index d7a05e8..b6c3ef5 100644 --- a/Sources/CSErrors/POSIX Errors.swift +++ b/Sources/CSErrors/POSIX Errors.swift @@ -5,14 +5,24 @@ // Created by Charles Srstka on 1/10/23. // +#if canImport(SystemPackage) +import SystemPackage +#else import System +#endif #if canImport(Darwin) import Darwin -func getErrno() -> Int32 { Darwin.errno } +@inline(__always) @usableFromInline func getErrno() -> Int32 { Darwin.errno } #elseif canImport(Glibc) import Glibc -func getErrno() -> Int32 { Glibc.errno } +@inline(__always) @usableFromInline func getErrno() -> Int32 { Glibc.errno } +#endif + +#if Foundation && canImport(Darwin) +private let _posixErrorDomain = NSPOSIXErrorDomain +#else +private let _posixErrorDomain = "NSPOSIXErrorDomain" #endif #if Foundation @@ -21,23 +31,31 @@ import FoundationEssentials #else import Foundation #endif -#endif -internal var posixErrorDomain: String { "NSPOSIXErrorDomain" } +extension POSIXError { + public static let posixErrorDomain = _posixErrorDomain +} +#endif extension Error { public func toErrno() -> Int32? { if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, macCatalyst 14.0, *), versionCheck(11), - let err = self as? System.Errno { + let err = self as? Errno { return err.rawValue } +#if Foundation + if let posixErr = self as? POSIXError { + return posixErr.code.rawValue + } +#endif + if let osStatusErr = self as? OSStatusError, let err = translateOSStatusToPOSIX(osStatusErr.rawValue) { return err } switch self._domain { - case posixErrorDomain: + case _posixErrorDomain: return Int32(self._code) case OSStatusError.osStatusErrorDomain: return translateOSStatusToPOSIX(OSStatus(self._code)) @@ -50,21 +68,26 @@ extension Error { #if Foundation -public func errno(_ code: Int32 = Foundation.errno, url: URL?, isWrite: Bool = false) -> any Error { +public func errno(_ code: Int32 = getErrno(), url: URL?, isWrite: Bool = false) -> any Error { if code == 0 { return CocoaError(isWrite ? .fileWriteUnknown : .fileReadUnknown) } let cocoaCode = cocoaCode(posixCode: code, isWrite: isWrite) + +#if canImport(Darwin) let err: any Error if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, macCatalyst 14.0, *), versionCheck(11) { - err = System.Errno(rawValue: code) + err = Errno(rawValue: code) } else if let posixCode = POSIXErrorCode(rawValue: code) { err = POSIXError(posixCode) } else { err = NSError(domain: NSPOSIXErrorDomain, code: Int(code)) } +#else + let err = Errno(rawValue: code) +#endif if err.isCancelledError { return CocoaError(.userCancelled, url: url, underlying: err) @@ -99,7 +122,7 @@ public func errno(_ code: Int32? = nil, path: FilePath, isWrite: Bool = false) - return GenericError.unknownError(isWrite: isWrite) } - return System.Errno(rawValue: code) + return Errno(rawValue: code) #endif } @@ -123,10 +146,10 @@ public func errno(_ code: Int32? = nil, path: String? = nil, isWrite: Bool = fal } guard #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, macCatalyst 14.0, *), versionCheck(11) else { - return GenericError(_domain: "NSPOSIXErrorDomain", _code: Int(code)) + return GenericError(_domain: _posixErrorDomain, _code: Int(code)) } - return System.Errno(rawValue: code) + return Errno(rawValue: code) #endif } @@ -367,7 +390,11 @@ internal func translateErrno(_ code: Int32, path: FilePath, isWrite: Bool) -> an return translateErrno(code, path: path.string, isWrite: isWrite) } +#if canImport(FoundationEssentials) + return errno(code, url: URL(filePath: path.string), isWrite: isWrite) +#else return errno(code, url: URL(filePath: path), isWrite: isWrite) +#endif } internal func translateErrno(_ code: Int32, path: String?, isWrite: Bool) -> any Error { diff --git a/Sources/CSErrors/Recoverability.swift b/Sources/CSErrors/Recoverability.swift index eb52d0c..f6734e9 100644 --- a/Sources/CSErrors/Recoverability.swift +++ b/Sources/CSErrors/Recoverability.swift @@ -5,7 +5,8 @@ //// Created by Charles Srstka on 1/10/23. //// -#if Foundation +#if Foundation && canImport(Darwin) + #if canImport(FoundationEssentials) import FoundationEssentials #else diff --git a/Sources/CSErrors/Stderr.swift b/Sources/CSErrors/Stderr.swift index 592e420..92a04bd 100644 --- a/Sources/CSErrors/Stderr.swift +++ b/Sources/CSErrors/Stderr.swift @@ -5,7 +5,11 @@ // Created by Charles Srstka on 3/12/23. // +#if canImport(SystemPackage) +import SystemPackage +#else import System +#endif #if canImport(Darwin) import Darwin @@ -20,11 +24,13 @@ public struct StandardErrorStream: TextOutputStream { try string.withCString { let len = self.lengthOfString($0) try UnsafeBufferPointer(start: $0, count: len).withMemoryRebound(to: UInt8.self) { buf in +#if canImport(Darwin) guard #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, macCatalyst 14.0, *), versionCheck(11) else { fwrite(buf.baseAddress, 1, buf.count, stderr) return } +#endif _ = try FileDescriptor.standardError.writeAll(buf) } diff --git a/Sources/CSErrors/URLError+CSErrors.swift b/Sources/CSErrors/URLError+CSErrors.swift index 0dd298b..d20a452 100644 --- a/Sources/CSErrors/URLError+CSErrors.swift +++ b/Sources/CSErrors/URLError+CSErrors.swift @@ -5,7 +5,8 @@ // Created by Charles Srstka on 4/17/20. // -#if Foundation +#if Foundation && canImport(Darwin) + #if canImport(FoundationEssentials) import FoundationEssentials #else diff --git a/Tests/CSErrorsTests/CocoaErrorTests.swift b/Tests/CSErrorsTests/CocoaErrorTests.swift index 4b71886..bcecfce 100644 --- a/Tests/CSErrorsTests/CocoaErrorTests.swift +++ b/Tests/CSErrorsTests/CocoaErrorTests.swift @@ -7,10 +7,21 @@ #if Foundation -import CSErrors +@testable import CSErrors +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else import System -import Testing +#endif + @Suite("CocoaError Tests") struct CocoaErrorTests { @@ -26,10 +37,13 @@ struct CocoaErrorTests { ) #expect(err.code == .fileNoSuchFile) +#if canImport(Darwin) #expect(err.errorCode == CocoaError.Code.fileNoSuchFile.rawValue) #expect(err.localizedDescription == "no can do") - #expect((err.userInfo[NSLocalizedFailureReasonErrorKey] as? String) == "i don't wanna") +#endif + #expect((err.userInfo[NSLocalizedDescriptionKey] as? String) == "no can do") #expect(err.stringEncoding == .macOSRoman) + #expect((err.userInfo[NSLocalizedFailureReasonErrorKey] as? String) == "i don't wanna") #expect(err.url == URL(filePath: "/path/to/file")) } @@ -44,8 +58,11 @@ struct CocoaErrorTests { ) #expect(err.code == .fileNoSuchFile) +#if canImport(Darwin) #expect(err.errorCode == CocoaError.Code.fileNoSuchFile.rawValue) #expect(err.localizedDescription == "no can do") +#endif + #expect((err.userInfo[NSLocalizedDescriptionKey] as? String) == "no can do") #expect((err.userInfo[NSLocalizedFailureReasonErrorKey] as? String) == "i don't wanna") #expect(err.stringEncoding == .macOSRoman) #expect(err.url == URL(filePath: "/path/to/file")) @@ -55,7 +72,9 @@ struct CocoaErrorTests { func testFileNotFound() { #expect(CocoaError(.fileNoSuchFile).isFileNotFoundError) #expect(CocoaError(.fileReadNoSuchFile).isFileNotFoundError) +#if canImport(Darwin) #expect(CocoaError(.ubiquitousFileUnavailable).isFileNotFoundError) +#endif #expect(!CocoaError(.fileWriteNoPermission).isFileNotFoundError) } diff --git a/Tests/CSErrorsTests/ErrnoCocoaMappingTests.swift b/Tests/CSErrorsTests/ErrnoCocoaMappingTests.swift index d7c0562..b707411 100644 --- a/Tests/CSErrorsTests/ErrnoCocoaMappingTests.swift +++ b/Tests/CSErrorsTests/ErrnoCocoaMappingTests.swift @@ -8,9 +8,26 @@ #if Foundation @testable import CSErrors +import Testing + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#endif + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else import System -import Testing +#endif + @Suite("Map errno to CocoaError") struct ErrnoCocoaMappingTests { @@ -45,7 +62,9 @@ struct ErrnoCocoaMappingTests { try self.checkMapping(code: EFBIG, cocoaCode: .fileReadTooLarge) try self.checkMapping(code: ENOSPC, cocoaCode: .fileWriteOutOfSpace) try self.checkMapping(code: EROFS, cocoaCode: .fileWriteVolumeReadOnly) +#if canImport(Darwin) try self.checkMapping(code: EFTYPE, cocoaCode: .fileReadCorruptFile) +#endif try self.checkMapping(code: ECANCELED, cocoaCode: .userCancelled) } @@ -58,8 +77,8 @@ struct ErrnoCocoaMappingTests { @Test("Map zero errno to unknown error, since a zero shouldn't have been raised") func testMappingZeroError() { - #expect(errno(0, isWrite: false) as? CocoaError == CocoaError(.fileReadUnknown)) - #expect(errno(0, isWrite: true) as? CocoaError == CocoaError(.fileWriteUnknown)) + #expect((errno(0, isWrite: false) as? CocoaError)?.code == .fileReadUnknown) + #expect((errno(0, isWrite: true) as? CocoaError)?.code == .fileWriteUnknown) } @Test("URL propagation on old macOS versions") @@ -77,6 +96,7 @@ struct ErrnoCocoaMappingTests { emulateMacOSVersion(11, closure: check) } +#if os(macOS) @Test("Return POSIXError on macOS 10.x") func testReturnPOSIXErrorOnMacOS10() { emulateMacOSVersion(10) { @@ -89,6 +109,7 @@ struct ErrnoCocoaMappingTests { } } } +#endif } #endif diff --git a/Tests/CSErrorsTests/ErrnoURLSupportTests.swift b/Tests/CSErrorsTests/ErrnoURLSupportTests.swift index 2616c31..795eca2 100644 --- a/Tests/CSErrorsTests/ErrnoURLSupportTests.swift +++ b/Tests/CSErrorsTests/ErrnoURLSupportTests.swift @@ -7,19 +7,37 @@ #if Foundation -import CSErrors +@testable import CSErrors +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else import System -import Testing +#endif @Suite("Errno URL Support Tests") struct ErrnoURLSupportTests { private func checkTypeAndCode(error: some Error, code: Int32, cocoaCode: CocoaError.Code) throws { let cocoaError = try #require(error as? CocoaError) - let errnoError = try #require(cocoaError.underlyingError as? Errno) - #expect(cocoaError.code == cocoaCode) + +#if canImport(Darwin) + let errnoError = try #require(cocoaError.underlyingError as? Errno) #expect(errnoError.rawValue == code) +#endif } private func checkMapping(code: Int32, cocoaCode: CocoaError.Code, isWrite: Bool = false) throws { @@ -45,7 +63,9 @@ struct ErrnoURLSupportTests { try self.checkMapping(code: EFBIG, cocoaCode: .fileReadTooLarge) try self.checkMapping(code: ENOSPC, cocoaCode: .fileWriteOutOfSpace) try self.checkMapping(code: EROFS, cocoaCode: .fileWriteVolumeReadOnly) +#if canImport(Darwin) try self.checkMapping(code: EFTYPE, cocoaCode: .fileReadCorruptFile) +#endif try self.checkMapping(code: ECANCELED, cocoaCode: .userCancelled) } @@ -62,19 +82,19 @@ struct ErrnoURLSupportTests { func testSystemErrno() throws { let url = URL(filePath: "/path/to/some/file") - Foundation.errno = ENOENT + setErrno(ENOENT) let enoent = errno(url: url, isWrite: true) try self.checkTypeAndCode(error: enoent, code: ENOENT, cocoaCode: .fileNoSuchFile) #expect((enoent as? CocoaError)?.userInfo[NSURLErrorKey] as? URL == url) #expect((enoent as? CocoaError)?.userInfo[NSFilePathErrorKey] as? String == url.path) - Foundation.errno = ECANCELED + setErrno(ECANCELED) let canceled = errno(url: url) try self.checkTypeAndCode(error: canceled, code: ECANCELED, cocoaCode: .userCancelled) #expect((canceled as? CocoaError)?.userInfo[NSURLErrorKey] as? URL == url) #expect((canceled as? CocoaError)?.userInfo[NSFilePathErrorKey] as? String == url.path) - Foundation.errno = EINTR + setErrno(EINTR) #expect(errno(url: url) as? Errno == Errno.interrupted) } @@ -83,8 +103,35 @@ struct ErrnoURLSupportTests { let url = FileManager.default.temporaryDirectory.appending(component: UUID().uuidString) try "Testing 1 2 3".write(to: url, atomically: true, encoding: .utf8) - #expect(throws: Never.self) { try callPOSIXFunction(expect: .zero, url: url, isWrite: true) { unlink(url.path) } } + #expect(try #require(throws: Errno.self) { + try callPOSIXFunction(url: url) { fopen(url.path, "z") } + } == .invalidArgument) + + #expect(throws: Never.self) { + let file = try callPOSIXFunction(url: url) { fopen(url.path, "r") } + fclose(file) + } + + let limit = try callPOSIXFunction(expect: .zero, url: url) { +#if canImport(Darwin) + getrlimit(RLIMIT_CORE, $0) +#else + getrlimit(Int32(RLIMIT_CORE.rawValue), $0) +#endif + } + #expect(limit.rlim_max >= limit.rlim_cur) + + #expect(try #require(throws: Errno.self) { + _ = try callPOSIXFunction(expect: .zero, url: url) { getrlimit(999, $0) } + } == .invalidArgument) + + try callPOSIXFunction(expect: .zero, url: url) { kill(getpid(), 0) } + #expect(try #require(throws: Errno.self) { + try callPOSIXFunction(expect: .zero, url: url) { kill(getpid(), -1) } + } == .invalidArgument) + + #expect(throws: Never.self) { try callPOSIXFunction(expect: .zero, url: url, isWrite: true) { unlink(url.path) } } let unlinkError = try #require(throws: CocoaError.self) { try callPOSIXFunction(expect: .zero, url: url, isWrite: true) { unlink(url.path) } @@ -101,26 +148,6 @@ struct ErrnoURLSupportTests { #expect(opendirError.code == .fileReadNoSuchFile) #expect((opendirError.userInfo[NSURLErrorKey] as? URL) == url) #expect((opendirError.userInfo[NSFilePathErrorKey] as? String) == url.path) - - #expect(try #require(throws: Errno.self) { try callPOSIXFunction(url: url) { acl_init(-1) } } == .invalidArgument) - - let acl: acl_t = try callPOSIXFunction(url: url) { acl_init(0) } - defer { acl_free(UnsafeMutableRawPointer(acl)) } - - var optionalACL: acl_t? = acl - - let aclEntry = try callPOSIXFunction(expect: .zero, url: url) { acl_create_entry(&optionalACL, $0) } - - #expect(try #require(throws: Errno.self) { - try callPOSIXFunction(url: url) { acl_get_qualifier(aclEntry) } - } == .invalidArgument) - - try callPOSIXFunction(expect: .zero, url: url) { acl_set_tag_type(aclEntry, ACL_EXTENDED_ALLOW) } - - #expect(throws: Never.self) { - let qualifier = try callPOSIXFunction(url: url) { acl_get_qualifier(aclEntry) } - acl_free(qualifier) - } } } diff --git a/Tests/CSErrorsTests/ErrorMetadataTests.swift b/Tests/CSErrorsTests/ErrorMetadataTests.swift index 922585f..a299b9f 100644 --- a/Tests/CSErrorsTests/ErrorMetadataTests.swift +++ b/Tests/CSErrorsTests/ErrorMetadataTests.swift @@ -8,9 +8,19 @@ #if Foundation @testable import CSErrors +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else import System -import Testing +#endif @Suite("Error Metadata Tests") struct ErrorMetadataTests { @@ -146,7 +156,6 @@ import Testing let metadata = ErrorMetadata(description: "Et Tu Brute", stringEncoding: .isoLatin1) #expect(metadata.stringEncoding == .isoLatin1) - #expect(metadata.toUserInfo()[NSStringEncodingErrorKey] as? UInt == String.Encoding.isoLatin1.rawValue) } @Test("Non-file URL property") diff --git a/Tests/CSErrorsTests/HTTPErrorTests.swift b/Tests/CSErrorsTests/HTTPErrorTests.swift index f1c2a04..541d93a 100644 --- a/Tests/CSErrorsTests/HTTPErrorTests.swift +++ b/Tests/CSErrorsTests/HTTPErrorTests.swift @@ -6,9 +6,14 @@ // import CSErrors -import Foundation import Testing +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + @Suite("HTTPError Tests") struct HTTPErrorTests { @@ -20,7 +25,7 @@ struct HTTPErrorTests { #expect(err.statusCode == statusCode) #expect(err._code == statusCode) -#if Foundation +#if Foundation && canImport(Darwin) let reason = HTTPURLResponse.localizedString(forStatusCode: statusCode) #expect(err.failureReason == "HTTP \(statusCode) (\(reason))") #expect(err.errorDescription == "HTTP \(statusCode) (\(reason))") @@ -82,8 +87,13 @@ struct HTTPErrorTests { #expect(err.statusCode == statusCode) #expect(err._code == statusCode) +#if canImport(Darwin) #expect(err.failureReason == "HTTP \(statusCode) (\(reason))") #expect(err.errorDescription == "HTTP \(statusCode) (\(reason))") +#else + #expect(err.failureReason == "HTTP \(statusCode)") + #expect(err.errorDescription == "HTTP \(statusCode)") +#endif } } #endif diff --git a/Tests/CSErrorsTests/NSErrorTests.swift b/Tests/CSErrorsTests/NSErrorTests.swift index ad5d8b1..53c59c7 100644 --- a/Tests/CSErrorsTests/NSErrorTests.swift +++ b/Tests/CSErrorsTests/NSErrorTests.swift @@ -5,7 +5,7 @@ // Created by Charles Srstka on 11/7/23. // -#if Foundation +#if Foundation && canImport(Darwin) import Foundation import Testing diff --git a/Tests/CSErrorsTests/OSStatusErrorTests.swift b/Tests/CSErrorsTests/OSStatusErrorTests.swift index 43778ac..799e98d 100644 --- a/Tests/CSErrorsTests/OSStatusErrorTests.swift +++ b/Tests/CSErrorsTests/OSStatusErrorTests.swift @@ -7,10 +7,20 @@ #if canImport(Darwin) -import System -import Testing @testable import CSErrors +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else +import System +#endif @Suite("OSStatusError Tests") struct OSStatusErrorTests { @@ -402,7 +412,13 @@ struct OSStatusErrorTests { #expect((err as? OSStatusError)?.rawValue == OSStatusError.Codes.eofErr) let userInfo = (err as NSError).userInfo - #expect(userInfo[NSStringEncodingErrorKey] as? UInt == String.Encoding.windowsCP1250.rawValue) + +#if Foundation && canImport(Darwin) + #expect((userInfo[NSStringEncodingErrorKey] as? UInt) == String.Encoding.windowsCP1250.rawValue) +#endif + + #expect((userInfo[NSStringEncodingErrorKeyNonDarwin] as? Int) == Int(String.Encoding.windowsCP1250.rawValue)) + #expect(userInfo[NSURLErrorKey] as? URL == URL(filePath: "/path/to/file")) #expect(userInfo[NSFilePathErrorKey] as? String == "/path/to/file") } diff --git a/Tests/CSErrorsTests/POSIXErrorTests.swift b/Tests/CSErrorsTests/POSIXErrorTests.swift index e3ea038..0374e53 100644 --- a/Tests/CSErrorsTests/POSIXErrorTests.swift +++ b/Tests/CSErrorsTests/POSIXErrorTests.swift @@ -5,6 +5,8 @@ // Created by Charles Srstka on 1/15/23. // +#if Foundation + import Testing @testable import CSErrors @@ -58,8 +60,8 @@ struct POSIXErrorTests { #expect(POSIXError(.ENOENT).isFileNotFoundError) #expect(!POSIXError(.EINVAL).isFileNotFoundError) - #expect(GenericError(_domain: NSPOSIXErrorDomain, _code: Int(ENOENT)).isFileNotFoundError) - #expect(!GenericError(_domain: NSPOSIXErrorDomain, _code: Int(EINVAL)).isFileNotFoundError) + #expect(GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(ENOENT)).isFileNotFoundError) + #expect(!GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(EINVAL)).isFileNotFoundError) } @Test("isPermissionError") @@ -72,9 +74,9 @@ struct POSIXErrorTests { #expect(POSIXError(.EPERM).isPermissionError) #expect(!POSIXError(.ENOENT).isPermissionError) - #expect(GenericError(_domain: NSPOSIXErrorDomain, _code: Int(EACCES)).isPermissionError) - #expect(GenericError(_domain: NSPOSIXErrorDomain, _code: Int(EPERM)).isPermissionError) - #expect(!GenericError(_domain: NSPOSIXErrorDomain, _code: Int(ENOENT)).isPermissionError) + #expect(GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(EACCES)).isPermissionError) + #expect(GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(EPERM)).isPermissionError) + #expect(!GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(ENOENT)).isPermissionError) } @Test("isCancelledError") @@ -85,19 +87,23 @@ struct POSIXErrorTests { #expect(POSIXError(.ECANCELED).isCancelledError) #expect(!POSIXError(.EINVAL).isCancelledError) - #expect(GenericError(_domain: NSPOSIXErrorDomain, _code: Int(ECANCELED)).isCancelledError) - #expect(!GenericError(_domain: NSPOSIXErrorDomain, _code: Int(EINVAL)).isCancelledError) + #expect(GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(ECANCELED)).isCancelledError) + #expect(!GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(EINVAL)).isCancelledError) } @Test("toErrno() conversion") func testToErrno() { + let kPOSIXErrorEINVAL = 100022 + #expect(Errno.invalidArgument.toErrno() == EINVAL) #expect(OSStatusError(rawValue: OSStatus(kPOSIXErrorEINVAL)).toErrno() == EINVAL) #expect(POSIXError(.EINVAL).toErrno() == EINVAL) - #expect(NSError(domain: NSPOSIXErrorDomain, code: Int(EINVAL)).toErrno() == EINVAL) - #expect(GenericError(_domain: NSPOSIXErrorDomain, _code: Int(EINVAL)).toErrno() == EINVAL) - #expect(NSError(domain: NSOSStatusErrorDomain, code: kPOSIXErrorEINVAL).toErrno() == EINVAL) - #expect(GenericError(_domain: NSOSStatusErrorDomain, _code: kPOSIXErrorEINVAL).toErrno() == EINVAL) + #expect(GenericError(_domain: POSIXError.posixErrorDomain, _code: Int(EINVAL)).toErrno() == EINVAL) + #expect(GenericError(_domain: OSStatusError.osStatusErrorDomain, _code: kPOSIXErrorEINVAL).toErrno() == EINVAL) +#if canImport(Darwin) + #expect(NSError(domain: POSIXError.posixErrorDomain, code: Int(EINVAL)).toErrno() == EINVAL) + #expect(NSError(domain: OSStatusError.osStatusErrorDomain, code: kPOSIXErrorEINVAL).toErrno() == EINVAL) +#endif #expect(CocoaError(.fileReadNoSuchFile).toErrno() == nil) } @@ -139,18 +145,16 @@ struct POSIXErrorTests { @Test("errno(0) maps to unknown errors") func testZeroErrno() { - #expect(errno(0) as NSError == CocoaError(.fileReadUnknown) as NSError) - #expect(errno(0, path: "/dev/null") as NSError == CocoaError(.fileReadUnknown) as NSError) - #expect(errno(0, path: FilePath("/dev/null")) as NSError == CocoaError(.fileReadUnknown) as NSError) + #expect((errno(0) as? CocoaError)?.code == .fileReadUnknown) + #expect((errno(0, path: "/dev/null" as String) as? CocoaError)?.code == .fileReadUnknown) + #expect((errno(0, path: "/dev/null" as FilePath) as? CocoaError)?.code == .fileReadUnknown) - #expect(errno(0, isWrite: true) as NSError == CocoaError(.fileWriteUnknown) as NSError) - #expect(errno(0, path: "/dev/null", isWrite: true) as NSError == CocoaError(.fileWriteUnknown) as NSError) - #expect( - errno(0, path: FilePath("/dev/null"), isWrite: true) as NSError - == CocoaError(.fileWriteUnknown) as NSError - ) + #expect((errno(0, isWrite: true) as? CocoaError)?.code == .fileWriteUnknown) + #expect((errno(0, path: "/dev/null" as String, isWrite: true) as? CocoaError)?.code == .fileWriteUnknown) + #expect((errno(0, path: "/dev/null" as FilePath, isWrite: true) as? CocoaError)?.code == .fileWriteUnknown) } +#if os(macOS) @Test("errno behavior on macOS 10.x") func testErrnoOnMacOS10() { emulateMacOSVersion(10) { @@ -165,14 +169,15 @@ struct POSIXErrorTests { } #else #expect(err is GenericError) - #expect(err._domain == NSPOSIXErrorDomain) + #expect(err._domain == POSIXError.posixErrorDomain) #expect(err._code == Int(eachCode)) #endif } } } +#endif - @Test("callPOSIXFunction with .zero expectation") + @Test("callPOSIXFunction on function that returns zero on success") func testFunctionWithZeroReturn() throws { let url = FileManager.default.temporaryDirectory.appending(component: UUID().uuidString) @@ -181,24 +186,24 @@ struct POSIXErrorTests { self.assertErrno(.noSuchFileOrDirectory) { try callPOSIXFunction(expect: .zero) { unlink(url.path) } } } - @Test("callPOSIXFunction with .nonNegative expectation") + @Test("callPOSIXFunction on function that returns negative on error") func testFunctionWithNonNegativeReturn() throws { try { - let fd = try callPOSIXFunction(expect: .nonNegative) { Darwin.open("/dev/null", O_RDONLY) } - defer { #expect(Darwin.close(fd) == 0) } + let fd = try callPOSIXFunction(expect: .nonNegative) { open("/dev/null", O_RDONLY) } + defer { #expect(close(fd) == 0) } #expect(fd > 2) - var bytesRead = try callPOSIXFunction(expect: .nonNegative) { Darwin.read(fd, nil, 0) } + var bytesRead = try callPOSIXFunction(expect: .nonNegative) { read(fd, nil, 0) } #expect(bytesRead == 0) - bytesRead = try callPOSIXFunction(expect: .nonNegative, path: FilePath("/dev/null")) { Darwin.read(fd, nil, 0) } + bytesRead = try callPOSIXFunction(expect: .nonNegative, path: FilePath("/dev/null")) { read(fd, nil, 0) } #expect(bytesRead == 0) }() try { - let fd = try callPOSIXFunction(expect: .nonNegative) { Darwin.open("/dev/random", O_RDONLY) } - defer { #expect(Darwin.close(fd) == 0) } + let fd = try callPOSIXFunction(expect: .nonNegative) { open("/dev/random", O_RDONLY) } + defer { #expect(close(fd) == 0) } #expect(fd > 2) @@ -206,7 +211,7 @@ struct POSIXErrorTests { var data = Data(count: 10) return data.withUnsafeMutableBytes { - Darwin.read(fd, $0.baseAddress, $0.count) + read(fd, $0.baseAddress, $0.count) } } #expect(bytesRead == 10) @@ -215,24 +220,24 @@ struct POSIXErrorTests { var data = Data(count: 10) return data.withUnsafeMutableBytes { - Darwin.read(fd, $0.baseAddress, $0.count) + read(fd, $0.baseAddress, $0.count) } } #expect(bytesRead == 10) }() self.assertErrno(.noSuchFileOrDirectory) { - try callPOSIXFunction(expect: .nonNegative) { Darwin.open(Self.nonexistentPath, O_RDONLY) } + try callPOSIXFunction(expect: .nonNegative) { open(Self.nonexistentPath, O_RDONLY) } } self.assertErrno(.noSuchFileOrDirectory) { try callPOSIXFunction(expect: .nonNegative, path: FilePath(Self.nonexistentPath)) { - Darwin.open(Self.nonexistentPath, O_RDONLY) + open(Self.nonexistentPath, O_RDONLY) } } } - @Test("callPOSIXFunction with .specific expectation") + @Test("callPOSIXFunction on function that returns a specific value for success") func testFunctionWithSpecificReturn() throws { func someWeirdThingThatExpects5(_ i: Int32) -> Int32 { if i != 5 { @@ -248,7 +253,7 @@ struct POSIXErrorTests { } } - @Test("callPOSIXFunction with .notSpecific expectation") + @Test("callPOSIXFunction on function that returns a specific value for error") func testFunctionWithNotSpecificReturn() { #expect(throws: Never.self) { try callPOSIXFunction(expect: .notSpecific(-1)) { fcntl(STDOUT_FILENO, F_GETFD) } } @@ -261,16 +266,16 @@ struct POSIXErrorTests { #expect(throws: Never.self) { try callPOSIXFunction(expect: .notSpecific(-1)) { returnsOtherNegative() } } } - @Test("callPOSIXFunction with .zero and path — lstat()") + @Test("callPOSIXFunction on function that returns by reference") func testReturnByReference() throws { - var url = FileManager.default.temporaryDirectory.appending(component: UUID().uuidString) + let url = FileManager.default.temporaryDirectory.appending(component: UUID().uuidString) try "Hello World".write(to: url, atomically: true, encoding: .ascii) defer { _ = try? FileManager.default.removeItem(at: url) } - var resourceValues = try url.resourceValues(forKeys: [.fileSecurityKey]) - CFFileSecuritySetMode(resourceValues.fileSecurity as CFFileSecurity?, 0o751) - CFFileSecuritySetOwner(resourceValues.fileSecurity as CFFileSecurity?, getuid()) - try url.setResourceValues(resourceValues) + try FileManager.default.setAttributes( + [.ownerAccountID: getuid(), .posixPermissions: 0o751], + ofItemAtPath: url.path + ) let info = try callPOSIXFunction(expect: .zero, path: url.path) { lstat(url.path, $0) } #expect(info.st_size == 11) @@ -279,9 +284,7 @@ struct POSIXErrorTests { try "Why Hello There World".write(to: url, atomically: true, encoding: .ascii) - resourceValues = try url.resourceValues(forKeys: [.fileSecurityKey]) - CFFileSecuritySetMode(resourceValues.fileSecurity as CFFileSecurity?, 0o644) - try url.setResourceValues(resourceValues) + try FileManager.default.setAttributes([.posixPermissions: 0o644], ofItemAtPath: url.path) let info2 = try callPOSIXFunction(expect: .zero, path: FilePath(url.path)) { lstat(url.path, $0) } #expect(info2.st_size == 21) @@ -316,23 +319,33 @@ struct POSIXErrorTests { #endif } - @Test("callPOSIXFunction with direct pointer returns — opendir()") + @Test("callPOSIXFunction on function that returns pointer directly") func testDirectPointerReturn() throws { let tempURL = FileManager.default.temporaryDirectory let tempFileName = UUID().uuidString let tempFileURL = tempURL.appending(path: tempFileName) +#if canImport(Darwin) + typealias DirType = UnsafeMutablePointer + func nameLen(_ entry: UnsafeMutablePointer) -> Int { Int(entry.pointee.d_namlen) } +#else + typealias DirType = OpaquePointer + func nameLen(_ entry: UnsafeMutablePointer) -> Int { + withUnsafeBytes(of: entry.pointee.d_name) { strlen($0.baseAddress!) } + } +#endif + try Data().write(to: tempFileURL) defer { _ = try? FileManager.default.removeItem(at: tempFileURL) } - func checkDir(_ dir: UnsafeMutablePointer) -> Bool { + func checkDir(_ dir: DirType) -> Bool { defer { closedir(dir) } var foundIt = false while let entry = readdir(dir) { var nameBytes = entry.pointee.d_name let name = withUnsafePointer(to: &nameBytes) { - String(data: Data(bytes: $0, count: Int(entry.pointee.d_namlen)), encoding: .utf8) + String(data: Data(bytes: $0, count: nameLen(entry)), encoding: .utf8) } if name == tempFileName { @@ -357,80 +370,35 @@ struct POSIXErrorTests { try callPOSIXFunction(path: FilePath(tempURL.path)) { opendir(tempFileURL.path) } } == .notDirectory ) - - #expect( - try #require(throws: Errno.self) { - try callPOSIXFunction(path: "/tmp") { acl_init(-1) } - } == .invalidArgument - ) - - #expect( - try #require(throws: Errno.self) { - try callPOSIXFunction(path: FilePath("/tmp")) { acl_init(-1) } - } == .invalidArgument - ) - - let acl: acl_t = try callPOSIXFunction(path: "/tmp") { acl_init(0) } - defer { acl_free(UnsafeMutableRawPointer(acl)) } - - let acl2: acl_t = try callPOSIXFunction(path: FilePath("/tmp")) { acl_init(0) } - defer { acl_free(UnsafeMutableRawPointer(acl2)) } - - var optionalACL: acl_t? = acl - - let aclEntry = try callPOSIXFunction(expect: .zero) { acl_create_entry(&optionalACL, $0) } - - #expect( - try #require(throws: Errno.self) { - try callPOSIXFunction(path: "/tmp") { acl_get_qualifier(aclEntry) } - } == .invalidArgument - ) - - #expect( - try #require(throws: Errno.self) { - try callPOSIXFunction(path: FilePath("/tmp")) { acl_get_qualifier(aclEntry) } - } == .invalidArgument - ) - - try callPOSIXFunction(expect: .zero) { acl_set_tag_type(aclEntry, ACL_EXTENDED_ALLOW) } - - #expect(throws: Never.self) { - let qualifier: UnsafeMutableRawPointer? = try callPOSIXFunction(path: "/tmp") { - acl_get_qualifier(aclEntry) - } - acl_free(qualifier) - } - - #expect(throws: Never.self) { - let qualifier: UnsafeMutableRawPointer? = try callPOSIXFunction(path: FilePath("/tmp")) { - acl_get_qualifier(aclEntry) - } - acl_free(qualifier) - } } @Test("callPOSIXFunction with .returnValue errorFrom") func testDirectErrorReturn() throws { - var attr = try callPOSIXFunction(expect: .zero, errorFrom: .returnValue) { posix_spawnattr_init($0) } + var attr = pthread_mutexattr_t() + try callPOSIXFunction(expect: .zero, errorFrom: .returnValue) { pthread_mutexattr_init(&attr) } defer { #expect(throws: Never.self) { _ = try? callPOSIXFunction(expect: .zero, errorFrom: .returnValue) { - posix_spawnattr_destroy(&attr) + pthread_mutexattr_destroy(&attr) } } } #expect(throws: Never.self) { try callPOSIXFunction(expect: .zero, errorFrom: .returnValue) { - posix_spawnattr_setflags(&attr, 1) + pthread_mutexattr_settype(&attr, Int32(PTHREAD_MUTEX_NORMAL)) } } #expect( try #require(throws: Errno.self) { - try callPOSIXFunction(expect: .zero, errorFrom: .returnValue) { posix_spawnattr_setflags(nil, 1) } + try callPOSIXFunction(expect: .zero, errorFrom: .returnValue) { + pthread_mutexattr_settype(&attr, -99999) + } } == .invalidArgument ) } } +#endif + diff --git a/Tests/CSErrorsTests/RecoverabilityTests.swift b/Tests/CSErrorsTests/RecoverabilityTests.swift index 6506273..4b3f804 100644 --- a/Tests/CSErrorsTests/RecoverabilityTests.swift +++ b/Tests/CSErrorsTests/RecoverabilityTests.swift @@ -5,12 +5,22 @@ // Created by Charles Srstka on 1/16/23. // -#if Foundation +#if Foundation && canImport(Darwin) import CSErrors +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else import System -import Testing +#endif @Suite("Recoverability Tests") struct RecoverabilityTests { diff --git a/Tests/CSErrorsTests/StderrTests.swift b/Tests/CSErrorsTests/StderrTests.swift index f482c35..2cf07a6 100644 --- a/Tests/CSErrorsTests/StderrTests.swift +++ b/Tests/CSErrorsTests/StderrTests.swift @@ -6,9 +6,25 @@ // @testable import CSErrors +import Testing + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#endif + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else import System -import Testing +#endif @Suite("Stderr Tests", .serialized) struct StderrTests { @Test("Print to Stderr") diff --git a/Tests/CSErrorsTests/URLErrorTests.swift b/Tests/CSErrorsTests/URLErrorTests.swift index 75ec0bc..f7a5143 100644 --- a/Tests/CSErrorsTests/URLErrorTests.swift +++ b/Tests/CSErrorsTests/URLErrorTests.swift @@ -5,12 +5,22 @@ // Created by Charles Srstka on 1/16/23. // -#if Foundation +#if Foundation && canImport(Darwin) -import CSErrors +@testable import CSErrors +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif + +#if canImport(SystemPackage) +import SystemPackage +#else import System -import Testing +#endif @Suite("URLError Tests") struct URLErrorTests { @@ -42,7 +52,10 @@ struct URLErrorTests { #expect(userInfo[NSLocalizedRecoveryOptionsErrorKey] as? [String] == ["Go somewhere else", "Fret"]) #expect(userInfo[NSRecoveryAttempterErrorKey] as? String == "Complain to Webmaster") #expect(userInfo[NSHelpAnchorErrorKey] as? String == "Haaaaalp") +#if Foundation && canImport(Darwin) #expect(userInfo[NSStringEncodingErrorKey] as? UInt == String.Encoding.utf8.rawValue) +#endif + #expect(userInfo[NSStringEncodingErrorKeyNonDarwin] as? Int == Int(String.Encoding.utf8.rawValue)) #expect(userInfo[NSURLErrorKey] as? URL == url) #expect(userInfo[NSFilePathErrorKey] == nil) #expect(userInfo["foo"] as? String == "bar")