Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/ex_unit/lib/ex_unit/assertions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ 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)
Expand Down Expand Up @@ -357,6 +362,14 @@ defmodule ExUnit.Assertions do
end

@doc false
def __match__({:when, _, _} = left, _, _, _, _) do
raise ArgumentError, """
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the best way to raise here would be.
Ideally this would be a CompileError.

BTW, I noticed that right now without assert we're getting:

error: undefined function when/2 (there is no such import)

But I think we can have a better error message in this case, given that when is a reserved word and can't be used as a function name?

invalid pattern in assert/1: #{Macro.to_string(left)}

Use assert match?(... when ...) if you need to assert with a guard.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could even print out the actual code they were trying to use, right? We have all the ASTs and stuff. Maybe

Use this if you need to assert with a guard:

    assert match?(actual_code)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it, but I can see how it could end up with a big suggestion if the pattern is a huge map for instance (and Macro.to_string/2 doesn't have a limit like inspect).
What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure someone would assert on a huge map with a guard at the end, as in I’m not sure that's a common pattern. If that ends up happening, you could always format the code to make it fit nicely.

Copy link
Contributor Author

@sabiwara sabiwara Sep 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM! I went with 6cb2a91, WDYT?

"""
end

def __match__(left, right, code, check, caller) do
left = __expand_pattern__(left, caller)
vars = collect_vars_from_pattern(left)
Expand Down
17 changes: 17 additions & 0 deletions lib/ex_unit/test/ex_unit/assertions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ defmodule ExUnit.AssertionsTest do
end
end

test "assert match with `when` in the pattern fails" do
assert_raise ArgumentError, ~r/invalid pattern in assert\/1: x when is_map\(x\)/, 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 ->
Expand Down
Loading