use super::super::args::{
Cli, Commands, DaemonCommands, DaemonProfileCommands, RustPlanBackendArg, RustPlanCommands,
KNOWN_SUBCOMMANDS,
};
use super::super::rust_plan::rust_plan_gha_version;
use super::super::session::{
session_stats_error_json, session_stats_json, session_stats_unavailable_json,
session_summary_warnings, SessionStartPrivateOptions,
};
#[test]
fn rust_plan_cli_parses_validate_restore_save() {
use clap::Parser;
let validate = Cli::try_parse_from([
"zccache",
"rust-plan",
"validate",
"--plan",
"plan.json",
"--json",
])
.unwrap();
assert!(matches!(
validate.command,
Some(Commands::RustPlan {
action: RustPlanCommands::Validate { json: true, .. }
})
));
let restore = Cli::try_parse_from([
"zccache",
"rust-plan",
"restore",
"--plan",
"plan.json",
"--backend",
"local",
"--session-id",
"session-123",
"--endpoint",
"tcp:127.0.0.1:9",
"--journal",
"session.jsonl",
"--cache-dir",
".cache/rust-plan",
])
.unwrap();
assert!(matches!(
restore.command,
Some(Commands::RustPlan {
action: RustPlanCommands::Restore {
backend: RustPlanBackendArg::Local,
session_id: Some(_),
endpoint: Some(_),
journal: Some(_),
..
}
})
));
let save = Cli::try_parse_from([
"zccache",
"rust-plan",
"save",
"--plan",
"plan.json",
"--backend",
"gha",
])
.unwrap();
assert!(matches!(
save.command,
Some(Commands::RustPlan {
action: RustPlanCommands::Save {
backend: RustPlanBackendArg::Gha,
..
}
})
));
let restore_layered = Cli::try_parse_from([
"zccache",
"rust-plan",
"restore-layered",
"--plan",
"plan.json",
"--base-cache-dir",
".cache/base",
"--delta-cache-dir",
".cache/delta",
])
.unwrap();
assert!(matches!(
restore_layered.command,
Some(Commands::RustPlan {
action: RustPlanCommands::RestoreLayered { .. }
})
));
let save_delta = Cli::try_parse_from([
"zccache",
"rust-plan",
"save-delta",
"--plan",
"plan.json",
"--base-cache-dir",
".cache/base",
"--delta-cache-dir",
".cache/delta",
])
.unwrap();
assert!(matches!(
save_delta.command,
Some(Commands::RustPlan {
action: RustPlanCommands::SaveDelta { .. }
})
));
}
#[test]
fn rust_plan_session_stats_json_separates_compile_cache_stats() {
let stats = crate::protocol::SessionStats {
duration_ms: 1000,
compilations: 10,
hits: 7,
misses: 3,
non_cacheable: 2,
errors: 1,
errors_cached: 1,
time_saved_ms: 250,
unique_sources: 8,
bytes_read: 1024,
bytes_written: 2048,
lookup_outcomes: crate::protocol::LookupOutcomes {
depgraph_hit_artifact_hit: 7,
depgraph_hit_artifact_miss: 2,
depgraph_cold_skip: 3,
depgraph_other_miss: [("headers_changed".to_string(), 1)].into_iter().collect(),
},
phase_profile: None,
};
let json = session_stats_json("session-123", &stats);
assert_eq!(json["status"], "ok");
assert_eq!(json["session_id"], "session-123");
assert_eq!(json["compilations"], 10);
assert_eq!(json["hits"], 7);
assert_eq!(json["misses"], 3);
assert_eq!(json["hit_rate"].as_f64().unwrap(), 0.7);
assert_eq!(json["lookup_outcomes"]["depgraph_hit_artifact_hit"], 7);
assert_eq!(json["lookup_outcomes"]["depgraph_hit_artifact_miss"], 2);
assert_eq!(json["lookup_outcomes"]["depgraph_cold_skip"], 3);
assert_eq!(
json["lookup_outcomes"]["depgraph_other_miss"]["headers_changed"],
1
);
}
#[test]
fn session_summary_warning_thresholds_detect_degraded_cache() {
let mut stats = session_stats_fixture();
stats.lookup_outcomes.depgraph_hit_artifact_hit = 80;
stats.lookup_outcomes.depgraph_hit_artifact_miss = 20;
let warnings = session_summary_warnings(&stats, false, false);
assert_eq!(warnings.len(), 1);
let rendered = warnings[0].render(false);
assert!(rendered.starts_with("WARNING: 20.0% of depgraph hits"));
assert!(rendered.contains("#680 / #796 SI-3"));
assert_eq!(
warnings[0].render(true),
format!("\x1b[33m{rendered}\x1b[0m")
);
}
#[test]
fn session_summary_warning_thresholds_detect_cold_skip_with_depgraph() {
let mut stats = session_stats_fixture();
stats.lookup_outcomes.depgraph_hit_artifact_hit = 10;
stats.lookup_outcomes.depgraph_cold_skip = 60;
stats.lookup_outcomes.depgraph_other_miss =
[("headers_changed".to_string(), 10)].into_iter().collect();
let warnings = session_summary_warnings(&stats, true, false);
assert_eq!(warnings.len(), 1);
let rendered = warnings[0].render(false);
assert!(rendered.starts_with("WARNING: 75.0% of lookups returned cold_skip"));
assert!(rendered.contains("#320 / #796 SI-2"));
assert!(
session_summary_warnings(&stats, false, false).is_empty(),
"cold_skip warning requires a populated depgraph on disk"
);
}
#[test]
fn session_summary_warning_thresholds_gate_catastrophic_hit_rate_on_verbose() {
let mut stats = session_stats_fixture();
stats.compilations = 80;
stats.hits = 2;
stats.misses = 78;
assert!(session_summary_warnings(&stats, true, false).is_empty());
let warnings = session_summary_warnings(&stats, true, true);
assert_eq!(warnings.len(), 1);
let rendered = warnings[0].render(false);
assert!(rendered.starts_with("WARNING: catastrophic hit rate (2.5%)"));
assert!(rendered.contains("over 80 compilations"));
}
#[test]
fn session_summary_warning_thresholds_stay_quiet_for_healthy_session() {
let mut stats = session_stats_fixture();
stats.compilations = 100;
stats.hits = 90;
stats.misses = 10;
stats.lookup_outcomes.depgraph_hit_artifact_hit = 90;
stats.lookup_outcomes.depgraph_hit_artifact_miss = 1;
stats.lookup_outcomes.depgraph_cold_skip = 2;
stats.lookup_outcomes.depgraph_other_miss =
[("headers_changed".to_string(), 7)].into_iter().collect();
assert!(session_summary_warnings(&stats, true, true).is_empty());
}
fn session_stats_fixture() -> crate::protocol::SessionStats {
crate::protocol::SessionStats {
duration_ms: 1000,
compilations: 10,
hits: 7,
misses: 3,
non_cacheable: 0,
errors: 0,
errors_cached: 0,
time_saved_ms: 0,
unique_sources: 8,
bytes_read: 0,
bytes_written: 0,
lookup_outcomes: crate::protocol::LookupOutcomes::default(),
phase_profile: None,
}
}
#[test]
fn session_end_accepts_json_flag() {
use clap::Parser;
let cli = Cli::try_parse_from(["zccache", "session-end", "session-123", "--json"]).unwrap();
assert!(matches!(
cli.command,
Some(Commands::SessionEnd { json: true, .. })
));
}
#[test]
fn session_stats_accepts_json_flag() {
use clap::Parser;
let cli = Cli::try_parse_from(["zccache", "session-stats", "session-123", "--json"]).unwrap();
assert!(matches!(
cli.command,
Some(Commands::SessionStatsCmd { json: true, .. })
));
}
#[test]
fn engine_profile_accepts_json_flag() {
use clap::Parser;
let cli = Cli::try_parse_from([
"zccache",
"engine-profile",
"last-session-stats.json",
"--json",
])
.unwrap();
assert!(matches!(
cli.command,
Some(Commands::EngineProfile { json: true, .. })
));
}
#[test]
fn session_start_profile_flag_defaults_to_false() {
use clap::Parser;
let cli = Cli::try_parse_from(["zccache", "session-start"]).unwrap();
match cli.command {
Some(Commands::SessionStart { profile, .. }) => assert!(!profile),
other => panic!("expected SessionStart, got {other:?}"),
}
}
#[test]
fn session_start_profile_flag_parses_when_set() {
use clap::Parser;
let cli = Cli::try_parse_from(["zccache", "session-start", "--profile"]).unwrap();
match cli.command {
Some(Commands::SessionStart { profile, .. }) => assert!(profile),
other => panic!("expected SessionStart, got {other:?}"),
}
}
#[test]
fn daemon_top_parses_bind_open_and_endpoint() {
use clap::Parser;
let cli = Cli::try_parse_from([
"zccache",
"daemon",
"top",
"localhost:1234",
"--endpoint",
"tcp:127.0.0.1:9",
])
.unwrap();
match cli.command {
Some(Commands::Daemon {
command:
DaemonCommands::Top {
bind,
bind_addr,
no_open,
endpoint,
},
}) => {
assert_eq!(bind.as_deref(), Some("localhost:1234"));
assert!(bind_addr.is_none());
assert!(!no_open);
assert_eq!(endpoint.as_deref(), Some("tcp:127.0.0.1:9"));
}
other => panic!("expected Daemon profile start, got {other:?}"),
}
}
#[test]
fn daemon_top_parses_no_open_and_long_bind_aliases() {
use clap::Parser;
let cli =
Cli::try_parse_from(["zccache", "daemon", "top", "--bind", "localhost:1234"]).unwrap();
match cli.command {
Some(Commands::Daemon {
command:
DaemonCommands::Top {
bind,
bind_addr,
no_open,
endpoint,
},
}) => {
assert!(bind.is_none());
assert_eq!(bind_addr.as_deref(), Some("localhost:1234"));
assert!(!no_open);
assert!(endpoint.is_none());
}
other => panic!("expected Daemon top, got {other:?}"),
}
let cli =
Cli::try_parse_from(["zccache", "daemon", "top", "localhost:1234", "--no-open"]).unwrap();
match cli.command {
Some(Commands::Daemon {
command:
DaemonCommands::Top {
bind,
bind_addr,
no_open,
endpoint,
},
}) => {
assert_eq!(bind.as_deref(), Some("localhost:1234"));
assert!(bind_addr.is_none());
assert!(no_open);
assert!(endpoint.is_none());
}
other => panic!("expected Daemon top, got {other:?}"),
}
}
#[test]
fn daemon_profile_start_parses_as_compatibility_alias() {
use clap::Parser;
let cli = Cli::try_parse_from([
"zccache",
"daemon",
"profile",
"start",
"--bind",
"localhost:1234",
"--open",
])
.unwrap();
match cli.command {
Some(Commands::Daemon {
command:
DaemonCommands::Profile {
command:
DaemonProfileCommands::Start {
bind,
open,
endpoint,
},
},
}) => {
assert_eq!(bind.as_deref(), Some("localhost:1234"));
assert!(open);
assert!(endpoint.is_none());
}
other => panic!("expected Daemon profile start alias, got {other:?}"),
}
}
#[test]
fn session_start_private_daemon_flags_parse() {
use clap::Parser;
let cli = Cli::try_parse_from([
"zccache",
"session-start",
"--private-daemon",
"--daemon-name",
"soldr-dev",
"--cache-dir",
".zccache-soldr-dev",
"--owner-pid",
"1234",
"--owner-pid",
"5678",
"--private-env",
"ZCCACHE_PATH_REMAP=auto",
])
.unwrap();
match cli.command {
Some(Commands::SessionStart {
private_daemon,
daemon_name,
cache_dir,
owner_pid,
private_env,
..
}) => {
assert!(private_daemon);
assert_eq!(daemon_name.as_deref(), Some("soldr-dev"));
assert_eq!(cache_dir.as_deref(), Some(".zccache-soldr-dev"));
assert_eq!(owner_pid, vec![1234, 5678]);
assert_eq!(private_env, vec!["ZCCACHE_PATH_REMAP=auto"]);
}
other => panic!("expected SessionStart, got {other:?}"),
}
}
#[test]
fn private_env_assignment_parser_rejects_missing_equals() {
let raw = vec!["ZCCACHE_PATH_REMAP".to_string()];
assert!(super::super::session::parse_private_env_assignments(&raw).is_err());
}
#[test]
fn session_start_private_options_generate_private_daemon_identity() {
let mut options = SessionStartPrivateOptions {
private_daemon: true,
..SessionStartPrivateOptions::default()
};
options.ensure_private_identity(None);
assert!(options
.daemon_name
.as_deref()
.is_some_and(|name| name.starts_with("private-")));
}
#[test]
fn session_start_explicit_endpoint_generates_non_default_private_identity() {
let mut options = SessionStartPrivateOptions {
private_env: vec![("ZCCACHE_PATH_REMAP".to_string(), "auto".to_string())],
..SessionStartPrivateOptions::default()
};
options.ensure_private_identity(Some("127.0.0.1:45454"));
let daemon_name = options.daemon_name.as_deref().unwrap();
assert_ne!(daemon_name, crate::core::config::DEFAULT_DAEMON_NAMESPACE);
assert!(daemon_name.starts_with("endpoint-"));
}
#[test]
fn analyze_parses_session_crate_outcome_sort_top_flags() {
use clap::Parser;
let cli = Cli::try_parse_from([
"zccache",
"analyze",
"x.jsonl",
"--session",
"s1",
"--crate",
"soldr_cli",
"--outcome",
"miss",
"--sort",
"misses",
"--top",
"5",
])
.unwrap();
match cli.command {
Some(Commands::Analyze {
session,
crate_name,
outcome,
sort,
top,
..
}) => {
assert_eq!(session.as_deref(), Some("s1"));
assert_eq!(crate_name.as_deref(), Some("soldr_cli"));
assert_eq!(outcome.as_deref(), Some("miss"));
assert_eq!(sort, "misses");
assert_eq!(top, Some(5));
}
other => panic!("expected Analyze, got {other:?}"),
}
}
#[test]
fn analyze_sort_defaults_to_wall_clock() {
use clap::Parser;
let cli = Cli::try_parse_from(["zccache", "analyze", "x.jsonl"]).unwrap();
match cli.command {
Some(Commands::Analyze { sort, top, .. }) => {
assert_eq!(sort, "wall-clock");
assert!(top.is_none());
}
other => panic!("expected Analyze, got {other:?}"),
}
}
#[test]
fn session_stats_unavailable_json_has_scrapeable_status() {
let json = session_stats_unavailable_json("session-123", "stats_not_enabled");
assert_eq!(json["status"], "unavailable");
assert_eq!(json["session_id"], "session-123");
assert_eq!(json["reason"], "stats_not_enabled");
}
#[test]
fn session_stats_error_json_has_scrapeable_status() {
let json = session_stats_error_json("session-123", "unknown session");
assert_eq!(json["status"], "error");
assert_eq!(json["session_id"], "session-123");
assert_eq!(json["error"], "unknown session");
}
#[test]
fn rust_plan_gha_version_is_stable_for_backend_diagnostics() {
let key = "rust-plan-v1-test";
assert_eq!(rust_plan_gha_version(key), rust_plan_gha_version(key));
assert_ne!(rust_plan_gha_version(key), rust_plan_gha_version("other"));
}
#[test]
fn known_subcommands_matches_clap_enum() {
use clap::CommandFactory;
let cmd = Cli::command();
let clap_subs: std::collections::BTreeSet<String> = cmd
.get_subcommands()
.map(|s| s.get_name().to_string())
.collect();
let known: std::collections::BTreeSet<String> = KNOWN_SUBCOMMANDS
.iter()
.filter(|s| !s.starts_with('-'))
.filter(|s| **s != "help")
.map(|s| s.to_string())
.collect();
let missing: Vec<&String> = clap_subs.difference(&known).collect();
assert!(
missing.is_empty(),
"KNOWN_SUBCOMMANDS is missing {} clap subcommand(s): {:?}. \
Without these entries, `zccache <name>` falls through to wrap mode \
and fails with 'failed to run compiler: program not found'. Add them \
to KNOWN_SUBCOMMANDS in args.rs.",
missing.len(),
missing,
);
let stale: Vec<&String> = known.difference(&clap_subs).collect();
assert!(
stale.is_empty(),
"KNOWN_SUBCOMMANDS lists {} name(s) not in the clap Commands enum: {:?}. \
Either re-add the subcommand or drop the stale entry.",
stale.len(),
stale,
);
}
#[test]
fn top_level_help_lists_discoverable_env_vars() {
use clap::CommandFactory;
let help = Cli::command().render_help().to_string();
for var in [
"ZCCACHE_PATH_REMAP",
"ZCCACHE_STRICT_PATHS",
"ZCCACHE_CACHE_DIR",
] {
assert!(
help.contains(var),
"`zccache --help` no longer mentions {var}. \
If the env var was renamed or removed, update args.rs::ENV_VARS_HELP; \
do not silently drop it (issue #672)."
);
}
}