use super::*;
use std::os::unix::fs::symlink;
use tempfile::{TempDir, tempdir};
struct Fixture {
root: TempDir,
podman: PathBuf,
checker: PathBuf,
candidate: PathBuf,
projection: PathBuf,
log: PathBuf,
request: PathBuf,
}
impl Fixture {
fn new(report: &[u8], status: i32, delay: bool) -> Self {
let root = tempdir().unwrap();
let path = |name| root.path().join(name);
let podman = path("podman");
let checker = path("checker");
let candidate = path("candidate");
let projection = path("projection");
let log = path("log");
let request = path("request");
let output = path("output");
fs::create_dir(&candidate).unwrap();
fs::create_dir(&projection).unwrap();
fs::write(&output, report).unwrap();
executable(&checker, "#!/bin/sh\nexit 2\n");
let script = format!(
"#!/bin/sh\nprintf 'CALL\\n' >> {0}\nprintf '%s\\n' \"$@\" >> {0}\ncat > {1}\n{2}cat {3}\nprintf diagnostic >&2\nexit {4}\n",
quote(&log),
quote(&request),
if delay { "sleep 1\n" } else { "" },
quote(&output),
status,
);
executable(&podman, &script);
Self {
root,
podman,
checker,
candidate,
projection,
log,
request,
}
}
fn config(&self) -> WebPodmanConfig {
WebPodmanConfig {
podman: self.podman.clone(),
image: format!("registry.invalid/checker@sha256:{}", "a".repeat(64)),
checker: self.checker.clone(),
chromium: PathBuf::from("/usr/bin/chromium"),
chromium_version: "123.4".to_owned(),
cpu_millis: 1000,
memory_bytes: 268_435_456,
pids_limit: 64,
tmpfs_bytes: 67_108_864,
shm_bytes: 67_108_864,
checker_timeout: Duration::from_millis(100),
wall_timeout: Duration::from_secs(2),
}
}
fn input(&self) -> CheckInput {
CheckInput {
candidate: WebIdInput {
authority: "0".repeat(24),
name: "demo".to_owned(),
version: "1.0.0".to_owned(),
},
candidate_root: self.candidate.clone(),
projection_root: self.projection.clone(),
entry: "entry.js".to_owned(),
tests: "tests.js".to_owned(),
selections: Vec::new(),
}
}
}
fn report(outcome: &str, schema: u32) -> Vec<u8> {
format!(concat!(
"{{\"schema\":{schema},\"outcome\":{outcome},\"diagnostics\":",
"{{\"browser_stdout\":[],\"browser_stderr\":[],\"browser_exit\":null,\"page_error\":null,\"server_errors\":[],\"cleanup_errors\":[]}},",
"\"timings\":{{\"validation_ms\":0,\"server_ms\":0,\"browser_ms\":0,\"cleanup_ms\":0,\"total_ms\":0}}}}\n"
), schema = schema, outcome = outcome).into_bytes()
}
fn executable(path: &Path, contents: &str) {
fs::write(path, contents).unwrap();
fs::set_permissions(path, fs::Permissions::from_mode(0o700)).unwrap();
}
fn quote(path: &Path) -> String {
format!("'{}'", path.display())
}
#[test]
fn success_uses_exact_fresh_command_and_rewrites_request() {
let bytes = report("{\"kind\":\"success\"}", SCHEMA_VERSION);
let fixture = Fixture::new(&bytes, 0, false);
let podman = WebPodman::new(fixture.config()).unwrap();
for _ in 0..2 {
let output = podman.check(fixture.input()).unwrap();
assert_eq!(output.diagnostics.status.code(), Some(0));
assert_eq!(output.diagnostics.stdout, bytes);
assert_eq!(output.diagnostics.stderr, b"diagnostic");
}
let fixed = [
"--remote=false",
"run",
"--rm",
"--pull=never",
"--network=none",
"--read-only",
"--userns=keep-id",
"--cap-drop=ALL",
"--security-opt=no-new-privileges",
"--http-proxy=false",
"--ipc=private",
"--workdir=/tmp",
"--env=HOME=/tmp/home",
"--env=TMPDIR=/tmp",
"--cpus=1.000",
"--memory=268435456",
"--memory-swap=268435456",
"--pids-limit=64",
"--tmpfs=/tmp:rw,nosuid,nodev,noexec,size=67108864",
"--shm-size=67108864",
];
assert_eq!(
podman
.args
.iter()
.map(|value| value.to_str().unwrap())
.collect::<Vec<_>>(),
fixed
);
let expected = format!(
"CALL\n{}\n--volume\n{}:{}:{}\n--volume\n{}:{}:{}\n--volume\n{}:{}:{}\n--\n{}\n{}\n",
fixed.join("\n"),
fixture.candidate.display(),
CANDIDATE,
DATA_OPTIONS,
fixture.projection.display(),
PROJECTION,
DATA_OPTIONS,
fixture.checker.display(),
CHECKER,
CHECKER_OPTIONS,
fixture.config().image,
CHECKER,
);
assert_eq!(
fs::read_to_string(&fixture.log).unwrap(),
expected.repeat(2)
);
let request =
kcode_k1_web_checker_protocol::decode_request(&fs::read(&fixture.request).unwrap())
.unwrap();
assert_eq!(request.candidate_root, Path::new(CANDIDATE));
assert_eq!(request.projection_root, Path::new(PROJECTION));
assert_eq!(request.timeout_ms, 100);
assert!(
podman.checker_digest().starts_with("sha256:")
&& podman.command_policy_identity().starts_with("sha256:")
);
}
#[test]
fn failures_are_separate_and_paths_are_validated() {
let failure = report(
"{\"kind\":\"failure\",\"stage\":\"test\",\"message\":\"failed\"}",
SCHEMA_VERSION,
);
let completed = Fixture::new(&failure, 1, false);
assert!(matches!(
WebPodman::new(completed.config())
.unwrap()
.check(completed.input())
.unwrap()
.report
.outcome,
Outcome::Failure { .. }
));
let schema = Fixture::new(&report("{\"kind\":\"success\"}", 2), 0, false);
assert!(matches!(
WebPodman::new(schema.config())
.unwrap()
.check(schema.input()),
Err(WebPodmanError::Infrastructure {
report: Some(_),
..
})
));
let nonzero = Fixture::new(&report("{\"kind\":\"success\"}", SCHEMA_VERSION), 3, false);
assert!(matches!(
WebPodman::new(nonzero.config())
.unwrap()
.check(nonzero.input()),
Err(WebPodmanError::Infrastructure { .. })
));
let spawn = Fixture::new(&report("{\"kind\":\"success\"}", SCHEMA_VERSION), 0, false);
let runner = WebPodman::new(spawn.config()).unwrap();
executable(&spawn.podman, "#!/missing/interpreter\n");
assert!(matches!(
runner.check(spawn.input()),
Err(WebPodmanError::Spawn(_))
));
let timeout = Fixture::new(&report("{\"kind\":\"success\"}", SCHEMA_VERSION), 0, true);
let mut config = timeout.config();
config.wall_timeout = Duration::from_millis(120);
config.checker_timeout = Duration::from_millis(20);
assert!(matches!(
WebPodman::new(config).unwrap().check(timeout.input()),
Err(WebPodmanError::Timeout { .. })
));
let link = completed.root.path().join("checker-link");
symlink(&completed.checker, &link).unwrap();
let mut config = completed.config();
config.checker = link;
assert!(matches!(
WebPodman::new(config),
Err(WebPodmanError::InvalidInput { .. })
));
let runner = WebPodman::new(completed.config()).unwrap();
let mut input = completed.input();
input.projection_root = input.candidate_root.clone();
assert!(matches!(
runner.check(input),
Err(WebPodmanError::InvalidInput { .. })
));
}