diff --git a/README.md b/README.md index f86a26f..65a3eb2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ interactive watch mode, `mix test.interactive` allows you to dynamically change which tests should be run with a few keystrokes. It allows you to easily switch between running all tests, stale tests, or failed -tests. Or, you can run only the tests whose filenames contain a substring. +tests. Or, you can run only the tests whose filenames contain a substring. You +can also control which tags are included or excluded and easily specify the test +seed to use. Includes an optional "watch mode" which runs tests after every file change. ## Installation @@ -73,14 +75,13 @@ specified in the configuration. All of the `` are passed through to `mix test` on every test run. -`mix test.interactive` will detect the `--failed`, `--seed`, and `--stale` -options and use those as initial settings in interactive mode. You can then use -the interactive mode commands to adjust those options as needed. It will also -detect any filename or pattern arguments and use those as initial settings. -However, it does not detect any filenames passed with `--include` or `--only`. -Note that if you specify a pattern on the command-line, `mix test.interactive` -will find all test files matching that pattern and pass those to `mix test` as -if you had used the `p` command. +`mix test.interactive` will detect the `--exclude`, `--failed`, `--include`, +`--only`, `--seed`, and `--stale` options and use those as initial settings in +interactive mode. You can then use the interactive mode commands to adjust those +options as needed. It will also detect any filename or pattern arguments and use +those as initial settings. Note that if you specify a pattern on the +command-line, `mix test.interactive` will find all test files matching that +pattern and pass those to `mix test` as if you had used the `p` command. ### Patterns and filenames @@ -91,40 +92,35 @@ more patterns on the command-line, `mix test.interactive` will find all test files matching those patterns and pass them to `mix test` as if you had used the `p` command (described below). -After the tests run, you can use the interactive mode to change which tests will -run. - -Use the `p` command to run only test files that match one or more provided -patterns. A pattern is the project-root-relative path to a test file (with or -without a line number specification) or a string that matches a portion of full -pathname. e.g. `test/my_project/my_test.exs`, -`test/my_project/my_test.exs:12:24` or `my`. - -Any patterns that contain a line number specification are passed directly to -`mix test`. Remaining patterns are matched against test filenames as above. - -``` -p pattern1 pattern2 -``` - -Use the `s` command to run only test files that reference modules that have -changed since the last run (equivalent to the `--stale` option of `mix test`). - -Use the `f` command to run only tests that failed on the last run (equivalent to -the `--failed` option of `mix test`). - -Use the `a` command to run all tests, turning off the `--failed` and `--stale` -flags as well as clearing any patterns. - -Use the `d ` command to run tests with a specific seed, and then use `d` -(with no seed) to remove the seed. - -Use the `w` command to turn file-watching mode on or off. - -Use the `Enter` key to re-run the current set of tests without requiring a file -change. - -Use the `q` command, or press `Ctrl-D` to exit the program. +After the tests run, you can use the interactive commands to change which tests +will run. + +- `a`: Run all tests. Clears the `--failed` and `--stale` options as well as + any patterns. +- `d `: Run the tests with a specific seed. +- `d`: Clear any previously specified seed. +- `f`: Run only tests that failed on the last run (equivalent to the + `--failed` option of `mix test`). +- `i `: Include tests tagged with the listed tags (equivalent to the + `--include` option of `mix test`). +- `i`: Clear any included tags. +- `o `: Run only tests tagged with the listed tags (equivalent to the + `--only` option of `mix test`). +- `o`: Clear any "only" tags. +- `p`: Run only test files that match one or more provided patterns. A pattern + is the project-root-relative path to a test file (with or without a line + number specification) or a string that matches a portion of full pathname. + e.g. `test/my_project/my_test.exs`, `test/my_project/my_test.exs:12:24` or + `my`. +- `q`: Exit the program. (Can also use `Ctrl-D`.) +- `s`: Run only test files that reference modules that have changed since the + last run (equivalent to the `--stale` option of `mix test`). +- `x `: Exclude tests tagged with the listed tags (equivalent to the + `--exclude` option of `mix test`). +- `x`: Clear any excluded tags. +- `w`: Turn file-watching mode on or off. +- `Enter`: Re-run the current set of tests without requiring a file change. +- `?`: Show usage help. ## Configuration diff --git a/lib/mix/tasks/test/interactive.ex b/lib/mix/tasks/test/interactive.ex index b12f532..40ac4e4 100644 --- a/lib/mix/tasks/test/interactive.ex +++ b/lib/mix/tasks/test/interactive.ex @@ -5,8 +5,9 @@ defmodule Mix.Tasks.Test.Interactive do `mix test.interactive` allows you to easily switch between running all tests, stale tests, or failed tests. Or, you can run only the tests whose filenames - contain a substring. Includes an optional "watch mode" which runs tests after - every file change. + contain a substring. You can also control which tags are included or excluded + and easily specify the test seed to use. Includes an optional "watch mode" + which runs tests after every file change. ## Usage @@ -49,9 +50,13 @@ defmodule Mix.Tasks.Test.Interactive do All of the `` are passed through to `mix test` on every test run. - `mix test.interactive` will detect the `--failed`, `--seed`, and `--stale` - options and use those as initial settings in interactive mode. You can then - use the interactive mode commands to adjust those options as needed. + `mix test.interactive` will detect the `--exclude`, `--failed`, `--include`, + `--only`, `--seed`, and `--stale` options and use those as initial settings in + interactive mode. You can then use the interactive mode commands to adjust + those options as needed. It will also detect any filename or pattern arguments + and use those as initial settings. Note that if you specify a pattern on the + command-line, `mix test.interactive` will find all test files matching that + pattern and pass those to `mix test` as if you had used the `p` command. ### Patterns and filenames @@ -64,8 +69,8 @@ defmodule Mix.Tasks.Test.Interactive do ## Interactive Commands - After the tests run, you can use the interactive mode to change which tests - will run. + After the tests run, you can use the interactive commands to change which + tests will run. - `a`: Run all tests. Clears the `--failed` and `--stale` options as well as any patterns. @@ -73,6 +78,12 @@ defmodule Mix.Tasks.Test.Interactive do - `d`: Clear any previously specified seed. - `f`: Run only tests that failed on the last run (equivalent to the `--failed` option of `mix test`). + - `i `: Include tests tagged with the listed tags (equivalent to the + `--include` option of `mix test`). + - `i`: Clear any included tags. + - `o `: Run only tests tagged with the listed tags (equivalent to the + `--only` option of `mix test`). + - `o`: Clear any "only" tags. - `p`: Run only test files that match one or more provided patterns. A pattern is the project-root-relative path to a test file (with or without a line number specification) or a string that matches a portion of full pathname. @@ -81,8 +92,12 @@ defmodule Mix.Tasks.Test.Interactive do - `q`: Exit the program. (Can also use `Ctrl-D`.) - `s`: Run only test files that reference modules that have changed since the last run (equivalent to the `--stale` option of `mix test`). + - `x `: Exclude tests tagged with the listed tags (equivalent to the + `--exclude` option of `mix test`). + - `x`: Clear any excluded tags. - `w`: Turn file-watching mode on or off. - `Enter`: Re-run the current set of tests without requiring a file change. + - `?`: Show usage help. ## Configuration diff --git a/lib/mix_test_interactive/command/exclude.ex b/lib/mix_test_interactive/command/exclude.ex new file mode 100644 index 0000000..a1856e7 --- /dev/null +++ b/lib/mix_test_interactive/command/exclude.ex @@ -0,0 +1,26 @@ +defmodule MixTestInteractive.Command.Exclude do + @moduledoc """ + Specify or clear tags to exclude. + + Runs the tests excluding the given tags if provided. If not provided, the + excludes are cleared and the tests will run with any excludes configured in + your `ExUnit.configure/1` call (if any). + """ + use MixTestInteractive.Command, command: "x", desc: "set or clear excluded tags" + + alias MixTestInteractive.Command + alias MixTestInteractive.Settings + + @impl Command + def name, do: "x []" + + @impl Command + def run([], %Settings{} = settings) do + {:ok, Settings.clear_excludes(settings)} + end + + @impl Command + def run(tags, %Settings{} = settings) do + {:ok, Settings.with_excludes(settings, tags)} + end +end diff --git a/lib/mix_test_interactive/command/include.ex b/lib/mix_test_interactive/command/include.ex new file mode 100644 index 0000000..07c6553 --- /dev/null +++ b/lib/mix_test_interactive/command/include.ex @@ -0,0 +1,26 @@ +defmodule MixTestInteractive.Command.Include do + @moduledoc """ + Specify or clear tags to include. + + Runs the tests excluding the given tags if provided. If not provided, the + includes are cleared and the tests will run with any includes configured in + your `ExUnit.configure/1` call (if any). + """ + use MixTestInteractive.Command, command: "i", desc: "set or clear included tags" + + alias MixTestInteractive.Command + alias MixTestInteractive.Settings + + @impl Command + def name, do: "i []" + + @impl Command + def run([], %Settings{} = settings) do + {:ok, Settings.clear_includes(settings)} + end + + @impl Command + def run(tags, %Settings{} = settings) do + {:ok, Settings.with_includes(settings, tags)} + end +end diff --git a/lib/mix_test_interactive/command/only.ex b/lib/mix_test_interactive/command/only.ex new file mode 100644 index 0000000..8258fce --- /dev/null +++ b/lib/mix_test_interactive/command/only.ex @@ -0,0 +1,26 @@ +defmodule MixTestInteractive.Command.Only do + @moduledoc """ + Specify or clear the only tags to run. + + Runs the tests with only the given tags if provided. If not provided, the list + of only tags is cleared and the tests will run with any only configured in + your `ExUnit.configure/1` call (if any). + """ + use MixTestInteractive.Command, command: "o", desc: "set or clear only tags" + + alias MixTestInteractive.Command + alias MixTestInteractive.Settings + + @impl Command + def name, do: "o []" + + @impl Command + def run([], %Settings{} = settings) do + {:ok, Settings.clear_only(settings)} + end + + @impl Command + def run(tags, %Settings{} = settings) do + {:ok, Settings.with_only(settings, tags)} + end +end diff --git a/lib/mix_test_interactive/command_line_parser.ex b/lib/mix_test_interactive/command_line_parser.ex index b8c1a30..a147a9c 100644 --- a/lib/mix_test_interactive/command_line_parser.ex +++ b/lib/mix_test_interactive/command_line_parser.ex @@ -163,14 +163,20 @@ defmodule MixTestInteractive.CommandLineParser do defp build_settings(mti_opts, mix_test_opts, patterns) do no_patterns? = Enum.empty?(patterns) + {excludes, mix_test_opts} = Keyword.pop_values(mix_test_opts, :exclude) {failed?, mix_test_opts} = Keyword.pop(mix_test_opts, :failed, false) + {includes, mix_test_opts} = Keyword.pop_values(mix_test_opts, :include) + {only, mix_test_opts} = Keyword.pop_values(mix_test_opts, :only) {seed, mix_test_opts} = Keyword.pop(mix_test_opts, :seed) {stale?, mix_test_opts} = Keyword.pop(mix_test_opts, :stale, false) watching? = Keyword.get(mti_opts, :watch, true) %Settings{ + excludes: excludes, failed?: no_patterns? && failed?, + includes: includes, initial_cli_args: OptionParser.to_argv(mix_test_opts), + only: only, patterns: patterns, seed: seed && to_string(seed), stale?: no_patterns? && !failed? && stale?, @@ -262,10 +268,10 @@ defmodule MixTestInteractive.CommandLineParser do defp parse_one_option_value(_name, value), do: {:ok, value} - defp combine_multiples(mti_opts) do + defp combine_multiples(opts) do @options |> Enum.filter(fn {_name, type} -> type == :keep end) - |> Enum.reduce(mti_opts, fn {name, _type}, acc -> + |> Enum.reduce(opts, fn {name, _type}, acc -> case Keyword.pop_values(acc, name) do {[], _new_opts} -> acc {values, new_opts} -> Keyword.put(new_opts, name, values) diff --git a/lib/mix_test_interactive/command_processor.ex b/lib/mix_test_interactive/command_processor.ex index 5c04430..9db2a5a 100644 --- a/lib/mix_test_interactive/command_processor.ex +++ b/lib/mix_test_interactive/command_processor.ex @@ -5,8 +5,11 @@ defmodule MixTestInteractive.CommandProcessor do alias MixTestInteractive.Command alias MixTestInteractive.Command.AllTests + alias MixTestInteractive.Command.Exclude alias MixTestInteractive.Command.Failed alias MixTestInteractive.Command.Help + alias MixTestInteractive.Command.Include + alias MixTestInteractive.Command.Only alias MixTestInteractive.Command.Pattern alias MixTestInteractive.Command.Quit alias MixTestInteractive.Command.RunTests @@ -19,8 +22,11 @@ defmodule MixTestInteractive.CommandProcessor do @commands [ AllTests, + Exclude, Failed, Help, + Include, + Only, Pattern, Quit, RunTests, @@ -52,6 +58,7 @@ defmodule MixTestInteractive.CommandProcessor do usage = settings |> applicable_commands() + |> Enum.sort_by(& &1.command()) |> Enum.flat_map(&usage_line/1) IO.ANSI.format([:bright, "Usage:\n", :normal] ++ usage) diff --git a/lib/mix_test_interactive/settings.ex b/lib/mix_test_interactive/settings.ex index 41f39af..5db93ee 100644 --- a/lib/mix_test_interactive/settings.ex +++ b/lib/mix_test_interactive/settings.ex @@ -14,9 +14,12 @@ defmodule MixTestInteractive.Settings do @default_list_all_files &TestFiles.list/0 typedstruct do + field :excludes, [String.t()], default: [] field :failed?, boolean(), default: false + field :includes, [String.t()], default: [] field :initial_cli_args, [String.t()], default: [] field :list_all_files, (-> [String.t()]), default: @default_list_all_files + field :only, [String.t()], default: [] field :patterns, [String.t()], default: [] field :seed, String.t() field :stale?, boolean(), default: false @@ -31,6 +34,30 @@ defmodule MixTestInteractive.Settings do %{settings | failed?: false, patterns: [], stale?: false} end + @doc """ + Update settings to clear any excluded tags. + """ + @spec clear_excludes(t()) :: t() + def clear_excludes(%__MODULE__{} = settings) do + %{settings | excludes: []} + end + + @doc """ + Update settings to clear any included tags. + """ + @spec clear_includes(t()) :: t() + def clear_includes(%__MODULE__{} = settings) do + %{settings | includes: []} + end + + @doc """ + Update settings to clear any "only" tags. + """ + @spec clear_only(t()) :: t() + def clear_only(%__MODULE__{} = settings) do + %{settings | only: []} + end + @doc """ Update settings to run tests with a random seed, clearing any specified seed. """ @@ -113,10 +140,60 @@ defmodule MixTestInteractive.Settings do "Ran all tests" end - case settings.seed do - nil -> run_summary - seed -> run_summary <> " with seed: #{seed}" - end + case_result = + case settings.seed do + nil -> run_summary + seed -> run_summary <> " with seed: #{seed}" + end + + append_tag_filters(case_result, settings) + end + + defp append_tag_filters(summary, %__MODULE__{} = settings) do + [ + summary, + tag_filters("Excluding tags", settings.excludes), + tag_filters("Including tags", settings.includes), + tag_filters("Only tags", settings.only) + ] + |> Enum.reject(&is_nil/1) + |> Enum.join("\n") + end + + defp tag_filters(_label, []), do: nil + + defp tag_filters(label, tags) do + label <> ": " <> inspect(tags) + end + + @doc """ + Exclude tests with the specified tags. + + Corresponds to `mix test --exclude --exclude ...`. + """ + @spec with_excludes(t(), [String.t()]) :: t() + def with_excludes(%__MODULE__{} = settings, excludes) do + %{settings | excludes: excludes} + end + + @doc """ + Include tests with the specified tags. + + Corresponds to `mix test --include --include ...`. + """ + @spec with_includes(t(), [String.t()]) :: t() + def with_includes(%__MODULE__{} = settings, includes) do + %{settings | includes: includes} + end + + @doc """ + Run only the tests with the specified tags. + + Corresponds to `mix test --only --only ...`. + """ + @spec with_only(t(), [String.t()]) :: t() + def with_only(%__MODULE__{} = settings, only) do + %{settings | only: only} end @doc """ @@ -155,8 +232,32 @@ defmodule MixTestInteractive.Settings do end defp opts_from_settings(%__MODULE__{} = settings) do - [settings.failed? && "--failed", settings.seed && ["--seed", to_string(settings.seed)], settings.stale? && "--stale"] - |> Enum.filter(& &1) - |> List.flatten() + settings + |> Map.from_struct() + |> Enum.sort() + |> Enum.flat_map(&opts_from_single_setting/1) + end + + defp opts_from_single_setting({:excludes, excludes}) do + Enum.flat_map(excludes, &["--exclude", &1]) + end + + defp opts_from_single_setting({:failed?, false}), do: [] + defp opts_from_single_setting({:failed?, true}), do: ["--failed"] + + defp opts_from_single_setting({:includes, includes}) do + Enum.flat_map(includes, &["--include", &1]) end + + defp opts_from_single_setting({:only, only}) do + Enum.flat_map(only, &["--only", &1]) + end + + defp opts_from_single_setting({:seed, nil}), do: [] + defp opts_from_single_setting({:seed, seed}), do: ["--seed", seed] + + defp opts_from_single_setting({:stale?, false}), do: [] + defp opts_from_single_setting({:stale?, true}), do: ["--stale"] + + defp opts_from_single_setting({_key, _value}), do: [] end diff --git a/test/mix_test_interactive/command_line_parser_test.exs b/test/mix_test_interactive/command_line_parser_test.exs index 7256321..c90ed18 100644 --- a/test/mix_test_interactive/command_line_parser_test.exs +++ b/test/mix_test_interactive/command_line_parser_test.exs @@ -196,9 +196,22 @@ defmodule MixTestInteractive.CommandLineParserTest do assert settings.initial_cli_args == [] end - test "extracts stale setting from arguments" do - {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--stale", "--raise"]) - assert settings.stale? + test "extracts excludes from arguments" do + {:ok, %{settings: settings}} = + CommandLineParser.parse([ + "--", + "--exclude", + "tag1", + "--trace", + "--exclude", + "tag2", + "--failed", + "--raise", + "--exclude", + "tag3" + ]) + + assert settings.excludes == ["tag1", "tag2", "tag3"] assert settings.initial_cli_args == ["--trace", "--raise"] end @@ -208,12 +221,44 @@ defmodule MixTestInteractive.CommandLineParserTest do assert settings.initial_cli_args == ["--trace", "--raise"] end + test "extracts includes from arguments" do + {:ok, %{settings: settings}} = + CommandLineParser.parse([ + "--include", + "tag1", + "--trace", + "--include", + "tag2", + "--failed", + "--raise", + "--include", + "tag3" + ]) + + assert settings.includes == ["tag1", "tag2", "tag3"] + assert settings.initial_cli_args == ["--trace", "--raise"] + end + + test "extracts only from arguments" do + {:ok, %{settings: settings}} = + CommandLineParser.parse(["--only", "tag1", "--trace", "--only", "tag2", "--failed", "--raise", "--only", "tag3"]) + + assert settings.only == ["tag1", "tag2", "tag3"] + assert settings.initial_cli_args == ["--trace", "--raise"] + end + test "extracts seed from arguments" do {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--seed", "5432", "--raise"]) assert settings.seed == "5432" assert settings.initial_cli_args == ["--trace", "--raise"] end + test "extracts stale setting from arguments" do + {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--stale", "--raise"]) + assert settings.stale? + assert settings.initial_cli_args == ["--trace", "--raise"] + end + test "extracts patterns from arguments" do {:ok, %{settings: settings}} = CommandLineParser.parse(["pattern1", "--trace", "pattern2"]) assert settings.patterns == ["pattern1", "pattern2"] @@ -247,7 +292,7 @@ defmodule MixTestInteractive.CommandLineParserTest do CommandLineParser.parse(["--exclude", "~$", "--", "--exclude", "integration"]) assert config.exclude == [~r/~$/] - assert settings.initial_cli_args == ["--exclude", "integration"] + assert settings.excludes == ["integration"] end test "requires -- separator to distinguish the sets of arguments" do diff --git a/test/mix_test_interactive/command_processor_test.exs b/test/mix_test_interactive/command_processor_test.exs index ff27c31..fe3881e 100644 --- a/test/mix_test_interactive/command_processor_test.exs +++ b/test/mix_test_interactive/command_processor_test.exs @@ -50,6 +50,34 @@ defmodule MixTestInteractive.CommandProcessorTest do assert {:ok, ^expected} = process_command("f", settings) end + test "i includes the given tags" do + settings = %Settings{} + expected = Settings.with_includes(settings, ["tag1", "tag2"]) + + assert {:ok, ^expected} = process_command("i tag1 tag2", settings) + end + + test "i with no tags clears the includes" do + {:ok, settings} = process_command("i tag1", %Settings{}) + expected = Settings.clear_includes(settings) + + assert {:ok, ^expected} = process_command("i", settings) + end + + test "o runs with only the given tags" do + settings = %Settings{} + expected = Settings.with_only(settings, ["tag1", "tag2"]) + + assert {:ok, ^expected} = process_command("o tag1 tag2", settings) + end + + test "o with no tags clears the only" do + {:ok, settings} = process_command("o tag1", %Settings{}) + expected = Settings.clear_only(settings) + + assert {:ok, ^expected} = process_command("o", settings) + end + test "p filters test files to those matching provided pattern" do settings = %Settings{} expected = Settings.only_patterns(settings, ["pattern"]) @@ -79,6 +107,20 @@ defmodule MixTestInteractive.CommandProcessorTest do assert {:no_run, ^expected} = process_command("w", settings) end + test "x excludes the given tags" do + settings = %Settings{} + expected = Settings.with_excludes(settings, ["tag1", "tag2"]) + + assert {:ok, ^expected} = process_command("x tag1 tag2", settings) + end + + test "x with no tags clears the excludes" do + {:ok, settings} = process_command("x tag1", %Settings{}) + expected = Settings.clear_excludes(settings) + + assert {:ok, ^expected} = process_command("x", settings) + end + test "? returns :help" do settings = %Settings{} diff --git a/test/mix_test_interactive/end_to_end_test.exs b/test/mix_test_interactive/end_to_end_test.exs index caed238..b382f56 100644 --- a/test/mix_test_interactive/end_to_end_test.exs +++ b/test/mix_test_interactive/end_to_end_test.exs @@ -29,7 +29,7 @@ defmodule MixTestInteractive.EndToEndTest do %{pid: pid} end - test "end to end workflow test", %{pid: pid} do + test "failed/stale/pattern workflow", %{pid: pid} do assert_ran_tests() assert :ok = InteractiveMode.process_command(pid, "") @@ -49,20 +49,43 @@ defmodule MixTestInteractive.EndToEndTest do assert :ok = InteractiveMode.note_file_changed(pid) assert_ran_tests(["--stale"]) + end - assert :ok = InteractiveMode.process_command(pid, "w") - refute_ran_tests() + test "tag workflow", %{pid: pid} do + assert_ran_tests() - assert :ok = InteractiveMode.note_file_changed(pid) - refute_ran_tests() + assert :ok = InteractiveMode.process_command(pid, "i tag1 tag2") + assert_ran_tests(["--include", "tag1", "--include", "tag2"]) - assert :ok = InteractiveMode.process_command(pid, "w") - refute_ran_tests() + assert :ok = InteractiveMode.process_command(pid, "o tag3") + assert_ran_tests(["--include", "tag1", "--include", "tag2", "--only", "tag3"]) - assert :ok = InteractiveMode.note_file_changed(pid) - assert_ran_tests(["--stale"]) + assert :ok = InteractiveMode.process_command(pid, "x tag4 tag5") - assert :ok = InteractiveMode.process_command(pid, "a") + assert_ran_tests([ + "--exclude", + "tag4", + "--exclude", + "tag5", + "--include", + "tag1", + "--include", + "tag2", + "--only", + "tag3" + ]) + + assert :ok = InteractiveMode.process_command(pid, "o") + assert_ran_tests(["--exclude", "tag4", "--exclude", "tag5", "--include", "tag1", "--include", "tag2"]) + + assert :ok = InteractiveMode.process_command(pid, "i") + assert_ran_tests(["--exclude", "tag4", "--exclude", "tag5"]) + + assert :ok = InteractiveMode.process_command(pid, "x") + assert_ran_tests() + end + + test "seed workflow", %{pid: pid} do assert_ran_tests() assert :ok = InteractiveMode.process_command(pid, "d 4242") @@ -71,7 +94,26 @@ defmodule MixTestInteractive.EndToEndTest do assert :ok = InteractiveMode.note_file_changed(pid) assert_ran_tests(["--seed", "4242"]) + assert :ok = InteractiveMode.process_command(pid, "s") + assert_ran_tests(["--seed", "4242", "--stale"]) + assert :ok = InteractiveMode.process_command(pid, "d") + assert_ran_tests(["--stale"]) + end + + test "watch on/off workflow", %{pid: pid} do + assert_ran_tests() + + assert :ok = InteractiveMode.process_command(pid, "w") + refute_ran_tests() + + assert :ok = InteractiveMode.note_file_changed(pid) + refute_ran_tests() + + assert :ok = InteractiveMode.process_command(pid, "w") + refute_ran_tests() + + assert :ok = InteractiveMode.note_file_changed(pid) assert_ran_tests() end diff --git a/test/mix_test_interactive/settings_test.exs b/test/mix_test_interactive/settings_test.exs index e674354..95b58cd 100644 --- a/test/mix_test_interactive/settings_test.exs +++ b/test/mix_test_interactive/settings_test.exs @@ -3,7 +3,7 @@ defmodule MixTestInteractive.SettingsTest do alias MixTestInteractive.Settings - describe "filtering tests" do + describe "filtering test files" do test "filters to files matching patterns" do all_files = ~w(file1 file2 no_match other) @@ -41,14 +41,6 @@ defmodule MixTestInteractive.SettingsTest do assert args == ["--trace", "--stale"] end - test "runs with seed" do - seed = "5678" - settings = Settings.with_seed(%Settings{initial_cli_args: ["--trace"]}, seed) - - {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--seed", seed] - end - test "pattern filter clears failed flag" do settings = %Settings{} @@ -146,6 +138,82 @@ defmodule MixTestInteractive.SettingsTest do end end + describe "filtering tests by tags" do + test "excludes specified tags" do + tags = ["tag1", "tag2"] + settings = Settings.with_excludes(%Settings{initial_cli_args: ["--trace"]}, tags) + + {:ok, args} = Settings.cli_args(settings) + assert args == ["--trace", "--exclude", "tag1", "--exclude", "tag2"] + end + + test "clears excluded tags" do + settings = + %Settings{} + |> Settings.with_excludes(["tag1"]) + |> Settings.clear_excludes() + + {:ok, args} = Settings.cli_args(settings) + assert args == [] + end + + test "includes specified tags" do + tags = ["tag1", "tag2"] + settings = Settings.with_includes(%Settings{initial_cli_args: ["--trace"]}, tags) + + {:ok, args} = Settings.cli_args(settings) + assert args == ["--trace", "--include", "tag1", "--include", "tag2"] + end + + test "clears included tags" do + settings = + %Settings{} + |> Settings.with_includes(["tag1"]) + |> Settings.clear_includes() + + {:ok, args} = Settings.cli_args(settings) + assert args == [] + end + + test "runs only specified tags" do + tags = ["tag1", "tag2"] + settings = Settings.with_only(%Settings{initial_cli_args: ["--trace"]}, tags) + + {:ok, args} = Settings.cli_args(settings) + assert args == ["--trace", "--only", "tag1", "--only", "tag2"] + end + + test "clears only tags" do + settings = + %Settings{} + |> Settings.with_only(["tag1"]) + |> Settings.clear_only() + + {:ok, args} = Settings.cli_args(settings) + assert args == [] + end + end + + describe "specifying the seed" do + test "runs with seed" do + seed = "5678" + settings = Settings.with_seed(%Settings{initial_cli_args: ["--trace"]}, seed) + + {:ok, args} = Settings.cli_args(settings) + assert args == ["--trace", "--seed", seed] + end + + test "clears the seed" do + settings = + %Settings{} + |> Settings.with_seed("1234") + |> Settings.clear_seed() + + {:ok, args} = Settings.cli_args(settings) + assert args == [] + end + end + describe "summary" do test "ran all tests" do settings = %Settings{} @@ -204,5 +272,19 @@ defmodule MixTestInteractive.SettingsTest do assert Settings.summary(settings) == "Ran all test files matching p1, p2 with seed: #{seed}" end + + test "appends tag filters" do + settings = + %Settings{} + |> Settings.with_excludes(["tag1", "tag2"]) + |> Settings.with_includes(["tag3", "tag4"]) + |> Settings.with_only(["tag5", "tag6"]) + + summary = Settings.summary(settings) + + assert summary =~ ~s(Excluding tags: ["tag1", "tag2"]) + assert summary =~ ~s(Including tags: ["tag3", "tag4"]) + assert summary =~ ~s(Only tags: ["tag5", "tag6"]) + end end end