nmarshall's picture
formats: add txt conversion cli
18d65b9
Raw History Blame Contribute Delete
6.08 kB
use super::*;
struct Dir(PathBuf);
impl Dir {
fn new() -> Self {
for _ in 0..100 {
let p = std::env::temp_dir().join(format!(
"nanobrush-cli-test-{}-{}",
std::process::id(),
NEXT_TEMP.fetch_add(1, Ordering::Relaxed)
));
match fs::create_dir(&p) {
Ok(()) => return Self(p),
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => (),
Err(e) => panic!("{e}"),
}
}
panic!("temp dir collision")
}
fn file(&self, name: &str, bytes: &[u8]) -> PathBuf {
let p = self.0.join(name);
fs::write(&p, bytes).unwrap();
p
}
}
impl Drop for Dir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn call(args: &[&std::ffi::OsStr]) -> Result<Vec<u8>> {
let mut out = Vec::new();
run(args.iter().map(|s| s.to_os_string()), &mut out)?;
Ok(out)
}
const EMPTY: &[u8] = b"world(computedFeatures false)";
#[test]
fn inspect_validate_and_both_versions() {
let d = Dir::new();
let input = d.file("é space.txt", EMPTY);
let summary = call(&["inspect".as_ref(), input.as_os_str()]).unwrap();
assert!(
String::from_utf8(summary)
.unwrap()
.contains("\"dat_version\":null")
);
for format in ["dat-201501280", "dat-201512200"] {
let target = d.0.join(format);
call(&[
"convert".as_ref(),
input.as_os_str(),
target.as_os_str(),
"--to".as_ref(),
format.as_ref(),
])
.unwrap();
assert!(mb3_dat::decode(&fs::read(&target).unwrap(), &Limits::default()).is_ok());
call(&["validate".as_ref(), target.as_os_str()]).unwrap();
let txt = d.0.join(format!("{format}.txt"));
call(&[
"convert".as_ref(),
target.as_os_str(),
txt.as_os_str(),
"--to".as_ref(),
"txt".as_ref(),
])
.unwrap();
assert_eq!(
mb3_txt::decode(&fs::read(txt).unwrap(), &Limits::default()).unwrap(),
World::default()
);
}
}
#[test]
fn usage_and_explicit_limits() {
for args in [
vec![],
vec!["x"],
vec!["inspect"],
vec!["convert", "in", "out"],
vec!["inspect", "in", "--force"],
vec!["inspect", "in", "--max-items", "-1"],
vec!["inspect", "in", "--from", "txt", "--from", "dat"],
] {
let args: Vec<_> = args.iter().map(std::ffi::OsStr::new).collect();
assert!(call(&args).unwrap_err().usage);
}
assert!(!call(&["--help".as_ref()]).unwrap().is_empty());
let d = Dir::new();
let p = d.file("in", EMPTY);
assert!(
!call(&[
"validate".as_ref(),
p.as_os_str(),
"--max-bytes".as_ref(),
"1".as_ref()
])
.unwrap_err()
.usage
);
assert!(
call(&[
"validate".as_ref(),
p.as_os_str(),
"--from".as_ref(),
"dat".as_ref()
])
.is_err()
);
}
#[test]
fn no_clobber_force_and_same_file_protection() {
let d = Dir::new();
let input = d.file("in", EMPTY);
let output = d.file("out", b"ORIGINAL");
assert!(save(&input, &output, b"NEW", false).is_err());
assert_eq!(fs::read(&output).unwrap(), b"ORIGINAL");
save(&input, &output, b"NEW", true).unwrap();
assert_eq!(fs::read(&output).unwrap(), b"NEW");
assert!(save(&input, &input, b"BAD", true).is_err());
let link = d.0.join("hardlink");
fs::hard_link(&input, &link).unwrap();
assert!(save(&input, &link, b"BAD", true).is_err());
assert_eq!(fs::read(input).unwrap(), EMPTY);
assert_eq!(fs::read_dir(&d.0).unwrap().count(), 3);
}
#[test]
fn failed_validation_creates_no_output_or_temporary() {
let d = Dir::new();
let input = d.file(
"in",
b"world(computedFeatures false stringProperties[\"p\"])",
);
let output = d.0.join("out");
assert!(
call(&[
"convert".as_ref(),
input.as_os_str(),
output.as_os_str(),
"--to".as_ref(),
"dat-201501280".as_ref()
])
.is_err()
);
assert!(!output.exists());
assert_eq!(fs::read_dir(&d.0).unwrap().count(), 1);
}
#[cfg(unix)]
#[test]
fn symlink_destinations_rejected() {
let d = Dir::new();
let input = d.file("in", EMPTY);
let target = d.file("target", b"ORIGINAL");
let link = d.0.join("link");
std::os::unix::fs::symlink(&target, &link).unwrap();
assert!(save(&input, &link, b"BAD", true).is_err());
assert_eq!(fs::read(target).unwrap(), b"ORIGINAL");
}
#[test]
fn failed_publication_cleans_temporary_and_preserves_existing() {
let d = Dir::new();
let input = d.file("in", EMPTY);
let output = d.0.join("out");
fs::create_dir(&output).unwrap();
assert!(save(&input, &output, b"NEW", false).is_err());
assert_eq!(fs::read_dir(&d.0).unwrap().count(), 2);
}
#[test]
fn publication_race_is_no_clobber_and_owned_temp_is_cleaned() {
let d = Dir::new();
let output = d.file("winner", b"EXISTING");
let path = d.file("staged", b"REPLACEMENT");
assert!(publish(Temp(path.clone()), &output, false).is_err());
assert!(!path.exists());
assert_eq!(fs::read(output).unwrap(), b"EXISTING");
}
#[test]
fn invalid_conversion_with_force_keeps_existing_destination() {
let d = Dir::new();
let input = d.file(
"unicode.txt",
"world(computedFeatures false stringProperties[\"é\"])".as_bytes(),
);
let output = d.file("out.dat", b"KEEP");
assert!(
call(&[
"convert".as_ref(),
input.as_os_str(),
output.as_os_str(),
"--to".as_ref(),
"dat-201512200".as_ref(),
"--force".as_ref()
])
.is_err()
);
assert_eq!(fs::read(output).unwrap(), b"KEEP");
assert_eq!(fs::read_dir(&d.0).unwrap().count(), 2);
}