├── .github ├── release.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.LGPL ├── Package.swift ├── README.md ├── Sources └── SkipFoundation │ ├── AttributedString.swift │ ├── Bundle.swift │ ├── Calendar.swift │ ├── CharacterSet.swift │ ├── ComparisonResult.swift │ ├── Data.swift │ ├── Date.swift │ ├── DateComponents.swift │ ├── DateComponentsFormatter.swift │ ├── DateFormatter.swift │ ├── DateInterval.swift │ ├── DateIntervalFormatter.swift │ ├── Digest.swift │ ├── Dispatch.swift │ ├── FileManager.swift │ ├── Formatter.swift │ ├── HTTPURLResponse.swift │ ├── ISO8601DateFormatter.swift │ ├── IndexPath.swift │ ├── IndexSet.swift │ ├── JSONDecoder.swift │ ├── JSONEncoder.swift │ ├── JSONSerialization+Parser.swift │ ├── JSONSerialization.swift │ ├── Locale.swift │ ├── LocalizedStringResource.swift │ ├── Logger.swift │ ├── MarkdownNode.swift │ ├── NSError.swift │ ├── NSLock.swift │ ├── NotificationCenter.swift │ ├── Number.swift │ ├── NumberFormatter.swift │ ├── OSAllocatedUnfairLock.swift │ ├── OperationQueue.swift │ ├── ProcessInfo.swift │ ├── PropertyListSerialization.swift │ ├── RelativeDateTimeFormatter.swift │ ├── Scanner.swift │ ├── Skip │ ├── UrlEncoderUtil.kt │ └── skip.yml │ ├── SkipFoundation.swift │ ├── String.swift │ ├── StringEncoding.swift │ ├── StringLocalizationValue.swift │ ├── Thread.swift │ ├── TimeZone.swift │ ├── Timer.swift │ ├── URL.swift │ ├── URLComponents.swift │ ├── URLRequest.swift │ ├── URLResponse.swift │ ├── URLSession.swift │ ├── URLSessionConfiguration.swift │ ├── URLSessionTask.swift │ ├── UUID.swift │ ├── Unicode.swift │ ├── UserDefaults.swift │ └── XMLParser.swift └── Tests └── SkipFoundationTests ├── CoreLibs ├── TestAffineTransform.swift ├── TestCharacterSet.swift ├── TestCodable.swift ├── TestDecimal.swift ├── TestIndexPath.swift ├── TestIndexSet.swift ├── TestPersonNameComponents.swift ├── TestUUID.swift ├── TestUserDefaults.swift └── TestUtils.swift ├── DateTime ├── TestCalendar.swift ├── TestDate.swift ├── TestDateComponents.swift ├── TestDateInterval.swift └── TestTimeZone.swift ├── Formats ├── TestAttributedString.swift ├── TestAttributedStringCOW.swift ├── TestAttributedStringPerformance.swift ├── TestAttributedStringSupport.swift ├── TestJSONEncoder.swift ├── TestJSONSerialization.swift ├── TestMarkdown.swift ├── TestPropertyListEncoder.swift ├── TestPropertyListSerialization.swift ├── TestScanner.swift ├── TestXMLDocument.swift └── TestXMLParser.swift ├── Formatters ├── TestByteCountFormatter.swift ├── TestDateFormatter.swift ├── TestDateIntervalFormatter.swift ├── TestEnergyFormatter.swift ├── TestISO8601DateFormatter.swift ├── TestLengthFormatter.swift ├── TestMassFormatter.swift ├── TestNumberFormatter.swift └── TestRelativeDateTimeFormatter.swift ├── Foundation ├── BundleTests.swift ├── CurrencyTests.swift ├── DataTests.swift ├── DateTests.swift ├── DigestTests.swift ├── FileManagerTests.swift ├── FixtureValues.swift ├── JSONTests.swift ├── LocaleTests.swift ├── LoggerTests.swift ├── URLTests.swift ├── UUIDTests.swift └── Utilities.swift ├── Network ├── TestCachedURLResponse.swift ├── TestDataURLProtocol.swift ├── TestHTTPCookie.swift ├── TestHTTPCookieStorage.swift ├── TestHTTPURLResponse.swift ├── TestHost.swift ├── TestSocketPort.swift ├── TestURL.swift ├── TestURLCache.swift ├── TestURLComponents.swift ├── TestURLCredential.swift ├── TestURLCredentialStorage.swift ├── TestURLProtectionSpace.swift ├── TestURLProtocol.swift ├── TestURLRequest.swift ├── TestURLResponse.swift ├── TestURLSession.swift └── TestURLSessionFTP.swift ├── Resources ├── Localizable.xcstrings ├── NSString-ISO-8859-1-data.txt ├── NSString-UTF16-BE-data.txt ├── NSString-UTF16-LE-data.txt ├── NSString-UTF32-BE-data.txt ├── NSString-UTF32-LE-data.txt ├── NSStringTestData.txt ├── NSURLTestData.plist ├── NSXMLDTDTestData.xml ├── NSXMLDocumentTestData.xml ├── PropertyList-1.0.dtd ├── SampleInfo.plist ├── Test.plist ├── TestFileWithZeros.txt └── textasset.txt ├── Shims.swift ├── Skip ├── AndroidManifest.xml └── skip.yml ├── SkipFoundationTests.swift ├── System ├── TestBundle.swift ├── TestFileHandle.swift ├── TestFileManager.swift ├── TestNotification.swift ├── TestNotificationCenter.swift ├── TestNotificationQueue.swift ├── TestOperationQueue.swift ├── TestPipe.swift ├── TestProcess.swift ├── TestProcessInfo.swift ├── TestProgress.swift ├── TestRunLoop.swift ├── TestStream.swift ├── TestThread.swift └── TestTimer.swift ├── Units ├── TestDimension.swift ├── TestMeasurement.swift ├── TestUnit.swift ├── TestUnitConverter.swift ├── TestUnitInformationStorage.swift └── TestUnitVolume.swift └── XCSkipTests.swift /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - skipbuilder 7 | categories: 8 | - title: Breaking Change 9 | labels: 10 | - Semver-Major 11 | - breaking-change 12 | - title: Enhancement 13 | labels: 14 | - Semver-Minor 15 | - enhancement 16 | - title: Other Changes 17 | labels: 18 | - "*" 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: skip-foundation 2 | on: 3 | push: 4 | branches: [ main ] 5 | tags: "[0-9]+.[0-9]+.[0-9]+" 6 | schedule: 7 | - cron: '0 12,22 * * *' 8 | workflow_dispatch: 9 | pull_request: 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | call-workflow: 16 | uses: skiptools/actions/.github/workflows/skip-framework.yml@v1 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | xcodebuild*.log 9 | 10 | java_pid*.hprof 11 | 12 | .*.swp 13 | .DS_Store 14 | 15 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 16 | *.xcscmblueprint 17 | *.xccheckout 18 | 19 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 20 | build/ 21 | DerivedData/ 22 | .android/ 23 | .kotlin/ 24 | *.moved-aside 25 | *.pbxuser 26 | !default.pbxuser 27 | *.mode1v3 28 | !default.mode1v3 29 | *.mode2v3 30 | !default.mode2v3 31 | *.perspectivev3 32 | !default.perspectivev3 33 | 34 | ## Obj-C/Swift specific 35 | *.hmap 36 | 37 | ## App packaging 38 | *.ipa 39 | *.dSYM.zip 40 | *.dSYM 41 | 42 | ## Playgrounds 43 | timeline.xctimeline 44 | playground.xcworkspace 45 | 46 | # Swift Package Manager 47 | # 48 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 49 | Packages/ 50 | Package.pins 51 | Package.resolved 52 | *.xcodeproj 53 | 54 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 55 | # hence it is not needed unless you have added a package configuration file to your project 56 | .swiftpm 57 | 58 | .build/ 59 | 60 | # CocoaPods 61 | # 62 | # We recommend against adding the Pods directory to your .gitignore. However 63 | # you should judge for yourself, the pros and cons are mentioned at: 64 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 65 | # 66 | # Pods/ 67 | # 68 | # Add this line if you want to avoid checking in source code from the Xcode workspace 69 | # *.xcworkspace 70 | 71 | # Carthage 72 | # 73 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 74 | # Carthage/Checkouts 75 | 76 | Carthage/Build/ 77 | 78 | # Accio dependency management 79 | Dependencies/ 80 | .accio/ 81 | 82 | # fastlane 83 | # 84 | # It is recommended to not store the screenshots in the git repo. 85 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 86 | # For more information about the recommended setup visit: 87 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 88 | 89 | fastlane/report.xml 90 | fastlane/Preview.html 91 | fastlane/screenshots/**/*.png 92 | fastlane/test_output 93 | 94 | # Code Injection 95 | # 96 | # After new code Injection tools there's a generated folder /iOSInjectionProject 97 | # https://github.com/johnno1962/injectionforxcode 98 | 99 | iOSInjectionProject/ 100 | 101 | 102 | 103 | # Ignore Gradle project-specific cache directory 104 | .gradle 105 | 106 | # Ignore Gradle build output directory 107 | build 108 | 109 | # gradle properties 110 | local.properties 111 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "skip-foundation", 6 | defaultLocalization: "en", 7 | platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16), .watchOS(.v9), .macCatalyst(.v16)], 8 | products: [ 9 | .library(name: "SkipFoundation", targets: ["SkipFoundation"]), 10 | ], 11 | dependencies: [ 12 | .package(url: "https://source.skip.tools/skip.git", from: "1.5.14"), 13 | .package(url: "https://source.skip.tools/skip-lib.git", from: "1.3.6"), 14 | ], 15 | targets: [ 16 | .target(name: "SkipFoundation", dependencies: [.product(name: "SkipLib", package: "skip-lib")], plugins: [.plugin(name: "skipstone", package: "skip")]), 17 | .testTarget(name: "SkipFoundationTests", dependencies: ["SkipFoundation", .product(name: "SkipTest", package: "skip")], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]), 18 | ] 19 | ) 20 | 21 | if Context.environment["SKIP_BRIDGE"] ?? "0" != "0" { 22 | // all library types must be dynamic to support bridging 23 | package.products = package.products.map({ product in 24 | guard let libraryProduct = product as? Product.Library else { return product } 25 | return .library(name: libraryProduct.name, type: .dynamic, targets: libraryProduct.targets) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/AttributedString.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | public struct AttributedString: Hashable { 5 | // Allow e.g. SwiftUI to access our state 6 | public let string: String 7 | public let markdownNode: MarkdownNode? 8 | 9 | public init() { 10 | string = "" 11 | markdownNode = nil 12 | } 13 | 14 | public init(stringLiteral: String) { 15 | string = stringLiteral 16 | markdownNode = nil 17 | } 18 | 19 | public init(markdown: String) throws { 20 | string = markdown 21 | markdownNode = MarkdownNode.from(string: markdown) 22 | } 23 | 24 | public init(localized keyAndValue: String.LocalizationValue, /* options: AttributedString.FormattingOptions = [], */ table: String? = nil, bundle: Bundle? = nil, locale: Locale? = nil, comment: String? = nil) { 25 | let key = keyAndValue.patternFormat // interpolated string: "Hello \(name)" keyed as: "Hello %@" 26 | let (_, locfmt, locnode) = (bundle ?? Bundle.main).localizedInfo(forKey: key, value: nil, table: table, locale: locale) 27 | // re-interpret the placeholder strings in the resulting localized string with the string interpolation's values 28 | self.string = locfmt.format(*keyAndValue.stringInterpolation.values.toTypedArray()) 29 | self.markdownNode = locnode?.format(keyAndValue.stringInterpolation.values) 30 | } 31 | 32 | public init(localized key: String, table: String? = nil, bundle: Bundle? = nil, locale: Locale? = nil, comment: String? = nil) { 33 | let (locstring, _, locnode) = (bundle ?? Bundle.main).localizedInfo(forKey: key, value: nil, table: table, locale: locale) 34 | self.string = locstring 35 | self.markdownNode = locnode 36 | } 37 | 38 | public var description: String { 39 | return string 40 | } 41 | 42 | public static func ==(lhs: AttributedString, rhs: AttributedString) { 43 | return lhs.string == rhs.string 44 | } 45 | 46 | public func hash(into hasher: inout Hasher) { 47 | hasher.combine(string) 48 | } 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/ComparisonResult.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public enum ComparisonResult : Int { 6 | case orderedAscending = -1 7 | case orderedSame = 0 8 | case orderedDescending = 1 9 | } 10 | 11 | extension ComparisonResult { 12 | public static var ascending: ComparisonResult { .orderedAscending } 13 | public static var same: ComparisonResult { .orderedSame } 14 | public static var descending: ComparisonResult { .orderedDescending } 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/DateComponentsFormatter.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | @available(*, unavailable) 6 | public struct DateComponentsFormatter { 7 | } 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/DateInterval.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public struct DateInterval : Hashable, Comparable, Codable { 6 | public let start: Date 7 | 8 | public var end: Date { 9 | return start.addingTimeInterval(duration) 10 | } 11 | 12 | public let duration: TimeInterval 13 | 14 | public init() { 15 | self.init(start: Date(), duration: 0.0) 16 | } 17 | 18 | public init(start: Date, end: Date) { 19 | self.init(start: start, duration: end.timeIntervalSince1970 - start.timeIntervalSince1970) 20 | } 21 | 22 | public init(start: Date, duration: TimeInterval) { 23 | self.start = start 24 | self.duration = duration 25 | } 26 | 27 | public func intersects(_ dateInterval: DateInterval) -> Bool { 28 | return intersection(with: dateInterval) != nil 29 | } 30 | 31 | public func intersection(with dateInterval: DateInterval) -> DateInterval? { 32 | let start = max(self.start, dateInterval.start) 33 | let end = min(self.end, dateInterval.end) 34 | guard start <= end else { 35 | return nil 36 | } 37 | return DateInterval(start: start, end: end) 38 | } 39 | 40 | public func contains(_ date: Date) -> Bool { 41 | return start <= date && end >= date 42 | } 43 | 44 | public func compare(_ with: DateInterval) -> ComparisonResult { 45 | if self == with { 46 | return .orderedSame 47 | } else if self < with { 48 | return .orderedAscending 49 | } else { 50 | return .orderedDescending 51 | } 52 | } 53 | 54 | public static func == (lhs: DateInterval, rhs: DateInterval) -> Bool { 55 | return lhs.start == rhs.start && lhs.duration == rhs.duration 56 | } 57 | 58 | public static func <(lhs: DateInterval, rhs: DateInterval) -> Bool { 59 | return lhs.start < rhs.start || (lhs.start == rhs.start && lhs.duration < rhs.duration) 60 | } 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/DateIntervalFormatter.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | @available(*, unavailable) 6 | public struct DateIntervalFormatter { 7 | } 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Dispatch.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.GlobalScope 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | 9 | // Stubs that allow SkipModel to implement Publisher.receive(on:) for the main queue 10 | 11 | public protocol Scheduler { 12 | } 13 | 14 | public struct RunLoop : Scheduler { 15 | public static let main = RunLoop() 16 | 17 | public enum Mode: Int { 18 | case `default`, common, eventTracking, modalPanel, tracking 19 | } 20 | 21 | public struct SchedulerOptions { 22 | } 23 | 24 | private init() { 25 | } 26 | 27 | public func add(_ timer: Timer, forMode mode: Mode) { 28 | timer.start() // We don't yet support non-main run loops and timer always uses main 29 | } 30 | } 31 | 32 | public typealias DispatchWallTime = Double 33 | public typealias DispatchTime = Double 34 | 35 | // Mirror Double's cast functions, which typealiasing doesn't cover 36 | public func DispatchWallTime(number: Number) -> DispatchWallTime { 37 | return Double(number: number) 38 | } 39 | public func DispatchWallTime(number: UInt8) -> DispatchWallTime { 40 | return Double(number: number) 41 | } 42 | public func DispatchWallTime(number: UInt16) -> DispatchWallTime { 43 | return Double(number: number) 44 | } 45 | public func DispatchWallTime(number: UInt32) -> DispatchWallTime { 46 | return Double(number: number) 47 | } 48 | public func DispatchWallTime(number: UInt64) -> DispatchWallTime { 49 | return Double(number: number) 50 | } 51 | public func DispatchWallTime(string: String) -> DispatchWallTime? { 52 | return Double(string: string) 53 | } 54 | public func DispatchTime(number: Number) -> DispatchTime { 55 | return Double(number: number) 56 | } 57 | public func DispatchTime(number: UInt8) -> DispatchTime { 58 | return Double(number: number) 59 | } 60 | public func DispatchTime(number: UInt16) -> DispatchTime { 61 | return Double(number: number) 62 | } 63 | public func DispatchTime(number: UInt32) -> DispatchTime { 64 | return Double(number: number) 65 | } 66 | public func DispatchTime(number: UInt64) -> DispatchTime { 67 | return Double(number: number) 68 | } 69 | public func DispatchTime(string: String) -> DispatchTime? { 70 | return Double(string: string) 71 | } 72 | 73 | extension Double { 74 | public static func now() -> Double { 75 | Double(System.currentTimeMillis()) / 1000.0 76 | } 77 | } 78 | 79 | public struct DispatchQueue : Scheduler { 80 | public static let main = DispatchQueue() 81 | 82 | private init() { 83 | } 84 | 85 | @available(*, unavailable) 86 | public init(label: String, qos: Any, attributes: Any, autoreleaseFrequency: Any, target: DispatchQueue?) { 87 | } 88 | 89 | @available(*, unavailable) 90 | public static func global(qos: Any) -> DispatchQueue { 91 | fatalError() 92 | } 93 | 94 | public func async(execute: () -> Void) { 95 | GlobalScope.launch(Dispatchers.Main) { 96 | execute() 97 | } 98 | } 99 | 100 | // SKIP DECLARE: fun asyncAfter(deadline: Double, unusedp: Nothing? = null, execute: () -> Unit) 101 | public func asyncAfter(deadline: DispatchTime, execute: () -> Void) { 102 | GlobalScope.launch(Dispatchers.Main) { 103 | delay(Int64(deadline * 1000.0) - System.currentTimeMillis()) 104 | execute() 105 | } 106 | } 107 | 108 | @available(*, unavailable) 109 | public func asyncAfter(deadline: DispatchTime, qos: Any, flags: Any, execute: () -> Void) { 110 | } 111 | 112 | public func asyncAfter(wallDeadline: DispatchWallTime, execute: () -> Void) { 113 | GlobalScope.launch(Dispatchers.Main) { 114 | delay(Int64(wallDeadline * 1000.0) - System.currentTimeMillis()) 115 | execute() 116 | } 117 | } 118 | 119 | @available(*, unavailable) 120 | public func asyncAfter(wallDeadline: DispatchWallTime, qos: Any, flags: Any, execute: () -> Void) { 121 | } 122 | 123 | @available(*, unavailable) 124 | public func sync(execute: () -> Void) { 125 | } 126 | 127 | @available(*, unavailable) 128 | public func sync(execute: () -> Any) -> Any { 129 | fatalError() 130 | } 131 | 132 | @available(*, unavailable) 133 | public func sync(flags: Any, execute: () -> Any) -> Any { 134 | fatalError() 135 | } 136 | 137 | @available(*, unavailable) 138 | public func asyncAndWait(execute: () -> Void) { 139 | } 140 | 141 | @available(*, unavailable) 142 | public static func concurrentPerform(iterations: Int, execute: (Int) -> Void) { 143 | } 144 | 145 | @available(*, unavailable) 146 | public func async(group: Any, execute: () -> Void) { 147 | } 148 | 149 | @available(*, unavailable) 150 | public func async(group: Any?, qos: Any, flags: Any, execute: () -> Void) { 151 | } 152 | 153 | @available(*, unavailable) 154 | public var label: String { 155 | get { 156 | fatalError() 157 | } 158 | set { 159 | } 160 | } 161 | 162 | @available(*, unavailable) 163 | public var qos: Any? { 164 | get { 165 | fatalError() 166 | } 167 | set { 168 | } 169 | } 170 | 171 | @available(*, unavailable) 172 | public func setTarget(queue: Any?) { 173 | } 174 | 175 | @available(*, unavailable) 176 | public func setSpecific(key: Any, value: Any?) { 177 | } 178 | 179 | @available(*, unavailable) 180 | public func getSpecific(key: Any) -> Any? { 181 | fatalError() 182 | } 183 | 184 | @available(*, unavailable) 185 | public static func getSpecific(key: Any) -> Any? { 186 | fatalError() 187 | } 188 | 189 | @available(*, unavailable) 190 | public func dispatchMain() -> Any { 191 | fatalError() 192 | } 193 | 194 | @available(*, unavailable) 195 | public func schedule(options: Any?, _ operation: () -> Void) { 196 | } 197 | 198 | @available(*, unavailable) 199 | public func schedule(after: Any, tolerance: Any, options: Any?, _ operation: () -> Void) { 200 | } 201 | 202 | @available(*, unavailable) 203 | public func schedule(after: Any, interval: Any, tolerance: Any, options: Any?, _ operation: () -> Void) -> Any { 204 | fatalError() 205 | } 206 | } 207 | 208 | #endif 209 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Formatter.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public class Formatter { 6 | public func string(for obj: Any?) -> String? { 7 | return nil 8 | } 9 | 10 | @available(*, unavailable) 11 | public func attributedString(for obj: Any, withDefaultAttributes attrs: [AnyHashable : Any]? = nil) -> Any? { 12 | return nil 13 | } 14 | 15 | @available(*, unavailable) 16 | public func editingString(for obj: Any) -> String? { 17 | return nil 18 | } 19 | 20 | @available(*, unavailable) 21 | public func getObjectValue(_ obj: Any?, for string: String, errorDescription error: Any?) -> Bool { 22 | return false 23 | } 24 | 25 | @available(*, unavailable) 26 | public func isPartialStringValid(_ partialString: String, newEditingString newString: Any?, errorDescription error: Any?) -> Bool { 27 | return false 28 | } 29 | 30 | @available(*, unavailable) 31 | public func isPartialStringValid(_ partialStringPtr: Any, proposedSelectedRange proposedSelRangePtr: Any?, originalString origString: String, originalSelectedRange origSelRange: Any, errorDescription error: Any?) -> Bool { 32 | return false 33 | } 34 | 35 | public var formattingContext: Formatter.Context = .unknown 36 | 37 | @available(*, unavailable) 38 | public func getObjectValue(_ obj: Any?, for string: String, range rangep: Any?, unusedp: Nothing? = nil) throws { 39 | } 40 | } 41 | 42 | extension Formatter { 43 | public enum Context: Int { 44 | case unknown = 0 45 | @available(*, unavailable) 46 | case dynamic = 1 47 | case standalone = 2 48 | case listItem = 3 49 | case beginningOfSentence = 4 50 | case middleOfSentence = 5 51 | 52 | internal var capitalization: android.icu.text.DisplayContext { 53 | switch self { 54 | case .beginningOfSentence: 55 | android.icu.text.DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE 56 | // case .dynamic: 57 | // android.icu.text.DisplayContext.CAPITALIZATION_NONE 58 | case .listItem: 59 | android.icu.text.DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU 60 | case .middleOfSentence: 61 | android.icu.text.DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE 62 | case .standalone: 63 | android.icu.text.DisplayContext.CAPITALIZATION_FOR_STANDALONE 64 | case .unknown: 65 | android.icu.text.DisplayContext.CAPITALIZATION_NONE 66 | default: 67 | android.icu.text.DisplayContext.CAPITALIZATION_NONE 68 | } 69 | } 70 | } 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/IndexPath.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public struct IndexPath : Codable, Comparable, Hashable, CustomStringConvertible, MutableCollection, RandomAccessCollection, KotlinConverting> { 6 | public typealias Element = Int 7 | 8 | private let arrayList: ArrayList = ArrayList() 9 | 10 | override var mutableList: MutableList { 11 | return arrayList 12 | } 13 | override func willMutateStorage() { 14 | willmutate() 15 | } 16 | override func didMutateStorage() { 17 | didmutate() 18 | } 19 | 20 | public init() { 21 | } 22 | 23 | public init(indexes: Sequence) { 24 | arrayList.addAll(indexes) 25 | } 26 | 27 | public init(indexes: [Int]) { 28 | arrayList.addAll(indexes) 29 | } 30 | 31 | public init(index: Int) { 32 | arrayList.add(index) 33 | } 34 | 35 | // Override copy constructor 36 | public init(from: MutableStruct) { 37 | arrayList.addAll((from as! IndexPath).arrayList) 38 | } 39 | 40 | public init(from decoder: Decoder) { 41 | let unkeyedContainer = decoder.unkeyedContainer() 42 | while (!unkeyedContainer.isAtEnd) { 43 | arrayList.add(unkeyedContainer.decode(Int.self)) 44 | } 45 | } 46 | 47 | public func encode(to encoder: Encoder) { 48 | let unkeyedContainer = encoder.unkeyedContainer() 49 | unkeyedContainer.encode(contentsOf: Array(collection: arrayList)) 50 | } 51 | 52 | public var description: String { 53 | return arrayList.description 54 | } 55 | 56 | // SKIP DECLARE: operator fun plus(other: IndexPath): IndexPath 57 | public func plus(other: IndexPath) -> IndexPath { 58 | let combined = IndexPath() 59 | combined.arrayList.addAll(arrayList) 60 | combined.arrayList.addAll(other.arrayList) 61 | return combined 62 | } 63 | 64 | public func dropLast() -> IndexPath { 65 | let dropped = IndexPath() 66 | dropped.arrayList.addAll(arrayList) 67 | if !dropped.arrayList.isEmpty() { 68 | // cannot use removeLast() anymore: https://developer.android.com/about/versions/15/behavior-changes-15#openjdk-api-changes 69 | //dropped.arrayList.removeLast() 70 | dropped.arrayList.removeAt(dropped.arrayList.lastIndex) 71 | } 72 | return dropped 73 | } 74 | 75 | public mutating func append(_ other: IndexPath) { 76 | arrayList.addAll(other.arrayList) 77 | } 78 | 79 | public mutating func append(_ other: Int) { 80 | arrayList.add(other) 81 | } 82 | 83 | public mutating func append(_ other: [Int]) { 84 | arrayList.addAll(other) 85 | } 86 | 87 | public func appending(_ other: IndexPath) -> IndexPath { 88 | let copy = IndexPath() 89 | copy.arrayList.addAll(arrayList) 90 | copy.arrayList.addAll(other.arrayList) 91 | return copy 92 | } 93 | 94 | public func appending(_ other: Int) -> IndexPath { 95 | let copy = IndexPath() 96 | copy.arrayList.addAll(arrayList) 97 | copy.arrayList.add(other) 98 | return copy 99 | } 100 | 101 | public func appending(_ other: [Int]) -> IndexPath { 102 | let copy = IndexPath() 103 | copy.arrayList.addAll(arrayList) 104 | copy.arrayList.addAll(other) 105 | return copy 106 | } 107 | 108 | public override subscript(range: Range) -> IndexPath { 109 | let copy = IndexPath() 110 | for i in range { 111 | guard i < arrayList.size else { 112 | break 113 | } 114 | copy.arrayList.add(arrayList[i]) 115 | } 116 | return copy 117 | } 118 | 119 | public static func <(lhs: IndexPath, rhs: IndexPath) { 120 | for i in 0.. rhs[i] { 126 | return false 127 | } 128 | } 129 | return lhs.count < rhs.count 130 | } 131 | 132 | 133 | public func compare(_ with: IndexPath) -> ComparisonResult { 134 | if self == with { 135 | return .orderedSame 136 | } else if self < with { 137 | return .orderedAscending 138 | } else { 139 | return .orderedDescending 140 | } 141 | } 142 | public override func kotlin(nocopy: Bool = false) -> MutableList { 143 | return nocopy ? arrayList : ArrayList(arrayList) 144 | } 145 | } 146 | 147 | #endif 148 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/IndexSet.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public typealias IndexSet = IntSet 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/JSONSerialization+Parser.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | internal class JSONParser { 6 | var reader: org.json.JSONTokener 7 | 8 | init(bytes: [UInt8]) { 9 | self.reader = org.json.JSONTokener(String(data: Data(bytes), encoding: .utf8) ?? "") 10 | } 11 | 12 | public func parseJSONValue() throws -> JSONValue { 13 | try createJSONValue(from: reader.nextValue()) 14 | } 15 | 16 | public func parseSwiftValue() throws -> Any { 17 | try createSwiftValue(from: reader.nextValue()) 18 | } 19 | 20 | /// https://developer.android.com/reference/org/json/JSONTokener#nextValue() 21 | /// a JSONObject, JSONArray, String, Boolean, Integer, Long, Double or JSONObject#NULL. 22 | func createSwiftValue(from token: Any) throws -> Any { 23 | if token === nil || token === org.json.JSONObject.NULL { 24 | return NSNull.null 25 | } else { 26 | switch token { 27 | case let bd as java.math.BigDecimal: // happens with org.json package, but not Android's version 28 | if let dbl = Double(bd.toString()) { 29 | return dbl 30 | } else { 31 | throw JSONError.numberIsNotRepresentableInSwift(parsed: bd.toString()) 32 | } 33 | case let obj as org.json.JSONObject: 34 | var dict = Dictionary() 35 | for key in obj.keys() { 36 | dict[key] = createSwiftValue(from: obj.get(key)) 37 | } 38 | return dict 39 | case let arr as org.json.JSONArray: 40 | var array = Array() 41 | for i in 0.. JSONValue { 52 | if token === nil || token === org.json.JSONObject.NULL { 53 | return JSONValue.null 54 | } else { 55 | switch token { 56 | case let str as String: 57 | return JSONValue.string(str) 58 | case let lng as Long: 59 | return JSONValue.number(lng.toString()) 60 | case let int as Integer: 61 | return JSONValue.number(int.toString()) 62 | case let dbl as Double: 63 | return JSONValue.number(dbl.toString()) 64 | case let bd as java.math.BigDecimal: // happens with org.json package, but not Android's version 65 | return JSONValue.number(bd.toString()) 66 | case let bol as Boolean: 67 | return JSONValue.bool(bol) 68 | case let obj as org.json.JSONObject: 69 | var dict = Dictionary() 70 | for key in obj.keys() { 71 | dict[key] = createJSONValue(from: obj.get(key)) 72 | } 73 | return JSONValue.object(dict) 74 | case let arr as org.json.JSONArray: 75 | var array = Array() 76 | for i in 0.. UInt8 { 89 | ascii.first().toByte().toUByte() 90 | } 91 | 92 | /// Mimics the constructor `UInt8(ascii:)` 93 | func UInt8(ascii: String) -> UInt8 { 94 | ascii.first().toByte().toUByte() 95 | } 96 | 97 | internal let UInt8_space: UInt8 = UInt8(ascii: " ") 98 | internal let UInt8_return: UInt8 = UInt8(ascii: "\r") 99 | internal let UInt8_newline: UInt8 = UInt8(ascii: "\n") 100 | internal let UInt8_tab: UInt8 = UInt8(ascii: "\t") 101 | 102 | internal let UInt8_colon: UInt8 = UInt8(ascii: ":") 103 | internal let UInt8_comma: UInt8 = UInt8(ascii: ",") 104 | 105 | internal let UInt8_openbrace: UInt8 = UInt8(ascii: "{") 106 | internal let UInt8_closebrace: UInt8 = UInt8(ascii: "}") 107 | 108 | internal let UInt8_openbracket: UInt8 = UInt8(ascii: "[") 109 | internal let UInt8_closebracket: UInt8 = UInt8(ascii: "]") 110 | 111 | internal let UInt8_quote: UInt8 = UInt8(ascii: "\"") 112 | internal let UInt8_backslash: UInt8 = UInt8(ascii: "\\") 113 | 114 | internal let UInt8Array_true: Array = [UInt8(ascii: "t"), UInt8(ascii: "r"), UInt8(ascii: "u"), UInt8(ascii: "e")] 115 | internal let UInt8Array_false: Array = [UInt8(ascii: "f"), UInt8(ascii: "a"), UInt8(ascii: "l"), UInt8(ascii: "s"), UInt8(ascii: "e")] 116 | internal let UInt8Array_null: Array = [UInt8(ascii: "n"), UInt8(ascii: "u"), UInt8(ascii: "l"), UInt8(ascii: "l")] 117 | 118 | enum JSONError: Error, Equatable { 119 | case cannotConvertInputDataToUTF8 120 | case unexpectedCharacter(ascii: UInt8, characterIndex: Int) 121 | case unexpectedEndOfFile 122 | case tooManyNestedArraysOrDictionaries(characterIndex: Int) 123 | case invalidHexDigitSequence(String, index: Int) 124 | case unexpectedEscapedCharacter(ascii: UInt8, in: String, index: Int) 125 | case unescapedControlCharacterInString(ascii: UInt8, in: String, index: Int) 126 | case expectedLowSurrogateUTF8SequenceAfterHighSurrogate(in: String, index: Int) 127 | case couldNotCreateUnicodeScalarFromUInt32(in: String, index: Int, unicodeScalarValue: UInt32) 128 | case numberWithLeadingZero(index: Int) 129 | case numberIsNotRepresentableInSwift(parsed: String) 130 | case singleFragmentFoundButNotAllowed 131 | case invalidUTF8Sequence(Data, characterIndex: Int) 132 | } 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/LocalizedStringResource.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public final class LocalizedStringResource: Hashable { 6 | public let keyAndValue: String.LocalizationValue 7 | public var defaultValue: String.LocalizationValue? = nil 8 | public var table: String? = nil 9 | public var locale: Locale? = nil 10 | public var bundle: BundleDescription? = nil 11 | public var comment: String? = nil 12 | 13 | /// The raw string used to create the keyAndValue `String.LocalizationValue` 14 | public var key: String { 15 | keyAndValue.patternFormat 16 | } 17 | 18 | public init(stringLiteral: String) { 19 | self.keyAndValue = String.LocalizationValue(stringLiteral) 20 | self.bundle = .main 21 | } 22 | 23 | public init(_ keyAndValue: String.LocalizationValue, defaultValue: String.LocalizationValue? = nil, table: String? = nil, locale: Locale? = nil, bundle: BundleDescription? = nil, comment: String? = nil) { 24 | self.keyAndValue = keyAndValue 25 | self.defaultValue = defaultValue 26 | self.table = table 27 | self.locale = locale 28 | self.bundle = bundle 29 | self.comment = comment 30 | } 31 | 32 | public static func == (lhs: Self, rhs: Self) -> Bool { 33 | lhs.keyAndValue == rhs.keyAndValue 34 | && lhs.defaultValue == rhs.defaultValue 35 | && lhs.table == rhs.table 36 | && lhs.locale == rhs.locale 37 | && lhs.bundle == rhs.bundle 38 | && lhs.comment == rhs.comment 39 | } 40 | 41 | public func hash(into hasher: inout Hasher) { 42 | hasher.combine(keyAndValue.hashCode()) 43 | if let defaultValue = defaultValue { 44 | hasher.combine(defaultValue.hashCode()) 45 | } 46 | if let table = table { 47 | hasher.combine(table.hashCode()) 48 | } 49 | if let locale = locale { 50 | hasher.combine(locale.hashCode()) 51 | } 52 | if let bundle = bundle { 53 | hasher.combine(bundle.hashCode()) 54 | } 55 | if let comment = comment { 56 | hasher.combine(comment.hashCode()) 57 | } 58 | } 59 | 60 | public enum BundleDescription: CustomStringConvertible, Hashable { 61 | case main 62 | case forClass(AnyClass) 63 | case atURL(URL) 64 | 65 | public var description: String { 66 | switch self { 67 | case .main: return "bundle: main" 68 | case .forClass(let c): return "bundle: \(c)" 69 | case .atURL(let url): return "bundle: \(url)" 70 | } 71 | } 72 | 73 | public var bundle: Bundle { 74 | Bundle(location: self) 75 | } 76 | } 77 | } 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Logger.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | /// Skip `Logger` aliases to `SkipLogger` type and wraps `java.util.logging.Logger` 6 | public typealias Logger = SkipLogger 7 | public typealias LogMessage = String 8 | public typealias OSLogType = SkipLogger.LogType 9 | 10 | /// Logger cover for versions before Logger was available (which coincides with Concurrency). 11 | public class SkipLogger { 12 | let logName: String 13 | 14 | public enum LogType { 15 | case `default` 16 | case info 17 | case debug 18 | case error 19 | case fault 20 | } 21 | 22 | public init(subsystem: String, category: String) { 23 | self.logName = subsystem + "." + category 24 | } 25 | 26 | public func isEnabled(type: OSLogType) -> Bool { 27 | return true 28 | } 29 | 30 | public func log(level: OSLogType, _ message: LogMessage) { 31 | switch level { 32 | case .default: log(message) 33 | case .info: info(message) 34 | case .debug: debug(message) 35 | case .error: error(message) 36 | case .fault: fault(message) 37 | default: log(message) 38 | } 39 | } 40 | 41 | public func log(_ message: LogMessage) { 42 | do { 43 | android.util.Log.i(logName, message) 44 | } catch { 45 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.INFO, message) 46 | } 47 | } 48 | 49 | public func trace(_ message: LogMessage) { 50 | do { 51 | android.util.Log.v(logName, message) 52 | } catch { 53 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.FINER, message) 54 | } 55 | } 56 | 57 | public func debug(_ message: LogMessage) { 58 | do { 59 | android.util.Log.d(logName, message) 60 | } catch { 61 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.FINE, message) 62 | } 63 | } 64 | 65 | public func info(_ message: LogMessage) { 66 | do { 67 | android.util.Log.i(logName, message) 68 | } catch { 69 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.INFO, message) 70 | } 71 | } 72 | 73 | public func notice(_ message: LogMessage) { 74 | do { 75 | android.util.Log.i(logName, message) 76 | } catch { 77 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.CONFIG, message) 78 | } 79 | } 80 | 81 | public func warning(_ message: LogMessage) { 82 | do { 83 | android.util.Log.w(logName, message) 84 | } catch { 85 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.WARNING, message) 86 | } 87 | } 88 | 89 | public func error(_ message: LogMessage) { 90 | do { 91 | android.util.Log.e(logName, message) 92 | } catch { 93 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.SEVERE, message) 94 | } 95 | } 96 | 97 | public func critical(_ message: LogMessage) { 98 | do { 99 | android.util.Log.wtf(logName, message) 100 | } catch { 101 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.SEVERE, message) 102 | } 103 | } 104 | 105 | public func fault(_ message: LogMessage) { 106 | do { 107 | android.util.Log.wtf(logName, message) 108 | } catch { 109 | java.util.logging.Logger.getLogger(logName).log(java.util.logging.Level.SEVERE, message) 110 | } 111 | } 112 | } 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/NSLock.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | public protocol NSLocking { 5 | func lock() 6 | func unlock() 7 | } 8 | 9 | extension NSLocking { 10 | public func withLock(_ body: () -> R) -> R { 11 | lock() 12 | defer { unlock() } 13 | return body() 14 | } 15 | } 16 | 17 | public final class NSLock: NSLocking, KotlinConverting { 18 | public let platformValue: java.util.concurrent.Semaphore 19 | 20 | public init() { 21 | platformValue = java.util.concurrent.Semaphore(1) 22 | } 23 | 24 | public init(platformValue: java.util.concurrent.Semaphore) { 25 | self.platformValue = platformValue 26 | } 27 | 28 | public var name: String? = nil 29 | 30 | public func lock() { 31 | platformValue.acquireUninterruptibly() 32 | } 33 | 34 | public func unlock() { 35 | platformValue.release() 36 | } 37 | 38 | public func `try`() -> Bool { 39 | return platformValue.tryAcquire() 40 | } 41 | 42 | public func lock(before: Date) -> Bool { 43 | let millis = before.currentTimeMillis - Date.now.currentTimeMillis 44 | return platformValue.tryAcquire(millis, java.util.concurrent.TimeUnit.MILLISECONDS) 45 | } 46 | 47 | public override func kotlin(nocopy: Bool = false) -> java.util.concurrent.Semaphore { 48 | return platformValue 49 | } 50 | } 51 | 52 | public final class NSRecursiveLock: NSLocking, KotlinConverting { 53 | public let platformValue: java.util.concurrent.locks.Lock 54 | 55 | public init() { 56 | platformValue = java.util.concurrent.locks.ReentrantLock() 57 | } 58 | 59 | public init(platformValue: java.util.concurrent.locks.Lock) { 60 | self.platformValue = platformValue 61 | } 62 | 63 | public var name: String? = nil 64 | 65 | public func lock() { 66 | platformValue.lock() 67 | } 68 | 69 | public func unlock() { 70 | platformValue.unlock() 71 | } 72 | 73 | public func `try`() -> Bool { 74 | return platformValue.tryLock() 75 | } 76 | 77 | public func lock(before: Date) -> Bool { 78 | let millis = before.currentTimeMillis - Date.now.currentTimeMillis 79 | return platformValue.tryLock(millis, java.util.concurrent.TimeUnit.MILLISECONDS) 80 | } 81 | 82 | public override func kotlin(nocopy: Bool = false) -> java.util.concurrent.locks.Lock { 83 | return platformValue 84 | } 85 | } 86 | #endif 87 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Number.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public typealias Decimal = java.math.BigDecimal 6 | public typealias NSDecimalNumber = java.math.BigDecimal 7 | public typealias NSNumber = java.lang.Number 8 | 9 | public extension java.lang.Number { 10 | var doubleValue: Double { doubleValue() } 11 | var intValue: Int { intValue() } 12 | var longValue: Int64 { longValue() } 13 | var int64Value: Int64 { longValue() } 14 | var int32Value: Int32 { intValue() } 15 | var int16Value: Int16 { shortValue() } 16 | var int8Value: Int8 { byteValue() } 17 | } 18 | 19 | // Initializing an NSNumber with a numeric value just returns the instance itself 20 | public func NSNumber(value: Int8) -> NSNumber { value as NSNumber } 21 | public func NSNumber(value: Int16) -> NSNumber { value as NSNumber } 22 | public func NSNumber(value: Int32) -> NSNumber { value as NSNumber } 23 | public func NSNumber(value: Int64) -> NSNumber { value as NSNumber } 24 | public func NSNumber(value: UInt8) -> NSNumber { value as NSNumber } 25 | public func NSNumber(value: UInt16) -> NSNumber { value as NSNumber } 26 | public func NSNumber(value: UInt32) -> NSNumber { value as NSNumber } 27 | public func NSNumber(value: UInt64) -> NSNumber { value as NSNumber } 28 | public func NSNumber(value: Float) -> NSNumber { value as NSNumber } 29 | public func NSNumber(value: Double) -> NSNumber { value as NSNumber } 30 | 31 | // NSNumber also accepts unlabeled values. Add an additional unused argument to satisfy the Kotlin compiler that they are different functions. 32 | public func NSNumber(_ v: Int8, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 33 | public func NSNumber(_ v: Int16, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 34 | public func NSNumber(_ v: Int32, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 35 | public func NSNumber(_ v: Int64, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 36 | public func NSNumber(_ v: UInt8, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 37 | public func NSNumber(_ v: UInt16, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 38 | public func NSNumber(_ v: UInt32, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 39 | public func NSNumber(_ v: UInt64, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 40 | public func NSNumber(_ v: Float, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 41 | public func NSNumber(_ v: Double, unusedp: ()? = nil) -> NSNumber { v as NSNumber } 42 | 43 | public func Decimal(string: String, locale: Locale? = nil) -> Decimal? { 44 | do { 45 | return java.math.BigDecimal(string) 46 | } catch { // NumberFormatException - if val is not a valid representation of a BigDecimal. 47 | return nil 48 | } 49 | } 50 | 51 | public extension java.math.BigDecimal { 52 | // static let zero = java.math.BigDecimal(0) 53 | // static let pi = java.math.BigDecimal("3.14159265358979323846264338327950288419") 54 | // 55 | // @available(*, unavailable) 56 | // static let nan = java.math.BigDecimal(0) // BigDecimal class provides no representation of nan 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/OSAllocatedUnfairLock.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public func OSAllocatedUnfairLock() -> OSAllocatedUnfairLock { 6 | return OSAllocatedUnfairLock(uncheckedState: ()) 7 | } 8 | 9 | public struct OSAllocatedUnfairLock : Sendable { 10 | private var state: State 11 | private let lock: java.util.concurrent.locks.Lock = java.util.concurrent.locks.ReentrantLock() 12 | 13 | public init(initialState: State) { 14 | self.state = initialState 15 | } 16 | 17 | public init(uncheckedState initialState: State) { 18 | self.state = initialState 19 | } 20 | 21 | public func lock() { 22 | lock.lock() 23 | } 24 | 25 | public func unlock() { 26 | lock.unlock() 27 | } 28 | 29 | public func lockIfAvailable() -> Bool { 30 | return lock.tryLock() 31 | } 32 | 33 | public func withLockUnchecked(_ body: (inout State) throws -> R) rethrows -> R { 34 | return withLock(body) 35 | } 36 | 37 | public func withLockUnchecked(_ body: () throws -> R) rethrows -> R { 38 | return withLock(body) 39 | } 40 | 41 | public func withLock(_ body: (inout State) throws -> R) rethrows -> R { 42 | lock.lock() 43 | defer { lock.unlock() } 44 | return body(&state) 45 | } 46 | 47 | public func withLock(_ body: () throws -> R) rethrows -> R { 48 | lock.lock() 49 | defer { lock.unlock() } 50 | return body() 51 | } 52 | 53 | public func withLockIfAvailableUnchecked(_ body: (inout State) throws -> R) rethrows -> R? { 54 | return withLockIfAvailable(body) 55 | } 56 | 57 | public func withLockIfAvailableUnchecked(_ body: () throws -> R) rethrows -> R? { 58 | return withLockIfAvailable(body) 59 | } 60 | 61 | public func withLockIfAvailable(_ body: @Sendable (inout State) throws -> R) rethrows -> R? { 62 | guard lock.tryLock() else { 63 | return nil 64 | } 65 | defer { lock.unlock() } 66 | return body(&state) 67 | } 68 | 69 | public func withLockIfAvailable(_ body: () throws -> R) rethrows -> R? { 70 | guard lock.tryLock() else { 71 | return nil 72 | } 73 | defer { lock.unlock() } 74 | return body() 75 | } 76 | 77 | @available(*, unavailable) 78 | public func precondition(_ condition: Any) { 79 | } 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/OperationQueue.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | import java.util.concurrent.Executors 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.GlobalScope 7 | import kotlinx.coroutines.launch 8 | 9 | public class OperationQueue { 10 | /// Stub representing the main queue. 11 | public static let main = OperationQueue(runBlock: { block in 12 | GlobalScope.launch(Dispatchers.Main) { 13 | block() 14 | } 15 | }) 16 | 17 | @available(*, unavailable) 18 | public static var current: OperationQueue? { 19 | return nil 20 | } 21 | 22 | let runBlock: (() -> Void) -> Void 23 | 24 | public init() { 25 | let executorService = Executors.newSingleThreadExecutor() 26 | self.runBlock = { block in executorService.submit(block) } 27 | } 28 | 29 | public init(runBlock: (() -> Void) -> Void) { 30 | self.runBlock = runBlock 31 | } 32 | 33 | @available(*, unavailable) 34 | public func addOperation(_ operation: Operation) { 35 | } 36 | 37 | @available(*, unavailable) 38 | public func addOperations(_ operations: [Operation], waitUntilFinished: Bool) { 39 | } 40 | 41 | @available(*, unavailable) 42 | public func addOperation(_ value: () -> Void) { 43 | } 44 | 45 | @available(*, unavailable) 46 | public func addBarrierBlock(_ value: () -> Void) { 47 | } 48 | 49 | @available(*, unavailable) 50 | public func cancelAllOperations() { 51 | } 52 | 53 | @available(*, unavailable) 54 | public func waitUntilAllOperationsAreFinished() { 55 | } 56 | 57 | @available(*, unavailable) 58 | public var operations: [Operation] { 59 | fatalError() 60 | } 61 | 62 | @available(*, unavailable) 63 | public var operationCount: Int { 64 | fatalError() 65 | } 66 | 67 | @available(*, unavailable) 68 | public var qualityOfService: Any? { 69 | get { 70 | fatalError() 71 | } 72 | set { 73 | } 74 | } 75 | 76 | @available(*, unavailable) 77 | public var maxConcurrentOperationCount: Int { 78 | get { 79 | fatalError() 80 | } 81 | set { 82 | } 83 | } 84 | 85 | @available(*, unavailable) 86 | public static var defaultMaxConcurrentOperationCount: Int { 87 | get { 88 | fatalError() 89 | } 90 | set { 91 | } 92 | } 93 | 94 | @available(*, unavailable) 95 | public var progress: Any? { 96 | get { 97 | fatalError() 98 | } 99 | set { 100 | } 101 | } 102 | 103 | @available(*, unavailable) 104 | public var isSuspended: Bool { 105 | get { 106 | fatalError() 107 | } 108 | set { 109 | } 110 | } 111 | 112 | @available(*, unavailable) 113 | public var name: String? { 114 | get { 115 | fatalError() 116 | } 117 | set { 118 | } 119 | } 120 | 121 | @available(*, unavailable) 122 | public var underlyingQueue: Any? { 123 | get { 124 | fatalError() 125 | } 126 | set { 127 | } 128 | } 129 | 130 | @available(*, unavailable) 131 | public func schedule(after: Any, tolerance: Any, options: Any?, _ operation: () -> Void) { 132 | } 133 | 134 | @available(*, unavailable) 135 | public func schedule(after: Any, interval: Any, tolerance: Any, options: Any?, _ operation: () -> Void) -> Any { 136 | fatalError() 137 | } 138 | 139 | @available(*, unavailable) 140 | public func schedule(options: Any?, _ operation: () -> Void) { 141 | } 142 | 143 | @available(*, unavailable) 144 | public var now: Any { 145 | fatalError() 146 | } 147 | 148 | @available(*, unavailable) 149 | public var minimumTolerance: Any? { 150 | get { 151 | fatalError() 152 | } 153 | set { 154 | } 155 | } 156 | } 157 | 158 | public class Operation { 159 | @available(*, unavailable) 160 | public init() { 161 | } 162 | 163 | @available(*, unavailable) 164 | public func start() { 165 | } 166 | 167 | @available(*, unavailable) 168 | public func main() { 169 | } 170 | 171 | @available(*, unavailable) 172 | public var completionBlock: (() -> Void)? { 173 | get { 174 | fatalError() 175 | } 176 | set { 177 | } 178 | } 179 | 180 | @available(*, unavailable) 181 | public func cancel() { 182 | } 183 | 184 | @available(*, unavailable) 185 | public var isCancelled: Bool { 186 | fatalError() 187 | } 188 | 189 | @available(*, unavailable) 190 | public var isExecuting: Bool { 191 | fatalError() 192 | } 193 | 194 | @available(*, unavailable) 195 | public var isFinished: Bool { 196 | fatalError() 197 | } 198 | 199 | @available(*, unavailable) 200 | public var isConcurrent: Bool { 201 | fatalError() 202 | } 203 | 204 | @available(*, unavailable) 205 | public var isAsynchronous: Bool { 206 | fatalError() 207 | } 208 | 209 | @available(*, unavailable) 210 | public var isReady: Bool { 211 | fatalError() 212 | } 213 | 214 | @available(*, unavailable) 215 | public var name: String? { 216 | get { 217 | fatalError() 218 | } 219 | set { 220 | } 221 | } 222 | 223 | @available(*, unavailable) 224 | public func addDependency(_ operation: Operation) { 225 | fatalError() 226 | } 227 | 228 | @available(*, unavailable) 229 | public func removeDependency(_ operation: Operation) { 230 | fatalError() 231 | } 232 | 233 | @available(*, unavailable) 234 | public var dependencies: [Operation] { 235 | get { 236 | fatalError() 237 | } 238 | set { 239 | } 240 | } 241 | 242 | @available(*, unavailable) 243 | public var qualityOfService: QualityOfService { 244 | get { 245 | fatalError() 246 | } 247 | set { 248 | } 249 | } 250 | 251 | @available(*, unavailable) 252 | public var queuePriority: Operation.QueuePriority { 253 | get { 254 | fatalError() 255 | } 256 | set { 257 | } 258 | } 259 | 260 | @available(*, unavailable) 261 | public func waitUntilFinished() { 262 | } 263 | 264 | public enum QueuePriority: Int { 265 | case veryLow, low, normal, high, veryHigh 266 | } 267 | } 268 | 269 | public enum QualityOfService: Int { 270 | case userInteractive, userInitiated, utility, background, `default` 271 | } 272 | 273 | #endif 274 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/PropertyListSerialization.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public class PropertyListSerialization { 6 | @available(*, unavailable) 7 | public static func propertyList(_ propertyList: Any, isValidFor: PropertyListSerialization.PropertyListFormat) -> Bool { 8 | fatalError() 9 | } 10 | 11 | 12 | public static func propertyList(from: Data, options: PropertyListSerialization.ReadOptions = [], format: Any?) throws -> [String: String]? { 13 | // TODO: auto-detect format from data content if the format argument is unset 14 | return try openStepPropertyList(from: from, options: options) 15 | } 16 | 17 | static func openStepPropertyList(from: Data, options: PropertyListSerialization.ReadOptions = []) throws -> [String: String]? { 18 | var dict: Dictionary = [:] 19 | 20 | let text = from.utf8String 21 | 22 | guard let text = text else { 23 | // should this throw an error? 24 | return nil 25 | } 26 | 27 | let lines = text.components(separatedBy: "\n") 28 | 29 | for line in lines { 30 | if !line.hasPrefix("\"") { 31 | continue // maybe a comment? (note: we do no support multi-line /* */ comments 32 | } 33 | var key: String? 34 | var value: String? 35 | var isParsingKey = true 36 | var currentToken = "" 37 | var isEscaped = false 38 | var isInsideString = false 39 | 40 | for char in line { 41 | if isEscaped { 42 | if char == "n" { 43 | currentToken += "\n" 44 | } else if char == "r" { 45 | currentToken += "\r" 46 | } else if char == "t" { 47 | currentToken += "\t" 48 | //} else if char == "u" { // TODO: handle unicode escapes like \uXXXX 49 | } else { 50 | // otherwise, just add the literal characters (like " or \) 51 | currentToken += char 52 | } 53 | isEscaped = false 54 | continue 55 | } 56 | 57 | switch char { 58 | case "\\": 59 | isEscaped = true 60 | case "\"": 61 | isInsideString = !isInsideString 62 | if !isInsideString { 63 | if isParsingKey { 64 | key = currentToken 65 | isParsingKey = false 66 | } else { 67 | value = currentToken 68 | } 69 | currentToken = "" 70 | } 71 | case "=": 72 | if isInsideString { 73 | currentToken += char 74 | } else { 75 | isParsingKey = false 76 | } 77 | 78 | case ";": 79 | if isInsideString { 80 | currentToken += char 81 | } else { 82 | if let k = key, let v = value { 83 | dict[k] = v 84 | } 85 | } 86 | 87 | default: 88 | if isInsideString { 89 | currentToken += char 90 | } 91 | } 92 | } 93 | } 94 | 95 | return dict 96 | } 97 | 98 | @available(*, unavailable) 99 | public static func data(fromPropertyList: Any, format: PropertyListSerialization.PropertyListFormat, options: PropertyListSerialization.WriteOptions) -> Data { 100 | fatalError() 101 | } 102 | 103 | @available(*, unavailable) 104 | public static func writePropertyList(_ propertyList: Any, to: Any, format: PropertyListSerialization.PropertyListFormat, options: PropertyListSerialization.WriteOptions, error: Any) -> Int { 105 | fatalError() 106 | } 107 | 108 | @available(*, unavailable) 109 | public static func propertyList(with: Any, options: PropertyListSerialization.ReadOptions = [], format: Any?) -> Any { 110 | fatalError() 111 | } 112 | 113 | public enum PropertyListFormat: UInt { 114 | case openStep = 1 115 | case xml = 100 116 | case binary = 200 117 | } 118 | 119 | public struct ReadOptions: RawRepresentable, OptionSet { 120 | public let rawValue: UInt 121 | 122 | public init(rawValue: UInt) { 123 | self.rawValue = rawValue 124 | } 125 | 126 | public static let mutableContainers = ReadOptions(rawValue: UInt(1)) 127 | public static let mutableContainersAndLeaves = ReadOptions(rawValue: UInt(2)) 128 | } 129 | 130 | public typealias WriteOptions = Int 131 | } 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Scanner.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public class Scanner: KotlinConverting { 6 | let platformValue: java.util.Scanner 7 | 8 | public init(platformValue: java.util.Scanner) { 9 | self.platformValue = platformValue 10 | } 11 | 12 | public init(_ string: String) { 13 | self.platformValue = java.util.Scanner(string) 14 | } 15 | 16 | public var description: String { 17 | return platformValue.description 18 | } 19 | 20 | @available(*, unavailable) 21 | public var string: String { 22 | fatalError() 23 | } 24 | 25 | @available(*, unavailable) 26 | public var charactersToBeSkipped: CharacterSet? { 27 | get { 28 | fatalError() 29 | } 30 | set { 31 | } 32 | } 33 | 34 | @available(*, unavailable) 35 | public var caseSensitive: Bool { 36 | get { 37 | fatalError() 38 | } 39 | set { 40 | } 41 | } 42 | 43 | @available(*, unavailable) 44 | public var locale: Any? { 45 | get { 46 | fatalError() 47 | } 48 | set { 49 | } 50 | } 51 | 52 | @available(*, unavailable) 53 | public var currentIndex: Int { 54 | get { 55 | fatalError() 56 | } 57 | set { 58 | } 59 | } 60 | 61 | @available(*, unavailable) 62 | public func scanInt(representation: Scanner.NumberRepresentation = .decimal) -> Int? { 63 | fatalError() 64 | } 65 | 66 | @available(*, unavailable) 67 | public func scanInt32(representation: Scanner.NumberRepresentation = .decimal) -> Int32? { 68 | fatalError() 69 | } 70 | 71 | @available(*, unavailable) 72 | public func scanInt64(representation: Scanner.NumberRepresentation = .decimal) -> Int64? { 73 | fatalError() 74 | } 75 | 76 | @available(*, unavailable) 77 | public func scanUInt64(representation: Scanner.NumberRepresentation = .decimal) -> UInt64? { 78 | fatalError() 79 | } 80 | 81 | @available(*, unavailable) 82 | public func scanFloat(representation: Scanner.NumberRepresentation = .decimal) -> Float? { 83 | fatalError() 84 | } 85 | 86 | @available(*, unavailable) 87 | public func scanDouble(representation: Scanner.NumberRepresentation = .decimal) -> Double? { 88 | fatalError() 89 | } 90 | 91 | @available(*, unavailable) 92 | public func scanDecimal() -> Decimal? { 93 | fatalError() 94 | } 95 | 96 | @available(*, unavailable) 97 | public func scanString(_ searchString: String) -> String? { 98 | fatalError() 99 | } 100 | 101 | @available(*, unavailable) 102 | public func scanCharacters(from set: CharacterSet) -> String? { 103 | fatalError() 104 | } 105 | 106 | @available(*, unavailable) 107 | public func scanUpToString(_ substring: String) -> String? { 108 | fatalError() 109 | } 110 | 111 | @available(*, unavailable) 112 | public func scanUpToCharacters(from set: CharacterSet) -> String? { 113 | fatalError() 114 | } 115 | 116 | @available(*, unavailable) 117 | public func scanCharacter() -> Character? { 118 | fatalError() 119 | } 120 | 121 | @available(*, unavailable) 122 | public func scanInt(_ result: Any?) -> Bool { 123 | fatalError() 124 | } 125 | 126 | @available(*, unavailable) 127 | public func scanInt64(_ result: Any?) -> Bool { 128 | fatalError() 129 | } 130 | 131 | @available(*, unavailable) 132 | public func scanUnsignedLongLong(_ result: Any?) -> Bool { 133 | fatalError() 134 | } 135 | 136 | @available(*, unavailable) 137 | public func scanHexInt64(_ result: Any?) -> Bool { 138 | fatalError() 139 | } 140 | 141 | @available(*, unavailable) 142 | public func scanHexFloat(_ result: Any?) -> Bool { 143 | fatalError() 144 | } 145 | 146 | @available(*, unavailable) 147 | public func scanHexDouble(_ result: Any?) -> Bool { 148 | fatalError() 149 | } 150 | 151 | @available(*, unavailable) 152 | public func scanString(_ string: String, into result: Any?) -> Bool { 153 | fatalError() 154 | } 155 | 156 | @available(*, unavailable) 157 | public func scanCharacters(from set: CharacterSet, into result: Any?) -> Bool { 158 | fatalError() 159 | } 160 | 161 | @available(*, unavailable) 162 | public var isAtEnd: Bool { 163 | fatalError() 164 | } 165 | 166 | @available(*, unavailable) 167 | public static func localizedScanner(with string: String) -> Any { 168 | fatalError() 169 | } 170 | 171 | public enum NumberRepresentation: Hashable { 172 | case decimal 173 | case hexadecimal 174 | } 175 | 176 | public override func kotlin(nocopy: Bool = false) -> java.util.Scanner { 177 | return platformValue 178 | } 179 | } 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Skip/skip.yml: -------------------------------------------------------------------------------- 1 | # skip.tools per-configuration file 2 | 3 | #skip: 4 | # package: 'skip.foundation' 5 | 6 | # the blocks to add to the settings.gradle.kts 7 | settings: 8 | contents: 9 | - block: 'dependencyResolutionManagement' 10 | contents: 11 | - block: 'versionCatalogs' 12 | contents: 13 | - block: 'create("testLibs")' 14 | contents: 15 | - 'library("json", "org.json", "json").version("20240303")' 16 | 17 | # the blocks to add to the build.gradle.kts 18 | build: 19 | contents: 20 | - block: 'dependencies' 21 | contents: 22 | - 'implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))' 23 | - 'implementation("com.squareup.okhttp3:okhttp")' 24 | - 'implementation("org.commonmark:commonmark:0.24.0")' 25 | - 'implementation("org.commonmark:commonmark-ext-gfm-strikethrough:0.24.0")' 26 | # needed to run local tests that use the JSON parser with a full Robolectric shadow environment 27 | - 'testImplementation(testLibs.json)' 28 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/SkipFoundation.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | internal func SkipFoundationInternalModuleName() -> String { 4 | return "SkipFoundation" 5 | } 6 | 7 | public func SkipFoundationPublicModuleName() -> String { 8 | return "SkipFoundation" 9 | } 10 | 11 | /// A shim that pretends to return any `T`, but just crashes with a fatal error. 12 | func SkipCrash(_ reason: String) -> T { 13 | fatalError("skipme: \(reason)") 14 | } 15 | 16 | #if SKIP 17 | 18 | public func NSLog(_ message: String) { 19 | print(message) 20 | } 21 | 22 | extension Array where Element: Hashable { 23 | /// Returns an array containing only the unique elements of the array, preserving the order of the first occurrence of each element. 24 | func distinctValues() -> [Element] { 25 | var seen = Set() 26 | return filter { element in 27 | if seen.contains(element) { 28 | return false 29 | } else { 30 | seen.insert(element) 31 | return true 32 | } 33 | } 34 | } 35 | } 36 | 37 | public typealias NSObject = java.lang.Object 38 | 39 | public protocol NSObjectProtocol { 40 | } 41 | 42 | public class NSNull { 43 | public static let null = NSNull() 44 | public init() { 45 | } 46 | } 47 | 48 | /// The Objective-C BOOL type. 49 | public struct ObjCBool { 50 | public var boolValue: Bool 51 | public init(_ value: Bool) { self.boolValue = value } 52 | public init(booleanLiteral value: Bool) { self.boolValue = value } 53 | } 54 | 55 | // MARK: Foundation Stubs 56 | 57 | internal struct EnergyFormatter { 58 | } 59 | 60 | internal struct LengthFormatter { 61 | } 62 | 63 | internal struct MassFormatter { 64 | } 65 | 66 | internal protocol SocketPort { 67 | } 68 | 69 | internal protocol PersonNameComponents { 70 | } 71 | 72 | public class NSCoder : NSObject { 73 | } 74 | 75 | internal protocol NSRange { 76 | } 77 | 78 | internal class NSArray : NSObject { 79 | } 80 | 81 | internal class NSMutableArray : NSArray { 82 | } 83 | 84 | internal class NSPredicate : NSObject { 85 | } 86 | 87 | internal class NSTextCheckingResult : NSObject { 88 | } 89 | 90 | internal protocol NSBinarySearchingOptions { 91 | } 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/StringEncoding.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public struct StringEncoding : RawRepresentable, Hashable { 6 | public static let utf8 = StringEncoding(rawValue: Charsets.UTF_8) 7 | public static let utf16 = StringEncoding(rawValue: Charsets.UTF_16) 8 | public static let utf16LittleEndian = StringEncoding(rawValue: Charsets.UTF_16LE) 9 | public static let utf16BigEndian = StringEncoding(rawValue: Charsets.UTF_16BE) 10 | public static let utf32 = StringEncoding(rawValue: Charsets.UTF_32) 11 | public static let utf32LittleEndian = StringEncoding(rawValue: Charsets.UTF_32LE) 12 | public static let utf32BigEndian = StringEncoding(rawValue: Charsets.UTF_32BE) 13 | 14 | public let rawValue: java.nio.charset.Charset 15 | 16 | public init(rawValue: java.nio.charset.Charset) { 17 | self.rawValue = rawValue 18 | } 19 | 20 | public init(_ rawValue: java.nio.charset.Charset) { 21 | self.rawValue = rawValue 22 | } 23 | 24 | public var description: String { 25 | rawValue.description 26 | } 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/StringLocalizationValue.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | extension String { 5 | public typealias LocalizationValue = StringLocalizationValue 6 | } 7 | #endif 8 | 9 | public struct StringLocalizationValue : ExpressibleByStringInterpolation { 10 | /// A type that represents a string literal. 11 | /// 12 | /// Valid types for `StringLiteralType` are `String` and `StaticString`. 13 | public typealias StringLiteralType = String 14 | 15 | public let stringInterpolation: StringLocalizationValue.StringInterpolation 16 | 17 | public init(_ value: String) { 18 | var interp = StringLocalizationValue.StringInterpolation(literalCapacity: 0, interpolationCount: 0) 19 | interp.appendLiteral(value) 20 | self.stringInterpolation = interp 21 | } 22 | 23 | public init(stringLiteral value: String) { 24 | var interp = StringLocalizationValue.StringInterpolation(literalCapacity: 0, interpolationCount: 0) 25 | interp.appendLiteral(value) 26 | self.stringInterpolation = interp 27 | } 28 | 29 | public init(stringInterpolation: StringLocalizationValue.StringInterpolation) { 30 | self.stringInterpolation = stringInterpolation 31 | } 32 | 33 | /// Returns the pattern string to use for looking up localized values in the `.xcstrings` file 34 | public var patternFormat: String { 35 | stringInterpolation.pattern 36 | } 37 | 38 | public typealias StringInterpolation = ValueStringInterpolation 39 | 40 | public struct ValueStringInterpolation : StringInterpolationProtocol, Equatable { 41 | /// The type that should be used for literal segments. 42 | public typealias StringLiteralType = String 43 | 44 | #if SKIP 45 | // public so it can be accessed from SkipUI 46 | public let values: MutableList = mutableListOf() 47 | #endif 48 | public var pattern = "" 49 | 50 | public init(literalCapacity: Int, interpolationCount: Int) { 51 | } 52 | 53 | public mutating func appendLiteral(_ literal: String) { 54 | #if SKIP 55 | // need to escape out Java-specific format marker 56 | pattern += literal.replacingOccurrences(of: "%", with: "%%") 57 | #endif 58 | } 59 | 60 | public mutating func appendInterpolation(_ string: String) { 61 | #if SKIP 62 | values.add(string) 63 | #endif 64 | pattern += "%@" 65 | } 66 | 67 | public mutating func appendInterpolation(_ int: Int) { 68 | #if SKIP 69 | values.add(int) 70 | #endif 71 | pattern += "%lld" 72 | } 73 | 74 | public mutating func appendInterpolation(_ int: Int16) { 75 | #if SKIP 76 | values.add(int) 77 | #endif 78 | pattern += "%d" 79 | } 80 | 81 | public mutating func appendInterpolation(_ int: Int64) { 82 | #if SKIP 83 | values.add(int) 84 | #endif 85 | pattern += "%lld" 86 | } 87 | 88 | public mutating func appendInterpolation(_ int: UInt) { 89 | #if SKIP 90 | values.add(int) 91 | #endif 92 | pattern += "%llu" 93 | } 94 | 95 | public mutating func appendInterpolation(_ int: UInt16) { 96 | #if SKIP 97 | values.add(int) 98 | #endif 99 | pattern += "%u" 100 | } 101 | 102 | public mutating func appendInterpolation(_ int: UInt64) { 103 | #if SKIP 104 | values.add(int) 105 | #endif 106 | pattern += "%llu" 107 | } 108 | 109 | public mutating func appendInterpolation(_ double: Double) { 110 | #if SKIP 111 | values.add(double) 112 | #endif 113 | pattern += "%lf" 114 | } 115 | 116 | public mutating func appendInterpolation(_ float: Float) { 117 | #if SKIP 118 | values.add(float) 119 | #endif 120 | pattern += "%f" 121 | } 122 | 123 | public mutating func appendInterpolation(_ value: T) { 124 | #if SKIP 125 | values.add(value as Any) 126 | #endif 127 | pattern += "%@" 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Thread.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public typealias NSThread = Thread 6 | 7 | public struct Thread : Hashable, Sendable, KotlinConverting { 8 | internal var platformValue: java.lang.Thread 9 | 10 | /// Re-initialized from `ProcessInfo.launch(context:)` 11 | public internal(set) static var main: Thread = Thread.current 12 | 13 | public static var isMainThread: Bool { 14 | Thread.current == Thread.main 15 | } 16 | 17 | public var isMainThread: Bool { 18 | self == Thread.main 19 | } 20 | 21 | public static var current: Thread { 22 | return Thread(platformValue: java.lang.Thread.currentThread()) 23 | } 24 | 25 | public var isExecuting: Bool { 26 | self == Thread.current 27 | } 28 | 29 | public static var callStackSymbols: [String] { 30 | Array(Thread.current.platformValue.getStackTrace().map({ $0.toString() })) 31 | } 32 | 33 | public static func sleep(forTimeInterval: TimeInterval) { 34 | java.lang.Thread.sleep(Long(forTimeInterval * 1000.0)) 35 | } 36 | 37 | public static func sleep(until date: Date) { 38 | sleep(forTimeInterval: date.timeIntervalSince(Date.now)) 39 | } 40 | 41 | public override func kotlin(nocopy: Bool = false) -> java.lang.Thread { 42 | return platformValue 43 | } 44 | 45 | public func isEqual(_ other: Any?) -> Bool { 46 | guard let other = other as? Thread else { 47 | return false 48 | } 49 | return self.platformValue == other.platformValue 50 | } 51 | 52 | public static func ==(lhs: Thread, rhs: Thread) -> Bool { 53 | return lhs.platformValue == rhs.platformValue 54 | } 55 | 56 | public func hash(into hasher: inout Hasher) { 57 | hasher.combine(platformValue) 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/TimeZone.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public typealias NSTimeZone = TimeZone 6 | 7 | public struct TimeZone : Hashable, Codable, CustomStringConvertible, Sendable, KotlinConverting { 8 | internal var platformValue: java.util.TimeZone 9 | 10 | public static var current: TimeZone { 11 | return TimeZone(platformValue: java.util.TimeZone.getDefault()) 12 | } 13 | 14 | public static var `default`: TimeZone { 15 | get { 16 | return TimeZone(platformValue: java.util.TimeZone.getDefault()) 17 | } 18 | 19 | set { 20 | java.util.TimeZone.setDefault(newValue.platformValue) 21 | } 22 | } 23 | 24 | public static var system: TimeZone { 25 | return TimeZone(platformValue: java.util.TimeZone.getDefault()) 26 | } 27 | 28 | public static var local: TimeZone { 29 | return TimeZone(platformValue: java.util.TimeZone.getDefault()) 30 | } 31 | 32 | public static var autoupdatingCurrent: TimeZone { 33 | return TimeZone(platformValue: java.util.TimeZone.getDefault()) 34 | } 35 | 36 | public static var gmt: TimeZone { 37 | return TimeZone(platformValue: java.util.TimeZone.getTimeZone("GMT")) 38 | } 39 | 40 | public init(platformValue: java.util.TimeZone) { 41 | self.platformValue = platformValue 42 | } 43 | 44 | public init?(identifier: String) { 45 | guard let tz = java.util.TimeZone.getTimeZone(identifier) else { 46 | return nil 47 | } 48 | self.platformValue = tz 49 | } 50 | 51 | public init?(abbreviation: String) { 52 | guard let identifier = Self.abbreviationDictionary[abbreviation] else { 53 | } 54 | guard let tz = java.util.TimeZone.getTimeZone(identifier) else { 55 | return nil 56 | } 57 | self.platformValue = tz 58 | } 59 | 60 | public init?(secondsFromGMT seconds: Int) { 61 | // java.time.ZoneId is more modern, but doesn't seem to be able to vend a java.util.TimeZone 62 | // guard let tz = PlatformTimeZone.getTimeZone(java.time.ZoneId.ofOffset(seconds)) 63 | 64 | //let timeZoneId = seconds >= 0 65 | // ? String.format("GMT+%02d:%02d", seconds / 3600, (seconds % 3600) / 60) 66 | // : String.format("GMT-%02d:%02d", -seconds / 3600, (-seconds % 3600) / 60) 67 | //guard let tz = PlatformTimeZone.getTimeZone(timeZoneId) else { 68 | // return nil 69 | //} 70 | 71 | self.platformValue = java.util.SimpleTimeZone(seconds, "GMT") 72 | } 73 | 74 | public init(from decoder: Decoder) throws { 75 | let container = try decoder.singleValueContainer() 76 | let identifier = try container.decode(String.self) 77 | self.platformValue = java.util.TimeZone.getTimeZone(identifier) 78 | } 79 | 80 | public func encode(to encoder: Encoder) throws { 81 | var container = encoder.singleValueContainer() 82 | try container.encode(identifier) 83 | } 84 | 85 | public var identifier: String { 86 | return platformValue.getID() 87 | } 88 | 89 | public func abbreviation(for date: Date = Date()) -> String? { 90 | return platformValue.getDisplayName(true, java.util.TimeZone.SHORT) 91 | } 92 | 93 | public func secondsFromGMT(for date: Date = Date()) -> Int { 94 | return platformValue.getOffset(date.currentTimeMillis) / 1000 // offset is in milliseconds 95 | } 96 | 97 | public var description: String { 98 | return platformValue.description 99 | } 100 | 101 | public func isDaylightSavingTime(for date: Date = Date()) -> Bool { 102 | return platformValue.toZoneId().rules.isDaylightSavings(java.time.ZonedDateTime.ofInstant(date.platformValue.toInstant(), platformValue.toZoneId()).toInstant()) 103 | } 104 | 105 | public func daylightSavingTimeOffset(for date: Date = Date()) -> TimeInterval { 106 | return isDaylightSavingTime(for: date) ? java.time.ZonedDateTime.ofInstant(date.platformValue.toInstant(), platformValue.toZoneId()).offset.getTotalSeconds().toDouble() : 0.0 107 | } 108 | 109 | public var nextDaylightSavingTimeTransition: Date? { 110 | return nextDaylightSavingTimeTransition(after: Date()) 111 | } 112 | 113 | public func nextDaylightSavingTimeTransition(after date: Date) -> Date? { 114 | // testSkipModule(): java.lang.NullPointerException: Cannot invoke "java.time.zone.ZoneOffsetTransition.getInstant()" because the return value of "java.time.zone.ZoneRules.nextTransition(java.time.Instant)" is null 115 | let zonedDateTime = java.time.ZonedDateTime.ofInstant(date.platformValue.toInstant(), platformValue.toZoneId()) 116 | guard let transition = platformValue.toZoneId().rules.nextTransition(zonedDateTime.toInstant()) else { 117 | return nil 118 | } 119 | return Date(platformValue: java.util.Date.from(transition.getInstant())) 120 | } 121 | 122 | public static var knownTimeZoneIdentifiers: [String] { 123 | return Array(java.time.ZoneId.getAvailableZoneIds()) 124 | } 125 | 126 | public static var knownTimeZoneNames: [String] { 127 | return Array(java.time.ZoneId.getAvailableZoneIds()) 128 | } 129 | 130 | public static var abbreviationDictionary: [String : String] = [:] 131 | 132 | @available(*, unavailable) 133 | public static var timeZoneDataVersion: String { 134 | fatalError("TODO: TimeZone") 135 | } 136 | 137 | public func localizedName(for style: NameStyle, locale: Locale?) -> String? { 138 | switch style { 139 | case .generic: 140 | return platformValue.toZoneId().getDisplayName(java.time.format.TextStyle.FULL, locale?.platformValue) 141 | case .standard: 142 | return platformValue.toZoneId().getDisplayName(java.time.format.TextStyle.FULL_STANDALONE, locale?.platformValue) 143 | case .shortStandard: 144 | return platformValue.toZoneId().getDisplayName(java.time.format.TextStyle.SHORT_STANDALONE, locale?.platformValue) 145 | case .daylightSaving: 146 | return platformValue.toZoneId().getDisplayName(java.time.format.TextStyle.FULL, locale?.platformValue) 147 | case .shortDaylightSaving: 148 | return platformValue.toZoneId().getDisplayName(java.time.format.TextStyle.SHORT, locale?.platformValue) 149 | case .shortGeneric: 150 | return platformValue.toZoneId().getDisplayName(java.time.format.TextStyle.SHORT, locale?.platformValue) 151 | } 152 | } 153 | 154 | public enum NameStyle : Int { 155 | case standard = 0 156 | case shortStandard = 1 157 | case daylightSaving = 2 158 | case shortDaylightSaving = 3 159 | case generic = 4 160 | case shortGeneric = 5 161 | } 162 | 163 | public override func kotlin(nocopy: Bool = false) -> java.util.TimeZone { 164 | return nocopy ? platformValue : platformValue.clone() as java.util.TimeZone 165 | } 166 | } 167 | 168 | #endif 169 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Timer.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.GlobalScope 6 | import kotlinx.coroutines.launch 7 | 8 | public typealias NSTimer = Timer 9 | 10 | public final class Timer : KotlinConverting { 11 | private var timer: java.util.Timer? 12 | private var repeats = false 13 | private var block: ((Timer) -> Void)? 14 | private var invalidated = false 15 | 16 | public init(platformValue: java.util.Timer) { 17 | self.timer = platformValue 18 | } 19 | 20 | public override func kotlin(nocopy: Bool = false) -> java.util.Timer? { 21 | return timer 22 | } 23 | 24 | @available(*, unavailable) 25 | public init(timeInterval ti: TimeInterval, invocation: Any, repeats: Bool) { 26 | fatalError() 27 | } 28 | 29 | @available(*, unavailable) 30 | open class func scheduledTimer(timeInterval ti: TimeInterval, invocation: Any, repeats: Bool) -> Timer { 31 | fatalError() 32 | } 33 | 34 | @available(*, unavailable) 35 | public init(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Any, userInfo: Any?, repeats: Bool) { 36 | fatalError() 37 | } 38 | 39 | @available(*, unavailable) 40 | open class func scheduledTimer(timeInterval ti: TimeInterval, target aTarget: Any, selector aSelector: Any, userInfo: Any?, repeats: Bool) -> Timer { 41 | fatalError() 42 | } 43 | 44 | public init(timeInterval: TimeInterval, repeats: Bool, block: (Timer) -> Void) { 45 | self.timeInterval = timeInterval 46 | self.repeats = repeats 47 | self.block = block 48 | } 49 | 50 | public static func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: (Timer) -> Void) -> Timer { 51 | let timer = Timer(timeInterval: interval, repeats: repeats, block: block) 52 | timer.start() 53 | return timer 54 | } 55 | 56 | @available(*, unavailable) 57 | public convenience init(fire date: Date, interval: TimeInterval, repeats: Bool, block: (Timer) -> Void) { 58 | fatalError() 59 | } 60 | 61 | @available(*, unavailable) 62 | public init(fireAt date: Date, interval ti: TimeInterval, target t: Any, selector s: Any, userInfo ui: Any?, repeats rep: Bool) { 63 | fatalError() 64 | } 65 | 66 | @available(*, unavailable) 67 | public func fire() { 68 | fatalError() 69 | } 70 | 71 | @available(*, unavailable) 72 | public var fireDate: Date { 73 | get { fatalError() } 74 | set { fatalError() } 75 | } 76 | 77 | public private(set) var timeInterval: TimeInterval = 0.0 78 | 79 | @available(*, unavailable) 80 | public var tolerance: TimeInterval { 81 | get { fatalError() } 82 | set { fatalError() } 83 | } 84 | 85 | public func invalidate() { 86 | synchronized(self) { 87 | timer?.cancel() 88 | timer = nil 89 | block = nil 90 | invalidated = true 91 | } 92 | } 93 | 94 | public var isValid: Bool { 95 | synchronized(self) { 96 | return !invalidated 97 | } 98 | } 99 | 100 | public var userInfo: Any? 101 | 102 | /// Used by the run loop to start the timer. 103 | public func start() { 104 | synchronized(self) { 105 | guard !invalidated && block != nil else { 106 | return 107 | } 108 | let block = self.block 109 | let timerTask = Task { block?(self) } 110 | timer = java.util.Timer(true) 111 | let delayms = Int64(timeInterval * 1000.0) 112 | if repeats { 113 | timer?.schedule(timerTask, delayms, delayms) 114 | } else { 115 | timer?.schedule(timerTask, delayms) 116 | } 117 | } 118 | } 119 | 120 | private final class Task: java.util.TimerTask { 121 | let task: () -> Void 122 | 123 | init(task: () -> Void) { 124 | self.task = task 125 | } 126 | 127 | override func run() { 128 | GlobalScope.launch(Dispatchers.Main) { 129 | task() 130 | } 131 | } 132 | } 133 | } 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/URLRequest.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public typealias NSURLRequest = URLRequest 6 | 7 | public struct URLRequest : Hashable, CustomStringConvertible { 8 | public var url: URL? 9 | public var httpMethod: String? = "GET" { 10 | didSet { 11 | if let method = httpMethod, 12 | method != method.uppercased(), 13 | ["GET", "PUT", "HEAD", "POST", "DELETE", "CONNECT"].contains(method.uppercased()) 14 | { 15 | // standard method names are always uppercase 16 | self.httpMethod = method.uppercased() 17 | } 18 | } 19 | } 20 | public var httpBody: Data? = nil 21 | public var allHTTPHeaderFields: [String : String]? = nil 22 | public var cachePolicy: CachePolicy = CachePolicy.useProtocolCachePolicy 23 | public var timeoutInterval: TimeInterval = 0.0 24 | public var allowsCellularAccess: Bool = true 25 | public var allowsExpensiveNetworkAccess: Bool = true 26 | public var allowsConstrainedNetworkAccess: Bool = true 27 | public var assumesHTTP3Capable: Bool = true 28 | public var requiresDNSSECValidation: Bool = false 29 | public var httpShouldHandleCookies: Bool = true 30 | public var httpShouldUsePipelining: Bool = true 31 | public var mainDocumentURL: URL? = nil 32 | public var networkServiceType: URLRequest.NetworkServiceType = URLRequest.NetworkServiceType.default 33 | public var attribution: URLRequest.Attribution = URLRequest.Attribution.developer 34 | @available(*, unavailable) 35 | public var httpBodyStream: Any? { 36 | get { 37 | fatalError() 38 | } 39 | set { 40 | } 41 | } 42 | 43 | public init(url: URL, cachePolicy: CachePolicy = CachePolicy.useProtocolCachePolicy, timeoutInterval: TimeInterval = 0.0) { 44 | self.url = url 45 | self.cachePolicy = cachePolicy 46 | self.timeoutInterval = timeoutInterval 47 | } 48 | 49 | public var description: String { 50 | return url?.toString() ?? "url: nil" 51 | } 52 | 53 | public func value(forHTTPHeaderField field: String) -> String? { 54 | return Self.value(forHTTPHeaderField: field, in: allHTTPHeaderFields ?? [:]) 55 | } 56 | 57 | /// Perform a case-insensitive header lookup for the given field name in the header fields. 58 | internal static func value(forHTTPHeaderField fieldName: String, in headerFields: [String: String]) -> String? { 59 | if let value = headerFields[fieldName] { 60 | // fast case-sensitive match 61 | return value.description 62 | } else { 63 | // case-insensitive key lookup 64 | let fieldKey = fieldName.lowercased() 65 | for (key, value) in headerFields { 66 | if fieldKey == key.lowercased() { 67 | return value 68 | } 69 | } 70 | return nil // not found 71 | } 72 | } 73 | 74 | // The lowercased header heys that are reserved 75 | private static let reservedHeaderKeys = Set([ 76 | "Content-Length".lowercased(), 77 | "Authorization".lowercased(), 78 | "Connection".lowercased(), 79 | "Host".lowercased(), 80 | "Proxy-Authenticate".lowercased(), 81 | "Proxy-Authorization".lowercased(), 82 | "WWW-Authenticate".lowercased(), 83 | ]) 84 | 85 | private func transformHeaderKey(value: String) -> String { 86 | let lowerName = value.lowercased() 87 | if lowerName == "accept" { 88 | return "Accept" 89 | } 90 | return value 91 | } 92 | 93 | public mutating func setValue(_ value: String?, forHTTPHeaderField field: String) { 94 | if Self.reservedHeaderKeys.contains(field) { 95 | return // ignore reserved keys 96 | } 97 | var fields = self.allHTTPHeaderFields ?? [:] 98 | fields[transformHeaderKey(field)] = value 99 | self.allHTTPHeaderFields = fields 100 | } 101 | 102 | public mutating func addValue(_ value: String, forHTTPHeaderField field: String) { 103 | if Self.reservedHeaderKeys.contains(field) { 104 | return // ignore reserved keys 105 | } 106 | let fieldKey = transformHeaderKey(field) 107 | var fields = self.allHTTPHeaderFields ?? [:] 108 | var fieldValue: String = value 109 | // multiple vales are appended together with commas 110 | if let existingValue = fields[fieldKey], !existingValue.isEmpty, !value.isEmpty { 111 | fieldValue = existingValue + "," + value 112 | } 113 | fields[fieldKey] = fieldValue 114 | self.allHTTPHeaderFields = fields 115 | } 116 | 117 | public enum Attribution : Int { 118 | case developer 119 | case user 120 | } 121 | 122 | public enum CachePolicy : Int { 123 | case useProtocolCachePolicy = 0 124 | case reloadIgnoringLocalCacheData = 1 125 | case reloadIgnoringLocalAndRemoteCacheData = 4 126 | case returnCacheDataElseLoad = 2 127 | case returnCacheDataDontLoad = 3 128 | case reloadRevalidatingCacheData = 5 129 | } 130 | 131 | public enum NetworkServiceType : Int { 132 | case `default` 133 | case voip 134 | case video 135 | case background 136 | case voice 137 | case responsiveData 138 | case avStreaming 139 | case responsiveAV 140 | case callSignaling 141 | } 142 | } 143 | 144 | #endif 145 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/URLResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public class URLResponse : Hashable, CustomStringConvertible { 6 | public internal(set) var url: URL? 7 | public internal(set) var mimeType: String? 8 | public internal(set) var expectedContentLength: Int64 = -1 9 | public internal(set) var textEncodingName: String? 10 | 11 | public init() { 12 | } 13 | 14 | public init(url: URL, mimeType: String?, expectedContentLength: Int, textEncodingName: String?) { 15 | self.url = url 16 | self.mimeType = mimeType 17 | self.expectedContentLength = Int64(expectedContentLength) 18 | self.textEncodingName = textEncodingName 19 | } 20 | 21 | public var suggestedFilename: String? { 22 | // A filename specified using the content disposition header. 23 | // The last path component of the URL. 24 | // The host of the URL. 25 | // If the host of URL can't be converted to a valid filename, the filename “unknown” is used. 26 | if let component = self.url?.lastPathComponent, !component.isEmpty { 27 | return component 28 | } 29 | // not expected by the test cases 30 | //if let host = self.url?.host { 31 | // return host 32 | //} 33 | return "Unknown" 34 | } 35 | 36 | public func copy() -> Any { 37 | if let url = self.url { 38 | return URLResponse(url: url, mimeType: self.mimeType, expectedContentLength: Int(self.expectedContentLength), textEncodingName: self.textEncodingName) 39 | } else { 40 | return URLResponse() 41 | } 42 | } 43 | 44 | public func isEqual(_ other: Any?) -> Bool { 45 | guard let other = other as? URLResponse else { 46 | return false 47 | } 48 | return self.url == other.url && 49 | self.mimeType == other.mimeType && 50 | self.expectedContentLength == other.expectedContentLength && 51 | self.textEncodingName == other.textEncodingName 52 | } 53 | 54 | public static func ==(lhs: URLResponse, rhs: URLResponse) -> Bool { 55 | return lhs.isEqual(rhs) 56 | } 57 | 58 | public func hash(into hasher: inout Hasher) { 59 | hasher.combine(url) 60 | hasher.combine(mimeType) 61 | hasher.combine(expectedContentLength) 62 | hasher.combine(textEncodingName) 63 | } 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/URLSessionConfiguration.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | open class URLSessionConfiguration { 6 | open class var `default`: URLSessionConfiguration { 7 | return _default 8 | } 9 | private static let _default = URLSessionConfiguration() 10 | 11 | open class var ephemeral: URLSessionConfiguration { 12 | return _ephemeral 13 | } 14 | // TODO: ephemeral config 15 | private static let _ephemeral = URLSessionConfiguration() 16 | 17 | @available(*, unavailable) 18 | public class func background(withIdentifier: String) -> URLSessionConfiguration { 19 | fatalError() 20 | } 21 | 22 | public var identifier: String? 23 | public var requestCachePolicy: URLRequest.CachePolicy = URLRequest.CachePolicy.useProtocolCachePolicy 24 | public var timeoutIntervalForRequest: TimeInterval = 60.0 25 | public var timeoutIntervalForResource: TimeInterval = 604800.0 26 | public var networkServiceType: URLRequest.NetworkServiceType = URLRequest.NetworkServiceType.default 27 | public var allowsCellularAccess: Bool = true 28 | public var allowsExpensiveNetworkAccess: Bool = true 29 | public var allowsConstrainedNetworkAccess: Bool = true 30 | public var requiresDNSSECValidation: Bool = true 31 | public var waitsForConnectivity: Bool = false 32 | public var isDiscretionary: Bool = false 33 | public var sharedContainerIdentifier: String? = nil 34 | public var sessionSendsLaunchEvents: Bool = false 35 | public var connectionProxyDictionary: [AnyHashable : Any]? = nil 36 | public var httpShouldUsePipelining: Bool = false 37 | public var httpShouldSetCookies: Bool = true 38 | public var httpAdditionalHeaders: [AnyHashable : Any]? = nil 39 | public var httpMaximumConnectionsPerHost: Int = 6 40 | public var shouldUseExtendedBackgroundIdleMode: Bool = false 41 | public var protocolClasses: [AnyClass]? = nil 42 | 43 | public init() { 44 | } 45 | 46 | @available(*, unavailable) 47 | public var httpCookieAcceptPolicy: Any { 48 | get { 49 | fatalError() 50 | } 51 | set { 52 | } 53 | } 54 | 55 | @available(*, unavailable) 56 | public var httpCookieStorage: Any? { 57 | get { 58 | fatalError() 59 | } 60 | set { 61 | } 62 | } 63 | 64 | @available(*, unavailable) 65 | public var urlCredentialStorage: Any? { 66 | get { 67 | fatalError() 68 | } 69 | set { 70 | } 71 | } 72 | 73 | @available(*, unavailable) 74 | public var urlCache: Any? { 75 | get { 76 | fatalError() 77 | } 78 | set { 79 | } 80 | } 81 | 82 | @available(*, unavailable) 83 | public var tlsMinimumSupportedProtocol: Any { 84 | get { 85 | fatalError() 86 | } 87 | set { 88 | } 89 | } 90 | 91 | @available(*, unavailable) 92 | public var tlsMaximumSupportedProtocol: Any { 93 | get { 94 | fatalError() 95 | } 96 | set { 97 | } 98 | } 99 | 100 | @available(*, unavailable) 101 | public var tlsMinimumSupportedProtocolVersion: Any { 102 | get { 103 | fatalError() 104 | } 105 | set { 106 | } 107 | } 108 | 109 | @available(*, unavailable) 110 | public var tlsMaximumSupportedProtocolVersion: Any { 111 | get { 112 | fatalError() 113 | } 114 | set { 115 | } 116 | } 117 | 118 | @available(*, unavailable) 119 | public var multipathServiceType: Any { 120 | get { 121 | fatalError() 122 | } 123 | set { 124 | } 125 | } 126 | 127 | @available(*, unavailable) 128 | public var proxyConfigurations: [Any] { 129 | get { 130 | fatalError() 131 | } 132 | set { 133 | } 134 | } 135 | } 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/UUID.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | public typealias NSUUID = UUID 6 | 7 | public struct UUID : Hashable, Comparable, CustomStringConvertible, Codable, KotlinConverting, SwiftCustomBridged { 8 | internal var platformValue: java.util.UUID 9 | 10 | public init?(uuidString: String) { 11 | // Java throws an exception for bad UUID, but Foundation expects it to return nil 12 | guard let uuid = try? java.util.UUID.fromString(uuidString) else { 13 | return nil 14 | } 15 | self.platformValue = uuid 16 | } 17 | 18 | public init(platformValue: java.util.UUID) { 19 | self.platformValue = platformValue 20 | } 21 | 22 | public init() { 23 | self.platformValue = java.util.UUID.randomUUID() 24 | } 25 | 26 | public init(from decoder: Decoder) throws { 27 | var container = decoder.singleValueContainer() 28 | self.platformValue = java.util.UUID.fromString(container.decode(String.self)) 29 | } 30 | 31 | public func encode(to encoder: Encoder) throws { 32 | var container = encoder.singleValueContainer() 33 | try container.encode(self.uuidString) 34 | } 35 | 36 | public static func fromString(uuidString: String) -> UUID? { 37 | // Java throws an exception for bad UUID, but Foundation expects it to return nil 38 | // return try? UUID(platformValue: PlatformUUID.fromString(uuidString)) // mistranspiles to: (PlatformUUID.companionObjectInstance as java.util.UUID.Companion).fromString(uuidString)) 39 | return try? UUID(platformValue: java.util.UUID.fromString(uuidString)) 40 | } 41 | 42 | @available(*, unavailable) 43 | public var uuid: Any { 44 | fatalError() 45 | } 46 | 47 | public var uuidString: String { 48 | // java.util.UUID is lowercase, Foundation.UUID is uppercase 49 | return platformValue.toString().uppercase() 50 | } 51 | 52 | public var description: String { 53 | return uuidString 54 | } 55 | 56 | public static func <(lhs: UUID, rhs: UUID) -> Bool { 57 | return lhs.platformValue < rhs.platformValue 58 | } 59 | 60 | public override func kotlin(nocopy: Bool = false) -> java.util.UUID { 61 | return platformValue 62 | } 63 | } 64 | 65 | // SKIP INSERT: public fun UUID(mostSigBits: Long, leastSigBits: Long): UUID { return UUID(java.util.UUID(mostSigBits, leastSigBits)) } 66 | 67 | #endif 68 | 69 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/Unicode.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | // This code is adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which has the following license: 6 | 7 | //===----------------------------------------------------------------------===// 8 | // 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See https://swift.org/LICENSE.txt for license information 15 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | //===----------------------------------------------------------------------===// 18 | 19 | public typealias UnicodeScalarValue = UInt32 20 | 21 | @available(*, unavailable) 22 | public func String(_ scalar: Unicode.Scalar) -> String { 23 | fatalError("SKIP TODO") 24 | } 25 | 26 | public struct Unicode { 27 | public struct ASCII { 28 | } 29 | 30 | public struct UTF16 : Sendable { 31 | //case _swift3Buffer(Unicode.UTF16.ForwardParser) 32 | } 33 | 34 | public struct UTF32 : Sendable { 35 | //case _swift3Codec 36 | } 37 | 38 | public struct UTF8 : Sendable { 39 | //case struct(Unicode.UTF8.ForwardParser) 40 | } 41 | 42 | public typealias Encoding = _UnicodeEncoding 43 | 44 | public enum ParseResult { 45 | case valid(T) 46 | case emptyInput 47 | case error(length: Int) 48 | } 49 | 50 | public typealias Parser = _UnicodeParser 51 | 52 | public struct Scalar : RawRepresentable, Hashable, Comparable, Sendable { 53 | public let rawValue: UnicodeScalarValue 54 | 55 | public init?(rawValue: UnicodeScalarValue) { 56 | self.rawValue = rawValue 57 | } 58 | 59 | public init?(_ rawValue: UnicodeScalarValue) { 60 | self.rawValue = rawValue 61 | } 62 | 63 | public static func < (lhs: Unicode.Scalar, rhs: Unicode.Scalar) -> Bool { 64 | return lhs.rawValue < rhs.rawValue 65 | } 66 | 67 | } 68 | 69 | public typealias Version = (major: Int, minor: Int) 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/SkipFoundation/XMLParser.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | 5 | @available(*, unavailable) 6 | public struct XMLParser { 7 | } 8 | 9 | @available(*, unavailable) 10 | public protocol XMLParserDelegate { 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/CoreLibs/TestPersonNameComponents.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | private func assertEqual(_ lhs:PersonNameComponents, 19 | _ rhs: PersonNameComponents) { 20 | #if SKIP 21 | throw XCTSkip("TODO") 22 | #else 23 | assert(equal: true, lhs, rhs) 24 | #endif // !SKIP 25 | } 26 | 27 | private func assertNotEqual(_ lhs:PersonNameComponents, 28 | _ rhs: PersonNameComponents) { 29 | #if SKIP 30 | throw XCTSkip("TODO") 31 | #else 32 | assert(equal: false, lhs, rhs) 33 | #endif // !SKIP 34 | } 35 | 36 | private func assert(equal: Bool, 37 | _ lhs:PersonNameComponents, 38 | _ rhs: PersonNameComponents) { 39 | #if SKIP 40 | throw XCTSkip("TODO") 41 | #else 42 | if equal { 43 | XCTAssertEqual(lhs, rhs) 44 | XCTAssertEqual(lhs._bridgeToObjectiveC(), rhs._bridgeToObjectiveC()) 45 | XCTAssertTrue(lhs._bridgeToObjectiveC().isEqual(rhs)) 46 | } else { 47 | XCTAssertNotEqual(lhs, rhs) 48 | XCTAssertNotEqual(lhs._bridgeToObjectiveC(), rhs._bridgeToObjectiveC()) 49 | XCTAssertFalse(lhs._bridgeToObjectiveC().isEqual(rhs)) 50 | } 51 | #endif // !SKIP 52 | } 53 | 54 | class TestPersonNameComponents : XCTestCase { 55 | 56 | 57 | func testCopy() { 58 | #if SKIP 59 | throw XCTSkip("TODO") 60 | #else 61 | let original = NSPersonNameComponents() 62 | original.givenName = "Maria" 63 | original.phoneticRepresentation = PersonNameComponents() 64 | original.phoneticRepresentation!.givenName = "Jeff" 65 | let copy = original.copy(with:nil) as! NSPersonNameComponents 66 | copy.givenName = "Rebecca" 67 | 68 | XCTAssertNotEqual(original.givenName, copy.givenName) 69 | XCTAssertEqual(original.phoneticRepresentation!.givenName,copy.phoneticRepresentation!.givenName) 70 | XCTAssertNil(copy.phoneticRepresentation!.phoneticRepresentation) 71 | #endif // !SKIP 72 | } 73 | 74 | func testEquality() { 75 | #if SKIP 76 | throw XCTSkip("TODO") 77 | #else 78 | do { 79 | let lhs = PersonNameComponents() 80 | let rhs = PersonNameComponents() 81 | assertEqual(lhs, rhs) 82 | } 83 | 84 | let lhs = self.makePersonNameComponentsWithTestValues() 85 | do { 86 | let rhs = self.makePersonNameComponentsWithTestValues() 87 | assertEqual(lhs, rhs) 88 | } 89 | do { 90 | var rhs = self.makePersonNameComponentsWithTestValues() 91 | rhs.namePrefix = "differentValue" 92 | assertNotEqual(lhs, rhs) 93 | } 94 | do { 95 | var rhs = self.makePersonNameComponentsWithTestValues() 96 | rhs.givenName = "differentValue" 97 | assertNotEqual(lhs, rhs) 98 | } 99 | do { 100 | var rhs = self.makePersonNameComponentsWithTestValues() 101 | rhs.middleName = "differentValue" 102 | assertNotEqual(lhs, rhs) 103 | } 104 | do { 105 | var rhs = self.makePersonNameComponentsWithTestValues() 106 | rhs.familyName = "differentValue" 107 | assertNotEqual(lhs, rhs) 108 | } 109 | do { 110 | var rhs = self.makePersonNameComponentsWithTestValues() 111 | rhs.nameSuffix = "differentValue" 112 | assertNotEqual(lhs, rhs) 113 | } 114 | do { 115 | var rhs = self.makePersonNameComponentsWithTestValues() 116 | rhs.nickname = "differentValue" 117 | assertNotEqual(lhs, rhs) 118 | } 119 | do { 120 | var rhs = self.makePersonNameComponentsWithTestValues() 121 | rhs.phoneticRepresentation?.namePrefix = "differentValue" 122 | assertNotEqual(lhs, rhs) 123 | } 124 | #endif // !SKIP 125 | } 126 | 127 | // MARK: - Helpers 128 | 129 | private func makePersonNameComponentsWithTestValues() -> PersonNameComponents { 130 | #if SKIP 131 | throw XCTSkip("TODO") 132 | #else 133 | var components = PersonNameComponents() 134 | components.namePrefix = "namePrefix" 135 | components.givenName = "givenName" 136 | components.middleName = "middleName" 137 | components.familyName = "familyName" 138 | components.nameSuffix = "nameSuffix" 139 | components.nickname = "nickname" 140 | components.phoneticRepresentation = { 141 | var components = PersonNameComponents() 142 | components.namePrefix = "phonetic_namePrefix" 143 | components.givenName = "phonetic_givenName" 144 | components.middleName = "phonetic_middleName" 145 | components.familyName = "phonetic_familyName" 146 | components.nameSuffix = "phonetic_nameSuffix" 147 | components.nickname = "phonetic_nickname" 148 | return components 149 | }() 150 | return components 151 | #endif // !SKIP 152 | } 153 | } 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/CoreLibs/TestUUID.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestUUID : XCTestCase { 19 | 20 | 21 | func test_UUIDEquality() { 22 | #if SKIP 23 | throw XCTSkip("TODO") 24 | #else 25 | let uuidA = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F") 26 | let uuidB = UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f") 27 | let uuidC = UUID(uuid: (0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f)) 28 | let uuidD = UUID() 29 | 30 | XCTAssertEqual(uuidA, uuidB, "String case must not matter.") 31 | XCTAssertEqual(uuidA, uuidC, "A UUID initialized with a string must be equal to the same UUID initialized with its UnsafePointer equivalent representation.") 32 | XCTAssertNotEqual(uuidC, uuidD, "Two different UUIDs must not be equal.") 33 | #endif // !SKIP 34 | } 35 | 36 | func test_UUIDInvalid() { 37 | let uuid = UUID(uuidString: "Invalid UUID") ?? nil // SKIP TODO: needed to force treatment as optional and avoid `skip.lib.NullReturnException at TestUUID.kt:33` 38 | XCTAssertNil(uuid, "The convenience initializer `init?(uuidString string:)` must return nil for an invalid UUID string.") 39 | } 40 | 41 | // `uuidString` should return an uppercase string 42 | // See: https://bugs.swift.org/browse/SR-865 43 | func test_UUIDuuidString() { 44 | #if SKIP 45 | throw XCTSkip("TODO") 46 | #else 47 | let uuid = UUID(uuid: (0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f)) 48 | XCTAssertEqual(uuid.uuidString, "E621E1F8-C36C-495A-93FC-0C247A3E6E5F", "The uuidString representation must be uppercase.") 49 | #endif // !SKIP 50 | } 51 | 52 | func test_UUIDdescription() { 53 | let uuid = UUID() 54 | XCTAssertEqual(uuid.description, uuid.uuidString, "The description must be the same as the uuidString.") 55 | } 56 | 57 | func test_hash() { 58 | let values: [UUID] = [ 59 | // This list takes a UUID and tweaks every byte while 60 | // leaving the version/variant intact. 61 | UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76b256c")!, 62 | UUID(uuidString: "a63baa1c-b4f5-48db-9467-9786b76b256c")!, 63 | UUID(uuidString: "a53caa1c-b4f5-48db-9467-9786b76b256c")!, 64 | UUID(uuidString: "a53bab1c-b4f5-48db-9467-9786b76b256c")!, 65 | UUID(uuidString: "a53baa1d-b4f5-48db-9467-9786b76b256c")!, 66 | UUID(uuidString: "a53baa1c-b5f5-48db-9467-9786b76b256c")!, 67 | UUID(uuidString: "a53baa1c-b4f6-48db-9467-9786b76b256c")!, 68 | UUID(uuidString: "a53baa1c-b4f5-49db-9467-9786b76b256c")!, 69 | UUID(uuidString: "a53baa1c-b4f5-48dc-9467-9786b76b256c")!, 70 | UUID(uuidString: "a53baa1c-b4f5-48db-9567-9786b76b256c")!, 71 | UUID(uuidString: "a53baa1c-b4f5-48db-9468-9786b76b256c")!, 72 | UUID(uuidString: "a53baa1c-b4f5-48db-9467-9886b76b256c")!, 73 | UUID(uuidString: "a53baa1c-b4f5-48db-9467-9787b76b256c")!, 74 | UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b86b256c")!, 75 | UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76c256c")!, 76 | UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76b266c")!, 77 | UUID(uuidString: "a53baa1c-b4f5-48db-9467-9786b76b256d")!, 78 | ] 79 | #if !SKIP 80 | checkHashable(values, equalityOracle: { values[$0] == values[$1] }) 81 | #endif 82 | } 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/CoreLibs/TestUtils.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | func ensureFiles(_ fileNames: [String]) -> Bool { 19 | var result = true 20 | let fm = FileManager.default 21 | for name in fileNames { 22 | guard !fm.fileExists(atPath: name) else { 23 | continue 24 | } 25 | 26 | if name.hasSuffix("/") { 27 | do { 28 | try fm.createDirectory(atPath: name, withIntermediateDirectories: true, attributes: nil) 29 | } catch { 30 | print(error) 31 | return false 32 | } 33 | } else { 34 | 35 | var isDir: ObjCBool = ObjCBool(false) 36 | let dir = NSString(string: name).deletingLastPathComponent 37 | if !fm.fileExists(atPath: dir, isDirectory: &isDir) { 38 | do { 39 | try fm.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: nil) 40 | } catch { 41 | print(error) 42 | return false 43 | } 44 | } else if !isDir.boolValue { 45 | return false 46 | } 47 | 48 | result = result && fm.createFile(atPath: name, contents: nil, attributes: nil) 49 | } 50 | } 51 | return result 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Formats/TestAttributedStringSupport.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | #if !SKIP // disabled for to reduce test count and avoid io.grpc.StatusRuntimeException: RESOURCE_EXHAUSTED: gRPC message exceeds maximum size 9 | 10 | //===----------------------------------------------------------------------===// 11 | // 12 | // This source file is part of the Swift.org open source project 13 | // 14 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 15 | // Licensed under Apache License v2.0 with Runtime Library Exception 16 | // 17 | // See https://swift.org/LICENSE.txt for license information 18 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 19 | // 20 | //===----------------------------------------------------------------------===// 21 | 22 | 23 | extension NSAttributedString.Key { 24 | static let testInt = NSAttributedString.Key("TestInt") 25 | static let testString = NSAttributedString.Key("TestString") 26 | static let testDouble = NSAttributedString.Key("TestDouble") 27 | static let testBool = NSAttributedString.Key("TestBool") 28 | static let link: NSAttributedString.Key = .init(rawValue: "NSLink") 29 | } 30 | 31 | struct Color : Hashable, Codable { 32 | let name: String 33 | 34 | static let black: Color = Color(name: "black") 35 | static let blue: Color = Color(name: "black") 36 | static let white: Color = Color(name: "white") 37 | } 38 | 39 | @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) 40 | extension AttributeScopes.TestAttributes { 41 | 42 | enum TestIntAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { 43 | typealias Value = Int 44 | static let name = "TestInt" 45 | } 46 | 47 | enum TestStringAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { 48 | typealias Value = String 49 | static let name = "TestString" 50 | } 51 | 52 | enum TestDoubleAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { 53 | typealias Value = Double 54 | static let name = "TestDouble" 55 | } 56 | 57 | enum TestBoolAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { 58 | typealias Value = Bool 59 | static let name = "TestBool" 60 | } 61 | 62 | enum TestForegroundColorAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { 63 | typealias Value = Color 64 | static let name = "ForegroundColor" 65 | } 66 | 67 | enum TestBackgroundColorAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { 68 | typealias Value = Color 69 | static let name = "BackgroundColor" 70 | } 71 | 72 | #if false 73 | @frozen enum MisspelledAttribute : CodableAttributedStringKey, FixableAttribute { 74 | typealias Value = Bool 75 | static let name = "Misspelled" 76 | 77 | public static func fixAttribute(in string: inout AttributedString) { 78 | let words = string.characters.subranges { 79 | !$0.isWhitespace && !$0.isPunctuation 80 | } 81 | 82 | // First make sure that no non-words are marked as misspelled 83 | let nonWords = RangeSet(string.startIndex ..< string.endIndex).subtracting(words) 84 | string[nonWords].misspelled = nil 85 | 86 | // Then make sure that any word ranges containing the attribute span the entire word 87 | for (misspelled, range) in string.runs[\.misspelledAttribute] { 88 | if let misspelled = misspelled, misspelled { 89 | let fullRange = words.ranges[words.ranges.firstIndex { $0.contains(range.lowerBound) }!] 90 | string[fullRange].misspelled = true 91 | } 92 | } 93 | } 94 | } 95 | #endif 96 | 97 | enum NonCodableAttribute : AttributedStringKey { 98 | typealias Value = NonCodableType 99 | static let name = "NonCodable" 100 | } 101 | 102 | enum CustomCodableAttribute : CodableAttributedStringKey { 103 | typealias Value = NonCodableType 104 | static let name = "NonCodableConvertible" 105 | 106 | static func encode(_ value: NonCodableType, to encoder: Encoder) throws { 107 | var c = encoder.singleValueContainer() 108 | try c.encode(value.inner) 109 | } 110 | 111 | static func decode(from decoder: Decoder) throws -> NonCodableType { 112 | let c = try decoder.singleValueContainer() 113 | let inner = try c.decode(Int.self) 114 | return NonCodableType(inner: inner) 115 | } 116 | } 117 | 118 | } 119 | 120 | struct NonCodableType : Hashable { 121 | var inner : Int 122 | } 123 | 124 | @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) 125 | extension AttributeScopes { 126 | var test: TestAttributes.Type { TestAttributes.self } 127 | 128 | struct TestAttributes : AttributeScope { 129 | var testInt : TestIntAttribute 130 | var testString : TestStringAttribute 131 | var testDouble : TestDoubleAttribute 132 | var testBool : TestBoolAttribute 133 | var foregroundColor: TestForegroundColorAttribute 134 | var backgroundColor: TestBackgroundColorAttribute 135 | #if false 136 | var misspelled : MisspelledAttribute 137 | #endif 138 | } 139 | } 140 | 141 | @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) 142 | extension AttributeDynamicLookup { 143 | subscript(dynamicMember keyPath: KeyPath) -> T { 144 | get { self[T.self] } 145 | } 146 | } 147 | 148 | #endif 149 | 150 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Formats/TestMarkdown.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | #if SKIP 4 | import Foundation 5 | import XCTest 6 | 7 | final class TestMarkdown: XCTestCase { 8 | func testNoMarkdown() { 9 | XCTAssertNil(MarkdownNode.from(string: "")) 10 | XCTAssertNil(MarkdownNode.from(string: "This is some plain text")) 11 | XCTAssertNil(MarkdownNode.from(string: "This is some plain text\n\n- with block\n\n-elements")) 12 | } 13 | 14 | func testInlineFormatting() { 15 | XCTAssertEqual(stringify(MarkdownNode.from(string: "**This is some bold text**")), "This is some bold text") 16 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is some **bold** text")), "This is some bold text") 17 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is some ***bold italic*** text")), "This is some bold italic text") 18 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is some ~~strikethrough~~ text")), "This is some strikethrough text") 19 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is some ~~strikethrough *italic*~~ text")), "This is some strikethrough italic text") 20 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is some `code` text")), "This is some code text") 21 | } 22 | 23 | func testLinebreaks() { 24 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is **some** text\nwith line\n\nbreaks")), "This is some text\nwith line\n\nbreaks") 25 | } 26 | 27 | func testLink() { 28 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is [link 1](http://link1.com) and [*link 2*](http://link2.com)")), "This is link 1 and link 2") 29 | } 30 | 31 | func testInterpolation() { 32 | XCTAssertEqual(stringify(MarkdownNode.from(string: "**This is some %@ text**")), "This is some %s text[1]") 33 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is %@ **bold *%@*** text %@")), "This is %s [1]bold %s[2] text %s[3]") 34 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is %2$@ **bold *%1$@*** text %@")), "This is %s [2]bold %s[1] text %s[3]") 35 | XCTAssertEqual(stringify(MarkdownNode.from(string: "This is %@ [link %@](http://%@.com) text")), "This is %s [1]link %s[2] text") 36 | } 37 | 38 | private func stringify(_ node: MarkdownNode?, isFirstChild: Bool = true) -> String { 39 | guard let node else { 40 | return "" 41 | } 42 | switch node.type { 43 | case .bold: 44 | return "" + stringify(node.children) + "" 45 | case .code: 46 | return "" + (node.string ?? "") + stringifyInterpolations(node.interpolationIndexes) + "" 47 | case .italic: 48 | return "" + stringify(node.children) + "" 49 | case .link: 50 | return "" + stringify(node.children) + "" 51 | case .paragraph: 52 | return (isFirstChild ? "" : "\n\n") + stringify(node.children) 53 | case .strikethrough: 54 | return "" + stringify(node.children) + "" 55 | case .root: 56 | return stringify(node.children) 57 | case .text: 58 | return (node.string ?? "") + stringifyInterpolations(node.interpolationIndexes) 59 | case .unknown: 60 | return "" + stringify(node.children) + "" 61 | } 62 | } 63 | 64 | private func stringify(_ nodes: List?) -> String { 65 | guard let nodes else { 66 | return "" 67 | } 68 | var string = "" 69 | for i in 0..?) -> String { 76 | guard let interpolations else { 77 | return "" 78 | } 79 | return "[" + interpolations.joinToString(separator: ",") + "]" 80 | } 81 | } 82 | #endif 83 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Formats/TestPropertyListEncoder.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestPropertyListEncoder : XCTestCase { 19 | } 20 | 21 | extension TestPropertyListEncoder { 22 | #if !SKIP 23 | class TestBaseClass: Codable { 24 | enum IntEnum: Int, Codable, Equatable { 25 | case one = 1 26 | case two 27 | } 28 | 29 | struct InnerStruct: Codable, Equatable { 30 | enum StringEnum: String, Codable, Equatable { 31 | case one = "1" 32 | case two 33 | } 34 | 35 | let string: String? 36 | let optionalInt: Int? 37 | let url: URL 38 | let stringEnum: StringEnum? 39 | var data: Data 40 | var date: Date? 41 | } 42 | 43 | let intEnum: IntEnum 44 | let innerStruct: InnerStruct 45 | 46 | init(intEnum: IntEnum, innerStruct: InnerStruct) { 47 | self.intEnum = intEnum 48 | self.innerStruct = innerStruct 49 | } 50 | } 51 | #endif 52 | 53 | func test_basicEncodeDecode() throws { 54 | #if SKIP 55 | throw XCTSkip("TODO") 56 | #else 57 | let propertyListFormats: [PropertyListSerialization.PropertyListFormat?] = [nil, .binary, .xml] 58 | 59 | for format in propertyListFormats { 60 | let optionalInt: Int? = 1234 61 | let url = URL(string: "https://swift.org")! 62 | let innerClassString = "demo_string" 63 | let testData = innerClassString.data(using: .utf8)! 64 | let testDate = Date.distantPast 65 | 66 | let innerStruct = TestBaseClass.InnerStruct(string: innerClassString, optionalInt: optionalInt, url: url, stringEnum: .two, data: testData, date: testDate) 67 | let testClass = TestBaseClass(intEnum: .one, innerStruct: innerStruct) 68 | 69 | let encoder = PropertyListEncoder() 70 | if let format = format { 71 | encoder.outputFormat = format 72 | } 73 | let data = try? encoder.encode(testClass) 74 | XCTAssertNotNil(data) 75 | 76 | let decoder = PropertyListDecoder() 77 | let decodedClass: TestBaseClass 78 | decodedClass = try decoder.decode(TestBaseClass.self, from: data!) 79 | XCTAssertEqual(decodedClass.innerStruct, testClass.innerStruct) 80 | XCTAssertEqual(decodedClass.intEnum, testClass.intEnum) 81 | 82 | if format == .xml { 83 | XCTAssertNotNil(String(data: data!, encoding: .utf8)) 84 | } 85 | } 86 | #endif // !SKIP 87 | } 88 | } 89 | 90 | extension TestPropertyListEncoder { 91 | static let propertyListXML = """ 92 | 93 | 94 | 95 | 96 | CFBundleDevelopmentRegion 97 | en 98 | CFBundleExecutable 99 | xdgTestHelper 100 | CFBundleIdentifier 101 | org.swift.xdgTestHelper 102 | CFBundleInfoDictionaryVersion 103 | 6.0 104 | CFBundleIntKey 105 | -100 106 | CFBundleBoolKey 107 | 108 | CFBundleOtherKey 109 | other... 110 | CFBundleDataArrayKey 111 | 112 | 113 | VEVTVF9EQVRB 114 | 115 | 116 | CFBundleDateKey 117 | 1970-01-01T00:00:20Z 118 | 119 | 120 | """ 121 | 122 | #if !SKIP 123 | struct InfoPlist: Codable, Equatable { 124 | let CFBundleDevelopmentRegion: String 125 | let CFBundleExecutable: String? 126 | let CFBundleIdentifier: String 127 | let CFBundleInfoDictionaryVersion: Double 128 | let CFBundleIntKey: Int 129 | let CFFakeOptionalKey: Int? 130 | let CFBundleBoolKey: Bool 131 | let CFBundleDataArrayKey: [Data] 132 | let CFBundleDateKey: Date 133 | } 134 | #endif 135 | 136 | func test_xmlDecoder() throws { 137 | #if SKIP 138 | throw XCTSkip("TODO") 139 | #else 140 | let resultInfoPlist = InfoPlist( 141 | CFBundleDevelopmentRegion: "en", 142 | CFBundleExecutable: "xdgTestHelper", 143 | CFBundleIdentifier: "org.swift.xdgTestHelper", 144 | CFBundleInfoDictionaryVersion: 6.0, 145 | CFBundleIntKey: -100, 146 | CFFakeOptionalKey: nil, 147 | CFBundleBoolKey: true, 148 | CFBundleDataArrayKey: ["TEST_DATA".data(using: .utf8)!], 149 | CFBundleDateKey: Date(timeIntervalSince1970: 20) 150 | ) 151 | 152 | let testData = TestPropertyListEncoder.propertyListXML.data(using: .utf8)! 153 | let decoder = PropertyListDecoder() 154 | var format: PropertyListSerialization.PropertyListFormat = .binary 155 | let decodedInfoPlist = try decoder.decode(InfoPlist.self, from: testData, format: &format) 156 | XCTAssertEqual(format, .xml) 157 | XCTAssertEqual(decodedInfoPlist, resultInfoPlist) 158 | #endif // !SKIP 159 | } 160 | } 161 | 162 | 163 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Formats/TestPropertyListSerialization.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestPropertyListSerialization : XCTestCase { 19 | 20 | func test_BasicConstruction() { 21 | #if SKIP 22 | throw XCTSkip("TODO") 23 | #else 24 | let dict = NSMutableDictionary(capacity: 0) 25 | // dict["foo"] = "bar" 26 | var data: Data? = nil 27 | do { 28 | data = try PropertyListSerialization.data(fromPropertyList: dict, format: .binary, options: 0) 29 | } catch { 30 | 31 | } 32 | XCTAssertNotNil(data) 33 | XCTAssertEqual(data!.count, 42, "empty dictionary should be 42 bytes") 34 | #endif // !SKIP 35 | } 36 | 37 | func test_decodeData() { 38 | #if SKIP 39 | throw XCTSkip("TODO") 40 | #else 41 | var decoded: Any? 42 | var fmt = PropertyListSerialization.PropertyListFormat.binary 43 | let path = testBundle().url(forResource: "Test", withExtension: "plist") 44 | let data = try! Data(contentsOf: path!) 45 | do { 46 | decoded = try withUnsafeMutablePointer(to: &fmt) { (format: UnsafeMutablePointer) -> Any in 47 | return try PropertyListSerialization.propertyList(from: data, options: [], format: format) 48 | } 49 | } catch { 50 | 51 | } 52 | 53 | XCTAssertNotNil(decoded) 54 | let dict = decoded as! Dictionary 55 | XCTAssertEqual(dict.count, 3) 56 | let val = dict["Foo"] 57 | XCTAssertNotNil(val) 58 | if let str = val as? String { 59 | XCTAssertEqual(str, "Bar") 60 | } else { 61 | XCTFail("value stored is not a string") 62 | } 63 | #endif // !SKIP 64 | } 65 | 66 | func test_decodeStream() throws { 67 | #if SKIP 68 | throw XCTSkip("TODO") 69 | #else 70 | var decoded: Any? 71 | var fmt = PropertyListSerialization.PropertyListFormat.binary 72 | let path = testBundle().url(forResource: "Test", withExtension: "plist") 73 | let stream = try XCTUnwrap(InputStream(url: XCTUnwrap(path))) 74 | stream.open() 75 | do { 76 | decoded = try withUnsafeMutablePointer(to: &fmt) { (format: UnsafeMutablePointer) -> Any in 77 | return try PropertyListSerialization.propertyList(with: stream, options: [], format: format) 78 | } 79 | } catch { 80 | 81 | } 82 | 83 | XCTAssertNotNil(decoded) 84 | let dict = decoded as! Dictionary 85 | XCTAssertEqual(dict.count, 3) 86 | let val = dict["Foo"] 87 | XCTAssertNotNil(val) 88 | if let str = val as? String { 89 | XCTAssertEqual(str, "Bar") 90 | } else { 91 | XCTFail("value stored is not a string") 92 | } 93 | #endif // !SKIP 94 | } 95 | 96 | func test_decodeEmptyData() { 97 | #if SKIP 98 | throw XCTSkip("TODO") 99 | #else 100 | XCTAssertThrowsError(try PropertyListSerialization.propertyList(from: Data(), format: nil)) { error in 101 | let nserror = error as! NSError 102 | XCTAssertEqual(nserror.domain, NSCocoaErrorDomain) 103 | XCTAssertEqual(CocoaError(_nsError: nserror).code, .propertyListReadCorrupt) 104 | XCTAssertEqual(nserror.userInfo[NSDebugDescriptionErrorKey] as? String, "Cannot parse a NULL or zero-length data") 105 | } 106 | 107 | #endif // !SKIP 108 | } 109 | } 110 | 111 | 112 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/BundleTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import OSLog 5 | import XCTest 6 | 7 | @available(macOS 13, iOS 16, watchOS 10, tvOS 16, *) 8 | final class BundleTests: XCTestCase { 9 | let logger: Logger = Logger(subsystem: "test", category: "BundleTests") 10 | 11 | func testBundle() throws { 12 | // Swift will be: Contents/Resources/ -- file:///~/Library/Developer/Xcode/DerivedData/DemoApp-ABCDEF/Build/Products/Debug/SkipFoundationTests.xctest/Contents/Resources/Skip_SkipFoundationTests.bundle/ 13 | // Kotlin will be: file:///~/Library/Developer/Xcode/DerivedData/DemoApp-ABCDEF/Build/Products/Debug/SkipFoundationTests.xctest/Contents/Resources/skip-foundation_SkipFoundationTests.bundle/Contents/Resources/textasset.txt 14 | 15 | let resourceURL: URL = try XCTUnwrap(Bundle.module.url(forResource: "textasset", withExtension: "txt", subdirectory: nil, localization: nil)) 16 | logger.info("resourceURL: \(resourceURL.absoluteString)") 17 | let str = try String(contentsOf: resourceURL) 18 | XCTAssertEqual("Some text\n", str) 19 | } 20 | 21 | func testBundleInfo() throws { 22 | #if !SKIP 23 | // sadly, the Info.plist is auto-generated by SPM and cannot be overridden 24 | let _ = Bundle.module.infoDictionary ?? [:] 25 | let name = Bundle.module.bundleURL.deletingPathExtension().lastPathComponent 26 | XCTAssertTrue(name.hasSuffix("_SkipFoundationTests"), "unexpected name: \(name)") 27 | let moduleName = name.split(separator: "_").last?.description ?? name // turn "Skip_SkipFoundationTests" into "SkipFoundationTests" 28 | XCTAssertEqual("SkipFoundationTests", moduleName) 29 | 30 | //XCTAssertEqual(["BuildMachineOSBuild", "CFBundleDevelopmentRegion", "CFBundleIdentifier", "CFBundleInfoDictionaryVersion", "CFBundleName", "CFBundlePackageType", "CFBundleSupportedPlatforms", "DTCompiler", "DTPlatformBuild", "DTPlatformName", "DTPlatformVersion", "DTSDKBuild", "DTSDKName", "DTXcode", "DTXcodeBuild", "LSMinimumSystemVersion"], Set(info.keys)) 31 | //XCTAssertEqual("Skip_SkipFoundationTests", info["CFBundleName"] as? String) 32 | #endif 33 | } 34 | 35 | func testMainBundleInfo() throws { 36 | let info = Bundle.main.infoDictionary 37 | if !isRobolectric { 38 | throw XCTSkip("testMainBundleInfo only checked for Robolectric") 39 | } 40 | 41 | XCTAssertEqual("skip.foundation.test", info?["CFBundleIdentifier"] as? String) // "com.apple.dt.xctest.tool" 42 | XCTAssertEqual("skip.foundation.test", info?["CFBundleName"] as? String) // "xctest" 43 | XCTAssertEqual("skip.foundation.test", info?["CFBundleDisplayName"] as? String) 44 | XCTAssertEqual("skip.foundation.test", info?["CFBundleExecutable"] as? String) // xctest 45 | 46 | XCTAssertEqual("0", info?["CFBundleVersion"] as? String) // 23196 47 | XCTAssertEqual("", info?["CFBundleShortVersionString"] as? String) // 16.0 48 | 49 | XCTAssertEqual("android", info?["DTPlatformName"] as? String) // macosx 50 | XCTAssertEqual("29", info?["DTPlatformVersion"] as? String) // 15.0 51 | XCTAssertEqual("29", info?["MinimumOSVersion"] as? String) // nil 52 | XCTAssertEqual("android29", info?["DTSDKName"] as? String) // macosx15.0.internal 53 | XCTAssertEqual("robolectric", info?["BuildMachineOSBuild"] as? String) // 22A380021 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/CurrencyTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | @available(macOS 13, iOS 16, watchOS 10, tvOS 16, *) 7 | final class CurrencyTests: XCTestCase { 8 | 9 | func testUSD() throws { 10 | let usd = Locale.Currency("USD") 11 | let usdLiteral: Locale.Currency = "USD" 12 | 13 | XCTAssertEqual(usd, usdLiteral) 14 | XCTAssertEqual(usd.identifier, "USD") 15 | XCTAssertTrue(usd.isISOCurrency) 16 | } 17 | 18 | func testCommonCurrencies() throws { 19 | for currencyCode in Locale.commonISOCurrencyCodes { 20 | let currency = Locale.Currency(currencyCode) 21 | XCTAssertEqual(currency.identifier, currencyCode) 22 | XCTAssertTrue(currency.isISOCurrency) 23 | } 24 | } 25 | 26 | func testIsoCurrencies() throws { 27 | let isoCurrencies = Locale.Currency.isoCurrencies 28 | 29 | let currencyCodes = isoCurrencies.map { $0.identifier } 30 | XCTAssertEqual(Set(currencyCodes).sorted(), currencyCodes) 31 | #if SKIP 32 | 33 | for currency in isoCurrencies { 34 | XCTAssertTrue(currency.isISOCurrency) 35 | } 36 | #endif 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/DataTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | @available(macOS 11, iOS 14, watchOS 7, tvOS 14, *) 7 | final class DataTests: XCTestCase { 8 | func testDataFile() throws { 9 | let hostsFile: URL = URL(fileURLWithPath: "/etc/hosts", isDirectory: false) 10 | 11 | let hostsData: Data = try Data(contentsOf: hostsFile) 12 | XCTAssertNotEqual(0, hostsData.count) 13 | } 14 | 15 | func testDataDownload() throws { 16 | try failOnAndroid() // no android.permission.INTERNET 17 | let url: URL = try XCTUnwrap(URL(string: "https://www.example.com")) 18 | 19 | let urlData: Data = try Data(contentsOf: url) 20 | 21 | //logger.log("downloaded url size: \(urlData.count)") // ~1256 22 | XCTAssertNotEqual(0, urlData.count) 23 | 24 | let url2 = try XCTUnwrap(URL(string: "domains/reserved", relativeTo: URL(string: "https://www.iana.org"))) 25 | let url2Data: Data = try Data(contentsOf: url2) 26 | 27 | //logger.log("downloaded url2 size: \(url2Data.count)") // ~1256 28 | XCTAssertNotEqual(0, url2Data.count) 29 | } 30 | 31 | func testStringEncoding() { 32 | do { 33 | let str = "𝄞𝕥𝟶𠂊" 34 | 35 | XCTAssertEqual("f09d849ef09d95a5f09d9fb6f0a0828a", str.data(using: .utf8)?.hex()) 36 | 37 | XCTAssertEqual("fffe34d81edd35d865dd35d8f6df40d88adc", str.data(using: .utf16)?.hex()) 38 | XCTAssertEqual("d834dd1ed835dd65d835dff6d840dc8a", str.data(using: .utf16BigEndian)?.hex()) 39 | XCTAssertEqual("34d81edd35d865dd35d8f6df40d88adc", str.data(using: .utf16LittleEndian)?.hex()) 40 | 41 | XCTAssertEqual("fffe00001ed1010065d50100f6d701008a000200", str.data(using: .utf32)?.hex()) 42 | XCTAssertEqual("1ed1010065d50100f6d701008a000200", str.data(using: .utf32LittleEndian)?.hex()) 43 | XCTAssertEqual("0001d11e0001d5650001d7f60002008a", str.data(using: .utf32BigEndian)?.hex()) 44 | } 45 | 46 | do { 47 | let str = "\u{0065}\u{0301}" // eWithAcuteCombining 48 | 49 | XCTAssertEqual("65cc81", str.data(using: .utf8)?.hex()) 50 | 51 | XCTAssertEqual("fffe65000103", str.data(using: .utf16)?.hex()) 52 | XCTAssertEqual("65000103", str.data(using: .utf16LittleEndian)?.hex()) 53 | XCTAssertEqual("00650301", str.data(using: .utf16BigEndian)?.hex()) 54 | 55 | XCTAssertEqual("fffe00006500000001030000", str.data(using: .utf32)?.hex()) 56 | XCTAssertEqual("6500000001030000", str.data(using: .utf32LittleEndian)?.hex()) 57 | XCTAssertEqual("0000006500000301", str.data(using: .utf32BigEndian)?.hex()) 58 | } 59 | } 60 | 61 | func testDataContains() throws { 62 | XCTAssertTrue(Data().contains(Data())) 63 | XCTAssertTrue(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data())) 64 | 65 | XCTAssertTrue(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data([UInt8(0x02)]))) 66 | XCTAssertTrue(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data([UInt8(0x01), UInt8(0x02)]))) 67 | XCTAssertTrue(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]))) 68 | XCTAssertTrue(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data([UInt8(0x02), UInt8(0x03)]))) 69 | XCTAssertTrue(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data([UInt8(0x03)]))) 70 | 71 | XCTAssertFalse(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data([UInt8(0x04)]))) 72 | XCTAssertFalse(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]).contains(Data([UInt8(0x01), UInt8(0x02), UInt8(0x03), UInt8(0x04)]))) 73 | XCTAssertFalse(Data([UInt8(0x01)]).contains(Data([UInt8(0x02), UInt8(0x03), UInt8(0x04)]))) 74 | XCTAssertFalse(Data([UInt8(0x01)]).contains(Data([UInt8(0x01), UInt8(0x02)]))) 75 | XCTAssertFalse(Data([UInt8(0x01)]).contains(Data([UInt8(0x02)]))) 76 | XCTAssertFalse(Data().contains(Data([UInt8(0x01)]))) 77 | } 78 | 79 | func testBase64EncodedString() { 80 | let data = Data([UInt8(0x01), UInt8(0x02), UInt8(0x03)]) 81 | XCTAssertEqual("AQID", data.base64EncodedString()) 82 | let data2 = Data(base64Encoded: "AQID") 83 | XCTAssertEqual(data, data2) 84 | } 85 | 86 | func testByteArrays() { 87 | let inbytes = [UInt8(8), UInt8(9), UInt8(10)] 88 | let data = Data(inbytes) 89 | let outbytes = [UInt8](data) 90 | XCTAssertEqual(outbytes, inbytes) 91 | 92 | let outbytes2: [UInt8] = .init(data) 93 | XCTAssertEqual(outbytes2, inbytes) 94 | } 95 | } 96 | 97 | extension Sequence where Element == UInt8 { 98 | /// Convert this sequence of bytes into a hex string 99 | public func hex() -> String { map { String(format: "%02x", $0) }.joined() } 100 | } 101 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/DigestTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | #if canImport(CryptoKit) 7 | import CryptoKit 8 | 9 | extension Data { 10 | public func sha256() -> Data { 11 | return Data(SHA256.hash(data: self)) 12 | } 13 | } 14 | #endif 15 | 16 | @available(macOS 11, iOS 14, watchOS 7, tvOS 14, *) 17 | final class DigestTests: XCTestCase { 18 | func testSHA256() { 19 | //XCTAssertEqual("Hello World".utf8.sha256().hex(), "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e") 20 | XCTAssertEqual(Data("Hello World".utf8).sha256().hex(), "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e") 21 | } 22 | 23 | func testDigestHashSHA256() { 24 | let inputData = Data("Hello World".utf8) 25 | let hashedData: SHA256Digest = SHA256.hash(data: inputData) 26 | XCTAssertEqual("SHA256 digest: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e", hashedData.description) 27 | XCTAssertEqual("SHA256 digest: db67b0f5582ae32b540e43ab326f582a1f43803e9dfb856206e87b66e3137660", SHA256.hash(data: Data("ZZ Top".utf8)).description) 28 | } 29 | 30 | func testDigestHashSHA384() { 31 | let inputData = Data("Hello World".utf8) 32 | let hashedData = SHA384.hash(data: inputData) 33 | XCTAssertEqual("SHA384 digest: 99514329186b2f6ae4a1329e7ee6c610a729636335174ac6b740f9028396fcc803d0e93863a7c3d90f86beee782f4f3f", hashedData.description) 34 | XCTAssertEqual("SHA384 digest: acfccf00f321f7ae0fcd2352ec0053f06519dcc2084a2b08fb340cce844c0654abf416c23a0ea94483ab2cee21359184", SHA384.hash(data: Data("ZZ Top".utf8)).description) 35 | } 36 | 37 | func testDigestHashSHA512() { 38 | let inputData = Data("Hello World".utf8) 39 | let hashedData = SHA512.hash(data: inputData) 40 | XCTAssertEqual("SHA512 digest: 2c74fd17edafd80e8447b0d46741ee243b7eb74dd2149a0ab1b9246fb30382f27e853d8585719e0e67cbda0daa8f51671064615d645ae27acb15bfb1447f459b", hashedData.description) 41 | XCTAssertEqual("SHA512 digest: 5a293a75e255fbdc85d826483fb1dc05519a750b98e76dfe2922a513df5a4aec2c9daa07cd3abcb0156bdeba3e41897bd0b8f06a3e1df4dd0ec1d0ffefc8abe1", SHA512.hash(data: Data("ZZ Top".utf8)).description) 42 | } 43 | 44 | func testDigestHashMD5() { 45 | let inputData = Data("Hello World".utf8) 46 | let hashedData: Insecure.MD5Digest = Insecure.MD5.hash(data: inputData) 47 | XCTAssertEqual("MD5 digest: b10a8db164e0754105b7a99be72e3fe5", hashedData.description) 48 | XCTAssertEqual("MD5 digest: 5f17026cffec9e27c6657f2f2a54e655", Insecure.MD5.hash(data: Data("ZZ Top".utf8)).description) 49 | } 50 | 51 | func testDigestHashSHA1() { 52 | let inputData = Data("Hello World".utf8) 53 | let hashedData: Insecure.SHA1Digest = Insecure.SHA1.hash(data: inputData) 54 | XCTAssertEqual("SHA1 digest: 0a4d55a8d778e5022fab701977c5d840bbc486d0", hashedData.description) 55 | XCTAssertEqual("SHA1 digest: 962f927d8fb5f84a01d2c7c7a2bdefff151dff09", Insecure.SHA1.hash(data: Data("ZZ Top".utf8)).description) 56 | } 57 | 58 | func testDigestAsSequence() { 59 | let inputData = Data("Hello World".utf8) 60 | let hashedData: SHA256Digest = SHA256.hash(data: inputData) 61 | if let max = hashedData.max() { 62 | XCTAssertGreaterThan(max, UInt8(0)) 63 | } else { 64 | XCTFail() 65 | } 66 | var iterator = hashedData.makeIterator() 67 | XCTAssertNotNil(iterator.next()) 68 | } 69 | 70 | func testHMACSignSHA256() { 71 | #if !SKIP 72 | // cannot reference HMAC in SKIP because Kotlin can't access the generic type from a static context (to get the algorithm name) 73 | typealias HMACSHA256 = HMAC 74 | #endif 75 | 76 | let message = "Your message to sign" 77 | let secret = "your-secret-key" 78 | let signature = HMACSHA256.authenticationCode(for: Data(message.utf8), using: SymmetricKey(data: Data(secret.utf8))) 79 | XCTAssertEqual("mUohryR4fJJFBXxnYup30d7IcYsG+o9Oyke/Nz87bfs=", Data(signature).base64EncodedString()) 80 | } 81 | 82 | func testHMACSigning() { 83 | #if !SKIP 84 | typealias HMACSHA256 = HMAC 85 | typealias HMACSHA384 = HMAC 86 | typealias HMACSHA512 = HMAC 87 | #endif 88 | 89 | //XCTAssertEqual("U1V0aL+omPKt3L1+Fso3cA==", Data(HMACMD5.authenticationCode(for: Data("Your message to sign".utf8), using: SymmetricKey(data: Data("your-secret-key".utf8)))).base64EncodedString()) 90 | //XCTAssertEqual("6FxBqkItAuJRW8qmco9PnY0gWqY=", Data(HMACSHA1.authenticationCode(for: Data("Your message to sign".utf8), using: SymmetricKey(data: Data("your-secret-key".utf8)))).base64EncodedString()) 91 | XCTAssertEqual("mUohryR4fJJFBXxnYup30d7IcYsG+o9Oyke/Nz87bfs=", Data(HMACSHA256.authenticationCode(for: Data("Your message to sign".utf8), using: SymmetricKey(data: Data("your-secret-key".utf8)))).base64EncodedString()) 92 | XCTAssertEqual("svisOwCPCKT+Z+hn2vSONxsxn0M40URfq1mHKJ4QfAYVTwX58g4BZ8hhSmK20RKD", Data(HMACSHA384.authenticationCode(for: Data("Your message to sign".utf8), using: SymmetricKey(data: Data("your-secret-key".utf8)))).base64EncodedString()) 93 | XCTAssertEqual("TrfwQeSZQ8gTfY7U+NQY+CDxexfk6hHVDJ2pevtMM2OXmxY9X/60uWhsXnMym1+vzg7XGgO729yGQgsPdAY19A==", Data(HMACSHA512.authenticationCode(for: Data("Your message to sign".utf8), using: SymmetricKey(data: Data("your-secret-key".utf8)))).base64EncodedString()) 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/FileManagerTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | @available(macOS 11, iOS 14, watchOS 7, tvOS 14, *) 7 | final class FileManagerTests: XCTestCase { 8 | func testFileManager() throws { 9 | let tmp = NSTemporaryDirectory() 10 | //logger.log("temporary folder: \(tmp)") 11 | XCTAssertNotNil(tmp) 12 | XCTAssertNotEqual("", tmp) 13 | 14 | let fm = FileManager.default 15 | XCTAssertNotNil(fm) 16 | 17 | XCTAssertEqual("NSFileAppendOnly", FileAttributeKey.appendOnly.rawValue) 18 | XCTAssertEqual("NSFileCreationDate", FileAttributeKey.creationDate.rawValue) 19 | XCTAssertEqual("NSFileDeviceIdentifier", FileAttributeKey.deviceIdentifier.rawValue) 20 | XCTAssertEqual("NSFileExtensionHidden", FileAttributeKey.extensionHidden.rawValue) 21 | XCTAssertEqual("NSFileGroupOwnerAccountID", FileAttributeKey.groupOwnerAccountID.rawValue) 22 | XCTAssertEqual("NSFileGroupOwnerAccountName", FileAttributeKey.groupOwnerAccountName.rawValue) 23 | XCTAssertEqual("NSFileHFSCreatorCode", FileAttributeKey.hfsCreatorCode.rawValue) 24 | XCTAssertEqual("NSFileHFSTypeCode", FileAttributeKey.hfsTypeCode.rawValue) 25 | XCTAssertEqual("NSFileImmutable", FileAttributeKey.immutable.rawValue) 26 | XCTAssertEqual("NSFileModificationDate", FileAttributeKey.modificationDate.rawValue) 27 | XCTAssertEqual("NSFileOwnerAccountID", FileAttributeKey.ownerAccountID.rawValue) 28 | XCTAssertEqual("NSFileOwnerAccountName", FileAttributeKey.ownerAccountName.rawValue) 29 | XCTAssertEqual("NSFilePosixPermissions", FileAttributeKey.posixPermissions.rawValue) 30 | XCTAssertEqual("NSFileProtectionKey", FileAttributeKey.protectionKey.rawValue) 31 | XCTAssertEqual("NSFileReferenceCount", FileAttributeKey.referenceCount.rawValue) 32 | XCTAssertEqual("NSFileSystemFileNumber", FileAttributeKey.systemFileNumber.rawValue) 33 | XCTAssertEqual("NSFileSystemFreeNodes", FileAttributeKey.systemFreeNodes.rawValue) 34 | XCTAssertEqual("NSFileSystemFreeSize", FileAttributeKey.systemFreeSize.rawValue) 35 | XCTAssertEqual("NSFileSystemNodes", FileAttributeKey.systemNodes.rawValue) 36 | XCTAssertEqual("NSFileSystemNumber", FileAttributeKey.systemNumber.rawValue) 37 | XCTAssertEqual("NSFileSystemSize", FileAttributeKey.systemSize.rawValue) 38 | XCTAssertEqual("NSFileType", FileAttributeKey.type.rawValue) 39 | XCTAssertEqual("NSFileBusy", FileAttributeKey.busy.rawValue) 40 | 41 | } 42 | 43 | func testDocumentsDirectory() throws { 44 | let url = try FileManager.default.url(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false) 45 | #if !SKIP 46 | XCTAssertEqual("Documents", url.lastPathComponent) 47 | #else 48 | // Robolectric: /var/folders/zl/wkdjv4s1271fbm6w0plzknkh0000gn/T/robolectric-FileManagerTests_testFileLocations_SkipFoundation_debugUnitTest525656974178778848/skip.foundation.test-dataDir/files/ 49 | // Emulator: /data/user/0/skip.foundation.test/files/ 50 | XCTAssertEqual("files", url.lastPathComponent) 51 | #endif 52 | } 53 | 54 | func testCachesDirectory() throws { 55 | let url = try FileManager.default.url(for: FileManager.SearchPathDirectory.cachesDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: false) 56 | #if !SKIP 57 | XCTAssertEqual("Caches", url.lastPathComponent) 58 | #else 59 | // Emulator: /data/user/0/skip.foundation.test/cache/ 60 | XCTAssertEqual("cache", url.lastPathComponent) 61 | #endif 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/LoggerTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import OSLog 4 | import XCTest 5 | 6 | /// This test is a minimal example of `OSLog.Logger` being transpiled to use `skip.foundation.SkipLogger` on the Kotlin side. 7 | @available(macOS 14, iOS 16, watchOS 9, tvOS 16, *) 8 | final class LoggerTests: XCTestCase { 9 | let logger: Logger = Logger(subsystem: "test", category: "LoggerTests") 10 | 11 | public func testLogDebug() { 12 | logger.debug("logger debug test") 13 | } 14 | 15 | public func testLogInfo() { 16 | logger.info("logger info test") 17 | } 18 | 19 | public func testLogWarning() { 20 | logger.warning("logger warning test") 21 | } 22 | 23 | public func testLogError() { 24 | logger.error("logger error test") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/URLTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | final class URLTests: XCTestCase { 7 | func testURLs() throws { 8 | let url: URL? = URL(string: "https://github.com/skiptools/skip.git") 9 | XCTAssertEqual("https://github.com/skiptools/skip.git", url?.absoluteString) 10 | XCTAssertEqual("/skiptools/skip.git", url!.path()) 11 | XCTAssertEqual("github.com", url?.host) 12 | XCTAssertEqual("git", url?.pathExtension) 13 | XCTAssertEqual("skip.git", url?.lastPathComponent) 14 | XCTAssertEqual(false, url?.isFileURL) 15 | 16 | #if false 17 | // This fails on CI 18 | let complexURL = URL(string: "https://github.com:443/user/new?user=foo&password=password@^%|1") 19 | XCTAssertNotNil(complexURL) 20 | XCTAssertEqual(443, complexURL?.port) 21 | XCTAssertEqual("user=foo&password=password@%5E%25%7C1", complexURL?.query()) 22 | #endif 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Foundation/UUIDTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | @available(macOS 11, iOS 14, watchOS 7, tvOS 14, *) 7 | final class UUIDTests: XCTestCase { 8 | func testRandomUUID() throws { 9 | XCTAssertNotEqual(UUID(), UUID()) 10 | XCTAssertNotEqual("", UUID().uuidString) 11 | //logger.log("UUID: \(UUID().uuidString)") 12 | } 13 | 14 | func testFixedUUID() throws { 15 | let uuid: UUID? = UUID(uuidString: "d500d1f7-ddb0-439b-ab90-22fdbe5b5790") 16 | XCTAssertEqual("D500D1F7-DDB0-439B-AB90-22FDBE5B5790", uuid?.uuidString) 17 | } 18 | 19 | func testUUIDFromBits() throws { 20 | #if SKIP // works, but would require import SkipFoundation.UUID 21 | XCTAssertEqual("00000000-0000-0000-0000-000000000000", UUID(mostSigBits: 0, leastSigBits: 0).uuidString) 22 | XCTAssertEqual("00000000-0000-0001-0000-000000000000", UUID(mostSigBits: 1, leastSigBits: 0).uuidString) 23 | XCTAssertEqual("00000000-0000-0000-0000-000000000001", UUID(mostSigBits: 0, leastSigBits: 1).uuidString) 24 | XCTAssertEqual("00000000-0000-0064-0000-000000000064", UUID(mostSigBits: 100, leastSigBits: 100).uuidString) 25 | XCTAssertEqual("112210F4-7DE9-8115-0DB4-DA5F49F8B478", UUID(mostSigBits: 1234567890123456789, leastSigBits: 987654321098765432).uuidString) 26 | XCTAssertEqual("00000000-0005-3D9D-0000-000151280C98", UUID(mostSigBits: 343453, leastSigBits: 5656546456).uuidString) 27 | 28 | // SKIP REPLACE: val mx = Long.MAX_VALUE 29 | let mx = Int64.max 30 | // SKIP REPLACE: val mn = Long.MIN_VALUE 31 | let mn = Int64.min 32 | 33 | XCTAssertEqual("7FFFFFFF-FFFF-FFFF-7FFF-FFFFFFFFFFFF", UUID(mostSigBits: mx, leastSigBits: mx).uuidString) 34 | XCTAssertEqual("7FFFFFFF-FFFF-FFFF-8000-000000000000", UUID(mostSigBits: mx, leastSigBits: mn).uuidString) 35 | XCTAssertEqual("80000000-0000-0000-8000-000000000000", UUID(mostSigBits: mn, leastSigBits: mn).uuidString) 36 | XCTAssertEqual("7FFFFFFF-FFFF-FFFF-8000-000000000000", UUID(mostSigBits: mx, leastSigBits: mn).uuidString) 37 | #endif 38 | } 39 | 40 | func test_UUIDEquality() { 41 | let uuidA = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F") 42 | let uuidB = UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f") 43 | // let uuidC = UUID(uuid: (0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f)) 44 | let uuidD = UUID() 45 | 46 | XCTAssertEqual(uuidA, uuidB, "String case must not matter.") 47 | // XCTAssertEqual(uuidA, uuidC, "A UUID initialized with a string must be equal to the same UUID initialized with its UnsafePointer equivalent representation.") 48 | XCTAssertNotEqual(uuidB, uuidD, "Two different UUIDs must not be equal.") 49 | } 50 | 51 | func test_UUIDInvalid() { 52 | let uuid = UUID(uuidString: "Invalid UUID") 53 | XCTAssertNil(uuid, "The convenience initializer `init?(uuidString string:)` must return nil for an invalid UUID string.") 54 | } 55 | 56 | // func test_UUIDuuidString() { 57 | // let uuid = UUID(uuid: (0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f)) 58 | // XCTAssertEqual(uuid.uuidString, "E621E1F8-C36C-495A-93FC-0C247A3E6E5F", "The uuidString representation must be uppercase.") 59 | // } 60 | 61 | func test_UUIDdescription() { 62 | let uuid = UUID() 63 | XCTAssertEqual(uuid.description, uuid.uuidString, "The description must be the same as the uuidString.") 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Network/TestHost.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestHost: XCTestCase { 19 | 20 | #if os(iOS) 21 | #else 22 | // SR-6391 23 | func test_addressesDoNotGrow() { 24 | #if SKIP 25 | throw XCTSkip("TODO") 26 | #else 27 | let local = Host.current() 28 | let localAddressesFirst = local.addresses 29 | let localAddressesSecond = local.addresses 30 | XCTAssertEqual(localAddressesSecond.count, localAddressesFirst.count) 31 | 32 | let dns = Host(address: "8.8.8.8") 33 | let dnsAddressesFirst = dns.addresses 34 | let dnsAddressesSecond = dns.addresses 35 | XCTAssertEqual(dnsAddressesSecond.count, dnsAddressesFirst.count) 36 | 37 | let swift = Host(name: "localhost") 38 | let swiftAddressesFirst = swift.addresses 39 | let swiftAddressesSecond = swift.addresses 40 | XCTAssertEqual(swiftAddressesSecond.count, swiftAddressesFirst.count) 41 | #endif // !SKIP 42 | } 43 | 44 | func test_isEqual() { 45 | #if SKIP 46 | throw XCTSkip("TODO") 47 | #else 48 | let host0 = Host(address: "8.8.8.8") 49 | let host1 = Host(address: "8.8.8.8") 50 | XCTAssertTrue(host0.isEqual(to: host1)) 51 | 52 | let host2 = Host(address: "8.8.8.9") 53 | XCTAssertFalse(host0.isEqual(to: host2)) 54 | 55 | let swift0 = Host(name: "localhost") 56 | let swift1 = Host(name: "localhost") 57 | XCTAssertTrue(swift0.isEqual(to: swift1)) 58 | 59 | let google = Host(name: "google.com") 60 | XCTAssertFalse(swift0.isEqual(to: google)) 61 | #endif // !SKIP 62 | } 63 | 64 | // SR-14197 65 | func test_localNamesNonEmpty() { 66 | #if SKIP 67 | throw XCTSkip("TODO") 68 | #else 69 | let local = Host.current() 70 | XCTAssertTrue(local.names.count > 0) 71 | 72 | let swift = Host(name: "localhost") 73 | XCTAssertTrue(swift.names.count > 0) 74 | #endif // !SKIP 75 | } 76 | #endif 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Network/TestSocketPort.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | #if !SKIP // disabled for to reduce test count and avoid io.grpc.StatusRuntimeException: RESOURCE_EXHAUSTED: gRPC message exceeds maximum size 7 | 8 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 9 | 10 | 11 | // This source file is part of the Swift.org open source project 12 | // 13 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 14 | // Licensed under Apache License v2.0 with Runtime Library Exception 15 | // 16 | // See http://swift.org/LICENSE.txt for license information 17 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 18 | // 19 | #if os(Windows) 20 | import WinSDK 21 | #endif 22 | 23 | //class TestPortDelegateWithBlock: NSObject, PortDelegate { 24 | // let block: (PortMessage) -> Void 25 | // 26 | // init(block: @escaping (PortMessage) -> Void) { 27 | // self.block = block 28 | // } 29 | // 30 | // func handle(_ message: PortMessage) { 31 | // block(message) 32 | // } 33 | //} 34 | 35 | class TestSocketPort : XCTestCase { 36 | 37 | func tcpOrUdpPort(of socketPort: SocketPort) -> Int? { 38 | #if SKIP 39 | throw XCTSkip("TODO") 40 | #else 41 | let data = socketPort.address 42 | 43 | #if canImport(Darwin) || os(FreeBSD) || os(OpenBSD) 44 | let familyOffset = 1 45 | #else 46 | let familyOffset = 0 47 | #endif 48 | 49 | if data[data.startIndex + familyOffset] == AF_INET { 50 | return data.withUnsafeBytes { (buffer) in 51 | var sin = sockaddr_in() 52 | withUnsafeMutableBytes(of: &sin) { 53 | $0.copyMemory(from: buffer) 54 | } 55 | 56 | return Int(sin.sin_port.bigEndian) 57 | } 58 | } else if data[data.startIndex + familyOffset] == AF_INET6 { 59 | return data.withUnsafeBytes { (buffer) in 60 | var sin = sockaddr_in6() 61 | withUnsafeMutableBytes(of: &sin) { 62 | $0.copyMemory(from: buffer) 63 | } 64 | 65 | return Int(sin.sin6_port.bigEndian) 66 | } 67 | } else { 68 | return nil 69 | } 70 | #endif // !SKIP 71 | } 72 | 73 | func testRemoteSocketPortsAreUniqued() { 74 | #if SKIP 75 | throw XCTSkip("TODO") 76 | #else 77 | let a = SocketPort(remoteWithTCPPort: 10000, host: "localhost") 78 | let b = SocketPort(remoteWithTCPPort: 10000, host: "localhost") 79 | XCTAssertEqual(a, b) 80 | #endif // !SKIP 81 | } 82 | 83 | func testInitPicksATCPPort() throws { 84 | #if SKIP 85 | throw XCTSkip("TODO") 86 | #else 87 | let local = try XCTUnwrap(SocketPort(tcpPort: 0)) 88 | defer { local.invalidate() } 89 | 90 | let port = try XCTUnwrap(tcpOrUdpPort(of: local)) 91 | XCTAssertNotEqual(port, 0) 92 | XCTAssert(port >= 1024) 93 | #endif // !SKIP 94 | } 95 | 96 | func testSendingOneMessageRemoteToLocal() throws { 97 | #if SKIP 98 | throw XCTSkip("TODO") 99 | #else 100 | // let local = try XCTUnwrap(SocketPort(tcpPort: 0)) 101 | // defer { local.invalidate() } 102 | // 103 | // let tcpPort = try UInt16(XCTUnwrap(tcpOrUdpPort(of: local))) 104 | // 105 | // let remote = try XCTUnwrap(SocketPort(remoteWithTCPPort: tcpPort, host: "localhost")) 106 | // defer { remote.invalidate() } 107 | // 108 | // let data = Data("I cannot weave".utf8) 109 | // 110 | // let received = expectation(description: "Message received") 111 | // let delegate = TestPortDelegateWithBlock { message in 112 | // XCTAssertEqual(message.components as? [AnyHashable], [data as NSData]) 113 | // received.fulfill() 114 | // } 115 | // 116 | // withExtendedLifetime(delegate) { 117 | // local.setDelegate(delegate) 118 | // local.schedule(in: .main, forMode: .default) 119 | // remote.schedule(in: .main, forMode: .default) 120 | // 121 | // defer { 122 | // local.setDelegate(nil) 123 | // local.remove(from: .main, forMode: .default) 124 | // remote.remove(from: .main, forMode: .default) 125 | // } 126 | // 127 | // let sent = remote.send(before: Date(timeIntervalSinceNow: 5), components: NSMutableArray(array: [data]), from: nil, reserved: 0) 128 | // XCTAssertTrue(sent) 129 | // 130 | // waitForExpectations(timeout: 5.5) 131 | // } 132 | #endif // !SKIP 133 | } 134 | 135 | } 136 | 137 | 138 | #endif 139 | 140 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Network/TestURLCredential.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestURLCredential : XCTestCase { 19 | 20 | 21 | func test_construction() { 22 | #if SKIP 23 | throw XCTSkip("TODO") 24 | #else 25 | let credential = URLCredential(user: "swiftUser", password: "swiftPassword", persistence: .forSession) 26 | XCTAssertEqual(credential.user, "swiftUser") 27 | XCTAssertEqual(credential.password, "swiftPassword") 28 | XCTAssertEqual(credential.persistence, URLCredential.Persistence.forSession) 29 | XCTAssertEqual(credential.hasPassword, true) 30 | #endif // !SKIP 31 | } 32 | 33 | func test_copy() { 34 | #if SKIP 35 | throw XCTSkip("TODO") 36 | #else 37 | let credential = URLCredential(user: "swiftUser", password: "swiftPassword", persistence: .forSession) 38 | let copy = credential.copy() as! URLCredential 39 | XCTAssertTrue(copy.isEqual(credential)) 40 | #endif // !SKIP 41 | } 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Network/TestURLSessionFTP.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | //import Foundation 4 | //import XCTest 5 | // 6 | //// These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | // 8 | //#if !SKIP 9 | // 10 | //// This source file is part of the Swift.org open source project 11 | //// 12 | //// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors 13 | //// Licensed under Apache License v2.0 with Runtime Library Exception 14 | //// 15 | //// See http://swift.org/LICENSE.txt for license information 16 | //// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 17 | //// 18 | // 19 | // } 20 | // 21 | // func test_ftpDataTaskDelegate() { 22 | // let urlString = "ftp://127.0.0.1:\(TestURLSessionFTP.serverPort)/test.txt" 23 | // let url = URL(string: urlString)! 24 | // let dataTask = FTPDataTask(with: expectation(description: "data task")) 25 | // dataTask.run(with: url) 26 | // waitForExpectations(timeout: 60) 27 | // if !dataTask.error { 28 | // XCTAssertNotNil(dataTask.fileData) 29 | // } 30 | // } 31 | //} 32 | // 33 | //class FTPDataTask : NSObject { 34 | // let dataTaskExpectation: XCTestExpectation! 35 | // var fileData: NSMutableData = NSMutableData() 36 | // var session: URLSession! = nil 37 | // var task: URLSessionDataTask! = nil 38 | // var cancelExpectation: XCTestExpectation? 39 | // var responseReceivedExpectation: XCTestExpectation? 40 | // var hasTransferCompleted = false 41 | // 42 | // private var errorLock = NSLock() 43 | // private var _error = false 44 | // public var error: Bool { 45 | // get { errorLock.synchronized { _error } } 46 | // set { errorLock.synchronized { _error = newValue } } 47 | // } 48 | // 49 | // init(with expectation: XCTestExpectation) { 50 | // dataTaskExpectation = expectation 51 | // } 52 | // 53 | // func run(with request: URLRequest) { 54 | // let config = URLSessionConfiguration.default 55 | // config.timeoutIntervalForRequest = 8 56 | // session = URLSession(configuration: config, delegate: self, delegateQueue: nil) 57 | // task = session.dataTask(with: request) 58 | // task.resume() 59 | // } 60 | // 61 | // func run(with url: URL) { 62 | // let config = URLSessionConfiguration.default 63 | // config.timeoutIntervalForRequest = 8 64 | // session = URLSession(configuration: config, delegate: self, delegateQueue: nil) 65 | // task = session.dataTask(with: url) 66 | // task.resume() 67 | // } 68 | // 69 | // func cancel() { 70 | // task.cancel() 71 | // } 72 | //} 73 | // 74 | //extension FTPDataTask : URLSessionDataDelegate { 75 | // public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 76 | // fileData.append(data) 77 | // responseReceivedExpectation?.fulfill() 78 | // } 79 | // 80 | // public func urlSession(_ session: URLSession, 81 | // dataTask: URLSessionDataTask, 82 | // didReceive response: URLResponse, 83 | // completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { 84 | // guard responseReceivedExpectation != nil else { return } 85 | // responseReceivedExpectation!.fulfill() 86 | // } 87 | //} 88 | // 89 | //extension FTPDataTask : URLSessionTaskDelegate { 90 | // public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 91 | // dataTaskExpectation.fulfill() 92 | // guard (error as? URLError) != nil else { return } 93 | // if let cancellation = cancelExpectation { 94 | // cancellation.fulfill() 95 | // } 96 | // self.error = true 97 | // } 98 | //} 99 | //#endif 100 | // 101 | //#endif 102 | // 103 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/Localizable.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "%@" : { 5 | 6 | }, 7 | "%@ %@" : { 8 | "localizations" : { 9 | "en" : { 10 | "stringUnit" : { 11 | "state" : "new", 12 | "value" : "%1$@ %2$@" 13 | } 14 | } 15 | } 16 | }, 17 | "❄️" : { 18 | 19 | }, 20 | "🌞" : { 21 | 22 | }, 23 | "Done" : { 24 | "localizations" : { 25 | "ar" : { 26 | "stringUnit" : { 27 | "state" : "translated", 28 | "value" : "تم" 29 | } 30 | }, 31 | "fr" : { 32 | "stringUnit" : { 33 | "state" : "translated", 34 | "value" : "Terminé" 35 | } 36 | }, 37 | "he" : { 38 | "stringUnit" : { 39 | "state" : "translated", 40 | "value" : "סיום" 41 | } 42 | }, 43 | "ja" : { 44 | "stringUnit" : { 45 | "state" : "translated", 46 | "value" : "完了" 47 | } 48 | }, 49 | "pt-BR" : { 50 | "stringUnit" : { 51 | "state" : "translated", 52 | "value" : "OK" 53 | } 54 | }, 55 | "ru" : { 56 | "stringUnit" : { 57 | "state" : "translated", 58 | "value" : "Готово" 59 | } 60 | }, 61 | "sv" : { 62 | "stringUnit" : { 63 | "state" : "translated", 64 | "value" : "Klar" 65 | } 66 | }, 67 | "uk" : { 68 | "stringUnit" : { 69 | "state" : "translated", 70 | "value" : "Готово" 71 | } 72 | }, 73 | "zh-Hans" : { 74 | "stringUnit" : { 75 | "state" : "translated", 76 | "value" : "完成" 77 | } 78 | } 79 | } 80 | }, 81 | "Done %@" : { 82 | "extractionState" : "manual", 83 | "localizations" : { 84 | "ar" : { 85 | "stringUnit" : { 86 | "state" : "translated", 87 | "value" : "تم%@" 88 | } 89 | }, 90 | "fr" : { 91 | "stringUnit" : { 92 | "state" : "translated", 93 | "value" : "Terminé %@" 94 | } 95 | } 96 | } 97 | }, 98 | "lower-case" : { 99 | "extractionState" : "manual", 100 | "localizations" : { 101 | "en" : { 102 | "stringUnit" : { 103 | "state" : "translated", 104 | "value" : "UPPER-CASE" 105 | } 106 | } 107 | } 108 | }, 109 | "lower-case %@ string" : { 110 | "extractionState" : "manual", 111 | "localizations" : { 112 | "en" : { 113 | "stringUnit" : { 114 | "state" : "translated", 115 | "value" : "UPPER-CASE %@ STRING" 116 | } 117 | } 118 | } 119 | }, 120 | "Multi-Line String!\nWith some \"quoted\" text." : { 121 | "extractionState" : "manual", 122 | "localizations" : { 123 | "fr" : { 124 | "stringUnit" : { 125 | "state" : "translated", 126 | "value" : "Chaîne multi-ligne !\nAvec un peu de texte « entre guillemets »." 127 | } 128 | } 129 | } 130 | }, 131 | "Settings" : { 132 | 133 | } 134 | }, 135 | "version" : "1.0" 136 | } -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSString-ISO-8859-1-data.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skiptools/skip-foundation/7687a44cf750594a0d7dce37c38fdebdcd9bcbb2/Tests/SkipFoundationTests/Resources/NSString-ISO-8859-1-data.txt -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSString-UTF16-BE-data.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skiptools/skip-foundation/7687a44cf750594a0d7dce37c38fdebdcd9bcbb2/Tests/SkipFoundationTests/Resources/NSString-UTF16-BE-data.txt -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSString-UTF16-LE-data.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skiptools/skip-foundation/7687a44cf750594a0d7dce37c38fdebdcd9bcbb2/Tests/SkipFoundationTests/Resources/NSString-UTF16-LE-data.txt -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSString-UTF32-BE-data.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skiptools/skip-foundation/7687a44cf750594a0d7dce37c38fdebdcd9bcbb2/Tests/SkipFoundationTests/Resources/NSString-UTF32-BE-data.txt -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSString-UTF32-LE-data.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skiptools/skip-foundation/7687a44cf750594a0d7dce37c38fdebdcd9bcbb2/Tests/SkipFoundationTests/Resources/NSString-UTF32-LE-data.txt -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSStringTestData.txt: -------------------------------------------------------------------------------- 1 | swift-corelibs-foundation -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSXMLDTDTestData.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | ]> 15 | 16 | 17 | Hello 18 | world 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/NSXMLDocumentTestData.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello world 4 | 5 | I'm here 6 | 7 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/PropertyList-1.0.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/SampleInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | TestFoundation 9 | CFBundleIdentifier 10 | org.swift.TestFoundation 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | TestFoundation 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/Test.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Foo 6 | Bar 7 | emptyValue 8 | 9 | Baz 10 | 11 | hello 12 | property 13 | lists 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/TestFileWithZeros.txt: -------------------------------------------------------------------------------- 1 | SometextwithNULbytesinsteadofspaces. 2 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Resources/textasset.txt: -------------------------------------------------------------------------------- 1 | Some text 2 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Shims.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | #if !SKIP 6 | @testable import SkipFoundation 7 | #endif 8 | 9 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Skip/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Skip/skip.yml: -------------------------------------------------------------------------------- 1 | # skip.tools per-configuration file 2 | 3 | # the blocks to add to the build.gradle.kts 4 | #build: 5 | # contents: 6 | # - block: 'dependencies' 7 | # contents: 8 | # - '// no new dependencies in SkipFoundationKtTests' 9 | # skip.tools per-configuration file 10 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/SkipFoundationTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import OSLog 5 | import XCTest 6 | #if !SKIP 7 | @testable import func SkipFoundation.SkipFoundationInternalModuleName 8 | @testable import func SkipFoundation.SkipFoundationPublicModuleName 9 | #endif 10 | 11 | final class SkipFoundationTests: XCTestCase { 12 | let logger: Logger = Logger(subsystem: "test", category: "SkipFoundationTests") 13 | 14 | func testSkipFoundation() throws { 15 | XCTAssertEqual(3, 1 + 2) 16 | XCTAssertEqual("SkipFoundation", SkipFoundationInternalModuleName()) 17 | XCTAssertEqual("SkipFoundation", SkipFoundationPublicModuleName()) 18 | } 19 | 20 | func testSystemProperties() throws { 21 | let env = ProcessInfo.processInfo.environment 22 | 23 | // returns the value of the given key iff we are on Robolectric 24 | func check(_ key: String, value: String) { 25 | if isRobolectric { 26 | XCTAssertEqual(value, env[key], "Unexpected value for Robolectric system property \(key): \(env[key] ?? "")") 27 | } else if isAndroidEmulator { 28 | logger.log("ANDROID ENV: \(key)=\(env[key] ?? "")") 29 | XCTAssertNotNil(env[key], "Android system property should not been nil for key \(key)") 30 | } else { 31 | XCTAssertNil(env[key], "Swift system property should have been nil for key \(key) value=\(env[key] ?? "")") 32 | } 33 | } 34 | 35 | check("android.os.Build.BOARD", value: "unknown") 36 | check("android.os.Build.BOOTLOADER", value: "unknown") 37 | check("android.os.Build.BRAND", value: "unknown") 38 | check("android.os.Build.DEVICE", value: "robolectric") 39 | check("android.os.Build.DISPLAY", value: "sdk_phone_x86-userdebug 10 QPP6.190730.006 5803371 test-keys") 40 | check("android.os.Build.FINGERPRINT", value: "robolectric") 41 | check("android.os.Build.HARDWARE", value: "robolectric") 42 | check("android.os.Build.HOST", value: "wphn1.hot.corp.google.com") 43 | check("android.os.Build.ID", value: "QPP6.190730.006") 44 | check("android.os.Build.MANUFACTURER", value: "unknown") 45 | check("android.os.Build.MODEL", value: "robolectric") 46 | check("android.os.Build.PRODUCT", value: "robolectric") 47 | check("android.os.Build.TAGS", value: "test-keys") 48 | check("android.os.Build.TYPE", value: "userdebug") 49 | check("android.os.Build.USER", value: "android-build") 50 | 51 | //XCTAssertEqual("", env["android.os.Build.SUPPORTED_32_BIT_ABIS"]) 52 | //XCTAssertEqual("", env["android.os.Build.SUPPORTED_64_BIT_ABIS"]) 53 | //XCTAssertEqual("", env["android.os.Build.SUPPORTED_ABIS"]) 54 | 55 | check("android.os.Build.VERSION.BASE_OS", value: "") 56 | check("android.os.Build.VERSION.CODENAME", value: "REL") 57 | check("android.os.Build.VERSION.INCREMENTAL", value: "5803371") 58 | check("android.os.Build.VERSION.PREVIEW_SDK_INT", value: "0") 59 | check("android.os.Build.VERSION.RELEASE", value: "10") 60 | check("android.os.Build.VERSION.SDK_INT", value: "29") 61 | check("android.os.Build.VERSION.SECURITY_PATCH", value: "2019-08-01") 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/System/TestNotification.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestNotification : XCTestCase { 19 | 20 | 21 | func test_customReflection() { 22 | #if SKIP 23 | throw XCTSkip("TODO") 24 | #else 25 | let someName = "somenotifname" 26 | let targetObject = NSObject() 27 | let userInfo = ["hello": "world", "indexThis": 350] as [AnyHashable: Any] 28 | let notif = Notification(name: Notification.Name(rawValue: someName), object: targetObject, userInfo: userInfo) 29 | let mirror = notif.customMirror 30 | 31 | XCTAssertEqual(mirror.displayStyle, .class) 32 | XCTAssertNil(mirror.superclassMirror) 33 | 34 | var children = Array(mirror.children).makeIterator() 35 | let firstChild = children.next() 36 | let secondChild = children.next() 37 | let thirdChild = children.next() 38 | XCTAssertEqual(firstChild?.label, "name") 39 | XCTAssertEqual(firstChild?.value as? String, someName) 40 | 41 | XCTAssertEqual(secondChild?.label, "object") 42 | XCTAssertEqual(secondChild?.value as? NSObject, targetObject) 43 | 44 | XCTAssertEqual(thirdChild?.label, "userInfo") 45 | XCTAssertEqual((thirdChild?.value as? [AnyHashable: Any])?["hello"] as? String, "world") 46 | XCTAssertEqual((thirdChild?.value as? [AnyHashable: Any])?["indexThis"] as? Int, 350) 47 | 48 | #endif // !SKIP 49 | } 50 | 51 | func test_NotificationNameInit() { 52 | #if SKIP 53 | throw XCTSkip("TODO") 54 | #else 55 | let name = "TestNotificationNameInit" 56 | XCTAssertEqual(Notification.Name(name), Notification.Name(rawValue: name)) 57 | #endif // !SKIP 58 | } 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/System/TestPipe.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016. 2018 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See https://swift.org/LICENSE.txt for license information 15 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | #if !SKIP // disabled for to reduce test count and avoid io.grpc.StatusRuntimeException: RESOURCE_EXHAUSTED: gRPC message exceeds maximum size 19 | 20 | 21 | @available(macOS 10.15, iOS 13.4, watchOS 6.0, tvOS 13.0, *) 22 | class TestPipe: XCTestCase { 23 | 24 | 25 | #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT 26 | func test_MaxPipes() { 27 | #if SKIP 28 | throw XCTSkip("TODO") 29 | #else 30 | // Try and create enough pipes to exhaust the process's limits. 1024 is a reasonable 31 | // hard limit for the test. This is reached when testing on Linux (at around 488 pipes) 32 | // but not on macOS. 33 | 34 | var pipes: [Pipe] = [] 35 | let maxPipes = 1024 36 | pipes.reserveCapacity(maxPipes) 37 | for _ in 1...maxPipes { 38 | let pipe = Pipe() 39 | if !pipe.fileHandleForReading._isPlatformHandleValid { 40 | #if os(Windows) 41 | XCTAssertEqual(pipe.fileHandleForReading._handle, pipe.fileHandleForWriting._handle) 42 | #else 43 | XCTAssertEqual(pipe.fileHandleForReading.fileDescriptor, pipe.fileHandleForWriting.fileDescriptor) 44 | #endif 45 | break 46 | } 47 | pipes.append(pipe) 48 | } 49 | pipes = [] 50 | #endif // !SKIP 51 | } 52 | #endif 53 | 54 | func test_Pipe() throws { 55 | #if SKIP 56 | throw XCTSkip("TODO") 57 | #else 58 | let aPipe = Pipe() 59 | let text = "test-pipe" 60 | 61 | // First write some data into the pipe 62 | let stringAsData = try XCTUnwrap(text.data(using: .utf8)) 63 | try aPipe.fileHandleForWriting.write(contentsOf: stringAsData) 64 | 65 | // SR-10240 - Check empty Data() can be written without crashing 66 | aPipe.fileHandleForWriting.write(Data()) 67 | 68 | // Then read it out again 69 | let data = try XCTUnwrap(aPipe.fileHandleForReading.read(upToCount: stringAsData.count)) 70 | 71 | // Confirm that we did read data 72 | XCTAssertEqual(data.count, stringAsData.count, "Expected to read \(String(describing:stringAsData.count)) from pipe but read \(data.count) instead") 73 | 74 | // Confirm the data can be converted to a String 75 | let convertedData = String(data: data, encoding: .utf8) 76 | XCTAssertNotNil(convertedData) 77 | 78 | // Confirm the data written in is the same as the data we read 79 | XCTAssertEqual(text, convertedData) 80 | #endif // !SKIP 81 | } 82 | } 83 | 84 | 85 | #endif 86 | 87 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/System/TestThread.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | #if !(os(macOS) || os(iOS) || os(watchOS) || os(tvOS)) 19 | import CoreFoundation 20 | #endif 21 | 22 | 23 | class TestThread : XCTestCase { 24 | 25 | func test_currentThread() { 26 | let thread1 = Thread.current 27 | let thread2 = Thread.current 28 | XCTAssertEqual(thread1, thread2) 29 | XCTAssertEqual(thread1, Thread.main) 30 | } 31 | 32 | func test_threadStart() { 33 | #if SKIP 34 | throw XCTSkip("TODO") 35 | #else 36 | let condition = NSCondition() 37 | condition.lock() 38 | 39 | let thread = Thread() { 40 | condition.lock() 41 | condition.broadcast() 42 | condition.unlock() 43 | } 44 | XCTAssertEqual(thread.qualityOfService, .default) 45 | thread.start() 46 | 47 | let ok = condition.wait(until: Date(timeIntervalSinceNow: 2)) 48 | condition.unlock() 49 | XCTAssertTrue(ok, "NSCondition wait timed out") 50 | #endif // !SKIP 51 | } 52 | 53 | func test_threadName() { 54 | #if SKIP 55 | throw XCTSkip("TODO") 56 | #else 57 | 58 | func testInternalThreadName(_ name: String?) { 59 | #if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT 60 | XCTAssertEqual(Thread.current._name, name) 61 | #endif 62 | } 63 | 64 | #if os(Linux) || os(Android) // Linux sets the initial thread name to the process name. 65 | XCTAssertEqual(Thread.current.name, "TestFoundation") 66 | testInternalThreadName("TestFoundation") 67 | #elseif os(OpenBSD) // OpenBSD sets the initial thread name to this. 68 | XCTAssertEqual(Thread.current.name, "Original thread") 69 | testInternalThreadName("Original thread") 70 | #else 71 | // No name is set initially 72 | XCTAssertEqual(Thread.current.name, "") 73 | testInternalThreadName("") 74 | #endif 75 | Thread.current.name = "mainThread" 76 | XCTAssertEqual(Thread.main.name, "mainThread") 77 | testInternalThreadName("mainThread") 78 | 79 | Thread.current.name = "12345678901234567890" 80 | #if os(Linux) || os(Android) 81 | // pthread_setname_np() only allows 15 characters on Linux, so setting it fails 82 | // and the previous name will still be there. 83 | XCTAssertEqual(Thread.current.name, "mainThread") 84 | #else 85 | XCTAssertEqual(Thread.current.name, "12345678901234567890") 86 | #endif 87 | testInternalThreadName(Thread.current.name) 88 | #endif // !SKIP 89 | } 90 | 91 | func test_mainThread() { 92 | XCTAssertTrue(Thread.isMainThread) 93 | let t = Thread.main 94 | XCTAssertTrue(t.isMainThread) 95 | let c = Thread.current 96 | XCTAssertTrue(c.isMainThread) 97 | XCTAssertTrue(c.isExecuting) 98 | XCTAssertTrue(c.isEqual(t)) 99 | 100 | #if !SKIP 101 | let condition = NSCondition() 102 | condition.lock() 103 | 104 | let thread = Thread() { 105 | condition.lock() 106 | XCTAssertFalse(Thread.isMainThread) 107 | XCTAssertFalse(Thread.main == Thread.current) 108 | condition.broadcast() 109 | condition.unlock() 110 | } 111 | thread.start() 112 | 113 | let ok = condition.wait(until: Date(timeIntervalSinceNow: 10)) 114 | condition.unlock() 115 | XCTAssertTrue(ok, "NSCondition wait timed out") 116 | #endif 117 | } 118 | 119 | func test_callStackSymbols() { 120 | let symbols = Thread.callStackSymbols 121 | XCTAssertTrue(symbols.count > 0) 122 | XCTAssertTrue(symbols.count <= 128) 123 | } 124 | 125 | func test_callStackReturnAddresses() { 126 | #if SKIP 127 | throw XCTSkip("TODO") 128 | #else 129 | let addresses = Thread.callStackReturnAddresses 130 | XCTAssertTrue(addresses.count > 0) 131 | XCTAssertTrue(addresses.count <= 128) 132 | #endif // !SKIP 133 | } 134 | 135 | func test_sleepForTimeInterval() { 136 | let measureOversleep = { (timeInterval: TimeInterval) -> TimeInterval in 137 | let start = Date() 138 | Thread.sleep(forTimeInterval: timeInterval) 139 | 140 | // Measures time Thread.sleep spends over specified timeInterval value 141 | return -(start.timeIntervalSinceNow + timeInterval) 142 | } 143 | 144 | // Allow a little early wake-ups. Sleep timer on Windows 145 | // is more precise than timer used in Date implementation. 146 | let allowedOversleepRange = -0.00001..<0.8 147 | 148 | let oversleep1 = measureOversleep(TimeInterval(0.9)) 149 | XCTAssertTrue(allowedOversleepRange.contains(oversleep1), "Oversleep \(oversleep1) is not in expected range \(allowedOversleepRange)") 150 | 151 | let oversleep2 = measureOversleep(TimeInterval(1.2)) 152 | XCTAssertTrue(allowedOversleepRange.contains(oversleep2), "Oversleep \(oversleep2) is not in expected range \(allowedOversleepRange)") 153 | 154 | let oversleep3 = measureOversleep(TimeInterval(1.0)) 155 | XCTAssertTrue(allowedOversleepRange.contains(oversleep3), "Oversleep \(oversleep3) is not in expected range \(allowedOversleepRange)") 156 | } 157 | 158 | func test_sleepUntilDate() { 159 | let measureOversleep = { (date: Date) -> TimeInterval in 160 | Thread.sleep(until: date) 161 | return -date.timeIntervalSinceNow 162 | } 163 | 164 | let allowedOversleepRange = -0.1..<0.8 165 | 166 | let oversleep1 = measureOversleep(Date(timeIntervalSinceNow: 0.8)) 167 | XCTAssertTrue(allowedOversleepRange.contains(oversleep1), "Oversleep \(oversleep1) is not in expected range \(allowedOversleepRange)") 168 | 169 | let oversleep2 = measureOversleep(Date(timeIntervalSinceNow: 1.1)) 170 | XCTAssertTrue(allowedOversleepRange.contains(oversleep2), "Oversleep \(oversleep2) is not in expected range \(allowedOversleepRange)") 171 | 172 | let oversleep3 = measureOversleep(Date(timeIntervalSinceNow: 1.0)) 173 | XCTAssertTrue(allowedOversleepRange.contains(oversleep3), "Oversleep \(oversleep3) is not in expected range \(allowedOversleepRange)") 174 | } 175 | 176 | } 177 | 178 | 179 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/System/TestTimer.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestTimer : XCTestCase { 19 | 20 | func test_timerInit() { 21 | #if SKIP 22 | throw XCTSkip("TODO") 23 | #else 24 | let fireDate = Date() 25 | let timeInterval: TimeInterval = 0.3 26 | 27 | let timer = Timer(fire: fireDate, interval: timeInterval, repeats: false) { _ in } 28 | XCTAssertEqual(timer.fireDate, fireDate) 29 | XCTAssertEqual(timer.timeInterval, 0, "Time interval should be 0 for a non repeating Timer") 30 | XCTAssert(timer.isValid) 31 | 32 | let repeatingTimer = Timer(fire: fireDate, interval: timeInterval, repeats: true) { _ in } 33 | XCTAssertEqual(repeatingTimer.fireDate, fireDate) 34 | XCTAssertEqual(repeatingTimer.timeInterval, timeInterval) 35 | XCTAssert(timer.isValid) 36 | #endif // !SKIP 37 | } 38 | 39 | func test_timerTickOnce() { 40 | #if SKIP 41 | throw XCTSkip("TODO") 42 | #else 43 | var flag = false 44 | 45 | let dummyTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: false) { timer in 46 | XCTAssertFalse(flag) 47 | 48 | flag = true 49 | timer.invalidate() 50 | } 51 | 52 | let runLoop = RunLoop.current 53 | runLoop.add(dummyTimer, forMode: .default) 54 | runLoop.run(until: Date(timeIntervalSinceNow: 0.05)) 55 | 56 | XCTAssertTrue(flag) 57 | #endif // !SKIP 58 | } 59 | 60 | func test_timerRepeats() { 61 | #if SKIP 62 | throw XCTSkip("TODO") 63 | #else 64 | var flag = 0 65 | let interval = TimeInterval(0.1) 66 | let numberOfRepeats = 3 67 | var previousInterval = Date().timeIntervalSince1970 68 | 69 | let dummyTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in 70 | XCTAssertEqual(timer.timeInterval, interval) 71 | 72 | let currentInterval = Date().timeIntervalSince1970 73 | XCTAssertEqual(currentInterval, previousInterval + interval, accuracy: 0.2) 74 | previousInterval = currentInterval 75 | 76 | flag += 1 77 | if (flag == numberOfRepeats) { 78 | timer.invalidate() 79 | } 80 | } 81 | 82 | let runLoop = RunLoop.current 83 | runLoop.add(dummyTimer, forMode: .default) 84 | runLoop.run(until: Date(timeIntervalSinceNow: interval * Double(numberOfRepeats + 1))) 85 | 86 | //XCTAssertEqual(flag, numberOfRepeats) // fails intermittently on slow CI machines: XCTAssertEqual failed: ("2") is not equal to ("3") 87 | #endif 88 | } 89 | 90 | func test_timerInvalidate() { 91 | #if SKIP 92 | throw XCTSkip("TODO") 93 | #else 94 | var flag = false 95 | 96 | let dummyTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in 97 | XCTAssertTrue(timer.isValid) 98 | XCTAssertFalse(flag) // timer should tick only once 99 | 100 | flag = true 101 | 102 | timer.invalidate() 103 | XCTAssertFalse(timer.isValid) 104 | } 105 | 106 | let runLoop = RunLoop.current 107 | runLoop.add(dummyTimer, forMode: .default) 108 | runLoop.run(until: Date(timeIntervalSinceNow: 0.05)) 109 | 110 | XCTAssertTrue(flag) 111 | #endif // !SKIP 112 | } 113 | 114 | } 115 | 116 | 117 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Units/TestDimension.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See https://swift.org/LICENSE.txt for license information 15 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestDimension: XCTestCase { 19 | 20 | func test_encodeDecode() { 21 | #if SKIP 22 | throw XCTSkip("TODO") 23 | #else 24 | let original = Dimension(symbol: "symbol", converter: UnitConverterLinear(coefficient: 1.0)) 25 | #endif // !SKIP 26 | } 27 | 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Units/TestMeasurement.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | #if false && !DARWIN_COMPATIBILITY_TESTS // https://bugs.swift.org/browse/SR-10904 19 | class CustomUnit: Unit { 20 | override required init(symbol: String) { 21 | super.init(symbol: symbol) 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) { 25 | super.init(coder: aDecoder) 26 | } 27 | 28 | public static let bugs = CustomUnit(symbol: "bug") 29 | public static let features = CustomUnit(symbol: "feature") 30 | } 31 | #endif 32 | 33 | class TestMeasurement: XCTestCase { 34 | func testHashing() { 35 | #if SKIP 36 | throw XCTSkip("TODO") 37 | #else 38 | let lengths: [[Measurement]] = [ 39 | [ 40 | Measurement(value: 5, unit: UnitLength.kilometers), 41 | Measurement(value: 5000, unit: UnitLength.meters), 42 | Measurement(value: 5000, unit: UnitLength.meters), 43 | ], 44 | [ 45 | Measurement(value: 1, unit: UnitLength.kilometers), 46 | Measurement(value: 1000, unit: UnitLength.meters), 47 | ], 48 | [ 49 | Measurement(value: 1, unit: UnitLength.meters), 50 | Measurement(value: 1000, unit: UnitLength.millimeters), 51 | ], 52 | ] 53 | checkHashableGroups(lengths) 54 | 55 | let durations: [[Measurement]] = [ 56 | [ 57 | Measurement(value: 3600, unit: UnitDuration.seconds), 58 | Measurement(value: 60, unit: UnitDuration.minutes), 59 | Measurement(value: 1, unit: UnitDuration.hours), 60 | ], 61 | [ 62 | Measurement(value: 1800, unit: UnitDuration.seconds), 63 | Measurement(value: 30, unit: UnitDuration.minutes), 64 | Measurement(value: 0.5, unit: UnitDuration.hours), 65 | ] 66 | ] 67 | checkHashableGroups(durations) 68 | 69 | #if false && !DARWIN_COMPATIBILITY_TESTS 70 | let custom: [Measurement] = [ 71 | Measurement(value: 1, unit: CustomUnit.bugs), 72 | Measurement(value: 2, unit: CustomUnit.bugs), 73 | Measurement(value: 3, unit: CustomUnit.bugs), 74 | Measurement(value: 4, unit: CustomUnit.bugs), 75 | Measurement(value: 1, unit: CustomUnit.features), 76 | Measurement(value: 2, unit: CustomUnit.features), 77 | Measurement(value: 3, unit: CustomUnit.features), 78 | Measurement(value: 4, unit: CustomUnit.features), 79 | ] 80 | checkHashable(custom, equalityOracle: { $0 == $1 }) 81 | #endif 82 | #endif // !SKIP 83 | } 84 | 85 | #if !SKIP 86 | let fixtures = [ 87 | Fixtures.zeroMeasurement, 88 | Fixtures.lengthMeasurement, 89 | Fixtures.frequencyMeasurement, 90 | Fixtures.angleMeasurement, 91 | ] 92 | #endif 93 | 94 | func testCodingRoundtrip() throws { 95 | #if SKIP 96 | throw XCTSkip("TODO") 97 | #else 98 | for fixture in fixtures { 99 | try fixture.assertValueRoundtripsInCoder() 100 | } 101 | #endif // !SKIP 102 | } 103 | 104 | func testLoadedValuesMatch() throws { 105 | #if SKIP 106 | throw XCTSkip("TODO") 107 | #else 108 | for fixture in fixtures { 109 | // try fixture.assertLoadedValuesMatch() 110 | } 111 | #endif // !SKIP 112 | } 113 | } 114 | 115 | 116 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Units/TestUnit.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See https://swift.org/LICENSE.txt for license information 15 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestUnit: XCTestCase { 19 | 20 | @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) 21 | func test_equality() { 22 | #if SKIP 23 | throw XCTSkip("TODO") 24 | #else 25 | let s1 = "a" 26 | let s2 = "ab" 27 | 28 | let u1 = Unit(symbol: s1) 29 | let u2 = Unit(symbol: s1) 30 | let u3 = Unit(symbol: s2) 31 | 32 | XCTAssertEqual(u1, u2) 33 | XCTAssertEqual(u2, u1) 34 | XCTAssertNotEqual(u1, u3) 35 | XCTAssertNotEqual(u3, u1) 36 | 37 | let uc1 = UnitConverterLinear(coefficient: 1, constant: 2) 38 | let uc2 = UnitConverterLinear(coefficient: 1, constant: 3) 39 | 40 | let d1 = Dimension(symbol: s1, converter: uc1) 41 | let d2 = Dimension(symbol: s1, converter: uc1) 42 | let d3 = Dimension(symbol: s2, converter: uc1) 43 | let d4 = Dimension(symbol: s1, converter: uc2) 44 | 45 | XCTAssertEqual(d1, d2) 46 | XCTAssertEqual(d2, d1) 47 | XCTAssertNotEqual(d1, d3) 48 | XCTAssertNotEqual(d3, d1) 49 | XCTAssertNotEqual(d1, d4) 50 | XCTAssertNotEqual(d4, d1) 51 | 52 | // XCTAssertEqual(u1, d1) 53 | XCTAssertNotEqual(d1, u1) 54 | 55 | func testEquality(ofDimensionSubclass: T.Type) { 56 | let u0 = Unit(symbol: s1) 57 | let d1 = Dimension(symbol: s1, converter: uc1) 58 | 59 | let u1 = T(symbol: s1, converter: uc1) 60 | let u2 = T(symbol: s1, converter: uc1) 61 | let u3 = T(symbol: s1, converter: uc2) 62 | let u4 = T(symbol: s2, converter: uc1) 63 | 64 | XCTAssertEqual(u1, u2) 65 | XCTAssertEqual(u2, u1) 66 | XCTAssertNotEqual(u1, u3) 67 | XCTAssertNotEqual(u3, u1) 68 | XCTAssertNotEqual(u1, u4) 69 | XCTAssertNotEqual(u4, u1) 70 | 71 | // XCTAssertEqual(u0, u1) 72 | XCTAssertNotEqual(u1, u0) 73 | 74 | // XCTAssertEqual(d1, u1) 75 | XCTAssertNotEqual(u1, d1) 76 | } 77 | 78 | testEquality(ofDimensionSubclass: UnitAcceleration.self) 79 | testEquality(ofDimensionSubclass: UnitAngle.self) 80 | testEquality(ofDimensionSubclass: UnitArea.self) 81 | testEquality(ofDimensionSubclass: UnitConcentrationMass.self) 82 | testEquality(ofDimensionSubclass: UnitDispersion.self) 83 | testEquality(ofDimensionSubclass: UnitDuration.self) 84 | testEquality(ofDimensionSubclass: UnitElectricCharge.self) 85 | testEquality(ofDimensionSubclass: UnitElectricCurrent.self) 86 | testEquality(ofDimensionSubclass: UnitElectricPotentialDifference.self) 87 | testEquality(ofDimensionSubclass: UnitElectricResistance.self) 88 | testEquality(ofDimensionSubclass: UnitEnergy.self) 89 | testEquality(ofDimensionSubclass: UnitFrequency.self) 90 | testEquality(ofDimensionSubclass: UnitFuelEfficiency.self) 91 | testEquality(ofDimensionSubclass: UnitIlluminance.self) 92 | testEquality(ofDimensionSubclass: UnitInformationStorage.self) 93 | testEquality(ofDimensionSubclass: UnitLength.self) 94 | testEquality(ofDimensionSubclass: UnitMass.self) 95 | testEquality(ofDimensionSubclass: UnitPower.self) 96 | testEquality(ofDimensionSubclass: UnitPressure.self) 97 | testEquality(ofDimensionSubclass: UnitSpeed.self) 98 | testEquality(ofDimensionSubclass: UnitTemperature.self) 99 | testEquality(ofDimensionSubclass: UnitVolume.self) 100 | #endif // !SKIP 101 | } 102 | 103 | } 104 | 105 | 106 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Units/TestUnitInformationStorage.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestUnitInformationStorage: XCTestCase { 19 | @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) 20 | func testUnitInformationStorage() { 21 | #if SKIP 22 | throw XCTSkip("TODO") 23 | #else 24 | let bits = Measurement(value: 8, unit: UnitInformationStorage.bits) 25 | XCTAssertEqual( 26 | bits.converted(to: .bytes).value, 27 | 1, 28 | "Conversion from bits to bytes" 29 | ) 30 | XCTAssertEqual( 31 | bits.converted(to: .nibbles).value, 32 | 2, 33 | "Conversion from bits to nibbles" 34 | ) 35 | XCTAssertEqual( 36 | bits.converted(to: .yottabits).value, 37 | 8.0e-24, 38 | accuracy: 1.0e-27, 39 | "Conversion from bits to yottabits" 40 | ) 41 | XCTAssertEqual( 42 | bits.converted(to: .gibibits).value, 43 | 7.450581e-09, 44 | accuracy: 1.0e-12, 45 | "Conversion from bits to gibibits" 46 | ) 47 | #endif // !SKIP 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/Units/TestUnitVolume.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | import XCTest 5 | 6 | // These tests are adapted from https://github.com/apple/swift-corelibs-foundation/blob/main/Tests/Foundation/Tests which have the following license: 7 | 8 | 9 | // This source file is part of the Swift.org open source project 10 | // 11 | // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors 12 | // Licensed under Apache License v2.0 with Runtime Library Exception 13 | // 14 | // See http://swift.org/LICENSE.txt for license information 15 | // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 16 | // 17 | 18 | class TestUnitVolume: XCTestCase { 19 | func testMetricVolumeConversions() { 20 | #if SKIP 21 | throw XCTSkip("TODO") 22 | #else 23 | let cubicKilometers = Measurement(value: 4, unit: UnitVolume.cubicKilometers) 24 | XCTAssertEqual(cubicKilometers, Measurement(value: 4e9, unit: UnitVolume.cubicMeters), "Conversion from cubicKilometers to cubicMeters") 25 | 26 | let megaliters = Measurement(value: 3, unit: UnitVolume.megaliters) 27 | XCTAssertEqual(megaliters, Measurement(value: 3_000_000, unit: UnitVolume.liters), "Conversion from megaliters to liters") 28 | 29 | let kiloliters = Measurement(value: 2, unit: UnitVolume.kiloliters) 30 | XCTAssertEqual(kiloliters, Measurement(value: 2000, unit: UnitVolume.liters), "Conversion from kiloliters to liters") 31 | 32 | let cubicMeters = Measurement(value: 2, unit: UnitVolume.cubicMeters) 33 | XCTAssertEqual(cubicMeters, Measurement(value: 2000, unit: UnitVolume.liters), "Conversion from cubicMeters to liters") 34 | XCTAssertEqual(kiloliters, cubicMeters, "Conversion from kiloliters to cubicMeters") 35 | 36 | let liters = Measurement(value: 5, unit: UnitVolume.liters) 37 | XCTAssertEqual(liters, Measurement(value: 5, unit: UnitVolume.cubicDecimeters), "Conversion from liters to cubicDecimeters") 38 | XCTAssertEqual(liters, Measurement(value: 50, unit: UnitVolume.deciliters), "Conversion from liters to deciliters") 39 | XCTAssertEqual(liters, Measurement(value: 500, unit: UnitVolume.centiliters), "Conversion from liters to centiliters") 40 | XCTAssertEqual(liters, Measurement(value: 5000, unit: UnitVolume.milliliters), "Conversion from liters to milliliters") 41 | XCTAssertEqual(liters, Measurement(value: 5000, unit: UnitVolume.cubicCentimeters), "Conversion from liters to cubicCentimeters") 42 | XCTAssertEqual(liters, Measurement(value: 5e6, unit: UnitVolume.cubicMillimeters), "Conversion from liters to cubicMillimeters") 43 | #endif // !SKIP 44 | } 45 | 46 | func testMetricToImperialVolumeConversion() { 47 | #if SKIP 48 | throw XCTSkip("TODO") 49 | #else 50 | let liters = Measurement(value: 10, unit: UnitVolume.liters) 51 | XCTAssertEqual(liters.converted(to: .cubicInches).value, 610.236, accuracy: 0.001, "Conversion from liters to cubicInches") 52 | #endif // !SKIP 53 | } 54 | 55 | func testImperialVolumeConversions() { 56 | #if SKIP 57 | throw XCTSkip("TODO") 58 | #else 59 | let cubicMiles = Measurement(value: 1, unit: UnitVolume.cubicMiles) 60 | XCTAssertEqual(cubicMiles.converted(to: .cubicYards).value, 1760 * 1760 * 1760, accuracy: 1_000_000, "Conversion from cubicMiles to cubicYards") 61 | 62 | let cubicYards = Measurement(value: 1, unit: UnitVolume.cubicYards) 63 | XCTAssertEqual(cubicYards.converted(to: .cubicFeet).value, 27, accuracy: 0.001, "Conversion from cubicYards to cubicFeet") 64 | 65 | let cubicFeet = Measurement(value: 1, unit: UnitVolume.cubicFeet) 66 | XCTAssertEqual(cubicFeet.converted(to: .cubicInches).value, 1728, accuracy: 0.01, "Conversion from cubicFeet to cubicInches") 67 | 68 | let gallons = Measurement(value: 1, unit: UnitVolume.gallons) 69 | XCTAssertEqual(gallons.converted(to: .quarts).value, 4, accuracy: 0.001, "Conversion from gallons to quarts") 70 | 71 | let quarts = Measurement(value: 1, unit: UnitVolume.quarts) 72 | XCTAssertEqual(quarts.converted(to: .pints).value, 2, accuracy: 0.001, "Conversion from quarts to pints") 73 | 74 | let pints = Measurement(value: 1, unit: UnitVolume.pints) 75 | XCTAssertEqual(pints.converted(to: .cups).value, 2, accuracy: 0.05, "Conversion from pints to cups") 76 | 77 | let cups = Measurement(value: 1, unit: UnitVolume.cups) 78 | XCTAssertEqual(cups.converted(to: .fluidOunces).value, 8.12, accuracy: 0.01, "Conversion from cups to fluidOunces") 79 | 80 | let fluidOunces = Measurement(value: 1, unit: UnitVolume.fluidOunces) 81 | XCTAssertEqual(fluidOunces.converted(to: .tablespoons).value, 2, accuracy: 0.001, "Conversion from fluidOunces to tablespoons") 82 | 83 | let tablespoons = Measurement(value: 1, unit: UnitVolume.tablespoons) 84 | XCTAssertEqual(tablespoons.converted(to: .teaspoons).value, 3, accuracy: 0.001, "Conversion from tablespoons to teaspoons") 85 | 86 | let teaspoons = Measurement(value: 1, unit: UnitVolume.teaspoons) 87 | XCTAssertEqual(teaspoons.converted(to: .cubicInches).value, 0.3, accuracy: 0.001, "Conversion from teaspoons to cubicInches") 88 | #endif // !SKIP 89 | } 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /Tests/SkipFoundationTests/XCSkipTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023–2025 Skip 2 | // SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception 3 | import Foundation 4 | 5 | #if os(macOS) 6 | import SkipTest 7 | 8 | /// This test case will run the transpiled tests for the Skip module. 9 | @available(macOS 13, macCatalyst 16, *) 10 | final class XCSkipTests: XCTestCase, XCGradleHarness { 11 | public func testSkipModule() async throws { 12 | // set device ID to run in Android emulator vs. robolectric 13 | try await runGradleTests() 14 | } 15 | } 16 | #endif 17 | 18 | /// True when running in a transpiled Java runtime environment 19 | let isJava = ProcessInfo.processInfo.environment["java.io.tmpdir"] != nil 20 | /// True when running within an Android environment (either an emulator or device) 21 | let isAndroid = isJava && ProcessInfo.processInfo.environment["ANDROID_ROOT"] != nil 22 | /// True is the transpiled code is currently running in the local Robolectric test environment 23 | let isRobolectric = isJava && !isAndroid 24 | /// True if the system's `Int` type is 32-bit. 25 | let is32BitInteger = Int64(Int.max) == Int64(Int32.max) 26 | #if os(macOS) 27 | let isMacOS = true 28 | #else 29 | let isMacOS = false 30 | #endif 31 | --------------------------------------------------------------------------------