Skip to content

Commit 03b9fde

Browse files
committed
Allow test names longer than 255 characters
1 parent f16d2a3 commit 03b9fde

6 files changed

Lines changed: 76 additions & 48 deletions

File tree

lib/ex_unit/lib/ex_unit.ex

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,28 @@ defmodule ExUnit do
143143
144144
It is received by formatters and contains the following fields:
145145
146-
* `:name` - the test name
146+
* `:name` - the test name. This is the function name in the test module,
147+
which is truncated and hashed when above 250 characters
147148
* `:module` - the test module
149+
* `:description` - the test description (the name without truncation)
148150
* `:state` - the finished test state (see `t:ExUnit.state/0`)
149151
* `:time` - the duration in microseconds of the test's runtime
150152
* `:tags` - the test tags
151153
* `:logs` - the captured logs
152154
* `:parameters` - the test parameters
153155
154156
"""
155-
defstruct [:name, :case, :module, :state, time: 0, tags: %{}, logs: "", parameters: %{}]
157+
defstruct [
158+
:name,
159+
:description,
160+
:case,
161+
:module,
162+
:state,
163+
time: 0,
164+
tags: %{},
165+
logs: "",
166+
parameters: %{}
167+
]
156168

157169
# TODO: Remove the `:case` field on v2.0
158170
@type t :: %__MODULE__{
@@ -163,7 +175,8 @@ defmodule ExUnit do
163175
time: non_neg_integer,
164176
tags: map,
165177
logs: String.t(),
166-
parameters: map
178+
parameters: map,
179+
description: String.t()
167180
}
168181
end
169182

lib/ex_unit/lib/ex_unit/case.ex

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -695,17 +695,18 @@ defmodule ExUnit.Case do
695695
moduletag = Module.get_attribute(mod, :moduletag)
696696
tag = Module.delete_attribute(mod, :tag)
697697

698-
{name, describe, describe_line, describetag} =
698+
{description, describe, describe_line, describetag} =
699699
case Module.get_attribute(mod, :ex_unit_describe) do
700700
{line, describe, _counter} ->
701-
test_name = validate_test_name("#{test_type} #{describe} #{name}")
702-
{test_name, describe, line, Module.get_attribute(mod, :describetag)}
701+
{"#{test_type} #{describe} #{name}", describe, line,
702+
Module.get_attribute(mod, :describetag)}
703703

704704
nil ->
705-
test_name = validate_test_name("#{test_type} #{name}")
706-
{test_name, nil, nil, []}
705+
{"#{test_type} #{name}", nil, nil, []}
707706
end
708707

708+
name = build_test_name(description)
709+
709710
if Module.defines?(mod, {name, 1}) do
710711
raise ArgumentError, ~s("#{name}" is already defined in #{inspect(mod)})
711712
end
@@ -723,7 +724,7 @@ defmodule ExUnit.Case do
723724
test_type: test_type
724725
})
725726

726-
test = %ExUnit.Test{name: name, case: mod, tags: tags, module: mod}
727+
test = %ExUnit.Test{name: name, description: description, case: mod, tags: tags, module: mod}
727728
Module.put_attribute(mod, :ex_unit_tests, test)
728729

729730
for attribute <- Module.get_attribute(mod, :ex_unit_registered_test_attributes) do
@@ -733,15 +734,8 @@ defmodule ExUnit.Case do
733734
name
734735
end
735736

736-
@doc """
737-
Registers a test with the given environment.
738-
739-
This function is deprecated in favor of `register_test/6` which performs
740-
better under tight loops by avoiding `__ENV__`.
741-
"""
742737
# TODO: Remove me Elixir v2.0
743738
@deprecated "Use register_test/6 instead"
744-
@doc since: "1.3.0"
745739
def register_test(%{module: mod, file: file, line: line}, test_type, name, tags) do
746740
register_test(mod, file, line, test_type, name, tags)
747741
end
@@ -925,7 +919,12 @@ defmodule ExUnit.Case do
925919
tags
926920
end
927921

928-
defp validate_test_name(name) do
922+
defp build_test_name(name) when byte_size(name) > 255 do
923+
hash = :erlang.md5(name) |> binary_slice(0, 3) |> Base.encode64()
924+
build_test_name(String.byte_slice(name, 0, 246) <> "... " <> hash)
925+
end
926+
927+
defp build_test_name(name) do
929928
try do
930929
String.to_atom(name)
931930
rescue

lib/ex_unit/lib/ex_unit/cli_formatter.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ defmodule ExUnit.CLIFormatter do
240240
end
241241

242242
defp trace_test_started(test) do
243-
String.replace(" * #{test.name}", "\n", " ")
243+
String.replace(" * #{test.description}", "\n", " ")
244244
end
245245

246246
defp trace_test_result(test) do
@@ -256,7 +256,7 @@ defmodule ExUnit.CLIFormatter do
256256
end
257257

258258
defp trace_aborted(%ExUnit.Test{} = test) do
259-
"* #{test.name} [#{trace_test_file_line(test)}]"
259+
"* #{test.description} [#{trace_test_file_line(test)}]"
260260
end
261261

262262
defp trace_aborted(%ExUnit.TestModule{name: name, file: file}) do

lib/ex_unit/lib/ex_unit/formatter.ex

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ defmodule ExUnit.Formatter do
259259
260260
iex> failure = {:error, catch_error(raise "oops"), _stacktrace = []}
261261
iex> formatter_cb = fn _key, value -> value end
262-
iex> test = %ExUnit.Test{name: :"it works", module: MyTest, tags: %{file: "file.ex", line: 7}}
262+
iex> test = %ExUnit.Test{name: :"it works", description: "it works", module: MyTest, tags: %{file: "file.ex", line: 7}}
263263
iex> format_test_failure(test, [failure], 1, 80, formatter_cb)
264264
" 1) it works (MyTest)\n file.ex:7\n ** (RuntimeError) oops\n"
265265
@@ -273,9 +273,15 @@ defmodule ExUnit.Formatter do
273273
) :: String.t()
274274
when failure: {atom, term, Exception.stacktrace()}
275275
def format_test_failure(test, failures, counter, width, formatter) do
276-
%ExUnit.Test{name: name, module: module, tags: tags, parameters: parameters} = test
277-
278-
test_info(with_counter(counter, "#{name} (#{inspect(module)})"), formatter) <>
276+
%ExUnit.Test{
277+
name: name,
278+
description: description,
279+
module: module,
280+
tags: tags,
281+
parameters: parameters
282+
} = test
283+
284+
test_info(with_counter(counter, "#{description} (#{inspect(module)})"), formatter) <>
279285
test_parameters(parameters, formatter) <>
280286
test_location(with_location(tags), formatter) <>
281287
Enum.map_join(Enum.with_index(failures), "", fn {{kind, reason, stack}, index} ->

lib/ex_unit/test/ex_unit/case_test.exs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -127,30 +127,6 @@ defmodule ExUnit.CaseTest do
127127
end
128128
end
129129

130-
test "raises when name is longer than 255 characters" do
131-
assert_raise SystemLimitError,
132-
~r/must be shorter than 255 characters, got: "test a{256}"/,
133-
fn ->
134-
defmodule LongNameTest do
135-
use ExUnit.Case
136-
137-
test String.duplicate("a", 256)
138-
end
139-
end
140-
141-
assert_raise SystemLimitError,
142-
~r/must be shorter than 255 characters, got: "test a{100} a{156}"/,
143-
fn ->
144-
defmodule LongDescribeNameTest do
145-
use ExUnit.Case
146-
147-
describe String.duplicate("a", 100) do
148-
test String.duplicate("a", 156)
149-
end
150-
end
151-
end
152-
end
153-
154130
test "warns when using @tag outside of describe" do
155131
stderr =
156132
ExUnit.CaptureIO.capture_io(:stderr, fn ->

lib/ex_unit/test/ex_unit/formatter_test.exs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,23 @@ defmodule ExUnit.FormatterTest do
2525
end
2626

2727
defp test do
28-
%ExUnit.Test{name: :world, module: Hello, tags: %{file: __ENV__.file, line: 1}}
28+
%ExUnit.Test{
29+
name: :world,
30+
description: "world",
31+
module: Hello,
32+
tags: %{file: __ENV__.file, line: 1}
33+
}
34+
end
35+
36+
defp long_test do
37+
long_name = "test " <> String.duplicate("a", 300)
38+
39+
%ExUnit.Test{
40+
name: String.to_atom("truncated"),
41+
description: long_name,
42+
module: Hello,
43+
tags: %{file: __ENV__.file, line: 1}
44+
}
2945
end
3046

3147
def falsy() do
@@ -622,4 +638,22 @@ defmodule ExUnit.FormatterTest do
622638
** (ExUnit.FormatterTest.BadMessage) #{message}
623639
"""
624640
end
641+
642+
test "formats long test name using full description" do
643+
failure = [{:error, catch_error(raise "oops"), []}]
644+
long = long_test()
645+
646+
result = format_test_failure(long, failure, 1, 80, &formatter/2)
647+
assert result =~ "test #{String.duplicate("a", 300)} (Hello)"
648+
end
649+
650+
test "formats long test name with assertions using full description" do
651+
assertion_error = catch_assertion(assert 1 == 2)
652+
failure = [{:error, assertion_error, []}]
653+
long = long_test()
654+
655+
result = format_test_failure(long, failure, 1, 80, &formatter/2)
656+
assert result =~ "test #{String.duplicate("a", 300)} (Hello)"
657+
assert result =~ "Assertion with == failed"
658+
end
625659
end

0 commit comments

Comments
 (0)