Add test harness for IO serial tests
This commit is contained in:
parent
41cd56fa54
commit
89d1634a63
14 changed files with 250 additions and 23 deletions
32
.justfile
32
.justfile
|
|
@ -1,5 +1,5 @@
|
|||
watch command="run" args="":
|
||||
DEBUG=${DEBUG:-} watchexec -c -w src -- cargo {{ command }} {{ args }}
|
||||
watch *args:
|
||||
watchexec -c -w src -w Cargo.toml -- just {{ args }}
|
||||
|
||||
alias w := watch
|
||||
|
||||
|
|
@ -12,3 +12,31 @@ verify:
|
|||
&& return
|
||||
done
|
||||
|
||||
[env("PROPTEST_CASES", "16640")]
|
||||
test pattern="":
|
||||
cargo test {{ pattern}} --timings -- --test-threads=1 'serial_tests::'
|
||||
cargo test {{ pattern}} --timings --bin tori
|
||||
cargo test {{ pattern}} --timings --doc
|
||||
cargo test {{ pattern}} --timings --lib -- --skip 'serial_tests::'
|
||||
|
||||
|
||||
|
||||
mutate:
|
||||
cargo mutants --iterate \
|
||||
-E '<impl Debug<' \
|
||||
-E '<impl From<' \
|
||||
-E '<impl std::fmt::Display for ' \
|
||||
-E 'print_help -> bool' \
|
||||
--output target/mutants
|
||||
|
||||
|
||||
cover:
|
||||
cargo llvm-cov --no-report
|
||||
cargo llvm-cov report --html
|
||||
cargo llvm-cov report \
|
||||
| tail -1 | awk \
|
||||
'{ print " [ Regions:", $4, "• Functions:", $7, "• Lines:", $10, "]" }'
|
||||
|
||||
cover-open:
|
||||
cargo llvm-cov report --open
|
||||
|
||||
|
|
|
|||
|
|
@ -170,3 +170,9 @@ used_underscore_items = "warn"
|
|||
useless_let_if_seq = "warn"
|
||||
zero_sized_map_values = "warn"
|
||||
zombie_processes = "deny"
|
||||
|
||||
[profile.test.package.proptest]
|
||||
opt-level = 3
|
||||
|
||||
[profile.test.package.rand_chacha]
|
||||
opt-level = 3
|
||||
|
|
|
|||
72
src/conf.rs
72
src/conf.rs
|
|
@ -5,24 +5,28 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
log::{self, elog},
|
||||
dev::{log::elog},
|
||||
run::Command,
|
||||
};
|
||||
|
||||
pub fn load() -> Result<Configuration, Error> {
|
||||
log::elog("Loading configuration");
|
||||
elog("Loading configuration");
|
||||
|
||||
let mut candidate = Configuration::default();
|
||||
|
||||
let root = get_root();
|
||||
elog(&format!("Reading 'tori.conf' from: {root:?}"));
|
||||
let contents = fs::read_to_string(root.join("tori.conf"))?;
|
||||
elog(&format!("Read configuration: {contents:?}"));
|
||||
|
||||
let map: HashMap<String, String> = contents
|
||||
.lines()
|
||||
.filter_map(|line| line.split_once('='))
|
||||
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
||||
.map(|(k, v)| (k.trim().to_owned(), v.trim().to_owned()))
|
||||
.collect();
|
||||
|
||||
elog(&format!("Assembled configuration map: {map:#?}"));
|
||||
|
||||
if let Some(su_command) = map.get("su_command") {
|
||||
let wraps = map.get("su_command_wraps").is_some_and(|v| v == "true");
|
||||
candidate.su_command = parse_su_command(su_command, wraps)?;
|
||||
|
|
@ -36,6 +40,7 @@ pub fn load() -> Result<Configuration, Error> {
|
|||
}
|
||||
}
|
||||
|
||||
elog(&format!("Assembled configuration candidate: {candidate:?}"));
|
||||
Ok(candidate)
|
||||
}
|
||||
|
||||
|
|
@ -183,9 +188,10 @@ impl Default for SuCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
message: String,
|
||||
kind: ErrorKind,
|
||||
pub message: String,
|
||||
pub kind: ErrorKind,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
|
@ -221,6 +227,7 @@ impl From<std::io::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
CommandNotInPath,
|
||||
VarError,
|
||||
|
|
@ -242,3 +249,58 @@ impl std::fmt::Display for ErrorKind {
|
|||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[expect(clippy::panic_in_result_fn, //clippy::unwrap_in_result
|
||||
)]
|
||||
mod serial_tests {
|
||||
use std::{env, fs, os::unix::fs::PermissionsExt as _, io::{Write as _}};
|
||||
use super::*;
|
||||
use crate::{dev::test::{Directories, Error}};
|
||||
|
||||
#[test]
|
||||
fn failed_config_read() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("failed_config_read")?;
|
||||
|
||||
fs::write(&dirs.conf, [1, 0, 1])?;
|
||||
let mut permissions = fs::metadata(&dirs.conf)?.permissions();
|
||||
permissions.set_mode(0o200);
|
||||
fs::set_permissions(&dirs.conf, permissions)?;
|
||||
|
||||
let new_permissions = fs::metadata(&dirs.conf)?.permissions();
|
||||
assert_eq!(new_permissions.mode() & 0o777, 0o200);
|
||||
|
||||
let error = load().unwrap_err();
|
||||
|
||||
assert!(matches!(&error.kind, ErrorKind::IO));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefer_system() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("prefer_system")?;
|
||||
|
||||
let mut conf = fs::File::create_new(&dirs.conf)?;
|
||||
println!("conf: {conf:#?}");
|
||||
println!("XDG_CONFIG_DIR: {:#?}", env::var("XDG_CONFIG_DIR"));
|
||||
|
||||
let conf_root_contents = dirs.conf_root.read_dir();
|
||||
println!("conf_root_contents: {conf_root_contents:#?}");
|
||||
|
||||
let write_result = conf.write_all(b"merge_strategy = prefer system\n");
|
||||
println!("write_result: {write_result:#?}");
|
||||
conf.sync_all()?;
|
||||
|
||||
let mut perms = fs::metadata(&dirs.conf)?.permissions();
|
||||
println!("perms: {perms:#?}");
|
||||
perms.set_mode(0o664);
|
||||
conf.set_permissions(perms)?;
|
||||
|
||||
let configuration = load()?;
|
||||
println!("configuration: {configuration:#?}");
|
||||
|
||||
assert!(matches!(configuration.merge_strategy, MergeStrategy::PreferSystem));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
src/dev.rs
Normal file
2
src/dev.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod log;
|
||||
pub mod test;
|
||||
124
src/dev/test.rs
Normal file
124
src/dev/test.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
use std::{env, fs, io, path::PathBuf};
|
||||
|
||||
use crate::{dev::log::elog, conf};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Directories {
|
||||
pub original: PathBuf,
|
||||
pub tube: PathBuf,
|
||||
pub conf: PathBuf,
|
||||
pub conf_root: PathBuf,
|
||||
}
|
||||
|
||||
impl Directories {
|
||||
/// Sets up self-cleaning original, temporary and 'templates' directories.
|
||||
///
|
||||
/// # Errors
|
||||
/// May return Error when:
|
||||
/// - Current directory does not exist or lacking permissions
|
||||
/// - Several I/O possibilities from directory creation failures
|
||||
/// - Several I/O possibilities from working directory changing failures
|
||||
pub fn setup(dir_name: &str) -> Result<Directories, Error> {
|
||||
let original = env::current_dir()?;
|
||||
let tube = original.join(format!("target/tubes/{dir_name}"));
|
||||
let xdg_conf = tube.join(".config");
|
||||
let conf_root = xdg_conf.join("tori");
|
||||
let conf = conf_root.join("tori.conf");
|
||||
|
||||
drop(fs::remove_dir_all(&tube));
|
||||
|
||||
if let Err(error) = fs::create_dir_all(&conf_root) {
|
||||
return Err(Error::with_io(
|
||||
"Failed configuration root directory creation",
|
||||
error,
|
||||
))
|
||||
}
|
||||
|
||||
if let Err(error) = env::set_current_dir(&tube) {
|
||||
return Err(Error::with_io("Failed current directory change", error))
|
||||
}
|
||||
|
||||
unsafe { env::set_var("XDG_CONFIG_DIR", &xdg_conf); }
|
||||
|
||||
Ok(Directories {
|
||||
original,
|
||||
tube,
|
||||
conf,
|
||||
conf_root,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Directories {
|
||||
fn drop(&mut self) {
|
||||
if let Err(error) = std::env::set_current_dir(&self.original) {
|
||||
elog(&format!("Couldn't reset to original directory: {error}"));
|
||||
}
|
||||
if let Err(error) = std::fs::remove_dir_all(&self.tube) {
|
||||
elog(&format!("Couldn't cleanup tube directory: {error}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub message: String,
|
||||
pub inner: Option<InnerErrors>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InnerErrors {
|
||||
pub io: Option<io::Error>,
|
||||
pub conf: Option<conf::Error>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn with_io(message: &str, inner: io::Error) -> Error {
|
||||
Error {
|
||||
message: String::from(message),
|
||||
inner: Some(InnerErrors { io: Some(inner), conf: None }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let mut message = self.message.clone();
|
||||
|
||||
if let Some(inner) = &self.inner {
|
||||
message = format!("{message}\n{inner:#?}");
|
||||
}
|
||||
|
||||
write!(f, "{message}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(string: String) -> Error {
|
||||
Error {
|
||||
message: string,
|
||||
inner: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(str: &str) -> Error { Error::from(String::from(str)) }
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(inner: io::Error) -> Error {
|
||||
let mut error = Error::from(inner.to_string());
|
||||
error.inner = Some(InnerErrors { io: Some(inner), ..InnerErrors::default() });
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
impl From<conf::Error> for Error {
|
||||
fn from(conf_error: conf::Error) -> Error {
|
||||
Error {
|
||||
message: conf_error.message.clone(),
|
||||
inner: Some(InnerErrors { conf: Some(conf_error), io: None }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
#![feature(slice_partition_dedup)]
|
||||
#![allow(unused_features)]
|
||||
|
||||
pub mod conf;
|
||||
pub mod state;
|
||||
|
||||
pub mod os;
|
||||
pub mod run;
|
||||
|
||||
pub mod log;
|
||||
pub mod dev;
|
||||
|
|
|
|||
14
src/main.rs
14
src/main.rs
|
|
@ -1,7 +1,7 @@
|
|||
use tori::{conf, log, run, state};
|
||||
use tori::{conf, dev::log::elog, run, state};
|
||||
|
||||
fn main() -> std::process::ExitCode {
|
||||
log::elog(&format!("tori {}", env!("CARGO_PKG_VERSION")));
|
||||
elog(&format!("tori {}", env!("CARGO_PKG_VERSION")));
|
||||
let configuration = match conf::load() {
|
||||
Ok(c) => c,
|
||||
Err(error) => {
|
||||
|
|
@ -9,13 +9,13 @@ fn main() -> std::process::ExitCode {
|
|||
return 1.into();
|
||||
}
|
||||
};
|
||||
log::elog(&format!("Configuration: {configuration:#?}"));
|
||||
elog(&format!("Configuration: {configuration:#?}"));
|
||||
let order = run::teller::parse(std::env::args());
|
||||
log::elog(&format!("Order: {order:#?}"));
|
||||
elog(&format!("Order: {order:#?}"));
|
||||
let state = state::setup(configuration, &[order]);
|
||||
log::elog(&format!("State: {state:#?}"));
|
||||
let result = run::expeditor::fulfill(&state);
|
||||
log::elog(&format!("Filled Order: {result:#?}"));
|
||||
elog(&format!("State: {state:#?}"));
|
||||
let result = run::expeditor::expedite(&state);
|
||||
elog(&format!("Filled Order: {result:#?}"));
|
||||
|
||||
if result.is_ok() { 0.into() } else { 1.into() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{collections::HashSet, fs::read_to_string, iter};
|
|||
|
||||
use crate::{
|
||||
conf::Configuration,
|
||||
log::elog,
|
||||
dev::log::elog,
|
||||
os::{
|
||||
Kind, OperatingSystem,
|
||||
pkg::{self, Package, PackagerVariant, Packages},
|
||||
|
|
@ -269,9 +269,11 @@ mod tests {
|
|||
auto_set.insert("sunflower".to_string());
|
||||
auto_set.insert("turnip".to_string());
|
||||
|
||||
let manual = Apt::determine_manual(raw_all, &auto_set);
|
||||
let mut manual = Apt::determine_manual(raw_all, &auto_set);
|
||||
let (uniques, dupes) = manual.as_mut_slice().partition_dedup();
|
||||
assert!(dupes.is_empty());
|
||||
assert_eq!(
|
||||
manual,
|
||||
uniques,
|
||||
vec![
|
||||
"avocado".into(),
|
||||
"carrot".into(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{conf::Configuration, log::elog};
|
||||
use crate::{conf::Configuration, dev::log::elog};
|
||||
|
||||
pub mod executor;
|
||||
pub mod expeditor;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::process;
|
||||
|
||||
use crate::{
|
||||
log::elog,
|
||||
dev::log::elog,
|
||||
os::pkg::Package,
|
||||
run::{Command, Transaction, TransactionCommandStatus},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
state::State,
|
||||
};
|
||||
|
||||
pub fn fulfill(state: &State) -> Result<(), Error> {
|
||||
pub fn expedite(state: &State) -> Result<(), Error> {
|
||||
let orders = state.orders();
|
||||
|
||||
for order in orders {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
log::elog,
|
||||
dev::log::elog,
|
||||
run::{Order, Task, TaskKind},
|
||||
};
|
||||
use std::{env, path::PathBuf};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||
|
||||
use crate::{
|
||||
conf::Configuration,
|
||||
log::elog,
|
||||
dev::log::elog,
|
||||
os::OperatingSystem,
|
||||
run::{Command, Order, executor::read},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue