#![forbid(unsafe_code)]
#![cfg(unix)]
#[path = "../../haematite/tests/common/runtime_options.rs"]
mod runtime_options;
use std::collections::BTreeMap;
use std::error::Error;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use haematite::{LeafLog, NodeCacheBudget};
type TestResult = Result<(), Box<dyn Error>>;
fn files(path: &Path) -> Result<BTreeMap<PathBuf, Vec<u8>>, Box<dyn Error>> {
let mut found = BTreeMap::new();
let mut pending = vec![path.to_path_buf()];
while let Some(directory) = pending.pop() {
for entry in fs::read_dir(directory)? {
let entry = entry?;
let kind = entry.file_type()?;
if kind.is_dir() {
pending.push(entry.path());
} else if kind.is_file() {
found.insert(
entry.path().strip_prefix(path)?.to_path_buf(),
fs::read(entry.path())?,
);
} else {
return Err("fixture contains a non-file entry".into());
}
}
}
Ok(found)
}
fn refused(output: &Output) -> TestResult {
let stderr = std::str::from_utf8(&output.stderr)?;
assert_eq!(output.status.code(), Some(1), "{stderr}");
assert!(stderr.contains("LeafLogStore"), "{stderr}");
assert!(output.stdout.is_empty());
Ok(())
}
#[test]
fn serve_refuses_the_leaf_mark_before_creating_a_lock_or_socket() -> TestResult {
let directory = tempfile::tempdir()?;
fs::set_permissions(directory.path(), fs::Permissions::from_mode(0o700))?;
let path = directory.path().join("log");
drop(LeafLog::create(
&path,
b"origin",
NodeCacheBudget::Unlimited,
runtime_options::runtime_options(),
)?);
fs::remove_file(path.join("writer.lock"))?;
let socket = directory.path().join("service.sock");
let config = directory.path().join("serve.json");
fs::write(
&config,
serde_json::to_vec(&serde_json::json!({
"data_dir": path,
"socket": socket,
"branch_cache_bytes_per_shard": 65_536,
"max_frame": 1_048_576,
"max_clients": 1,
"max_page": 1,
"timeout_ms": 1_000,
"create": {"shards": 1, "cache_bytes_total": 65_536, "executor_threads": 1}
}))?,
)?;
let before = files(&path)?;
for create in [false, true] {
let mut command = Command::new(env!("CARGO_BIN_EXE_haematite"));
command.arg("serve").arg("--config").arg(&config);
if create {
command.arg("--create");
}
refused(&command.output()?)?;
assert_eq!(files(&path)?, before);
assert!(!path.join("writer.lock").try_exists()?);
assert!(!socket.try_exists()?);
}
Ok(())
}
#[test]
fn migration_and_abort_refuse_the_leaf_mark_without_creating_a_lock() -> TestResult {
let directory = tempfile::tempdir()?;
let path = directory.path().join("log");
drop(LeafLog::create(
&path,
b"origin",
NodeCacheBudget::Unlimited,
runtime_options::runtime_options(),
)?);
fs::remove_file(path.join("writer.lock"))?;
let before = files(&path)?;
for direction in ["--migrate", "--abort"] {
let output = Command::new(env!("CARGO_BIN_EXE_haematite"))
.arg("migrate-chunking")
.arg(&path)
.arg(direction)
.arg("--json")
.output()?;
refused(&output)?;
assert_eq!(files(&path)?, before);
assert!(!path.join("writer.lock").try_exists()?);
}
Ok(())
}
#[test]
fn stats_and_offline_vacuum_dry_run_still_answer_for_a_leaf_log() -> TestResult {
let directory = tempfile::tempdir()?;
let path = directory.path().join("log");
drop(LeafLog::create(
&path,
b"origin",
NodeCacheBudget::Unlimited,
runtime_options::runtime_options(),
)?);
let before = files(&path)?;
for operation in ["stats", "vacuum"] {
let mut command = Command::new(env!("CARGO_BIN_EXE_haematite"));
command.arg(operation).arg(&path).arg("--json");
if operation == "vacuum" {
command.arg("--dry-run").arg("--attest-metadata-complete");
}
let output = command.output()?;
assert_eq!(
output.status.code(),
Some(0),
"{}",
std::str::from_utf8(&output.stderr)?
);
let report: serde_json::Value = serde_json::from_slice(&output.stdout)?;
assert_eq!(report["shard_count"], 1);
assert_eq!(
report["mode"],
if operation == "stats" {
"stats"
} else {
"sweep"
}
);
assert_eq!(files(&path)?, before);
if operation == "vacuum" {
assert_eq!(report["sweep"]["executed"], false);
}
}
let (log, _) = LeafLog::open(&path, runtime_options::runtime_options())?;
assert_eq!(log.origin(), b"origin");
assert_eq!(log.extent(), 0);
Ok(())
}