1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! `Tree::frames` / `Tree::edges` — the **stable** answer to "what is in this
//! tree" (`docs/API.md` §2.6 row 4, §3.2's Python mirror).
//!
//! These live in their own binary rather than in `construction.rs` because they
//! are the one part of the facade's read surface that walks the arena's tables
//! by index. Every assertion below is written so that it fails when the walk is
//! wrong, not merely when it is absent: the tree each test builds has frame
//! headroom, edge headroom, a runtime-interned frame and a chain in which no
//! edge is another's reverse.
//!
//! **Each test's doc comment lists the mutants that were actually run against
//! it and what they printed — including two that survived.** A single-process
//! tree cannot separate the frame walk's three filters from each other, because
//! each of them alone already excludes what the other two exclude; that is
//! recorded at `frames_lists_exactly_the_declared_frames_in_id_order` rather
//! than papered over.
use ;
/// A tree with headroom in both tables, so a walk that used the *table* bound
/// instead of the *count* bound reports slots that are not frames or edges.
/// The declared frames, in `FrameId` order, and **nothing else**.
///
/// # What was mutated, and what each mutant did
///
/// Run against this file, reverted after each:
///
/// * `1..=count` → `1..count` ⇒ FAIL, `left: ["map", "odom", "base"]`. The upper
/// bound is checked in the direction that drops a frame.
/// * `stored_name`'s `&bytes[..n]` → `bytes` ⇒ FAIL,
/// `left: ["map\0\0…", …]`. A name is `name_len` bytes, not the 48-byte
/// record field.
/// * `1..=count` → `1..=count + 4`, walking into the headroom ⇒ **PASS, all
/// five tests.** Recorded because it is the interesting one: the
/// `name_hash != 0` filter below already drops a zeroed headroom slot, so on
/// a quiescent single-process tree the bound and the filter are *mutually
/// redundant* and no test here can separate them. Deleting the filter **as
/// well** ⇒ FAIL, `left: ["map", "odom", "base", "lidar", "", "", "", ""]`.
/// The pair is defence in depth against a concurrent interner, which is a
/// condition this binary does not create; `just shm-check`'s multiprocess
/// targets are where that would have to be exercised.
/// * `0..=count` ⇒ **PASS.** `FrameId::new(0)` declines the root sentinel, so
/// the lower bound is redundant with it in the same way. Stated rather than
/// dressed up as a caught mutant.
/// A frame interned after `build()` appears, at the end.
///
/// This is the half of the contract a build-time-only walk satisfies by
/// accident: `frame_count` is bumped at intern time, so a walk that cached a
/// count or read `max_frames` would answer the same list before and after.
/// `(parent, child)` pairs, in `EdgeId` order, with no sentinel and no headroom.
///
/// The pair order is asserted against a topology where every edge's parent and
/// child differ *and no pair is the reverse of another*, so swapping the two
/// fields of the tuple is a failure rather than a permutation of the same list.
///
/// # What was mutated
///
/// * `out.push((parent, child))` → `(child, parent)` ⇒ FAIL,
/// `left: [("odom", "map"), ("base", "odom"), ("lidar", "base")]`.
/// * the `let … else { continue }` that drops an edge whose endpoint does not
/// resolve → `name(…).unwrap_or_default()` ⇒ FAIL,
/// `left: [… , ("", "")]`, and `no_enumeration_reports_an_empty_name` fails
/// with it. That is the `edge_headroom(3)` slot arriving as a pair of empty
/// names, which is what the drop exists to prevent.
/// Neither list ever contains an empty name.
///
/// Stated separately from the two `assert_eq!`s above because it is the
/// property that survives a topology change: whatever the tree is, a zeroed slot
/// read as a frame shows up as `""`, and that is the shape of every failure mode
/// this walk has.
/// The stable tier answers `frame` and `frames` consistently.
///
/// `Tree::frame(name)` is the singular and this is the plural; a caller that
/// enumerates and then resolves must get ids `1..=len`. That is what makes the
/// list usable without `Tree::arena_view`, which is the whole reason these two
/// methods are on the stable surface (`docs/API.md` §2.6).