blob: a532d0cfd6612b4449cf15a1b0424d649e0487ef [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <stout/foreach.hpp>
#include <stout/gtest.hpp>
#include <stout/stringify.hpp>
#include <stout/version.hpp>
using std::map;
using std::pair;
using std::string;
using std::vector;
// Verify version comparison operations.
TEST(VersionTest, Comparison)
{
const vector<string> inputs = {
"0.0.0",
"0.2.3",
"0.9.9",
"0.10.4",
"0.20.3",
"1.0.0-alpha",
"1.0.0-alpha.1",
"1.0.0-alpha.-02",
"1.0.0-alpha.-1",
"1.0.0-alpha.1-1",
"1.0.0-alpha.beta",
"1.0.0-beta",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-rc.1",
"1.0.0-rc.1.2",
"1.0.0",
"1.0.1",
"2.0.0"
};
vector<Version> versions;
foreach (const string& input, inputs) {
Try<Version> version = Version::parse(input);
ASSERT_SOME(version);
versions.push_back(version.get());
}
// Check that `versions` is in ascending order.
for (size_t i = 0; i < versions.size(); i++) {
EXPECT_FALSE(versions[i] < versions[i])
<< "Expected " << versions[i] << " < " << versions[i] << " to be false";
for (size_t j = i + 1; j < versions.size(); j++) {
EXPECT_TRUE(versions[i] < versions[j])
<< "Expected " << versions[i] << " < " << versions[j];
EXPECT_FALSE(versions[j] < versions[i])
<< "Expected " << versions[i] << " < " << versions[j] << " to be false";
}
}
}
// Verify that build metadata labels are ignored when determining
// equality and ordering between versions.
TEST(VersionTest, BuildMetadataComparison)
{
Version plain = Version(1, 2, 3);
Version buildMetadata = Version(1, 2, 3, {}, {"abc"});
EXPECT_TRUE(plain == buildMetadata);
EXPECT_FALSE(plain != buildMetadata);
EXPECT_FALSE(plain < buildMetadata);
EXPECT_FALSE(plain > buildMetadata);
}
// Verify that valid version strings are parsed successfully.
TEST(VersionTest, ParseValid)
{
// Each test case consists of an input value and a corresponding
// expected value: the `Version` that corresponds to the input, and
// the result of `operator<<` for that Version.
typedef pair<Version, string> ExpectedValue;
const map<string, ExpectedValue> testCases = {
{"1.20.3", {Version(1, 20, 3), "1.20.3"}},
{"1.20", {Version(1, 20, 0), "1.20.0"}},
{"1", {Version(1, 0, 0), "1.0.0"}},
{"1.20.3-rc1", {Version(1, 20, 3, {"rc1"}), "1.20.3-rc1"}},
{"1.20.3--", {Version(1, 20, 3, {"-"}), "1.20.3--"}},
{"1.20.3+-.-", {Version(1, 20, 3, {}, {"-", "-"}), "1.20.3+-.-"}},
{"1.0.0-alpha.1", {Version(1, 0, 0, {"alpha", "1"}), "1.0.0-alpha.1"}},
{"1.0.0-alpha+001",
{Version(1, 0, 0, {"alpha"}, {"001"}), "1.0.0-alpha+001"}},
{"1.0.0-alpha.-123",
{Version(1, 0, 0, {"alpha", "-123"}), "1.0.0-alpha.-123"}},
{"1+20130313144700",
{Version(1, 0, 0, {}, {"20130313144700"}), "1.0.0+20130313144700"}},
{"1.0.0-beta+exp.sha.5114f8",
{Version(1, 0, 0, {"beta"}, {"exp", "sha", "5114f8"}),
"1.0.0-beta+exp.sha.5114f8"}},
{"1.0.0--1", {Version(1, 0, 0, {"-1"}), "1.0.0--1"}},
{"1.0.0-----1", {Version(1, 0, 0, {"----1"}), "1.0.0-----1"}},
{"1-2-3+4-5",
{Version(1, 0, 0, {"2-3"}, {"4-5"}), "1.0.0-2-3+4-5"}},
{"1-2-3.4+5.6-7",
{Version(1, 0, 0, {"2-3", "4"}, {"5", "6-7"}), "1.0.0-2-3.4+5.6-7"}},
{"1-2.-3+4.-5",
{Version(1, 0, 0, {"2", "-3"}, {"4", "-5"}), "1.0.0-2.-3+4.-5"}},
// Allow leading zeros: in violation of the SemVer spec, but we
// tolerate it for compatibility with common practice (e.g., Docker).
{"01.2.3", {Version(1, 2, 3), "1.2.3"}},
{"1.02.3", {Version(1, 2, 3), "1.2.3"}},
{"1.2.03", {Version(1, 2, 3), "1.2.3"}},
{"1.2.3-alpha.001", {Version(1, 2, 3, {"alpha", "001"}), "1.2.3-alpha.001"}}
};
foreachpair (const string& input, const ExpectedValue& expected, testCases) {
Try<Version> actual = Version::parse(input);
ASSERT_SOME(actual)
<< "Error parsing input '" << input << "'";
EXPECT_EQ(std::get<0>(expected), actual.get())
<< "Incorrect parse of input '" << input << "'";
EXPECT_EQ(std::get<1>(expected), stringify(actual.get()))
<< "Unexpected stringify output for input '" << input << "'";
}
}
// Verify that invalid version strings result in a parse error.
TEST(VersionTest, ParseInvalid)
{
const vector<string> inputs = {
"",
"0.a.b",
"a",
"1.",
".1.2",
"0.1.-2",
"0.-1.2",
"1.2.3.4",
"-1.1.2",
"1.1.2-",
"1.1.2+",
"1.1.2-+",
"1.1.2-.",
"1.1.2+.",
"1.1.2-foo..",
"1.1.2-.foo",
"1.1.2+",
"1.1.2+foo..",
"1.1.2+.foo",
"1.1.2-al^pha",
"1.1.2+exp;",
"-foo",
"+foo",
u8"1.0.0-b\u00e9ta"
};
foreach (const string& input, inputs) {
Try<Version> parse = Version::parse(input);
EXPECT_ERROR(parse)
<< "Expected error on input '" << input
<< "'; got " << parse.get();
}
}