defmodule CheckEscript.Summary do @moduledoc "Results summary, failure details, and output persistence." alias CheckEscript.{Coverage, Failed, Tasks} def print(results, total_seconds, tasks, coverage) do IO.write("\n") save_credo_outputs(results) save_format_failure_marker(results) merge_partition_outputs(results) partition_failures = if Tasks.has_test_tasks?(tasks), do: Failed.extract(), else: [] check_failures = results |> Enum.filter(fn {_name, status, _output} -> status != 0 end) |> Enum.flat_map(fn {_name, _status, output} -> Failed.extract_from_output(output) end) Failed.save((partition_failures ++ check_failures) |> Enum.uniq()) coverage_failed? = if Tasks.has_test_tasks?(tasks), do: Coverage.merge(coverage) == :failed, else: false failed_checks = Enum.filter(results, fn {_name, status, _output} -> status != 0 end) IO.puts("\nCompleted in #{total_seconds}s") report_result(failed_checks, coverage_failed?) end defp report_result([], false) do IO.puts([IO.ANSI.format([:green, "\nāœ“ All checks passed!"])]) end defp report_result([], true), do: System.halt(1) defp report_result(failed_checks, _coverage_failed?) do IO.puts([IO.ANSI.format([:red, "\nāœ— #{length(failed_checks)} check(s) failed"])]) print_failure_details(failed_checks) System.halt(1) end defp print_failure_details(failed_checks) do IO.puts("\n" <> String.duplicate("=", 60)) IO.puts("FAILURE DETAILS") IO.puts(String.duplicate("=", 60)) Enum.each(failed_checks, &print_check_failure/1) end defp print_check_failure({name, status, output}) do IO.puts([IO.ANSI.format([:red, :bright, "\n#{name} failed:"])]) IO.puts(String.duplicate("-", 40)) if String.starts_with?(name, "Tests (") do print_test_failure(status, output) else print_check_output(output) end end defp print_test_failure(status, output) do IO.puts(extract_test_summary(output)) if status == :warnings do warning_count = output |> String.split("warning:") |> length() |> Kernel.-(1) IO.puts([IO.ANSI.format([:yellow, "#{warning_count} warning(s) detected"])]) end IO.puts([IO.ANSI.format([:yellow, "\nFull test output saved to check_tests.txt"])]) end defp print_check_output(output) do output |> String.split("\n") |> Enum.reject(&(&1 == "")) |> Enum.each(&IO.puts(colorize_line(&1))) end def colorize_line(line) do cond do String.contains?(line, "** ") -> [IO.ANSI.format([:red, line])] String.contains?(line, "warning:") -> [IO.ANSI.format([:yellow, line])] String.contains?(line, "error:") -> [IO.ANSI.format([:red, line])] String.contains?(line, "ā”ƒ") or String.contains?(line, "│") -> [IO.ANSI.format([:cyan, line])] true -> line end end def extract_test_summary(output) when is_binary(output) do case Regex.run(~r/(\d+ tests?, \d+ failures?(, \d+ excluded)?.*)/m, output) do [_, summary | _] -> summary nil -> "See .check/check_tests.txt for details" end end defp save_credo_outputs(results) do File.mkdir_p!(".check") Enum.each(results, fn {"Credo", _status, output} -> File.write!(".check/credo.txt", output) {"Credo Strict", _status, output} -> File.write!(".check/credo_strict.txt", output) _ -> :ok end) end defp save_format_failure_marker(results) do File.mkdir_p!(".check") format_failed? = Enum.any?(results, fn {name, status, _output} -> name == "Formatting" and status != 0 end) if format_failed?, do: File.write!(".check/.format_failed", ""), else: File.rm(".check/.format_failed") end defp merge_partition_outputs(results) do partition_results = results |> Enum.filter(fn {name, _status, _output} -> String.starts_with?(name, "Tests (") end) |> Enum.sort_by(&partition_number/1) if Enum.any?(partition_results) do timestamp = DateTime.utc_now() |> DateTime.to_string() content = ["Test Output - Generated at #{timestamp}", String.duplicate("=", 60), ""] ++ Enum.flat_map(partition_results, fn {name, _status, output} -> ["\nPARTITION #{partition_label(name)}", String.duplicate("-", 60), output, ""] end) File.mkdir_p!(".check") File.write!(".check/check_tests.txt", Enum.join(content, "\n")) end end defp partition_number({name, _status, _output}) do case Regex.run(~r/Tests \((\d+)\/\d+\)/, name) do [_, n] -> String.to_integer(n) _ -> 0 end end defp partition_label(name) do case Regex.run(~r/Tests \((\d+)\/(\d+)\)/, name) do [_, p, t] -> "#{p}/#{t}" _ -> "?" end end end