branchproof 0.8.0 → 0.10.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/README.md +120 -28
  4. data/doc/Branchproof/DefaultInstrumentation.md +13 -0
  5. data/doc/Branchproof/DefaultRuntime.md +11 -0
  6. data/doc/Branchproof/DefaultSyntax.md +7 -0
  7. data/doc/Branchproof/Evidence.md +3 -0
  8. data/doc/Branchproof/ExceptionInstrumentation.md +9 -0
  9. data/doc/Branchproof/ExceptionRuntime.md +27 -0
  10. data/doc/Branchproof/ExceptionSyntax.md +9 -0
  11. data/doc/Branchproof/ExtendedAlternativeRuntime.md +19 -0
  12. data/doc/Branchproof/Instrumenter.md +1 -1
  13. data/doc/Branchproof/IterationInstrumentation.md +7 -0
  14. data/doc/Branchproof/IterationRuntime.md +21 -0
  15. data/doc/Branchproof/IterationSyntax.md +11 -0
  16. data/doc/Branchproof/Project.md +4 -1
  17. data/doc/Branchproof/RSpecAdapter/ClassRunnerGuard.md +11 -0
  18. data/doc/Branchproof/RSpecAdapter/ContextLifecycle.md +14 -0
  19. data/doc/Branchproof/RSpecAdapter/DefaultDiscovery.md +11 -0
  20. data/doc/Branchproof/RSpecAdapter/ExampleLifecycle.md +11 -0
  21. data/doc/Branchproof/RSpecAdapter/RunnerGuard.md +11 -0
  22. data/doc/Branchproof/RSpecAdapter/UnsupportedRunner.md +12 -0
  23. data/doc/Branchproof/RSpecAdapter.md +47 -0
  24. data/doc/Branchproof/RailsSupport.md +35 -2
  25. data/doc/Branchproof/Report.md +3 -0
  26. data/doc/Branchproof/Runtime.md +59 -1
  27. data/doc/Branchproof/Source.md +7 -1
  28. data/doc/Branchproof/ValueInstrumentation.md +7 -0
  29. data/doc/Branchproof/ValueRuntime.md +26 -0
  30. data/doc/Branchproof/ValueSyntax.md +26 -0
  31. data/doc/Branchproof/Worker.md +8 -1
  32. data/doc/Branchproof.md +24 -3
  33. data/doc/CHANGELOG.md +37 -0
  34. data/doc/README.md +120 -28
  35. data/lib/branchproof/cli.rb +74 -28
  36. data/lib/branchproof/comparison.rb +52 -6
  37. data/lib/branchproof/decision_syntax.rb +19 -9
  38. data/lib/branchproof/default_instrumentation.rb +140 -0
  39. data/lib/branchproof/default_runtime.rb +16 -0
  40. data/lib/branchproof/default_syntax.rb +94 -0
  41. data/lib/branchproof/evidence.rb +87 -1
  42. data/lib/branchproof/exception_instrumentation.rb +96 -0
  43. data/lib/branchproof/exception_runtime.rb +41 -0
  44. data/lib/branchproof/exception_syntax.rb +157 -0
  45. data/lib/branchproof/extended_alternative_runtime.rb +23 -0
  46. data/lib/branchproof/flow_instrumentation.rb +36 -7
  47. data/lib/branchproof/focused_report.rb +5 -0
  48. data/lib/branchproof/instrumenter.rb +50 -5
  49. data/lib/branchproof/iteration_instrumentation.rb +66 -0
  50. data/lib/branchproof/iteration_runtime.rb +79 -0
  51. data/lib/branchproof/iteration_syntax.rb +75 -0
  52. data/lib/branchproof/loader.rb +6 -1
  53. data/lib/branchproof/project.rb +49 -2
  54. data/lib/branchproof/rails_support.rb +101 -0
  55. data/lib/branchproof/report.rb +21 -1
  56. data/lib/branchproof/rspec_adapter.rb +423 -0
  57. data/lib/branchproof/runtime.rb +14 -0
  58. data/lib/branchproof/saved_report.rb +9 -2
  59. data/lib/branchproof/source.rb +59 -32
  60. data/lib/branchproof/value_instrumentation.rb +35 -0
  61. data/lib/branchproof/value_runtime.rb +73 -0
  62. data/lib/branchproof/value_syntax.rb +116 -0
  63. data/lib/branchproof/version.rb +1 -1
  64. data/lib/branchproof/worker.rb +75 -15
  65. data/lib/branchproof.rb +1 -0
  66. data/llms.txt +24 -3
  67. data/sig/branchproof.rbs +45 -1
  68. metadata +36 -2
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Branchproof
4
+ # Source rewrite for value observations; it preserves the wrapped value.
5
+ module ValueInstrumentation
6
+ private
7
+
8
+ def render_flow(bytes, decision, nested, encloses)
9
+ return render_value(bytes, decision, nested, encloses) if decision.dig(:instrumentation, :type) == "value"
10
+
11
+ super
12
+ end
13
+
14
+ # rubocop:disable-next Metrics/MethodLength -- success and exception traces differ.
15
+ def render_value(bytes, decision, nested, encloses)
16
+ expression = render_children(bytes, decision[:byte_start], decision[:byte_length], nested, encloses)
17
+ metadata = decision.fetch(:instrumentation)
18
+ runtime = self.class::RUNTIME
19
+ id = decision.fetch(:id).inspect
20
+ domain = metadata.fetch(:domain).to_sym.inspect
21
+ count = decision.fetch(:alternatives).length
22
+ enter = "#{runtime}.enter(#{id}); #{runtime}.set_alternative_count(#{id}, #{count})"
23
+ if metadata.fetch(:domain).to_sym == :dispatch
24
+ "(begin; #{enter}; begin; " \
25
+ "(begin; #{runtime}.dispatch_path(#{id}, (#{expression}), false); end); " \
26
+ "rescue ::Exception; #{runtime}.dispatch_path(#{id}, nil, true); raise; " \
27
+ "ensure; #{runtime}.leave(#{id}); end; end)"
28
+ else
29
+ "(begin; #{enter}; begin; " \
30
+ "#{runtime}.value_path(#{id}, (#{expression}), #{domain}); " \
31
+ "ensure; #{runtime}.leave(#{id}); end; end)"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Branchproof
4
+ # Runtime mapping for value observations.
5
+ module ValueRuntime
6
+ # rubocop:disable Style/CaseEquality, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
7
+ def comparison_index(value)
8
+ return 3 if nil.equal?(value)
9
+ return unless Integer === value || Float === value || Rational === value
10
+ return if Float === value && value.nan?
11
+ return 0 if value < 0 # rubocop:disable Style/NumericPredicate
12
+ return 1 if value == 0 # rubocop:disable Style/NumericPredicate
13
+ return 2 if value > 0 # rubocop:disable Style/NumericPredicate
14
+
15
+ nil
16
+ end
17
+ module_function :comparison_index
18
+ # rubocop:enable Style/CaseEquality, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
19
+
20
+ def set_alternative_count(decision_id, count)
21
+ frame = current_frame(decision_id)
22
+ frame[:alternative_count] = count if frame
23
+ nil
24
+ end
25
+
26
+ # rubocop:disable-next Metrics/MethodLength -- trace state branches are explicit.
27
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- domain dispatch mirrors the observable value contract.
28
+ def value_path(decision_id, value, domain)
29
+ frame = current_frame(decision_id)
30
+ return value unless frame
31
+ return value if frame[:finished]
32
+
33
+ index = case domain.to_sym
34
+ when :truthiness, :defined then value ? 1 : 0
35
+ when :lookup
36
+ if nil.equal?(value)
37
+ 2
38
+ elsif value
39
+ 0
40
+ else
41
+ 1
42
+ end
43
+ when :comparison then comparison_index(value)
44
+ when :dispatch then 0
45
+ end
46
+ if index
47
+ frame[:observations] = if frame.fetch(:alternative_count, index + 1) == 2
48
+ [[0, index.zero?], [1, index == 1]]
49
+ else
50
+ (0..index).map { |position| [position, position == index] }
51
+ end
52
+ frame[:outcome] = true
53
+ frame[:finished] = true
54
+ end
55
+ value
56
+ rescue NoMethodError, ArgumentError
57
+ # A user-defined <=> may return an incomparable object. Preserve it and
58
+ # leave the vector incomplete instead of inventing a domain bucket.
59
+ value
60
+ end
61
+
62
+ def dispatch_path(decision_id, value, raised)
63
+ frame = current_frame(decision_id)
64
+ return value unless frame
65
+
66
+ index = raised ? 1 : 0
67
+ frame[:observations] = [[0, index.zero?], [1, index == 1]]
68
+ frame[:outcome] = true
69
+ frame[:finished] = true
70
+ value
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Branchproof
6
+ # Inventory for expressions whose result is itself the observable value.
7
+ # This deliberately excludes control-flow predicates already owned by Source.
8
+ module ValueSyntax
9
+ BOOLEAN_OPERATORS = %i[== != < <= > >= === =~ !~].freeze
10
+ DISPATCH_METHODS = %i[public_send send __send__].freeze
11
+
12
+ # rubocop:disable-next Metrics/ParameterLists -- source seam mirrors Source#decisions_for.
13
+ def additional_decisions_for(program, bytes, source_id, file_reasons, encoding, decisions:, collected:)
14
+ occupied = decisions.each_with_object({}) do |decision, ranges|
15
+ ranges[range_key(decision[:byte_start], decision[:byte_length])] = true
16
+ Array(decision[:conditions]).each do |condition|
17
+ ranges[range_key(condition[:byte_start], condition[:byte_length])] = true
18
+ end
19
+ end
20
+ value_decisions_for(
21
+ program, bytes, source_id, nodes: collected.fetch(:value_nodes), occupied_ranges: occupied,
22
+ file_reasons: file_reasons, encoding: encoding
23
+ )
24
+ end
25
+
26
+ # `nodes:` is supplied by Source's fused AST walk. It remains optional for
27
+ # the standalone discovery API used by focused syntax tests.
28
+ # rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists -- source seam mirrors Source#decisions_for.
29
+ def value_decisions_for(program, bytes, source_id, nodes: nil, occupied_ranges: {}, file_reasons: [],
30
+ encoding: "UTF-8")
31
+ occupied = occupied_ranges.transform_keys do |range|
32
+ range.is_a?(Array) ? range_key(*range) : range
33
+ end
34
+ decisions = []
35
+ each_value_node(program, nodes) do |node|
36
+ spec = value_spec(node)
37
+ next unless spec
38
+
39
+ key = range_key(node.location.start_offset, node.location.length)
40
+ next if occupied[key]
41
+
42
+ decisions << build_value_decision(node, spec, bytes, source_id, file_reasons, encoding)
43
+ occupied[key] = true
44
+ end
45
+ decisions
46
+ end
47
+
48
+ private
49
+
50
+ def value_candidate_node?(node)
51
+ !value_spec(node).nil?
52
+ end
53
+
54
+ def each_value_node(program, nodes, &)
55
+ return nodes.each(&) if nodes
56
+
57
+ walker = respond_to?(:walk_skipping_defined_operands, true) ? :walk_skipping_defined_operands : :walk
58
+ send(walker, program, &)
59
+ end
60
+
61
+ def range_key(start_offset, length)
62
+ (start_offset << 32) | length
63
+ end
64
+
65
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
66
+ def value_spec(node)
67
+ if Prism.const_defined?(:MatchWriteNode) && node.is_a?(Prism::MatchWriteNode)
68
+ return { context: "match_capture", domain: :truthiness, alternatives: %w[false true], type: "value" }
69
+ end
70
+ return nil unless node.is_a?(Prism::CallNode)
71
+
72
+ name = node.name.to_sym
73
+ if name == :<=>
74
+ return { context: "comparison", domain: :comparison, alternatives: %w[negative zero positive nil],
75
+ type: "value" }
76
+ end
77
+ return { context: "lookup", domain: :lookup, alternatives: %w[truthy false nil], type: "value" } if name == :[]
78
+ if DISPATCH_METHODS.include?(name)
79
+ return { context: "dispatch", domain: :dispatch, alternatives: %w[success exception],
80
+ type: "value" }
81
+ end
82
+ if BOOLEAN_OPERATORS.include?(name) || name.to_s.end_with?("?")
83
+ return { context: "predicate", domain: :truthiness, alternatives: %w[false true],
84
+ type: "value" }
85
+ end
86
+
87
+ nil
88
+ end
89
+
90
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/ParameterLists, Metrics/PerceivedComplexity -- record assembly is intentionally explicit.
91
+ def build_value_decision(node, spec, bytes, source_id, file_reasons, encoding)
92
+ location = node.location
93
+ expression = text_value(bytes.byteslice(location.start_offset, location.length), encoding)
94
+ alternatives = spec.fetch(:alternatives).each_with_index.map do |label, index|
95
+ { index: index, id: nil, expression: label, byte_start: location.start_offset, byte_length: location.length }
96
+ end
97
+ id = Records.decision_id(source_id: source_id, context: spec.fetch(:context),
98
+ byte_start: location.start_offset, byte_length: location.length,
99
+ tree: nil)
100
+ alternatives = alternatives.map { |item| item.merge(id: Records.condition_id(id, item.fetch(:index))) }
101
+ kind = alternatives.length == 2 ? "implicit" : "multiway"
102
+ reasons = Array(file_reasons)
103
+ reasons += Array(unsupported_reasons(node, bytes)) if respond_to?(:unsupported_reasons, true)
104
+ limit = @limits[:conditions_per_decision] if defined?(@limits) && @limits.respond_to?(:[])
105
+ reasons << "alternative_limit_exceeded" if limit && alternatives.length > limit
106
+ Records.build(id: id, source_id: source_id, kind: kind, context: spec.fetch(:context),
107
+ byte_start: location.start_offset, byte_length: location.length,
108
+ line: location.start_line, column: location.start_column,
109
+ expression: expression, tree: nil, conditions: [],
110
+ alternatives: alternatives, discovered_condition_count: 0,
111
+ support_status: reasons.empty? ? "SUPPORTED" : "UNSUPPORTED",
112
+ support_reasons: reasons.uniq, opaque_ranges: [],
113
+ instrumentation: { type: "value", domain: spec.fetch(:domain) })
114
+ end
115
+ end
116
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Branchproof
4
- VERSION = "0.8.0"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -8,19 +8,18 @@ require "stringio"
8
8
  require "fileutils"
