use std::process::Command;
pub(crate) const WRAP_MARKER: &str = "LEAN_CTX_WRAPPED";
const EXEC_DEPTH_MARKER: &str = "LEAN_CTX_EXEC_DEPTH";
pub(crate) const MAX_EXEC_DEPTH: u32 = 8;
#[must_use]
pub(crate) fn exec_depth() -> u32 {
std::env::var(EXEC_DEPTH_MARKER)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0)
}
#[must_use]
pub(crate) fn exec_depth_exceeded() -> bool {
exec_depth() >= MAX_EXEC_DEPTH
}
pub(crate) fn stamp_exec_depth(cmd: &mut Command) {
cmd.env(EXEC_DEPTH_MARKER, (exec_depth() + 1).to_string());
}
pub(crate) const ACTIVE_MARKER: &str = "LEAN_CTX_ACTIVE";
#[must_use]
pub(crate) fn is_disabled() -> bool {
std::env::var("LEAN_CTX_DISABLED").is_ok()
}
#[must_use]
pub fn is_wrapped() -> bool {
std::env::var(WRAP_MARKER).is_ok()
}
#[must_use]
pub fn should_pass_through() -> bool {
is_wrapped() || is_disabled()
}
pub fn mark_child(cmd: &mut Command) {
cmd.env(ACTIVE_MARKER, "1").env(WRAP_MARKER, "1");
stamp_exec_depth(cmd);
}
pub(crate) fn clear_shell_default_markers(cmd: &mut Command) {
cmd.env_remove(ACTIVE_MARKER)
.env_remove(WRAP_MARKER)
.env_remove("LEAN_CTX_COMPRESS");
}
#[cfg(test)]
mod tests {
use super::{
ACTIVE_MARKER, EXEC_DEPTH_MARKER, MAX_EXEC_DEPTH, WRAP_MARKER, clear_shell_default_markers,
exec_depth, exec_depth_exceeded, is_disabled, is_wrapped, mark_child, should_pass_through,
stamp_exec_depth,
};
#[test]
fn exec_depth_defaults_to_zero() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var(EXEC_DEPTH_MARKER);
assert_eq!(exec_depth(), 0);
assert!(!exec_depth_exceeded());
}
#[test]
fn stamp_exec_depth_increments_on_child() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var(EXEC_DEPTH_MARKER, "3");
let mut cmd = std::process::Command::new("true");
stamp_exec_depth(&mut cmd);
crate::test_env::remove_var(EXEC_DEPTH_MARKER);
let envs: std::collections::HashMap<String, String> = cmd
.get_envs()
.filter_map(|(k, v)| Some((k.to_str()?.to_string(), v?.to_str()?.to_string())))
.collect();
assert_eq!(envs.get(EXEC_DEPTH_MARKER).map(String::as_str), Some("4"));
}
#[test]
fn exec_depth_exceeded_at_ceiling() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var(EXEC_DEPTH_MARKER, MAX_EXEC_DEPTH.to_string());
let exceeded = exec_depth_exceeded();
crate::test_env::remove_var(EXEC_DEPTH_MARKER);
assert!(
exceeded,
"depth equal to the ceiling must already be treated as exceeded"
);
}
#[test]
fn mark_child_also_stamps_exec_depth() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var(EXEC_DEPTH_MARKER);
let mut cmd = std::process::Command::new("true");
mark_child(&mut cmd);
let envs: std::collections::HashMap<String, String> = cmd
.get_envs()
.filter_map(|(k, v)| Some((k.to_str()?.to_string(), v?.to_str()?.to_string())))
.collect();
assert_eq!(envs.get(EXEC_DEPTH_MARKER).map(String::as_str), Some("1"));
}
#[test]
fn clear_shell_default_markers_does_not_reset_exec_depth() {
let mut cmd = std::process::Command::new("true");
cmd.env(EXEC_DEPTH_MARKER, "5");
clear_shell_default_markers(&mut cmd);
let envs: std::collections::HashMap<String, String> = cmd
.get_envs()
.filter_map(|(k, v)| Some((k.to_str()?.to_string(), v?.to_str()?.to_string())))
.collect();
assert_eq!(
envs.get(EXEC_DEPTH_MARKER).map(String::as_str),
Some("5"),
"exec depth must survive clear_shell_default_markers"
);
}
#[test]
fn wrap_marker_triggers_passthrough() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var("LEAN_CTX_DISABLED");
crate::test_env::remove_var(ACTIVE_MARKER);
crate::test_env::set_var(WRAP_MARKER, "1");
assert!(is_wrapped());
assert!(should_pass_through());
crate::test_env::remove_var(WRAP_MARKER);
}
#[test]
fn disabled_triggers_passthrough() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var(WRAP_MARKER);
crate::test_env::remove_var(ACTIVE_MARKER);
crate::test_env::set_var("LEAN_CTX_DISABLED", "1");
assert!(is_disabled());
assert!(should_pass_through());
crate::test_env::remove_var("LEAN_CTX_DISABLED");
}
#[test]
fn inherited_active_does_not_trigger_passthrough() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::remove_var(WRAP_MARKER);
crate::test_env::remove_var("LEAN_CTX_DISABLED");
crate::test_env::set_var(ACTIVE_MARKER, "1");
assert!(
!should_pass_through(),
"inherited LEAN_CTX_ACTIVE must not disable compression (#533)"
);
crate::test_env::remove_var(ACTIVE_MARKER);
}
#[test]
fn mark_child_sets_both_markers() {
let mut cmd = std::process::Command::new("true");
mark_child(&mut cmd);
let envs: std::collections::HashMap<String, String> = cmd
.get_envs()
.filter_map(|(k, v)| Some((k.to_str()?.to_string(), v?.to_str()?.to_string())))
.collect();
assert_eq!(envs.get(WRAP_MARKER).map(String::as_str), Some("1"));
assert_eq!(envs.get(ACTIVE_MARKER).map(String::as_str), Some("1"));
}
#[test]
fn clear_shell_default_markers_removes_wrapping_state() {
let mut cmd = std::process::Command::new("true");
mark_child(&mut cmd);
cmd.env("LEAN_CTX_COMPRESS", "1");
clear_shell_default_markers(&mut cmd);
let envs: std::collections::HashMap<String, Option<String>> = cmd
.get_envs()
.filter_map(|(k, v)| {
Some((
k.to_str()?.to_string(),
v.and_then(|v| v.to_str().map(str::to_string)),
))
})
.collect();
assert_eq!(envs.get(WRAP_MARKER), Some(&None));
assert_eq!(envs.get(ACTIVE_MARKER), Some(&None));
assert_eq!(envs.get("LEAN_CTX_COMPRESS"), Some(&None));
}
}