diff --git a/deps/rabbit/src/rabbit_plugins.erl b/deps/rabbit/src/rabbit_plugins.erl index 26fff64905d3..dfe99eb335ec 100644 --- a/deps/rabbit/src/rabbit_plugins.erl +++ b/deps/rabbit/src/rabbit_plugins.erl @@ -479,9 +479,11 @@ is_version_supported("", _) -> true; is_version_supported("0.0.0", _) -> true; is_version_supported(_Version, []) -> true; is_version_supported(VersionFull, ExpectedVersions) -> - %% Pre-release version should be supported in plugins, - %% therefore preview part should be removed - Version = remove_version_preview_part(VersionFull), + %% Development and pre-release versions can be fairly complex, + %% such as "tanzu+rabbitmq.v3.13.12.dev". + %% Use rabbit_semver to extract the "base" X.Y.Z version + %% before doing any comparisons. + Version = rabbit_semver:normalize_then_format(VersionFull), case lists:any(fun(ExpectedVersion) -> rabbit_misc:strict_version_minor_equivalent(ExpectedVersion, Version) @@ -493,10 +495,6 @@ is_version_supported(VersionFull, ExpectedVersions) -> false -> false end. -remove_version_preview_part(Version) -> - {Ver, _Preview} = rabbit_semver:parse(Version), - iolist_to_binary(rabbit_semver:format({Ver, {[], []}})). - clean_plugins(Plugins) -> ExpandDir = plugins_expand_dir(), [clean_plugin(Plugin, ExpandDir) || Plugin <- Plugins]. diff --git a/deps/rabbit_common/src/rabbit_semver.erl b/deps/rabbit_common/src/rabbit_semver.erl index c80db0c27a22..d2eb3775d66d 100644 --- a/deps/rabbit_common/src/rabbit_semver.erl +++ b/deps/rabbit_common/src/rabbit_semver.erl @@ -16,6 +16,7 @@ -export([parse/1, format/1, + normalize_then_format/1, eql/2, gt/2, gte/2, @@ -59,16 +60,16 @@ %%% API %%%=================================================================== -%% @doc parse a string or binary into a valid semver representation +%% @doc Parse a version string or binary into a semver tuple. -spec parse(any_version()) -> semver(). -parse(Version) when erlang:is_list(Version) -> +parse(Version) when is_list(Version) -> case rabbit_semver_parser:parse(Version) of {fail, _} -> - {erlang:iolist_to_binary(Version), {[],[]}}; + {iolist_to_binary(Version), {[],[]}}; Good -> Good end; -parse(Version) when erlang:is_binary(Version) -> +parse(Version) when is_binary(Version) -> case rabbit_semver_parser:parse(Version) of {fail, _} -> {Version, {[],[]}}; @@ -80,8 +81,8 @@ parse(Version) -> -spec format(semver()) -> iolist(). format({Maj, {AlphaPart, BuildPart}}) - when erlang:is_integer(Maj); - erlang:is_binary(Maj) -> + when is_integer(Maj); + is_binary(Maj) -> [format_version_part(Maj), format_vsn_rest(<<"-">>, AlphaPart), format_vsn_rest(<<"+">>, BuildPart)]; @@ -104,118 +105,116 @@ format({{Maj, Min, Patch, MinPatch}, {AlphaPart, BuildPart}}) -> format_vsn_rest(<<"-">>, AlphaPart), format_vsn_rest(<<"+">>, BuildPart)]. +-spec normalize_then_format(any_version()) -> binary(). +normalize_then_format(Version) -> + {{Maj, Min, Patch, MinPatch}, _} = normalize(parse(Version)), + iolist_to_binary(format({{Maj, Min, Patch, MinPatch}, {[], []}})). + -spec format_version_part(integer() | binary()) -> iolist(). -format_version_part(Vsn) - when erlang:is_integer(Vsn) -> - erlang:integer_to_list(Vsn); -format_version_part(Vsn) - when erlang:is_binary(Vsn) -> +format_version_part(Vsn) when is_integer(Vsn) -> + integer_to_list(Vsn); +format_version_part(Vsn) when is_binary(Vsn) -> Vsn. - - -%% @doc test for quality between semver versions +%% @doc Test for equality between semver versions. -spec eql(any_version(), any_version()) -> boolean(). eql(VsnA, VsnB) -> NVsnA = normalize(parse(VsnA)), NVsnB = normalize(parse(VsnB)), NVsnA =:= NVsnB. -%% @doc Test that VsnA is greater than VsnB +%% @doc Test that VsnA is greater than VsnB. -spec gt(any_version(), any_version()) -> boolean(). gt(VsnA, VsnB) -> - {MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)), - {MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)), - ((MMPA > MMPB) - orelse - ((MMPA =:= MMPB) - andalso - ((AlphaA =:= [] andalso AlphaB =/= []) - orelse - ((not (AlphaB =:= [] andalso AlphaA =/= [])) - andalso - (AlphaA > AlphaB)))) - orelse - ((MMPA =:= MMPB) - andalso - (AlphaA =:= AlphaB) - andalso - ((PatchB =:= [] andalso PatchA =/= []) - orelse - PatchA > PatchB))). - -%% @doc Test that VsnA is greater than or equal to VsnB + gt_normalized(normalize(parse(VsnA)), normalize(parse(VsnB))). + +-spec gt_normalized(semver(), semver()) -> boolean(). +gt_normalized({MMPA, {AlphaA, PatchA}}, {MMPB, {AlphaB, PatchB}}) -> + (MMPA > MMPB) + orelse + ((MMPA =:= MMPB) + andalso + ((AlphaA =:= [] andalso AlphaB =/= []) + orelse + ((not (AlphaB =:= [] andalso AlphaA =/= [])) + andalso + (AlphaA > AlphaB)))) + orelse + ((MMPA =:= MMPB) + andalso + (AlphaA =:= AlphaB) + andalso + ((PatchB =:= [] andalso PatchA =/= []) + orelse + PatchA > PatchB)). + +%% @doc Test that VsnA is greater than or equal to VsnB. -spec gte(any_version(), any_version()) -> boolean(). gte(VsnA, VsnB) -> NVsnA = normalize(parse(VsnA)), NVsnB = normalize(parse(VsnB)), - gt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB). + NVsnA =:= NVsnB orelse gt_normalized(NVsnA, NVsnB). -%% @doc Test that VsnA is less than VsnB +%% @doc Test that VsnA is less than VsnB. -spec lt(any_version(), any_version()) -> boolean(). lt(VsnA, VsnB) -> - {MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)), - {MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)), - ((MMPA < MMPB) - orelse - ((MMPA =:= MMPB) - andalso - ((AlphaB =:= [] andalso AlphaA =/= []) - orelse - ((not (AlphaA =:= [] andalso AlphaB =/= [])) - andalso - (AlphaA < AlphaB)))) - orelse - ((MMPA =:= MMPB) - andalso - (AlphaA =:= AlphaB) - andalso - ((PatchA =:= [] andalso PatchB =/= []) - orelse - PatchA < PatchB))). - -%% @doc Test that VsnA is less than or equal to VsnB + lt_normalized(normalize(parse(VsnA)), normalize(parse(VsnB))). + +-spec lt_normalized(semver(), semver()) -> boolean(). +lt_normalized({MMPA, {AlphaA, PatchA}}, {MMPB, {AlphaB, PatchB}}) -> + (MMPA < MMPB) + orelse + ((MMPA =:= MMPB) + andalso + ((AlphaB =:= [] andalso AlphaA =/= []) + orelse + ((not (AlphaA =:= [] andalso AlphaB =/= [])) + andalso + (AlphaA < AlphaB)))) + orelse + ((MMPA =:= MMPB) + andalso + (AlphaA =:= AlphaB) + andalso + ((PatchA =:= [] andalso PatchB =/= []) + orelse + PatchA < PatchB)). + +%% @doc Test that VsnA is less than or equal to VsnB. -spec lte(any_version(), any_version()) -> boolean(). lte(VsnA, VsnB) -> NVsnA = normalize(parse(VsnA)), NVsnB = normalize(parse(VsnB)), - lt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB). + NVsnA =:= NVsnB orelse lt_normalized(NVsnA, NVsnB). %% @doc Test that VsnMatch is greater than or equal to Vsn1 and -%% less than or equal to Vsn2 +%% less than or equal to Vsn2. -spec between(any_version(), any_version(), any_version()) -> boolean(). between(Vsn1, Vsn2, VsnMatch) -> - NVsnA = normalize(parse(Vsn1)), - NVsnB = normalize(parse(Vsn2)), + NVsn1 = normalize(parse(Vsn1)), + NVsn2 = normalize(parse(Vsn2)), NVsnMatch = normalize(parse(VsnMatch)), - gte(NVsnMatch, NVsnA) andalso - lte(NVsnMatch, NVsnB). + (NVsnMatch =:= NVsn1 orelse gt_normalized(NVsnMatch, NVsn1)) andalso + (NVsnMatch =:= NVsn2 orelse lt_normalized(NVsnMatch, NVsn2)). -%% @doc check that VsnA is Approximately greater than VsnB -%% -%% Specifying ">= 2.6.5" is an optimistic version constraint. All -%% versions greater than the one specified, including major releases -%% (e.g. 3.0.0) are allowed. -%% -%% Conversely, specifying "~> 2.6" is pessimistic about future major -%% revisions and "~> 2.6.5" is pessimistic about future minor -%% revisions. +%% @doc Pessimistic version constraint check (the ~> operator). %% -%% "~> 2.6" matches cookbooks >= 2.6.0 AND < 3.0.0 -%% "~> 2.6.5" matches cookbooks >= 2.6.5 AND < 2.7.0 +%% "~> 2.6" matches versions >= 2.6.0 and < 3.0.0 +%% "~> 2.6.5" matches versions >= 2.6.5 and < 2.7.0 +-spec pes(any_version(), any_version()) -> boolean(). pes(VsnA, VsnB) -> internal_pes(parse(VsnA), parse(VsnB)). %%%=================================================================== %%% Friend Functions %%%=================================================================== -%% @doc helper function for the peg grammar to parse the iolist into a semver + +%% @doc Callback for the PEG parser to build a semver tuple from parsed tokens. -spec internal_parse_version(iolist()) -> semver(). internal_parse_version([MMP, AlphaPart, BuildPart, _]) -> {parse_major_minor_patch_minpatch(MMP), {parse_alpha_part(AlphaPart), parse_alpha_part(BuildPart)}}. -%% @doc helper function for the peg grammar to parse the iolist into a major_minor_patch -spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch(). parse_major_minor_patch_minpatch([MajVsn, [], [], []]) -> strip_maj_version(MajVsn); @@ -231,23 +230,19 @@ parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinPatch]]) -> {strip_maj_version(MajVsn), MinVsn, PatchVsn, MinPatch}. -%% @doc helper function for the peg grammar to parse the iolist into an alpha part -spec parse_alpha_part(iolist()) -> [alpha_part()]. parse_alpha_part([]) -> []; parse_alpha_part([_, AV1, Rest]) -> - [erlang:iolist_to_binary(AV1) | + [iolist_to_binary(AV1) | [format_alpha_part(Part) || Part <- Rest]]. -%% @doc according to semver alpha parts that can be treated like -%% numbers must be. We implement that here by taking the alpha part -%% and trying to convert it to a number, if it succeeds we use -%% it. Otherwise we do not. +%% Per semver spec, numeric identifiers must be compared as integers. -spec format_alpha_part(iolist()) -> integer() | binary(). format_alpha_part([<<".">>, AlphaPart]) -> - Bin = erlang:iolist_to_binary(AlphaPart), + Bin = iolist_to_binary(AlphaPart), try - erlang:list_to_integer(erlang:binary_to_list(Bin)) + binary_to_integer(Bin) catch error:badarg -> Bin @@ -265,9 +260,9 @@ strip_maj_version(MajVsn) -> MajVsn. -spec to_list(integer() | binary() | string()) -> string() | binary(). -to_list(Detail) when erlang:is_integer(Detail) -> - erlang:integer_to_list(Detail); -to_list(Detail) when erlang:is_list(Detail); erlang:is_binary(Detail) -> +to_list(Detail) when is_integer(Detail) -> + integer_to_list(Detail); +to_list(Detail) when is_list(Detail); is_binary(Detail) -> Detail. -spec format_vsn_rest(binary() | string(), [integer() | binary()]) -> iolist(). @@ -277,454 +272,77 @@ format_vsn_rest(TypeMark, [Head | Rest]) -> [TypeMark, Head | [[".", to_list(Detail)] || Detail <- Rest]]. -%% @doc normalize the semver so they can be compared +%% @doc Normalize a semver tuple to a 4-part version for comparison. -spec normalize(semver()) -> semver(). -normalize({Vsn, Rest}) - when erlang:is_binary(Vsn); - erlang:is_integer(Vsn) -> +normalize({Vsn, {Alpha, Build}}) when is_binary(Vsn) -> + case extract_version_from_build(Build) of + {ok, ExtractedVsn} -> + {normalize_version(ExtractedVsn), {Alpha, Build}}; + none -> + {{Vsn, 0, 0, 0}, {Alpha, Build}} + end; +normalize({Vsn, Rest}) when is_integer(Vsn) -> {{Vsn, 0, 0, 0}, Rest}; normalize({{Maj, Min}, Rest}) -> {{Maj, Min, 0, 0}, Rest}; normalize({{Maj, Min, Patch}, Rest}) -> {{Maj, Min, Patch, 0}, Rest}; -normalize(Other = {{_, _, _, _}, {_,_}}) -> - Other. +normalize({{_, _, _, _}, {_, _}} = Vsn) -> + Vsn. + +-spec normalize_version(major_minor_patch_minpatch()) -> + {version_element(), non_neg_integer(), non_neg_integer(), non_neg_integer()}. +normalize_version(Vsn) when is_integer(Vsn) -> + {Vsn, 0, 0, 0}; +normalize_version({Maj, Min}) -> + {Maj, Min, 0, 0}; +normalize_version({Maj, Min, Patch}) -> + {Maj, Min, Patch, 0}; +normalize_version({Maj, Min, Patch, MinPatch}) -> + {Maj, Min, Patch, MinPatch}. + +%% Extracts version from build metadata for prefixed versions like +%% "tanzu+rabbitmq.v3.13.12.dev" where the actual version is embedded. +-spec extract_version_from_build([alpha_part()]) -> + {ok, major_minor_patch_minpatch()} | none. +extract_version_from_build([]) -> + none; +extract_version_from_build([<<"v", Rest/binary>> | T]) -> + try + Major = binary_to_integer(Rest), + build_version_tuple(Major, T) + catch + error:badarg -> + extract_version_from_build(T) + end; +extract_version_from_build([_ | T]) -> + extract_version_from_build(T). + +-spec build_version_tuple(non_neg_integer(), [alpha_part()]) -> + {ok, major_minor_patch_minpatch()}. +build_version_tuple(Major, [Minor, Patch, MinPatch | _]) + when is_integer(Minor), is_integer(Patch), is_integer(MinPatch) -> + {ok, {Major, Minor, Patch, MinPatch}}; +build_version_tuple(Major, [Minor, Patch | _]) + when is_integer(Minor), is_integer(Patch) -> + {ok, {Major, Minor, Patch}}; +build_version_tuple(Major, [Minor | _]) when is_integer(Minor) -> + {ok, {Major, Minor}}; +build_version_tuple(Major, _) -> + {ok, Major}. -%% @doc to do the pessimistic compare we need a parsed semver. This is -%% the internal implementation of the of the pessimistic run. The -%% external just ensures that versions are parsed. -spec internal_pes(semver(), semver()) -> boolean(). internal_pes(VsnA, {{LM, LMI}, _}) - when erlang:is_integer(LM), - erlang:is_integer(LMI) -> + when is_integer(LM), is_integer(LMI) -> gte(VsnA, {{LM, LMI, 0}, {[], []}}) andalso lt(VsnA, {{LM + 1, 0, 0, 0}, {[], []}}); internal_pes(VsnA, {{LM, LMI, LP}, _}) - when erlang:is_integer(LM), - erlang:is_integer(LMI), - erlang:is_integer(LP) -> - gte(VsnA, {{LM, LMI, LP}, {[], []}}) - andalso + when is_integer(LM), is_integer(LMI), is_integer(LP) -> + gte(VsnA, {{LM, LMI, LP}, {[], []}}) andalso lt(VsnA, {{LM, LMI + 1, 0, 0}, {[], []}}); internal_pes(VsnA, {{LM, LMI, LP, LMP}, _}) - when erlang:is_integer(LM), - erlang:is_integer(LMI), - erlang:is_integer(LP), - erlang:is_integer(LMP) -> - gte(VsnA, {{LM, LMI, LP, LMP}, {[], []}}) - andalso + when is_integer(LM), is_integer(LMI), is_integer(LP), is_integer(LMP) -> + gte(VsnA, {{LM, LMI, LP, LMP}, {[], []}}) andalso lt(VsnA, {{LM, LMI, LP + 1, 0}, {[], []}}); internal_pes(Vsn, LVsn) -> gte(Vsn, LVsn). - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - -eql_test() -> - ?assertMatch(true, eql("1.0.0-alpha", - "1.0.0-alpha")), - ?assertMatch(true, eql(<<"1.0.0-alpha">>, - "1.0.0-alpha")), - ?assertMatch(true, eql("1.0.0-alpha", - <<"1.0.0-alpha">>)), - ?assertMatch(true, eql(<<"1.0.0-alpha">>, - <<"1.0.0-alpha">>)), - ?assertMatch(true, eql("v1.0.0-alpha", - "1.0.0-alpha")), - ?assertMatch(true, eql("1", - "1.0.0")), - ?assertMatch(true, eql("v1", - "v1.0.0")), - ?assertMatch(true, eql("1.0", - "1.0.0")), - ?assertMatch(true, eql("1.0.0", - "1")), - ?assertMatch(true, eql("1.0.0.0", - "1")), - ?assertMatch(true, eql("1.0+alpha.1", - "1.0.0+alpha.1")), - ?assertMatch(true, eql("1.0-alpha.1+build.1", - "1.0.0-alpha.1+build.1")), - ?assertMatch(true, eql("1.0-alpha.1+build.1", - "1.0.0.0-alpha.1+build.1")), - ?assertMatch(true, eql("1.0-alpha.1+build.1", - "v1.0.0.0-alpha.1+build.1")), - ?assertMatch(true, eql("aa", "aa")), - ?assertMatch(true, eql("AA.BB", "AA.BB")), - ?assertMatch(true, eql("BBB-super", "BBB-super")), - ?assertMatch(true, not eql("1.0.0", - "1.0.1")), - ?assertMatch(true, not eql(<<"1.0.0">>, - "1.0.1")), - ?assertMatch(true, not eql("1.0.0", - <<"1.0.1">>)), - ?assertMatch(true, not eql(<<"1.0.0">>, - <<"1.0.1">>)), - ?assertMatch(true, not eql("1.0.0-alpha", - "1.0.1+alpha")), - ?assertMatch(true, not eql("1.0.0+build.1", - "1.0.1+build.2")), - ?assertMatch(true, not eql("1.0.0.0+build.1", - "1.0.0.1+build.2")), - ?assertMatch(true, not eql("FFF", "BBB")), - ?assertMatch(true, not eql("1", "1BBBB")). - -gt_test() -> - ?assertMatch(true, gt("1.0.0-alpha.1", - "1.0.0-alpha")), - ?assertMatch(true, gt("1.0.0.1-alpha.1", - "1.0.0.1-alpha")), - ?assertMatch(true, gt("1.0.0.4-alpha.1", - "1.0.0.2-alpha")), - ?assertMatch(true, gt("1.0.0.0-alpha.1", - "1.0.0-alpha")), - ?assertMatch(true, gt("1.0.0-beta.2", - "1.0.0-alpha.1")), - ?assertMatch(true, gt("1.0.0-beta.11", - "1.0.0-beta.2")), - ?assertMatch(true, gt("1.0.0-beta.11", - "1.0.0.0-beta.2")), - ?assertMatch(true, gt("1.0.0-rc.1", "1.0.0-beta.11")), - ?assertMatch(true, gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), - ?assertMatch(true, gt("1.0.0", "1.0.0-rc.1+build.1")), - ?assertMatch(true, gt("1.0.0+0.3.7", "1.0.0")), - ?assertMatch(true, gt("1.3.7+build", "1.0.0+0.3.7")), - ?assertMatch(true, gt("1.3.7+build.2.b8f12d7", - "1.3.7+build")), - ?assertMatch(true, gt("1.3.7+build.2.b8f12d7", - "1.3.7.0+build")), - ?assertMatch(true, gt("1.3.7+build.11.e0f985a", - "1.3.7+build.2.b8f12d7")), - ?assertMatch(true, gt("aa.cc", - "aa.bb")), - ?assertMatch(true, not gt("1.0.0-alpha", - "1.0.0-alpha.1")), - ?assertMatch(true, not gt("1.0.0-alpha", - "1.0.0.0-alpha.1")), - ?assertMatch(true, not gt("1.0.0-alpha.1", - "1.0.0-beta.2")), - ?assertMatch(true, not gt("1.0.0-beta.2", - "1.0.0-beta.11")), - ?assertMatch(true, not gt("1.0.0-beta.11", - "1.0.0-rc.1")), - ?assertMatch(true, not gt("1.0.0-rc.1", - "1.0.0-rc.1+build.1")), - ?assertMatch(true, not gt("1.0.0-rc.1+build.1", - "1.0.0")), - ?assertMatch(true, not gt("1.0.0", - "1.0.0+0.3.7")), - ?assertMatch(true, not gt("1.0.0+0.3.7", - "1.3.7+build")), - ?assertMatch(true, not gt("1.3.7+build", - "1.3.7+build.2.b8f12d7")), - ?assertMatch(true, not gt("1.3.7+build.2.b8f12d7", - "1.3.7+build.11.e0f985a")), - ?assertMatch(true, not gt("1.0.0-alpha", - "1.0.0-alpha")), - ?assertMatch(true, not gt("1", - "1.0.0")), - ?assertMatch(true, not gt("aa.bb", - "aa.bb")), - ?assertMatch(true, not gt("aa.cc", - "aa.dd")), - ?assertMatch(true, not gt("1.0", - "1.0.0")), - ?assertMatch(true, not gt("1.0.0", - "1")), - ?assertMatch(true, not gt("1.0+alpha.1", - "1.0.0+alpha.1")), - ?assertMatch(true, not gt("1.0-alpha.1+build.1", - "1.0.0-alpha.1+build.1")). - -lt_test() -> - ?assertMatch(true, lt("1.0.0-alpha", - "1.0.0-alpha.1")), - ?assertMatch(true, lt("1.0.0-alpha", - "1.0.0.0-alpha.1")), - ?assertMatch(true, lt("1.0.0-alpha.1", - "1.0.0-beta.2")), - ?assertMatch(true, lt("1.0.0-beta.2", - "1.0.0-beta.11")), - ?assertMatch(true, lt("1.0.0-beta.11", - "1.0.0-rc.1")), - ?assertMatch(true, lt("1.0.0.1-beta.11", - "1.0.0.1-rc.1")), - ?assertMatch(true, lt("1.0.0-rc.1", - "1.0.0-rc.1+build.1")), - ?assertMatch(true, lt("1.0.0-rc.1+build.1", - "1.0.0")), - ?assertMatch(true, lt("1.0.0", - "1.0.0+0.3.7")), - ?assertMatch(true, lt("1.0.0+0.3.7", - "1.3.7+build")), - ?assertMatch(true, lt("1.3.7+build", - "1.3.7+build.2.b8f12d7")), - ?assertMatch(true, lt("1.3.7+build.2.b8f12d7", - "1.3.7+build.11.e0f985a")), - ?assertMatch(true, not lt("1.0.0-alpha", - "1.0.0-alpha")), - ?assertMatch(true, not lt("1", - "1.0.0")), - ?assertMatch(true, lt("1", - "1.0.0.1")), - ?assertMatch(true, lt("AA.DD", - "AA.EE")), - ?assertMatch(true, not lt("1.0", - "1.0.0")), - ?assertMatch(true, not lt("1.0.0.0", - "1")), - ?assertMatch(true, not lt("1.0+alpha.1", - "1.0.0+alpha.1")), - ?assertMatch(true, not lt("AA.DD", "AA.CC")), - ?assertMatch(true, not lt("1.0-alpha.1+build.1", - "1.0.0-alpha.1+build.1")), - ?assertMatch(true, not lt("1.0.0-alpha.1", - "1.0.0-alpha")), - ?assertMatch(true, not lt("1.0.0-beta.2", - "1.0.0-alpha.1")), - ?assertMatch(true, not lt("1.0.0-beta.11", - "1.0.0-beta.2")), - ?assertMatch(true, not lt("1.0.0-rc.1", "1.0.0-beta.11")), - ?assertMatch(true, not lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), - ?assertMatch(true, not lt("1.0.0", "1.0.0-rc.1+build.1")), - ?assertMatch(true, not lt("1.0.0+0.3.7", "1.0.0")), - ?assertMatch(true, not lt("1.3.7+build", "1.0.0+0.3.7")), - ?assertMatch(true, not lt("1.3.7+build.2.b8f12d7", - "1.3.7+build")), - ?assertMatch(true, not lt("1.3.7+build.11.e0f985a", - "1.3.7+build.2.b8f12d7")). - -gte_test() -> - ?assertMatch(true, gte("1.0.0-alpha", - "1.0.0-alpha")), - - ?assertMatch(true, gte("1", - "1.0.0")), - - ?assertMatch(true, gte("1.0", - "1.0.0")), - - ?assertMatch(true, gte("1.0.0", - "1")), - - ?assertMatch(true, gte("1.0.0.0", - "1")), - - ?assertMatch(true, gte("1.0+alpha.1", - "1.0.0+alpha.1")), - - ?assertMatch(true, gte("1.0-alpha.1+build.1", - "1.0.0-alpha.1+build.1")), - - ?assertMatch(true, gte("1.0.0-alpha.1+build.1", - "1.0.0.0-alpha.1+build.1")), - ?assertMatch(true, gte("1.0.0-alpha.1", - "1.0.0-alpha")), - ?assertMatch(true, gte("1.0.0-beta.2", - "1.0.0-alpha.1")), - ?assertMatch(true, gte("1.0.0-beta.11", - "1.0.0-beta.2")), - ?assertMatch(true, gte("aa.bb", "aa.bb")), - ?assertMatch(true, gte("dd", "aa")), - ?assertMatch(true, gte("1.0.0-rc.1", "1.0.0-beta.11")), - ?assertMatch(true, gte("1.0.0-rc.1+build.1", "1.0.0-rc.1")), - ?assertMatch(true, gte("1.0.0", "1.0.0-rc.1+build.1")), - ?assertMatch(true, gte("1.0.0+0.3.7", "1.0.0")), - ?assertMatch(true, gte("1.3.7+build", "1.0.0+0.3.7")), - ?assertMatch(true, gte("1.3.7+build.2.b8f12d7", - "1.3.7+build")), - ?assertMatch(true, gte("1.3.7+build.11.e0f985a", - "1.3.7+build.2.b8f12d7")), - ?assertMatch(true, not gte("1.0.0-alpha", - "1.0.0-alpha.1")), - ?assertMatch(true, not gte("CC", "DD")), - ?assertMatch(true, not gte("1.0.0-alpha.1", - "1.0.0-beta.2")), - ?assertMatch(true, not gte("1.0.0-beta.2", - "1.0.0-beta.11")), - ?assertMatch(true, not gte("1.0.0-beta.11", - "1.0.0-rc.1")), - ?assertMatch(true, not gte("1.0.0-rc.1", - "1.0.0-rc.1+build.1")), - ?assertMatch(true, not gte("1.0.0-rc.1+build.1", - "1.0.0")), - ?assertMatch(true, not gte("1.0.0", - "1.0.0+0.3.7")), - ?assertMatch(true, not gte("1.0.0+0.3.7", - "1.3.7+build")), - ?assertMatch(true, not gte("1.0.0", - "1.0.0+build.1")), - ?assertMatch(true, not gte("1.3.7+build", - "1.3.7+build.2.b8f12d7")), - ?assertMatch(true, not gte("1.3.7+build.2.b8f12d7", - "1.3.7+build.11.e0f985a")). -lte_test() -> - ?assertMatch(true, lte("1.0.0-alpha", - "1.0.0-alpha.1")), - ?assertMatch(true, lte("1.0.0-alpha.1", - "1.0.0-beta.2")), - ?assertMatch(true, lte("1.0.0-beta.2", - "1.0.0-beta.11")), - ?assertMatch(true, lte("1.0.0-beta.11", - "1.0.0-rc.1")), - ?assertMatch(true, lte("1.0.0-rc.1", - "1.0.0-rc.1+build.1")), - ?assertMatch(true, lte("1.0.0-rc.1+build.1", - "1.0.0")), - ?assertMatch(true, lte("1.0.0", - "1.0.0+0.3.7")), - ?assertMatch(true, lte("1.0.0+0.3.7", - "1.3.7+build")), - ?assertMatch(true, lte("1.3.7+build", - "1.3.7+build.2.b8f12d7")), - ?assertMatch(true, lte("1.3.7+build.2.b8f12d7", - "1.3.7+build.11.e0f985a")), - ?assertMatch(true, lte("1.0.0-alpha", - "1.0.0-alpha")), - ?assertMatch(true, lte("1", - "1.0.0")), - ?assertMatch(true, lte("1.0", - "1.0.0")), - ?assertMatch(true, lte("1.0.0", - "1")), - ?assertMatch(true, lte("1.0+alpha.1", - "1.0.0+alpha.1")), - ?assertMatch(true, lte("1.0.0.0+alpha.1", - "1.0.0+alpha.1")), - ?assertMatch(true, lte("1.0-alpha.1+build.1", - "1.0.0-alpha.1+build.1")), - ?assertMatch(true, lte("aa","cc")), - ?assertMatch(true, lte("cc","cc")), - ?assertMatch(true, not lte("1.0.0-alpha.1", - "1.0.0-alpha")), - ?assertMatch(true, not lte("cc", "aa")), - ?assertMatch(true, not lte("1.0.0-beta.2", - "1.0.0-alpha.1")), - ?assertMatch(true, not lte("1.0.0-beta.11", - "1.0.0-beta.2")), - ?assertMatch(true, not lte("1.0.0-rc.1", "1.0.0-beta.11")), - ?assertMatch(true, not lte("1.0.0-rc.1+build.1", "1.0.0-rc.1")), - ?assertMatch(true, not lte("1.0.0", "1.0.0-rc.1+build.1")), - ?assertMatch(true, not lte("1.0.0+0.3.7", "1.0.0")), - ?assertMatch(true, not lte("1.3.7+build", "1.0.0+0.3.7")), - ?assertMatch(true, not lte("1.3.7+build.2.b8f12d7", - "1.3.7+build")), - ?assertMatch(true, not lte("1.3.7+build.11.e0f985a", - "1.3.7+build.2.b8f12d7")). - -between_test() -> - ?assertMatch(true, between("1.0.0-alpha", - "1.0.0-alpha.3", - "1.0.0-alpha.2")), - ?assertMatch(true, between("1.0.0-alpha.1", - "1.0.0-beta.2", - "1.0.0-alpha.25")), - ?assertMatch(true, between("1.0.0-beta.2", - "1.0.0-beta.11", - "1.0.0-beta.7")), - ?assertMatch(true, between("1.0.0-beta.11", - "1.0.0-rc.3", - "1.0.0-rc.1")), - ?assertMatch(true, between("1.0.0-rc.1", - "1.0.0-rc.1+build.3", - "1.0.0-rc.1+build.1")), - - ?assertMatch(true, between("1.0.0.0-rc.1", - "1.0.0-rc.1+build.3", - "1.0.0-rc.1+build.1")), - ?assertMatch(true, between("1.0.0-rc.1+build.1", - "1.0.0", - "1.0.0-rc.33")), - ?assertMatch(true, between("1.0.0", - "1.0.0+0.3.7", - "1.0.0+0.2")), - ?assertMatch(true, between("1.0.0+0.3.7", - "1.3.7+build", - "1.2")), - ?assertMatch(true, between("1.3.7+build", - "1.3.7+build.2.b8f12d7", - "1.3.7+build.1")), - ?assertMatch(true, between("1.3.7+build.2.b8f12d7", - "1.3.7+build.11.e0f985a", - "1.3.7+build.10.a36faa")), - ?assertMatch(true, between("1.0.0-alpha", - "1.0.0-alpha", - "1.0.0-alpha")), - ?assertMatch(true, between("1", - "1.0.0", - "1.0.0")), - ?assertMatch(true, between("1.0", - "1.0.0", - "1.0.0")), - - ?assertMatch(true, between("1.0", - "1.0.0.0", - "1.0.0.0")), - ?assertMatch(true, between("1.0.0", - "1", - "1")), - ?assertMatch(true, between("1.0+alpha.1", - "1.0.0+alpha.1", - "1.0.0+alpha.1")), - ?assertMatch(true, between("1.0-alpha.1+build.1", - "1.0.0-alpha.1+build.1", - "1.0.0-alpha.1+build.1")), - ?assertMatch(true, between("aaa", - "ddd", - "cc")), - ?assertMatch(true, not between("1.0.0-alpha.1", - "1.0.0-alpha.22", - "1.0.0")), - ?assertMatch(true, not between("1.0.0", - "1.0.0-alpha.1", - "2.0")), - ?assertMatch(true, not between("1.0.0-beta.1", - "1.0.0-beta.11", - "1.0.0-alpha")), - ?assertMatch(true, not between("1.0.0-beta.11", "1.0.0-rc.1", - "1.0.0-rc.22")), - ?assertMatch(true, not between("aaa", "ddd", "zzz")). - -pes_test() -> - ?assertMatch(true, pes("2.6.0", "2.6")), - ?assertMatch(true, pes("2.7", "2.6")), - ?assertMatch(true, pes("2.8", "2.6")), - ?assertMatch(true, pes("2.9", "2.6")), - ?assertMatch(true, pes("A.B", "A.A")), - ?assertMatch(true, not pes("3.0.0", "2.6")), - ?assertMatch(true, not pes("2.5", "2.6")), - ?assertMatch(true, pes("2.6.5", "2.6.5")), - ?assertMatch(true, pes("2.6.6", "2.6.5")), - ?assertMatch(true, pes("2.6.7", "2.6.5")), - ?assertMatch(true, pes("2.6.8", "2.6.5")), - ?assertMatch(true, pes("2.6.9", "2.6.5")), - ?assertMatch(true, pes("2.6.0.9", "2.6.0.5")), - ?assertMatch(true, not pes("2.7", "2.6.5")), - ?assertMatch(true, not pes("2.1.7", "2.1.6.5")), - ?assertMatch(true, not pes("A.A", "A.B")), - ?assertMatch(true, not pes("2.5", "2.6.5")). - -version_format_test() -> - ?assertEqual(["1", [], []], format({1, {[],[]}})), - ?assertEqual(["1", ".", "2", ".", "34", [], []], format({{1,2,34},{[],[]}})), - ?assertEqual(<<"a">>, erlang:iolist_to_binary(format({<<"a">>, {[],[]}}))), - ?assertEqual(<<"a.b">>, erlang:iolist_to_binary(format({{<<"a">>,<<"b">>}, {[],[]}}))), - ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))), - ?assertEqual(<<"1.2">>, erlang:iolist_to_binary(format({{1,2}, {[],[]}}))), - ?assertEqual(<<"1.2.2">>, erlang:iolist_to_binary(format({{1,2,2}, {[],[]}}))), - ?assertEqual(<<"1.99.2">>, erlang:iolist_to_binary(format({{1,99,2}, {[],[]}}))), - ?assertEqual(<<"1.99.2-alpha">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>],[]}}))), - ?assertEqual(<<"1.99.2-alpha.1">>, erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>,1], []}}))), - ?assertEqual(<<"1.99.2+build.1.a36">>, - erlang:iolist_to_binary(format({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}}))), - ?assertEqual(<<"1.99.2.44+build.1.a36">>, - erlang:iolist_to_binary(format({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}}))), - ?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>, - erlang:iolist_to_binary(format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))), - ?assertEqual(<<"1">>, erlang:iolist_to_binary(format({1, {[],[]}}))). - --endif. diff --git a/deps/rabbit_common/test/rabbit_semver_SUITE.erl b/deps/rabbit_common/test/rabbit_semver_SUITE.erl new file mode 100644 index 000000000000..33874083f865 --- /dev/null +++ b/deps/rabbit_common/test/rabbit_semver_SUITE.erl @@ -0,0 +1,387 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_semver_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +all() -> + [ + {group, comparison}, + {group, parsing}, + {group, formatting}, + {group, prefixed_versions} + ]. + +groups() -> + [ + {comparison, [parallel], [ + eql_test, + gt_test, + lt_test, + gte_test, + lte_test, + between_test, + pes_test, + edge_cases_test + ]}, + {parsing, [parallel], [ + prefixed_version_parse_test, + prefixed_version_normalize_test + ]}, + {formatting, [parallel], [ + version_format_test, + normalize_then_format_test + ]}, + {prefixed_versions, [parallel], [ + prefixed_version_compare_test + ]} + ]. + +init_per_suite(Config) -> Config. +end_per_suite(Config) -> Config. +init_per_group(_, Config) -> Config. +end_per_group(_, Config) -> Config. +init_per_testcase(_, Config) -> Config. +end_per_testcase(_, Config) -> Config. + +eql_test(_Config) -> + ?assert(rabbit_semver:eql("1.0.0-alpha", "1.0.0-alpha")), + ?assert(rabbit_semver:eql(<<"1.0.0-alpha">>, "1.0.0-alpha")), + ?assert(rabbit_semver:eql("1.0.0-alpha", <<"1.0.0-alpha">>)), + ?assert(rabbit_semver:eql(<<"1.0.0-alpha">>, <<"1.0.0-alpha">>)), + ?assert(rabbit_semver:eql("v1.0.0-alpha", "1.0.0-alpha")), + ?assert(rabbit_semver:eql("1", "1.0.0")), + ?assert(rabbit_semver:eql("v1", "v1.0.0")), + ?assert(rabbit_semver:eql("1.0", "1.0.0")), + ?assert(rabbit_semver:eql("1.0.0", "1")), + ?assert(rabbit_semver:eql("1.0.0.0", "1")), + ?assert(rabbit_semver:eql("1.0+alpha.1", "1.0.0+alpha.1")), + ?assert(rabbit_semver:eql("1.0-alpha.1+build.1", "1.0.0-alpha.1+build.1")), + ?assert(rabbit_semver:eql("1.0-alpha.1+build.1", "1.0.0.0-alpha.1+build.1")), + ?assert(rabbit_semver:eql("1.0-alpha.1+build.1", "v1.0.0.0-alpha.1+build.1")), + ?assert(rabbit_semver:eql("aa", "aa")), + ?assert(rabbit_semver:eql("AA.BB", "AA.BB")), + ?assert(rabbit_semver:eql("BBB-super", "BBB-super")), + ?assertNot(rabbit_semver:eql("1.0.0", "1.0.1")), + ?assertNot(rabbit_semver:eql(<<"1.0.0">>, "1.0.1")), + ?assertNot(rabbit_semver:eql("1.0.0", <<"1.0.1">>)), + ?assertNot(rabbit_semver:eql(<<"1.0.0">>, <<"1.0.1">>)), + ?assertNot(rabbit_semver:eql("1.0.0-alpha", "1.0.1+alpha")), + ?assertNot(rabbit_semver:eql("1.0.0+build.1", "1.0.1+build.2")), + ?assertNot(rabbit_semver:eql("1.0.0.0+build.1", "1.0.0.1+build.2")), + ?assertNot(rabbit_semver:eql("FFF", "BBB")), + ?assertNot(rabbit_semver:eql("1", "1BBBB")). + +gt_test(_Config) -> + ?assert(rabbit_semver:gt("1.0.0-alpha.1", "1.0.0-alpha")), + ?assert(rabbit_semver:gt("1.0.0.1-alpha.1", "1.0.0.1-alpha")), + ?assert(rabbit_semver:gt("1.0.0.4-alpha.1", "1.0.0.2-alpha")), + ?assert(rabbit_semver:gt("1.0.0.0-alpha.1", "1.0.0-alpha")), + ?assert(rabbit_semver:gt("1.0.0-beta.2", "1.0.0-alpha.1")), + ?assert(rabbit_semver:gt("1.0.0-beta.11", "1.0.0-beta.2")), + ?assert(rabbit_semver:gt("1.0.0-beta.11", "1.0.0.0-beta.2")), + ?assert(rabbit_semver:gt("1.0.0-rc.1", "1.0.0-beta.11")), + ?assert(rabbit_semver:gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assert(rabbit_semver:gt("1.0.0", "1.0.0-rc.1+build.1")), + ?assert(rabbit_semver:gt("1.0.0+0.3.7", "1.0.0")), + ?assert(rabbit_semver:gt("1.3.7+build", "1.0.0+0.3.7")), + ?assert(rabbit_semver:gt("1.3.7+build.2.b8f12d7", "1.3.7+build")), + ?assert(rabbit_semver:gt("1.3.7+build.2.b8f12d7", "1.3.7.0+build")), + ?assert(rabbit_semver:gt("1.3.7+build.11.e0f985a", "1.3.7+build.2.b8f12d7")), + ?assert(rabbit_semver:gt("aa.cc", "aa.bb")), + ?assertNot(rabbit_semver:gt("1.0.0-alpha", "1.0.0-alpha.1")), + ?assertNot(rabbit_semver:gt("1.0.0-alpha", "1.0.0.0-alpha.1")), + ?assertNot(rabbit_semver:gt("1.0.0-alpha.1", "1.0.0-beta.2")), + ?assertNot(rabbit_semver:gt("1.0.0-beta.2", "1.0.0-beta.11")), + ?assertNot(rabbit_semver:gt("1.0.0-beta.11", "1.0.0-rc.1")), + ?assertNot(rabbit_semver:gt("1.0.0-rc.1", "1.0.0-rc.1+build.1")), + ?assertNot(rabbit_semver:gt("1.0.0-rc.1+build.1", "1.0.0")), + ?assertNot(rabbit_semver:gt("1.0.0", "1.0.0+0.3.7")), + ?assertNot(rabbit_semver:gt("1.0.0+0.3.7", "1.3.7+build")), + ?assertNot(rabbit_semver:gt("1.3.7+build", "1.3.7+build.2.b8f12d7")), + ?assertNot(rabbit_semver:gt("1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a")), + ?assertNot(rabbit_semver:gt("1.0.0-alpha", "1.0.0-alpha")), + ?assertNot(rabbit_semver:gt("1", "1.0.0")), + ?assertNot(rabbit_semver:gt("aa.bb", "aa.bb")), + ?assertNot(rabbit_semver:gt("aa.cc", "aa.dd")), + ?assertNot(rabbit_semver:gt("1.0", "1.0.0")), + ?assertNot(rabbit_semver:gt("1.0.0", "1")), + ?assertNot(rabbit_semver:gt("1.0+alpha.1", "1.0.0+alpha.1")), + ?assertNot(rabbit_semver:gt("1.0-alpha.1+build.1", "1.0.0-alpha.1+build.1")). + +lt_test(_Config) -> + ?assert(rabbit_semver:lt("1.0.0-alpha", "1.0.0-alpha.1")), + ?assert(rabbit_semver:lt("1.0.0-alpha", "1.0.0.0-alpha.1")), + ?assert(rabbit_semver:lt("1.0.0-alpha.1", "1.0.0-beta.2")), + ?assert(rabbit_semver:lt("1.0.0-beta.2", "1.0.0-beta.11")), + ?assert(rabbit_semver:lt("1.0.0-beta.11", "1.0.0-rc.1")), + ?assert(rabbit_semver:lt("1.0.0.1-beta.11", "1.0.0.1-rc.1")), + ?assert(rabbit_semver:lt("1.0.0-rc.1", "1.0.0-rc.1+build.1")), + ?assert(rabbit_semver:lt("1.0.0-rc.1+build.1", "1.0.0")), + ?assert(rabbit_semver:lt("1.0.0", "1.0.0+0.3.7")), + ?assert(rabbit_semver:lt("1.0.0+0.3.7", "1.3.7+build")), + ?assert(rabbit_semver:lt("1.3.7+build", "1.3.7+build.2.b8f12d7")), + ?assert(rabbit_semver:lt("1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a")), + ?assertNot(rabbit_semver:lt("1.0.0-alpha", "1.0.0-alpha")), + ?assertNot(rabbit_semver:lt("1", "1.0.0")), + ?assert(rabbit_semver:lt("1", "1.0.0.1")), + ?assert(rabbit_semver:lt("AA.DD", "AA.EE")), + ?assertNot(rabbit_semver:lt("1.0", "1.0.0")), + ?assertNot(rabbit_semver:lt("1.0.0.0", "1")), + ?assertNot(rabbit_semver:lt("1.0+alpha.1", "1.0.0+alpha.1")), + ?assertNot(rabbit_semver:lt("AA.DD", "AA.CC")), + ?assertNot(rabbit_semver:lt("1.0-alpha.1+build.1", "1.0.0-alpha.1+build.1")), + ?assertNot(rabbit_semver:lt("1.0.0-alpha.1", "1.0.0-alpha")), + ?assertNot(rabbit_semver:lt("1.0.0-beta.2", "1.0.0-alpha.1")), + ?assertNot(rabbit_semver:lt("1.0.0-beta.11", "1.0.0-beta.2")), + ?assertNot(rabbit_semver:lt("1.0.0-rc.1", "1.0.0-beta.11")), + ?assertNot(rabbit_semver:lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assertNot(rabbit_semver:lt("1.0.0", "1.0.0-rc.1+build.1")), + ?assertNot(rabbit_semver:lt("1.0.0+0.3.7", "1.0.0")), + ?assertNot(rabbit_semver:lt("1.3.7+build", "1.0.0+0.3.7")), + ?assertNot(rabbit_semver:lt("1.3.7+build.2.b8f12d7", "1.3.7+build")), + ?assertNot(rabbit_semver:lt("1.3.7+build.11.e0f985a", "1.3.7+build.2.b8f12d7")). + +gte_test(_Config) -> + ?assert(rabbit_semver:gte("1.0.0-alpha", "1.0.0-alpha")), + ?assert(rabbit_semver:gte("1", "1.0.0")), + ?assert(rabbit_semver:gte("1.0", "1.0.0")), + ?assert(rabbit_semver:gte("1.0.0", "1")), + ?assert(rabbit_semver:gte("1.0.0.0", "1")), + ?assert(rabbit_semver:gte("1.0+alpha.1", "1.0.0+alpha.1")), + ?assert(rabbit_semver:gte("1.0-alpha.1+build.1", "1.0.0-alpha.1+build.1")), + ?assert(rabbit_semver:gte("1.0.0-alpha.1+build.1", "1.0.0.0-alpha.1+build.1")), + ?assert(rabbit_semver:gte("1.0.0-alpha.1", "1.0.0-alpha")), + ?assert(rabbit_semver:gte("1.0.0-beta.2", "1.0.0-alpha.1")), + ?assert(rabbit_semver:gte("1.0.0-beta.11", "1.0.0-beta.2")), + ?assert(rabbit_semver:gte("aa.bb", "aa.bb")), + ?assert(rabbit_semver:gte("dd", "aa")), + ?assert(rabbit_semver:gte("1.0.0-rc.1", "1.0.0-beta.11")), + ?assert(rabbit_semver:gte("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assert(rabbit_semver:gte("1.0.0", "1.0.0-rc.1+build.1")), + ?assert(rabbit_semver:gte("1.0.0+0.3.7", "1.0.0")), + ?assert(rabbit_semver:gte("1.3.7+build", "1.0.0+0.3.7")), + ?assert(rabbit_semver:gte("1.3.7+build.2.b8f12d7", "1.3.7+build")), + ?assert(rabbit_semver:gte("1.3.7+build.11.e0f985a", "1.3.7+build.2.b8f12d7")), + ?assertNot(rabbit_semver:gte("1.0.0-alpha", "1.0.0-alpha.1")), + ?assertNot(rabbit_semver:gte("CC", "DD")), + ?assertNot(rabbit_semver:gte("1.0.0-alpha.1", "1.0.0-beta.2")), + ?assertNot(rabbit_semver:gte("1.0.0-beta.2", "1.0.0-beta.11")), + ?assertNot(rabbit_semver:gte("1.0.0-beta.11", "1.0.0-rc.1")), + ?assertNot(rabbit_semver:gte("1.0.0-rc.1", "1.0.0-rc.1+build.1")), + ?assertNot(rabbit_semver:gte("1.0.0-rc.1+build.1", "1.0.0")), + ?assertNot(rabbit_semver:gte("1.0.0", "1.0.0+0.3.7")), + ?assertNot(rabbit_semver:gte("1.0.0+0.3.7", "1.3.7+build")), + ?assertNot(rabbit_semver:gte("1.0.0", "1.0.0+build.1")), + ?assertNot(rabbit_semver:gte("1.3.7+build", "1.3.7+build.2.b8f12d7")), + ?assertNot(rabbit_semver:gte("1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a")). + +lte_test(_Config) -> + ?assert(rabbit_semver:lte("1.0.0-alpha", "1.0.0-alpha.1")), + ?assert(rabbit_semver:lte("1.0.0-alpha.1", "1.0.0-beta.2")), + ?assert(rabbit_semver:lte("1.0.0-beta.2", "1.0.0-beta.11")), + ?assert(rabbit_semver:lte("1.0.0-beta.11", "1.0.0-rc.1")), + ?assert(rabbit_semver:lte("1.0.0-rc.1", "1.0.0-rc.1+build.1")), + ?assert(rabbit_semver:lte("1.0.0-rc.1+build.1", "1.0.0")), + ?assert(rabbit_semver:lte("1.0.0", "1.0.0+0.3.7")), + ?assert(rabbit_semver:lte("1.0.0+0.3.7", "1.3.7+build")), + ?assert(rabbit_semver:lte("1.3.7+build", "1.3.7+build.2.b8f12d7")), + ?assert(rabbit_semver:lte("1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a")), + ?assert(rabbit_semver:lte("1.0.0-alpha", "1.0.0-alpha")), + ?assert(rabbit_semver:lte("1", "1.0.0")), + ?assert(rabbit_semver:lte("1.0", "1.0.0")), + ?assert(rabbit_semver:lte("1.0.0", "1")), + ?assert(rabbit_semver:lte("1.0+alpha.1", "1.0.0+alpha.1")), + ?assert(rabbit_semver:lte("1.0.0.0+alpha.1", "1.0.0+alpha.1")), + ?assert(rabbit_semver:lte("1.0-alpha.1+build.1", "1.0.0-alpha.1+build.1")), + ?assert(rabbit_semver:lte("aa", "cc")), + ?assert(rabbit_semver:lte("cc", "cc")), + ?assertNot(rabbit_semver:lte("1.0.0-alpha.1", "1.0.0-alpha")), + ?assertNot(rabbit_semver:lte("cc", "aa")), + ?assertNot(rabbit_semver:lte("1.0.0-beta.2", "1.0.0-alpha.1")), + ?assertNot(rabbit_semver:lte("1.0.0-beta.11", "1.0.0-beta.2")), + ?assertNot(rabbit_semver:lte("1.0.0-rc.1", "1.0.0-beta.11")), + ?assertNot(rabbit_semver:lte("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assertNot(rabbit_semver:lte("1.0.0", "1.0.0-rc.1+build.1")), + ?assertNot(rabbit_semver:lte("1.0.0+0.3.7", "1.0.0")), + ?assertNot(rabbit_semver:lte("1.3.7+build", "1.0.0+0.3.7")), + ?assertNot(rabbit_semver:lte("1.3.7+build.2.b8f12d7", "1.3.7+build")), + ?assertNot(rabbit_semver:lte("1.3.7+build.11.e0f985a", "1.3.7+build.2.b8f12d7")). + +between_test(_Config) -> + ?assert(rabbit_semver:between("1.0.0-alpha", "1.0.0-alpha.3", "1.0.0-alpha.2")), + ?assert(rabbit_semver:between("1.0.0-alpha.1", "1.0.0-beta.2", "1.0.0-alpha.25")), + ?assert(rabbit_semver:between("1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-beta.7")), + ?assert(rabbit_semver:between("1.0.0-beta.11", "1.0.0-rc.3", "1.0.0-rc.1")), + ?assert(rabbit_semver:between("1.0.0-rc.1", "1.0.0-rc.1+build.3", "1.0.0-rc.1+build.1")), + ?assert(rabbit_semver:between("1.0.0.0-rc.1", "1.0.0-rc.1+build.3", "1.0.0-rc.1+build.1")), + ?assert(rabbit_semver:between("1.0.0-rc.1+build.1", "1.0.0", "1.0.0-rc.33")), + ?assert(rabbit_semver:between("1.0.0", "1.0.0+0.3.7", "1.0.0+0.2")), + ?assert(rabbit_semver:between("1.0.0+0.3.7", "1.3.7+build", "1.2")), + ?assert(rabbit_semver:between("1.3.7+build", "1.3.7+build.2.b8f12d7", "1.3.7+build.1")), + ?assert(rabbit_semver:between("1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a", "1.3.7+build.10.a36faa")), + ?assert(rabbit_semver:between("1.0.0-alpha", "1.0.0-alpha", "1.0.0-alpha")), + ?assert(rabbit_semver:between("1", "1.0.0", "1.0.0")), + ?assert(rabbit_semver:between("1.0", "1.0.0", "1.0.0")), + ?assert(rabbit_semver:between("1.0", "1.0.0.0", "1.0.0.0")), + ?assert(rabbit_semver:between("1.0.0", "1", "1")), + ?assert(rabbit_semver:between("1.0+alpha.1", "1.0.0+alpha.1", "1.0.0+alpha.1")), + ?assert(rabbit_semver:between("1.0-alpha.1+build.1", "1.0.0-alpha.1+build.1", "1.0.0-alpha.1+build.1")), + ?assert(rabbit_semver:between("aaa", "ddd", "cc")), + ?assertNot(rabbit_semver:between("1.0.0-alpha.1", "1.0.0-alpha.22", "1.0.0")), + ?assertNot(rabbit_semver:between("1.0.0", "1.0.0-alpha.1", "2.0")), + ?assertNot(rabbit_semver:between("1.0.0-beta.1", "1.0.0-beta.11", "1.0.0-alpha")), + ?assertNot(rabbit_semver:between("1.0.0-beta.11", "1.0.0-rc.1", "1.0.0-rc.22")), + ?assertNot(rabbit_semver:between("aaa", "ddd", "zzz")). + +pes_test(_Config) -> + ?assert(rabbit_semver:pes("2.6.0", "2.6")), + ?assert(rabbit_semver:pes("2.7", "2.6")), + ?assert(rabbit_semver:pes("2.8", "2.6")), + ?assert(rabbit_semver:pes("2.9", "2.6")), + ?assert(rabbit_semver:pes("A.B", "A.A")), + ?assertNot(rabbit_semver:pes("3.0.0", "2.6")), + ?assertNot(rabbit_semver:pes("2.5", "2.6")), + ?assert(rabbit_semver:pes("2.6.5", "2.6.5")), + ?assert(rabbit_semver:pes("2.6.6", "2.6.5")), + ?assert(rabbit_semver:pes("2.6.7", "2.6.5")), + ?assert(rabbit_semver:pes("2.6.8", "2.6.5")), + ?assert(rabbit_semver:pes("2.6.9", "2.6.5")), + ?assert(rabbit_semver:pes("2.6.0.9", "2.6.0.5")), + ?assertNot(rabbit_semver:pes("2.7", "2.6.5")), + ?assertNot(rabbit_semver:pes("2.1.7", "2.1.6.5")), + ?assertNot(rabbit_semver:pes("A.A", "A.B")), + ?assertNot(rabbit_semver:pes("2.5", "2.6.5")). + +version_format_test(_Config) -> + ?assertEqual(["1", [], []], rabbit_semver:format({1, {[],[]}})), + ?assertEqual(["1", ".", "2", ".", "34", [], []], rabbit_semver:format({{1,2,34},{[],[]}})), + ?assertEqual(<<"a">>, iolist_to_binary(rabbit_semver:format({<<"a">>, {[],[]}}))), + ?assertEqual(<<"a.b">>, iolist_to_binary(rabbit_semver:format({{<<"a">>,<<"b">>}, {[],[]}}))), + ?assertEqual(<<"1">>, iolist_to_binary(rabbit_semver:format({1, {[],[]}}))), + ?assertEqual(<<"1.2">>, iolist_to_binary(rabbit_semver:format({{1,2}, {[],[]}}))), + ?assertEqual(<<"1.2.2">>, iolist_to_binary(rabbit_semver:format({{1,2,2}, {[],[]}}))), + ?assertEqual(<<"1.99.2">>, iolist_to_binary(rabbit_semver:format({{1,99,2}, {[],[]}}))), + ?assertEqual(<<"1.99.2-alpha">>, iolist_to_binary(rabbit_semver:format({{1,99,2}, {[<<"alpha">>],[]}}))), + ?assertEqual(<<"1.99.2-alpha.1">>, iolist_to_binary(rabbit_semver:format({{1,99,2}, {[<<"alpha">>,1], []}}))), + ?assertEqual(<<"1.99.2+build.1.a36">>, + iolist_to_binary(rabbit_semver:format({{1,99,2}, {[], [<<"build">>, 1, <<"a36">>]}}))), + ?assertEqual(<<"1.99.2.44+build.1.a36">>, + iolist_to_binary(rabbit_semver:format({{1,99,2,44}, {[], [<<"build">>, 1, <<"a36">>]}}))), + ?assertEqual(<<"1.99.2-alpha.1+build.1.a36">>, + iolist_to_binary(rabbit_semver:format({{1,99,2}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))), + ?assertEqual(<<"1.99.2.44-alpha.1+build.1.a36">>, + iolist_to_binary(rabbit_semver:format({{1,99,2,44}, {[<<"alpha">>, 1], [<<"build">>, 1, <<"a36">>]}}))), + ?assertEqual(<<"a-pre+build">>, + iolist_to_binary(rabbit_semver:format({<<"a">>, {[<<"pre">>], [<<"build">>]}}))). + +normalize_then_format_test(_Config) -> + %% Standard versions + ?assertEqual(<<"1.0.0.0">>, rabbit_semver:normalize_then_format("1.0.0")), + ?assertEqual(<<"1.2.3.0">>, rabbit_semver:normalize_then_format("1.2.3")), + ?assertEqual(<<"1.2.3.4">>, rabbit_semver:normalize_then_format("1.2.3.4")), + ?assertEqual(<<"1.0.0.0">>, rabbit_semver:normalize_then_format("1")), + ?assertEqual(<<"1.2.0.0">>, rabbit_semver:normalize_then_format("1.2")), + %% Pre-release and build metadata stripped + ?assertEqual(<<"1.0.0.0">>, rabbit_semver:normalize_then_format("1.0.0-alpha")), + ?assertEqual(<<"1.0.0.0">>, rabbit_semver:normalize_then_format("1.0.0+build")), + ?assertEqual(<<"1.0.0.0">>, rabbit_semver:normalize_then_format("1.0.0-alpha+build")), + %% Prefixed versions extract embedded version + ?assertEqual(<<"3.13.12.0">>, rabbit_semver:normalize_then_format("tanzu+rabbitmq.v3.13.12.dev")), + ?assertEqual(<<"3.13.12.0">>, rabbit_semver:normalize_then_format("tanzu+rabbitmq.v3.13.12.dev.1.8.g37372ff")), + ?assertEqual(<<"4.0.0.0">>, rabbit_semver:normalize_then_format("tanzu+rabbitmq.v4.0.0.dev")), + ?assertEqual(<<"3.13.12.5">>, rabbit_semver:normalize_then_format("tanzu+rabbitmq.v3.13.12.5.dev")), + %% Binary input + ?assertEqual(<<"1.2.3.0">>, rabbit_semver:normalize_then_format(<<"1.2.3">>)), + ?assertEqual(<<"3.13.12.0">>, rabbit_semver:normalize_then_format(<<"tanzu+rabbitmq.v3.13.12.dev">>)). + +prefixed_version_parse_test(_Config) -> + ?assertMatch({<<"tanzu">>, + {[], [<<"rabbitmq">>,<<"v3">>,13,12,<<"dev">>,1,8,<<"g37372ff">>]}}, + rabbit_semver:parse("tanzu+rabbitmq.v3.13.12.dev.1.8.g37372ff")), + ?assertMatch({{4,2,0}, + {[], [<<"beta">>,4,104,<<"ge49ed93">>]}}, + rabbit_semver:parse("4.2.0+beta.4.104.ge49ed93")), + ?assertMatch({{4,0,9}, {[],[]}}, rabbit_semver:parse("4.0.9")), + ?assertMatch({{4,1,7}, {[],[]}}, rabbit_semver:parse("4.1.7")), + ?assertMatch({{4,2,3}, {[],[]}}, rabbit_semver:parse("4.2.3")), + ?assertMatch({{4,2,3}, {[],[<<"4">>,<<"gbbde858">>,<<"dirty">>]}}, + rabbit_semver:parse("4.2.3+4.gbbde858.dirty")). + +prefixed_version_normalize_test(_Config) -> + ?assertMatch({{3, 13, 12, 0}, {[], _}}, + rabbit_semver:normalize(rabbit_semver:parse("tanzu+rabbitmq.v3.13.12.dev.1.8.g37372ff"))), + ?assertMatch({{4, 2, 0, 0}, {[], _}}, + rabbit_semver:normalize(rabbit_semver:parse("4.2.0+beta.4.104.ge49ed93"))), + ?assertMatch({{3, 13, 0, 0}, {[], _}}, + rabbit_semver:normalize(rabbit_semver:parse("tanzu+rabbitmq.v3.13.dev"))), + ?assertMatch({{3, 0, 0, 0}, {[], _}}, + rabbit_semver:normalize(rabbit_semver:parse("tanzu+rabbitmq.v3.dev"))), + ?assertMatch({{3, 13, 12, 5}, {[], _}}, + rabbit_semver:normalize(rabbit_semver:parse("tanzu+rabbitmq.v3.13.12.5.dev"))), + ?assertMatch({{<<"noversion">>, 0, 0, 0}, {[], [<<"noembedded">>]}}, + rabbit_semver:normalize(rabbit_semver:parse("noversion+noembedded"))), + ?assertMatch({{3, 13, 12, 0}, {[<<"prerelease">>], _}}, + rabbit_semver:normalize(rabbit_semver:parse("tanzu-prerelease+rabbitmq.v3.13.12"))), + ?assertMatch({{4, 0, 9, 0}, {[], []}}, + rabbit_semver:normalize(rabbit_semver:parse("4.0.9"))), + ?assertMatch({{4, 1, 7, 0}, {[], []}}, + rabbit_semver:normalize(rabbit_semver:parse("4.1.7"))), + ?assertMatch({{4, 2, 3, 0}, {[], []}}, + rabbit_semver:normalize(rabbit_semver:parse("4.2.3"))), + ?assertMatch({{4, 2, 3, 0}, {[], _}}, + rabbit_semver:normalize(rabbit_semver:parse("4.2.3+4.gbbde858.dirty"))). + +edge_cases_test(_Config) -> + %% Large numbers + ?assert(rabbit_semver:gt("999.999.999", "999.999.998")), + ?assert(rabbit_semver:lt("0.0.0", "0.0.1")), + %% Single component + ?assert(rabbit_semver:eql("5", "5.0.0")), + ?assert(rabbit_semver:gt("6", "5")), + %% v prefix + ?assert(rabbit_semver:eql("v0.0.0", "0.0.0")), + ?assert(rabbit_semver:eql("v1", "1.0.0")), + %% Build metadata + ?assert(rabbit_semver:gt("1.0.0+zzz", "1.0.0+aaa")), + ?assert(rabbit_semver:lt("1.0.0+001", "1.0.0+002")), + %% Pre-release ordering + ?assert(rabbit_semver:lt("1.0.0-alpha", "1.0.0-alpha.1")), + ?assert(rabbit_semver:lt("1.0.0-alpha.1", "1.0.0-alpha.beta")), + ?assert(rabbit_semver:lt("1.0.0-alpha.beta", "1.0.0-beta")), + ?assert(rabbit_semver:lt("1.0.0-beta", "1.0.0-beta.2")), + ?assert(rabbit_semver:lt("1.0.0-beta.2", "1.0.0-beta.11")), + ?assert(rabbit_semver:lt("1.0.0-rc.1", "1.0.0")), + %% Numeric pre-release comparison + ?assert(rabbit_semver:lt("1.0.0-alpha.1", "1.0.0-alpha.2")), + ?assert(rabbit_semver:lt("1.0.0-alpha.2", "1.0.0-alpha.10")). + +prefixed_version_compare_test(_Config) -> + ?assert(rabbit_semver:gt("4.2.0", "tanzu+rabbitmq.v3.13.12.dev.1.8.g37372ff")), + ?assert(rabbit_semver:lt("tanzu+rabbitmq.v3.13.12.dev.1.8.g37372ff", "4.2.0")), + ?assert(rabbit_semver:gt("tanzu+rabbitmq.v4.0.0.dev.1.8.g37372ff", "3.13.12")), + ?assert(rabbit_semver:lt("3.13.12", "tanzu+rabbitmq.v4.0.0.dev.1.8.g37372ff")), + ?assert(rabbit_semver:gte("tanzu+rabbitmq.v3.13.12.dev", "3.13.12")), + ?assert(rabbit_semver:gt("tanzu+rabbitmq.v3.13.12.dev", "3.13.12")), + ?assertNot(rabbit_semver:eql("tanzu+rabbitmq.v3.13.12.dev", "3.13.11")), + ?assert(rabbit_semver:gt("tanzu+a.v4.0.0", "tanzu+b.v3.13.12")), + ?assert(rabbit_semver:lt("tanzu+a.v3.13.12", "tanzu+b.v4.0.0")), + ?assert(rabbit_semver:lt("tanzu+a.v3.13.12.dev", "tanzu+b.v3.13.12.dev")), + ?assert(rabbit_semver:gt("3.13.12", "tanzu-prerelease+rabbitmq.v3.13.12")), + ?assert(rabbit_semver:lt("tanzu-alpha+rabbitmq.v3.13.12", "tanzu-beta+rabbitmq.v3.13.12")), + ?assert(rabbit_semver:gt("4.2.3", "4.1.7")), + ?assert(rabbit_semver:gt("4.1.7", "4.0.9")), + ?assert(rabbit_semver:lt("4.0.9", "4.1.7")), + ?assert(rabbit_semver:lt("4.1.7", "4.2.3")), + ?assert(rabbit_semver:gt("4.2.3+4.gbbde858.dirty", "4.2.3")), + ?assert(rabbit_semver:lt("4.2.3", "4.2.3+4.gbbde858.dirty")), + ?assert(rabbit_semver:lt("4.2.3+4.gbbde858.dirty", "tanzu+rabbitmq.v4.2.3")), + ?assert(rabbit_semver:gte("4.2.3+4.gbbde858.dirty", "4.2.3")). diff --git a/deps/rabbit_common/test/rabbit_semver_prop_SUITE.erl b/deps/rabbit_common/test/rabbit_semver_prop_SUITE.erl new file mode 100644 index 000000000000..ad9aa97953f2 --- /dev/null +++ b/deps/rabbit_common/test/rabbit_semver_prop_SUITE.erl @@ -0,0 +1,392 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(rabbit_semver_prop_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("proper/include/proper.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +all() -> + [ + {group, basic_properties}, + {group, prerelease_properties}, + {group, prefixed_version_properties} + ]. + +groups() -> + [ + {basic_properties, [parallel], [ + prop_gt_asymmetric, + prop_lt_asymmetric, + prop_eql_symmetric, + prop_eql_reflexive, + prop_gte_definition, + prop_lte_definition, + prop_gt_lt_inverse, + prop_gt_transitive, + prop_lt_transitive, + prop_parse_format_roundtrip, + prop_normalize_idempotent + ]}, + {prerelease_properties, [parallel], [ + prop_prerelease_lt_release, + prop_prerelease_trichotomy + ]}, + {prefixed_version_properties, [parallel], [ + prop_prefixed_normalize_extracts_version, + prop_prefixed_gt_base_version, + prop_prefixed_respects_version_order, + prop_prefixed_trichotomy, + prop_prefixed_gt_transitive, + prop_prefixed_vs_regular_consistent + ]} + ]. + +init_per_suite(Config) -> Config. +end_per_suite(Config) -> Config. +init_per_group(_, Config) -> Config. +end_per_group(_, Config) -> Config. +init_per_testcase(_, Config) -> Config. +end_per_testcase(_, Config) -> Config. + +%% +%% Generators +%% + +semver_tuple_gen() -> + oneof([ + {non_neg_integer(), {[], []}}, + {{non_neg_integer(), non_neg_integer()}, {[], []}}, + {{non_neg_integer(), non_neg_integer(), non_neg_integer()}, {[], []}}, + {{non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}, {[], []}} + ]). + +version_string_gen() -> + ?LET({Maj, Min, Patch}, + {range(0, 100), range(0, 100), range(0, 100)}, + lists:flatten(io_lib:format("~b.~b.~b", [Maj, Min, Patch]))). + +prerelease_gen() -> + oneof([ + [], + [<<"alpha">>], + [<<"beta">>], + [<<"rc">>, range(1, 10)] + ]). + +version_with_prerelease_gen() -> + ?LET({Maj, Min, Patch, Pre}, + {range(0, 50), range(0, 50), range(0, 50), prerelease_gen()}, + {Maj, Min, Patch, Pre}). + +prefix_name_gen() -> + oneof([<<"tanzu">>, <<"custom">>, <<"vendor">>, <<"test">>]). + +build_suffix_gen() -> + oneof([ + [], + [<<"dev">>], + [<<"dev">>, range(1, 99)], + [<<"dev">>, range(1, 10), range(1, 10), <<"gabcdef">>] + ]). + +prefixed_version_gen() -> + ?LET({Prefix, Maj, Min, Patch, Suffix}, + {prefix_name_gen(), range(1, 50), range(0, 50), range(0, 50), build_suffix_gen()}, + {Prefix, Maj, Min, Patch, Suffix}). + +%% +%% Helper functions +%% + +vsn_with_pre_to_string({Maj, Min, Patch, []}) -> + lists:flatten(io_lib:format("~b.~b.~b", [Maj, Min, Patch])); +vsn_with_pre_to_string({Maj, Min, Patch, [Pre]}) -> + lists:flatten(io_lib:format("~b.~b.~b-~s", [Maj, Min, Patch, Pre])); +vsn_with_pre_to_string({Maj, Min, Patch, [Pre, Num]}) -> + lists:flatten(io_lib:format("~b.~b.~b-~s.~b", [Maj, Min, Patch, Pre, Num])). + +prefixed_vsn_to_string({Prefix, Maj, Min, Patch, Suffix}) -> + Base = io_lib:format("~s+rabbitmq.v~b.~b.~b", [Prefix, Maj, Min, Patch]), + case Suffix of + [] -> lists:flatten(Base); + _ -> lists:flatten([Base, ".", format_suffix(Suffix)]) + end. + +format_suffix([]) -> []; +format_suffix([H]) when is_binary(H) -> H; +format_suffix([H]) when is_integer(H) -> integer_to_list(H); +format_suffix([H | T]) when is_binary(H) -> [H, ".", format_suffix(T)]; +format_suffix([H | T]) when is_integer(H) -> [integer_to_list(H), ".", format_suffix(T)]. + +prefixed_vsn_embedded_version({_Prefix, Maj, Min, Patch, _Suffix}) -> + lists:flatten(io_lib:format("~b.~b.~b", [Maj, Min, Patch])). + +proper_opts() -> + [{numtests, 100}, + {on_output, fun(".", _) -> ok; + (F, A) -> ct:pal(?LOW_IMPORTANCE, F, A) + end}]. + +%% +%% Basic property tests +%% + +prop_gt_asymmetric(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB}, {version_string_gen(), version_string_gen()}, + case rabbit_semver:gt(VsnA, VsnB) of + true -> not rabbit_semver:gt(VsnB, VsnA); + false -> true + end), + proper_opts())). + +prop_lt_asymmetric(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB}, {version_string_gen(), version_string_gen()}, + case rabbit_semver:lt(VsnA, VsnB) of + true -> not rabbit_semver:lt(VsnB, VsnA); + false -> true + end), + proper_opts())). + +prop_eql_symmetric(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB}, {version_string_gen(), version_string_gen()}, + rabbit_semver:eql(VsnA, VsnB) =:= rabbit_semver:eql(VsnB, VsnA)), + proper_opts())). + +prop_eql_reflexive(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL(Vsn, version_string_gen(), + rabbit_semver:eql(Vsn, Vsn)), + proper_opts())). + +prop_gte_definition(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB}, {version_string_gen(), version_string_gen()}, + rabbit_semver:gte(VsnA, VsnB) =:= + (rabbit_semver:gt(VsnA, VsnB) orelse rabbit_semver:eql(VsnA, VsnB))), + proper_opts())). + +prop_lte_definition(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB}, {version_string_gen(), version_string_gen()}, + rabbit_semver:lte(VsnA, VsnB) =:= + (rabbit_semver:lt(VsnA, VsnB) orelse rabbit_semver:eql(VsnA, VsnB))), + proper_opts())). + +prop_gt_lt_inverse(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB}, {version_string_gen(), version_string_gen()}, + case {rabbit_semver:gt(VsnA, VsnB), + rabbit_semver:lt(VsnA, VsnB), + rabbit_semver:eql(VsnA, VsnB)} of + {true, false, false} -> rabbit_semver:lt(VsnB, VsnA); + {false, true, false} -> rabbit_semver:gt(VsnB, VsnA); + {false, false, true} -> true; + _ -> false + end), + proper_opts())). + +prop_gt_transitive(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB, VsnC}, + {version_string_gen(), version_string_gen(), version_string_gen()}, + case rabbit_semver:gt(VsnA, VsnB) andalso rabbit_semver:gt(VsnB, VsnC) of + true -> rabbit_semver:gt(VsnA, VsnC); + false -> true + end), + proper_opts())). + +prop_lt_transitive(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({VsnA, VsnB, VsnC}, + {version_string_gen(), version_string_gen(), version_string_gen()}, + case rabbit_semver:lt(VsnA, VsnB) andalso rabbit_semver:lt(VsnB, VsnC) of + true -> rabbit_semver:lt(VsnA, VsnC); + false -> true + end), + proper_opts())). + +prop_parse_format_roundtrip(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL(Vsn, semver_tuple_gen(), + begin + Formatted = iolist_to_binary(rabbit_semver:format(Vsn)), + Reparsed = rabbit_semver:parse(Formatted), + rabbit_semver:eql(Vsn, Reparsed) + end), + proper_opts())). + +prop_normalize_idempotent(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL(Vsn, semver_tuple_gen(), + rabbit_semver:normalize(Vsn) =:= + rabbit_semver:normalize(rabbit_semver:normalize(Vsn))), + proper_opts())). + +%% +%% Pre-release property tests +%% + +prop_prerelease_lt_release(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL(Tuple, version_with_prerelease_gen(), + begin + {Maj, Min, Patch, PreRelease} = Tuple, + case PreRelease of + [] -> true; + _ -> + VsnPre = vsn_with_pre_to_string(Tuple), + VsnRelease = lists:flatten( + io_lib:format("~b.~b.~b", [Maj, Min, Patch])), + rabbit_semver:lt(VsnPre, VsnRelease) + end + end), + proper_opts())). + +prop_prerelease_trichotomy(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({TupleA, TupleB}, + {version_with_prerelease_gen(), version_with_prerelease_gen()}, + begin + VsnA = vsn_with_pre_to_string(TupleA), + VsnB = vsn_with_pre_to_string(TupleB), + Gt = rabbit_semver:gt(VsnA, VsnB), + Lt = rabbit_semver:lt(VsnA, VsnB), + Eq = rabbit_semver:eql(VsnA, VsnB), + case {Gt, Lt, Eq} of + {true, false, false} -> true; + {false, true, false} -> true; + {false, false, true} -> true; + _ -> false + end + end), + proper_opts())). + +%% +%% Prefixed version property tests +%% + +prop_prefixed_normalize_extracts_version(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL(Tuple, prefixed_version_gen(), + begin + {_Prefix, Maj, Min, Patch, _Suffix} = Tuple, + PrefixedVsn = prefixed_vsn_to_string(Tuple), + {{NMaj, NMin, NPatch, 0}, {[], _}} = + rabbit_semver:normalize(rabbit_semver:parse(PrefixedVsn)), + NMaj =:= Maj andalso NMin =:= Min andalso NPatch =:= Patch + end), + proper_opts())). + +prop_prefixed_gt_base_version(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL(Tuple, prefixed_version_gen(), + begin + PrefixedVsn = prefixed_vsn_to_string(Tuple), + BaseVsn = prefixed_vsn_embedded_version(Tuple), + rabbit_semver:gt(PrefixedVsn, BaseVsn) + end), + proper_opts())). + +prop_prefixed_respects_version_order(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({TupleA, TupleB}, {prefixed_version_gen(), prefixed_version_gen()}, + begin + {_PrefixA, MajA, MinA, PatchA, _SuffixA} = TupleA, + {_PrefixB, MajB, MinB, PatchB, _SuffixB} = TupleB, + PrefixedA = prefixed_vsn_to_string(TupleA), + PrefixedB = prefixed_vsn_to_string(TupleB), + case {MajA, MinA, PatchA} > {MajB, MinB, PatchB} of + true -> rabbit_semver:gt(PrefixedA, PrefixedB); + false -> + case {MajA, MinA, PatchA} < {MajB, MinB, PatchB} of + true -> rabbit_semver:lt(PrefixedA, PrefixedB); + false -> true + end + end + end), + proper_opts())). + +prop_prefixed_trichotomy(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({TupleA, TupleB}, {prefixed_version_gen(), prefixed_version_gen()}, + begin + VsnA = prefixed_vsn_to_string(TupleA), + VsnB = prefixed_vsn_to_string(TupleB), + Gt = rabbit_semver:gt(VsnA, VsnB), + Lt = rabbit_semver:lt(VsnA, VsnB), + Eq = rabbit_semver:eql(VsnA, VsnB), + case {Gt, Lt, Eq} of + {true, false, false} -> true; + {false, true, false} -> true; + {false, false, true} -> true; + _ -> false + end + end), + proper_opts())). + +prop_prefixed_gt_transitive(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({TupleA, TupleB, TupleC}, + {prefixed_version_gen(), prefixed_version_gen(), prefixed_version_gen()}, + begin + VsnA = prefixed_vsn_to_string(TupleA), + VsnB = prefixed_vsn_to_string(TupleB), + VsnC = prefixed_vsn_to_string(TupleC), + case rabbit_semver:gt(VsnA, VsnB) andalso rabbit_semver:gt(VsnB, VsnC) of + true -> rabbit_semver:gt(VsnA, VsnC); + false -> true + end + end), + proper_opts())). + +prop_prefixed_vs_regular_consistent(_Config) -> + ?assert( + proper:quickcheck( + ?FORALL({PrefixedTuple, RegularVsn}, {prefixed_version_gen(), version_string_gen()}, + begin + {_Prefix, PMaj, PMin, PPatch, _Suffix} = PrefixedTuple, + PrefixedVsn = prefixed_vsn_to_string(PrefixedTuple), + {RMaj, RMin, RPatch} = case string:tokens(RegularVsn, ".") of + [M, N, P] -> {list_to_integer(M), list_to_integer(N), list_to_integer(P)} + end, + case {PMaj, PMin, PPatch} > {RMaj, RMin, RPatch} of + true -> rabbit_semver:gt(PrefixedVsn, RegularVsn); + false -> + case {PMaj, PMin, PPatch} < {RMaj, RMin, RPatch} of + true -> rabbit_semver:lt(PrefixedVsn, RegularVsn); + false -> + %% Same version tuple: prefixed > regular due to build metadata + rabbit_semver:gt(PrefixedVsn, RegularVsn) + end + end + end), + proper_opts())).