branchproof 0.10.0 → 0.11.1

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.
@@ -4,11 +4,13 @@
4
4
 
5
5
  require "json"
6
6
  require "pathname"
7
+ require_relative "coverage_policy"
8
+ require_relative "report_selection"
7
9
 
8
10
  module Branchproof
9
11
  # Renders versioned terminal and JSON analysis reports.
10
12
  class Report
11
- SCHEMA_VERSION = "1.3"
13
+ SCHEMA_VERSION = "1.4"
12
14
  CRITERION_VERSION = "masking_occurrence_v1"
13
15
  VIEWS = %i[decisions conditions tests decision_tables].freeze
14
16
  DECISION_TABLE_LABELS = { "true" => "T", "false" => "F", "dont_care" => "-" }.freeze
@@ -16,7 +18,7 @@ module Branchproof
16
18
  "excluded" => "EXCLUDED" }.freeze
17
19
 
18
20
  def initialize(inventory:, evidence:, analysis:, minima:, baseline:, diagnostics:, level: 3, missing_only: false,
19
- view: :decisions, run_metadata: {}, saved_document: nil)
21
+ view: :decisions, run_metadata: {}, saved_document: nil, focus: nil, top: nil, minimum: nil)
20
22
  raise ArgumentError, "level must be 1, 2, or 3" unless [1, 2, 3].include?(level.to_i)
21
23
  unless VIEWS.include?(view.to_sym)
22
24
  raise ArgumentError, "view must be :decisions, :conditions, :tests, or :decision_tables"
@@ -33,21 +35,29 @@ module Branchproof
33
35
  @view = view.to_sym
34
36
  @run_metadata = run_metadata || {}
35
37
  @saved_document = saved_document
38
+ raise ArgumentError, "minimum must be a hash" if !minimum.nil? && !minimum.is_a?(Hash)
39
+
40
+ @minimum_override = minimum.is_a?(Hash) && !minimum.empty? ? CoveragePolicy.normalize(minimum) : nil
41
+ @minimum = effective_minimum
42
+ @selection = ReportSelection.new(focus: focus, top: top)
36
43
  end
37
44
 
38
- def self.from_document(document:, level: nil, view: :decisions, missing_only: false)
45
+ def self.from_document(document:, level: nil, view: :decisions, missing_only: false, focus: nil, top: nil,
46
+ minimum: nil)
39
47
  data = document || {}
40
48
  new(inventory: data[:source_inventory] || data["source_inventory"],
41
49
  evidence: data[:observations] || data["observations"],
42
50
  analysis: data[:analysis] || data["analysis"], minima: data[:minima] || data["minima"],
43
51
  baseline: data[:baseline] || data["baseline"], diagnostics: data[:diagnostics] || data["diagnostics"],
44
52
  level: level || (data[:analysis] || data["analysis"] ? 3 : 1), view: view, missing_only: missing_only,
45
- run_metadata: data[:run_metadata] || data["run_metadata"], saved_document: data)
53
+ focus: focus, top: top,
54
+ run_metadata: data[:run_metadata] || data["run_metadata"], saved_document: data, minimum: minimum)
46
55
  end
47
56
 
48
57
  def write(io:, format:)
49
58
  format = format.to_sym
50
59
  raise ArgumentError, "format must be :terminal or :json" unless %i[terminal json].include?(format)
60
+ raise ArgumentError, "focus and top filters are terminal-only" if format == :json && @selection.active?
51
61
 
52
62
  io.write(format == :json ? JSON.generate(json_document) : terminal_document)
53
63
  nil
@@ -105,6 +115,10 @@ module Branchproof
105
115
  def exit_code
106
116
  return 2 unless usage_valid?
107
117
 
118
+ policy_status = value(coverage_policy, :status).to_s
119
+ return 2 if policy_status == "unavailable"
120
+ return 1 if policy_status == "failed"
121
+
108
122
  status = value(@baseline, :status).to_s.upcase
109
123
  return 2 if %w[ERROR INCOMPLETE].include?(status)
110
124
  return 1 if status == "FAILED"
@@ -131,7 +145,11 @@ module Branchproof
131
145
  private
