diff --git a/examples/simple_umbrella_app/apps/app_a/mix.exs b/examples/simple_umbrella_app/apps/app_a/mix.exs index 0becebbc..4acc0a6b 100644 --- a/examples/simple_umbrella_app/apps/app_a/mix.exs +++ b/examples/simple_umbrella_app/apps/app_a/mix.exs @@ -12,13 +12,6 @@ defmodule AppA.MixProject do start_permanent: Mix.env() == :prod, deps: deps(), - gradient: [ - - enabled: true, - - - ] - ] end diff --git a/examples/simple_umbrella_app/apps/app_b/mix.exs b/examples/simple_umbrella_app/apps/app_b/mix.exs index 2930a514..346f09f3 100644 --- a/examples/simple_umbrella_app/apps/app_b/mix.exs +++ b/examples/simple_umbrella_app/apps/app_b/mix.exs @@ -12,11 +12,6 @@ defmodule AppB.MixProject do start_permanent: Mix.env() == :prod, deps: deps(), - gradient: [ - - - ] - ] end diff --git a/examples/simple_umbrella_app/mix.exs b/examples/simple_umbrella_app/mix.exs index e87614d0..ea5b6053 100644 --- a/examples/simple_umbrella_app/mix.exs +++ b/examples/simple_umbrella_app/mix.exs @@ -9,7 +9,7 @@ defmodule SimpleUmbrellaApp.MixProject do gradient: [ - enabled: false, + enabled: true, ] diff --git a/lib/gradient.ex b/lib/gradient.ex index c7ca793f..89ba229b 100644 --- a/lib/gradient.ex +++ b/lib/gradient.ex @@ -154,8 +154,7 @@ defmodule Gradient do defp maybe_specify_forms(forms, opts) do unless opts[:no_specify] do - forms - |> AstSpecifier.specify() + AstSpecifier.specify(forms) else forms end @@ -180,7 +179,7 @@ defmodule Gradient do {:attribute, anno, :file, {path, line}} = hd(forms) [ - {:attribute, anno, :file, {String.to_charlist(app_path) ++ '/' ++ path, line}} + {:attribute, anno, :file, {maybe_prepend_app_path(app_path, path), line}} | tl(forms) ] end @@ -190,6 +189,18 @@ defmodule Gradient do end end + defp maybe_prepend_app_path(app_path, path) do + unless is_absolute_path(path) do + String.to_charlist(app_path) ++ '/' ++ path + else + path + end + end + + # Check if the specified path (either binary or charlist) is an absolute path + defp is_absolute_path(path) when is_list(path), do: path |> to_string() |> is_absolute_path() + defp is_absolute_path(path) when is_binary(path), do: path == Path.absname(path) + defp get_first_forms(forms) do forms |> List.first() diff --git a/lib/mix/tasks/clean_examples.ex b/lib/mix/tasks/clean_examples.ex index 0fba8966..a2c4aa3c 100644 --- a/lib/mix/tasks/clean_examples.ex +++ b/lib/mix/tasks/clean_examples.ex @@ -1,14 +1,19 @@ defmodule Mix.Tasks.CleanExamples do @moduledoc """ Mix task for removing compiled files under test/examples/_build and - test/examples/erlang/_build. Usefult for getting a clean build for tests. + test/examples/erlang/_build. Useful for getting a clean build for tests. """ use Mix.Task - @impl true + @example_dirs [ + "test/examples/_build", + "test/examples/erlang/_build" + ] + + @impl Mix.Task def run(_args) do - File.rm_rf!("test/examples/_build") - File.rm_rf!("test/examples/erlang/_build") + IO.puts("Cleaning compiled examples in: " <> inspect(@example_dirs)) + Enum.each(@example_dirs, &File.rm_rf!/1) end end diff --git a/lib/mix/tasks/gradient.ex b/lib/mix/tasks/gradient.ex index 089f4e0f..ea528b2d 100644 --- a/lib/mix/tasks/gradient.ex +++ b/lib/mix/tasks/gradient.ex @@ -77,10 +77,10 @@ defmodule Mix.Tasks.Gradient do options = Enum.reduce(options, [], &prepare_option/2) - # Load dependencies - maybe_load_deps(options) # Compile the project before the analysis maybe_compile_project(options) + # Load dependencies + maybe_load_deps(options) # Start Gradualizer application Application.ensure_all_started(:gradualizer) # Get paths to files diff --git a/mix.exs b/mix.exs index 51efa3e9..96c963da 100644 --- a/mix.exs +++ b/mix.exs @@ -38,7 +38,7 @@ defmodule Gradient.MixProject do def aliases do [ - # Befor testing, remove compiled test/examples files for a clean build + # Before testing, remove compiled test/examples files for a clean build test: ["clean_examples", "test"] ] end diff --git a/test/mix/tasks/gradient_test.exs b/test/mix/tasks/gradient_test.exs index b4d01d20..1289b6b9 100644 --- a/test/mix/tasks/gradient_test.exs +++ b/test/mix/tasks/gradient_test.exs @@ -41,12 +41,209 @@ defmodule Mix.Tasks.GradientTest do :ok end + test "--no-compile option" do + info = "Compiling project..." + + output = run_task([@s_wrong_ret_beam]) + assert String.contains?(output, info) + + output = run_task(["--no-compile", "--", @s_wrong_ret_beam]) + assert not String.contains?(output, info) + + assert_receive {:system_halt, 1} + end + + test "path to the beam file" do + output = run_task(test_opts([@s_wrong_ret_beam])) + assert 3 == String.split(output, @s_wrong_ret_ex) |> length() + + assert_receive {:system_halt, 1} + end + + test "path to the ex file" do + output = run_task(test_opts([@s_wrong_ret_ex])) + assert 3 == String.split(output, @s_wrong_ret_ex) |> length() + + assert_receive {:system_halt, 1} + end + + test "no_fancy option" do + output = run_task(test_opts([@s_wrong_ret_beam])) + assert String.contains?(output, "The integer on line") + assert String.contains?(output, "The tuple on line") + + assert_receive {:system_halt, 1} + + output = run_task(test_opts(["--no-fancy", "--", @s_wrong_ret_beam])) + assert String.contains?(output, "The integer \e[33m1\e[0m on line") + assert String.contains?(output, "The tuple \e[33m{:ok, []}\e[0m on line") + + assert_receive {:system_halt, 1} + end + + describe "colors" do + test "no_colors option" do + output = run_task(test_opts([@s_wrong_ret_beam])) + assert String.contains?(output, IO.ANSI.cyan()) + assert String.contains?(output, IO.ANSI.red()) + assert_receive {:system_halt, 1} + + output = run_task(test_opts(["--no-colors", "--", @s_wrong_ret_beam])) + assert not String.contains?(output, IO.ANSI.cyan()) + assert String.contains?(output, IO.ANSI.red()) + assert_receive {:system_halt, 1} + end + + test "--expr-color and --type-color option" do + output = + run_task( + test_opts([ + "--no-fancy", + "--expr-color", + "green", + "--type-color", + "magenta", + "--", + @s_wrong_ret_beam + ]) + ) + + assert String.contains?(output, IO.ANSI.green()) + assert String.contains?(output, IO.ANSI.magenta()) + assert_receive {:system_halt, 1} + end + + test "--underscore_color option" do + output = + run_task( + test_opts([ + "--underscore-color", + "green", + "--", + @s_wrong_ret_beam + ]) + ) + + assert String.contains?(output, IO.ANSI.green()) + assert String.contains?(output, IO.ANSI.red()) + assert_receive {:system_halt, 1} + end + end + + test "--no-gradualizer-check option" do + output = run_task(test_opts(["--no-gradualizer-check", "--", @s_wrong_ret_beam])) + + assert String.contains?(output, @no_errors_msg) + refute_receive {:system_halt, 1} + end + + test "--no-ex-check option" do + beam = Path.join(@build_path, "Elixir.SpecWrongName.beam") + ex_spec_error_msg = "The spec convert/1" + + output = run_task(test_opts([beam])) + assert String.contains?(output, ex_spec_error_msg) + + output = run_task(test_opts(["--no-ex-check", "--", beam])) + assert not String.contains?(output, ex_spec_error_msg) + assert_receive {:system_halt, 1} + end + + @tag :ex_lt_1_13 + test "--no-specify option" do + output = run_task(test_opts([@s_wrong_ret_beam])) + assert String.contains?(output, "on line 3") + assert String.contains?(output, "on line 6") + assert_receive {:system_halt, 1} + + output = run_task(test_opts(["--no-specify", "--", @s_wrong_ret_beam])) + assert String.contains?(output, "on line 0") + assert not String.contains?(output, "on line 3") + assert not String.contains?(output, "on line 6") + assert_receive {:system_halt, 1} + end + + test "--stop-on-first-error option" do + output = run_task(test_opts(["--stop-on-first-error", "--", @s_wrong_ret_beam])) + + assert 2 == String.split(output, @s_wrong_ret_ex) |> length() + assert_receive {:system_halt, 1} + end + + test "--fmt-location option" do + output = run_task(test_opts(["--fmt-location", "none", "--", @s_wrong_ret_beam])) + + assert String.contains?(output, "s_wrong_ret.ex: The integer is expected to have type") + assert_receive {:system_halt, 1} + + output = run_task(test_opts(["--fmt-location", "brief", "--", @s_wrong_ret_beam])) + + assert String.contains?(output, "s_wrong_ret.ex:3: The integer is expected to have type") + assert_receive {:system_halt, 1} + + output = run_task(test_opts(["--fmt-location", "verbose", "--", @s_wrong_ret_beam])) + + assert String.contains?( + output, + "s_wrong_ret.ex: The integer on line 3 is expected to have type" + ) + + assert_receive {:system_halt, 1} + end + + test "--no-deps option" do + info = "Loading deps..." + + output = run_task(["--no-compile", "--", @s_wrong_ret_beam]) + assert String.contains?(output, info) + assert_receive {:system_halt, 1} + + output = run_task(["--no-compile", "--no-deps", "--", @s_wrong_ret_beam]) + assert not String.contains?(output, info) + assert_receive {:system_halt, 1} + end + + test "--infer option" do + beam = Path.join(@build_path, "Elixir.ListInfer.beam") + output = run_task(test_opts([beam])) + assert String.contains?(output, @no_errors_msg) + refute_receive {:system_halt, 1} + + output = run_task(test_opts(["--infer", "--", beam])) + assert not String.contains?(output, @no_errors_msg) + assert String.contains?(output, "list_infer.ex: The variable on line 4") + assert_receive {:system_halt, 1} + end + + test "--source-path option" do + ex_file = "wrong_ret.ex" + + output = run_task(test_opts(["--source-path", ex_file, "--", @s_wrong_ret_beam])) + + assert not String.contains?(output, @s_wrong_ret_ex) + assert String.contains?(output, ex_file) + assert_receive {:system_halt, 1} + end + + test "counts errors" do + assert run_task([@s_wrong_ret_beam]) =~ "Total errors: 2" + assert_receive {:system_halt, 1} + + assert run_task([@s_wrong_ret_beam, @s_wrong_ret_ex]) =~ "Total errors: 4" + assert_receive {:system_halt, 1} + end + + test "dependent modules are loaded" do + assert run_task([@examples_path <> "/dependent_modules.ex"]) =~ "No errors found!" + end + describe "Umbrella project app filtering:" do # Templates for dynamically creating variations on the umbrella/subapp # mix.exs config files at runtime @umbrella_mix_exs """ defmodule SimpleUmbrellaApp.MixProject do use Mix.Project + def project do [ apps_path: "apps", @@ -58,6 +255,7 @@ defmodule Mix.Tasks.GradientTest do <%= if enabled != nil do %> enabled: <%= enabled %>, <% end %> + <%= if overrides != nil do %> file_overrides: <%= overrides %>, <% end %> @@ -65,6 +263,7 @@ defmodule Mix.Tasks.GradientTest do <% end %> ] end + defp deps do [{:gradient, path: "../../", override: true}] end @@ -74,6 +273,7 @@ defmodule Mix.Tasks.GradientTest do @subapp_mix_exs """ defmodule App<%= String.upcase(app) %>.MixProject do use Mix.Project + def project do [ app: :app_<%= app %>, @@ -90,6 +290,7 @@ defmodule Mix.Tasks.GradientTest do <%= if enabled != nil do %> enabled: <%= enabled %>, <% end %> + <%= if overrides != nil do %> file_overrides: <%= overrides %>, <% end %> @@ -97,6 +298,7 @@ defmodule Mix.Tasks.GradientTest do <% end %> ] end + <%= if app == "b" do %> defp deps, do: [{:app_a, in_umbrella: true}] <% else %> @@ -215,32 +417,39 @@ defmodule Mix.Tasks.GradientTest do |> Map.get(:apps_files) end + # Strip off "_build/dev/" or "_build/test/" from the beginning of a path + defp strip_beam_path("_build/dev/" <> path), do: path + defp strip_beam_path("_build/test/" <> path), do: path + defp strip_beam_path(path), do: path + @tag umbrella: %{no_config: true}, app_a: %{no_config: true}, app_b: %{no_config: true} test "defaults to enabled when no gradient config in any mix.exs files" do - assert %{ - "app_a" => [ - "_build/test/lib/app_a/ebin/Elixir.AppA.beam", - "_build/test/lib/app_a/ebin/Elixir.AppAHelper.beam" - ], - "app_b" => [ - "_build/test/lib/app_b/ebin/Elixir.AppB.beam", - "_build/test/lib/app_b/ebin/Elixir.AppBHelper.beam" - ] - } == run_task_and_return_files() + assert %{"app_a" => app_a_files, "app_b" => app_b_files} = run_task_and_return_files() + + assert [ + "lib/app_a/ebin/Elixir.AppA.beam", + "lib/app_a/ebin/Elixir.AppAHelper.beam" + ] == Enum.map(app_a_files, &strip_beam_path/1) + + assert [ + "lib/app_b/ebin/Elixir.AppB.beam", + "lib/app_b/ebin/Elixir.AppBHelper.beam" + ] == Enum.map(app_b_files, &strip_beam_path/1) end @tag umbrella: %{enabled: true}, app_a: %{no_config: true}, app_b: %{no_config: true} test "when gradient is enabled for umbrella, checks all files" do - assert %{ - "app_a" => [ - "_build/test/lib/app_a/ebin/Elixir.AppA.beam", - "_build/test/lib/app_a/ebin/Elixir.AppAHelper.beam" - ], - "app_b" => [ - "_build/test/lib/app_b/ebin/Elixir.AppB.beam", - "_build/test/lib/app_b/ebin/Elixir.AppBHelper.beam" - ] - } == run_task_and_return_files() + assert %{"app_a" => app_a_files, "app_b" => app_b_files} = run_task_and_return_files() + + assert [ + "lib/app_a/ebin/Elixir.AppA.beam", + "lib/app_a/ebin/Elixir.AppAHelper.beam" + ] == Enum.map(app_a_files, &strip_beam_path/1) + + assert [ + "lib/app_b/ebin/Elixir.AppB.beam", + "lib/app_b/ebin/Elixir.AppBHelper.beam" + ] == Enum.map(app_b_files, &strip_beam_path/1) end @tag umbrella: %{enabled: false}, app_a: %{no_config: true}, app_b: %{no_config: true} @@ -250,239 +459,53 @@ defmodule Mix.Tasks.GradientTest do @tag umbrella: %{enabled: false}, app_a: %{enabled: true} test "gradient can be enabled for a subapp even if disabled for umbrella" do - assert %{ - "app_a" => [ - "_build/test/lib/app_a/ebin/Elixir.AppA.beam", - "_build/test/lib/app_a/ebin/Elixir.AppAHelper.beam" - ] - } == run_task_and_return_files() + assert %{"app_a" => app_a_files} = run_task_and_return_files() + + assert [ + "lib/app_a/ebin/Elixir.AppA.beam", + "lib/app_a/ebin/Elixir.AppAHelper.beam" + ] == Enum.map(app_a_files, &strip_beam_path/1) end @tag umbrella: %{enabled: true}, app_a: %{enabled: false} test "gradient can be enabled for the umbrella and disabled for a subapp" do - assert %{ - "app_b" => [ - "_build/test/lib/app_b/ebin/Elixir.AppB.beam", - "_build/test/lib/app_b/ebin/Elixir.AppBHelper.beam" - ] - } == run_task_and_return_files() + assert %{"app_b" => app_b_files} = run_task_and_return_files() + + assert [ + "lib/app_b/ebin/Elixir.AppB.beam", + "lib/app_b/ebin/Elixir.AppBHelper.beam" + ] == Enum.map(app_b_files, &strip_beam_path/1) end @tag umbrella: %{enabled: true, overrides: true}, edit_files: %{"app_a/lib/app_a_helper.ex" => false} test "individual files can be disabled" do - assert %{ - "app_a" => [ - "_build/test/lib/app_a/ebin/Elixir.AppA.beam" - ], - "app_b" => [ - "_build/test/lib/app_b/ebin/Elixir.AppB.beam", - "_build/test/lib/app_b/ebin/Elixir.AppBHelper.beam" - ] - } == run_task_and_return_files() - end - - @tag umbrella: %{enabled: false, overrides: true}, edit_files: %{"app_a/lib/app_a.ex" => true} - test "individual files can be enabled" do - assert %{"app_a" => ["_build/test/lib/app_a/ebin/Elixir.AppA.beam"]} == - run_task_and_return_files() - end - end - - test "--no-compile option" do - info = "Compiling project..." - - output = run_task([@s_wrong_ret_beam]) - assert String.contains?(output, info) - - output = run_task(["--no-compile", "--", @s_wrong_ret_beam]) - assert not String.contains?(output, info) - - assert_receive {:system_halt, 1} - end - - test "path to the beam file" do - output = run_task(test_opts([@s_wrong_ret_beam])) - assert 3 == String.split(output, @s_wrong_ret_ex) |> length() - - assert_receive {:system_halt, 1} - end - - test "path to the ex file" do - output = run_task(test_opts([@s_wrong_ret_ex])) - assert 3 == String.split(output, @s_wrong_ret_ex) |> length() - - assert_receive {:system_halt, 1} - end - - test "no_fancy option" do - output = run_task(test_opts([@s_wrong_ret_beam])) - assert String.contains?(output, "The integer on line") - assert String.contains?(output, "The tuple on line") - - assert_receive {:system_halt, 1} - - output = run_task(test_opts(["--no-fancy", "--", @s_wrong_ret_beam])) - assert String.contains?(output, "The integer \e[33m1\e[0m on line") - assert String.contains?(output, "The tuple \e[33m{:ok, []}\e[0m on line") - - assert_receive {:system_halt, 1} - end + assert %{"app_a" => app_a_files, "app_b" => app_b_files} = run_task_and_return_files() - describe "colors" do - test "no_colors option" do - output = run_task(test_opts([@s_wrong_ret_beam])) - assert String.contains?(output, IO.ANSI.cyan()) - assert String.contains?(output, IO.ANSI.red()) - assert_receive {:system_halt, 1} + assert ["lib/app_a/ebin/Elixir.AppA.beam"] == Enum.map(app_a_files, &strip_beam_path/1) - output = run_task(test_opts(["--no-colors", "--", @s_wrong_ret_beam])) - assert not String.contains?(output, IO.ANSI.cyan()) - assert String.contains?(output, IO.ANSI.red()) - assert_receive {:system_halt, 1} + assert [ + "lib/app_b/ebin/Elixir.AppB.beam", + "lib/app_b/ebin/Elixir.AppBHelper.beam" + ] == Enum.map(app_b_files, &strip_beam_path/1) end - test "--expr-color and --type-color option" do - output = - run_task( - test_opts([ - "--no-fancy", - "--expr-color", - "green", - "--type-color", - "magenta", - "--", - @s_wrong_ret_beam - ]) - ) - - assert String.contains?(output, IO.ANSI.green()) - assert String.contains?(output, IO.ANSI.magenta()) - assert_receive {:system_halt, 1} - end - - test "--underscore_color option" do - output = - run_task( - test_opts([ - "--underscore-color", - "green", - "--", - @s_wrong_ret_beam - ]) - ) + @tag umbrella: %{enabled: false, overrides: true}, edit_files: %{"app_a/lib/app_a.ex" => true} + test "individual files can be enabled" do + assert %{"app_a" => app_a_files} = run_task_and_return_files() - assert String.contains?(output, IO.ANSI.green()) - assert String.contains?(output, IO.ANSI.red()) - assert_receive {:system_halt, 1} + assert ["lib/app_a/ebin/Elixir.AppA.beam"] == Enum.map(app_a_files, &strip_beam_path/1) end end - test "--no-gradualizer-check option" do - output = run_task(test_opts(["--no-gradualizer-check", "--", @s_wrong_ret_beam])) - - assert String.contains?(output, @no_errors_msg) - refute_receive {:system_halt, 1} - end - - test "--no-ex-check option" do - beam = Path.join(@build_path, "Elixir.SpecWrongName.beam") - ex_spec_error_msg = "The spec convert/1" - - output = run_task(test_opts([beam])) - assert String.contains?(output, ex_spec_error_msg) - - output = run_task(test_opts(["--no-ex-check", "--", beam])) - assert not String.contains?(output, ex_spec_error_msg) - assert_receive {:system_halt, 1} - end - - @tag :ex_lt_1_13 - test "--no-specify option" do - output = run_task(test_opts([@s_wrong_ret_beam])) - assert String.contains?(output, "on line 3") - assert String.contains?(output, "on line 6") - assert_receive {:system_halt, 1} - - output = run_task(test_opts(["--no-specify", "--", @s_wrong_ret_beam])) - assert String.contains?(output, "on line 0") - assert not String.contains?(output, "on line 3") - assert not String.contains?(output, "on line 6") - assert_receive {:system_halt, 1} - end - - test "--stop-on-first-error option" do - output = run_task(test_opts(["--stop-on-first-error", "--", @s_wrong_ret_beam])) - - assert 2 == String.split(output, @s_wrong_ret_ex) |> length() - assert_receive {:system_halt, 1} - end - - test "--fmt-location option" do - output = run_task(test_opts(["--fmt-location", "none", "--", @s_wrong_ret_beam])) - - assert String.contains?(output, "s_wrong_ret.ex: The integer is expected to have type") - assert_receive {:system_halt, 1} - - output = run_task(test_opts(["--fmt-location", "brief", "--", @s_wrong_ret_beam])) - - assert String.contains?(output, "s_wrong_ret.ex:3: The integer is expected to have type") - assert_receive {:system_halt, 1} - - output = run_task(test_opts(["--fmt-location", "verbose", "--", @s_wrong_ret_beam])) - - assert String.contains?( - output, - "s_wrong_ret.ex: The integer on line 3 is expected to have type" - ) - - assert_receive {:system_halt, 1} - end - - test "--no-deps option" do - info = "Loading deps..." - - output = run_task(["--no-compile", "--", @s_wrong_ret_beam]) - assert String.contains?(output, info) - assert_receive {:system_halt, 1} - - output = run_task(["--no-compile", "--no-deps", "--", @s_wrong_ret_beam]) - assert not String.contains?(output, info) - assert_receive {:system_halt, 1} - end - - test "--infer option" do - beam = Path.join(@build_path, "Elixir.ListInfer.beam") - output = run_task(test_opts([beam])) - assert String.contains?(output, @no_errors_msg) - refute_receive {:system_halt, 1} - - output = run_task(test_opts(["--infer", "--", beam])) - assert not String.contains?(output, @no_errors_msg) - assert String.contains?(output, "list_infer.ex: The variable on line 4") - assert_receive {:system_halt, 1} - end - - test "--source-path option" do - ex_file = "wrong_ret.ex" - - output = run_task(test_opts(["--source-path", ex_file, "--", @s_wrong_ret_beam])) - - assert not String.contains?(output, @s_wrong_ret_ex) - assert String.contains?(output, ex_file) - assert_receive {:system_halt, 1} - end - - test "counts errors" do - assert run_task([@s_wrong_ret_beam]) =~ "Total errors: 2" - assert_receive {:system_halt, 1} - - assert run_task([@s_wrong_ret_beam, @s_wrong_ret_ex]) =~ "Total errors: 4" - assert_receive {:system_halt, 1} - end - - test "dependent modules are loaded" do - assert run_task([@examples_path <> "/dependent_modules.ex"]) =~ "No errors found!" + # Run the task in the current process. Useful for running on a single file. + # def run_task(args), do: capture_io(fn -> Mix.Tasks.Gradient.run(args) end) + def run_task(args) do + capture_io(fn -> + capture_io(:stderr, fn -> + Mix.Tasks.Gradient.run(args) + end) + end) end # Run the task in a new process, via the shell. Useful for running on an entire project. @@ -493,8 +516,11 @@ defmodule Mix.Tasks.GradientTest do # cd to the specified dir File.cd!(dir) + # mix clean first + assert {_, 0} = System.cmd("mix", ["clean"]) + # Run the task - {output, exit_code} = System.cmd("mix", ["gradient"] ++ args, env: [{"MIX_ENV", "test"}]) + {output, exit_code} = System.cmd("mix", ["gradient"] ++ args) assert exit_code == 0, output output @@ -503,7 +529,5 @@ defmodule Mix.Tasks.GradientTest do end end - def run_task(args), do: capture_io(fn -> Mix.Tasks.Gradient.run(args) end) - - def test_opts(opts), do: ["--no-comile", "--no-deps"] ++ opts + def test_opts(opts), do: ["--no-compile", "--no-deps"] ++ opts end