Skip to content

Commit 5e43b87

Browse files
committed
(mix test) add test_load_pattern and test_warn_pattern
When using the default project configuration, mix test would only warn about files ending in `_test.ex`. The only mistake this warns about is forgetting the `s` of `.exs`. In a work project we noticed (after more than a year) that we had multiple test files that did not match the test pattern, preventing them from running in mix test locally and CI. Because we have many other tests, nobody noticed this. If CI passes, all is good, right? Because there is no easy way to evaluate glob patterns in Elixir without touching the filesystem, I decided to deprecate the old configurations and instead add two new configurations: `test_load_pattern` and `test_warn_pattern`. Now, we can load all potential test files once and then match their paths to the patterns. The `test_load_pattern` is used to filter the files that are loaded by mix test. This defaults to the regex equivalent of "*_test.exs". The `test_warn_pattern` is used to filter the files that we warned about if they are not loaded. By default, we warn about any file that either ends in `_test.ex` (the old behavior) but also any file that ends in `.exs`, but does not end in `_helper.exs`, which will prevent the default test_helper.exs from generating a warning and also provides a way to name other files that might be required explicitly in tests. As an escape hatch the `test_warn_ignore_files` list can be used to ignore specific files where the warning should not be generated. For projects with an existing `test_pattern` or `warn_test_pattern` configuration, a deprecation warning is logged. The new warnings can be disabled by setting `test_warn_pattern` to a falsy value.
1 parent b75cccc commit 5e43b87

File tree

12 files changed

+113
-17
lines changed

12 files changed

+113
-17
lines changed

lib/mix/lib/mix/tasks/test.ex

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,18 @@ defmodule Mix.Tasks.Test do
228228
`["test"]` if the `test` directory exists, otherwise, it defaults to `[]`.
229229
It is expected that all test paths contain a `test_helper.exs` file
230230
231-
* `:test_pattern` - a pattern to load test files. Defaults to `*_test.exs`
231+
* `:test_load_pattern` - a regular expression to load test files.
232+
Defaults to `~r/.*_test\\.exs$/`
232233
233-
* `:warn_test_pattern` - a pattern to match potentially misnamed test files
234-
and display a warning. Defaults to `*_test.ex`
234+
* `:test_warn_pattern` - a regular expression to match potentially
235+
misnamed test files and display a warning.
236+
Defaults to `~r/^(?!.*_helper\.exs$)(?:.*_test\.ex|.*\.exs)$/`,
237+
matching any file that ends in `_test.ex` or `.exs`, ignoring files
238+
ending in `_helper.exs`. Warnings can be disabled by setting this to
239+
`nil` or `false`.
240+
241+
* `:test_warn_ignore_files` - a list of files to ignore when displaying
242+
warnings about potentially misnamed test files. Defaults to `[]`.
235243
236244
## Coloring
237245
@@ -595,20 +603,49 @@ defmodule Mix.Tasks.Test do
595603

596604
# Prepare and extract all files to require and run
597605
test_paths = project[:test_paths] || default_test_paths()
598-
test_pattern = project[:test_pattern] || "*_test.exs"
599-
warn_test_pattern = project[:warn_test_pattern] || "*_test.ex"
606+
test_load_pattern = project[:test_load_pattern] || ~r/.*_test\.exs$/
607+
608+
test_warn_pattern =
609+
Keyword.get(project, :test_warn_pattern, ~r/^(?!.*_helper\.exs$)(?:.*_test\.ex|.*\.exs)$/)
610+
611+
test_warn_ignore_files = project[:test_warn_ignore_files] || []
612+
613+
# Warn about deprecated configurations
614+
if project[:test_pattern] do
615+
Mix.shell().info(
616+
"warning: the `:test_pattern` configuration is deprecated and will be ignored. Use `:test_load_pattern` instead."
617+
)
618+
end
619+
620+
if project[:warn_test_pattern] do
621+
Mix.shell().info(
622+
"warning: the `:warn_test_pattern` configuration is deprecated and will be ignored. Use `:test_warn_pattern` instead."
623+
)
624+
end
600625

601626
{test_files, test_opts} =
602627
if files != [], do: ExUnit.Filters.parse_paths(files), else: {test_paths, []}
603628

604-
unfiltered_test_files = Mix.Utils.extract_files(test_files, test_pattern)
629+
# get a list of all files in the test folders, which we filter by the test_load_pattern
630+
potential_test_files = Mix.Utils.extract_files(test_files, "**")
631+
632+
unfiltered_test_files =
633+
Enum.filter(potential_test_files, &Regex.match?(test_load_pattern, &1))
605634

