Skip to content

Commit

Permalink
Merge pull request #41 from ma2gedev/feature/split-render-code
Browse files Browse the repository at this point in the history
Refactor rendering code
  • Loading branch information
ma2gedev authored Dec 29, 2020
2 parents feba7db + 2619247 commit 5f73e71
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 124 deletions.
144 changes: 21 additions & 123 deletions lib/power_assert/assertion.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
defmodule PowerAssert.PositionAndValue do
defstruct [:position, :value]
end

defmodule PowerAssert.Assertion do
@moduledoc """
This module handles Power Assert main function
Expand Down Expand Up @@ -38,7 +42,7 @@ defmodule PowerAssert.Assertion do
if right do
right
else
message = PowerAssert.Assertion.render_values(expr, values)
message = PowerAssert.Renderer.render(expr, position_and_values)
unquote(message_ast)

raise ExUnit.AssertionError,
Expand All @@ -59,7 +63,7 @@ defmodule PowerAssert.Assertion do
unquote(vars)

_ ->
message = PowerAssert.Assertion.render_values(expr, values)
message = PowerAssert.Renderer.render(expr, position_and_values)
unquote(message_ast)

raise ExUnit.AssertionError,
Expand All @@ -82,12 +86,17 @@ defmodule PowerAssert.Assertion do
quote do
unquote(injected_lhs_ast)
left = result
left_values = values
left_position_and_values = position_and_values
unquote(injected_rhs_ast)
# wrap result for avoid warning: this check/guard will always yield the same result
unless left == (fn x -> x end).(result) do
message =
PowerAssert.Assertion.render_values(unquote(code), left_values ++ values, left, result)
PowerAssert.Renderer.render(
unquote(code),
left_position_and_values ++ position_and_values,
left,
result
)

unquote(message_ast)

Expand All @@ -109,7 +118,7 @@ defmodule PowerAssert.Assertion do
unquote(injected_ast)

unless result do
message = PowerAssert.Assertion.render_values(unquote(code), values)
message = PowerAssert.Renderer.render(unquote(code), position_and_values)
unquote(message_ast)

raise ExUnit.AssertionError,
Expand Down Expand Up @@ -155,7 +164,7 @@ defmodule PowerAssert.Assertion do
quote do
{:ok, buffer} = Agent.start_link(fn -> [] end)
result = unquote(injected_ast)
values = Agent.get(buffer, & &1)
position_and_values = Agent.get(buffer, & &1)
Agent.stop(buffer)
end
end
Expand Down Expand Up @@ -484,7 +493,12 @@ defmodule PowerAssert.Assertion do
defp store_value_ast(ast, pos) do
quote do
v = unquote(ast)
Agent.update(buffer, &[[unquote(pos), v] | &1])

Agent.update(
buffer,
&[%PowerAssert.PositionAndValue{position: unquote(pos), value: v} | &1]
)

v
end
end
Expand Down Expand Up @@ -606,122 +620,6 @@ defmodule PowerAssert.Assertion do

defp catcher(ast, injector), do: {ast, injector}

## render
def render_values(code, values, left \\ nil, right \\ nil)

def render_values(code, [], left, right) do
Macro.to_string(code) <> extra_information(left, right)
end

def render_values(code, values, left, right) do
code_str = Macro.to_string(code)
values = Enum.sort(values, fn [x_pos, _], [y_pos, _] -> x_pos > y_pos end)
[max_pos, _] = Enum.max_by(values, fn [pos, _] -> pos end)
first_line = String.duplicate(" ", max_pos + 1) |> replace_with_bar(values)
lines = make_lines([], Enum.count(values), values, -1)
Enum.join([code_str, first_line] ++ lines, "\n") <> extra_information(left, right)
end

defp make_lines(lines, 0, _, _latest_pos) do
lines
end

defp make_lines(lines, times, values, latest_pos) do
[[pos, value] | t] = values
value = inspect(value)
value_len = String.length(value)

lines =
if latest_pos != -1 && latest_pos - (pos + value_len) > 0 do
[last_line | tail_lines] = Enum.reverse(lines)
{before_str, after_str} = String.split_at(last_line, pos)
{_removed_str, after_str} = String.split_at(after_str, value_len)
line = before_str <> value <> after_str
Enum.reverse([line | tail_lines])
else
line = String.duplicate(" ", pos + 1)
line = replace_with_bar(line, values)
line = String.replace(line, ~r/\|$/, value)
lines ++ [line]
end

make_lines(lines, times - 1, t, pos)
end

defp replace_with_bar(line, values) do
Enum.reduce(values, line, fn [pos, _value], line ->
{front, back} = String.split_at(line, pos + 1)
String.replace(front, ~r/ $/, "|") <> back
end)
end

defp extra_information(left, right) when is_list(left) and is_list(right) do
[
"\n\nonly in lhs: " <> ((left -- right) |> inspect),
"only in rhs: " <> ((right -- left) |> inspect)
]
|> Enum.join("\n")
end

defp extra_information(left, right) when is_map(left) and is_map(right) do
left = Map.delete(left, :__struct__)
right = Map.delete(right, :__struct__)
in_left = Map.split(left, Map.keys(right)) |> elem(1)
in_right = Map.split(right, Map.keys(left)) |> elem(1)
str = "\n"

str =
if map_size(in_left) != 0 do
str <> "\nonly in lhs: " <> inspect(in_left)
else
str
end

str =
if map_size(in_right) != 0 do
str <> "\nonly in rhs: " <> inspect(in_right)
else
str
end

diff = collect_map_diff(left, right)

str =
case Enum.empty?(diff) do
true -> str
false -> str <> "\ndifference:\n" <> Enum.join(diff, "\n")
end

str
end

defp extra_information(left, right) do
if String.valid?(left) && String.valid?(right) do
extra_information_for_string(left, right)
else
""
end
end

defp extra_information_for_string(left, right) do
"\n\ndifference:" <> "\n" <> left <> "\n" <> right
end

defp collect_map_diff(map1, map2) do
Enum.reduce(map2, [], fn {k, v}, acc ->
case Map.fetch(map1, k) do
{:ok, ^v} ->
acc

{:ok, map1_value} ->
acc ++ ["key #{inspect(k)} => {#{inspect(map1_value)}, #{inspect(v)}}"]

_ ->
acc
end
end)
end

defp collect_vars_from_pattern(expr) do
{_, vars} =
Macro.prewalk(expr, [], fn
Expand Down
5 changes: 4 additions & 1 deletion lib/power_assert/debug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ defmodule PowerAssert.Debug do
unquote(injected_ast)

IO.puts(
PowerAssert.Assertion.render_values(unquote(code), var!(values, PowerAssert.Assertion))
PowerAssert.Renderer.render(
unquote(code),
var!(position_and_values, PowerAssert.Assertion)
)
)

var!(result, PowerAssert.Assertion)
Expand Down
132 changes: 132 additions & 0 deletions lib/power_assert/renderer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
defmodule PowerAssert.Renderer do
@moduledoc false

alias PowerAssert.PositionAndValue

@doc """
renders test result
"""
def render(code_ast, position_and_values, lhs_result \\ nil, rhs_result \\ nil)

def render(code_ast, [], lhs_result, rhs_result) do
Macro.to_string(code_ast) <> extra_information(lhs_result, rhs_result)
end

def render(code_ast, position_and_values, lhs_result, rhs_result) do
code_str = Macro.to_string(code_ast)

position_and_values =
Enum.sort(position_and_values, fn %PositionAndValue{position: x_pos},
%PositionAndValue{position: y_pos} ->
x_pos > y_pos
end)

%PositionAndValue{position: max_pos} =
Enum.max_by(position_and_values, fn %PositionAndValue{position: pos} -> pos end)

first_line = String.duplicate(" ", max_pos + 1) |> replace_with_bar(position_and_values)
lines = make_lines([], Enum.count(position_and_values), position_and_values, -1)
Enum.join([code_str, first_line] ++ lines, "\n") <> extra_information(lhs_result, rhs_result)
end

defp make_lines(lines, 0, _, _latest_pos) do
lines
end

defp make_lines(lines, times, position_and_values, latest_pos) do
[%PositionAndValue{position: pos, value: value} | t] = position_and_values
value = inspect(value)
value_len = String.length(value)

lines =
if latest_pos != -1 && latest_pos - (pos + value_len) > 0 do
[last_line | tail_lines] = Enum.reverse(lines)
{before_str, after_str} = String.split_at(last_line, pos)
{_removed_str, after_str} = String.split_at(after_str, value_len)
line = before_str <> value <> after_str
Enum.reverse([line | tail_lines])
else
line = String.duplicate(" ", pos + 1)
line = replace_with_bar(line, position_and_values)
line = String.replace(line, ~r/\|$/, value)
lines ++ [line]
end

make_lines(lines, times - 1, t, pos)
end

defp replace_with_bar(line, position_and_values) do
Enum.reduce(position_and_values, line, fn %PositionAndValue{position: pos}, line ->
{front, back} = String.split_at(line, pos + 1)
String.replace(front, ~r/ $/, "|") <> back
end)
end

defp extra_information(lhs_result, rhs_result)
when is_list(lhs_result) and is_list(rhs_result) do
[
"\n\nonly in lhs: " <> ((lhs_result -- rhs_result) |> inspect),
"only in rhs: " <> ((rhs_result -- lhs_result) |> inspect)
]
|> Enum.join("\n")
end

defp extra_information(lhs_result, rhs_result) when is_map(lhs_result) and is_map(rhs_result) do
lhs_result = Map.delete(lhs_result, :__struct__)
rhs_result = Map.delete(rhs_result, :__struct__)
in_left = Map.split(lhs_result, Map.keys(rhs_result)) |> elem(1)
in_right = Map.split(rhs_result, Map.keys(lhs_result)) |> elem(1)
str = "\n"

str =
if map_size(in_left) != 0 do
str <> "\nonly in lhs: " <> inspect(in_left)
else
str
end

str =
if map_size(in_right) != 0 do
str <> "\nonly in rhs: " <> inspect(in_right)
else
str
end

diff = collect_map_diff(lhs_result, rhs_result)

str =
case Enum.empty?(diff) do
true -> str
false -> str <> "\ndifference:\n" <> Enum.join(diff, "\n")
end

str
end

defp extra_information(lhs_result, rhs_result) do
if String.valid?(lhs_result) && String.valid?(rhs_result) do
extra_information_for_string(lhs_result, rhs_result)
else
""
end
end

defp extra_information_for_string(lhs_result, rhs_result) do
"\n\ndifference:" <> "\n" <> lhs_result <> "\n" <> rhs_result
end

defp collect_map_diff(map1, map2) do
Enum.reduce(map2, [], fn {k, v}, acc ->
case Map.fetch(map1, k) do
{:ok, ^v} ->
acc

{:ok, map1_value} ->
acc ++ ["key #{inspect(k)} => {#{inspect(map1_value)}, #{inspect(v)}}"]

_ ->
acc
end
end)
end
end

0 comments on commit 5f73e71

Please sign in to comment.