Support semver-style prerelease identifiers in version parsing
Use ec_semver from erlware for implementation. This is the same module used in rebar3. This changes some existing behaviour, e.g.: 3.0 is now minor-equivalent to 3.0.0 and 3.0.0.1 'master' is now a valid in version_compare Add property tests [#131650399]
This commit is contained in:
parent
acbb153054
commit
64238b139d
|
|
@ -1,7 +1,7 @@
|
|||
PROJECT = rabbit_common
|
||||
|
||||
BUILD_DEPS = rabbitmq_codegen
|
||||
TEST_DEPS = mochiweb
|
||||
TEST_DEPS = mochiweb proper
|
||||
|
||||
.DEFAULT_GOAL = all
|
||||
|
||||
|
|
|
|||
|
|
@ -737,52 +737,27 @@ compose_pid(Node, Cre, Id, Ser) ->
|
|||
<<131,NodeEnc/binary>> = term_to_binary(Node),
|
||||
binary_to_term(<<131,103,NodeEnc/binary,Id:32,Ser:32,Cre:8>>).
|
||||
|
||||
version_compare(A, B, lte) ->
|
||||
case version_compare(A, B) of
|
||||
eq -> true;
|
||||
lt -> true;
|
||||
gt -> false
|
||||
end;
|
||||
version_compare(A, B, gte) ->
|
||||
case version_compare(A, B) of
|
||||
eq -> true;
|
||||
gt -> true;
|
||||
lt -> false
|
||||
end;
|
||||
version_compare(A, B, Result) ->
|
||||
Result =:= version_compare(A, B).
|
||||
version_compare(A, B, eq) -> ec_semver:eql(A, B);
|
||||
version_compare(A, B, lt) -> ec_semver:lt(A, B);
|
||||
version_compare(A, B, lte) -> ec_semver:lte(A, B);
|
||||
version_compare(A, B, gt) -> ec_semver:gt(A, B);
|
||||
version_compare(A, B, gte) -> ec_semver:gte(A, B).
|
||||
|
||||
version_compare(A, A) ->
|
||||
eq;
|
||||
version_compare([], [$0 | B]) ->
|
||||
version_compare([], dropdot(B));
|
||||
version_compare([], _) ->
|
||||
lt; %% 2.3 < 2.3.1
|
||||
version_compare([$0 | A], []) ->
|
||||
version_compare(dropdot(A), []);
|
||||
version_compare(_, []) ->
|
||||
gt; %% 2.3.1 > 2.3
|
||||
version_compare(A, B) ->
|
||||
{AStr, ATl} = lists:splitwith(fun (X) -> X =/= $. end, A),
|
||||
{BStr, BTl} = lists:splitwith(fun (X) -> X =/= $. end, B),
|
||||
ANum = list_to_integer(AStr),
|
||||
BNum = list_to_integer(BStr),
|
||||
if ANum =:= BNum -> version_compare(dropdot(ATl), dropdot(BTl));
|
||||
ANum < BNum -> lt;
|
||||
ANum > BNum -> gt
|
||||
version_compare(A, B) ->
|
||||
case version_compare(A, B, lt) of
|
||||
true -> lt;
|
||||
false -> case version_compare(A, B, gt) of
|
||||
true -> gt;
|
||||
false -> eq
|
||||
end
|
||||
end.
|
||||
|
||||
%% a.b.c and a.b.d match, but a.b.c and a.d.e don't. If
|
||||
%% versions do not match that pattern, just compare them.
|
||||
version_minor_equivalent(A, B) ->
|
||||
{ok, RE} = re:compile("^(\\d+\\.\\d+)(\\.\\d+)\$"),
|
||||
Opts = [{capture, all_but_first, list}],
|
||||
case {re:run(A, RE, Opts), re:run(B, RE, Opts)} of
|
||||
{{match, [A1|_]}, {match, [B1|_]}} -> A1 =:= B1;
|
||||
_ -> A =:= B
|
||||
end.
|
||||
|
||||
dropdot(A) -> lists:dropwhile(fun (X) -> X =:= $. end, A).
|
||||
{{MajA, MinA, _, _}, _} = ec_semver:normalize(ec_semver:parse(A)),
|
||||
{{MajB, MinB, _, _}, _} = ec_semver:normalize(ec_semver:parse(B)),
|
||||
MajA =:= MajB andalso MinA =:= MinB.
|
||||
|
||||
dict_cons(Key, Value, Dict) ->
|
||||
dict:update(Key, fun (List) -> [Value | List] end, [Value], Dict).
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
-module(unit_SUITE).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("proper/include/proper.hrl").
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
|
|
@ -28,7 +29,9 @@ all() ->
|
|||
groups() ->
|
||||
[
|
||||
{parallel_tests, [parallel], [
|
||||
version_equivalence
|
||||
version_equivalence,
|
||||
version_minor_equivalence_properties,
|
||||
version_comparison
|
||||
]}
|
||||
].
|
||||
|
||||
|
|
@ -39,8 +42,168 @@ version_equivalence(_Config) ->
|
|||
true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.0"),
|
||||
true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.1"),
|
||||
true = rabbit_misc:version_minor_equivalent("%%VSN%%", "%%VSN%%"),
|
||||
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.1.0"),
|
||||
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.0"),
|
||||
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.0.1"),
|
||||
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.foo"),
|
||||
passed.
|
||||
true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0"),
|
||||
true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.0.1"),
|
||||
true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.foo"),
|
||||
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.1.0").
|
||||
|
||||
version_minor_equivalence_properties(_Config) ->
|
||||
true = proper:counterexample(
|
||||
?FORALL(
|
||||
{A, B},
|
||||
{version(), version()},
|
||||
check_minor_equivalent(A, B)
|
||||
),
|
||||
[
|
||||
quiet,
|
||||
{numtests, 10000},
|
||||
{on_output, fun(F, A) -> ct:pal(?LOW_IMPORTANCE, F, A) end}
|
||||
]
|
||||
).
|
||||
|
||||
version_comparison(_Config) ->
|
||||
true = proper:counterexample(
|
||||
?FORALL(
|
||||
{A, B},
|
||||
{version(), version()},
|
||||
check_and_compare_versions(A, B)
|
||||
),
|
||||
[
|
||||
quiet,
|
||||
{numtests, 10000},
|
||||
{on_output, fun(F, A) -> ct:pal(?LOW_IMPORTANCE, F, A) end}
|
||||
]
|
||||
).
|
||||
|
||||
version() ->
|
||||
union([
|
||||
[],
|
||||
release(),
|
||||
prerelease()
|
||||
]).
|
||||
|
||||
release() ->
|
||||
union([
|
||||
identifier(),
|
||||
[non_neg_integer()],
|
||||
[non_neg_integer(), ".", 0],
|
||||
[non_neg_integer(), ".", frequency([{1, 0}, {1, pos_integer()}])],
|
||||
[non_neg_integer(), ".", non_neg_integer(), ".", frequency([{1, 0}, {1, pos_integer()}])]
|
||||
]).
|
||||
|
||||
prerelease() ->
|
||||
{release(), "-", identifier()}.
|
||||
|
||||
identifier() ->
|
||||
union(
|
||||
[[identifier_first_char()],
|
||||
non_empty(list(identifier_char()))]
|
||||
).
|
||||
|
||||
identifier_first_char() ->
|
||||
union([non_zero_digit(), uppercase(), lowercase()]).
|
||||
|
||||
%% FIXME: We should have $- as a valid identifier_char(), but the
|
||||
%% ec_semver library doesn't support having a dash as the last
|
||||
%% character in an identifier. For now, do not use dashes in an
|
||||
%% identifier. We could probably fix the property to only generate dash
|
||||
%% as the non-first non-last character.
|
||||
identifier_char() ->
|
||||
union([digit(), uppercase(), lowercase()]).
|
||||
|
||||
digit() -> integer(48, 57).
|
||||
non_zero_digit() -> integer(49, 57).
|
||||
uppercase() -> integer(65, 90).
|
||||
lowercase() -> integer(97, 122).
|
||||
|
||||
check_minor_equivalent({Release, Sep, Extra}, B) ->
|
||||
A = Release ++ [Sep, Extra],
|
||||
check_minor_equivalent(A, B);
|
||||
check_minor_equivalent(A, {Release, Sep, Extra}) ->
|
||||
B = Release ++ [Sep, Extra],
|
||||
check_minor_equivalent(A, B);
|
||||
|
||||
check_minor_equivalent([], []) ->
|
||||
check_minor_equivalent([], [], true);
|
||||
check_minor_equivalent([Maj, ".", 0 | _] = A, [Maj] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
check_minor_equivalent([Maj, ".", 0 | _] = A, [Maj, "-", _ | _] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
|
||||
check_minor_equivalent([Maj] = A, [Maj, ".", 0 | _] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
check_minor_equivalent([Maj, "-", _ | _] = A, [Maj, ".", 0 | _] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
|
||||
check_minor_equivalent([Maj, ".", 0 | _] = A, [Maj, ".", 0 | _] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
|
||||
check_minor_equivalent([Maj] = A, [Maj] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
check_minor_equivalent([Maj, "-", _ | _] = A, [Maj] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
check_minor_equivalent([Maj] = A, [Maj, "-", _ | _] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
check_minor_equivalent([Maj, "-", _ | _] = A, [Maj, "-", _ | _] = B)
|
||||
when is_integer(Maj) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
|
||||
check_minor_equivalent([Maj, ".", Min | _] = A, [Maj, ".", Min | _] = B)
|
||||
when is_integer(Maj) andalso is_integer(Min) ->
|
||||
check_minor_equivalent(A, B, true);
|
||||
|
||||
check_minor_equivalent(A, B) ->
|
||||
check_minor_equivalent(A, B, false).
|
||||
|
||||
check_minor_equivalent(RawA, RawB, Expected) ->
|
||||
A = lists:flatten([raw_to_string(Char) || Char <- RawA]),
|
||||
B = lists:flatten([raw_to_string(Char) || Char <- RawB]),
|
||||
Expected =:= rabbit_misc:version_minor_equivalent(A, B).
|
||||
|
||||
check_and_compare_versions({Release, Sep, Extra}, B) ->
|
||||
A = Release ++ [Sep, Extra],
|
||||
check_and_compare_versions(A, B);
|
||||
check_and_compare_versions(A, {Release, Sep, Extra}) ->
|
||||
B = Release ++ [Sep, Extra],
|
||||
check_and_compare_versions(A, B);
|
||||
check_and_compare_versions(RawA, RawB) ->
|
||||
A = lists:flatten([raw_to_string(Char) || Char <- RawA]),
|
||||
B = lists:flatten([raw_to_string(Char) || Char <- RawB]),
|
||||
Result1 = rabbit_misc:version_compare(A, B),
|
||||
Result2 = rabbit_misc:version_compare(B, A),
|
||||
case {Result1, Result2} of
|
||||
{lt, gt} ->
|
||||
true =:= rabbit_misc:version_compare(A, B, lte) andalso
|
||||
false =:= rabbit_misc:version_compare(A, B, gte) andalso
|
||||
false =:= rabbit_misc:version_compare(A, B, eq);
|
||||
{gt, lt} ->
|
||||
true =:= rabbit_misc:version_compare(A, B, gte) andalso
|
||||
false =:= rabbit_misc:version_compare(A, B, lte) andalso
|
||||
false =:= rabbit_misc:version_compare(A, B, eq);
|
||||
{eq, eq} ->
|
||||
true =:= rabbit_misc:version_compare(A, B, gte) andalso
|
||||
true =:= rabbit_misc:version_compare(A, B, lte) andalso
|
||||
true =:= rabbit_misc:version_compare(A, B, eq);
|
||||
_ ->
|
||||
ct:pal(
|
||||
"rabbit_misc:version_compare/2 failure:~n"
|
||||
"A: ~p~n"
|
||||
"B: ~p~n"
|
||||
"Result1: ~p~n"
|
||||
"Result2: ~p~n", [A, B, Result1, Result2]),
|
||||
false
|
||||
end.
|
||||
|
||||
raw_to_string(Char)
|
||||
when is_integer(Char) ->
|
||||
integer_to_list(Char);
|
||||
raw_to_string(Char) ->
|
||||
Char.
|
||||
|
|
|
|||
Loading…
Reference in New Issue