132
146
 
133
147
  def json_document
134
- return normalize(@saved_document) if @saved_document
148
+ if @saved_document
149
+ document = normalize(@saved_document)
150
+ document["coverage_policy"] = normalize(coverage_policy) if @minimum_override
151
+ return document
152
+ end
135
153
 
136
154
  normalize(schema_version: SCHEMA_VERSION,
137
155
  tool_version: (defined?(Branchproof::VERSION) ? Branchproof::VERSION : "unknown"),
@@ -139,14 +157,15 @@ module Branchproof
139
157
  run_ids: Array(value(@evidence, :run_ids)),
140
158
  source_inventory: inventory_with_default_kinds, baseline: @baseline, observations: @evidence,
141
159
  analysis: @analysis, minima: @minima, metrics: metrics,
142
- diagnostics: @diagnostics, completeness: completeness,
160
+ diagnostics: @diagnostics, completeness: completeness, coverage_policy: coverage_policy,
143
161
  run_metadata: @run_metadata)
144
162
  end
145
163
 
146
164
  def terminal_document
147
165
  unless @view == :decisions
148
166
  return FocusedReport.new(document: json_document, view: @view, level: @level,
149
- missing_only: @missing_only, coordinator: self).render
167
+ missing_only: @missing_only, coordinator: self,
168
+ selection: @selection).render
150
169
  end
151
170
 
152
171
  @terminal_ids = terminal_ids
@@ -161,10 +180,13 @@ module Branchproof
161
180
  "#{metrics[:unattributed]} unattributed",
162
181
  values_legend]
163
182
  lines.concat(coverage_ladder_lines)
183
+ lines.concat(coverage_policy_lines)
164
184
  lines << missing_summary_line if @missing_only
185
+ selected_decisions, hidden = selected_decisions_for_display
186
+ append_selection_lines(lines, hidden, "decision")
165
187
  lines << "Scope: supported decisions, conditions, and alternatives"
166
188
  lines << ""
167
- decisions_to_render.each { |decision| render_decision(lines, decision) }
189
+ selected_decisions.each { |decision| render_decision(lines, decision) }
168
190
  render_minima(lines) unless @missing_only
169
191
  unless @diagnostics.empty?
170
192
  lines << "Diagnostics:"
@@ -930,6 +952,33 @@ module Branchproof
930
952
  end
931
953
  end
932
954
 
955
+ def selected_decisions_for_display
956
+ decisions = decisions_to_render
957
+ return [decisions, 0] unless @selection.active?
958
+
959
+ inventory = value(@inventory, :source_units) ? @inventory : { source_units: [], decisions: inventory_decisions }
960
+ decisions = @selection.filter_decisions(decisions, inventory: inventory)
961
+ decisions = decisions.sort_by { |decision| @selection.sort_key(decision, inventory: inventory) }
962
+ @selection.limit(decisions)
963
+ end
964
+
965
+ def append_selection_lines(lines, hidden, unit)
966
+ if @selection.focus_active?
967
+ matching = @selection.matching_decision_ids(json_document)
968
+ lines << if matching.empty?
969
+ "Focus: no matching decisions for #{@selection.focus_label}"
970
+ else
971
+ "Focus: #{@selection.focus_label}"
972
+ end
973
+ end
974
+ return unless @selection.top
975
+
976
+ noun = hidden == 1 ? unit : "#{unit}s"
977
+ hidden_noun = hidden == 1 ? unit : "#{unit}s"
978
+ lines << "Display limit: top #{@selection.top} #{noun}; hidden #{hidden} #{hidden_noun} " \
979
+ "(not risk-ranked)"
980
+ end
981
+
933
982
  def alternatives_to_render(decision)
934
983
  alternatives = Array(value(decision, :alternatives))
935
984
  return alternatives unless @missing_only && analysis_available?
@@ -962,6 +1011,44 @@ module Branchproof
962
1011
  !@analysis.nil?
963
1012
  end
964
1013
 
