ace-test-runner 0.18.1 → 0.25.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ace-defaults/test/runner.yml +9 -9
- data/.ace-defaults/test-runner/config.yml +24 -20
- data/CHANGELOG.md +125 -0
- data/README.md +6 -4
- data/exe/ace-test-suite +19 -0
- data/lib/ace/test_runner/atoms/test_folder_detector.rb +4 -4
- data/lib/ace/test_runner/atoms/test_type_detector.rb +9 -5
- data/lib/ace/test_runner/cli/commands/test.rb +13 -9
- data/lib/ace/test_runner/formatters/progress_formatter.rb +25 -28
- data/lib/ace/test_runner/models/test_configuration.rb +26 -20
- data/lib/ace/test_runner/models/{test_group.rb → test_target.rb} +16 -20
- data/lib/ace/test_runner/molecules/cli_argument_parser.rb +2 -2
- data/lib/ace/test_runner/molecules/config_loader.rb +1 -1
- data/lib/ace/test_runner/molecules/pattern_resolver.rb +60 -39
- data/lib/ace/test_runner/molecules/smart_test_executor.rb +2 -2
- data/lib/ace/test_runner/organisms/{sequential_group_executor.rb → sequential_target_executor.rb} +28 -29
- data/lib/ace/test_runner/organisms/test_orchestrator.rb +26 -31
- data/lib/ace/test_runner/suite/orchestrator.rb +11 -2
- data/lib/ace/test_runner/suite/process_monitor.rb +242 -94
- data/lib/ace/test_runner/suite/result_aggregator.rb +39 -3
- data/lib/ace/test_runner/suite/simple_display_manager.rb +1 -0
- data/lib/ace/test_runner/version.rb +1 -1
- metadata +4 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
4
|
require "json"
|
|
5
|
+
require "timeout"
|
|
5
6
|
|
|
6
7
|
module Ace
|
|
7
8
|
module TestRunner
|
|
@@ -9,8 +10,11 @@ module Ace
|
|
|
9
10
|
class ProcessMonitor
|
|
10
11
|
attr_reader :processes, :max_parallel
|
|
11
12
|
|
|
12
|
-
def initialize(max_parallel = 10)
|
|
13
|
+
def initialize(max_parallel = 10, package_timeout: nil, termination_grace_period: 1.0, clock: nil)
|
|
13
14
|
@max_parallel = max_parallel
|
|
15
|
+
@package_timeout = package_timeout
|
|
16
|
+
@termination_grace_period = termination_grace_period
|
|
17
|
+
@clock = clock || -> { Time.now }
|
|
14
18
|
@processes = {}
|
|
15
19
|
@queue = []
|
|
16
20
|
@completed = []
|
|
@@ -33,8 +37,8 @@ module Ace
|
|
|
33
37
|
"ACE_ASSIGN_ID" => nil,
|
|
34
38
|
"ACE_ASSIGN_FORK_ROOT" => nil
|
|
35
39
|
})
|
|
36
|
-
start_time =
|
|
37
|
-
stdin, stdout, stderr, thread = Open3.popen3(env, cmd, chdir: package["path"])
|
|
40
|
+
start_time = now
|
|
41
|
+
stdin, stdout, stderr, thread = Open3.popen3(env, *cmd, chdir: package["path"], pgroup: true)
|
|
38
42
|
|
|
39
43
|
@processes[package["name"]] = {
|
|
40
44
|
package: package,
|
|
@@ -45,10 +49,18 @@ module Ace
|
|
|
45
49
|
start_time: start_time,
|
|
46
50
|
callback: callback,
|
|
47
51
|
output: +"",
|
|
52
|
+
stderr_output: +"",
|
|
48
53
|
report_root: test_options["report_dir"],
|
|
49
54
|
test_count: 0,
|
|
50
55
|
tests_run: 0,
|
|
51
|
-
dots: +""
|
|
56
|
+
dots: +"",
|
|
57
|
+
timeout: @package_timeout,
|
|
58
|
+
pid: thread.pid,
|
|
59
|
+
pgid: thread.pid,
|
|
60
|
+
terminating: false,
|
|
61
|
+
timeout_triggered: false,
|
|
62
|
+
terminated_by: nil,
|
|
63
|
+
terminate_at: nil
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
# Initial callback
|
|
@@ -61,96 +73,32 @@ module Ace
|
|
|
61
73
|
thread = process_info[:thread]
|
|
62
74
|
callback = process_info[:callback]
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
progress: process_info[:tests_run],
|
|
79
|
-
total: process_info[:test_count],
|
|
80
|
-
dots: process_info[:dots],
|
|
81
|
-
elapsed: elapsed
|
|
82
|
-
}, chunk)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
rescue IO::WaitReadable, EOFError
|
|
86
|
-
# No data available or stream closed
|
|
76
|
+
stdout_chunk = drain_stream(process_info[:stdout], process_info[:output])
|
|
77
|
+
stderr_chunk = drain_stream(process_info[:stderr], process_info[:stderr_output])
|
|
78
|
+
|
|
79
|
+
parse_progress(process_info, stdout_chunk) if stdout_chunk && !stdout_chunk.empty?
|
|
80
|
+
|
|
81
|
+
if callback && ((stdout_chunk && !stdout_chunk.empty?) || (stderr_chunk && !stderr_chunk.empty?))
|
|
82
|
+
elapsed = now - process_info[:start_time]
|
|
83
|
+
callback.call(package, {
|
|
84
|
+
status: :running,
|
|
85
|
+
progress: process_info[:tests_run],
|
|
86
|
+
total: process_info[:test_count],
|
|
87
|
+
dots: process_info[:dots],
|
|
88
|
+
elapsed: elapsed
|
|
89
|
+
}, stdout_chunk)
|
|
87
90
|
end
|
|
88
91
|
|
|
92
|
+
enforce_timeout(process_info)
|
|
93
|
+
|
|
89
94
|
# Check if process completed
|
|
90
95
|
unless thread.alive?
|
|
91
|
-
elapsed =
|
|
96
|
+
elapsed = now - process_info[:start_time]
|
|
92
97
|
exit_status = thread.value.exitstatus
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
rescue
|
|
98
|
-
""
|
|
99
|
-
end
|
|
100
|
-
process_info[:output] << remaining_output
|
|
101
|
-
|
|
102
|
-
# Try to get accurate results from summary.json first
|
|
103
|
-
results = nil
|
|
104
|
-
reports_dir = Atoms::ReportPathResolver.report_directory(
|
|
105
|
-
package["path"],
|
|
106
|
-
report_root: process_info[:report_root],
|
|
107
|
-
package_name: package["name"]
|
|
108
|
-
)
|
|
109
|
-
summary_file = reports_dir ? File.join(reports_dir, "summary.json") : nil
|
|
110
|
-
if summary_file && File.exist?(summary_file)
|
|
111
|
-
begin
|
|
112
|
-
json_data = JSON.parse(File.read(summary_file))
|
|
113
|
-
results = {
|
|
114
|
-
tests: json_data["total"] || 0,
|
|
115
|
-
assertions: json_data["assertions"] || 0,
|
|
116
|
-
failures: json_data["failed"] || 0,
|
|
117
|
-
errors: json_data["errors"] || 0,
|
|
118
|
-
duration: json_data["duration"] || elapsed,
|
|
119
|
-
success: json_data["success"] || false
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
# Also try to get assertions from report.json if not in summary
|
|
123
|
-
if results[:assertions] == 0
|
|
124
|
-
report_file = File.join(reports_dir, "report.json")
|
|
125
|
-
if File.exist?(report_file)
|
|
126
|
-
report_data = JSON.parse(File.read(report_file))
|
|
127
|
-
results[:assertions] = report_data.dig("result", "assertions") || 0
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
rescue JSON::ParserError
|
|
131
|
-
# Fall back to parsing output
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Fall back to parsing output if no JSON data
|
|
136
|
-
results ||= parse_results(process_info[:output])
|
|
137
|
-
|
|
138
|
-
# Close streams
|
|
139
|
-
begin
|
|
140
|
-
process_info[:stdout].close
|
|
141
|
-
rescue
|
|
142
|
-
nil
|
|
143
|
-
end
|
|
144
|
-
begin
|
|
145
|
-
process_info[:stderr].close
|
|
146
|
-
rescue
|
|
147
|
-
nil
|
|
148
|
-
end
|
|
149
|
-
begin
|
|
150
|
-
process_info[:stdin].close
|
|
151
|
-
rescue
|
|
152
|
-
nil
|
|
153
|
-
end
|
|
99
|
+
collect_remaining_output(process_info)
|
|
100
|
+
results = build_results(process_info, elapsed, exit_status)
|
|
101
|
+
close_streams(process_info)
|
|
154
102
|
|
|
155
103
|
# Final callback
|
|
156
104
|
if callback
|
|
@@ -164,6 +112,8 @@ module Ace
|
|
|
164
112
|
success: success_status,
|
|
165
113
|
exit_code: exit_status,
|
|
166
114
|
elapsed: elapsed,
|
|
115
|
+
timed_out: process_info[:timeout_triggered],
|
|
116
|
+
interrupted: process_info[:terminated_by] == :interrupt,
|
|
167
117
|
results: results
|
|
168
118
|
}, process_info[:output])
|
|
169
119
|
end
|
|
@@ -188,6 +138,38 @@ module Ace
|
|
|
188
138
|
!@processes.empty? || !@queue.empty?
|
|
189
139
|
end
|
|
190
140
|
|
|
141
|
+
def stop_all(reason: :interrupt)
|
|
142
|
+
@queue.clear
|
|
143
|
+
|
|
144
|
+
@processes.each_value do |process_info|
|
|
145
|
+
terminate_process_group(process_info, signal: "TERM", reason: reason)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
deadline = now + @termination_grace_period
|
|
149
|
+
while @processes.values.any? { |info| info[:thread].alive? } && now < deadline
|
|
150
|
+
sleep 0.05
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
@processes.each_value do |process_info|
|
|
154
|
+
next unless process_info[:thread].alive?
|
|
155
|
+
|
|
156
|
+
terminate_process_group(process_info, signal: "KILL", reason: reason)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@processes.each_value do |process_info|
|
|
160
|
+
begin
|
|
161
|
+
Timeout.timeout(0.5) { process_info[:thread].value if process_info[:thread].alive? }
|
|
162
|
+
rescue Timeout::Error, StandardError
|
|
163
|
+
nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
close_streams(process_info)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
@processes.clear
|
|
170
|
+
@completed.clear
|
|
171
|
+
end
|
|
172
|
+
|
|
191
173
|
def wait_all
|
|
192
174
|
while running?
|
|
193
175
|
check_processes
|
|
@@ -200,8 +182,8 @@ module Ace
|
|
|
200
182
|
def build_command(package, options)
|
|
201
183
|
cmd_parts = ["ace-test"]
|
|
202
184
|
|
|
203
|
-
#
|
|
204
|
-
#
|
|
185
|
+
# Suite package execution intentionally bypasses grouped mode so each
|
|
186
|
+
# package runs its full target scope as one batch under suite orchestration.
|
|
205
187
|
cmd_parts << "--run-in-single-batch"
|
|
206
188
|
|
|
207
189
|
# Add format (use progress if compact is specified since ace-test doesn't have compact format)
|
|
@@ -219,10 +201,176 @@ module Ace
|
|
|
219
201
|
cmd_parts << "--report-dir" << pkg_report_dir
|
|
220
202
|
end
|
|
221
203
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
cmd_parts
|
|
204
|
+
target = options["target"]
|
|
205
|
+
cmd_parts << target if target && !target.to_s.empty?
|
|
206
|
+
|
|
207
|
+
cmd_parts
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def now
|
|
211
|
+
@clock.call
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def enforce_timeout(process_info)
|
|
215
|
+
timeout = process_info[:timeout]
|
|
216
|
+
return unless timeout
|
|
217
|
+
|
|
218
|
+
elapsed = now - process_info[:start_time]
|
|
219
|
+
|
|
220
|
+
if !process_info[:timeout_triggered] && elapsed > timeout
|
|
221
|
+
process_info[:timeout_triggered] = true
|
|
222
|
+
process_info[:terminate_at] = now + @termination_grace_period
|
|
223
|
+
terminate_process_group(process_info, signal: "TERM", reason: :timeout)
|
|
224
|
+
elsif process_info[:timeout_triggered] && process_info[:thread].alive? && process_info[:terminate_at] && now >= process_info[:terminate_at]
|
|
225
|
+
terminate_process_group(process_info, signal: "KILL", reason: :timeout)
|
|
226
|
+
process_info[:terminate_at] = nil
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def terminate_process_group(process_info, signal:, reason:)
|
|
231
|
+
process_info[:terminated_by] = reason
|
|
232
|
+
process_info[:terminating] = true
|
|
233
|
+
Process.kill(signal, -process_info[:pgid])
|
|
234
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def drain_stream(io, buffer)
|
|
239
|
+
return nil unless io && !io.closed?
|
|
240
|
+
|
|
241
|
+
chunk = +""
|
|
242
|
+
loop do
|
|
243
|
+
ready = IO.select([io], nil, nil, 0)
|
|
244
|
+
break unless ready
|
|
245
|
+
|
|
246
|
+
chunk << io.read_nonblock(4096)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
buffer << chunk unless chunk.empty?
|
|
250
|
+
chunk
|
|
251
|
+
rescue IO::WaitReadable, EOFError
|
|
252
|
+
buffer << chunk unless chunk.empty?
|
|
253
|
+
chunk
|
|
254
|
+
rescue IOError
|
|
255
|
+
nil
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def collect_remaining_output(process_info)
|
|
259
|
+
process_info[:output] << safe_read(process_info[:stdout])
|
|
260
|
+
process_info[:stderr_output] << safe_read(process_info[:stderr])
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def safe_read(io)
|
|
264
|
+
return "" unless io && !io.closed?
|
|
265
|
+
|
|
266
|
+
io.read
|
|
267
|
+
rescue StandardError
|
|
268
|
+
""
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def close_streams(process_info)
|
|
272
|
+
%i[stdout stderr stdin].each do |stream|
|
|
273
|
+
begin
|
|
274
|
+
process_info[stream]&.close
|
|
275
|
+
rescue StandardError
|
|
276
|
+
nil
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def build_results(process_info, elapsed, exit_status)
|
|
282
|
+
return timeout_results(process_info, elapsed) if process_info[:timeout_triggered]
|
|
283
|
+
return interrupted_results(elapsed) if process_info[:terminated_by] == :interrupt
|
|
284
|
+
fresh_summary = load_summary_results(process_info, elapsed)
|
|
285
|
+
return fresh_summary if exit_status == 0 && fresh_summary
|
|
286
|
+
return failed_process_results(process_info, elapsed, exit_status, fresh_summary) if exit_status != 0
|
|
287
|
+
|
|
288
|
+
parse_results(process_info[:output])
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def timeout_results(process_info, elapsed)
|
|
292
|
+
{
|
|
293
|
+
tests: 0,
|
|
294
|
+
assertions: 0,
|
|
295
|
+
failures: 0,
|
|
296
|
+
errors: 1,
|
|
297
|
+
duration: elapsed,
|
|
298
|
+
success: false,
|
|
299
|
+
error: "Timed out after #{process_info[:timeout]} seconds"
|
|
300
|
+
}
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def interrupted_results(elapsed)
|
|
304
|
+
{
|
|
305
|
+
tests: 0,
|
|
306
|
+
assertions: 0,
|
|
307
|
+
failures: 0,
|
|
308
|
+
errors: 1,
|
|
309
|
+
duration: elapsed,
|
|
310
|
+
success: false,
|
|
311
|
+
error: "Interrupted before completion"
|
|
312
|
+
}
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def failed_process_results(process_info, elapsed, exit_status, fresh_summary)
|
|
316
|
+
return fresh_summary if fresh_summary && fresh_summary[:success] == false
|
|
317
|
+
|
|
318
|
+
parsed = parse_results([process_info[:output], process_info[:stderr_output]].join("\n"))
|
|
319
|
+
parsed[:duration] ||= elapsed
|
|
320
|
+
parsed[:success] = false
|
|
321
|
+
parsed[:error] ||= failure_message_for(process_info, exit_status)
|
|
322
|
+
parsed[:errors] = 1 if parsed[:failures].to_i == 0 && parsed[:errors].to_i == 0
|
|
323
|
+
parsed
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def failure_message_for(process_info, exit_status)
|
|
327
|
+
stderr = process_info[:stderr_output].to_s.strip
|
|
328
|
+
stdout = process_info[:output].to_s.strip
|
|
329
|
+
message = stderr.empty? ? stdout : stderr
|
|
330
|
+
return message unless message.empty?
|
|
331
|
+
|
|
332
|
+
"ace-test exited with status #{exit_status}"
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def load_summary_results(process_info, elapsed)
|
|
336
|
+
package = process_info[:package]
|
|
337
|
+
reports_dir = Atoms::ReportPathResolver.report_directory(
|
|
338
|
+
package["path"],
|
|
339
|
+
report_root: process_info[:report_root],
|
|
340
|
+
package_name: package["name"]
|
|
341
|
+
)
|
|
342
|
+
summary_file = reports_dir ? File.join(reports_dir, "summary.json") : nil
|
|
343
|
+
return nil unless summary_file && File.exist?(summary_file)
|
|
344
|
+
return nil unless summary_fresh_for_run?(summary_file, process_info[:start_time])
|
|
345
|
+
|
|
346
|
+
json_data = JSON.parse(File.read(summary_file))
|
|
347
|
+
results = {
|
|
348
|
+
tests: json_data["total"] || 0,
|
|
349
|
+
assertions: json_data["assertions"] || 0,
|
|
350
|
+
failures: json_data["failed"] || 0,
|
|
351
|
+
errors: json_data["errors"] || 0,
|
|
352
|
+
skipped: json_data["skipped"] || 0,
|
|
353
|
+
duration: json_data["duration"] || elapsed,
|
|
354
|
+
success: json_data["success"] || false
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if results[:assertions] == 0
|
|
358
|
+
report_file = File.join(reports_dir, "report.json")
|
|
359
|
+
if File.exist?(report_file)
|
|
360
|
+
report_data = JSON.parse(File.read(report_file))
|
|
361
|
+
results[:assertions] = report_data.dig("result", "assertions") || 0
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
results
|
|
366
|
+
rescue JSON::ParserError
|
|
367
|
+
nil
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def summary_fresh_for_run?(summary_file, start_time)
|
|
371
|
+
File.mtime(summary_file) >= (start_time - 0.001)
|
|
372
|
+
rescue StandardError
|
|
373
|
+
false
|
|
226
374
|
end
|
|
227
375
|
|
|
228
376
|
def parse_progress(process_info, chunk)
|
|
@@ -8,9 +8,10 @@ module Ace
|
|
|
8
8
|
class ResultAggregator
|
|
9
9
|
attr_reader :packages
|
|
10
10
|
|
|
11
|
-
def initialize(packages, report_root: nil)
|
|
11
|
+
def initialize(packages, report_root: nil, runtime_results: {})
|
|
12
12
|
@packages = packages
|
|
13
13
|
@report_root = report_root
|
|
14
|
+
@runtime_results = runtime_results || {}
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def aggregate
|
|
@@ -46,6 +47,9 @@ module Ace
|
|
|
46
47
|
|
|
47
48
|
def collect_results
|
|
48
49
|
@packages.map do |package|
|
|
50
|
+
runtime = runtime_result(package)
|
|
51
|
+
next runtime if runtime_result_overrides_summary?(package, runtime)
|
|
52
|
+
|
|
49
53
|
reports_dir = Atoms::ReportPathResolver.report_directory(
|
|
50
54
|
package["path"],
|
|
51
55
|
report_root: @report_root,
|
|
@@ -90,8 +94,7 @@ module Ace
|
|
|
90
94
|
}
|
|
91
95
|
end
|
|
92
96
|
else
|
|
93
|
-
|
|
94
|
-
{
|
|
97
|
+
runtime || {
|
|
95
98
|
package: package["name"],
|
|
96
99
|
path: package["path"],
|
|
97
100
|
report_root: @report_root,
|
|
@@ -106,6 +109,13 @@ module Ace
|
|
|
106
109
|
end
|
|
107
110
|
end
|
|
108
111
|
|
|
112
|
+
def runtime_result_overrides_summary?(package, runtime)
|
|
113
|
+
return false unless runtime
|
|
114
|
+
|
|
115
|
+
status = @runtime_results[package["name"]] || {}
|
|
116
|
+
status[:timed_out] || status[:interrupted] || runtime[:success] == false
|
|
117
|
+
end
|
|
118
|
+
|
|
109
119
|
def collect_failed_packages(results)
|
|
110
120
|
results.select { |r| !r[:success] }.map do |result|
|
|
111
121
|
{
|
|
@@ -119,6 +129,32 @@ module Ace
|
|
|
119
129
|
end
|
|
120
130
|
end
|
|
121
131
|
|
|
132
|
+
def runtime_result(package)
|
|
133
|
+
status = @runtime_results[package["name"]]
|
|
134
|
+
return nil unless status && status[:completed]
|
|
135
|
+
|
|
136
|
+
results = status[:results] || {}
|
|
137
|
+
total = results[:tests] || 0
|
|
138
|
+
failures = results[:failures] || 0
|
|
139
|
+
errors = results[:errors] || 0
|
|
140
|
+
skipped = results[:skipped] || 0
|
|
141
|
+
|
|
142
|
+
{
|
|
143
|
+
package: package["name"],
|
|
144
|
+
path: package["path"],
|
|
145
|
+
report_root: @report_root,
|
|
146
|
+
success: status[:success],
|
|
147
|
+
error: results[:error],
|
|
148
|
+
total: total,
|
|
149
|
+
passed: total - failures - errors - skipped,
|
|
150
|
+
failed: failures,
|
|
151
|
+
errors: errors,
|
|
152
|
+
skipped: skipped,
|
|
153
|
+
duration: results[:duration] || status[:elapsed] || 0,
|
|
154
|
+
assertions: results[:assertions] || 0
|
|
155
|
+
}
|
|
156
|
+
end
|
|
157
|
+
|
|
122
158
|
def generate_report(summary)
|
|
123
159
|
report = []
|
|
124
160
|
report << "# ACE Test Suite Report"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-test-runner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.25.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-04-20 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -189,9 +189,9 @@ files:
|
|
|
189
189
|
- lib/ace/test_runner/formatters/progress_formatter.rb
|
|
190
190
|
- lib/ace/test_runner/models/test_configuration.rb
|
|
191
191
|
- lib/ace/test_runner/models/test_failure.rb
|
|
192
|
-
- lib/ace/test_runner/models/test_group.rb
|
|
193
192
|
- lib/ace/test_runner/models/test_report.rb
|
|
194
193
|
- lib/ace/test_runner/models/test_result.rb
|
|
194
|
+
- lib/ace/test_runner/models/test_target.rb
|
|
195
195
|
- lib/ace/test_runner/molecules/cli_argument_parser.rb
|
|
196
196
|
- lib/ace/test_runner/molecules/config_loader.rb
|
|
197
197
|
- lib/ace/test_runner/molecules/deprecation_fixer.rb
|
|
@@ -206,7 +206,7 @@ files:
|
|
|
206
206
|
- lib/ace/test_runner/molecules/test_executor.rb
|
|
207
207
|
- lib/ace/test_runner/organisms/agent_reporter.rb
|
|
208
208
|
- lib/ace/test_runner/organisms/report_generator.rb
|
|
209
|
-
- lib/ace/test_runner/organisms/
|
|
209
|
+
- lib/ace/test_runner/organisms/sequential_target_executor.rb
|
|
210
210
|
- lib/ace/test_runner/organisms/test_orchestrator.rb
|
|
211
211
|
- lib/ace/test_runner/rake_task.rb
|
|
212
212
|
- lib/ace/test_runner/suite.rb
|