From ce55d50f5a7d11a233c83b9cd2ca6ab5b14d5ba9 Mon Sep 17 00:00:00 2001 From: Randy Coulman Date: Sat, 21 Sep 2024 18:44:58 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20t=20command=20to=20toggle=20t?= =?UTF-8?q?racing=20on/off=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- lib/mix/tasks/test/interactive.ex | 7 ++- .../command/toggle_tracing.ex | 19 +++++++ .../command_line_parser.ex | 2 + lib/mix_test_interactive/command_processor.ex | 2 + lib/mix_test_interactive/settings.ex | 37 +++++++++--- .../command_line_parser_test.exs | 42 ++++++++------ .../command_processor_test.exs | 7 +++ test/mix_test_interactive/end_to_end_test.exs | 10 ++++ test/mix_test_interactive/settings_test.exs | 57 +++++++++++++------ 10 files changed, 142 insertions(+), 47 deletions(-) create mode 100644 lib/mix_test_interactive/command/toggle_tracing.ex diff --git a/README.md b/README.md index 81c2674..114fbe0 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ 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. You can also control which tags are included or excluded, modify the maximum number -of failures allowed, and specify the test seed to use. Includes an optional -"watch mode" which runs tests after every file change. +of failures allowed, specify the test seed to use, and toggle tracing on and +off. Includes an optional "watch mode" which runs tests after every file change. ## Installation @@ -120,6 +120,8 @@ will run. - `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`). +- `t`: Turn test tracing on or off (equivalent to the `--trace` 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. diff --git a/lib/mix/tasks/test/interactive.ex b/lib/mix/tasks/test/interactive.ex index cd50b98..2d24003 100644 --- a/lib/mix/tasks/test/interactive.ex +++ b/lib/mix/tasks/test/interactive.ex @@ -6,8 +6,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. You can also control which tags are included or excluded, - modify the maximum number of failures allowed, and specify the test seed to use. - Includes an optional "watch mode" which runs tests after every file change. + modify the maximum number of failures allowed, specify the test seed to use, + and toggle tracing on and off. Includes an optional "watch mode" which runs + tests after every file change. ## Usage @@ -95,6 +96,8 @@ 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`). + - `t`: Turn test tracing on or off (equivalent to the `--trace` 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. diff --git a/lib/mix_test_interactive/command/toggle_tracing.ex b/lib/mix_test_interactive/command/toggle_tracing.ex new file mode 100644 index 0000000..de7d41a --- /dev/null +++ b/lib/mix_test_interactive/command/toggle_tracing.ex @@ -0,0 +1,19 @@ +defmodule MixTestInteractive.Command.ToggleTracing do + @moduledoc """ + Toggle test tracing on or off. + + Runs the tests in trace mode when tracing is on and normally when off. + + Corresponds to `mix test --trace`. + """ + + use MixTestInteractive.Command, command: "t", desc: "turn test tracing on/off" + + alias MixTestInteractive.Command + alias MixTestInteractive.Settings + + @impl Command + def run(_args, settings) do + {:ok, Settings.toggle_tracing(settings)} + end +end diff --git a/lib/mix_test_interactive/command_line_parser.ex b/lib/mix_test_interactive/command_line_parser.ex index 5a8cf2e..6c7f355 100644 --- a/lib/mix_test_interactive/command_line_parser.ex +++ b/lib/mix_test_interactive/command_line_parser.ex @@ -170,6 +170,7 @@ defmodule MixTestInteractive.CommandLineParser do {max_failures, mix_test_opts} = Keyword.pop(mix_test_opts, :max_failures) {seed, mix_test_opts} = Keyword.pop(mix_test_opts, :seed) {stale?, mix_test_opts} = Keyword.pop(mix_test_opts, :stale, false) + {trace?, mix_test_opts} = Keyword.pop(mix_test_opts, :trace, false) watching? = Keyword.get(mti_opts, :watch, true) %Settings{ @@ -182,6 +183,7 @@ defmodule MixTestInteractive.CommandLineParser do patterns: patterns, seed: seed && to_string(seed), stale?: no_patterns? && !failed? && stale?, + tracing?: trace?, watching?: watching? } end diff --git a/lib/mix_test_interactive/command_processor.ex b/lib/mix_test_interactive/command_processor.ex index 032374b..0ddec42 100644 --- a/lib/mix_test_interactive/command_processor.ex +++ b/lib/mix_test_interactive/command_processor.ex @@ -16,6 +16,7 @@ defmodule MixTestInteractive.CommandProcessor do alias MixTestInteractive.Command.RunTests alias MixTestInteractive.Command.Seed alias MixTestInteractive.Command.Stale + alias MixTestInteractive.Command.ToggleTracing alias MixTestInteractive.Command.ToggleWatchMode alias MixTestInteractive.Settings @@ -34,6 +35,7 @@ defmodule MixTestInteractive.CommandProcessor do RunTests, Seed, Stale, + ToggleTracing, ToggleWatchMode ] diff --git a/lib/mix_test_interactive/settings.ex b/lib/mix_test_interactive/settings.ex index de2061f..7a7a484 100644 --- a/lib/mix_test_interactive/settings.ex +++ b/lib/mix_test_interactive/settings.ex @@ -24,6 +24,7 @@ defmodule MixTestInteractive.Settings do field :patterns, [String.t()], default: [] field :seed, String.t() field :stale?, boolean(), default: false + field :tracing?, boolean(), default: false field :watching?, boolean(), default: true end @@ -157,8 +158,9 @@ defmodule MixTestInteractive.Settings do end with_seed - |> append_max_failures(settings) |> append_tag_filters(settings) + |> append_max_failures(settings) + |> append_tracing(settings) end defp append_max_failures(summary, %__MODULE__{max_failures: nil} = _settings) do @@ -180,12 +182,34 @@ defmodule MixTestInteractive.Settings do |> Enum.join("\n") end + defp append_tracing(summary, %__MODULE__{tracing?: false}), do: summary + + defp append_tracing(summary, %__MODULE__{tracing?: true}) do + summary <> "\nTracing: ON" + end + defp tag_filters(_label, []), do: nil defp tag_filters(label, tags) do label <> ": " <> inspect(tags) end + @doc """ + Toggle test tracing on or off. + """ + @spec toggle_tracing(t()) :: t() + def toggle_tracing(%__MODULE__{} = settings) do + %{settings | tracing?: !settings.tracing?} + end + + @doc """ + Toggle file-watching mode on or off. + """ + @spec toggle_watch_mode(t()) :: t() + def toggle_watch_mode(%__MODULE__{} = settings) do + %{settings | watching?: !settings.watching?} + end + @doc """ Exclude tests with the specified tags. @@ -236,14 +260,6 @@ defmodule MixTestInteractive.Settings do %{settings | seed: seed} end - @doc """ - Toggle file-watching mode on or off. - """ - @spec toggle_watch_mode(t()) :: t() - def toggle_watch_mode(%__MODULE__{} = settings) do - %{settings | watching?: !settings.watching?} - end - defp args_from_settings(%__MODULE{} = settings) do with {:ok, files} <- files_from_patterns(settings) do {:ok, opts_from_settings(settings) ++ files} @@ -292,5 +308,8 @@ defmodule MixTestInteractive.Settings do defp opts_from_single_setting({:stale?, false}), do: [] defp opts_from_single_setting({:stale?, true}), do: ["--stale"] + defp opts_from_single_setting({:tracing?, false}), do: [] + defp opts_from_single_setting({:tracing?, true}), do: ["--trace"] + 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 701ec9b..0e0c71e 100644 --- a/test/mix_test_interactive/command_line_parser_test.exs +++ b/test/mix_test_interactive/command_line_parser_test.exs @@ -186,8 +186,8 @@ defmodule MixTestInteractive.CommandLineParserTest do describe "mix test arguments" do test "records initial `mix test` arguments" do - {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--raise"]) - assert settings.initial_cli_args == ["--trace", "--raise"] + {:ok, %{settings: settings}} = CommandLineParser.parse(["--color", "--raise"]) + assert settings.initial_cli_args == ["--color", "--raise"] end test "records no `mix test` arguments by default" do @@ -207,7 +207,7 @@ defmodule MixTestInteractive.CommandLineParserTest do "--", "--exclude", "tag1", - "--trace", + "--color", "--exclude", "tag2", "--failed", @@ -217,13 +217,13 @@ defmodule MixTestInteractive.CommandLineParserTest do ]) assert settings.excludes == ["tag1", "tag2", "tag3"] - assert settings.initial_cli_args == ["--trace", "--raise"] + assert settings.initial_cli_args == ["--color", "--raise"] end test "extracts failed flag from arguments" do - {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--failed", "--raise"]) + {:ok, %{settings: settings}} = CommandLineParser.parse(["--color", "--failed", "--raise"]) assert settings.failed? - assert settings.initial_cli_args == ["--trace", "--raise"] + assert settings.initial_cli_args == ["--color", "--raise"] end test "extracts includes from arguments" do @@ -231,7 +231,7 @@ defmodule MixTestInteractive.CommandLineParserTest do CommandLineParser.parse([ "--include", "tag1", - "--trace", + "--color", "--include", "tag2", "--failed", @@ -241,39 +241,45 @@ defmodule MixTestInteractive.CommandLineParserTest do ]) assert settings.includes == ["tag1", "tag2", "tag3"] - assert settings.initial_cli_args == ["--trace", "--raise"] + assert settings.initial_cli_args == ["--color", "--raise"] end test "extracts max-failures from arguments" do - {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--max-failures", "7", "--raise"]) + {:ok, %{settings: settings}} = CommandLineParser.parse(["--color", "--max-failures", "7", "--raise"]) assert settings.max_failures == "7" - assert settings.initial_cli_args == ["--trace", "--raise"] + assert settings.initial_cli_args == ["--color", "--raise"] end test "extracts only from arguments" do {:ok, %{settings: settings}} = - CommandLineParser.parse(["--only", "tag1", "--trace", "--only", "tag2", "--failed", "--raise", "--only", "tag3"]) + CommandLineParser.parse(["--only", "tag1", "--color", "--only", "tag2", "--failed", "--raise", "--only", "tag3"]) assert settings.only == ["tag1", "tag2", "tag3"] - assert settings.initial_cli_args == ["--trace", "--raise"] + assert settings.initial_cli_args == ["--color", "--raise"] end test "extracts seed from arguments" do - {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--seed", "5432", "--raise"]) + {:ok, %{settings: settings}} = CommandLineParser.parse(["--color", "--seed", "5432", "--raise"]) assert settings.seed == "5432" - assert settings.initial_cli_args == ["--trace", "--raise"] + assert settings.initial_cli_args == ["--color", "--raise"] end test "extracts stale setting from arguments" do - {:ok, %{settings: settings}} = CommandLineParser.parse(["--trace", "--stale", "--raise"]) + {:ok, %{settings: settings}} = CommandLineParser.parse(["--color", "--stale", "--raise"]) assert settings.stale? - assert settings.initial_cli_args == ["--trace", "--raise"] + assert settings.initial_cli_args == ["--color", "--raise"] + end + + test "extracts trace flag from arguments" do + {:ok, %{settings: settings}} = CommandLineParser.parse(["--color", "--trace", "--raise"]) + assert settings.tracing? + assert settings.initial_cli_args == ["--color", "--raise"] end test "extracts patterns from arguments" do - {:ok, %{settings: settings}} = CommandLineParser.parse(["pattern1", "--trace", "pattern2"]) + {:ok, %{settings: settings}} = CommandLineParser.parse(["pattern1", "--color", "pattern2"]) assert settings.patterns == ["pattern1", "pattern2"] - assert settings.initial_cli_args == ["--trace"] + assert settings.initial_cli_args == ["--color"] end test "failed takes precedence over stale" do diff --git a/test/mix_test_interactive/command_processor_test.exs b/test/mix_test_interactive/command_processor_test.exs index 5373b0e..80ed7a0 100644 --- a/test/mix_test_interactive/command_processor_test.exs +++ b/test/mix_test_interactive/command_processor_test.exs @@ -114,6 +114,13 @@ defmodule MixTestInteractive.CommandProcessorTest do assert {:ok, ^expected} = process_command("s", settings) end + test "t toggles tracing" do + settings = %Settings{} + expected = Settings.toggle_tracing(settings) + + assert {:ok, ^expected} = process_command("t", settings) + end + test "w toggles watch mode" do settings = %Settings{} expected = Settings.toggle_watch_mode(settings) diff --git a/test/mix_test_interactive/end_to_end_test.exs b/test/mix_test_interactive/end_to_end_test.exs index 7d14b2b..72e4553 100644 --- a/test/mix_test_interactive/end_to_end_test.exs +++ b/test/mix_test_interactive/end_to_end_test.exs @@ -123,6 +123,16 @@ defmodule MixTestInteractive.EndToEndTest do assert_ran_tests() end + test "trace on/off workflow", %{pid: pid} do + assert_ran_tests() + + assert :ok = InteractiveMode.process_command(pid, "t") + assert_ran_tests(["--trace"]) + + assert :ok = InteractiveMode.process_command(pid, "t") + assert_ran_tests() + end + test "watch on/off workflow", %{pid: pid} do assert_ran_tests() diff --git a/test/mix_test_interactive/settings_test.exs b/test/mix_test_interactive/settings_test.exs index 28e4bf0..04551f3 100644 --- a/test/mix_test_interactive/settings_test.exs +++ b/test/mix_test_interactive/settings_test.exs @@ -8,12 +8,12 @@ defmodule MixTestInteractive.SettingsTest do all_files = ~w(file1 file2 no_match other) settings = - %Settings{initial_cli_args: ["--trace"]} + %Settings{initial_cli_args: ["--color"]} |> with_fake_file_list(all_files) |> Settings.only_patterns(["file", "other"]) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "file1", "file2", "other"] + assert args == ["--color", "file1", "file2", "other"] end test "returns error if no files match pattern" do @@ -27,18 +27,18 @@ defmodule MixTestInteractive.SettingsTest do test "restricts to failed tests" do settings = - Settings.only_failed(%Settings{initial_cli_args: ["--trace"]}) + Settings.only_failed(%Settings{initial_cli_args: ["--color"]}) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--failed"] + assert args == ["--color", "--failed"] end test "restricts to stale tests" do settings = - Settings.only_stale(%Settings{initial_cli_args: ["--trace"]}) + Settings.only_stale(%Settings{initial_cli_args: ["--color"]}) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--stale"] + assert args == ["--color", "--stale"] end test "pattern filter clears failed flag" do @@ -141,10 +141,10 @@ defmodule MixTestInteractive.SettingsTest do describe "filtering tests by tags" do test "excludes specified tags" do tags = ["tag1", "tag2"] - settings = Settings.with_excludes(%Settings{initial_cli_args: ["--trace"]}, tags) + settings = Settings.with_excludes(%Settings{initial_cli_args: ["--color"]}, tags) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--exclude", "tag1", "--exclude", "tag2"] + assert args == ["--color", "--exclude", "tag1", "--exclude", "tag2"] end test "clears excluded tags" do @@ -159,10 +159,10 @@ defmodule MixTestInteractive.SettingsTest do test "includes specified tags" do tags = ["tag1", "tag2"] - settings = Settings.with_includes(%Settings{initial_cli_args: ["--trace"]}, tags) + settings = Settings.with_includes(%Settings{initial_cli_args: ["--color"]}, tags) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--include", "tag1", "--include", "tag2"] + assert args == ["--color", "--include", "tag1", "--include", "tag2"] end test "clears included tags" do @@ -177,10 +177,10 @@ defmodule MixTestInteractive.SettingsTest do test "runs only specified tags" do tags = ["tag1", "tag2"] - settings = Settings.with_only(%Settings{initial_cli_args: ["--trace"]}, tags) + settings = Settings.with_only(%Settings{initial_cli_args: ["--color"]}, tags) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--only", "tag1", "--only", "tag2"] + assert args == ["--color", "--only", "tag1", "--only", "tag2"] end test "clears only tags" do @@ -197,10 +197,10 @@ defmodule MixTestInteractive.SettingsTest do describe "specifying maximum failures" do test "stops after a specified number of failures" do max = "3" - settings = Settings.with_max_failures(%Settings{initial_cli_args: ["--trace"]}, max) + settings = Settings.with_max_failures(%Settings{initial_cli_args: ["--color"]}, max) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--max-failures", max] + assert args == ["--color", "--max-failures", max] end test "clears maximum failures" do @@ -217,10 +217,10 @@ defmodule MixTestInteractive.SettingsTest do describe "specifying the seed" do test "runs with seed" do seed = "5678" - settings = Settings.with_seed(%Settings{initial_cli_args: ["--trace"]}, seed) + settings = Settings.with_seed(%Settings{initial_cli_args: ["--color"]}, seed) {:ok, args} = Settings.cli_args(settings) - assert args == ["--trace", "--seed", seed] + assert args == ["--color", "--seed", seed] end test "clears the seed" do @@ -234,6 +234,25 @@ defmodule MixTestInteractive.SettingsTest do end end + describe "tracing the test run" do + test "toggles tracing on" do + settings = Settings.toggle_tracing(%Settings{initial_cli_args: ["--color"]}) + + {:ok, args} = Settings.cli_args(settings) + assert args == ["--color", "--trace"] + end + + test "toggles tracing off" do + settings = + %Settings{} + |> Settings.toggle_tracing() + |> Settings.toggle_tracing() + + {:ok, args} = Settings.cli_args(settings) + assert args == [] + end + end + describe "summary" do test "ran all tests" do settings = %Settings{} @@ -312,5 +331,11 @@ defmodule MixTestInteractive.SettingsTest do assert summary =~ ~s(Including tags: ["tag3", "tag4"]) assert summary =~ ~s(Only tags: ["tag5", "tag6"]) end + + test "appends tracing" do + settings = Settings.toggle_tracing(%Settings{}) + + assert Settings.summary(settings) =~ "Tracing: ON" + end end end