diff --git a/Sources/TSCUtility/Version.swift b/Sources/TSCUtility/Version.swift index d9b08af1..f75b9c54 100644 --- a/Sources/TSCUtility/Version.swift +++ b/Sources/TSCUtility/Version.swift @@ -48,22 +48,29 @@ public struct Version { /// An error that occurs during the creation of a version. public enum VersionError: Error, CustomStringConvertible { /// The version string contains non-ASCII characters. + /// - Parameter versionString: The version string. case nonASCIIVersionString(_ versionString: String) /// The version core contains an invalid number of Identifiers. - case invalidVersionCoreIdentifiersCount(_ identifiers: [String]) + /// - Parameters: + /// - identifiers: The version core identifiers in the version string. + /// - usesLenientParsing: A Boolean value indicating whether or not the lenient parsing mode was enabled when this error occurred. + case invalidVersionCoreIdentifiersCount(_ identifiers: [String], usesLenientParsing: Bool) /// Some or all of the version core identifiers contain non-numerical characters or are empty. + /// - Parameter identifiers: The version core identifiers in the version string. case nonNumericalOrEmptyVersionCoreIdentifiers(_ identifiers: [String]) /// Some or all of the pre-release identifiers contain characters other than alpha-numerics and hyphens. + /// - Parameter identifiers: The pre-release identifiers in the version string. case nonAlphaNumerHyphenalPrereleaseIdentifiers(_ identifiers: [String]) /// Some or all of the build metadata identifiers contain characters other than alpha-numerics and hyphens. + /// - Parameter identifiers: The build metadata identifiers in the version string. case nonAlphaNumerHyphenalBuildMetadataIdentifiers(_ identifiers: [String]) public var description: String { switch self { case let .nonASCIIVersionString(versionString): return "non-ASCII characters in version string '\(versionString)'" - case let .invalidVersionCoreIdentifiersCount(identifiers): - return "\(identifiers.count < 3 ? "fewer" : "more") than 3 identifiers in version core '\(identifiers.joined(separator: "."))'" + case let .invalidVersionCoreIdentifiersCount(identifiers, usesLenientParsing): + return "\(identifiers.count > 3 ? "more than 3" : "fewer than \(usesLenientParsing ? 2 : 3)") identifiers in version core '\(identifiers.joined(separator: "."))'" case let .nonNumericalOrEmptyVersionCoreIdentifiers(identifiers): if !identifiers.allSatisfy( { !$0.isEmpty } ) { return "empty identifiers in version core '\(identifiers.joined(separator: "."))'" @@ -85,15 +92,17 @@ public enum VersionError: Error, CustomStringConvertible { } extension Version { - // TODO: Rename this function to `init(string: String) throws`, after `init?(string: String)` is removed. + // TODO: Rename this function to `init(string:usesLenientParsing:) throws`, after `init?(string: String)` is removed. // TODO: Find a better error-checking order. // Currently, if a version string is "forty-two", this initializer throws an error that says "forty" is only 1 version core identifier, which is not enough. // But this is misleading the user to consider "forty" as a valid version core identifier. // We should find a way to check for (or throw) "wrong characters used" errors first, but without overly-complicating the logic. /// Creates a version from the given string. - /// - Parameter versionString: The string to create the version from. + /// - Parameters: + /// - versionString: The string to create the version from. + /// - usesLenientParsing: A Boolean value indicating whether or not the version string should be parsed leniently. If `true`, then the patch version is assumed to be `0` if it's not provided in the version string; otherwise, the parsing strictly follows the Semantic Versioning 2.0.0 rules. This value defaults to `false`. /// - Throws: A `VersionError` instance if the `versionString` doesn't follow [SemVer 2.0.0](https://semver.org). - public init(versionString: String) throws { + public init(versionString: String, usesLenientParsing: Bool = false) throws { // SemVer 2.0.0 allows only ASCII alphanumerical characters and "-" in the version string, except for "." and "+" as delimiters. ("-" is used as a delimiter between the version core and pre-release identifiers, but it's allowed within pre-release and metadata identifiers as well.) // Alphanumerics check will come later, after each identifier is split out (i.e. after the delimiters are removed). guard versionString.allSatisfy(\.isASCII) else { @@ -107,8 +116,8 @@ extension Version { let versionCore = versionString[..<(prereleaseDelimiterIndex ?? metadataDelimiterIndex ?? versionString.endIndex)] let versionCoreIdentifiers = versionCore.split(separator: ".", omittingEmptySubsequences: false) - guard versionCoreIdentifiers.count == 3 else { - throw VersionError.invalidVersionCoreIdentifiersCount(versionCoreIdentifiers.map { String($0) }) + guard versionCoreIdentifiers.count == 3 || (usesLenientParsing && versionCoreIdentifiers.count == 2) else { + throw VersionError.invalidVersionCoreIdentifiersCount(versionCoreIdentifiers.map { String($0) }, usesLenientParsing: usesLenientParsing) } guard @@ -116,7 +125,7 @@ extension Version { // Converting each identifier from a substring to an integer doubles as checking if the identifiers have non-numeric characters. let major = Int(versionCoreIdentifiers[0]), let minor = Int(versionCoreIdentifiers[1]), - let patch = Int(versionCoreIdentifiers[2]) + let patch = usesLenientParsing && versionCoreIdentifiers.count == 2 ? 0 : Int(versionCoreIdentifiers[2]) else { throw VersionError.nonNumericalOrEmptyVersionCoreIdentifiers(versionCoreIdentifiers.map { String($0) }) } diff --git a/Tests/TSCUtilityTests/VersionTests.swift b/Tests/TSCUtilityTests/VersionTests.swift index f36b5d29..871cf301 100644 --- a/Tests/TSCUtilityTests/VersionTests.swift +++ b/Tests/TSCUtilityTests/VersionTests.swift @@ -55,27 +55,62 @@ class VersionTests: XCTestCase { XCTAssertNoThrow(try Version(versionString: "1.1.2")) XCTAssertEqual(try! Version(versionString: "1.1.2"), Version(1, 1, 2)) + XCTAssertNoThrow(try Version(versionString: "0.0.0", usesLenientParsing: false)) + XCTAssertEqual(try! Version(versionString: "0.0.0", usesLenientParsing: false), Version(0, 0, 0)) + + XCTAssertNoThrow(try Version(versionString: "1.1.2")) + XCTAssertEqual(try! Version(versionString: "1.1.2"), Version(1, 1, 2)) + + XCTAssertNoThrow(try Version(versionString: "0.0.0", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0.0", usesLenientParsing: true), Version(0, 0, 0)) + + XCTAssertNoThrow(try Version(versionString: "1.1.2", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "1.1.2", usesLenientParsing: true), Version(1, 1, 2)) + + XCTAssertNoThrow(try Version(versionString: "0.0", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0.0", usesLenientParsing: true), Version(0, 0, 0)) + + XCTAssertNoThrow(try Version(versionString: "1.2", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "1.2", usesLenientParsing: true), Version(1, 2, 0)) + // MARK: Malformed version core XCTAssertThrowsError(try Version(versionString: "3")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["3"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["3"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '3'") } + XCTAssertThrowsError(try Version(versionString: "3", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["3"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '3'") + } + XCTAssertThrowsError(try Version(versionString: "3 5")) { error in // checking for version core identifier count comes before checking for alpha-numerical characters - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["3 5"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["3 5"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '3 5'") } + XCTAssertThrowsError(try Version(versionString: "3 5", usesLenientParsing: true)) { error in + // checking for version core identifier count comes before checking for alpha-numerical characters + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["3 5"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '3 5'") + } + XCTAssertThrowsError(try Version(versionString: "5.8")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["5", "8"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["5", "8"], usesLenientParsing: false) = error else { XCTFail() return } @@ -86,21 +121,40 @@ class VersionTests: XCTestCase { // the version core is considered empty because of the leading '-' // everything after the first '-' is considered as the pre-release information (until the first '+', which doesn't exist in this version string) // the version core is NOT considered missing, because it has 1 identifier, despite the identifier being empty - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount([""]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount([""], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core ''") } + XCTAssertThrowsError(try Version(versionString: "-5.8.13", usesLenientParsing: true)) { error in + // the version core is considered empty because of the leading '-' + // everything after the first '-' is considered as the pre-release information (until the first '+', which doesn't exist in this version string) + // the version core is NOT considered missing, because it has 1 identifier, despite the identifier being empty + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount([""], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core ''") + } + XCTAssertThrowsError(try Version(versionString: "8.-13.21")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["8", ""]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["8", ""], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '8.'") } + XCTAssertThrowsError(try Version(versionString: "8.-13.21", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["8", ""]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "empty identifiers in version core '8.'") + } + XCTAssertThrowsError(try Version(versionString: "13.21.-34")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["13", "21", ""]) = error else { XCTFail() @@ -109,22 +163,46 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "empty identifiers in version core '13.21.'") } - XCTAssertThrowsError(try Version(versionString: ("-0.0.0" as String))) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount([""]) = error else { + XCTAssertThrowsError(try Version(versionString: "13.21.-34", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["13", "21", ""]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "empty identifiers in version core '13.21.'") + } + + XCTAssertThrowsError(try Version(versionString: "-0.0.0")) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount([""], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core ''") } + XCTAssertThrowsError(try Version(versionString: "-0.0.0", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount([""], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core ''") + } + XCTAssertThrowsError(try Version(versionString: "0.-0.0")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["0", ""]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["0", ""], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '0.'") } + XCTAssertThrowsError(try Version(versionString: "0.-0.0", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["0", ""]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "empty identifiers in version core '0.'") + } + XCTAssertThrowsError(try Version(versionString: "0.0.O")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["0", "0", "O"]) = error else { XCTFail() @@ -133,6 +211,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifier 'O'") } + XCTAssertThrowsError(try Version(versionString: "0.0.O", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["0", "0", "O"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifier 'O'") + } + XCTAssertThrowsError(try Version(versionString: "1.l1.O")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["1", "l1", "O"]) = error else { XCTFail() @@ -141,8 +227,24 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'l1', 'O'") } + XCTAssertThrowsError(try Version(versionString: "1.l1.O", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["1", "l1", "O"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'l1', 'O'") + } + XCTAssertThrowsError(try Version(versionString: "21.34.55.89")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["21", "34", "55", "89"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["21", "34", "55", "89"], usesLenientParsing: false) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "more than 3 identifiers in version core '21.34.55.89'") + } + + XCTAssertThrowsError(try Version(versionString: "21.34.55.89", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["21", "34", "55", "89"], usesLenientParsing: true) = error else { XCTFail() return } @@ -150,21 +252,37 @@ class VersionTests: XCTestCase { } XCTAssertThrowsError(try Version(versionString: "6 x 9 = 42")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '6 x 9 = 42'") } + XCTAssertThrowsError(try Version(versionString: "6 x 9 = 42", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '6 x 9 = 42'") + } + XCTAssertThrowsError(try Version(versionString: "forty two")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty two"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty two"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core 'forty two'") } + XCTAssertThrowsError(try Version(versionString: "forty two", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty two"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core 'forty two'") + } + XCTAssertThrowsError(try Version(versionString: "一点二点三")) { error in guard let error = error as? VersionError, case .nonASCIIVersionString("一点二点三") = error else { XCTFail() @@ -173,20 +291,52 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-ASCII characters in version string '一点二点三'") } + XCTAssertThrowsError(try Version(versionString: "一点二点三", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonASCIIVersionString("一点二点三") = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-ASCII characters in version string '一点二点三'") + } + // MARK: Well-formed version core, well-formed pre-release identifiers XCTAssertNoThrow(try Version(versionString: "0.0.0-pre-alpha")) XCTAssertEqual(try! Version(versionString: "0.0.0-pre-alpha"), Version(0, 0, 0, prereleaseIdentifiers: ["pre-alpha"])) + XCTAssertNoThrow(try Version(versionString: "0.0.0-pre-alpha", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0.0-pre-alpha", usesLenientParsing: true), Version(0, 0, 0, prereleaseIdentifiers: ["pre-alpha"])) + + XCTAssertNoThrow(try Version(versionString: "0.0-pre-alpha", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0-pre-alpha", usesLenientParsing: true), Version(0, 0, 0, prereleaseIdentifiers: ["pre-alpha"])) + XCTAssertNoThrow(try Version(versionString: "55.89.144-beta.1")) XCTAssertEqual(try! Version(versionString: "55.89.144-beta.1"), Version(55, 89, 144, prereleaseIdentifiers: ["beta", "1"])) + XCTAssertNoThrow(try Version(versionString: "55.89.144-beta.1", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "55.89.144-beta.1", usesLenientParsing: true), Version(55, 89, 144, prereleaseIdentifiers: ["beta", "1"])) + + XCTAssertNoThrow(try Version(versionString: "55.89-beta.1", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "55.89-beta.1", usesLenientParsing: true), Version(55, 89, 0, prereleaseIdentifiers: ["beta", "1"])) + XCTAssertNoThrow(try Version(versionString: "89.144.233-a.whole..lot.of.pre-release.identifiers")) XCTAssertEqual(try! Version(versionString: "89.144.233-a.whole..lot.of.pre-release.identifiers"), Version(89, 144, 233, prereleaseIdentifiers: ["a", "whole", "", "lot", "of", "pre-release", "identifiers"])) + XCTAssertNoThrow(try Version(versionString: "89.144.233-a.whole..lot.of.pre-release.identifiers", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "89.144.233-a.whole..lot.of.pre-release.identifiers", usesLenientParsing: true), Version(89, 144, 233, prereleaseIdentifiers: ["a", "whole", "", "lot", "of", "pre-release", "identifiers"])) + + XCTAssertNoThrow(try Version(versionString: "89.144-a.whole..lot.of.pre-release.identifiers", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "89.144-a.whole..lot.of.pre-release.identifiers", usesLenientParsing: true), Version(89, 144, 0, prereleaseIdentifiers: ["a", "whole", "", "lot", "of", "pre-release", "identifiers"])) + XCTAssertNoThrow(try Version(versionString: "144.233.377-")) XCTAssertEqual(try! Version(versionString: "144.233.377-"), Version(144, 233, 377, prereleaseIdentifiers: [""])) + XCTAssertNoThrow(try Version(versionString: "144.233.377-", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "144.233.377-", usesLenientParsing: true), Version(144, 233, 377, prereleaseIdentifiers: [""])) + + XCTAssertNoThrow(try Version(versionString: "144.233-", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "144.233-", usesLenientParsing: true), Version(144, 233, 0, prereleaseIdentifiers: [""])) + // MARK: Well-formed version core, malformed pre-release identifiers XCTAssertThrowsError(try Version(versionString: "233.377.610-hello world")) { error in @@ -197,6 +347,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in pre-release identifier 'hello world'") } + XCTAssertThrowsError(try Version(versionString: "233.377.610-hello world", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonAlphaNumerHyphenalPrereleaseIdentifiers(["hello world"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in pre-release identifier 'hello world'") + } + XCTAssertThrowsError(try Version(versionString: "1.2.3-测试版")) { error in guard let error = error as? VersionError, case .nonASCIIVersionString("1.2.3-测试版") = error else { XCTFail() @@ -205,18 +363,34 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-ASCII characters in version string '1.2.3-测试版'") } + XCTAssertThrowsError(try Version(versionString: "1.2.3-测试版", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonASCIIVersionString("1.2.3-测试版") = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-ASCII characters in version string '1.2.3-测试版'") + } + // MARK: Malformed version core, well-formed pre-release identifiers XCTAssertThrowsError(try Version(versionString: "987-Hello.world--------")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["987"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["987"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '987'") } + XCTAssertThrowsError(try Version(versionString: "987-Hello.world--------", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["987"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '987'") + } + XCTAssertThrowsError(try Version(versionString: "987.1597-half-life.3")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["987", "1597"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["987", "1597"], usesLenientParsing: false) = error else { XCTFail() return } @@ -224,7 +398,15 @@ class VersionTests: XCTestCase { } XCTAssertThrowsError(try Version(versionString: "1597.2584.4181.6765-a.whole.lot.of.pre-release.identifiers")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["1597", "2584", "4181", "6765"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["1597", "2584", "4181", "6765"], usesLenientParsing: false) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "more than 3 identifiers in version core '1597.2584.4181.6765'") + } + + XCTAssertThrowsError(try Version(versionString: "1597.2584.4181.6765-a.whole.lot.of.pre-release.identifiers", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["1597", "2584", "4181", "6765"], usesLenientParsing: true) = error else { XCTFail() return } @@ -232,21 +414,37 @@ class VersionTests: XCTestCase { } XCTAssertThrowsError(try Version(versionString: "6 x 9 = 42-")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '6 x 9 = 42'") } + XCTAssertThrowsError(try Version(versionString: "6 x 9 = 42-", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '6 x 9 = 42'") + } + XCTAssertThrowsError(try Version(versionString: "forty-two")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core 'forty'") } + XCTAssertThrowsError(try Version(versionString: "forty-two", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core 'forty'") + } + XCTAssertThrowsError(try Version(versionString: "l.2.3")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["l", "2", "3"]) = error else { XCTFail() @@ -255,6 +453,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifier 'l'") } + XCTAssertThrowsError(try Version(versionString: "l.2.3", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["l", "2", "3"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifier 'l'") + } + XCTAssertThrowsError(try Version(versionString: "l.b.3")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["l", "b", "3"]) = error else { XCTFail() @@ -263,6 +469,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'l', 'b'") } + XCTAssertThrowsError(try Version(versionString: "l.b.3", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["l", "b", "3"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'l', 'b'") + } + XCTAssertThrowsError(try Version(versionString: "l.2.З")) { error in guard let error = error as? VersionError, case .nonASCIIVersionString("l.2.З") = error else { XCTFail() @@ -271,6 +485,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-ASCII characters in version string 'l.2.З'") } + XCTAssertThrowsError(try Version(versionString: "l.2.З", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonASCIIVersionString("l.2.З") = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-ASCII characters in version string 'l.2.З'") + } + XCTAssertThrowsError(try Version(versionString: "一点二点三-beta")) { error in guard let error = error as? VersionError, case .nonASCIIVersionString("一点二点三-beta") = error else { XCTFail() @@ -279,20 +501,52 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-ASCII characters in version string '一点二点三-beta'") } + XCTAssertThrowsError(try Version(versionString: "一点二点三-beta", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonASCIIVersionString("一点二点三-beta") = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-ASCII characters in version string '一点二点三-beta'") + } + // MARK: Well-formed version core, well-formed build metadata identifiers XCTAssertNoThrow(try Version(versionString: "0.0.0+some-metadata")) XCTAssertEqual(try! Version(versionString: "0.0.0+some-metadata"), Version(0, 0, 0, buildMetadataIdentifiers: ["some-metadata"])) + XCTAssertNoThrow(try Version(versionString: "0.0.0+some-metadata", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0.0+some-metadata", usesLenientParsing: true), Version(0, 0, 0, buildMetadataIdentifiers: ["some-metadata"])) + + XCTAssertNoThrow(try Version(versionString: "0.0+some-metadata", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0+some-metadata", usesLenientParsing: true), Version(0, 0, 0, buildMetadataIdentifiers: ["some-metadata"])) + XCTAssertNoThrow(try Version(versionString: "4181.6765.10946+more.meta..more.data")) XCTAssertEqual(try! Version(versionString: "4181.6765.10946+more.meta..more.data"), Version(4181, 6765, 10946, buildMetadataIdentifiers: ["more", "meta", "", "more", "data"])) + XCTAssertNoThrow(try Version(versionString: "4181.6765.10946+more.meta..more.data", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "4181.6765.10946+more.meta..more.data", usesLenientParsing: true), Version(4181, 6765, 10946, buildMetadataIdentifiers: ["more", "meta", "", "more", "data"])) + + XCTAssertNoThrow(try Version(versionString: "4181.6765+more.meta..more.data", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "4181.6765+more.meta..more.data", usesLenientParsing: true), Version(4181, 6765, 0, buildMetadataIdentifiers: ["more", "meta", "", "more", "data"])) + XCTAssertNoThrow(try Version(versionString: "6765.10946.17711+-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------")) XCTAssertEqual(try! Version(versionString: "6765.10946.17711+-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------"), Version(6765, 10946, 17711, buildMetadataIdentifiers: ["-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------"])) + XCTAssertNoThrow(try Version(versionString: "6765.10946.17711+-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "6765.10946.17711+-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------", usesLenientParsing: true), Version(6765, 10946, 17711, buildMetadataIdentifiers: ["-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------"])) + + XCTAssertNoThrow(try Version(versionString: "6765.10946+-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "6765.10946+-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------", usesLenientParsing: true), Version(6765, 10946, 0, buildMetadataIdentifiers: ["-a-very--long---build-----metadata--------identifier-------------with---------------------many----------------------------------hyphens-------------------------------------------------------"])) + XCTAssertNoThrow(try Version(versionString: "10946.17711.28657+")) XCTAssertEqual(try! Version(versionString: "10946.17711.28657+"), Version(10946, 17711, 28657, buildMetadataIdentifiers: [""])) + XCTAssertNoThrow(try Version(versionString: "10946.17711.28657+", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "10946.17711.28657+", usesLenientParsing: true), Version(10946, 17711, 28657, buildMetadataIdentifiers: [""])) + + XCTAssertNoThrow(try Version(versionString: "10946.17711+", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "10946.17711+", usesLenientParsing: true), Version(10946, 17711, 0, buildMetadataIdentifiers: [""])) + // MARK: Well-formed version core, malformed build metadata identifiers XCTAssertThrowsError(try Version(versionString: "17711.28657.46368+hello world.hello-.-world")) { error in @@ -303,6 +557,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in build metadata identifier 'hello world'") } + XCTAssertThrowsError(try Version(versionString: "17711.28657.46368+hello world.hello-.-world", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonAlphaNumerHyphenalBuildMetadataIdentifiers(["hello world", "hello-", "-world"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in build metadata identifier 'hello world'") + } + XCTAssertThrowsError(try Version(versionString: "28657.46368.75025+hello+world.hello world")) { error in guard let error = error as? VersionError, case .nonAlphaNumerHyphenalBuildMetadataIdentifiers(["hello+world", "hello world"]) = error else { XCTFail() @@ -311,18 +573,34 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in build metadata identifiers 'hello+world', 'hello world'") } + XCTAssertThrowsError(try Version(versionString: "28657.46368.75025+hello+world.hello world", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonAlphaNumerHyphenalBuildMetadataIdentifiers(["hello+world", "hello world"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in build metadata identifiers 'hello+world', 'hello world'") + } + // MARK: Malformed version core, well-formed build metadata identifiers XCTAssertThrowsError(try Version(versionString: "121393+Hello.world--------")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["121393"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["121393"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '121393'") } + XCTAssertThrowsError(try Version(versionString: "121393+Hello.world--------", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["121393"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '121393'") + } + XCTAssertThrowsError(try Version(versionString: "121393.196418+half-life.3")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["121393", "196418"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["121393", "196418"], usesLenientParsing: false) = error else { XCTFail() return } @@ -330,7 +608,15 @@ class VersionTests: XCTestCase { } XCTAssertThrowsError(try Version(versionString: "196418.317811.514229.832040+a.whole.lot.of.build.metadata.identifiers")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["196418", "317811", "514229", "832040"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["196418", "317811", "514229", "832040"], usesLenientParsing: false) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "more than 3 identifiers in version core '196418.317811.514229.832040'") + } + + XCTAssertThrowsError(try Version(versionString: "196418.317811.514229.832040+a.whole.lot.of.build.metadata.identifiers", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["196418", "317811", "514229", "832040"], usesLenientParsing: true) = error else { XCTFail() return } @@ -338,7 +624,15 @@ class VersionTests: XCTestCase { } XCTAssertThrowsError(try Version(versionString: "196418.317811.514229.83204O+a.whole.lot.of.build.metadata.identifiers")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["196418", "317811", "514229", "83204O"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["196418", "317811", "514229", "83204O"], usesLenientParsing: false) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "more than 3 identifiers in version core '196418.317811.514229.83204O'") + } + + XCTAssertThrowsError(try Version(versionString: "196418.317811.514229.83204O+a.whole.lot.of.build.metadata.identifiers", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["196418", "317811", "514229", "83204O"], usesLenientParsing: true) = error else { XCTFail() return } @@ -353,6 +647,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifier '83204O'") } + XCTAssertThrowsError(try Version(versionString: "196418.317811.83204O+a.whole.lot.of.build.metadata.identifiers", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["196418", "317811", "83204O"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifier '83204O'") + } + XCTAssertThrowsError(try Version(versionString: "abc.def.ghi+a.whole.lot.of.build.metadata.identifiers")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["abc", "def", "ghi"]) = error else { XCTFail() @@ -361,22 +663,46 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'abc', 'def', 'ghi'") } + XCTAssertThrowsError(try Version(versionString: "abc.def.ghi+a.whole.lot.of.build.metadata.identifiers", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["abc", "def", "ghi"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'abc', 'def', 'ghi'") + } + XCTAssertThrowsError(try Version(versionString: "6 x 9 = 42+")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '6 x 9 = 42'") } + XCTAssertThrowsError(try Version(versionString: "6 x 9 = 42+", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["6 x 9 = 42"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '6 x 9 = 42'") + } + XCTAssertThrowsError(try Version(versionString: "forty two+a-very-long-build-metadata-identifier-with-many-hyphens")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty two"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty two"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core 'forty two'") } + XCTAssertThrowsError(try Version(versionString: "forty two+a-very-long-build-metadata-identifier-with-many-hyphens", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["forty two"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core 'forty two'") + } + XCTAssertThrowsError(try Version(versionString: "一.二.三+build.metadata")) { error in guard let error = error as? VersionError, case .nonASCIIVersionString("一.二.三+build.metadata") = error else { XCTFail() @@ -385,11 +711,35 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-ASCII characters in version string '一.二.三+build.metadata'") } + XCTAssertThrowsError(try Version(versionString: "一.二.三+build.metadata", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonASCIIVersionString("一.二.三+build.metadata") = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-ASCII characters in version string '一.二.三+build.metadata'") + } + // MARK: Well-formed version core, well-formed pre-release identifiers, well-formed build metadata identifiers XCTAssertNoThrow(try Version(versionString: "0.0.0-beta.-42+42-42.42")) XCTAssertEqual(try! Version(versionString: "0.0.0-beta.-42+42-42.42"), Version(0, 0, 0, prereleaseIdentifiers: ["beta", "-42"], buildMetadataIdentifiers: ["42-42", "42"])) + XCTAssertNoThrow(try Version(versionString: "0.0.0-beta.-42+42-42.42", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0.0-beta.-42+42-42.42", usesLenientParsing: true), Version(0, 0, 0, prereleaseIdentifiers: ["beta", "-42"], buildMetadataIdentifiers: ["42-42", "42"])) + + XCTAssertNoThrow(try Version(versionString: "0.0-beta.-42+42-42.42", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "0.0-beta.-42+42-42.42", usesLenientParsing: true), Version(0, 0, 0, prereleaseIdentifiers: ["beta", "-42"], buildMetadataIdentifiers: ["42-42", "42"])) + + XCTAssertNoThrow(try Version(versionString: "1.2.3-beta.-24+abc-xyz.42")) + XCTAssertEqual(try! Version(versionString: "1.2.3-beta.-24+abc-xyz.42"), Version(1, 2, 3, prereleaseIdentifiers: ["beta", "-24"], buildMetadataIdentifiers: ["abc-xyz", "42"])) + + XCTAssertNoThrow(try Version(versionString: "1.2.3-beta.-24+abc-xyz.42", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "1.2.3-beta.-24+abc-xyz.42", usesLenientParsing: true), Version(1, 2, 3, prereleaseIdentifiers: ["beta", "-24"], buildMetadataIdentifiers: ["abc-xyz", "42"])) + + XCTAssertNoThrow(try Version(versionString: "1.2-beta.-24+abc-xyz.42", usesLenientParsing: true)) + XCTAssertEqual(try! Version(versionString: "1.2-beta.-24+abc-xyz.42", usesLenientParsing: true), Version(1, 2, 0, prereleaseIdentifiers: ["beta", "-24"], buildMetadataIdentifiers: ["abc-xyz", "42"])) + + // MARK: Well-formed version core, well-formed pre-release identifiers, malformed build metadata identifiers XCTAssertThrowsError(try Version(versionString: "514229.832040.1346269-beta1+ ")) { error in @@ -400,6 +750,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in build metadata identifier ' '") } + XCTAssertThrowsError(try Version(versionString: "514229.832040.1346269-beta1+ ", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonAlphaNumerHyphenalBuildMetadataIdentifiers([" "]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in build metadata identifier ' '") + } + // MARK: Well-formed version core, malformed pre-release identifiers, well-formed build metadata identifiers XCTAssertThrowsError(try Version(versionString: "832040.1346269.2178309-beta 1.-+-")) { error in @@ -410,6 +768,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in pre-release identifier 'beta 1'") } + XCTAssertThrowsError(try Version(versionString: "832040.1346269.2178309-beta 1.-+-", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonAlphaNumerHyphenalPrereleaseIdentifiers(["beta 1", "-"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in pre-release identifier 'beta 1'") + } + // MARK: Well-formed version core, malformed pre-release identifiers, malformed build metadata identifiers // pre-release is diagnosed before build metadata is @@ -421,6 +787,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in pre-release identifier 'beta 1'") } + XCTAssertThrowsError(try Version(versionString: "1346269.2178309.3524578-beta 1++", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonAlphaNumerHyphenalPrereleaseIdentifiers(["beta 1"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "characters other than alpha-numerics and hyphens in pre-release identifier 'beta 1'") + } + // MARK: malformed version core, well-formed pre-release identifiers, well-formed build metadata identifiers XCTAssertThrowsError(try Version(versionString: " 832040.1346269.3524578-beta1+abc")) { error in @@ -431,6 +805,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifier ' 832040'") } + XCTAssertThrowsError(try Version(versionString: " 832040.1346269.3524578-beta1+abc", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers([" 832040", "1346269", "3524578"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifier ' 832040'") + } + // MARK: malformed version core, well-formed pre-release identifiers, malformed build metadata identifiers XCTAssertThrowsError(try Version(versionString: "l346269.3524578.5702887-beta1+😀")) { error in @@ -441,6 +823,14 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-ASCII characters in version string 'l346269.3524578.5702887-beta1+😀'") } + XCTAssertThrowsError(try Version(versionString: "l346269.3524578.5702887-beta1+😀", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonASCIIVersionString("l346269.3524578.5702887-beta1+😀") = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-ASCII characters in version string 'l346269.3524578.5702887-beta1+😀'") + } + // version core is diagnosed before build metadata is XCTAssertThrowsError(try Version(versionString: "l346269.abc.OOO-beta1+++.+.+")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["l346269", "abc", "OOO"]) = error else { @@ -450,9 +840,16 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'l346269', 'abc', 'OOO'") } + XCTAssertThrowsError(try Version(versionString: "l346269.abc.OOO-beta1+++.+.+", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["l346269", "abc", "OOO"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifiers 'l346269', 'abc', 'OOO'") + } + // MARK: malformed version core, malformed pre-release identifiers, well-formed build metadata identifiers - XCTAssertNil(Version("352A578.5702887.9227465-beta!@#$%^&*1+asdfghjkl123456789" as String)) // version core is diagnosed before pre-release is XCTAssertThrowsError(try Version(versionString: "352A578.5702887.9227465-beta!@#$%^&*1+asdfghjkl123456789")) { error in guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["352A578", "5702887", "9227465"]) = error else { @@ -462,6 +859,15 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-numerical characters in version core identifier '352A578'") } + XCTAssertThrowsError(try Version(versionString: "352A578.5702887.9227465-beta!@#$%^&*1+asdfghjkl123456789", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonNumericalOrEmptyVersionCoreIdentifiers(["352A578", "5702887", "9227465"]) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-numerical characters in version core identifier '352A578'") + } + + // MARK: malformed version core, malformed pre-release identifiers, malformed build metadata identifiers XCTAssertThrowsError(try Version(versionString: "5702887.9227465-bètá1+±")) { error in @@ -472,14 +878,44 @@ class VersionTests: XCTestCase { XCTAssertEqual(error.description, "non-ASCII characters in version string '5702887.9227465-bètá1+±'") } + XCTAssertThrowsError(try Version(versionString: "5702887.9227465-bètá1+±", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .nonASCIIVersionString("5702887.9227465-bètá1+±") = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "non-ASCII characters in version string '5702887.9227465-bètá1+±'") + } + XCTAssertThrowsError(try Version(versionString: "5702887.9227465-bet@.1!+met@.d@t@")) { error in - guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["5702887", "9227465"]) = error else { + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["5702887", "9227465"], usesLenientParsing: false) = error else { XCTFail() return } XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '5702887.9227465'") } + XCTAssertThrowsError(try Version(versionString: "5702887-bet@.1!+met@.d@t@")) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["5702887"], usesLenientParsing: false) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 3 identifiers in version core '5702887'") + } + + XCTAssertThrowsError(try Version(versionString: "5702887-bet@.1!+met@.d@t@", usesLenientParsing: true)) { error in + guard let error = error as? VersionError, case .invalidVersionCoreIdentifiersCount(["5702887"], usesLenientParsing: true) = error else { + XCTFail() + return + } + XCTAssertEqual(error.description, "fewer than 2 identifiers in version core '5702887'") + } + + } + + func testAdditionalLenientVersionStringParsing() { + XCTAssertEqual(try! Version(versionString: "1.2", usesLenientParsing: true), Version("1.2.0")) + XCTAssertEqual(try? Version(versionString: "1", usesLenientParsing: true), nil) + XCTAssertEqual(try? Version(versionString: "1.2", usesLenientParsing: false), nil) } func testVersionComparison() {