1Code.require_file("../../test_helper.exs", __DIR__)
2
3defmodule Mix.Tasks.DepsTest do
4  use MixTest.Case
5
6  defmodule DepsApp do
7    def project do
8      [
9        app: :deps,
10        version: "0.1.0",
11        deps: [
12          {:ok, "0.1.0", github: "elixir-lang/ok"},
13          {:invalidvsn, "0.2.0", path: "deps/invalidvsn"},
14          {:invalidapp, "0.1.0", path: "deps/invalidapp"},
15          {:noappfile, "0.1.0", path: "deps/noappfile"},
16          {:nosemver, "~> 0.1", path: "deps/nosemver"}
17        ]
18      ]
19    end
20  end
21
22  defmodule SuccessfulDepsApp do
23    def project do
24      [
25        app: :sample,
26        version: "0.1.0",
27        deps: [
28          {:ok, "0.1.0", path: "deps/ok"}
29        ]
30      ]
31    end
32  end
33
34  defmodule ReqDepsApp do
35    def project do
36      [
37        app: :req_deps,
38        version: "0.1.0",
39        deps: [
40          {:ok, ">= 2.0.0", path: "deps/ok"},
41          {:noappfile, path: "deps/noappfile", app: false},
42          {:apppath, path: "deps/noappfile", app: "../deps/ok/ebin/ok.app"}
43        ]
44      ]
45    end
46  end
47
48  defmodule MissingLocalDepsApp do
49    def project do
50      [
51        app: :missing_local_deps,
52        version: "0.1.0",
53        deps: [
54          {:ok, path: "missing/dep"}
55        ]
56      ]
57    end
58  end
59
60  ## deps
61
62  test "prints list of dependencies and their status" do
63    in_fixture("deps_status", fn ->
64      Mix.Project.push(DepsApp)
65
66      Mix.Tasks.Deps.run([])
67
68      assert_received {:mix_shell, :info, ["* ok (https://github.com/elixir-lang/ok.git) (mix)"]}
69      msg = "  the dependency is not available, run \"mix deps.get\""
70      assert_received {:mix_shell, :info, [^msg]}
71
72      assert_received {:mix_shell, :info, ["* invalidvsn (deps/invalidvsn)"]}
73      assert_received {:mix_shell, :info, ["  the app file contains an invalid version: :ok"]}
74
75      assert_received {:mix_shell, :info, ["* invalidapp (deps/invalidapp) (mix)"]}
76      msg = "  the app file at \"_build/dev/lib/invalidapp/ebin/invalidapp.app\" is invalid"
77      assert_received {:mix_shell, :info, [^msg]}
78
79      assert_received {:mix_shell, :info, ["* noappfile (deps/noappfile)"]}
80      assert_received {:mix_shell, :info, ["  could not find an app file at" <> _]}
81
82      assert_received {:mix_shell, :info, ["* nosemver (deps/nosemver)"]}
83      assert_received {:mix_shell, :info, ["  the app file specified a non-Semantic" <> _]}
84    end)
85  end
86
87  test "prints list of dependencies and their status, including req mismatches and custom apps" do
88    in_fixture("deps_status", fn ->
89      Mix.Project.push(ReqDepsApp)
90
91      Mix.Tasks.Deps.run([])
92
93      assert_received {:mix_shell, :info, ["* ok (deps/ok) (mix)"]}
94      msg = "  the dependency does not match the requirement \">= 2.0.0\", got \"0.1.0\""
95      assert_received {:mix_shell, :info, [^msg]}
96
97      assert_received {:mix_shell, :info, ["* apppath (deps/noappfile)"]}
98      refute_received {:mix_shell, :info, ["  could not find app file at " <> _]}
99
100      assert_received {:mix_shell, :info, ["* noappfile (deps/noappfile)"]}
101      refute_received {:mix_shell, :info, ["  could not find app file at " <> _]}
102    end)
103  end
104
105  test "prints misspelled dependency name hint" do
106    in_fixture("deps_status", fn ->
107      Mix.Project.push(DepsApp)
108
109      other_app_path = Path.join(Mix.Project.build_path(), "lib/noappfile/ebin/other_app.app")
110      File.mkdir_p!(Path.dirname(other_app_path))
111      File.write!(other_app_path, "")
112
113      Mix.Tasks.Deps.run([])
114
115      message =
116        "  could not find an app file at \"_build/dev/lib/noappfile/ebin/noappfile.app\". " <>
117          "Another app file was found in the same directory " <>
118          "\"_build/dev/lib/noappfile/ebin/other_app.app\", " <>
119          "try changing the dependency name to :other_app"
120
121      assert_received {:mix_shell, :info, ["* noappfile (deps/noappfile)"]}
122      assert_received {:mix_shell, :info, [^message]}
123    end)
124  end
125
126  test "prints Elixir req mismatches" do
127    in_fixture("deps_status", fn ->
128      Mix.Project.push(ReqDepsApp)
129
130      File.write!("deps/ok/mix.exs", """
131      defmodule Deps.OkApp do
132        use Mix.Project
133
134        def project do
135          [elixir: "~> 0.1.0", app: :ok, version: "2.0.0"]
136        end
137      end
138      """)
139
140      Mix.Tasks.Deps.Compile.run([:ok])
141
142      msg =
143        "warning: the dependency :ok requires Elixir \"~> 0.1.0\" " <>
144          "but you are running on v#{System.version()}"
145
146      assert_received {:mix_shell, :error, [^msg]}
147
148      Mix.Tasks.Deps.Compile.run([])
149    end)
150  end
151
152  test "prints list of dependencies and their lock status" do
153    in_fixture("deps_status", fn ->
154      Mix.Project.push(DepsApp)
155
156      File.cd!("deps/ok", fn ->
157        System.cmd("git", ~w[-c core.hooksPath='' init])
158      end)
159
160      Mix.Tasks.Deps.run([])
161      assert_received {:mix_shell, :info, ["* ok (https://github.com/elixir-lang/ok.git) (mix)"]}
162
163      msg =
164        "  the dependency is not locked. To generate the \"mix.lock\" file run \"mix deps.get\""
165
166      assert_received {:mix_shell, :info, [^msg]}
167
168      Mix.Dep.Lock.write(%{ok: {:git, "https://github.com/elixir-lang/ok.git", "abcdefghi", []}})
169      Mix.Tasks.Deps.run([])
170
171      assert_received {:mix_shell, :info, ["* ok (https://github.com/elixir-lang/ok.git) (mix)"]}
172      assert_received {:mix_shell, :info, ["  locked at abcdefg"]}
173
174      msg =
175        "  lock mismatch: the dependency is out of date. To fetch locked version run \"mix deps.get\""
176
177      assert_received {:mix_shell, :info, [^msg]}
178
179      Mix.Dep.Lock.write(%{
180        ok: {:git, "git://github.com/elixir-lang/another.git", "abcdefghi", []}
181      })
182
183      Mix.Tasks.Deps.run([])
184
185      assert_received {:mix_shell, :info, ["* ok (https://github.com/elixir-lang/ok.git) (mix)"]}
186
187      msg =
188        "  lock outdated: the lock is outdated compared to the options in your mix.exs. To fetch locked version run \"mix deps.get\""
189
190      assert_received {:mix_shell, :info, [^msg]}
191    end)
192  end
193
194  test "cleans and recompiles artifacts if --force is given" do
195    in_fixture("deps_status", fn ->
196      Mix.Project.push(SuccessfulDepsApp)
197
198      Mix.Tasks.Deps.Compile.run([])
199      File.touch!("_build/dev/lib/ok/clean-me")
200
201      Mix.Tasks.Deps.Compile.run(["--force"])
202      refute File.exists?("_build/dev/lib/ok/clean-me")
203    end)
204  end
205
206  test "doesn't compile any umbrella apps if --skip-umbrella-children is given" do
207    in_fixture("umbrella_dep/deps/umbrella", fn ->
208      Mix.Project.in_project(:umbrella, ".", fn _ ->
209        Mix.Tasks.Deps.Compile.run(["--skip-umbrella-children"])
210        refute File.exists?("_build/dev/lib/foo/ebin")
211        refute File.exists?("_build/dev/lib/bar/ebin")
212      end)
213    end)
214  end
215
216  test "doesn't compile any path deps if --skip-local-deps is given" do
217    in_fixture("deps_status", fn ->
218      Mix.Project.push(SuccessfulDepsApp)
219
220      File.rm_rf!("_build/dev/lib/ok/ebin")
221      Mix.Tasks.Deps.Compile.run(["--skip-local-deps"])
222      refute File.exists?("_build/dev/lib/ok/ebin")
223    end)
224  end
225
226  test "checks if local dependencies are available before compiling" do
227    Mix.Project.push(MissingLocalDepsApp)
228
229    error_message =
230      "Cannot compile dependency :ok because it isn't available, please ensure the dependency " <>
231        "is at \"missing/dep\""
232
233    assert_raise Mix.Error, error_message, fn ->
234      Mix.Tasks.Deps.Compile.run([])
235    end
236  end
237
238  ## deps.loadpaths
239
240  test "checks list of dependencies and their status with success" do
241    in_fixture("deps_status", fn ->
242      Mix.Project.push(SuccessfulDepsApp)
243
244      Mix.Tasks.Deps.Loadpaths.run([])
245    end)
246  end
247
248  test "checks list of dependencies and their status on failure" do
249    in_fixture("deps_status", fn ->
250      Mix.Project.push(DepsApp)
251
252      assert_raise Mix.Error, fn ->
253        Mix.Tasks.Deps.Loadpaths.run([])
254      end
255
256      assert_received {:mix_shell, :error, ["* ok (https://github.com/elixir-lang/ok.git)"]}
257      msg = "  the dependency is not available, run \"mix deps.get\""
258      assert_received {:mix_shell, :error, [^msg]}
259
260      assert_received {:mix_shell, :error, ["* invalidvsn (deps/invalidvsn)"]}
261      assert_received {:mix_shell, :error, ["  the app file contains an invalid version: :ok"]}
262
263      assert_received {:mix_shell, :error, ["* invalidapp (deps/invalidapp)"]}
264      msg = "  the app file at \"_build/dev/lib/invalidapp/ebin/invalidapp.app\" is invalid"
265      assert_received {:mix_shell, :error, [^msg]}
266
267      # This one is compiled automatically
268      refute_received {:mix_shell, :error, ["* noappfile (deps/noappfile)"]}
269      refute_received {:mix_shell, :error, ["  could not find an app file at " <> _]}
270    end)
271  end
272
273  test "does not load deps with --no-load-deps" do
274    in_fixture("deps_status", fn ->
275      Mix.Project.push(SuccessfulDepsApp)
276
277      # Start from scratch!
278      File.rm_rf("_build")
279
280      Mix.Tasks.Deps.Compile.run([])
281      Mix.Tasks.Deps.Loadpaths.run([])
282      assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
283      assert File.exists?("_build/dev/lib/ok/priv/sample")
284
285      Mix.Tasks.Compile.run([])
286      assert to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
287      assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
288
289      # Remove the deps without build_path
290      Mix.ProjectStack.post_config(deps: [])
291      Mix.State.clear_cache()
292      Mix.Project.pop()
293      Mix.Project.push(SuccessfulDepsApp)
294      Code.delete_path("_build/dev/lib/ok/ebin")
295
296      Mix.Tasks.Deps.Loadpaths.run(["--no-load-deps"])
297      refute to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
298      assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
299      assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
300    end)
301  end
302
303  ## deps.unlock
304
305  test "unlocks all deps", context do
306    in_tmp(context.test, fn ->
307      Mix.Project.push(DepsApp)
308
309      Mix.Dep.Lock.write(%{git_repo: "abcdef"})
310      assert Mix.Dep.Lock.read() == %{git_repo: "abcdef"}
311      Mix.Tasks.Deps.Unlock.run(["--all"])
312      assert Mix.Dep.Lock.read() == %{}
313    end)
314  end
315
316  test "checks lock file has unused deps with --check-unused", context do
317    in_tmp(context.test, fn ->
318      Mix.Project.push(DepsApp)
319
320      Mix.Dep.Lock.write(%{whatever: "0.2.0", something_else: "1.2.3", ok: "0.1.0"})
321      assert Mix.Dep.Lock.read() == %{whatever: "0.2.0", something_else: "1.2.3", ok: "0.1.0"}
322
323      error = """
324      Unused dependencies in mix.lock file:
325
326        * :something_else
327        * :whatever
328      """
329
330      assert_raise Mix.Error, error, fn ->
331        Mix.Tasks.Deps.Unlock.run(["--check-unused"])
332      end
333
334      assert Mix.Dep.Lock.read() == %{whatever: "0.2.0", something_else: "1.2.3", ok: "0.1.0"}
335
336      Mix.Tasks.Deps.Unlock.run(["--unused"])
337      Mix.Tasks.Deps.Unlock.run(["--check-unused"])
338      assert Mix.Dep.Lock.read() == %{ok: "0.1.0"}
339    end)
340  end
341
342  test "unlocks unused deps", context do
343    in_tmp(context.test, fn ->
344      Mix.Project.push(DepsApp)
345
346      Mix.Dep.Lock.write(%{whatever: "abcdef", ok: "abcdef"})
347      assert Mix.Dep.Lock.read() == %{whatever: "abcdef", ok: "abcdef"}
348      Mix.Tasks.Deps.Unlock.run(["--unused"])
349      assert Mix.Dep.Lock.read() == %{ok: "abcdef"}
350
351      output = """
352      Unlocked deps:
353      * whatever
354      """
355
356      assert_received {:mix_shell, :info, [^output]}
357    end)
358  end
359
360  test "unlocking a dep that is not locked is a no-op", context do
361    in_tmp(context.test, fn ->
362      Mix.Project.push(DepsApp)
363      Mix.Tasks.Deps.Unlock.run(["unlocked_dep"])
364
365      assert_received {:mix_shell, :error, ["warning: unlocked_dep dependency is not locked"]}
366      refute_received {:mix_shell, :info, [_]}
367    end)
368  end
369
370  test "unlocks specific deps", context do
371    in_tmp(context.test, fn ->
372      Mix.Project.push(DepsApp)
373
374      Mix.Dep.Lock.write(%{git_repo: "abcdef", another: "hash"})
375      Mix.Tasks.Deps.Unlock.run(["git_repo", "unknown"])
376      assert Mix.Dep.Lock.read() == %{another: "hash"}
377      error = "warning: unknown dependency is not locked"
378      assert_received {:mix_shell, :error, [^error]}
379
380      output = """
381      Unlocked deps:
382      * git_repo
383      """
384
385      assert_received {:mix_shell, :info, [^output]}
386    end)
387  end
388
389  test "unlocks filtered deps", context do
390    in_tmp(context.test, fn ->
391      Mix.Project.push(DepsApp)
392
393      Mix.Dep.Lock.write(%{git_repo: "abcdef", another: "hash", another_one: "hash"})
394      Mix.Tasks.Deps.Unlock.run(["--filter", "another"])
395      assert Mix.Dep.Lock.read() == %{git_repo: "abcdef"}
396
397      output = """
398      Unlocked deps:
399      * another
400      * another_one
401      """
402
403      assert_received {:mix_shell, :info, [^output]}
404    end)
405  end
406
407  test "fails with message on missing dependencies" do
408    Mix.Project.push(DepsApp)
409
410    assert_raise Mix.Error, ~r/"mix deps\.unlock" expects dependencies as arguments/, fn ->
411      Mix.Tasks.Deps.Unlock.run([])
412    end
413  end
414
415  ## Deps environment
416
417  defmodule DepsEnvApp do
418    def project do
419      [
420        app: :raw_sample,
421        version: "0.1.0",
422        deps: [
423          {:raw_repo, "0.1.0", path: "custom/raw_repo"}
424        ]
425      ]
426    end
427  end
428
429  defmodule CustomDepsEnvApp do
430    def project do
431      [
432        app: :raw_sample,
433        version: "0.1.0",
434        deps: [
435          {:raw_repo, "0.1.0", path: "custom/raw_repo", env: :dev}
436        ]
437      ]
438    end
439  end
440
441  test "sets deps env to prod by default" do
442    in_fixture("deps_status", fn ->
443      Mix.Project.push(DepsEnvApp)
444
445      Mix.Tasks.Deps.Update.run(["--all"])
446      assert_received {:mix_shell, :info, [":raw_repo env is prod"]}
447    end)
448  end
449
450  test "can customize environment" do
451    in_fixture("deps_status", fn ->
452      Mix.Project.push(CustomDepsEnvApp)
453
454      Mix.Tasks.Deps.Update.run(["--all"])
455      assert_received {:mix_shell, :info, [":raw_repo env is dev"]}
456    end)
457  end
458
459  ## Nested dependencies
460
461  defmodule ConflictDepsApp do
462    def project do
463      [
464        app: :raw_sample,
465        version: "0.1.0",
466        deps: [
467          {:git_repo, "0.1.0", path: "custom/raw_repo"},
468          {:bad_deps_repo, "0.1.0", path: "custom/bad_deps_repo"}
469        ]
470      ]
471    end
472  end
473
474  defmodule DivergedDepsApp do
475    def project do
476      [
477        app: :raw_sample,
478        version: "0.1.0",
479        deps: [
480          {:deps_repo, "0.1.0", path: "custom/deps_repo"},
481          {:bad_deps_repo, "0.1.0", path: "custom/bad_deps_repo"}
482        ]
483      ]
484    end
485  end
486
487  defmodule ConvergedDepsApp do
488    def project do
489      [
490        app: :raw_sample,
491        version: "0.1.0",
492        deps: [
493          {:deps_repo, "0.1.0", path: "custom/deps_repo"},
494          {:git_repo, ">= 0.1.0", git: MixTest.Case.fixture_path("git_repo")}
495        ]
496      ]
497    end
498  end
499
500  defmodule OverriddenDepsApp do
501    def project do
502      [
503        app: :raw_sample,
504        version: "0.1.0",
505        deps: [
506          {:bad_deps_repo, "0.1.0", path: "custom/bad_deps_repo"},
507          {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo"), override: true}
508        ]
509      ]
510    end
511  end
512
513  defmodule NonOverriddenDepsApp do
514    def project do
515      [
516        app: :raw_sample,
517        version: "0.1.0",
518        deps: [
519          {:bad_deps_repo, "0.1.0", path: "custom/bad_deps_repo"},
520          {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
521        ]
522      ]
523    end
524  end
525
526  test "fails on missing dependencies" do
527    in_fixture("deps_status", fn ->
528      Mix.Project.push(SuccessfulDepsApp)
529
530      assert_raise Mix.Error, ~r/Unknown dependency invalid for environment dev/, fn ->
531        Mix.Tasks.Deps.Update.run(["invalid"])
532      end
533    end)
534  end
535
536  @overriding_msg "  the dependency git_repo in mix.exs is overriding a child dependency"
537
538  test "fails on diverged dependencies on get/update" do
539    in_fixture("deps_status", fn ->
540      Mix.Project.push(ConflictDepsApp)
541
542      assert_raise Mix.Error, fn ->
543        Mix.Tasks.Deps.Loadpaths.run([])
544      end
545
546      assert_received {:mix_shell, :error, [@overriding_msg <> _]}
547
548      assert_raise Mix.Error, fn ->
549        Mix.Tasks.Deps.Get.run([])
550      end
551
552      assert_received {:mix_shell, :error, [@overriding_msg <> _]}
553
554      assert_raise Mix.Error, fn ->
555        Mix.Tasks.Deps.Update.run(["--all"])
556      end
557
558      assert_received {:mix_shell, :error, [@overriding_msg <> _]}
559    end)
560  end
561
562  test "fails on diverged dependencies on check" do
563    in_fixture("deps_status", fn ->
564      Mix.Project.push(DivergedDepsApp)
565
566      assert_raise Mix.Error, fn ->
567        Mix.Tasks.Deps.Loadpaths.run([])
568      end
569
570      assert_received {:mix_shell, :error, ["  different specs were given" <> _ = received_msg]}
571      assert received_msg =~ "In custom/deps_repo/mix.exs:"
572
573      assert received_msg =~
574               "{:git_repo, \"0.1.0\", [env: :prod, git: #{inspect(fixture_path("git_repo"))}]}"
575    end)
576  end
577
578  test "fails on diverged dependencies by requirement" do
579    in_fixture("deps_status", fn ->
580      Mix.Project.push(ConvergedDepsApp)
581
582      File.write!("custom/deps_repo/mix.exs", """
583      defmodule DepsRepo do
584        use Mix.Project
585
586        def project do
587          [
588            app: :deps_repo,
589            version: "0.1.0",
590            deps: [
591              {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")}
592            ]
593          ]
594        end
595      end
596      """)
597
598      assert_raise Mix.Error, fn ->
599        Mix.Tasks.Deps.Get.run([])
600        Mix.Tasks.Deps.Loadpaths.run([])
601      end
602
603      assert_received {:mix_shell, :error, ["  the dependency git_repo 0.1.0" <> _ = msg]}
604      assert msg =~ "In custom/deps_repo/mix.exs:"
605
606      assert msg =~
607               "{:git_repo, \"0.2.0\", [env: :prod, git: #{inspect(fixture_path("git_repo"))}]}"
608    end)
609  end
610
611  @overriding_msg "  the dependency git_repo in mix.exs is overriding"
612
613  test "fails on diverged dependencies even when optional" do
614    in_fixture("deps_status", fn ->
615      Mix.Project.push(ConvergedDepsApp)
616
617      File.write!("custom/deps_repo/mix.exs", """
618      defmodule DepsRepo do
619        use Mix.Project
620
621        def project do
622          [
623            app: :deps_repo,
624            version: "0.1.0",
625            deps: [
626              {:git_repo, git: MixTest.Case.fixture_path("bad_git_repo"), branch: "omg"}
627            ]
628          ]
629        end
630      end
631      """)
632
633      assert_raise Mix.Error, fn ->
634        Mix.Tasks.Deps.Get.run([])
635        Mix.Tasks.Deps.Loadpaths.run([])
636      end
637
638      assert_received {:mix_shell, :error, [@overriding_msg <> _]}
639    end)
640  end
641
642  test "works with converged dependencies" do
643    in_fixture("deps_status", fn ->
644      Mix.Project.push(ConvergedDepsApp)
645
646      Mix.Tasks.Deps.Get.run([])
647      message = "* Getting git_repo (#{fixture_path("git_repo")})"
648      assert_received {:mix_shell, :info, [^message]}
649
650      # Make sure retriever uses converger,
651      # so the message appears just once
652      refute_received {:mix_shell, :info, [^message]}
653
654      Mix.Task.clear()
655      Mix.Tasks.Deps.Update.run(["--all"])
656
657      message = "* Updating git_repo (#{fixture_path("git_repo")})"
658      assert_received {:mix_shell, :info, [^message]}
659    end)
660  after
661    purge([GitRepo, GitRepo.MixProject])
662  end
663
664  test "does not check dependencies if --no-deps-check is provided" do
665    in_fixture("deps_status", fn ->
666      Mix.Project.push(SuccessfulDepsApp)
667
668      Mix.Tasks.Deps.Get.run([])
669      File.rm_rf!("deps/ok")
670
671      assert_raise Mix.Error, fn ->
672        Mix.Tasks.Compile.run([])
673      end
674
675      Mix.Tasks.Compile.run(["--no-deps-check"])
676    end)
677  end
678
679  test "works with overridden dependencies" do
680    in_fixture("deps_status", fn ->
681      Mix.Project.push(OverriddenDepsApp)
682
683      Mix.Tasks.Deps.Get.run([])
684      message = "* Getting git_repo (#{fixture_path("git_repo")})"
685      assert_received {:mix_shell, :info, [^message]}
686
687      # Make sure retriever uses converger,
688      # so the message appears just once
689      refute_received {:mix_shell, :info, [^message]}
690
691      Mix.Task.clear()
692      Mix.Tasks.Deps.Update.run(["--all"])
693
694      message = "* Updating git_repo (#{fixture_path("git_repo")})"
695      assert_received {:mix_shell, :info, [^message]}
696    end)
697  after
698    purge([GitRepo, GitRepo.MixProject])
699  end
700
701  test "converged dependencies errors if not overriding" do
702    in_fixture("deps_status", fn ->
703      Mix.Project.push(NonOverriddenDepsApp)
704
705      assert_raise Mix.Error, fn ->
706        Mix.Tasks.Deps.Loadpaths.run([])
707      end
708
709      receive do
710        {:mix_shell, :error, ["  the dependency git_repo in mix.exs" <> _ = msg]} ->
711          assert msg =~ "In mix.exs:"
712
713          assert msg =~
714                   "{:git_repo, \"0.1.0\", [env: :prod, git: #{inspect(fixture_path("git_repo"))}]}"
715      after
716        0 -> flunk("expected overriding error message")
717      end
718    end)
719  after
720    purge([GitRepo, GitRepo.MixProject])
721  end
722
723  test "checks if dependencies are using old Elixir version" do
724    in_fixture("deps_status", fn ->
725      Mix.Project.push(SuccessfulDepsApp)
726
727      Mix.Tasks.Deps.Compile.run([])
728      Mix.Tasks.Deps.Loadpaths.run([])
729
730      File.mkdir_p!("_build/dev/lib/ok/ebin")
731      File.mkdir_p!("_build/dev/lib/ok/.mix")
732      manifest_data = :erlang.term_to_binary({:v1, "the_future", :scm})
733      File.write!("_build/dev/lib/ok/.mix/compile.elixir_scm", manifest_data)
734      Mix.Task.clear()
735
736      msg =
737        "  the dependency was built with an out-of-date Elixir version, run \"mix deps.compile\""
738
739      Mix.Tasks.Deps.run([])
740      assert_received {:mix_shell, :info, [^msg]}
741
742      # deps.loadpaths will automatically recompile it
743      Mix.Tasks.Deps.Loadpaths.run([])
744
745      Mix.Tasks.Deps.run([])
746      refute_received {:mix_shell, :info, [^msg]}
747    end)
748  end
749
750  test "checks if dependencies are using old scm version" do
751    in_fixture("deps_status", fn ->
752      Mix.Project.push(SuccessfulDepsApp)
753
754      Mix.Tasks.Deps.Compile.run([])
755      Mix.Tasks.Deps.Loadpaths.run([])
756
757      File.mkdir_p!("_build/dev/lib/ok/ebin")
758      File.mkdir_p!("_build/dev/lib/ok/.mix")
759
760      manifest_data =
761        :erlang.term_to_binary({1, {System.version(), :erlang.system_info(:otp_release)}, :scm})
762
763      File.write!("_build/dev/lib/ok/.mix/compile.elixir_scm", manifest_data)
764      Mix.Task.clear()
765
766      msg = "  the dependency was built with another SCM, run \"mix deps.compile\""
767      Mix.Tasks.Deps.run([])
768      assert_received {:mix_shell, :info, [^msg]}
769
770      # deps.loadpaths will automatically recompile it
771      Mix.Tasks.Deps.Loadpaths.run([])
772
773      Mix.Tasks.Deps.run([])
774      refute_received {:mix_shell, :info, [^msg]}
775    end)
776  end
777
778  defmodule NonCompilingDeps do
779    def project do
780      [
781        app: :raw_sample,
782        version: "0.1.0",
783        deps: [
784          {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo"), compile: false}
785        ]
786      ]
787    end
788  end
789
790  test "does not compile deps that have explicit option" do
791    in_fixture("deps_status", fn ->
792      Mix.Project.push(NonCompilingDeps)
793
794      Mix.Tasks.Deps.Compile.run([])
795      refute_received {:mix_shell, :info, ["==> git_repo"]}
796    end)
797  end
798
799  defmodule DupDeps do
800    def project do
801      [
802        app: :raw_sample,
803        version: "0.1.0",
804        deps: [
805          # Simulate dependencies gathered together from umbrella
806          {:ok, "0.1.0", path: "deps/ok"},
807          {:ok, "0.1.0", path: "deps/ok"}
808        ]
809      ]
810    end
811  end
812
813  test "warns and converges duplicated deps at the same level" do
814    in_fixture("deps_status", fn ->
815      Mix.Project.push(DupDeps)
816
817      Mix.Tasks.Deps.run([])
818
819      msg =
820        "warning: the dependency :ok is duplicated at the top level, please remove one of them"
821
822      assert_received {:mix_shell, :error, [^msg]}
823
824      msg = "* ok 0.1.0 (deps/ok) (mix)"
825      assert_received {:mix_shell, :info, [^msg]}
826      refute_received {:mix_shell, :info, [^msg]}
827    end)
828  end
829
830  ## deps.clean
831
832  defmodule CleanDepsApp do
833    def project do
834      [
835        app: :raw_sample,
836        version: "0.1.0",
837        deps: [
838          {:git_repo, ">= 0.1.0", git: MixTest.Case.fixture_path("git_repo")},
839          {:ok, ">= 2.0.0", path: "deps/ok"}
840        ]
841      ]
842    end
843  end
844
845  test "cleans dependencies" do
846    in_fixture("deps_status", fn ->
847      Mix.Project.push(CleanDepsApp)
848
849      File.mkdir_p!("_build/dev/lib/raw_sample")
850      File.mkdir_p!("_build/dev/lib/git_repo")
851      File.mkdir_p!("_build/test/lib/git_repo")
852      File.mkdir_p!("_build/dev/lib/ok")
853      File.mkdir_p!("_build/test/lib/ok")
854
855      message =
856        "\"mix deps.clean\" expects dependencies as arguments or " <>
857          "an option indicating which dependencies to clean. " <>
858          "The --all option will clean all dependencies while " <>
859          "the --unused option cleans unused dependencies"
860
861      assert_raise Mix.Error, message, fn ->
862        Mix.Tasks.Deps.Clean.run([])
863      end
864
865      Mix.Tasks.Deps.Clean.run(["--only", "dev", "--all"])
866      refute File.exists?("_build/dev/lib/git_repo")
867      refute File.exists?("_build/dev/lib/ok")
868      assert File.exists?("_build/test/lib/git_repo")
869      assert File.exists?("_build/dev/lib/raw_sample")
870
871      Mix.Tasks.Deps.Clean.run(["--all"])
872      refute File.exists?("_build/dev/lib/git_repo")
873      refute File.exists?("_build/test/lib/git_repo")
874      assert File.exists?("_build/dev/lib/raw_sample")
875    end)
876  end
877
878  test "cleans unused dependencies" do
879    in_fixture("deps_status", fn ->
880      Mix.Project.push(CleanDepsApp)
881
882      File.mkdir_p!("_build/dev/lib/raw_sample")
883      File.mkdir_p!("deps/git_repo")
884      File.mkdir_p!("_build/dev/lib/git_repo")
885      File.mkdir_p!("deps/git_repo_unused")
886      File.mkdir_p!("_build/dev/lib/git_repo_unused")
887
888      Mix.Tasks.Deps.Clean.run(["--unused"])
889      assert File.exists?("deps/git_repo")
890      assert File.exists?("_build/dev/lib/git_repo")
891      refute File.exists?("deps/git_repo_unused")
892      refute File.exists?("_build/dev/lib/git_repo_unused")
893      assert File.exists?("_build/dev/lib/raw_sample")
894    end)
895  end
896
897  test "cleans dependencies build" do
898    in_fixture("deps_status", fn ->
899      Mix.Project.push(CleanDepsApp)
900
901      File.mkdir_p!("deps/raw_sample")
902      File.mkdir_p!("_build/dev/lib/raw_sample")
903
904      Mix.Tasks.Deps.Clean.run(["raw_sample", "--build"])
905      assert File.exists?("deps/raw_sample")
906      refute File.exists?("_build/dev/lib/raw_sample")
907    end)
908  end
909
910  test "warns on invalid path on clean dependencies" do
911    in_fixture("deps_status", fn ->
912      Mix.Project.push(CleanDepsApp)
913
914      File.mkdir_p!("deps/raw_sample")
915      File.mkdir_p!("_build/dev/lib/raw_sample")
916
917      Mix.Tasks.Deps.Clean.run(["raw_sample_with_a_typo"])
918      assert File.exists?("deps/raw_sample")
919
920      msg = "warning: the dependency raw_sample_with_a_typo is not present in the build directory"
921      assert_received {:mix_shell, :error, [^msg]}
922    end)
923  end
924
925  test "does not remove dependency source when using :path" do
926    in_fixture("deps_status", fn ->
927      Mix.Project.push(CleanDepsApp)
928
929      assert File.exists?("deps/ok")
930
931      Mix.Tasks.Deps.Clean.run(["raw_sample", "--all"])
932      refute File.exists?("_build/dev/lib/ok")
933      refute File.exists?("_build/test/lib/ok")
934      assert File.exists?("deps/ok")
935    end)
936  end
937end
938