use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
static FIXTURE_SEQ: AtomicU64 = AtomicU64::new(0);
fn cardio(args: &[&str]) -> String {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(args)
.output()
.expect("cardio binary should run");
assert!(
out.status.success(),
"cardio exited {}: stderr: {:?}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
struct Fixture(std::path::PathBuf);
impl Drop for Fixture {
fn drop(&mut self) { std::fs::remove_file(&self.0).ok(); }
}
fn fixture(content: &str) -> Fixture {
let path = std::env::temp_dir().join(format!(
"cardio_test_{}_{}.log",
std::process::id(),
FIXTURE_SEQ.fetch_add(1, Ordering::Relaxed),
));
std::fs::write(&path, content).expect("write fixture");
Fixture(path)
}
#[test]
fn trims_timestamp_and_level() {
let path = fixture("2026-10-09T12:00:00Z INFO User login ok\n2026-10-09T12:00:01Z DEBUG INFORMATION retained\n");
let out = cardio(&[path.0.to_string_lossy().as_ref()]);
assert!(out.contains("User login ok"), "got: {out:?}");
assert!(out.contains("INFORMATION retained"), "word INFO inside INFORMATION must survive; got: {out:?}");
}
#[test]
fn empty_after_trim_is_skipped() {
let path = fixture("2026-10-09T12:00:00Z INFO\n2026-10-09T12:00:01Z DEBUG real event\n");
let out = cardio(&[path.0.to_string_lossy().as_ref()]);
assert!(!out.contains("\n2026-"), "pure-noise lines must vanish");
assert!(out.contains("real event"));
}
#[test]
fn since_excludes_earlier_days() {
let path = fixture(
"2026-10-08T23:00:00Z INFO day8\n2026-10-09T12:00:00Z INFO day9\n2025-01-01T00:00:00Z INFO old\n",
);
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--since", "2026-10-09"]);
assert!(out.contains("day9"));
assert!(!out.contains("day8"));
assert!(!out.contains("old"));
}
#[test]
fn groups_sessions_by_trace_id() {
let path = fixture(
"2026-10-09T12:00:00Z INFO req_42 start\n2026-10-09T12:00:01Z INFO req_42 fail\n2026-10-09T12:00:02Z INFO req_43 start\n",
);
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--trace", "req_([0-9]+)"]);
assert!(out.contains("--- [42] ---"));
assert!(out.contains("req_42 start") && out.contains("req_42 fail"));
assert!(out.contains("--- [43] ---"));
}
#[test]
fn json_output_is_lines_of_json() {
let path = fixture("2026-10-09T12:00:00Z INFO hello\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--json"]);
for line in out.lines().filter(|l| !l.is_empty()) {
let v: serde_json::Value = serde_json::from_str(line)
.unwrap_or_else(|e| panic!("each line must be JSON: {e} in {line:?}"));
assert!(v.get("content").is_some());
}
}
#[test]
fn space_separated_ts_compared_correctly() {
let path = fixture(
"2026-10-09 01:00:00 INFO early\n2026-10-09 07:00:00 INFO mid\n2026-10-09 09:00:00 INFO late\n",
);
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--since", "2026-10-09T05:00:00"]);
assert!(out.contains("mid"), "07:00 must pass a 05:00 bound; got: {out:?}");
assert!(out.contains("late"));
assert!(!out.contains("early"));
}
#[test]
fn space_separated_bounds_normalized_too() {
let path = fixture("2026-10-09 05:00:00 INFO early\n2026-10-09 11:00:00 INFO late\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "2026-10-09 09:00:00"]);
assert!(out.contains("early"), "got: {out:?}");
assert!(!out.contains("late"), "got: {out:?}");
}
#[test]
fn apache_ts_lines_bypass_range_filter() {
let path = fixture("09/Oct/2026:11:22:33 error disk full\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--since", "2026-01-01"]);
assert!(out.contains("disk full"), "non-ISO ts must pass, got: {out:?}");
}
#[test]
fn bracket_prefixed_ts_is_range_filtered() {
let path = fixture("[2026-10-09T12:00:00Z INFO boot]\n[2026-10-10T12:00:00Z INFO later]\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--since", "2026-10-10"]);
assert!(out.contains("later"), "got: {out:?}");
assert!(!out.contains("boot"), "prefixed ts must be filtered, got: {out:?}");
}
#[test]
fn date_bounds_keep_end_day() {
let path = fixture(
"2026-10-08T10:00:00Z INFO d8\n2026-10-09T10:00:00Z INFO d9\n2026-10-10T10:00:00Z INFO d10\n",
);
let out = cardio(&[
path.0.to_string_lossy().as_ref(),
"--since", "2026-10-08", "--until", "2026-10-09",
]);
assert!(out.contains("d8") && out.contains("d9"), "got: {out:?}");
assert!(!out.contains("d10"), "got: {out:?}");
}
#[test]
fn date_until_rolls_month() {
let path = fixture("2026-02-28T10:00:00Z INFO feb28\n2026-02-29T10:00:00Z INFO feb29\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "2026-03-01"]);
assert!(out.contains("feb28") && out.contains("feb29"), "got: {out:?}");
}
#[test]
fn summary_keeps_ids_with_first_last() {
let path = fixture(
"2026-10-09T12:00:00Z INFO req_42 start\n2026-10-09T12:00:01Z INFO req_42 mid\n2026-10-09T12:00:02Z INFO req_42 done\n2026-10-09T12:00:03Z INFO req_43 start\n",
);
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--summary", "--trace", "req_([0-9]+)"]);
assert!(out.contains("[42] 3 events"), "got: {out:?}");
assert!(out.contains("req_42 start") && out.contains("req_42 done"), "got: {out:?}");
assert!(!out.contains("req_42 mid"), "summary keeps only first/last, got: {out:?}");
assert!(out.contains("[43] 1 event"), "got: {out:?}");
}
#[test]
fn summary_json_has_id_count_bounds() {
let path = fixture("2026-10-09T12:00:00Z INFO req_42 start\n2026-10-09T12:00:02Z INFO req_42 done\n");
let out = cardio(&[
path.0.to_string_lossy().as_ref(),
"--summary", "--trace", "req_([0-9]+)", "--json",
]);
let lines: Vec<&str> = out.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(lines.len(), 1);
let v: serde_json::Value = serde_json::from_str(lines[0])
.unwrap_or_else(|e| panic!("summary line must be JSON: {e}"));
assert_eq!(v["id"], "42");
assert_eq!(v["count"], serde_json::json!(2));
assert!(v["first"].as_str().unwrap().contains("start"));
assert!(v["last"].as_str().unwrap().contains("done"));
}
#[test]
fn single_event_summary_prints_once() {
let path = fixture("2026-10-09T12:00:03Z INFO req_43 only one\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--summary", "--trace", "req_([0-9]+)"]);
assert!(out.contains("[43] 1 event"), "got: {out:?}");
assert_eq!(out.matches("only one").count(), 1, "1-event session must print its line once, got: {out:?}");
}
#[test]
fn stats_plus_summary_emits_nothing() {
let path = fixture("2026-10-09T12:00:00Z INFO req_42 start\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--stats", "--summary", "--trace", "req_([0-9]+)"]);
assert_eq!(out.trim(), "", "got: {out:?}");
}
#[test]
fn summary_last_is_exactly_one_event() {
let path = fixture(
"2026-10-09T12:00:00Z INFO req_42 start\n2026-10-09T12:00:01Z INFO req_42 mid\n2026-10-09T12:00:02Z INFO req_42 done\n",
);
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--summary", "--trace", "req_([0-9]+)"]);
assert!(out.lines().any(|l| l == "req_42 done"), "got: {out:?}");
assert!(!out.contains("donereq"), "got: {out:?}");
}
#[cfg(unix)]
#[test]
fn bq_csv_rows_are_clean_events() {
let stdout = fake_bq(
"timestamp,text\n2026-10-13T12:00:00Z,\"req_1 login, ok\"\n2026-10-13T12:00:02Z,\n",
&["--bq", "SELECT timestamp, text FROM t", "--json"],
);
let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(lines.len(), 1, "header + empty-text row are not events: {stdout:?}");
let v: serde_json::Value = serde_json::from_str(lines[0])
.unwrap_or_else(|e| panic!("line must be JSON: {e}"));
assert_eq!(v["content"], "req_1 login, ok", "no separator residue: {stdout:?}");
}
#[cfg(unix)]
#[test]
fn bq_crlf_text_field_flattens_to_one_line() {
let stdout = fake_bq(
"timestamp,text\n\"line1\r\nline2 more\",x\n",
&["--bq", "SELECT 1"],
);
let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(lines.len(), 1, "CRLF field must flatten to one event: {stdout:?}");
assert_eq!(lines[0], "line1 line2 more\tx", "no CR residue: {stdout:?}");
}
#[cfg(unix)]
#[test]
fn bq_lone_cr_content_is_preserved() {
let stdout = fake_bq("timestamp,text\n\"code:\r17\",x\n", &["--bq", "SELECT 1"]);
let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
assert_eq!(lines.len(), 1, "lone CR stays inside one event: {stdout:?}");
assert_eq!(lines[0], "code:\r17\tx", "no content byte destroyed: {stdout:?}");
}
#[cfg(unix)]
fn fake_bq(csv: &str, args: &[&str]) -> String {
let body = format!("#!/bin/sh\ncat <<'CARDIO_CSV'\n{csv}\nCARDIO_CSV\n");
let out = run_with_fake("bq", body.as_str(), args);
assert!(
out.status.success(),
"cardio exited {}: stderr: {:?}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
#[test]
fn invalid_first_ts_does_not_bypass_bounds() {
let path = fixture("x 2026-99-99T10:00:00 boot 2026-10-09T09:00:00 ok\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "2026-10-09T08:00:00"]);
assert!(!out.contains("boot"), "line must use its valid ts, got: {out:?}");
}
#[test]
fn fractional_seconds_compare_equal_at_bound() {
let path1 = fixture("2026-10-09T14:30:45.5 event\n");
let path2 = fixture("2026-10-09T14:30:45.5 event\n");
let dropped = cardio(&[path1.0.to_string_lossy().as_ref(), "--until", "2026-10-09T14:30:45.500"]);
assert!(!dropped.contains("event"), "equal instant fails the exclusive bound, got: {dropped:?}");
let kept = cardio(&[path2.0.to_string_lossy().as_ref(), "--since", "2026-10-09T14:30:45.500"]);
assert!(kept.contains("event"), "equal instant passes the inclusive bound, got: {kept:?}");
}
#[test]
fn long_fraction_bound_truncates_comparable() {
let p1 = fixture("2026-10-09T12:34:45.1 event\n");
let p2 = fixture("2026-10-09T12:34:45.1 event\n");
let s1 = p1.0.to_string_lossy().into_owned();
let s2 = p2.0.to_string_lossy().into_owned();
let kept = cardio(&["--since", "2026-10-09T12:34:45.1000000000", &s1]);
assert!(kept.contains("event"), "equal instant passes inclusive bound, got: {kept:?}");
let dropped = cardio(&["--until", "2026-10-09T12:34:45.1000000000", &s2]);
assert!(!dropped.contains("event"), "equal instant fails exclusive bound, got: {dropped:?}");
}
#[test]
fn tz_suffixed_minute_bound_pads_to_seconds() {
let p1 = fixture("2026-10-09T12:34:30 event\n");
let p2 = fixture("2026-10-09T12:34:30 event\n");
let s1 = p1.0.to_string_lossy().into_owned();
let s2 = p2.0.to_string_lossy().into_owned();
let kept = cardio(&["--since", "2026-10-09T12:34Z", &s1]);
assert!(kept.contains("event"), "mid-minute line passes the minute bound, got: {kept:?}");
let dropped = cardio(&["--until", "2026-10-09T12:34Z", &s2]);
assert!(!dropped.contains("event"), "mid-minute line must not cross the bound, got: {dropped:?}");
}
#[test]
fn until_long_fraction_keeps_before_bucket() {
let path = fixture("2026-10-09T12:34:45.9999999995 event\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "2026-10-09T12:34:45.9999999999"]);
assert!(out.contains("event"), "got: {out:?}");
}
#[test]
fn over_max_frac_digits_truncate_and_still_order() {
let path = fixture("2026-10-09T12:34:45.9999999999999999995 event\n");
let out = cardio(&[
path.0.to_string_lossy().as_ref(),
"--until", "2026-10-09T12:34:45.99999999999999999999",
]);
assert!(out.contains("event"), "truncated ..995 stays below the bound, got: {out:?}");
}
#[test]
fn non_zero_offset_strips_naive() {
let path = fixture("2026-10-09T13:00:00+01:00 event\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "2026-10-09T13:00:00"]);
assert!(!out.contains("event"), "naive equal instant fails the exclusive bound, got: {out:?}");
}
#[test]
fn zero_offset_and_z_compare_equal() {
let p1 = fixture("2026-10-09T12:34:45+00:00 event\n");
let p2 = fixture("2026-10-09T12:34:45+00:00 event\n");
let p3 = fixture("2026-10-09T12:34:45Z event\n");
let s1 = p1.0.to_string_lossy().into_owned();
let s2 = p2.0.to_string_lossy().into_owned();
let s3 = p3.0.to_string_lossy().into_owned();
let kept = cardio(&["--since", "2026-10-09T12:34:45Z", &s1]);
assert!(kept.contains("event"), "equal instant passes inclusive bound, got: {kept:?}");
let dropped = cardio(&["--until", "2026-10-09T12:34:45Z", &s2]);
assert!(!dropped.contains("event"), "equal instant fails exclusive bound, got: {dropped:?}");
let dropped2 = cardio(&["--until", "2026-10-09T12:34:45+00:00", &s3]);
assert!(!dropped2.contains("event"), "symmetric case (Z line, +00:00 bound), got: {dropped2:?}");
}
#[test]
fn minute_precision_ts_pads_to_seconds() {
let cfg = fixture("timestamp_regex = '\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}'\n");
let path = fixture("2026-10-09 12:00 event\n2026-10-09 11:00 old\n");
let cfgarg = cfg.0.to_string_lossy().to_string();
let patharg = path.0.to_string_lossy().to_string();
let kept = cardio(&["--config", &cfgarg, "--since", "2026-10-09T12:00:00", &patharg]);
assert!(kept.contains("event"), "12:00 (= 12:00:00) must pass --since, got: {kept:?}");
assert!(!kept.contains("old"), "got: {kept:?}");
let dropped = cardio(&["--config", &cfgarg, "--until", "2026-10-09T12:00:00", &patharg]);
assert!(!dropped.contains("event"), "exclusive --until drops the exact minute, got: {dropped:?}");
assert!(dropped.contains("old"), "11:00 stays behind the bound, got: {dropped:?}");
}
#[test]
fn date_only_ts_passes_its_own_since_day() {
let cfg = fixture("timestamp_regex = '\\d{4}-\\d{2}-\\d{2}'\n");
let path = fixture("2026-10-09 boot\n2026-10-10 later\n");
let out = cardio(&[
"--config", cfg.0.to_string_lossy().as_ref(),
"--since", "2026-10-09T00:00:00",
"--until", "2026-10-10T00:00:00",
path.0.to_string_lossy().as_ref(),
]);
assert!(out.contains("boot"), "same-day line must pass --since, got: {out:?}");
assert!(!out.contains("later"), "got: {out:?}");
}
#[test]
fn impossible_date_bound_is_rejected() {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(["--until", "2026-99-99"])
.output()
.expect("cardio binary should run");
assert!(!out.status.success(), "invalid bound must fail fast");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("not a full ISO"), "got: {err:?}");
}
#[test]
fn out_of_range_time_bound_is_rejected() {
for b in ["2026-10-09T99:99:99", "2026-10-09T12:99:00", "2026-10-09T24:00.5"] {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(["--since", b])
.output()
.expect("cardio binary should run");
assert!(!out.status.success(), "{b} must fail fast, got: {:?}", out.status);
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("not a full ISO"), "{b}: got: {err:?}");
}
}
#[test]
fn iso_end_of_day_24_00_compares_as_next_day() {
let path = fixture("2026-10-09T20:00:00Z early\n2026-10-09T24:00:00Z boot\n");
let p = path.0.to_string_lossy().into_owned();
let kept = cardio(&["--since", "2026-10-09T20:00:00", &p]);
assert!(kept.contains("boot") && kept.contains("early"), "got: {kept:?}");
let dropped = cardio(&["--until", "2026-10-10T00:00:00", &p]);
assert!(dropped.contains("early"), "got: {dropped:?}");
assert!(!dropped.contains("boot"), "24:00 == next-day 00:00 instant, got: {dropped:?}");
}
#[test]
fn minute_precision_eod_bound_clamps_to_next_day() {
let path = fixture("2026-10-09T20:00:00 boot\n2026-10-10T00:00:00 up\n");
let p = path.0.to_string_lossy().into_owned();
let kept = cardio(&["--since", "2026-10-09T24:00", &p]);
assert!(kept.contains("up") && !kept.contains("boot"), "got: {kept:?}");
let dropped = cardio(&["--until", "2026-10-09T24:00Z", &p]);
assert!(dropped.contains("boot") && !dropped.contains("up"), "got: {dropped:?}");
}
#[test]
fn minute_precision_line_enters_range_filter() {
let path = fixture("2026-10-01T05:00 early thing\n2026-10-13T05:00 late thing\n");
let p = path.0.to_string_lossy().into_owned();
let out = cardio(&["--since", "2026-10-09", &p]);
assert!(out.contains("late") && !out.contains("early"), "got: {out:?}");
}
#[test]
fn agent_card_never_goes_stale() {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.arg("--help")
.output()
.expect("cardio --help should run");
let help = String::from_utf8_lossy(&out.stdout);
let card = cardio(&["--agent-md"]);
for line in help.lines().map(str::trim_start).filter(|l| l.starts_with("--")) {
let name = line.split_whitespace().next().unwrap_or("");
if name == "--help" || name == "--version" {
continue;
}
assert!(
card.contains(name),
"--agent-md is missing the flag {name} (card must never be stale)"
);
}
}
#[test]
fn agent_md_prints_card() {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.arg("--agent-md")
.output()
.expect("cardio binary should run");
assert!(out.status.success(), "got: {:?}", out.status);
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("lean by default") && s.to_lowercase().contains("unique") && s.contains("--raw") && s.contains("--since"),
"got: {s:?}"
);
}
#[test]
fn default_pipeline_dedups_and_caps_raw_skips_it() {
let mut content = String::new();
content.push_str("2026-10-09T12:00:00 repeated start\n");
for i in 0..2100 {
content.push_str(&format!("2026-10-09T12:{:02}:{:02} unique-{i:04}\n", i / 60, i % 60));
}
content.push_str("2026-10-09T23:00:00 repeated start\n");
let path = fixture(&content);
let p = path.0.to_string_lossy().into_owned();
let out = cardio(&[&p]);
let count = out.matches("repeated start").count();
assert_eq!(count, 0, "head of the range must leave the window, got {count}");
assert!(out.contains("unique-2099") && !out.contains("unique-0099"), "cap 2000 must keep the most recent");
let capped = cardio(&["--tail", "5", &p]);
assert_eq!(capped.lines().count(), 5, "got {}", capped.lines().count());
assert!(capped.contains("unique-2099"), "explicit cap keeps the latest: {capped:?}");
let raw = cardio(&["--raw", &p]);
assert_eq!(raw.matches("repeated start").count(), 2, "--raw must keep repeats");
assert!(raw.contains("unique-2099"), "--raw must keep the tail");
}
#[test]
fn tail_keeps_last_n_after_dedup() {
let mut content = String::new();
content.push_str("2026-10-09T12:00:00 repeated start\n");
for i in 0..2100 {
content.push_str(&format!("2026-10-09T12:{:02}:{:02} unique-{i:04}\n", i / 60, i % 60));
}
content.push_str("2026-10-09T23:00:00 repeated start\n");
let path = fixture(&content);
let p = path.0.to_string_lossy().into_owned();
let out = cardio(&["--tail", "5", &p]);
let lines: Vec<&str> = out.lines().collect();
assert_eq!(lines.len(), 5, "got {}", lines.len());
assert!(out.contains("unique-2099"), "latest line kept: {out:?}");
assert!(!out.contains("unique-0000") && !out.contains("repeated start"), "early lines excluded: {out:?}");
let j = cardio(&["--tail", "1", "--json", &p]);
assert!(j.contains("\"index\":") && j.lines().count() == 1, "got: {j:?}");
}
#[test]
fn lean_signals_what_it_dropped() {
let path = fixture("2026-10-09T12:00:00 dup\n2026-10-09T12:00:01 dup\n2026-10-09T12:00:02 keep\n");
let p = path.0.to_string_lossy().into_owned();
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args([&p])
.output()
.expect("cardio binary should run");
assert!(out.status.success(), "got: {:?}", out.status);
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("dedup dropped 1 repeat line"), "got: {err:?}");
assert!(!err.contains("kept the last"), "no cap in a short run: {err:?}");
let big: String = (0..2001).map(|i| format!("2026-10-09T12:{:02}:{:02} u{i:04}\n", i / 60, i % 60)).collect();
let path = fixture(&big);
let p = path.0.to_string_lossy().into_owned();
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args([&p])
.output()
.expect("cardio binary should run");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("kept the last 2000 lines"), "cap signal missing: {err:?}");
assert!(!err.contains("the rest of the input was not read"), "no early-cut in lean: {err:?}");
}
#[test]
fn stats_json_emits_patterns_on_stdout() {
let path = fixture("2026-10-09T12:00:00 alpha\n2026-10-09T12:00:01 beta\n2026-10-09T12:00:02 alpha\n");
let p = path.0.to_string_lossy().into_owned();
let out = cardio(&["--stats", "--json", &p]);
assert!(out.contains("\"count\":2") && out.contains("alpha"), "got: {out:?}");
assert!(out.contains("\"count\":1") && out.contains("beta"), "got: {out:?}");
}
#[test]
fn frac_on_minutes_bound_is_rejected() {
let path = fixture("2026-10-09T12:34:30 event\n");
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args([path.0.to_string_lossy().as_ref(), "--since", "2026-10-09T12:34.5"])
.output()
.expect("cardio binary should run");
assert!(!out.status.success(), "invalid bound must fail fast");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("not a full ISO"), "got: {err:?}");
}
#[test]
fn last_year_bound_keeps_end_day() {
let path = fixture("9999-12-31T22:00:00Z late\n9999-12-30T10:00:00Z earlier\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "9999-12-31"]);
assert!(out.contains("late"), "end day survives the last --until, got: {out:?}");
assert!(out.contains("earlier"), "got: {out:?}");
}
#[test]
fn over_max_frac_line_collapses_to_max_shape() {
let path = fixture(
"2026-10-09T12:34:45.99999999999999999995 short\n2026-10-09T12:34:45.99999999999999999999 long\n2026-10-09T12:34:46 later\n",
);
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "2026-10-09T12:34:46"]);
assert!(out.contains("short"), "20-digit ..995 stays under :46, got: {out:?}");
assert!(out.contains("long"), "20-digit ..999 stays under :46, got: {out:?}");
assert!(!out.contains("later"), ":46.0 fails the exclusive bound, got: {out:?}");
let out = cardio(&[
path.0.to_string_lossy().as_ref(),
"--until", "2026-10-09T12:34:45.9999999999999999999",
]);
assert!(
!out.contains("short") && !out.contains("long"),
"both truncations collapse onto the equal instant, got: {out:?}"
);
}
fn cardio_out(args: &[&str]) -> (String, String) {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(args)
.output()
.expect("cardio binary should run");
assert!(
out.status.success(),
"cardio exited {}: stderr: {:?}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
(
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
#[cfg(unix)]
fn run_with_fake(tool: &str, script_body: &str, args: &[&str]) -> std::process::Output {
use std::os::unix::fs::PermissionsExt;
let fake = std::env::temp_dir().join(format!(
"cardio_fakebin_{}_{}",
tool,
FIXTURE_SEQ.fetch_add(1, Ordering::Relaxed),
));
std::fs::create_dir_all(&fake).expect("mkdir fakebin");
let script = fake.join(tool);
std::fs::write(&script, script_body).expect("write fake script");
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755))
.expect("make fake executable");
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(args)
.env(
"PATH",
format!("{}:{}", fake.display(), std::env::var("PATH").unwrap_or_default()),
)
.output()
.expect("cardio binary should run");
std::fs::remove_dir_all(&fake).ok();
out
}
#[cfg(unix)]
fn fake_bq_argv(csv: &str, args: &[&str]) -> (String, String) {
let body = format!(
"#!/bin/sh\nfor a in \"$@\"; do echo \"ARG:$a\" >&2; done\ncat <<'CARDIO_CSV'\n{csv}\nCARDIO_CSV\n"
);
let out = run_with_fake("bq", body.as_str(), args);
assert!(
out.status.success(),
"cardio exited {}: stderr: {:?}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
(
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
#[test]
fn raw_is_verbatim_timestamps_and_levels_survive() {
let path = fixture("2026-10-09T12:00:00Z INFO User login ok\n\n2026-10-09T12:00:01Z app boom\n");
let (out, _) = cardio_out(&[path.0.to_string_lossy().as_ref(), "--raw"]);
assert!(out.contains("2026-10-09T12:00:00Z"), "--raw must not strip timestamps: {out:?}");
assert!(out.contains("INFO"), "--raw must not strip boilerplate: {out:?}");
assert!(out.contains("\n\n"), "--raw keeps blank lines: {out:?}");
let (lean, _) = cardio_out(&[path.0.to_string_lossy().as_ref()]);
assert!(!lean.contains("2026-10-09T12:00:00Z"), "lean strips the timestamp: {lean:?}");
}
#[cfg(unix)]
#[test]
fn bq_argv_carries_cost_and_project_flags() {
let (_, err) = fake_bq_argv(
"timestamp,text\nt1,hello\n",
&[
"--bq", "SELECT 1",
"--project", "other-project",
"--dry-run",
"--max-bytes-billed", "1111",
"--bq-args=--location=eu",
],
);
for flag in ["--quiet", "--project_id=other-project", "--dry_run", "--maximum_bytes_billed=1111", "--location=eu"] {
assert!(err.contains(&format!("ARG:{flag}")), "bq argv missing {flag}: {err:?}");
}
assert!(err.contains("Note: bq columns: timestamp | text"), "columns note missing: {err:?}");
let (_, err) = fake_bq_argv("timestamp,text\nt1,hello\n", &["--bq", "SELECT 1"]);
assert!(err.contains("ARG:--quiet"), "default bq argv must be quiet: {err:?}");
assert!(!err.contains("project_id"), "no project flag by default: {err:?}");
assert!(!err.contains("dry_run"), "no dry-run by default: {err:?}");
}
#[test]
fn bq_flags_error_without_bq() {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(["--dry-run"])
.output()
.expect("cardio binary should run");
assert!(!out.status.success(), "--dry-run without --bq must fail loudly");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("only works with --bq"), "usage error missing: {err:?}");
}
#[test]
fn trace_json_full_sessions_preserve_every_event() {
let path = fixture(
"2026-10-09T12:00:00Z req_id=a start\n\
2026-10-09T12:00:01Z req_id=a end\n\
2026-10-09T12:00:02Z req_id=b solo\n",
);
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--trace", "req_id=([a-z])", "--json"]);
let a: serde_json::Value = {
let line = out.lines().find(|l| l.contains("\"id\":\"a\"")).expect("session a line");
serde_json::from_str(line).expect("session a parses as JSON")
};
let events = a["events"].as_array().expect("events array");
assert_eq!(events.len(), 2, "every event of the session survives: {a:?}");
assert!(events[0].as_str().unwrap().contains("start"), "first event is a start: {a:?}");
}
#[test]
fn stdin_source_works_without_a_file() {
use std::io::Write as _;
let mut child = Command::new(env!("CARGO_BIN_EXE_cardio"))
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.stdin(std::process::Stdio::piped())
.spawn()
.expect("cardio spawned");
child
.stdin
.as_mut()
.unwrap()
.write_all(b"2026-10-09T12:00:00Z INFO via stdin\n")
.expect("stdin write");
let out = child.wait_with_output().expect("cardio waits");
assert!(out.status.success(), "exited {}: {}", out.status, String::from_utf8_lossy(&out.stderr));
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("via stdin"), "stdin read: {stdout:?}");
}
#[test]
fn stats_folds_digit_runs_into_one_pattern() {
let path = fixture(
"op sid=123 ok\nop sid=456 ok\nop sid=789 ok\nplain line\n",
);
let (out, _) = cardio_out(&[path.0.to_string_lossy().as_ref(), "--stats", "--json"]);
let patterns: Vec<String> = out.lines().map(String::from).collect();
assert!(
patterns.iter().any(|p| p.contains("\"pattern\":\"op sid=# ok\""))
&& patterns.iter().any(|p| p.contains("\"count\":3")),
"digit runs fold to one pattern: {patterns:?}"
);
assert!(!patterns.iter().any(|p| p.contains("op sid=1")), "no per-UUID lines: {patterns:?}");
}
#[cfg(unix)]
fn fake_bq_script(script_body: &str, args: &[&str]) -> std::process::Output {
run_with_fake("bq", script_body, args)
}
#[cfg(unix)]
#[test]
fn gcloud_or_filter_is_parenthesized_on_pushdown() {
let echo = fake_gcloud(&[
"--gcloud", "resource.type=gce OR resource.type=k8s",
"--since", "2026-10-15",
]);
assert!(echo.contains("(resource.type=gce OR resource.type=k8s) AND timestamp>="), "filter wrapped: {echo:?}");
}
#[cfg(unix)]
#[test]
fn fetcher_zero_rows_still_states_volume() {
let (_, err) = fake_bq_argv("timestamp,text\n", &["--bq", "SELECT 1"]);
assert!(err.contains("Note: bq streamed 0 lines."), "zero-row witness: {err:?}");
}
#[cfg(unix)]
#[test]
fn timeout_fires_even_when_child_closes_stdout_alive() {
let out = fake_bq_script(
"#!/bin/sh\necho \"timestamp,text\nrow\n\"\nexec 1>&-\nsleep 30\n",
&["--bq", "SELECT 1", "--raw", "--timeout", "1"],
);
assert!(!out.status.success());
assert!(
String::from_utf8_lossy(&out.stderr).contains("killed after --timeout 1s"),
"watchdog killed it: {:?}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn max_field_len_keeps_lines_distinct_for_dedup() {
let path = fixture(&format!(
"{}-one\n{}-two\n",
"x".repeat(300).as_str(),
"x".repeat(300).as_str(),
));
let out = cardio(&[
path.0.to_string_lossy().as_ref(),
"--max-field-len", "10",
]);
assert_eq!(out.lines().count(), 2, "no post-clamp dedup collision: {out:?}");
assert!(out.contains("one>") || out.contains("…"), "clamped: {out:?}");
}
#[test]
fn top_n_json_stream_stays_parseable() {
let path = fixture(
"2026-10-09T12:00:00Z req_id=a e1\n2026-10-09T12:00:01Z req_id=a e2\n 2026-10-09T12:00:02Z req_id=b e1\n2026-10-09T12:00:03Z req_id=b e2\n 2026-10-09T12:00:04Z req_id=b e3\n",
);
let (out, err) = cardio_out(&[
path.0.to_string_lossy().as_ref(),
"--trace", "req_id=([a-z])", "--json", "--top", "1",
]);
for line in out.lines() {
serde_json::from_str::<serde_json::Value>(line)
.unwrap_or_else(|e| panic!("--top --json keeps stdout pure NDJSON: {e:?} in {line:?}"));
}
assert!(err.contains("1 smaller session dropped"), "json witness on stderr: {err:?}");
}
#[cfg(unix)]
#[test]
fn fetcher_failure_exits_nonzero() {
let out = fake_bq_script(
"#!/bin/sh\necho \"timestamp,text\nevent one\n\"\necho job_abc >&2\nexit 1\n",
&["--bq", "SELECT 1", "--raw"],
);
assert!(!out.status.success(), "failure must propagate, got success");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("bq exited"), "failure reported: {err:?}");
}
#[cfg(unix)]
#[test]
fn timeout_kills_a_hung_fetcher() {
let out = fake_bq_script(
"#!/bin/sh\necho \"timestamp,text\nevent one\n\"\nsleep 30\n",
&["--bq", "SELECT 1", "--raw", "--timeout", "1"],
);
assert!(!out.status.success(), "killed run must fail, got success");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("killed after --timeout 1s"), "timeout notice: {err:?}");
}
#[cfg(unix)]
#[test]
fn timeout_errors_without_a_fetcher() {
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(["--timeout", "5"])
.output()
.expect("cardio binary should run");
assert!(!out.status.success());
assert!(
String::from_utf8_lossy(&out.stderr).contains("only works with --bq, --gcloud, or --k8s"),
"usage error missing: {:?}",
String::from_utf8_lossy(&out.stderr)
);
}
#[cfg(unix)]
fn fake_gcloud(args: &[&str]) -> String {
let out = run_with_fake("gcloud", "#!/bin/sh\necho \"$3\" >&2\n", args);
assert!(
out.status.success(),
"cardio exited {}: stderr: {:?}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stderr).into_owned()
}
#[cfg(unix)]
#[test]
fn gcloud_filter_embeds_plain_seconds() {
let echo = fake_gcloud(&[
"--gcloud", "resource.type=x", "--since", "2026-10-09", "--until", "2026-10-09T12:34:45.5",
]);
assert!(echo.contains("timestamp>=\"2026-10-09T00:00:00\""), "got: {echo:?}");
assert!(echo.contains("timestamp<\"2026-10-09T12:34:46\""), "until embeds < next second");
assert!(!echo.contains("=0000000000"), "bare 19-digit pad leaked to the server: {echo:?}");
assert!(!echo.contains(".5\""), "frac leaked to the server literal: {echo:?}");
}
#[cfg(unix)]
#[test]
fn gcloud_last_year_until_skips_embed() {
let echo = fake_gcloud(&["--gcloud", "resource.type=x", "--until", "9999-12-31"]);
assert!(echo.contains("resource.type=x"), "got: {echo:?}");
assert!(!echo.contains("timestamp<"), "no next second -> no until embed, got: {echo:?}");
}
#[cfg(unix)]
#[test]
fn gcloud_until_day_rollover_embeds_next_day() {
let echo = fake_gcloud(&["--gcloud", "resource.type=x", "--until", "2026-10-09T23:59:59"]);
assert!(
echo.contains("timestamp<\"2026-10-10T00:00:00\""),
"day-roll embeds one well-formed literal, got: {echo:?}"
);
assert!(!echo.contains("T00:00:00T"), "double datetime suffix leaked: {echo:?}");
}
#[test]
fn last_year_final_second_fracs_survive_until() {
let path = fixture("9999-12-31T23:59:59.5 last instant\n9999-12-31T12:00:00 day\n");
let out = cardio(&[path.0.to_string_lossy().as_ref(), "--until", "9999-12-31"]);
assert!(out.contains("day"), "got: {out:?}");
assert!(out.contains("last instant"), "mid-second instant stays under the clamp, got: {out:?}");
}
#[cfg(windows)]
fn run_with_fake(tool: &str, bat_body: &str, args: &[&str]) -> std::process::Output {
let fake = std::env::temp_dir().join(format!(
"cardio_fakebin_{}_{}",
tool,
FIXTURE_SEQ.fetch_add(1, Ordering::Relaxed),
));
std::fs::create_dir_all(&fake).expect("mkdir fakebin");
let script = fake.join(format!("{tool}.cmd"));
std::fs::write(&script, bat_body).expect("write fake script");
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(args)
.env("PATH", format!("{};C:\\Windows\\System32", fake.display()))
.output()
.expect("cardio binary should run");
std::fs::remove_dir_all(&fake).ok();
out
}
#[cfg(windows)]
#[test]
fn win_bq_csv_rows_flow_and_join_tab() {
let out = run_with_fake(
"bq",
"@echo off\r\necho timestamp,text\r\necho e1,x\r\necho e1,x\r\n",
&["--bq", "SELECT 1", "--raw"],
);
assert!(out.status.success(), "stderr: {:?}", String::from_utf8_lossy(&out.stderr));
let stdout = String::from_utf8_lossy(&out.stdout);
assert_eq!(stdout.lines().count(), 2, "header skipped: {stdout:?}");
assert!(stdout.contains("e1\tx"), "tab join: {stdout:?}");
}
#[cfg(windows)]
#[test]
fn win_fetcher_failure_exits_nonzero() {
let out = run_with_fake(
"bq",
"@echo off\r\necho timestamp,text\r\necho job_abc\r\nexit /b 1\r\n",
&["--bq", "SELECT 1", "--raw"],
);
assert!(!out.status.success(), "failure must propagate");
assert!(
String::from_utf8_lossy(&out.stderr).contains("bq exited"),
"fetcher failure reported: {:?}",
String::from_utf8_lossy(&out.stderr)
);
}
#[cfg(windows)]
#[test]
fn win_timeout_kills_a_hung_fetcher() {
let out = run_with_fake(
"bq",
"@echo off\r\necho timestamp,text\r\necho row one\r\nping -n 30 127.0.0.1 >NUL\r\n",
&["--bq", "SELECT 1", "--raw", "--timeout", "1"],
);
assert!(!out.status.success(), "killed run must fail");
assert!(
String::from_utf8_lossy(&out.stderr).contains("killed after --timeout 1s"),
"timeout notice: {:?}",
String::from_utf8_lossy(&out.stderr)
);
}
#[cfg(windows)]
#[test]
fn win_k8s_since_pushdown() {
let out = run_with_fake(
"kubectl",
"@echo off\r\necho ARGS:%* 1>&2\r\n",
&["--k8s", "deployment/my-api", "--since", "2026-10-19"],
);
assert!(out.status.success(), "stderr: {:?}", String::from_utf8_lossy(&out.stderr));
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("--since-time=2026-10-19T00:00:00Z"), "pushdown literal: {err:?}");
assert!(err.contains("deployment/my-api"), "selector passed: {err:?}");
}
#[cfg(unix)]
#[test]
fn fetcher_default_window_keeps_head_not_tail() {
let mut csv = String::from("timestamp,text\n");
for i in 0..2100 {
csv.push_str(&format!("2026-10-09T12:{:02}:{:02} row-{i:04}\n", i / 60, i % 60));
}
let out = fake_bq(&csv, &["--bq", "SELECT 1"]);
assert!(out.contains("row-0000"), "head keeps the first rows: {out:?}");
assert!(!out.contains("row-2099"), "tail rows beyond cap drop: {out:?}");
let (_, err) = fake_bq_argv(&csv, &[
"--bq", "SELECT 1", "--head", "2100",
]);
assert!(err.contains("streamed 2100"), "explicit head reads all: {err:?}");
}
#[cfg(unix)]
#[test]
fn skip_pages_bq_results() {
let mut csv = String::from("timestamp,text\n");
for i in 0..20 {
csv.push_str(&format!("2026-10-09T12:00:{:02} row-{i:02}\n", i));
}
let page1 = fake_bq(&csv, &["--bq", "SELECT 1", "--head", "10"]);
assert!(page1.contains("row-00") && !page1.contains("row-10"), "page 1: {page1:?}");
let page2 = fake_bq(&csv, &["--bq", "SELECT 1", "--skip", "10", "--head", "10"]);
assert!(page2.contains("row-10") && page2.contains("row-19"), "page 2: {page2:?}");
assert!(!page2.contains("row-00"), "page 2 excludes page 1: {page2:?}");
}
#[test]
fn trace_default_caps_events_at_head() {
let mut content = String::new();
for i in 0..2100 {
content.push_str(&format!("2026-10-09T12:{:02}:{:02} req_id={i:04} event\n", i / 60, i % 60));
}
let path = fixture(&content);
let p = path.0.to_string_lossy().into_owned();
let (out, err) = cardio_out(&[&p, "--trace", "req_id=([a-z0-9-]+)", "--summary", "--json"]);
assert_eq!(out.lines().count(), 2000, "default head cap on sessions: {}", out.lines().count());
assert!(out.contains("\"id\":\"0050\"") || out.contains("id\":\"050"), "early ids kept: {out:?}");
assert!(err.contains("head cap (2000 lines)"), "cap witness: {err:?}");
}
#[cfg(unix)]
#[test]
fn k8s_until_binds_locally_and_lines_carry_timestamps() {
let out = run_with_fake(
"kubectl",
"#!/bin/sh\necho \"ARGS:$*\" >&2\necho \"2026-10-19T10:00:00Z in-window\"\necho \"2026-10-20T10:00:00Z out-window\"\n",
&["--k8s", "deployment/my-api", "--since", "2026-10-19", "--until", "2026-10-20T00:00:00"],
);
assert!(out.status.success(), "stderr: {:?}", String::from_utf8_lossy(&out.stderr));
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(stdout.contains("in-window"), "kept: {stdout:?}");
assert!(!stdout.contains("out-window"), "--until EXCLUDES (was a silent no-op): {stdout:?}");
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("--timestamps"), "ts line format on for local bounds: {err:?}");
assert!(err.contains("--since-time=2026-10-19T00:00:00Z"), "since pushdown intact: {err:?}");
}
#[test]
fn dedup_fold_collapses_digit_only_differences() {
let path = fixture(
"2026-10-09T12:00:00 op sid=111 ok\n\
2026-10-09T12:00:01 op sid=222 ok\n\
2026-10-09T12:00:02 op distinct event\n\
2026-10-09T12:00:03 op sid=999 ok\n",
);
let p = path.0.to_string_lossy().into_owned();
let out = cardio(&[&p]);
assert_eq!(out.matches("op sid").count(), 3, "exact keeps all: {out:?}");
let (folded, err) = cardio_out(&[&p, "--dedup-fold"]);
assert_eq!(folded.matches("op sid").count(), 1, "fold collapses: {folded:?}");
assert!(folded.contains("op distinct"), "different content stays: {folded:?}");
assert!(err.contains("fold-dedup collapsed"), "fold announced: {err:?}");
}
#[cfg(unix)]
#[test]
fn cardio_max_bytes_env_guard() {
let (_, err) = fake_bq_argv("timestamp,text\nt1,hello\n", &["--bq", "SELECT 1"]);
let _ = err;
use std::os::unix::fs::PermissionsExt as _;
let fake = std::env::temp_dir().join(format!("cardio_envbq_{}", FIXTURE_SEQ.fetch_add(1, Ordering::Relaxed)));
std::fs::create_dir_all(&fake).expect("mkdir");
let script = fake.join("bq");
std::fs::write(&script, "#!/bin/sh\necho ARG:$* >&2\ntrue\n").expect("write");
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).expect("chmod");
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(["--bq", "SELECT 1"])
.env("PATH", format!("{}:{}", fake.display(), std::env::var("PATH").unwrap_or_default()))
.env("CARDIO_MAX_BYTES", "100GB")
.output()
.expect("cardio run");
assert!(out.status.success(), "stderr: {:?}", String::from_utf8_lossy(&out.stderr));
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("--maximum_bytes_billed=107374182400"), "env guard pushed: {stderr:?}");
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(["--bq", "SELECT 1", "--max-bytes-billed", "5"])
.env("PATH", format!("{}:{}", fake.display(), std::env::var("PATH").unwrap_or_default()))
.env("CARDIO_MAX_BYTES", "100GB")
.output()
.expect("cardio run");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("--maximum_bytes_billed=5"), "explicit flag wins: {stderr:?}");
assert!(!stderr.contains("--maximum_bytes_billed=107374182400"), "env suppressed: {stderr:?}");
let out = Command::new(env!("CARGO_BIN_EXE_cardio"))
.args(["--bq", "SELECT 1"])
.env("PATH", format!("{}:{}", fake.display(), std::env::var("PATH").unwrap_or_default()))
.env("CARDIO_MAX_BYTES", "0")
.output()
.expect("cardio run");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(!stderr.contains("maximum_bytes_billed"), "0 disables: {stderr:?}");
std::fs::remove_dir_all(&fake).ok();
}
#[cfg(unix)]
#[test]
fn group_col_counts_bq_columns() {
let (out, err) = fake_bq_argv(
"ts,day,status\n\
2026-10-09T12:00:00,mon,ok\n\
2026-10-09T13:00:00,mon,ok\n\
2026-10-09T14:00:00,tue,fail\n",
&["--bq", "SELECT 1", "--group-col", "status", "--json"],
);
assert!(out.contains("\"pattern\":\"ok\"") && out.contains("\"count\":2"), "ok group: {out:?}");
assert!(out.contains("\"pattern\":\"fail\"") && out.contains("\"count\":1"), "fail group: {out:?}");
assert!(!out.contains("2026-10-09"), "census emits counts, not rows: {out:?}");
let _ = err;
let (out, _) = fake_bq_argv(
"ts,day,status\n2026-10-09T12:00:00,mon,ok\n2026-10-09T13:00:00,tue,fail\n",
&["--bq", "SELECT 1", "--group-col", "day,status", "--json"],
);
assert!(out.contains("mon|ok"), "multi-col key: {out:?}");
let body = "#!/bin/sh\ncat <<'CSV'\nts,day,status\n2026-10-09T12:00:00,mon,ok\nCSV\n";
let out = run_with_fake(
"bq", body,
&["--bq", "SELECT 1", "--group-col", "nope"],
);
assert!(!out.status.success(), "exit 2 on unknown column");
assert_eq!(out.status.code(), Some(2), "usage error: {:?}", out.status.code());
let e = String::from_utf8_lossy(&out.stderr);
assert!(e.contains("nope") && e.contains("not in the bq header"), "{e:?}");
}