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
172
173
174
175
176
177
178
179
180
181
182
183
//! Tests for `tm divert bulk-read` (#6887).
//!
//! Why: these cover what the command decides around the worker call — how files
//! are gathered and bounded, and what the diversion ledger records. The worker
//! call itself is covered in `divert_worker_tests.rs`.
//! What: the `#[cfg(test)]` module `divert.rs` includes.
use super::*;
/// Build a reply with the shape a real child returns.
fn reply() -> WorkerReply {
WorkerReply {
text: "two functions".to_string(),
model: "claude-haiku-4-5".to_string(),
input_tokens: 10,
output_tokens: 412,
cache_read_tokens: 9094,
cache_creation_tokens: 21922,
cost_usd: 0.019422,
}
}
/// Why: the hook's block reason quotes [`FALLTHROUGH_MARKER`] verbatim so the
/// agent has a literal to match. If the two drift, a fall-through leaves the
/// agent with a string it was told to look for and cannot find, and no next
/// move.
/// What: asserts the hook's reason contains the marker this module prints.
#[test]
fn fallthrough_marker_matches_the_hook_reason() {
let reason = crate::commands::divert_check::block_reason("a.rs", 900);
assert!(
reason.contains(FALLTHROUGH_MARKER),
"the block reason must quote the fall-through marker verbatim: {reason}"
);
}
/// Why (#6887 acceptance criterion 2): the block reason is the agent's ONLY
/// channel back, so it must name the exact replacement command. A reason that
/// only says "too big" strands the agent.
/// What: asserts the reason names `tm divert bulk-read` and the file.
#[test]
fn block_reason_names_the_worker_command() {
let reason = crate::commands::divert_check::block_reason("src/big.rs", 1200);
assert!(
reason.contains("tm divert bulk-read src/big.rs"),
"{reason}"
);
assert!(
reason.contains("1200"),
"the reason must state the line count"
);
}
/// Why: the worker must be told which bytes came from which file, or its answer
/// silently describes the wrong one. A named file that cannot be read is a hard
/// error for the same reason — a skipped file yields a confident answer about
/// content that was never sent.
/// What: each file becomes one `(path, content)` pair; a missing file errors.
#[test]
fn read_sources_labels_each_file() {
let tmp = tempfile::tempdir().unwrap();
let a = tmp.path().join("a.rs");
let b = tmp.path().join("b.rs");
std::fs::write(&a, "fn a() {}\n").unwrap();
std::fs::write(&b, "fn b() {}\n").unwrap();
let sources = read_sources(&[a.clone(), b.clone()]).expect("both files read");
assert_eq!(sources.len(), 2);
assert_eq!(sources[0].0, a.display().to_string());
assert_eq!(sources[0].1, "fn a() {}\n");
assert_eq!(sources[1].0, b.display().to_string());
let missing = tmp.path().join("nope.rs");
let err = read_sources(&[missing]).expect_err("a missing file must not be skipped");
assert!(err.to_string().contains("cannot read"), "{err}");
}
/// Why: an unbounded blob would fail API-side as a transport error, which reads
/// as a worker outage rather than as "these files are too big". Truncating
/// explicitly keeps the diagnosis local.
/// What: content past the budget is cut, marked, and stops the loop.
#[test]
fn read_sources_truncates_past_the_budget() {
let tmp = tempfile::tempdir().unwrap();
let big = tmp.path().join("big.rs");
let tail = tmp.path().join("tail.rs");
std::fs::write(&big, "x".repeat(MAX_CONTENT_BYTES + 4096)).unwrap();
std::fs::write(&tail, "fn never_sent() {}").unwrap();
let sources = read_sources(&[big, tail]).expect("truncation is not an error");
assert_eq!(sources.len(), 1, "the loop stops once the budget is spent");
assert!(sources[0].1.contains("truncated: content budget reached"));
assert!(sources[0].1.len() <= MAX_CONTENT_BYTES + 64);
}
/// Why (#6887 acceptance criterion 6): this line IS the record — #6873's usage
/// ledger is not merged — so it has to carry the running count AND the child's
/// own token and cost numbers. A line missing any of them cannot answer "what
/// did diversion save".
/// What: asserts the greppable marker and every field a reader needs, including
/// the cache counters that dominate a Claude Code child's prompt spend.
#[test]
fn diversion_line_carries_the_count_and_the_child_usage() {
let line = diversion_line(3, 2, &reply());
assert!(line.starts_with(DIVERSION_LOG_MARKER), "{line}");
for field in [
"count=3",
"files=2",
"model=claude-haiku-4-5",
"input_tokens=10",
"output_tokens=412",
"cache_read_tokens=9094",
"cache_creation_tokens=21922",
"cost_usd=0.019422",
] {
assert!(line.contains(field), "missing {field} in: {line}");
}
}
/// Why (#6887 acceptance criterion 6): "count per session" cannot come from a
/// process that exits after one diversion, so it has to be durable. The ledger
/// file is the count — one line per diversion — and two sessions must not share
/// one counter.
/// What: three diversions on one session count 1, 2, 3; a second session starts
/// at 1 again; the file holds one line per diversion.
#[test]
fn record_diversion_counts_per_session() {
let tmp = tempfile::tempdir().unwrap();
let reply = reply();
for expected in 1..=3 {
let count = record_diversion(tmp.path(), "sess-a", 1, &reply).expect("ledger write");
assert_eq!(count, expected, "the count must advance per diversion");
}
assert_eq!(
record_diversion(tmp.path(), "sess-b", 1, &reply).expect("ledger write"),
1,
"a different session must not inherit another's count"
);
let log = std::fs::read_to_string(tmp.path().join("divert").join("sess-a.log")).unwrap();
assert_eq!(log.lines().count(), 3);
assert!(log.lines().next().unwrap().contains("count=1"));
assert!(log.lines().last().unwrap().contains("count=3"));
// A session id carrying a path separator must not escape the ledger dir.
record_diversion(tmp.path(), "../escape", 1, &reply).expect("ledger write");
assert!(tmp.path().join("divert").join(".._escape.log").exists());
}
/// Why (#6959): the savings row is priced against the bytes the session did not
/// ingest, so the count has to be every file body the diversion gathered. A
/// count that read only the first file would understate every multi-file
/// diversion.
/// What: the sum of the bodies, with the path labels contributing nothing.
#[test]
fn source_bytes_sums_every_file_body() {
let sources = vec![
(
"a/very/long/path/that/must/not/count.rs".to_string(),
"abcd".to_string(),
),
("b.rs".to_string(), "efghij".to_string()),
];
assert_eq!(source_bytes(&sources), 10);
assert_eq!(source_bytes(&[]), 0);
}
/// Why (#6959): the savings row is attributed to the PARENT session, which only
/// works because this process keeps the harness's session id while the worker it
/// spawns does not. If the id were ever scrubbed here too, every row would be
/// declined and the statusline would stay empty with nothing saying why.
/// What: asserts the variable this module reads — the shared
/// [`trusty_mpm::core::savings::CLAUDE_CODE_SESSION_ID_ENV`] since #7209 — is on
/// the worker's scrub list: the child loses it, this process keeps it.
#[test]
fn parent_session_id_env_is_scrubbed_from_the_worker_only() {
assert!(
crate::commands::divert_worker::NESTED_SESSION_ENV
.contains(&trusty_mpm::core::savings::CLAUDE_CODE_SESSION_ID_ENV),
"the worker must be scrubbed of the parent session id"
);
}