use std::{
fs::Permissions,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
};
use anyhow::Result;
use qlean::{Distro, Image, ImageConfig, MachineConfig, with_machine};
use serial_test::serial;
mod utils;
use utils::tracing_subscriber_init;
#[tokio::test]
#[serial]
async fn hello() -> Result<()> {
tracing_subscriber_init();
let image = Image::new(ImageConfig::default().with_distro(Distro::Debian)).await?;
let config = MachineConfig::default();
with_machine(&image, &config, |vm| {
Box::pin(async {
let result = vm.exec("whoami").await?;
assert!(result.status.success());
assert_eq!(str::from_utf8(&result.stdout)?.trim(), "root");
Ok(())
})
})
.await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_file_transfer() -> Result<()> {
tracing_subscriber_init();
let image = Image::new(ImageConfig::default().with_distro(Distro::Debian)).await?;
let config = MachineConfig::default();
with_machine(&image, &config, |vm| {
Box::pin(async {
let test_file_content = b"Hello, Qlean!";
let test_file = tempfile::NamedTempFile::new()?;
let test_file_name = test_file.path().file_name().unwrap().to_str().unwrap();
let test_file_recv = tempfile::NamedTempFile::new()?;
tokio::fs::write(test_file.path(), test_file_content).await?;
vm.upload(test_file.path(), Path::new("/tmp")).await?;
let result = vm.exec(format!("cat /tmp/{}", test_file_name)).await?;
assert!(result.status.success());
assert_eq!(
str::from_utf8(&result.stdout)?.trim(),
str::from_utf8(test_file_content)?.trim()
);
vm.download(
Path::new(&format!("/tmp/{}", test_file_name)),
test_file_recv.path(),
)
.await?;
let downloaded_content = tokio::fs::read(test_file_recv.path()).await?;
assert_eq!(downloaded_content, test_file_content);
let test_dir = tempfile::tempdir()?;
let test_dir_name = test_dir.path().file_name().unwrap().to_str().unwrap();
let dir_one = test_dir.path().join("One");
tokio::fs::create_dir(&dir_one).await?;
let val_path = dir_one.join("val.txt");
tokio::fs::write(&val_path, b"Number 1").await?;
let file_two = test_dir.path().join("Two.txt");
let file_three = test_dir.path().join("Three.txt");
tokio::fs::write(&file_two, b"Number 2").await?;
tokio::fs::write(&file_three, b"Number 3").await?;
let test_dir_recv = tempfile::tempdir()?;
vm.upload(test_dir.path(), Path::new("/tmp")).await?;
let result = vm
.exec(format!("cat /tmp/{}/One/val.txt", test_dir_name))
.await?;
assert!(result.status.success());
assert_eq!(str::from_utf8(&result.stdout)?.trim(), "Number 1");
let result = vm
.exec(format!("cat /tmp/{}/Two.txt", test_dir_name))
.await?;
assert!(result.status.success());
assert_eq!(str::from_utf8(&result.stdout)?.trim(), "Number 2");
let result = vm
.exec(format!("cat /tmp/{}/Three.txt", test_dir_name))
.await?;
assert!(result.status.success());
assert_eq!(str::from_utf8(&result.stdout)?.trim(), "Number 3");
vm.download(
Path::new(&format!("/tmp/{}", test_dir_name)),
test_dir_recv.path(),
)
.await?;
let downloaded_path = test_dir_recv.path().join(test_dir_name);
let downloaded_val =
tokio::fs::read_to_string(downloaded_path.join("One").join("val.txt")).await?;
assert_eq!(downloaded_val, "Number 1");
let downloaded_two = tokio::fs::read_to_string(downloaded_path.join("Two.txt")).await?;
assert_eq!(downloaded_two, "Number 2");
let downloaded_three =
tokio::fs::read_to_string(downloaded_path.join("Three.txt")).await?;
assert_eq!(downloaded_three, "Number 3");
Ok(())
})
})
.await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_file_operation() -> Result<()> {
tracing_subscriber_init();
let image = Image::new(ImageConfig::default().with_distro(Distro::Debian)).await?;
let config = MachineConfig::default();
with_machine(&image, &config, |vm| {
Box::pin(async {
let base_dir_cmd = vm.exec("mkdir -p /tmp/qlean").await?;
assert!(base_dir_cmd.status.success());
let base_dir = PathBuf::from("/tmp/qlean");
let single_dir = base_dir.join("single");
vm.create_dir(&single_dir).await?;
let nested_dir = base_dir.join("nested/child");
vm.create_dir_all(&nested_dir).await?;
assert!(vm.exists(&single_dir).await?);
assert!(vm.exists(&nested_dir).await?);
assert!(!vm.exists(base_dir.join("missing")).await?);
let file_path = base_dir.join("hello.txt");
let file_content = b"Filesystem test";
vm.write(&file_path, file_content).await?;
let read_bytes = vm.read(&file_path).await?;
assert_eq!(read_bytes, file_content);
let read_string = vm.read_to_string(&file_path).await?;
assert_eq!(read_string, "Filesystem test");
let meta = vm.metadata(&file_path).await?;
assert!(meta.file_type().is_file());
assert_eq!(meta.len(), file_content.len() as u64);
vm.set_permissions(&file_path, Permissions::from_mode(0o600))
.await?;
let meta = vm.metadata(&file_path).await?;
assert!(
meta.permissions
.map(|p| p & 0o777 == 0o600)
.unwrap_or(false)
);
let copy_path = base_dir.join("copy.txt");
vm.copy(&file_path, ©_path).await?;
assert_eq!(vm.read(©_path).await?, file_content);
let renamed_path = base_dir.join("renamed.txt");
vm.rename(©_path, &renamed_path).await?;
assert!(vm.exists(&renamed_path).await?);
assert!(!vm.exists(©_path).await?);
let hard_link_path = base_dir.join("hard.txt");
vm.hard_link(&file_path, &hard_link_path).await?;
assert_eq!(vm.read(&hard_link_path).await?, file_content);
let symlink_path = base_dir.join("symlink.txt");
let symlink_cmd = format!("ln -s {} {}", file_path.display(), symlink_path.display());
let symlink_res = vm.exec(&symlink_cmd).await?;
assert!(symlink_res.status.success());
let target = vm.read_link(&symlink_path).await?;
assert_eq!(target, file_path);
let mut entries = vm.read_dir(&base_dir).await?;
let mut names = Vec::new();
while let Some(entry) = entries.next() {
names.push(entry.file_name());
}
assert!(names.contains(&"hello.txt".to_string()));
assert!(names.contains(&"renamed.txt".to_string()));
assert!(names.contains(&"hard.txt".to_string()));
assert!(names.contains(&"symlink.txt".to_string()));
assert!(names.contains(&"single".to_string()));
vm.remove_file(&renamed_path).await?;
vm.remove_file(&hard_link_path).await?;
vm.remove_file(&symlink_path).await?;
vm.remove_file(&file_path).await?;
vm.remove_dir_all(&nested_dir).await?;
vm.remove_dir_all(&base_dir.join("nested")).await?;
vm.remove_dir_all(&single_dir).await?;
vm.remove_dir_all(&base_dir).await?;
assert!(!vm.exists(&base_dir).await?);
Ok(())
})
})
.await?;
Ok(())
}