1014
+ def coverage_policy
1015
+ @coverage_policy ||= CoveragePolicy.new(minimum: @minimum).call(document: policy_document)
1016
+ end
1017
+
1018
+ def coverage_policy_lines
1019
+ policy = coverage_policy
1020
+ minimum = value(policy, :minimum) || {}
1021
+ return [] if minimum.empty?
1022
+
1023
+ lines = ["Coverage policy: #{value(policy, :status).to_s.upcase}"]
1024
+ Array(value(policy, :gates)).each do |gate|
1025
+ numerator = value(gate, :numerator)
1026
+ denominator = value(gate, :denominator)
1027
+ count = if denominator.nil? || (denominator.respond_to?(:zero?) && denominator.zero?)
1028
+ "N/A"
1029
+ else
1030
+ "#{numerator || "N/A"}/#{denominator}"
1031
+ end
1032
+ suffix = value(gate, :reason) ? "; reason: #{value(gate, :reason)}" : ""
1033
+ lines << " #{value(gate, :criterion)}: #{count}, threshold #{value(gate, :minimum)}, " \
1034
+ "#{value(gate, :status).to_s.upcase}#{suffix}"
1035
+ end
1036
+ lines << ""
1037
+ lines
1038
+ end
1039
+
1040
+ def policy_document
1041
+ { baseline: @baseline, completeness: value(@saved_document, :completeness) || completeness,
1042
+ observations: value(@saved_document, :observations) || @evidence,
1043
+ analysis: value(@saved_document, :analysis) || @analysis }
1044
+ end
1045
+
1046
+ def effective_minimum
1047
+ inherited = value(value(@saved_document, :coverage_policy), :minimum)
1048
+ base = inherited.is_a?(Hash) ? CoveragePolicy.normalize(inherited) : {}
1049
+ base.merge(@minimum_override || {})
1050
+ end
1051
+
965
1052
  def coverage_available?
966
1053
  analysis_available? && !value(@analysis, :coverage).nil?
967
1054
  end
@@ -1170,7 +1257,8 @@ module Branchproof
1170
1257
  def normalize_unknown(object)
1171
1258
  object.to_s
1172
1259
  end
1173
- public :condition_coverage_evidence, :coverage_ladder_lines, :coverage_status_label,
1260
+ public :condition_coverage_evidence, :coverage_ladder_lines, :coverage_policy_lines, :coverage_policy,
1261
+ :coverage_status_label,
1174
1262
  :decision_table_requirement, :decision_table_reachability, :decision_table_expected_heading
1175
1263
  end
1176
1264
  end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Branchproof
