├── .gitignore ├── LICENSE.txt ├── Package.swift ├── README.md ├── Sources ├── CwlFrameAddress │ ├── CwlFrameAddress.c │ └── include │ │ └── CwlFrameAddress.h ├── CwlUtils │ ├── CwlAddressInfo.swift │ ├── CwlCaseNameCodable.swift │ ├── CwlCollection.swift │ ├── CwlCustomExecutionContext.swift │ ├── CwlDebugContext.swift │ ├── CwlDeferredWork.swift │ ├── CwlDeque.swift │ ├── CwlDispatch.swift │ ├── CwlExec.swift │ ├── CwlExecutionType.swift │ ├── CwlFew.swift │ ├── CwlKeyValueObserver.swift │ ├── CwlLifetime.swift │ ├── CwlMutex.swift │ ├── CwlOnDelete.swift │ ├── CwlRandom.swift │ ├── CwlResult.swift │ ├── CwlScalarScanner.swift │ ├── CwlSerializingContext.swift │ ├── CwlStackFrame.swift │ ├── CwlSysctl.swift │ ├── CwlUnanticipatedError.swift │ ├── CwlUtils.h │ ├── CwlWrappers.swift │ └── Info.plist └── ReferenceRandomGenerators │ ├── include │ └── ReferenceRandomGenerators.h │ ├── mt19937-64.c │ └── xoshiro256starstar.c └── Tests ├── CwlUtilsPerformanceTests ├── CwlDequePerformanceTests.swift ├── CwlMutexPerformanceTests.swift └── CwlRandomPerformanceTests.swift ├── CwlUtilsTests ├── CwlAddressInfoTests.swift ├── CwlCaseNameCodableTests.swift ├── CwlCollectionTests.swift ├── CwlDebugContextTests.swift ├── CwlDeferredWorkTests.swift ├── CwlDequeTests.swift ├── CwlDispatchTests.swift ├── CwlExecTests.swift ├── CwlKeyValueObserverTests.swift ├── CwlMutexTests.swift ├── CwlOnDeleteTests.swift ├── CwlRandomTests.swift ├── CwlScalarScannerTests.swift ├── CwlStackFrameTests.swift ├── CwlSysctlTests.swift ├── CwlUnanticipatedErrorTests.swift ├── CwlUtilsTests-BridgingHeader.h ├── CwlWrapperTests.swift └── Info.plist ├── CwlUtils_iOSTestApp ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── CwlUtils_iOSTestAppUITests └── CwlUtils_iOSTestAppUITests.swift ├── CwlUtils_macOSTestApp ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Info.plist └── ViewController.swift └── CwlUtils_macOSTestAppUITests └── CwlUtils_macOSTestAppUITests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | project.xcworkspace/ 3 | *.xcscmblueprint 4 | xcuserdata/ 5 | .build 6 | Package.pins 7 | Package.resolved 8 | Packages/ 9 | .swiftpm -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright © 2017 Matt Gallagher ( http://cocoawithlove.com ). All rights reserved. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 12 | SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | 18 | 19 | Testing targets (not the release build of CwlUtils) include code under the following two licenses: 20 | 21 | /* 22 | A C-program for MT19937-64 (2004/9/29 version). 23 | Coded by Takuji Nishimura and Makoto Matsumoto. 24 | 25 | This is a 64-bit version of Mersenne Twister pseudorandom number 26 | generator. 27 | 28 | Before using, initialize the state by using init_genrand64(seed) 29 | or init_by_array64(init_key, key_length). 30 | 31 | Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura, 32 | All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions 36 | are met: 37 | 38 | 1. Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | 41 | 2. Redistributions in binary form must reproduce the above copyright 42 | notice, this list of conditions and the following disclaimer in the 43 | documentation and/or other materials provided with the distribution. 44 | 45 | 3. The names of its contributors may not be used to endorse or promote 46 | products derived from this software without specific prior written 47 | permission. 48 | 49 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 50 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 51 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 52 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 53 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 54 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 55 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 56 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 57 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 58 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 59 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 60 | 61 | References: 62 | T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' 63 | ACM Transactions on Modeling and 64 | Computer Simulation 10. (2000) 348--357. 65 | M. Matsumoto and T. Nishimura, 66 | ``Mersenne Twister: a 623-dimensionally equidistributed 67 | uniform pseudorandom number generator'' 68 | ACM Transactions on Modeling and 69 | Computer Simulation 8. (Jan. 1998) 3--30. 70 | 71 | Any feedback is very welcome. 72 | http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html 73 | email: m-mat @ math.sci.hiro 74 | 75 | 76 | 77 | /* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org) 78 | 79 | To the extent possible under law, the author has dedicated all copyright 80 | and related and neighboring rights to this software to the public domain 81 | worldwide. This software is distributed without any warranty. 82 | 83 | See . */ 84 | shima-u.ac.jp (remove spaces) 85 | */ 86 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "CwlUtils", 6 | products: [ 7 | .library(name: "CwlUtils", targets: ["CwlUtils"]), 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", from: "2.0.0"), 11 | ], 12 | targets: [ 13 | .target( 14 | name: "CwlUtils", 15 | dependencies: [ 16 | .target(name: "CwlFrameAddress"), 17 | .target(name: "ReferenceRandomGenerators") 18 | ] 19 | ), 20 | .target(name: "CwlFrameAddress"), 21 | .target(name: "ReferenceRandomGenerators"), 22 | .testTarget( 23 | name: "CwlUtilsTests", 24 | dependencies: [ 25 | .target(name: "CwlUtils"), 26 | .product(name: "CwlPreconditionTesting") 27 | ] 28 | ), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CwlUtils 2 | 3 | A collection of utilities written as part of articles on [Cocoa with Love](https://www.cocoawithlove.com) 4 | 5 | ## Included functionality 6 | 7 | The following features are included in the library. 8 | 9 | * [CwlStackFrame.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlStackFrame.swift) from [Better stack traces in Swift](https://cocoawithlove.com/blog/2016/02/28/stack-traces-in-swift.html) 10 | * [CwlSysctl.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlSysctl.swift) from [Gathering system information in Swift with sysctl](https://www.cocoawithlove.com/blog/2016/03/08/swift-wrapper-for-sysctl.html) 11 | * [CwlUnanticipatedError.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlUnanticipatedError.swift) from [Presenting unanticipated errors to users](https://www.cocoawithlove.com/blog/2016/04/14/error-recovery-attempter.html) 12 | * [CwlScalarScanner.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlScalarScanner.swift) from [Swift name demangling: C++ vs Swift for parsing](https://www.cocoawithlove.com/blog/2016/05/01/swift-name-demangling.html) 13 | * [CwlRandom.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlRandom.swift) from [Random number generators in Swift](https://www.cocoawithlove.com/blog/2016/05/19/random-numbers.html) 14 | * [CwlMutex.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlMutex.swift) from [Mutexes and closure capture in Swift](https://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html) 15 | * [CwlDispatch.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlDispatch.swift) from [Design patterns for safe timer usage](https://www.cocoawithlove.com/blog/2016/07/30/timer-problems.html) 16 | * [CwlResult.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlResult.swift) from [Values and errors, part 1: 'Result' in Swift](https://www.cocoawithlove.com/blog/2016/08/21/result-types-part-one.html) 17 | * [CwlDeque.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlDeque.swift) from [Optimizing a copy-on-write double-ended queue in Swift](https://www.cocoawithlove.com/blog/2016/09/22/deque.html) 18 | * [CwlExec.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlExec.swift) from [Specifying function execution contexts](https://www.cocoawithlove.com/blog/specifying-execution-contexts.html) 19 | * [CwlDebugContext.swift](https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlDebugContext.swift) from [Testing actions over time](https://www.cocoawithlove.com/blog/testing-actions-over-time.html) 20 | 21 | ## Adding to your project 22 | 23 | The CwlUtils library requires the [Swift Package Manager](#swift-package-manager). Minimum requirements are iOS 8 or macOS 10.10 and Swift 5.0. 24 | 25 | Add the following to the `dependencies` array in your "Package.swift" file: 26 | 27 | .package(url: "https://github.com/mattgallagher/CwlUtils.git", from: Version(3, 0, 0)), 28 | 29 | > NOTE: even though this git repository includes its dependencies in the Dependencies folder, building via the Swift Package manager fetches and builds these dependencies independently. 30 | 31 | ## CocoaPods and Carthage 32 | 33 | Up to version 2.2.1, this library supported CocoaPods and Carthage. If you wish to use these package managers, you can check out the [CwlUtils 2.2.1 tag](https://github.com/mattgallagher/CwlUtils/releases/tag/2.2.1) 34 | -------------------------------------------------------------------------------- /Sources/CwlFrameAddress/CwlFrameAddress.c: -------------------------------------------------------------------------------- 1 | // 2 | // CwlFrameAddress.c 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/26. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | #include "CwlFrameAddress.h" 22 | 23 | uintptr_t frame_address() 24 | { 25 | return (uintptr_t)__builtin_frame_address(1); 26 | } 27 | -------------------------------------------------------------------------------- /Sources/CwlFrameAddress/include/CwlFrameAddress.h: -------------------------------------------------------------------------------- 1 | // 2 | // CwlFrameAddress.h 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/26. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | #include 22 | 23 | #ifndef CwlFrameAddress_h 24 | #define CwlFrameAddress_h 25 | 26 | uintptr_t frame_address(void); 27 | 28 | #endif /* CwlFrameAddress_h */ 29 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlAddressInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlAddressInfo.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/26. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | /// A wrapper around dl_info, used for symbolicating instruction addresses. 24 | public struct AddressInfo { 25 | private let info: dl_info 26 | 27 | /// Address for which this struct was constructed 28 | public let address: UInt 29 | 30 | /// Construct for an address 31 | public init(address: UInt) { 32 | self.address = address 33 | 34 | var i = dl_info() 35 | dladdr(UnsafeRawPointer(bitPattern: address), &i) 36 | self.info = i 37 | } 38 | 39 | /// -returns: the "image" (shared object pathname) for the instruction 40 | public var image: String { 41 | if let dli_fname = info.dli_fname, let fname = String(validatingUTF8: dli_fname), let _ = fname.range(of: "/", options: .backwards, range: nil, locale: nil) { 42 | return (fname as NSString).lastPathComponent 43 | } else { 44 | return "???" 45 | } 46 | } 47 | 48 | /// - returns: the symbol nearest the address 49 | public var symbol: String { 50 | if let dli_sname = info.dli_sname, let sname = String(validatingUTF8: dli_sname) { 51 | return sname 52 | } else if let dli_fname = info.dli_fname, let _ = String(validatingUTF8: dli_fname) { 53 | return self.image 54 | } else { 55 | return String(format: "0x%1x", UInt(bitPattern: info.dli_saddr)) 56 | } 57 | } 58 | 59 | /// - returns: the address' offset relative to the nearest symbol 60 | public var offset: Int { 61 | if let dli_sname = info.dli_sname, let _ = String(validatingUTF8: dli_sname) { 62 | return Int(address - UInt(bitPattern: info.dli_saddr)) 63 | } else if let dli_fname = info.dli_fname, let _ = String(validatingUTF8: dli_fname) { 64 | return Int(address - UInt(bitPattern: info.dli_fbase)) 65 | } else { 66 | return Int(address - UInt(bitPattern: info.dli_saddr)) 67 | } 68 | } 69 | 70 | /// - parameter index: the stack frame index 71 | /// - returns: a formatted string matching that used by NSThread.callStackSymbols 72 | public func formattedDescription(index: Int) -> String { 73 | return self.image.utf8CString.withUnsafeBufferPointer { (imageBuffer: UnsafeBufferPointer) -> String in 74 | #if arch(x86_64) || arch(arm64) 75 | return String(format: "%-4ld%-35s 0x%016llx %@ + %ld", index, UInt(bitPattern: imageBuffer.baseAddress), self.address, self.symbol, self.offset) 76 | #else 77 | return String(format: "%-4d%-35s 0x%08lx %@ + %d", index, UInt(bitPattern: imageBuffer.baseAddress), self.address, self.symbol, self.offset) 78 | #endif 79 | } 80 | } 81 | } 82 | 83 | /// When applied to the output of callStackReturnAddresses, produces identical output to the execinfo function "backtrace_symbols" or NSThread.callStackSymbols 84 | /// - parameter addresses: an array of memory addresses, generally as produced by `callStackReturnAddresses` 85 | /// - returns: an array of formatted, symbolicated stack frame descriptions. 86 | public func symbolsForCallStack(addresses: [UInt]) -> [String] { 87 | return Array(addresses.enumerated().map { tuple -> String in 88 | return AddressInfo(address: tuple.element).formattedDescription(index: tuple.offset) 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlCaseNameCodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlCaseNameCodable.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 9/3/19. 6 | // Copyright © 2019 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private struct EmptyKeyedDecodingContainer: KeyedDecodingContainerProtocol { 12 | typealias Key = K 13 | 14 | var codingPath: [CodingKey] { return [] } 15 | var allKeys: [K] { return [] } 16 | 17 | func contains(_ key: K) -> Bool { 18 | return false 19 | } 20 | 21 | private func keyNotFoundError(_ key: K) -> Error { 22 | return DecodingError.keyNotFound(key, DecodingError.Context.init(codingPath: [], debugDescription: "EmptyKeyedDecodingContainer contains no values")) 23 | } 24 | 25 | func decodeNil(forKey key: K) throws -> Bool { throw keyNotFoundError(key) } 26 | func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { throw keyNotFoundError(key) } 27 | func decode(_ type: String.Type, forKey key: K) throws -> String { throw keyNotFoundError(key) } 28 | func decode(_ type: Double.Type, forKey key: K) throws -> Double { throw keyNotFoundError(key) } 29 | func decode(_ type: Float.Type, forKey key: K) throws -> Float { throw keyNotFoundError(key) } 30 | func decode(_ type: Int.Type, forKey key: K) throws -> Int { throw keyNotFoundError(key) } 31 | func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { throw keyNotFoundError(key) } 32 | func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { throw keyNotFoundError(key) } 33 | func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { throw keyNotFoundError(key) } 34 | func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { throw keyNotFoundError(key) } 35 | func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { throw keyNotFoundError(key) } 36 | func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { throw keyNotFoundError(key) } 37 | func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { throw keyNotFoundError(key) } 38 | func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { throw keyNotFoundError(key) } 39 | func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { throw keyNotFoundError(key) } 40 | func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { throw keyNotFoundError(key) } 41 | func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey : CodingKey { throw keyNotFoundError(key) } 42 | func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { throw keyNotFoundError(key) } 43 | func superDecoder() throws -> Decoder { fatalError() } 44 | func superDecoder(forKey key: K) throws -> Decoder { throw keyNotFoundError(key) } 45 | } 46 | 47 | public extension KeyedDecodingContainer { 48 | static func empty() -> KeyedDecodingContainer { 49 | return KeyedDecodingContainer(EmptyKeyedDecodingContainer()) 50 | } 51 | } 52 | 53 | /// A protocol that enums with associated values can adopt to help in implementing Codable conformance. 54 | /// The downside is that there are some runtime enforced requirements: 55 | /// 1. the `CaseName` associated type must be raw constructible from each of the case names in `CaseNameCodable` 56 | /// 2. the `decode(from:)` method must validly construct each `CaseNameCodable` 57 | /// NOTE: the encoded value will be `nil` if the associated value has no contents or there is no associated value. In these cases, you shouldn't attempt to read the value. 58 | public protocol CaseNameCodable: Codable { 59 | associatedtype CaseName: CaseNameDecoder where CaseName.AssociatedEnum == Self 60 | } 61 | 62 | public protocol CaseNameDecoder: Codable, CodingKey, RawRepresentable where RawValue == String { 63 | associatedtype AssociatedEnum 64 | func decode(from container: KeyedDecodingContainer) throws -> AssociatedEnum 65 | } 66 | 67 | public extension CaseNameCodable { 68 | private func caseName(from mirror: Mirror) -> CaseName { 69 | let label = mirror.children.first?.label ?? String(describing: self) 70 | guard let key = CaseName(rawValue: label) else { 71 | fatalError("Unable to find a CaseName for \(self) matching \(label)") 72 | } 73 | return key 74 | } 75 | 76 | var caseName: CaseName { 77 | return caseName(from: Mirror(reflecting: self)) 78 | } 79 | 80 | func encode(to encoder: Encoder) throws { 81 | let mirror = Mirror(reflecting: self) 82 | let key = caseName(from: mirror) 83 | var container = encoder.container(keyedBy: CaseName.self) 84 | if let value = mirror.children.first?.value as? Encodable { 85 | try container.encode(EncodableWrapper(value: value), forKey: key) 86 | } else { 87 | try container.encodeNil(forKey: key) 88 | } 89 | } 90 | 91 | init(from decoder: Decoder) throws { 92 | let container = try decoder.container(keyedBy: CaseName.self) 93 | guard let key = container.allKeys.first else { 94 | throw DecodingError.dataCorrupted(DecodingError.Context.init(codingPath: decoder.codingPath, debugDescription: "Missing enum key")) 95 | } 96 | self = try key.decode(from: container) 97 | } 98 | } 99 | 100 | private struct EncodableWrapper: Encodable { 101 | let value: Encodable 102 | 103 | func encode(to encoder: Encoder) throws { 104 | try value.encode(to: encoder) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlCollection.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | extension Optional { 24 | /// Reurns true if the current optional is nil. Useful for testing for nil at a specific point during chained unwrapping of nested optionals. 25 | public var isNil: Bool { 26 | return self == nil 27 | } 28 | } 29 | 30 | extension Collection { 31 | /// Returns the element at the specified index iff it is within bounds, otherwise nil. 32 | public func at(_ i: Index) -> Iterator.Element? { 33 | return (i >= startIndex && i < endIndex) ? self[i] : nil 34 | } 35 | } 36 | 37 | extension Collection { 38 | /// Constrains the range to the indices of self and returns a SubSequence. 39 | public func at(_ range: Range) -> SubSequence { 40 | let start = (range.lowerBound >= startIndex) ? (range.lowerBound < endIndex ? range.lowerBound : endIndex) : startIndex 41 | let end = (range.upperBound < endIndex) ? (range.upperBound > startIndex ? range.upperBound : startIndex) : endIndex 42 | return self[start..) -> SubSequence { 49 | let start = (range.lowerBound >= startIndex) ? (range.lowerBound < endIndex ? range.lowerBound : endIndex) : startIndex 50 | let end = (range.upperBound < endIndex) ? (range.upperBound > startIndex ? range.upperBound : startIndex) : endIndex 51 | return self[start.. Self { 62 | var result = self 63 | result.append(newElement) 64 | return result 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlDeferredWork.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlDeferredWork.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 5/10/2015. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | // This type is designed for guarding against mutex re-entrancy by following two simple rules: 24 | // 25 | // 1. No user "work" (functions or closures) should be invoked inside a private mutex 26 | // 2. No user supplied data should be released inside a private mutex 27 | // 28 | // To facilitate these requirements, any user "work" or data ownership should be handled inside `DeferredWork` blocks. These blocks allow this user code to be queued in the desired order but since the `runWork` function should only be called outside the mutex, these blocks run safely outside the mutex. 29 | // 30 | // This pattern has two associated risks: 31 | // 1. If the deferred work calls back into the mutex, it must be able to ensure that it is still relevant (hasn't been superceded by an action that may have occurred between the end of the mutex and the performing of the `DeferredWork`. This may involve a token (inside the mutex, only the most recent token is accepted) or the mutex queueing further requests until the most recent `DeferredWork` completes. 32 | // 2. The `runWork` must be manually invoked. Automtic invocation (e.g in the `deinit` of a lifetime managed `class` instance) would add heap allocation overhead and would also be easy to accidentally release at the wrong point (inside the mutex) causing erratic problems. Instead, the `runWork` is guarded with a `DEBUG`-only `OnDelete` check that ensures that the `runWork` has been correctly invoked by the time the `DeferredWork` falls out of scope. 33 | public struct DeferredWork { 34 | typealias PossibleWork = Few<() -> Void> 35 | 36 | var work: PossibleWork 37 | 38 | #if DEBUG 39 | let invokeCheck: OnDelete = { () -> OnDelete in 40 | var sourceStack = Thread.callStackReturnAddresses 41 | return OnDelete { 42 | let symbols = symbolsForCallStack(addresses: sourceStack.map { $0.uintValue }) 43 | preconditionFailure("Failed to perform work deferred at location:\n" + symbols.joined(separator: "\n")) 44 | } 45 | }() 46 | #endif 47 | 48 | public init() { 49 | work = .none 50 | } 51 | 52 | public init(initial: @escaping () -> Void) { 53 | work = .single(initial) 54 | } 55 | 56 | public mutating func append(_ other: DeferredWork) { 57 | #if DEBUG 58 | precondition(invokeCheck.isValid && other.invokeCheck.isValid, "Work appended to an already cancelled/invoked DeferredWork") 59 | other.invokeCheck.invalidate() 60 | #endif 61 | 62 | switch other.work { 63 | case .none: break 64 | case .single(let otherWork): self.append(otherWork) 65 | case .array(let otherWork): 66 | switch work { 67 | case .none: work = .array(otherWork) 68 | case .single(let existing): 69 | var newWork: Array<() -> Void> = [existing] 70 | newWork.append(contentsOf: otherWork) 71 | work = .array(newWork) 72 | case .array(var existing): 73 | work = .none 74 | existing.append(contentsOf: otherWork) 75 | work = .array(existing) 76 | } 77 | } 78 | } 79 | 80 | public mutating func append(_ additionalWork: @escaping () -> Void) { 81 | #if DEBUG 82 | precondition(invokeCheck.isValid, "Work appended to an already cancelled/invoked DeferredWork") 83 | #endif 84 | 85 | switch work { 86 | case .none: work = .single(additionalWork) 87 | case .single(let existing): work = .array([existing, additionalWork]) 88 | case .array(var existing): 89 | work = .none 90 | existing.append(additionalWork) 91 | work = .array(existing) 92 | } 93 | } 94 | 95 | public mutating func runWork() { 96 | #if DEBUG 97 | precondition(invokeCheck.isValid, "Work run multiple times") 98 | invokeCheck.invalidate() 99 | #endif 100 | 101 | switch work { 102 | case .none: break 103 | case .single(let w): w() 104 | case .array(let ws): 105 | for w in ws { 106 | w() 107 | } 108 | } 109 | work = .none 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlDispatch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlDispatch.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/07/29. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | public extension DispatchSource { 24 | // An overload of timer that immediately sets the handler and schedules the timer 25 | class func singleTimer(interval: DispatchTimeInterval, leeway: DispatchTimeInterval = .nanoseconds(0), queue: DispatchQueue, handler: @escaping () -> Void) -> DispatchSourceTimer { 26 | let result = DispatchSource.makeTimerSource(queue: queue) 27 | result.setEventHandler(handler: handler) 28 | #if swift(>=4) 29 | result.schedule(deadline: DispatchTime.now() + interval, leeway: leeway) 30 | #else 31 | result.scheduleOneshot(deadline: DispatchTime.now() + interval, leeway: leeway) 32 | #endif 33 | result.resume() 34 | return result 35 | } 36 | 37 | // An overload of timer that always uses the default global queue (because it is intended to enter the appropriate mutex as a separate step) and passes a user-supplied Int to the handler function to allow ignoring callbacks if cancelled or rescheduled before mutex acquisition. 38 | class func singleTimer(parameter: T, interval: DispatchTimeInterval, leeway: DispatchTimeInterval = .nanoseconds(0), queue: DispatchQueue = DispatchQueue.global(), handler: @escaping (T) -> Void) -> DispatchSourceTimer { 39 | let result = DispatchSource.makeTimerSource(queue: queue) 40 | result.scheduleOneshot(parameter: parameter, interval: interval, leeway: leeway, handler: handler) 41 | result.resume() 42 | return result 43 | } 44 | 45 | // An overload of timer that immediately sets the handler and schedules the timer 46 | class func repeatingTimer(interval: DispatchTimeInterval, leeway: DispatchTimeInterval = .nanoseconds(0), queue: DispatchQueue = DispatchQueue.global(), handler: @escaping () -> Void) -> DispatchSourceTimer { 47 | let result = DispatchSource.makeTimerSource(queue: queue) 48 | result.setEventHandler(handler: handler) 49 | #if swift(>=4) 50 | result.schedule(deadline: DispatchTime.now() + interval, repeating: interval, leeway: leeway) 51 | #else 52 | result.scheduleRepeating(deadline: DispatchTime.now() + interval, interval: interval, leeway: leeway) 53 | #endif 54 | result.resume() 55 | return result 56 | } 57 | 58 | // An overload of timer that always uses the default global queue (because it is intended to enter the appropriate mutex as a separate step) and passes a user-supplied Int to the handler function to allow ignoring callbacks if cancelled or rescheduled before mutex acquisition. 59 | class func repeatingTimer(parameter: T, interval: DispatchTimeInterval, leeway: DispatchTimeInterval = .nanoseconds(0), queue: DispatchQueue = DispatchQueue.global(), handler: @escaping (T) -> Void) -> DispatchSourceTimer { 60 | let result = DispatchSource.makeTimerSource(queue: queue) 61 | result.scheduleRepeating(parameter: parameter, interval: interval, leeway: leeway, handler: handler) 62 | result.resume() 63 | return result 64 | } 65 | } 66 | 67 | public extension DispatchSourceTimer { 68 | // An overload of scheduleOneshot that updates the handler function with a new user-supplied parameter when it changes the expiry deadline 69 | func scheduleOneshot(parameter: T, interval: DispatchTimeInterval, leeway: DispatchTimeInterval = .nanoseconds(0), handler: @escaping (T) -> Void) { 70 | suspend() 71 | setEventHandler { handler(parameter) } 72 | #if swift(>=4) 73 | schedule(deadline: DispatchTime.now() + interval, leeway: leeway) 74 | #else 75 | scheduleOneshot(deadline: DispatchTime.now() + interval, leeway: leeway) 76 | #endif 77 | resume() 78 | } 79 | 80 | // An overload of scheduleOneshot that updates the handler function with a new user-supplied parameter when it changes the expiry deadline 81 | func scheduleRepeating(parameter: T, interval: DispatchTimeInterval, leeway: DispatchTimeInterval = .nanoseconds(0), handler: @escaping (T) -> Void) { 82 | suspend() 83 | setEventHandler { handler(parameter) } 84 | #if swift(>=4) 85 | schedule(deadline: DispatchTime.now() + interval, repeating: interval, leeway: leeway) 86 | #else 87 | scheduleRepeating(deadline: DispatchTime.now() + interval, interval: interval, leeway: leeway) 88 | #endif 89 | resume() 90 | } 91 | } 92 | 93 | public extension DispatchTime { 94 | func since(_ previous: DispatchTime) -> DispatchTimeInterval { 95 | return .nanoseconds(Int(uptimeNanoseconds - previous.uptimeNanoseconds)) 96 | } 97 | } 98 | 99 | public extension DispatchTimeInterval { 100 | static func interval(_ seconds: TimeInterval) -> DispatchTimeInterval { 101 | if MemoryLayout.size < 8 { 102 | return .milliseconds(Int(seconds * Double(NSEC_PER_SEC / NSEC_PER_MSEC))) 103 | } else { 104 | return .nanoseconds(Int(seconds * Double(NSEC_PER_SEC))) 105 | } 106 | } 107 | 108 | var seconds: Double { 109 | #if swift (>=3.2) 110 | switch self { 111 | case .seconds(let t): return Double(t) 112 | case .milliseconds(let t): return (Double(NSEC_PER_MSEC) / Double(NSEC_PER_SEC)) * Double(t) 113 | case .microseconds(let t): return (Double(NSEC_PER_USEC) / Double(NSEC_PER_SEC)) * Double(t) 114 | case .nanoseconds(let t): return (1.0 / Double(NSEC_PER_SEC)) * Double(t) 115 | case .never: return Double.infinity 116 | #if swift (>=5) 117 | default: fatalError("Unknown case") 118 | #endif 119 | } 120 | #else 121 | switch self { 122 | case .seconds(let t): return Double(t) 123 | case .milliseconds(let t): return (Double(NSEC_PER_MSEC) / Double(NSEC_PER_SEC)) * Double(t) 124 | case .microseconds(let t): return (Double(NSEC_PER_USEC) / Double(NSEC_PER_SEC)) * Double(t) 125 | case .nanoseconds(let t): return (1.0 / Double(NSEC_PER_SEC)) * Double(t) 126 | } 127 | #endif 128 | } 129 | 130 | var nanoseconds: Int64 { 131 | #if swift (>=3.2) 132 | switch self { 133 | case .seconds(let t): return Int64(NSEC_PER_SEC) * Int64(t) 134 | case .milliseconds(let t): return Int64(NSEC_PER_MSEC) * Int64(t) 135 | case .microseconds(let t): return Int64(NSEC_PER_USEC) * Int64(t) 136 | case .nanoseconds(let t): return Int64(t) 137 | case .never: return Int64.max 138 | #if swift (>=5) 139 | default: fatalError("Unknown case") 140 | #endif 141 | } 142 | #else 143 | switch self { 144 | case .seconds(let t): return Int64(NSEC_PER_SEC) * Int64(t) 145 | case .milliseconds(let t): return Int64(NSEC_PER_MSEC) * Int64(t) 146 | case .microseconds(let t): return Int64(NSEC_PER_USEC) * Int64(t) 147 | case .nanoseconds(let t): return Int64(t) 148 | } 149 | #endif 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlExecutionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlExecutionType.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 30/1/19. 6 | // Copyright © 2019 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without 9 | // fee is hereby granted, provided that the above copyright notice and this permission notice 10 | // appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 13 | // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 16 | // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 17 | // OF THIS SOFTWARE. 18 | // 19 | 20 | /// Describes 7 key execution context types. The six most common exist as a pair between a sychronous and asynchronous version: 21 | /// * immediate and concurrentAsync 22 | /// * mutex and serialAsync 23 | /// * thread and threadAsync 24 | /// With the final context being an additional variation on mutex: 25 | /// * recursiveMutex 26 | /// 27 | /// This list of of execution context types should *not* be considered exhaustive so in general, it is better to interrogate the boolean properties in which you're interested. 28 | /// These properties are currently: 29 | /// * isImmediate[InCurrentContext|Always] 30 | /// * isReentrant 31 | /// * isConcurrent 32 | public enum ExecutionType { 33 | /// This execution type models a simple function invocation. 34 | /// * completes before `invoke` returns (immediate) 35 | /// * applies no mutex so nested calls to `invoke` will succeed (reentrant) 36 | /// * will let parallel calls run at the same time (concurrent) 37 | /// * invocation always inherits the caller's context (nest always) 38 | /// e.g. directly calling 39 | case immediate 40 | 41 | /// This execution type models a global concurrent work pool. 42 | /// * runs outside the current context and might not complete before `invoke` returns (asynchronous) 43 | /// * involves no mutex so nested calls to `invokeSync` are permitted (reentrant) 44 | /// * will let parallel calls run at the same time (concurrent) 45 | /// * normally async but `invokeSync` is invoked from the calling context (sync nests) 46 | /// e.g. DispatchQueue.global().async 47 | case concurrentAsync 48 | 49 | /// This execution type models a scoped non-recursive mutex. 50 | /// * completes before `invoke` returns (immediate) 51 | /// * applies a non-reentrant mutex so nested calls to `invoke` will deadlock (non-reentrant) 52 | /// * will serialize parallel calls to run one at a time (serial) 53 | /// * invocation always inherits the caller's context (nest always) 54 | /// e.g. dispatchQueue.sync 55 | case mutex 56 | 57 | /// This execution type models a scoped recursive mutex. The associated test function returns `true` if the `invoke` or `invokeSync` function can be elided (replaced by direct invocation, since the current context is known to be inside the mutex). 58 | /// * completes before `invoke` returns (immediate) 59 | /// * applies a mutex but a nested `invoke` will safely re-enter the mutex (reentrant) 60 | /// * will serialize parallel calls to run one at a time (serial) 61 | /// * invocation always inherits the caller's context (nest always) 62 | /// e.g. NSRecursiveLock.lock(before:) 63 | case recursiveMutex(() -> Bool) 64 | 65 | /// This execution type models a thread. The associated test function returns `true` if the `invoke` or `invokeSync` function can be elided (replaced by direct invocation, since the current context is known to be inside the thread). 66 | /// * if test function returns true, then `invoke` is immediate in the current context, otherwise asychronous (immediate/asynchronous) 67 | /// * nested calls to `invoke` are permitted since they will simply be run immediately (reentrant) 68 | /// * will serialize parallel calls to run one at a time (serial) 69 | /// * invocation only inherits the caller's context if test function returns true in current context (nest thread) 70 | /// e.g. `if Thread.isMainThread { /* do work */ } else { DispatchQueue.main.async { /* do work */ }` 71 | case thread(() -> Bool) 72 | 73 | /// This execution type models a thread on which work is typically performed asynchronously. The associated test function returns `true` if the `invoke` or `invokeSync` function can be elided (replaced by direct invocation, since the current context is known to be inside the thread). 74 | /// * `invoke` is always asynchronous (asynchronous) 75 | /// * detects when it is already on the current thread so nested calls to `invokeSync` will not deadlock (reentrant) 76 | /// * will serialize parallel calls to run one at a time (serial) 77 | /// * normally async but `invokeSync` nests if already on its thread (nest syncThread) 78 | /// e.g. DispatchQueue.main.async 79 | case threadAsync(() -> Bool) 80 | 81 | /// This execution type models an asynchronous resource that lacks any synchronous access. 82 | /// * runs outside the current context and might not complete before `invoke` returns (asynchronous) 83 | /// * applies a non-reentrant mutex so nested calls to `invokeSync` will deadlock (non-reentrant) 84 | /// * will serialize parallel calls to run one at a time (serial) 85 | /// * invocation never inherits the caller's context (nest no) 86 | /// e.g. a serial resource that offers a `performAsync(_:() -> Void)` but doesn't offer a `performSync(_:() -> Void)` 87 | case serialAsync 88 | } 89 | 90 | public extension ExecutionType { 91 | /// Returns true if a block executed with `invoke` is guaranteed to complete before `invoke` returns in the current context. 92 | /// The inverse of this value is "isAsyncInCurrentContext". 93 | /// 94 | /// NOTE: this property runs a function for case `.thread` to see if the current thread is the target thread. Any change queue/thread may break the guarantee (the name "thread" is representative-only and might not refer to a literal thread). 95 | var isImmediateInCurrentContext: Bool { 96 | switch self { 97 | case .immediate, .mutex, .recursiveMutex: return true 98 | case .thread(let isCurrent): return isCurrent() 99 | case .serialAsync, .concurrentAsync, .threadAsync: return false 100 | } 101 | } 102 | 103 | /// Inverse of `isImmediateInCurrentContext` 104 | var isAsyncInCurrentContext: Bool { return !isImmediateInCurrentContext } 105 | 106 | /// Returns true if a block executed with `invoke` is always guaranteed to complete before `invoke` returns. 107 | /// The inverse of this value is "isPotentiallyAsync" 108 | var isImmediateAlways: Bool { 109 | switch self { 110 | case .immediate, .mutex, .recursiveMutex: return true 111 | case .thread, .serialAsync, .concurrentAsync, .threadAsync: return false 112 | } 113 | } 114 | 115 | /// Inverse of `isImmediateAlways` 116 | var isPotentiallyAsync: Bool { return !isImmediateInCurrentContext } 117 | 118 | /// Returns true if calling `invoke` or `invokeSync` within an executed block will succeed (not deadlock). 119 | /// The inverse of this value is "non-reentrant" 120 | var isReentrant: Bool { 121 | switch self { 122 | case .immediate, .recursiveMutex, .thread, .threadAsync, .concurrentAsync: return true 123 | case .mutex, .serialAsync: return false 124 | } 125 | } 126 | 127 | /// Inverse of `isReentrant` 128 | var isNonReentrant: Bool { return !isReentrant } 129 | 130 | /// Returns true if calling `invoke` simultaneously on separate threads may result in simultaneous execution. 131 | /// The inverse of this value is "serial" 132 | var isConcurrent: Bool { 133 | switch self { 134 | case .immediate, .concurrentAsync: return true 135 | case .mutex, .recursiveMutex, .serialAsync, .thread, .threadAsync: return false 136 | } 137 | } 138 | 139 | /// Inverse of `isConcurrent` 140 | var isSerial: Bool { return !isConcurrent } 141 | } 142 | 143 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlFew.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlFew.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 29/1/19. 6 | // Copyright © 2019 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without 9 | // fee is hereby granted, provided that the above copyright notice and this permission notice 10 | // appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 13 | // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 16 | // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 17 | // OF THIS SOFTWARE. 18 | // 19 | 20 | import Foundation 21 | 22 | public enum Few { 23 | case none 24 | case single(T) 25 | case array(Array) 26 | } 27 | 28 | extension Few: Collection { 29 | public func index(after i: Int) -> Int { 30 | return i + 1 31 | } 32 | 33 | public var count: Int { 34 | switch self { 35 | case .none: return 0 36 | case .single: return 1 37 | case .array(let a): return a.count 38 | } 39 | } 40 | 41 | public var startIndex: Int { 42 | return 0 43 | } 44 | 45 | public var endIndex: Int { 46 | switch self { 47 | case .none: return 0 48 | case .single: return 1 49 | case .array(let a): return a.endIndex 50 | } 51 | } 52 | 53 | public subscript(key: Int) -> T { 54 | switch self { 55 | case .none: fatalError() 56 | case .single(let value): return value 57 | case .array(let a): return a[key] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlLifetime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlLifetime.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2017/04/18. 6 | // Copyright © 2017 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | /// This protocol exists to keep alive and terminate-at-will asynchronous and ongoing tasks. It is an 24 | /// implementation of the "Disposable" pattern. 25 | /// 26 | /// While conformance to this protocol requires just one function, conforming to this protocol also signals three important traits: 27 | /// 1. instances manage an underlying resource 28 | /// 2. the resource will last until one the first of the following end-conditions occurs: 29 | /// a. The resource terminates on its own 30 | /// b. All references to the Lifetime instance are released 31 | /// c. The `cancel()` function is invoked 32 | /// 33 | /// ideally, as well: 34 | /// 35 | /// 3. no further effects or actions of any kind will occur after the first end-condition is registered in the 36 | /// resource's context, no further messages or notifications sent or received, no resurrection possible 37 | /// 4. any subsequent end conditions after the first are safe and have no effect 38 | /// 5. if Self is a reference type, `cancel` should be explicitly invoked on deinit 39 | /// 6. `cancel` should invoke `cancel` on any owned child Lifetime instances 40 | /// 41 | /// Examples of violations of the last 4 points exist be should be kept rare. 42 | public protocol Lifetime { 43 | /// Immediately set the resource managed by this instance to an "end-of-life" state. 44 | /// This a mutating method and should be called only in executation contexts where changing `self` is threadsafe. 45 | mutating func cancel() 46 | } 47 | 48 | public typealias Cancellable = Lifetime 49 | 50 | /// An array of Lifetime that conforms to Lifetime. Note that a conditional conformance on Array can't properly conform 51 | // to Lifetime since it would permit adding new lifetimes after the aggregate was cancelled. 52 | public class AggregateLifetime: Lifetime { 53 | private var lifetimes: [Lifetime]? 54 | public init(lifetimes: [Lifetime] = []) { 55 | self.lifetimes = lifetimes 56 | } 57 | public func cancel() { 58 | if var ls = lifetimes { 59 | for i in ls.indices { 60 | ls[i].cancel() 61 | } 62 | lifetimes = nil 63 | } 64 | } 65 | public static func +=(left: AggregateLifetime, right: Lifetime) { 66 | left.lifetimes?.append(right) 67 | } 68 | deinit { 69 | cancel() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlMutex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlMutex.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | #if os(Linux) 22 | import Glibc 23 | #else 24 | import Darwin 25 | #endif 26 | 27 | /// A basic mutex protocol that requires nothing more than "performing work inside the mutex". 28 | public protocol ScopedMutex { 29 | /// Perform work inside the mutex 30 | func sync(execute work: () throws -> R) rethrows -> R 31 | 32 | /// Perform work inside the mutex, returning immediately if the mutex is in-use 33 | func trySync(execute work: () throws -> R) rethrows -> R? 34 | } 35 | 36 | /// A more specific kind of mutex that assume an underlying primitive and unbalanced lock/trylock/unlock operators 37 | public protocol RawMutex: ScopedMutex { 38 | associatedtype MutexPrimitive 39 | 40 | var underlyingMutex: MutexPrimitive { get set } 41 | 42 | func unbalancedLock() 43 | func unbalancedTryLock() -> Bool 44 | func unbalancedUnlock() 45 | } 46 | 47 | extension RawMutex { 48 | public func sync(execute work: () throws -> R) rethrows -> R { 49 | unbalancedLock() 50 | defer { unbalancedUnlock() } 51 | return try work() 52 | } 53 | public func trySync(execute work: () throws -> R) rethrows -> R? { 54 | guard unbalancedTryLock() else { return nil } 55 | defer { unbalancedUnlock() } 56 | return try work() 57 | } 58 | } 59 | 60 | /// A basic wrapper around the "NORMAL" and "RECURSIVE" `pthread_mutex_t` (a general purpose mutex). This type is a "class" type to take advantage of the "deinit" method and prevent accidental copying of the `pthread_mutex_t`. 61 | public final class PThreadMutex: RawMutex { 62 | public typealias MutexPrimitive = pthread_mutex_t 63 | 64 | // Non-recursive "PTHREAD_MUTEX_NORMAL" and recursive "PTHREAD_MUTEX_RECURSIVE" mutex types. 65 | public enum PThreadMutexType { 66 | case normal 67 | case recursive 68 | } 69 | 70 | public var underlyingMutex = pthread_mutex_t() 71 | 72 | /// Default constructs as ".Normal" or ".Recursive" on request. 73 | public init(type: PThreadMutexType = .normal) { 74 | var attr = pthread_mutexattr_t() 75 | guard pthread_mutexattr_init(&attr) == 0 else { 76 | preconditionFailure() 77 | } 78 | switch type { 79 | case .normal: 80 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL) 81 | case .recursive: 82 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) 83 | } 84 | guard pthread_mutex_init(&underlyingMutex, &attr) == 0 else { 85 | preconditionFailure() 86 | } 87 | pthread_mutexattr_destroy(&attr) 88 | } 89 | 90 | deinit { 91 | pthread_mutex_destroy(&underlyingMutex) 92 | } 93 | 94 | public func unbalancedLock() { 95 | pthread_mutex_lock(&underlyingMutex) 96 | } 97 | 98 | public func unbalancedTryLock() -> Bool { 99 | return pthread_mutex_trylock(&underlyingMutex) == 0 100 | } 101 | 102 | public func unbalancedUnlock() { 103 | pthread_mutex_unlock(&underlyingMutex) 104 | } 105 | } 106 | 107 | /// A basic wrapper around `os_unfair_lock` (a non-FIFO, high performance lock that offers safety against priority inversion). This type is a "class" type to prevent accidental copying of the `os_unfair_lock`. 108 | @available(OSX 10.12, iOS 10, tvOS 10, watchOS 3, *) 109 | public final class UnfairLock: RawMutex { 110 | public typealias MutexPrimitive = os_unfair_lock 111 | 112 | public init() { 113 | } 114 | 115 | /// Exposed as an "unsafe" public property so non-scoped patterns can be implemented, if required. 116 | public var underlyingMutex = os_unfair_lock() 117 | 118 | public func unbalancedLock() { 119 | os_unfair_lock_lock(&underlyingMutex) 120 | } 121 | 122 | public func unbalancedTryLock() -> Bool { 123 | return os_unfair_lock_trylock(&underlyingMutex) 124 | } 125 | 126 | public func unbalancedUnlock() { 127 | os_unfair_lock_unlock(&underlyingMutex) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlOnDelete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlOnDelete.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Swift 22 | 23 | public final class OnDelete: Lifetime { 24 | var block: (() -> Void)? 25 | 26 | public init(_ b: @escaping () -> Void) { 27 | block = b 28 | } 29 | 30 | public func invalidate() { 31 | block = nil 32 | } 33 | 34 | public func cancel() { 35 | block?() 36 | block = nil 37 | } 38 | 39 | public var isValid: Bool { 40 | return block != nil 41 | } 42 | 43 | deinit { 44 | cancel() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlRandom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlRandom.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/05/17. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | #if !swift(>=4.2) 24 | public protocol RandomNumberGenerator { 25 | mutating func next() -> UInt64 26 | } 27 | public struct SystemRandomNumberGenerator: RandomNumberGenerator { 28 | public init() {} 29 | public mutating func next() -> UInt64 { 30 | var value: UInt64 = 0 31 | arc4random_buf(&value, MemoryLayout.size) 32 | return value 33 | } 34 | } 35 | #endif 36 | 37 | public protocol RandomGenerator: RandomNumberGenerator { 38 | mutating func randomize(buffer: UnsafeMutableRawBufferPointer) 39 | } 40 | 41 | extension RandomGenerator { 42 | public mutating func randomize(value: inout Value) { 43 | withUnsafeMutablePointer(to: &value) { ptr in 44 | self.randomize(buffer: UnsafeMutableRawBufferPointer(start: ptr, count: MemoryLayout.size)) 45 | } 46 | } 47 | } 48 | 49 | public struct DevRandom: RandomGenerator { 50 | class FileDescriptor { 51 | let value: CInt 52 | init() { 53 | value = open("/dev/urandom", O_RDONLY) 54 | precondition(value >= 0) 55 | } 56 | deinit { 57 | close(value) 58 | } 59 | } 60 | 61 | let fd: FileDescriptor 62 | public init() { 63 | fd = FileDescriptor() 64 | } 65 | 66 | public mutating func randomize(buffer: UnsafeMutableRawBufferPointer) { 67 | let result = read(fd.value, buffer.baseAddress, buffer.count) 68 | precondition(result == buffer.count) 69 | } 70 | 71 | public mutating func next() -> UInt64 { 72 | var bits: UInt64 = 0 73 | withUnsafeMutablePointer(to: &bits) { ptr in 74 | self.randomize(buffer: UnsafeMutableRawBufferPointer(start: ptr, count: MemoryLayout.size)) 75 | } 76 | return bits 77 | } 78 | } 79 | 80 | public struct Xoshiro: RandomNumberGenerator { 81 | public typealias StateType = (UInt64, UInt64, UInt64, UInt64) 82 | 83 | private var state: StateType = (0, 0, 0, 0) 84 | 85 | public init() { 86 | var dr = DevRandom() 87 | dr.randomize(value: &state) 88 | } 89 | 90 | public init(seed: StateType) { 91 | self.state = seed 92 | } 93 | 94 | public mutating func next() -> UInt64 { 95 | // Derived from public domain implementation of xoshiro256** here: 96 | // http://xoshiro.di.unimi.it 97 | // by David Blackman and Sebastiano Vigna 98 | let x = state.1 &* 5 99 | let result = ((x &<< 7) | (x &>> 57)) &* 9 100 | let t = state.1 &<< 17 101 | state.2 ^= state.0 102 | state.3 ^= state.1 103 | state.1 ^= state.2 104 | state.0 ^= state.3 105 | state.2 ^= t 106 | state.3 = (state.3 &<< 45) | (state.3 &>> 19) 107 | return result 108 | } 109 | } 110 | 111 | public struct MersenneTwister: RandomNumberGenerator { 112 | // 312 words of storage is 13 x 6 x 4 113 | private typealias StateType = ( 114 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 115 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 116 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 117 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 118 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 119 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 120 | 121 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 122 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 123 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 124 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 125 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 126 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 127 | 128 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 129 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 130 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 131 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 132 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 133 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 134 | 135 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 136 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 137 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 138 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 139 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, 140 | UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt64 141 | ) 142 | 143 | private var state_internal: StateType = ( 144 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 146 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150 | 151 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 155 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 156 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157 | 158 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 159 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 161 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 162 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164 | 165 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 167 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 169 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 170 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 171 | ) 172 | private var index: Int 173 | private static let stateCount: Int = 312 174 | 175 | public init() { 176 | var dr = DevRandom() 177 | dr.randomize(value: &state_internal) 178 | index = MersenneTwister.stateCount 179 | } 180 | 181 | public init(seed: UInt64) { 182 | index = MersenneTwister.stateCount 183 | withUnsafeMutablePointer(to: &state_internal) { $0.withMemoryRebound(to: UInt64.self, capacity: MersenneTwister.stateCount) { state in 184 | state[0] = seed 185 | for i in 1..> 62)) &+ UInt64(i) 187 | } 188 | } } 189 | } 190 | 191 | public mutating func next() -> UInt64 { 192 | if index == MersenneTwister.stateCount { 193 | withUnsafeMutablePointer(to: &state_internal) { $0.withMemoryRebound(to: UInt64.self, capacity: MersenneTwister.stateCount) { state in 194 | let n = MersenneTwister.stateCount 195 | let m = n / 2 196 | let a: UInt64 = 0xB5026F5AA96619E9 197 | let lowerMask: UInt64 = (1 << 31) - 1 198 | let upperMask: UInt64 = ~lowerMask 199 | var (i, j, stateM) = (0, m, state[m]) 200 | repeat { 201 | let x1 = (state[i] & upperMask) | (state[i &+ 1] & lowerMask) 202 | state[i] = state[i &+ m] ^ (x1 >> 1) ^ ((state[i &+ 1] & 1) &* a) 203 | let x2 = (state[j] & upperMask) | (state[j &+ 1] & lowerMask) 204 | state[j] = state[j &- m] ^ (x2 >> 1) ^ ((state[j &+ 1] & 1) &* a) 205 | (i, j) = (i &+ 1, j &+ 1) 206 | } while i != m &- 1 207 | 208 | let x3 = (state[m &- 1] & upperMask) | (stateM & lowerMask) 209 | state[m &- 1] = state[n &- 1] ^ (x3 >> 1) ^ ((stateM & 1) &* a) 210 | let x4 = (state[n &- 1] & upperMask) | (state[0] & lowerMask) 211 | state[n &- 1] = state[m &- 1] ^ (x4 >> 1) ^ ((state[0] & 1) &* a) 212 | } } 213 | 214 | index = 0 215 | } 216 | 217 | var result = withUnsafePointer(to: &state_internal) { $0.withMemoryRebound(to: UInt64.self, capacity: MersenneTwister.stateCount) { ptr in 218 | return ptr[index] 219 | } } 220 | index = index &+ 1 221 | 222 | result ^= (result >> 29) & 0x5555555555555555 223 | result ^= (result << 17) & 0x71D67FFFEDA60000 224 | result ^= (result << 37) & 0xFFF7EEE000000000 225 | result ^= result >> 43 226 | 227 | return result 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlResult.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | /// Either a Success value or an Failure error 24 | public extension Result { 25 | /// Convenience tester/getter for the value 26 | var value: Success? { 27 | switch self { 28 | case .success(let s): return s 29 | case .failure: return nil 30 | } 31 | } 32 | 33 | /// Convenience tester/getter for the error 34 | var error: Failure? { 35 | switch self { 36 | case .success: return nil 37 | case .failure(let f): return f 38 | } 39 | } 40 | 41 | /// Test whether the result is an error. 42 | var isSuccess: Bool { 43 | return !isFailure 44 | } 45 | 46 | /// Test whether the result is an error. 47 | var isFailure: Bool { 48 | switch self { 49 | case .success: return false 50 | case .failure: return true 51 | } 52 | } 53 | } 54 | 55 | public extension Result where Failure == Swift.Error { 56 | /// Chains another Result to this one. In the event that this Result is a .Success, the provided transformer closure is used to transform the value into another value (of a potentially new type) and a new Result is made from that value. In the event that this Result is a .Failure, the next Result will have the same error as this one. 57 | func mapThrows(_ transform: (Success) throws -> U) -> Result { 58 | switch self { 59 | case .success(let val): return Result { try transform(val) } 60 | case .failure(let e): return .failure(e) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlSerializingContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlSerializingContext.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 19/1/19. 6 | // Copyright © 2019 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without 9 | // fee is hereby granted, provided that the above copyright notice and this permission notice 10 | // appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 13 | // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 16 | // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 17 | // OF THIS SOFTWARE. 18 | // 19 | 20 | import Foundation 21 | 22 | /// An `ExecutionContext` wraps a mutex around calls invoked by an underlying execution context. The effect is to serialize concurrent contexts (immediate or concurrent). 23 | public struct SerializingContext: CustomExecutionContext { 24 | public let underlying: Exec 25 | public let mutex = PThreadMutex(type: .recursive) 26 | 27 | public init(concurrentContext: Exec) { 28 | underlying = concurrentContext 29 | } 30 | 31 | public var type: ExecutionType { 32 | switch underlying.type { 33 | case .immediate: return .mutex 34 | case .concurrentAsync: return .serialAsync 35 | case .mutex, .recursiveMutex, .thread, .threadAsync, .serialAsync: return underlying.type 36 | } 37 | } 38 | 39 | public func invoke(_ execute: @escaping () -> Void) { 40 | if case .direct = underlying { 41 | mutex.sync(execute: execute) 42 | } else { 43 | underlying.invoke { [mutex] in mutex.sync(execute: execute) } 44 | } 45 | } 46 | 47 | public func invokeAsync(_ execute: @escaping () -> Void) { 48 | underlying.invokeAsync { [mutex] in mutex.sync(execute: execute) } 49 | } 50 | 51 | @available(*, deprecated, message: "Use invokeSync instead") 52 | public func invokeAndWait(_ execute: @escaping () -> Void) { 53 | invokeSync(execute) 54 | } 55 | 56 | public func invokeSync(_ execute: () throws -> Return) rethrows -> Return { 57 | if case .direct = underlying { 58 | return try mutex.sync(execute: execute) 59 | } else { 60 | return try underlying.invokeSync { [mutex] () throws -> Return in try mutex.sync(execute: execute) } 61 | } 62 | } 63 | 64 | /// Run `execute` on the execution context after `interval` (plus `leeway`) unless the returned `Lifetime` is cancelled or released before running occurs. 65 | public func singleTimer(interval: DispatchTimeInterval, leeway: DispatchTimeInterval, handler: @escaping () -> Void) -> Lifetime { 66 | return mutex.sync { () -> Lifetime in 67 | let wrapper = MutexWrappedLifetime(mutex: mutex) 68 | let lifetime = underlying.singleTimer(interval: interval, leeway: leeway) { [weak wrapper] in 69 | if let w = wrapper { 70 | w.mutex.sync { 71 | // Need to perform this double check since the timer may have been cancelled/changed before we managed to enter the mutex 72 | if w.lifetime != nil { 73 | handler() 74 | } 75 | } 76 | } 77 | } 78 | wrapper.lifetime = lifetime 79 | return wrapper 80 | } 81 | } 82 | 83 | /// Run `execute` on the execution context after `interval` (plus `leeway`), passing the `parameter` value as an argument, unless the returned `Lifetime` is cancelled or released before running occurs. 84 | public func singleTimer(parameter: T, interval: DispatchTimeInterval, leeway: DispatchTimeInterval, handler: @escaping (T) -> Void) -> Lifetime { 85 | return mutex.sync { () -> Lifetime in 86 | let wrapper = MutexWrappedLifetime(mutex: mutex) 87 | let lifetime = underlying.singleTimer(parameter: parameter, interval: interval, leeway: leeway) { [weak wrapper] p in 88 | if let w = wrapper { 89 | w.mutex.sync { 90 | // Need to perform this double check since the timer may have been cancelled/changed before we managed to enter the mutex 91 | if w.lifetime != nil { 92 | handler(p) 93 | } 94 | } 95 | } 96 | } 97 | wrapper.lifetime = lifetime 98 | return wrapper 99 | } 100 | } 101 | 102 | /// Run `execute` on the execution context after `interval` (plus `leeway`), and again every `interval` (within a `leeway` margin of error) unless the returned `Lifetime` is cancelled or released before running occurs. 103 | public func periodicTimer(interval: DispatchTimeInterval, leeway: DispatchTimeInterval, handler: @escaping () -> Void) -> Lifetime { 104 | return mutex.sync { () -> Lifetime in 105 | let wrapper = MutexWrappedLifetime(mutex: mutex) 106 | let lifetime = underlying.periodicTimer(interval: interval, leeway: leeway) { [weak wrapper] in 107 | if let w = wrapper { 108 | w.mutex.sync { 109 | // Need to perform this double check since the timer may have been cancelled/changed before we managed to enter the mutex 110 | if w.lifetime != nil { 111 | handler() 112 | } 113 | } 114 | } 115 | } 116 | wrapper.lifetime = lifetime 117 | return wrapper 118 | } 119 | } 120 | 121 | /// Run `execute` on the execution context after `interval` (plus `leeway`), passing the `parameter` value as an argument, and again every `interval` (within a `leeway` margin of error) unless the returned `Lifetime` is cancelled or released before running occurs. 122 | public func periodicTimer(parameter: T, interval: DispatchTimeInterval, leeway: DispatchTimeInterval, handler: @escaping (T) -> Void) -> Lifetime { 123 | return mutex.sync { () -> Lifetime in 124 | let wrapper = MutexWrappedLifetime(mutex: mutex) 125 | let lifetime = underlying.periodicTimer(parameter: parameter, interval: interval, leeway: leeway) { [weak wrapper] p in 126 | if let w = wrapper { 127 | w.mutex.sync { 128 | if w.lifetime != nil { 129 | handler(p) 130 | } 131 | } 132 | } 133 | } 134 | wrapper.lifetime = lifetime 135 | return wrapper 136 | } 137 | } 138 | 139 | /// Gets a timestamp representing the host uptime the in the current context 140 | public func timestamp() -> DispatchTime { 141 | return underlying.timestamp() 142 | } 143 | } 144 | 145 | /// A wrapper around Lifetime that applies a mutex on the cancel operation. 146 | /// This is a class so that `SerializingContext` can pass it weakly to the timer closure, avoiding having the timer keep itself alive. 147 | private class MutexWrappedLifetime: Lifetime { 148 | var lifetime: Lifetime? = nil 149 | let mutex: PThreadMutex 150 | 151 | init(mutex: PThreadMutex) { 152 | self.mutex = mutex 153 | } 154 | 155 | func cancel() { 156 | mutex.sync { 157 | lifetime?.cancel() 158 | lifetime = nil 159 | } 160 | } 161 | 162 | deinit { 163 | cancel() 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlStackFrame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlStackFrame.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/26. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Darwin 22 | 23 | #if SWIFT_PACKAGE 24 | import CwlFrameAddress 25 | #endif 26 | 27 | /// A utility class for walking through stack frames. 28 | public struct StackFrame { 29 | /// The underlying data of the struct is a basic UInt. A value of 0 represents an invalid frame. 30 | public let address: UInt 31 | 32 | /// The return address is pushed onto the stack immediately ahead of the previous frame pointer. If `self.address` is `0` this will return `0` 33 | /// - returns: the return address for the stack frame identified by `address` 34 | public var returnAddress: UInt { get { 35 | guard address != 0 else { return 0 } 36 | return UnsafeMutablePointer(bitPattern: address)!.advanced(by: FP_LINK_OFFSET).pointee 37 | } } 38 | 39 | /// Preferred constructor gives the "current" StackFrame (where "current" refers to the frame that invokes this function). Also returns the `stackBounds` for use in subsequent calls to `next`. 40 | /// - returns: a `StackFrame` representing the caller's stack frame and `stackBounds` which should be passed into any future calls to `next`. 41 | @inline(never) 42 | public static func current() -> (frame: StackFrame, stackBounds: ClosedRange) { 43 | let stackBounds = currentStackBounds() 44 | let frame = StackFrame(address: frame_address()) 45 | 46 | if !stackBounds.contains(frame.address) || !isAligned(frame.address) { 47 | return (StackFrame(address: 0), stackBounds: stackBounds); 48 | } 49 | 50 | return (frame: frame.next(inBounds: stackBounds), stackBounds: stackBounds) 51 | } 52 | 53 | /// Follow the frame link pointer and return the result as another StackFrame. 54 | /// - returns: a `StackFrame` representing the stack frame after self, if it exists and is valid. 55 | public func next(inBounds stackBounds: ClosedRange) -> StackFrame { 56 | guard address != 0 else { return self } 57 | let nextFrameAddress = UnsafeMutablePointer(bitPattern: address)?.pointee 58 | if !stackBounds.contains(nextFrameAddress!) || !isAligned(nextFrameAddress!) || (nextFrameAddress ?? 0) <= address { 59 | return StackFrame(address: 0) 60 | } 61 | 62 | return StackFrame(address: nextFrameAddress!) 63 | } 64 | } 65 | 66 | /// Traverses the frames on current stack and gathers the return addresses for traversed stack frames as an array of UInt. 67 | /// - parameter skip: number of stack frames to skip over before copying return addresses to the result array. 68 | /// - parameter maximumAddresses: limit on the number of return addresses to return (default is `Int.max`) 69 | /// - returns: The array of return addresses on the current stack within the skip/maximumAddresses bounds. 70 | @inline(never) 71 | public func callStackReturnAddresses(skip: UInt = 0, maximumAddresses: Int = Int.max) -> [UInt] { 72 | guard maximumAddresses > 0 else { return [] } 73 | 74 | var result = [UInt]() 75 | var skipsRemaining = skip 76 | var addressesRemaining = maximumAddresses 77 | 78 | let maximumReserve = 32 79 | result.reserveCapacity(maximumAddresses < maximumReserve ? maximumAddresses : maximumReserve) 80 | 81 | var (frame, bounds) = StackFrame.current() 82 | var returnAddress = frame.returnAddress 83 | 84 | while returnAddress != 0 && addressesRemaining > 0 { 85 | if skipsRemaining > 0 { 86 | skipsRemaining -= 1 87 | } else { 88 | result.append(returnAddress) 89 | addressesRemaining -= 1 90 | } 91 | frame = frame.next(inBounds: bounds) 92 | returnAddress = frame.returnAddress 93 | } 94 | 95 | return result 96 | } 97 | 98 | // These values come from: 99 | // http://www.opensource.apple.com/source/Libc/Libc-997.90.3/gen/thread_stack_pcs.c 100 | #if arch(x86_64) 101 | let ISALIGNED_MASK: UInt = 0xf 102 | let ISALIGNED_RESULT: UInt = 0 103 | let FP_LINK_OFFSET = 1 104 | #elseif arch(i386) 105 | let ISALIGNED_MASK: UInt = 0xf 106 | let ISALIGNED_RESULT: UInt = 8 107 | let FP_LINK_OFFSET = 1 108 | #else 109 | let ISALIGNED_MASK: UInt = 0x1 110 | let ISALIGNED_RESULT: UInt = 0 111 | let FP_LINK_OFFSET = 1 112 | #endif 113 | 114 | /// Use the pthread functions to get the bounds of the current stack as a closed interval. 115 | /// - returns: a closed interval containing the memory address range for the current stack 116 | private func currentStackBounds() -> ClosedRange { 117 | let currentThread = pthread_self() 118 | let t = UInt(bitPattern: pthread_get_stackaddr_np(currentThread)) 119 | return ((t - UInt(bitPattern: pthread_get_stacksize_np(currentThread))) ... t) 120 | } 121 | 122 | /// We traverse the stack using "downstack links". To avoid problems with these links, we ensure that frame pointers are "aligned" (valid stack frames are 16 byte aligned on x86 and 2 byte aligned on ARM). 123 | /// - parameter address: the address to analyze 124 | /// - returns: true if `address` is aligned according to stack rules for the current architecture 125 | private func isAligned(_ address: UInt) -> Bool { 126 | return (address & ISALIGNED_MASK) == ISALIGNED_RESULT 127 | } 128 | 129 | /// Get the calling function's address and look it up, attempting to find the symbol. 130 | /// NOTE: This is mostly useful in debug environements. Outside this, non-public functions and images without symbols will return incomplete information. 131 | /// - parameter skipCount: the number of stack frames to skip over before analyzing 132 | /// - returns: the `dladdr` identifier for the specified frame, if one exists 133 | @inline(never) 134 | public func callingFunctionIdentifier(skipCount: UInt = 0) -> String { 135 | let address = callStackReturnAddresses(skip: skipCount + 1, maximumAddresses: 1).first ?? 0 136 | return AddressInfo(address: address).symbol 137 | } 138 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlSysctl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlSysctl.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/03. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | /// A "static"-only namespace around a series of functions that operate on buffers returned from the `Darwin.sysctl` function 24 | public struct Sysctl { 25 | /// Possible errors. 26 | public enum Error: Swift.Error { 27 | case unknown 28 | case malformedUTF8 29 | case invalidSize 30 | case posixError(POSIXErrorCode) 31 | } 32 | 33 | /// Access the raw data for an array of sysctl identifiers. 34 | public static func data(for keys: [Int32]) throws -> [Int8] { 35 | return try keys.withUnsafeBufferPointer() { keysPointer throws -> [Int8] in 36 | // Preflight the request to get the required data size 37 | var requiredSize = 0 38 | let preFlightResult = Darwin.sysctl(UnsafeMutablePointer(mutating: keysPointer.baseAddress), UInt32(keys.count), nil, &requiredSize, nil, 0) 39 | if preFlightResult != 0 { 40 | throw POSIXErrorCode(rawValue: errno).map { 41 | print($0.rawValue) 42 | return Error.posixError($0) 43 | } ?? Error.unknown 44 | } 45 | 46 | // Run the actual request with an appropriately sized array buffer 47 | let data = Array(repeating: 0, count: requiredSize) 48 | let result = data.withUnsafeBufferPointer() { dataBuffer -> Int32 in 49 | return Darwin.sysctl(UnsafeMutablePointer(mutating: keysPointer.baseAddress), UInt32(keys.count), UnsafeMutableRawPointer(mutating: dataBuffer.baseAddress), &requiredSize, nil, 0) 50 | } 51 | if result != 0 { 52 | throw POSIXErrorCode(rawValue: errno).map { Error.posixError($0) } ?? Error.unknown 53 | } 54 | 55 | return data 56 | } 57 | } 58 | 59 | /// Convert a sysctl name string like "hw.memsize" to the array of `sysctl` identifiers (e.g. [CTL_HW, HW_MEMSIZE]) 60 | public static func keys(for name: String) throws -> [Int32] { 61 | var keysBufferSize = Int(CTL_MAXNAME) 62 | var keysBuffer = Array(repeating: 0, count: keysBufferSize) 63 | try keysBuffer.withUnsafeMutableBufferPointer { (lbp: inout UnsafeMutableBufferPointer) throws in 64 | try name.withCString { (nbp: UnsafePointer) throws in 65 | guard sysctlnametomib(nbp, lbp.baseAddress, &keysBufferSize) == 0 else { 66 | throw POSIXErrorCode(rawValue: errno).map { Error.posixError($0) } ?? Error.unknown 67 | } 68 | } 69 | } 70 | if keysBuffer.count > keysBufferSize { 71 | keysBuffer.removeSubrange(keysBufferSize..(ofType: T.Type, forKeys keys: [Int32]) throws -> T { 78 | let buffer = try data(for: keys) 79 | if buffer.count != MemoryLayout.size { 80 | throw Error.invalidSize 81 | } 82 | return try buffer.withUnsafeBufferPointer() { bufferPtr throws -> T in 83 | guard let baseAddress = bufferPtr.baseAddress else { throw Error.unknown } 84 | return baseAddress.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } 85 | } 86 | } 87 | 88 | /// Invoke `sysctl` with an array of identifers, interpreting the returned buffer as the specified type. This function will throw `Error.invalidSize` if the size of buffer returned from `sysctl` fails to match the size of `T`. 89 | public static func value(ofType type: T.Type, forKeys keys: Int32...) throws -> T { 90 | return try value(ofType: type, forKeys: keys) 91 | } 92 | 93 | /// Invoke `sysctl` with the specified name, interpreting the returned buffer as the specified type. This function will throw `Error.invalidSize` if the size of buffer returned from `sysctl` fails to match the size of `T`. 94 | public static func value(ofType type: T.Type, forName name: String) throws -> T { 95 | return try value(ofType: type, forKeys: keys(for: name)) 96 | } 97 | 98 | /// Invoke `sysctl` with an array of identifers, interpreting the returned buffer as a `String`. This function will throw `Error.malformedUTF8` if the buffer returned from `sysctl` cannot be interpreted as a UTF8 buffer. 99 | public static func string(for keys: [Int32]) throws -> String { 100 | let optionalString = try data(for: keys).withUnsafeBufferPointer() { dataPointer -> String? in 101 | dataPointer.baseAddress.flatMap { String(validatingUTF8: $0) } 102 | } 103 | guard let s = optionalString else { 104 | throw Error.malformedUTF8 105 | } 106 | return s 107 | } 108 | 109 | /// Invoke `sysctl` with an array of identifers, interpreting the returned buffer as a `String`. This function will throw `Error.malformedUTF8` if the buffer returned from `sysctl` cannot be interpreted as a UTF8 buffer. 110 | public static func string(for keys: Int32...) throws -> String { 111 | return try string(for: keys) 112 | } 113 | 114 | /// Invoke `sysctl` with the specified name, interpreting the returned buffer as a `String`. This function will throw `Error.malformedUTF8` if the buffer returned from `sysctl` cannot be interpreted as a UTF8 buffer. 115 | public static func string(for name: String) throws -> String { 116 | return try string(for: keys(for: name)) 117 | } 118 | 119 | /// e.g. "MyComputer.local" (from System Preferences -> Sharing -> Computer Name) or 120 | /// "My-Name-iPhone" (from Settings -> General -> About -> Name) 121 | public static var hostName: String { return try! Sysctl.string(for: [CTL_KERN, KERN_HOSTNAME]) } 122 | 123 | /// e.g. "x86_64" or "N71mAP" 124 | /// NOTE: this is *corrected* on iOS devices to fetch hw.model 125 | public static var machine: String { 126 | #if os(iOS) && !arch(x86_64) && !arch(i386) 127 | return try! Sysctl.string(for: [CTL_HW, HW_MODEL]) 128 | #else 129 | return try! Sysctl.string(for: [CTL_HW, HW_MACHINE]) 130 | #endif 131 | } 132 | 133 | /// e.g. "MacPro4,1" or "iPhone8,1" 134 | /// NOTE: this is *corrected* on iOS devices to fetch hw.machine 135 | public static var model: String { 136 | #if os(iOS) && !arch(x86_64) && !arch(i386) 137 | return try! Sysctl.string(for: [CTL_HW, HW_MACHINE]) 138 | #else 139 | return try! Sysctl.string(for: [CTL_HW, HW_MODEL]) 140 | #endif 141 | } 142 | 143 | /// e.g. "8" or "2" 144 | public static var activeCPUs: Int32 { return try! Sysctl.value(ofType: Int32.self, forKeys: [CTL_HW, HW_AVAILCPU]) } 145 | 146 | /// e.g. "15.3.0" or "15.0.0" 147 | public static var osRelease: String { return try! Sysctl.string(for: [CTL_KERN, KERN_OSRELEASE]) } 148 | 149 | /// e.g. "Darwin" or "Darwin" 150 | public static var osType: String { return try! Sysctl.string(for: [CTL_KERN, KERN_OSTYPE]) } 151 | 152 | /// e.g. "15D21" or "13D20" 153 | public static var osVersion: String { return try! Sysctl.string(for: [CTL_KERN, KERN_OSVERSION]) } 154 | 155 | /// e.g. "Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64" or 156 | /// "Darwin Kernel Version 15.0.0: Wed Dec 9 22:19:38 PST 2015; root:xnu-3248.31.3~2/RELEASE_ARM64_S8000" 157 | public static var version: String { return try! Sysctl.string(for: [CTL_KERN, KERN_VERSION]) } 158 | 159 | #if os(macOS) 160 | /// e.g. 199506 (not available on iOS) 161 | public static var osRev: Int32 { return try! Sysctl.value(ofType: Int32.self, forKeys: [CTL_KERN, KERN_OSREV]) } 162 | 163 | /// e.g. 2659000000 (not available on iOS) 164 | public static var cpuFreq: Int64 { return try! Sysctl.value(ofType: Int64.self, forName: "hw.cpufrequency") } 165 | 166 | /// e.g. 25769803776 (not available on iOS) 167 | public static var memSize: UInt64 { return try! Sysctl.value(ofType: UInt64.self, forKeys: [CTL_HW, HW_MEMSIZE]) } 168 | #endif 169 | } 170 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlUnanticipatedError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlUnanticipatedError.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/03/05. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | #if os(macOS) 22 | import Cocoa 23 | #elseif os(iOS) 24 | import UIKit 25 | import MobileCoreServices 26 | #endif 27 | 28 | public extension Error { 29 | /// Return an NSError with the same properties as this error but with an `UnanticipatedErrorRecoveryAttempter` attached. 30 | func withUnanticipatedErrorRecoveryAttempter(file: String = #file, line: Int = #line) -> NSError { 31 | let e = self as NSError 32 | var userInfo: [String: Any] = e.userInfo 33 | 34 | // Move any existing NSLocalizedRecoverySuggestionErrorKey to a new key (we want to replace it but don't want to lose potentially useful information) 35 | if let previousSuggestion = userInfo[NSLocalizedRecoverySuggestionErrorKey] { 36 | userInfo[UnanticipatedErrorRecoveryAttempter.previousRecoverySuggestionKey] = previousSuggestion 37 | } 38 | 39 | // Attach a new NSLocalizedRecoverySuggestionErrorKey and our recovery attempter and options 40 | let directory = ((file as NSString).deletingLastPathComponent as NSString).lastPathComponent 41 | let filename = (file as NSString).lastPathComponent 42 | let suggestion = String(format: NSLocalizedString("The error occurred at line %ld of the %@/%@ file in the program's code.", comment: ""), line, directory, filename) 43 | userInfo[NSLocalizedDescriptionKey] = self.localizedDescription 44 | userInfo[NSLocalizedRecoverySuggestionErrorKey] = suggestion 45 | userInfo[NSLocalizedRecoveryOptionsErrorKey] = UnanticipatedErrorRecoveryAttempter.localizedRecoveryOptions() 46 | userInfo[NSRecoveryAttempterErrorKey] = UnanticipatedErrorRecoveryAttempter() 47 | 48 | // Attach the call stack 49 | userInfo[UnanticipatedErrorRecoveryAttempter.callStackSymbols] = Thread.callStackSymbols 50 | 51 | return NSError(domain: e.domain, code: e.code, userInfo: userInfo) 52 | } 53 | } 54 | 55 | // A function that returns an `Error` of a non-public type, that already has `withUnanticipatedErrorRecoveryAttempter` 56 | public func undeclaredError(file: String = #file, line: Int = #line) -> Error { 57 | struct UndeclaredError: LocalizedError { 58 | var errorDescription: String? { return NSLocalizedString("An unspecified error occurred.", comment: "") } 59 | } 60 | return UndeclaredError().withUnanticipatedErrorRecoveryAttempter(file: file, line: line ) 61 | } 62 | 63 | /// A convenience wrapper that applies `withUnanticipatedErrorRecoveryAttempter` to any error thrown by the wrapped function 64 | public func rethrowUnanticipated(file: String = #file, line: Int = #line, execute: () throws -> T) throws -> T { 65 | do { 66 | return try execute() 67 | } catch { 68 | throw error.withUnanticipatedErrorRecoveryAttempter(file: file, line: line) 69 | } 70 | } 71 | 72 | /// Class usable as the NSRecoveryAttempterErrorKey object in an NSError that presents the 'Unexpected' error and gives the option of copying the full error to the pasteboard. 73 | public class UnanticipatedErrorRecoveryAttempter: NSObject { 74 | /// Key used in NSError.userInfo dictionaries to store call stack addresses 75 | public static let callStackSymbols = "CwlUtils.CallStackReturnAddresses" 76 | 77 | /// Key used in NSError.userInfo dictionaries to store an OnDelete object that raises a fatal error if not cancelled 78 | public static let previousRecoverySuggestionKey = "CwlUtils.PreviousRecoverySuggestion" 79 | 80 | /// Present two buttons: "Copy details" and "OK" 81 | fileprivate class func localizedRecoveryOptions() -> [String] { 82 | return [NSLocalizedString("OK", comment:""), NSLocalizedString("Copy details", comment:"")] 83 | } 84 | 85 | /// There are two possible `attemptRecoveryFromError` methods. This one just feeds into the other. 86 | public override func attemptRecovery(fromError error: Error, optionIndex: Int, delegate: Any?, didRecoverSelector: Selector?, contextInfo: UnsafeMutableRawPointer?) -> Void { 87 | _ = self.attemptRecovery(fromError: error, optionIndex: optionIndex) 88 | } 89 | 90 | /// Generate the "detailed" information for the pasteboard (the error dialog itself will show the brief details) 91 | private func extendedErrorInformation(_ error: NSError) -> String { 92 | var userInfo = error.userInfo 93 | 94 | // Fetch and format diagnostic information for display 95 | let callStackSymbols = (userInfo[UnanticipatedErrorRecoveryAttempter.callStackSymbols] as? [String]).map { $0.joined(separator: "\n") } ?? NSLocalizedString("(Call stack unavailable)", comment: "") 96 | let localizedDescription = error.localizedDescription 97 | let localizedRecoverySuggestion = error.localizedRecoverySuggestion ?? "" 98 | let applicationName = (Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String) ?? ProcessInfo.processInfo.processName 99 | let applicationVersion = (Bundle.main.infoDictionary?[kCFBundleVersionKey as String] as? String) ?? NSLocalizedString("(App version unavailable)", comment: "") 100 | let locales = Locale.preferredLanguages.joined(separator: ", ") 101 | let machineInfo = "\(Sysctl.machine)/\(Sysctl.model), \(ProcessInfo.processInfo.operatingSystemVersionString)" 102 | 103 | // Remove already handled keys from the userInfo. Anything not yet handled will be output as part of the diagnostic information. 104 | userInfo.removeValue(forKey: NSLocalizedRecoverySuggestionErrorKey) 105 | userInfo.removeValue(forKey: NSLocalizedRecoveryOptionsErrorKey) 106 | userInfo.removeValue(forKey: NSRecoveryAttempterErrorKey) 107 | userInfo.removeValue(forKey: UnanticipatedErrorRecoveryAttempter.previousRecoverySuggestionKey) 108 | userInfo.removeValue(forKey: UnanticipatedErrorRecoveryAttempter.callStackSymbols) 109 | 110 | return "\(applicationName)/\(applicationVersion), \(machineInfo), \(locales)\n\n\(localizedDescription)\n\(localizedRecoverySuggestion)\n\n\(error.domain): \(error.code). \(userInfo)\n\n\(callStackSymbols)" 111 | } 112 | 113 | /// When a button is tapped, either close the dialog or copy the error details as appropriate. 114 | public override func attemptRecovery(fromError error: Error, optionIndex: Int) -> Bool { 115 | // The "Copy details" button is index 1 in the buttons array. 116 | let copyDetailsButtonIndex = 1 117 | 118 | switch optionIndex { 119 | case copyDetailsButtonIndex: 120 | #if os(macOS) 121 | #if swift(>=4) 122 | NSPasteboard.general.clearContents() 123 | NSPasteboard.general.setString(extendedErrorInformation(error as NSError), forType:NSPasteboard.PasteboardType.string) 124 | #else 125 | NSPasteboard.general().clearContents() 126 | NSPasteboard.general().setString(extendedErrorInformation(error as NSError), forType:NSPasteboardTypeString) 127 | #endif 128 | #elseif os(iOS) 129 | UIPasteboard.general.string = extendedErrorInformation(error as NSError) 130 | #endif 131 | return true 132 | default: 133 | return false; 134 | } 135 | } 136 | } 137 | 138 | #if os(iOS) 139 | 140 | /// A protocol to provide functionality similar to NSResponder.presentError on Mac OS X. 141 | public protocol ErrorPresenter { 142 | func presentError(_ error: NSError, _ completion: (() -> Void)?) 143 | } 144 | 145 | // Implement the ErrorPresent on UIViewController rather than UIResponder since presenting a `UIAlertController` requires a parent `UIViewController` 146 | extension UIViewController: ErrorPresenter { 147 | /// An adapter function that allows the UnanticipatedErrorRecoveryAttempter to be used on iOS to present errors over a UIViewController. 148 | public func presentError(_ error: NSError, _ completion: (() -> Void)? = nil) { 149 | #if swift(>=4.2) 150 | let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion ?? error.localizedFailureReason, preferredStyle: UIAlertController.Style.alert) 151 | 152 | if let ro = error.localizedRecoveryOptions, let ra = error.recoveryAttempter as? UnanticipatedErrorRecoveryAttempter { 153 | for (index, option) in ro.enumerated() { 154 | alert.addAction(UIAlertAction(title: option, style: UIAlertAction.Style.default, handler: { (action: UIAlertAction?) -> Void in 155 | _ = ra.attemptRecovery(fromError: error, optionIndex: index) 156 | })) 157 | } 158 | } 159 | #else 160 | let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion ?? error.localizedFailureReason, preferredStyle: UIAlertControllerStyle.alert) 161 | 162 | if let ro = error.localizedRecoveryOptions, let ra = error.recoveryAttempter as? UnanticipatedErrorRecoveryAttempter { 163 | for (index, option) in ro.enumerated() { 164 | alert.addAction(UIAlertAction(title: option, style: UIAlertActionStyle.default, handler: { (action: UIAlertAction?) -> Void in 165 | _ = ra.attemptRecovery(fromError: error, optionIndex: index) 166 | })) 167 | } 168 | } 169 | #endif 170 | self.present(alert, animated: true, completion: completion) 171 | } 172 | } 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // CwlUtils.h 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/26. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | #import 22 | 23 | //! Project version number for CwlUtils. 24 | FOUNDATION_EXPORT double CwlUtilsVersionNumber; 25 | 26 | //! Project version string for CwlUtils. 27 | FOUNDATION_EXPORT const unsigned char CwlUtilsVersionString[]; 28 | 29 | #import "CwlFrameAddress.h" 30 | -------------------------------------------------------------------------------- /Sources/CwlUtils/CwlWrappers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlWrappers.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | 23 | /// A class wrapper around a type (usually a value type) so it can be moved without copying. 24 | public class Box { 25 | public fileprivate(set) var value: T 26 | public init(_ t: T) { 27 | value = t 28 | } 29 | } 30 | 31 | //// A class wrapper around a type (usually a value type) so changes to it can be shared (usually as an ad hoc communication channel). NOTE: this version is *not* threadsafe, use AtomicBox for that. 32 | public final class MutableBox: Box { 33 | public override var value: T { get { return super.value } set { super.value = newValue } } 34 | public override init(_ t: T) { 35 | super.init(t) 36 | } 37 | } 38 | 39 | // A class wrapper around a type (usually a value type) so changes to it can be shared in a thread-safe manner (usually as an ad hoc communication channel). 40 | /// "Atomic" in this sense refers to the semantics, not the implementation. This uses a pthread mutex, not CAS-style atomic operations. 41 | public final class AtomicBox { 42 | private var mutex = PThreadMutex() 43 | private var internalValue: T 44 | 45 | public init(_ t: T) { 46 | internalValue = t 47 | } 48 | 49 | public var value: T { 50 | get { 51 | mutex.unbalancedLock() 52 | defer { mutex.unbalancedUnlock() } 53 | return internalValue 54 | } 55 | } 56 | 57 | @discardableResult 58 | public func mutate(_ f: (inout T) throws -> Void) rethrows -> T { 59 | mutex.unbalancedLock() 60 | defer { mutex.unbalancedUnlock() } 61 | try f(&internalValue) 62 | return internalValue 63 | } 64 | } 65 | 66 | /// A struct wrapper around an optional and a construction function that presents the optional through the `value()` function as though it's a lazy var. Unlike a true lazy var, you can query if the value has been initialized. 67 | public struct Lazy { 68 | var valueIfInitialized: T? 69 | let valueConstructor: () -> T 70 | 71 | public init(valueConstructor: @escaping () -> T) { 72 | self.valueConstructor = valueConstructor 73 | } 74 | public var isInitialized: Bool { return valueIfInitialized != nil } 75 | public mutating func value() -> T { 76 | if let v = valueIfInitialized { 77 | return v 78 | } 79 | let v = valueConstructor() 80 | valueIfInitialized = v 81 | return v 82 | } 83 | } 84 | 85 | /// A wrapper around a type (usually a class type) so it can be weakly referenced from an Array or other strong container. 86 | public struct Weak { 87 | public weak var value: T? 88 | 89 | public init(_ value: T?) { 90 | self.value = value 91 | } 92 | 93 | public func contains(_ other: T) -> Bool { 94 | if let v = value { 95 | return v === other 96 | } else { 97 | return false 98 | } 99 | } 100 | } 101 | 102 | /// A wrapper around a type (usually a class type) so it can be referenced unowned from an Array or other strong container. 103 | public struct Unowned { 104 | public unowned let value: T 105 | public init(_ value: T) { 106 | self.value = value 107 | } 108 | } 109 | 110 | /// A enum wrapper around a type (usually a class type) so its ownership can be set at runtime. 111 | public enum PossiblyWeak { 112 | case strong(T) 113 | case weak(Weak) 114 | 115 | public init(strong value: T) { 116 | self = PossiblyWeak.strong(value) 117 | } 118 | 119 | public init(weak value: T) { 120 | self = PossiblyWeak.weak(Weak(value)) 121 | } 122 | 123 | public var value: T? { 124 | switch self { 125 | case .strong(let t): return t 126 | case .weak(let weakT): return weakT.value 127 | } 128 | } 129 | 130 | public func contains(_ other: T) -> Bool { 131 | switch self { 132 | case .strong(let t): return t === other 133 | case .weak(let weakT): 134 | if let wt = weakT.value { 135 | return wt === other 136 | } 137 | return false 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Sources/CwlUtils/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | NSHumanReadableCopyright 26 | Copyright © 2017 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 27 | 28 | 29 | -------------------------------------------------------------------------------- /Sources/ReferenceRandomGenerators/include/ReferenceRandomGenerators.h: -------------------------------------------------------------------------------- 1 | // 2 | // ReferenceRandomGenerators.h 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and distribute this software for any purpose with or without 9 | // fee is hereby granted, provided that the above copyright notice and this permission notice 10 | // appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 13 | // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 16 | // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 17 | // OF THIS SOFTWARE. 18 | // 19 | 20 | #ifndef ReferenceRandomGenerators_h 21 | #define ReferenceRandomGenerators_h 22 | 23 | #import 24 | 25 | typedef struct { 26 | uint64_t s[4]; 27 | } xoshiro_state; 28 | 29 | uint64_t xoshiro_next(xoshiro_state *s); 30 | 31 | struct mt19937_64 { 32 | unsigned long long mt[312]; 33 | int mti; 34 | }; 35 | 36 | void init_genrand64(struct mt19937_64* context, unsigned long long seed); 37 | unsigned long long genrand64_int64(struct mt19937_64* context); 38 | double genrand64_real1(struct mt19937_64* context); 39 | double genrand64_real2(struct mt19937_64* context); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/ReferenceRandomGenerators/mt19937-64.c: -------------------------------------------------------------------------------- 1 | /* 2 | A C-program for MT19937-64 (2004/9/29 version). 3 | Coded by Takuji Nishimura and Makoto Matsumoto. 4 | 5 | This is a 64-bit version of Mersenne Twister pseudorandom number 6 | generator. 7 | 8 | Before using, initialize the state by using init_genrand64(seed) 9 | or init_by_array64(init_key, key_length). 10 | 11 | Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura, 12 | All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions 16 | are met: 17 | 18 | 1. Redistributions of source code must retain the above copyright 19 | notice, this list of conditions and the following disclaimer. 20 | 21 | 2. Redistributions in binary form must reproduce the above copyright 22 | notice, this list of conditions and the following disclaimer in the 23 | documentation and/or other materials provided with the distribution. 24 | 25 | 3. The names of its contributors may not be used to endorse or promote 26 | products derived from this software without specific prior written 27 | permission. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 32 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 33 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 34 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 37 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 38 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 39 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | 41 | References: 42 | T. Nishimura, ``Tables of 64-bit Mersenne Twisters'' 43 | ACM Transactions on Modeling and 44 | Computer Simulation 10. (2000) 348--357. 45 | M. Matsumoto and T. Nishimura, 46 | ``Mersenne Twister: a 623-dimensionally equidistributed 47 | uniform pseudorandom number generator'' 48 | ACM Transactions on Modeling and 49 | Computer Simulation 8. (Jan. 1998) 3--30. 50 | 51 | Any feedback is very welcome. 52 | http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html 53 | email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces) 54 | */ 55 | 56 | 57 | #include 58 | #include 59 | #include 60 | 61 | #define NN 312 62 | #define MM 156 63 | #define MATRIX_A 0xB5026F5AA96619E9ULL 64 | #define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */ 65 | #define LM 0x7FFFFFFFULL /* Least significant 31 bits */ 66 | 67 | struct mt19937_64 { 68 | unsigned long long mt[NN]; 69 | size_t mti; 70 | }; 71 | 72 | /* initializes mt[NN] with a seed */ 73 | void init_genrand64(struct mt19937_64* context, unsigned long long seed) 74 | { 75 | context->mt[0] = seed; 76 | for (context->mti=1; context->mtimti++) 77 | context->mt[context->mti] = (6364136223846793005ULL * (context->mt[context->mti-1] ^ (context->mt[context->mti-1] >> 62)) + context->mti); 78 | } 79 | 80 | struct mt19937_64 init_genrand64_fromtime() 81 | { 82 | struct mt19937_64 context; 83 | init_genrand64(&context, mach_absolute_time()); 84 | return context; 85 | } 86 | 87 | /* initialize by an array with array-length */ 88 | /* init_key is the array for initializing keys */ 89 | /* key_length is its length */ 90 | void init_by_array64(struct mt19937_64* context, unsigned long long init_key[], 91 | unsigned long long key_length) 92 | { 93 | unsigned long long i, j, k; 94 | init_genrand64(context, 19650218ULL); 95 | i=1; j=0; 96 | k = (NN>key_length ? NN : key_length); 97 | for (; k; k--) { 98 | context->mt[i] = (context->mt[i] ^ ((context->mt[i-1] ^ (context->mt[i-1] >> 62)) * 3935559000370003845ULL)) 99 | + init_key[j] + j; /* non linear */ 100 | i++; j++; 101 | if (i>=NN) { context->mt[0] = context->mt[NN-1]; i=1; } 102 | if (j>=key_length) j=0; 103 | } 104 | for (k=NN-1; k; k--) { 105 | context->mt[i] = (context->mt[i] ^ ((context->mt[i-1] ^ (context->mt[i-1] >> 62)) * 2862933555777941757ULL)) 106 | - i; /* non linear */ 107 | i++; 108 | if (i>=NN) { context->mt[0] = context->mt[NN-1]; i=1; } 109 | } 110 | 111 | context->mt[0] = 1ULL << 63; /* MSB is 1; assuring non-zero initial array */ 112 | } 113 | 114 | /* generates a random number on [0, 2^64-1]-interval */ 115 | unsigned long long genrand64_int64(struct mt19937_64* context) 116 | { 117 | #if 0 118 | /* This is the original implementation. It is replaced by the alternate implementation, below. */ 119 | int i; 120 | unsigned long long x; 121 | static unsigned long long mag01[2]={0ULL, MATRIX_A}; 122 | 123 | if (mti >= NN) { /* generate NN words at one time */ 124 | 125 | /* if init_genrand64() has not been called, */ 126 | /* a default initial seed is used */ 127 | if (mti == NN+1) 128 | init_genrand64(5489ULL); 129 | 130 | for (i=0;i>1) ^ mag01[(int)(x&1ULL)]; 133 | } 134 | for (;i>1) ^ mag01[(int)(x&1ULL)]; 137 | } 138 | x = (mt[NN-1]&UM)|(mt[0]&LM); 139 | mt[NN-1] = mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)]; 140 | 141 | mti = 0; 142 | } 143 | 144 | x = mt[mti++]; 145 | 146 | x ^= (x >> 29) & 0x5555555555555555ULL; 147 | x ^= (x << 17) & 0x71D67FFFEDA60000ULL; 148 | x ^= (x << 37) & 0xFFF7EEE000000000ULL; 149 | x ^= (x >> 43); 150 | 151 | return x; 152 | #else 153 | /* This is the altered Cocoa with Love implementation. */ 154 | size_t i; 155 | size_t j; 156 | unsigned long long result; 157 | 158 | if (context->mti >= NN) {/* generate NN words at one time */ 159 | size_t mid = NN / 2; 160 | unsigned long long stateMid = context->mt[mid]; 161 | unsigned long long x; 162 | unsigned long long y; 163 | 164 | /* NOTE: this "untwist" code is modified from the original to improve 165 | * performance, as described here: 166 | * http://www.cocoawithlove.com/blog/2016/05/19/random-numbers.html 167 | * These modifications are offered for use under the original icense at 168 | * the top of this file. 169 | */ 170 | for (i = 0, j = mid; i != mid - 1; i++, j++) { 171 | x = (context->mt[i] & UM) | (context->mt[i + 1] & LM); 172 | context->mt[i] = context->mt[i + mid] ^ (x >> 1) ^ ((context->mt[i + 1] & 1) * MATRIX_A); 173 | y = (context->mt[j] & UM) | (context->mt[j + 1] & LM); 174 | context->mt[j] = context->mt[j - mid] ^ (y >> 1) ^ ((context->mt[j + 1] & 1) * MATRIX_A); 175 | } 176 | x = (context->mt[mid - 1] & UM) | (stateMid & LM); 177 | context->mt[mid - 1] = context->mt[NN - 1] ^ (x >> 1) ^ ((stateMid & 1) * MATRIX_A); 178 | y = (context->mt[NN - 1] & UM) | (context->mt[0] & LM); 179 | context->mt[NN - 1] = context->mt[mid - 1] ^ (y >> 1) ^ ((context->mt[0] & 1) * MATRIX_A); 180 | 181 | context->mti = 0; 182 | } 183 | 184 | result = context->mt[context->mti]; 185 | context->mti = context->mti + 1; 186 | 187 | result ^= (result >> 29) & 0x5555555555555555ULL; 188 | result ^= (result << 17) & 0x71D67FFFEDA60000ULL; 189 | result ^= (result << 37) & 0xFFF7EEE000000000ULL; 190 | result ^= (result >> 43); 191 | 192 | return result; 193 | #endif 194 | } 195 | 196 | 197 | /* generates a random number on [0, 2^63-1]-interval */ 198 | long long genrand64_int63(struct mt19937_64* context) 199 | { 200 | return (long long)(genrand64_int64(context) >> 1); 201 | } 202 | 203 | /* generates a random number on [0,1]-real-interval */ 204 | double genrand64_real1(struct mt19937_64* context) 205 | { 206 | return (genrand64_int64(context) >> 11) * (1.0/9007199254740991.0); 207 | } 208 | 209 | /* generates a random number on [0,1)-real-interval */ 210 | double genrand64_real2(struct mt19937_64* context) 211 | { 212 | return (genrand64_int64(context) >> 11) * (1.0/9007199254740992.0); 213 | } 214 | 215 | /* generates a random number on (0,1)-real-interval */ 216 | double genrand64_real3(struct mt19937_64* context) 217 | { 218 | return ((genrand64_int64(context) >> 12) + 0.5) * (1.0/4503599627370496.0); 219 | } 220 | -------------------------------------------------------------------------------- /Sources/ReferenceRandomGenerators/xoshiro256starstar.c: -------------------------------------------------------------------------------- 1 | /* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) 2 | 3 | To the extent possible under law, the author has dedicated all copyright 4 | and related and neighboring rights to this software to the public domain 5 | worldwide. This software is distributed without any warranty. 6 | 7 | See . */ 8 | 9 | #include 10 | 11 | /* This is xoshiro256** 1.0, our all-purpose, rock-solid generator. It has 12 | excellent (sub-ns) speed, a state (256 bits) that is large enough for 13 | any parallel application, and it passes all tests we are aware of. 14 | 15 | For generating just floating-point numbers, xoshiro256+ is even faster. 16 | 17 | The state must be seeded so that it is not everywhere zero. If you have 18 | a 64-bit seed, we suggest to seed a splitmix64 generator and use its 19 | output to fill s. */ 20 | 21 | static inline uint64_t rotl(const uint64_t x, int k) { 22 | return (x << k) | (x >> (64 - k)); 23 | } 24 | 25 | typedef struct { 26 | uint64_t s[4]; 27 | } xoshiro_state; 28 | 29 | uint64_t xoshiro_next(xoshiro_state *state) { 30 | const uint64_t result_starstar = rotl(state->s[1] * 5, 7) * 9; 31 | 32 | const uint64_t t = state->s[1] << 17; 33 | 34 | state->s[2] ^= state->s[0]; 35 | state->s[3] ^= state->s[1]; 36 | state->s[1] ^= state->s[2]; 37 | state->s[0] ^= state->s[3]; 38 | 39 | state->s[2] ^= t; 40 | 41 | state->s[3] = rotl(state->s[3], 45); 42 | 43 | return result_starstar; 44 | } 45 | -------------------------------------------------------------------------------- /Tests/CwlUtilsPerformanceTests/CwlDequePerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlDequePerformanceTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/09/13. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | #if DEBUG 26 | let outerCount = 100 27 | #else 28 | let outerCount = 100_000 29 | #endif 30 | 31 | let innerCount = 20 32 | 33 | class DequePerformanceTests: XCTestCase { 34 | func testFIFOPerformance() { 35 | measure { () -> Void in 36 | var accumulator = 0 37 | for _ in 1...outerCount { 38 | var deque = Deque() 39 | for i in 1...innerCount { 40 | deque.append(i) 41 | accumulator ^= (deque.last ?? 0) 42 | } 43 | for _ in 1...innerCount { 44 | accumulator ^= (deque.first ?? 0) 45 | deque.remove(at: 0) 46 | } 47 | } 48 | XCTAssert(accumulator == 0) 49 | } 50 | } 51 | 52 | func testReferenceArrayPerformance() { 53 | measure { () -> Void in 54 | var accumulator = 0 55 | for _ in 1...outerCount { 56 | var deque = Array() 57 | for i in 1...innerCount { 58 | deque.append(i) 59 | accumulator ^= (deque.last ?? 0) 60 | } 61 | for _ in 1...innerCount { 62 | accumulator ^= (deque.first ?? 0) 63 | deque.remove(at: 0) 64 | } 65 | } 66 | XCTAssert(accumulator == 0) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/CwlUtilsPerformanceTests/CwlMutexPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlMutexAdditionalComparisons.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/06/16. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without 9 | // fee is hereby granted, provided that the above copyright notice and this permission notice 10 | // appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 13 | // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 16 | // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 17 | // OF THIS SOFTWARE. 18 | // 19 | 20 | import Foundation 21 | import CwlUtils 22 | import XCTest 23 | 24 | private extension PThreadMutex { 25 | func sync_same_file(f: () throws -> R) rethrows -> R { 26 | pthread_mutex_lock(&underlyingMutex) 27 | defer { pthread_mutex_unlock(&underlyingMutex) } 28 | return try f() 29 | } 30 | } 31 | 32 | public struct DispatchSemaphoreWrapper { 33 | let s = DispatchSemaphore(value: 1) 34 | init() {} 35 | func sync(f: () throws -> R) rethrows -> R { 36 | _ = s.wait(timeout: DispatchTime.distantFuture) 37 | defer { s.signal() } 38 | return try f() 39 | } 40 | } 41 | 42 | let iterations = 10_000_000 43 | 44 | class TestClass { 45 | var testVariable: Int = 0 46 | init() { 47 | } 48 | func increment() { 49 | testVariable += 1 50 | } 51 | } 52 | 53 | class MutexPerformanceTests: XCTestCase { 54 | 55 | func testPThreadSyncCapturingClosurePerformance() { 56 | let mutex = PThreadMutex() 57 | measure { () -> Void in 58 | var total = 0 59 | for _ in 0.. Void in 71 | var total = 0 72 | for _ in 0.. Void in 84 | var total = 0 85 | for _ in 0.. Void in 97 | var total = 0 98 | for _ in 0.. Void in 110 | var total = 0 111 | for _ in 0.. Void in 123 | var total = 0 124 | for _ in 0.. Void in 136 | var total = 0 137 | for _ in 0.. Void in 149 | var total = 0 150 | for _ in 0.. Void in 163 | var total = 0 164 | for _ in 0.. Void in 32 | var sum: UInt64 = 0 33 | for _ in 0..<(PerformanceIterations / 100) { 34 | sum = sum &+ generator.next() 35 | } 36 | XCTAssert(sum != 0) 37 | } 38 | } 39 | 40 | func testArc4RandomDiv10() { 41 | var generator = SystemRandomNumberGenerator() 42 | 43 | measure { () -> Void in 44 | var sum: UInt64 = 0 45 | for _ in 0..<(PerformanceIterations / 10) { 46 | sum = sum &+ generator.next() 47 | } 48 | XCTAssert(sum != 0) 49 | } 50 | } 51 | 52 | func testLfsr258() { 53 | var generator = Lfsr258() 54 | 55 | measure { () -> Void in 56 | var sum: UInt64 = 0 57 | for _ in 0.. Void in 68 | var sum: UInt64 = 0 69 | for _ in 0.. Void in 80 | var sum: UInt64 = 0 81 | for _ in 0.. Void in 92 | var sum: UInt64 = 0 93 | for _ in 0.. Void in 104 | var sum: UInt64 = 0 105 | for _ in 0.. Void in 116 | var sum: UInt64 = 0 117 | for _ in 0.. Void in 128 | var sum: UInt64 = 0 129 | for _ in 0.. UInt64 { 156 | return genrand64_int64(&state) 157 | } 158 | } 159 | 160 | private struct Xoshiro256starstar: RandomNumberGenerator { 161 | var state = { () -> xoshiro_state in var dr = DevRandom(); return xoshiro_state(s: (dr.next(), dr.next(), dr.next(), dr.next())) }() 162 | 163 | init() { 164 | } 165 | 166 | init(seed: (UInt64, UInt64, UInt64, UInt64)) { 167 | self.state.s = seed 168 | } 169 | 170 | mutating func next() -> UInt64 { 171 | return xoshiro_next(&state) 172 | } 173 | } 174 | 175 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlAddressInfoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlAddressInfoTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/26. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class AddressInfoTests: XCTestCase { 26 | func testCallingFunctionIdentifier() { 27 | let name = callingFunctionIdentifier() 28 | #if SWIFT_PACKAGE 29 | XCTAssert(name.hasSuffix("11AddressInfoC0C29testCallingFunctionIdentifieryyF")) 30 | #else 31 | XCTAssert(name.hasSuffix("16AddressInfoTestsC29testCallingFunctionIdentifieryyF")) 32 | #endif 33 | } 34 | 35 | func testSymbolsForCallStackAddresses() { 36 | var b = Thread.callStackSymbols 37 | b.remove(at: 0) 38 | var a = symbolsForCallStack(addresses: callStackReturnAddresses()) 39 | a.remove(at: 0) 40 | XCTAssert(a == b) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlCaseNameCodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlCaseNameCodableTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 9/3/19. 6 | // Copyright © 2019 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | import CwlUtils 12 | 13 | struct A: Codable { 14 | var value: Int = 123 15 | } 16 | 17 | struct B: Codable { 18 | } 19 | 20 | enum Sum: CaseNameCodable { 21 | enum CaseName: String, CaseNameDecoder { 22 | case a 23 | case b 24 | case c 25 | 26 | func decode(from container: KeyedDecodingContainer) throws -> Sum { 27 | switch self { 28 | case .a: return .a(try container.decode(A.self, forKey: self)) 29 | case .b: return .b(B()) 30 | case .c: return .c 31 | } 32 | } 33 | } 34 | 35 | case a(A) 36 | case b(B) 37 | case c 38 | } 39 | 40 | class CaseNameCodableTests: XCTestCase { 41 | func testCaseNameCodableAssociatedValue() { 42 | let x = Sum.a(A()) 43 | let data = try! JSONEncoder().encode(x) 44 | let string = String(data: data, encoding: .utf8)! 45 | XCTAssertEqual(string, #"{"a":{"value":123}}"#) 46 | 47 | let y = try! JSONDecoder().decode(Sum.self, from: data) 48 | if case .a(let a) = y, a.value == 123 { 49 | } else { 50 | XCTFail("Failed to decode as expected") 51 | } 52 | } 53 | 54 | func testCaseNameCodableEmptyAssociatedValue() { 55 | let x = Sum.b(B()) 56 | let data = try! JSONEncoder().encode(x) 57 | let string = String(data: data, encoding: .utf8)! 58 | XCTAssertEqual(string, #"{"b":{}}"#) 59 | 60 | let y = try! JSONDecoder().decode(Sum.self, from: data) 61 | if case .b = y { 62 | } else { 63 | XCTFail("Failed to decode as expected") 64 | } 65 | } 66 | 67 | func testCaseNameCodableNoAssociatedValue() { 68 | let x = Sum.c 69 | let data = try! JSONEncoder().encode(x) 70 | let string = String(data: data, encoding: .utf8)! 71 | XCTAssertEqual(string, #"{"c":null}"#) 72 | 73 | let y = try! JSONDecoder().decode(Sum.self, from: data) 74 | if case .c = y { 75 | } else { 76 | XCTFail("Failed to decode as expected") 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlCollectionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlCollectionTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class CollectionTests: XCTestCase { 26 | func testIsNil() { 27 | let a = Optional>.some(nil) 28 | XCTAssert(a.isNil == false) 29 | XCTAssert(a?.isNil == true) 30 | } 31 | func testAtCollection() { 32 | let a = 7..<9 33 | let at_0 = a.at(7) 34 | let at_2 = a.at(9) 35 | let at_1 = a.at(-1) 36 | XCTAssert(at_0! == 7 && at_1 == nil && at_2 == nil) 37 | } 38 | func testCollectionAppend() { 39 | var a = [0, 1, 2] 40 | a += 3 41 | a += Optional.some(4) 42 | a += nil 43 | XCTAssertEqual(a, [0, 1, 2, 3, 4]) 44 | 45 | let b = [0, 1] 46 | let c = b.appending(2) 47 | XCTAssertEqual(c, [0, 1, 2]) 48 | } 49 | func testRangeAtCollection() { 50 | let a = 7..<29 51 | let at_1 = a.at(5..<6 as Range) 52 | let at_2 = a.at(5..<7 as Range) 53 | let at_3 = a.at(5..<8 as Range) 54 | let at_4 = a.at(12..<15 as Range) 55 | let at_5 = a.at(5..<30 as Range) 56 | let at_6 = a.at(28..<30 as Range) 57 | let at_7 = a.at(29..<30 as Range) 58 | let at_8 = a.at(30..<30 as Range) 59 | XCTAssert(at_1 == 7..<7) 60 | XCTAssert(at_2 == 7..<7) 61 | XCTAssert(at_3 == 7..<8) 62 | XCTAssert(at_4 == 12..<15) 63 | XCTAssert(at_5 == 7..<29) 64 | XCTAssert(at_6 == 28..<29) 65 | XCTAssert(at_7 == 29..<29) 66 | XCTAssert(at_8 == 29..<29) 67 | } 68 | func testCountableRangeAtCollection() { 69 | let a = 7..<29 70 | let at_1 = a.at(5..<6 as CountableRange) 71 | let at_2 = a.at(5..<7 as CountableRange) 72 | let at_3 = a.at(5..<8 as CountableRange) 73 | let at_4 = a.at(12..<15 as CountableRange) 74 | let at_5 = a.at(5..<30 as CountableRange) 75 | let at_6 = a.at(28..<30 as CountableRange) 76 | let at_7 = a.at(29..<30 as CountableRange) 77 | let at_8 = a.at(30..<30 as CountableRange) 78 | XCTAssert(at_1 == 7..<7) 79 | XCTAssert(at_2 == 7..<7) 80 | XCTAssert(at_3 == 7..<8) 81 | XCTAssert(at_4 == 12..<15) 82 | XCTAssert(at_5 == 7..<29) 83 | XCTAssert(at_6 == 28..<29) 84 | XCTAssert(at_7 == 29..<29) 85 | XCTAssert(at_8 == 29..<29) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlDeferredWorkTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlDeferredWorkTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 11/6/16. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | import CwlPreconditionTesting 25 | 26 | #if SWIFT_PACKAGE 27 | import CwlMachBadInstructionHandler 28 | #endif 29 | 30 | class DeferredWorkTests: XCTestCase { 31 | 32 | #if os(iOS) || os(macOS) 33 | override func setUp() { 34 | // Suppress Swift runtime's direct triggering of the debugger. 35 | _swift_reportFatalErrorsToDebugger = false 36 | } 37 | 38 | override func tearDown() { 39 | // Undo our debugger change. 40 | _swift_reportFatalErrorsToDebugger = true 41 | } 42 | #endif 43 | 44 | func testDeferredWork() { 45 | var dw = DeferredWork() 46 | dw.runWork() 47 | } 48 | 49 | func testDeferredWorkDoubleRun() { 50 | var dw = DeferredWork() 51 | dw.runWork() 52 | 53 | let e = catchBadInstruction { 54 | dw.runWork() 55 | } 56 | XCTAssert(e != nil) 57 | } 58 | 59 | func testDeferredWorkAppendRun() { 60 | var a = false 61 | var b = false 62 | var c = false 63 | var dw = DeferredWork { a = true } 64 | let dw2 = DeferredWork { b = true } 65 | 66 | dw.append(dw2) 67 | dw.runWork() 68 | 69 | XCTAssert(a == true && b == true) 70 | 71 | let dw3 = DeferredWork { c = true } 72 | 73 | let e1 = catchBadInstruction { 74 | dw.append(dw3) 75 | } 76 | XCTAssert(e1 != nil) 77 | XCTAssert(c == false) 78 | 79 | let e2 = catchBadInstruction { 80 | var x = false 81 | dw.append { 82 | XCTFail() 83 | x = true 84 | withExtendedLifetime(x) {} 85 | } 86 | } 87 | XCTAssert(e2 != nil) 88 | } 89 | 90 | func testDeferredWorkClosureOnConstruction() { 91 | var reachedPoint1 = false 92 | var dw = DeferredWork { reachedPoint1 = true } 93 | XCTAssert(!reachedPoint1) 94 | dw.runWork() 95 | XCTAssert(reachedPoint1) 96 | } 97 | 98 | func testDeferredWorkAppend() { 99 | var reachedPoint1 = false 100 | var reachedPoint2 = false 101 | var reachedPoint3 = false 102 | var dw = DeferredWork { 103 | XCTAssert(!reachedPoint2) 104 | XCTAssert(!reachedPoint3) 105 | reachedPoint1 = true 106 | } 107 | dw.append { 108 | XCTAssert(reachedPoint1) 109 | XCTAssert(!reachedPoint3) 110 | reachedPoint2 = true 111 | } 112 | dw.append { 113 | XCTAssert(reachedPoint1) 114 | XCTAssert(reachedPoint2) 115 | reachedPoint3 = true 116 | } 117 | XCTAssert(!reachedPoint1) 118 | XCTAssert(!reachedPoint2) 119 | XCTAssert(!reachedPoint3) 120 | dw.runWork() 121 | XCTAssert(reachedPoint1) 122 | XCTAssert(reachedPoint2) 123 | XCTAssert(reachedPoint3) 124 | 125 | var reachedPoint4 = false 126 | var dw2 = DeferredWork() 127 | dw2.append { reachedPoint4 = true } 128 | XCTAssert(!reachedPoint4) 129 | dw2.runWork() 130 | XCTAssert(reachedPoint4) 131 | } 132 | 133 | func testDeferredWorkAppendOther() { 134 | do { 135 | // None plus none 136 | var dw1 = DeferredWork() 137 | let dw2 = DeferredWork() 138 | dw1.append(dw2) 139 | dw1.runWork() 140 | } 141 | 142 | do { 143 | // None plus single 144 | var reachedPoint2 = false 145 | var dw1 = DeferredWork() 146 | let dw2 = DeferredWork { reachedPoint2 = true } 147 | dw1.append(dw2) 148 | XCTAssert(!reachedPoint2) 149 | dw1.runWork() 150 | XCTAssert(reachedPoint2) 151 | } 152 | 153 | do { 154 | // None plus multiple 155 | var reachedPoint1 = false 156 | var reachedPoint2 = false 157 | var dw1 = DeferredWork() 158 | var dw2 = DeferredWork { reachedPoint2 = true } 159 | dw2.append { reachedPoint1 = true } 160 | dw1.append(dw2) 161 | XCTAssert(!reachedPoint1) 162 | XCTAssert(!reachedPoint2) 163 | dw1.runWork() 164 | XCTAssert(reachedPoint1) 165 | XCTAssert(reachedPoint2) 166 | } 167 | 168 | do { 169 | // Single plus none 170 | var reachedPoint1 = false 171 | var dw1 = DeferredWork { reachedPoint1 = true } 172 | let dw2 = DeferredWork() 173 | dw1.append(dw2) 174 | XCTAssert(!reachedPoint1) 175 | dw1.runWork() 176 | XCTAssert(reachedPoint1) 177 | } 178 | 179 | do { 180 | // Single plus single 181 | var reachedPoint1 = false 182 | var reachedPoint2 = false 183 | var dw1 = DeferredWork { reachedPoint1 = true } 184 | let dw2 = DeferredWork { reachedPoint2 = true } 185 | dw1.append(dw2) 186 | XCTAssert(!reachedPoint1) 187 | XCTAssert(!reachedPoint2) 188 | dw1.runWork() 189 | XCTAssert(reachedPoint1) 190 | XCTAssert(reachedPoint2) 191 | } 192 | 193 | do { 194 | // Single plus multiple 195 | var reachedPoint1 = false 196 | var reachedPoint2 = false 197 | var reachedPoint3 = false 198 | var dw1 = DeferredWork { reachedPoint1 = true } 199 | var dw2 = DeferredWork { reachedPoint2 = true } 200 | dw2.append { reachedPoint3 = true } 201 | dw1.append(dw2) 202 | XCTAssert(!reachedPoint1) 203 | XCTAssert(!reachedPoint2) 204 | XCTAssert(!reachedPoint3) 205 | dw1.runWork() 206 | XCTAssert(reachedPoint1) 207 | XCTAssert(reachedPoint2) 208 | XCTAssert(reachedPoint3) 209 | } 210 | 211 | do { 212 | // Multiple plus none 213 | var reachedPoint1 = false 214 | var reachedPoint2 = false 215 | var dw1 = DeferredWork { reachedPoint1 = true } 216 | let dw2 = DeferredWork() 217 | dw1.append { reachedPoint2 = true } 218 | dw1.append(dw2) 219 | XCTAssert(!reachedPoint1) 220 | XCTAssert(!reachedPoint2) 221 | dw1.runWork() 222 | XCTAssert(reachedPoint1) 223 | XCTAssert(reachedPoint2) 224 | } 225 | 226 | do { 227 | // Multiple plus single 228 | var reachedPoint1 = false 229 | var reachedPoint2 = false 230 | var reachedPoint3 = false 231 | var dw1 = DeferredWork { reachedPoint1 = true } 232 | let dw2 = DeferredWork { reachedPoint3 = true } 233 | dw1.append { reachedPoint2 = true } 234 | dw1.append(dw2) 235 | XCTAssert(!reachedPoint1) 236 | XCTAssert(!reachedPoint2) 237 | XCTAssert(!reachedPoint3) 238 | dw1.runWork() 239 | XCTAssert(reachedPoint1) 240 | XCTAssert(reachedPoint2) 241 | XCTAssert(reachedPoint3) 242 | } 243 | 244 | do { 245 | // Multiple plus multiple 246 | var reachedPoint1 = false 247 | var reachedPoint2 = false 248 | var reachedPoint3 = false 249 | var reachedPoint4 = false 250 | var dw1 = DeferredWork { reachedPoint1 = true } 251 | var dw2 = DeferredWork { reachedPoint3 = true } 252 | dw1.append { reachedPoint2 = true } 253 | dw2.append { reachedPoint4 = true } 254 | dw1.append(dw2) 255 | XCTAssert(!reachedPoint1) 256 | XCTAssert(!reachedPoint2) 257 | XCTAssert(!reachedPoint3) 258 | XCTAssert(!reachedPoint4) 259 | dw1.runWork() 260 | XCTAssert(reachedPoint1) 261 | XCTAssert(reachedPoint2) 262 | XCTAssert(reachedPoint3) 263 | XCTAssert(reachedPoint4) 264 | } 265 | } 266 | 267 | func testDeferredWorkWithoutRunning() { 268 | #if DEBUG 269 | let e = catchBadInstruction { 270 | _ = DeferredWork() 271 | } 272 | XCTAssert(e != nil) 273 | #endif 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlDequeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlDequeTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/09/13. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class DequeTests: XCTestCase { 26 | func testAppend() { 27 | var deque = Deque>() 28 | for i in 1...100 { 29 | deque.append(.success(i)) 30 | } 31 | XCTAssert(deque.count == 100) 32 | 33 | for i in 1...2_000 { 34 | deque.remove(at: 0) 35 | deque.append(.success(i)) 36 | } 37 | for i in 1...2_000 { 38 | deque.remove(at: deque.count - 1) 39 | deque.insert(.success(i), at: 0) 40 | } 41 | 42 | var i = 0 43 | while deque.count > 0 { 44 | deque.remove(at: 0) 45 | i += 1 46 | } 47 | XCTAssert(i == 100) 48 | } 49 | 50 | func testRemoveFirst() { 51 | var deque = Deque>() 52 | deque.append(.success(1)) 53 | deque.append(.success(3)) 54 | deque.append(.success(5)) 55 | deque.append(.success(7)) 56 | deque.append(.success(11)) 57 | 58 | XCTAssert(deque.removeFirst().value == 1) 59 | XCTAssert(deque.removeFirst().value == 3) 60 | 61 | deque.append(.success(13)) 62 | deque.append(.success(17)) 63 | deque.append(.success(19)) 64 | deque.append(.success(23)) 65 | 66 | XCTAssert(deque[4].value == 17) 67 | 68 | XCTAssert(deque.removeFirst().value == 5) 69 | 70 | deque.append(.success(29)) 71 | 72 | XCTAssert(deque.removeFirst().value == 7) 73 | XCTAssert(deque.removeFirst().value == 11) 74 | XCTAssert(deque.removeFirst().value == 13) 75 | 76 | deque.append(.success(31)) 77 | deque.append(.success(37)) 78 | deque.append(.success(41)) 79 | deque.append(.success(43)) 80 | XCTAssert(deque.removeFirst().value == 17) 81 | 82 | XCTAssert(deque.count == 7) 83 | } 84 | 85 | func testAdvancingRemoveInsert() { 86 | var deque: Deque = [0, 1, 2] 87 | deque.remove(at: 0) 88 | XCTAssert(Array(deque) == [1, 2]) 89 | deque.insert(3, at: 2) 90 | XCTAssert(Array(deque) == [1, 2, 3]) 91 | deque.remove(at: 0) 92 | XCTAssert(Array(deque) == [2, 3]) 93 | deque.insert(4, at: 2) 94 | XCTAssert(Array(deque) == [2, 3, 4]) 95 | deque.remove(at: 0) 96 | XCTAssert(Array(deque) == [3, 4]) 97 | deque.insert(5, at: 2) 98 | XCTAssert(Array(deque) == [3, 4, 5]) 99 | 100 | XCTAssert(deque[0] == 3) 101 | XCTAssert(deque[1] == 4) 102 | XCTAssert(deque[2] == 5) 103 | } 104 | 105 | func testRegressingRemoveInsert() { 106 | var deque: Deque = [0, 1, 2] 107 | deque.remove(at: 2) 108 | XCTAssert(Array(deque) == [0, 1]) 109 | deque.insert(-1, at: 0) 110 | XCTAssert(Array(deque) == [-1, 0, 1]) 111 | deque.remove(at: 2) 112 | XCTAssert(Array(deque) == [-1, 0]) 113 | deque.insert(-2, at: 0) 114 | XCTAssert(Array(deque) == [-2, -1, 0]) 115 | deque.remove(at: 2) 116 | XCTAssert(Array(deque) == [-2, -1]) 117 | deque.insert(-3, at: 0) 118 | XCTAssert(Array(deque) == [-3, -2, -1]) 119 | 120 | XCTAssert(deque[0] == -3) 121 | XCTAssert(deque[1] == -2) 122 | XCTAssert(deque[2] == -1) 123 | } 124 | 125 | func testRangeReplacing() { 126 | var deque: Deque = [0, 1, 2] 127 | deque.replaceSubrange(1..<1, with: [3, 4, 5, 6, 7, 8]) 128 | XCTAssert(Array(deque) == [0, 3, 4, 5, 6, 7, 8, 1, 2]) 129 | 130 | deque.replaceSubrange(4..<6, with: [9, 10, 11, 12, 13]) 131 | XCTAssert(Array(deque) == [0, 3, 4, 5, 9, 10, 11, 12, 13, 8, 1, 2]) 132 | 133 | deque.replaceSubrange(9..<12, with: [14]) 134 | XCTAssert(Array(deque) == [0, 3, 4, 5, 9, 10, 11, 12, 13, 14]) 135 | 136 | deque.replaceSubrange(0..<8, with: [15, 16]) 137 | XCTAssert(Array(deque) == [15, 16, 13, 14]) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlDispatchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlDispatchTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/07/30. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class DispatchTests: XCTestCase { 26 | let queue = DispatchQueue(label: "") 27 | let value = 3 28 | let key = DispatchSpecificKey() 29 | 30 | override func setUp() { 31 | queue.setSpecific(key: key, value: value) 32 | } 33 | 34 | func testSynchronousSingleTimer() { 35 | do { 36 | // Ensure falling out of scope cancels the timer 37 | _ = DispatchSource.singleTimer(interval: .milliseconds(10), queue: queue) { 38 | XCTFail() 39 | } 40 | } 41 | 42 | let ex = expectation(description: "") 43 | var timer: DispatchSourceTimer? = nil 44 | queue.sync { 45 | timer = DispatchSource.singleTimer(interval: .milliseconds(50), queue: queue) { 46 | // Ensure test occurs on the appropriate queue 47 | XCTAssert(DispatchQueue.getSpecific(key: self.key) != nil) 48 | ex.fulfill() 49 | } 50 | } 51 | waitForExpectations(timeout: 1e2, handler: nil) 52 | withExtendedLifetime(timer) {} 53 | } 54 | 55 | func testSynchronousPeriodicTimer() { 56 | do { 57 | // Ensure falling out of scope cancels the timer 58 | _ = DispatchSource.repeatingTimer(interval: .milliseconds(10), queue: queue) { 59 | XCTFail() 60 | } 61 | } 62 | 63 | let ex = expectation(description: "") 64 | var timer: DispatchSourceTimer? = nil 65 | var fireCount = 0 66 | queue.sync { 67 | timer = DispatchSource.repeatingTimer(interval: .milliseconds(50), queue: queue) { 68 | // Ensure test occurs on the appropriate queue 69 | XCTAssert(DispatchQueue.getSpecific(key: self.key) == self.value) 70 | 71 | fireCount += 1 72 | if fireCount == 3 { 73 | ex.fulfill() 74 | } 75 | } 76 | } 77 | waitForExpectations(timeout: 1e2, handler: nil) 78 | withExtendedLifetime(timer) {} 79 | } 80 | 81 | func testParametricSingleTimer() { 82 | do { 83 | // Ensure falling out of scope cancels the timer 84 | _ = DispatchSource.singleTimer(parameter: 0, interval: .milliseconds(10)) { p in 85 | XCTFail() 86 | } 87 | } 88 | 89 | let ex1 = expectation(description: "") 90 | let ex2 = expectation(description: "") 91 | var timer: DispatchSourceTimer? = nil 92 | var outerMutexComplete = false 93 | 94 | // Test rescheduling a timer during the callback for an earlier scheduling 95 | queue.sync { 96 | timer = DispatchSource.singleTimer(parameter: 1, interval: .milliseconds(1)) { p in 97 | XCTAssert(!outerMutexComplete) 98 | self.queue.sync { 99 | XCTAssert(outerMutexComplete) 100 | XCTAssert(p == 1) 101 | ex1.fulfill() 102 | } 103 | } 104 | Thread.sleep(forTimeInterval: 0.1) 105 | timer?.scheduleOneshot(parameter: 2, interval: .milliseconds(1)) { p in 106 | XCTAssert(outerMutexComplete) 107 | self.queue.sync { 108 | XCTAssert(outerMutexComplete) 109 | XCTAssert(p == 2) 110 | ex2.fulfill() 111 | } 112 | } 113 | Thread.sleep(forTimeInterval: 0.1) 114 | outerMutexComplete = true 115 | } 116 | waitForExpectations(timeout: 1e2, handler: nil) 117 | withExtendedLifetime(timer) {} 118 | } 119 | 120 | func testParametricPeriodicTimer() { 121 | do { 122 | // Ensure falling out of scope cancels the timer 123 | _ = DispatchSource.repeatingTimer(parameter: 0, interval: .milliseconds(10)) { p in 124 | XCTFail() 125 | } 126 | } 127 | 128 | let ex1 = expectation(description: "") 129 | let ex2 = expectation(description: "") 130 | var timer: DispatchSourceTimer? = nil 131 | var outerMutexComplete = false 132 | var fireCount = 0 133 | 134 | // Test rescheduling a timer during the callback for an earlier scheduling 135 | queue.sync { 136 | timer = DispatchSource.repeatingTimer(parameter: 1, interval: .milliseconds(1)) { p in 137 | XCTAssert(!outerMutexComplete) 138 | self.queue.sync { 139 | XCTAssert(outerMutexComplete) 140 | XCTAssert(p == 1) 141 | ex1.fulfill() 142 | } 143 | } 144 | Thread.sleep(forTimeInterval: 0.1) 145 | timer?.scheduleRepeating(parameter: 2, interval: .milliseconds(1)) { p in 146 | XCTAssert(outerMutexComplete) 147 | self.queue.sync { 148 | XCTAssert(outerMutexComplete) 149 | XCTAssert(p == 2) 150 | 151 | fireCount += 1 152 | if fireCount == 3 { 153 | ex2.fulfill() 154 | } 155 | } 156 | } 157 | Thread.sleep(forTimeInterval: 0.1) 158 | outerMutexComplete = true 159 | } 160 | waitForExpectations(timeout: 1e2, handler: nil) 161 | withExtendedLifetime(timer) {} 162 | } 163 | 164 | func testFromTimeInterval() { 165 | let d = DispatchTimeInterval.interval(1.125) 166 | XCTAssert(d.seconds == 1.125) 167 | } 168 | 169 | func testToSeconds() { 170 | let s = DispatchTimeInterval.seconds(1).seconds 171 | XCTAssert(s > 0.99999999 && s < 1.00000001) 172 | 173 | let m = DispatchTimeInterval.milliseconds(1).seconds 174 | XCTAssert(m > 0.99999999e-3 && m < 1.00000001e-3) 175 | 176 | let u = DispatchTimeInterval.microseconds(1).seconds 177 | XCTAssert(u > 0.99999999e-6 && u < 1.00000001e-6) 178 | 179 | let n = DispatchTimeInterval.nanoseconds(1).seconds 180 | XCTAssert(n > 0.99999999e-9 && n < 1.00000001e-9) 181 | } 182 | 183 | func testToNanoseconds() { 184 | let s = DispatchTimeInterval.seconds(1).nanoseconds 185 | XCTAssert(s == Int64(1e9)) 186 | 187 | let m = DispatchTimeInterval.milliseconds(1).nanoseconds 188 | XCTAssert(m == Int64(1e6)) 189 | 190 | let u = DispatchTimeInterval.microseconds(1).nanoseconds 191 | XCTAssert(u == Int64(1e3)) 192 | 193 | let n = DispatchTimeInterval.nanoseconds(1).nanoseconds 194 | XCTAssert(n == Int64(1)) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlExecTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlExecTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class ExecTests: XCTestCase { 26 | func testDirect() { 27 | XCTAssert(Exec.direct.type.isConcurrent == true) 28 | XCTAssert(Exec.direct.type.isImmediateInCurrentContext == true) 29 | 30 | var x = false 31 | Exec.direct.invoke { x = true } 32 | XCTAssert(x, "Block ran") 33 | 34 | var y = false 35 | Exec.direct.invokeSync { y = true } 36 | XCTAssert(y, "Block ran") 37 | 38 | let e2 = expectation(description: "Block 2 not invoked") 39 | Exec.direct.invokeAsync { e2.fulfill() } 40 | 41 | waitForExpectations(timeout: 1e1, handler: nil) 42 | 43 | let serialized = Exec.direct.serialized() 44 | if case .custom = serialized { 45 | } else { 46 | XCTFail() 47 | } 48 | } 49 | 50 | func testMain() { 51 | XCTAssert(Thread.current == Thread.main) 52 | XCTAssert(Exec.main.type.isConcurrent == false) 53 | XCTAssert(Exec.main.type.isImmediateInCurrentContext == true) 54 | 55 | let e3 = expectation(description: "Block not invoked") 56 | Exec.global.invoke { 57 | XCTAssert(Exec.main.type.isImmediateInCurrentContext == false) 58 | 59 | let lock = DispatchQueue(label: "") 60 | var x = false 61 | lock.sync { 62 | Exec.main.invoke { 63 | lock.sync { x = true } 64 | } 65 | XCTAssert(!x, "Block should not yet run") 66 | } 67 | 68 | var y = false 69 | Exec.main.invokeSync { y = true } 70 | XCTAssert(y, "Block ran") 71 | 72 | e3.fulfill() 73 | } 74 | 75 | var x = false 76 | Exec.main.invoke { x = true } 77 | XCTAssert(x, "Block ran") 78 | 79 | var y = false 80 | Exec.main.invokeSync { y = true } 81 | XCTAssert(y, "Block ran") 82 | 83 | let e2 = expectation(description: "Block not invoked") 84 | Exec.main.invokeAsync { e2.fulfill() } 85 | 86 | waitForExpectations(timeout: 1e1, handler: nil) 87 | 88 | let serialized = Exec.main.serialized() 89 | if case .main = serialized { 90 | } else { 91 | XCTFail() 92 | } 93 | } 94 | 95 | func testMainAsync() { 96 | XCTAssert(Exec.mainAsync.type.isConcurrent == false) 97 | XCTAssert(Exec.mainAsync.type.isImmediateInCurrentContext == false) 98 | 99 | let e1 = expectation(description: "Block not invoked") 100 | var run1 = false 101 | Exec.mainAsync.invoke { 102 | run1 = true 103 | e1.fulfill() 104 | } 105 | XCTAssert(run1 == false) 106 | 107 | var run2 = false 108 | Exec.mainAsync.invokeSync { run2 = true } 109 | XCTAssert(run2 == true) 110 | 111 | let e5 = expectation(description: "Block not invoked") 112 | Exec.global.invoke { 113 | let lock = DispatchQueue(label: "") 114 | var x = false 115 | lock.sync { 116 | Exec.mainAsync.invoke { 117 | lock.sync { x = true } 118 | } 119 | } 120 | XCTAssert(!x, "Block should not yet run") 121 | 122 | var y = false 123 | Exec.mainAsync.invokeSync { y = true } 124 | XCTAssert(y, "Block ran") 125 | 126 | Exec.mainAsync.invokeAsync { 127 | e5.fulfill() 128 | } 129 | } 130 | 131 | let e4 = expectation(description: "Block not invoked") 132 | var run3 = false 133 | Exec.mainAsync.invokeAsync { 134 | run3 = true 135 | e4.fulfill() 136 | } 137 | XCTAssert(run3 == false) 138 | 139 | waitForExpectations(timeout: 1e1, handler: nil) 140 | 141 | let serialized = Exec.mainAsync.serialized() 142 | if case .queue(let q, let t) = serialized, case .threadAsync = t { 143 | XCTAssert(q == DispatchQueue.main) 144 | } else { 145 | XCTFail() 146 | } 147 | } 148 | 149 | func testQueue() { 150 | let ec1 = Exec.syncQueue() 151 | guard case .queue(let q1, _) = ec1 else { 152 | fatalError() 153 | } 154 | let sk1 = DispatchSpecificKey() 155 | q1.setSpecific(key: sk1, value: ()) 156 | 157 | XCTAssert(ec1.type.isConcurrent == false) 158 | XCTAssert(ec1.type.isImmediateInCurrentContext == true) 159 | 160 | let ec2 = Exec.asyncQueue() 161 | guard case .queue(let q2, _) = ec2 else { 162 | fatalError() 163 | } 164 | let sk2 = DispatchSpecificKey() 165 | q2.setSpecific(key: sk2, value: ()) 166 | XCTAssert(ec2.type.isConcurrent == false) 167 | XCTAssert(ec2.type.isImmediateInCurrentContext == false) 168 | 169 | var a = false 170 | ec1.invoke() { 171 | XCTAssert(DispatchQueue.getSpecific(key: sk1) != nil) 172 | a = true 173 | } 174 | XCTAssert(a) 175 | 176 | let x1 = expectation(description: "Block not invoked") 177 | ec2.invoke() { 178 | XCTAssert(DispatchQueue.getSpecific(key: sk2) != nil) 179 | x1.fulfill() 180 | } 181 | 182 | let x2 = expectation(description: "Block 2 not invoked") 183 | ec1.invokeAsync() { 184 | XCTAssert(DispatchQueue.getSpecific(key: sk1) != nil) 185 | x2.fulfill() 186 | } 187 | 188 | let x3 = expectation(description: "Block 3 not invoked") 189 | ec2.invokeAsync() { 190 | XCTAssert(DispatchQueue.getSpecific(key: sk2) != nil) 191 | x3.fulfill() 192 | } 193 | 194 | var y1 = false 195 | ec1.invokeSync() { 196 | XCTAssert(DispatchQueue.getSpecific(key: sk1) != nil) 197 | y1 = true 198 | } 199 | XCTAssert(y1) 200 | 201 | var y2 = false 202 | ec2.invokeSync() { 203 | XCTAssert(DispatchQueue.getSpecific(key: sk2) != nil) 204 | y2 = true 205 | } 206 | XCTAssert(y2) 207 | 208 | waitForExpectations(timeout: 1e1, handler: nil) 209 | 210 | let serialized = ec1.serialized() 211 | if case .queue(_, let t) = serialized, case .mutex = t { 212 | } else { 213 | XCTFail() 214 | } 215 | } 216 | 217 | func testGlobal() { 218 | let variants: [Exec] = [.interactive, .user, .global, .utility, .background] 219 | let expectations1: [XCTestExpectation] = variants.map({ v in self.expectation(description: "Block \(v), 1 not invoked") }) 220 | let expectations2: [XCTestExpectation] = variants.map({ v in self.expectation(description: "Block \(v), 2 not invoked") }) 221 | for (i, variant) in variants.enumerated() { 222 | variant.invoke { expectations1[i].fulfill() } 223 | variant.invokeAsync { expectations2[i].fulfill() } 224 | XCTAssert(variant.type.isConcurrent == true) 225 | XCTAssert(variant.type.isImmediateInCurrentContext == false) 226 | 227 | var x = false 228 | variant.invokeSync { x = true } 229 | XCTAssert(x) 230 | 231 | let serialized = variant.serialized() 232 | if case .custom = serialized { 233 | } else { 234 | XCTFail() 235 | } 236 | } 237 | waitForExpectations(timeout: 1e1, handler: nil) 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlKeyValueObserverTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlKeyValueObserverTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | #if RUN_DEPRECATED_TESTS 26 | 27 | class TestObservable: NSObject { 28 | @objc dynamic var someProperty: String 29 | @objc dynamic var unrelatedProperty: NSNumber 30 | @objc dynamic weak var weakProperty: TestObservable? 31 | @objc dynamic var chainedObservable: TestObservable? 32 | 33 | init(value: String) { 34 | someProperty = value 35 | unrelatedProperty = false 36 | super.init() 37 | } 38 | } 39 | 40 | class KeyValueObserverTests: XCTestCase { 41 | func testBasicProperty() { 42 | var results: [(String?, KeyValueObserver.CallbackReason)] = Array() 43 | var kvo2Count = 0 44 | var testObservable1: TestObservable? = TestObservable(value: "empty") 45 | var kvo: KeyValueObserver? 46 | var kvo2: KeyValueObserver? 47 | 48 | if let to1 = testObservable1 { 49 | kvo = KeyValueObserver(source: to1, keyPath: #keyPath(TestObservable.someProperty), options: NSKeyValueObservingOptions.new) { (change: [NSKeyValueChangeKey: Any], reason: KeyValueObserver.CallbackReason) -> Void in 50 | results.append((change[NSKeyValueChangeKey.newKey] as? String, reason)) 51 | } 52 | } 53 | 54 | if let to1 = testObservable1 { 55 | kvo2 = KeyValueObserver(source: to1, keyPath: #keyPath(TestObservable.someProperty), options: NSKeyValueObservingOptions()) { (change: [NSKeyValueChangeKey: Any], reason: KeyValueObserver.CallbackReason) -> Void in 56 | kvo2Count += 1 57 | } 58 | } 59 | 60 | // Test basic observer notifications 61 | testObservable1?.someProperty = "filled" 62 | 63 | // Test source deleted notification 64 | testObservable1 = nil 65 | 66 | XCTAssert(results[0].0 == "filled" && results[0].1 == .valueChanged) 67 | XCTAssert(results[1].0 == nil && results[1].1 == .sourceDeleted) 68 | XCTAssert(results.count == 2) 69 | 70 | XCTAssert(kvo2Count == 2) 71 | 72 | withExtendedLifetime(kvo) {} 73 | withExtendedLifetime(kvo2) {} 74 | } 75 | 76 | func testCancel() { 77 | var results: [(String?, KeyValueObserver.CallbackReason)] = Array() 78 | let testObservable = TestObservable(value: "empty") 79 | let kvo = KeyValueObserver(source: testObservable, keyPath: #keyPath(TestObservable.someProperty)) { (change: [NSKeyValueChangeKey: Any], reason: KeyValueObserver.CallbackReason) -> Void in 80 | results.append((change[NSKeyValueChangeKey.newKey] as? String, reason)) 81 | } 82 | 83 | testObservable.someProperty = "one" 84 | kvo.cancel() 85 | testObservable.someProperty = "two" 86 | 87 | XCTAssert(results.count == 2) 88 | XCTAssert(results.at(0)?.0 == "empty" && results.at(0)?.1 == .valueChanged) 89 | XCTAssert(results.at(1)?.0 == "one" && results.at(1)?.1 == .valueChanged) 90 | } 91 | 92 | func testChainedProperty() { 93 | var results: [(String?, KeyValueObserver.CallbackReason)] = Array() 94 | var testObservable1: TestObservable? = TestObservable(value: "one") 95 | let testObservable2: TestObservable? = TestObservable(value: "two") 96 | let testObservable3: TestObservable? = TestObservable(value: "three") 97 | let testObservable4: TestObservable? = TestObservable(value: "four") 98 | var kvo: KeyValueObserver? 99 | 100 | if let to1 = testObservable1, let to2 = testObservable2, let to3 = testObservable3 { 101 | to2.chainedObservable = to3 102 | to1.chainedObservable = to2 103 | kvo = KeyValueObserver(source: to1, keyPath: "chainedObservable.chainedObservable.someProperty", options: NSKeyValueObservingOptions.new) { (change: [NSKeyValueChangeKey: Any], reason: KeyValueObserver.CallbackReason) -> Void in 104 | results.append((change[NSKeyValueChangeKey.newKey] as? String, reason)) 105 | } 106 | } 107 | 108 | // Test key path observer notifications 109 | testObservable3?.someProperty = "filled" 110 | XCTAssert(results[0].0 == "filled" && results[0].1 == .valueChanged) 111 | 112 | // Test various combinations of path changes 113 | testObservable2?.chainedObservable = testObservable4 114 | XCTAssert(results[1].0 == "four" && results[1].1 == .pathChanged) 115 | 116 | testObservable4?.someProperty = "cleared" 117 | XCTAssert(results[2].0 == "cleared" && results[2].1 == .valueChanged) 118 | 119 | testObservable2?.chainedObservable = nil 120 | XCTAssert(results[3].0 == nil && results[3].1 == .pathChanged) 121 | 122 | testObservable2?.chainedObservable = testObservable3 123 | XCTAssert(results[4].0 == "filled" && results[4].1 == .pathChanged) 124 | 125 | testObservable2?.chainedObservable = nil 126 | XCTAssert(results[5].0 == nil && results[5].1 == .pathChanged) 127 | 128 | testObservable1 = nil 129 | XCTAssert(results[6].0 == nil && results[6].1 == .sourceDeleted) 130 | XCTAssert(results.count == 7) 131 | 132 | withExtendedLifetime(kvo) { () -> Void in } 133 | } 134 | 135 | func testWeakPropertyNotifications() { 136 | var results: [(String?, KeyValueObserver.CallbackReason)] = Array() 137 | let testObservable1: TestObservable? = TestObservable(value: "empty") 138 | var kvo: KeyValueObserver? 139 | 140 | if let to1 = testObservable1 { 141 | kvo = KeyValueObserver(source: to1, keyPath: "weakProperty", options: NSKeyValueObservingOptions.new) { (change: [NSKeyValueChangeKey: Any], reason: KeyValueObserver.CallbackReason) -> Void in 142 | results.append(((change[NSKeyValueChangeKey.newKey] as? TestObservable)?.someProperty, reason)) 143 | } 144 | } 145 | 146 | autoreleasepool() { 147 | var to2: TestObservable? = TestObservable(value: "monkey") 148 | testObservable1?.weakProperty = to2 149 | XCTAssert(results[0].0 == "monkey" && results[0].1 == .valueChanged) 150 | to2 = nil 151 | } 152 | 153 | XCTAssert(results[1].0 == nil && results[1].1 == .valueChanged) 154 | XCTAssert(results.count == 2) 155 | 156 | withExtendedLifetime(kvo!) { () -> Void in } 157 | } 158 | 159 | func testChangeOptions() { 160 | var results: [[AnyHashable: Any]] = Array() 161 | let options: [NSKeyValueObservingOptions] = [NSKeyValueObservingOptions(), NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old, [NSKeyValueObservingOptions.initial, NSKeyValueObservingOptions.new], [NSKeyValueObservingOptions.prior, NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old]] 162 | 163 | for o in options { 164 | let testObservable1 = TestObservable(value: "empty") 165 | let testObservable2 = TestObservable(value: "empty") 166 | let testObservable3 = TestObservable(value: "empty") 167 | testObservable1.chainedObservable = testObservable2 168 | testObservable2.chainedObservable = testObservable3 169 | let kvo = KeyValueObserver(source: testObservable1, keyPath: "chainedObservable.chainedObservable.someProperty", options: o) { (change: [NSKeyValueChangeKey: Any], reason: KeyValueObserver.CallbackReason) -> Void in 170 | results.append(change) 171 | } 172 | 173 | // Test basic observer notifications 174 | testObservable3.someProperty = "filled" 175 | 176 | // Cancel to avoid TargetDeleted notifications 177 | kvo.cancel() 178 | } 179 | 180 | XCTAssert(results.count == 7) 181 | 182 | // allZeros 183 | XCTAssert(Set(results[0].keys) == [NSKeyValueChangeKey.kindKey]) 184 | 185 | // New 186 | XCTAssert(Set(results[1].keys) == [NSKeyValueChangeKey.kindKey, NSKeyValueChangeKey.newKey]) 187 | 188 | // Old 189 | XCTAssert(Set(results[2].keys) == [NSKeyValueChangeKey.kindKey, NSKeyValueChangeKey.oldKey]) 190 | 191 | // Initial 192 | XCTAssert(Set(results[3].keys) == [NSKeyValueChangeKey.kindKey, NSKeyValueChangeKey.newKey]) 193 | 194 | // Initial, Setting 195 | XCTAssert(Set(results[4].keys) == [NSKeyValueChangeKey.kindKey, NSKeyValueChangeKey.newKey]) 196 | 197 | // Prior (prior) 198 | XCTAssert(Set(results[5].keys) == [NSKeyValueChangeKey.kindKey, NSKeyValueChangeKey.notificationIsPriorKey, NSKeyValueChangeKey.oldKey]) 199 | 200 | // Prior (post) 201 | XCTAssert(Set(results[6].keys) == [NSKeyValueChangeKey.kindKey, NSKeyValueChangeKey.newKey, NSKeyValueChangeKey.oldKey]) 202 | } 203 | } 204 | 205 | #endif 206 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlMutexTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlMutexTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class PthreadTests: XCTestCase { 26 | func testPthreadMutex() { 27 | let mutex1 = PThreadMutex() 28 | 29 | let e1 = expectation(description: "Block1 not invoked") 30 | mutex1.sync { 31 | e1.fulfill() 32 | let reenter: Void? = mutex1.trySync() { 33 | XCTFail() 34 | } 35 | XCTAssert(reenter == nil) 36 | } 37 | 38 | let mutex2 = PThreadMutex(type: .recursive) 39 | 40 | let e2 = expectation(description: "Block2 not invoked") 41 | let e3 = expectation(description: "Block3 not invoked") 42 | mutex2.sync { 43 | e2.fulfill() 44 | let reenter: Void? = mutex2.trySync() { 45 | e3.fulfill() 46 | } 47 | XCTAssert(reenter != nil) 48 | } 49 | 50 | let e4 = expectation(description: "Block4 not invoked") 51 | let r = mutex1.sync { () -> Int in 52 | e4.fulfill() 53 | let reenter: Void? = mutex1.trySync() { 54 | XCTFail() 55 | } 56 | XCTAssert(reenter == nil) 57 | return 13 58 | } 59 | XCTAssert(r == 13) 60 | 61 | waitForExpectations(timeout: 0, handler: nil) 62 | } 63 | } 64 | 65 | extension PThreadMutex { 66 | public func sync_generic_param(_ param: inout T, f: (inout T) throws -> R) rethrows -> R { 67 | pthread_mutex_lock(&underlyingMutex) 68 | defer { pthread_mutex_unlock(&underlyingMutex) } 69 | return try f(¶m) 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlOnDeleteTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlOnDeleteTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class OnDeleteTests: XCTestCase { 26 | func testOnDelete() { 27 | var x = false 28 | do { 29 | let o = OnDelete { x = true } 30 | XCTAssert(x == false) 31 | withExtendedLifetime(o) { () in } 32 | } 33 | XCTAssert(x == true) 34 | 35 | var y = false 36 | do { 37 | let o = OnDelete { y = true } 38 | XCTAssert(y == false) 39 | 40 | XCTAssert(o.isValid == true) 41 | 42 | o.invalidate() 43 | 44 | XCTAssert(o.isValid == false) 45 | } 46 | XCTAssert(y == false) 47 | 48 | var z = false 49 | do { 50 | let o = OnDelete { z = true } 51 | XCTAssert(z == false) 52 | o.cancel() 53 | } 54 | XCTAssert(z == true) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlRandomTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlRandomTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/05/17. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | #if SWIFT_PACKAGE 26 | import ReferenceRandomGenerators 27 | #endif 28 | 29 | let VerificationIterations = 1000 30 | 31 | class RandomTests: XCTestCase { 32 | func genericTest(generator: inout Generator, testUnique: Bool = true) { 33 | let a = generator.next() 34 | let b = generator.next() 35 | let c = generator.next() 36 | let d = generator.next() 37 | 38 | if testUnique { 39 | XCTAssert(a != b && a != c && a != d && b != c && b != d && c != d, "Technically, we *could* get a collision...") 40 | } 41 | } 42 | 43 | func testDevRandom() { 44 | var generator = DevRandom() 45 | genericTest(generator: &generator) 46 | } 47 | 48 | func testArc4Random() { 49 | var generator = SystemRandomNumberGenerator() 50 | genericTest(generator: &generator) 51 | } 52 | 53 | func testLfsr258() { 54 | var generator = Lfsr258() 55 | genericTest(generator: &generator) 56 | } 57 | 58 | func testLfsr176() { 59 | var generator = Lfsr176() 60 | genericTest(generator: &generator) 61 | } 62 | 63 | func testConstantNonRandom() { 64 | var generator = ConstantNonRandom() 65 | genericTest(generator: &generator, testUnique: false) 66 | } 67 | 68 | func testXoshiro() { 69 | var generator = Xoshiro() 70 | genericTest(generator: &generator) 71 | 72 | // Test Xoshiro against the reference implementation to verify output 73 | var g1 = Xoshiro(seed: (12345678, 87654321, 10293847, 29384756)) 74 | var g2 = Xoshiro256starstar(seed: (12345678, 87654321, 10293847, 29384756)) 75 | for _ in 0.. UInt64 { 117 | return state 118 | } 119 | } 120 | 121 | public struct Lfsr258: RandomNumberGenerator { 122 | public typealias StateType = (UInt64, UInt64, UInt64, UInt64, UInt64) 123 | 124 | static let k: (UInt64, UInt64, UInt64, UInt64, UInt64) = (1, 9, 12, 17, 23) 125 | static let q: (UInt64, UInt64, UInt64, UInt64, UInt64) = (1, 24, 3, 5, 3) 126 | static let s: (UInt64, UInt64, UInt64, UInt64, UInt64) = (10, 5, 29, 23, 8) 127 | 128 | var state: StateType = (0, 0, 0, 0, 0) 129 | 130 | public init() { 131 | var r = DevRandom() 132 | repeat { 133 | r.randomize(value: &state.0) 134 | } while state.0 < Lfsr258.k.0 135 | repeat { 136 | r.randomize(value: &state.1) 137 | } while state.1 < Lfsr258.k.1 138 | repeat { 139 | r.randomize(value: &state.2) 140 | } while state.2 < Lfsr258.k.2 141 | repeat { 142 | r.randomize(value: &state.3) 143 | } while state.3 < Lfsr258.k.3 144 | repeat { 145 | r.randomize(value: &state.4) 146 | } while state.4 < Lfsr258.k.4 147 | } 148 | 149 | public init(seed: StateType) { 150 | self.state = seed 151 | } 152 | 153 | public mutating func next() -> UInt64 { 154 | // Constants from "Tables of Maximally-Equidistributed Combined LFSR Generators" by Pierre L'Ecuyer: 155 | // http://www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme2.ps 156 | let l: UInt64 = 64 157 | let x0 = (((state.0 << Lfsr258.q.0) ^ state.0) >> (l - Lfsr258.k.0 - Lfsr258.s.0)) 158 | state.0 = ((state.0 & (UInt64.max << Lfsr258.k.0)) << Lfsr258.s.0) | x0 159 | let x1 = (((state.1 << Lfsr258.q.1) ^ state.1) >> (l - Lfsr258.k.1 - Lfsr258.s.1)) 160 | state.1 = ((state.1 & (UInt64.max << Lfsr258.k.1)) << Lfsr258.s.1) | x1 161 | let x2 = (((state.2 << Lfsr258.q.2) ^ state.2) >> (l - Lfsr258.k.2 - Lfsr258.s.2)) 162 | state.2 = ((state.2 & (UInt64.max << Lfsr258.k.2)) << Lfsr258.s.2) | x2 163 | let x3 = (((state.3 << Lfsr258.q.3) ^ state.3) >> (l - Lfsr258.k.3 - Lfsr258.s.3)) 164 | state.3 = ((state.3 & (UInt64.max << Lfsr258.k.3)) << Lfsr258.s.3) | x3 165 | let x4 = (((state.4 << Lfsr258.q.4) ^ state.4) >> (l - Lfsr258.k.4 - Lfsr258.s.4)) 166 | state.4 = ((state.4 & (UInt64.max << Lfsr258.k.4)) << Lfsr258.s.4) | x4 167 | return (state.0 ^ state.1 ^ state.2 ^ state.3 ^ state.4) 168 | } 169 | } 170 | 171 | public struct Lfsr176: RandomNumberGenerator { 172 | public typealias StateType = (UInt64, UInt64, UInt64) 173 | 174 | static let k: (UInt64, UInt64, UInt64) = (1, 6, 9) 175 | static let q: (UInt64, UInt64, UInt64) = (5, 19, 24) 176 | static let s: (UInt64, UInt64, UInt64) = (24, 13, 17) 177 | 178 | var state: StateType = (0, 0, 0) 179 | 180 | public init() { 181 | var r = DevRandom() 182 | repeat { 183 | r.randomize(value: &state.0) 184 | } while state.0 < Lfsr176.k.0 185 | repeat { 186 | r.randomize(value: &state.1) 187 | } while state.1 < Lfsr176.k.1 188 | repeat { 189 | r.randomize(value: &state.2) 190 | } while state.2 < Lfsr176.k.2 191 | } 192 | 193 | public init(seed: StateType) { 194 | self.state = seed 195 | } 196 | 197 | public mutating func next() -> UInt64 { 198 | // Constants from "Tables of Maximally-Equidistributed Combined LFSR Generators" by Pierre L'Ecuyer: 199 | // http://www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme2.ps 200 | let l: UInt64 = 64 201 | let x0 = (((state.0 << Lfsr176.q.0) ^ state.0) >> (l - Lfsr176.k.0 - Lfsr176.s.0)) 202 | state.0 = ((state.0 & (UInt64.max << Lfsr176.k.0)) << Lfsr176.s.0) | x0 203 | let x1 = (((state.1 << Lfsr176.q.1) ^ state.1) >> (l - Lfsr176.k.1 - Lfsr176.s.1)) 204 | state.1 = ((state.1 & (UInt64.max << Lfsr176.k.1)) << Lfsr176.s.1) | x1 205 | let x2 = (((state.2 << Lfsr176.q.2) ^ state.2) >> (l - Lfsr176.k.2 - Lfsr176.s.2)) 206 | state.2 = ((state.2 & (UInt64.max << Lfsr176.k.2)) << Lfsr176.s.2) | x2 207 | return (state.0 ^ state.1 ^ state.2) 208 | } 209 | } 210 | 211 | 212 | // 213 | // NOTE: the following two implementations are duplicated in CwlRandomPerformanceTests.swift and in CwlRandomTests.swift file. 214 | // The reason for this duplication is that CwlRandomPerformanceTests.swift is normally excluded from the build, so CwlRandomTests.swift can't rely on it. But CwlRandomPerformanceTests.swift needs these structs included locally because they need to be inlined (to make the performance comparison fair) and whole module optimization is disabled for this testing bundle (to allow testing across module boundaries where desired). 215 | // 216 | 217 | private struct MT19937_64: RandomNumberGenerator { 218 | typealias WordType = UInt64 219 | var state = mt19937_64() 220 | 221 | init() { 222 | var dr = DevRandom() 223 | init_genrand64(&state, dr.next()) 224 | } 225 | 226 | init(seed: UInt64) { 227 | init_genrand64(&state, seed) 228 | } 229 | 230 | mutating func next() -> UInt64 { 231 | return genrand64_int64(&state) 232 | } 233 | } 234 | 235 | private struct Xoshiro256starstar: RandomNumberGenerator { 236 | var state = { () -> xoshiro_state in var dr = DevRandom(); return xoshiro_state(s: (dr.next(), dr.next(), dr.next(), dr.next())) }() 237 | 238 | init() { 239 | } 240 | 241 | init(seed: (UInt64, UInt64, UInt64, UInt64)) { 242 | self.state.s = seed 243 | } 244 | 245 | mutating func next() -> UInt64 { 246 | return xoshiro_next(&state) 247 | } 248 | } 249 | 250 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlStackFrameTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlStackFrameTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/26. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class StackFrameTests: XCTestCase { 26 | func testCallStackReturnAddresses() { 27 | var a = callStackReturnAddresses() 28 | a.remove(at: 0) 29 | var b = Thread.callStackReturnAddresses.map { $0.uintValue } 30 | b.remove(at: 0) 31 | XCTAssert(a == b) 32 | 33 | a = callStackReturnAddresses(skip: 2) 34 | b.remove(at: 0) 35 | XCTAssert(a == b) 36 | 37 | a = callStackReturnAddresses(skip: 2, maximumAddresses: 10) 38 | XCTAssert(a == Array(b[0..<10])) 39 | } 40 | 41 | func testNSThreadCallStackReturnAddressesPerformance() { 42 | measure { 43 | var traces = [[NSNumber]]() 44 | for _ in 0..<1000 { 45 | traces.append(Thread.callStackReturnAddresses) 46 | } 47 | } 48 | } 49 | 50 | func testCallStackReturnAddressesPerformance() { 51 | measure { 52 | var traces = [[UInt]]() 53 | for _ in 0..<1000 { 54 | traces.append(callStackReturnAddresses()) 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlSysctlTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlSysctlTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2016/02/03. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class SysctlTests: XCTestCase { 26 | func testSysctl() { 27 | let hostName = Sysctl.hostName 28 | XCTAssert(hostName != "") 29 | 30 | let machine = Sysctl.machine 31 | 32 | #if arch(x86_64) 33 | XCTAssert(machine == "x86_64") 34 | #else 35 | XCTAssert(machine != "") 36 | #endif 37 | 38 | let model = Sysctl.model 39 | XCTAssert(model != "") 40 | 41 | let osRelease = Sysctl.osRelease 42 | XCTAssert(osRelease != "") 43 | 44 | let osType = Sysctl.osType 45 | XCTAssert(osType == "Darwin") 46 | 47 | let osVersion = Sysctl.osVersion 48 | XCTAssert(osVersion != "") 49 | 50 | let version = Sysctl.version 51 | XCTAssert(version.hasPrefix("Darwin Kernel Version")) 52 | 53 | let activeCPUs = Sysctl.activeCPUs 54 | XCTAssert(activeCPUs > 0) 55 | 56 | #if os(macOS) 57 | let osRev = Sysctl.osRev 58 | XCTAssert(osRev != 0) 59 | 60 | let cpuFreq = Sysctl.cpuFreq 61 | XCTAssert(cpuFreq > 1_000_000_000) 62 | 63 | let memSize = Sysctl.memSize 64 | XCTAssert(memSize > 1_000_000_000) 65 | #endif 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlUnanticipatedErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlUnanticipatedErrorTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/03/05. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | enum TestCode: Int, Error { 26 | case zeroValue = 0 27 | case oneValue = 1 28 | case testValue = 2 29 | } 30 | 31 | #if os(iOS) || os(macOS) 32 | 33 | class UnanticipatedErrorTests: XCTestCase { 34 | func testUnanticipatedError() { 35 | let e = TestCode.testValue.withUnanticipatedErrorRecoveryAttempter() 36 | 37 | // NOTE: error codes equal raw values only if the raw values are zero based with no gaps 38 | XCTAssert(e.code == TestCode.testValue.rawValue) 39 | 40 | let userInfo = e.userInfo 41 | if let callStackSymbols = userInfo[UnanticipatedErrorRecoveryAttempter.callStackSymbols] as? [String] { 42 | XCTAssert(callStackSymbols.count > 1, "No call stack symbols present") 43 | } else { 44 | XCTFail("Call stack symbols not present") 45 | } 46 | 47 | XCTAssertNotNil(userInfo[NSLocalizedRecoverySuggestionErrorKey]) 48 | XCTAssertNotNil(userInfo[NSLocalizedRecoveryOptionsErrorKey]) 49 | XCTAssertNotNil(userInfo[NSRecoveryAttempterErrorKey]) 50 | 51 | XCTAssert((userInfo[NSLocalizedRecoveryOptionsErrorKey] as? [Any])?.count == 2, "Wrong number of options") 52 | 53 | let attempter = userInfo[NSRecoveryAttempterErrorKey] as? NSObject 54 | let backup = pasteboardBackup() 55 | attempter?.attemptRecovery(fromError: e as NSError, optionIndex: 1) 56 | let clipboardString = pasteboardString() 57 | 58 | // The following (ugly) compile-time conditional is a best effort at testing for the simulator (no actual simulator macro is provided) 59 | XCTAssert(clipboardString?.range(of: e.localizedRecoverySuggestion!) != nil, "Simulator pasteboard will fail prior to iOS 10/Xcode 8") 60 | 61 | restorePasteboard(backup) 62 | } 63 | } 64 | 65 | #endif 66 | 67 | #if os(iOS) 68 | 69 | func pasteboardBackup() -> [Dictionary] { 70 | return ((UIPasteboard.general.items as NSArray).copy() as? [Dictionary]) ?? Array>() 71 | } 72 | 73 | func restorePasteboard(_ items: [Dictionary]) { 74 | UIPasteboard.general.items = items 75 | } 76 | 77 | func pasteboardString() -> String? { 78 | return UIPasteboard.general.string 79 | } 80 | 81 | #elseif os(macOS) 82 | 83 | func pasteboardBackup() -> [NSPasteboardItem] { 84 | #if swift(>=4) 85 | let general = NSPasteboard.general 86 | #else 87 | let general = NSPasteboard.general() 88 | #endif 89 | return general.pasteboardItems?.map { item in 90 | let backupItem = NSPasteboardItem() 91 | for type in item.types { 92 | if let data = (item.data(forType: type) as NSData?)?.copy() as? Data { 93 | backupItem.setData(data, forType: type) 94 | } 95 | } 96 | return backupItem 97 | } ?? [] 98 | } 99 | 100 | func restorePasteboard(_ items: [NSPasteboardItem]) { 101 | #if swift(>=4) 102 | let general = NSPasteboard.general 103 | #else 104 | let general = NSPasteboard.general() 105 | #endif 106 | general.clearContents() 107 | general.writeObjects(items) 108 | } 109 | 110 | func pasteboardString() -> String? { 111 | #if swift(>=4) 112 | return NSPasteboard.general.string(forType: NSPasteboard.PasteboardType.string) 113 | #else 114 | return NSPasteboard.general().string(forType: NSPasteboardTypeString) 115 | #endif 116 | } 117 | 118 | #endif 119 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlUtilsTests-BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // CwlUtilsBridgingHeader.h 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and distribute this software for any purpose with or without 9 | // fee is hereby granted, provided that the above copyright notice and this permission notice 10 | // appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS 13 | // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 14 | // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 16 | // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 17 | // OF THIS SOFTWARE. 18 | // 19 | 20 | #ifndef CwlUtilsTests_BridgingHeader_h 21 | #define CwlUtilsTests_BridgingHeader_h 22 | 23 | #import 24 | 25 | #import "ReferenceRandomGenerators.h" 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/CwlWrapperTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlWrapperTests.swift 3 | // CwlUtils 4 | // 5 | // Created by Matt Gallagher on 2015/02/03. 6 | // Copyright © 2015 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Foundation 22 | import XCTest 23 | import CwlUtils 24 | 25 | class WrapperTests: XCTestCase { 26 | func testBox() { 27 | let b = Box(5) 28 | XCTAssert(b.value == 5) 29 | } 30 | 31 | func testAtomicBox() { 32 | let a = AtomicBox(5) 33 | let b = a 34 | a.mutate { $0 = 3 } 35 | XCTAssert(a.value == 3 && b.value == 3) 36 | } 37 | 38 | func testLazy() { 39 | var x = Lazy(valueConstructor: { 3 }) 40 | XCTAssert(!x.isInitialized) 41 | XCTAssert(x.value() == 3) 42 | XCTAssert(x.isInitialized) 43 | } 44 | 45 | func testWeak() { 46 | let q = NSObject() 47 | let w = { () -> Weak in 48 | let o = NSObject() 49 | let p = NSObject() 50 | let innerW = Weak(o) 51 | XCTAssert(innerW.value != nil) 52 | XCTAssert(innerW.contains(o)) 53 | XCTAssert(!innerW.contains(p)) 54 | return innerW 55 | }() 56 | XCTAssert(w.value == nil) 57 | XCTAssert(!w.contains(q)) 58 | } 59 | 60 | func testUnownedWrapper() { 61 | var uow: Unowned? = nil 62 | let w = { () -> Weak in 63 | let o = NSObject() 64 | let innerW = Weak(o) 65 | uow = Unowned(o) 66 | XCTAssert(innerW.value != nil) 67 | XCTAssert(uow != nil) 68 | XCTAssert(uow!.value == o) 69 | return innerW 70 | }() 71 | XCTAssert(w.value == nil) 72 | XCTAssert(uow != nil) 73 | } 74 | 75 | func testPossiblyWeak() { 76 | let q = NSObject() 77 | let w = { () -> PossiblyWeak in 78 | let o = NSObject() 79 | let p = NSObject() 80 | let innerW = PossiblyWeak.strong(o) 81 | XCTAssert(innerW.value != nil) 82 | XCTAssert(innerW.contains(o)) 83 | XCTAssert(!innerW.contains(p)) 84 | return innerW 85 | }() 86 | XCTAssert(w.value != nil) 87 | XCTAssert(!w.contains(q)) 88 | 89 | let x = { () -> PossiblyWeak in 90 | let o = NSObject() 91 | let p = NSObject() 92 | let innerW = PossiblyWeak.weak(Weak(o)) 93 | XCTAssert(innerW.value != nil) 94 | XCTAssert(innerW.contains(o)) 95 | XCTAssert(!innerW.contains(p)) 96 | return innerW 97 | }() 98 | XCTAssert(x.value == nil) 99 | XCTAssert(!x.contains(q)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/CwlUtilsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/CwlUtils_iOSTestApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CwlUtils_iOSTestApp 4 | // 5 | // Created by Matt Gallagher on 2016/18/04. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | // 21 | 22 | import UIKit 23 | 24 | @UIApplicationMain 25 | class AppDelegate: UIResponder, UIApplicationDelegate { 26 | var window: UIWindow? 27 | 28 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 29 | // Override point for customization after application launch. 30 | return true 31 | } 32 | 33 | func applicationWillResignActive(_ application: UIApplication) { 34 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 35 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 36 | } 37 | 38 | func applicationDidEnterBackground(_ application: UIApplication) { 39 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | func applicationWillEnterForeground(_ application: UIApplication) { 44 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 45 | } 46 | 47 | func applicationDidBecomeActive(_ application: UIApplication) { 48 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 49 | } 50 | 51 | func applicationWillTerminate(_ application: UIApplication) { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /Tests/CwlUtils_iOSTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Tests/CwlUtils_iOSTestApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Tests/CwlUtils_iOSTestApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Tests/CwlUtils_iOSTestApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Tests/CwlUtils_iOSTestApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CwlUtils_iOSTestApp 4 | // 5 | // Created by Matt Gallagher on 2016/18/04. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import UIKit 22 | import CwlUtils 23 | 24 | func showAlert(error: NSError) { 25 | let alert = UIAlertController(title: error.localizedDescription, message: error.localizedFailureReason, preferredStyle: UIAlertController.Style.alert) 26 | alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: UIAlertAction.Style.default, handler: nil)) 27 | UIApplication.shared.windows[0].rootViewController!.present(alert, animated: true, completion: nil) 28 | } 29 | 30 | class ViewController: UIViewController { 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | // Do any additional setup after loading the view, typically from a nib. 34 | } 35 | 36 | override func didReceiveMemoryWarning() { 37 | super.didReceiveMemoryWarning() 38 | // Dispose of any resources that can be recreated. 39 | } 40 | 41 | func process(data: NSData) { 42 | } 43 | 44 | func someProcessingTask1(path: String) { 45 | do { 46 | let data = try NSData(contentsOfFile: path, options: .mappedIfSafe) 47 | process(data: data) 48 | } catch { 49 | showAlert(error: error as NSError) 50 | } 51 | } 52 | 53 | @IBAction func someUserAction1(_ sender: AnyObject) { 54 | someProcessingTask1(path: "/invalid/path") 55 | } 56 | 57 | func someProcessingTask2(path: String) throws { 58 | try rethrowUnanticipated { 59 | let data = try NSData(contentsOfFile: path, options: .mappedIfSafe) 60 | process(data: data) 61 | } 62 | } 63 | 64 | @IBAction func someUserAction2(_ sender: AnyObject) { 65 | do { 66 | try someProcessingTask2(path: "/invalid/path") 67 | } catch { 68 | presentError(error as NSError) 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Tests/CwlUtils_iOSTestAppUITests/CwlUtils_iOSTestAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlUtils_iOSTestAppUITests.swift 3 | // CwlUtils_iOSTestAppUITests 4 | // 5 | // Created by Matt Gallagher on 2016/18/04. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import XCTest 22 | 23 | class CwlUtils_iOSTestAppUITests: XCTestCase { 24 | override func setUp() { 25 | super.setUp() 26 | 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | 29 | // In UI tests it is usually best to stop immediately when a failure occurs. 30 | continueAfterFailure = false 31 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 32 | XCUIApplication().launch() 33 | 34 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 35 | } 36 | 37 | override func tearDown() { 38 | // Put teardown code here. This method is called after the invocation of each test method in the class. 39 | super.tearDown() 40 | } 41 | 42 | func testAlerts() { 43 | let app = XCUIApplication() 44 | app.buttons["Trigger site handled error"].tap() 45 | 46 | let theFilePathCouldnTBeOpenedBecauseThereIsNoSuchFileAlert = app.alerts["The file “path” couldn’t be opened because there is no such file."] 47 | let okButton = theFilePathCouldnTBeOpenedBecauseThereIsNoSuchFileAlert.buttons["OK"] 48 | okButton.tap() 49 | 50 | let triggerUnanticipatedErrorButton = app.buttons["Trigger unanticipated error"] 51 | triggerUnanticipatedErrorButton.tap() 52 | okButton.tap() 53 | 54 | triggerUnanticipatedErrorButton.tap() 55 | let copyDetailsButton = theFilePathCouldnTBeOpenedBecauseThereIsNoSuchFileAlert.buttons["Copy details"] 56 | copyDetailsButton.tap() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/CwlUtils_macOSTestApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CwlUtils_macOSTestApp 4 | // 5 | // Created by Matt Gallagher on 2017/01/31. 6 | // Copyright © 2017 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Cocoa 22 | 23 | @NSApplicationMain 24 | class AppDelegate: NSObject, NSApplicationDelegate { 25 | 26 | 27 | 28 | func applicationDidFinishLaunching(_ aNotification: Notification) { 29 | // Insert code here to initialize your application 30 | } 31 | 32 | func applicationWillTerminate(_ aNotification: Notification) { 33 | // Insert code here to tear down your application 34 | } 35 | 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Tests/CwlUtils_macOSTestApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Tests/CwlUtils_macOSTestApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSMainStoryboardFile 26 | Main 27 | NSPrincipalClass 28 | NSApplication 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/CwlUtils_macOSTestApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CwlUtils_macOSTestApp 4 | // 5 | // Created by Matt Gallagher on 2017/01/31. 6 | // Copyright © 2017 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import Cocoa 22 | import CwlUtils 23 | 24 | func showAlert(error: Error) { 25 | NSAlert(error: error).runModal() 26 | } 27 | 28 | class ViewController: NSViewController { 29 | 30 | let b = NSButton() 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | // Do any additional setup after loading the view. 36 | } 37 | 38 | override var representedObject: Any? { 39 | didSet { 40 | // Update the view, if already loaded. 41 | } 42 | } 43 | 44 | func process(data: NSData) { 45 | } 46 | 47 | func someProcessingTask1(path: String) { 48 | do { 49 | let data = try NSData(contentsOfFile: path, options: .mappedIfSafe) 50 | process(data: data) 51 | } catch { 52 | showAlert(error: error) 53 | } 54 | } 55 | 56 | @IBAction func someUserAction1(_ sender: AnyObject) { 57 | someProcessingTask1(path: "/invalid/path") 58 | } 59 | 60 | func someProcessingTask2(path: String) throws { 61 | try rethrowUnanticipated { 62 | let data = try NSData(contentsOfFile: path, options: .mappedIfSafe) 63 | process(data: data) 64 | } 65 | } 66 | 67 | @IBAction func someUserAction2(_ sender: AnyObject) { 68 | do { 69 | try someProcessingTask2(path: "/invalid/path") 70 | } catch { 71 | presentError(error) 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /Tests/CwlUtils_macOSTestAppUITests/CwlUtils_macOSTestAppUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CwlUtils_macOSTestAppUITests.swift 3 | // CwlUtils_macOSTestAppUITests 4 | // 5 | // Created by Matt Gallagher on 2016/18/04. 6 | // Copyright © 2016 Matt Gallagher ( https://www.cocoawithlove.com ). All rights reserved. 7 | // 8 | // Permission to use, copy, modify, and/or distribute this software for any 9 | // purpose with or without fee is hereby granted, provided that the above 10 | // copyright notice and this permission notice appear in all copies. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 15 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 18 | // IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | // 20 | 21 | import XCTest 22 | 23 | class CwlUtils_macOSTestAppUITests: XCTestCase { 24 | override func setUp() { 25 | super.setUp() 26 | 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | 29 | // In UI tests it is usually best to stop immediately when a failure occurs. 30 | continueAfterFailure = false 31 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 32 | XCUIApplication().launch() 33 | 34 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 35 | } 36 | 37 | override func tearDown() { 38 | // Put teardown code here. This method is called after the invocation of each test method in the class. 39 | super.tearDown() 40 | } 41 | 42 | func testAlerts() { 43 | let app = XCUIApplication() 44 | let cwlutilsOsxharnessWindow = app.windows["Window"] 45 | let alertContents = app.dialogs["alert"] 46 | 47 | cwlutilsOsxharnessWindow.buttons["Trigger site handled error"].click() 48 | alertContents.buttons["OK"].click() 49 | 50 | let unanticipatedButton = cwlutilsOsxharnessWindow.buttons["Trigger unanticipated error"] 51 | 52 | unanticipatedButton.click() 53 | alertContents.buttons["OK"].click() 54 | 55 | unanticipatedButton.click() 56 | alertContents.buttons["Copy details"].click() 57 | } 58 | } 59 | --------------------------------------------------------------------------------