xrpld
Loading...
Searching...
No Matches
tests/libxrpl/beast/SemanticVersion.cpp
1#include <xrpl/beast/core/SemanticVersion.h>
2
3#include <gtest/gtest.h>
4
5#include <algorithm>
6#include <array>
7#include <cctype>
8#include <locale>
9#include <string>
10#include <string_view>
11#include <vector>
12
13namespace beast {
14namespace {
15
16using IdentifierList = SemanticVersion::IdentifierList;
17
18// Version strings are not valid C++ identifiers, so squash their punctuation to
19// turn one into a gtest parameter name.
20std::string
21identifierFor(std::string_view version)
22{
23 std::string name{version};
25 name, [](char c) { return !std::isalnum(c, std::locale::classic()); }, '_');
26 if (!name.empty() && std::isdigit(name.front(), std::locale::classic()))
27 name.insert(0, "v_");
28 return name;
29}
30
31// Pre-release and metadata suffixes, each applied to a "major.minor.patch" base.
32// The valid ones leave a well-formed base well-formed; the invalid ones make any
33// base malformed.
34constexpr auto kValidPreRelease =
35 std::to_array<std::string_view>({"", "-1", "-a", "-a1", "-a1.b1", "-ab.cd", "--"});
36constexpr auto kInvalidPreRelease =
37 std::to_array<std::string_view>({"+", "!", "-", "-!", "-.", "-a.!", "-0.a"});
38constexpr auto kValidMetaData = std::to_array<std::string_view>({"", "+a", "+1", "+a.b", "+ab.cd"});
39constexpr auto kInvalidMetaData =
40 std::to_array<std::string_view>({"!", "+", "++", "+!", "+.", "+a.!"});
41
42// Assembles base + preRelease + metaData and checks whether it parses. A version
43// we accept must also round-trip through print().
44void
45expectParse(
46 std::string_view base,
47 std::string_view preRelease,
48 std::string_view metaData,
49 bool shouldPass)
50{
51 auto const input = std::string{base}.append(preRelease).append(metaData);
52 SCOPED_TRACE(::testing::Message() << '"' << input << '"');
53
55
56 if (shouldPass)
57 {
58 EXPECT_TRUE(v.parse(input));
59 EXPECT_EQ(v.print(), input);
60 }
61 else
62 {
63 EXPECT_FALSE(v.parse(input));
64 }
65}
66
67struct ParseCase
68{
69 std::string_view testName;
70 std::string_view base;
71 bool shouldPass;
72};
73
74std::string
75parseCaseName(::testing::TestParamInfo<ParseCase> const& info)
76{
77 return std::string{info.param.testName};
78}
79
80constexpr auto kParseCases = std::to_array<ParseCase>({
81 {.testName = "zeroes", .base = "0.0.0", .shouldPass = true},
82 {.testName = "simple", .base = "1.2.3", .shouldPass = true},
83 {.testName = "max_int", .base = "2147483647.2147483647.2147483647", .shouldPass = true},
84
85 // negative values
86 {.testName = "negative_major", .base = "-1.2.3", .shouldPass = false},
87 {.testName = "negative_minor", .base = "1.-2.3", .shouldPass = false},
88 {.testName = "negative_patch", .base = "1.2.-3", .shouldPass = false},
89
90 // missing parts
91 {.testName = "empty", .base = "", .shouldPass = false},
92 {.testName = "major_only", .base = "1", .shouldPass = false},
93 {.testName = "major_then_dot", .base = "1.", .shouldPass = false},
94 {.testName = "major_and_minor", .base = "1.2", .shouldPass = false},
95 {.testName = "major_minor_then_dot", .base = "1.2.", .shouldPass = false},
96 {.testName = "missing_major", .base = ".2.3", .shouldPass = false},
97
98 // whitespace
99 {.testName = "leading_space", .base = " 1.2.3", .shouldPass = false},
100 {.testName = "space_after_major", .base = "1 .2.3", .shouldPass = false},
101 {.testName = "space_after_minor", .base = "1.2 .3", .shouldPass = false},
102 {.testName = "trailing_space", .base = "1.2.3 ", .shouldPass = false},
103
104 // leading zeroes
105 {.testName = "leading_zero_in_major", .base = "01.2.3", .shouldPass = false},
106 {.testName = "leading_zero_in_minor", .base = "1.02.3", .shouldPass = false},
107 {.testName = "leading_zero_in_patch", .base = "1.2.03", .shouldPass = false},
108});
109
110struct ValuesCase
111{
112 std::string_view testName;
113 std::string_view input;
114 int majorVersion;
115 int minorVersion;
116 int patchVersion;
117 IdentifierList preReleaseIdentifiers{}; // NOLINT(readability-redundant-member-init)
118 IdentifierList metaData{}; // NOLINT(readability-redundant-member-init)
119};
120
121std::string
122valuesCaseName(::testing::TestParamInfo<ValuesCase> const& info)
123{
124 return std::string{info.param.testName};
125}
126
127std::vector<ValuesCase> const kValuesCases{
128 {
129 .testName = "zero_major",
130 .input = "0.1.2",
131 .majorVersion = 0,
132 .minorVersion = 1,
133 .patchVersion = 2,
134 },
135 {
136 .testName = "simple",
137 .input = "1.2.3",
138 .majorVersion = 1,
139 .minorVersion = 2,
140 .patchVersion = 3,
141 },
142 {
143 .testName = "one_pre_release_identifier",
144 .input = "1.2.3-rc1",
145 .majorVersion = 1,
146 .minorVersion = 2,
147 .patchVersion = 3,
148 .preReleaseIdentifiers = {"rc1"},
149 },
150 {
151 .testName = "two_pre_release_identifiers",
152 .input = "1.2.3-rc1.debug",
153 .majorVersion = 1,
154 .minorVersion = 2,
155 .patchVersion = 3,
156 .preReleaseIdentifiers = {"rc1", "debug"},
157 },
158 {
159 .testName = "three_pre_release_identifiers",
160 .input = "1.2.3-rc1.debug.asm",
161 .majorVersion = 1,
162 .minorVersion = 2,
163 .patchVersion = 3,
164 .preReleaseIdentifiers = {"rc1", "debug", "asm"},
165 },
166 {
167 .testName = "one_metadata_identifier",
168 .input = "1.2.3+full",
169 .majorVersion = 1,
170 .minorVersion = 2,
171 .patchVersion = 3,
172 .metaData = {"full"},
173 },
174 {
175 .testName = "two_metadata_identifiers",
176 .input = "1.2.3+full.prod",
177 .majorVersion = 1,
178 .minorVersion = 2,
179 .patchVersion = 3,
180 .metaData = {"full", "prod"},
181 },
182 {
183 .testName = "three_metadata_identifiers",
184 .input = "1.2.3+full.prod.x86",
185 .majorVersion = 1,
186 .minorVersion = 2,
187 .patchVersion = 3,
188 .metaData = {"full", "prod", "x86"},
189 },
190 {
191 .testName = "pre_release_and_metadata",
192 .input = "1.2.3-rc1.debug.asm+full.prod.x86",
193 .majorVersion = 1,
194 .minorVersion = 2,
195 .patchVersion = 3,
196 .preReleaseIdentifiers = {"rc1", "debug", "asm"},
197 .metaData = {"full", "prod", "x86"},
198 },
199};
200
201struct OrderCase
202{
203 std::string_view lesser;
204 std::string_view greater;
205};
206
207std::string
208orderCaseName(::testing::TestParamInfo<OrderCase> const& info)
209{
210 return identifierFor(info.param.lesser) + "_below_" + identifierFor(info.param.greater);
211}
212
213constexpr auto kOrderCases = std::to_array<OrderCase>({
214 {.lesser = "1.0.0-alpha", .greater = "1.0.0-alpha.1"},
215 {.lesser = "1.0.0-alpha.1", .greater = "1.0.0-alpha.beta"},
216 {.lesser = "1.0.0-alpha.beta", .greater = "1.0.0-beta"},
217 {.lesser = "1.0.0-beta", .greater = "1.0.0-beta.2"},
218 {.lesser = "1.0.0-beta.2", .greater = "1.0.0-beta.11"},
219 {.lesser = "1.0.0-beta.11", .greater = "1.0.0-rc.1"},
220 {.lesser = "1.0.0-rc.1", .greater = "1.0.0"},
221 {.lesser = "0.9.9", .greater = "1.0.0"},
222});
223
224} // namespace
225
226class SemanticVersionParse : public ::testing::TestWithParam<ParseCase>
227{
228};
229
230// Exercises the base string on its own and with every combination of appended
231// pre-release identifiers and metadata.
232TEST_P(SemanticVersionParse, pre_release_and_metadata_combinations)
233{
234 auto const& [testName, base, shouldPass] = GetParam();
235
236 for (auto const preRelease : kValidPreRelease)
237 {
238 for (auto const metaData : kValidMetaData)
239 expectParse(base, preRelease, metaData, shouldPass);
240
241 for (auto const metaData : kInvalidMetaData)
242 expectParse(base, preRelease, metaData, false);
243 }
244
245 // A malformed pre-release section poisons the whole string, whatever
246 // metadata follows it.
247 for (auto const preRelease : kInvalidPreRelease)
248 {
249 for (auto const metaData : kValidMetaData)
250 expectParse(base, preRelease, metaData, false);
251
252 for (auto const metaData : kInvalidMetaData)
253 expectParse(base, preRelease, metaData, false);
254 }
255}
256
258 Inputs,
260 ::testing::ValuesIn(kParseCases),
261 parseCaseName);
262
263class SemanticVersionValues : public ::testing::TestWithParam<ValuesCase>
264{
265};
266
267TEST_P(SemanticVersionValues, decomposes_into_components)
268{
269 auto const& expected = GetParam();
270
272 EXPECT_TRUE(v.parse(expected.input));
273
274 EXPECT_EQ(v.majorVersion, expected.majorVersion);
275 EXPECT_EQ(v.minorVersion, expected.minorVersion);
276 EXPECT_EQ(v.patchVersion, expected.patchVersion);
277
278 EXPECT_EQ(v.preReleaseIdentifiers, expected.preReleaseIdentifiers);
279 EXPECT_EQ(v.metaData, expected.metaData);
280}
281
283 Inputs,
285 ::testing::ValuesIn(kValuesCases),
286 valuesCaseName);
287
288class SemanticVersionOrder : public ::testing::TestWithParam<OrderCase>
289{
290};
291
292TEST_P(SemanticVersionOrder, lesser_precedes_greater)
293{
294 auto const& [lesser, greater] = GetParam();
295
296 // Metadata takes no part in precedence, so attaching it to either side must
297 // leave the ordering untouched.
298 static constexpr auto kMetaData = std::to_array<std::string_view>({"", "+meta"});
299
300 for (auto const lesserMetaData : kMetaData)
301 {
302 for (auto const greaterMetaData : kMetaData)
303 {
304 auto const lesserInput = std::string{lesser}.append(lesserMetaData);
305 auto const greaterInput = std::string{greater}.append(greaterMetaData);
306 SCOPED_TRACE(
307 ::testing::Message() << '"' << lesserInput << "\" < \"" << greaterInput << '"');
308
309 SemanticVersion lesserVersion;
310 SemanticVersion greaterVersion;
311 EXPECT_TRUE(lesserVersion.parse(lesserInput));
312 EXPECT_TRUE(greaterVersion.parse(greaterInput));
313
314 EXPECT_EQ(compare(lesserVersion, lesserVersion), 0);
315 EXPECT_EQ(compare(greaterVersion, greaterVersion), 0);
316 EXPECT_LT(compare(lesserVersion, greaterVersion), 0);
317 EXPECT_GT(compare(greaterVersion, lesserVersion), 0);
318
319 EXPECT_LT(lesserVersion, greaterVersion);
320 EXPECT_GT(greaterVersion, lesserVersion);
321 EXPECT_EQ(lesserVersion, lesserVersion);
322 EXPECT_EQ(greaterVersion, greaterVersion);
323 }
324 }
325}
326
328 Pairs,
330 ::testing::ValuesIn(kOrderCases),
331 orderCaseName);
332
333} // namespace beast
T append(T... args)
A Semantic Version number.
IdentifierList preReleaseIdentifiers
bool parse(std::string_view input)
Parse a semantic version string.
std::vector< std::string > IdentifierList
T classic(T... args)
T empty(T... args)
T front(T... args)
T insert(T... args)
int compare(SemanticVersion const &lhs, SemanticVersion const &rhs)
Compare two SemanticVersions against each other.
INSTANTIATE_TEST_SUITE_P(Inputs, SemanticVersionParse, ::testing::ValuesIn(kParseCases), parseCaseName)
TEST_P(SemanticVersionParse, pre_release_and_metadata_combinations)
T replace_if(T... args)