6
+ # Validates and applies terminal-only report display filters.
7
+ # rubocop:disable Metrics/ClassLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
8
+ class ReportSelection
9
+ attr_reader :focus, :top
10
+
11
+ def initialize(focus: nil, top: nil)
12
+ @focus = normalize_focus(focus)
13
+ @top = normalize_top(top)
14
+ end
15
+
16
+ def active?
17
+ !@focus.nil? || !@top.nil?
18
+ end
19
+
20
+ def focus_active?
21
+ !@focus.nil?
22
+ end
23
+
24
+ def focus_label
25
+ return unless @focus
26
+
27
+ @focus[:line] ? "#{@focus[:path]}:#{@focus[:line]}" : @focus[:path]
28
+ end
29
+
30
+ def matching_decision_ids(document)
31
+ inventory = fetch(document, :source_inventory) || fetch(document, :inventory) || {}
32
+ sources = source_map(inventory)
33
+ Array(fetch(inventory, :decisions)).filter_map do |decision|
34
+ next unless matches_decision?(decision, sources)
35
+
36
+ fetch(decision, :id).to_s
37
+ end
38
+ end
39
+
40
+ def filter_decisions(decisions, inventory: {})
41
+ return decisions unless focus_active?
42
+
43
+ sources = source_map(inventory)
44
+ decisions.select { |decision| matches_decision?(decision, sources) }
45
+ end
46
+
47
+ def limit(items)
48
+ return [items, 0] unless @top
49
+
50
+ [items.first(@top), [items.length - @top, 0].max]
51
+ end
52
+
53
+ def sort_key(decision, inventory: {})
54
+ source = source_for(decision, source_map(inventory))
55
+ [normalize_source_path(fetch(source, :relative_path)).to_s,
56
+ fetch(decision, :line).to_i, fetch(decision, :column).to_i, fetch(decision, :id).to_s]
57
+ end
58
+
59
+ private
60
+
61
+ def normalize_focus(focus)
62
+ return nil if focus.nil?
63
+ raise ArgumentError, "focus must be PATH or PATH:LINE" unless focus.is_a?(String)
64
+
65
+ raw = focus.strip
66
+ raise ArgumentError, "focus path must not be empty" if raw.empty?
67
+
68
+ path, line = if raw.match?(/:\d+\z/)
69
+ match = raw.match(/\A(.+):([0-9]+)\z/)
70
+ raise ArgumentError, "focus path must not be empty" unless match
71
+
72
+ [match[1], match[2].to_i]
73
+ else
74
+ raise ArgumentError, "focus line must be a positive integer" if raw.include?(":")
75
+
76
+ [raw, nil]
77
+ end
78
+ raise ArgumentError, "focus line must be a positive integer" if line && line <= 0
79
+
80
+ { path: normalize_path(path), line: line }.freeze
81
+ end
82
+
83
+ def normalize_top(top)
84
+ return nil if top.nil?
85
+
86
+ value = if top.is_a?(Integer)
87
+ top
88
+ elsif top.is_a?(String) && top.match?(/\A[1-9][0-9]*\z/)
89
+ top.to_i
90
+ end
91
+ raise ArgumentError, "top must be a positive integer" unless value&.positive?
92
+
93
+ value
94
+ end
95
+
96
+ def normalize_path(path)
97
+ text = path.to_s
98
+ raise ArgumentError, "focus path must not be empty" if text.empty? || text.include?("\0")
99
+
100
+ pathname = Pathname.new(text)
101
+ raise ArgumentError, "focus path must be project-relative" if pathname.absolute?
102
+
103
+ normalized = pathname.cleanpath.to_s
104
+ if normalized == "." || normalized == ".." || normalized.start_with?("../")
105
+ raise ArgumentError, "focus path must stay within the project"
106
+ end
107
+
108
+ normalized
109
+ rescue ArgumentError
110
+ raise
111
+ rescue StandardError => e
112
+ raise ArgumentError, "invalid focus path: #{e.message}"
113
+ end
114
+
115
+ def matches_decision?(decision, sources)
116
+ return false unless @focus
117
+
118
+ source = source_for(decision, sources)
119
+ return false unless normalize_source_path(fetch(source, :relative_path)) == @focus[:path]
120
+ return true unless @focus[:line]
121
+
122
+ start_line = fetch(decision, :line).to_i
123
+ return false unless start_line.positive?
124
+
125
+ end_line = start_line + fetch(decision, :expression).to_s.count("\n")
126
+ @focus[:line].between?(start_line, end_line)
127
+ end
128
+
129
+ def source_for(decision, sources)
130
+ sources[fetch(decision, :source_id).to_s] || decision
131
+ end
132
+
133
+ def source_map(inventory)
134
+ Array(fetch(inventory, :source_units)).to_h do |source|
135
+ [fetch(source, :source_id).to_s, source]
136
+ end
137
+ end
138
+
139
+ def normalize_source_path(path)
140
+ return nil if path.nil? || path.to_s.empty?
141
+ return nil if Pathname.new(path.to_s).absolute?
142
+
143
+ Pathname.new(path.to_s).cleanpath.to_s
144
+ rescue ArgumentError
145
+ nil
146
+ end
147
+
148
+ def fetch(hash, key)
149
+ return nil unless hash.respond_to?(:key?)
150
+ return hash[key] if hash.key?(key)
151
+ return hash[key.to_s] if hash.key?(key.to_s)
152
+
153
+ nil
154
+ end
155
+ end
156
+ end
157
+
158
+ # rubocop:enable Metrics/ClassLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
@@ -7,13 +7,14 @@
7
7
  require "json"
8
8
  # rubocop:disable-next Lint/RedundantRequireStatement -- supports standalone core entry
9
9
  require "set"
10
+ require_relative "coverage_policy"
10
11
  require_relative "decision_table"
11
12
 
