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 @reset IO.ANSI.reset @status_colors [success: @green, failure: @red, pending: @yellow] @status_symbols [success: ".", failure: "F", pending: "*"] 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 "#{color}#{symbol}#{@reset}" 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), else: string string = string <> format_footer(examples, failed, pending) string = string <> format_times(durations, failed, pending) string <> format_seed() end defp format_failed(failed) do res = failed |> Enum.with_index |> Enum.map(fn({example, index}) -> do_format_example(example, example.error.message |> String.replace("\n", "\n\t "), index) end) Enum.join(res, "\n") end defp format_pending(pending) do res = pending |> Enum.with_index |> Enum.map(fn({example, index}) -> do_format_example(example, example.result, index) end) Enum.join(res, "\n") end defp do_format_example(example, info, index) do color = color_for_status(example.status) description = one_line_description(example) [ "\n", "\t#{index + 1}) #{description}", "\t#{@cyan}#{example.file}:#{example.line}#{@reset}", "\t#{color}#{info}#{@reset}", ] |> Enum.join("\n") end defp format_footer(examples, failed, pending) do color = get_color(failed, pending) parts = ["#{Enum.count(examples)} examples", "#{Enum.count(failed)} failures"] parts = if Enum.any?(pending), do: parts ++ ["#{Enum.count(pending)} pending"], else: parts "\n\n\t#{color}#{Enum.join(parts, ", ")}#{@reset}" end defp format_times({start_loading_time, finish_loading_time, finish_specs_time}, failed, pending) do color = get_color(failed, pending) load_time = :timer.now_diff(finish_loading_time, start_loading_time) spec_time = :timer.now_diff(finish_specs_time, finish_loading_time) "\n\n\t#{color}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)#{@reset}\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(failed, pending) do if Enum.any?(failed) do @red else if Enum.any?(pending), do: @yellow, else: @green end end defp one_line_description(example) do desc = Example.context_descriptions(example) ++ [example.description] desc |> Enum.join(" ") |> String.rstrip end defp trace_description(example) do color = color_for_status(example.status) ex_desc = if String.length(example.description) > 0 do "#{color}#{example.description}#{@reset}" 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 "#{color}#{example.error.message}#{@reset}" else "#{color}#{inspect example.result}#{@reset}" 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