Skip to content

Commit fb4687a

Browse files
committed
Enable relink test
1 parent 5ba9efb commit fb4687a

File tree

1 file changed

+169
-156
lines changed

1 file changed

+169
-156
lines changed

external-crates/move/crates/move-vm-runtime/src/unit_tests/package_cache_tests.rs

Lines changed: 169 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,27 @@
55
use crate::{
66
cache::move_cache::{MoveCache, Package},
77
dev_utils::{
8-
compilation_utils::{compile_packages, compile_packages_in_file},
8+
compilation_utils::{
9+
compile_packages, compile_packages_in_file, expect_modules, make_base_path,
10+
},
911
in_memory_test_adapter::InMemoryTestAdapter,
12+
storage::StoredPackage,
1013
vm_test_adapter::VMTestAdapter,
1114
},
1215
runtime::{package_resolution::resolve_packages, telemetry::TransactionTelemetryContext},
13-
shared::{linkage_context::LinkageContext, types::VersionId},
16+
shared::{
17+
linkage_context::LinkageContext,
18+
types::{OriginalId, VersionId},
19+
},
20+
};
21+
use indexmap::IndexMap;
22+
use move_binary_format::{CompiledModule, errors::VMResult};
23+
use move_compiler::Compiler;
24+
use move_core_types::{
25+
account_address::AccountAddress,
26+
identifier::Identifier,
27+
resolver::{IntraPackageName, ModuleResolver},
1428
};
15-
use move_binary_format::errors::VMResult;
16-
use move_core_types::{account_address::AccountAddress, resolver::ModuleResolver};
1729
use move_vm_config::runtime::VMConfig;
1830
use std::collections::BTreeMap;
1931
use std::sync::Arc;
@@ -723,165 +735,166 @@ fn cache_reuses_packages_across_linkage_contexts() {
723735
assert_eq!(pkg1_before.loaded_types_len(), pkg1_reloaded.loaded_types_len());
724736
}
725737