12
13
  module Branchproof
13
14
  # Reads and validates a persisted JSON report without loading the project.
14
15
  class SavedReport
15
- SUPPORTED_SCHEMAS = %w[1.0 1.1 1.2 1.3].freeze
16
- STRICT_FLOW_SCHEMAS = %w[1.2 1.3].freeze
16
+ SUPPORTED_SCHEMAS = %w[1.0 1.1 1.2 1.3 1.4].freeze
17
+ STRICT_FLOW_SCHEMAS = %w[1.2 1.3 1.4].freeze
17
18
  DECISION_TABLE_STATUSES = %w[calculated not_calculated].freeze
18
19
  RULE_CONDITION_VALUES = DecisionTable::CONDITION_VALUES
19
20
  RULE_COVERAGE_STATUSES = DecisionTable::COVERAGE_STATUSES
@@ -60,6 +61,7 @@ module Branchproof
60
61
  validate_completeness(@document["completeness"])
61
62
  validate_completeness(@document.dig("observations", "completeness"))
62
63
  validate_completeness(@document.dig("analysis", "completeness")) if @document["analysis"]
64
+ validate_coverage_policy
63
65
  validate_optional_sections
64
66
  validate_analysis_coverage
65
67
  @document
@@ -218,7 +220,9 @@ module Branchproof
218
220
  fail_with("nonboolean condition results must be empty") unless results.empty?
219
221
  validate_nonboolean_analysis(decision, inventory_decision)
220
222
  elsif decision.key?("decision_table")
221
- fail_with("decision tables are unsupported in legacy report schemas") unless @schema_version == "1.3"
223
+ unless %w[1.3 1.4].include?(@schema_version)
224
+ fail_with("decision tables are unsupported in legacy report schemas")
225
+ end
222
226
  validate_decision_table(decision["decision_table"], inventory_decision)
223
227
  end
224
228
  seen_conditions = {}
@@ -626,6 +630,28 @@ module Branchproof
626
630
  fail_with("document contains non-string object keys") unless all_hashes_have_string_keys?(@document)
627
631
  end
628
632
 
633
+ def validate_coverage_policy
634
+ policy = @document["coverage_policy"]
635
+ fail_with("missing field: coverage_policy") if @schema_version == "1.4" && !policy
636
+ return if policy.nil?
637
+
638
+ fail_with("coverage_policy must be an object") unless hash_with_string_keys?(policy)
639
+ minimum = policy["minimum"]
640
+ fail_with("coverage_policy minimum must be an object") unless hash_with_string_keys?(minimum)
641
+ normalized = CoveragePolicy.normalize(minimum)
642
+ fail_with("coverage_policy status must be a string") unless policy["status"].is_a?(String)
643
+ fail_with("coverage_policy gates must be an array") unless policy["gates"].is_a?(Array)
644
+ expected = CoveragePolicy.new(minimum: normalized).call(
645
+ document: { baseline: @document["baseline"], completeness: @document["completeness"],
646
+ observations: @document["observations"], analysis: @document["analysis"] }
647
+ )
648
+ actual = JSON.parse(JSON.generate(policy))
649
+ expected = JSON.parse(JSON.generate(expected))
650
+ fail_with("coverage_policy does not match report coverage") unless actual == expected
651
+ rescue ArgumentError => e
652
+ fail_with(e.message.sub(/\Ainvalid saved report: /, ""))
653
+ end
654
+
629
655
  def validate_phases(vector)
630
656
  phases = vector["phases_by_test"]
631
657
  return if phases.nil?
@@ -22,6 +22,15 @@ module Branchproof
22
22
  prepend DefaultSyntax
23
23
  prepend ValueSyntax
24
24
 
25
+ # Strings, symbols, and numbers are always truthy. A regexp literal in a
26
+ # condition matches against $_, so its truth is not static.
27
+ ALWAYS_TRUTHY_LITERALS = [
28
+ Prism::TrueNode, Prism::StringNode, Prism::InterpolatedStringNode,
29
+ Prism::SymbolNode, Prism::InterpolatedSymbolNode, Prism::IntegerNode,
30
+ Prism::FloatNode, Prism::RationalNode, Prism::ImaginaryNode
31
+ ].freeze
32
+ private_constant :ALWAYS_TRUTHY_LITERALS
33
+
25
34
  attr_reader :root, :limits
