Skip to content

Commit a0df137

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 `warn_test_pattern` configuration, a deprecation warning is logged. The new warnings can be disabled by setting `test_warn_pattern` to a falsy value. Projects with an existing custom `test_pattern` should check if their pattern conflicts with the new `test_load_pattern`.
1 parent b75cccc commit a0df137

File tree

12 files changed

+117
-17
lines changed

12 files changed

+117
-17
lines changed

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

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,21 @@ 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_pattern` - a pattern to find potential test files.
232+
Defaults to `*.{ex,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_load_pattern` - a regular expression to load test files.
235+
Defaults to `~r/.*_test\\.exs$/`
236+
237+
* `:test_warn_pattern` - a regular expression to match potentially
238+
misnamed test files and display a warning.
239+
Defaults to `~r/^(?!.*_helper\.exs$)(?:.*_test\.ex|.*\.exs)$/`,
240+
matching any file that ends in `_test.ex` or `.exs`, ignoring files
241+
ending in `_helper.exs`. Warnings can be disabled by setting this to
242+
`nil` or `false`.
243+
244+
* `:test_warn_ignore_files` - a list of files to ignore when displaying
245+
warnings about potentially misnamed test files. Defaults to `[]`.
235246
236247
## Coloring
237248
@@ -595,20 +606,50 @@ defmodule Mix.Tasks.Test do
595606

596607
# Prepare and extract all files to require and run
597608
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"
609+
test_pattern = project[:test_pattern] || "*.{ex,exs}"
610+
test_load_pattern = project[:test_load_pattern] || ~r/.*_test\.exs$/
611+
612+
test_warn_pattern =
613+
Keyword.get(project, :test_warn_pattern, ~r/^(?!.*_helper\.exs$)(?:.*_test\.ex|.*\.exs)$/)
614+
615+
test_warn_ignore_files = project[:test_warn_ignore_files] || []
616+
617+
# Warn about deprecated configurations
618+
if project[:test_pattern] do
619+
Mix.shell().info(
620+
"warning: the `:test_pattern` configuration is deprecated and will be ignored. Use `:test_load_pattern` instead."
621+
)
622+
end
623+
624+
if project[:warn_test_pattern] do
625+
Mix.shell().info(
626+
"warning: the `:warn_test_pattern` configuration is deprecated and will be ignored. Use `:test_warn_pattern` instead."
627+
)
628+
end
600629

601630
{test_files, test_opts} =
602631
if files != [], do: ExUnit.Filters.parse_paths(files), else: {test_paths, []}
603632

604-
unfiltered_test_files = Mix.Utils.extract_files(test_files, test_pattern)
633+
# get a list of all files in the test folders, which we filter by the test_load_pattern
634+
potential_test_files = Mix.Utils.extract_files(test_files, test_pattern)
635+
636+
unfiltered_test_files =
637+
Enum.filter(potential_test_files, &Regex.match?(test_load_pattern, &1))
605638

606639
matched_test_files =
607640
unfiltered_test_files
608641
|> filter_to_allowed_files(allowed_files)
609642
|> filter_by_partition(shell, partitions)
610643

611-
display_warn_test_pattern(test_files, test_pattern, unfiltered_test_files, warn_test_pattern)
644+
# do not warn if test_warn_pattern is nil or false
645+
test_warn_pattern &&
646+
maybe_warn_misnamed_test_files(
647+
potential_test_files,
648+
unfiltered_test_files,
649+
test_warn_ignore_files,
650+
test_load_pattern,
651+
test_warn_pattern
652+
)
612653

613654
try do
614655
Enum.each(test_paths, &require_test_helper(shell, &1))
@@ -679,13 +720,31 @@ defmodule Mix.Tasks.Test do
679720
end
680721
end
681722

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
723+
defp maybe_warn_misnamed_test_files(
724+
potential_test_files,
725+
unfiltered_test_files,
726+
test_warn_ignore_files,
727+
test_load_pattern,
728+
test_warn_pattern
729+
)
730+
when is_struct(test_warn_pattern, Regex) do
731+
missing_files = (potential_test_files -- unfiltered_test_files) -- test_warn_ignore_files
684732

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

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)