diff --git a/lib/ex_unit/lib/ex_unit/assertions.ex b/lib/ex_unit/lib/ex_unit/assertions.ex index 26abca98ad..2eaf44abd0 100644 --- a/lib/ex_unit/lib/ex_unit/assertions.ex +++ b/lib/ex_unit/lib/ex_unit/assertions.ex @@ -266,7 +266,7 @@ defmodule ExUnit.Assertions do if name == ExUnit.AssertionError do raise(error) else - flunk "Expected exception #{inspect exception}, got #{inspect name}" + flunk "Expected exception #{inspect exception}, got #{inspect name} (#{error.message})" end end end diff --git a/lib/ex_unit/test/ex_unit/assertions_test.exs b/lib/ex_unit/test/ex_unit/assertions_test.exs index 3b4a7e7779..3012b39938 100644 --- a/lib/ex_unit/test/ex_unit/assertions_test.exs +++ b/lib/ex_unit/test/ex_unit/assertions_test.exs @@ -188,7 +188,7 @@ defmodule ExUnit.AssertionsTest do end rescue error in [ExUnit.AssertionError] -> - "Expected exception ArgumentError, got UndefinedFunctionError" = error.message + "Expected exception ArgumentError, got UndefinedFunctionError (undefined function: Certainly.Undefined.function/3)" = error.message end end @@ -199,7 +199,7 @@ defmodule ExUnit.AssertionsTest do end rescue error in [ExUnit.AssertionError] -> - "Expected exception SyntaxError, got FunctionClauseError" = error.message + "Expected exception SyntaxError, got FunctionClauseError (no function clause matching: :lists.flatten(1))" = error.message end end diff --git a/lib/mix/lib/mix/cli.ex b/lib/mix/lib/mix/cli.ex index 042ca11552..be66f7f673 100644 --- a/lib/mix/lib/mix/cli.ex +++ b/lib/mix/lib/mix/cli.ex @@ -2,7 +2,7 @@ defmodule Mix.CLI do @moduledoc false defmacrop exceptions do - [Mix.Error, Mix.NoTaskError, Mix.InvalidTaskError, Mix.NoProjectError] + [Mix.Error, Mix.NoTaskError, Mix.InvalidTaskError, Mix.NoProjectError, Mix.NotMetDepsError] end @doc """ diff --git a/lib/mix/lib/mix/exceptions.ex b/lib/mix/lib/mix/exceptions.ex index 45f9424e68..7c52d98812 100644 --- a/lib/mix/lib/mix/exceptions.ex +++ b/lib/mix/lib/mix/exceptions.ex @@ -12,4 +12,6 @@ end defexception Mix.NoProjectError, message: "Could not find a Mix.Project" -defexception Mix.Error, message: nil \ No newline at end of file +defexception Mix.Error, message: nil + +defexception Mix.NotMetDepsError, message: "Some dependencies were not met" \ No newline at end of file diff --git a/lib/mix/lib/mix/scm/git.ex b/lib/mix/lib/mix/scm/git.ex index 36e61f02c8..4c5ff57de7 100644 --- a/lib/mix/lib/mix/scm/git.ex +++ b/lib/mix/lib/mix/scm/git.ex @@ -1,5 +1,5 @@ defmodule Mix.SCM.Git do - def cloned?(path) do + def available?(path) do File.dir?(File.join(path, ".git")) end end \ No newline at end of file diff --git a/lib/mix/lib/mix/tasks/deps.check.ex b/lib/mix/lib/mix/tasks/deps.check.ex new file mode 100644 index 0000000000..acc1f64635 --- /dev/null +++ b/lib/mix/lib/mix/tasks/deps.check.ex @@ -0,0 +1,32 @@ +defmodule Mix.Tasks.Deps.Check do + use Mix.Task + + import Mix.Tasks.Deps, [all: 0, format_dep: 1, format_status: 1] + + @moduledoc """ + Checks if all dependencies are valid and if not, abort. + Prints the invalid dependencies status before aborting. + + This task is not exhibited in `mix help` but it is part + of mix public API and can be depended on. + """ + def run(_) do + case Enum.partition all, match?({ _, _, _, { :ok, _ }, _ }, &1) do + { _, [] } -> :ok + { _, not_ok } -> + if Enum.all? not_ok, match?({ _, _, _, { :unavailable, _ }, _ }, &1) do + raise Mix.NotMetDepsError, message: + "Dependencies are not available, run `mix deps.get` before proceeding" + else + shell = Mix.shell + + Enum.each not_ok, fn(dep) -> + shell.error "* #{format_dep(dep)}" + shell.error " #{format_status elem(dep, 4)}" + end + + raise Mix.NotMetDepsError + end + end + end +end diff --git a/lib/mix/lib/mix/tasks/deps.ex b/lib/mix/lib/mix/tasks/deps.ex index 411f8fb0cd..7d9ef4ecf9 100644 --- a/lib/mix/lib/mix/tasks/deps.ex +++ b/lib/mix/lib/mix/tasks/deps.ex @@ -3,6 +3,10 @@ defmodule Mix.Tasks.Deps do @shortdoc "List dependencies and their status" + @moduledoc """ + List all dependencies and their status. + """ + @doc """ Returns all dependencies in the following format: @@ -24,18 +28,25 @@ defmodule Mix.Tasks.Deps do Enum.map deps, with_scm_and_status(&1) end + @doc """ + Get all dependencies that match the specific `status`. + """ + def all(status) do + Enum.filter all, match?({ _, _, _, { ^status, _ }, _ }, &1) + end + @doc """ Formats the status of a dependency. It can be either: * `{ :ok, vsn }` - Everything is :ok, got version `vsn`; - * `{ :uncloned, path }` - The dependency is not checked out; + * `{ :unavailable, path }` - The dependency is not available; * `{ :noappfile, path }` - The .app file at path could not be found; * `{ :invalidapp, path }` - The .app file at path is not properly formatted; * `{ :invalidvsn, actual }` - The dependency does not match the specified requirement, got `actual`; """ def format_status({ :ok, _vsn }), do: "ok" - def format_status({ :uncloned, path }), do: "the dependency is not checked out at: #{path}" + def format_status({ :unavailable, _ }), do: "the dependency is not available, run `mix deps.get`" def format_status({ :noappfile, path }), do: "could not find app file at #{path}" def format_status({ :invalidapp, path }), do: "the app file at #{path} is invalid" def format_status({ :invalidvsn, vsn }), do: "the dependency does not match the specified version, got #{vsn}" @@ -57,13 +68,10 @@ defmodule Mix.Tasks.Deps do "#{app} #{version}[#{opts}]" end - @doc """ - - """ def run(_) do shell = Mix.shell - Enum.map all, fn(dep) -> + Enum.each all, fn(dep) -> shell.info "* #{format_dep(dep)}" shell.info " #{format_status elem(dep, 4)}" end @@ -97,11 +105,11 @@ defmodule Mix.Tasks.Deps do defp status(scm, app, req, _) do deps_path = deps_path(app) - if scm.cloned? deps_path do + if scm.available? deps_path do app_path = File.join deps_path, "ebin/#{app}.app" validate_app_file(app_path, app, req) else - { :uncloned, deps_path } + { :unavailable, deps_path } end end diff --git a/lib/mix/lib/mix/tasks/deps.get.ex b/lib/mix/lib/mix/tasks/deps.get.ex index 45d25005e6..4d03cf9326 100644 --- a/lib/mix/lib/mix/tasks/deps.get.ex +++ b/lib/mix/lib/mix/tasks/deps.get.ex @@ -1,5 +1,5 @@ defmodule Mix.Tasks.Deps.Get do use Mix.Task - @shortdoc "Get uncloned dependencies" + @shortdoc "Get all unavailable dependencies" end \ No newline at end of file diff --git a/lib/mix/test/mix/tasks/deps_test.exs b/lib/mix/test/mix/tasks/deps_test.exs index a1beb1d2c8..efd6fc2bfb 100644 --- a/lib/mix/test/mix/tasks/deps_test.exs +++ b/lib/mix/test/mix/tasks/deps_test.exs @@ -17,7 +17,28 @@ defmodule Mix.Tasks.DepsTest do end end - test "mix deps prints list of dependencies and their status" do + defmodule SuccessfulDepsApp do + def project do + [ + deps: [ + { "ok", "0.1.0", git: "https://github.com/elixir-lang/ok.git" } + ] + ] + end + end + + defmodule UnclonedDepsApp do + def project do + [ + deps: [ + { "uncloned", "0.1.0", git: "https://github.com/elixir-lang/uncloned.git" } + ] + ] + end + end + + + test "prints list of dependencies and their status" do Mix.Project.push DepsApp in_fixture "deps_status", fn -> @@ -32,7 +53,56 @@ defmodule Mix.Tasks.DepsTest do assert_received { :mix_shell, :info, ["* noappfile [git: \"https://github.com/elixir-lang/noappfile.git\"]"] } assert_received { :mix_shell, :info, [" could not find app file at deps/noappfile/ebin/noappfile.app"] } assert_received { :mix_shell, :info, ["* uncloned [git: \"https://github.com/elixir-lang/uncloned.git\"]"] } - assert_received { :mix_shell, :info, [" the dependency is not checked out at: deps/uncloned"] } + assert_received { :mix_shell, :info, [" the dependency is not available, run `mix deps.get`"] } + end + after + Mix.Project.pop + end + + test "check list of dependencies and their status on success" do + Mix.Project.push SuccessfulDepsApp + + in_fixture "deps_status", fn -> + Mix.Tasks.Deps.Check.run [] + end + after + Mix.Project.pop + end + + test "check list of dependencies and their status on first run" do + Mix.Project.push UnclonedDepsApp + + in_fixture "deps_status", fn -> + assert_raise Mix.NotMetDepsError, "Dependencies are not available, run `mix deps.get` before proceeding", fn -> + Mix.Tasks.Deps.Check.run [] + end + + refute_received { :mix_shell, :error, ["* uncloned [git: \"https://github.com/elixir-lang/uncloned.git\"]"] } + refute_received { :mix_shell, :error, [" the dependency is not available, run `mix deps.get`"] } + end + after + Mix.Project.pop + end + + test "check list of dependencies and their status on failure" do + Mix.Project.push DepsApp + + in_fixture "deps_status", fn -> + assert_raise Mix.NotMetDepsError, fn -> + Mix.Tasks.Deps.Check.run [] + end + + refute_received { :mix_shell, :error, ["* ok (0.1.0) [git: \"https://github.com/elixir-lang/ok.git\"]"] } + refute_received { :mix_shell, :error, [" ok"] } + + assert_received { :mix_shell, :error, ["* invalidvsn [git: \"https://github.com/elixir-lang/invalidvsn.git\"]"] } + assert_received { :mix_shell, :error, [" the dependency does not match the specified version, got 0.1.0"] } + assert_received { :mix_shell, :error, ["* invalidapp [git: \"https://github.com/elixir-lang/invalidapp.git\"]"] } + assert_received { :mix_shell, :error, [" the app file at deps/invalidapp/ebin/invalidapp.app is invalid"] } + assert_received { :mix_shell, :error, ["* noappfile [git: \"https://github.com/elixir-lang/noappfile.git\"]"] } + assert_received { :mix_shell, :error, [" could not find app file at deps/noappfile/ebin/noappfile.app"] } + assert_received { :mix_shell, :error, ["* uncloned [git: \"https://github.com/elixir-lang/uncloned.git\"]"] } + assert_received { :mix_shell, :error, [" the dependency is not available, run `mix deps.get`"] } end after Mix.Project.pop