26
35
 
27
36
  def initialize(root:, limits:)
@@ -410,7 +419,7 @@ module Branchproof
410
419
  end
411
420
 
412
421
  def literal_truth(node)
413
- return true if node.is_a?(Prism::TrueNode)
422
+ return true if ALWAYS_TRUTHY_LITERALS.any? { |type| node.is_a?(type) }
414
423
  return false if node.is_a?(Prism::FalseNode) || node.is_a?(Prism::NilNode)
415
424
 
416
425
  nil
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Branchproof
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.1"
5
5
  end
data/lib/branchproof.rb CHANGED
@@ -9,6 +9,9 @@ require_relative "branchproof/project"
9
9
  # Public namespace for source inventory and one-run MC/DC reporting.
10
10
  module Branchproof
11
11
  class Error < StandardError; end
12
+ autoload :Configuration, "branchproof/configuration"
13
+ autoload :CoveragePolicy, "branchproof/coverage_policy"
14
+ autoload :ReportSelection, "branchproof/report_selection"
12
15
  autoload :CLI, "branchproof/cli"
13
16
  autoload :Instrumenter, "branchproof/instrumenter"
14
17
  autoload :Loader, "branchproof/loader"
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/comparison.rb, lib/branchproof/constraints.rb, lib/branchproof/instrumenter.rb, lib/branchproof/runtime_flow.rb, lib/branchproof/saved_report.rb, lib/branchproof/value_syntax.rb, lib/branchproof/rails_support.rb, lib/branchproof/rspec_adapter.rb, lib/branchproof/value_runtime.rb, lib/branchproof/coverage_index.rb, lib/branchproof/decision_table.rb, lib/branchproof/default_syntax.rb, lib/branchproof/focused_report.rb, lib/branchproof/decision_syntax.rb, lib/branchproof/default_runtime.rb, lib/branchproof/exception_syntax.rb, lib/branchproof/iteration_syntax.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb, lib/branchproof/exception_runtime.rb, lib/branchproof/iteration_runtime.rb, lib/branchproof/flow_instrumentation.rb, lib/branchproof/value_instrumentation.rb, lib/branchproof/default_instrumentation.rb, lib/branchproof/exception_instrumentation.rb, lib/branchproof/iteration_instrumentation.rb, lib/branchproof/extended_alternative_runtime.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/constraints.rb, lib/branchproof/instrumenter.rb, lib/branchproof/runtime_flow.rb, lib/branchproof/saved_report.rb, lib/branchproof/value_syntax.rb, lib/branchproof/configuration.rb, lib/branchproof/rails_support.rb, lib/branchproof/rspec_adapter.rb, lib/branchproof/value_runtime.rb, lib/branchproof/coverage_index.rb, lib/branchproof/decision_table.rb, lib/branchproof/default_syntax.rb, lib/branchproof/focused_report.rb, lib/branchproof/coverage_policy.rb, lib/branchproof/decision_syntax.rb, lib/branchproof/default_runtime.rb, lib/branchproof/exception_syntax.rb, lib/branchproof/iteration_syntax.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/report_selection.rb, lib/branchproof/comparison_report.rb, lib/branchproof/exception_runtime.rb, lib/branchproof/iteration_runtime.rb, lib/branchproof/flow_instrumentation.rb, lib/branchproof/value_instrumentation.rb, lib/branchproof/default_instrumentation.rb, lib/branchproof/exception_instrumentation.rb, lib/branchproof/iteration_instrumentation.rb, lib/branchproof/extended_alternative_runtime.rb |
6
6
 
7
7
  Keep source-boundary and parameter-binding rules together for auditing.
8
8
  rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity,
@@ -18,9 +18,11 @@ Not documented.
18
18
  - [Branchproof/CLI.md](doc/Branchproof/CLI.md)
19
19
  - [Branchproof/Comparison.md](doc/Branchproof/Comparison.md)
