Skip to content

Commit 3afc1b3

Browse files
authored
Add slowest_modules option to mix test (#13409)
1 parent ce2210c commit 3afc1b3

File tree

5 files changed

+86
-3
lines changed

5 files changed

+86
-3
lines changed

lib/ex_unit/lib/ex_unit.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ defmodule ExUnit do
321321
ExUnit with slow test reporting automatically runs in `trace` mode. It
322322
is disabled by default;
323323
324+
* `:slowest_modules` - prints timing information for the N slowest test modules. Running
325+
ExUnit with slow test reporting automatically runs in `trace` mode. It
326+
is disabled by default;
327+
324328
* `:stacktrace_depth` - configures the stacktrace depth to be used
325329
on formatting and reporters, defaults to `20`;
326330
@@ -532,7 +536,7 @@ defmodule ExUnit do
532536
end
533537

534538
defp put_slowest(opts) do
535-
if opts[:slowest] > 0 do
539+
if opts[:slowest] > 0 or opts[:slowest_modules] > 0 do
536540
Keyword.put(opts, :trace, true)
537541
else
538542
opts

lib/ex_unit/lib/ex_unit/cli_formatter.ex

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ defmodule ExUnit.CLIFormatter do
1616
colors: colors(opts),
1717
width: get_terminal_width(),
1818
slowest: opts[:slowest],
19+
slowest_modules: opts[:slowest_modules],
1920
test_counter: %{},
2021
test_timings: [],
2122
failure_counter: 0,
@@ -45,6 +46,10 @@ defmodule ExUnit.CLIFormatter do
4546
IO.puts(format_slowest_tests(config, times_us.run))
4647
end
4748

49+
if config.slowest_modules > 0 do
50+
IO.puts(format_slowest_modules(config, times_us.run))
51+
end
52+
4853
print_summary(config, false)
4954
{:noreply, config}
5055
end
@@ -266,13 +271,49 @@ defmodule ExUnit.CLIFormatter do
266271
]
267272
end
268273

274+
defp format_slowest_modules(%{slowest_modules: slowest, test_timings: timings}, run_us) do
275+
slowest_tests =
276+
timings
277+
|> Enum.group_by(
278+
fn %{module: module, tags: tags} ->
279+
{module, tags.file}
280+
end,
281+
fn %{time: time} -> time end
282+
)
283+
|> Enum.into([], fn {{module, trace_test_file_line}, timings} ->
284+
{module, trace_test_file_line, Enum.sum(timings)}
285+
end)
286+
|> Enum.sort_by(fn {_module, _, sum_timings} -> sum_timings end, :desc)
287+
|> Enum.take(slowest)
288+
289+
slowest_us =
290+
Enum.reduce(slowest_tests, 0, fn {_module, _, sum_timings}, acc ->
291+
acc + sum_timings
292+
end)
293+
294+
slowest_time = slowest_us |> normalize_us() |> format_us()
295+
percentage = Float.round(slowest_us / run_us * 100, 1)
296+
297+
[
298+
"\nTop #{slowest} slowest (#{slowest_time}s), #{percentage}% of total time:\n\n"
299+
| Enum.map(slowest_tests, &format_slow_module/1)
300+
]
301+
end
302+
269303
defp format_slow_test(%ExUnit.Test{time: time, module: module} = test) do
270304
"#{trace_test_started(test)} (#{inspect(module)}) (#{format_us(time)}ms) " <>
271305
"[#{trace_test_file_line(test)}]\n"
272306
end
273307

274-
defp update_test_timings(%{slowest: slowest} = config, %ExUnit.Test{} = test) do
275-
if slowest > 0 do
308+
defp format_slow_module({module, test_file_path, timings}) do
309+
"#{inspect(module)} (#{format_us(timings)}ms)\n [#{Path.relative_to_cwd(test_file_path)}]\n"
310+
end
311+
312+
defp update_test_timings(
313+
%{slowest: slowest, slowest_modules: slowest_modules} = config,
314+
%ExUnit.Test{} = test
315+
) do
316+
if slowest > 0 or slowest_modules > 0 do
276317
# Do not store logs, as they are not used for timings and consume memory.
277318
update_in(config.test_timings, &[%{test | logs: ""} | &1])
278319
else

lib/ex_unit/mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ defmodule ExUnit.MixProject do
3030
rand_algorithm: :exsss,
3131
refute_receive_timeout: 100,
3232
slowest: 0,
33+
slowest_modules: 0,
3334
stacktrace_depth: 20,
3435
timeout: 60000,
3536
trace: false,

lib/ex_unit/test/ex_unit_test.exs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,41 @@ defmodule ExUnitTest do
239239
assert output =~ ~r"\* test delayed \(.+ms\)"
240240
end
241241

242+
test "reports slow test modules" do
243+
defmodule SlowTestModule do
244+
use ExUnit.Case
245+
246+
test "slow" do
247+
refute false
248+
end
249+
end
250+
251+
defmodule SlowerTestModule do
252+
use ExUnit.Case
253+
254+
test "slower" do
255+
Process.sleep(5)
256+
refute false
257+
end
258+
end
259+
260+
defmodule SlowestTestModule do
261+
use ExUnit.Case
262+
263+
test "slowest" do
264+
Process.sleep(10)
265+
refute false
266+
end
267+
end
268+
269+
configure_and_reload_on_exit(slowest_modules: 2)
270+
271+
output = capture_io(fn -> ExUnit.run() end)
272+
assert output =~ ~r"Top 2 slowest \(\d+\.\d+s\), \d+.\d% of total time:"
273+
assert output =~ ~r"SlowestTestModule \(.+ms\)"
274+
assert output =~ ~r"SlowerTestModule \(.+ms\)"
275+
end
276+
242277
test "sets max cases to one with trace enabled" do
243278
configure_and_reload_on_exit(trace: true, max_cases: 10)
244279
config = ExUnit.configuration()

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ defmodule Mix.Tasks.Test do
447447
listen_on_stdin: :boolean,
448448
formatter: :keep,
449449
slowest: :integer,
450+
slowest_modules: :integer,
450451
partitions: :integer,
451452
preload_modules: :boolean,
452453
warnings_as_errors: :boolean,
@@ -681,6 +682,7 @@ defmodule Mix.Tasks.Test do
681682
:formatters,
682683
:colors,
683684
:slowest,
685+
:slowest_modules,
684686
:failures_manifest_file,
685687
:only_test_ids,
686688
:test_location_relative_path,

0 commit comments

Comments
 (0)