branchproof 0.6.0 → 0.8.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 +45 -0
- data/README.md +279 -27
- data/doc/Branchproof/Analyzer.md +2 -2
- data/doc/Branchproof/CLI.md +4 -0
- data/doc/Branchproof/ComparisonReport.md +4 -0
- data/doc/Branchproof/Constraints/Solver.md +21 -0
- data/doc/Branchproof/Constraints.md +98 -0
- data/doc/Branchproof/CoverageIndex.md +6 -0
- data/doc/Branchproof/DecisionSyntax.md +22 -0
- data/doc/Branchproof/DecisionTable.md +153 -0
- data/doc/Branchproof/FlowInstrumentation.md +7 -0
- data/doc/Branchproof/FocusedReport.md +5 -2
- data/doc/Branchproof/Instrumenter.md +1 -0
- data/doc/Branchproof/Loader.md +0 -4
- data/doc/Branchproof/MinitestAdapter.md +0 -3
- data/doc/Branchproof/Report.md +18 -0
- data/doc/Branchproof/Runtime.md +23 -0
- data/doc/Branchproof/RuntimeFlow.md +27 -0
- data/doc/Branchproof/SavedReport.md +21 -0
- data/doc/Branchproof/Source.md +1 -0
- data/doc/Branchproof.md +9 -2
- data/doc/CHANGELOG.md +45 -0
- data/doc/README.md +279 -27
- data/lib/branchproof/analyzer.rb +222 -54
- data/lib/branchproof/cli.rb +28 -19
- data/lib/branchproof/comparison.rb +207 -19
- data/lib/branchproof/comparison_report.rb +49 -1
- data/lib/branchproof/constraints.rb +363 -0
- data/lib/branchproof/coverage_index.rb +120 -3
- data/lib/branchproof/decision_syntax.rb +310 -0
- data/lib/branchproof/decision_table.rb +377 -0
- data/lib/branchproof/evidence.rb +101 -36
- data/lib/branchproof/flow_instrumentation.rb +107 -0
- data/lib/branchproof/focused_report.rb +161 -26
- data/lib/branchproof/instrumenter.rb +65 -39
- data/lib/branchproof/limits.rb +4 -1
- data/lib/branchproof/loader.rb +18 -6
- data/lib/branchproof/minimizer.rb +18 -13
- data/lib/branchproof/minitest_adapter.rb +11 -16
- data/lib/branchproof/records.rb +2 -0
- data/lib/branchproof/report.rb +470 -66
- data/lib/branchproof/runtime.rb +25 -30
- data/lib/branchproof/runtime_flow.rb +58 -0
- data/lib/branchproof/saved_report.rb +438 -13
- data/lib/branchproof/source.rb +239 -41
- data/lib/branchproof/version.rb +1 -1
- data/lib/branchproof/worker.rb +1 -4
- data/lib/branchproof.rb +2 -0
- data/llms.txt +9 -2
- data/sig/branchproof.rbs +54 -1
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c76fa40f75fbd8122fc0fbb45a1c9d460fa3478f783951674e0e4c2707d44175
|
|
4
|
+
data.tar.gz: b4b0b88c1a0d5e61797ab52174f1841c57f1dde359d405dc1205bcff27810968
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c32db269cece96c843093cc72accffd3921e56eb2a6b974b61c2d87cd696444ffd0d7fff25ed50ac3c5cadf2e6c316ccfd99aaf4f6c28344112c30451cf8e22a
|
|
7
|
+
data.tar.gz: 54a46ff6c7a753e2015bdb814d13fc576bc2d9bd88fa18d0bb4bee8f1f939fbcea62b9f006d31a3c5e99ac0d9c95de8a8bbea8d43db05c1a767c3ff828430f8b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.8.0] - 2026-09-17
|
|
4
|
+
|
|
5
|
+
- Derive a reduced decision table for every supported Boolean decision from its
|
|
6
|
+
`AND`/`OR`/`NOT`/atom structure, preserving Ruby short-circuit semantics with
|
|
7
|
+
an explicit `dont_care` value instead of exhaustive Cartesian expansion.
|
|
8
|
+
- Give every rule a stable identity derived from the decision, its normalized
|
|
9
|
+
condition vector, the expected outcome, and the table schema version, so
|
|
10
|
+
saved reports compare across runs, test order, and Minitest seeds.
|
|
11
|
+
- Overlay the existing run's observations onto the rules, attribute covered
|
|
12
|
+
rules to their Minitest tests, and describe uncovered rules as condition-value
|
|
13
|
+
requirements. No additional test execution is performed.
|
|
14
|
+
- Report Decision Table Coverage as its own criterion in the coverage ladder,
|
|
15
|
+
per decision and in aggregate, independently from MC/DC in both directions.
|
|
16
|
+
- Add conservative reachability: `observed`, `unknown`, and
|
|
17
|
+
`statically_impossible` with stable reason codes. Constraint analysis version 2
|
|
18
|
+
requires safe source constraints before excluding rules, keeping arbitrary
|
|
19
|
+
comparison receivers, mutation, and unordered numeric cases unknown.
|
|
20
|
+
- Exclude statically impossible rules from coverage denominators while keeping
|
|
21
|
+
them visible, and let runtime evidence withdraw an impossibility claim with a
|
|
22
|
+
`constraint_model_conflict` diagnostic.
|
|
23
|
+
- Add `--view decision-tables`, which honours `--missing-only` and never lists
|
|
24
|
+
an impossible rule as a missing obligation.
|
|
25
|
+
- Bound table derivation with the new `max_conditions_for_decision_table` and
|
|
26
|
+
`decision_table_rules_per_decision` limits.
|
|
27
|
+
- Persist the table, rule identities, coverage, attribution, and reachability in
|
|
28
|
+
schema `1.3` reports, and distinguish rule-coverage changes from reachability
|
|
29
|
+
changes in offline comparison.
|
|
30
|
+
- Enforce exact rule-limit boundaries and index runtime rule matching rather
|
|
31
|
+
than scanning all observations per rule.
|
|
32
|
+
- Add `--no-reachability` to keep all generated rules as coverage obligations.
|
|
33
|
+
- Align the JSON regression flag with decision-table CLI failures, report
|
|
34
|
+
analysis-version changes, and validate saved rule identities and evidence.
|
|
35
|
+
- Keep uncalculated decision locations and reasons in missing-only reports;
|
|
36
|
+
label `unless` and `until` outcomes as predicate values.
|
|
37
|
+
|
|
38
|
+
## [0.7.0] - 2026-09-10
|
|
39
|
+
|
|
40
|
+
- Discover Boolean loop predicates, subjectless case candidates, standalone
|
|
41
|
+
short-circuit expressions, and pattern predicates through Prism.
|
|
42
|
+
- Analyze unary NOT and keyword `and`/`or` with Ruby's parsed precedence.
|
|
43
|
+
- Attribute selected paths for ordinary case, unguarded case/in, safe navigation,
|
|
44
|
+
and conditional assignments without adding them to MC/DC denominators.
|
|
45
|
+
- Expose decision kinds, contexts, alternative evidence, and explicit unsupported
|
|
46
|
+
constructs in schema 1.2 reports while retaining older saved-report support.
|
|
47
|
+
|
|
3
48
|
## [0.6.0] - 2026-09-10
|
|
4
49
|
|
|
5
50
|
- Report Decision, Condition, and Condition/Decision Coverage alongside
|
data/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Branchproof
|
|
2
2
|
|
|
3
|
-
Branchproof measures modified condition/decision
|
|
4
|
-
serial Minitest run. It
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Branchproof measures decision, condition, modified condition/decision (MC/DC),
|
|
4
|
+
and decision-table coverage from one serial Minitest run. It discovers Ruby
|
|
5
|
+
decisions through Prism, records their runtime paths, and attributes evidence
|
|
6
|
+
to tests. Boolean decisions receive the coverage ladder; `case`, pattern
|
|
7
|
+
alternatives, safe navigation, and conditional assignments receive alternative
|
|
8
|
+
coverage.
|
|
8
9
|
|
|
9
10
|
The gem and primary command are named `branchproof`. The `mcdc` command and
|
|
10
11
|
`MCDC` namespace remain compatibility aliases with the same behavior.
|
|
@@ -128,7 +129,7 @@ json` when a consumer needs the complete identifiers and versioned schema.
|
|
|
128
129
|
|
|
129
130
|
### Coverage ladder
|
|
130
131
|
|
|
131
|
-
Each successful `analyze` run calculates
|
|
132
|
+
Each successful `analyze` run calculates five criteria from the same completed
|
|
132
133
|
observations. The report shows their status for each supported decision and
|
|
133
134
|
aggregate counts with explicit denominators:
|
|
134
135
|
|
|
@@ -138,6 +139,7 @@ aggregate counts with explicit denominators:
|
|
|
138
139
|
| Condition | Every atomic condition evaluated both true and false | Two required truth values per supported condition |
|
|
139
140
|
| Condition/Decision | Both Decision and Condition Coverage hold for a decision | Supported decisions |
|
|
140
141
|
| MC/DC | Every condition has an independence witness pair | Supported conditions |
|
|
142
|
+
| Decision Table | Every executable logical rule was exercised | Non-impossible generated rules |
|
|
141
143
|
|
|
142
144
|
For `logged_in? && admin?`, observations `[F-] => F` and `[TT] => T` give:
|
|
143
145
|
|
|
@@ -146,6 +148,7 @@ Decision PASS
|
|
|
146
148
|
Condition FAIL (3/4 values observed)
|
|
147
149
|
Condition/Decision FAIL
|
|
148
150
|
MC/DC FAIL (1/2 conditions proven)
|
|
151
|
+
Decision Table FAIL (2/3 rules covered)
|
|
149
152
|
```
|
|
150
153
|
|
|
151
154
|
The skipped `admin?` in `[F-]` counts as neither true nor false. Conditions
|
|
@@ -155,6 +158,12 @@ for `logged_in?`; `admin?` still needs `[TF] => F`.
|
|
|
155
158
|
|
|
156
159
|
Decision and Condition Coverage are calculated independently from the captured
|
|
157
160
|
evidence. Condition/Decision requires both; MC/DC adds independence evidence.
|
|
161
|
+
Decision Table Coverage is calculated independently of all of them: MC/DC asks
|
|
162
|
+
whether each condition can independently affect the outcome, while Decision
|
|
163
|
+
Table Coverage asks whether each logical rule was exercised. Neither status is
|
|
164
|
+
inferred from the other, so `MC/DC PASS` with `Decision Table FAIL` and the
|
|
165
|
+
reverse are both valid results.
|
|
166
|
+
|
|
158
167
|
Unsupported decisions are excluded from every denominator, while
|
|
159
168
|
unexecuted supported decisions remain in scope. Empty denominators are N/A.
|
|
160
169
|
|
|
@@ -170,6 +179,184 @@ under `analysis.decisions[].coverage`, and condition value evidence under
|
|
|
170
179
|
pairs, counterpart constraints, and raw vectors remain available. Statuses
|
|
171
180
|
distinguish `covered`, `partial`, `unexecuted`, and `unsupported` results.
|
|
172
181
|
|
|
182
|
+
### Decision table coverage
|
|
183
|
+
|
|
184
|
+
For a Boolean decision Branchproof can represent with `AND`, `OR`, `NOT`, and
|
|
185
|
+
atomic conditions, it derives a reduced decision table statically from the
|
|
186
|
+
Boolean structure and then overlays the runtime evidence of the same run. No
|
|
187
|
+
application code is executed while the table is derived, and no extra test run
|
|
188
|
+
is performed while it is overlaid.
|
|
189
|
+
|
|
190
|
+
Rules come directly from the short-circuit evaluation paths of the Ruby expression,
|
|
191
|
+
so a condition the interpreter would
|
|
192
|
+
skip appears as an explicit don't-care (`-`) rather than as two separate rules.
|
|
193
|
+
Exhaustive Boolean expansion is deferred; it is not required to calculate coverage
|
|
194
|
+
and is never performed on the reporting path.
|
|
195
|
+
For `premium? && (admin? || owner?)`:
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
Decision Table: 3/4 rules covered (75.0%)
|
|
199
|
+
Rule premium? admin? owner? Result Status
|
|
200
|
+
R1 F - - F COVERED
|
|
201
|
+
R2 T T - T COVERED
|
|
202
|
+
R3 T F T T MISSING
|
|
203
|
+
R4 T F F F COVERED
|
|
204
|
+
R1 tests: UserAccessTest#test_free_user
|
|
205
|
+
R2 tests: UserAccessTest#test_admin
|
|
206
|
+
R3
|
|
207
|
+
Need:
|
|
208
|
+
premium? = truthy
|
|
209
|
+
admin? = falsey
|
|
210
|
+
owner? = truthy
|
|
211
|
+
Expected decision:
|
|
212
|
+
true
|
|
213
|
+
Reachability:
|
|
214
|
+
unknown
|
|
215
|
+
R4 tests: UserAccessTest#test_denied
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
A missing rule describes condition values only. It does not claim which
|
|
219
|
+
application inputs would produce them.
|
|
220
|
+
|
|
221
|
+
An observation matches a rule when every required condition value matches and
|
|
222
|
+
the decision outcome matches. Conditions Ruby skipped can only line up with
|
|
223
|
+
don't-care positions; they never satisfy a required true or false. Every rule
|
|
224
|
+
carries a stable identity derived from the decision, its normalized condition
|
|
225
|
+
vector, the expected outcome, and the table schema version — never from a test
|
|
226
|
+
name, an observation order, or the Minitest seed, so saved reports compare
|
|
227
|
+
across runs.
|
|
228
|
+
|
|
229
|
+
Decision tables are derived for `if`, `unless`, `elsif`, ternary, `while`,
|
|
230
|
+
`until`, subjectless `case`/`when`, and supported Boolean pattern guards.
|
|
231
|
+
Multi-way `case`/`when`, `case`/`in` alternatives, safe navigation, conditional
|
|
232
|
+
assignment, and exception handling keep their alternative coverage model and
|
|
233
|
+
produce no Boolean table.
|
|
234
|
+
For `unless` and `until`, the outcome is the predicate value, not whether the
|
|
235
|
+
body executes; the terminal report labels it accordingly.
|
|
236
|
+
|
|
237
|
+
#### Reachability and impossible rules
|
|
238
|
+
|
|
239
|
+
Each rule carries a reachability status: `observed`, `unknown`, or
|
|
240
|
+
`statically_impossible`. Branchproof proves impossibility or leaves
|
|
241
|
+
reachability unknown. It never infers impossibility from a missing test, a
|
|
242
|
+
missing observation, application conventions, Rails validations, database
|
|
243
|
+
constraints, comments, or method names.
|
|
244
|
+
|
|
245
|
+
Constraint analysis version 2 requires evidence that a constraint is safe before
|
|
246
|
+
using it to exclude a rule. A variable name and a numeric literal do not prove
|
|
247
|
+
that the receiver is a number, that its comparison methods use built-in semantics,
|
|
248
|
+
or that its value stays unchanged. Numeric, equality, and nil-check expressions
|
|
249
|
+
are still normalized for inspection, but unproven source constraints remain
|
|
250
|
+
`unknown`. The standalone constraint solver describes its explicit model, not
|
|
251
|
+
arbitrary Ruby objects.
|
|
252
|
+
|
|
253
|
+
Literal truth values can establish impossibility without invoking application
|
|
254
|
+
methods. For `age && false`, the rule requiring the literal `false` to be truthy
|
|
255
|
+
cannot execute:
|
|
256
|
+
|
|
257
|
+
```text
|
|
258
|
+
Decision Table: 2/2 rules covered (100.0%)
|
|
259
|
+
Statically impossible rules excluded: 1
|
|
260
|
+
Rule age false Result Status
|
|
261
|
+
R1 F - F COVERED
|
|
262
|
+
R2 T F F COVERED
|
|
263
|
+
R3 T T T EXCLUDED
|
|
264
|
+
R3 TT => T
|
|
265
|
+
Status:
|
|
266
|
+
EXCLUDED
|
|
267
|
+
Reachability:
|
|
268
|
+
STATICALLY IMPOSSIBLE
|
|
269
|
+
Reason:
|
|
270
|
+
conflicting Boolean literal requirements
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Impossible rules stay visible in the full report but leave the coverage
|
|
274
|
+
denominator. Reason codes are stable: `conflicting_numeric_bounds`,
|
|
275
|
+
`conflicting_equalities`, `equality_outside_numeric_range`, `nil_conflict`, and
|
|
276
|
+
`boolean_literal_conflict`. Human-readable messages may change independently.
|
|
277
|
+
|
|
278
|
+
Runtime evidence is authoritative. If an observation matches a rule the static
|
|
279
|
+
model called impossible, the rule becomes `observed`, the impossibility claim is
|
|
280
|
+
withdrawn, the rule returns to the denominator, and a `constraint_model_conflict`
|
|
281
|
+
diagnostic records the disagreement.
|
|
282
|
+
|
|
283
|
+
For example, `age > 10 && age < 5` stays unknown without a proven domain;
|
|
284
|
+
custom comparison methods can make both comparisons true. Similarly,
|
|
285
|
+
`x > 10 && (x = 0) && x < 5` can execute successfully. `Float::NAN` also prevents
|
|
286
|
+
treating a false comparison as its ordered complement. Runtime evidence can cover
|
|
287
|
+
these rules, but lack of evidence cannot exclude them. Ruby truthiness remains
|
|
288
|
+
distinct from Boolean equality: only `false` and `nil` are falsey.
|
|
289
|
+
|
|
290
|
+
Use `analyze --no-reachability` to disable all static exclusions, including literal
|
|
291
|
+
proofs. Every generated rule then remains an obligation; reports show
|
|
292
|
+
`Reachability: not analyzed`. The mode is persisted with the report and considered
|
|
293
|
+
when comparing analysis contexts. Reading a saved report preserves its original
|
|
294
|
+
analysis; it does not recalculate it under a different mode.
|
|
295
|
+
|
|
296
|
+
#### Decision table views and limits
|
|
297
|
+
|
|
298
|
+
`--view decision-tables` groups the terminal report by decision table, and
|
|
299
|
+
`--missing-only` narrows it to uncovered, non-impossible rules while retaining
|
|
300
|
+
uncalculated decisions with their location and reason. `decision_tables` is an
|
|
301
|
+
accepted compatibility spelling for the view:
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
bundle exec branchproof analyze 'lib/**/*.rb' \
|
|
305
|
+
--view decision-tables \
|
|
306
|
+
--missing-only
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Tables grow exponentially with condition count, so
|
|
310
|
+
`max_conditions_for_decision_table` (default 12) and
|
|
311
|
+
`decision_table_rules_per_decision` (default 4096) bound the derivation. A
|
|
312
|
+
decision above either limit reports `Decision Table: NOT CALCULATED` with the
|
|
313
|
+
reason `decision_table_condition_limit_exceeded` or
|
|
314
|
+
`decision_table_rule_limit_exceeded` instead of a partial table.
|
|
315
|
+
|
|
316
|
+
JSON stores the table under `analysis.decisions[].decision_table` with its
|
|
317
|
+
`schema_version`, `constraint_analysis_version`, rules, rule identities, rule
|
|
318
|
+
coverage, test attribution, reachability, and reachability reason. This abbreviated
|
|
319
|
+
example omits counts and evidence bookkeeping fields; full reports also retain
|
|
320
|
+
rule indexes, vector IDs, unattributed counts, and withdrawn-impossibility details:
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"decision_table": {
|
|
325
|
+
"status": "calculated",
|
|
326
|
+
"schema_version": 1,
|
|
327
|
+
"constraint_analysis_version": 2,
|
|
328
|
+
"rules": [
|
|
329
|
+
{
|
|
330
|
+
"id": "8f1c...",
|
|
331
|
+
"label": "R1",
|
|
332
|
+
"conditions": ["false", "dont_care", "dont_care"],
|
|
333
|
+
"outcome": false,
|
|
334
|
+
"coverage": "covered",
|
|
335
|
+
"reachability": "observed",
|
|
336
|
+
"tests": ["..."]
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"id": "3ad0...",
|
|
340
|
+
"label": "R3",
|
|
341
|
+
"conditions": ["true", "false", "true"],
|
|
342
|
+
"outcome": true,
|
|
343
|
+
"coverage": "missing",
|
|
344
|
+
"reachability": "unknown",
|
|
345
|
+
"tests": []
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Aggregate counts live under `analysis.coverage.decision_table` and keep rule
|
|
353
|
+
coverage and fully covered decisions as distinct metrics:
|
|
354
|
+
|
|
355
|
+
```text
|
|
356
|
+
DT (Decision table coverage): 83.9% (47/56 rules)
|
|
357
|
+
Decision tables fully covered: 13/18 decisions
|
|
358
|
+
```
|
|
359
|
+
|
|
173
360
|
### Find missing cases
|
|
174
361
|
|
|
175
362
|
Use `--missing-only` to focus the terminal report on conditions that still
|
|
@@ -231,7 +418,7 @@ analysis even at Level 1. A failed, unsupported, or incomplete run is reported
|
|
|
231
418
|
with its status and diagnostics and cannot become a successful coverage
|
|
232
419
|
result by changing the display level.
|
|
233
420
|
|
|
234
|
-
### Focused condition and
|
|
421
|
+
### Focused condition, test, and decision-table views
|
|
235
422
|
|
|
236
423
|
Use `--view conditions` to group the report by condition. Each condition shows
|
|
237
424
|
its expression, decision, and project-relative source location with the
|
|
@@ -253,6 +440,10 @@ conditions. A test that evaluates both Boolean values is evidence of execution;
|
|
|
253
440
|
it is a proof contributor only when the analyzer's independent witness pair
|
|
254
441
|
uses its observations.
|
|
255
442
|
|
|
443
|
+
Use `--view decision-tables` to group the report by decision table. See
|
|
444
|
+
[Decision table coverage](#decision-table-coverage) for the rule, reachability,
|
|
445
|
+
and attribution detail it renders.
|
|
446
|
+
|
|
256
447
|
`--view` changes terminal grouping and does not change instrumentation or test
|
|
257
448
|
execution. JSON output always contains the complete evidence document, so an
|
|
258
449
|
explicit view cannot be combined with `--format json`. The `mcdc` executable
|
|
@@ -308,7 +499,12 @@ patterns is valid comparison context, and seed differences are disclosed.
|
|
|
308
499
|
|
|
309
500
|
`compare` exits 0 for a complete comparison, including one with coverage
|
|
310
501
|
changes; `--fail-on-regression` exits 1 when a complete comparable run loses
|
|
311
|
-
proof
|
|
502
|
+
MC/DC proof or decision-table rule coverage. The JSON `regression` flag includes
|
|
503
|
+
either kind of loss; `regressions` retains the MC/DC count and
|
|
504
|
+
`decision_table_regressions` supplies the separate rule-loss count. Changes to
|
|
505
|
+
table schemas, constraint-analysis versions, or reachability modes are reported
|
|
506
|
+
as analysis context changes rather than silently treated as unchanged analysis.
|
|
507
|
+
Invalid input or an incomplete comparison exits 2, which takes
|
|
312
508
|
precedence. Reports are explicit snapshots: comparison never creates history,
|
|
313
509
|
promotes a baseline, or overwrites either input.
|
|
314
510
|
|
|
@@ -321,33 +517,89 @@ short-circuited or masked rather than fixed to the same observed values. For exa
|
|
|
321
517
|
short-circuits it once, but does not prove `right`; `[TF]` is also required.
|
|
322
518
|
The condition and test views preserve that distinction.
|
|
323
519
|
|
|
324
|
-
### Supported
|
|
520
|
+
### Supported decision forms
|
|
325
521
|
|
|
326
|
-
|
|
327
|
-
|
|
522
|
+
Every decision has a stable `kind` and `context` in JSON. The Boolean ladder
|
|
523
|
+
applies to the following forms:
|
|
328
524
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
525
|
+
| Construct | Kind | Context |
|
|
526
|
+
| --- | --- | --- |
|
|
527
|
+
| `if`, modifier `if`, `elsif`, `unless`, ternary | `boolean` | `if`, `elsif`, `unless`, `ternary` |
|
|
528
|
+
| `while`, `until`, including modifier and post-test loops | `boolean` | `while`, `until` |
|
|
529
|
+
| Each subjectless `case` candidate | `boolean` | `case_when` |
|
|
530
|
+
| Standalone `value in pattern` | `boolean` | `pattern_in` |
|
|
531
|
+
| Evaluated pattern guard predicate | `boolean` | `pattern_guard` |
|
|
532
|
+
| Standalone `&&`, `||`, `and`, `or` | `boolean` | `short_circuit` |
|
|
533
|
+
|
|
534
|
+
Prism determines precedence. `!` and `not` appear as NOT nodes in the Boolean
|
|
535
|
+
tree; their operands remain the conditions. Short-circuited operands remain
|
|
536
|
+
not evaluated. A Boolean subtree already decomposed in a decision is not
|
|
537
|
+
inventoried again as a standalone decision.
|
|
538
|
+
|
|
539
|
+
Loop outcomes describe the predicate as written: an `until` predicate that
|
|
540
|
+
returns true ends the loop. Every predicate evaluation receives an execution
|
|
541
|
+
ID. Repeated equivalent executions aggregate into a vector's `count`, retaining
|
|
542
|
+
the supporting tests. Ternary outcomes likewise describe the predicate, not
|
|
543
|
+
the value returned by the chosen branch.
|
|
544
|
+
|
|
545
|
+
Other constructs use alternative coverage, separate from MC/DC:
|
|
546
|
+
|
|
547
|
+
| Construct | Kind | Context | Required alternatives |
|
|
548
|
+
| --- | --- | --- | --- |
|
|
549
|
+
| `case subject` | `multiway` | `case` | Each `when` candidate and `else` (or implicit no-match path) |
|
|
550
|
+
| `case/in` | `pattern` | `case_in` | Each pattern clause and explicit `else`, if present |
|
|
551
|
+
| `receiver&.method` | `implicit` | `safe_navigation` | Receiver nil / non-nil |
|
|
552
|
+
| `lhs ||= rhs` | `implicit` | `or_assignment` | RHS skipped / executed |
|
|
553
|
+
| `lhs &&= rhs` | `implicit` | `and_assignment` | RHS skipped / executed |
|
|
554
|
+
|
|
555
|
+
Each safe-navigation operation in a chain is a distinct decision. Assignment
|
|
556
|
+
instrumentation preserves Ruby's native local, instance, class, global,
|
|
557
|
+
constant, method, and indexed assignment operations, including receiver and
|
|
558
|
+
index evaluation order. Safe navigation distinguishes nil from false.
|
|
559
|
+
|
|
560
|
+
For multiway decisions, vector values mean selected (`true`), evaluated but
|
|
561
|
+
not selected (`false`), and skipped (`null`). Later alternatives remain skipped
|
|
562
|
+
when an earlier candidate matches. Implicit vectors record the selected path
|
|
563
|
+
and its unselected complement. Their `outcome` is a selection marker, not the
|
|
564
|
+
truthiness of the application's return value. Reports label these as paths,
|
|
565
|
+
not Boolean outcomes. Each alternative exposes selected, not-selected, and
|
|
566
|
+
skipped evidence with test and vector IDs. The alternative denominator is the
|
|
567
|
+
number of supported selectable alternatives; these decisions do not enter
|
|
568
|
+
Boolean-ladder or MC/DC denominators.
|
|
569
|
+
|
|
570
|
+
A `case/in` without `else` retains Ruby's native no-match exception. An execution
|
|
571
|
+
that fails before choosing a branch is aborted, not counted as a selected
|
|
572
|
+
alternative. Selected branches and assignment paths remain observed even when
|
|
573
|
+
their bodies or right-hand sides subsequently raise or return.
|
|
574
|
+
|
|
575
|
+
Unsupported syntax stays visible and outside coverage denominators. Current
|
|
576
|
+
exclusions include guarded `case/in` (`unsupported_pattern_guard`), dynamic
|
|
577
|
+
`when` splats (`unsupported_case_splat`), safe-navigation compound assignment
|
|
578
|
+
(`unsupported_assignment_target`), and rescue alternatives
|
|
579
|
+
(`unsupported_rescue_control_flow`). A guard predicate can still supply Boolean
|
|
580
|
+
evidence when Ruby evaluates it; an unsupported guarded case does not claim
|
|
581
|
+
pattern-match coverage from that evidence. Flip-flops remain
|
|
582
|
+
`unsupported_flip_flop`. Decisions inside `defined?`, contextual regular
|
|
583
|
+
expressions, heredocs, unsafe predicates, and limit overflows retain explicit
|
|
584
|
+
exclusions. Ruby-defined custom `!` methods keep their runtime behavior;
|
|
585
|
+
evidence that contradicts Boolean negation is rejected instead of proving
|
|
586
|
+
coverage with an invalid logical model.
|
|
587
|
+
|
|
588
|
+
New reports use schema `1.3`; saved schema `1.0`, `1.1`, and `1.2` reports
|
|
589
|
+
remain readable. Comparison distinguishes decision-table coverage movement
|
|
590
|
+
(`rule coverage gained`, `rule coverage lost`) from analysis movement
|
|
591
|
+
(`rule reachability changed`), and treats a structurally changed decision as a
|
|
592
|
+
changed decision-table context instead of guessing which old rule a new rule
|
|
593
|
+
corresponds to. Expanded discovery changes coverage denominators, so compare reports
|
|
594
|
+
with their supported syntax scope in mind.
|
|
344
595
|
|
|
345
596
|
### Limits
|
|
346
597
|
|
|
347
598
|
`--limits` accepts a JSON object containing positive integer overrides. The
|
|
348
599
|
available keys are `conditions_per_decision`, `vectors_per_decision`,
|
|
349
600
|
`owner_associations_per_run`, `tests_per_run`, `exact_candidates`,
|
|
350
|
-
`exact_search_nodes`,
|
|
601
|
+
`exact_search_nodes`, `constraint_search_states`,
|
|
602
|
+
`max_conditions_for_decision_table`, and `decision_table_rules_per_decision`.
|
|
351
603
|
|
|
352
604
|
```json
|
|
353
605
|
{
|
data/doc/Branchproof/Analyzer.md
CHANGED
|
@@ -16,11 +16,11 @@ Not documented.
|
|
|
16
16
|
### `call()` <a id="method-i-call"></a> <a id="call-instance_method"></a>
|
|
17
17
|
Not documented.
|
|
18
18
|
|
|
19
|
-
### `initialize(inventory:, evidence:, limits:)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
19
|
+
### `initialize(inventory:, evidence:, limits:, reachability: = true)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
20
20
|
- **@return** [Analyzer] a new instance of Analyzer
|
|
21
21
|
|
|
22
22
|
### `missing(decision_id:, condition_index:)` <a id="method-i-missing"></a> <a id="missing-instance_method"></a>
|
|
23
23
|
Not documented.
|
|
24
24
|
|
|
25
|
-
### `pair?(decision_id:, condition_index:, left:, right:)` <a id="method-i-pair-3F"></a> <a id="pair?-instance_method"></a>
|
|
25
|
+
### `pair?(decision_id:, condition_index:, left:, right:, masks: = nil)` <a id="method-i-pair-3F"></a> <a id="pair?-instance_method"></a>
|
|
26
26
|
- **@return** [Boolean]
|
data/doc/Branchproof/CLI.md
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
Coordinates source inventory, isolated test execution, and report output.
|
|
9
9
|
|
|
10
|
+
## Constants
|
|
11
|
+
### `VIEWS` <a id="constant-VIEWS"></a> <a id="VIEWS-constant"></a>
|
|
12
|
+
Not documented.
|
|
13
|
+
|
|
10
14
|
## Public Instance Methods
|
|
11
15
|
### `call(argv)` <a id="method-i-call"></a> <a id="call-instance_method"></a>
|
|
12
16
|
Not documented.
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
Renders the offline document returned by Comparison.
|
|
9
9
|
|
|
10
|
+
## Constants
|
|
11
|
+
### `RULE_LABELS` <a id="constant-RULE_LABELS"></a> <a id="RULE_LABELS-constant"></a>
|
|
12
|
+
Not documented.
|
|
13
|
+
|
|
10
14
|
## Public Instance Methods
|
|
11
15
|
### `exit_code(fail_on_regression: = false)` <a id="method-i-exit_code"></a> <a id="exit_code-instance_method"></a>
|
|
12
16
|
Not documented.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Class Branchproof::Constraints::Solver <a id="class-Branchproof-Constraints-Solver"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | Object |
|
|
6
|
+
| **Defined in** | lib/branchproof/constraints.rb |
|
|
7
|
+
|
|
8
|
+
Accumulates the constraints one decision-table rule requires and reports the
|
|
9
|
+
first proven contradiction. Subjects never interact with each other.
|
|
10
|
+
|
|
11
|
+
## Public Instance Methods
|
|
12
|
+
### `add(constraint, truth)` <a id="method-i-add"></a> <a id="add-instance_method"></a>
|
|
13
|
+
Returns a reason code when the rule became unsatisfiable, otherwise nil.
|
|
14
|
+
|
|
15
|
+
### `add_prepared(constraint, truth)` <a id="method-i-add_prepared"></a> <a id="add_prepared-instance_method"></a>
|
|
16
|
+
Adds a constraint that has already been symbolized and validated by
|
|
17
|
+
<code>usable?</code>. Source inventory can use this path after preparing each
|
|
18
|
+
leaf once instead of repeating normalization for every solver state.
|
|
19
|
+
|
|
20
|
+
### `initialize()` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
21
|
+
- **@return** [Solver] a new instance of Solver
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Module Branchproof::Constraints <a id="module-Branchproof-Constraints"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/branchproof/constraints.rb |
|
|
6
|
+
|
|
7
|
+
Normalizes simple atomic conditions into subject/operator/value records and
|
|
8
|
+
decides, conservatively, whether a set of required condition truths can hold
|
|
9
|
+
at the same time.
|
|
10
|
+
|
|
11
|
+
The module never evaluates application code. It only inspects the syntax of a
|
|
12
|
+
condition, and it answers "contradictory" only when the contradiction follows
|
|
13
|
+
from the normalized constraints alone. Every other shape stays unrepresented,
|
|
14
|
+
which leaves the owning decision-table rule reachability `unknown`.
|
|
15
|
+
|
|
16
|
+
## Constants
|
|
17
|
+
### `COMPARISON_OPERATORS` <a id="constant-COMPARISON_OPERATORS"></a> <a id="COMPARISON_OPERATORS-constant"></a>
|
|
18
|
+
Not documented.
|
|
19
|
+
|
|
20
|
+
### `FALSEY_TYPES` <a id="constant-FALSEY_TYPES"></a> <a id="FALSEY_TYPES-constant"></a>
|
|
21
|
+
Not documented.
|
|
22
|
+
|
|
23
|
+
### `FLIPPED` <a id="constant-FLIPPED"></a> <a id="FLIPPED-constant"></a>
|
|
24
|
+
Not documented.
|
|
25
|
+
|
|
26
|
+
### `LITERAL_TYPES` <a id="constant-LITERAL_TYPES"></a> <a id="LITERAL_TYPES-constant"></a>
|
|
27
|
+
Not documented.
|
|
28
|
+
|
|
29
|
+
### `NUMERIC_OPERATORS` <a id="constant-NUMERIC_OPERATORS"></a> <a id="NUMERIC_OPERATORS-constant"></a>
|
|
30
|
+
Not documented.
|
|
31
|
+
|
|
32
|
+
### `NUMERIC_TYPES` <a id="constant-NUMERIC_TYPES"></a> <a id="NUMERIC_TYPES-constant"></a>
|
|
33
|
+
Not documented.
|
|
34
|
+
|
|
35
|
+
### `OPERATORS` <a id="constant-OPERATORS"></a> <a id="OPERATORS-constant"></a>
|
|
36
|
+
Not documented.
|
|
37
|
+
|
|
38
|
+
### `REASONS` <a id="constant-REASONS"></a> <a id="REASONS-constant"></a>
|
|
39
|
+
Not documented.
|
|
40
|
+
|
|
41
|
+
### `REASON_MESSAGES` <a id="constant-REASON_MESSAGES"></a> <a id="REASON_MESSAGES-constant"></a>
|
|
42
|
+
Not documented.
|
|
43
|
+
|
|
44
|
+
### `SUBJECT_KINDS` <a id="constant-SUBJECT_KINDS"></a> <a id="SUBJECT_KINDS-constant"></a>
|
|
45
|
+
Not documented.
|
|
46
|
+
|
|
47
|
+
### `VERSION` <a id="constant-VERSION"></a> <a id="VERSION-constant"></a>
|
|
48
|
+
Not documented.
|
|
49
|
+
|
|
50
|
+
## Public Class Methods
|
|
51
|
+
### `comparison_constraint(node, name)` <a id="method-c-comparison_constraint"></a> <a id="comparison_constraint-class_method"></a>
|
|
52
|
+
Not documented.
|
|
53
|
+
|
|
54
|
+
### `for_node(node)` <a id="method-c-for_node"></a> <a id="for_node-class_method"></a>
|
|
55
|
+
Derives the normalized constraint of one atomic condition, or nil when the
|
|
56
|
+
expression is outside the supported vocabulary.
|
|
57
|
+
|
|
58
|
+
### `literal_for(node)` <a id="method-c-literal_for"></a> <a id="literal_for-class_method"></a>
|
|
59
|
+
String equality stays unsupported in v1 so that encoding and mutability
|
|
60
|
+
questions cannot turn into an impossibility claim.
|
|
61
|
+
|
|
62
|
+
### `message(reason)` <a id="method-c-message"></a> <a id="message-class_method"></a>
|
|
63
|
+
Not documented.
|
|
64
|
+
|
|
65
|
+
### `mixed_numeric_literals?(left, right)` <a id="method-c-mixed_numeric_literals-3F"></a> <a id="mixed_numeric_literals?-class_method"></a>
|
|
66
|
+
- **@return** [Boolean]
|
|
67
|
+
|
|
68
|
+
### `nil_constraint(node)` <a id="method-c-nil_constraint"></a> <a id="nil_constraint-class_method"></a>
|
|
69
|
+
Not documented.
|
|
70
|
+
|
|
71
|
+
### `numeric?(literal)` <a id="method-c-numeric-3F"></a> <a id="numeric?-class_method"></a>
|
|
72
|
+
- **@return** [Boolean]
|
|
73
|
+
|
|
74
|
+
### `same_literal?(left, right)` <a id="method-c-same_literal-3F"></a> <a id="same_literal?-class_method"></a>
|
|
75
|
+
- **@return** [Boolean]
|
|
76
|
+
|
|
77
|
+
### `simple_call?(node)` <a id="method-c-simple_call-3F"></a> <a id="simple_call?-class_method"></a>
|
|
78
|
+
- **@return** [Boolean]
|
|
79
|
+
|
|
80
|
+
### `single_argument(node)` <a id="method-c-single_argument"></a> <a id="single_argument-class_method"></a>
|
|
81
|
+
Not documented.
|
|
82
|
+
|
|
83
|
+
### `subject_for(node)` <a id="method-c-subject_for"></a> <a id="subject_for-class_method"></a>
|
|
84
|
+
Only unambiguously identifiable storage locations become subjects. Method-call
|
|
85
|
+
receivers stay unsupported: a repeated call may return a different value or
|
|
86
|
+
have side effects (see the v1 constraint scope).
|
|
87
|
+
|
|
88
|
+
### `subject_key(subject)` <a id="method-c-subject_key"></a> <a id="subject_key-class_method"></a>
|
|
89
|
+
Not documented.
|
|
90
|
+
|
|
91
|
+
### `symbolize(value)` <a id="method-c-symbolize"></a> <a id="symbolize-class_method"></a>
|
|
92
|
+
Not documented.
|
|
93
|
+
|
|
94
|
+
### `usable?(constraint)` <a id="method-c-usable-3F"></a> <a id="usable?-class_method"></a>
|
|
95
|
+
- **@return** [Boolean]
|
|
96
|
+
|
|
97
|
+
### `valid_literal_value?(literal)` <a id="method-c-valid_literal_value-3F"></a> <a id="valid_literal_value?-class_method"></a>
|
|
98
|
+
- **@return** [Boolean]
|
|
@@ -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
|
|
|
@@ -15,5 +18,8 @@ Returns the value of attribute conditions.
|
|
|
15
18
|
Returns the value of attribute tests.
|
|
16
19
|
|
|
17
20
|
## Public Instance Methods
|
|
21
|
+
### `decision_tables()` <a id="method-i-decision_tables"></a> <a id="decision_tables-instance_method"></a>
|
|
22
|
+
Not documented.
|
|
23
|
+
|
|
18
24
|
### `initialize(document:)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
19
25
|
- **@return** [CoverageIndex] a new instance of CoverageIndex
|
|
@@ -0,0 +1,22 @@
|
|
|
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_CLASSES` <a id="constant-AND_WRITE_NODE_CLASSES"></a> <a id="AND_WRITE_NODE_CLASSES-constant"></a>
|
|
13
|
+
Not documented.
|
|
14
|
+
|
|
15
|
+
### `OR_WRITE_NODE_CLASSES` <a id="constant-OR_WRITE_NODE_CLASSES"></a> <a id="OR_WRITE_NODE_CLASSES-constant"></a>
|
|
16
|
+
Not documented.
|
|
17
|
+
|
|
18
|
+
## Public Instance Methods
|
|
19
|
+
### `flow_decisions_for(program, bytes, source_id, file_reasons = [], encoding = "UTF-8", nodes: = nil)` <a id="method-i-flow_decisions_for"></a> <a id="flow_decisions_for-instance_method"></a>
|
|
20
|
+
nodes: flow-decision nodes already collected by a caller's own AST walk
|
|
21
|
+
(Source merges this discovery into one pass). Falls back to its own walk when
|
|
22
|
+
nothing is passed in, so this method still works standalone.
|