Skip to content

Commit 3c0d26c

Browse files
author
José Valim
committed
Raise if trying to override reserved tag, closes #4236
1 parent f7a31ac commit 3c0d26c

File tree

3 files changed

+88
-26
lines changed

3 files changed

+88
-26
lines changed

lib/ex_unit/lib/ex_unit/callbacks.ex

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ defmodule ExUnit.Callbacks do
146146

147147
## Helpers
148148

149+
@reserved ~w(case test line file capture_log skip timeout report async)a
150+
149151
@doc false
150152
def __merge__(_mod, context, :ok) do
151153
{:ok, context}
@@ -163,12 +165,16 @@ defmodule ExUnit.Callbacks do
163165
raise_merge_failed!(mod, data)
164166
end
165167

166-
defp context_merge(_mod, context, %{} = data) do
167-
Map.merge(context, data)
168+
defp context_merge(mod, context, %{} = data) do
169+
Map.merge(context, data, fn
170+
_, v, v -> v
171+
k, _, v when k in @reserved -> raise_merge_reserved!(mod, k, v)
172+
_, _, v -> v
173+
end)
168174
end
169175

170-
defp context_merge(_mod, context, data) when is_list(data) do
171-
Enum.into(data, context)
176+
defp context_merge(mod, context, data) when is_list(data) do
177+
context_merge(mod, context, Map.new(data))
172178
end
173179

174180
defp context_merge(mod, _context, data) do
@@ -177,7 +183,12 @@ defmodule ExUnit.Callbacks do
177183

178184
defp raise_merge_failed!(mod, data) do
179185
raise "expected ExUnit callback in #{inspect mod} to return :ok " <>
180-
" or {:ok, keyword | map}, got #{inspect data} instead"
186+
"or {:ok, keyword | map}, got #{inspect data} instead"
187+
end
188+
189+
defp raise_merge_reserved!(mod, key, value) do
190+
raise "expected ExUnit callback in #{inspect mod} is trying to set " <>
191+
"reserved field #{inspect key} to #{inspect value}"
181192
end
182193

183194
defp escape(contents) do

lib/ex_unit/lib/ex_unit/case.ex

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ defmodule ExUnit.Case do
114114
The following tags are set automatically by ExUnit and are
115115
therefore reserved:
116116
117-
* `:case` - the test case module
118-
* `:test` - the test name
119-
* `:line` - the line on which the test was defined
120-
* `:file` - the file on which the test was defined
117+
* `:case` - the test case module
118+
* `:test` - the test name
119+
* `:line` - the line on which the test was defined
120+
* `:file` - the file on which the test was defined
121+
* `:async` - if the test case is in async mode
121122
122123
The following tags customize how tests behaves:
123124
@@ -174,7 +175,7 @@ defmodule ExUnit.Case do
174175
175176
This default can be overriden by `@tag capture_log: false` or `@moduletag capture_log: false`.
176177
177-
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
178+
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
178179
between tests) are never captured. If you want to suppress these messages as well, remove the
179180
console backend globally:
180181
@@ -193,20 +194,16 @@ defmodule ExUnit.Case do
193194
end
194195

195196
quote do
197+
async = !!unquote(async)
198+
196199
unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
197200
Enum.each [:ex_unit_tests, :tag, :moduletag],
198201
&Module.register_attribute(__MODULE__, &1, accumulate: true)
199202

200-
if unquote(async) do
201-
@moduletag async: true
202-
ExUnit.Server.add_async_case(__MODULE__)
203-
else
204-
@moduletag async: false
205-
ExUnit.Server.add_sync_case(__MODULE__)
206-
end
207-
203+
@moduletag async: async
208204
@before_compile ExUnit.Case
209205
@ex_unit_test_names %{}
206+
@ex_unit_async async
210207
use ExUnit.Callbacks
211208
end
212209

@@ -254,7 +251,7 @@ defmodule ExUnit.Case do
254251

