featureparity 0.0.5 → 0.0.6
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/cli.rb +1 -0
- data/lib/fp/client.rb +15 -0
- data/lib/fp/commands/ci_report.rb +135 -0
- data/lib/fp/commands/help.rb +11 -0
- data/lib/fp/commands.rb +1 -0
- data/lib/fp/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 59cbbdb1a7c4c7af5ab1d7016d22c7fdf670855c9e75a5c9d7350752c885b625
|
|
4
|
+
data.tar.gz: 25d42454129c6d27c222e5d512578991b13a2abda65af67f8f39139550d24e96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 639bc26fbac4a013d0cef2051c438f1019ce915b5ce90537fe6cea0339f443f27234ae85062ba22f93cd989e7c6982390fb3be3068cd63bf658e081795243fca
|
|
7
|
+
data.tar.gz: 0db47c4854e39bd6410c44839c26338d79833d129e6f68d3de26e83afff12a45b7199348fee0f1528a35697caace3e8d8084ee31438ec58474d175dcaf9a040c
|
data/lib/fp/cli.rb
CHANGED
data/lib/fp/client.rb
CHANGED
|
@@ -87,6 +87,21 @@ module Fp
|
|
|
87
87
|
post("/api/projects/#{project_id}/evidence", params)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# POST /projects/:project_id/ci_results — the CI-only path.
|
|
91
|
+
#
|
|
92
|
+
# Unlike report_evidence (agents: present/stub), this hands the raw JUnit XML
|
|
93
|
+
# to the backend, which parses it, maps testcases to fp:<slug> markers, and
|
|
94
|
+
# writes passing/failing evidence per surface. It also marks previously
|
|
95
|
+
# passing slugs that have vanished from the results as failing. This is the
|
|
96
|
+
# half of the loop only CI is allowed to close (#1222).
|
|
97
|
+
def ingest_ci_results(project_id, ci_url:, surface:, junit_xml:)
|
|
98
|
+
post("/api/projects/#{project_id}/ci_results", {
|
|
99
|
+
ci_url: ci_url,
|
|
100
|
+
surface: surface,
|
|
101
|
+
junit_xml: junit_xml
|
|
102
|
+
})
|
|
103
|
+
end
|
|
104
|
+
|
|
90
105
|
# GET /matrix (when API is ready)
|
|
91
106
|
def get_matrix(project_id, format: :json)
|
|
92
107
|
path = format == :csv ? '/api/matrix.csv' : '/api/matrix'
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fp
|
|
4
|
+
module Commands
|
|
5
|
+
# CI-only evidence reporting from a JUnit XML report.
|
|
6
|
+
#
|
|
7
|
+
# fp ci-report --junit <path> --project <slug> --surface X --ci-url URL
|
|
8
|
+
#
|
|
9
|
+
# This is the CI counterpart to `fp report --junit`. Where `fp report`
|
|
10
|
+
# writes agent evidence (present/stub) and never claims a test passed, this
|
|
11
|
+
# command hands the raw JUnit XML to the backend's CI ingest endpoint, which:
|
|
12
|
+
#
|
|
13
|
+
# - maps each testcase to its fp:<slug> marker (server-side, same
|
|
14
|
+
# file-scanning convention as the CLI),
|
|
15
|
+
# - writes passing / failing evidence per requirement for the surface,
|
|
16
|
+
# - and marks any slug that was passing but has now vanished from the
|
|
17
|
+
# report as failing.
|
|
18
|
+
#
|
|
19
|
+
# That last part is why the whole report goes up as one document rather than
|
|
20
|
+
# per-slug: the server can only detect a disappeared test by diffing the full
|
|
21
|
+
# result set against what it had. Reporting passing/failing is a privilege of
|
|
22
|
+
# CI (#1222) — agents can't reach this path, they only ever report evidence
|
|
23
|
+
# via `fp report`.
|
|
24
|
+
#
|
|
25
|
+
# Multiple --junit flags may be passed to ingest several reports for the same
|
|
26
|
+
# surface in one invocation (e.g. a suite split across shards). Each is
|
|
27
|
+
# uploaded in turn.
|
|
28
|
+
class CiReport < Base
|
|
29
|
+
KNOWN_FLAGS = {
|
|
30
|
+
project: :string,
|
|
31
|
+
surface: :string,
|
|
32
|
+
ci_url: :string,
|
|
33
|
+
junit: :string
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
def run(args)
|
|
37
|
+
# Collect every --junit occurrence (parse_flags keeps only the last of a
|
|
38
|
+
# repeated flag, so pull them out first).
|
|
39
|
+
junit_paths, rest = extract_repeated(args, '--junit')
|
|
40
|
+
opts, _positional = parse_flags(rest, KNOWN_FLAGS)
|
|
41
|
+
|
|
42
|
+
require_flag(opts, :project)
|
|
43
|
+
require_flag(opts, :surface)
|
|
44
|
+
require_flag(opts, :ci_url)
|
|
45
|
+
|
|
46
|
+
if junit_paths.empty?
|
|
47
|
+
output.error('--junit <path> is required (may be given more than once).')
|
|
48
|
+
exit 1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
missing = junit_paths.reject { |p| File.file?(p) }
|
|
52
|
+
unless missing.empty?
|
|
53
|
+
output.error("JUnit file(s) not found: #{missing.join(', ')}")
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
project_id = resolve_project_id(opts[:project])
|
|
58
|
+
|
|
59
|
+
totals = { created: 0, updated: 0, missing_failing: 0, reports: [] }
|
|
60
|
+
|
|
61
|
+
junit_paths.each do |path|
|
|
62
|
+
xml = File.read(path)
|
|
63
|
+
result = client.ingest_ci_results(
|
|
64
|
+
project_id,
|
|
65
|
+
ci_url: opts[:ci_url],
|
|
66
|
+
surface: opts[:surface],
|
|
67
|
+
junit_xml: xml
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
unless result[:ok]
|
|
71
|
+
output.error("Failed to ingest #{path}: #{result[:error]}", status: result[:status])
|
|
72
|
+
exit 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
data = result[:data] || {}
|
|
76
|
+
totals[:created] += data['evidence_created'].to_i
|
|
77
|
+
totals[:updated] += data['evidence_updated'].to_i
|
|
78
|
+
totals[:missing_failing] += data['missing_marked_failing'].to_i
|
|
79
|
+
totals[:reports] << { path: path, results: data['results'] || [] }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
emit_summary(opts, totals)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Pull every occurrence of a repeatable flag (and its value) out of args,
|
|
88
|
+
# returning [collected_values, remaining_args]. Leaves all other flags for
|
|
89
|
+
# the normal parser.
|
|
90
|
+
def extract_repeated(args, flag)
|
|
91
|
+
values = []
|
|
92
|
+
rest = []
|
|
93
|
+
i = 0
|
|
94
|
+
while i < args.length
|
|
95
|
+
arg = args[i]
|
|
96
|
+
if arg == flag && i + 1 < args.length
|
|
97
|
+
values << args[i + 1]
|
|
98
|
+
i += 2
|
|
99
|
+
elsif arg.start_with?("#{flag}=")
|
|
100
|
+
values << arg.split('=', 2)[1]
|
|
101
|
+
i += 1
|
|
102
|
+
else
|
|
103
|
+
rest << arg
|
|
104
|
+
i += 1
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
[values, rest]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def emit_summary(opts, totals)
|
|
111
|
+
data = {
|
|
112
|
+
project: opts[:project],
|
|
113
|
+
surface: opts[:surface],
|
|
114
|
+
ci_url: opts[:ci_url],
|
|
115
|
+
evidence_created: totals[:created],
|
|
116
|
+
evidence_updated: totals[:updated],
|
|
117
|
+
missing_marked_failing: totals[:missing_failing],
|
|
118
|
+
reports: totals[:reports]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
output.success(data) do
|
|
122
|
+
puts "Ingested CI results for surface '#{opts[:surface]}':"
|
|
123
|
+
totals[:reports].each do |report|
|
|
124
|
+
report[:results].each do |r|
|
|
125
|
+
glyph = r['status'] == 'passing' ? '✓' : (r['status'] == 'failing' ? '✗' : '•')
|
|
126
|
+
puts " #{glyph} #{r['slug']} (#{r['status']}) #{r['source_file']}"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
puts
|
|
130
|
+
puts " created: #{totals[:created]} updated: #{totals[:updated]} marked failing (missing): #{totals[:missing_failing]}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
data/lib/fp/commands/help.rb
CHANGED
|
@@ -20,6 +20,7 @@ module Fp
|
|
|
20
20
|
propose --project <slug> ... Propose a new requirement (as draft)
|
|
21
21
|
report <slug> --project <slug>... Report evidence for a requirement
|
|
22
22
|
report --junit <file> ... Upload evidence from a JUnit XML report (no CI)
|
|
23
|
+
ci-report --junit <file> ... Ingest CI test results (passing/failing) — CI only
|
|
23
24
|
matrix --project <slug> Show the parity matrix
|
|
24
25
|
profile add|list Manage named profiles
|
|
25
26
|
config set|get|unset|list Manage global settings
|
|
@@ -127,6 +128,16 @@ module Fp
|
|
|
127
128
|
# --base-dir sets where relative test file paths in the report resolve from
|
|
128
129
|
# Skipped tests are reported as 'stub'; everything else as 'present'.
|
|
129
130
|
|
|
131
|
+
# CI ONLY: ingest real pass/fail results. Run in your CI pipeline after
|
|
132
|
+
# the suite (on green AND red builds). Unlike `fp report`, this writes
|
|
133
|
+
# passing/failing evidence and marks vanished-but-previously-passing
|
|
134
|
+
# slugs as failing. This is the half of the loop that turns the matrix
|
|
135
|
+
# circles into ✓ / ✗.
|
|
136
|
+
fp ci-report --junit junit.xml --project stowzilla \\
|
|
137
|
+
--surface api \\
|
|
138
|
+
--ci-url "$CI_RUN_URL"
|
|
139
|
+
# Pass --junit more than once to ingest several reports for one surface.
|
|
140
|
+
|
|
130
141
|
# Show the parity matrix
|
|
131
142
|
fp matrix --project stowzilla
|
|
132
143
|
|
data/lib/fp/commands.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative 'commands/list'
|
|
|
7
7
|
require_relative 'commands/show'
|
|
8
8
|
require_relative 'commands/propose'
|
|
9
9
|
require_relative 'commands/report'
|
|
10
|
+
require_relative 'commands/ci_report'
|
|
10
11
|
require_relative 'commands/matrix'
|
|
11
12
|
require_relative 'commands/profile'
|
|
12
13
|
require_relative 'commands/config_cmd'
|
data/lib/fp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -95,6 +95,7 @@ files:
|
|
|
95
95
|
- lib/fp/client.rb
|
|
96
96
|
- lib/fp/commands.rb
|
|
97
97
|
- lib/fp/commands/base.rb
|
|
98
|
+
- lib/fp/commands/ci_report.rb
|
|
98
99
|
- lib/fp/commands/config_cmd.rb
|
|
99
100
|
- lib/fp/commands/help.rb
|
|
100
101
|
- lib/fp/commands/list.rb
|