branchproof 0.6.0 → 0.7.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 +10 -0
- data/README.md +74 -23
- data/doc/Branchproof/CoverageIndex.md +3 -0
- data/doc/Branchproof/DecisionSyntax.md +20 -0
- data/doc/Branchproof/FlowInstrumentation.md +7 -0
- data/doc/Branchproof/Instrumenter.md +1 -0
- data/doc/Branchproof/Runtime.md +19 -0
- data/doc/Branchproof/RuntimeFlow.md +27 -0
- data/doc/Branchproof/SavedReport.md +6 -0
- data/doc/Branchproof/Source.md +1 -0
- data/doc/Branchproof.md +6 -2
- data/doc/CHANGELOG.md +10 -0
- data/doc/README.md +74 -23
- data/lib/branchproof/analyzer.rb +153 -21
- data/lib/branchproof/comparison.rb +1 -1
- data/lib/branchproof/coverage_index.rb +90 -2
- data/lib/branchproof/decision_syntax.rb +294 -0
- data/lib/branchproof/evidence.rb +54 -9
- data/lib/branchproof/flow_instrumentation.rb +107 -0
- data/lib/branchproof/focused_report.rb +85 -18
- data/lib/branchproof/instrumenter.rb +28 -13
- data/lib/branchproof/report.rb +195 -24
- data/lib/branchproof/runtime.rb +3 -0
- data/lib/branchproof/runtime_flow.rb +58 -0
- data/lib/branchproof/saved_report.rb +245 -6
- data/lib/branchproof/source.rb +191 -35
- data/lib/branchproof/version.rb +1 -1
- data/llms.txt +6 -2
- data/sig/branchproof.rbs +7 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c4c0c87db7babd3785baf2ea439a90ec852253e7ade2837becec235ee9ac7055
|
|
4
|
+
data.tar.gz: 29b5cd02d6ab9419343af3c9c1d49fd07a77c2487eaf2631aafbc90bafdf4296
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bffba10f09dc43d851a79373b1e3ec8b2457179a2f69da8b8076d1f48c002f3a8f6a88e7a30f8c970c5f9960a73abbec4829dfcbee2581da61c5ff3cd1e233f4
|
|
7
|
+
data.tar.gz: c808687e23a8b27ed794d723d4a390f8296d5ade907fb6255660ac025e657530eff850d578d7d600b1177076dcf2376d577b15a4310e620cd618bf4598f626fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
- Discover Boolean loop predicates, subjectless case candidates, standalone
|
|
6
|
+
short-circuit expressions, and pattern predicates through Prism.
|
|
7
|
+
- Analyze unary NOT and keyword `and`/`or` with Ruby's parsed precedence.
|
|
8
|
+
- Attribute selected paths for ordinary case, unguarded case/in, safe navigation,
|
|
9
|
+
and conditional assignments without adding them to MC/DC denominators.
|
|
10
|
+
- Expose decision kinds, contexts, alternative evidence, and explicit unsupported
|
|
11
|
+
constructs in schema 1.2 reports while retaining older saved-report support.
|
|
12
|
+
|
|
3
13
|
## [0.6.0] - 2026-09-10
|
|
4
14
|
|
|
5
15
|
- Report Decision, Condition, and Condition/Decision Coverage alongside
|
data/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Branchproof
|
|
2
2
|
|
|
3
|
-
Branchproof measures modified condition/decision
|
|
4
|
-
serial Minitest run. It
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Branchproof measures decision, condition, and modified condition/decision
|
|
4
|
+
coverage (MC/DC) from one serial Minitest run. It discovers Ruby decisions
|
|
5
|
+
through Prism, records their runtime paths, and attributes evidence to tests.
|
|
6
|
+
Boolean decisions receive the coverage ladder; `case`, pattern alternatives,
|
|
7
|
+
safe navigation, and conditional assignments receive alternative coverage.
|
|
8
8
|
|
|
9
9
|
The gem and primary command are named `branchproof`. The `mcdc` command and
|
|
10
10
|
`MCDC` namespace remain compatibility aliases with the same behavior.
|
|
@@ -321,26 +321,77 @@ short-circuited or masked rather than fixed to the same observed values. For exa
|
|
|
321
321
|
short-circuits it once, but does not prove `right`; `[TF]` is also required.
|
|
322
322
|
The condition and test views preserve that distinction.
|
|
323
323
|
|
|
324
|
-
### Supported
|
|
324
|
+
### Supported decision forms
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
Every decision has a stable `kind` and `context` in JSON. The Boolean ladder
|
|
327
|
+
applies to the following forms:
|
|
328
328
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
329
|
+
| Construct | Kind | Context |
|
|
330
|
+
| --- | --- | --- |
|
|
331
|
+
| `if`, modifier `if`, `elsif`, `unless`, ternary | `boolean` | `if`, `elsif`, `unless`, `ternary` |
|
|
332
|
+
| `while`, `until`, including modifier and post-test loops | `boolean` | `while`, `until` |
|
|
333
|
+
| Each subjectless `case` candidate | `boolean` | `case_when` |
|
|
334
|
+
| Standalone `value in pattern` | `boolean` | `pattern_in` |
|
|
335
|
+
| Evaluated pattern guard predicate | `boolean` | `pattern_guard` |
|
|
336
|
+
| Standalone `&&`, `||`, `and`, `or` | `boolean` | `short_circuit` |
|
|
337
|
+
|
|
338
|
+
Prism determines precedence. `!` and `not` appear as NOT nodes in the Boolean
|
|
339
|
+
tree; their operands remain the conditions. Short-circuited operands remain
|
|
340
|
+
not evaluated. A Boolean subtree already decomposed in a decision is not
|
|
341
|
+
inventoried again as a standalone decision.
|
|
342
|
+
|
|
343
|
+
Loop outcomes describe the predicate as written: an `until` predicate that
|
|
344
|
+
returns true ends the loop. Every predicate evaluation receives an execution
|
|
345
|
+
ID. Repeated equivalent executions aggregate into a vector's `count`, retaining
|
|
346
|
+
the supporting tests. Ternary outcomes likewise describe the predicate, not
|
|
347
|
+
the value returned by the chosen branch.
|
|
348
|
+
|
|
349
|
+
Other constructs use alternative coverage, separate from MC/DC:
|
|
350
|
+
|
|
351
|
+
| Construct | Kind | Context | Required alternatives |
|
|
352
|
+
| --- | --- | --- | --- |
|
|
353
|
+
| `case subject` | `multiway` | `case` | Each `when` candidate and `else` (or implicit no-match path) |
|
|
354
|
+
| `case/in` | `pattern` | `case_in` | Each pattern clause and explicit `else`, if present |
|
|
355
|
+
| `receiver&.method` | `implicit` | `safe_navigation` | Receiver nil / non-nil |
|
|
356
|
+
| `lhs ||= rhs` | `implicit` | `or_assignment` | RHS skipped / executed |
|
|
357
|
+
| `lhs &&= rhs` | `implicit` | `and_assignment` | RHS skipped / executed |
|
|
358
|
+
|
|
359
|
+
Each safe-navigation operation in a chain is a distinct decision. Assignment
|
|
360
|
+
instrumentation preserves Ruby's native local, instance, class, global,
|
|
361
|
+
constant, method, and indexed assignment operations, including receiver and
|
|
362
|
+
index evaluation order. Safe navigation distinguishes nil from false.
|
|
363
|
+
|
|
364
|
+
For multiway decisions, vector values mean selected (`true`), evaluated but
|
|
365
|
+
not selected (`false`), and skipped (`null`). Later alternatives remain skipped
|
|
366
|
+
when an earlier candidate matches. Implicit vectors record the selected path
|
|
367
|
+
and its unselected complement. Their `outcome` is a selection marker, not the
|
|
368
|
+
truthiness of the application's return value. Reports label these as paths,
|
|
369
|
+
not Boolean outcomes. Each alternative exposes selected, not-selected, and
|
|
370
|
+
skipped evidence with test and vector IDs. The alternative denominator is the
|
|
371
|
+
number of supported selectable alternatives; these decisions do not enter
|
|
372
|
+
Boolean-ladder or MC/DC denominators.
|
|
373
|
+
|
|
374
|
+
A `case/in` without `else` retains Ruby's native no-match exception. An execution
|
|
375
|
+
that fails before choosing a branch is aborted, not counted as a selected
|
|
376
|
+
alternative. Selected branches and assignment paths remain observed even when
|
|
377
|
+
their bodies or right-hand sides subsequently raise or return.
|
|
378
|
+
|
|
379
|
+
Unsupported syntax stays visible and outside coverage denominators. Current
|
|
380
|
+
exclusions include guarded `case/in` (`unsupported_pattern_guard`), dynamic
|
|
381
|
+
`when` splats (`unsupported_case_splat`), safe-navigation compound assignment
|
|
382
|
+
(`unsupported_assignment_target`), and rescue alternatives
|
|
383
|
+
(`unsupported_rescue_control_flow`). A guard predicate can still supply Boolean
|
|
384
|
+
evidence when Ruby evaluates it; an unsupported guarded case does not claim
|
|
385
|
+
pattern-match coverage from that evidence. Flip-flops remain
|
|
386
|
+
`unsupported_flip_flop`. Decisions inside `defined?`, contextual regular
|
|
387
|
+
expressions, heredocs, unsafe predicates, and limit overflows retain explicit
|
|
388
|
+
exclusions. Ruby-defined custom `!` methods keep their runtime behavior;
|
|
389
|
+
evidence that contradicts Boolean negation is rejected instead of proving
|
|
390
|
+
coverage with an invalid logical model.
|
|
391
|
+
|
|
392
|
+
New reports use schema `1.2`; saved schema `1.0` and `1.1` reports remain
|
|
393
|
+
readable. Expanded discovery changes coverage denominators, so compare reports
|
|
394
|
+
with their supported syntax scope in mind.
|
|
344
395
|
|
|
345
396
|
### Limits
|
|
346
397
|
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
Derives condition- and test-oriented rows from one report document.
|
|
9
9
|
|
|
10
10
|
## Attributes
|
|
11
|
+
### `alternatives` [R] <a id="attribute-i-alternatives"></a> <a id="alternatives-instance_method"></a>
|
|
12
|
+
Returns the value of attribute alternatives.
|
|
13
|
+
|
|
11
14
|
### `conditions` [R] <a id="attribute-i-conditions"></a> <a id="conditions-instance_method"></a>
|
|
12
15
|
Returns the value of attribute conditions.
|
|
13
16
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Module Branchproof::DecisionSyntax <a id="module-Branchproof-DecisionSyntax"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/branchproof/decision_syntax.rb |
|
|
6
|
+
|
|
7
|
+
Discovers control-flow expressions whose truth is not represented by an
|
|
8
|
+
ordinary Prism IfNode. The records intentionally contain byte ranges and
|
|
9
|
+
scalar metadata only; Prism nodes must not escape the source pass.
|
|
10
|
+
|
|
11
|
+
## Constants
|
|
12
|
+
### `AND_WRITE_NODE_NAMES` <a id="constant-AND_WRITE_NODE_NAMES"></a> <a id="AND_WRITE_NODE_NAMES-constant"></a>
|
|
13
|
+
Not documented.
|
|
14
|
+
|
|
15
|
+
### `OR_WRITE_NODE_NAMES` <a id="constant-OR_WRITE_NODE_NAMES"></a> <a id="OR_WRITE_NODE_NAMES-constant"></a>
|
|
16
|
+
Not documented.
|
|
17
|
+
|
|
18
|
+
## Public Instance Methods
|
|
19
|
+
### `flow_decisions_for(program, bytes, source_id, file_reasons = [], encoding = "UTF-8")` <a id="method-i-flow_decisions_for"></a> <a id="flow_decisions_for-instance_method"></a>
|
|
20
|
+
Not documented.
|
data/doc/Branchproof/Runtime.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| | |
|
|
4
4
|
| --- | --- |
|
|
5
|
+
| **Extended by** | [Branchproof::RuntimeFlow](RuntimeFlow.md) |
|
|
5
6
|
| **Defined in** | lib/branchproof/runtime.rb |
|
|
6
7
|
|
|
7
8
|
Process-local execution recorder. It deliberately never coerces or stores
|
|
@@ -27,6 +28,24 @@ Not documented.
|
|
|
27
28
|
### `finish(decision_id, value)` <a id="method-c-finish"></a> <a id="finish-class_method"></a>
|
|
28
29
|
Not documented.
|
|
29
30
|
|
|
31
|
+
### `flow_candidate(decision_id, index)` <a id="method-c-flow_candidate"></a> <a id="flow_candidate-class_method"></a>
|
|
32
|
+
Not documented.
|
|
33
|
+
|
|
34
|
+
### `flow_finish(decision_id, value, default_path = nil)` <a id="method-c-flow_finish"></a> <a id="flow_finish-class_method"></a>
|
|
35
|
+
Not documented.
|
|
36
|
+
|
|
37
|
+
### `flow_path(decision_id, index)` <a id="method-c-flow_path"></a> <a id="flow_path-class_method"></a>
|
|
38
|
+
Not documented.
|
|
39
|
+
|
|
40
|
+
### `flow_receiver(decision_id, receiver)` <a id="method-c-flow_receiver"></a> <a id="flow_receiver-class_method"></a>
|
|
41
|
+
Not documented.
|
|
42
|
+
|
|
43
|
+
### `flow_select(decision_id, index)` <a id="method-c-flow_select"></a> <a id="flow_select-class_method"></a>
|
|
44
|
+
Not documented.
|
|
45
|
+
|
|
46
|
+
### `flow_selected(decision_id)` <a id="method-c-flow_selected"></a> <a id="flow_selected-class_method"></a>
|
|
47
|
+
Not documented.
|
|
48
|
+
|
|
30
49
|
### `leave(decision_id)` <a id="method-c-leave"></a> <a id="leave-class_method"></a>
|
|
31
50
|
Not documented.
|
|
32
51
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Module Branchproof::RuntimeFlow <a id="module-Branchproof-RuntimeFlow"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/branchproof/runtime_flow.rb |
|
|
6
|
+
|
|
7
|
+
Records native Ruby path selection without evaluating application values
|
|
8
|
+
twice.
|
|
9
|
+
|
|
10
|
+
## Public Instance Methods
|
|
11
|
+
### `flow_candidate(decision_id, index)` <a id="method-i-flow_candidate"></a> <a id="flow_candidate-instance_method"></a>
|
|
12
|
+
Not documented.
|
|
13
|
+
|
|
14
|
+
### `flow_finish(decision_id, value, default_path = nil)` <a id="method-i-flow_finish"></a> <a id="flow_finish-instance_method"></a>
|
|
15
|
+
Not documented.
|
|
16
|
+
|
|
17
|
+
### `flow_path(decision_id, index)` <a id="method-i-flow_path"></a> <a id="flow_path-instance_method"></a>
|
|
18
|
+
Not documented.
|
|
19
|
+
|
|
20
|
+
### `flow_receiver(decision_id, receiver)` <a id="method-i-flow_receiver"></a> <a id="flow_receiver-instance_method"></a>
|
|
21
|
+
Not documented.
|
|
22
|
+
|
|
23
|
+
### `flow_select(decision_id, index)` <a id="method-i-flow_select"></a> <a id="flow_select-instance_method"></a>
|
|
24
|
+
Not documented.
|
|
25
|
+
|
|
26
|
+
### `flow_selected(decision_id)` <a id="method-i-flow_selected"></a> <a id="flow_selected-instance_method"></a>
|
|
27
|
+
Not documented.
|
|
@@ -14,6 +14,12 @@ Not documented.
|
|
|
14
14
|
### `CRITERION_VERSION` <a id="constant-CRITERION_VERSION"></a> <a id="CRITERION_VERSION-constant"></a>
|
|
15
15
|
Not documented.
|
|
16
16
|
|
|
17
|
+
### `DECISION_KINDS` <a id="constant-DECISION_KINDS"></a> <a id="DECISION_KINDS-constant"></a>
|
|
18
|
+
Not documented.
|
|
19
|
+
|
|
20
|
+
### `NONBOOLEAN_KINDS` <a id="constant-NONBOOLEAN_KINDS"></a> <a id="NONBOOLEAN_KINDS-constant"></a>
|
|
21
|
+
Not documented.
|
|
22
|
+
|
|
17
23
|
### `REQUIRED_FIELDS` <a id="constant-REQUIRED_FIELDS"></a> <a id="REQUIRED_FIELDS-constant"></a>
|
|
18
24
|
Not documented.
|
|
19
25
|
|
data/doc/Branchproof/Source.md
CHANGED
data/doc/Branchproof.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
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/instrumenter.rb, lib/branchproof/saved_report.rb, lib/branchproof/rails_support.rb, lib/branchproof/coverage_index.rb, lib/branchproof/focused_report.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb |
|
|
5
|
+
| **Defined in** | lib/branchproof.rb, lib/branchproof/cli.rb, lib/branchproof/limits.rb, lib/branchproof/loader.rb, lib/branchproof/report.rb, lib/branchproof/source.rb, lib/branchproof/worker.rb, lib/branchproof/project.rb, lib/branchproof/records.rb, lib/branchproof/runtime.rb, lib/branchproof/version.rb, lib/branchproof/analyzer.rb, lib/branchproof/evidence.rb, lib/branchproof/minimizer.rb, lib/branchproof/comparison.rb, lib/branchproof/instrumenter.rb, lib/branchproof/runtime_flow.rb, lib/branchproof/saved_report.rb, lib/branchproof/rails_support.rb, lib/branchproof/coverage_index.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 |
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Keep each bounded source rewrite together so its evaluation order can be
|
|
8
|
+
audited. rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
8
9
|
|
|
9
10
|
## Constants
|
|
10
11
|
### `VERSION` <a id="constant-VERSION"></a> <a id="VERSION-constant"></a>
|
|
@@ -17,8 +18,10 @@ Not documented.
|
|
|
17
18
|
- [Branchproof/Comparison.md](Branchproof/Comparison.md)
|
|
18
19
|
- [Branchproof/ComparisonReport.md](Branchproof/ComparisonReport.md)
|
|
19
20
|
- [Branchproof/CoverageIndex.md](Branchproof/CoverageIndex.md)
|
|
21
|
+
- [Branchproof/DecisionSyntax.md](Branchproof/DecisionSyntax.md)
|
|
20
22
|
- [Branchproof/Error.md](Branchproof/Error.md)
|
|
21
23
|
- [Branchproof/Evidence.md](Branchproof/Evidence.md)
|
|
24
|
+
- [Branchproof/FlowInstrumentation.md](Branchproof/FlowInstrumentation.md)
|
|
22
25
|
- [Branchproof/FocusedReport.md](Branchproof/FocusedReport.md)
|
|
23
26
|
- [Branchproof/Instrumenter.md](Branchproof/Instrumenter.md)
|
|
24
27
|
- [Branchproof/Limits.md](Branchproof/Limits.md)
|
|
@@ -31,6 +34,7 @@ Not documented.
|
|
|
31
34
|
- [Branchproof/Records.md](Branchproof/Records.md)
|
|
32
35
|
- [Branchproof/Report.md](Branchproof/Report.md)
|
|
33
36
|
- [Branchproof/Runtime.md](Branchproof/Runtime.md)
|
|
37
|
+
- [Branchproof/RuntimeFlow.md](Branchproof/RuntimeFlow.md)
|
|
34
38
|
- [Branchproof/SavedReport.md](Branchproof/SavedReport.md)
|
|
35
39
|
- [Branchproof/Source.md](Branchproof/Source.md)
|
|
36
40
|
- [Branchproof/Worker.md](Branchproof/Worker.md)
|
data/doc/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-09-10
|
|
4
|
+
|
|
5
|
+
- Discover Boolean loop predicates, subjectless case candidates, standalone
|
|
6
|
+
short-circuit expressions, and pattern predicates through Prism.
|
|
7
|
+
- Analyze unary NOT and keyword `and`/`or` with Ruby's parsed precedence.
|
|
8
|
+
- Attribute selected paths for ordinary case, unguarded case/in, safe navigation,
|
|
9
|
+
and conditional assignments without adding them to MC/DC denominators.
|
|
10
|
+
- Expose decision kinds, contexts, alternative evidence, and explicit unsupported
|
|
11
|
+
constructs in schema 1.2 reports while retaining older saved-report support.
|
|
12
|
+
|
|
3
13
|
## [0.6.0] - 2026-09-10
|
|
4
14
|
|
|
5
15
|
- Report Decision, Condition, and Condition/Decision Coverage alongside
|
data/doc/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Branchproof
|
|
2
2
|
|
|
3
|
-
Branchproof measures modified condition/decision
|
|
4
|
-
serial Minitest run. It
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Branchproof measures decision, condition, and modified condition/decision
|
|
4
|
+
coverage (MC/DC) from one serial Minitest run. It discovers Ruby decisions
|
|
5
|
+
through Prism, records their runtime paths, and attributes evidence to tests.
|
|
6
|
+
Boolean decisions receive the coverage ladder; `case`, pattern alternatives,
|
|
7
|
+
safe navigation, and conditional assignments receive alternative coverage.
|
|
8
8
|
|
|
9
9
|
The gem and primary command are named `branchproof`. The `mcdc` command and
|
|
10
10
|
`MCDC` namespace remain compatibility aliases with the same behavior.
|
|
@@ -321,26 +321,77 @@ short-circuited or masked rather than fixed to the same observed values. For exa
|
|
|
321
321
|
short-circuits it once, but does not prove `right`; `[TF]` is also required.
|
|
322
322
|
The condition and test views preserve that distinction.
|
|
323
323
|
|
|
324
|
-
### Supported
|
|
324
|
+
### Supported decision forms
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
Every decision has a stable `kind` and `context` in JSON. The Boolean ladder
|
|
327
|
+
applies to the following forms:
|
|
328
328
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
329
|
+
| Construct | Kind | Context |
|
|
330
|
+
| --- | --- | --- |
|
|
331
|
+
| `if`, modifier `if`, `elsif`, `unless`, ternary | `boolean` | `if`, `elsif`, `unless`, `ternary` |
|
|
332
|
+
| `while`, `until`, including modifier and post-test loops | `boolean` | `while`, `until` |
|
|
333
|
+
| Each subjectless `case` candidate | `boolean` | `case_when` |
|
|
334
|
+
| Standalone `value in pattern` | `boolean` | `pattern_in` |
|
|
335
|
+
| Evaluated pattern guard predicate | `boolean` | `pattern_guard` |
|
|
336
|
+
| Standalone `&&`, `||`, `and`, `or` | `boolean` | `short_circuit` |
|
|
337
|
+
|
|
338
|
+
Prism determines precedence. `!` and `not` appear as NOT nodes in the Boolean
|
|
339
|
+
tree; their operands remain the conditions. Short-circuited operands remain
|
|
340
|
+
not evaluated. A Boolean subtree already decomposed in a decision is not
|
|
341
|
+
inventoried again as a standalone decision.
|
|
342
|
+
|
|
343
|
+
Loop outcomes describe the predicate as written: an `until` predicate that
|
|
344
|
+
returns true ends the loop. Every predicate evaluation receives an execution
|
|
345
|
+
ID. Repeated equivalent executions aggregate into a vector's `count`, retaining
|
|
346
|
+
the supporting tests. Ternary outcomes likewise describe the predicate, not
|
|
347
|
+
the value returned by the chosen branch.
|
|
348
|
+
|
|
349
|
+
Other constructs use alternative coverage, separate from MC/DC:
|
|
350
|
+
|
|
351
|
+
| Construct | Kind | Context | Required alternatives |
|
|
352
|
+
| --- | --- | --- | --- |
|
|
353
|
+
| `case subject` | `multiway` | `case` | Each `when` candidate and `else` (or implicit no-match path) |
|
|
354
|
+
| `case/in` | `pattern` | `case_in` | Each pattern clause and explicit `else`, if present |
|
|
355
|
+
| `receiver&.method` | `implicit` | `safe_navigation` | Receiver nil / non-nil |
|
|
356
|
+
| `lhs ||= rhs` | `implicit` | `or_assignment` | RHS skipped / executed |
|
|
357
|
+
| `lhs &&= rhs` | `implicit` | `and_assignment` | RHS skipped / executed |
|
|
358
|
+
|
|
359
|
+
Each safe-navigation operation in a chain is a distinct decision. Assignment
|
|
360
|
+
instrumentation preserves Ruby's native local, instance, class, global,
|
|
361
|
+
constant, method, and indexed assignment operations, including receiver and
|
|
362
|
+
index evaluation order. Safe navigation distinguishes nil from false.
|
|
363
|
+
|
|
364
|
+
For multiway decisions, vector values mean selected (`true`), evaluated but
|
|
365
|
+
not selected (`false`), and skipped (`null`). Later alternatives remain skipped
|
|
366
|
+
when an earlier candidate matches. Implicit vectors record the selected path
|
|
367
|
+
and its unselected complement. Their `outcome` is a selection marker, not the
|
|
368
|
+
truthiness of the application's return value. Reports label these as paths,
|
|
369
|
+
not Boolean outcomes. Each alternative exposes selected, not-selected, and
|
|
370
|
+
skipped evidence with test and vector IDs. The alternative denominator is the
|
|
371
|
+
number of supported selectable alternatives; these decisions do not enter
|
|
372
|
+
Boolean-ladder or MC/DC denominators.
|
|
373
|
+
|
|
374
|
+
A `case/in` without `else` retains Ruby's native no-match exception. An execution
|
|
375
|
+
that fails before choosing a branch is aborted, not counted as a selected
|
|
376
|
+
alternative. Selected branches and assignment paths remain observed even when
|
|
377
|
+
their bodies or right-hand sides subsequently raise or return.
|
|
378
|
+
|
|
379
|
+
Unsupported syntax stays visible and outside coverage denominators. Current
|
|
380
|
+
exclusions include guarded `case/in` (`unsupported_pattern_guard`), dynamic
|
|
381
|
+
`when` splats (`unsupported_case_splat`), safe-navigation compound assignment
|
|
382
|
+
(`unsupported_assignment_target`), and rescue alternatives
|
|
383
|
+
(`unsupported_rescue_control_flow`). A guard predicate can still supply Boolean
|
|
384
|
+
evidence when Ruby evaluates it; an unsupported guarded case does not claim
|
|
385
|
+
pattern-match coverage from that evidence. Flip-flops remain
|
|
386
|
+
`unsupported_flip_flop`. Decisions inside `defined?`, contextual regular
|
|
387
|
+
expressions, heredocs, unsafe predicates, and limit overflows retain explicit
|
|
388
|
+
exclusions. Ruby-defined custom `!` methods keep their runtime behavior;
|
|
389
|
+
evidence that contradicts Boolean negation is rejected instead of proving
|
|
390
|
+
coverage with an invalid logical model.
|
|
391
|
+
|
|
392
|
+
New reports use schema `1.2`; saved schema `1.0` and `1.1` reports remain
|
|
393
|
+
readable. Expanded discovery changes coverage denominators, so compare reports
|
|
394
|
+
with their supported syntax scope in mind.
|
|
344
395
|
|
|
345
396
|
### Limits
|
|
346
397
|
|