1Code.require_file("../test_helper.exs", __DIR__) 2 3defmodule Mix.DepTest do 4 use MixTest.Case 5 6 defmodule DepsApp do 7 def project do 8 [ 9 app: :deps_app, 10 deps: [ 11 {:ok, "0.1.0", path: "deps/ok"}, 12 {:invalidvsn, "0.2.0", path: "deps/invalidvsn"}, 13 {:invalidapp, "0.1.0", path: "deps/invalidapp"}, 14 {:noappfile, "0.1.0", path: "deps/noappfile"}, 15 {:uncloned, git: "https://github.com/elixir-lang/uncloned.git"}, 16 {:optional, git: "https://github.com/elixir-lang/optional.git", optional: true} 17 ] 18 ] 19 end 20 end 21 22 defmodule ProcessDepsApp do 23 def project do 24 [app: :process_deps_app, deps: Process.get(:mix_deps)] 25 end 26 end 27 28 defp with_deps(deps, fun) do 29 Process.put(:mix_deps, deps) 30 Mix.Project.push(ProcessDepsApp) 31 fun.() 32 after 33 Mix.Project.pop() 34 end 35 36 defp assert_wrong_dependency(deps) do 37 with_deps(deps, fn -> 38 assert_raise Mix.Error, ~r"Dependency specified in the wrong format", fn -> 39 Mix.Dep.load_on_environment([]) 40 end 41 end) 42 end 43 44 test "clear deps cache" do 45 Mix.Project.push(DepsApp) 46 47 Mix.Dep.cached() 48 key = {:cached_deps, DepsApp} 49 50 {env_target, deps} = Mix.State.read_cache(key) 51 assert env_target == {Mix.env(), Mix.target()} 52 assert length(deps) == 6 53 54 Mix.Dep.clear_cached() 55 refute Mix.State.read_cache(key) 56 end 57 58 test "extracts all dependencies from the given project" do 59 Mix.Project.push(DepsApp) 60 61 in_fixture("deps_status", fn -> 62 deps = Mix.Dep.load_on_environment([]) 63 assert length(deps) == 6 64 assert Enum.find(deps, &match?(%Mix.Dep{app: :ok, status: {:ok, _}}, &1)) 65 assert Enum.find(deps, &match?(%Mix.Dep{app: :invalidvsn, status: {:invalidvsn, :ok}}, &1)) 66 assert Enum.find(deps, &match?(%Mix.Dep{app: :invalidapp, status: {:invalidapp, _}}, &1)) 67 assert Enum.find(deps, &match?(%Mix.Dep{app: :noappfile, status: {:noappfile, {_, _}}}, &1)) 68 assert Enum.find(deps, &match?(%Mix.Dep{app: :uncloned, status: {:unavailable, _}}, &1)) 69 assert Enum.find(deps, &match?(%Mix.Dep{app: :optional, status: {:unavailable, _}}, &1)) 70 end) 71 end 72 73 test "extracts all dependencies paths/scms from the given project" do 74 Mix.Project.push(DepsApp) 75 76 in_fixture("deps_status", fn -> 77 apps = Mix.Project.deps_apps() 78 assert length(apps) == 6 79 assert :ok in apps 80 assert :uncloned in apps 81 82 paths = Mix.Project.deps_paths() 83 assert map_size(paths) == 6 84 assert paths[:ok] =~ "deps/ok" 85 assert paths[:uncloned] =~ "deps/uncloned" 86 87 paths = Mix.Project.deps_scms() 88 assert map_size(paths) == 6 89 assert paths[:ok] == Mix.SCM.Path 90 assert paths[:uncloned] == Mix.SCM.Git 91 end) 92 end 93 94 test "fails on invalid dependencies" do 95 assert_wrong_dependency([{:ok}]) 96 assert_wrong_dependency([{:ok, nil}]) 97 assert_wrong_dependency([{:ok, nil, []}]) 98 end 99 100 test "use requirements for dependencies" do 101 deps = [{:ok, "~> 0.1", path: "deps/ok"}] 102 103 with_deps(deps, fn -> 104 in_fixture("deps_status", fn -> 105 deps = Mix.Dep.load_on_environment([]) 106 assert Enum.find(deps, &match?(%Mix.Dep{app: :ok, status: {:ok, _}}, &1)) 107 end) 108 end) 109 end 110 111 test "raises when no SCM is specified" do 112 deps = [{:ok, "~> 0.1", not_really: :ok}] 113 114 with_deps(deps, fn -> 115 in_fixture("deps_status", fn -> 116 send(self(), {:mix_shell_input, :yes?, false}) 117 msg = "Could not find an SCM for dependency :ok from Mix.DepTest.ProcessDepsApp" 118 assert_raise Mix.Error, msg, fn -> Mix.Dep.load_on_environment([]) end 119 end) 120 end) 121 end 122 123 test "does not set the manager before the dependency was loaded" do 124 # It is important to not eagerly set the manager because the dependency 125 # needs to be loaded (i.e. available in the file system) in order to get 126 # the proper manager. 127 Mix.Project.push(DepsApp) 128 129 {_, true, _} = 130 Mix.Dep.Converger.converge(false, [], nil, fn dep, acc, lock -> 131 assert is_nil(dep.manager) 132 {dep, acc or true, lock} 133 end) 134 end 135 136 test "raises on invalid deps req" do 137 deps = [{:ok, "+- 0.1.0", path: "deps/ok"}] 138 139 with_deps(deps, fn -> 140 in_fixture("deps_status", fn -> 141 assert_raise Mix.Error, ~r"Invalid requirement", fn -> 142 Mix.Dep.load_on_environment([]) 143 end 144 end) 145 end) 146 end 147 148 test "nested deps come first" do 149 deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo"}] 150 151 with_deps(deps, fn -> 152 in_fixture("deps_status", fn -> 153 assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:git_repo, :deps_repo] 154 end) 155 end) 156 end 157 158 test "nested optional deps are never added" do 159 deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo"}] 160 161 with_deps(deps, fn -> 162 in_fixture("deps_status", fn -> 163 File.write!("custom/deps_repo/mix.exs", """ 164 defmodule DepsRepo do 165 use Mix.Project 166 167 def project do 168 [app: :deps_repo, 169 version: "0.1.0", 170 deps: [{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), optional: true}]] 171 end 172 end 173 """) 174 175 assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:deps_repo] 176 end) 177 end) 178 end 179 180 test "nested deps with convergence" do 181 deps = [ 182 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 183 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")} 184 ] 185 186 with_deps(deps, fn -> 187 in_fixture("deps_status", fn -> 188 assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:git_repo, :deps_repo] 189 end) 190 end) 191 end 192 193 test "nested deps with convergence and managers" do 194 Process.put(:custom_deps_git_repo_opts, manager: :make) 195 196 deps = [ 197 {:deps_repo, "0.1.0", path: "custom/deps_repo", manager: :rebar}, 198 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")} 199 ] 200 201 with_deps(deps, fn -> 202 in_fixture("deps_status", fn -> 203 [dep1, dep2] = Mix.Dep.load_on_environment([]) 204 assert dep1.manager == nil 205 assert dep2.manager == :rebar 206 end) 207 end) 208 end 209 210 test "nested deps with optional matching" do 211 Process.put(:custom_deps_git_repo_opts, optional: true) 212 213 # deps_repo brings git_repo but it is optional 214 deps = [ 215 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 216 {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")} 217 ] 218 219 with_deps(deps, fn -> 220 in_fixture("deps_status", fn -> 221 File.mkdir_p!("custom/deps_repo/lib") 222 223 File.write!("custom/deps_repo/lib/a.ex", """ 224 # Check that the child dependency is top_level and optional 225 [%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached() 226 true = Keyword.fetch!(opts, :optional) 227 """) 228 229 Mix.Tasks.Deps.Get.run([]) 230 Mix.Tasks.Deps.Compile.run([]) 231 end) 232 end) 233 end 234 235 test "nested deps with convergence and optional dependencies" do 236 deps = [ 237 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 238 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")} 239 ] 240 241 with_deps(deps, fn -> 242 in_fixture("deps_status", fn -> 243 File.write!("custom/deps_repo/mix.exs", """ 244 defmodule DepsRepo do 245 use Mix.Project 246 247 def project do 248 [app: :deps_repo, 249 version: "0.1.0", 250 deps: [{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), optional: true}]] 251 end 252 end 253 """) 254 255 assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == [:git_repo, :deps_repo] 256 end) 257 end) 258 end 259 260 test "nested deps with optional dependencies and cousin conflict" do 261 deps = [ 262 {:deps_repo1, "0.1.0", path: "custom/deps_repo1"}, 263 {:deps_repo2, "0.1.0", path: "custom/deps_repo2"} 264 ] 265 266 with_deps(deps, fn -> 267 in_fixture("deps_status", fn -> 268 File.mkdir_p!("custom/deps_repo1") 269 270 File.write!("custom/deps_repo1/mix.exs", """ 271 defmodule DepsRepo1 do 272 use Mix.Project 273 274 def project do 275 [app: :deps_repo1, 276 version: "0.1.0", 277 deps: [{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), optional: true}]] 278 end 279 end 280 """) 281 282 File.mkdir_p!("custom/deps_repo2") 283 284 File.write!("custom/deps_repo2/mix.exs", """ 285 defmodule DepsRepo2 do 286 use Mix.Project 287 288 def project do 289 [app: :deps_repo2, 290 version: "0.1.0", 291 deps: [{:git_repo, "0.2.0", path: "somewhere"}]] 292 end 293 end 294 """) 295 296 Mix.Tasks.Deps.run([]) 297 assert_received {:mix_shell, :info, ["* deps_repo1" <> _]} 298 assert_received {:mix_shell, :info, [_]} 299 assert_received {:mix_shell, :info, ["* deps_repo2" <> _]} 300 assert_received {:mix_shell, :info, [_]} 301 assert_received {:mix_shell, :info, ["* git_repo" <> _]} 302 assert_received {:mix_shell, :info, [msg]} 303 assert msg =~ "different specs were given for the git_repo" 304 end) 305 end) 306 end 307 308 test "deps with system_env set" do 309 file_path = tmp_path("load dependency with env vars/dep-test") 310 dep_path = tmp_path("rebar_dep") 311 312 system_env = [{"FILE_FROM_ENV", file_path}, {"CONTENTS_FROM_ENV", "contents dep test"}] 313 deps = [{:rebar_dep, path: dep_path, app: false, manager: :rebar, system_env: system_env}] 314 315 with_deps(deps, fn -> 316 in_tmp("load dependency with env vars", fn -> 317 Mix.Dep.load_on_environment([]) 318 assert {:ok, "contents dep test"} = File.read(file_path) 319 end) 320 end) 321 end 322 323 test "diverged with system_env set" do 324 Process.put(:custom_deps_git_repo_opts, system_env: [{"FOO", "BAR"}]) 325 326 deps = [ 327 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 328 {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")} 329 ] 330 331 with_deps(deps, fn -> 332 in_fixture("deps_status", fn -> 333 [git_repo, _] = Mix.Dep.load_on_environment([]) 334 %{app: :git_repo, status: {:overridden, _}} = git_repo 335 end) 336 end) 337 end 338 339 ## Remote converger 340 341 defmodule IdentityRemoteConverger do 342 @behaviour Mix.RemoteConverger 343 344 def remote?(%Mix.Dep{app: :deps_repo}), do: false 345 def remote?(%Mix.Dep{}), do: true 346 def deps(_dep, _lock), do: [] 347 def post_converge, do: :ok 348 349 def converge(deps, lock) do 350 Process.put(:remote_converger, deps) 351 lock 352 end 353 end 354 355 test "remote converger" do 356 deps = [ 357 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 358 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")} 359 ] 360 361 with_deps(deps, fn -> 362 Mix.RemoteConverger.register(IdentityRemoteConverger) 363 364 in_fixture("deps_status", fn -> 365 Mix.Tasks.Deps.Get.run([]) 366 367 message = "* Getting git_repo (#{fixture_path("git_repo")})" 368 assert_received {:mix_shell, :info, [^message]} 369 370 assert Process.get(:remote_converger) 371 end) 372 end) 373 after 374 Mix.RemoteConverger.register(nil) 375 end 376 377 defmodule DivergingRemoteConverger do 378 @behaviour Mix.RemoteConverger 379 380 def remote?(%Mix.Dep{app: :deps_repo, scm: Mix.SCM.Path}), do: true 381 def remote?(%Mix.Dep{app: :git_repo, scm: Mix.SCM.Path}), do: true 382 def remote?(%Mix.Dep{}), do: false 383 def deps(%Mix.Dep{app: :deps_repo}, _lock), do: [{:git_repo, path: "custom/git_repo"}] 384 def deps(%Mix.Dep{app: :git_repo}, _lock), do: [] 385 def post_converge, do: :ok 386 387 def converge(_deps, lock) do 388 lock 389 |> Map.put(:deps_repo, :custom) 390 |> Map.put(:git_repo, :custom) 391 end 392 end 393 394 test "converger detects diverged deps from remote converger" do 395 deps = [ 396 {:deps_on_git_repo, "0.2.0", git: MixTest.Case.fixture_path("deps_on_git_repo")}, 397 {:deps_repo, "0.1.0", path: "custom/deps_repo"} 398 ] 399 400 with_deps(deps, fn -> 401 Mix.RemoteConverger.register(DivergingRemoteConverger) 402 403 in_fixture("deps_status", fn -> 404 assert_raise Mix.Error, fn -> 405 Mix.Tasks.Deps.Get.run([]) 406 end 407 408 assert_received {:mix_shell, :error, ["Dependencies have diverged:"]} 409 end) 410 end) 411 after 412 Mix.RemoteConverger.register(nil) 413 end 414 415 test "pass dependencies to remote converger in defined order" do 416 deps = [ 417 {:ok, "0.1.0", path: "deps/ok"}, 418 {:invalidvsn, "0.2.0", path: "deps/invalidvsn"}, 419 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 420 {:invalidapp, "0.1.0", path: "deps/invalidapp"}, 421 {:noappfile, "0.1.0", path: "deps/noappfile"} 422 ] 423 424 with_deps(deps, fn -> 425 Mix.RemoteConverger.register(IdentityRemoteConverger) 426 427 in_fixture("deps_status", fn -> 428 Mix.Tasks.Deps.Get.run([]) 429 430 deps = Process.get(:remote_converger) |> Enum.map(& &1.app) 431 assert deps == [:ok, :invalidvsn, :deps_repo, :invalidapp, :noappfile, :git_repo] 432 end) 433 end) 434 after 435 Mix.RemoteConverger.register(nil) 436 end 437 438 defmodule RaiseRemoteConverger do 439 @behaviour Mix.RemoteConverger 440 441 def remote?(_app), do: false 442 def deps(_dep, _lock), do: :ok 443 def post_converge, do: :ok 444 445 def converge(_deps, lock) do 446 Process.put(:remote_converger, true) 447 lock 448 end 449 end 450 451 test "remote converger is not invoked if deps diverge" do 452 deps = [ 453 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 454 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), only: :test} 455 ] 456 457 with_deps(deps, fn -> 458 Mix.RemoteConverger.register(RaiseRemoteConverger) 459 460 in_fixture("deps_status", fn -> 461 assert_raise Mix.Error, fn -> 462 Mix.Tasks.Deps.Get.run([]) 463 end 464 465 assert_received {:mix_shell, :error, ["Dependencies have diverged:"]} 466 refute Process.get(:remote_converger) 467 end) 468 end) 469 after 470 Mix.RemoteConverger.register(nil) 471 end 472 473 test "remote converger is not invoked if deps graph has cycles" do 474 deps = [{:app1, "0.1.0", path: "app1"}, {:app2, "0.1.0", path: "app2"}] 475 476 with_deps(deps, fn -> 477 Mix.RemoteConverger.register(RaiseRemoteConverger) 478 479 in_fixture("deps_cycle", fn -> 480 assert_raise Mix.Error, ~r/cycles in the dependency graph/, fn -> 481 Mix.Tasks.Deps.Get.run([]) 482 end 483 484 refute Process.get(:remote_converger) 485 end) 486 end) 487 after 488 Mix.RemoteConverger.register(nil) 489 end 490 491 test "deps_paths" do 492 deps = [ 493 {:abc_repo, "0.1.0", path: "custom/abc_repo"}, 494 {:deps_repo, "0.1.0", path: "custom/deps_repo"} 495 ] 496 497 with_deps(deps, fn -> 498 in_fixture("deps_status", fn -> 499 assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) == 500 [:git_repo, :abc_repo, :deps_repo] 501 502 assert Map.keys(Mix.Project.deps_paths()) == [:abc_repo, :deps_repo, :git_repo] 503 504 assert Map.keys(Mix.Project.deps_paths(depth: 1)) == [:abc_repo, :deps_repo] 505 assert Map.keys(Mix.Project.deps_paths(depth: 2)) == [:abc_repo, :deps_repo, :git_repo] 506 assert Map.keys(Mix.Project.deps_paths(depth: 3)) == [:abc_repo, :deps_repo, :git_repo] 507 508 assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo])) == [:abc_repo] 509 assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo])) == [:deps_repo, :git_repo] 510 assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo])) == [:git_repo] 511 512 assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 1)) == [:abc_repo] 513 assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 1)) == [:deps_repo] 514 assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 1)) == [:git_repo] 515 516 assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 2)) == [:abc_repo] 517 assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 2)) == [:git_repo] 518 519 assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 2)) == 520 [:deps_repo, :git_repo] 521 522 assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo])) == 523 [:abc_repo, :deps_repo, :git_repo] 524 525 assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 1)) == 526 [:abc_repo, :deps_repo] 527 528 assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 2)) == 529 [:abc_repo, :deps_repo, :git_repo] 530 end) 531 end) 532 end 533 534 describe "only handling" do 535 test "extracts deps matching environment" do 536 deps = [ 537 {:foo, github: "elixir-lang/foo"}, 538 {:bar, github: "elixir-lang/bar", only: :other_env} 539 ] 540 541 with_deps(deps, fn -> 542 in_fixture("deps_status", fn -> 543 deps = Mix.Dep.load_on_environment(env: :other_env) 544 assert length(deps) == 2 545 546 deps = Mix.Dep.load_on_environment([]) 547 assert length(deps) == 2 548 549 assert [dep] = Mix.Dep.load_on_environment(env: :prod) 550 assert dep.app == :foo 551 end) 552 end) 553 end 554 555 test "fetches parent deps matching specified env" do 556 deps = [{:only, github: "elixir-lang/only", only: [:dev]}] 557 558 with_deps(deps, fn -> 559 in_fixture("deps_status", fn -> 560 Mix.Tasks.Deps.Get.run(["--only", "prod"]) 561 refute_received {:mix_shell, :info, ["* Getting" <> _]} 562 563 assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn -> 564 Mix.Tasks.Deps.Loadpaths.run([]) 565 end 566 567 Mix.State.clear_cache() 568 Mix.env(:prod) 569 Mix.Tasks.Deps.Loadpaths.run([]) 570 end) 571 end) 572 end 573 574 test "selects only prod dependencies on nested deps" do 575 Process.put(:custom_deps_git_repo_opts, only: :test) 576 deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo"}] 577 578 with_deps(deps, fn -> 579 in_fixture("deps_status", fn -> 580 loaded = Mix.Dep.load_on_environment([]) 581 assert [:deps_repo] = Enum.map(loaded, & &1.app) 582 583 loaded = Mix.Dep.load_on_environment(env: :test) 584 assert [:deps_repo] = Enum.map(loaded, & &1.app) 585 end) 586 end) 587 end 588 589 test "conflicts on nested deps" do 590 # deps_repo wants all git_repo, git_repo is restricted to only test 591 deps = [ 592 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 593 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), only: :test} 594 ] 595 596 with_deps(deps, fn -> 597 in_fixture("deps_status", fn -> 598 loaded = Mix.Dep.load_on_environment([]) 599 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 600 assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 601 602 loaded = Mix.Dep.load_on_environment(env: :dev) 603 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 604 assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 605 606 loaded = Mix.Dep.load_on_environment(env: :test) 607 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 608 assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 609 610 Mix.Tasks.Deps.run([]) 611 assert_received {:mix_shell, :info, ["* deps_repo" <> _]} 612 assert_received {:mix_shell, :info, [_]} 613 assert_received {:mix_shell, :info, ["* git_repo" <> _]} 614 assert_received {:mix_shell, :info, [msg]} 615 assert msg =~ "Remove the :only restriction from your dep" 616 end) 617 end) 618 end 619 620 test "does not conflict with optional deps on nested deps" do 621 Process.put(:custom_deps_git_repo_opts, optional: true) 622 623 # deps_repo wants all git_repo, git_repo is restricted to only test 624 deps = [ 625 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 626 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), only: :test} 627 ] 628 629 with_deps(deps, fn -> 630 in_fixture("deps_status", fn -> 631 loaded = Mix.Dep.load_on_environment([]) 632 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 633 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 634 635 loaded = Mix.Dep.load_on_environment(env: :dev) 636 assert [:deps_repo] = Enum.map(loaded, & &1.app) 637 assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status) 638 639 loaded = Mix.Dep.load_on_environment(env: :test) 640 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 641 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 642 end) 643 end) 644 end 645 646 test "does not conflict on valid subsets on nested deps" do 647 # deps_repo wants git_repo for prod, git_repo is restricted to only prod and test 648 deps = [ 649 {:deps_repo, "0.1.0", path: "custom/deps_repo", only: :prod}, 650 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), only: [:prod, :test]} 651 ] 652 653 with_deps(deps, fn -> 654 in_fixture("deps_status", fn -> 655 loaded = Mix.Dep.load_on_environment([]) 656 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 657 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 658 659 loaded = Mix.Dep.load_on_environment(env: :dev) 660 assert [] = Enum.map(loaded, & &1.app) 661 662 loaded = Mix.Dep.load_on_environment(env: :test) 663 assert [:git_repo] = Enum.map(loaded, & &1.app) 664 assert [unavailable: _] = Enum.map(loaded, & &1.status) 665 666 loaded = Mix.Dep.load_on_environment(env: :prod) 667 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 668 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 669 end) 670 end) 671 end 672 673 test "conflicts on invalid only subset on nested deps" do 674 # deps_repo wants git_repo for dev, git_repo is restricted to only test 675 deps = [ 676 {:deps_repo, "0.1.0", path: "custom/deps_repo", only: :dev}, 677 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), only: [:test]} 678 ] 679 680 with_deps(deps, fn -> 681 in_fixture("deps_status", fn -> 682 loaded = Mix.Dep.load_on_environment([]) 683 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 684 assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 685 686 loaded = Mix.Dep.load_on_environment(env: :dev) 687 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 688 assert [divergedonly: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 689 690 loaded = Mix.Dep.load_on_environment(env: :test) 691 assert [:git_repo] = Enum.map(loaded, & &1.app) 692 assert [unavailable: _] = Enum.map(loaded, & &1.status) 693 694 Mix.Tasks.Deps.run([]) 695 assert_received {:mix_shell, :info, ["* deps_repo" <> _]} 696 assert_received {:mix_shell, :info, [_]} 697 assert_received {:mix_shell, :info, ["* git_repo" <> _]} 698 assert_received {:mix_shell, :info, [msg]} 699 assert msg =~ "Ensure you specify at least the same environments in :only in your dep" 700 end) 701 end) 702 end 703 704 test "does not conflict with valid only in both parent and child on nested deps" do 705 Process.put(:custom_deps_git_repo_opts, only: :test) 706 707 # deps_repo has environment set to test so it loads the deps_git_repo set to test too 708 deps = [ 709 {:deps_repo, "0.1.0", path: "custom/deps_repo", env: :test, only: [:dev, :test]}, 710 {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo"), only: :test} 711 ] 712 713 with_deps(deps, fn -> 714 in_fixture("deps_status", fn -> 715 loaded = Mix.Dep.load_on_environment([]) 716 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 717 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 718 719 loaded = Mix.Dep.load_on_environment(env: :dev) 720 assert [:deps_repo] = Enum.map(loaded, & &1.app) 721 assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status) 722 723 loaded = Mix.Dep.load_on_environment(env: :test) 724 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 725 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 726 727 loaded = Mix.Dep.load_on_environment(env: :prod) 728 assert [] = Enum.map(loaded, & &1.app) 729 end) 730 end) 731 end 732 733 test "converges and diverges when only is not in_upper" do 734 loaded = fn deps -> 735 with_deps(deps, fn -> 736 in_fixture("deps_status", fn -> 737 File.mkdir_p!("custom/other_repo") 738 739 File.write!("custom/other_repo/mix.exs", """ 740 defmodule OtherRepo do 741 use Mix.Project 742 743 def project do 744 [app: :other_repo, 745 version: "0.1.0", 746 deps: [{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}]] 747 end 748 end 749 """) 750 751 Mix.State.clear_cache() 752 loaded = Mix.Dep.load_on_environment([]) 753 Enum.map(loaded, &{&1.app, &1.opts[:only]}) 754 end) 755 end) 756 end 757 758 deps = [ 759 {:deps_repo, "0.1.0", path: "custom/deps_repo", only: :prod}, 760 {:other_repo, "0.1.0", path: "custom/other_repo", only: :test} 761 ] 762 763 assert loaded.(deps) == [git_repo: [:test, :prod], other_repo: :test, deps_repo: :prod] 764 765 deps = [ 766 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 767 {:other_repo, "0.1.0", path: "custom/other_repo", only: :test} 768 ] 769 770 assert loaded.(deps) == [git_repo: nil, other_repo: :test, deps_repo: nil] 771 772 deps = [ 773 {:deps_repo, "0.1.0", path: "custom/deps_repo", only: :prod}, 774 {:other_repo, "0.1.0", path: "custom/other_repo"} 775 ] 776 777 assert loaded.(deps) == [git_repo: nil, other_repo: nil, deps_repo: :prod] 778 779 deps = [ 780 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 781 {:other_repo, "0.1.0", path: "custom/other_repo"} 782 ] 783 784 assert loaded.(deps) == [git_repo: nil, other_repo: nil, deps_repo: nil] 785 786 Process.put(:custom_deps_git_repo_opts, optional: true) 787 788 deps = [ 789 {:deps_repo, "0.1.0", path: "custom/deps_repo", only: :prod}, 790 {:other_repo, "0.1.0", path: "custom/other_repo", only: :test} 791 ] 792 793 assert loaded.(deps) == [git_repo: :test, other_repo: :test, deps_repo: :prod] 794 end 795 796 test "converges and diverges when only is not specified" do 797 Process.put(:custom_deps_git_repo_opts, only: :test) 798 799 deps = [ 800 {:abc_repo, "0.1.0", path: "custom/abc_repo", from_umbrella: true}, 801 {:deps_repo, "0.1.0", path: "custom/deps_repo", from_umbrella: true} 802 ] 803 804 with_deps(deps, fn -> 805 in_fixture("deps_status", fn -> 806 File.mkdir_p!("custom/abc_repo") 807 808 File.write!("custom/abc_repo/mix.exs", """ 809 defmodule OtherRepo do 810 use Mix.Project 811 812 def project do 813 [app: :abc_repo, 814 version: "0.1.0", 815 deps: [{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}]] 816 end 817 end 818 """) 819 820 Mix.Tasks.Deps.Get.run([]) 821 Mix.Tasks.Deps.Compile.run([]) 822 refute_receive {:mix_shell, :error, ["Could not compile :git_repo" <> _]} 823 end) 824 end) 825 end 826 end 827 828 describe "targets handling" do 829 test "extracts deps matching target" do 830 deps = [ 831 {:foo, github: "elixir-lang/foo"}, 832 {:bar, github: "elixir-lang/bar", targets: :rpi3} 833 ] 834 835 with_deps(deps, fn -> 836 in_fixture("deps_status", fn -> 837 deps = Mix.Dep.load_on_environment(target: :rpi3) 838 assert length(deps) == 2 839 840 deps = Mix.Dep.load_on_environment([]) 841 assert length(deps) == 2 842 843 assert [dep] = Mix.Dep.load_on_environment(target: :host) 844 assert dep.app == :foo 845 end) 846 end) 847 end 848 849 test "fetches parent deps matching specified target" do 850 deps = [{:target, github: "elixir-lang/target", targets: [:host]}] 851 852 with_deps(deps, fn -> 853 in_fixture("deps_status", fn -> 854 Mix.Tasks.Deps.Get.run(["--target", "rpi3"]) 855 refute_received {:mix_shell, :info, ["* Getting" <> _]} 856 857 assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn -> 858 Mix.Tasks.Deps.Loadpaths.run([]) 859 end 860 861 Mix.State.clear_cache() 862 Mix.target(:rpi3) 863 Mix.Tasks.Deps.Loadpaths.run([]) 864 end) 865 end) 866 end 867 868 test "conflicts on nested deps" do 869 # deps_repo wants all git_repo, git_repo is restricted to targets rpi3 870 deps = [ 871 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 872 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), targets: :rpi3} 873 ] 874 875 with_deps(deps, fn -> 876 in_fixture("deps_status", fn -> 877 loaded = Mix.Dep.load_on_environment([]) 878 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 879 assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 880 881 loaded = Mix.Dep.load_on_environment(target: :host) 882 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 883 assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 884 885 loaded = Mix.Dep.load_on_environment(target: :rpi3) 886 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 887 assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 888 889 Mix.Tasks.Deps.run([]) 890 assert_received {:mix_shell, :info, ["* deps_repo" <> _]} 891 assert_received {:mix_shell, :info, [_]} 892 assert_received {:mix_shell, :info, ["* git_repo" <> _]} 893 assert_received {:mix_shell, :info, [msg]} 894 assert msg =~ "Remove the :targets restriction from your dep" 895 end) 896 end) 897 end 898 899 test "does not conflict with optional deps on nested deps" do 900 Process.put(:custom_deps_git_repo_opts, optional: true) 901 902 # deps_repo wants all git_repo, git_repo is restricted to targets rpi3 903 deps = [ 904 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 905 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), targets: :rpi3} 906 ] 907 908 with_deps(deps, fn -> 909 in_fixture("deps_status", fn -> 910 loaded = Mix.Dep.load_on_environment([]) 911 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 912 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 913 914 loaded = Mix.Dep.load_on_environment(target: :host) 915 assert [:deps_repo] = Enum.map(loaded, & &1.app) 916 assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status) 917 918 loaded = Mix.Dep.load_on_environment(target: :rpi3) 919 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 920 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 921 end) 922 end) 923 end 924 925 test "does not conflict on valid subsets on nested deps" do 926 # deps_repo wants git_repo for prod, git_repo is restricted to targets bbb and rpi3 927 deps = [ 928 {:deps_repo, "0.1.0", path: "custom/deps_repo", targets: :rpi3}, 929 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), targets: [:bbb, :rpi3]} 930 ] 931 932 with_deps(deps, fn -> 933 in_fixture("deps_status", fn -> 934 loaded = Mix.Dep.load_on_environment([]) 935 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 936 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 937 938 loaded = Mix.Dep.load_on_environment(target: :host) 939 assert [] = Enum.map(loaded, & &1.app) 940 941 loaded = Mix.Dep.load_on_environment(target: :bbb) 942 assert [:git_repo] = Enum.map(loaded, & &1.app) 943 assert [unavailable: _] = Enum.map(loaded, & &1.status) 944 945 loaded = Mix.Dep.load_on_environment(target: :rpi3) 946 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 947 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 948 end) 949 end) 950 end 951 952 test "conflicts on invalid only subset on nested deps" do 953 # deps_repo wants git_repo for rpi3, git_repo is restricted to only test 954 deps = [ 955 {:deps_repo, "0.1.0", path: "custom/deps_repo", targets: :host}, 956 {:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo"), targets: [:rpi3]} 957 ] 958 959 with_deps(deps, fn -> 960 in_fixture("deps_status", fn -> 961 loaded = Mix.Dep.load_on_environment([]) 962 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 963 assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 964 965 loaded = Mix.Dep.load_on_environment(target: :host) 966 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 967 assert [divergedtargets: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 968 969 loaded = Mix.Dep.load_on_environment(target: :rpi3) 970 assert [:git_repo] = Enum.map(loaded, & &1.app) 971 assert [unavailable: _] = Enum.map(loaded, & &1.status) 972 973 Mix.Tasks.Deps.run([]) 974 assert_received {:mix_shell, :info, ["* deps_repo" <> _]} 975 assert_received {:mix_shell, :info, [_]} 976 assert_received {:mix_shell, :info, ["* git_repo" <> _]} 977 assert_received {:mix_shell, :info, [msg]} 978 assert msg =~ "Ensure you specify at least the same targets in :targets in your dep" 979 end) 980 end) 981 end 982 983 test "does not conflict with valid only in both parent and child on nested deps" do 984 Process.put(:custom_deps_git_repo_opts, targets: :bbb) 985 986 # deps_repo has environment set to bbb so it loads the deps_git_repo set to bbb too 987 deps = [ 988 {:deps_repo, "0.1.0", path: "custom/deps_repo", env: :test, targets: [:host, :bbb]}, 989 {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo"), targets: :bbb} 990 ] 991 992 with_deps(deps, fn -> 993 in_fixture("deps_status", fn -> 994 loaded = Mix.Dep.load_on_environment([]) 995 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 996 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 997 998 loaded = Mix.Dep.load_on_environment(target: :host) 999 assert [:deps_repo] = Enum.map(loaded, & &1.app) 1000 assert [noappfile: {_, _}] = Enum.map(loaded, & &1.status) 1001 1002 loaded = Mix.Dep.load_on_environment(target: :bbb) 1003 assert [:git_repo, :deps_repo] = Enum.map(loaded, & &1.app) 1004 assert [unavailable: _, noappfile: {_, _}] = Enum.map(loaded, & &1.status) 1005 1006 loaded = Mix.Dep.load_on_environment(target: :rpi3) 1007 assert [] = Enum.map(loaded, & &1.app) 1008 end) 1009 end) 1010 end 1011 1012 test "converges and diverges when only is not in_upper" do 1013 loaded = fn deps -> 1014 with_deps(deps, fn -> 1015 in_fixture("deps_status", fn -> 1016 File.mkdir_p!("custom/other_repo") 1017 1018 File.write!("custom/other_repo/mix.exs", """ 1019 defmodule OtherRepo do 1020 use Mix.Project 1021 1022 def project do 1023 [app: :other_repo, 1024 version: "0.1.0", 1025 deps: [{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}]] 1026 end 1027 end 1028 """) 1029 1030 Mix.State.clear_cache() 1031 loaded = Mix.Dep.load_on_environment([]) 1032 Enum.map(loaded, &{&1.app, &1.opts[:targets]}) 1033 end) 1034 end) 1035 end 1036 1037 deps = [ 1038 {:deps_repo, "0.1.0", path: "custom/deps_repo", targets: :rpi3}, 1039 {:other_repo, "0.1.0", path: "custom/other_repo", targets: :bbb} 1040 ] 1041 1042 assert loaded.(deps) == [git_repo: [:bbb, :rpi3], other_repo: :bbb, deps_repo: :rpi3] 1043 1044 deps = [ 1045 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 1046 {:other_repo, "0.1.0", path: "custom/other_repo", targets: :bbb} 1047 ] 1048 1049 assert loaded.(deps) == [git_repo: nil, other_repo: :bbb, deps_repo: nil] 1050 1051 deps = [ 1052 {:deps_repo, "0.1.0", path: "custom/deps_repo", targets: :rpi3}, 1053 {:other_repo, "0.1.0", path: "custom/other_repo"} 1054 ] 1055 1056 assert loaded.(deps) == [git_repo: nil, other_repo: nil, deps_repo: :rpi3] 1057 1058 deps = [ 1059 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 1060 {:other_repo, "0.1.0", path: "custom/other_repo"} 1061 ] 1062 1063 assert loaded.(deps) == [git_repo: nil, other_repo: nil, deps_repo: nil] 1064 1065 Process.put(:custom_deps_git_repo_opts, optional: true) 1066 1067 deps = [ 1068 {:deps_repo, "0.1.0", path: "custom/deps_repo", targets: :rpi3}, 1069 {:other_repo, "0.1.0", path: "custom/other_repo", targets: :bbb} 1070 ] 1071 1072 assert loaded.(deps) == [git_repo: :bbb, other_repo: :bbb, deps_repo: :rpi3] 1073 end 1074 end 1075 1076 describe "overrides" do 1077 test "are not required when there are no conflicts" do 1078 deps = [ 1079 {:deps_repo, "0.1.0", path: "custom/deps_repo"} 1080 ] 1081 1082 with_deps(deps, fn -> 1083 in_fixture("deps_status", fn -> 1084 File.mkdir_p!("custom/deps_repo/lib") 1085 1086 File.write!("custom/deps_repo/lib/a.ex", """ 1087 # Check that the child dependency is top_level 1088 [%Mix.Dep{app: :git_repo, top_level: true}] = Mix.Dep.cached() 1089 """) 1090 1091 Mix.Tasks.Deps.Get.run([]) 1092 Mix.Tasks.Deps.Compile.run([]) 1093 end) 1094 end) 1095 end 1096 1097 test "are required when there are conflicts" do 1098 # deps_repo brings git_repo but it is overridden 1099 deps = [ 1100 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 1101 {:git_repo, ">= 0.0.0", git: MixTest.Case.fixture_path("git_repo"), override: true} 1102 ] 1103 1104 with_deps(deps, fn -> 1105 in_fixture("deps_status", fn -> 1106 File.mkdir_p!("custom/deps_repo/lib") 1107 1108 File.write!("custom/deps_repo/lib/a.ex", """ 1109 # Check that the overridden requirement shows up in the child dependency 1110 [%Mix.Dep{app: :git_repo, requirement: ">= 0.0.0"}] = Mix.Dep.cached() 1111 """) 1112 1113 Mix.Tasks.Deps.Get.run([]) 1114 Mix.Tasks.Deps.Compile.run([]) 1115 end) 1116 end) 1117 end 1118 end 1119 1120 describe "app generation" do 1121 test "considers runtime from current app on nested deps" do 1122 Process.put(:custom_deps_git_repo_opts, runtime: false) 1123 1124 deps = [ 1125 {:deps_repo, "0.1.0", path: "custom/deps_repo"}, 1126 {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")} 1127 ] 1128 1129 with_deps(deps, fn -> 1130 in_fixture("deps_status", fn -> 1131 Mix.Tasks.Deps.Compile.run([]) 1132 1133 {:ok, [{:application, :deps_repo, opts}]} = 1134 :file.consult("_build/dev/lib/deps_repo/ebin/deps_repo.app") 1135 1136 assert :git_repo not in Keyword.get(opts, :applications) 1137 end) 1138 end) 1139 end 1140 1141 test "considers only from current app on nested deps" do 1142 Process.put(:custom_deps_git_repo_opts, only: :other) 1143 1144 deps = [ 1145 {:deps_repo, "0.1.0", path: "custom/deps_repo", from_umbrella: true}, 1146 {:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo"), from_umbrella: true} 1147 ] 1148 1149 with_deps(deps, fn -> 1150 in_fixture("deps_status", fn -> 1151 Mix.Tasks.Deps.Compile.run([]) 1152 1153 {:ok, [{:application, :deps_repo, opts}]} = 1154 :file.consult("_build/dev/lib/deps_repo/ebin/deps_repo.app") 1155 1156 assert :git_repo not in Keyword.get(opts, :applications) 1157 end) 1158 end) 1159 end 1160 end 1161end 1162