#![cfg(unix)]
use start_command::cgroup_memory::{
build_cgroup_memory_log_snippet, build_cgroup_sampler_start_snippet,
build_cgroup_sampler_stop_snippet,
};
use std::{fs, process::Command};
fn sample(mode: &str, interval: Option<&str>, clock_step: &str) -> String {
let dir = tempfile::tempdir().unwrap();
let script = dir.path().join("sample.sh");
let final_read = if mode == "final" {
r#"printf 'oom 3\noom_kill 3\n' > "$START_COMMAND_CGROUP_ROOT/docker/238abc/memory.events""#
} else {
":"
};
fs::write(
&script,
[
build_cgroup_sampler_start_snippet("task '238'"),
r#"wait "$__start_command_cgroup_sampler""#.into(),
final_read.into(),
build_cgroup_sampler_stop_snippet(),
r#"printf 'sample=%s\n' "$__start_command_cgroup""#.into(),
build_cgroup_memory_log_snippet(r#""$TMPDIR/memory.log""#),
r#"cat "$TMPDIR/memory.log" "$TMPDIR/calls""#.into(),
r#"sed 's/^/sleep=/' "$TMPDIR/sleeps""#.into(),
r#"for file in "$TMPDIR"/start-command-cgroup*; do [ ! -e "$file" ] || exit 1; done"#
.into(),
]
.join("; "),
)
.unwrap();
let mut command = Command::new("/bin/sh");
command
.arg(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../experiments/issue-238-cgroup-polling.sh"
))
.arg(mode)
.arg(&script)
.env_remove("START_COMMAND_CGROUP_SAMPLE_INTERVAL_SECONDS")
.env("START_SAMPLER_CLOCK_STEP", clock_step);
if let Some(interval) = interval {
command.env("START_COMMAND_CGROUP_SAMPLE_INTERVAL_SECONDS", interval);
}
let result = command.output().unwrap();
assert!(result.status.success(), "{:?}", result);
String::from_utf8(result.stdout).unwrap()
}
fn count(output: &str, text: &str) -> usize {
output.lines().filter(|line| line.contains(text)).count()
}
#[test]
fn default_interval_and_local_liveness_after_removal() {
let output = sample("local", None, "5");
assert_eq!(count(&output, "sleep=5"), 3);
assert_eq!(count(&output, "{{.State.Running}}"), 1);
assert_eq!(count(&output, "{{.HostConfig.CgroupnsMode}}"), 1);
assert_eq!(count(&output, "task '238' sh -c"), 0);
}
#[test]
fn override_and_cached_remote_namespace() {
let output = sample("remote", Some("2"), "5");
assert_eq!(count(&output, "sleep=2"), 3);
assert_eq!(count(&output, "{{.HostConfig.CgroupnsMode}}"), 1);
assert_eq!(count(&output, "{{.State.Running}}"), 1);
assert_eq!(count(&output, "task '238' sh -c"), 1);
assert!(output.contains("memory.max=67108864 memory.peak=33554432 oom=1 oom_kill=3"));
}
#[test]
fn invalid_intervals_fall_back_without_a_busy_loop() {
for interval in [
"",
"0",
"000",
"-1",
"0.5",
"nope",
"1; exit 7",
"99999999999999999999999999",
] {
assert_eq!(count(&sample("remote", Some(interval), "5"), "sleep=5"), 3);
}
}
#[test]
fn remote_outages_and_unavailable_diagnostics() {
for mode in ["shared", "no-shell", "outage"] {
let output = sample(mode, None, "5");
assert_eq!(count(&output, "{{.HostConfig.CgroupnsMode}}"), 1);
let (running, execs) = match mode {
"shared" => (0, 0),
"no-shell" => (0, 1),
_ => (4, 4),
};
assert_eq!(count(&output, "{{.State.Running}}"), running);
assert_eq!(count(&output, "task '238' sh -c"), execs);
if mode == "outage" {
assert!(output.contains("sample=67108864 33554432 1 3 0 100"));
} else {
assert!(output.contains("Memory: unavailable"));
assert!(output.contains(if mode == "shared" {
"namespace=host"
} else {
"sh: not found"
}));
}
}
}
#[test]
fn final_read_keeps_conservative_oom_timing() {
assert!(sample("final", None, "5").contains("sample=67108864 33554432 3 3 0 115"));
assert!(sample("final", Some("1"), "1").contains("sample=67108864 33554432 3 3 103 103"));
}