#![cfg(test)]
use super::*;
#[test]
fn cgroup_manager_path() {
let cg = CgroupManager::new("/sys/fs/cgroup/test");
assert_eq!(
cg.parent_path(),
std::path::Path::new("/sys/fs/cgroup/test")
);
}
#[test]
fn create_cgroup_in_tmpdir() {
let _tempdir_keep_alive = make_inline_tempdir("create-in-tmpdir");
let dir = _tempdir_keep_alive.path();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.create_cgroup("test_cg").unwrap();
assert!(dir.join("test_cg").exists());
cg.create_cgroup("nested/deep").unwrap();
assert!(dir.join("nested/deep").exists());
}
#[test]
fn create_cgroup_idempotent() {
let _tempdir_keep_alive = make_inline_tempdir("idem");
let dir = _tempdir_keep_alive.path();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.create_cgroup("cg_0").unwrap();
cg.create_cgroup("cg_0").unwrap(); assert!(dir.join("cg_0").exists());
}
#[test]
fn cleanup_all_on_nonexistent() {
let cg = CgroupManager::new("/nonexistent/ktstr-test-path");
assert!(cg.cleanup_all().is_ok());
}
#[test]
fn remove_cgroup_nonexistent() {
let cg = CgroupManager::new("/nonexistent/ktstr-test-path");
assert!(cg.remove_cgroup("no_such_cgroup").is_ok());
}
#[test]
fn cleanup_removes_child_dirs() {
let _tempdir_keep_alive = make_inline_tempdir("clean");
let dir = _tempdir_keep_alive.path();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.create_cgroup("a").unwrap();
cg.create_cgroup("b").unwrap();
cg.create_cgroup("c/deep").unwrap();
assert!(dir.join("a").exists());
assert!(dir.join("c/deep").exists());
cg.cleanup_all().unwrap();
assert!(!dir.join("a").exists());
assert!(!dir.join("b").exists());
assert!(!dir.join("c").exists());
}
#[test]
fn drain_tasks_nonexistent_source() {
let cg = CgroupManager::new("/nonexistent/ktstr-drain-test");
assert!(cg.drain_tasks("missing_cgroup").is_ok());
}
#[test]
fn cleanup_all_skips_non_dir_entries() {
let _tempdir_keep_alive = make_inline_tempdir("nondir");
let dir = _tempdir_keep_alive.path();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.create_cgroup("cg_child").unwrap();
let stray_file = dir.join("stray.txt");
fs::write(&stray_file, b"do not descend").unwrap();
assert!(dir.join("cg_child").exists());
assert!(stray_file.exists());
cg.cleanup_all().unwrap();
assert!(
!dir.join("cg_child").exists(),
"cleanup_all should remove the child directory",
);
assert!(
stray_file.exists(),
"cleanup_all must not descend into or remove regular files",
);
assert_eq!(fs::read_to_string(&stray_file).unwrap(), "do not descend");
}
#[test]
fn cleanup_recursive_removes_nested_dirs_depth_first() {
let _tempdir_keep_alive = make_inline_tempdir("nested");
let base = _tempdir_keep_alive.path();
let root = base.join("root");
fs::create_dir_all(root.join("mid").join("leaf")).unwrap();
fs::create_dir_all(root.join("sibling")).unwrap();
assert!(root.join("mid/leaf").exists());
assert!(root.join("sibling").exists());
cleanup_recursive(&root, base);
assert!(
!root.exists(),
"cleanup_recursive should remove root and every descendant",
);
}
#[test]
fn setup_non_cgroup_path() {
let _tempdir_keep_alive = make_inline_tempdir("setup");
let dir = _tempdir_keep_alive.path();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.setup(&BTreeSet::new()).unwrap();
assert!(dir.exists());
}
#[test]
fn setup_writes_requested_controllers_only() {
let _tempdir_keep_alive = make_inline_tempdir("setup-controllers");
let root = _tempdir_keep_alive.path();
let parent = root.join("ktstr");
fs::create_dir_all(&parent).unwrap();
fs::write(root.join("cgroup.controllers"), "cpuset cpu memory pids io").unwrap();
fs::write(root.join("cgroup.subtree_control"), "").unwrap();
fs::write(parent.join("cgroup.subtree_control"), "").unwrap();
let cg = CgroupManager::new(parent.to_str().unwrap());
let mut requested = BTreeSet::new();
requested.insert(Controller::Cpuset);
requested.insert(Controller::Memory);
cg.setup_under_root(&requested, root).unwrap();
let written = fs::read_to_string(parent.join("cgroup.subtree_control")).unwrap();
assert!(
written.contains("+cpuset"),
"subtree_control must contain +cpuset; got: {written:?}",
);
assert!(
written.contains("+memory"),
"subtree_control must contain +memory; got: {written:?}",
);
assert!(
!written.contains("+pids"),
"+pids must be absent when not requested; got: {written:?}",
);
assert!(
!written.contains("+io"),
"+io must be absent when not requested; got: {written:?}",
);
let cpu_positions: Vec<usize> = written.match_indices("+cpu").map(|(i, _)| i).collect();
for pos in cpu_positions {
let suffix = &written[pos..];
assert!(
suffix.starts_with("+cpuset"),
"+cpu must be absent when not requested (only +cpuset allowed); \
got '{suffix}' at pos {pos} in {written:?}",
);
}
}
#[test]
fn setup_rejects_unavailable_controller() {
let _tempdir_keep_alive = make_inline_tempdir("setup-unavail");
let root = _tempdir_keep_alive.path();
let parent = root.join("ktstr");
fs::create_dir_all(&parent).unwrap();
fs::write(root.join("cgroup.controllers"), "memory").unwrap();
fs::write(root.join("cgroup.subtree_control"), "").unwrap();
fs::write(parent.join("cgroup.subtree_control"), "").unwrap();
let cg = CgroupManager::new(parent.to_str().unwrap());
let mut requested = BTreeSet::new();
requested.insert(Controller::Cpuset);
let err = cg.setup_under_root(&requested, root).unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("cpuset") && msg.contains("not available"),
"error must cite missing 'cpuset' and 'not available'; got {msg:?}",
);
}
#[test]
fn write_with_timeout_success() {
let _tempdir_keep_alive = make_inline_tempdir("write-timeout");
let dir = _tempdir_keep_alive.path();
let f = dir.join("test_write");
write_with_timeout(&f, "hello", Duration::from_secs(5)).unwrap();
assert_eq!(fs::read_to_string(&f).unwrap(), "hello");
}
#[test]
fn write_with_timeout_bad_path() {
let f = Path::new("/nonexistent/dir/file");
assert!(write_with_timeout(f, "data", Duration::from_secs(5)).is_err());
}
#[test]
fn move_task_nonexistent_cgroup() {
let cg = CgroupManager::new("/nonexistent/ktstr-move-test");
assert!(cg.move_task("no_cgroup", 1).is_err());
}
#[test]
fn set_cpuset_empty() {
let _tempdir_keep_alive = make_inline_tempdir("cpuset");
let dir = _tempdir_keep_alive.path();
let dir_a = dir.join("cg_a");
fs::create_dir_all(&dir_a).unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.set_cpuset("cg_a", &BTreeSet::new()).unwrap();
assert_eq!(fs::read_to_string(dir_a.join("cpuset.cpus")).unwrap(), "");
}
#[test]
fn move_tasks_partial_failure() {
let cg = CgroupManager::new("/nonexistent/ktstr-partial");
let err = cg.move_tasks("cg", &[1, 2, 3]).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("cgroup.procs"), "unexpected error: {msg}");
}
#[test]
fn move_tasks_empty_pids_returns_ok() {
let cg = CgroupManager::new("/nonexistent/ktstr-empty");
assert!(
cg.move_tasks("cg", &[]).is_ok(),
"move_tasks with empty pids slice must succeed without \
touching any cgroup.procs file (no all-vanished bail)",
);
}
#[cfg(test)]
fn synth_esrch() -> anyhow::Error {
anyhow::Error::new(std::io::Error::from_raw_os_error(libc::ESRCH))
.context("synthesised ESRCH for move_tasks_inner unit test")
}
#[test]
fn move_tasks_inner_partial_esrch_returns_ok() {
let pids: [libc::pid_t; 3] = [100, 200, 300];
let result = move_tasks_inner("cg_x", &pids, |_name, pid| {
if pid == 200 {
Err(synth_esrch())
} else {
Ok(())
}
});
assert!(
result.is_ok(),
"partial vanish (1 of 3 ESRCH) must NOT trigger the \
all-vanished bail; got {:?}",
result.err().map(|e| format!("{e:#}")),
);
}
#[test]
fn move_tasks_inner_all_esrch_bails_with_actionable_diagnostic() {
let pids: [libc::pid_t; 2] = [100, 200];
let result = move_tasks_inner("cg_x", &pids, |_name, _pid| Err(synth_esrch()));
let err = result.expect_err("all-ESRCH must trigger the bail (no silent Ok return)");
let msg = format!("{err:#}");
assert!(
msg.contains("cg_x") && msg.contains("ESRCH"),
"diagnostic must name the cgroup + the ESRCH cause; got {msg:?}",
);
assert!(
msg.contains("pre_exec") || msg.contains("scheduler-attach"),
"diagnostic must enumerate root-cause hypotheses; got {msg:?}",
);
assert!(
msg.contains("empty pids slice"),
"diagnostic must point at the empty-slice escape hatch; got {msg:?}",
);
}
#[test]
fn move_tasks_inner_non_esrch_error_propagates_immediately() {
let pids: [libc::pid_t; 3] = [100, 200, 300];
let mut visited: Vec<libc::pid_t> = Vec::new();
let visited_ref = &mut visited;
let result = move_tasks_inner("cg_x", &pids, move |_name, pid| {
visited_ref.push(pid);
if pid == 200 {
Err(
anyhow::Error::new(std::io::Error::from_raw_os_error(libc::EBUSY))
.context("synthesised EBUSY (not ESRCH)"),
)
} else {
Ok(())
}
});
assert!(
result.is_err(),
"non-ESRCH error must propagate, not be tolerated"
);
assert_eq!(
visited,
vec![100, 200],
"loop must short-circuit at the first non-ESRCH error and NOT visit pid 300",
);
}
#[test]
fn move_tasks_inner_empty_pids_returns_ok() {
let result = move_tasks_inner("cg_x", &[], |_name, _pid| {
panic!("write closure must not be called for empty pids slice")
});
assert!(
result.is_ok(),
"empty pids slice must return Ok without invoking the write closure",
);
}
#[test]
fn drain_tasks_empty_cgroup() {
let _tempdir_keep_alive = make_inline_tempdir("drain");
let dir = _tempdir_keep_alive.path();
let dir_d = dir.join("cg_d");
fs::create_dir_all(&dir_d).unwrap();
fs::write(dir_d.join("cgroup.procs"), "").unwrap();
fs::write(dir.join("cgroup.procs"), "").unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
assert!(cg.drain_tasks("cg_d").is_ok());
}
#[test]
fn read_procs_nonexistent_source_errors() {
let cg = CgroupManager::new("/nonexistent/ktstr-read-procs-test");
let err = cg
.read_procs("missing_cgroup")
.expect_err("missing cgroup directory must surface as Err");
let msg = format!("{err:#}");
assert!(
msg.contains("missing_cgroup"),
"diagnostic must name the supplied cgroup name; got: {msg}",
);
assert!(
msg.contains("Op::AddCgroup") || msg.contains("workload_root_cgroup"),
"diagnostic must surface the actionable hint; got: {msg}",
);
}
#[test]
fn read_procs_empty_cgroup_returns_empty_vec() {
let _tempdir_keep_alive = make_inline_tempdir("read-procs-empty");
let dir = _tempdir_keep_alive.path();
let dir_d = dir.join("cg_d");
fs::create_dir_all(&dir_d).unwrap();
fs::write(dir_d.join("cgroup.procs"), "").unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
let pids = cg
.read_procs("cg_d")
.expect("empty cgroup must return Ok(vec![])");
assert!(
pids.is_empty(),
"empty cgroup.procs must yield empty Vec; got: {pids:?}",
);
}
#[test]
fn read_procs_returns_pids_in_file_order() {
let _tempdir_keep_alive = make_inline_tempdir("read-procs-pids");
let dir = _tempdir_keep_alive.path();
let dir_d = dir.join("cg_d");
fs::create_dir_all(&dir_d).unwrap();
fs::write(dir_d.join("cgroup.procs"), "100\n200\n300\n").unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
let pids = cg
.read_procs("cg_d")
.expect("populated cgroup.procs must return Ok");
assert_eq!(
pids,
vec![100, 200, 300],
"pids must be returned in file order; got: {pids:?}",
);
}
#[test]
fn read_procs_skips_malformed_pid_lines() {
let _tempdir_keep_alive = make_inline_tempdir("read-procs-malformed");
let dir = _tempdir_keep_alive.path();
let dir_d = dir.join("cg_d");
fs::create_dir_all(&dir_d).unwrap();
fs::write(dir_d.join("cgroup.procs"), "100\nGARBAGE\n200\n\n300\n").unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
let pids = cg
.read_procs("cg_d")
.expect("malformed lines must not error; valid pids must surface");
assert_eq!(
pids,
vec![100, 200, 300],
"malformed lines must be skipped, valid pids preserved; got: {pids:?}",
);
}
#[test]
fn read_procs_invalid_name_rejected_by_validate() {
let cg = CgroupManager::new("/nonexistent/read-procs-validate");
assert!(
cg.read_procs("..").is_err(),
"parent-directory traversal must be rejected",
);
assert!(
cg.read_procs("name\0withnull").is_err(),
"NUL-byte in cgroup name must be rejected",
);
}
#[test]
fn is_esrch_detects_esrch_in_chain() {
let io_err = std::io::Error::from_raw_os_error(libc::ESRCH);
let anyhow_err = anyhow::Error::new(io_err).context("write cgroup.procs");
assert!(is_esrch(&anyhow_err));
}
#[test]
fn is_esrch_rejects_enoent() {
let io_err = std::io::Error::from_raw_os_error(libc::ENOENT);
let anyhow_err = anyhow::Error::new(io_err).context("write cgroup.procs");
assert!(!is_esrch(&anyhow_err));
}
#[test]
fn is_ebusy_detects_ebusy_in_chain() {
let io_err = std::io::Error::from_raw_os_error(libc::EBUSY);
let anyhow_err = anyhow::Error::new(io_err).context("write cgroup.procs");
assert!(is_ebusy(&anyhow_err));
}
#[test]
fn is_ebusy_rejects_esrch() {
let io_err = std::io::Error::from_raw_os_error(libc::ESRCH);
let anyhow_err = anyhow::Error::new(io_err).context("write cgroup.procs");
assert!(!is_ebusy(&anyhow_err));
}
#[test]
fn clear_subtree_control_nonexistent() {
let cg = CgroupManager::new("/nonexistent/ktstr-clear-sc");
assert!(cg.clear_subtree_control("cg_0").is_ok());
}
#[test]
fn clear_subtree_control_empty() {
let _tempdir_keep_alive = make_inline_tempdir("subtree-control");
let dir = _tempdir_keep_alive.path();
let dir_a = dir.join("cg_a");
fs::create_dir_all(&dir_a).unwrap();
fs::write(dir_a.join("cgroup.subtree_control"), "").unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
assert!(cg.clear_subtree_control("cg_a").is_ok());
}
#[test]
fn write_with_timeout_blocks_on_fifo() {
use std::ffi::CString;
let _tempdir_keep_alive = make_inline_tempdir("fifo");
let dir = _tempdir_keep_alive.path();
let fifo_path = dir.join("blocked_write");
let c_path = CString::new(fifo_path.to_str().unwrap()).unwrap();
let rc = unsafe { libc::mkfifo(c_path.as_ptr(), 0o700) };
assert_eq!(rc, 0, "mkfifo failed: {}", std::io::Error::last_os_error());
let err = write_with_timeout(&fifo_path, "data", Duration::from_millis(50)).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("timed out"), "unexpected error: {msg}");
}
#[test]
fn anyhow_first_io_errno_extracts_raw_errno() {
let io = std::io::Error::from_raw_os_error(libc::EBUSY);
let err = anyhow::Error::new(io);
assert_eq!(anyhow_first_io_errno(&err), Some(libc::EBUSY));
}
#[test]
fn anyhow_first_io_errno_through_context() {
let io = std::io::Error::from_raw_os_error(libc::ESRCH);
let err = anyhow::Error::new(io).context("wrapping context");
assert_eq!(anyhow_first_io_errno(&err), Some(libc::ESRCH));
}
#[test]
fn anyhow_first_io_errno_no_io_returns_none() {
let err = anyhow::anyhow!("plain text error");
assert_eq!(anyhow_first_io_errno(&err), None);
}
#[test]
fn add_parent_subtree_controller_missing_file_noop() {
let cg = CgroupManager::new("/nonexistent/ktstr-add-parent-sc");
assert!(cg.add_parent_subtree_controller("cpuset").is_ok());
}
#[test]
fn add_parent_subtree_controller_writes_plus_prefixed_token() {
let _tempdir_keep_alive = make_inline_tempdir("addparent");
let dir = _tempdir_keep_alive.path();
let sc = dir.join("cgroup.subtree_control");
fs::write(&sc, "").unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.add_parent_subtree_controller("cpuset").unwrap();
assert_eq!(fs::read_to_string(&sc).unwrap(), "+cpuset");
}
fn make_inline_tempdir(label: &str) -> tempfile::TempDir {
tempfile::Builder::new()
.prefix(&format!("ktstr-{label}-"))
.tempdir()
.expect("tempdir")
}
fn make_test_cgroup(label: &str) -> (tempfile::TempDir, PathBuf, CgroupManager) {
let tmp = make_inline_tempdir(&format!("cg-{label}"));
let dir = tmp.path().to_path_buf();
fs::create_dir_all(dir.join("cg_x")).unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
(tmp, dir, cg)
}
fn make_test_cgroup_with_procs(label: &str) -> (tempfile::TempDir, PathBuf, CgroupManager) {
let (tmp, dir, cg) = make_test_cgroup(label);
let inner = dir.join("cg_x");
fs::write(inner.join("cgroup.procs"), "").unwrap();
(tmp, inner, cg)
}
fn make_test_cgroup_with_seeded_file(
label: &str,
filename: &str,
) -> (tempfile::TempDir, PathBuf, CgroupManager) {
let (tmp, dir, cg) = make_test_cgroup(label);
let target = dir.join("cg_x").join(filename);
fs::write(&target, "").unwrap();
(tmp, target, cg)
}
#[test]
fn set_cpu_max_writes_quota_and_period_when_some() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("cpu-max-some", "cpu.max");
cg.set_cpu_max("cg_x", Some(50_000), 100_000).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "50000 100000");
}
#[test]
fn set_cpu_max_writes_max_keyword_when_none() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("cpu-max-none", "cpu.max");
cg.set_cpu_max("cg_x", None, 100_000).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "max 100000");
}
#[test]
fn set_cpu_weight_writes_decimal_value() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("cpu-weight", "cpu.weight");
cg.set_cpu_weight("cg_x", 250).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "250");
}
#[test]
fn set_memory_max_writes_bytes_or_max_keyword() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("mem-max", "memory.max");
cg.set_memory_max("cg_x", Some(1_048_576)).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "1048576");
cg.set_memory_max("cg_x", None).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "max");
}
#[test]
fn set_memory_high_writes_bytes_or_max_keyword() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("mem-high", "memory.high");
cg.set_memory_high("cg_x", Some(524_288)).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "524288");
cg.set_memory_high("cg_x", None).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "max");
}
#[test]
fn set_memory_low_writes_bytes_or_zero() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("mem-low", "memory.low");
cg.set_memory_low("cg_x", Some(2_048)).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "2048");
cg.set_memory_low("cg_x", None).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "0");
}
#[test]
fn set_io_weight_writes_decimal_value() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("io-weight", "io.weight");
cg.set_io_weight("cg_x", 500).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "500");
}
#[test]
fn set_freeze_writes_zero_or_one() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("freeze", "cgroup.freeze");
cg.set_freeze("cg_x", true).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "1");
cg.set_freeze("cg_x", false).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "0");
}
#[test]
fn set_pids_max_writes_decimal_or_max_keyword() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("pids-max", "pids.max");
cg.set_pids_max("cg_x", Some(1024)).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "1024");
cg.set_pids_max("cg_x", None).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "max");
}
#[test]
fn set_memory_swap_max_writes_bytes_or_max_keyword() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("mem-swap-max", "memory.swap.max");
cg.set_memory_swap_max("cg_x", Some(2 * 1024 * 1024))
.unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "2097152");
cg.set_memory_swap_max("cg_x", None).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "max");
}
#[test]
fn validate_cgroup_name_rejects_unsafe_shapes() {
for (name, reason) in [
("", "empty"),
("/abs", "starts with '/'"),
("nul\0byte", "NUL byte"),
(".hidden", "leading-dot component"),
("..", "'..' component"),
("a/..", "'..' component"),
("../escape", "'..' component"),
(".", "'.' component"),
("a//b", "empty path component"),
("ok/.dotfile", "leading-dot component"),
] {
let err =
validate_cgroup_name(name).expect_err(&format!("must reject {name:?} ({reason})"));
assert!(
err.to_string().contains(reason),
"error for {name:?} must mention {reason:?}; got: {err:#}"
);
}
}
#[test]
fn validate_cgroup_name_accepts_valid_shapes() {
for name in [
"cg_0",
"cg-1",
"cg.0",
"cg_0/narrow",
"level1/level2/level3",
"a.b.c",
"x",
] {
validate_cgroup_name(name).unwrap_or_else(|e| {
panic!("must accept legitimate name {name:?}; got: {e:#}");
});
}
}
#[test]
fn cgroup_methods_reject_bad_names_before_fs_writes() {
let _tempdir_keep_alive = make_inline_tempdir("badname");
let dir = _tempdir_keep_alive.path();
let cg = CgroupManager::new(dir.to_str().unwrap());
let bad = "../escape";
let err = cg.create_cgroup(bad).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let err = cg.set_freeze(bad, true).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let err = cg.set_pids_max(bad, Some(10)).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let err = cg.set_memory_swap_max(bad, Some(1024)).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let err = cg.set_cpuset_mems(bad, &BTreeSet::new()).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let err = cg.move_task(bad, 1).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let err = cg.drain_tasks(bad).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let err = cg.remove_cgroup(bad).unwrap_err();
assert!(err.to_string().contains("'..' component"));
let escape_marker = dir.join("escape");
assert!(
!escape_marker.exists(),
"validator must bail before fs writes; saw {escape_marker:?}"
);
}
#[test]
fn setup_under_root_outside_root_creates_dir_and_skips_walk() {
let outside = std::env::temp_dir().join(format!("ktstr-out-{}", std::process::id()));
let _unrelated_root_keep_alive = make_inline_tempdir("unrelated-root");
let unrelated_root = _unrelated_root_keep_alive.path();
let cg = CgroupManager::new(outside.to_str().unwrap());
let mut requested = BTreeSet::new();
requested.insert(Controller::Cpuset);
cg.setup_under_root(&requested, unrelated_root).unwrap();
assert!(outside.exists(), "setup must create the parent directory");
assert!(
!outside.join("cgroup.subtree_control").exists(),
"no subtree_control walk should fire when the parent is not under root"
);
let _ = fs::remove_dir_all(&outside);
}
#[test]
fn set_freeze_is_idempotent_when_already_in_target_state() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("freeze-idem", "cgroup.freeze");
cg.set_freeze("cg_x", true).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "1");
cg.set_freeze("cg_x", true).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "1");
cg.set_freeze("cg_x", false).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "0");
cg.set_freeze("cg_x", false).unwrap();
assert_eq!(fs::read_to_string(&target).unwrap(), "0");
}
#[test]
fn set_pids_max_writes_u64_max_verbatim() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("pids-overflow", "pids.max");
cg.set_pids_max("cg_x", Some(u64::MAX)).unwrap();
assert_eq!(
fs::read_to_string(&target).unwrap(),
u64::MAX.to_string(),
"u64::MAX must round-trip without narrowing or sign change"
);
}
#[test]
fn set_memory_swap_max_writes_u64_max_verbatim() {
let (_tempdir_keep_alive, target, cg) =
make_test_cgroup_with_seeded_file("swap-overflow", "memory.swap.max");
cg.set_memory_swap_max("cg_x", Some(u64::MAX)).unwrap();
assert_eq!(
fs::read_to_string(&target).unwrap(),
u64::MAX.to_string(),
"u64::MAX must round-trip without narrowing or sign change"
);
}
#[test]
fn set_pids_max_returns_err_when_pids_max_file_missing() {
let cg = CgroupManager::new("/nonexistent/ktstr-pids-test");
let err = cg
.set_pids_max("cg_x", Some(1024))
.expect_err("missing pids.max must surface as Err");
let msg = format!("{err:#}");
assert!(
msg.contains("pids.max"),
"error chain must name the missing file: {msg}"
);
}
#[test]
fn set_memory_swap_max_returns_err_when_file_missing() {
let cg = CgroupManager::new("/nonexistent/ktstr-swap-test");
let err = cg
.set_memory_swap_max("cg_x", Some(2_000_000))
.expect_err("missing memory.swap.max must surface as Err");
let msg = format!("{err:#}");
assert!(
msg.contains("memory.swap.max"),
"error chain must name the missing file: {msg}"
);
}
#[test]
fn set_freeze_returns_err_with_enoent_when_freeze_file_missing() {
let cg = CgroupManager::new("/nonexistent/ktstr-freeze-test");
let err = cg
.set_freeze("cg_x", true)
.expect_err("missing cgroup.freeze must surface as Err");
assert_eq!(
anyhow_first_io_errno(&err),
Some(libc::ENOENT),
"ENOENT errno must be reachable from the error chain so \
remove_cgroup's auto-unfreeze can suppress it; got: {err:#}"
);
}
#[test]
fn set_cpu_max_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-cpu-max-test");
let err = cg
.set_cpu_max("cg_alpha", Some(50_000), 100_000)
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_alpha"), "missing cgroup name: {msg}");
assert!(
msg.contains("set cpu.max='50000 100000'"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+cpu"), "missing controller hint: {msg}");
}
#[test]
fn set_cpu_weight_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-cpu-weight-test");
let err = cg
.set_cpu_weight("cg_beta", 250)
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_beta"), "missing cgroup name: {msg}");
assert!(
msg.contains("set cpu.weight=250"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+cpu"), "missing controller hint: {msg}");
}
#[test]
fn set_memory_max_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-mem-max-test");
let err = cg
.set_memory_max("cg_gamma", Some(1_048_576))
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_gamma"), "missing cgroup name: {msg}");
assert!(
msg.contains("set memory.max='1048576'"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+memory"), "missing controller hint: {msg}");
}
#[test]
fn set_memory_high_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-mem-high-test");
let err = cg
.set_memory_high("cg_delta", Some(524_288))
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_delta"), "missing cgroup name: {msg}");
assert!(
msg.contains("set memory.high='524288'"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+memory"), "missing controller hint: {msg}");
}
#[test]
fn set_memory_low_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-mem-low-test");
let err = cg
.set_memory_low("cg_epsilon", Some(262_144))
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_epsilon"), "missing cgroup name: {msg}");
assert!(
msg.contains("set memory.low='262144'"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+memory"), "missing controller hint: {msg}");
}
#[test]
fn set_io_weight_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-io-weight-test");
let err = cg
.set_io_weight("cg_zeta", 500)
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_zeta"), "missing cgroup name: {msg}");
assert!(
msg.contains("set io.weight=500"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+io"), "missing controller hint: {msg}");
}
#[test]
fn set_freeze_err_contains_cgroup_name_value_and_core_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-freeze-test");
let err = cg
.set_freeze("cg_eta", true)
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_eta"), "missing cgroup name: {msg}");
assert!(
msg.contains("set cgroup.freeze='1'"),
"missing knob-prefixed value: {msg}"
);
assert!(
msg.contains("cgroup-core"),
"missing cgroup-core hint: {msg}"
);
}
#[test]
fn set_pids_max_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-pids-max-test");
let err = cg
.set_pids_max("cg_theta", Some(4096))
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_theta"), "missing cgroup name: {msg}");
assert!(
msg.contains("set pids.max='4096'"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+pids"), "missing controller hint: {msg}");
}
#[test]
fn set_memory_swap_max_err_contains_cgroup_name_value_and_controller_hint() {
let cg = CgroupManager::new("/nonexistent/ktstr-set-swap-max-test");
let err = cg
.set_memory_swap_max("cg_iota", Some(8_388_608))
.expect_err("missing cgroup must surface as Err");
let msg = format!("{err:#}");
assert!(msg.contains("cg_iota"), "missing cgroup name: {msg}");
assert!(
msg.contains("set memory.swap.max='8388608'"),
"missing knob-prefixed value: {msg}"
);
assert!(msg.contains("+memory"), "missing controller hint: {msg}");
}
#[test]
fn remove_cgroup_auto_unfreezes_before_drain() {
let (_tempdir_keep_alive, inner, cg) = make_test_cgroup_with_procs("autounf");
let freeze_path = inner.join("cgroup.freeze");
fs::write(&freeze_path, "1").unwrap();
let _ = cg.remove_cgroup("cg_x");
assert_eq!(
fs::read_to_string(&freeze_path).unwrap(),
"0",
"remove_cgroup must write '0' to cgroup.freeze before draining"
);
}
#[test]
fn remove_cgroup_tolerates_missing_freeze_file() {
let (_tempdir_keep_alive, _inner, cg) = make_test_cgroup_with_procs("nofrz");
let _ = cg.remove_cgroup("cg_x");
}
#[test]
fn cleanup_recursive_auto_unfreezes_before_drain() {
let _tempdir_keep_alive = make_inline_tempdir("cleanup-rec-autounf");
let dir = _tempdir_keep_alive.path();
let freeze_path = dir.join("cgroup.freeze");
fs::write(&freeze_path, "1").unwrap();
fs::write(dir.join("cgroup.procs"), "").unwrap();
cleanup_recursive(dir, dir);
assert_eq!(
fs::read_to_string(&freeze_path).unwrap(),
"0",
"cleanup_recursive must write '0' to cgroup.freeze before draining \
(mirrors remove_cgroup auto-unfreeze for state hygiene)",
);
}
#[test]
fn remove_cgroup_increments_outstanding_on_failure() {
let (_tempdir_keep_alive, inner, cg) = make_test_cgroup_with_procs("outstanding");
assert_eq!(cg.outstanding_removes(), 0);
let _ = cg.remove_cgroup("cg_x");
assert_eq!(
cg.outstanding_removes(),
1,
"outstanding_removes must increment when rmdir fails"
);
fs::create_dir_all(&inner).unwrap();
fs::write(inner.join("cgroup.procs"), "").unwrap();
let _ = cg.remove_cgroup("cg_x");
assert_eq!(
cg.outstanding_removes(),
2,
"outstanding_removes must increment monotonically on repeat failures"
);
}
#[test]
fn remove_cgroup_decrements_outstanding_on_success() {
let _tempdir_keep_alive = make_inline_tempdir("decrement");
let dir = _tempdir_keep_alive.path();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.outstanding_removes.store(3, Ordering::Relaxed);
let inner = dir.join("cg_clean");
fs::create_dir_all(&inner).unwrap();
cg.remove_cgroup("cg_clean").unwrap();
assert_eq!(
cg.outstanding_removes(),
2,
"successful remove must decrement outstanding_removes by 1"
);
}
#[test]
fn remove_cgroup_bails_when_cap_exceeded() {
let _tempdir_keep_alive = make_inline_tempdir("cap");
let dir = _tempdir_keep_alive.path();
let inner = dir.join("cg_x");
fs::create_dir_all(&inner).unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
cg.outstanding_removes
.store(MAX_OUTSTANDING_REMOVES + 1, Ordering::Relaxed);
let err = cg
.remove_cgroup("cg_x")
.expect_err("cap-exceeded remove must surface as Err");
let msg = format!("{err:#}");
assert!(
msg.contains("outstanding") && msg.contains("cap"),
"error must cite the cap; got: {msg}"
);
assert!(
inner.exists(),
"cap-exceeded bail must not touch the filesystem"
);
}
#[test]
fn remove_cgroup_missing_dir_does_not_touch_counter() {
let cg = CgroupManager::new("/nonexistent/ktstr-missing-counter");
cg.outstanding_removes.store(5, Ordering::Relaxed);
cg.remove_cgroup("no_such_cgroup").unwrap();
assert_eq!(
cg.outstanding_removes(),
5,
"missing-dir early return must not decrement the counter"
);
}
#[test]
fn move_task_refuses_when_cpuset_cpus_set_but_effective_mems_empty() {
let _tempdir_keep_alive = make_inline_tempdir("cpuset-gate");
let dir = _tempdir_keep_alive.path();
let inner = dir.join("cg_x");
fs::create_dir_all(&inner).unwrap();
fs::write(inner.join("cpuset.cpus"), "0-1").unwrap();
fs::write(inner.join("cpuset.mems.effective"), "").unwrap();
let cg = CgroupManager::new(dir.to_str().unwrap());
let err = cg
.move_task("cg_x", 1)
.expect_err("half-configured cpuset must refuse move_task");
let msg = format!("{err:#}");
assert!(
msg.contains("cpuset.mems.effective") && msg.contains("set_cpuset_mems"),
"error must cite cpuset.mems.effective and direct caller to set_cpuset_mems; got: {msg}"
);
let procs_path = inner.join("cgroup.procs");
assert!(
!procs_path.exists(),
"gate must bail before any cgroup.procs write; cgroup.procs exists at {procs_path:?}"
);
}
#[test]
fn move_task_admits_when_cpus_set_and_effective_mems_non_empty() {
let (_tempdir_keep_alive, inner, cg) = make_test_cgroup_with_procs("cpuset-ok");
fs::write(inner.join("cpuset.cpus"), "0-1").unwrap();
fs::write(inner.join("cpuset.mems.effective"), "0").unwrap();
cg.move_task("cg_x", 1)
.expect("non-empty effective mems must admit move_task");
}
#[test]
fn move_task_admits_when_local_mems_empty_but_effective_inherited() {
let (_tempdir_keep_alive, inner, cg) = make_test_cgroup_with_procs("cpuset-inherit-mems");
fs::write(inner.join("cpuset.cpus"), "0-1").unwrap();
fs::write(inner.join("cpuset.mems"), "").unwrap();
fs::write(inner.join("cpuset.mems.effective"), "0").unwrap();
cg.move_task("cg_x", 1)
.expect("inherited effective mems must admit move_task");
}
#[test]
fn move_task_admits_when_cpuset_cpus_empty() {
let (_tempdir_keep_alive, inner, cg) = make_test_cgroup_with_procs("cpuset-inherit");
fs::write(inner.join("cpuset.cpus"), "").unwrap();
fs::write(inner.join("cpuset.mems.effective"), "").unwrap();
cg.move_task("cg_x", 1)
.expect("inherit-cpuset cgroup must admit move_task");
}
#[test]
fn move_task_admits_when_cpuset_files_absent() {
let (_tempdir_keep_alive, _inner, cg) = make_test_cgroup_with_procs("no-cpuset");
cg.move_task("cg_x", 1)
.expect("no-cpuset cgroup must admit move_task");
}
#[test]
fn move_task_admits_when_effective_mems_file_absent() {
let (_tempdir_keep_alive, inner, cg) = make_test_cgroup_with_procs("no-effective-mems");
fs::write(inner.join("cpuset.cpus"), "0-1").unwrap();
cg.move_task("cg_x", 1)
.expect("missing cpuset.mems.effective must admit move_task (read-failure absorb)");
}
#[test]
fn walk_root_default_is_canonical_root() {
let cg = CgroupManager::new("/sys/fs/cgroup/ktstr");
assert_eq!(cg.walk_root(), Path::new("/sys/fs/cgroup"));
}
#[test]
fn with_walk_root_validates_parent_below() {
let err = CgroupManager::new("/sys/fs/cgroup/foo/ktstr")
.with_walk_root("/sys/fs/cgroup/bar")
.expect_err("parent not below walk_root must reject");
let msg = format!("{err:#}");
assert!(
msg.contains("not below walk_root"),
"error must cite the prefix invariant; got {msg:?}",
);
}
#[test]
fn with_walk_root_admits_parent_equals_walk_root() {
CgroupManager::new("/sys/fs/cgroup/ktstr-build-foo")
.with_walk_root("/sys/fs/cgroup/ktstr-build-foo")
.expect("parent == walk_root must be admitted");
}
#[test]
fn with_walk_root_rejects_parent_dir_component_in_parent() {
let err = CgroupManager::new("/sys/fs/cgroup/op/../escape")
.with_walk_root("/sys/fs/cgroup/op")
.expect_err("parent containing `..` MUST be rejected");
let msg = format!("{err:#}");
assert!(
msg.contains("`..`") && msg.contains("parent"),
"error must name the `..` violation and the offending side; got: {msg}",
);
}
#[test]
fn with_walk_root_rejects_parent_dir_component_in_root() {
let err = CgroupManager::new("/sys/fs/cgroup/op/sub")
.with_walk_root("/sys/fs/cgroup/op/..")
.expect_err("walk_root containing `..` MUST be rejected");
let msg = format!("{err:#}");
assert!(
msg.contains("`..`") && msg.contains("walk_root"),
"error must name the `..` violation and the offending side; got: {msg}",
);
}
#[test]
fn setup_walks_under_walk_root_only() {
let _tempdir_keep_alive = make_inline_tempdir("walk-root-only");
let tmpdir = _tempdir_keep_alive.path();
let delegated = tmpdir.join("delegated");
let parent = delegated.join("inner");
fs::create_dir_all(&parent).unwrap();
fs::write(tmpdir.join("cgroup.subtree_control"), "SENTINEL_ABOVE").unwrap();
fs::write(delegated.join("cgroup.controllers"), "cpuset memory").unwrap();
fs::write(delegated.join("cgroup.subtree_control"), "").unwrap();
fs::write(parent.join("cgroup.subtree_control"), "").unwrap();
let cg = CgroupManager::new(parent.to_str().unwrap())
.with_walk_root(&delegated)
.expect("parent below delegated must be admitted");
let mut requested = BTreeSet::new();
requested.insert(Controller::Cpuset);
cg.setup(&requested)
.expect("setup must succeed under walk_root");
assert!(
fs::read_to_string(delegated.join("cgroup.subtree_control"))
.unwrap()
.contains("+cpuset"),
"walk must write +cpuset at the walk root",
);
assert!(
fs::read_to_string(parent.join("cgroup.subtree_control"))
.unwrap()
.contains("+cpuset"),
"walk must write +cpuset at the parent",
);
assert_eq!(
fs::read_to_string(tmpdir.join("cgroup.subtree_control")).unwrap(),
"SENTINEL_ABOVE",
"walk must not cross above walk_root",
);
}
#[test]
fn setup_above_walk_root_refused() {
let _tempdir_keep_alive = make_inline_tempdir("walk-root-above");
let tmpdir = _tempdir_keep_alive.path();
let walk_root = tmpdir.join("walk-root");
let outside_parent = tmpdir.join("outside");
fs::create_dir_all(&walk_root).unwrap();
fs::create_dir_all(&outside_parent).unwrap();
let cg = CgroupManager::new(outside_parent.to_str().unwrap());
let err = cg
.with_walk_root(&walk_root)
.expect_err("outside-walk_root parent must refuse");
let msg = format!("{err:#}");
assert!(
msg.contains("not below walk_root"),
"error must cite walk_root prefix invariant; got {msg:?}",
);
}
#[test]
fn drain_pids_writes_to_walk_root_procs() {
let _tempdir_keep_alive = make_inline_tempdir("drain-walk-root");
let walk_root = _tempdir_keep_alive.path();
let parent = walk_root.join("ktstr");
let child = parent.join("cg_x");
fs::create_dir_all(&child).unwrap();
let child_procs = child.join("cgroup.procs");
fs::write(&child_procs, "12345\n").unwrap();
let walk_root_procs = walk_root.join("cgroup.procs");
fs::write(&walk_root_procs, "").unwrap();
let cg = CgroupManager::new(parent.to_str().unwrap())
.with_walk_root(walk_root)
.expect("parent below walk_root must be admitted");
cg.drain_tasks("cg_x")
.expect("drain_tasks must succeed against tmpfs procs file");
let written = fs::read_to_string(&walk_root_procs).unwrap();
assert!(
written.contains("12345"),
"drained pid must land in {{walk_root}}/cgroup.procs; got {written:?}",
);
}
#[test]
fn cleanup_recursive_drain_respects_walk_root() {
let _tempdir_keep_alive = make_inline_tempdir("cleanup-walk-root");
let walk_root = _tempdir_keep_alive.path();
let parent = walk_root.join("ktstr");
let child = parent.join("child");
fs::create_dir_all(&child).unwrap();
let walk_root_procs = walk_root.join("cgroup.procs");
fs::write(&walk_root_procs, "").unwrap();
fs::write(child.join("cgroup.procs"), "2222\n").unwrap();
cleanup_recursive(&parent, walk_root);
let written = fs::read_to_string(&walk_root_procs).unwrap();
assert!(
written.contains("2222"),
"child pid must land in walk_root cgroup.procs (not canonical \
/sys/fs/cgroup/cgroup.procs); got {written:?}",
);
}
#[test]
fn cleanup_all_threads_walk_root_to_cleanup_recursive() {
let _tempdir_keep_alive = make_inline_tempdir("cleanup-all-walk-root");
let walk_root = _tempdir_keep_alive.path();
let parent = walk_root.join("ktstr");
let child = parent.join("child");
fs::create_dir_all(&child).unwrap();
let walk_root_procs = walk_root.join("cgroup.procs");
fs::write(&walk_root_procs, "").unwrap();
fs::write(child.join("cgroup.procs"), "3333\n").unwrap();
let cg = CgroupManager::new(parent.to_str().unwrap())
.with_walk_root(walk_root)
.expect("parent below walk_root must be admitted");
cg.cleanup_all().expect("cleanup_all must succeed on tmpfs");
let written = fs::read_to_string(&walk_root_procs).unwrap();
assert!(
written.contains("3333"),
"cleanup_all must drain child pids to walk_root cgroup.procs; got {written:?}",
);
}
#[test]
fn default_root_requires_root_under_parent_non_root_requires() {
assert!(CgroupManager::default_root_requires_root(
std::path::Path::new("/sys/fs/cgroup"),
std::path::Path::new("/sys/fs/cgroup/ktstr"),
1000,
));
}
#[test]
fn default_root_requires_root_under_parent_root_exempt() {
assert!(!CgroupManager::default_root_requires_root(
std::path::Path::new("/sys/fs/cgroup"),
std::path::Path::new("/sys/fs/cgroup/ktstr"),
0,
));
}
#[test]
fn default_root_requires_root_outside_parent_non_root_exempt() {
assert!(!CgroupManager::default_root_requires_root(
std::path::Path::new("/sys/fs/cgroup"),
std::path::Path::new("/tmp/some-test-dir"),
1000,
));
}
#[test]
fn default_root_requires_root_delegated_root_non_root_exempt() {
assert!(!CgroupManager::default_root_requires_root(
std::path::Path::new("/sys/fs/cgroup/user.slice/deleg"),
std::path::Path::new("/sys/fs/cgroup/user.slice/deleg/ktstr"),
1000,
));
}
#[test]
fn default_root_requires_root_tmpdir_root_non_root_exempt() {
assert!(!CgroupManager::default_root_requires_root(
std::path::Path::new("/tmp/some-test-root"),
std::path::Path::new("/tmp/some-test-root/ktstr"),
1000,
));
}