113 lines
3.8 KiB
Rust
113 lines
3.8 KiB
Rust
//! Fixtures: throw-away vaults with unique names under the temp directory.
|
|
//!
|
|
//! Every test gets its own folder, so tests can run in parallel and a crashed
|
|
//! one never poisons the next: the folder is removed when [`TempVault`] drops,
|
|
//! and `notescraft-` prefixed folders older than a day are swept away too.
|
|
|
|
#![allow(dead_code)] // shared test helpers: each test file uses only some of them
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
/// Flate-decompression of exported PDF bytes in tests, plus the reader that
|
|
/// recovers the text a document draws (see `pdf.rs`).
|
|
pub mod pdf;
|
|
|
|
/// Counter making two vaults of the same test different.
|
|
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
|
|
/// How long a leftover vault folder is kept before the next run sweeps it.
|
|
const STALE_AFTER: Duration = Duration::from_hours(24);
|
|
|
|
/// A vault folder that deletes itself when it goes out of scope.
|
|
pub struct TempVault {
|
|
path: PathBuf,
|
|
}
|
|
|
|
impl TempVault {
|
|
/// The vault root to hand to `Session::open`.
|
|
#[must_use]
|
|
pub fn path(&self) -> &Path {
|
|
&self.path
|
|
}
|
|
}
|
|
|
|
impl Drop for TempVault {
|
|
fn drop(&mut self) {
|
|
let _ = std::fs::remove_dir_all(&self.path);
|
|
}
|
|
}
|
|
|
|
/// An empty vault folder named after the test and the process.
|
|
#[must_use]
|
|
pub fn temp_vault(name: &str) -> TempVault {
|
|
let unique = COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
let path = std::env::temp_dir().join(format!("notescraft-engine-{}-{name}-{unique}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&path);
|
|
std::fs::create_dir_all(&path).expect("create the temp vault");
|
|
sweep(&path);
|
|
TempVault { path }
|
|
}
|
|
|
|
/// Writes `(path, content)` pairs into `vault`, creating the folders.
|
|
pub fn seed(vault: &Path, files: &[(&str, &str)]) {
|
|
for (path, content) in files {
|
|
let target = vault.join(path);
|
|
if let Some(parent) = target.parent() {
|
|
std::fs::create_dir_all(parent).expect("create the temp folder");
|
|
}
|
|
std::fs::write(&target, content).expect("write the fixture file");
|
|
}
|
|
}
|
|
|
|
/// A throw-away folder for a `config.toml`, same mechanics as `temp_vault`:
|
|
/// unique, self-removing, swept when stale.
|
|
#[must_use]
|
|
pub fn temp_config(name: &str) -> TempVault {
|
|
temp_vault(&format!("config-{name}"))
|
|
}
|
|
|
|
/// A vault with `count` notes, `note-00.md` …, each linking to the next one.
|
|
#[must_use]
|
|
#[allow(dead_code)] // used by the session tests only
|
|
pub fn linked_vault(name: &str, count: usize) -> TempVault {
|
|
let vault = temp_vault(name);
|
|
let files: Vec<(String, String)> = (0..count)
|
|
.map(|i| {
|
|
let title = format!("note-{i:02}");
|
|
let next = format!("note-{:02}", (i + 1) % count);
|
|
(format!("{title}.md"), format!("# {title}\n\n[[{next}]] #work/tag\n"))
|
|
})
|
|
.collect();
|
|
let borrowed: Vec<(&str, &str)> = files.iter().map(|(path, body)| (path.as_str(), body.as_str())).collect();
|
|
seed(vault.path(), &borrowed);
|
|
vault
|
|
}
|
|
|
|
/// The text of a note as it is now on disk.
|
|
#[must_use]
|
|
pub fn on_disk(vault: &Path, path: &str) -> String {
|
|
std::fs::read_to_string(vault.join(path)).expect("read the note from disk")
|
|
}
|
|
|
|
/// Removes the leftovers of older runs.
|
|
fn sweep(current: &Path) {
|
|
let Ok(entries) = std::fs::read_dir(std::env::temp_dir()) else { return };
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
if path == current || !path.to_string_lossy().starts_with("notescraft-engine-") {
|
|
continue;
|
|
}
|
|
let stale = entry
|
|
.metadata()
|
|
.and_then(|meta| meta.modified())
|
|
.ok()
|
|
.and_then(|modified| SystemTime::now().duration_since(modified).ok())
|
|
.is_some_and(|age| age > STALE_AFTER);
|
|
if stale {
|
|
let _ = std::fs::remove_dir_all(&path);
|
|
}
|
|
}
|
|
}
|