258 lines
8.6 KiB
Rust
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();
|
|
}
|