aboutsummaryrefslogtreecommitdiffstats
path: root/test/rlx_depsolver_tests.erl
blob: 206bad4851028176d9f7c5386b4d8973bd8149b9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*-
%% ex: ts=4 sx=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 <[email protected]>
%%-------------------------------------------------------------------
-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)).