featureparity 0.0.11 → 0.0.13
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/fp/client.rb +5 -1
- data/lib/fp/commands/ci_report.rb +48 -3
- data/lib/fp/junit.rb +20 -0
- data/lib/fp/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3dba0cc6ba18239f6952822d96bd4526d26d207e8b7e5448554de745e6517a8
|
|
4
|
+
data.tar.gz: 1521147f4b1574062b3e04f22cef0ce1cb7ef6b822f767c3f75ed66a200b6a5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a65ac140de45599a9a5a27f4705e179fd816bad77fe563477fd6b92efbc0d592cea80f8c90c2e519d559fc604927d6b321c3a3af99bb10c2b99b9e06b39467f5
|
|
7
|
+
data.tar.gz: c9ba4966e9a2d036f266b3646eb0c7569399d606d196175df189047de7693c55badce253c94205045449ca7b20117a6f496eeee486067c03ca2291f2caed026f
|
data/lib/fp/client.rb
CHANGED
|
@@ -151,7 +151,8 @@ module Fp
|
|
|
151
151
|
# server can fall back to resolving markers itself for older clients — but it can
|
|
152
152
|
# only do that by fetching the test files from GitHub, which fails silently on a
|
|
153
153
|
# private repo, so resolved results are always sent when we have them.
|
|
154
|
-
def ingest_ci_results(project_id, ci_url:, surface:, junit_xml:, pr_url: nil, results: nil
|
|
154
|
+
def ingest_ci_results(project_id, ci_url:, surface:, junit_xml:, pr_url: nil, results: nil,
|
|
155
|
+
unmarked_failures: nil)
|
|
155
156
|
body = {
|
|
156
157
|
ci_url: ci_url,
|
|
157
158
|
surface: surface,
|
|
@@ -160,6 +161,9 @@ module Fp
|
|
|
160
161
|
# Only send pr_url when set, so a merged-branch run stays a plain ingest.
|
|
161
162
|
body[:pr_url] = pr_url if pr_url && !pr_url.empty?
|
|
162
163
|
body[:results] = results if results && !results.empty?
|
|
164
|
+
# Loose ends: failing tests with no fp:<slug> marker. The server stores them so the
|
|
165
|
+
# matrix can surface a red suite whose failures aren't bound to any requirement.
|
|
166
|
+
body[:unmarked_failures] = unmarked_failures if unmarked_failures && !unmarked_failures.empty?
|
|
163
167
|
|
|
164
168
|
post("/api/projects/#{project_id}/ci_results", body)
|
|
165
169
|
end
|
|
@@ -82,7 +82,7 @@ module Fp
|
|
|
82
82
|
known = known_slugs(project_id)
|
|
83
83
|
|
|
84
84
|
totals = { created: 0, updated: 0, missing_failing: 0, reports: [], resolved: 0,
|
|
85
|
-
unmatched: 0, unknown: [] }
|
|
85
|
+
unmatched: 0, unknown: [], unmarked_failures: [] }
|
|
86
86
|
|
|
87
87
|
junit_paths.each do |path|
|
|
88
88
|
xml = File.read(path)
|
|
@@ -92,6 +92,7 @@ module Fp
|
|
|
92
92
|
results, unknown = partition_known(resolved[:results], known)
|
|
93
93
|
totals[:resolved] += results.length
|
|
94
94
|
totals[:unknown].concat(unknown)
|
|
95
|
+
totals[:unmarked_failures].concat(resolved[:unmarked_failures])
|
|
95
96
|
|
|
96
97
|
result = client.ingest_ci_results(
|
|
97
98
|
project_id,
|
|
@@ -99,7 +100,8 @@ module Fp
|
|
|
99
100
|
surface: opts[:surface],
|
|
100
101
|
junit_xml: xml,
|
|
101
102
|
pr_url: opts[:pr],
|
|
102
|
-
results: results
|
|
103
|
+
results: results,
|
|
104
|
+
unmarked_failures: resolved[:unmarked_failures]
|
|
103
105
|
)
|
|
104
106
|
|
|
105
107
|
unless result[:ok]
|
|
@@ -140,6 +142,33 @@ module Fp
|
|
|
140
142
|
'been updated by this run.')
|
|
141
143
|
end
|
|
142
144
|
|
|
145
|
+
# Orphan markers on the ingest path. `fp check --junit`'s preview already surfaces
|
|
146
|
+
# a marker that binds to no requirement as a run-level finding, but `fp ci-report`
|
|
147
|
+
# dropped the very same markers into a buried `puts` line nobody reads in a
|
|
148
|
+
# collapsed CI step — the silent-drop #115 set out to kill, still alive on the
|
|
149
|
+
# path CI actually records evidence through. Annotate them the same way, so a
|
|
150
|
+
# test that *looks* bound but proves nothing shows up on the run rather than
|
|
151
|
+
# vanishing. FP surfaces the orphan; it does not invent a requirement from it.
|
|
152
|
+
unless totals[:unknown].empty?
|
|
153
|
+
orphans = totals[:unknown].uniq
|
|
154
|
+
annotate("fp ci-report resolved #{orphans.length} orphan marker(s) — an fp:<slug> that " \
|
|
155
|
+
"matches no requirement in this project, so no evidence was recorded for it: " \
|
|
156
|
+
"#{orphans.join(', ')}. Propose the requirement (fp propose) or fix the slug.")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Loose ends: tests that failed but carry no fp:<slug> marker. Not a gate failure
|
|
160
|
+
# (they bind to no requirement, so nothing can go red), but a red test nobody is
|
|
161
|
+
# tracking is worth a human's eyes — surfaced the same way orphan markers are, and
|
|
162
|
+
# persisted so it shows on the matrix's "loose ends" panel.
|
|
163
|
+
unless totals[:unmarked_failures].empty?
|
|
164
|
+
count = totals[:unmarked_failures].length
|
|
165
|
+
names = totals[:unmarked_failures].map { |f| f[:name] }.compact.take(10).join(', ')
|
|
166
|
+
annotate("#{count} failing test(s) on surface '#{opts[:surface]}' carry no fp:<slug> " \
|
|
167
|
+
"marker, so no requirement tracks them: #{names}#{'…' if count > 10}. " \
|
|
168
|
+
'Mark them (fp:<slug>) to bind them to a requirement, or leave them — ' \
|
|
169
|
+
'they show on the matrix as loose ends either way.')
|
|
170
|
+
end
|
|
171
|
+
|
|
143
172
|
emit_summary(opts, totals)
|
|
144
173
|
end
|
|
145
174
|
|
|
@@ -200,7 +229,18 @@ module Fp
|
|
|
200
229
|
failure_count: r.failure_count
|
|
201
230
|
}
|
|
202
231
|
end,
|
|
203
|
-
unmatched: resolved[:unmatched].length
|
|
232
|
+
unmatched: resolved[:unmatched].length,
|
|
233
|
+
# Loose ends: failing tests with no fp:<slug> marker. Shipped to the server so a
|
|
234
|
+
# human can see them on the matrix; a red test nobody tracks against a
|
|
235
|
+
# requirement is a real gap, even though it isn't a requirement cell.
|
|
236
|
+
unmarked_failures: Fp::JUnit.unmarked_failures(resolved[:unmatched]).map do |f|
|
|
237
|
+
{
|
|
238
|
+
name: f.name,
|
|
239
|
+
classname: f.classname,
|
|
240
|
+
source_file: f.file,
|
|
241
|
+
source_line: f.line
|
|
242
|
+
}
|
|
243
|
+
end
|
|
204
244
|
}
|
|
205
245
|
rescue ArgumentError => e
|
|
206
246
|
output.error(e.message)
|
|
@@ -220,6 +260,7 @@ module Fp
|
|
|
220
260
|
missing_marked_failing: totals[:missing_failing],
|
|
221
261
|
markers_resolved: totals[:resolved],
|
|
222
262
|
unmatched_testcases: totals[:unmatched],
|
|
263
|
+
unmarked_failures: totals[:unmarked_failures],
|
|
223
264
|
skipped_unknown: totals[:unknown].uniq,
|
|
224
265
|
reports: totals[:reports]
|
|
225
266
|
}
|
|
@@ -239,6 +280,10 @@ module Fp
|
|
|
239
280
|
puts " skipped #{totals[:unknown].uniq.length} marker(s) with no matching " \
|
|
240
281
|
"requirement: #{totals[:unknown].uniq.join(', ')}"
|
|
241
282
|
end
|
|
283
|
+
unless totals[:unmarked_failures].empty?
|
|
284
|
+
puts " loose ends: #{totals[:unmarked_failures].length} failing test(s) with no " \
|
|
285
|
+
'fp:<slug> marker (shown on the matrix, not a gate failure)'
|
|
286
|
+
end
|
|
242
287
|
puts " created: #{totals[:created]} updated: #{totals[:updated]} marked failing (missing): #{totals[:missing_failing]}"
|
|
243
288
|
end
|
|
244
289
|
end
|
data/lib/fp/junit.rb
CHANGED
|
@@ -35,6 +35,14 @@ module Fp
|
|
|
35
35
|
CiResult = Struct.new(:slug, :surface, :status, :file, :line, :test_count, :failure_count,
|
|
36
36
|
keyword_init: true)
|
|
37
37
|
|
|
38
|
+
# A failing testcase that carries no fp:<slug> marker at all — a "loose end". Not a
|
|
39
|
+
# cell on the parity grid (nothing binds it to a requirement) and not an orphan
|
|
40
|
+
# marker (there is no marker); it's a red test nobody is tracking against a
|
|
41
|
+
# requirement. CI ships these to the server so a human can see, on the matrix, that
|
|
42
|
+
# the suite has failures no requirement claims. Green unmarked tests are ignored on
|
|
43
|
+
# purpose — not everything needs a requirement; only a *failing* one is a signal.
|
|
44
|
+
UnmarkedFailure = Struct.new(:name, :classname, :file, :line, keyword_init: true)
|
|
45
|
+
|
|
38
46
|
# A testcase paired with the marker that annotates it. `precise` records whether we
|
|
39
47
|
# know *which* test the marker sits above (matched by line or by test name) or only
|
|
40
48
|
# that the marker is somewhere in the same file — a title is only worth reporting in
|
|
@@ -249,6 +257,18 @@ module Fp
|
|
|
249
257
|
{ results: results, unmatched: unmatched, missing_surface: missing_surface.uniq }
|
|
250
258
|
end
|
|
251
259
|
|
|
260
|
+
# The loose ends in a set of unmatched testcases: the ones that *failed* but carry no
|
|
261
|
+
# fp:<slug> marker. Pass the `unmatched` list from `ci_results` (or `bind_markers`).
|
|
262
|
+
#
|
|
263
|
+
# Only failures are returned. A green unmarked test is fine — not everything needs a
|
|
264
|
+
# requirement — but a red one is a failure nobody is tracking, which is exactly the
|
|
265
|
+
# signal a human should see. Skipped tests are not failures, so they're excluded too.
|
|
266
|
+
def unmarked_failures(unmatched)
|
|
267
|
+
unmatched.select(&:failed?).map do |tc|
|
|
268
|
+
UnmarkedFailure.new(name: tc.name, classname: tc.classname, file: tc.file, line: tc.line)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
252
272
|
# Walk testcases and yield a Match for every one we can tie to a marker.
|
|
253
273
|
# Shared by bind_markers (agent evidence) and ci_results (CI verdicts) so the two
|
|
254
274
|
# can never disagree about which test a marker annotates.
|
data/lib/fp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: featureparity
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-http
|