Packages
espec
1.9.0
1.10.0
1.9.2
1.9.1
1.9.0
1.8.3
1.8.2
1.8.1
1.8.0
1.7.0
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.2
1.2.1
1.2.0
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.7
0.3.5
0.3.0
0.2.0
0.1.0
BDD testing framework for Elixir inspired by RSpec.
Current section
Files
Jump to
Current section
Files
lib/espec/formatters/doc.ex
defmodule ESpec.Formatters.Doc do
@moduledoc """
Generates plain colored text output.
"""
@green IO.ANSI.green()
@red IO.ANSI.red()
@cyan IO.ANSI.cyan()
@yellow IO.ANSI.yellow()
# @grey IO.ANSI.light_black()
@reset IO.ANSI.reset()
@main_colors [stacktrace: @cyan, diff_headers: @cyan]
@status_colors [success: @green, failure: @red, pending: @yellow]
@status_symbols [success: ".", failure: "F", pending: "*"]
@diff_colors [
diff_delete: :red,
diff_delete_whitespace: IO.ANSI.color_background(2, 0, 0),
diff_insert: :green,
diff_insert_whitespace: IO.ANSI.color_background(0, 2, 0)
]
alias ESpec.Example
use ESpec.Formatters.Base
@doc "Formats an example result."
def format_example(example, opts) do
color = color_for_status(example.status)
symbol = symbol_for_status(example.status)
if opts[:details] do
trace_description(example)
else
colorize(color, symbol)
end
end
@doc "Formats the final result."
def format_result(examples, durations, opts) do
pending = Example.pendings(examples)
string = ""
string = if Enum.any?(pending), do: string <> format_pending(pending), else: string
failed = Example.failure(examples)
string = if Enum.any?(failed), do: string <> format_failed(failed, opts), else: string
string = string <> format_footer(examples, failed, pending)
string = string <> format_times(durations, failed, pending)
string <> format_seed()
end
defp format_failed(failed, opts) do
failed
|> Enum.with_index()
|> Enum.map_join("\n", fn {example, index} ->
do_format_failed_example(example, index, opts)
end)
end
defp format_pending(pending) do
pending
|> Enum.with_index()
|> Enum.map_join("\n", fn {example, index} ->
do_format_pending_example(example, example.result, index)
end)
end
defp do_format_pending_example(example, info, index) do
color = color_for_status(example.status)
description = one_line_description(example)
file_line_ref = "#{example.file}:#{example.line}"
[
"\n",
"\t#{index + 1}) #{description}",
"\t#{colorize(@main_colors[:stacktrace], file_line_ref)}",
"\t#{colorize(color, info)}"
]
|> Enum.join("\n")
end
defp do_format_failed_example(example, index, opts) do
color = color_for_status(example.status)
description = one_line_description(example)
error_message =
example.error.message
|> String.replace("\n", "\n\t ")
stacktrace = do_format_stacktrace(example)
Enum.join(
[
"\n",
"\t#{index + 1}) #{description}",
"\t#{colorize(@main_colors[:stacktrace], stacktrace)}",
"\t#{colorize(color, error_message)}"
],
"\n"
) <> if Map.get(opts, :diff_enabled?, true), do: do_format_diff(example.error), else: ""
end
defp do_format_stacktrace(example) do
list =
example
|> do_format_stacktrace_list()
|> Enum.reverse()
line = "#{Path.relative_to_cwd(example.file)}:#{example.line}"
if Enum.empty?(list) do
line
else
["#{line}: (example)" | list]
|> Enum.reverse()
|> Enum.join("\n\t")
end
end
defp do_format_stacktrace_list(example) do
if is_nil(example.error.stacktrace) do
[]
else
example.error.stacktrace
|> remove_example_if_first(example)
|> Enum.map(fn {module, function, arity, [file: file, line: line]} = item ->
if in_this_example?(example, item) do
"#{file}:#{line}: (inside example)"
else
"#{file}:#{line}: #{module}.#{function}/#{arity}"
end
end)
end
end
defp in_this_example?(example, {module, function, arity, [file: file, line: _line]}) do
module == example.module && function == example.function && arity == 1 &&
file == String.to_charlist(Path.relative_to_cwd(example.file))
end
defp remove_example_if_first([], _example) do
[]
end
defp remove_example_if_first([{_, _, _, [file: _, line: line]} = first | rest] = trace, example) do
if in_this_example?(example, first) && line == example.line do
rest
else
trace
end
end
defp do_format_diff(%ESpec.AssertionError{extra: %{diff_fn: f}}) when is_function(f, 0) do
format_diff(f.())
end
defp do_format_diff(_), do: ""
defp colorize(ansi_color_code, string) do
[ansi_color_code, string, @reset]
|> IO.ANSI.format_fragment(true)
|> IO.iodata_to_binary()
end
defp format_diff(nil) do
""
end
if Version.match?(System.version(), ">= 1.10.0") do
defp format_diff(%ExUnit.Diff{
left: left,
right: right
}) do
left =
left
|> ExUnit.Diff.to_algebra(fn doc ->
Inspect.Algebra.color(
doc,
get_color_by_content(doc, :diff_delete, :diff_delete_whitespace),
%Inspect.Opts{syntax_colors: @diff_colors}
)
end)
|> Inspect.Algebra.nest(20)
|> Inspect.Algebra.format(80)
right =
right
|> ExUnit.Diff.to_algebra(fn doc ->
Inspect.Algebra.color(
doc,
get_color_by_content(doc, :diff_insert, :diff_insert_whitespace),
%Inspect.Opts{syntax_colors: @diff_colors}
)
end)
|> Inspect.Algebra.nest(20)
|> Inspect.Algebra.format(80)
"\n\t #{colorize(@main_colors[:diff_headers], "expected:")} " <>
IO.iodata_to_binary(left) <>
"\n\t #{colorize(@main_colors[:diff_headers], "actual:")} " <>
IO.iodata_to_binary(right)
end
defp get_color_by_content(content, color_if_normal, color_if_whitespace)
when is_binary(content) do
if String.trim_leading(content) == "", do: color_if_whitespace, else: color_if_normal
end
defp get_color_by_content(_content, color_if_normal, _color_if_whitespace) do
color_if_normal
end
else
defp format_diff({l, r}) do
[
"",
"\t #{colorize(@main_colors[:diff_headers], "expected:")} #{colorize_diff(r)}",
"\t #{colorize(@main_colors[:diff_headers], "actual:")} #{colorize_diff(l)}"
]
|> Enum.join("\n")
end
defp colorize_diff([{:eq, text} | rest]) do
text <> colorize_diff(rest)
end
defp colorize_diff([{:ins, text} | rest]) do
colorize(@diff_colors[:diff_insert], text) <> colorize_diff(rest)
end
defp colorize_diff([{:del, text} | rest]) do
colorize(@diff_colors[:diff_delete], text) <> colorize_diff(rest)
end
defp colorize_diff([{:ins_whitespace, length} | rest]) do
colorize(@diff_colors[:diff_insert_whitespace], String.duplicate(" ", length)) <>
colorize_diff(rest)
end
defp colorize_diff([]) do
""
end
end
defp format_footer(examples, failed, pending) do
color = get_color_for_content(failed, pending)
parts = ["#{Enum.count(examples)} examples", "#{Enum.count(failed)} failures"]
parts = if Enum.any?(pending), do: parts ++ ["#{Enum.count(pending)} pending"], else: parts
parts_string = Enum.join(parts, ", ")
"\n\n\t#{colorize(color, parts_string)}"
end
defp format_times({start_loading_time, finish_loading_time, finish_specs_time}, failed, pending) do
color = get_color_for_content(failed, pending)
load_time = :timer.now_diff(finish_loading_time, start_loading_time)
spec_time = :timer.now_diff(finish_specs_time, finish_loading_time)
finished_in =
"Finished in #{us_to_sec(load_time + spec_time)} seconds" <>
" (#{us_to_sec(load_time)}s on load, #{us_to_sec(spec_time)}s on specs)"
"\n\n\t#{colorize(color, finished_in)}\n\n"
end
defp format_seed do
if ESpec.Configuration.get(:order) do
""
else
seed = ESpec.Configuration.get(:seed)
"\tRandomized with seed #{seed}\n\n"
end
end
defp us_to_sec(us), do: div(us, 10_000) / 100
defp get_color_for_content(failed, pending) do
if Enum.any?(failed) do
@status_colors[:failure]
else
if Enum.any?(pending), do: @status_colors[:pending], else: @status_colors[:success]
end
end
defp one_line_description(example) do
desc = Example.context_descriptions(example) ++ [example.description]
desc
|> Enum.join(" ")
|> String.trim_trailing()
end
defp trace_description(example) do
color = color_for_status(example.status)
ex_desc =
if String.length(example.description) > 0 do
"#{colorize(color, example.description)}"
else
status_message(example, color)
end
array = Example.context_descriptions(example) ++ [ex_desc]
{result, _} =
Enum.reduce(array, {"", ""}, fn description, acc ->
{d, w} = acc
{d <> w <> "#{description}" <> "\n", w <> " "}
end)
result
end
defp status_message(example, color) do
if example.status == :failure do
"#{colorize(color, example.error.message)}"
else
"#{colorize(color, inspect(example.result))}"
end
end
defp color_for_status(status), do: Keyword.get(@status_colors, status)
defp symbol_for_status(status), do: Keyword.get(@status_symbols, status)
end