rail_verdict 1.3.0 → 1.4.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 +4 -4
- data/lib/rail_verdict/analyzers/bundler_audit.rb +2 -1
- data/lib/rail_verdict/analyzers/minitest.rb +47 -34
- data/lib/rail_verdict/analyzers/rspec.rb +120 -42
- data/lib/rail_verdict/analyzers/rubocop.rb +2 -1
- data/lib/rail_verdict/analyzers/simplecov.rb +71 -75
- data/lib/rail_verdict/check.rb +1 -1
- data/lib/rail_verdict/cli.rb +350 -3
- data/lib/rail_verdict/doctor.rb +2 -1
- data/lib/rail_verdict/handoff.rb +122 -0
- data/lib/rail_verdict/mcp/cache.rb +10 -0
- data/lib/rail_verdict/mcp/server.rb +7 -1
- data/lib/rail_verdict/mcp/tools/create_handoff.rb +115 -0
- data/lib/rail_verdict/mcp/tools/inspect_handoff.rb +72 -0
- data/lib/rail_verdict/mcp/tools/verify_handoff.rb +117 -0
- data/lib/rail_verdict/reuse.rb +168 -0
- data/lib/rail_verdict/schema_validator.rb +5 -0
- data/lib/rail_verdict/verification_environment.rb +4 -44
- data/lib/rail_verdict/version.rb +1 -1
- data/lib/rail_verdict.rb +2 -0
- data/schemas/verification-handoff-v1.schema.json +69 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 220003ceaeba89b1fb2f8f7082a8a2e6f6dcdcc070005221000602524e3b7576
|
|
4
|
+
data.tar.gz: 13fb2353b109fed755c762be216060174b3a89a59bbc6e443f06d133edbf0537
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a58b08566a039a6c32fae4a4e4154be529a2c4e0567afc2ee7ac8b24c6e944e0b0555359eb8ea532c02f6143aa4e7594cce208c41bc284243d519d704f07d62
|
|
7
|
+
data.tar.gz: a79f0ce2a5eb249cd902f60f74b41318dba4f52112004109b42d394540f9ab8aac846a0e5e8d22c1cee009d649016fbf75dc8d715a2440fb498e61b3600247ee
|
|
@@ -19,13 +19,14 @@ module RailVerdict
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def probe(repository_root, runner: ProcessRunner, timeout_seconds: 15.0)
|
|
22
|
+
effective_timeout = [timeout_seconds.to_f, 5.0].min
|
|
22
23
|
command = @command_resolver.call(repository_root)
|
|
23
24
|
invocation = Shared.invocation_for(command, ["version"])
|
|
24
25
|
result = runner.run(
|
|
25
26
|
command.fetch(:executable),
|
|
26
27
|
invocation.fetch("argv"),
|
|
27
28
|
chdir: repository_root,
|
|
28
|
-
timeout_seconds:
|
|
29
|
+
timeout_seconds: effective_timeout
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
return Probe.new(status: "unavailable", message: Shared.detail_for(result)) if result.status == :spawn_failed
|
|
@@ -21,13 +21,14 @@ module RailVerdict
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def probe(repository_root, runner: ProcessRunner, timeout_seconds: 15.0)
|
|
24
|
+
effective_timeout = [timeout_seconds.to_f, 5.0].min
|
|
24
25
|
command = @command_resolver.call(repository_root)
|
|
25
26
|
probe_argv = probe_argv_for(command, repository_root)
|
|
26
27
|
result = runner.run(
|
|
27
28
|
command.fetch(:executable),
|
|
28
29
|
probe_argv,
|
|
29
30
|
chdir: repository_root,
|
|
30
|
-
timeout_seconds:
|
|
31
|
+
timeout_seconds: effective_timeout
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
return Probe.new(status: "unavailable", message: Shared.detail_for(result)) if result.status == :spawn_failed
|
|
@@ -63,7 +64,7 @@ module RailVerdict
|
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
invocation = Shared.invocation_for(command, ["run"])
|
|
66
|
-
output_path = File.join(
|
|
67
|
+
output_path = File.join(Dir.tmpdir, "railverdict-minitest-#{SecureRandom.hex(8)}.json")
|
|
67
68
|
env_reset_required = false
|
|
68
69
|
previous_env = ENV["RAILVERDICT_MINITEST_OUTPUT"]
|
|
69
70
|
begin
|
|
@@ -93,7 +94,25 @@ module RailVerdict
|
|
|
93
94
|
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "malformed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
94
95
|
end
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
# Process exit reconciliation (RH-03):
|
|
98
|
+
failures_and_errors = (summary["failures"] || 0) + (summary["errors"] || 0)
|
|
99
|
+
if result.exit_code == 0
|
|
100
|
+
if failures_and_errors > 0 || !findings.empty?
|
|
101
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "malformed", message: "Minitest exited with status 0 but reported #{failures_and_errors} failures/errors", tool_version: tool_version), []]
|
|
102
|
+
end
|
|
103
|
+
elsif result.exit_code == 1
|
|
104
|
+
if failures_and_errors == 0 && findings.empty?
|
|
105
|
+
detail = Shared.detail_for(result)
|
|
106
|
+
msg = detail.strip.empty? ? "Minitest exited with status 1 but reported 0 failed tests" : detail
|
|
107
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "failed", message: msg, tool_version: tool_version), []]
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
detail = Shared.detail_for(result)
|
|
111
|
+
msg = detail.strip.empty? ? "Minitest exited with unexpected status #{result.exit_code}" : detail
|
|
112
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "failed", message: msg, tool_version: tool_version), []]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
analyzer_result = AnalyzerResult.new(
|
|
97
116
|
analyzer: ANALYZER_ID,
|
|
98
117
|
tool_version: tool_version,
|
|
99
118
|
invocation: invocation,
|
|
@@ -141,16 +160,11 @@ module RailVerdict
|
|
|
141
160
|
|
|
142
161
|
def resolve_reporter_path
|
|
143
162
|
candidates = []
|
|
144
|
-
begin
|
|
145
|
-
specs = Gem::Specification.find_all_by_name("rail_verdict")
|
|
146
|
-
if specs.any?
|
|
147
|
-
best = specs.max_by(&:version)
|
|
148
|
-
candidates << File.join(best.full_gem_path, "exe", "railverdict-minitest-reporter.rb")
|
|
149
|
-
end
|
|
150
|
-
rescue StandardError
|
|
151
|
-
nil
|
|
152
|
-
end
|
|
153
163
|
candidates << File.expand_path("../../../exe/railverdict-minitest-reporter.rb", __dir__)
|
|
164
|
+
if defined?(Gem) && Gem.respond_to?(:loaded_specs) && Gem.loaded_specs["rail_verdict"]
|
|
165
|
+
spec = Gem.loaded_specs["rail_verdict"]
|
|
166
|
+
candidates << File.join(spec.full_gem_path, "exe", "railverdict-minitest-reporter.rb")
|
|
167
|
+
end
|
|
154
168
|
candidates.find { |path| File.file?(path) && File.readable?(path) }
|
|
155
169
|
end
|
|
156
170
|
|
|
@@ -177,31 +191,30 @@ module RailVerdict
|
|
|
177
191
|
end
|
|
178
192
|
|
|
179
193
|
def load_reporter_document(output_path, run_result, invocation, tool_version)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
end
|
|
186
|
-
if bytes.bytesize > 4 * 1024 * 1024
|
|
187
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "truncated", message: "Minitest reporter output exceeds 4 MiB", tool_version: tool_version), []]
|
|
188
|
-
end
|
|
189
|
-
text = bytes.dup.force_encoding(Encoding::UTF_8)
|
|
190
|
-
unless text.valid_encoding?
|
|
191
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "parse_failed", message: "Minitest reporter output is not valid UTF-8", tool_version: tool_version), []]
|
|
192
|
-
end
|
|
193
|
-
begin
|
|
194
|
-
return JSON.parse(text)
|
|
195
|
-
rescue JSON::ParserError => error
|
|
196
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "parse_failed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
197
|
-
end
|
|
194
|
+
unless File.file?(output_path)
|
|
195
|
+
detail = Shared.detail_for(run_result)
|
|
196
|
+
msg = detail.strip.empty? ? "Minitest reporter did not produce output" : detail
|
|
197
|
+
status = run_result.exit_code && run_result.exit_code != 0 ? "failed" : "malformed"
|
|
198
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: status, message: msg, tool_version: tool_version), []]
|
|
198
199
|
end
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
|
|
201
|
+
begin
|
|
202
|
+
bytes = File.binread(output_path)
|
|
203
|
+
rescue SystemCallError => error
|
|
204
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "malformed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
202
205
|
end
|
|
206
|
+
|
|
207
|
+
if bytes.bytesize > 4 * 1024 * 1024
|
|
208
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "truncated", message: "Minitest reporter output exceeds 4 MiB", tool_version: tool_version), []]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
text = bytes.dup.force_encoding(Encoding::UTF_8)
|
|
212
|
+
unless text.valid_encoding?
|
|
213
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "parse_failed", message: "Minitest reporter output is not valid UTF-8", tool_version: tool_version), []]
|
|
214
|
+
end
|
|
215
|
+
|
|
203
216
|
begin
|
|
204
|
-
JSON.parse(
|
|
217
|
+
JSON.parse(text)
|
|
205
218
|
rescue JSON::ParserError => error
|
|
206
219
|
[Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "parse_failed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
207
220
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require "securerandom"
|
|
4
6
|
|
|
5
7
|
require_relative "_shared"
|
|
6
8
|
|
|
@@ -19,13 +21,16 @@ module RailVerdict
|
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def probe(repository_root, runner: ProcessRunner, timeout_seconds: 15.0)
|
|
24
|
+
effective_timeout = [timeout_seconds.to_f, 5.0].min
|
|
22
25
|
command = @command_resolver.call(repository_root)
|
|
23
|
-
|
|
26
|
+
clean_prefix = clean_args_prefix(command.fetch(:args_prefix))
|
|
27
|
+
clean_command = command.merge(args_prefix: clean_prefix)
|
|
28
|
+
invocation = Shared.invocation_for(clean_command, ["--version"])
|
|
24
29
|
result = runner.run(
|
|
25
|
-
|
|
30
|
+
clean_command.fetch(:executable),
|
|
26
31
|
invocation.fetch("argv"),
|
|
27
32
|
chdir: repository_root,
|
|
28
|
-
timeout_seconds:
|
|
33
|
+
timeout_seconds: effective_timeout
|
|
29
34
|
)
|
|
30
35
|
|
|
31
36
|
return Probe.new(status: "unavailable", message: Shared.detail_for(result)) if result.status == :spawn_failed
|
|
@@ -47,61 +52,136 @@ module RailVerdict
|
|
|
47
52
|
|
|
48
53
|
def run(repository_root, runner: ProcessRunner, timeout_seconds: 30.0, probe_result: nil, configuration: nil)
|
|
49
54
|
command = @command_resolver.call(repository_root)
|
|
55
|
+
clean_prefix = clean_args_prefix(command.fetch(:args_prefix))
|
|
56
|
+
clean_command = command.merge(args_prefix: clean_prefix)
|
|
50
57
|
probe_result ||= probe(repository_root, runner: runner, timeout_seconds: timeout_seconds)
|
|
51
|
-
version_invocation = Shared.invocation_for(
|
|
58
|
+
version_invocation = Shared.invocation_for(clean_command, ["--version"])
|
|
52
59
|
|
|
53
60
|
unless probe_result.status == "succeeded"
|
|
54
61
|
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: version_invocation, status: probe_result.status, message: probe_result.message, tool_version: probe_result.version), []]
|
|
55
62
|
end
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
output_path = File.join(Dir.tmpdir, "railverdict-rspec-#{SecureRandom.hex(8)}.json")
|
|
65
|
+
public_invocation = Shared.invocation_for(clean_command, ["--format", "json"])
|
|
66
|
+
run_argv = clean_prefix.dup.concat(["--format", "json", "--out", output_path])
|
|
67
|
+
|
|
59
68
|
max_stdout = resolve_stdout_limit(configuration, repository_root, 16 * 1024 * 1024)
|
|
60
|
-
result = runner.run(
|
|
61
|
-
command.fetch(:executable),
|
|
62
|
-
invocation.fetch("argv"),
|
|
63
|
-
chdir: repository_root,
|
|
64
|
-
timeout_seconds: timeout_seconds,
|
|
65
|
-
max_stdout_bytes: max_stdout
|
|
66
|
-
)
|
|
67
69
|
tool_version = probe_result.version
|
|
68
70
|
|
|
69
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "unavailable", message: Shared.detail_for(result), tool_version: tool_version), []] if result.status == :spawn_failed
|
|
70
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "truncated", message: Shared.detail_for(result), tool_version: tool_version), []] if Shared.truncated?(result)
|
|
71
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "timed_out", message: Shared.detail_for(result), tool_version: tool_version), []] if result.status == :timed_out
|
|
72
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "signaled", message: Shared.detail_for(result), tool_version: tool_version), []] if result.status == :signaled
|
|
73
|
-
|
|
74
71
|
begin
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
result = runner.run(
|
|
73
|
+
clean_command.fetch(:executable),
|
|
74
|
+
run_argv,
|
|
75
|
+
chdir: repository_root,
|
|
76
|
+
timeout_seconds: timeout_seconds,
|
|
77
|
+
max_stdout_bytes: max_stdout
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "unavailable", message: Shared.detail_for(result), tool_version: tool_version), []] if result.status == :spawn_failed
|
|
81
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "truncated", message: Shared.detail_for(result), tool_version: tool_version), []] if Shared.truncated?(result)
|
|
82
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "timed_out", message: Shared.detail_for(result), tool_version: tool_version), []] if result.status == :timed_out
|
|
83
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "signaled", message: Shared.detail_for(result), tool_version: tool_version), []] if result.status == :signaled
|
|
84
|
+
|
|
85
|
+
unless File.file?(output_path)
|
|
86
|
+
detail = Shared.detail_for(result)
|
|
87
|
+
msg = detail.strip.empty? ? "RSpec did not produce structured output" : detail
|
|
88
|
+
status = result.exit_code && result.exit_code != 0 ? "failed" : "malformed"
|
|
89
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: status, message: msg, tool_version: tool_version), []]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
begin
|
|
93
|
+
bytes = File.binread(output_path)
|
|
94
|
+
rescue SystemCallError => error
|
|
95
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "malformed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if bytes.bytesize > max_stdout
|
|
99
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "truncated", message: "RSpec output exceeds #{max_stdout} bytes", tool_version: tool_version), []]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
text = bytes.dup.force_encoding(Encoding::UTF_8)
|
|
103
|
+
unless text.valid_encoding?
|
|
104
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "parse_failed", message: "RSpec output is not valid UTF-8", tool_version: tool_version), []]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
begin
|
|
108
|
+
document = JSON.parse(text)
|
|
109
|
+
rescue JSON::ParserError => error
|
|
110
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "parse_failed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
begin
|
|
114
|
+
summary, findings = normalize_document(document)
|
|
115
|
+
rescue MalformedOutput => error
|
|
116
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "malformed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Process exit reconciliation (RH-02):
|
|
120
|
+
# 0: all examples passed, 0 failures/errors
|
|
121
|
+
# 1: failed examples present (failures > 0 or findings non-empty)
|
|
122
|
+
# Any other exit code or contradiction: fail closed
|
|
123
|
+
failures_count = summary["failures"] || 0
|
|
124
|
+
if result.exit_code == 0
|
|
125
|
+
if failures_count > 0 || !findings.empty?
|
|
126
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "malformed", message: "RSpec exited with status 0 but reported #{failures_count} failures", tool_version: tool_version), []]
|
|
127
|
+
end
|
|
128
|
+
elsif result.exit_code == 1
|
|
129
|
+
if failures_count == 0 && findings.empty?
|
|
130
|
+
detail = Shared.detail_for(result)
|
|
131
|
+
msg = detail.strip.empty? ? "RSpec exited with status 1 but reported 0 failed examples" : detail
|
|
132
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "failed", message: msg, tool_version: tool_version), []]
|
|
133
|
+
end
|
|
134
|
+
else
|
|
135
|
+
detail = Shared.detail_for(result)
|
|
136
|
+
msg = detail.strip.empty? ? "RSpec exited with unexpected status #{result.exit_code}" : detail
|
|
137
|
+
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: public_invocation, status: "failed", message: msg, tool_version: tool_version), []]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
analyzer_result = AnalyzerResult.new(
|
|
141
|
+
analyzer: ANALYZER_ID,
|
|
142
|
+
tool_version: tool_version,
|
|
143
|
+
invocation: public_invocation,
|
|
144
|
+
execution_status: "succeeded",
|
|
145
|
+
finding_ids: findings.map(&:id),
|
|
146
|
+
evidence_summary: summary
|
|
147
|
+
)
|
|
148
|
+
[analyzer_result, findings]
|
|
149
|
+
ensure
|
|
150
|
+
begin
|
|
151
|
+
File.unlink(output_path) if output_path && File.exist?(output_path)
|
|
152
|
+
rescue StandardError
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
78
155
|
end
|
|
79
|
-
|
|
80
|
-
begin
|
|
81
|
-
summary, findings = normalize_document(document)
|
|
82
|
-
rescue MalformedOutput => error
|
|
83
|
-
return [Shared.failure_result(analyzer_id: ANALYZER_ID, invocation: invocation, status: "malformed", message: Shared.bounded_message(error.message), tool_version: tool_version), []]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
analyzer_result = AnalyzerResult.new(
|
|
87
|
-
analyzer: ANALYZER_ID,
|
|
88
|
-
tool_version: tool_version,
|
|
89
|
-
invocation: invocation,
|
|
90
|
-
execution_status: "succeeded",
|
|
91
|
-
finding_ids: findings.map(&:id),
|
|
92
|
-
evidence_summary: summary
|
|
93
|
-
)
|
|
94
|
-
[analyzer_result, findings]
|
|
95
156
|
end
|
|
96
157
|
|
|
97
158
|
private
|
|
98
159
|
|
|
99
160
|
def default_command(repository_root)
|
|
100
161
|
if File.file?(File.join(repository_root, "Gemfile"))
|
|
101
|
-
{ executable: "bundle", args_prefix: ["exec", "rspec"
|
|
162
|
+
{ executable: "bundle", args_prefix: ["exec", "rspec"] }
|
|
102
163
|
else
|
|
103
|
-
{ executable: "rspec", args_prefix: [
|
|
164
|
+
{ executable: "rspec", args_prefix: [] }
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def clean_args_prefix(prefix)
|
|
169
|
+
cleaned = []
|
|
170
|
+
skip_next = false
|
|
171
|
+
Array(prefix).each_with_index do |arg, i|
|
|
172
|
+
if skip_next
|
|
173
|
+
skip_next = false
|
|
174
|
+
next
|
|
175
|
+
end
|
|
176
|
+
if arg == "--format" && Array(prefix)[i + 1] == "json"
|
|
177
|
+
skip_next = true
|
|
178
|
+
next
|
|
179
|
+
elsif arg == "--format=json"
|
|
180
|
+
next
|
|
181
|
+
end
|
|
182
|
+
cleaned << arg
|
|
104
183
|
end
|
|
184
|
+
cleaned
|
|
105
185
|
end
|
|
106
186
|
|
|
107
187
|
def normalize_document(document)
|
|
@@ -142,7 +222,7 @@ module RailVerdict
|
|
|
142
222
|
raw_msg = (example["exception"] && example["exception"]["message"]) || example["full_description"] || example["description"] || nil
|
|
143
223
|
message = Shared.normalize_finding_message(ANALYZER_ID, raw_msg.nil? || raw_msg.to_s.strip.empty? ? "rspec example failed" : raw_msg)
|
|
144
224
|
|
|
145
|
-
rule_id = "rspec/example:#{example[
|
|
225
|
+
rule_id = "rspec/example:#{example[id] || id_for(example, index)}"
|
|
146
226
|
path = normalize_path(example["file_path"] || example["file"] || "spec/unknown_spec.rb")
|
|
147
227
|
failure_line, failure_path = failure_location(example, path)
|
|
148
228
|
start_line = failure_line || example["line_number"] || extract_line(example["id"])
|
|
@@ -273,14 +353,12 @@ module RailVerdict
|
|
|
273
353
|
end
|
|
274
354
|
|
|
275
355
|
def resolve_stdout_limit(configuration, repository_root, default_bytes)
|
|
276
|
-
# Prefer explicit per-analyzer config if present (future-compatible), else default.
|
|
277
356
|
raw_limit = nil
|
|
278
357
|
if configuration
|
|
279
358
|
sel = configuration.analyzers[ANALYZER_ID] rescue nil
|
|
280
359
|
raw_limit = sel && sel["output_limit_bytes"]
|
|
281
360
|
end
|
|
282
361
|
raw_limit ||= default_bytes
|
|
283
|
-
# Clamp to safe ceiling
|
|
284
362
|
limit = Integer(raw_limit) rescue default_bytes
|
|
285
363
|
limit = default_bytes if limit <= 0
|
|
286
364
|
max = RailVerdict::ProcessRunner::MAX_SAFE_STDOUT_BYTES
|
|
@@ -27,13 +27,14 @@ module RailVerdict
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def probe(repository_root, runner: ProcessRunner, timeout_seconds: 15.0)
|
|
30
|
+
effective_timeout = [timeout_seconds.to_f, 5.0].min
|
|
30
31
|
command = @command_resolver.call(repository_root)
|
|
31
32
|
invocation = invocation_for(command, ["--version"])
|
|
32
33
|
result = runner.run(
|
|
33
34
|
command.fetch(:executable),
|
|
34
35
|
invocation.fetch("argv"),
|
|
35
36
|
chdir: repository_root,
|
|
36
|
-
timeout_seconds:
|
|
37
|
+
timeout_seconds: effective_timeout
|
|
37
38
|
)
|
|
38
39
|
|
|
39
40
|
return Probe.new(status: "unavailable", message: detail_for(result)) if result.status == :spawn_failed
|