726-
// Test that we properly publish and relink (and reuse) packages.
727-
// FIXME FIXME FIXME
728-
#[test]
729-
fn relink() {
730-
/*
731-
let mut adapter = InMemoryTestAdapter::new();
732-
733-
let st_c_v1_addr = AccountAddress::from_hex_literal("0x42").unwrap();
734-
let st_b_v1_addr = AccountAddress::from_hex_literal("0x43").unwrap();
735-
736-
let c_original_addr = AccountAddress::from_hex_literal("0x2").unwrap();
737-
let b_original_addr = AccountAddress::from_hex_literal("0x3").unwrap();
738-
let _a_original_addr = AccountAddress::from_hex_literal("0x4").unwrap();
739-
740-
// publish c v0
741-
let packages = compile_modules_in_file("rt_c_v0.move", &[]);
742-
assert!(packages.len() == 1);
743-
let (runtime_package_id, modules) = packages.into_iter().next().unwrap();
744-
adapter
745-
.publish_package_to_storage(
746-
runtime_package_id,
747-
runtime_package_id,
748-
modules,
749-
BTreeMap::new(),
750-
BTreeSet::new(),
751-
)
752-
.unwrap();
753-
754-
assert_eq!(adapter.runtime()cache().package_cache().len(), 0);
755-
756-
// publish c v1
757-
let packages = compile_modules_in_file("rt_c_v1.move", &[]);
758-
assert!(packages.len() == 1);
759-
let (runtime_package_id, modules) = packages.into_iter().next().unwrap();
760-
adapter
761-
.publish_package_to_storage(
762-
runtime_package_id,
763-
st_c_v1_addr,
764-
modules,
765-
[(
766-
(
767-
ModuleId::new(runtime_package_id, Identifier::new("c").unwrap()),
768-
Identifier::new("S").unwrap(),
769-
),
770-
ModuleId::new(c_original_addr, Identifier::new("c").unwrap()),
771-
)]
772-
.into_iter()
773-
.collect(),
774-
BTreeSet::new(),
775-
)
776-
.unwrap();
738+
/// Compile a relinker test module with the given original/version IDs and dependencies.
739+
fn relinker_pkg(
740+
original_id: OriginalId,
741+
version_id: VersionId,
742+
module: &str,
743+
deps: &[&str],
744+
) -> StoredPackage {
745+
let base = make_base_path();
746+
let source = base
747+
.join(format!("rt_{module}.move"))
748+
.to_string_lossy()
749+
.to_string();
750+
let dep_paths: Vec<String> = deps
751+
.iter()
752+
.map(|d| base.join(format!("rt_{d}.move")).to_string_lossy().to_string())
753+
.collect();
754+
755+
let (_, units) = Compiler::from_files(
756+
None,
757+
vec![source],
758+
dep_paths,
759+
BTreeMap::<String, _>::new(),
760+
)
761+
.build_and_report()
762+
.expect("compilation failed");
777763

778-
assert_eq!(adapter.cache.package_cache().len(), 1);
764+
let modules: Vec<CompiledModule> = expect_modules(units)
765+
.filter(|m| *m.self_id().address() == original_id)
766+
.collect();
767+
StoredPackage::from_modules_for_testing(version_id, modules).unwrap()
768+
}
779769

780-
// publish b_v0 <- c_v0
781-
let packages = compile_modules_in_file("rt_b_v0.move", &["rt_c_v0.move"]);
782-
assert!(packages.len() == 1);
783-
let (runtime_package_id, modules) = packages.into_iter().next().unwrap();
784-
adapter
785-
.publish_package_to_storage(
786-
runtime_package_id,
787-
runtime_package_id,
788-
modules,
789-
BTreeMap::new(),
790-
[c_original_addr].into_iter().collect(),
791-
)
792-
.unwrap();
770+
// Test that publishing and relinking packages properly updates the cache:
771+
// - Different linkage contexts sharing a dependency reuse its cached Arc
772+
// - Different versions of the same original package (C v0 vs C v1) are cached separately
773+
// - A failed publish (linkage mismatch) does not corrupt the cache
774+
//
775+
// Package dependency structure:
776+
// C v0 (0x2): struct S, fun c() -> 42
777+
// C v1 (0x5): struct S, struct R, fun c() -> 43, fun d() -> 44
778+
// B v0 (0x3): fun b() = c::c() + 1 (depends on C)
779+
// A v0 (0x4): fun a() = b::b() + c::d() (depends on B and C v1)
780+
781+
//Stores 4 packages with a relinking dependency chain: C v0 (0x2), C v1 (0x5, upgrade of C), B v0 (0x3, depends on C), A v0 (0x4, depends on B + C v1)
782+
//Loads B's dependency tree (B + C v0) into cache — verifies cache = 2
783+
//Loads A's dependency tree (A + B + C v1) into cache — verifies cache = 4 and that B v0 is reused from cache (Arc::ptr_eq) rather than recompiled
784+
//Verifies C v0 and C v1 are separate cache entries (different VersionIds)
785+
//Attempts a bad publish of A linked against C v0 (A calls d() which only exists in C v1) — verifies it fails and cache is unchanged
786+
#[test]
787+
fn relink() {
788+
let c_orig = AccountAddress::from_hex_literal("0x2").unwrap();
789+
let b_orig = AccountAddress::from_hex_literal("0x3").unwrap();
790+
let a_orig = AccountAddress::from_hex_literal("0x4").unwrap();
791+
let c_v1_addr = AccountAddress::from_hex_literal("0x5").unwrap();
793792

794-
assert_eq!(adapter.cache.package_cache().len(), 2);
793+
let mut adapter = InMemoryTestAdapter::new();
795794

796-
// publish b_v0 <- c_v1
797-
let packages = compile_modules_in_file("rt_b_v0.move", &["rt_c_v1.move"]);
798-
assert!(packages.len() == 1);
799-
let (runtime_package_id, modules) = packages.into_iter().next().unwrap();
800-
adapter
801-
.publish_package_to_storage(
802-
runtime_package_id,
803-
st_b_v1_addr,
804-
modules,
805-
[
806-
(
807-
(
808-
ModuleId::new(c_original_addr, Identifier::new("c").unwrap()),
809-
Identifier::new("S").unwrap(),
810-
),
811-
ModuleId::new(c_original_addr, Identifier::new("c").unwrap()),
812-
),
813-
(
814-
(
815-
ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()),
816-
Identifier::new("R").unwrap(),
817-
),
818-
ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()),
819-
),
820-
]
821-
.into_iter()
822-
.collect(),
823-
[st_c_v1_addr].into_iter().collect(),
824-
)
825-
.unwrap();
795+
// -- Store all packages in storage --
796+
797+
// C v0 (original=0x2, stored at 0x2)
798+
let c0 = relinker_pkg(c_orig, c_orig, "c_v0", &[]);
799+
adapter.insert_package_into_storage(c0);
800+
801+
// C v1 (original=0x2, stored at 0x5) with type origins:
802+
// S was defined in C v0 (0x2), R is new in C v1 (0x5)
803+
let mut c1 = relinker_pkg(c_orig, c_v1_addr, "c_v1", &[]);
804+
c1.0.type_origin_table = IndexMap::from([
805+
(
806+
IntraPackageName {
807+
module_name: Identifier::new("c").unwrap(),
808+
type_name: Identifier::new("S").unwrap(),
809+
},
810+
c_orig,
811+
),
812+
(
813+
IntraPackageName {
814+
module_name: Identifier::new("c").unwrap(),
815+
type_name: Identifier::new("R").unwrap(),
816+
},
817+
c_v1_addr,
818+
),
819+
]);
820+
adapter.insert_package_into_storage(c1);
821+
822+
// B v0 (original=0x3, stored at 0x3) linked against C v0
823+
let mut b0 = relinker_pkg(b_orig, b_orig, "b_v0", &["c_v0"]);
824+
b0.0.linkage_table = BTreeMap::from([(c_orig, c_orig), (b_orig, b_orig)]);
825+
adapter.insert_package_into_storage(b0);
826+
827+
// A v0 (original=0x4, stored at 0x4) linked against C v1 and B v0
828+
let mut a0 = relinker_pkg(a_orig, a_orig, "a_v0", &["b_v0", "c_v1"]);
829+
a0.0.linkage_table =
830+
BTreeMap::from([(c_orig, c_v1_addr), (b_orig, b_orig), (a_orig, a_orig)]);
831+
adapter.insert_package_into_storage(a0);
832+
833+
// -- Load packages into cache with different linkage contexts --
834+
835+
assert_eq!(adapter.runtime().cache().package_cache().len(), 0);
836+
837+
// Load B v0's dependency tree: B v0 + C v0
838+
let link_b = LinkageContext::new(BTreeMap::from([(c_orig, c_orig), (b_orig, b_orig)]));
839+
load_linkage_packages_into_runtime(&mut adapter, &link_b).unwrap();
840+
assert_eq!(adapter.runtime().cache().package_cache().len(), 2);
826841

827-
assert_eq!(adapter.cache.package_cache().len(), 4);
842+
// Capture B v0's cached Arc to verify reuse across linkage contexts
843+
let b_cached = adapter
844+
.runtime()
845+
.cache()
846+
.cached_package_at(b_orig)
847+
.expect("B v0 should be cached");
848+
849+
// Load A v0's dependency tree: A v0 + B v0 + C v1.
850+
// B v0 (at 0x3) should be reused from cache.
851+
// C v1 (at 0x5) is new — a different VersionId from C v0 (at 0x2).
852+
let link_a = LinkageContext::new(BTreeMap::from([
853+
(c_orig, c_v1_addr),
854+
(b_orig, b_orig),
855+
(a_orig, a_orig),
856+
]));
857+
load_linkage_packages_into_runtime(&mut adapter, &link_a).unwrap();
858+
// Cache now has: C v0 (0x2), C v1 (0x5), B v0 (0x3), A v0 (0x4)
859+
assert_eq!(adapter.runtime().cache().package_cache().len(), 4);
860+
861+
// B v0 is the same Arc (reused, not recompiled)
862+
let b_reused = adapter
863+
.runtime()
864+
.cache()
865+
.cached_package_at(b_orig)
866+
.expect("B v0 should still be cached");
867+
assert!(
868+
Arc::ptr_eq(&b_cached, &b_reused),
869+
"B v0 should be reused across linkage contexts"
870+
);
828871

829-
// publish a_v0 <- c_v1 && b_v0
830-
let packages = compile_modules_in_file("rt_a_v0.move", &["rt_c_v1.move", "rt_b_v0.move"]);
831-
assert!(packages.len() == 1);
832-
let (runtime_package_id, modules) = packages.into_iter().next().unwrap();
833-
adapter
834-
.publish_package_to_storage(
835-
runtime_package_id,
836-
runtime_package_id,
837-
modules,
838-
[
839-
(
840-
(
841-
ModuleId::new(c_original_addr, Identifier::new("c").unwrap()),
842-
Identifier::new("S").unwrap(),
843-
),
844-
ModuleId::new(c_original_addr, Identifier::new("c").unwrap()),
845-
),
846-
(
847-
(
848-
ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()),
849-
Identifier::new("R").unwrap(),
850-
),
851-
ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()),
852-
),
853-
]
854-
.into_iter()
855-
.collect(),
856-
[st_c_v1_addr, b_original_addr].into_iter().collect(),
857-
)
858-
.unwrap();
872+
// Both C versions are cached as separate entries
873+
assert!(adapter.runtime().cache().cached_package_at(c_orig).is_some());
874+
assert!(
875+
adapter
876+
.runtime()
877+
.cache()
878+
.cached_package_at(c_v1_addr)
879+
.is_some()
880+
);
859881

860-
assert_eq!(adapter.cache.package_cache().len(), 5);
882+
let cache_before_bad = adapter.runtime().cache().package_cache().len();
861883

862-
// publish a_v0 <- c_v0 && b_v0 -- ERROR since a_v0 requires c_v1+
863-
let packages = compile_modules_in_file("rt_a_v0.move", &["rt_c_v1.move", "rt_b_v0.move"]);
864-
assert!(packages.len() == 1);
865-
let (runtime_package_id, modules) = packages.into_iter().next().unwrap();
866-
adapter
867-
.publish_package_to_storage(
868-
runtime_package_id,
869-
runtime_package_id,
870-
modules,
871-
[(
872-
(
873-
ModuleId::new(c_original_addr, Identifier::new("c").unwrap()),
874-
Identifier::new("S").unwrap(),
875-
),
876-
ModuleId::new(c_original_addr, Identifier::new("c").unwrap()),
877-
)]
878-
.into_iter()
879-
.collect(),
880-
[c_original_addr, b_original_addr].into_iter().collect(),
881-
)
882-
.unwrap_err();
884+
// Try publishing A v0 linked against C v0 instead of C v1.
885+
// A calls d() which only exists in C v1, so verification should fail.
886+
let mut a0_bad = relinker_pkg(a_orig, a_orig, "a_v0", &["b_v0", "c_v1"]);
887+
a0_bad.0.linkage_table =
888+
BTreeMap::from([(c_orig, c_orig), (b_orig, b_orig), (a_orig, a_orig)]);
889+
let result = adapter.publish_package(a_orig, a0_bad.into_serialized_package());
890+
assert!(
891+
result.is_err(),
892+
"A linked against C v0 should fail (missing d())"
893+
);
883894

884-
// cache stays the same since the publish failed
885-
assert_eq!(adapter.cache.package_cache().len(), 5);
886-
*/
895+
// Cache must not change from a failed publish
896+
assert_eq!(
897+
adapter.runtime().cache().package_cache().len(),
898+
cache_before_bad,
899+
);
887900
}

0 commit comments

Comments
 (0)