Merge pull request #208 from fiddlerwoaroof/fix/b9f2-width-guard
Determinism law: the width guard measures the drills it compared (improvise-b9f2)
This commit is contained in:
+35
-12
@@ -3379,18 +3379,41 @@ one run in eight (measured: 46 of 400). So the arguments now include **every
|
||||
item of every regular category as a one-coordinate drill**, read off the parsed
|
||||
document. A one-coordinate drill spans every other category whole, so it matches
|
||||
the whole slice of records under that item, not one record picked by name. In
|
||||
the demo that is four records and eight cells for each of the eight drills. The
|
||||
test
|
||||
also asserts that the widest candidate it actually passes matches at least four
|
||||
records. That guard catches a demo with too little data, and it catches the
|
||||
derived drills being dropped from the argument list. It does **not** catch the
|
||||
comparison losing its wide drills some other way, because it measures what the
|
||||
argument lists match, not what the built commands ordered (improvise-b9f2). The
|
||||
review showed this with a rival: a `drill-into-cell` parser that refuses fewer
|
||||
than two arguments sends every one-coordinate drill to the one-record cursor
|
||||
fallback. The guard still reports four records, and with the store's sort
|
||||
removed the law passed 38 of 300 runs. With the store's sort removed and the
|
||||
parser intact, the law fails 400 of 400 runs.
|
||||
the demo that is four records and eight cells for each of the eight drills. With
|
||||
the store's sort removed, the law fails 400 of 400 runs.
|
||||
|
||||
**The width guard measures what the comparison compared (improvise-b9f2,
|
||||
improvise-btyw).** The test asserts that some `drill-into-cell` the comparison
|
||||
actually built and compared froze at least four records. For each one it applies
|
||||
those very effects to one scratch `App` and counts the records in its
|
||||
`drill_state`. It counts them only when their `Debug` text, which carries their
|
||||
order, appears in the text the comparison compared. The comparison reads `Debug`
|
||||
while the scratch `App` reads the applied state, and those are two
|
||||
representations. The review showed they can disagree: give `StartDrill` a
|
||||
`Debug` that prints only a row count, and the frozen records are still four
|
||||
while the compared text no longer holds their order. With the store's sort
|
||||
removed the law then passed 300 of 300 runs, with the guard green. Requiring
|
||||
the ordered records in the compared text ties the guard to what the comparison
|
||||
could actually see. A first
|
||||
version measured what the *argument lists* match in the store instead. That was
|
||||
a parallel derivation, and the review beat it with a `drill-into-cell` parser
|
||||
that refuses fewer than two arguments. Every one-coordinate drill then went to
|
||||
the one-record cursor fallback, the argument lists still matched four records,
|
||||
and with the store's sort removed the law passed 38 of 300 runs.
|
||||
|
||||
Counting frozen records is not enough by itself. A drill over no coordinates
|
||||
(any candidate with no `Cat/Item` argument, such as `["1", "1"]`) freezes
|
||||
**every** record, and it reads them through `iter_cells` in insertion order,
|
||||
never through the narrowing index where gotcha 18 lives. Under the parser rival
|
||||
that whole-store drill still froze 16 records, and a guard counting it stayed
|
||||
silent. So only a drill that **narrowed** the store counts: one that froze fewer
|
||||
records than the store holds. With the parser rival in place, the guard fires on
|
||||
every run ("froze 1 records"), and it fires on every run under the `Debug` rival
|
||||
too ("froze 0 records").
|
||||
|
||||
What the guard still cannot see is a narrowing drill whose records are frozen in
|
||||
an order that does not come from the store. It checks that the law had a wide
|
||||
drill to compare, not why the drill's order would differ.
|
||||
|
||||
**What it cannot see.** Only commands and arguments the fixture reaches. A
|
||||
command whose nondeterminism needs some other state, such as records mode, a
|
||||
|
||||
@@ -5509,14 +5509,41 @@ rivals it catches:
|
||||
still pin the store directly.
|
||||
- **The derived drills dropped from the argument list** (the
|
||||
`all.extend(one_coordinate_drills(model))` line commented out): the test
|
||||
fails before comparing anything, with "the widest drill orders 1 records".
|
||||
That is the only way of losing the wide drills the guard can see. It measures
|
||||
what the argument lists match, not what the built commands ordered, so it can
|
||||
pass while the comparison has lost its wide drills (improvise-b9f2). Review
|
||||
rival: `drill-into-cell`'s parser changed to refuse fewer than two arguments,
|
||||
which sends every one-coordinate drill to the one-record cursor fallback. The
|
||||
guard stays silent and, with gotcha 18 re-injected, the law passed 38 of 300
|
||||
runs.
|
||||
fails with "the widest narrowing drill compared froze 1 records".
|
||||
- **`drill-into-cell`'s parser changed to refuse fewer than two arguments**
|
||||
(improvise-b9f2): every one-coordinate drill then goes to the one-record cursor
|
||||
fallback. The first width guard measured what the argument lists match, so it
|
||||
stayed silent. With gotcha 18 re-injected the law passed 38 of 300 runs in
|
||||
review, and 31 of 300 here at `cfcc9bff`. The guard now applies the compared
|
||||
drills' own effects to a scratch `App` and counts the records they froze.
|
||||
Against this rival it fails on every run with "froze 1 records".
|
||||
- A first version of the fix counted all frozen records and **still passed**.
|
||||
A candidate with no `Cat/Item` argument drills the whole store (16 records),
|
||||
and it reads them in insertion order through `iter_cells`, which the
|
||||
injection cannot reach. So the guard counts only drills that froze fewer
|
||||
records than the store holds.
|
||||
- Measured at `7dfa90f1`, test binary in a loop:
|
||||
|
||||
| Faults | Law passes | Guard fired |
|
||||
|---|---|---|
|
||||
| gotcha 18 + parser rival | **0 of 300** | all 300 |
|
||||
| gotcha 18 alone | **0 of 300** | 0 (every failure a real divergence) |
|
||||
| neither | 300 of 300 | 0 |
|
||||
- **`StartDrill`'s `Debug` changed to print only its row count**
|
||||
(`StartDrill(4 rows)`, improvise-btyw): the law compares effects by their
|
||||
`Debug` text, and this rival takes the record order out of that text, while
|
||||
the frozen `drill_state` still holds four records. At `7dfa90f1`, with gotcha
|
||||
18 re-injected, the law **passed 200 of 200** runs and the guard stayed green.
|
||||
The guard now counts a drill only when its frozen records, as `Debug` prints
|
||||
them, appear in the text the comparison compared. The same measurements after
|
||||
the repair, 200 runs each:
|
||||
|
||||
| Faults | Law passes | Guard fired |
|
||||
|---|---|---|
|
||||
| gotcha 18 + `Debug` rival | **0 of 200** | all 200 ("froze 0 records") |
|
||||
| gotcha 18 + parser rival | **0 of 200** | all 200 |
|
||||
| gotcha 18 alone | **0 of 200** | 0 (every failure a real divergence) |
|
||||
| neither | 200 of 200 | 0 |
|
||||
- **A stale exemption**: `("quit", "stale")` listed in `OUTSIDE_THE_CONTEXT`.
|
||||
It fails as "listed … but did not".
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//! A command is a function of its context: two contexts built independently from
|
||||
//! equal data give equal effects — `context/code/command.md`, improvise-hzjo.
|
||||
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::core::{Cmd, CmdContext};
|
||||
use super::grid::DrillIntoCell;
|
||||
use super::registry::tests::candidate_args;
|
||||
use crate::command::key::{KeyCode, KeyModifiers};
|
||||
use crate::model::Model;
|
||||
use crate::model::cell::{CellKey, CellValue};
|
||||
use crate::model::record::record_of;
|
||||
use crate::ui::app::App;
|
||||
use crate::ui::effect::{self, Effect};
|
||||
|
||||
@@ -61,27 +61,23 @@ fn one_coordinate_drills(model: &Model) -> Vec<Vec<String>> {
|
||||
drills
|
||||
}
|
||||
|
||||
/// How many distinct records the widest all-`Cat/Item` candidate matches.
|
||||
fn widest_drill(model: &Model, candidates: &[Vec<String>]) -> usize {
|
||||
let coordinates = |args: &Vec<String>| -> Option<Vec<(String, String)>> {
|
||||
args.iter()
|
||||
.map(|a| {
|
||||
a.split_once('/')
|
||||
.map(|(c, i)| (c.to_string(), i.to_string()))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
candidates
|
||||
.iter()
|
||||
.filter(|args| !args.is_empty())
|
||||
.filter_map(coordinates)
|
||||
.map(|partial| {
|
||||
let cells = model.data.matching_cells(&partial);
|
||||
let records: HashSet<CellKey> = cells.iter().map(|(k, _)| record_of(k)).collect();
|
||||
records.len()
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
/// Records a compared drill froze, counted only if it narrowed the store and its
|
||||
/// ordered records are in `compared`, the text the law compared (improvise-btyw).
|
||||
fn records_narrowed(scratch: &mut App, effects: Vec<Box<dyn Effect>>, compared: &str) -> usize {
|
||||
let store = scratch
|
||||
.model_state
|
||||
.workbook
|
||||
.model
|
||||
.data
|
||||
.live_records()
|
||||
.count();
|
||||
scratch.apply_effects(effects);
|
||||
let frozen = scratch
|
||||
.drill_state
|
||||
.as_ref()
|
||||
.filter(|drill| compared.contains(&format!("{:?}", drill.records)))
|
||||
.map_or(0, |drill| drill.records.len());
|
||||
if frozen < store { frozen } else { 0 }
|
||||
}
|
||||
|
||||
/// The registry's candidates, plus names the demo really has: a drill or a fit
|
||||
@@ -108,9 +104,9 @@ fn args_the_document_names(model: &Model) -> Vec<Vec<String>> {
|
||||
all
|
||||
}
|
||||
|
||||
fn effects_of(name: &str, args: &[String], ctx: &CmdContext) -> Option<String> {
|
||||
fn effects_of(name: &str, args: &[String], ctx: &CmdContext) -> Option<Vec<Box<dyn Effect>>> {
|
||||
let cmd = ctx.registry.interactive(name, args, ctx).ok()?;
|
||||
Some(format!("{:?}", cmd.execute(ctx)))
|
||||
Some(cmd.execute(ctx))
|
||||
}
|
||||
|
||||
/// What running every registered command under `a` and under each of `others` found.
|
||||
@@ -120,20 +116,34 @@ struct Comparison {
|
||||
shown: Vec<String>,
|
||||
never_built: Vec<&'static str>,
|
||||
compared: usize,
|
||||
widest_drill: usize,
|
||||
}
|
||||
|
||||
fn compare(a: &CmdContext, others: &[CmdContext]) -> Comparison {
|
||||
let mut found = Comparison::default();
|
||||
let candidates = args_the_document_names(a.model);
|
||||
let drill = DrillIntoCell {
|
||||
key: CellKey::new(vec![]),
|
||||
};
|
||||
let mut scratch = fixture();
|
||||
for name in a.registry.names().collect::<Vec<_>>() {
|
||||
let before = found.compared;
|
||||
for args in &candidates {
|
||||
let Some(left) = effects_of(name, args, a) else {
|
||||
let Some(effects) = effects_of(name, args, a) else {
|
||||
continue;
|
||||
};
|
||||
let left = format!("{effects:?}");
|
||||
if name == drill.name() {
|
||||
found.widest_drill =
|
||||
found
|
||||
.widest_drill
|
||||
.max(records_narrowed(&mut scratch, effects, &left));
|
||||
}
|
||||
found.compared += 1;
|
||||
for b in others {
|
||||
let right = effects_of(name, args, b).unwrap_or_default();
|
||||
let right = effects_of(name, args, b)
|
||||
.map(|e| format!("{e:?}"))
|
||||
.unwrap_or_default();
|
||||
if left != right {
|
||||
found.diverged.insert(name);
|
||||
found
|
||||
@@ -157,13 +167,12 @@ fn every_command_gives_equal_effects_in_independently_built_equal_contexts() {
|
||||
.iter()
|
||||
.map(|app| app.cmd_context(KeyCode::Null, KeyModifiers::NONE))
|
||||
.collect();
|
||||
let model = contexts[0].model;
|
||||
let widest = widest_drill(model, &args_the_document_names(model));
|
||||
assert!(
|
||||
widest >= 4,
|
||||
"the widest drill orders {widest} records, so the store's order is nearly a coin toss"
|
||||
);
|
||||
let found = compare(&contexts[0], &contexts[1..]);
|
||||
assert!(
|
||||
found.widest_drill >= 4,
|
||||
"the widest narrowing drill compared froze {} records, so the store's order is nearly a coin toss",
|
||||
found.widest_drill
|
||||
);
|
||||
assert!(
|
||||
found.never_built.is_empty(),
|
||||
"no candidate args build these in the fixture, so the law never ran them: {:?}",
|
||||
|
||||
Reference in New Issue
Block a user