@@ -0,0 +1,27 @@ | |||
#pragma once | |||
#include <semver/ident.hpp> | |||
namespace semver { | |||
class build_metadata { | |||
std::vector<ident> _ids; | |||
public: | |||
build_metadata() = default; | |||
[[nodiscard]] bool empty() const noexcept { return _ids.empty(); } | |||
void add_ident(ident id) noexcept { _ids.push_back(id); } | |||
void add_ident(std::string_view s) { add_ident(ident(s)); } | |||
auto& idents() const noexcept { return _ids; } | |||
static build_metadata parse(std::string_view s) { | |||
build_metadata ret; | |||
ret._ids = ident::parse_dotted_seq(s); | |||
return ret; | |||
} | |||
}; | |||
} // namespace semver |
@@ -0,0 +1,114 @@ | |||
#include "./ident.hpp" | |||
#include <cassert> | |||
#include <charconv> | |||
using namespace semver; | |||
ident::ident(std::string_view str) { | |||
const auto str_begin = str.data(); | |||
auto ptr = str_begin; | |||
const auto str_end = str_begin + str.size(); | |||
bool any_alpha = false; | |||
if (ptr == str_end) { | |||
throw invalid_ident(std::string(str)); | |||
} | |||
for (; ptr != str_end; ++ptr) { | |||
any_alpha = any_alpha || (*ptr == '-' || std::isalpha(*ptr)); | |||
if (!std::isalnum(*ptr) && *ptr != '-') { | |||
break; | |||
} | |||
} | |||
if (ptr != str_end) { | |||
throw invalid_ident(std::string(str)); | |||
} | |||
_str.assign(str_begin, ptr); | |||
if (any_alpha) { | |||
_kind = ident_kind::alphanumeric; | |||
} else if (_str.size() > 1 && _str[0] == '0') { | |||
_kind = ident_kind::digits; | |||
} else { | |||
_kind = ident_kind::numeric; | |||
// Check that the integer is representable on this system | |||
std::uint64_t n; | |||
auto res = std::from_chars(str_begin, str_end, n); | |||
if (res.ptr != str_end) { | |||
throw invalid_ident(_str); | |||
} | |||
} | |||
} | |||
std::vector<ident> ident::parse_dotted_seq(const std::string_view s) { | |||
std::vector<ident> acc; | |||
auto remaining = s; | |||
while (!remaining.empty()) { | |||
auto next_dot = remaining.find('.'); | |||
auto id_sub = remaining.substr(0, next_dot); | |||
if (id_sub.empty()) { | |||
throw invalid_ident(std::string(s)); | |||
} | |||
acc.emplace_back(id_sub); | |||
if (next_dot == remaining.npos) { | |||
break; | |||
} | |||
remaining = remaining.substr(next_dot + 1); | |||
if (remaining.empty()) { | |||
throw invalid_ident(std::string(s)); | |||
} | |||
} | |||
if (acc.empty()) { | |||
throw invalid_ident(std::string(s)); | |||
} | |||
return acc; | |||
} | |||
order semver::compare(ident_kind lhs, ident_kind rhs) noexcept { | |||
assert(lhs != ident_kind::digits && "Ordering with digit identifiers is undefined"); | |||
assert(rhs != ident_kind::digits && "Ordering with digit identifiers is undefined"); | |||
if (lhs == rhs) { | |||
return order::equivalent; | |||
} else if (lhs == ident_kind::numeric) { | |||
// numeric is less than alnum | |||
return order::less; | |||
} else { | |||
return order::greater; | |||
} | |||
} | |||
order semver::compare(const ident& lhs, const ident& rhs) noexcept { | |||
auto ord = compare(lhs.kind(), rhs.kind()); | |||
if (ord != order::equivalent) { | |||
return ord; | |||
} | |||
assert(lhs.kind() == rhs.kind() && "[semver library bug]"); | |||
if (lhs.kind() == ident_kind::numeric) { | |||
auto as_int = [](auto& str) { | |||
std::uint64_t value; | |||
auto fc_res = std::from_chars(str.data(), str.data() + str.size(), value); | |||
assert(fc_res.ptr == str.data() + str.size()); | |||
return value; | |||
}; | |||
auto lhs_num = as_int(lhs.string()); | |||
auto rhs_num = as_int(rhs.string()); | |||
if (lhs_num == rhs_num) { | |||
return order::equivalent; | |||
} else if (lhs_num < rhs_num) { | |||
return order::less; | |||
} else { | |||
return order::greater; | |||
} | |||
} | |||
assert(lhs.kind() == ident_kind::alphanumeric && "[semver library bug]"); | |||
auto comp = lhs.string().compare(rhs.string()); | |||
if (comp == 0) { | |||
return order::equivalent; | |||
} else if (comp < 0) { | |||
return order::less; | |||
} else { | |||
return order::greater; | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
#pragma once | |||
#include <semver/order.hpp> | |||
#include <stdexcept> | |||
#include <string> | |||
#include <string_view> | |||
#include <vector> | |||
namespace semver { | |||
class invalid_ident : public std::runtime_error { | |||
std::string _str; | |||
public: | |||
invalid_ident(std::string s) | |||
: std::runtime_error::runtime_error("Invalid metadata identifier: " + s) | |||
, _str(s) {} | |||
auto& string() const noexcept { return _str; } | |||
}; | |||
enum class ident_kind { | |||
alphanumeric, | |||
numeric, | |||
digits, | |||
}; | |||
order compare(ident_kind lhs, ident_kind rhs) noexcept; | |||
class ident; | |||
order compare(const ident& lhs, const ident& rhs) noexcept; | |||
class ident { | |||
std::string _str; | |||
ident_kind _kind; | |||
public: | |||
explicit ident(std::string_view str); | |||
auto kind() const noexcept { return _kind; } | |||
const auto& string() const noexcept { return _str; } | |||
#define DEF_OP(op, expr) \ | |||
inline friend bool operator op(const ident& lhs, const ident& rhs) noexcept { \ | |||
auto o = compare(lhs, rhs); \ | |||
return (expr); \ | |||
} \ | |||
static_assert(true) | |||
DEF_OP(==, (o == order::equivalent)); | |||
DEF_OP(!=, (o != order::equivalent)); | |||
DEF_OP(<, (o == order::less)); | |||
DEF_OP(>, (o == order::greater)); | |||
DEF_OP(<=, (o == order::less || o == order::equivalent)); | |||
DEF_OP(>=, (o == order::greater || o == order::equivalent)); | |||
#undef DEF_OP | |||
static std::vector<ident> parse_dotted_seq(std::string_view s); | |||
}; | |||
} // namespace semver |
@@ -0,0 +1,89 @@ | |||
#include <semver/ident.hpp> | |||
#include <catch2/catch.hpp> | |||
TEST_CASE("Parse identifiers") { | |||
struct case_ { | |||
std::string str; | |||
semver::ident_kind expect_kind; | |||
}; | |||
case_ cases[] = { | |||
{"foo", semver::ident_kind::alphanumeric}, | |||
{"12", semver::ident_kind::numeric}, | |||
{"03412", semver::ident_kind::digits}, | |||
{"0", semver::ident_kind::numeric}, | |||
{"00", semver::ident_kind::digits}, | |||
}; | |||
for (auto [str, expect] : cases) { | |||
INFO("Checking parsing of '" << str << "'"); | |||
auto id = semver::ident(str); | |||
CHECK(id.string() == str); | |||
CHECK(id.kind() == expect); | |||
} | |||
} | |||
TEST_CASE("Invalid identifiers") { | |||
#define BAD_IDENT(x) CHECK_THROWS_AS(semver::ident(x), semver::invalid_ident) | |||
BAD_IDENT(""); | |||
BAD_IDENT("="); | |||
BAD_IDENT("asdf-_"); | |||
BAD_IDENT("."); | |||
BAD_IDENT(" "); | |||
BAD_IDENT("124a["); | |||
} | |||
TEST_CASE("Ident comparison") { | |||
using semver::order; | |||
struct comp_test { | |||
std::string lhs; | |||
std::string rhs; | |||
order expect_ordering; | |||
}; | |||
comp_test comparisons[] = { | |||
{"foo", "bar", order::greater}, | |||
{"foo", "foo", order::equivalent}, | |||
{"bar", "foo", order::less}, | |||
{"12", "333", order::less}, | |||
{"fooood", "3", order::greater}, | |||
{"0", "0", order::equivalent}, | |||
{"34", "f", order::less}, | |||
{"aaaaaaaaaaa", "z", order::less}, | |||
}; | |||
for (auto [lhs, rhs, expect] : comparisons) { | |||
auto lhs_id = semver::ident(lhs); | |||
auto rhs_id = semver::ident(rhs); | |||
INFO("Comparing '" << lhs << "' to '" << rhs << "'"); | |||
auto result = semver::compare(lhs_id, rhs_id); | |||
CHECK(result == expect); | |||
} | |||
} | |||
TEST_CASE("Parse dotted sequence") { | |||
struct case_ { | |||
std::string str; | |||
std::vector<std::string> expected; | |||
}; | |||
case_ cases[] = { | |||
{"foo", {"foo"}}, | |||
{"foo.bar", {"foo", "bar"}}, | |||
}; | |||
for (auto& [str, expected] : cases) { | |||
INFO("Parsing dotted-ident-sequence '" << str << "'"); | |||
auto actual = semver::ident::parse_dotted_seq(str); | |||
REQUIRE(actual.size() == expected.size()); | |||
} | |||
} | |||
TEST_CASE("Invalid dotted sequence") { | |||
std::string_view bad_seqs[] = { | |||
"", | |||
".", | |||
".foo", | |||
"foo.bar.", | |||
"foo..bar", | |||
}; | |||
for (auto s : bad_seqs) { | |||
INFO("Checking bad ident sequence '" << s << "'"); | |||
CHECK_THROWS_AS(semver::ident::parse_dotted_seq(s), semver::invalid_ident); | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
#pragma once | |||
namespace semver { | |||
enum class order { | |||
less = -1, | |||
equivalent = 0, | |||
greater = 1, | |||
}; | |||
} // namespace semver |
@@ -0,0 +1,40 @@ | |||
#include "./prerelease.hpp" | |||
using namespace semver; | |||
prerelease prerelease::parse(std::string_view s) { | |||
auto ids = ident::parse_dotted_seq(s); | |||
for (auto& id : ids) { | |||
if (id.kind() == ident_kind::digits) { | |||
throw invalid_ident("Prerelease strings may not have plain-digit identifiers: " | |||
+ std::string(s)); | |||
} | |||
} | |||
prerelease ret; | |||
ret._ids = std::move(ids); | |||
return ret; | |||
} | |||
order semver::compare(const prerelease& lhs, const prerelease& rhs) noexcept { | |||
auto lhs_iter = lhs.idents().cbegin(); | |||
auto rhs_iter = rhs.idents().cbegin(); | |||
const auto lhs_end = lhs.idents().cend(); | |||
const auto rhs_end = rhs.idents().cend(); | |||
for (; lhs_iter != lhs_end && rhs_iter != rhs_end; ++lhs_iter, ++rhs_iter) { | |||
auto ord = compare(*lhs_iter, *rhs_iter); | |||
if (ord != order::equivalent) { | |||
return ord; | |||
} | |||
} | |||
if (lhs_iter != lhs_end) { | |||
// Left-hand is longer | |||
return order::greater; | |||
} else if (rhs_iter != rhs_end) { | |||
// Right-hand is longer | |||
return order::less; | |||
} else { | |||
// They are equivalent | |||
return order::equivalent; | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
#pragma once | |||
#include <semver/ident.hpp> | |||
#include <semver/order.hpp> | |||
#include <stdexcept> | |||
#include <string_view> | |||
#include <vector> | |||
namespace semver { | |||
class prerelease; | |||
order compare(const prerelease& lhs, const prerelease& rhs) noexcept; | |||
class prerelease { | |||
std::vector<ident> _ids; | |||
public: | |||
prerelease() = default; | |||
[[nodiscard]] bool empty() const noexcept { return _ids.empty(); } | |||
void add_ident(ident id); | |||
void add_ident(std::string_view str) { add_ident(ident(str)); } | |||
auto& idents() const noexcept { return _ids; } | |||
static prerelease parse(std::string_view str); | |||
#define DEF_OP(op, expr) \ | |||
inline friend bool operator op(const prerelease& lhs, const prerelease& rhs) noexcept { \ | |||
auto o = compare(lhs, rhs); \ | |||
return (expr); \ | |||
} \ | |||
static_assert(true) | |||
DEF_OP(==, (o == order::equivalent)); | |||
DEF_OP(!=, (o != order::equivalent)); | |||
DEF_OP(<, (o == order::less)); | |||
DEF_OP(>, (o == order::greater)); | |||
DEF_OP(<=, (o == order::less || o == order::equivalent)); | |||
DEF_OP(>=, (o == order::greater || o == order::equivalent)); | |||
#undef DEF_OP | |||
}; | |||
} // namespace semver |
@@ -0,0 +1,48 @@ | |||
#include "./prerelease.hpp" | |||
#include <catch2/catch.hpp> | |||
TEST_CASE("Parse a prerelease") { | |||
auto pre = semver::prerelease::parse("foo.bar"); | |||
CHECK(pre.idents() == semver::ident::parse_dotted_seq("foo.bar")); | |||
pre = pre.parse("foo.0"); | |||
CHECK(pre.idents() == semver::ident::parse_dotted_seq("foo.0")); | |||
} | |||
TEST_CASE("Invalid prereleases") { | |||
std::string_view bad_tags[] = { | |||
"foo.", // Just plain bad | |||
"foo.122.03", // Leading zero | |||
}; | |||
for (auto bad : bad_tags) { | |||
CHECK_THROWS_AS(semver::prerelease::parse(bad), semver::invalid_ident); | |||
} | |||
} | |||
TEST_CASE("Compare prereleases") { | |||
using semver::order; | |||
struct case_ { | |||
std::string lhs; | |||
std::string rhs; | |||
order expected_ord; | |||
}; | |||
case_ cases[] = { | |||
{"foo.bar", "foo.bar", order::equivalent}, | |||
{"foo", "foo.bar", order::less}, | |||
{"foo.bar", "foo", order::greater}, | |||
{"foo", "foo", order::equivalent}, | |||
{"foo.1", "foo.bar", order::less}, // numeric is less | |||
{"foo.foo", "foo.bar", order::greater}, | |||
{"alpha", "beta", order::less}, | |||
{"alpha.1", "alpha.2", order::less}, | |||
}; | |||
for (auto& [lhs, rhs, exp] : cases) { | |||
INFO("Comparing prerelease '" << lhs << "' to '" << rhs << "'"); | |||
auto lhs_pre = semver::prerelease::parse(lhs); | |||
auto rhs_pre = semver::prerelease::parse(rhs); | |||
auto actual = semver::compare(lhs_pre, rhs_pre); | |||
CHECK(actual == exp); | |||
} | |||
} |
@@ -14,38 +14,57 @@ namespace { | |||
version parse(const char* ptr, std::size_t size) { | |||
version ret; | |||
// const auto str_begin = ptr; | |||
const auto str_end = ptr + size; | |||
const auto str_begin = ptr; | |||
const auto str_end = ptr + size; | |||
auto get_str = [=] { return std::string(str_begin, size); }; | |||
auto cur_off = [&] { return ptr - str_begin; }; | |||
std::from_chars_result fc_res; | |||
auto did_error = [&](int elem) { return fc_res.ec == std::errc::invalid_argument || elem < 0; }; | |||
// Parse major | |||
fc_res = std::from_chars(ptr, str_end, ret.major); | |||
ptr = fc_res.ptr; | |||
if (did_error(ret.major) || fc_res.ptr == str_end || *fc_res.ptr != '.') { | |||
throw invalid_version(0); | |||
throw invalid_version(get_str(), cur_off()); | |||
} | |||
// Parse minor | |||
ptr = fc_res.ptr + 1; | |||
fc_res = std::from_chars(ptr, str_end, ret.minor); | |||
ptr = fc_res.ptr; | |||
if (did_error(ret.minor) || fc_res.ptr == str_end || *fc_res.ptr != '.') { | |||
throw invalid_version(ptr - str_end); | |||
throw invalid_version(get_str(), cur_off()); | |||
} | |||
// Parse patch | |||
ptr = fc_res.ptr + 1; | |||
fc_res = std::from_chars(ptr, str_end, ret.patch); | |||
ptr = fc_res.ptr; | |||
if (did_error(ret.patch)) { | |||
throw invalid_version(ptr - str_end); | |||
throw invalid_version(get_str(), cur_off()); | |||
} | |||
if (fc_res.ptr != str_end) { | |||
assert(false && "More complex version numbers are not ready yet!"); | |||
throw invalid_version(-42); | |||
auto remaining = std::string_view(ptr, str_end - ptr); | |||
if (!remaining.empty() && remaining[0] == '-') { | |||
auto plus_pos = remaining.find('+'); | |||
auto prerelease_str = remaining.substr(1, plus_pos - 1); | |||
ret.prerelease = prerelease::parse(prerelease_str); | |||
remaining = remaining.substr(prerelease_str.size() + 1); | |||
} | |||
if (!remaining.empty() && remaining[0] == '+') { | |||
auto bmeta_str = remaining.substr(1); | |||
ret.build_metadata = build_metadata::parse(bmeta_str); | |||
remaining = remaining.substr(bmeta_str.size() + 1); | |||
} | |||
if (!remaining.empty()) { | |||
throw invalid_version(get_str(), remaining.data() - str_begin); | |||
} | |||
return ret; | |||
} | |||
} // namespace | |||
} // namespace | |||
@@ -72,5 +91,48 @@ std::string version::to_string() const noexcept { | |||
*buf_ptr++ = '.'; | |||
conv_one(patch); | |||
return std::string(buf_begin, (buf_ptr - buf_begin)); | |||
auto main_ver = std::string(buf_begin, (buf_ptr - buf_begin)); | |||
auto format_ids = [](auto& ids) { | |||
std::string acc; | |||
auto it = ids.cbegin(); | |||
auto stop = ids.cend(); | |||
while (it != stop) { | |||
acc += it->string(); | |||
++it; | |||
if (it != stop) { | |||
acc += "."; | |||
} | |||
} | |||
return acc; | |||
}; | |||
if (!prerelease.empty()) { | |||
main_ver += "-" + format_ids(prerelease.idents()); | |||
} | |||
if (!build_metadata.empty()) { | |||
main_ver += "+" + format_ids(build_metadata.idents()); | |||
} | |||
return main_ver; | |||
} | |||
order semver::compare(const version& lhs, const version& rhs) noexcept { | |||
auto lhs_tup = std::tie(lhs.major, lhs.minor, lhs.patch); | |||
auto rhs_tup = std::tie(rhs.major, rhs.minor, rhs.patch); | |||
if (lhs_tup < rhs_tup) { | |||
return order::less; | |||
} else if (lhs_tup > rhs_tup) { | |||
return order::greater; | |||
} else { | |||
if (!lhs.is_prerelease() && rhs.is_prerelease()) { | |||
// No prerelease is greater than any prerelease | |||
return order::greater; | |||
} else if (lhs.is_prerelease() && !rhs.is_prerelease()) { | |||
// A prerelease version is lesser than any non-prerelease version | |||
return order::less; | |||
} else { | |||
// Compare the prerelease tags | |||
return compare(lhs.prerelease, rhs.prerelease); | |||
} | |||
} | |||
} |
@@ -1,5 +1,8 @@ | |||
#pragma once | |||
#include <semver/build_metadata.hpp> | |||
#include <semver/prerelease.hpp> | |||
#include <stdexcept> | |||
#include <string> | |||
#include <string_view> | |||
@@ -9,37 +12,52 @@ | |||
namespace semver { | |||
class invalid_version : public std::runtime_error { | |||
std::string _string; | |||
std::ptrdiff_t _offset = 0; | |||
public: | |||
invalid_version(std::ptrdiff_t n) | |||
: runtime_error("Invalid version number") | |||
invalid_version(std::string string, std::ptrdiff_t n) | |||
: runtime_error("Invalid semantic version: " + string) | |||
, _string(string) | |||
, _offset(n) {} | |||
auto offset() const noexcept { return _offset; } | |||
auto& string() const noexcept { return _string; } | |||
auto offset() const noexcept { return _offset; } | |||
}; | |||
class version; | |||
order compare(const version& lhs, const version& rhs) noexcept; | |||
struct version { | |||
int major = 0; | |||
int minor = 0; | |||
int patch = 0; | |||
// Prerelease tag is optional: | |||
class prerelease prerelease = {}; | |||
// Build metadata is optional: | |||
class build_metadata build_metadata = {}; | |||
static version parse(std::string_view s); | |||
std::string to_string() const noexcept; | |||
auto tie() const noexcept { return std::tie(major, minor, patch); } | |||
auto tie() noexcept { return std::tie(major, minor, patch); } | |||
bool is_prerelease() const noexcept { return !prerelease.empty(); } | |||
#define DEF_OP(op, expr) \ | |||
inline friend bool operator op(const version& lhs, const version& rhs) noexcept { \ | |||
auto o = compare(lhs, rhs); \ | |||
return (expr); \ | |||
} \ | |||
static_assert(true) | |||
DEF_OP(==, (o == order::equivalent)); | |||
DEF_OP(!=, (o != order::equivalent)); | |||
DEF_OP(<, (o == order::less)); | |||
DEF_OP(>, (o == order::greater)); | |||
DEF_OP(<=, (o == order::less || o == order::equivalent)); | |||
DEF_OP(>=, (o == order::greater || o == order::equivalent)); | |||
#undef DEF_OP | |||
friend inline std::string to_string(const version& ver) noexcept { return ver.to_string(); } | |||
}; | |||
inline bool operator!=(const version& lhs, const version& rhs) noexcept { | |||
return lhs.tie() != rhs.tie(); | |||
} | |||
inline bool operator<(const version& lhs, const version& rhs) noexcept { | |||
return lhs.tie() < rhs.tie(); | |||
} | |||
inline std::string to_string(const version& ver) noexcept { return ver.to_string(); } | |||
} // namespace semver |
@@ -13,4 +13,82 @@ TEST_CASE("Parsing") { | |||
CHECK(v1.to_string() == "1.2.55"); | |||
v1.major = 999999; | |||
CHECK(v1.to_string() == "999999.2.55"); | |||
v1 = semver::version::parse("1.2.3-r1"); | |||
CHECK(v1.prerelease == semver::prerelease::parse("r1")); | |||
CHECK(v1.to_string() == "1.2.3-r1"); | |||
v1 = semver::version::parse("1.2.3-3"); | |||
CHECK(v1.prerelease == semver::prerelease::parse("3")); | |||
v1 = semver::version::parse("1.2.3-0"); | |||
CHECK(v1.prerelease == semver::prerelease::parse("0")); | |||
v1 = semver::version::parse("1.2.3--fasdf"); | |||
CHECK(v1.prerelease == semver::prerelease::parse("-fasdf")); | |||
v1 = semver::version::parse("1.2.3-foo.bar"); | |||
CHECK(v1.prerelease == semver::prerelease::parse("foo.bar")); | |||
v1 = semver::version::parse("1.2.3-foo.bar+cats"); | |||
CHECK(v1.prerelease == semver::prerelease::parse("foo.bar")); | |||
CHECK(v1.build_metadata.idents() == semver::ident::parse_dotted_seq("cats")); | |||
} | |||
TEST_CASE("Compare versions") { | |||
using semver::order; | |||
struct case_ { | |||
std::string_view lhs; | |||
std::string_view rhs; | |||
order expect_ord; | |||
}; | |||
case_ cases[] = { | |||
{"1.2.3", "1.2.3", order::equivalent}, | |||
{"1.2.3-alpha", "1.2.3-alpha", order::equivalent}, | |||
{"1.2.3-alpha", "1.2.3", order::less}, | |||
{"1.2.3-alpha.1", "1.2.3-beta", order::less}, | |||
{"1.2.3-alpha.1", "1.2.3-alpha.2", order::less}, | |||
{"1.2.3-alpha.4", "1.2.3-alpha.2", order::greater}, | |||
{"1.2.1", "1.2.2-alpha.2", order::less}, | |||
{"1.2.1", "1.2.1+foo", order::equivalent}, // Build metadata has no effect | |||
}; | |||
for (auto [lhs, rhs, exp] : cases) { | |||
INFO("Compare version '" << lhs << "' to '" << rhs << "'"); | |||
auto lhs_ver = semver::version::parse(lhs); | |||
auto rhs_ver = semver::version::parse(rhs); | |||
auto actual = semver::compare(lhs_ver, rhs_ver); | |||
CHECK(actual == exp); | |||
} | |||
} | |||
TEST_CASE("Invalid versions") { | |||
struct invalid_version { | |||
std::string str; | |||
int bad_offset; | |||
}; | |||
invalid_version versions[] = { | |||
{"", 0}, | |||
{"1.", 2}, | |||
{"1.2", 3}, | |||
{"1.2.", 4}, | |||
{"1.e", 2}, | |||
{"lol", 0}, | |||
{"1e.3.1", 1}, | |||
{"1.2.5-", 6}, | |||
{"1.2.5-02", 6}, | |||
{"1.2.3-1..3", 8}, | |||
}; | |||
for (auto&& [str, bad_offset] : versions) { | |||
INFO("Checking for failure while parsing bad version string '" << str << "'"); | |||
try { | |||
auto ver = semver::version::parse(str); | |||
FAIL_CHECK("Parsing didn't throw! Produced version: " << ver.to_string()); | |||
} catch (const semver::invalid_version& e) { | |||
CHECK(e.offset() == bad_offset); | |||
} catch (const semver::invalid_ident&) { | |||
// Nothing to check, but a valid error | |||
} | |||
} | |||
} |