use std::path::PathBuf;
pub(crate) fn unique_tmp(prefix: &str) -> PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static N: AtomicU64 = AtomicU64::new(0);
let n = N.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!("{prefix}_{}_{n}", std::process::id()))
}
#[cfg(test)]
mod guard {
use std::path::{Path, PathBuf};
const EXEMPT_FILES: &[&str] = &[
"test_support.rs",
"preview/pdf.rs",
"preview/command.rs",
"preview/video.rs",
];
fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(rd) = std::fs::read_dir(dir) else {
return;
};
for entry in rd.flatten() {
let path = entry.path();
if path.is_dir() {
collect_rs_files(&path, out);
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
out.push(path);
}
}
}
fn targets_a_path(line: &str) -> bool {
line.contains("temp_dir().join(") || line.contains(".push(")
}
const UNIQUENESS_PROOF: &str = "process::id()";
const PROOF_WINDOW: usize = 6;
fn find_offenders(src_dir: &Path) -> (usize, Vec<String>) {
let mut rs_files = Vec::new();
collect_rs_files(src_dir, &mut rs_files);
let mut files_scanned = 0usize;
let mut offenders = Vec::new();
for path in &rs_files {
let rel = path
.strip_prefix(src_dir)
.unwrap()
.to_string_lossy()
.replace('\\', "/");
if EXEMPT_FILES.iter().any(|f| rel == *f) {
continue;
}
files_scanned += 1;
let Ok(text) = std::fs::read_to_string(path) else {
continue;
};
let lines: Vec<&str> = text.lines().collect();
for (i, line) in lines.iter().enumerate() {
if !line.contains("temp_dir()") {
continue;
}
if !targets_a_path(line) {
continue; }
let window_end = (i + PROOF_WINDOW).min(lines.len());
let proven = lines[i..window_end]
.iter()
.any(|l| l.contains(UNIQUENESS_PROOF));
if !proven {
offenders.push(format!("{rel}:{}: {}", i + 1, line.trim()));
}
}
}
(files_scanned, offenders)
}
#[test]
fn scan_finds_at_least_20_source_files() {
let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let (files_scanned, _) = find_offenders(&src_dir);
assert!(
files_scanned >= 20,
"スキャン対象が少なすぎる(安全弁): {files_scanned} 件 — src/ の探索が壊れている可能性"
);
}
#[test]
fn no_fixed_name_temp_dirs_outside_unique_tmp() {
let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let (files_scanned, offenders) = find_offenders(&src_dir);
assert!(
files_scanned >= 20,
"スキャン対象が少なすぎる(安全弁): {files_scanned} 件"
);
assert!(
offenders.is_empty(),
"共有ヘルパー unique_tmp を経由しない固定名 temp_dir() 呼び出しを検出\n\
(固定名は並行実行中の2プロセスが衝突する — このモジュールの docコメント参照):\n{}",
offenders.join("\n")
);
}
}