Current section

15 Versions

Jump to

Compare versions

4 files changed
+93 additions
-20 deletions
  @@ -15,4 +15,4 @@
15 15 {<<"maintainers">>,[<<"Takayuki Matsubara">>]}.
16 16 {<<"name">>,<<"power_assert">>}.
17 17 {<<"requirements">>,[]}.
18 - {<<"version">>,<<"0.0.5">>}.
18 + {<<"version">>,<<"0.0.6">>}.
  @@ -4,6 +4,7 @@ defmodule PowerAssert.Assertion do
4 4 """
5 5
6 6 @assign_len 3 # length of " = "
7 + @equal_len 4 # length of " == "
7 8
8 9 @doc """
9 10 assert with descriptive messages
  @@ -15,11 +16,16 @@ defmodule PowerAssert.Assertion do
15 16 | 3
16 17 [1, 2, 3]
17 18 """
18 - defmacro assert({:=, _, [left, right]} = ast) do
19 + defmacro assert(ast, msg \\ nil)
20 +
21 + defmacro assert({:=, _, [left, right]} = ast, msg) do
19 22 # Almost the same code as ExUnit but rhs is displayed in detail
20 23 code = Macro.escape(ast)
24 + [_|t] = String.split(Macro.to_string(ast), " = ")
25 + rhs_expr = Enum.join(t, " = ")
21 26 rhs_index = (Macro.to_string(left) |> String.length) + @assign_len
22 - injected_rhs_ast = inject_store_code(right, rhs_index)
27 + injected_rhs_ast = inject_store_code(right, rhs_expr, rhs_index)
28 + message_ast = message_ast(msg)
23 29
24 30 {:if, meta, args} =
25 31 quote do
  @@ -27,6 +33,7 @@ defmodule PowerAssert.Assertion do
27 33 right
28 34 else
29 35 message = PowerAssert.Assertion.render_values(expr, values)
36 + unquote(message_ast)
30 37 raise ExUnit.AssertionError,
31 38 message: "Expected truthy, got #{inspect right}\n\n" <> message
32 39 end
  @@ -40,6 +47,7 @@ defmodule PowerAssert.Assertion do
40 47 unquote(return)
41 48 _ ->
42 49 message = PowerAssert.Assertion.render_values(expr, values)
50 + unquote(message_ast)
43 51 raise ExUnit.AssertionError,
44 52 message: message
45 53 end
  @@ -53,18 +61,36 @@ defmodule PowerAssert.Assertion do
53 61 end
54 62 end
55 63
56 - defmacro assert(ast, msg \\ nil) do
64 + defmacro assert({:==, _, [left, right]} = ast, msg) do
57 65 code = Macro.escape(ast)
58 - injected_ast = inject_store_code(ast)
66 + [lhs_expr|t] = String.split(Macro.to_string(ast), " == ")
67 + rhs_expr = Enum.join(t, " == ")
68 + injected_lhs_ast = inject_store_code(left, lhs_expr)
69 + rhs_index = (Macro.to_string(left) |> String.length) + @equal_len
70 + injected_rhs_ast = inject_store_code(right, rhs_expr, rhs_index)
71 + message_ast = message_ast(msg)
59 72
60 - # for avoid "this check/guard will always yield the same result"
61 - message_ast = if msg do
62 73 quote do
63 - message = "#{unquote(msg)}\n\n" <> message
74 + unquote(injected_lhs_ast)
75 + left = result
76 + left_values = values
77 + unquote(injected_rhs_ast)
78 + # wrap result for avoid warning: this check/guard will always yield the same result
79 + unless left == fn(x) -> x end.(result) do
80 + message = PowerAssert.Assertion.render_values(unquote(code), left_values ++ values, left, result)
81 + unquote(message_ast)
82 + raise ExUnit.AssertionError,
83 + message: message
64 84 end
65 - else
66 - nil
85 + result
67 86 end
87 + end
88 +
89 + defmacro assert(ast, msg) do
90 + code = Macro.escape(ast)
91 + injected_ast = inject_store_code(ast, Macro.to_string(ast))
92 +
93 + message_ast = message_ast(msg)
68 94
69 95 quote do
70 96 unquote(injected_ast)
  @@ -78,9 +104,17 @@ defmodule PowerAssert.Assertion do
78 104 end
79 105 end
80 106
107 + # for avoid "this check/guard will always yield the same result"
108 + defp message_ast(msg) when is_binary(msg) do
109 + quote do
110 + message = "#{unquote(msg)}\n\n" <> message
111 + end
112 + end
113 + defp message_ast(_msg), do: nil
114 +
81 115 @doc false
82 - def inject_store_code(ast, default_index \\ 0) do
83 - positions = detect_position(ast, default_index)
116 + def inject_store_code(ast, expr, default_index \\ 0) do
117 + positions = detect_position(ast, expr, default_index)
84 118 {injected_ast, {_, _}} = PowerAssert.Ast.traverse(ast, {Enum.reverse(positions), 0}, &pre_catcher/2, &catcher/2)
85 119 # IO.inspect injected_ast
86 120 # IO.inspect Macro.to_string injected_ast
  @@ -93,9 +127,9 @@ defmodule PowerAssert.Assertion do
93 127 end
94 128
95 129 ## detect positions
96 - defp detect_position(ast, default_index) do
130 + defp detect_position(ast, expr, default_index) do
97 131 {_ast, {_code, positions, _in_fn}} =
98 - PowerAssert.Ast.traverse(ast, {Macro.to_string(ast), [], 0}, &pre_collect_position/2, &collect_position/2)
132 + PowerAssert.Ast.traverse(ast, {expr, [], 0}, &pre_collect_position/2, &collect_position/2)
99 133 if default_index != 0 do
100 134 positions = Enum.map(positions, fn([pos, code]) -> [default_index + pos, code] end)
101 135 end
  @@ -397,16 +431,17 @@ defmodule PowerAssert.Assertion do
397 431
398 432
399 433 ## render
400 - def render_values(code, []) do
401 - Macro.to_string(code)
434 + def render_values(code, values, left \\ nil, right \\ nil)
435 + def render_values(code, [], left, right) do
436 + Macro.to_string(code) <> extra_information(left, right)
402 437 end
403 - def render_values(code, values) do
438 + def render_values(code, values, left, right) do
404 439 code_str = Macro.to_string(code)
405 440 values = Enum.sort(values, fn([x_pos, _], [y_pos, _]) -> x_pos > y_pos end)
406 441 [max_pos, _] = Enum.max_by(values, fn ([pos, _]) -> pos end)
407 442 first_line = String.duplicate(" ", max_pos + 1) |> replace_with_bar(values)
408 443 lines = make_lines([], Enum.count(values), values, -1)
409 - Enum.join([code_str, first_line] ++ lines, "\n")
444 + Enum.join([code_str, first_line] ++ lines, "\n") <> extra_information(left, right)
410 445 end
411 446
412 447 defp make_lines(lines, 0, _, _latest_pos) do
  @@ -437,4 +472,42 @@ defmodule PowerAssert.Assertion do
437 472 String.replace(front, ~r/ $/, "|") <> back
438 473 end)
439 474 end
475 +
476 + defp extra_information(left, right) when is_list(left) and is_list(right) do
477 + ["\n\nonly in lhs: " <> ((left -- right) |> inspect),
478 + "only in rhs: " <> ((right -- left) |> inspect)]
479 + |> Enum.join("\n")
480 + end
481 + defp extra_information(left, right) when is_map(left) and is_map(right) do
482 + if Map.has_key? left, :__struct__ do
483 + left = Map.from_struct(left)
484 + right = Map.from_struct(right)
485 + end
486 + in_left = Map.split(left, Map.keys(right)) |> elem(1)
487 + in_right = Map.split(right, Map.keys(left)) |> elem(1)
488 + str = "\n"
489 + unless Map.size(in_left) == 0 do
490 + str = str <> "\nonly in lhs: " <> inspect(in_left)
491 + end
492 + unless Map.size(in_right) == 0 do
493 + str = str <> "\nonly in rhs: " <> inspect(in_right)
494 + end
495 + diff = collect_map_diff(left, right)
496 + unless Enum.empty?(diff) do
497 + str = str <> "\ndifference:\n" <> Enum.join(diff, "\n")
498 + end
499 + str
500 + end
501 + defp extra_information(_left, _right), do: ""
502 +
503 + defp collect_map_diff(map1, map2) do
504 + Enum.reduce(map2, [], fn({k, v}, acc) ->
505 + case Map.fetch(map1, k) do
506 + {:ok, ^v} -> acc
507 + {:ok, map1_value} ->
508 + acc ++ ["key #{inspect k} => {#{inspect map1_value}, #{inspect v}}"]
509 + _ -> acc
510 + end
511 + end)
512 + end
440 513 end
  @@ -18,7 +18,7 @@ defmodule PowerAssert.Debug do
18 18 """
19 19 defmacro puts_expr(ast) do
20 20 code = Macro.escape(ast)
21 - injected_ast = inject_store_code(ast)
21 + injected_ast = inject_store_code(ast, Macro.to_string(ast))
22 22
23 23 quote do
24 24 unquote(injected_ast)
  @@ -3,7 +3,7 @@ defmodule PowerAssert.Mixfile do
3 3
4 4 def project do
5 5 [app: :power_assert,
6 - version: "0.0.5",
6 + version: "0.0.6",
7 7 elixir: "~> 1.0",
8 8 description: "Power Assert in Elixir",
9 9 package: [