Add dev::test module for filesystem tests setup/teardown

This commit is contained in:
Juno Takano 2026-03-16 20:04:48 -03:00
commit c305627822
3 changed files with 227 additions and 29 deletions

135
src/dev/test.rs Normal file
View file

@ -0,0 +1,135 @@
use std::{env, fs, io, path::PathBuf};
use crate::prelude::*;
pub struct Directories {
pub original: PathBuf,
pub templates: PathBuf,
pub test: 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 test = original.join(format!("target/mocks/{dir_name}"));
let templates = test.join("templates");
drop(fs::remove_dir_all(&test));
if let Err(error) = fs::create_dir_all(&test) {
return Err(Error::with_io(
"Failed test's directory creation",
error,
))
}
if let Err(error) = fs::create_dir_all(&templates) {
return Err(Error::with_io(
"Failed 'templates' directory creation",
error,
))
}
if let Err(error) = env::set_current_dir(&test) {
return Err(Error::with_io("Failed current directory change", error))
}
Ok(Directories {
original,
templates,
test,
})
}
}
impl Drop for Directories {
fn drop(&mut self) {
if let Err(error) = std::env::set_current_dir(&self.original) {
log!(ERROR, "Couldn't reset to original directory: {error}");
}
if let Err(error) = std::fs::remove_dir_all(&self.test) {
log!(WARN, "Couldn't cleanup test directory: {error}");
}
}
}
#[derive(Debug)]
pub struct Error {
pub message: String,
pub inner_io: Option<io::Error>,
pub inner_tera: Option<tera::Error>,
}
impl Error {
fn with_io(message: &str, inner_io: io::Error) -> Error {
Error {
message: String::from(message),
inner_io: Some(inner_io),
inner_tera: 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_io) = &self.inner_io {
message = format!("{message}\n{inner_io}");
}
if let Some(inner_tera) = &self.inner_tera {
message = format!("{message}\n{inner_tera}");
}
write!(f, "{message}")
}
}
impl From<String> for Error {
fn from(string: String) -> Error {
Error {
message: string,
inner_io: None,
inner_tera: 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_io = Some(inner);
error
}
}
impl From<tera::Error> for Error {
fn from(inner: tera::Error) -> Error {
let mut error = Error::from(inner.to_string());
error.inner_tera = Some(inner);
error
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bad_test_directory_name() {
let dirs = Directories::setup("\0");
assert!(dirs.is_err());
}
}

View file

@ -1308,24 +1308,19 @@ mod tests {
}
#[cfg(test)]
#[expect(clippy::panic_in_result_fn)]
mod serial_tests {
use super::*;
use crate::dev::test::{Directories, Error};
#[test]
fn bad_graph_path() {
let original_working_directory = std::env::current_dir().unwrap();
assert!(
std::env::set_current_dir(std::path::Path::new(
"tests/mocks/no_graph"
))
.is_ok()
);
fn bad_graph_path() -> Result<(), Error> {
let _dirs = Directories::setup("bad_graph_path")?;
let graph = Graph::load();
let message = graph.meta.messages.first().unwrap();
assert!(message.contains("Failed reading file at"));
assert!(std::env::set_current_dir(original_working_directory).is_ok());
Ok(())
}
}

View file

@ -496,33 +496,101 @@ mod tests {
}
#[cfg(test)]
#[expect(clippy::panic_in_result_fn)]
mod serial_tests {
use std::{ffi::OsStr, os::unix::ffi::OsStrExt as _};
use super::*;
use crate::dev::test::{Directories, Error};
#[cfg_attr(not(unix), ignore)]
#[test]
fn invalid_utf8_template_filename() {
use std::{ffi::OsStr, os::unix::ffi::OsStrExt as _, path::PathBuf};
#[cfg_attr(not(unix), ignore)]
fn invalid_utf8_template_filename() -> Result<(), Error> {
let dirs = Directories::setup("encoding")?;
let original_working_directory = std::env::current_dir().unwrap();
let base_dir = PathBuf::from("tests/mocks/encoding/temp");
let templates_dir = base_dir.clone().join("templates");
assert!(std::fs::create_dir_all(&base_dir).is_ok());
assert!(
std::env::set_current_dir(&base_dir)
.is_ok()
);
assert!(std::fs::create_dir_all(&templates_dir).is_ok());
let invalid_name = OsStr::from_bytes(&[0xff, 0xfe, 0x80]);
let file_path = dirs.templates.join(invalid_name);
fs::write(file_path, b"")?;
let template_load_result = load_templates();
let err = template_load_result.err().unwrap();
let invalid_name = OsStr::from_bytes(&[0xff, 0xfe, 0x00]);
let file_path = templates_dir.join(invalid_name);
assert!(std::fs::write(&file_path, b"eNJq4FPUqSKoozdg").is_ok());
let error_message = err.to_string();
assert!(error_message.contains("not valid unicode"));
let result = load_templates();
assert!(result.is_err());
Ok(())
}
assert!(std::fs::remove_dir_all(&base_dir).is_ok());
assert!(std::env::set_current_dir(original_working_directory).is_ok());
#[test]
fn custom_template() -> Result<(), Error> {
let dirs = Directories::setup("custom_template")?;
let file_name = "custom.html";
let file_path = dirs.templates.join(file_name);
fs::write(file_path, b"")?;
let engine = load_templates()?;
assert!(engine.get_template_names().any(|t| t == "custom.html"));
Ok(())
}
#[test]
fn custom_template_inheritance_error() -> Result<(), Error> {
let dirs = Directories::setup("custom_template")?;
let file_name = "custom.html";
let file_path = dirs.templates.join(file_name);
fs::write(file_path, br#"{% extends "nonexistent.html" %}"#)?;
let template_load_result = load_templates();
assert!(template_load_result.is_err());
Ok(())
}
#[test]
fn inner_template_no_op() -> Result<(), Error> {
let dirs = Directories::setup("inner_template")?;
let inner_dir = dirs.templates.join("inner");
fs::create_dir(&inner_dir)?;
let inner_template = inner_dir.join("inner.html");
fs::write(inner_template, br#"{% extends "nonexistent.html" %}"#)?;
let engine = load_templates()?;
let default_count = dirs.original.join("templates").read_dir()?.count();
let template_count = engine.get_template_names().count();
assert!(template_count == default_count);
Ok(())
}
#[test]
fn templates_dir_not_found_ok() -> Result<(), Error> {
let dirs = Directories::setup("not_found_error")?;
std::fs::remove_dir_all(&dirs.templates)?;
let template_load_result = load_templates();
template_load_result?;
Ok(())
}
#[test]
// Unexpected here means any error other than 'not found'
fn templates_dir_unexpected_error() -> Result<(), Error> {
let dirs = Directories::setup("unexpected_error")?;
log!(DEBUG, "Working directory is {:?}", std::env::current_dir());
std::fs::remove_dir_all(&dirs.templates)?;
let templates = dirs.test.join("templates");
fs::write(&templates, b"")?;
let template_load_result = load_templates();
assert!(template_load_result.is_err());
Ok(())
}
}