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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1208887c7ed99691e476438cdcb50d36f09e3ee3b22931c7d8d9ea246438fb89
4
- data.tar.gz: b2fb9b9d7bad99bde7e2f8b0789105072cd19647ed46f0ea4d4d6e43a28fd319
3
+ metadata.gz: 59cbbdb1a7c4c7af5ab1d7016d22c7fdf670855c9e75a5c9d7350752c885b625
4
+ data.tar.gz: 25d42454129c6d27c222e5d512578991b13a2abda65af67f8f39139550d24e96
5
5
  SHA512:
6
- metadata.gz: 5d70c2ae5afb585a1f54da0c2efb244965a0f419001c9ed8f46d1895a5f37ee12ece8a1195627859eeeaff2fd3910cf4dfdb593fc41c69440bc97020ff31a17a
7
- data.tar.gz: 23d9038f6850138f295501a56ea00f872ff828eab5a2822482c306a56ba39cdec2a1016761af8f4f87932a3d9f264faf0c9dad3302d2a96ea5adceb86b3e29c6
6
+ metadata.gz: 639bc26fbac4a013d0cef2051c438f1019ce915b5ce90537fe6cea0339f443f27234ae85062ba22f93cd989e7c6982390fb3be3068cd63bf658e081795243fca
7
+ data.tar.gz: 0db47c4854e39bd6410c44839c26338d79833d129e6f68d3de26e83afff12a45b7199348fee0f1528a35697caace3e8d8084ee31438ec58474d175dcaf9a040c
data/lib/fp/cli.rb CHANGED
@@ -10,6 +10,7 @@ module Fp
10
10
  'show' => Commands::Show,
11
11
  'propose' => Commands::Propose,
12
12
  'report' => Commands::Report,
13
+ 'ci-report' => Commands::CiReport,
13
14
  'matrix' => Commands::Matrix,
14
15
  'profile' => Commands::Profile,
15
16
  'config' => Commands::ConfigCmd,
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fp
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.6'
5
5
  end
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.5
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