Class: Ace::Test::EndToEndRunner::Molecules::SuiteReportWriter

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/test/end_to_end_runner/molecules/suite_report_writer.rb

Overview

Writes an aggregated package or suite report

Uses LLM synthesis to generate rich reports with root cause analysis, friction insights, and improvement suggestions. Falls back to a static template on LLM failure.

Constant Summary collapse

REPORT_KINDS =
{
  package: ->(timestamp, package) { "#{timestamp}-#{package}-report.md" },
  suite: ->(timestamp, _package) { "#{timestamp}-suite-report.md" },
  suite_final: ->(timestamp, _package) { "#{timestamp}-suite-final-report.md" }
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(config: nil) ⇒ SuiteReportWriter

Returns a new instance of SuiteReportWriter.

Parameters:

  • config (Hash, nil) (defaults to: nil)

    Configuration hash (reads reporting.model and reporting.timeout)



22
23
24
25
26
# File 'lib/ace/test/end_to_end_runner/molecules/suite_report_writer.rb', line 22

def initialize(config: nil)
  reporting = (config || {}).dig("reporting") || {}
  @model = reporting["model"] || "glite"
  @timeout = reporting["timeout"] || 60
end

Instance Method Details

#write(results, scenarios, package:, timestamp:, base_dir:, report_kind: :package, diagnostics: nil) ⇒ String

Write an aggregated report

Parameters:

  • results (Array<Models::TestResult>)

    Test results (ordered)

  • scenarios (Array<Models::TestScenario>)

    Corresponding scenarios

  • package (String)

    Package name (e.g., "ace-lint")

  • timestamp (String)

    Timestamp ID for this run

  • base_dir (String)

    Base directory for cache output

Returns:

  • (String)

    Path to the written report file



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ace/test/end_to_end_runner/molecules/suite_report_writer.rb', line 42

def write(results, scenarios, package:, timestamp:, base_dir:, report_kind: :package, diagnostics: nil)
  cache_dir = File.join(base_dir, ".ace-local", "test-e2e")
  FileUtils.mkdir_p(cache_dir)

  report_path = File.join(cache_dir, report_filename(report_kind, timestamp, package))

  overall_status = compute_status(results)
  executed_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
  results_data = build_results_data(results, scenarios)
  narrative_sections = synthesize_narrative_sections(
    results_data,
    package: package,
    timestamp: timestamp,
    overall_status: overall_status,
    executed_at: executed_at
  )
  content = build_report(
    results_data,
    package: package,
    timestamp: timestamp,
    overall_status: overall_status,
    executed_at: executed_at,
    narrative_sections: narrative_sections,
    diagnostics: diagnostics
  )

  File.write(report_path, content)
  report_path
end

#write_retry_summary(initial_results:, retry_results:, timestamp:, base_dir:, package: "suite") ⇒ Object

Write a deterministic wrapper report for a two-attempt suite run.

Preserves first-pass failure evidence while reflecting the final retry outcome.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/ace/test/end_to_end_runner/molecules/suite_report_writer.rb', line 75

def write_retry_summary(initial_results:, retry_results:, timestamp:, base_dir:, package: "suite")
  cache_dir = File.join(base_dir, ".ace-local", "test-e2e")
  FileUtils.mkdir_p(cache_dir)

  report_path = File.join(cache_dir, report_filename(:suite_final, timestamp, package))
  initial_entries = flatten_attempt_results(initial_results, base_dir: base_dir)
  retry_entries = flatten_attempt_results(retry_results, base_dir: base_dir)
  retry_by_test = retry_entries.each_with_object({}) { |entry, memo| memo[entry[:test_id]] = entry }

  flaky_entries = initial_entries.filter_map do |entry|
    next if entry[:status] == "pass"

    retry_entry = retry_by_test[entry[:test_id]]
    next unless retry_entry && retry_entry[:status] == "pass"

    entry.merge(retry_entry: retry_entry)
  end.sort_by { |entry| entry[:test_id] }
  remaining_entries = retry_entries.reject { |entry| entry[:status] == "pass" }.sort_by { |entry| entry[:test_id] }
  final_status = compute_retry_summary_status(retry_entries)

  content = build_retry_summary_content(
    timestamp: timestamp,
    initial_results: initial_results,
    retry_results: retry_results,
    initial_entries: initial_entries,
    flaky_entries: flaky_entries,
    remaining_entries: remaining_entries,
    final_status: final_status,
    base_dir: base_dir
  )

  File.write(report_path, content)
  report_path
end