Files
core/test/rust/native/dependency/library/python.rs
T
2026-11-03 03:32:07 +00:00

258 lines
8.6 KiB
Rust

//! 真实 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::<Vec<_>>(),
[
"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::<Vec<_>>(),
["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::<usize>();
eprintln!(
"cache {action}: reads={} bytes={read_bytes} fixtures={:?}",
reads.len(),
cache.keys().collect::<Vec<_>>()
);
assert_eq!(reads.len(), cache.len());
assert_eq!(read_bytes, cache.values().map(Vec::len).sum::<usize>());
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();
}