|
| 1 | +use anyhow::Context; |
| 2 | +use clap::Parser; |
| 3 | +use nix::sys::resource::{getrlimit, setrlimit, Resource}; |
| 4 | +use std::panic::catch_unwind; |
| 5 | +use std::path::PathBuf; |
| 6 | +use std::process::{Command, Stdio}; |
| 7 | +use std::{env, mem}; |
| 8 | +use tempdir::TempDir; |
| 9 | +use test_cases::{test_cases, Test, TestCase, TestSetup}; |
| 10 | + |
| 11 | +fn get_test(name: &str) -> anyhow::Result<Box<dyn Test>> { |
| 12 | + let tests = test_cases(); |
| 13 | + tests |
| 14 | + .into_iter() |
| 15 | + .find(|t| t.name() == name) |
| 16 | + .with_context(|| format!("No such test: {name}")) |
| 17 | + .map(|t| t.test) |
| 18 | +} |
| 19 | + |
| 20 | +fn start_vm(test_setup: TestSetup) -> anyhow::Result<()> { |
| 21 | + // Raise soft fd limit up to the hard limit |
| 22 | + let (_soft_limit, hard_limit) = |
| 23 | + getrlimit(Resource::RLIMIT_NOFILE).context("getrlimit RLIMIT_NOFILE")?; |
| 24 | + setrlimit(Resource::RLIMIT_NOFILE, hard_limit, hard_limit) |
| 25 | + .context("setrlimit RLIMIT_NOFILE")?; |
| 26 | + |
| 27 | + let test = get_test(&test_setup.test_case)?; |
| 28 | + test.start_vm(test_setup.clone()) |
| 29 | + .with_context(|| format!("testcase: {test_setup:?}"))?; |
| 30 | + Ok(()) |
| 31 | +} |
| 32 | + |
| 33 | +fn run_single_test(test_case: &str) -> anyhow::Result<bool> { |
| 34 | + let executable = env::current_exe().context("Failed to detect current executable")?; |
| 35 | + let tmp_dir = |
| 36 | + TempDir::new(&format!("krun-test-{test_case}")).context("Failed to create tmp dir")?; |
| 37 | + |
| 38 | + let child = Command::new(&executable) |
| 39 | + .arg("start-vm") |
| 40 | + .arg("--test-case") |
| 41 | + .arg(test_case) |
| 42 | + .arg("--tmp-dir") |
| 43 | + .arg(tmp_dir.path()) |
| 44 | + .stdin(Stdio::piped()) |
| 45 | + .stdout(Stdio::piped()) |
| 46 | + .stderr(Stdio::piped()) |
| 47 | + .spawn() |
| 48 | + .context("Failed to start subprocess for test")?; |
| 49 | + |
| 50 | + let _ = get_test(test_case)?; |
| 51 | + let result = catch_unwind(|| { |
| 52 | + let test = get_test(test_case).unwrap(); |
| 53 | + test.check(child); |
| 54 | + }); |
| 55 | + |
| 56 | + match result { |
| 57 | + Ok(()) => { |
| 58 | + println!("[{test_case}]: OK"); |
| 59 | + Ok(true) |
| 60 | + } |
| 61 | + Err(_e) => { |
| 62 | + println!("[{test_case}]: FAIL (dir {:?} kept)", tmp_dir.path()); |
| 63 | + mem::forget(tmp_dir); |
| 64 | + Ok(false) |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +fn run_tests(test_case: &str) -> anyhow::Result<()> { |
| 70 | + let mut num_tests = 1; |
| 71 | + let mut num_ok: usize = 0; |
| 72 | + |
| 73 | + if test_case == "all" { |
| 74 | + let test_cases = test_cases(); |
| 75 | + num_tests = test_cases.len(); |
| 76 | + |
| 77 | + for TestCase { name, test: _ } in test_cases { |
| 78 | + num_ok += run_single_test(name).context(name)? as usize; |
| 79 | + } |
| 80 | + } else { |
| 81 | + num_ok += run_single_test(test_case).context(test_case.to_string())? as usize; |
| 82 | + } |
| 83 | + |
| 84 | + let num_failures = num_tests - num_ok; |
| 85 | + if num_failures > 0 { |
| 86 | + println!("\nFAIL (PASSED {num_ok}/{num_tests})"); |
| 87 | + anyhow::bail!("") |
| 88 | + } else { |
| 89 | + println!("\nOK (PASSED {num_ok}/{num_tests})"); |
| 90 | + } |
| 91 | + |
| 92 | + Ok(()) |
| 93 | +} |
| 94 | + |
| 95 | +#[derive(clap::Subcommand, Clone, Debug)] |
| 96 | +enum CliCommand { |
| 97 | + Test { |
| 98 | + /// Specify which test to run or "all" |
| 99 | + #[arg(long, default_value = "all")] |
| 100 | + test_case: String, |
| 101 | + }, |
| 102 | + StartVm { |
| 103 | + #[arg(long)] |
| 104 | + test_case: String, |
| 105 | + #[arg(long)] |
| 106 | + tmp_dir: PathBuf, |
| 107 | + }, |
| 108 | +} |
| 109 | + |
| 110 | +impl Default for CliCommand { |
| 111 | + fn default() -> Self { |
| 112 | + Self::Test { |
| 113 | + test_case: "all".to_string(), |
| 114 | + } |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +#[derive(clap::Parser)] |
| 119 | +struct Cli { |
| 120 | + #[command(subcommand)] |
| 121 | + command: Option<CliCommand>, |
| 122 | +} |
| 123 | + |
| 124 | +fn main() -> anyhow::Result<()> { |
| 125 | + let cli = Cli::parse(); |
| 126 | + let command = cli.command.unwrap_or_default(); |
| 127 | + |
| 128 | + match command { |
| 129 | + CliCommand::StartVm { test_case, tmp_dir } => start_vm(TestSetup { test_case, tmp_dir }), |
| 130 | + CliCommand::Test { test_case } => run_tests(&test_case), |
| 131 | + } |
| 132 | +} |
0 commit comments