1
0
mirror of https://github.com/containers/youki synced 2024-11-23 01:11:58 +01:00

Merge pull request #310 from YJDoc2/add-test-utils

Add integration test utils necessary for implementing rest integration tests
This commit is contained in:
utam0k 2021-09-21 00:29:20 +09:00 committed by GitHub
commit 2af52169cf
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 431 additions and 139 deletions

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
@ -69,6 +71,78 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "darling"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "derive_builder"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "filetime"
version = "0.2.15"
@ -93,6 +167,12 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.3"
@ -104,6 +184,18 @@ dependencies = [
"wasi",
]
[[package]]
name = "getset"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b328c01a4d71d2d8173daa93562a73ab0fe85616876f02500f53d82948c504"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
@ -119,6 +211,12 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.7.0"
@ -129,6 +227,12 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -151,6 +255,19 @@ dependencies = [
"autocfg",
]
[[package]]
name = "oci-spec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b33c04af97b8e9e3155d9c530a1260e7e6c8d36a1e7353e54f3aa013d388a0c7"
dependencies = [
"derive_builder",
"getset",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "once_cell"
version = "1.8.0"
@ -260,6 +377,43 @@ dependencies = [
"bitflags",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -304,6 +458,26 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
@ -346,6 +520,17 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "which"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9"
dependencies = [
"either",
"lazy_static",
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -386,9 +571,11 @@ dependencies = [
"clap_derive",
"flate2",
"lazy_static",
"oci-spec",
"once_cell",
"rand",
"tar",
"test_framework",
"uuid",
"which",
]

@ -20,4 +20,6 @@ flate2 = "1.0"
test_framework = { version = "0.1.0", path = "../test_framework"}
anyhow = "1.0"
lazy_static = "1.4.0"
once_cell = "1.8.0"
once_cell = "1.8.0"
oci-spec = "0.5.1"
which = "4.2.2"

@ -1,2 +1,2 @@
pub mod support;
pub mod tests;
pub mod utils;

@ -1,17 +1,13 @@
mod support;
mod tests;
mod utils;
use anyhow::{bail, Result};
use crate::tests::lifecycle::{ContainerCreate, ContainerLifecycle};
use crate::utils::support::set_runtime_path;
use anyhow::Result;
use clap::Clap;
use std::path::PathBuf;
use test_framework::TestManager;
use crate::support::cleanup_test;
use crate::support::get_project_path;
use crate::support::initialize_test;
use crate::support::set_runtime_path;
use crate::tests::lifecycle::{ContainerCreate, ContainerLifecycle};
#[derive(Clap, Debug)]
#[clap(version = "0.0.1", author = "youki team")]
struct Opts {
@ -43,22 +39,26 @@ fn parse_tests(tests: &[String]) -> Vec<(&str, Option<Vec<&str>>)> {
fn main() -> Result<()> {
let opts: Opts = Opts::parse();
let path = std::fs::canonicalize(opts.runtime).expect("Invalid runtime path");
set_runtime_path(&path);
match std::fs::canonicalize(opts.runtime.clone()) {
// runtime path is relative or resolved correctly
Ok(path) => set_runtime_path(&path),
// runtime path is name of program which probably exists in $PATH
Err(_) => match which::which(opts.runtime) {
Ok(path) => set_runtime_path(&path),
Err(e) => {
eprintln!("Error in finding runtime : {}\nexiting.", e);
std::process::exit(66);
}
},
}
let mut tm = TestManager::new();
let project_path = get_project_path();
let cl = ContainerLifecycle::new(&project_path);
let cc = ContainerCreate::new(&project_path);
let cl = ContainerLifecycle::new();
let cc = ContainerCreate::new();
tm.add_test_group(&cl);
tm.add_test_group(&cc);
if initialize_test(&project_path).is_err() {
bail!("Can not initilize test.")
}
if let Some(tests) = opts.tests {
let tests_to_run = parse_tests(&tests);
tm.run_selected(tests_to_run);
@ -66,8 +66,5 @@ fn main() -> Result<()> {
tm.run_all();
}
if cleanup_test(&project_path).is_err() {
bail!("Can not cleanup test.")
}
Ok(())
}

@ -1,80 +0,0 @@
use flate2::read::GzDecoder;
use once_cell::sync::OnceCell;
use rand::Rng;
use std::fs::File;
use std::path::PathBuf;
use std::{env, fs, path::Path};
use tar::Archive;
use uuid::Uuid;
static RUNTIME_PATH: OnceCell<PathBuf> = OnceCell::new();
pub fn set_runtime_path(path: &Path) {
RUNTIME_PATH.set(path.to_owned()).unwrap();
}
pub fn get_runtime_path() -> &'static PathBuf {
RUNTIME_PATH.get().expect("Runtime path is not set")
}
pub fn initialize_test(project_path: &Path) -> Result<(), std::io::Error> {
prepare_test_workspace(project_path)
}
pub fn cleanup_test(project_path: &Path) -> Result<(), std::io::Error> {
delete_test_workspace(project_path)
}
pub fn get_project_path() -> PathBuf {
let current_dir_path_result = env::current_dir();
match current_dir_path_result {
Ok(path_buf) => path_buf,
Err(e) => panic!("directory is not found, {}", e),
}
}
// This will generate the UUID needed when creating the container.
pub fn generate_uuid() -> Uuid {
let mut rng = rand::thread_rng();
const CHARSET: &[u8] = b"0123456789abcdefABCDEF";
let rand_string: String = (0..32)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect();
match Uuid::parse_str(&rand_string) {
Ok(uuid) => uuid,
Err(e) => panic!("can not parse uuid, {}", e),
}
}
// Temporary files to be used for testing are created in the `integration-workspace`.
fn prepare_test_workspace(project_path: &Path) -> Result<(), std::io::Error> {
let integration_test_workspace_path = project_path.join("integration-workspace");
let create_dir_result = fs::create_dir_all(&integration_test_workspace_path);
if fs::create_dir_all(&integration_test_workspace_path).is_err() {
return create_dir_result;
}
let tar_file_name = "bundle.tar.gz";
let tar_path = integration_test_workspace_path.join(tar_file_name);
fs::copy(
tar_file_name,
&integration_test_workspace_path.join(tar_file_name),
)?;
let tar_gz = File::open(tar_path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(integration_test_workspace_path)?;
Ok(())
}
// This deletes all temporary files.
fn delete_test_workspace(project_path: &Path) -> Result<(), std::io::Error> {
fs::remove_dir_all(project_path.join("integration-workspace"))?;
Ok(())
}

@ -1,18 +1,26 @@
use super::{create, kill};
use crate::support::generate_uuid;
use std::path::{Path, PathBuf};
use crate::utils::TempDir;
use crate::utils::{generate_uuid, prepare_bundle};
use test_framework::{TestResult, TestableGroup};
pub struct ContainerCreate {
project_path: PathBuf,
project_path: TempDir,
container_id: String,
}
impl<'a> Default for ContainerCreate {
fn default() -> Self {
Self::new()
}
}
impl<'a> ContainerCreate {
pub fn new(project_path: &Path) -> Self {
pub fn new() -> Self {
let id = generate_uuid();
let temp_dir = prepare_bundle(&id).unwrap();
ContainerCreate {
project_path: project_path.to_owned(),
container_id: generate_uuid().to_string(),
project_path: temp_dir,
container_id: id.to_string(),
}
}

@ -1,20 +1,33 @@
use std::path::{Path, PathBuf};
use crate::support::generate_uuid;
use crate::utils::{generate_uuid, prepare_bundle, TempDir};
use std::thread::sleep;
use std::time::Duration;
use test_framework::{TestResult, TestableGroup};
use super::{create, delete, kill, start, state};
// By experimenting, somewhere around 50 is enough for youki process
// to get the kill signal and shut down
// here we add a little buffer time as well
const SLEEP_TIME: Duration = Duration::from_millis(75);
pub struct ContainerLifecycle {
project_path: PathBuf,
project_path: TempDir,
container_id: String,
}
impl Default for ContainerLifecycle {
fn default() -> Self {
Self::new()
}
}
impl ContainerLifecycle {
pub fn new(project_path: &Path) -> Self {
pub fn new() -> Self {
let id = generate_uuid();
let temp_dir = prepare_bundle(&id).unwrap();
ContainerLifecycle {
project_path: project_path.to_owned(),
container_id: generate_uuid().to_string(),
project_path: temp_dir,
container_id: id.to_string(),
}
}
@ -31,7 +44,11 @@ impl ContainerLifecycle {
}
pub fn kill(&self) -> TestResult {
kill::kill(&self.project_path, &self.container_id)
let ret = kill::kill(&self.project_path, &self.container_id);
// sleep a little, so the youki process actually gets the signal and shuts down
// otherwise, the tester moves on to next tests before the youki has gotten signal, and delete test can fail
sleep(SLEEP_TIME);
ret
}
pub fn delete(&self) -> TestResult {

@ -1,4 +1,4 @@
use crate::support::get_runtime_path;
use crate::utils::get_runtime_path;
use std::io;
use std::path::Path;
use std::process::{Command, Stdio};
@ -13,11 +13,11 @@ pub fn create(project_path: &Path, id: &str) -> TestResult {
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("--root")
.arg(project_path.join("integration-workspace").join("youki"))
.arg(project_path.join("runtime"))
.arg("create")
.arg(id)
.arg("--bundle")
.arg(project_path.join("integration-workspace").join("bundle"))
.arg(project_path.join("bundle"))
.arg(id)
.spawn()
.expect("Cannot execute create command")
.wait();

@ -1,5 +1,5 @@
use super::get_result_from_output;
use crate::support::get_runtime_path;
use crate::utils::get_runtime_path;
use std::path::Path;
use std::process::{Command, Stdio};
use test_framework::TestResult;
@ -9,7 +9,7 @@ pub fn delete(project_path: &Path, id: &str) -> TestResult {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.arg("--root")
.arg(project_path.join("integration-workspace").join("youki"))
.arg(project_path.join("runtime"))
.arg("delete")
.arg(id)
.spawn()

@ -1,30 +1,20 @@
use super::get_result_from_output;
use crate::support::get_runtime_path;
use crate::utils::get_runtime_path;
use std::path::Path;
use std::process::{Command, Stdio};
use std::thread::sleep;
use std::time::Duration;
use test_framework::TestResult;
// By experimenting, somewhere around 50 is enough for youki process
// to get the kill signal and shut down
// here we add a little buffer time as well
const SLEEP_TIME: u64 = 75;
pub fn kill(project_path: &Path, id: &str) -> TestResult {
let res = Command::new(get_runtime_path())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.arg("--root")
.arg(project_path.join("integration-workspace").join("youki"))
.arg(project_path.join("runtime"))
.arg("kill")
.arg(id)
.arg("9")
.spawn()
.expect("failed to execute kill command")
.wait_with_output();
// sleep a little, so the youki process actually gets the signal and shuts down
// otherwise, the tester moves on to next tests before the youki has gotten signal, and delete test can fail
sleep(Duration::from_millis(SLEEP_TIME));
get_result_from_output(res)
}

@ -1,5 +1,5 @@
use super::get_result_from_output;
use crate::support::get_runtime_path;
use crate::utils::get_runtime_path;
use std::path::Path;
use std::process::{Command, Stdio};
use test_framework::TestResult;
@ -9,7 +9,7 @@ pub fn start(project_path: &Path, id: &str) -> TestResult {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.arg("--root")
.arg(project_path.join("integration-workspace").join("youki"))
.arg(project_path.join("runtime"))
.arg("start")
.arg(id)
.spawn()

@ -1,4 +1,4 @@
use crate::support::get_runtime_path;
use crate::utils::get_runtime_path;
use std::io;
use std::path::Path;
use std::process::{Command, Stdio};
@ -9,7 +9,7 @@ pub fn state(project_path: &Path, id: &str) -> TestResult {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.arg("--root")
.arg(project_path.join("integration-workspace").join("youki"))
.arg(project_path.join("runtime"))
.arg("state")
.arg(id)
.spawn()

@ -0,0 +1,6 @@
pub mod support;
pub mod temp_dir;
pub mod test_utils;
pub use support::{get_project_path, get_runtime_path, set_runtime_path};
pub use temp_dir::{create_temp_dir, TempDir};
pub use test_utils::{generate_uuid, prepare_bundle, set_config, start_runtime, stop_runtime};

@ -0,0 +1,23 @@
use once_cell::sync::OnceCell;
use std::env;
use std::path::Path;
use std::path::PathBuf;
static RUNTIME_PATH: OnceCell<PathBuf> = OnceCell::new();
pub fn set_runtime_path(path: &Path) {
RUNTIME_PATH.set(path.to_owned()).unwrap();
}
pub fn get_runtime_path() -> &'static PathBuf {
RUNTIME_PATH.get().expect("Runtime path is not set")
}
#[allow(dead_code)]
pub fn get_project_path() -> PathBuf {
let current_dir_path_result = env::current_dir();
match current_dir_path_result {
Ok(path_buf) => path_buf,
Err(e) => panic!("directory is not found, {}", e),
}
}

@ -0,0 +1,59 @@
///! Thin wrapper struct for creating temp directories
///! Taken after cgroups/tempdir
use anyhow::Result;
use std::{
fs,
ops::Deref,
path::{Path, PathBuf},
};
use uuid::Uuid;
pub struct TempDir {
path: Option<PathBuf>,
}
impl TempDir {
pub fn new<P: Into<PathBuf>>(path: P) -> Result<Self> {
let p = path.into();
std::fs::create_dir_all(&p)?;
Ok(Self { path: Some(p) })
}
pub fn path(&self) -> &Path {
self.path
.as_ref()
.expect("temp dir has already been removed")
}
pub fn remove(&mut self) {
if let Some(p) = &self.path {
let _ = fs::remove_dir_all(p);
self.path = None;
}
}
}
impl Drop for TempDir {
fn drop(&mut self) {
self.remove();
}
}
impl AsRef<Path> for TempDir {
fn as_ref(&self) -> &Path {
self.path()
}
}
impl Deref for TempDir {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.path()
}
}
pub fn create_temp_dir(id: &Uuid) -> Result<TempDir> {
let dir = TempDir::new(std::env::temp_dir().join(id.to_string()))?;
Ok(dir)
}

@ -0,0 +1,83 @@
///! Contains utility functions for testing
///! Similar to https://github.com/opencontainers/runtime-tools/blob/master/validation/util/test.go
use super::get_runtime_path;
use super::{create_temp_dir, TempDir};
use anyhow::Result;
use flate2::read::GzDecoder;
use oci_spec::runtime::Spec;
use rand::Rng;
use std::fs::File;
use std::process::{Child, Command, Stdio};
use std::{fs, path::Path};
use tar::Archive;
use uuid::Uuid;
/// This will generate the UUID needed when creating the container.
pub fn generate_uuid() -> Uuid {
let mut rng = rand::thread_rng();
const CHARSET: &[u8] = b"0123456789abcdefABCDEF";
let rand_string: String = (0..32)
.map(|_| {
let idx = rng.gen_range(0..CHARSET.len());
CHARSET[idx] as char
})
.collect();
match Uuid::parse_str(&rand_string) {
Ok(uuid) => uuid,
Err(e) => panic!("can not parse uuid, {}", e),
}
}
/// Creates a bundle directory in a temp directory
pub fn prepare_bundle(id: &Uuid) -> Result<TempDir> {
let temp_dir = create_temp_dir(id)?;
let tar_file_name = "bundle.tar.gz";
let tar_path = std::env::current_dir()?.join(tar_file_name);
fs::copy(tar_path.clone(), (&temp_dir).join(tar_file_name))?;
let tar_gz = File::open(tar_path)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive.unpack(&temp_dir)?;
Ok(temp_dir)
}
/// Sets the config.json file as per given spec
#[allow(dead_code)]
pub fn set_config<P: AsRef<Path>>(project_path: P, config: &Spec) -> Result<()> {
let path = project_path.as_ref().join("config.json");
config.save(path)?;
Ok(())
}
/// Starts the runtime with given directory as root directory
#[allow(dead_code)]
pub fn start_runtime<P: AsRef<Path>>(id: &Uuid, dir: P) -> Result<Child> {
let res = Command::new(get_runtime_path())
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("--root")
.arg(dir.as_ref().join("runtime"))
.arg("create")
.arg(id.to_string())
.arg("--bundle")
.arg(dir.as_ref().join("bundle"))
.spawn()?;
Ok(res)
}
/// Sends a kill command to the given container process
#[allow(dead_code)]
pub fn stop_runtime<P: AsRef<Path>>(id: &Uuid, dir: P) -> Result<Child> {
let res = Command::new(get_runtime_path())
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("--root")
.arg(dir.as_ref().join("runtime"))
.arg("kill")
.arg(id.to_string())
.arg("9")
.spawn()?;
Ok(res)
}