20
20
  - [Branchproof/ComparisonReport.md](doc/Branchproof/ComparisonReport.md)
21
+ - [Branchproof/Configuration.md](doc/Branchproof/Configuration.md)
21
22
  - [Branchproof/Constraints/Solver.md](doc/Branchproof/Constraints/Solver.md)
22
23
  - [Branchproof/Constraints.md](doc/Branchproof/Constraints.md)
23
24
  - [Branchproof/CoverageIndex.md](doc/Branchproof/CoverageIndex.md)
25
+ - [Branchproof/CoveragePolicy.md](doc/Branchproof/CoveragePolicy.md)
24
26
  - [Branchproof/DecisionSyntax.md](doc/Branchproof/DecisionSyntax.md)
25
27
  - [Branchproof/DecisionTable.md](doc/Branchproof/DecisionTable.md)
26
28
  - [Branchproof/DefaultInstrumentation.md](doc/Branchproof/DefaultInstrumentation.md)
@@ -54,6 +56,7 @@ Not documented.
54
56
  - [Branchproof/RailsSupport.md](doc/Branchproof/RailsSupport.md)
55
57
  - [Branchproof/Records.md](doc/Branchproof/Records.md)
56
58
  - [Branchproof/Report.md](doc/Branchproof/Report.md)
59
+ - [Branchproof/ReportSelection.md](doc/Branchproof/ReportSelection.md)
57
60
  - [Branchproof/Runtime.md](doc/Branchproof/Runtime.md)
58
61
  - [Branchproof/RuntimeFlow.md](doc/Branchproof/RuntimeFlow.md)
59
62
  - [Branchproof/SavedReport.md](doc/Branchproof/SavedReport.md)
data/sig/branchproof.rbs CHANGED
@@ -40,6 +40,15 @@ module Branchproof
40
40
  def to_h: () -> record
41
41
  end
42
42
 
43
+ class Configuration
44
+ SCHEMA_VERSION: Integer
45
+ PROJECTS: Array[String]
46
+ FRAMEWORKS: Array[String]
47
+ MINIMUM_CRITERIA: Array[String]
48
+ FIELDS: Array[String]
49
+ def self.load: (path: String, root: String, ?explicit: bool, ?disabled: bool) -> record?
50
+ end
51
+
43
52
  module RailsSupport
44
53
  def self.boot: (project: record) -> record
45
54
  def self.validate_rspec!: (project: record) -> record?
@@ -117,6 +126,14 @@ module Branchproof
117
126
  def missing: (decision_id: String, condition_index: Integer) -> record?
118
127
  end
119
128
 
129
+ class CoveragePolicy
130
+ CRITERIA: Hash[String, Hash[Symbol, String]]
131
+ COMPLETENESS_FIELDS: Array[String]
132
+ def self.normalize: (Hash[String | Symbol, untyped] minimum) -> Hash[String, Integer | Float]
133
+ def initialize: (minimum: Hash[String | Symbol, untyped]) -> void
134
+ def call: (document: document?) -> record
135
+ end
136
+
120
137
  module Constraints
121
138
  VERSION: Integer
122
139
  NUMERIC_OPERATORS: Array[String]
@@ -194,13 +211,15 @@ module Branchproof
194
211
  SCHEMA_VERSION: String
195
212
  CRITERION_VERSION: String
196
213
  VIEWS: Array[Symbol]
197
- 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
198
- def self.from_document: (document: document, ?level: Integer?, ?view: Symbol, ?missing_only: bool) -> Report
214
+ 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?, ?focus: String?, ?top: (Integer | String)?, ?minimum: document?) -> void
215
+ def self.from_document: (document: document, ?level: Integer?, ?view: Symbol, ?missing_only: bool, ?focus: String?, ?top: (Integer | String)?, ?minimum: document?) -> Report
199
216
  def diagnostic_message: (Hash[Symbol | String, untyped] diagnostic) -> String
200
217
  def condition_explanation: (decision_id: String, condition_id: String) -> String
201
218
  def decision_table_requirement: (untyped value) -> String?
202
219
  def decision_table_reachability: (document rule) -> String
203
220
  def decision_table_expected_heading: (document decision) -> String
