use super::super::Directory; use dever::compiler::project::Project; use serde::Serialize; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; use std::io::{Cursor, Write}; use std::path::Path; #[derive(Clone, Serialize)] pub(super) struct Entry { pub name: String, pub version: String, pub sha256: String, pub bytes: u64, pub dependencies: Vec, pub manifest_sha256: String, } #[derive(Clone, Serialize)] pub(super) struct Dependency { pub name: String, pub version: String, } pub(super) struct Fixture { pub directory: Directory, pub entries: Vec, pub assets: BTreeMap>, } impl Fixture { pub fn new(roots: &[&str]) -> Self { let directory = Directory::new(); write( &directory.0, "config/setting.json", serde_json::to_vec(&serde_json::json!({ "package":{"registry":"https://packages.example.test","use":roots} })) .unwrap(), ); Self { directory, entries: Vec::new(), assets: BTreeMap::new(), } } pub fn package(&mut self, name: &str, dependencies: &[(&str, &str)], files: &[(&str, &str)]) { let manifest = serde_json::to_vec(&serde_json::json!({ "format":"dever-package-v1", "name":name, "version":"1.0.0", "dependencies":dependencies.iter().copied().collect::>(), "lib":[] })) .unwrap(); let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new())); let options = zip::write::SimpleFileOptions::default() .compression_method(zip::CompressionMethod::Deflated); zip.start_file("dever-package.json", options).unwrap(); zip.write_all(&manifest).unwrap(); for &(path, text) in files { zip.start_file(path, options).unwrap(); zip.write_all(text.as_bytes()).unwrap(); } let bytes = zip.finish().unwrap().into_inner(); let entry = Entry { name: name.into(), version: "1.0.0".into(), sha256: digest(&bytes), bytes: bytes.len() as u64, dependencies: dependencies .iter() .map(|(name, _)| Dependency { name: (*name).into(), version: "1.0.0".into(), }) .collect(), manifest_sha256: digest(&manifest), }; self.assets.insert(entry.sha256.clone(), bytes); self.entries.push(entry); self.lock(); } pub fn lock(&mut self) { #[derive(Serialize)] struct Lock<'entries> { format: &'static str, toolchain: &'static str, libs: Vec<()>, workers: Vec<()>, #[serde(skip_serializing_if = "Vec::is_empty")] packages: &'entries Vec, } self.entries .sort_by(|left, right| left.name.cmp(&right.name)); for entry in &mut self.entries { entry .dependencies .sort_by(|left, right| left.name.cmp(&right.name)); } let lock = Lock { format: "dever-lock-v6", toolchain: env!("CARGO_PKG_VERSION"), libs: vec![], workers: vec![], packages: &self.entries, }; write( &self.directory.0, "dever.lock", serde_json::to_string_pretty(&lock).unwrap() + "\n", ); } pub fn mutate_archive(&mut self, name: &str, change: impl FnOnce(&mut Vec)) { let entry = self .entries .iter_mut() .find(|entry| entry.name == name) .unwrap(); let mut bytes = self.assets.remove(&entry.sha256).unwrap(); change(&mut bytes); entry.sha256 = digest(&bytes); entry.bytes = bytes.len() as u64; self.assets.insert(entry.sha256.clone(), bytes); self.lock(); } pub fn load(&self) -> Result { Project::load_with_artifacts(&self.directory.0, |sha256, _| { self.assets .get(sha256) .cloned() .ok_or_else(|| "locked asset is missing".into()) }) } pub fn reject(&self, needle: &str) { let error = self.load().err().expect("invalid dependencies accepted"); assert!(error.contains(needle), "expected {needle}: {error}"); } } pub(super) fn digest(bytes: &[u8]) -> String { format!("{:x}", Sha256::digest(bytes)) } pub(super) fn write(root: &Path, relative: &str, contents: impl AsRef<[u8]>) { let path = root.join(relative); std::fs::create_dir_all(path.parent().unwrap()).unwrap(); std::fs::write(path, contents).unwrap(); } pub(super) fn app(expression: &str) -> String { format!("value(input: Int) (answer: Int) {{ answer = {expression} }}\n") } pub(super) fn command() -> String { "cmd value = app.value\n".to_owned() }