branchproof 0.2.0 → 0.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.
@@ -0,0 +1,366 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/ClassLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
4
+
5
+ require "json"
6
+
7
+ module Branchproof
8
+ # Reads and validates a persisted JSON report without loading the project.
9
+ class SavedReport
10
+ SUPPORTED_SCHEMAS = %w[1.0 1.1].freeze
11
+ CRITERION_VERSION = "masking_occurrence_v1"
12
+ REQUIRED_FIELDS = %w[schema_version tool_version criterion_version runtime run_ids source_inventory baseline
13
+ observations analysis minima metrics diagnostics completeness].freeze
14
+ COMPLETENESS_FIELDS = %w[observation attribution analysis].freeze
15
+
16
+ class << self
17
+ def read(path)
18
+ raise ArgumentError, "report path must be a string" unless path.is_a?(String)
19
+
20
+ json = File.binread(path)
21
+ document = JSON.parse(json)
22
+ new(document).validate!
23
+ rescue IOError, SystemCallError => e
24
+ raise ArgumentError, "cannot read report: #{e.message}"
25
+ rescue JSON::ParserError
26
+ raise ArgumentError, "invalid report JSON"
27
+ end
28
+ end
29
+
30
+ def initialize(document)
31
+ @document = document
32
+ end
33
+
34
+ def validate!
35
+ fail_with("document must be an object") unless hash_with_string_keys?(@document)
36
+ schema = @document["schema_version"]
37
+ fail_with("unsupported schema version") unless SUPPORTED_SCHEMAS.include?(schema)
38
+ missing = REQUIRED_FIELDS.reject { |field| @document.key?(field) }
39
+ fail_with("missing field: #{missing.first}") unless missing.empty?
40
+ fail_with("criterion version mismatch") unless @document["criterion_version"] == CRITERION_VERSION
41
+ fail_with("run_ids must be an array of strings") unless strings?(@document["run_ids"])
42
+
43
+ validate_inventory(@document["source_inventory"])
44
+ validate_observations(@document["observations"])
45
+ validate_baseline(@document["baseline"])
46
+ validate_analysis(@document["analysis"])
47
+ validate_completeness(@document["completeness"])
48
+ validate_completeness(@document.dig("observations", "completeness"))
49
+ validate_completeness(@document.dig("analysis", "completeness")) if @document["analysis"]
50
+ validate_optional_sections
51
+ validate_analysis_coverage
52
+ @document
53
+ end
54
+
55
+ private
56
+
57
+ def validate_inventory(inventory)
58
+ fail_with("source_inventory must be an object") unless hash_with_string_keys?(inventory)
59
+ validate_string_field(inventory, "root", nullable: true)
60
+ sources = inventory["source_units"]
61
+ decisions = inventory["decisions"]
62
+ fail_with("source_units must be an array") unless sources.is_a?(Array)
63
+ fail_with("decisions must be an array") unless decisions.is_a?(Array)
64
+ source_ids = unique_ids(sources, "source_id", "source")
65
+ decision_ids = unique_ids(decisions, "id", "decision")
66
+ decisions.each do |decision|
67
+ fail_with("invalid decision") unless hash_with_string_keys?(decision)
68
+ fail_with("unknown decision source") unless source_ids.include?(decision["source_id"])
69
+ validate_string_field(decision, "expression")
70
+ validate_integer_field(decision, "line")
71
+ conditions = decision["conditions"]
72
+ fail_with("conditions must be an array") unless conditions.is_a?(Array)
73
+ unique_ids(conditions, "id", "condition")
74
+ conditions.each do |condition|
75
+ fail_with("invalid condition") unless hash_with_string_keys?(condition) && condition["index"].is_a?(Integer)
76
+ validate_string_field(condition, "expression")
77
+ %w[line column byte_start byte_length].each do |field|
78
+ validate_integer_field(condition, field, nullable: true)
79
+ end
80
+ end
81
+ fail_with("condition indexes must be consecutive") unless conditions.map do |condition|
82
+ condition["index"]
83
+ end.sort == (0...conditions.length).to_a
84
+ end
85
+ @source_ids = source_ids
86
+ @decision_ids = decision_ids
87
+ @condition_ids = decisions.flat_map do |decision|
88
+ Array(decision["conditions"]).map do |condition|
89
+ condition["id"]
90
+ end
91
+ end
92
+ fail_with("duplicate condition id") unless @condition_ids.uniq.length == @condition_ids.length
93
+ @conditions_by_decision = decisions.to_h do |decision|
94
+ [decision["id"], Array(decision["conditions"]).map { |condition| condition["id"] }]
95
+ end
96
+ sources.each do |source|
97
+ validate_string_field(source, "relative_path", nullable: true)
98
+ validate_string_field(source, "absolute_path", nullable: true)
99
+ digest = source["digest"] || source["content_digest"]
100
+ fail_with("source digest must be a nonempty string") unless digest.is_a?(String) && !digest.empty?
101
+ end
102
+ paths = sources.map { |source| source["relative_path"] }.compact
103
+ fail_with("duplicate source relative path") unless paths.uniq.length == paths.length
104
+ @condition_counts = decisions.to_h { |decision| [decision["id"], decision["conditions"].length] }
105
+ @supported_ids = decisions.filter_map do |decision|
106
+ decision["id"] unless decision["support_status"] == "UNSUPPORTED"
107
+ end
108
+ end
109
+
110
+ def validate_observations(observations)
111
+ fail_with("observations must be an object") unless hash_with_string_keys?(observations)
112
+ tests = observations["tests"]
113
+ vectors = observations["vectors"]
114
+ fail_with("tests must be an array") unless tests.is_a?(Array)
115
+ fail_with("vectors must be an array") unless vectors.is_a?(Array)
116
+ @test_ids = unique_ids(tests, "id", "test")
117
+ tests.each { |test| validate_test(test) }
118
+ vector_ids = unique_ids(vectors, "id", "vector")
119
+ vectors.each do |vector|
120
+ fail_with("invalid vector") unless hash_with_string_keys?(vector)
121
+ fail_with("unknown vector decision") unless @decision_ids.include?(vector["decision_id"])
122
+ values = vector["values"]
123
+ valid_values = values.is_a?(Array) && values.all? do |value|
124
+ value.nil? || value == true || value == false
125
+ end
126
+ fail_with("vector values must be an array of booleans or null") unless valid_values
127
+ expected_length = @condition_counts.fetch(vector["decision_id"])
128
+ fail_with("vector values do not match decision conditions") unless values.length == expected_length
129
+ fail_with("vector outcome must be boolean") unless [true, false].include?(vector["outcome"])
130
+ fail_with("unknown vector test") unless strings?(vector["test_ids"]) && vector["test_ids"].all? do |id|
131
+ @test_ids.include?(id)
132
+ end
133
+ validate_phases(vector)
134
+ validate_integer_field(vector, "count")
135
+ validate_integer_field(vector, "unattributed_count")
136
+ end
137
+ @vector_ids = vector_ids
138
+ @vector_decision_by_id = vectors.to_h { |vector| [vector["id"], vector["decision_id"]] }
139
+ aborts = observations["abort_counts"]
140
+ return if aborts.nil? || aborts.is_a?(Integer) || (aborts.is_a?(Hash) && aborts.values.all?(Integer))
141
+
142
+ fail_with("invalid abort_counts")
143
+ end
144
+
145
+ def validate_baseline(baseline)
146
+ fail_with("baseline must be an object") unless hash_with_string_keys?(baseline)
147
+ fail_with("baseline status must be a string") unless baseline["status"].is_a?(String)
148
+ fail_with("baseline finalized must be boolean") unless [true, false].include?(baseline["finalized"])
149
+ return unless baseline.key?("tests")
150
+
151
+ fail_with("baseline tests must be an array") unless baseline["tests"].is_a?(Array)
152
+ unique_ids(baseline["tests"], "id", "baseline test")
153
+ baseline["tests"].each { |test| validate_test(test) }
154
+ end
155
+
156
+ def validate_analysis(analysis)
157
+ return if analysis.nil?
158
+
159
+ fail_with("analysis must be an object") unless hash_with_string_keys?(analysis)
160
+ %w[proven_count eligible_count].each { |field| validate_integer_field(analysis, field) }
161
+ decisions = analysis["decisions"]
162
+ fail_with("analysis decisions must be an array") unless decisions.is_a?(Array)
163
+ seen = {}
164
+ decisions.each do |decision|
165
+ fail_with("invalid analysis decision") unless hash_with_string_keys?(decision)
166
+ id = decision["decision_id"]
167
+ fail_with("unknown analysis decision") unless @decision_ids.include?(id) && !seen.key?(id)
168
+ seen[id] = true
169
+ results = decision["condition_results"]
170
+ fail_with("condition results must be an array") unless results.is_a?(Array)
171
+ seen_conditions = {}
172
+ results.each do |result|
173
+ condition_id = result["condition_id"] if hash_with_string_keys?(result)
174
+ fail_with("invalid condition result") unless @conditions_by_decision.fetch(id).include?(condition_id)
175
+ fail_with("duplicate condition result") if seen_conditions.key?(condition_id)
176
+ seen_conditions[condition_id] = true
177
+ validate_constraint(result["constraint_result"], id) if result["constraint_result"]
178
+ next unless result.key?("canonical_pair")
179
+
180
+ next if result["canonical_pair"].nil?
181
+
182
+ pair = result["canonical_pair"]
183
+ valid_pair = pair.is_a?(Array) && pair.length == 2 && pair.uniq.length == 2 && pair.all? do |id|
184
+ @vector_ids.include?(id) && @vector_decision_by_id[id] == decision["decision_id"]
185
+ end
186
+ fail_with("canonical pair must reference vectors") unless valid_pair
187
+ end
188
+ end
189
+ end
190
+
191
+ def validate_completeness(completeness)
192
+ fail_with("completeness must be an object") unless hash_with_string_keys?(completeness)
193
+ COMPLETENESS_FIELDS.each do |field|
194
+ valid = completeness.key?(field) && [true, false].include?(completeness[field])
195
+ fail_with("completeness #{field} must be boolean") unless valid
196
+ end
197
+ end
198
+
199
+ def validate_optional_sections
200
+ fail_with("minima must be an array") unless @document["minima"].is_a?(Array)
201
+ fail_with("metrics must be an object") unless hash_with_string_keys?(@document["metrics"])
202
+ fail_with("diagnostics must be an array") unless @document["diagnostics"].is_a?(Array)
203
+ @document["diagnostics"].each do |diagnostic|
204
+ fail_with("invalid diagnostic") unless hash_with_string_keys?(diagnostic)
205
+ end
206
+ @document["minima"].each { |minimum| validate_minimum(minimum) }
207
+ if @document.key?("run_metadata") && !hash_with_string_keys?(@document["run_metadata"])
208
+ fail_with("run_metadata must be an object")
209
+ end
210
+ validate_metadata(@document["run_metadata"]) if @document["run_metadata"]
211
+ fail_with("document contains non-string object keys") unless all_hashes_have_string_keys?(@document)
212
+ end
213
+
214
+ def validate_phases(vector)
215
+ phases = vector["phases_by_test"]
216
+ return if phases.nil?
217
+
218
+ valid = hash_with_string_keys?(phases) && phases.all? do |test_id, values|
219
+ valid_phases = %w[setup body teardown suite unattributed]
220
+ @test_ids.include?(test_id) && strings?(values) && values.all? { |phase| valid_phases.include?(phase) }
221
+ end
222
+ fail_with("invalid phases_by_test") unless valid
223
+ end
224
+
225
+ def validate_test(test)
226
+ %w[name class_name method_name source_path].each { |field| validate_string_field(test, field, nullable: true) }
227
+ validate_integer_field(test, "line", nullable: true)
228
+ phase_counts = test["phase_counts"]
229
+ if phase_counts
230
+ valid = hash_with_string_keys?(phase_counts) && phase_counts.values.all?(Integer)
231
+ fail_with("invalid test phase_counts") unless valid
232
+ end
233
+ source = test["source"]
234
+ return if source.nil?
235
+
236
+ fail_with("test source must be an object") unless hash_with_string_keys?(source)
237
+ validate_string_field(source, "path", nullable: true)
238
+ validate_string_field(source, "relative_path", nullable: true)
239
+ validate_integer_field(source, "line", nullable: true)
240
+ end
241
+
242
+ # Keep each record's shape and reference checks together.
243
+ def validate_minimum(minimum)
244
+ fail_with("invalid minimum") unless hash_with_string_keys?(minimum)
245
+ %w[scope_decision_ids selected_ids].each do |field|
246
+ fail_with("minimum #{field} must be an array of strings") if minimum.key?(field) && !strings?(minimum[field])
247
+ end
248
+ validate_string_field(minimum, "objective", nullable: true)
249
+ validate_string_field(minimum, "status", nullable: true)
250
+ scope = minimum["scope_decision_ids"] || []
251
+ fail_with("unknown minimum decision") unless (scope - @decision_ids).empty?
252
+ ids = minimum["objective"] == "tests" ? @test_ids : @vector_ids
253
+ fail_with("unknown minimum member") unless ((minimum["selected_ids"] || []) - ids).empty?
254
+ end
255
+
256
+ def validate_constraint(constraint, decision_id)
257
+ fail_with("constraint_result must be an object") unless hash_with_string_keys?(constraint)
258
+ %w[status feasibility_statement].each { |field| validate_string_field(constraint, field, nullable: true) }
259
+ %w[constraints candidate_vectors].each do |field|
260
+ fail_with("#{field} must be an array") if constraint.key?(field) && !constraint[field].is_a?(Array)
261
+ end
262
+ Array(constraint["candidate_vectors"]).each do |candidate|
263
+ fail_with("invalid candidate vector") unless hash_with_string_keys?(candidate)
264
+ values = candidate["values"]
265
+ unless values.is_a?(Array) && values.length == @condition_counts[decision_id] && values.all? do |item|
266
+ [true, false, nil].include?(item)
267
+ end
268
+ fail_with("invalid candidate values")
269
+ end
270
+ fail_with("invalid candidate outcome") unless [true, false].include?(candidate["outcome"])
271
+ end
272
+ existing = constraint["existing_vector_id"]
273
+ fail_with("unknown constraint vector") if existing && @vector_decision_by_id[existing] != decision_id
274
+ end
275
+
276
+ def validate_analysis_coverage
277
+ analysis = @document["analysis"]
278
+ return unless analysis && analysis.dig("completeness", "analysis") == true
279
+
280
+ results = analysis["decisions"].to_h { |decision| [decision["decision_id"], decision["condition_results"]] }
281
+ @supported_ids.each do |id|
282
+ actual = Array(results[id]).map { |result| result["condition_id"] }.sort
283
+ fail_with("complete analysis is missing condition results") unless actual == @conditions_by_decision[id].sort
284
+ end
285
+ end
286
+
287
+ def validate_string_field(record, field, nullable: false)
288
+ return unless record.key?(field)
289
+ return if nullable && record[field].nil?
290
+
291
+ fail_with("#{field} must be a string") unless record[field].is_a?(String)
292
+ end
293
+
294
+ def validate_integer_field(record, field, nullable: false)
295
+ return unless record.key?(field)
296
+ return if nullable && record[field].nil?
297
+
298
+ fail_with("#{field} must be an integer") unless record[field].is_a?(Integer)
299
+ end
300
+
301
+ def validate_metadata(metadata)
302
+ string_fields = %w[captured_at project_kind project_root]
303
+ string_fields.each do |field|
304
+ fail_with("run_metadata #{field} must be a string") if metadata.key?(field) && !metadata[field].is_a?(String)
305
+ end
306
+ array_fields = %w[source_patterns test_patterns test_files runner_args]
307
+ array_fields.each do |field|
308
+ if metadata.key?(field) && !strings?(metadata[field])
309
+ fail_with("run_metadata #{field} must be an array of strings")
310
+ end
311
+ end
312
+ if metadata.key?("requested_level") && !metadata["requested_level"].is_a?(Integer)
313
+ fail_with("run_metadata requested_level must be an integer")
314
+ end
315
+ if metadata.key?("limits") && !hash_with_string_keys?(metadata["limits"])
316
+ fail_with("run_metadata limits must be an object")
317
+ end
318
+ locations = metadata["test_locations"]
319
+ return unless locations
320
+
321
+ valid = hash_with_string_keys?(locations) && locations.all? do |test_id, location|
322
+ @test_ids.include?(test_id) && hash_with_string_keys?(location) &&
323
+ (!location.key?("relative_path") || location["relative_path"].nil? ||
324
+ location["relative_path"].is_a?(String)) &&
325
+ (!location.key?("line") || location["line"].nil? || location["line"].is_a?(Integer))
326
+ end
327
+ fail_with("invalid run_metadata test_locations") unless valid
328
+ end
329
+
330
+ def unique_ids(items, field, label)
331
+ ids = items.map do |item|
332
+ unless hash_with_string_keys?(item) && item[field].is_a?(String) && !item[field].empty?
333
+ fail_with("invalid #{label} record")
334
+ end
335
+ item[field]
336
+ end
337
+ fail_with("duplicate #{label} id") unless ids.uniq.length == ids.length
338
+ ids
339
+ end
340
+
341
+ def strings?(value)
342
+ value.is_a?(Array) && value.all?(String)
343
+ end
344
+
345
+ def hash_with_string_keys?(value)
346
+ value.is_a?(Hash) && value.keys.all?(String)
347
+ end
348
+
349
+ def all_hashes_have_string_keys?(value)
350
+ return true unless value.is_a?(Hash)
351
+ return false unless hash_with_string_keys?(value)
352
+
353
+ value.values.all? do |item|
354
+ all_hashes_have_string_keys?(item) || (item.is_a?(Array) && item.all? do |entry|
355
+ all_hashes_have_string_keys?(entry)
356
+ end)
357
+ end
358
+ end
359
+
360
+ def fail_with(message)
361
+ raise ArgumentError, "invalid saved report: #{message}"
362
+ end
363
+ end
364
+ end
365
+
366
+ # rubocop:enable Metrics/ClassLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
@@ -134,6 +134,7 @@ module Branchproof
134
134
  location = leaf.delete(:_location)
