use std::fs;
use std::os::unix::fs::DirBuilderExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
const SUN_PATH_LEN: usize = 104;
const LONGEST_ENTRY: &str = "sub/mdpreviewer.sock";
fn base_dir(name: &str) -> PathBuf {
let preferred = canonical(std::env::temp_dir());
if fits(&preferred, name) {
return preferred;
}
let fallback = canonical(PathBuf::from("/tmp"));
assert!(
fits(&fallback, name),
"no temporary directory is short enough for a Unix socket: \
tried {} and {}",
preferred.display(),
fallback.display(),
);
fallback
}
fn canonical(path: PathBuf) -> PathBuf {
path.canonicalize().unwrap_or(path)
}
fn fits(base: &Path, name: &str) -> bool {
base.join(name).join(LONGEST_ENTRY).as_os_str().len() < SUN_PATH_LEN
}
pub struct TestDir(PathBuf);
impl TestDir {
pub fn new(name: &str) -> Self {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let child = format!("mdpreviewer-test-{}-{name}-{n}", std::process::id());
let path = base_dir(&child).join(&child);
let _ = fs::remove_dir_all(&path);
fs::DirBuilder::new()
.mode(0o700)
.create(&path)
.expect("create test dir");
TestDir(path.canonicalize().expect("canonicalize test dir"))
}
pub fn path(&self) -> &Path {
&self.0
}
pub fn join(&self, name: &str) -> PathBuf {
self.0.join(name)
}
}
impl Drop for TestDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_test_dir_leaves_room_for_a_control_socket() {
let dir = TestDir::new("a-deliberately-long-descriptive-test-name");
let socket = dir.join(LONGEST_ENTRY);
assert!(
socket.as_os_str().len() < SUN_PATH_LEN,
"{} is {} bytes, too long to bind",
socket.display(),
socket.as_os_str().len(),
);
}
}