9
9
 
10
10
  module Branchproof
11
- # Runs one isolated Minitest worker and atomically exports its result.
11
+ # Runs one isolated test worker and atomically exports its result.
12
12
  module Worker
13
13
  module_function
14
14
 
15
15
  def child_process(config_path)
16
+ $stdout.sync = true
17
+ $stderr.sync = true
16
18
  payload = JSON.parse(File.binread(config_path))
17
19
  project = symbolize(payload.fetch("project"))
18
20
  prepend_load_paths(project)
19
21
  inventory = symbolize(payload.fetch("inventory"))
20
22
  limits = symbolize(payload.fetch("limits"))
21
- require "minitest"
22
- require "minitest/test"
23
- require_relative "minitest_adapter"
24
23
  evidence = Branchproof::Evidence.new(inventory: inventory, limits: limits, run_id: payload.fetch("run_id"))
25
24
  runtime = Branchproof::Runtime
26
25
  runtime.boot(evidence: evidence)
@@ -28,23 +27,38 @@ module Branchproof
28
27
  status = loader.install
29
28
  return write_failure(payload, "loader", status) unless status[:status].to_sym == :installed
30
29
 
31
- adapter = Branchproof::MinitestAdapter.new(runtime: runtime)
30
+ adapter = adapter_for(project, runtime)
31
+ guard_late_rspec_execution(adapter, payload) if project[:framework].to_s == "rspec"
32
32
  rails_metadata = nil
