ace-test-runner 0.18.0
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 +7 -0
- data/.ace-defaults/test/runner.yml +35 -0
- data/.ace-defaults/test/suite.yml +31 -0
- data/.ace-defaults/test-runner/config.yml +61 -0
- data/CHANGELOG.md +626 -0
- data/LICENSE +21 -0
- data/README.md +42 -0
- data/Rakefile +14 -0
- data/exe/ace-test +26 -0
- data/exe/ace-test-suite +149 -0
- data/lib/ace/test_runner/atoms/command_builder.rb +165 -0
- data/lib/ace/test_runner/atoms/lazy_loader.rb +62 -0
- data/lib/ace/test_runner/atoms/line_number_resolver.rb +86 -0
- data/lib/ace/test_runner/atoms/report_directory_resolver.rb +48 -0
- data/lib/ace/test_runner/atoms/report_path_resolver.rb +67 -0
- data/lib/ace/test_runner/atoms/result_parser.rb +254 -0
- data/lib/ace/test_runner/atoms/test_detector.rb +114 -0
- data/lib/ace/test_runner/atoms/test_folder_detector.rb +53 -0
- data/lib/ace/test_runner/atoms/test_type_detector.rb +83 -0
- data/lib/ace/test_runner/atoms/timestamp_generator.rb +103 -0
- data/lib/ace/test_runner/cli/commands/test.rb +326 -0
- data/lib/ace/test_runner/cli.rb +16 -0
- data/lib/ace/test_runner/formatters/base_formatter.rb +102 -0
- data/lib/ace/test_runner/formatters/json_formatter.rb +90 -0
- data/lib/ace/test_runner/formatters/markdown_formatter.rb +91 -0
- data/lib/ace/test_runner/formatters/progress_file_formatter.rb +164 -0
- data/lib/ace/test_runner/formatters/progress_formatter.rb +328 -0
- data/lib/ace/test_runner/models/test_configuration.rb +165 -0
- data/lib/ace/test_runner/models/test_failure.rb +95 -0
- data/lib/ace/test_runner/models/test_group.rb +105 -0
- data/lib/ace/test_runner/models/test_report.rb +145 -0
- data/lib/ace/test_runner/models/test_result.rb +86 -0
- data/lib/ace/test_runner/molecules/cli_argument_parser.rb +263 -0
- data/lib/ace/test_runner/molecules/config_loader.rb +162 -0
- data/lib/ace/test_runner/molecules/deprecation_fixer.rb +204 -0
- data/lib/ace/test_runner/molecules/failed_package_reporter.rb +100 -0
- data/lib/ace/test_runner/molecules/failure_analyzer.rb +249 -0
- data/lib/ace/test_runner/molecules/in_process_runner.rb +249 -0
- data/lib/ace/test_runner/molecules/package_resolver.rb +106 -0
- data/lib/ace/test_runner/molecules/pattern_resolver.rb +146 -0
- data/lib/ace/test_runner/molecules/rake_integration.rb +218 -0
- data/lib/ace/test_runner/molecules/report_storage.rb +303 -0
- data/lib/ace/test_runner/molecules/smart_test_executor.rb +107 -0
- data/lib/ace/test_runner/molecules/test_executor.rb +162 -0
- data/lib/ace/test_runner/organisms/agent_reporter.rb +384 -0
- data/lib/ace/test_runner/organisms/report_generator.rb +151 -0
- data/lib/ace/test_runner/organisms/sequential_group_executor.rb +185 -0
- data/lib/ace/test_runner/organisms/test_orchestrator.rb +648 -0
- data/lib/ace/test_runner/rake_task.rb +90 -0
- data/lib/ace/test_runner/suite/display_helpers.rb +117 -0
- data/lib/ace/test_runner/suite/display_manager.rb +204 -0
- data/lib/ace/test_runner/suite/duration_estimator.rb +50 -0
- data/lib/ace/test_runner/suite/orchestrator.rb +120 -0
- data/lib/ace/test_runner/suite/process_monitor.rb +268 -0
- data/lib/ace/test_runner/suite/result_aggregator.rb +176 -0
- data/lib/ace/test_runner/suite/simple_display_manager.rb +122 -0
- data/lib/ace/test_runner/suite.rb +22 -0
- data/lib/ace/test_runner/version.rb +7 -0
- data/lib/ace/test_runner.rb +69 -0
- metadata +246 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "ostruct"
|
|
5
|
+
require "ace/support/config"
|
|
6
|
+
|
|
7
|
+
module Ace
|
|
8
|
+
module TestRunner
|
|
9
|
+
module Molecules
|
|
10
|
+
# Load configuration using Ace::Support::Config.create() API
|
|
11
|
+
# Follows ADR-022: Configuration Default and Override Pattern
|
|
12
|
+
#
|
|
13
|
+
# Configuration priority (highest to lowest):
|
|
14
|
+
# 1. CLI options (handled by merge_with_options)
|
|
15
|
+
# 2. Explicit config_path if provided
|
|
16
|
+
# 3. Project config: .ace/test/runner.yml (nearest wins via cascade)
|
|
17
|
+
# 4. User config: ~/.ace/test/runner.yml
|
|
18
|
+
# 5. Gem defaults: ace-test-runner/.ace-defaults/test-runner/config.yml
|
|
19
|
+
class ConfigLoader
|
|
20
|
+
# Load gem defaults for direct access (used by tests)
|
|
21
|
+
# @return [Hash] Default configuration with symbol keys
|
|
22
|
+
def self.load_gem_defaults
|
|
23
|
+
gem_root = Gem.loaded_specs["ace-test-runner"]&.gem_dir ||
|
|
24
|
+
File.expand_path("../../../..", __dir__)
|
|
25
|
+
|
|
26
|
+
resolver = Ace::Support::Config.create(
|
|
27
|
+
config_dir: ".ace",
|
|
28
|
+
defaults_dir: ".ace-defaults",
|
|
29
|
+
gem_path: gem_root
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
config = resolver.resolve_namespace("test-runner").data
|
|
33
|
+
deep_symbolize_keys(config)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Reset method for test isolation (no-op since we don't cache at class level)
|
|
37
|
+
def self.reset_gem_defaults!
|
|
38
|
+
# No-op: Ace::Support::Config.create() is called fresh each time
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def load(config_path = nil)
|
|
42
|
+
gem_root = Gem.loaded_specs["ace-test-runner"]&.gem_dir ||
|
|
43
|
+
File.expand_path("../../../..", __dir__)
|
|
44
|
+
|
|
45
|
+
resolver = Ace::Support::Config.create(
|
|
46
|
+
config_dir: ".ace",
|
|
47
|
+
defaults_dir: ".ace-defaults",
|
|
48
|
+
gem_path: gem_root
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Get merged config from cascade
|
|
52
|
+
config = resolver.resolve_file(["test-runner/config.yml", "test/runner.yml"]).data
|
|
53
|
+
|
|
54
|
+
# If explicit config_path provided, merge it on top
|
|
55
|
+
if config_path && File.exist?(config_path)
|
|
56
|
+
user_config = load_from_file(config_path)
|
|
57
|
+
config = Ace::Support::Config::Atoms::DeepMerger.merge(config, user_config)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Convert to symbol keys for backward compatibility
|
|
61
|
+
config = deep_symbolize_keys(config)
|
|
62
|
+
|
|
63
|
+
validate_config(config)
|
|
64
|
+
normalize_config(config)
|
|
65
|
+
rescue => e
|
|
66
|
+
warn "Warning: Could not load ace-test-runner config: #{e.message}" if ENV["DEBUG"]
|
|
67
|
+
# Return minimal valid config on error
|
|
68
|
+
normalize_config({version: 1})
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def merge_with_options(config, options)
|
|
72
|
+
merged = deep_copy(config)
|
|
73
|
+
|
|
74
|
+
# Override defaults with command-line options
|
|
75
|
+
if options[:format]
|
|
76
|
+
merged[:defaults] ||= {}
|
|
77
|
+
merged[:defaults][:reporter] = options[:format]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if options.key?(:color)
|
|
81
|
+
merged[:defaults] ||= {}
|
|
82
|
+
merged[:defaults][:color] = options[:color]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if options.key?(:fail_fast)
|
|
86
|
+
merged[:defaults] ||= {}
|
|
87
|
+
merged[:defaults][:fail_fast] = options[:fail_fast]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if options[:report_dir]
|
|
91
|
+
merged[:defaults] ||= {}
|
|
92
|
+
merged[:defaults][:report_dir] = options[:report_dir]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Override failure limits with command-line options
|
|
96
|
+
if options[:max_display]
|
|
97
|
+
merged[:failure_limits] ||= {}
|
|
98
|
+
merged[:failure_limits][:max_display] = options[:max_display]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
OpenStruct.new(merged)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def load_from_file(path)
|
|
107
|
+
YAML.safe_load_file(path, permitted_classes: [], aliases: true) || {}
|
|
108
|
+
rescue => e
|
|
109
|
+
warn "Warning: Failed to load config from #{path}: #{e.message}"
|
|
110
|
+
{}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Recursively convert string keys to symbols
|
|
114
|
+
def deep_symbolize_keys(obj)
|
|
115
|
+
self.class.deep_symbolize_keys(obj)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Class method version for use in self.load_gem_defaults
|
|
119
|
+
def self.deep_symbolize_keys(obj)
|
|
120
|
+
case obj
|
|
121
|
+
when Hash
|
|
122
|
+
obj.each_with_object({}) do |(key, value), result|
|
|
123
|
+
result[key.to_sym] = deep_symbolize_keys(value)
|
|
124
|
+
end
|
|
125
|
+
when Array
|
|
126
|
+
obj.map { |item| deep_symbolize_keys(item) }
|
|
127
|
+
else
|
|
128
|
+
obj
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_config(config)
|
|
133
|
+
unless config[:version]
|
|
134
|
+
warn "Warning: Configuration missing version field, assuming version 1"
|
|
135
|
+
config[:version] = 1
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if config[:version] > 1
|
|
139
|
+
warn "Warning: Configuration version #{config[:version]} is newer than supported version 1"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
config
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def normalize_config(config)
|
|
146
|
+
# Ensure all sections exist as hashes (defaults already merged in load)
|
|
147
|
+
config[:patterns] ||= {}
|
|
148
|
+
config[:groups] ||= {}
|
|
149
|
+
config[:defaults] ||= {}
|
|
150
|
+
config[:failure_limits] ||= {}
|
|
151
|
+
config[:execution] ||= {}
|
|
152
|
+
|
|
153
|
+
config
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def deep_copy(obj)
|
|
157
|
+
Marshal.load(Marshal.dump(obj))
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module TestRunner
|
|
5
|
+
module Molecules
|
|
6
|
+
# Identifies and suggests fixes for deprecated code patterns
|
|
7
|
+
class DeprecationFixer
|
|
8
|
+
DEPRECATION_PATTERNS = [
|
|
9
|
+
{
|
|
10
|
+
pattern: /must_equal/,
|
|
11
|
+
replacement: "must_be :==",
|
|
12
|
+
description: "Replace must_equal with must_be :=="
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
pattern: /wont_equal/,
|
|
16
|
+
replacement: "wont_be :==",
|
|
17
|
+
description: "Replace wont_equal with wont_be :=="
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
pattern: /must_be_nil/,
|
|
21
|
+
replacement: "must_be_nil",
|
|
22
|
+
description: "Use must_be_nil (no change needed)"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pattern: /\.must_be\s+:([<>]=?)/,
|
|
26
|
+
replacement: 'must_be :\1',
|
|
27
|
+
description: "Comparison operators should use symbols"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
pattern: /assert_equal\s+nil,/,
|
|
31
|
+
replacement: "assert_nil",
|
|
32
|
+
description: "Use assert_nil instead of assert_equal nil"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
pattern: /refute_equal\s+nil,/,
|
|
36
|
+
replacement: "refute_nil",
|
|
37
|
+
description: "Use refute_nil instead of refute_equal nil"
|
|
38
|
+
}
|
|
39
|
+
].freeze
|
|
40
|
+
|
|
41
|
+
def find_deprecations(content)
|
|
42
|
+
deprecations = []
|
|
43
|
+
|
|
44
|
+
content.lines.each_with_index do |line, index|
|
|
45
|
+
DEPRECATION_PATTERNS.each do |pattern_info|
|
|
46
|
+
if line.match?(pattern_info[:pattern])
|
|
47
|
+
deprecations << {
|
|
48
|
+
line_number: index + 1,
|
|
49
|
+
line_content: line.strip,
|
|
50
|
+
pattern: pattern_info[:pattern].source,
|
|
51
|
+
suggestion: pattern_info[:description],
|
|
52
|
+
replacement: pattern_info[:replacement]
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
deprecations
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def fix_file(file_path, dry_run: false)
|
|
62
|
+
unless File.exist?(file_path)
|
|
63
|
+
return {success: false, error: "File not found: #{file_path}"}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
original_content = File.read(file_path)
|
|
67
|
+
fixed_content = apply_fixes(original_content)
|
|
68
|
+
|
|
69
|
+
if original_content == fixed_content
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
changes: 0,
|
|
73
|
+
message: "No deprecations found"
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
unless dry_run
|
|
78
|
+
File.write(file_path, fixed_content)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
success: true,
|
|
83
|
+
changes: count_changes(original_content, fixed_content),
|
|
84
|
+
message: dry_run ? "Would fix deprecations (dry run)" : "Fixed deprecations",
|
|
85
|
+
diff: generate_diff(original_content, fixed_content)
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def fix_deprecations_in_output(test_output)
|
|
90
|
+
fixes = []
|
|
91
|
+
|
|
92
|
+
# Look for deprecation warnings in test output
|
|
93
|
+
test_output.scan(/DEPRECATION WARNING: (.+)/) do |warning|
|
|
94
|
+
fix = analyze_warning(warning.first)
|
|
95
|
+
fixes << fix if fix
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
fixes.uniq
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def generate_fix_report(deprecations)
|
|
102
|
+
return "No deprecations found." if deprecations.empty?
|
|
103
|
+
|
|
104
|
+
lines = ["# Deprecation Fixes Required", ""]
|
|
105
|
+
|
|
106
|
+
grouped = deprecations.group_by { |d| d[:file] || "Unknown" }
|
|
107
|
+
|
|
108
|
+
grouped.each do |file, file_deprecations|
|
|
109
|
+
lines << "## File: #{file}"
|
|
110
|
+
lines << ""
|
|
111
|
+
|
|
112
|
+
file_deprecations.each do |dep|
|
|
113
|
+
lines << "- Line #{dep[:line_number]}: #{dep[:suggestion]}"
|
|
114
|
+
if dep[:replacement]
|
|
115
|
+
lines << " Replace: `#{dep[:line_content]}`"
|
|
116
|
+
lines << " With: `#{apply_single_fix(dep[:line_content], dep)}`"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
lines << ""
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
lines.join("\n")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def apply_fixes(content)
|
|
129
|
+
fixed_content = content.dup
|
|
130
|
+
|
|
131
|
+
DEPRECATION_PATTERNS.each do |pattern_info|
|
|
132
|
+
fixed_content.gsub!(pattern_info[:pattern], pattern_info[:replacement])
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
fixed_content
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def apply_single_fix(line, deprecation_info)
|
|
139
|
+
return line unless deprecation_info[:replacement]
|
|
140
|
+
|
|
141
|
+
pattern = Regexp.new(deprecation_info[:pattern])
|
|
142
|
+
line.gsub(pattern, deprecation_info[:replacement])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def count_changes(original, fixed)
|
|
146
|
+
original_lines = original.lines
|
|
147
|
+
fixed_lines = fixed.lines
|
|
148
|
+
|
|
149
|
+
changes = 0
|
|
150
|
+
[original_lines.length, fixed_lines.length].max.times do |i|
|
|
151
|
+
if original_lines[i] != fixed_lines[i]
|
|
152
|
+
changes += 1
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
changes
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def generate_diff(original, fixed)
|
|
160
|
+
# Simple diff generation
|
|
161
|
+
diff_lines = []
|
|
162
|
+
original_lines = original.lines
|
|
163
|
+
fixed_lines = fixed.lines
|
|
164
|
+
|
|
165
|
+
[original_lines.length, fixed_lines.length].max.times do |i|
|
|
166
|
+
if original_lines[i] != fixed_lines[i]
|
|
167
|
+
diff_lines << "- #{original_lines[i]}" if original_lines[i]
|
|
168
|
+
diff_lines << "+ #{fixed_lines[i]}" if fixed_lines[i]
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
diff_lines.join
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def analyze_warning(warning_text)
|
|
176
|
+
# Extract file and line information from warning if available
|
|
177
|
+
if warning_text =~ /(.+):(\d+):\s*(.+)/
|
|
178
|
+
{
|
|
179
|
+
file: $1,
|
|
180
|
+
line: $2.to_i,
|
|
181
|
+
message: $3,
|
|
182
|
+
suggestion: find_fix_for_warning($3)
|
|
183
|
+
}
|
|
184
|
+
else
|
|
185
|
+
{
|
|
186
|
+
message: warning_text,
|
|
187
|
+
suggestion: find_fix_for_warning(warning_text)
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def find_fix_for_warning(warning_text)
|
|
193
|
+
DEPRECATION_PATTERNS.each do |pattern_info|
|
|
194
|
+
if warning_text.match?(pattern_info[:pattern])
|
|
195
|
+
return pattern_info[:description]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
"Review deprecation warning and update code accordingly"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require_relative "../atoms/report_path_resolver"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module TestRunner
|
|
8
|
+
module Molecules
|
|
9
|
+
module FailedPackageReporter
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def format_for_display(package)
|
|
13
|
+
path = extract_path(package)
|
|
14
|
+
report_root = extract_report_root(package)
|
|
15
|
+
package_name = extract_name(package) || File.basename(path)
|
|
16
|
+
report_path = Atoms::ReportPathResolver.call(
|
|
17
|
+
path,
|
|
18
|
+
report_root: report_root,
|
|
19
|
+
package_name: package_name
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if report_path
|
|
23
|
+
begin
|
|
24
|
+
relative_path = Pathname.new(report_path).relative_path_from(Dir.pwd)
|
|
25
|
+
" → See #{relative_path}"
|
|
26
|
+
rescue => e
|
|
27
|
+
warn "Failed to calculate relative path: #{e.message}" if debug_mode?
|
|
28
|
+
" → See #{report_path}"
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
reports_path = fallback_reports_path(path, report_root, package_name)
|
|
32
|
+
begin
|
|
33
|
+
relative_path = Pathname.new(reports_path).relative_path_from(Dir.pwd)
|
|
34
|
+
" → Check #{relative_path}/ for details"
|
|
35
|
+
rescue => e
|
|
36
|
+
warn "Failed to calculate relative path: #{e.message}" if debug_mode?
|
|
37
|
+
" → Check #{reports_path}/ for details"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def format_for_markdown(package)
|
|
43
|
+
path = extract_path(package)
|
|
44
|
+
report_root = extract_report_root(package)
|
|
45
|
+
package_name = extract_name(package) || File.basename(path)
|
|
46
|
+
report_path = Atoms::ReportPathResolver.call(
|
|
47
|
+
path,
|
|
48
|
+
report_root: report_root,
|
|
49
|
+
package_name: package_name
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if report_path
|
|
53
|
+
relative_report_path = relative_or_absolute(report_path)
|
|
54
|
+
"- Report: `#{relative_report_path}`"
|
|
55
|
+
else
|
|
56
|
+
reports_path = fallback_reports_path(path, report_root, package_name)
|
|
57
|
+
relative_reports_path = relative_or_absolute(reports_path)
|
|
58
|
+
"- Report: Check `#{relative_reports_path}/` for details"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class << self
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def extract_path(package)
|
|
66
|
+
package[:path] || package["path"]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def extract_name(package)
|
|
70
|
+
package[:name] || package["name"]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def extract_report_root(package)
|
|
74
|
+
package[:report_root] || package["report_root"]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def fallback_reports_path(path, report_root, package_name)
|
|
78
|
+
if report_root
|
|
79
|
+
short_name = package_name.to_s.sub(/\Aace-/, "")
|
|
80
|
+
return File.join(report_root, short_name) unless short_name.empty?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
File.join(path, "test-reports")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def debug_mode?
|
|
87
|
+
ENV["DEBUG"]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def relative_or_absolute(path)
|
|
91
|
+
Pathname.new(path).relative_path_from(Dir.pwd).to_s
|
|
92
|
+
rescue => e
|
|
93
|
+
warn "Failed to calculate relative path: #{e.message}" if debug_mode?
|
|
94
|
+
path
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|