//! 真实 wheel、回环元数据及命令发布回归,不以字符串匹配代替完整闭包与文件校验。 use super::python_fixture::{Python, wheel, zip}; use dever::compiler::project::dependency::{ Lock, library::{self, python}, }; use dever::compiler::toolchain::BuildTarget; #[test] fn python_resolves_wheel_metadata_extras_markers_and_backtracks() { let mut fixture = Python::new(); fixture.transport.release("alpha", "1.0.0", "Requires-Dist: common>=1,<3\nRequires-Dist: helper; extra == 'test'\nProvides-Extra: test\nRequires-Dist: windows-only; sys_platform == 'win32'\n"); fixture .transport .release("common", "2.0.0", "Requires-Dist: helper>=2\n"); fixture .transport .release("common", "1.0.0", "Requires-Dist: helper<2\n"); fixture.transport.release("helper", "1.0.0", ""); fixture.transport.release("helper", "2.0.0", ""); fixture .command("add", &["pip:alpha[test]@1.0.0", "pip:helper@1.0.0"]) .unwrap(); let lock = fixture.lock(); assert_eq!( lock.libs .iter() .map(|lib| lib.spec.key()) .collect::>(), [ "pip:alpha[test]@1.0.0", "pip:common@1.0.0", "pip:helper@1.0.0" ] ); assert!( fixture .transport .requests .borrow() .iter() .all(|url| !url.contains("wrong-registry-summary") && !url.contains("windows-only")) ); let metadata = fixture.runtime(); let runtime = library::runtime::load( &fixture.signed, "pip", BuildTarget::host().unwrap(), &metadata.pack, ) .unwrap(); for lib in &lock.libs { let files = python::install( lib, &fixture.store.0.borrow()[&lib.artifacts[0].sha256], &runtime.manifest, &lock.libs, ) .unwrap(); assert!(files.keys().any(|path| path.ends_with("__init__.py"))); } } #[test] fn selected_update_preserves_other_roots_and_exact_install_never_resolves() { let mut fixture = Python::new(); for (name, version) in [("alpha", "1.0.0"), ("alpha", "2.0.0"), ("beta", "1.0.0")] { fixture.transport.release(name, version, ""); } fixture .command("add", &["pip:alpha@1.0.0", "pip:beta@1.0.0"]) .unwrap(); fixture.command("update", &["pip:alpha@2.0.0"]).unwrap(); let lock = fixture.lock(); assert_eq!( lock.libs .iter() .map(|lib| lib.spec.key()) .collect::>(), ["pip:alpha@2.0.0", "pip:beta@1.0.0"] ); let root = &fixture.fixture.directory.0; let before = [ std::fs::read(root.join("config/setting.json")).unwrap(), std::fs::read(root.join("dever.lock")).unwrap(), ]; fixture.store.0.borrow_mut().clear(); fixture.transport.requests.borrow_mut().clear(); fixture.command("install", &[]).unwrap(); assert_eq!(fixture.transport.requests.borrow().len(), 2); assert!( fixture .transport .requests .borrow() .iter() .all(|url| url.starts_with("https://files.pythonhosted.org/")) ); assert_eq!(fixture.store.0.borrow().len(), 2); fixture.transport.requests.borrow_mut().clear(); let cache = fixture.store.0.borrow().clone(); for action in ["install", "doctor"] { fixture.store.1.borrow_mut().clear(); fixture.command(action, &[]).unwrap(); let reads = fixture.store.1.borrow(); let read_bytes = reads.iter().map(|(_, bytes)| bytes).sum::(); eprintln!( "cache {action}: reads={} bytes={read_bytes} fixtures={:?}", reads.len(), cache.keys().collect::>() ); assert_eq!(reads.len(), cache.len()); assert_eq!(read_bytes, cache.values().map(Vec::len).sum::()); for hash in cache.keys() { assert_eq!(reads.iter().filter(|(key, _)| key == hash).count(), 1); } } assert_eq!(*fixture.store.0.borrow(), cache); assert!(fixture.transport.requests.borrow().is_empty()); assert_eq!( before, [ std::fs::read(root.join("config/setting.json")).unwrap(), std::fs::read(root.join("dever.lock")).unwrap() ] ); let hash = cache.keys().next().unwrap(); for damaged in [vec![0; cache[hash].len()], vec![]] { fixture.store.0.borrow_mut().insert(hash.clone(), damaged); for action in ["install", "doctor"] { assert!( fixture .command(action, &[]) .unwrap_err() .contains("digest or length") ); assert!(fixture.transport.requests.borrow().is_empty()); } } } #[test] fn failed_resolution_and_tampered_restore_leave_project_and_cache_unchanged() { let mut fixture = Python::new(); fixture.transport.release("alpha", "1.0.0", ""); fixture .transport .release("broken", "1.0.0", "Requires-Dist: alpha<1\n"); fixture.command("add", &["pip:alpha@1.0.0"]).unwrap(); let root = &fixture.fixture.directory.0; let before = [ std::fs::read(root.join("config/setting.json")).unwrap(), std::fs::read(root.join("dever.lock")).unwrap(), ]; assert!( fixture .command("add", &["pip:broken@1.0.0"]) .unwrap_err() .contains("conflict") ); let source = fixture.lock().libs[0].artifacts[0] .source .as_ref() .unwrap() .locator .clone(); fixture.transport.files.insert(source, b"tampered".to_vec()); fixture.store.0.borrow_mut().clear(); assert!( fixture .command("install", &[]) .unwrap_err() .contains("digest") ); assert!(fixture.store.0.borrow().is_empty()); assert_eq!( before, [ std::fs::read(root.join("config/setting.json")).unwrap(), std::fs::read(root.join("dever.lock")).unwrap() ] ); } #[test] fn wheel_record_rejects_mutation_omission_duplicate_and_weak_hash() { let fixture = Python::new(); let bytes = wheel("alpha", "1.0.0", ""); let lib = serde_json::from_value(serde_json::json!({"spec":{"ecosystem":"pip","name":"alpha","version":"1.0.0"},"dependencies":[],"runtime":fixture.runtime().pack,"artifacts":[],"schema":"pypi-wheel-v1"})).unwrap(); let runtime = library::runtime::load( &fixture.signed, "pip", BuildTarget::host().unwrap(), &fixture.runtime().pack, ) .unwrap(); let original = library::archive::files(&bytes, true).unwrap(); python::install(&lib, &bytes, &runtime.manifest, &[]).unwrap(); let record = "alpha-1.0.0.dist-info/RECORD"; for mode in 0..4 { let mut files = original.clone(); match mode { 0 => { files.insert("alpha/__init__.py".into(), b"VALUE = 43\n".to_vec()); } 1 => { files.insert("alpha/extra.py".into(), b"unlisted".to_vec()); } 2 => { let duplicate = files[record].clone(); files.get_mut(record).unwrap().extend_from_slice(&duplicate); } _ => { let text = String::from_utf8(files[record].clone()) .unwrap() .replace("sha256=", "md5="); files.insert(record.into(), text.into_bytes()); } } assert!( python::install(&lib, &zip(files), &runtime.manifest, &[]).is_err(), "accepted mode {mode}" ); } } #[test] fn python_resolver_failure_does_not_mutate_input_lock() { let mut fixture = Python::new(); fixture .transport .release("alpha", "1.0.0", "Requires-Dist: missing>=1\n"); let mut lock = Lock::new(vec![]); let before = lock.encode().unwrap(); let result = python::resolve( &["pip:alpha@1.0.0".parse().unwrap()], BuildTarget::host().unwrap(), &fixture.runtime(), &fixture.transport, &fixture.store, &mut lock, ); assert!(result.is_err()); assert_eq!(lock.encode().unwrap(), before); } #[test] fn self_requirements_are_checked_without_a_spurious_locked_edge() { let mut fixture = Python::new(); fixture .transport .release("alpha", "1.0.0", "Requires-Dist: alpha>=1\n"); fixture.command("add", &["pip:alpha@1.0.0"]).unwrap(); assert!(fixture.lock().libs[0].dependencies.is_empty()); fixture.command("doctor", &[]).unwrap(); }