33
33
  ARGV.replace(payload.fetch("runner_args"))
34
- adapter.run(test_files: payload.fetch("test_files"), runner_args: payload.fetch("runner_args"),
35
- before_load: lambda {
36
- rails_metadata = boot_project(project)
37
- }, on_complete: lambda { |baseline|
38
- result = completion_result(baseline: baseline, project: project, rails_metadata: rails_metadata,
39
- evidence: runtime.snapshot, tests: adapter.tests.values,
40
- diagnostics: loader.diagnostics)
41
- write_completion(payload, result)
42
- })
34
+ callbacks = if project[:framework].to_s == "rspec"
35
+ { test_selection_explicit: payload.fetch("test_selection_explicit", false), after_load: lambda {
36
+ require_relative "rails_support"
37
+ rails_metadata = Branchproof::RailsSupport.validate_rspec!(project: project)
38
+ } }
39
+ else
40
+ {}
41
+ end
42
+ before_load = lambda do
43
+ rails_metadata = boot_project(project) unless project[:framework].to_s == "rspec"
44
+ end
45
+ on_complete = lambda do |baseline|
46
+ result = completion_result(baseline: baseline, project: project, rails_metadata: rails_metadata,
47
+ evidence: runtime.snapshot, tests: adapter.tests.values,
48
+ diagnostics: loader.diagnostics)
49
+ write_completion(payload, result)
50
+ end
51
+ exit_status = adapter.run(test_files: payload.fetch("test_files"), runner_args: payload.fetch("runner_args"),
52
+ before_load: before_load, on_complete: on_complete, **callbacks)
53
+ return exit_status if project[:framework].to_s == "rspec"
54
+
43
55
  adapter.validate_runner!
