Class: Ace::TestRunner::Molecules::ReportStorage

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/test_runner/molecules/report_storage.rb

Overview

Handles storage of test reports to filesystem

Instance Method Summary collapse

Constructor Details

#initialize(base_dir: ".ace-local/test/reports", timestamp_generator: nil) ⇒ ReportStorage

Returns a new instance of ReportStorage.



8
9
10
11
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 8

def initialize(base_dir: ".ace-local/test/reports", timestamp_generator: nil)
  @base_dir = base_dir
  @timestamp_generator = timestamp_generator || Atoms::TimestampGenerator.new
end

Instance Method Details

#cleanup_old_reports(keep: 10, max_age_days: 30) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 145

def cleanup_old_reports(keep: 10, max_age_days: 30)
  ensure_base_directory

  cutoff_time = Time.now - (max_age_days * 24 * 60 * 60)
  deleted = []

  # In centralized mode, base_dir contains package subfolders; in legacy mode it may contain reports directly.
  reports_by_scope = report_directories
    .map { |d| report_info(d) }
    .compact
    .group_by { |r| File.dirname(r[:path]) }

  reports_by_scope.each_value do |reports|
    sorted = reports.sort_by { |r| r[:timestamp] }.reverse

    to_keep = sorted.take(keep)
    to_keep += sorted.select { |r| r[:timestamp] > cutoff_time }
    to_keep_paths = to_keep.map { |r| r[:path] }.uniq

    sorted.each do |report|
      next if to_keep_paths.include?(report[:path])

      FileUtils.rm_rf(report[:path])
      deleted << report[:path]
    end
  end

  deleted
end

#latest_report_pathObject



115
116
117
118
119
120
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 115

def latest_report_path
  latest_link = File.join(@base_dir, "latest")
  return nil unless File.exist?(latest_link) && File.symlink?(latest_link)

  File.readlink(latest_link)
end

#list_reports(limit: 10) ⇒ Object



104
105
106
107
108
109
110
111
112
113
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 104

def list_reports(limit: 10)
  ensure_base_directory

  report_directories
    .map { |d| report_info(d) }
    .compact
    .sort_by { |r| r[:timestamp] }
    .reverse
    .take(limit)
end

#load_report(report_dir) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 122

def load_report(report_dir)
  return nil unless Dir.exist?(report_dir)

  summary_file = File.join(report_dir, "summary.json")
  return nil unless File.exist?(summary_file)

  summary = JSON.parse(File.read(summary_file), symbolize_names: true)

  # Load additional data if available
  failures_file = File.join(report_dir, "failures.json")
  failures = if File.exist?(failures_file)
    JSON.parse(File.read(failures_file), symbolize_names: true)
  else
    []
  end

  {
    summary: summary,
    failures: failures,
    report_dir: report_dir
  }
end

#save_failures(failures, report_dir) ⇒ Object



68
69
70
71
72
73
74
75
76
77
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 68

def save_failures(failures, report_dir)
  return nil if failures.empty?

  ensure_directory(report_dir)
  failures_file = File.join(report_dir, "failures.json")

  failures_data = failures.map(&:to_h)
  File.write(failures_file, JSON.pretty_generate(failures_data))
  failures_file
end

#save_individual_failure_reports(failures, report_dir, formatter, max_display: nil) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 79

def save_individual_failure_reports(failures, report_dir, formatter, max_display: nil)
  return [] if failures.empty?

  failures_dir = File.join(report_dir, "failures")
  ensure_directory(failures_dir)

  # Limit number of individual .md files to max_display if specified
  failures_to_save = max_display ? failures.take(max_display) : failures

  report_files = []
  failures_to_save.each_with_index do |failure, index|
    filename = generate_failure_filename(failure, index + 1)
    filepath = File.join(failures_dir, filename)

    content = formatter.generate_failure_report(failure, index + 1)
    File.write(filepath, content)
    report_files << filepath
  end

  # Create an index file for ALL failures (not just the limited ones)
  create_failure_index(failures, failures_dir)

  report_files
end

#save_raw_output(output, report_dir) ⇒ Object



32
33
34
35
36
37
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 32

def save_raw_output(output, report_dir)
  ensure_directory(report_dir)
  output_file = File.join(report_dir, "raw_output.txt")
  File.write(output_file, output)
  output_file
end

#save_report(report, format: :json) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 13

def save_report(report, format: :json)
  ensure_base_directory
  report_dir = create_report_directory

  case format
  when :json
    save_json_report(report, report_dir)
  when :markdown
    save_markdown_report(report, report_dir)
  when :all
    save_all_formats(report, report_dir)
  else
    raise ArgumentError, "Unknown report format: #{format}"
  end

  create_latest_symlink(report_dir)
  report_dir
end

#save_stderr(stderr, report_dir) ⇒ Object



39
40
41
42
43
44
45
46
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 39

def save_stderr(stderr, report_dir)
  return nil if stderr.nil? || stderr.empty?

  ensure_directory(report_dir)
  stderr_file = File.join(report_dir, "raw_stderr.txt")
  File.write(stderr_file, stderr)
  stderr_file
end

#save_summary(result, report_dir) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/ace/test_runner/molecules/report_storage.rb', line 48

def save_summary(result, report_dir)
  ensure_directory(report_dir)
  summary_file = File.join(report_dir, "summary.json")

  summary_data = {
    passed: result.passed,
    failed: result.failed,
    errors: result.errors,
    skipped: result.skipped,
    total: result.total_tests,
    pass_rate: result.pass_rate,
    duration: result.duration,
    success: result.success?,
    timestamp: Time.now.iso8601
  }

  File.write(summary_file, JSON.pretty_generate(summary_data))
  summary_file
end