Files
core/test/rust/dependency/go/mod.rs
T
2026-11-03 03:32:07 +00:00

334 lines
10 KiB
Rust

//! Go 模块协议验收只使用拥有的传输和临时摘要缓存。
use super::*;
mod fixture;
mod proofs;
#[test]
fn independent_worker_graphs_merge_without_erasing_previous_modules() {
let mut registry = fixture::graph();
registry.module(
"example.com/z",
"1.0.0",
"module example.com/z\nrequire example.com/b v1.0.0\n",
);
registry.seal();
let cache = fixture::Cache::default();
let mut lock = Lock::new(vec![]);
for root in ["go:example.com/a@1.0.0", "go:example.com/z@1.0.0"] {
resolve_with(
&[root.parse().unwrap()],
BuildTarget::host().unwrap(),
&fixture::runtime(),
&registry,
&cache,
&mut lock,
&fixture::verifier(),
)
.unwrap();
}
assert_eq!(lock.libs.len(), 5);
assert_eq!(
lock.libs
.iter()
.filter(|lib| lib.spec.name == "example.com/b")
.count(),
2
);
validate_with(&lock, &fixture::verifier()).unwrap();
for (name, version) in [("a", "1.2.0"), ("z", "1.0.0")] {
let closure = selected(
&lock.libs,
&[format!("go:example.com/{name}@1.0.0").parse().unwrap()],
)
.unwrap();
assert_eq!(
closure
.iter()
.find(|lib| lib.spec.name == "example.com/b")
.unwrap()
.spec
.version,
version
);
}
}
#[test]
fn resolves_authenticated_mvs_graph_and_replays_locked_hashes() {
let registry = fixture::graph();
let cache = fixture::Cache::default();
let mut lock = Lock::new(vec![]);
let runtime = fixture::runtime();
resolve_with(
&["go:example.com/a@1.0.0".parse().unwrap()],
BuildTarget::host().unwrap(),
&runtime,
&registry,
&cache,
&mut lock,
&fixture::verifier(),
)
.unwrap();
assert_eq!(lock.libs.len(), 4);
assert_eq!(
selected(&lock.libs, &["go:example.com/a@1.0.0".parse().unwrap()])
.unwrap()
.into_iter()
.find(|lib| lib.spec.name == "example.com/b")
.unwrap()
.spec
.version,
"1.2.0"
);
let records = evidence(&lock).unwrap();
assert!(
records[0]
.records
.iter()
.any(|record| record.module == "example.com/b" && record.version == "1.0.0")
);
let checked = validate_with(&lock, &fixture::verifier()).unwrap();
for lib in &lock.libs {
let artifact = &lib.artifacts[0];
let bytes = cache
.get(&artifact.sha256, artifact.bytes)
.unwrap()
.unwrap();
checked
.verify_zip(&lib.spec.name, &lib.spec.version, &bytes)
.unwrap();
assert!(
checked
.verify_zip(&lib.spec.name, &lib.spec.version, &bytes[..bytes.len() / 2])
.is_err()
);
}
assert!(
validate_lock(&lock).is_err(),
"fixture signing key must never become a product trust root"
);
lock.libs[0].dependencies.clear();
assert!(validate_with(&lock, &fixture::verifier()).is_err());
}
#[test]
fn mvs_keeps_requirements_from_unselected_versions() {
let mut registry = fixture::Registry::default();
for (name, version, requires) in [
(
"a",
"1.0.0",
"require example.com/b v1.0.0\nrequire example.com/c v1.0.0\n",
),
("b", "1.0.0", "require example.com/d v1.2.0\n"),
("b", "1.1.0", "require example.com/d v1.0.0\n"),
(
"c",
"1.0.0",
"require example.com/b v1.1.0\nrequire example.com/d v1.0.0\n",
),
("d", "1.0.0", ""),
("d", "1.2.0", ""),
] {
registry.module(
&format!("example.com/{name}"),
version,
&format!("module example.com/{name}\ngo 1.16\n{requires}"),
);
}
registry.seal();
let roots = ["go:example.com/a@1.0.0".parse().unwrap()];
let mut lock = Lock::new(vec![]);
resolve_with(
&roots,
BuildTarget::host().unwrap(),
&fixture::runtime(),
&registry,
&fixture::Cache::default(),
&mut lock,
&fixture::verifier(),
)
.unwrap();
assert_eq!(lock.libs.len(), 6);
let versions = selected(&lock.libs, &roots)
.unwrap()
.into_iter()
.map(|lib| (lib.spec.name.as_str(), lib.spec.version.as_str()))
.collect::<BTreeMap<_, _>>();
assert_eq!(versions["example.com/b"], "1.1.0");
assert_eq!(versions["example.com/d"], "1.2.0");
let older = lock
.libs
.iter_mut()
.find(|lib| lib.spec.name == "example.com/b" && lib.spec.version == "1.0.0")
.unwrap();
older.dependencies[0].spec.version = "1.0.0".into();
assert!(validate_with(&lock, &fixture::verifier()).is_err());
}
#[test]
fn shared_module_keeps_distinct_worker_selections() {
let mut registry = fixture::Registry::default();
for (name, version, requires) in [
("x", "1.0.0", "require example.com/d v1.0.0\n"),
("y", "1.0.0", "require example.com/d v1.1.0\n"),
("d", "1.0.0", ""),
("d", "1.1.0", ""),
] {
registry.module(
&format!("example.com/{name}"),
version,
&format!("module example.com/{name}\n{requires}"),
);
}
registry.seal();
let first = vec!["go:example.com/x@1.0.0".parse().unwrap()];
let second = vec![
"go:example.com/x@1.0.0".parse().unwrap(),
"go:example.com/y@1.0.0".parse().unwrap(),
];
let mut lock = Lock::new(vec![]);
for roots in [&first, &second] {
resolve_with(
roots,
BuildTarget::host().unwrap(),
&fixture::runtime(),
&registry,
&fixture::Cache::default(),
&mut lock,
&fixture::verifier(),
)
.unwrap();
}
let restored: Lock = serde_json::from_str(&lock.encode().unwrap()).unwrap();
validate_with(&restored, &fixture::verifier()).unwrap();
assert_eq!(restored.libs.len(), 4);
for (roots, expected) in [(&first, "1.0.0"), (&second, "1.1.0")] {
let closure = selected(&restored.libs, roots).unwrap();
assert_eq!(
closure
.iter()
.filter(|lib| lib.spec.name == "example.com/d")
.count(),
1
);
assert_eq!(
closure
.iter()
.find(|lib| lib.spec.name == "example.com/d")
.unwrap()
.spec
.version,
expected
);
}
}
#[test]
fn rejected_metadata_archive_or_rollback_never_publishes_lock() {
let registry = fixture::graph();
let runtime = fixture::runtime();
for suffix in ["mod", "zip"] {
let mut changed = registry.clone();
changed
.0
.get_mut(&format!(
"https://proxy.golang.org/example.com/b/@v/v1.0.0.{suffix}"
))
.unwrap()
.extend(b"changed");
if suffix == "zip" {
changed.0.insert(
"https://proxy.golang.org/example.com/b/@v/v1.2.0.zip".into(),
fixture::zip("example.com/b", "1.2.0", "changed"),
);
}
let cache = fixture::Cache::default();
let mut lock = Lock::new(vec![]);
let before = lock.encode().unwrap();
assert!(
resolve_with(
&["go:example.com/a@1.0.0".parse().unwrap()],
BuildTarget::host().unwrap(),
&runtime,
&changed,
&cache,
&mut lock,
&fixture::verifier()
)
.is_err()
);
assert_eq!(lock.encode().unwrap(), before);
assert!(cache.0.borrow().is_empty());
}
let cache = fixture::Cache::default();
let mut lock = Lock::new(vec![]);
let mut first = fixture::Registry::default();
first.module("example.com/a", "1.0.0", "module example.com/a\n");
first.seal();
let roots = ["go:example.com/a@1.0.0".parse().unwrap()];
resolve_with(
&roots,
BuildTarget::host().unwrap(),
&runtime,
&first,
&cache,
&mut lock,
&fixture::verifier(),
)
.unwrap();
let mut next = first.clone();
next.module("example.com/z", "1.0.0", "module example.com/z\n");
next.seal();
resolve_with(
&roots,
BuildTarget::host().unwrap(),
&runtime,
&next,
&cache,
&mut lock,
&fixture::verifier(),
)
.unwrap();
assert_eq!(lock.go_sumdb.len(), 2);
let before = lock.encode().unwrap();
let error = resolve_with(
&roots,
BuildTarget::host().unwrap(),
&runtime,
&first,
&cache,
&mut lock,
&fixture::verifier(),
)
.unwrap_err();
assert!(error.contains("rollback"), "{error}");
assert_eq!(lock.encode().unwrap(), before);
}
#[test]
fn module_directives_check_identity_toolchain_and_requirements() {
let source = b"module example.com/a\ngo 1.25\nrequire (\nexample.com/b v1.2.0 // indirect\n)\n";
let parsed = module::requirements(source, "example.com/a", "1.25.0").unwrap();
assert_eq!(parsed["example.com/b"], "1.2.0");
assert!(module::requirements(source, "example.com/other", "1.25.0").is_err());
assert!(module::requirements(source, "example.com/a", "1.24.0").is_err());
for directive in [
"replace example.com/b => ../local",
"exclude example.com/b v1.0.0",
"require (\nexample.com/b v1.0.0",
"module example.com/a",
] {
assert!(
module::requirements(
format!("module example.com/a\n{directive}\n").as_bytes(),
"example.com/a",
"1.25.0"
)
.is_err()
);
}
assert_eq!(module::escape("example.com/Mixed"), "example.com/!mixed");
}