44
56
  Minitest.autorun
45
57
  nil
46
58
  rescue StandardError => e
47
- code = if e.message.include?("parallel")
59
+ code = if e.respond_to?(:diagnostic_code)
60
+ e.diagnostic_code
61
+ elsif e.message.match?(/parallel|runner|bisect|DRb|dry.run/)
48
62
  "unsupported_runner"
49
63
  elsif defined?(Branchproof::RailsSupport::Error) && e.is_a?(Branchproof::RailsSupport::Error)
50
64
  "rails_boot"
@@ -55,6 +69,44 @@ module Branchproof
55
69
  2
56
70
  end
57
71
 
72
+ def adapter_for(project, runtime)
73
+ if project[:framework].to_s == "rspec"
74
+ begin
75
+ require "rspec/core"
76
+ rescue LoadError => e
77
+ raise unless e.path == "rspec/core"
78
+
79
+ error = ArgumentError.new("RSpec is not available; add rspec to the application's test bundle")
80
+ def error.diagnostic_code = "rspec_missing"
81
+ raise error
82
+ end
83
+ require_relative "rspec_adapter"
84
+ Branchproof::RSpecAdapter.new(runtime: runtime)
85
+ else
86
+ require "minitest"
87
+ require "minitest/test"
88
+ require_relative "minitest_adapter"
89
+ Branchproof::MinitestAdapter.new(runtime: runtime)
90
+ end
91
+ end
92
+
93
+ # Installed before application hooks, so this runs after their at_exit work.
94
+ # A rescued second runner must still invalidate the already-exported result.
95
+ def guard_late_rspec_execution(adapter, payload)
96
+ at_exit do
97
+ error = adapter.late_execution_error
98
+ if error
99
+ result = symbolize(JSON.parse(File.binread(payload.fetch("result_path"))))
100
+ diagnostic = { code: "unsupported_runner", severity: "error", message: error.message }
101
+ result.merge!(status: "ERROR", finalized: false, exit_status: 2,
102
+ diagnostics: Array(result[:diagnostics]) + [diagnostic])
103
+ result[:evidence] = incomplete_evidence(result[:evidence], [diagnostic]) if result[:evidence]
104
+ write_completion(payload, result)
105
+ exit(2)
106
+ end
107
+ end
108
+ end
109
+
58
110
  def legacy_project
