crates/signald/tests/spool_bound.rs
58 lines · 1937 bytes
1//! The spool cannot grow without bound while the daemon is away.
2//!
3//! The hook appends one record per prompt and the daemon consumes the spool
4//! each tick. When the daemon is not running nothing consumes it, so the file
5//! grew forever and the daemon then read the whole thing into memory on
6//! recovery. The hook caps it at the source, which is the only place that
7//! actually bounds growth.
8
9use std::path::PathBuf;
10use std::process::Command;
11
12fn hook_path() -> PathBuf {
13 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
14 .join("../../shell-hooks/signald-hooks.zsh")
15 .canonicalize()
16 .expect("hook script")
17}
18
19/// Source the real hook, drive `precmd` past a small cap, and check the file
20/// was truncated rather than left to grow.
21#[test]
22fn the_hook_truncates_a_spool_past_the_cap() {
23 let dir = std::env::temp_dir().join(format!("signald-spoolcap-{}", std::process::id()));
24 let _ = std::fs::remove_dir_all(&dir);
25 std::fs::create_dir_all(&dir).unwrap();
26 let spool = dir.join("terminal.spool");
27
28 let script = format!(
29 r#"
30 export SIGNALD_SPOOL={spool}
31 export SIGNALD_SPOOL_MAX_BYTES=200
32 source {hook}
33 for i in {{1..40}}; do _signald_precmd; done
34 wc -c < $SIGNALD_SPOOL
35 "#,
36 spool = spool.display(),
37 hook = hook_path().display(),
38 );
39 let out = Command::new("zsh")
40 .arg("-c")
41 .arg(&script)
42 .output()
43 .expect("zsh runs the hook");
44 assert!(
45 out.status.success(),
46 "hook failed: {}",
47 String::from_utf8_lossy(&out.stderr)
48 );
49
50 let size: u64 = String::from_utf8_lossy(&out.stdout)
51 .trim()
52 .parse()
53 .expect("wc prints a size");
54 assert!(size <= 200, "spool grew to {size} bytes past a 200-byte cap");
55 assert!(size > 0, "truncation must not stop the current record landing");
56
57 let _ = std::fs::remove_dir_all(&dir);
58}