%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- %% ex: ts=4 sw=4 et %% %%------------------------------------------------------------------- %% Copyright 2012 Opscode, Inc. All Rights Reserved. %% %% This file is provided to you 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. %% %% @author Eric Merritt %%------------------------------------------------------------------- -module(rlx_depsolver_tests). -include_lib("eunit/include/eunit.hrl"). %%============================================================================ %% Tests %%============================================================================ first_test() -> Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2+build.33"}, {app3, "0.2", '>='}]}, {"0.2", []}, {"0.3", []}]}, {app2, [{"0.1", []}, {"0.2+build.33",[{app3, "0.3"}]}, {"0.3", []}]}, {app3, [{"0.1", []}, {"0.2", []}, {"0.3", []}]}]), case rlx_depsolver:solve(Dom0, [{app1, "0.1"}]) of {ok,[{app1,{{0,1},{[],[]}}}, {app2,{{0,2},{[],[<<"build">>,33]}}}, {app3,{{0,3},{[],[]}}}]} -> ok; E -> erlang:throw({invalid_result, E}) end. second_test() -> Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.1", '>='}, {app4, "0.2"}, {app3, "0.2", '>='}]}, {"0.2", []}, {"0.3", []}]}, {app2, [{"0.1", [{app3, "0.2", gte}]}, {"0.2", [{app3, "0.2", gte}]}, {"0.3", [{app3, "0.2", '>='}]}]}, {app3, [{"0.1", [{app4, "0.2", '>='}]}, {"0.2", [{app4, "0.2"}]}, {"0.3", []}]}, {app4, [{"0.1", []}, {"0.2", [{app2, "0.2", gte}, {app3, "0.3"}]}, {"0.3", []}]}]), X = rlx_depsolver:solve(Dom0, [{app1, "0.1"}, {app2, "0.3"}]), ?assertMatch({ok, [{app1,{{0,1},{[],[]}}}, {app4,{{0,2},{[],[]}}}, {app2,{{0,3},{[],[]}}}, {app3,{{0,3},{[],[]}}}]}, X). third_test() -> Pkg1Deps = [{app2, "0.1.0", '>='}, {app3, "0.1.1", "0.1.5", between}], Pkg2Deps = [{app4, "5.0.0", gte}], Pkg3Deps = [{app5, "2.0.0", '>='}], Pkg4Deps = [app5], Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}, {"0.1", Pkg2Deps}, {"1.0", Pkg2Deps}, {"3.0", Pkg2Deps}]}, {app3, [{"0.1.0", Pkg3Deps}, {"0.1.3", Pkg3Deps}, {"2.0.0", Pkg3Deps}, {"3.0.0", Pkg3Deps}, {"4.0.0", Pkg3Deps}]}, {app4, [{"0.1.0", Pkg4Deps}, {"0.3.0", Pkg4Deps}, {"5.0.0", Pkg4Deps}, {"6.0.0", Pkg4Deps}]}, {app5, [{"0.1.0", []}, {"0.3.0", []}, {"2.0.0", []}, {"6.0.0", []}]}]), ?assertMatch({ok, [{app1,{{3,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app4,{{6,0,0},{[],[]}}}, {app3,{{0,1,3},{[],[]}}}, {app5,{{6,0,0},{[],[]}}}]}, rlx_depsolver:solve(Dom0, [{app1, "3.0"}])), ?assertMatch({ok, [{app1,{{3,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app4,{{6,0,0},{[],[]}}}, {app3,{{0,1,3},{[],[]}}}, {app5,{{6,0,0},{[],[]}}}]}, rlx_depsolver:solve(Dom0, [app1])). fail_test() -> Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2"}, {app3, "0.2", gte}]}, {"0.2", []}, {"0.3", []}]}, {app2, [{"0.1", []}, {"0.2",[{app3, "0.1"}]}, {"0.3", []}]}, {app3, [{"0.1", []}, {"0.2", []}, {"0.3", []}]}]), Ret = rlx_depsolver:solve(Dom0, [{app1, "0.1"}]), %% We do this to make sure all errors can be formated. _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[{app1,{{0,1},{[],[]}}}], [{app1,{{0,1},{[],[]}}},[[{app2,{{0,2},{[],[]}}}]]]}], [{{app2,{{0,2},{[],[]}}},[{app3,{{0,1},{[],[]}}}]}, {{app1,{{0,1},{[],[]}}},[{app3,{{0,2},{[],[]}},gte}]}]}]}, Ret). conflicting_passing_test() -> Pkg1Deps = [{app2, "0.1.0", '>='}, {app5, "2.0.0"}, {app4, "0.3.0", "5.0.0", between}, {app3, "0.1.1", "0.1.5", between}], Pkg2Deps = [{app4, "3.0.0", gte}], Pkg3Deps = [{app5, "2.0.0", '>='}], Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}, {"0.1", Pkg2Deps}, {"1.0", Pkg2Deps}, {"3.0", Pkg2Deps}]}, {app3, [{"0.1.0", Pkg3Deps}, {"0.1.3", Pkg3Deps}, {"2.0.0", Pkg3Deps}, {"3.0.0", Pkg3Deps}, {"4.0.0", Pkg3Deps}]}, {app4, [{"0.1.0", [{app5, "0.1.0"}]}, {"0.3.0", [{app5, "0.3.0"}]}, {"5.0.0", [{app5, "2.0.0"}]}, {"6.0.0", [{app5, "6.0.0"}]}]}, {app5, [{"0.1.0", []}, {"0.3.0", []}, {"2.0.0", []}, {"6.0.0", []}]}]), ?assertMatch({ok, [{app1,{{3,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app4,{{5,0,0},{[],[]}}}, {app3,{{0,1,3},{[],[]}}}, {app5,{{2,0,0},{[],[]}}}]}, rlx_depsolver:solve(Dom0, [{app1, "3.0"}])), ?assertMatch({ok, [{app1,{{3,0},{[],[]}}}, {app2,{{3,0},{[],[]}}}, {app4,{{5,0,0},{[],[]}}}, {app3,{{0,1,3},{[],[]}}}, {app5,{{2,0,0},{[],[]}}}]}, rlx_depsolver:solve(Dom0, [app1, app2, app5])). circular_dependencies_test() -> Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", [app2]}]}, {app2, [{"0.0.1", [app1]}]}]), ?assertMatch({ok, [{app2,{{0,0,1},{[],[]}}},{app1,{{0,1,0},{[],[]}}}]}, rlx_depsolver:solve(Dom0, [{app1, "0.1.0"}])). conflicting_failing_test() -> Pkg1Deps = [app2, {app5, "2.0.0", '='}, {app4, "0.3.0", "5.0.0", between}], Pkg2Deps = [{app4, "5.0.0", gte}], Pkg3Deps = [{app5, "6.0.0"}], Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}]}, {app3, [{"0.1.0", Pkg3Deps}]}, {app4, [{"5.0.0", [{app5, "2.0.0"}]}]}, {app5, [{"2.0.0", []}, {"6.0.0", []}]}]), Ret = rlx_depsolver:solve(Dom0, [app1, app3]), _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[app3], [{app3,{{0,1,0},{[],[]}}},[[{app5,{{6,0,0},{[],[]}}}]]]}, {[app1], [{app1,{{3,0},{[],[]}}}, [[{app2,{{0,0,1},{[],[]}}},[[{app4,{{5,0,0},{[],[]}}}]]], [{app4,{{5,0,0},{[],[]}}}]]]}], [{{app4,{{5,0,0},{[],[]}}},[{app5,{{2,0,0},{[],[]}}}]}, {{app1,{{3,0},{[],[]}}},[{app5,{{2,0,0},{[],[]}},'='}]}]}]}, Ret). pessimistic_major_minor_patch_test() -> Pkg1Deps = [{app2, "2.1.1", '~>'}, {app3, "0.1.1", "0.1.5", between}], Pkg2Deps = [{app4, "5.0.0", gte}], Pkg3Deps = [{app5, "2.0.0", '>='}], Pkg4Deps = [app5], Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}, {"0.1", Pkg2Deps}, {"1.0", Pkg2Deps}, {"2.1.5", Pkg2Deps}, {"2.2", Pkg2Deps}, {"3.0", Pkg2Deps}]}, {app3, [{"0.1.0", Pkg3Deps}, {"0.1.3", Pkg3Deps}, {"2.0.0", Pkg3Deps}, {"3.0.0", Pkg3Deps}, {"4.0.0", Pkg3Deps}]}, {app4, [{"0.1.0", Pkg4Deps}, {"0.3.0", Pkg4Deps}, {"5.0.0", Pkg4Deps}, {"6.0.0", Pkg4Deps}]}, {app5, [{"0.1.0", []}, {"0.3.0", []}, {"2.0.0", []}, {"6.0.0", []}]}]), ?assertMatch({ok, [{app1,{{3,0},{[],[]}}}, {app2,{{2,1,5},{[],[]}}}, {app4,{{6,0,0},{[],[]}}}, {app3,{{0,1,3},{[],[]}}}, {app5,{{6,0,0},{[],[]}}}]}, rlx_depsolver:solve(Dom0, [{app1, "3.0"}])). pessimistic_major_minor_test() -> Pkg1Deps = [{app2, "2.1", '~>'}, {app3, "0.1.1", "0.1.5", between}], Pkg2Deps = [{app4, "5.0.0", gte}], Pkg3Deps = [{app5, "2.0.0", '>='}], Pkg4Deps = [app5], Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1.0", Pkg1Deps}, {"0.2", Pkg1Deps}, {"3.0", Pkg1Deps}]}, {app2, [{"0.0.1", Pkg2Deps}, {"0.1", Pkg2Deps}, {"1.0", Pkg2Deps}, {"2.1.5", Pkg2Deps}, {"2.2", Pkg2Deps}, {"3.0", Pkg2Deps}]}, {app3, [{"0.1.0", Pkg3Deps}, {"0.1.3", Pkg3Deps}, {"2.0.0", Pkg3Deps}, {"3.0.0", Pkg3Deps}, {"4.0.0", Pkg3Deps}]}, {app4, [{"0.1.0", Pkg4Deps}, {"0.3.0", Pkg4Deps}, {"5.0.0", Pkg4Deps}, {"6.0.0", Pkg4Deps}]}, {app5, [{"0.1.0", []}, {"0.3.0", []}, {"2.0.0", []}, {"6.0.0", []}]}]), ?assertMatch({ok, [{app1,{{3,0},{[],[]}}}, {app2,{{2,2},{[],[]}}}, {app4,{{6,0,0},{[],[]}}}, {app3,{{0,1,3},{[],[]}}}, {app5,{{6,0,0},{[],[]}}}]}, rlx_depsolver:solve(Dom0, [{app1, "3.0"}])). filter_versions_test() -> Cons = [{app2, "2.1", '~>'}, {app3, "0.1.1", "0.1.5", between}, {app4, "5.0.0", gte}, {app5, "2.0.0", '>='}, app5], Packages = [{app1, "0.1.0"}, {app1, "0.2"}, {app1, "0.2"}, {app1, "3.0"}, {app2, "0.0.1"}, {app2, "0.1"}, {app2, "1.0"}, {app2, "2.1.5"}, {app2, "2.2"}, {app2, "3.0"}, {app3, "0.1.0"}, {app3, "0.1.3"}, {app3, "2.0.0"}, {app3, "3.0.0"}, {app3, "4.0.0"}, {app4, "0.1.0"}, {app4, "0.3.0"}, {app4, "5.0.0"}, {app4, "6.0.0"}, {app5, "0.1.0"}, {app5, "0.3.0"}, {app5, "2.0.0"}, {app5, "6.0.0"}], ?assertMatch({ok, [{app1,"0.1.0"}, {app1,"0.2"}, {app1,"0.2"}, {app1,"3.0"}, {app2,"2.1.5"}, {app2,"2.2"}, {app3,"0.1.3"}, {app4,"5.0.0"}, {app4,"6.0.0"}, {app5,"2.0.0"}, {app5,"6.0.0"}]}, rlx_depsolver:filter_packages(Packages, Cons)), Ret = rlx_depsolver:filter_packages(Packages, [{"foo", "1.0.0", '~~~~'} | Cons]), _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, {invalid_constraints, [{<<"foo">>,{{1,0,0},{[],[]}},'~~~~'}]}}, Ret). -spec missing_test() -> ok. missing_test() -> Dom0 = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{app1, [{"0.1", [{app2, "0.2"}, {app3, "0.2", '>='}, {app4, "0.2", '='}]}, {"0.2", [{app4, "0.2"}]}, {"0.3", [{app4, "0.2", '='}]}]}, {app2, [{"0.1", []}, {"0.2",[{app3, "0.3"}]}, {"0.3", []}]}, {app3, [{"0.1", []}, {"0.2", []}, {"0.3", []}]}]), Ret1 = rlx_depsolver:solve(Dom0, [{app4, "0.1"}, {app3, "0.1"}]), _ = rlx_depsolver:format_error(Ret1), ?assertMatch({error,{unreachable_package,app4}}, Ret1), Ret2 = rlx_depsolver:solve(Dom0, [{app1, "0.1"}]), _ = rlx_depsolver:format_error(Ret2), ?assertMatch({error,{unreachable_package,app4}}, Ret2). binary_test() -> World = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, {<<"bar">>, [{<<"2.0.0">>, [{<<"foo">>, <<"3.0.0">>, gt}]}]}], Ret = rlx_depsolver:solve(rlx_depsolver:add_packages(rlx_depsolver:new_graph(), World), [<<"foo">>]), _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], [{{<<"foo">>,{{1,2,3},{[],[]}}}, [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). %% %% We don't have bar cookbook %% %% Ruby gives %% "message":"Unable to satisfy constraints on cookbook bar, which does not %% exist, due to run list item (foo >= 0.0.0). Run list items that may result %% in a constraint on bar: [(foo = 1.2.3) -> (bar > 2.0.0)]", %% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", %% "non_existent_cookbooks":["bar"]," %% "most_constrained_cookbooks":[]}" %% doesnt_exist_test() -> Constraints = [{<<"foo">>,[{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}], World = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), Constraints), Ret = rlx_depsolver:solve(World, [<<"foo">>]), _ = rlx_depsolver:format_error(Ret), ?assertMatch({error,{unreachable_package,<<"bar">>}}, Ret). %% %% We have v 2.0.0 of bar but want > 2.0.0 %% %% Ruby gives %% "message":"Unable to satisfy constraints on cookbook bar due to run list item %% (foo >= 0.0.0). Run list items that may result in a constraint on bar: [(foo %% = 1.2.3) -> (bar > 2.0.0)]", %% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", %% "non_existent_cookbooks":[], %% "most_constrained_cookbooks":["bar 2.0.0 -> []"] %% not_new_enough_test() -> Constraints = [{<<"foo">>, [{<<"1.2.3">>, [{<<"bar">>, <<"2.0.0">>, gt}]}]}, {<<"bar">>, [{<<"2.0.0">>, []}]}], World = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), Constraints), Ret = rlx_depsolver:solve(World, [<<"foo">>]), _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], [{{<<"foo">>,{{1,2,3},{[],[]}}}, [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). %% %% circular deps are bad %% %% Ruby gives %% "message":"Unable to satisfy constraints on cookbook bar due to run list item (foo >= 0.0.0). %% Run list items that may result in a constraint on bar: [(foo = 1.2.3) -> (bar > 2.0.0)]", %% "unsatisfiable_run_list_item":"(foo >= 0.0.0)", %% "non_existent_cookbooks":[], %% "most_constrained_cookbooks:["bar = 2.0.0 -> [(foo > 3.0.0)]"] %% impossible_dependency_test() -> World = rlx_depsolver:add_packages(rlx_depsolver:new_graph(), [{<<"foo">>, [{<<"1.2.3">>,[{ <<"bar">>, <<"2.0.0">>, gt}]}]}, {<<"bar">>, [{<<"2.0.0">>, [{ <<"foo">>, <<"3.0.0">>, gt}]}]}]), Ret = rlx_depsolver:solve(World, [<<"foo">>]), _ = rlx_depsolver:format_error(Ret), ?assertMatch({error, [{[{[<<"foo">>],[{<<"foo">>,{{1,2,3},{[],[]}}}]}], [{{<<"foo">>,{{1,2,3},{[],[]}}}, [{<<"bar">>,{{2,0,0},{[],[]}},gt}]}]}]}, Ret). %% %% Formatting tests %% format_test_() -> [{"format constraint", [equal_bin_string(<<"foo">>, rlx_depsolver:format_constraint(<<"foo">>)), equal_bin_string(<<"foo">>, rlx_depsolver:format_constraint(foo)), equal_bin_string(<<"(foo = 1.2.0)">>, rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}})), equal_bin_string(<<"(foo = 1.2.0)">>, rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '='})), equal_bin_string(<<"(foo > 1.2.0)">>, rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, '>'})), equal_bin_string(<<"(foo > 1.2.0)">>, rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {[], []}}, gt})), equal_bin_string(<<"(foo between 1.2.0 and 1.3.0)">>, rlx_depsolver:format_constraint({<<"foo">>,{{1,2,0}, {[], []}}, {{1,3,0}, {[], []}}, between})), equal_bin_string(<<"(foo > 1.2.0-alpha.1+build.36)">>, rlx_depsolver:format_constraint({<<"foo">>, {{1,2,0}, {["alpha", 1], ["build", 36]}}, gt})) ] }, {"format roots", [equal_bin_string(<<"(bar = 1.2.0)">>, rlx_depsolver:format_roots([ [{<<"bar">>, {{1,2,0},{[],[]}}}] ])), equal_bin_string(<<"(bar = 1.2.0), foo">>, rlx_depsolver:format_roots([[<<"foo">>, {<<"bar">>, {{1,2,0},{[],[]}}}]])), equal_bin_string(<<"(bar = 1.2.0), foo">>, rlx_depsolver:format_roots([[<<"foo">>], [{<<"bar">>, {{1,2,0},{[],[]}}}]])) ] } ]. %% %% Internal functions %% equal_bin_string(Expected, Got) -> ?_assertEqual(Expected, erlang:iolist_to_binary(Got)).