Skip to content

Commit 352bc46

Browse files
authored
Improve ExUnit failure/success output (#15168)
1 parent 74d6183 commit 352bc46

5 files changed

Lines changed: 125 additions & 61 deletions

File tree

lib/ex_unit/lib/ex_unit/cli_formatter.ex

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ defmodule ExUnit.CLIFormatter do
2727
test_counter: %{},
2828
test_timings: [],
2929
failure_counter: 0,
30+
failure_type_counter: %{},
3031
skipped_counter: 0,
3132
excluded_counter: 0,
3233
invalid_counter: 0
@@ -139,7 +140,14 @@ defmodule ExUnit.CLIFormatter do
139140

140141
test_counter = update_test_counter(config.test_counter, test)
141142
failure_counter = config.failure_counter + 1
142-
config = %{config | test_counter: test_counter, failure_counter: failure_counter}
143+
failure_type_counter = update_test_counter(config.failure_type_counter, test)
144+
145+
config = %{
146+
config
147+
| test_counter: test_counter,
148+
failure_counter: failure_counter,
149+
failure_type_counter: failure_type_counter
150+
}
143151

144152
{:noreply, update_test_timings(config, test)}
145153
end
@@ -176,8 +184,16 @@ defmodule ExUnit.CLIFormatter do
176184
# The failed tests have already contributed to the counter,
177185
# so we should only add the successful tests to the count
178186
config =
179-
update_in(config.failure_counter, fn counter ->
180-
counter + Enum.count(test_module.tests, &is_nil(&1.state))
187+
Enum.reduce(test_module.tests, config, fn
188+
%{state: nil} = test, acc ->
189+
%{
190+
acc
191+
| failure_counter: acc.failure_counter + 1,
192+
failure_type_counter: update_test_counter(acc.failure_type_counter, test)
193+
}
194+
195+
_test, acc ->
196+
acc
181197
end)
182198

183199
formatted =
@@ -352,11 +368,36 @@ defmodule ExUnit.CLIFormatter do
352368
defp print_summary(config, force_failures?) do
353369
test_type_counts = collect_test_type_counts(config)
354370
test_counter = test_counter_or_default(config, test_type_counts)
355-
formatted_test_type_counts = format_test_type_counts(test_counter)
356-
failure_pl = pluralize(config.failure_counter, "failure", "failures")
371+
372+
passed_total =
373+
test_type_counts - config.failure_counter - config.skipped_counter - config.invalid_counter
374+
375+
# Passed line: "Passed: 447/455 (53/54 doctests, 393/403 tests)" or
376+
# "Passed: 455 (70 tests, 14 properties)" when all pass
377+
all_passed? = passed_total == test_type_counts
378+
379+
passed_breakdown =
380+
format_passed_breakdown(test_counter, config.failure_type_counter, all_passed?)
381+
382+
passed_line =
383+
if all_passed? do
384+
"Passed: #{passed_total}"
385+
else
386+
"Passed: #{passed_total}/#{test_type_counts}"
387+
end
388+
|> if_true(passed_breakdown != "", &(&1 <> " (#{passed_breakdown})"))
389+
390+
# Failed line: "Failed: 8 tests, 1 property"
391+
failed_line =
392+
if config.failure_counter > 0 do
393+
failed_breakdown = format_type_counts(config.failure_type_counter)
394+
"\n" <> failure("Failed: #{failed_breakdown}", config)
395+
else
396+
""
397+
end
357398

358399
message =
359-
"#{formatted_test_type_counts}#{config.failure_counter} #{failure_pl}"
400+
("\n" <> passed_line)
360401
|> if_true(
361402
config.invalid_counter > 0,
362403
&(&1 <> ", #{config.invalid_counter} invalid")
@@ -372,7 +413,7 @@ defmodule ExUnit.CLIFormatter do
372413

373414
cond do
374415
config.failure_counter > 0 or force_failures? ->
375-
IO.puts(failure(message, config))
416+
IO.puts(message <> failed_line)
376417

377418
config.invalid_counter > 0 ->
378419
IO.puts(invalid(message, config))
@@ -404,14 +445,40 @@ defmodule ExUnit.CLIFormatter do
404445
IO.puts(formatted)
405446
end
406447

407-
defp format_test_type_counts(test_counter) do
408-
test_counter
448+
defp format_type_counts(type_counter) do
449+
type_counter
409450
|> Enum.sort()
410451
|> Enum.map(fn {test_type, count} ->
411-
type_pluralized = pluralize(count, test_type, ExUnit.plural_rule(test_type |> to_string()))
412-
413-
"#{count} #{type_pluralized}, "
452+
"#{count} #{pluralize_type(count, test_type)}"
414453
end)
454+
|> Enum.join(", ")
455+
end
456+
457+
defp format_passed_breakdown(test_counter, failure_type_counter, all_passed?) do
458+
# If there are no different test types, we just print "Passed: N/N"
459+
# without the type.
460+
if map_size(test_counter) in 0..1 do
461+
""
462+
else
463+
test_counter
464+
|> Map.keys()
465+
|> Enum.sort()
466+
|> Enum.map_join(", ", fn type ->
467+
total = Map.fetch!(test_counter, type)
468+
469+
if all_passed? do
470+
"#{total} #{pluralize_type(total, type)}"
471+
else
472+
failed = Map.get(failure_type_counter, type, 0)
473+
passed = total - failed
474+
"#{passed}/#{total} #{pluralize_type(total, type)}"
475+
end
476+
end)
477+
end
478+
end
479+
480+
defp pluralize_type(count, type) do
481+
pluralize(count, type, ExUnit.plural_rule(to_string(type)))
415482
end
416483

417484
defp test_counter_or_default(_config, 0) do

lib/ex_unit/test/ex_unit/callbacks_test.exs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ defmodule ExUnit.CallbacksTest do
3636
end
3737
end
3838

39-
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures"
39+
assert capture_io(fn -> ExUnit.run() end) =~ "Passed: 1"
4040
end
4141

4242
test "named callbacks run custom code in order" do
@@ -66,7 +66,7 @@ defmodule ExUnit.CallbacksTest do
6666
defp store_5(context), do: store(context, 5)
6767
end
6868

69-
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures"
69+
assert capture_io(fn -> ExUnit.run() end) =~ "Passed: 1"
7070
end
7171

7272
test "named callbacks support {module, function} tuples" do
@@ -87,7 +87,7 @@ defmodule ExUnit.CallbacksTest do
8787
def setup_3(_), do: [setup_3: true]
8888
end
8989

90-
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures"
90+
assert capture_io(fn -> ExUnit.run() end) =~ "Passed: 1"
9191
end
9292

9393
test "doesn't choke on setup errors" do
@@ -141,7 +141,7 @@ defmodule ExUnit.CallbacksTest do
141141
end
142142
end
143143

144-
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures, 1 invalid"
144+
assert capture_io(fn -> ExUnit.run() end) =~ "Passed: 0/1"
145145
end
146146

147147
test "doesn't choke on dead supervisor" do
@@ -209,7 +209,7 @@ defmodule ExUnit.CallbacksTest do
209209
end
210210
end
211211

212-
assert capture_io(fn -> ExUnit.run() end) =~ "2 tests, 2 failures"
212+
assert capture_io(fn -> ExUnit.run() end) =~ "Failed: 2 tests"
213213
end
214214

215215
defp no_formatters! do
@@ -253,7 +253,7 @@ defmodule ExUnit.CallbacksTest do
253253

254254
output = capture_io(fn -> ExUnit.run() end)
255255
assert output =~ "on_exit run"
256-
assert output =~ "1 test, 0 failures"
256+
assert output =~ "Passed: 1"
257257
end
258258

259259
test "runs multiple on_exit exits and overrides by ref" do

lib/ex_unit/test/ex_unit/doc_test_test.exs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ defmodule ExUnit.DocTestTest do
616616
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly, only: [one: 0, two: 0], import: true
617617
end
618618

619-
assert capture_io(fn -> ExUnit.run() end) =~ "2 doctests, 1 failure"
619+
assert capture_io(fn -> ExUnit.run() end) =~ "Failed: 1 doctest"
620620
end
621621

622622
test "empty :only" do
@@ -627,7 +627,7 @@ defmodule ExUnit.DocTestTest do
627627

628628
output = capture_io(fn -> ExUnit.run() end)
629629

630-
assert output =~ "0 failures"
630+
refute output =~ "Failed:"
631631
refute output =~ "doctest"
632632
end
633633

@@ -639,7 +639,7 @@ defmodule ExUnit.DocTestTest do
639639

640640
output = capture_io(fn -> ExUnit.run() end)
641641

642-
assert output =~ "0 failures"
642+
refute output =~ "Failed:"
643643
assert output =~ "2 skipped"
644644
end
645645

@@ -763,7 +763,7 @@ defmodule ExUnit.DocTestTest do
763763
assert output =~
764764
"#{stack(starting_line + 28)}ExUnit.DocTestTest.Failure (module)"
765765

766-
assert output =~ "8 doctests, 8 failures"
766+
assert output =~ "Failed: 8 doctests"
767767
end
768768

769769
test "doctest invalid" do
@@ -969,7 +969,7 @@ defmodule ExUnit.DocTestTest do
969969
#{stack(line)}ExUnit.DocTestTest.Invalid (module)
970970
"""
971971

972-
assert output =~ "10 doctests, 10 failures"
972+
assert output =~ "Failed: 10 doctests"
973973
end
974974

975975
test "pattern matching assertions in doctests" do
@@ -1075,7 +1075,7 @@ defmodule ExUnit.DocTestTest do
10751075
(for doctest at) #{location}:#{starting_line + 19}: (test)
10761076
"""
10771077

1078-
assert output =~ "10 doctests, 8 failures"
1078+
assert output =~ "Failed: 8 doctests"
10791079
end
10801080

10811081
test "IEx prefix contains a number" do
@@ -1084,7 +1084,7 @@ defmodule ExUnit.DocTestTest do
10841084
doctest ExUnit.DocTestTest.Numbered
10851085
end
10861086

1087-
assert capture_io(fn -> ExUnit.run() end) =~ "1 doctest, 0 failures"
1087+
assert capture_io(fn -> ExUnit.run() end) =~ "Passed: 1"
10881088
end
10891089

10901090
test "IEx prompt contains host" do
@@ -1253,7 +1253,7 @@ defmodule ExUnit.DocTestTest do
12531253
end
12541254

12551255
output = capture_io(fn -> ExUnit.run() end)
1256-
assert output =~ "2 doctests, 0 failures"
1256+
assert output =~ "Passed: 2"
12571257
end
12581258

12591259
test "failing" do

lib/ex_unit/test/ex_unit/register_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ defmodule ExUnit.RegisterTest do
4242

4343
assert capture_io(fn ->
4444
assert ExUnit.run() == %{failures: 0, skipped: 0, total: 2, excluded: 0}
45-
end) =~ "1 property, 1 test, 0 failures"
45+
end) =~ "Passed: 2 (1 property, 1 test)"
4646
end
4747

4848
test "plural test types" do
@@ -96,6 +96,6 @@ defmodule ExUnit.RegisterTest do
9696

9797
assert capture_io(fn ->
9898
assert ExUnit.run() == %{failures: 0, skipped: 0, total: 4, excluded: 0}
99-
end) =~ "2 properties, 2 tests, 0 failures"
99+
end) =~ "Passed: 4 (2 properties, 2 tests)"
100100
end
101101
end

0 commit comments

Comments
 (0)