From 5137c33098a2413649bd77cb2e634d00972fde7b Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Mon, 9 Sep 2024 17:42:11 +0900 Subject: [PATCH] Do not allow guards in assert/1 (#13817) --- lib/ex_unit/lib/ex_unit/assertions.ex | 23 +++++++++++++++++ lib/ex_unit/test/ex_unit/assertions_test.exs | 27 ++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/lib/ex_unit/lib/ex_unit/assertions.ex b/lib/ex_unit/lib/ex_unit/assertions.ex index 139dcbee451..3ac413dc8a1 100644 --- a/lib/ex_unit/lib/ex_unit/assertions.ex +++ b/lib/ex_unit/lib/ex_unit/assertions.ex @@ -121,6 +121,12 @@ defmodule ExUnit.Assertions do Even though the match works, `assert` still expects a truth value. In such cases, simply use `==/2` or `match?/2`. + + If you need more complex pattern matching using guards, you + need to use `match?/2`: + + assert match?([%{id: id} | _] when is_integer(id), records) + """ defmacro assert({:=, meta, [left, right]} = assertion) do code = escape_quoted(:assert, meta, assertion) @@ -357,6 +363,23 @@ defmodule ExUnit.Assertions do end @doc false + def __match__({:when, _, _} = left, right, _, _, _) do + suggestion = + quote do + assert match?(unquote(left), unquote(right)) + end + + raise ArgumentError, """ + invalid pattern in assert/1: + + #{Macro.to_string(left) |> Inspect.Error.pad(2)} + + To assert with guards, use match?/2: + + #{Macro.to_string(suggestion) |> Inspect.Error.pad(2)} + """ + end + def __match__(left, right, code, check, caller) do left = __expand_pattern__(left, caller) vars = collect_vars_from_pattern(left) diff --git a/lib/ex_unit/test/ex_unit/assertions_test.exs b/lib/ex_unit/test/ex_unit/assertions_test.exs index dcbf338afc8..17dc6b98350 100644 --- a/lib/ex_unit/test/ex_unit/assertions_test.exs +++ b/lib/ex_unit/test/ex_unit/assertions_test.exs @@ -256,6 +256,33 @@ defmodule ExUnit.AssertionsTest do end end + test "assert match with `when` in the pattern fails" do + message = """ + invalid pattern in assert\/1: + + x when is_map(x) + + To assert with guards, use match?/2: + + assert match?(x when is_map(x), %{}) + """ + + assert_raise ArgumentError, message, fn -> + Code.eval_string(""" + defmodule AssertGuard do + import ExUnit.Assertions + + def run do + assert (x when is_map(x)) = %{} + end + end + """) + end + after + :code.purge(AssertGuard) + :code.delete(AssertGuard) + end + test "assert match with __ENV__ in the pattern" do message = ExUnit.CaptureIO.capture_io(:stderr, fn ->