use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
const VALUE: &str = "2024-01-15";
struct Sandbox(PathBuf);
impl Sandbox {
fn new(name: &str) -> Self {
let path = Path::new(env!("CARGO_TARGET_TMPDIR"))
.join("platform")
.join(name);
let _ = std::fs::remove_dir_all(&path);
std::fs::create_dir_all(&path).expect("the sandbox is created");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
fn write(&self, name: &str, contents: &str) {
let path = self.0.join(name);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("the parent directory is created");
}
std::fs::write(path, contents).expect("the file is written");
}
}
impl Drop for Sandbox {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
struct Run {
stdout: String,
stderr: String,
code: Option<i32>,
}
impl Run {
fn reports(&self) -> Vec<serde_json::Value> {
self.stdout
.lines()
.map(|line| serde_json::from_str(line).expect("stdout is one JSON object per line"))
.collect()
}
fn files(&self) -> Vec<String> {
self.reports()
.iter()
.map(|report| report["file"].as_str().unwrap_or_default().to_string())
.collect()
}
}
fn run(root: &Path, arguments: &[&str]) -> Run {
let output = Command::new(env!("CARGO_BIN_EXE_dates-le"))
.arg(root.as_os_str())
.arg("--no-ignore")
.args(arguments)
.stdin(Stdio::null())
.output()
.expect("the binary runs");
Run {
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
code: output.status.code(),
}
}
#[test]
fn every_reported_path_uses_forward_slashes() {
let sandbox = Sandbox::new("separators");
sandbox.write("top.json", VALUE);
sandbox.write("nested/deeper/inner.json", VALUE);
std::fs::write(
sandbox.path().join("latin.log"),
b"caf\xe9 2024-01-15\n".as_slice(),
)
.expect("the file is written");
let run = run(sandbox.path(), &[]);
assert_eq!(run.code, Some(0), "{}", run.stderr);
assert_eq!(run.files().len(), 3, "{}", run.stdout);
assert!(
run.files()
.iter()
.any(|file| file.ends_with("nested/deeper/inner.json")),
"no nested path was reported, so the separator assertion proves nothing: {}",
run.stdout
);
for file in run.files() {
assert!(
!file.contains('\\'),
"a report path used the platform separator: {file}"
);
}
assert!(
run.stderr.contains("latin.log"),
"stderr named no path: {}",
run.stderr
);
assert!(
!run.stderr.contains('\\'),
"stderr spelled a path the platform's way: {}",
run.stderr
);
}
#[test]
fn the_walk_does_not_report_one_file_twice() {
let sandbox = Sandbox::new("case-folding");
sandbox.write("README.md", &format!("shipped {VALUE}\n"));
sandbox.write("readme.md", &format!("shipped {VALUE}\n"));
let on_disk = std::fs::read_dir(sandbox.path())
.expect("the sandbox is readable")
.count();
assert!(
(1..=2).contains(&on_disk),
"the sandbox holds {on_disk} entries, which is neither answer"
);
let run = run(sandbox.path(), &[]);
assert_eq!(run.code, Some(0), "{}", run.stderr);
let mut files = run.files();
files.sort();
let distinct = {
let mut unique = files.clone();
unique.dedup();
unique.len()
};
assert_eq!(
distinct,
files.len(),
"a file was reported twice: {files:?}"
);
assert_eq!(
files.len(),
on_disk,
"the walk reported {} files for {on_disk} on disk: {files:?}",
files.len()
);
}
#[test]
fn a_reserved_windows_name_does_not_stop_the_walk() {
let sandbox = Sandbox::new("reserved-names");
sandbox.write("ordinary.json", VALUE);
let mut created = Vec::new();
for name in ["CON", "PRN", "AUX", "NUL", "COM1"] {
let path = sandbox.path().join(name);
let expected = format!("shipped {VALUE}\n");
match std::fs::write(&path, &expected) {
Ok(()) => match std::fs::read_to_string(&path) {
Ok(back) if back == expected => created.push(name),
_ => eprintln!(
"platform: {name} accepted a write and did not hold it — a device name, not a file"
),
},
Err(error) => eprintln!("platform: skipped the reserved name {name} — {error}"),
}
}
let run = run(sandbox.path(), &[]);
assert_eq!(run.code, Some(0), "{}", run.stderr);
assert!(
run.files()
.iter()
.any(|file| file.ends_with("ordinary.json")),
"the walk stopped at a reserved name: {}",
run.stdout
);
for name in created {
assert!(
run.files().iter().any(|file| file.ends_with(name)),
"{name} was created and not read: {}",
run.stdout
);
}
}
#[test]
fn stdin_closed_early_is_an_exit_code_and_never_a_broken_write() {
let mut child = Command::new(env!("CARGO_BIN_EXE_dates-le"))
.args(["--nope", "--stdin"])
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("the binary starts");
{
let mut stdin = child.stdin.take().expect("stdin is piped");
let document = format!("{VALUE}\n").repeat(200_000);
let _ = stdin.write_all(document.as_bytes());
let _ = stdin.flush();
}
let status = child.wait().expect("the binary finishes");
assert_eq!(
status.code(),
Some(2),
"a refused question is exit 2 however the write went"
);
}
#[test]
fn a_named_zone_beats_the_environment_variable() {
let document = format!("{VALUE}T00:00:00\n");
let answers: Vec<String> = [Some("UTC"), Some("Asia/Kolkata"), None]
.into_iter()
.map(|zone| {
let mut command = Command::new(env!("CARGO_BIN_EXE_dates-le"));
command.args(["--stdin", "--format", "log", "--tz", "America/New_York"]);
match zone {
Some(zone) => command.env("TZ", zone),
None => command.env_remove("TZ"),
};
let mut child = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("the binary runs");
{
let mut stdin = child.stdin.take().expect("stdin is piped");
stdin
.write_all(document.as_bytes())
.expect("the document is written");
}
let mut stdout = String::new();
child
.stdout
.as_mut()
.expect("stdout is piped")
.read_to_string(&mut stdout)
.expect("stdout is read");
child.wait().expect("the binary finishes");
stdout
})
.collect();
assert_eq!(answers[0], answers[1], "TZ changed a --tz answer");
assert_eq!(answers[1], answers[2], "unsetting TZ changed a --tz answer");
assert!(
answers[0].contains("1705294800000"),
"2024-01-15T00:00:00 in New York is not what came back: {}",
answers[0]
);
}