606635
matched_test_files =
607636
unfiltered_test_files
608637
|> filter_to_allowed_files(allowed_files)
609638
|> filter_by_partition(shell, partitions)
610639

611-
display_warn_test_pattern(test_files, test_pattern, unfiltered_test_files, warn_test_pattern)
640+
# do not warn if test_warn_pattern is nil or false
641+
test_warn_pattern &&
642+
maybe_warn_misnamed_test_files(
643+
potential_test_files,
644+
unfiltered_test_files,
645+
test_warn_ignore_files,
646+
test_load_pattern,
647+
test_warn_pattern
648+
)
612649

613650
try do
614651
Enum.each(test_paths, &require_test_helper(shell, &1))
@@ -679,13 +716,31 @@ defmodule Mix.Tasks.Test do
679716
end
680717
end
681718

682-
defp display_warn_test_pattern(test_files, test_pattern, matched_test_files, warn_test_pattern) do
683-
files = Mix.Utils.extract_files(test_files, warn_test_pattern) -- matched_test_files
719+
defp maybe_warn_misnamed_test_files(
720+
potential_test_files,
721+
unfiltered_test_files,
722+
test_warn_ignore_files,
723+
test_load_pattern,
724+
test_warn_pattern
725+
)
726+
when is_struct(test_warn_pattern, Regex) do
727+
missing_files = (potential_test_files -- unfiltered_test_files) -- test_warn_ignore_files
684728

685-
for file <- files do
686-
Mix.shell().info(
687-
"warning: #{file} does not match #{inspect(test_pattern)} and won't be loaded"
688-
)
729+
ignored = Enum.filter(missing_files, &Regex.match?(test_warn_pattern, &1))
730+
731+
if ignored != [] do
732+
Mix.shell().info("""
733+
warning: the following files do not match the test load pattern #{inspect(test_load_pattern)} and won't be loaded:
734+
735+
#{Enum.join(ignored, "\n")}
736+
737+
This might indicate a typo in a test file name (for example, using "foo_tests.exs" instead of "foo_test.exs").
738+
739+
You can adjust which files trigger this warning by configuring the `:test_warn_pattern` option in your
740+
Mix project's configuration. To disable the warning entirely, set that option to false.
741+
742+
For more information, run `mix help test`.
743+
""")
689744
end
690745
end
691746

lib/mix/test/fixtures/test_failed/mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule TestFailed.MixProject do
55
[
66
app: :test_only_failures,
77
version: "0.0.1",
8-
test_pattern: "*_test_failed.exs"
8+
test_load_pattern: ~r/.*_test_failed\.exs/
99
]
1010
end
1111
end

lib/mix/test/fixtures/test_stale/mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule TestStale.MixProject do
55
[
66
app: :test_stale,
77
version: "0.0.1",
8-
test_pattern: "*_test_stale.exs"
8+
test_load_pattern: ~r/.*_test_stale\.exs/
99
]
1010
end
1111
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule TestWarn.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :test_warn,
7+
version: "0.0.1",
8+
test_load_pattern: ~r/.*_tests\.exs/,
9+
test_warn_pattern: ~r/^(?!.*_helper\.exs$)(?:.*_tests\.ex|.*\.exs)$/
10+
]
11+
end
12+
end

lib/mix/test/fixtures/test_warn/test/a_missing.exs

Whitespace-only changes.

lib/mix/test/fixtures/test_warn/test/a_tests.ex

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
defmodule ATests do
2+
use ExUnit.Case
3+
4+
test "dummy" do
5+
assert true
6+
end
7+
end

lib/mix/test/fixtures/test_warn/test/other_file.txt

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

lib/mix/test/fixtures/umbrella_test/apps/bar/mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule Bar.MixProject do
77
version: "0.1.0",
88
# Choose something besides *_test.exs so that these test files don't
99
# get accidentally swept up into the actual Mix test suite.
10-
test_pattern: "*_tests.exs",
10+
test_load_pattern: ~r/.*_tests\.exs/,
1111
test_coverage: [ignore_modules: [Bar, ~r/Ignore/]],
1212
aliases: [mytask: fn _ -> Mix.shell().info("bar_running") end]
1313
]

0 commit comments

Comments
 (0)