59
111
  { "kind" => "ruby", "root" => Dir.pwd,
60
112
  "load_paths" => [File.join(Dir.pwd, "lib"), File.join(Dir.pwd, "test")],
@@ -76,7 +128,14 @@ module Branchproof
76
128
  end
77
129
 
78
130
  def project_metadata(project, rails_metadata)
131
+ framework = project[:framework].to_s.empty? ? "minitest" : project[:framework].to_s
132
+ version = if framework == "rspec" && defined?(RSpec::Core::Version::STRING)
133
+ RSpec::Core::Version::STRING
134
+ elsif framework == "minitest" && defined?(Minitest::VERSION)
135
+ Minitest::VERSION
136
+ end
79
137
  metadata = { kind: project[:kind].to_s, root: project[:root].to_s,
138
+ framework: framework, framework_version: version,
80
139
  load_paths: Array(project[:load_paths]).map(&:to_s), serial_policy: { mode: "single_process", workers: 1 } }
81
140
  metadata.merge!(rails_metadata) if rails_metadata
82
141
  metadata
@@ -85,6 +144,7 @@ module Branchproof
85
144
  # This result boundary intentionally carries the complete worker payload.
86
145
  # rubocop:disable-next Metrics/ParameterLists
87
146
  def completion_result(baseline:, project:, rails_metadata:, evidence:, tests:, diagnostics:)
147
+ diagnostics = Array(baseline[:diagnostics]) + diagnostics
88
148
  result = baseline.merge(project: project_metadata(project, rails_metadata), evidence: evidence,
89
149
  tests: tests, diagnostics: diagnostics)
90
150
  return result unless diagnostics.any? { |diagnostic| diagnostic[:severity].to_s == "error" }
data/lib/branchproof.rb CHANGED
@@ -14,6 +14,7 @@ module Branchproof
14
14
  autoload :Loader, "branchproof/loader"
15
15
  autoload :Runtime, "branchproof/runtime"
16
16
  autoload :MinitestAdapter, "branchproof/minitest_adapter"
17
+ autoload :RSpecAdapter, "branchproof/rspec_adapter"
17
18
  autoload :Evidence, "branchproof/evidence"
18
19
  autoload :Analyzer, "branchproof/analyzer"
19
20
  autoload :Constraints, "branchproof/constraints"
data/llms.txt CHANGED
@@ -2,10 +2,11 @@
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/rails_support.rb, lib/branchproof/coverage_index.rb, lib/branchproof/decision_table.rb, lib/branchproof/focused_report.rb, lib/branchproof/decision_syntax.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb, lib/branchproof/flow_instrumentation.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/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 |
6
6
 
7
- Keep each bounded source rewrite together so its evaluation order can be
8
- audited. rubocop:disable Metrics/AbcSize, Metrics/MethodLength
7
+ Keep source-boundary and parameter-binding rules together for auditing.
8
+ rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity,
9
+ Metrics/MethodLength, Metrics/ModuleLength, Metrics/PerceivedComplexity
9
10
 
10
11
  ## Constants
11
12
  ### `VERSION` <a id="constant-VERSION"></a> <a id="VERSION-constant"></a>
@@ -22,16 +23,33 @@ Not documented.
22
23
  - [Branchproof/CoverageIndex.md](doc/Branchproof/CoverageIndex.md)
23
24
  - [Branchproof/DecisionSyntax.md](doc/Branchproof/DecisionSyntax.md)
24
25
  - [Branchproof/DecisionTable.md](doc/Branchproof/DecisionTable.md)
26
+ - [Branchproof/DefaultInstrumentation.md](doc/Branchproof/DefaultInstrumentation.md)
27
+ - [Branchproof/DefaultRuntime.md](doc/Branchproof/DefaultRuntime.md)
28
+ - [Branchproof/DefaultSyntax.md](doc/Branchproof/DefaultSyntax.md)
25
29
  - [Branchproof/Error.md](doc/Branchproof/Error.md)
26
30
  - [Branchproof/Evidence.md](doc/Branchproof/Evidence.md)
31
+ - [Branchproof/ExceptionInstrumentation.md](doc/Branchproof/ExceptionInstrumentation.md)
32
+ - [Branchproof/ExceptionRuntime.md](doc/Branchproof/ExceptionRuntime.md)
33
+ - [Branchproof/ExceptionSyntax.md](doc/Branchproof/ExceptionSyntax.md)
34
+ - [Branchproof/ExtendedAlternativeRuntime.md](doc/Branchproof/ExtendedAlternativeRuntime.md)
27
35
  - [Branchproof/FlowInstrumentation.md](doc/Branchproof/FlowInstrumentation.md)
28
36
  - [Branchproof/FocusedReport.md](doc/Branchproof/FocusedReport.md)
29
37
  - [Branchproof/Instrumenter.md](doc/Branchproof/Instrumenter.md)
38
+ - [Branchproof/IterationInstrumentation.md](doc/Branchproof/IterationInstrumentation.md)
39
+ - [Branchproof/IterationRuntime.md](doc/Branchproof/IterationRuntime.md)
40
+ - [Branchproof/IterationSyntax.md](doc/Branchproof/IterationSyntax.md)
30
41
  - [Branchproof/Limits.md](doc/Branchproof/Limits.md)
31
42
  - [Branchproof/Loader.md](doc/Branchproof/Loader.md)
32
43
  - [Branchproof/Minimizer.md](doc/Branchproof/Minimizer.md)
33
44
  - [Branchproof/MinitestAdapter.md](doc/Branchproof/MinitestAdapter.md)
34
45
  - [Branchproof/Project.md](doc/Branchproof/Project.md)
46
+ - [Branchproof/RSpecAdapter/ClassRunnerGuard.md](doc/Branchproof/RSpecAdapter/ClassRunnerGuard.md)
47
+ - [Branchproof/RSpecAdapter/ContextLifecycle.md](doc/Branchproof/RSpecAdapter/ContextLifecycle.md)
48
+ - [Branchproof/RSpecAdapter/DefaultDiscovery.md](doc/Branchproof/RSpecAdapter/DefaultDiscovery.md)
49
+ - [Branchproof/RSpecAdapter/ExampleLifecycle.md](doc/Branchproof/RSpecAdapter/ExampleLifecycle.md)
50
+ - [Branchproof/RSpecAdapter/RunnerGuard.md](doc/Branchproof/RSpecAdapter/RunnerGuard.md)
51
+ - [Branchproof/RSpecAdapter/UnsupportedRunner.md](doc/Branchproof/RSpecAdapter/UnsupportedRunner.md)
52
+ - [Branchproof/RSpecAdapter.md](doc/Branchproof/RSpecAdapter.md)
35
53
  - [Branchproof/RailsSupport/Error.md](doc/Branchproof/RailsSupport/Error.md)
36
54
  - [Branchproof/RailsSupport.md](doc/Branchproof/RailsSupport.md)
37
55
  - [Branchproof/Records.md](doc/Branchproof/Records.md)
@@ -40,6 +58,9 @@ Not documented.
40
58
  - [Branchproof/RuntimeFlow.md](doc/Branchproof/RuntimeFlow.md)
41
59
  - [Branchproof/SavedReport.md](doc/Branchproof/SavedReport.md)
42
60
  - [Branchproof/Source.md](doc/Branchproof/Source.md)
61
+ - [Branchproof/ValueInstrumentation.md](doc/Branchproof/ValueInstrumentation.md)
62
+ - [Branchproof/ValueRuntime.md](doc/Branchproof/ValueRuntime.md)
63
+ - [Branchproof/ValueSyntax.md](doc/Branchproof/ValueSyntax.md)
43
64
  - [Branchproof/Worker.md](doc/Branchproof/Worker.md)
44
65
  - [CHANGELOG.md](doc/CHANGELOG.md)
45
66
  - [README.md](doc/README.md)
data/sig/branchproof.rbs CHANGED
@@ -34,13 +34,15 @@ module Branchproof
34
34
 
35
35
  class Project
36
36
  MODES: Array[String]
37
+ FRAMEWORKS: Array[String]
37
38
  RAILS_ENVIRONMENT: record
38
- def initialize: (root: String, ?mode: String) -> void
39
+ def initialize: (root: String, ?mode: String, ?framework: String) -> void
39
40
  def to_h: () -> record
40
41
  end
41
42
 
42
43
  module RailsSupport
43
44
  def self.boot: (project: record) -> record
45
+ def self.validate_rspec!: (project: record) -> record?
44
46
  end
45
47
 
46
48
  class Instrumenter
@@ -57,6 +59,8 @@ module Branchproof
57
59
  end
58
60
 
59
61
  module Runtime
62
+ def self.register_test: (test: record) -> record?
63
+ def self.test_phase_counts: () -> Hash[String, Hash[String, Integer]]
60
64
  def self.boot: (evidence: Evidence) -> nil
61
65
  def self.enter: (String decision_id) -> nil
62
66
  def self.condition: (String decision_id, Integer index, untyped value) -> untyped
@@ -68,6 +72,23 @@ module Branchproof
68
72
  def self.flow_path: (String decision_id, Integer index) -> nil
69
73
  def self.flow_finish: (String decision_id, untyped value, ?Integer? default_path) -> untyped
70
74
  def self.flow_receiver: (String decision_id, untyped receiver) -> untyped
75
+ def self.flow_assignment_receiver: (String decision_id, untyped receiver) -> untyped
76
+ def self.flow_assignment_path: (String decision_id, Integer index) -> nil
77
+ def self.flow_assignment_finish: (String decision_id, untyped value, ?Integer? default_path) -> untyped
78
+ def self.exception_enter: (String decision_id, Integer unhandled_index) -> nil
79
+ def self.exception_path: (String decision_id, Integer index) -> nil
80
+ def self.exception_value: (String decision_id, untyped value, Integer index) -> untyped
81
+ def self.exception_finish: (String decision_id, untyped value) -> untyped
82
+ def self.exception_unhandled: (String decision_id) -> nil
83
+ def self.exception_leave: (String decision_id) -> nil
84
+ def self.default_binding: (String decision_id, Integer index) -> nil
85
+ def self.set_alternative_count: (String decision_id, Integer count) -> nil
86
+ def self.value_path: (String decision_id, untyped value, Symbol domain) -> untyped
87
+ def self.dispatch_path: (String decision_id, untyped value, bool raised) -> untyped
88
+ def self.flow_iteration_begin: (String decision_id, untyped receiver, ?Integer alternative_count) -> untyped
89
+ def self.flow_iteration_callback: (String decision_id, ?Integer alternative_count) -> nil
90
+ def self.flow_iteration_finish: (String decision_id, untyped value, ?Integer? default_path) -> untyped
91
+ def self.flow_iteration_leave: (String decision_id) -> nil
71
92
  def self.context: (?test_id: String?, ?phase: String?) -> nil
72
93
  def self.snapshot: () -> record
73
94
  def self.diagnostics: () -> Array[record]
@@ -83,6 +104,7 @@ module Branchproof
83
104
  def register_test: (test: record) -> record
84
105
  def record: (execution: record) -> record
85
106
  def diagnose: (diagnostic: record) -> nil
107
+ def test_phase_counts: () -> Hash[String, Hash[String, Integer]]
86
108
  def snapshot: () -> record
87
109
  def merge: (snapshot: record) -> record
88
110
  end
@@ -147,6 +169,27 @@ module Branchproof
147
169
  def run: (test_files: Array[String], runner_args: Array[String], on_complete: ^(record) -> untyped, ?before_load: ^() -> untyped?) -> nil
148
170
  end
149
171
 
172
+ class RSpecAdapter
173
+ class UnsupportedRunner < ArgumentError
174
+ def diagnostic_code: () -> String
175
+ end
176
+
177
+ FORBIDDEN_OPTIONS: Hash[Symbol, String]
178
+ attr_reader tests: Hash[String, record]
179
+ def self.active_adapter: () -> RSpecAdapter?
180
+ def self.active_adapter=: (RSpecAdapter?) -> RSpecAdapter?
181
+ def self.runner_adapter: () -> RSpecAdapter?
182
+ def self.runner_adapter=: (RSpecAdapter?) -> RSpecAdapter?
183
+ attr_reader late_execution_error: ArgumentError?
184
+ def initialize: (runtime: untyped) -> void
185
+ def reject_execution!: (String message) -> bot
186
+ def enter_runner!: (untyped runner) -> bool
187
+ def example_started: (untyped notification) -> untyped
188
+ def example_finished: (untyped notification) -> untyped
189
+ def run: (test_files: Array[String], runner_args: Array[String], on_complete: ^(record) -> untyped, ?before_load: ^() -> untyped?, ?after_load: ^() -> untyped?, ?test_selection_explicit: bool) -> untyped
190
+ def validate_runner!: () -> nil
191
+ end
192
+
150
193
  class Report
151
194
  SCHEMA_VERSION: String
152
195
  CRITERION_VERSION: String
@@ -158,6 +201,7 @@ module Branchproof
158
201
  def decision_table_requirement: (untyped value) -> String?
159
202
  def decision_table_reachability: (document rule) -> String
160
203
  def decision_table_expected_heading: (document decision) -> String
204
+ def rerun_command: (String test_id) -> String?
161
205
  def write: (io: _WriteIO, format: Symbol | String) -> nil
162
206
  def exit_code: () -> Integer
163
207
  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.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucian Ghinda
@@ -50,7 +50,7 @@ dependencies:
50
50
  - !ruby/object:Gem::Version
51
51
  version: '2'
52
52
  description: Inventory Ruby decisions and report masking MC/DC evidence from Minitest
53
- runs.
53
+ or RSpec runs.
54
54
  email:
55
55
  - lucianghinda@users.noreply.github.com
56
56
  executables:
@@ -75,16 +75,33 @@ files:
75
75
  - doc/Branchproof/CoverageIndex.md
76
76
  - doc/Branchproof/DecisionSyntax.md
77
77
  - doc/Branchproof/DecisionTable.md
78
+ - doc/Branchproof/DefaultInstrumentation.md
79
+ - doc/Branchproof/DefaultRuntime.md
80
+ - doc/Branchproof/DefaultSyntax.md
78
81
  - doc/Branchproof/Error.md
79
82
  - doc/Branchproof/Evidence.md
83
+ - doc/Branchproof/ExceptionInstrumentation.md
84
+ - doc/Branchproof/ExceptionRuntime.md
85
+ - doc/Branchproof/ExceptionSyntax.md
86
+ - doc/Branchproof/ExtendedAlternativeRuntime.md
80
87
  - doc/Branchproof/FlowInstrumentation.md
81
88
  - doc/Branchproof/FocusedReport.md
82
89
  - doc/Branchproof/Instrumenter.md
90
+ - doc/Branchproof/IterationInstrumentation.md
91
+ - doc/Branchproof/IterationRuntime.md
92
+ - doc/Branchproof/IterationSyntax.md
83
93
  - doc/Branchproof/Limits.md
84
94
  - doc/Branchproof/Loader.md
85
95
  - doc/Branchproof/Minimizer.md
86
96
  - doc/Branchproof/MinitestAdapter.md
87
97
  - doc/Branchproof/Project.md
98
+ - doc/Branchproof/RSpecAdapter.md
99
+ - doc/Branchproof/RSpecAdapter/ClassRunnerGuard.md
100
+ - doc/Branchproof/RSpecAdapter/ContextLifecycle.md
101
+ - doc/Branchproof/RSpecAdapter/DefaultDiscovery.md
102
+ - doc/Branchproof/RSpecAdapter/ExampleLifecycle.md
103
+ - doc/Branchproof/RSpecAdapter/RunnerGuard.md
104
+ - doc/Branchproof/RSpecAdapter/UnsupportedRunner.md
88
105
  - doc/Branchproof/RailsSupport.md
89
106
  - doc/Branchproof/RailsSupport/Error.md
90
107
  - doc/Branchproof/Records.md
@@ -93,6 +110,9 @@ files:
93
110
  - doc/Branchproof/RuntimeFlow.md
94
111
  - doc/Branchproof/SavedReport.md
95
112
  - doc/Branchproof/Source.md
113
+ - doc/Branchproof/ValueInstrumentation.md
114
+ - doc/Branchproof/ValueRuntime.md
115
+ - doc/Branchproof/ValueSyntax.md
96
116
  - doc/Branchproof/Worker.md
97
117
  - doc/CHANGELOG.md
98
118
  - doc/README.md
@@ -107,10 +127,20 @@ files:
107
127
  - lib/branchproof/coverage_index.rb
108
128
  - lib/branchproof/decision_syntax.rb
109
129
  - lib/branchproof/decision_table.rb
130
+ - lib/branchproof/default_instrumentation.rb
131
+ - lib/branchproof/default_runtime.rb
132
+ - lib/branchproof/default_syntax.rb
110
133
  - lib/branchproof/evidence.rb
134
+ - lib/branchproof/exception_instrumentation.rb
135
+ - lib/branchproof/exception_runtime.rb
136
+ - lib/branchproof/exception_syntax.rb
137
+ - lib/branchproof/extended_alternative_runtime.rb
111
138
  - lib/branchproof/flow_instrumentation.rb
112
139
  - lib/branchproof/focused_report.rb
113
140
  - lib/branchproof/instrumenter.rb
141
+ - lib/branchproof/iteration_instrumentation.rb
142
+ - lib/branchproof/iteration_runtime.rb
143
+ - lib/branchproof/iteration_syntax.rb
114
144
  - lib/branchproof/limits.rb
115
145
  - lib/branchproof/loader.rb
116
146
  - lib/branchproof/minimizer.rb
@@ -119,10 +149,14 @@ files:
119
149
  - lib/branchproof/rails_support.rb
120
150
  - lib/branchproof/records.rb
121
151
  - lib/branchproof/report.rb
152
+ - lib/branchproof/rspec_adapter.rb
122
153
  - lib/branchproof/runtime.rb
123
154
  - lib/branchproof/runtime_flow.rb
124
155
  - lib/branchproof/saved_report.rb
125
156
  - lib/branchproof/source.rb
157
+ - lib/branchproof/value_instrumentation.rb
158
+ - lib/branchproof/value_runtime.rb
159
+ - lib/branchproof/value_syntax.rb
126
160
  - lib/branchproof/version.rb
127
161
  - lib/branchproof/worker.rb
128
162
  - lib/mcdc.rb