//! 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(), ®istry, &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, ®istry, &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(), ®istry, &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::>(); 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(), ®istry, &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"); }