255252
quote bind_quoted: binding do
256253
test = :"test #{message}"
257-
ExUnit.Case.__on_definition__(__ENV__, test)
254+
ExUnit.Case.__on_definition__(__ENV__, test, [])
258255
def unquote(test)(unquote(var)), do: unquote(contents)
259256
end
260257
end
@@ -284,15 +281,20 @@ defmodule ExUnit.Case do
284281
@doc false
285282
defmacro __before_compile__(_) do
286283
quote do
284+
if @ex_unit_async do
285+
ExUnit.Server.add_async_case(__MODULE__)
286+
else
287+
ExUnit.Server.add_sync_case(__MODULE__)
288+
end
289+
287290
def __ex_unit__(:case) do
288291
%ExUnit.TestCase{name: __MODULE__, tests: @ex_unit_tests}
289292
end
290293
end
291294
end
292295

293296
@doc false
294-
def __on_definition__(env, name, tags \\ []) do
295-
mod = env.module
297+
def __on_definition__(%{module: mod, file: file, line: line}, name, tags) do
296298
moduletag = Module.get_attribute(mod, :moduletag)
297299

298300
unless moduletag do
@@ -304,7 +306,7 @@ defmodule ExUnit.Case do
304306
(tags ++ Module.get_attribute(mod, :tag) ++ moduletag)
305307
|> normalize_tags
306308
|> validate_tags
307-
|> Map.merge(%{line: env.line, file: env.file})
309+
|> Map.merge(%{line: line, file: file})
308310

309311
test = %ExUnit.Test{name: name, case: mod, tags: tags}
310312
test_names = Module.get_attribute(mod, :ex_unit_test_names)

lib/ex_unit/test/ex_unit_test.exs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ defmodule ExUnitTest do
152152
refute output =~ "[debug] four"
153153
end
154154

155-
test "it supports multi errors" do
155+
test "supports multi errors" do
156156
capture_io :stderr, fn ->
157157
defmodule MultiTest do
158158
use ExUnit.Case
@@ -187,7 +187,7 @@ defmodule ExUnitTest do
187187
assert output =~ "Failure #2"
188188
end
189189

190-
test "it registers only the first test with any given name" do
190+
test "registers only the first test with any given name" do
191191
capture_io :stderr, fn ->
192192
defmodule TestWithSameNames do
193193
use ExUnit.Case
@@ -207,7 +207,7 @@ defmodule ExUnitTest do
207207
end) =~ "1 test, 0 failure"
208208
end
209209

210-
test "it produces error on not implemented tests" do
210+
test "produces error on not implemented tests" do
211211
defmodule TestNotImplemented do
212212
use ExUnit.Case
213213

@@ -227,7 +227,7 @@ defmodule ExUnitTest do
227227
assert output =~ "1 test, 1 failure"
228228
end
229229

230-
test "it skips tagged test with skip" do
230+
test "skips tagged test with skip" do
231231
defmodule TestSkipped do
232232
use ExUnit.Case
233233

@@ -250,6 +250,55 @@ defmodule ExUnitTest do
250250
assert output =~ "2 tests, 0 failures, 2 skipped"
251251
end
252252

253+
test "raises on reserved tag in module" do
254+
assert_raise RuntimeError, "cannot set tag :file because it is reserved by ExUnit", fn ->
255+
defmodule ReservedTag do
256+
use ExUnit.Case
257+
258+
setup do
259+
{:ok, file: :foo}
260+
end
261+
262+
@tag file: "oops"
263+
test "sample", do: :ok
264+
end
265+
end
266+
end
267+
268+
test "raises on reserved tag in setup" do
269+
defmodule ReservedSetupTag do
270+
use ExUnit.Case
271+
272+
setup do
273+
{:ok, file: :foo}
274+
end
275+
276+
test "sample", do: :ok
277+
end
278+
279+
output = capture_io(fn ->
280+
assert ExUnit.run == %{failures: 1, skipped: 0, total: 1}
281+
end)
282+
283+
assert output =~ "trying to set reserved field :file"
284+
end
285+
286+
test "does not raise on reserved tag in setup_all (lower priority)" do
287+
defmodule ReservedSetupAllTag do
288+
use ExUnit.Case
289+
290+
setup_all do
291+
{:ok, file: :foo}
292+
end
293+
294+
test "sample", do: :ok
295+
end
296+
297+
capture_io(fn ->
298+
assert ExUnit.run == %{failures: 0, skipped: 0, total: 1}
299+
end)
300+
end
301+
253302
defp run_with_filter(filters, {async, sync, load_us}) do
254303
opts = Keyword.merge(ExUnit.configuration, filters)
255304
output = capture_io fn ->

0 commit comments

Comments
 (0)