//! A fake `podman` for the runtime tests: a shell script that records every call's arguments and, //! for `run`, does what the test says. Do not edit. //! //! Included with `#[path = "support/fake_podman.rs"] mod fake_podman;`. #![allow(dead_code)] // each test file uses a different part of this module use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex, MutexGuard}; use brokerd::config::{Config, Runner}; static NEXT: AtomicU32 = AtomicU32::new(0); static SERIAL: Mutex<()> = Mutex::new(()); /// Tests that write a script and run it take turns. Otherwise another test's fork can hold the /// script open for writing at the moment it is run, and running it fails with "text file busy" /// (ETXTBSY), which has nothing to do with the code under test. pub fn serial() -> MutexGuard<'static, ()> { SERIAL.lock().unwrap_or_else(|p| p.into_inner()) } pub const IMAGE: &str = "localhost/boxmaker-tools@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; pub struct Fake { pub dir: PathBuf, pub script: PathBuf, } impl Drop for Fake { fn drop(&mut self) { let _ = std::fs::remove_dir_all(&self.dir); } } impl Fake { /// A fake whose `run` does `run_body` (a shell fragment; `$D` is the fake's directory). Every /// other command (`kill`, `rm`) is recorded and succeeds. pub fn new(tag: &str, run_body: &str) -> Fake { let n = NEXT.fetch_add(1, Ordering::SeqCst); let dir = std::env::temp_dir().join(format!("bx-fp-{tag}-{}-{n}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); let script = dir.join("podman"); let text = format!( "#!/bin/sh\nD='{}'\nfor a in \"$@\"; do printf '%s\\n' \"$a\"; done >> \"$D/calls\"\necho --- >> \"$D/calls\"\n[ \"$1\" = run ] || exit 0\n{run_body}\n", dir.display() ); std::fs::write(&script, text).unwrap(); std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap(); Fake { dir, script } } /// Every call so far, each as its arguments. pub fn calls(&self) -> Vec> { let text = std::fs::read_to_string(self.dir.join("calls")).unwrap_or_default(); let mut calls = Vec::new(); let mut current = Vec::new(); for line in text.lines() { if line == "---" { calls.push(std::mem::take(&mut current)); } else { current.push(line.to_string()); } } calls } /// What the last `run` read on standard input, if the body saved it to `$D/stdin`. pub fn stdin(&self) -> String { std::fs::read_to_string(self.dir.join("stdin")).unwrap_or_default() } /// A `[runner]` using this fake, with `extra` lines added. pub fn runner(&self, extra: &str) -> Runner { let text = format!( "[runner]\npodman = \"{}\"\nimage = \"{IMAGE}\"\n{extra}\n", self.script.display() ); Config::parse(&text).unwrap().runner.unwrap() } } /// A log that keeps its lines. #[derive(Clone, Default)] pub struct Lines(pub Arc>>); impl Lines { pub fn sink(&self) -> Arc { let lines = Arc::clone(&self.0); Arc::new(move |l: &str| lines.lock().unwrap().push(l.to_string())) } pub fn all(&self) -> String { self.0.lock().unwrap().join("\n") } } pub fn path(p: &Path) -> String { p.to_str().unwrap().to_string() }