#![cfg(feature = "install")]
use flate2::write::GzEncoder;
use flate2::Compression;
use jmdict_fast::install::{InstallOptions, InstallSource};
use jmdict_fast::Dict;
use std::io::Write;
use std::path::{Path, PathBuf};
const REQUIRED_FILES: &[&str] = &[
"entries.bin",
"kana.fst",
"kanji.fst",
"romaji.fst",
"id.fst",
"gloss.fst",
"gloss_postings.bin",
];
fn dist_dir() -> PathBuf {
if let Ok(p) = std::env::var("JMDICT_DATA") {
return PathBuf::from(p);
}
Path::new(env!("CARGO_MANIFEST_DIR")).join("../dist")
}
fn build_tarball() -> Vec<u8> {
let dist = dist_dir();
let mut tar_buf = Vec::new();
{
let mut builder = tar::Builder::new(&mut tar_buf);
for name in REQUIRED_FILES {
let src = dist.join(name);
let bytes = std::fs::read(&src)
.unwrap_or_else(|e| panic!("read {} failed: {e}", src.display()));
let mut header = tar::Header::new_gnu();
header.set_path(name).unwrap();
header.set_size(bytes.len() as u64);
header.set_mode(0o644);
header.set_cksum();
builder.append(&header, &bytes[..]).unwrap();
}
builder.finish().unwrap();
}
let mut gz = GzEncoder::new(Vec::new(), Compression::fast());
gz.write_all(&tar_buf).unwrap();
gz.finish().unwrap()
}
#[test]
fn install_from_local_tarball_round_trips() {
let cache = tempfile::tempdir().unwrap();
let tarball_path = cache.path().join("data.tar.gz");
std::fs::write(&tarball_path, build_tarball()).unwrap();
let dict = Dict::install_with(
InstallOptions::default()
.cache_dir(cache.path())
.source(InstallSource::Tarball(tarball_path)),
)
.expect("install failed");
let results = dict.lookup_exact("猫");
assert!(!results.is_empty(), "installed dict couldn't find 猫");
}
#[test]
fn install_skips_extract_on_warm_cache() {
let cache = tempfile::tempdir().unwrap();
let tarball_path = cache.path().join("data.tar.gz");
std::fs::write(&tarball_path, build_tarball()).unwrap();
let opts = || {
InstallOptions::default()
.cache_dir(cache.path())
.source(InstallSource::Tarball(tarball_path.clone()))
};
Dict::install_with(opts()).expect("first install failed");
let installed_dir = cache
.path()
.join("jmdict-fast")
.join(format!("fmt{}", jmdict_fast::FORMAT_VERSION))
.join(jmdict_fast::JMDICT_VERSION);
let entries = installed_dir.join("entries.bin");
let before = std::fs::metadata(&entries).unwrap().modified().unwrap();
Dict::install_with(opts()).expect("second install failed");
let after = std::fs::metadata(&entries).unwrap().modified().unwrap();
assert_eq!(before, after, "warm install re-extracted entries.bin");
}
#[test]
fn install_force_overwrites_corrupted_file() {
let cache = tempfile::tempdir().unwrap();
let tarball_path = cache.path().join("data.tar.gz");
std::fs::write(&tarball_path, build_tarball()).unwrap();
let opts = |force| {
InstallOptions::default()
.cache_dir(cache.path())
.source(InstallSource::Tarball(tarball_path.clone()))
.force(force)
};
Dict::install_with(opts(false)).expect("first install failed");
let installed_dir = cache
.path()
.join("jmdict-fast")
.join(format!("fmt{}", jmdict_fast::FORMAT_VERSION))
.join(jmdict_fast::JMDICT_VERSION);
let entries = installed_dir.join("entries.bin");
std::fs::write(&entries, b"not a valid entries.bin").unwrap();
let dict = Dict::install_with(opts(true)).expect("forced install failed");
assert!(dict.entry_count() > 0, "forced reinstall should produce a working dict");
}
#[test]
fn install_rejects_tarball_missing_required_files() {
let cache = tempfile::tempdir().unwrap();
let tarball_path = cache.path().join("partial.tar.gz");
let mut tar_buf = Vec::new();
{
let mut builder = tar::Builder::new(&mut tar_buf);
let entries = std::fs::read(dist_dir().join("entries.bin")).unwrap();
let mut h = tar::Header::new_gnu();
h.set_path("entries.bin").unwrap();
h.set_size(entries.len() as u64);
h.set_mode(0o644);
h.set_cksum();
builder.append(&h, &entries[..]).unwrap();
builder.finish().unwrap();
}
let mut gz = GzEncoder::new(Vec::new(), Compression::fast());
gz.write_all(&tar_buf).unwrap();
std::fs::write(&tarball_path, gz.finish().unwrap()).unwrap();
let err = Dict::install_with(
InstallOptions::default()
.cache_dir(cache.path())
.source(InstallSource::Tarball(tarball_path)),
);
assert!(err.is_err(), "partial tarball should not produce a Dict");
}