135
135
  literal_truth = leaf.delete(:_literal_truth)
136
136
  Records.build(id: nil, index: index, byte_start: location.start_offset, byte_length: location.length,
137
+ line: location.start_line, column: location.start_column,
137
138
  expression: expression, literal_truth: literal_truth, coupling: "unknown")
138
139
  end
139
140
  decision_id = Records.decision_id(source_id: source_id, context: context, byte_start: start_offset,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Branchproof
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/branchproof.rb CHANGED
@@ -18,6 +18,11 @@ module Branchproof
18
18
  autoload :Analyzer, "branchproof/analyzer"
19
19
  autoload :Minimizer, "branchproof/minimizer"
20
20
  autoload :Report, "branchproof/report"
21
+ autoload :CoverageIndex, "branchproof/coverage_index"
22
+ autoload :FocusedReport, "branchproof/focused_report"
23
+ autoload :SavedReport, "branchproof/saved_report"
24
+ autoload :Comparison, "branchproof/comparison"
25
+ autoload :ComparisonReport, "branchproof/comparison_report"
21
26
  autoload :Worker, "branchproof/worker"
22
27
  end
23
28
 
data/llms.txt CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  | | |
4
4
  | --- | --- |
5
- | **Defined in** | lib/branchproof.rb, lib/branchproof/cli.rb, lib/branchproof/limits.rb, lib/branchproof/loader.rb, lib/branchproof/report.rb, lib/branchproof/source.rb, lib/branchproof/worker.rb, lib/branchproof/project.rb, lib/branchproof/records.rb, lib/branchproof/runtime.rb, lib/branchproof/version.rb, lib/branchproof/analyzer.rb, lib/branchproof/evidence.rb, lib/branchproof/minimizer.rb, lib/branchproof/instrumenter.rb, lib/branchproof/rails_support.rb, lib/branchproof/minitest_adapter.rb |
5
+ | **Defined in** | lib/branchproof.rb, lib/branchproof/cli.rb, lib/branchproof/limits.rb, lib/branchproof/loader.rb, lib/branchproof/report.rb, lib/branchproof/source.rb, lib/branchproof/worker.rb, lib/branchproof/project.rb, lib/branchproof/records.rb, lib/branchproof/runtime.rb, lib/branchproof/version.rb, lib/branchproof/analyzer.rb, lib/branchproof/evidence.rb, lib/branchproof/minimizer.rb, lib/branchproof/comparison.rb, lib/branchproof/instrumenter.rb, lib/branchproof/saved_report.rb, lib/branchproof/rails_support.rb, lib/branchproof/coverage_index.rb, lib/branchproof/focused_report.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb |
6
6
 
7
7
  Public namespace for source inventory and one-run MC/DC reporting.
8
8
 
@@ -14,8 +14,12 @@ Not documented.
14
14
 
15
15
  - [Branchproof/Analyzer.md](doc/Branchproof/Analyzer.md)
16
16
  - [Branchproof/CLI.md](doc/Branchproof/CLI.md)
17
+ - [Branchproof/Comparison.md](doc/Branchproof/Comparison.md)
18
+ - [Branchproof/ComparisonReport.md](doc/Branchproof/ComparisonReport.md)
19
+ - [Branchproof/CoverageIndex.md](doc/Branchproof/CoverageIndex.md)
17
20
  - [Branchproof/Error.md](doc/Branchproof/Error.md)
18
21
  - [Branchproof/Evidence.md](doc/Branchproof/Evidence.md)
22
+ - [Branchproof/FocusedReport.md](doc/Branchproof/FocusedReport.md)
19
23
  - [Branchproof/Instrumenter.md](doc/Branchproof/Instrumenter.md)
20
24
  - [Branchproof/Limits.md](doc/Branchproof/Limits.md)
21
25
  - [Branchproof/Loader.md](doc/Branchproof/Loader.md)
@@ -27,6 +31,7 @@ Not documented.
27
31
  - [Branchproof/Records.md](doc/Branchproof/Records.md)
28
32
  - [Branchproof/Report.md](doc/Branchproof/Report.md)
29
33
  - [Branchproof/Runtime.md](doc/Branchproof/Runtime.md)
34
+ - [Branchproof/SavedReport.md](doc/Branchproof/SavedReport.md)
30
35
  - [Branchproof/Source.md](doc/Branchproof/Source.md)
31
36
  - [Branchproof/Worker.md](doc/Branchproof/Worker.md)
32
37
  - [CHANGELOG.md](doc/CHANGELOG.md)
data/sig/branchproof.rbs CHANGED
@@ -2,6 +2,7 @@ module Branchproof
2
2
  VERSION: String
3
3
 
4
4
  type record = Hash[Symbol, untyped]
5
+ type document = Hash[String | Symbol, untyped]
5
6
  type limits = record
6
7
 
7
8
  class Error < StandardError
@@ -102,11 +103,39 @@ module Branchproof
102
103
  class Report
103
104
  SCHEMA_VERSION: String
104
105
  CRITERION_VERSION: String
105
- def initialize: (inventory: record, evidence: record, analysis: record?, minima: Array[record], baseline: record, diagnostics: Array[record]) -> void
106
+ def initialize: (inventory: document, evidence: document, analysis: document?, minima: Array[document], baseline: document, diagnostics: Array[document], ?level: Integer, ?missing_only: bool, ?view: Symbol, ?run_metadata: document, ?saved_document: document?) -> void
107
+ def self.from_document: (document: document, ?level: Integer?, ?view: Symbol, ?missing_only: bool) -> Report
108
+ def condition_explanation: (decision_id: String, condition_id: String) -> String
106
109
  def write: (io: _WriteIO, format: Symbol | String) -> nil
107
110
  def exit_code: () -> Integer
108
111
  end
109
112
 
113
+ class CoverageIndex
114
+ def initialize: (document: document) -> void
115
+ attr_reader conditions: Array[record]
116
+ attr_reader tests: Array[record]
117
+ end
118
+
119
+ class FocusedReport
120
+ def initialize: (document: document, view: Symbol, level: Integer, ?missing_only: bool) -> void
121
+ def render: () -> String
122
+ end
123
+
124
+ class SavedReport
125
+ def self.read: (String path) -> document
126
+ end
127
+
128
+ class Comparison
129
+ def initialize: (before: document, after: document) -> void
130
+ def call: () -> document
131
+ end
132
+
133
+ class ComparisonReport
134
+ def initialize: (document: document) -> void
135
+ def write: (io: _WriteIO, format: Symbol | String) -> nil
136
+ def exit_code: (?fail_on_regression: bool) -> Integer
137
+ end
138
+
110
139
  interface _WriteIO
111
140
  def write: (String) -> Integer
112
141
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: branchproof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucian Ghinda
@@ -54,20 +54,26 @@ description: Inventory Ruby decisions and report masking MC/DC evidence from Min
54
54
  email:
55
55
  - lucianghinda@users.noreply.github.com
56
56
  executables:
57
+ - branchproof
57
58
  - mcdc
58
59
  extensions: []
59
60
  extra_rdoc_files: []
60
61
  files:
61
62
  - CHANGELOG.md
62
63
  - CODE_OF_CONDUCT.md
64
+ - LICENSE
63
65
  - LICENSE.txt
64
66
  - NOTICE
65
67
  - README.md
66
68
  - doc/Branchproof.md
67
69
  - doc/Branchproof/Analyzer.md
68
70
  - doc/Branchproof/CLI.md
71
+ - doc/Branchproof/Comparison.md
72
+ - doc/Branchproof/ComparisonReport.md
73
+ - doc/Branchproof/CoverageIndex.md
69
74
  - doc/Branchproof/Error.md
70
75
  - doc/Branchproof/Evidence.md
76
+ - doc/Branchproof/FocusedReport.md
71
77
  - doc/Branchproof/Instrumenter.md
72
78
  - doc/Branchproof/Limits.md
73
79
  - doc/Branchproof/Loader.md
@@ -79,15 +85,21 @@ files:
79
85
  - doc/Branchproof/Records.md
80
86
  - doc/Branchproof/Report.md
81
87
  - doc/Branchproof/Runtime.md
88
+ - doc/Branchproof/SavedReport.md
82
89
  - doc/Branchproof/Source.md
83
90
  - doc/Branchproof/Worker.md
84
91
  - doc/CHANGELOG.md
85
92
  - doc/README.md
93
+ - exe/branchproof
86
94
  - exe/mcdc
87
95
  - lib/branchproof.rb
88
96
  - lib/branchproof/analyzer.rb
89
97
  - lib/branchproof/cli.rb
98
+ - lib/branchproof/comparison.rb
99
+ - lib/branchproof/comparison_report.rb
100
+ - lib/branchproof/coverage_index.rb
90
101
  - lib/branchproof/evidence.rb
102
+ - lib/branchproof/focused_report.rb
91
103
  - lib/branchproof/instrumenter.rb
92
104
  - lib/branchproof/limits.rb
93
105
  - lib/branchproof/loader.rb
@@ -98,6 +110,7 @@ files:
98
110
  - lib/branchproof/records.rb
99
111
  - lib/branchproof/report.rb
100
112
  - lib/branchproof/runtime.rb
113
+ - lib/branchproof/saved_report.rb
101
114
  - lib/branchproof/source.rb
102
115
  - lib/branchproof/version.rb
103
116
  - lib/branchproof/worker.rb