221
+ def coverage_policy: () -> record
222
+ def coverage_policy_lines: () -> Array[String]
204
223
  def rerun_command: (String test_id) -> String?
205
224
  def write: (io: _WriteIO, format: Symbol | String) -> nil
206
225
  def exit_code: () -> Integer
@@ -215,10 +234,23 @@ module Branchproof
215
234
  end
216
235
 
217
236
  class FocusedReport
218
- def initialize: (document: document, view: Symbol, level: Integer, ?missing_only: bool) -> void
237
+ def initialize: (document: document, view: Symbol, level: Integer, ?missing_only: bool, ?coordinator: Report?, ?focus: String?, ?top: (Integer | String)?, ?selection: ReportSelection?) -> void
219
238
  def render: () -> String
220
239
  end
221
240
 
241
+ class ReportSelection
242
+ attr_reader focus: record?
243
+ attr_reader top: Integer?
244
+ def initialize: (?focus: String?, ?top: (Integer | String)?) -> void
245
+ def active?: () -> bool
246
+ def focus_active?: () -> bool
247
+ def focus_label: () -> String?
248
+ def matching_decision_ids: (document: document) -> Array[String]
249
+ def filter_decisions: (Array[record] decisions, ?inventory: document) -> Array[record]
250
+ def limit: (Array[record] items) -> [Array[record], Integer]
251
+ def sort_key: (record decision, ?inventory: document) -> [String, Integer, Integer, String]
252
+ end
253
+
222
254
  class SavedReport
223
255
  def self.read: (String path) -> document
224
256
  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.10.0
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucian Ghinda
@@ -70,9 +70,11 @@ files:
70
70
  - doc/Branchproof/CLI.md
71
71
  - doc/Branchproof/Comparison.md
72
72
  - doc/Branchproof/ComparisonReport.md
73
+ - doc/Branchproof/Configuration.md
73
74
  - doc/Branchproof/Constraints.md
74
75
  - doc/Branchproof/Constraints/Solver.md
75
76
  - doc/Branchproof/CoverageIndex.md
77
+ - doc/Branchproof/CoveragePolicy.md
76
78
  - doc/Branchproof/DecisionSyntax.md
77
79
  - doc/Branchproof/DecisionTable.md
78
80
  - doc/Branchproof/DefaultInstrumentation.md
@@ -106,6 +108,7 @@ files:
106
108
  - doc/Branchproof/RailsSupport/Error.md
107
109
  - doc/Branchproof/Records.md
108
110
  - doc/Branchproof/Report.md
111
+ - doc/Branchproof/ReportSelection.md
109
112
  - doc/Branchproof/Runtime.md
110
113
  - doc/Branchproof/RuntimeFlow.md
111
114
  - doc/Branchproof/SavedReport.md
@@ -123,8 +126,10 @@ files:
123
126
  - lib/branchproof/cli.rb
124
127
  - lib/branchproof/comparison.rb
125
128
  - lib/branchproof/comparison_report.rb
129
+ - lib/branchproof/configuration.rb
126
130
  - lib/branchproof/constraints.rb
127
131
  - lib/branchproof/coverage_index.rb
132
+ - lib/branchproof/coverage_policy.rb
128
133
  - lib/branchproof/decision_syntax.rb
129
134
  - lib/branchproof/decision_table.rb
130
135
  - lib/branchproof/default_instrumentation.rb
@@ -149,6 +154,7 @@ files:
149
154
  - lib/branchproof/rails_support.rb
150
155
  - lib/branchproof/records.rb
151
156
  - lib/branchproof/report.rb
157
+ - lib/branchproof/report_selection.rb
152
158
  - lib/branchproof/rspec_adapter.rb
153
159
  - lib/branchproof/runtime.rb
154
160
  - lib/branchproof/runtime_flow.rb
@@ -184,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
190
  - !ruby/object:Gem::Version
185
191
  version: '0'
186
192
  requirements: []
187
- rubygems_version: 4.0.11
193
+ rubygems_version: 3.7.2
188
194
  specification_version: 4
189
195
  summary: Modified condition and decision coverage for Ruby tests
190
196
  test_files: []