Full semver 2.0.0 implementation and validation by a number of tests.

This commit is contained in:
Marko Zivanovic
2015-10-08 16:00:22 +02:00
parent 76506c5ffd
commit 2f5981c7d5
7 changed files with 409 additions and 342 deletions
+20 -11
View File
@@ -24,25 +24,25 @@ SOFTWARE.
#pragma once #pragma once
#include <cstdint> #include <iosfwd>
#include <string> #include <string>
#include <vector>
namespace version { namespace version {
enum class Identifier_type { alnum, num };
using Prerelease_identifier = std::pair<std::string, Identifier_type>;
using Prerelease_identifiers = std::vector<Prerelease_identifier>;
using Build_identifier = std::string;
using Build_identifiers = std::vector<Build_identifier>;
struct Version_data { struct Version_data {
int major; int major;
int minor; int minor;
int patch; int patch;
std::string release; Prerelease_identifiers prerelease_ids;
std::string build; Build_identifiers build_ids;
};
struct Semver200_parser {
Version_data parse(const std::string&);
};
struct Semver200_comparator {
int compare(const Version_data&, const Version_data&);
}; };
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
@@ -63,9 +63,18 @@ namespace version {
const Version_data ver_; const Version_data ver_;
}; };
struct Semver200_parser {
Version_data parse(const std::string&);
};
struct Semver200_comparator {
int compare(const Version_data&, const Version_data&);
};
class Semver200 : public Basic_version<Semver200_parser, Semver200_comparator> {}; class Semver200 : public Basic_version<Semver200_parser, Semver200_comparator> {};
class Parse_error : public std::runtime_error { class Parse_error : public std::runtime_error {
using std::runtime_error::runtime_error; using std::runtime_error::runtime_error;
}; };
} }
+1 -1
View File
@@ -3,5 +3,5 @@ include_directories(
) )
add_library(semver add_library(semver
version.cpp Semver200_parser.cpp Semver200_comparator.cpp
) )
+81
View File
@@ -0,0 +1,81 @@
/*
The MIT License (MIT)
Copyright (c) 2015 Marko Zivanovic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "version.h"
using namespace std;
namespace {
using namespace version;
int compare_prerel_identifiers(const Prerelease_identifier& l, Prerelease_identifier& r) {
if (l.second == Identifier_type::alnum && r.second == Identifier_type::alnum) {
return l.first.compare(r.first);
} else if (l.second == Identifier_type::alnum && r.second == Identifier_type::num) {
return 1;
} else if (l.second == Identifier_type::num && r.second == Identifier_type::alnum) {
return -1;
} else if (l.second == Identifier_type::num && r.second == Identifier_type::num) {
int li = stoi(l.first);
int ri = stoi(r.first);
if (li == ri) return 0;
return li > ri ? 1 : -1;
}
throw runtime_error("unexpected identifier types: " + to_string(static_cast<int>(l.second)) + ", " +
to_string(static_cast<int>(r.second)));
}
}
namespace version {
int Semver200_comparator::compare(const Version_data& l, const Version_data& r) {
if (l.major > r.major) return 1;
if (l.major < r.major) return -1;
if (l.minor > r.minor) return 1;
if (l.minor < r.minor) return -1;
if (l.patch > r.patch) return 1;
if (l.patch < r.patch) return -1;
// release version is always higher than prerelease
if (l.prerelease_ids.empty() && !r.prerelease_ids.empty()) return 1;
if (r.prerelease_ids.empty() && !l.prerelease_ids.empty()) return -1;
// compare release by looking at each identifier: numeric ones are compared as numbers, alphanum as ASCII strings
auto shorter = l.prerelease_ids.size() <= r.prerelease_ids.size() ? l.prerelease_ids.size() : r.prerelease_ids.size();
for (size_t i = 0; i < shorter; i++) {
auto lid = l.prerelease_ids[i];
auto rid = r.prerelease_ids[i];
int cmp = compare_prerel_identifiers(lid, rid);
if (cmp != 0) return cmp;
}
// prerels are the same, to the length of the shorter one;
// if they are the same length, then version are equal, otherwise, longer wins
if (l.prerelease_ids.size() == r.prerelease_ids.size()) return 0;
return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1;
}
}
+72 -46
View File
@@ -33,18 +33,19 @@ namespace {
major, minor, patch, release, build major, minor, patch, release, build
}; };
using Phase_transition_hook = function<void(const string&)>; using Phase_transition_hook = function<void(string&)>;
using Transition = tuple<const char, Parser_phase, Phase_transition_hook>; using Transition = tuple<const char, Parser_phase, Phase_transition_hook>;
using Validator = function<void(const string&, const char)>; using Validator = function<void(const string&, const char)>;
inline Transition mkx(const char c, Parser_phase p, Phase_transition_hook px) { inline Transition mkx(const char c, Parser_phase p, Phase_transition_hook pth) {
return make_tuple(c, p, px); return make_tuple(c, p, pth);
} }
inline void process_char(const char c, Parser_phase& phase, const vector<Transition> transitions, inline void process_char(const char c, Parser_phase& phase, Parser_phase& prev_phase,
string& target, Validator validate) { const vector<Transition> transitions, string& target, Validator validate) {
for (const auto& t : transitions) { for (const auto& t : transitions) {
if (c == get<0>(t)) { if (c == get<0>(t)) {
prev_phase = phase;
phase = get<1>(t); phase = get<1>(t);
if (get<2>(t)) get<2>(t)(target); if (get<2>(t)) get<2>(t)(target);
return; return;
@@ -54,35 +55,24 @@ namespace {
target.push_back(c); target.push_back(c);
} }
auto normal_version_validator = [](const string& tgt, const char c) { inline void normal_version_validator(const string& tgt, const char c) {
if (c < '0' || c > '9') throw version::Parse_error("invalid character encountered: " + string(1, c)); if (c < '0' || c > '9') throw version::Parse_error("invalid character encountered: " + string(1, c));
if (tgt.compare(0, 1, "0") == 0) throw version::Parse_error("leading 0 not allowed"); if (tgt.compare(0, 1, "0") == 0) throw version::Parse_error("leading 0 not allowed");
}; }
auto release_version_validator = [](const string& tgt, const char c) { inline void release_version_validator(const string& tgt, const char c) {
if ((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && c != '.' && c != '-') if ((c < '0' || c > '9') && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && c != '.' && c != '-')
throw version::Parse_error("invalid character encountered: " + string(1, c)); throw version::Parse_error("invalid character encountered: " + string(1, c));
};
void validate_release_identifier(const string& id) {
if (id.find_first_not_of("0123456789") == string::npos
&& id.compare(0, 1, "0") == 0) {
throw version::Parse_error("leading 0 not allowed");
}
} }
void validate_release_identifiers(const string& release) { bool is_identifier_numeric(const string& id) {
string id; return id.find_first_not_of("0123456789") == string::npos;
for (const auto& c : release) {
if (c == '.') {
validate_release_identifier(id);
id.clear();
} else {
id.push_back(c);
}
}
validate_release_identifier(id);
} }
bool check_identifier_for_leading_0(const string& id) {
return id.length() > 1 && id[0] == '0';
}
} }
namespace version { namespace version {
@@ -91,18 +81,32 @@ namespace version {
string major; string major;
string minor; string minor;
string patch; string patch;
string release; string release_id;
string build; string build_id;
Prerelease_identifiers release;
Build_identifiers build;
Parser_phase prev_phase;
auto process_release_component = [&](const string& t) { auto release_hook = [&](string& id) {
if (t.empty()) throw Parse_error("version identifier cannot be empty"); if (id.empty()) throw version::Parse_error("version identifier cannot be empty");
if (t.compare(t.length() - 1, 1, ".") == 0) throw Parse_error("version identifier cannot be empty"); Identifier_type t = Identifier_type::alnum;
release.push_back('.');}; if (is_identifier_numeric(id)) {
t = Identifier_type::num;
if (check_identifier_for_leading_0(id)) {
throw Parse_error("numeric identifiers cannot have leading 0");
}
}
release.push_back(Prerelease_identifier(id, t));
id.clear();
};
auto process_build_component = [&](const string& t) { auto build_hook = [&](string& id) {
if (t.empty()) throw Parse_error("version identifier cannot be empty"); // process last token left from parsing release data
if (t.compare(t.length() - 1, 1, ".") == 0) throw Parse_error("version identifier cannot be empty"); if (prev_phase == Parser_phase::release) release_hook(release_id);
build.push_back('.');}; if (id.empty()) throw version::Parse_error("version identifier cannot be empty");
build.push_back(id);
id.clear();
};
auto major_xs = { auto major_xs = {
mkx('.', Parser_phase::minor, {}) mkx('.', Parser_phase::minor, {})
@@ -115,34 +119,41 @@ namespace version {
mkx('+', Parser_phase::build, {}) mkx('+', Parser_phase::build, {})
}; };
auto release_xs = { auto release_xs = {
mkx('.', Parser_phase::release, process_release_component), mkx('.', Parser_phase::release, release_hook),
mkx('+', Parser_phase::build, {}) mkx('+', Parser_phase::build, {})
}; };
auto build_xs = { auto build_xs = {
mkx('.', Parser_phase::build, process_build_component) mkx('.', Parser_phase::build, build_hook)
}; };
Parser_phase phase = Parser_phase::major; Parser_phase phase = Parser_phase::major;
for (const auto& c : s) { for (const auto& c : s) {
switch (phase) { switch (phase) {
case Parser_phase::major: case Parser_phase::major:
process_char(c, phase, major_xs, major, normal_version_validator); process_char(c, phase, prev_phase, major_xs, major, normal_version_validator);
break; break;
case Parser_phase::minor: case Parser_phase::minor:
process_char(c, phase, minor_xs, minor, normal_version_validator); process_char(c, phase, prev_phase, minor_xs, minor, normal_version_validator);
break; break;
case Parser_phase::patch: case Parser_phase::patch:
process_char(c, phase, patch_xs, patch, normal_version_validator); process_char(c, phase, prev_phase, patch_xs, patch, normal_version_validator);
break; break;
case Parser_phase::release: case Parser_phase::release:
process_char(c, phase, release_xs, release, release_version_validator); process_char(c, phase, prev_phase, release_xs, release_id, release_version_validator);
break; break;
case Parser_phase::build: case Parser_phase::build:
process_char(c, phase, build_xs, build, release_version_validator); process_char(c, phase, prev_phase, build_xs, build_id, release_version_validator);
break; break;
} }
} }
validate_release_identifiers(release); // Trigger appropriate hooks in order to process last token, because no phase transition was
// triggered for it.
if (phase == Parser_phase::release) {
release_hook(release_id);
}
if (phase == Parser_phase::build) {
build_hook(build_id);
}
try { try {
return Version_data{ stoi(major), stoi(minor), stoi(patch), release, build }; return Version_data{ stoi(major), stoi(minor), stoi(patch), release, build };
@@ -151,8 +162,23 @@ namespace version {
} }
} }
int Semver200_comparator::compare(const Version_data& l, const Version_data& r) { ostream& operator<<(ostream& os, const Prerelease_identifier& id) {
throw 1; os << id.first << "<" << static_cast<int>(id.second) << ">";
return os;
}
ostream& operator<<(ostream& os, const Prerelease_identifiers& ids) {
for (const auto& id : ids) {
os << id << ",";
}
return os;
}
ostream& operator<<(ostream& os, const Build_identifiers& ids) {
for (const auto& id : ids) {
os << id << ",";
}
return os;
} }
} }
+57 -79
View File
@@ -29,99 +29,77 @@ SOFTWARE.
using namespace version; using namespace version;
int compare(const std::string& l, const std::string& r) { Semver200_comparator c;
Semver200_comparator c; Semver200_parser p;
Semver200_parser p;
inline int compare(const std::string& l, const std::string& r) {
auto lv = p.parse(l); auto lv = p.parse(l);
auto rv = p.parse(r); auto rv = p.parse(r);
return c.compare(lv, rv); return c.compare(lv, rv);
} }
BOOST_AUTO_TEST_CASE(compare_normal_eq) { #define GT(L, R) BOOST_CHECK(compare(L, R) > 0)
BOOST_CHECK(compare("0.0.0", "0.0.0") == 0); #define GE(L, R) BOOST_CHECK(compare(L, R) >= 0)
#define LT(L, R) BOOST_CHECK(compare(L, R) < 0)
#define LE(L, R) BOOST_CHECK(compare(L, R) <= 0)
#define EQ(L, R) BOOST_CHECK(compare(L, R) == 0)
// check normal precedence
BOOST_AUTO_TEST_CASE(compare_normal) {
EQ("1.2.3", "1.2.3");
GT("0.0.2", "0.0.1");
GT("0.2.0", "0.0.3");
GT("0.2.0", "0.1.3");
GT("2.0.0", "0.0.1");
GT("2.0.0", "0.3.1");
GT("2.0.0", "1.3.1");
} }
BOOST_AUTO_TEST_CASE(compare_normal_major_gt_1) { // normal and prerel precedence
BOOST_CHECK(compare("1.0.0", "0.0.0") == 1); BOOST_AUTO_TEST_CASE(compare_normal_prerel) {
GT("1.0.0", "1.0.0-alpha");
GT("1.0.0", "1.0.0-99");
GT("1.0.0", "1.0.0-ZZ");
} }
BOOST_AUTO_TEST_CASE(compare_normal_major_gt_2) { // same prerels
BOOST_CHECK(compare("1.0.0", "0.0.5") == 1); BOOST_AUTO_TEST_CASE(compare_equal_prerels) {
EQ("1.0.0-alpha", "1.0.0-alpha");
EQ("1.0.0-alpha.1", "1.0.0-alpha.1");
EQ("1.0.0-1", "1.0.0-1");
} }
BOOST_AUTO_TEST_CASE(compare_normal_major_gt_3) { // prerels precedence with numeric id
BOOST_CHECK(compare("1.0.0", "0.17.0") == 1); BOOST_AUTO_TEST_CASE(compare_numeric_prerels) {
GT("1.0.0-1", "1.0.0-0");
GT("1.0.0-10", "1.0.0-1");
GT("1.0.0-alpha.3", "1.0.0-alpha.1");
} }
BOOST_AUTO_TEST_CASE(compare_normal_minor_gt_1) { // prerels precedence with alphanum id
BOOST_CHECK(compare("1.1.0", "1.0.0") == 1); BOOST_AUTO_TEST_CASE(compare_alphanum_prerels) {
GT("1.0.0-1", "1.0.0-0");
GT("1.0.0-Z", "1.0.0-A");
GT("1.0.0-Z", "1.0.0-1");
GT("1.0.0-alpha-3", "1.0.0-alpha-1");
GT("1.0.0-alpha-3", "1.0.0-alpha-100");
} }
BOOST_AUTO_TEST_CASE(compare_normal_minor_gt_2) { // prerels precedence misc
BOOST_CHECK(compare("1.1.0", "1.0.32") == 1); BOOST_AUTO_TEST_CASE(compare_misc_prerels) {
} LT("1.0.0-alpha", "1.0.0-alpha.1");
LT("1.0.0-alpha.1", "1.0.0-alpha.beta");
BOOST_AUTO_TEST_CASE(compare_normal_patch_gt) { LT("1.0.0-alpha.beta", "1.0.0-beta");
BOOST_CHECK(compare("1.1.3", "1.1.0") == 1); LT("1.0.0-beta", "1.0.0-beta.2");
} LT("1.0.0-beta.2", "1.0.0-beta.11");
LT("1.0.0-beta.11", "1.0.0-rc.1");
BOOST_AUTO_TEST_CASE(compare_normal_major_lt_1) { LT("1.0.0-rc.1", "1.0.0");
BOOST_CHECK(compare("1.0.0", "2.0.0") == -1);
}
BOOST_AUTO_TEST_CASE(compare_normal_major_lt_2) {
BOOST_CHECK(compare("1.0.1", "2.0.0") == -1);
}
BOOST_AUTO_TEST_CASE(compare_normal_major_lt_3) {
BOOST_CHECK(compare("1.3.0", "2.0.0") == -1);
}
BOOST_AUTO_TEST_CASE(compare_normal_minor_lt_1) {
BOOST_CHECK(compare("1.1.0", "1.2.0") == -1);
}
BOOST_AUTO_TEST_CASE(compare_normal_minor_lt_2) {
BOOST_CHECK(compare("1.1.3", "1.2.0") == -1);
}
BOOST_AUTO_TEST_CASE(compare_normal_patch_lt) {
BOOST_CHECK(compare("1.1.3", "1.1.5") == -1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_single_eq) {
BOOST_CHECK(compare("1.1.3-prerel", "1.1.3-prerel") == 1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_multiple_eq) {
BOOST_CHECK(compare("1.1.3-prerel.1.2.3", "1.1.3-prerel.1.2.3") == 1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_multiple_num_eq) {
BOOST_CHECK(compare("1.1.3-prerel.123", "1.1.3-prerel.123") == 1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_multiple_num_gt) {
BOOST_CHECK(compare("1.1.3-prerel.123", "1.1.3-prerel.120") == 1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_multiple_num_lt) {
BOOST_CHECK(compare("1.1.3-prerel.123", "1.1.3-prerel.125") == -1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_single_alpha_gt) {
BOOST_CHECK(compare("1.1.3-alpha", "1.1.3-beta") == 1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_single_alpha_lt) {
BOOST_CHECK(compare("1.1.3-beta", "1.1.3-gamma") == -1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_single_alpha_gt_2) {
BOOST_CHECK(compare("1.1.3-alpha5", "1.1.3-alpha4") == 1);
}
BOOST_AUTO_TEST_CASE(compare_prerel_single_alpha_lt_2) {
BOOST_CHECK(compare("1.1.3-beta3", "1.1.3-beta5") == -1);
} }
// equal precedence based on build
BOOST_AUTO_TEST_CASE(compare_build) {
EQ("1.0.0", "1.0.0+build.1.2.3");
EQ("1.0.0+ZZZ", "1.0.0+build.1.2.3");
EQ("1.0.0+100", "1.0.0+200");
}
+80 -205
View File
@@ -24,234 +24,109 @@ SOFTWARE.
#define BOOST_TEST_MODULE semver200_parser_tests #define BOOST_TEST_MODULE semver200_parser_tests
#include <boost/test/unit_test.hpp> #include "semver200_parser_util.h"
#include "version.h"
using namespace version; using namespace version;
using namespace std;
Version_data parse(const std::string& s) { // normal must have major, minor and patch version
Semver200_parser p; BOOST_AUTO_TEST_CASE(parse_normal_ids) {
return p.parse(s); CHECK_NORMALS("0.0.0", 0, 0, 0);
CHECK_PARSE_ERROR("1");
CHECK_PARSE_ERROR("1.1");
} }
BOOST_AUTO_TEST_CASE(parse_normal) { // normal versions must be non negative integers
auto r = parse("1.0.0"); BOOST_AUTO_TEST_CASE(parse_normal_positive_ints) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_NORMALS("1.2.3", 1, 2, 3);
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_NORMALS("65535.65534.65533", 65535, 65534, 65533);
BOOST_CHECK_EQUAL(r.patch, 0); CHECK_PARSE_ERROR("-1.0.0");
BOOST_CHECK_EQUAL(r.release, ""); CHECK_PARSE_ERROR("1.-1.0");
BOOST_CHECK_EQUAL(r.build, ""); CHECK_PARSE_ERROR("1.1.-1");
CHECK_PARSE_ERROR("a.0.0");
CHECK_PARSE_ERROR("1.a.0");
CHECK_PARSE_ERROR("1.0.a");
} }
BOOST_AUTO_TEST_CASE(parse_normal_bad_format_1) { // normal versions must not have leading 0
BOOST_CHECK_THROW(parse("bad.version"), Parse_error); BOOST_AUTO_TEST_CASE(parse_normal_leading_0) {
CHECK_PARSE_ERROR("01.0.0");
CHECK_PARSE_ERROR("1.01.0");
CHECK_PARSE_ERROR("1.0.01");
} }
BOOST_AUTO_TEST_CASE(parse_normal_bad_format_2) { // prerel contains one or more dot-separated ids with distinct numeric and mixed ids
BOOST_CHECK_THROW(parse("1.2.3.4"), Parse_error); BOOST_AUTO_TEST_CASE(parse_prerel_ids_types) {
CHECK_PREREL("1.2.3-test", 1, 2, 3, Prerelease_identifiers({ {"test", A} }));
CHECK_PREREL("1.2.3-321", 1, 2, 3, Prerelease_identifiers({ {"321", N} }));
CHECK_PREREL("1.2.3-test.1", 1, 2, 3, Prerelease_identifiers({ {"test", A},{"1",N} }));
CHECK_PREREL("1.2.3-1.test", 1, 2, 3, Prerelease_identifiers({ {"1", N},{"test", A} }));
CHECK_PREREL("1.2.3-test.123456", 1, 2, 3, Prerelease_identifiers({ {"test", A},{"123456", N} }));
CHECK_PREREL("1.2.3-123456.test", 1, 2, 3, Prerelease_identifiers({ {"123456", N},{"test", A} }));
CHECK_PREREL("1.2.3-1.a.22.bb.333.ccc.4444.dddd.55555.fffff", 1, 2, 3, Prerelease_identifiers({ {"1", N},{"a", A},
{"22", N},{"bb", A},{"333", N },{"ccc", A} ,{"4444", N},{"dddd", A} ,{"55555", N},{"fffff", A} }));
} }
BOOST_AUTO_TEST_CASE(parse_prerel_single) { // prerel ids contain only alphanumerics and hyphen
auto r = parse("1.0.0-prerelease"); BOOST_AUTO_TEST_CASE(parse_prerel_legal_chars) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_PREREL("1.2.3-test-1-2-3-CAP", 1, 2, 3, Prerelease_identifiers({ { "test-1-2-3-CAP", A } }));
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_PARSE_ERROR("1.2.3-test#1");
BOOST_CHECK_EQUAL(r.patch, 0); CHECK_PARSE_ERROR("1.2.3-test.©2015");
BOOST_CHECK_EQUAL(r.release, "prerelease"); CHECK_PARSE_ERROR("1.2.3-????-????-1");
BOOST_CHECK_EQUAL(r.build, "");
} }
BOOST_AUTO_TEST_CASE(parse_prerel_ok_multiple_1) { // prerel ids may not be empty
auto r = parse("1.0.0-prerelease.rel-1"); BOOST_AUTO_TEST_CASE(parse_prerel_empty_ids) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_PARSE_ERROR("1.2.3-");
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_PARSE_ERROR("1.2.3-test.");
BOOST_CHECK_EQUAL(r.patch, 0); CHECK_PARSE_ERROR("1.2.3-test..");
BOOST_CHECK_EQUAL(r.release, "prerelease.rel-1"); CHECK_PARSE_ERROR("1.2.3-test..1");
BOOST_CHECK_EQUAL(r.build, "");
} }
BOOST_AUTO_TEST_CASE(parse_prerel_ok_multiple_2) { // prerel numeric ids must not have leading 0
auto r = parse("1.0.0-prerelease.rel-1.b.c.f"); BOOST_AUTO_TEST_CASE(parse_prerel_num_ids_no_leading_0) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_PARSE_ERROR("1.2.3-01");
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_PARSE_ERROR("1.2.3-test.0023");
BOOST_CHECK_EQUAL(r.patch, 0); CHECK_PREREL("1.2.3-test.01a", 1, 2, 3, Prerelease_identifiers({ { "test", A },{ "01a", A } }));
BOOST_CHECK_EQUAL(r.release, "prerelease.rel-1.b.c.f"); CHECK_PREREL("1.2.3-test.01-s", 1, 2, 3, Prerelease_identifiers({ { "test", A },{ "01-s", A } }));
BOOST_CHECK_EQUAL(r.build, "");
} }
BOOST_AUTO_TEST_CASE(parse_normal_build_single) { // build contains one or more dot separated ids
auto r = parse("1.0.0+test"); BOOST_AUTO_TEST_CASE(parse_build_ids) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_BUILD("1.2.3+test", 1, 2, 3, Build_identifiers({ "test" }));
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_BUILD("1.2.3+321", 1, 2, 3, Build_identifiers({ "321" }));
BOOST_CHECK_EQUAL(r.patch, 0); CHECK_BUILD("1.2.3+test.1", 1, 2, 3, Build_identifiers({ "test","1" }));
BOOST_CHECK_EQUAL(r.release, ""); CHECK_BUILD("1.2.3+1.test", 1, 2, 3, Build_identifiers({ "1","test" }));
BOOST_CHECK_EQUAL(r.build, "test"); CHECK_BUILD("1.2.3+test.123456", 1, 2, 3, Build_identifiers({ "test","123456" }));
CHECK_BUILD("1.2.3+123456.test", 1, 2, 3, Build_identifiers({ "123456","test" }));
CHECK_BUILD("1.2.3+1.a.22.bb.333.ccc.4444.dddd.55555.fffff", 1, 2, 3, Build_identifiers({ "1","a","22","bb",
"333","ccc","4444","dddd","55555","fffff" }));
} }
BOOST_AUTO_TEST_CASE(parse_normal_build_multiple) { // build ids contain only alphanumerics and hyphen
auto r = parse("1.0.0+test.123.4.5"); BOOST_AUTO_TEST_CASE(parse_build_legal_chars) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_BUILD("1.2.3+test-1-2-3-CAP", 1, 2, 3, Build_identifiers({ "test-1-2-3-CAP" }));
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_PARSE_ERROR("1.2.3+test#1");
BOOST_CHECK_EQUAL(r.patch, 0); CHECK_PARSE_ERROR("1.2.3+test.©2015");
BOOST_CHECK_EQUAL(r.release, ""); CHECK_PARSE_ERROR("1.2.3+????-????-1");
BOOST_CHECK_EQUAL(r.build, "test.123.4.5");
} }
BOOST_AUTO_TEST_CASE(parse_normal_prerel_single_build_single) { // build ids may not be empty
auto r = parse("1.0.0-prerel+test"); BOOST_AUTO_TEST_CASE(parse_build_empty_ids) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_PARSE_ERROR("1.2.3+");
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_PARSE_ERROR("1.2.3+test.");
BOOST_CHECK_EQUAL(r.patch, 0); CHECK_PARSE_ERROR("1.2.3+test..");
BOOST_CHECK_EQUAL(r.release, "prerel"); CHECK_PARSE_ERROR("1.2.3+test..1");
BOOST_CHECK_EQUAL(r.build, "test");
} }
BOOST_AUTO_TEST_CASE(parse_normal_prerel_single_build_multiple) { // optional prerel must come after patch and build after prerel
auto r = parse("1.0.0-prerel+test.1.2.3"); BOOST_AUTO_TEST_CASE(parse_prerel_build_order) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_PREREL_BUILD("1.2.3-r4+b5", 1, 2, 3, Prerelease_identifiers({ {"r4",A} }), Build_identifiers({ "b5" }));
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_PREREL_BUILD("1.2.3+b4-r5",1,2,3, no_rel_ids, Build_identifiers({ "b4-r5" }));
BOOST_CHECK_EQUAL(r.patch, 0);
BOOST_CHECK_EQUAL(r.release, "prerel");
BOOST_CHECK_EQUAL(r.build, "test.1.2.3");
} }
BOOST_AUTO_TEST_CASE(parse_normal_prerel_multiple_build_single) { // check some corner cases
auto r = parse("1.0.0-prerel.1.2.3+test"); BOOST_AUTO_TEST_CASE(parse_corner_cases) {
BOOST_CHECK_EQUAL(r.major, 1); CHECK_PARSE_ERROR("1.2.3-r4.+b5");
BOOST_CHECK_EQUAL(r.minor, 0); CHECK_PARSE_ERROR("1.2.3-r4+b5.");
BOOST_CHECK_EQUAL(r.patch, 0);
BOOST_CHECK_EQUAL(r.release, "prerel.1.2.3");
BOOST_CHECK_EQUAL(r.build, "test");
}
BOOST_AUTO_TEST_CASE(parse_normal_prerel_multiple_build_multiple) {
auto r = parse("1.0.0-prerel.1.2.3+test.1.2.3");
BOOST_CHECK_EQUAL(r.major, 1);
BOOST_CHECK_EQUAL(r.minor, 0);
BOOST_CHECK_EQUAL(r.patch, 0);
BOOST_CHECK_EQUAL(r.release, "prerel.1.2.3");
BOOST_CHECK_EQUAL(r.build, "test.1.2.3");
}
BOOST_AUTO_TEST_CASE(parse_normal_all_empty) {
BOOST_CHECK_THROW(parse(".."), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_major_empty) {
BOOST_CHECK_THROW(parse(".0.0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_minor_empty) {
BOOST_CHECK_THROW(parse("1..0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_patch_empty) {
BOOST_CHECK_THROW(parse("1.1."), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_major_with_letters) {
BOOST_CHECK_THROW(parse("1a.0.0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_minor_with_letters) {
BOOST_CHECK_THROW(parse("1.0a.0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_patch_with_letters) {
BOOST_CHECK_THROW(parse("1.0.0a"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_major_leading_0) {
BOOST_CHECK_THROW(parse("01.0.0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_minor_leading_0) {
BOOST_CHECK_THROW(parse("1.00.0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_patch_leading_0) {
BOOST_CHECK_THROW(parse("1.0.00"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_major_negative) {
BOOST_CHECK_THROW(parse("-1.0.0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_minor_negative) {
BOOST_CHECK_THROW(parse("1.-1.0"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_normal_patch_negative) {
BOOST_CHECK_THROW(parse("1.0.-1"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_multiple) {
BOOST_CHECK_NO_THROW(parse("1.0.0-pre-rel.123.test.deadbeef.31415"));
}
BOOST_AUTO_TEST_CASE(parse_prerel_invalid_chars) {
BOOST_CHECK_THROW(parse("1.0.0-prerelease#1"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_empty_id) {
BOOST_CHECK_THROW(parse("1.0.0-prerel..test"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_num_with_leading_0) {
BOOST_CHECK_THROW(parse("1.0.0-prerel.01"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_build_single_id) {
BOOST_CHECK_NO_THROW(parse("1.0.0+build"));
}
BOOST_AUTO_TEST_CASE(parse_build_multiple_id) {
BOOST_CHECK_NO_THROW(parse("1.0.0+build.123.test.deadbeef.31415"));
}
BOOST_AUTO_TEST_CASE(parse_build_invalid_chars) {
BOOST_CHECK_THROW(parse("1.0.0+build#1"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_build_empty_id) {
BOOST_CHECK_THROW(parse("1.0.0+build..test"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_single_build_single) {
BOOST_CHECK_NO_THROW(parse("1.0.0-prerelease+build"));
}
BOOST_AUTO_TEST_CASE(parse_prerel_multiple_build_single) {
BOOST_CHECK_NO_THROW(parse("1.0.0-pre-rel.123.test.deadbeef.31415+build"));
}
BOOST_AUTO_TEST_CASE(parse_prerel_invalid_chars_build_single) {
BOOST_CHECK_THROW(parse("1.0.0-prerelease#1+build"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_empty_id_build_single) {
BOOST_CHECK_THROW(parse("1.0.0-prerel..test+build"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_num_with_leading_0_build_single) {
BOOST_CHECK_THROW(parse("1.0.0-prerel.01+build"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_single_build_multiple) {
BOOST_CHECK_NO_THROW(parse("1.0.0-prerelease+build.1.2.3"));
}
BOOST_AUTO_TEST_CASE(parse_prerel_multiple_build_multiple) {
BOOST_CHECK_NO_THROW(parse("1.0.0-pre-rel.123.test.deadbeef.31415+build.1.2.3"));
}
BOOST_AUTO_TEST_CASE(parse_prerel_invalid_chars_build_multiple) {
BOOST_CHECK_THROW(parse("1.0.0-prerelease#1+build.1.2.3"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_empty_id_build_multiple) {
BOOST_CHECK_THROW(parse("1.0.0-prerel..test+build.1.2.3"), Parse_error);
}
BOOST_AUTO_TEST_CASE(parse_prerel_num_with_leading_0_build_multiple) {
BOOST_CHECK_THROW(parse("1.0.0-prerel.01+build.1.2.3"), Parse_error);
} }
+98
View File
@@ -0,0 +1,98 @@
/*
The MIT License (MIT)
Copyright (c) 2015 Marko Zivanovic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include <boost/test/unit_test.hpp>
#include <ostream>
#include "version.h"
version::Semver200_parser p;
const version::Prerelease_identifiers no_rel_ids;
const version::Build_identifiers no_build_ids;
#define N Identifier_type::num
#define A Identifier_type::alnum
namespace boost {
namespace test_tools {
namespace tt_detail {
template<>
void print_log_value<version::Build_identifiers>::operator()(std::ostream& os,
const version::Build_identifiers& ids) {
for (const auto& id : ids) {
os << id << ",";
}
}
template<>
void print_log_value<version::Prerelease_identifiers>::operator()(std::ostream& os,
const version::Prerelease_identifiers& ids) {
for (const auto& id : ids) {
os << id.first << "|" << static_cast<int>(id.second) << "|" << ",";
}
}
}
}
}
#define CHECK_NORMALS(VER, MAJOR, MINOR, PATCH) { \
auto v = p.parse(VER); \
BOOST_CHECK_EQUAL(v.major, MAJOR); \
BOOST_CHECK_EQUAL(v.minor, MINOR); \
BOOST_CHECK_EQUAL(v.patch, PATCH); \
BOOST_CHECK_EQUAL(v.prerelease_ids, no_rel_ids); \
BOOST_CHECK_EQUAL(v.build_ids, no_build_ids); \
}
#define CHECK_PARSE_ERROR(VER) { \
BOOST_CHECK_THROW(p.parse(VER), version::Parse_error); \
}
#define CHECK_PREREL(VER, MAJOR, MINOR, PATCH, IDS) { \
auto v = p.parse(VER); \
BOOST_CHECK_EQUAL(v.major, MAJOR); \
BOOST_CHECK_EQUAL(v.minor, MINOR); \
BOOST_CHECK_EQUAL(v.patch, PATCH); \
BOOST_CHECK_EQUAL(v.prerelease_ids, IDS); \
BOOST_CHECK_EQUAL(v.build_ids, no_build_ids); \
}
#define CHECK_BUILD(VER, MAJOR, MINOR, PATCH, IDS) { \
auto v = p.parse(VER); \
BOOST_CHECK_EQUAL(v.major, MAJOR); \
BOOST_CHECK_EQUAL(v.minor, MINOR); \
BOOST_CHECK_EQUAL(v.patch, PATCH); \
BOOST_CHECK_EQUAL(v.prerelease_ids, no_rel_ids); \
BOOST_CHECK_EQUAL(v.build_ids, IDS); \
}
#define CHECK_PREREL_BUILD(VER, MAJOR, MINOR, PATCH, RIDS, BIDS) { \
auto v = p.parse(VER); \
BOOST_CHECK_EQUAL(v.major, MAJOR); \
BOOST_CHECK_EQUAL(v.minor, MINOR); \
BOOST_CHECK_EQUAL(v.patch, PATCH); \
BOOST_CHECK_EQUAL(v.prerelease_ids, RIDS); \
BOOST_CHECK_EQUAL(v.build_ids, BIDS); \
}