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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -0
- data/README.md +120 -28
- data/doc/Branchproof/DefaultInstrumentation.md +13 -0
- data/doc/Branchproof/DefaultRuntime.md +11 -0
- data/doc/Branchproof/DefaultSyntax.md +7 -0
- data/doc/Branchproof/Evidence.md +3 -0
- data/doc/Branchproof/ExceptionInstrumentation.md +9 -0
- data/doc/Branchproof/ExceptionRuntime.md +27 -0
- data/doc/Branchproof/ExceptionSyntax.md +9 -0
- data/doc/Branchproof/ExtendedAlternativeRuntime.md +19 -0
- data/doc/Branchproof/Instrumenter.md +1 -1
- data/doc/Branchproof/IterationInstrumentation.md +7 -0
- data/doc/Branchproof/IterationRuntime.md +21 -0
- data/doc/Branchproof/IterationSyntax.md +11 -0
- data/doc/Branchproof/Project.md +4 -1
- data/doc/Branchproof/RSpecAdapter/ClassRunnerGuard.md +11 -0
- data/doc/Branchproof/RSpecAdapter/ContextLifecycle.md +14 -0
- data/doc/Branchproof/RSpecAdapter/DefaultDiscovery.md +11 -0
- data/doc/Branchproof/RSpecAdapter/ExampleLifecycle.md +11 -0
- data/doc/Branchproof/RSpecAdapter/RunnerGuard.md +11 -0
- data/doc/Branchproof/RSpecAdapter/UnsupportedRunner.md +12 -0
- data/doc/Branchproof/RSpecAdapter.md +47 -0
- data/doc/Branchproof/RailsSupport.md +35 -2
- data/doc/Branchproof/Report.md +3 -0
- data/doc/Branchproof/Runtime.md +59 -1
- data/doc/Branchproof/Source.md +7 -1
- data/doc/Branchproof/ValueInstrumentation.md +7 -0
- data/doc/Branchproof/ValueRuntime.md +26 -0
- data/doc/Branchproof/ValueSyntax.md +26 -0
- data/doc/Branchproof/Worker.md +8 -1
- data/doc/Branchproof.md +24 -3
- data/doc/CHANGELOG.md +37 -0
- data/doc/README.md +120 -28
- data/lib/branchproof/cli.rb +74 -28
- data/lib/branchproof/comparison.rb +52 -6
- data/lib/branchproof/decision_syntax.rb +19 -9
- data/lib/branchproof/default_instrumentation.rb +140 -0
- data/lib/branchproof/default_runtime.rb +16 -0
- data/lib/branchproof/default_syntax.rb +94 -0
- data/lib/branchproof/evidence.rb +87 -1
- data/lib/branchproof/exception_instrumentation.rb +96 -0
- data/lib/branchproof/exception_runtime.rb +41 -0
- data/lib/branchproof/exception_syntax.rb +157 -0
- data/lib/branchproof/extended_alternative_runtime.rb +23 -0
- data/lib/branchproof/flow_instrumentation.rb +36 -7
- data/lib/branchproof/focused_report.rb +5 -0
- data/lib/branchproof/instrumenter.rb +50 -5
- data/lib/branchproof/iteration_instrumentation.rb +66 -0
- data/lib/branchproof/iteration_runtime.rb +79 -0
- data/lib/branchproof/iteration_syntax.rb +75 -0
- data/lib/branchproof/loader.rb +6 -1
- data/lib/branchproof/project.rb +49 -2
- data/lib/branchproof/rails_support.rb +101 -0
- data/lib/branchproof/report.rb +21 -1
- data/lib/branchproof/rspec_adapter.rb +423 -0
- data/lib/branchproof/runtime.rb +14 -0
- data/lib/branchproof/saved_report.rb +9 -2
- data/lib/branchproof/source.rb +59 -32
- data/lib/branchproof/value_instrumentation.rb +35 -0
- data/lib/branchproof/value_runtime.rb +73 -0
- data/lib/branchproof/value_syntax.rb +116 -0
- data/lib/branchproof/version.rb +1 -1
- data/lib/branchproof/worker.rb +75 -15
- data/lib/branchproof.rb +1 -0
- data/llms.txt +24 -3
- data/sig/branchproof.rbs +45 -1
- 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
|
data/lib/branchproof/version.rb
CHANGED
data/lib/branchproof/worker.rb
CHANGED
|
@@ -8,19 +8,18 @@ require "stringio"
|
|
|
8
8
|
require "fileutils"
|
|
9
9
|
|
|
10
10
|
module Branchproof
|
|
11
|
-
# Runs one isolated
|
|
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 =
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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.
|
|
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
|
|
8
|
-
|
|
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.
|
|
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
|