diff --git a/Sources/SemVer/Semantic Version + Codable.swift b/Sources/SemVer/Semantic Version + Codable.swift new file mode 100644 index 0000000..c63a395 --- /dev/null +++ b/Sources/SemVer/Semantic Version + Codable.swift @@ -0,0 +1,47 @@ +// +// Semantic Version + Codable.swift +// SemVer +// +// Created by Ky Leggiero on 2021-10-18. +// + +import Foundation + + + +extension SemVer: Encodable { + + /// Encodes this semantic version to the given container + /// - Throws any error which occurs when encoding + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) + } +} + + + +extension SemVer: Decodable { + + /// Decodes a semantic version from its encoded form + /// - Throws any error which occurs when decoding, including a `SemVer.DecodingError` if that's specialized to Semantic Version decoding + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let raw = try container.decode(String.self) + + guard let parsed = Self.init(raw) else { + throw DecodingError.invalidSemanticVersion(encodedForm: raw) + } + + self = parsed + } + + + + /// An error that migth occur when decoding a semantic version + enum DecodingError: Error { + + /// The encoded form of the semantic version was an invalid semantic version + case invalidSemanticVersion(encodedForm: String) + } +} diff --git a/Sources/SemVer/Testing tools.swift b/Sources/SemVer/Testing tools.swift index 4db1ba7..3220707 100644 --- a/Sources/SemVer/Testing tools.swift +++ b/Sources/SemVer/Testing tools.swift @@ -10,10 +10,32 @@ import Foundation -/// A bodge to change behavior based on whether a test suite is running. -/// -/// Ideally, this would be a compile-time flag. However, it seems that isn't being respected with my current setup, so I don't trust it. -internal var isTesting = false +/// Some tools to help us better-test this module +internal enum TestingTools { + // Empty on-purpose; all members are in static extensions +} + + + +internal extension TestingTools { + + /// A bodge to change behavior based on whether a test suite is running. + /// + /// Ideally, this would be a compile-time flag. However, it seems that isn't being respected with my current setup, so I don't trust it. + static var isTesting = false + + + /// In non-test runs, this indicates that an internal sanity check failed. + /// + /// To perform an assertion in test runs as well, use `Swift.assertionFailure` instead. + /// + /// - Parameter message: _optional_ - A string to print in a playground or `-Onone` non-test build. Defaults to an empty string. + @inline(__always) + static func assertionFailure(_ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { + guard !isTesting else { return } + Swift.assertionFailure(message(), file: file, line: line) + } +} @@ -24,6 +46,5 @@ internal var isTesting = false /// - Parameter message: _optional_ - A string to print in a playground or `-Onone` non-test build. Defaults to an empty string. @inline(__always) internal func assertionFailure(_ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { - guard !isTesting else { return } - Swift.assertionFailure(message(), file: file, line: line) + TestingTools.assertionFailure(message(), file: file, line: line) } diff --git a/Tests/SemVerTests/SemVer mutations tests.swift b/Tests/SemVerTests/SemVer mutations tests.swift index 9d1fffc..938b1f2 100644 --- a/Tests/SemVerTests/SemVer mutations tests.swift +++ b/Tests/SemVerTests/SemVer mutations tests.swift @@ -7,15 +7,11 @@ // import XCTest -@testable import SemVer +import SemVer -class SemVerMutationTests: XCTestCase { - - override func setUp() { - isTesting = true - } +class SemVerMutationTests: SemVerTestClass { // MARK: - Increment diff --git a/Tests/SemVerTests/SemVer+Codable Tests.swift b/Tests/SemVerTests/SemVer+Codable Tests.swift new file mode 100644 index 0000000..866f21a --- /dev/null +++ b/Tests/SemVerTests/SemVer+Codable Tests.swift @@ -0,0 +1,104 @@ +// +// SemVer+Codable Tests.swift +// +// +// Created by Ky Leggiero on 2021-10-18. +// + +import XCTest +import SemVer + + + +class SemVerCodableTests: SemVerTestClass { + + func testEncode() throws { + let encoder = JSONEncoder() + + + func encode(_ semVer: SemVer) throws -> String { + String(data: try encoder.encode(Test(semVer)), encoding: .utf8)! + } + + + func expect(_ output: String) -> String { + #"{"semVer":"\#(output)"}"# + } + + + XCTAssertEqual(expect("0.0.0"), try encode(SemVer(0,0,0))) + XCTAssertEqual(expect("0.0.1"), try encode(SemVer(0,0,1))) + XCTAssertEqual(expect("0.1.0"), try encode(SemVer(0,1,0))) + XCTAssertEqual(expect("1.0.0"), try encode(SemVer(1,0,0))) + XCTAssertEqual(expect("0.0.999"), try encode(SemVer(0,0,999))) + XCTAssertEqual(expect("0.999.0"), try encode(SemVer(0,999,0))) + XCTAssertEqual(expect("999.0.0"), try encode(SemVer(999,0,0))) + + XCTAssertEqual(expect("1.2.3-RC.4+567"), try encode(SemVer(01,2,3, preRelease: "RC.4", build: 567)!)) + XCTAssertEqual(expect("1.2.3-RC.4+567"), try encode(SemVer(01,2,3, preRelease: ["RC","4"], build: 567)!)) + XCTAssertEqual(expect("1.2.3-RC.4+567"), try encode(SemVer(01,2,3, preRelease: ["RC","4"], build: [567])!)) + XCTAssertEqual(expect("1.2.3-RC.4+567"), try encode(SemVer("1.2.3-RC.4+567")!)) + } + + + func testDecode() throws { + let decoder = JSONDecoder() + + + func decode(_ semVer: String) throws -> SemVer { + try decoder.decode(Test.self, from: #"{"semVer":"\#(semVer)"}"#.data(using: .utf8)!).semVer + } + + + XCTAssertEqual(SemVer(0,0,0), try decode("0.0.0")) + XCTAssertEqual(SemVer(0,0,1), try decode("0.0.1")) + XCTAssertEqual(SemVer(0,1,0), try decode("0.1.0")) + XCTAssertEqual(SemVer(1,0,0), try decode("1.0.0")) + XCTAssertEqual(SemVer(0,0,999), try decode("0.0.999")) + XCTAssertEqual(SemVer(0,999,0), try decode("0.999.0")) + XCTAssertEqual(SemVer(999,0,0), try decode("999.0.0")) + + XCTAssertEqual(SemVer(01,2,3, preRelease: "RC.4", build: 567)!, try decode("1.2.3-RC.4+567")) + XCTAssertEqual(SemVer(01,2,3, preRelease: ["RC","4"], build: 567)!, try decode("1.2.3-RC.4+567")) + XCTAssertEqual(SemVer(01,2,3, preRelease: ["RC","4"], build: [567])!, try decode("1.2.3-RC.4+567")) + XCTAssertEqual(SemVer("1.2.3-RC.4+567")!, try decode("1.2.3-RC.4+567")) + } + + + func testEncodeDecode() { + let encoder = JSONEncoder() + let decoder = JSONDecoder() + + + func encodeDecode(_ semVer: SemVer) throws -> SemVer { + try decoder.decode(Test.self, from: try encoder.encode(Test(semVer))).semVer + } + + + + XCTAssertEqual(SemVer(0,0,0), try encodeDecode(SemVer(0,0,0))) + XCTAssertEqual(SemVer(0,0,1), try encodeDecode(SemVer(0,0,1))) + XCTAssertEqual(SemVer(0,1,0), try encodeDecode(SemVer(0,1,0))) + XCTAssertEqual(SemVer(1,0,0), try encodeDecode(SemVer(1,0,0))) + XCTAssertEqual(SemVer(0,0,999), try encodeDecode(SemVer(0,0,999))) + XCTAssertEqual(SemVer(0,999,0), try encodeDecode(SemVer(0,999,0))) + XCTAssertEqual(SemVer(999,0,0), try encodeDecode(SemVer(999,0,0))) + + XCTAssertEqual(SemVer(01,2,3, preRelease: "RC.4", build: 567)!, try encodeDecode(SemVer(01,2,3, preRelease: "RC.4", build: 567)!)) + XCTAssertEqual(SemVer(01,2,3, preRelease: ["RC","4"], build: 567)!, try encodeDecode(SemVer(01,2,3, preRelease: ["RC","4"], build: 567)!)) + XCTAssertEqual(SemVer(01,2,3, preRelease: ["RC","4"], build: [567])!, try encodeDecode(SemVer(01,2,3, preRelease: ["RC","4"], build: [567])!)) + XCTAssertEqual(SemVer("1.2.3-RC.4+567")!, try encodeDecode(SemVer("1.2.3-RC.4+567")!)) + } +} + + + +private struct Test: Codable { + + let semVer: SemVer + + + init(_ semVer: SemVer) { + self.semVer = semVer + } +} diff --git a/Tests/SemVerTests/SemVer+Hashable Tests.swift b/Tests/SemVerTests/SemVer+Hashable Tests.swift index 2dd3596..e1cfa38 100644 --- a/Tests/SemVerTests/SemVer+Hashable Tests.swift +++ b/Tests/SemVerTests/SemVer+Hashable Tests.swift @@ -7,15 +7,11 @@ // import XCTest -@testable import SemVer +import SemVer -class SemVerHashableTests: XCTestCase { - - override func setUp() { - isTesting = true - } +class SemVerHashableTests: SemVerTestClass { func testHashable() { diff --git a/Tests/SemVerTests/SemVerTests.swift b/Tests/SemVerTests/SemVerTests.swift index 3641a9b..be3f6ac 100644 --- a/Tests/SemVerTests/SemVerTests.swift +++ b/Tests/SemVerTests/SemVerTests.swift @@ -7,15 +7,11 @@ // import XCTest -@testable import SemVer +import SemVer -class SemVerTests: XCTestCase { - - override func setUp() { - isTesting = true - } +class SemVerTests: SemVerTestClass { func testDescription() { diff --git a/Tests/SemVerTests/Testing tools.swift b/Tests/SemVerTests/Testing tools.swift new file mode 100644 index 0000000..b9be206 --- /dev/null +++ b/Tests/SemVerTests/Testing tools.swift @@ -0,0 +1,40 @@ +// +// Testing tools.swift +// SemVerTests +// +// Created by Ky Leggiero on 2021-10-18. +// + +import XCTest +@testable import SemVer + + + +internal class SemVerTestClass: XCTestCase { + override func setUp() { + isTesting = true + super.setUp() + } +} + + + +/// A bodge to change behavior based on whether a test suite is running. +/// +/// Ideally, this would be a compile-time flag. However, it seems that isn't being respected with my current setup, so I don't trust it. +@inline(__always) +var isTesting: Bool { + get { TestingTools.isTesting } + set { TestingTools.isTesting = newValue } +} + + +/// In non-test runs, this indicates that an internal sanity check failed. +/// +/// To perform an assertion in test runs as well, use `Swift.assertionFailure` instead. +/// +/// - Parameter message: _optional_ - A string to print in a playground or `-Onone` non-test build. Defaults to an empty string. +@inline(__always) +func assertionFailure(_ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { + TestingTools.assertionFailure(message(), file: file, line: line) +}