use crate::library::{
AbiVersion, Dependency, Export, Lib, LibManifest, LibTarget, Linker, LoadCx, Registry, Version,
};
use crate::{Claim, ClaimPattern, Cx, Error, FunctionId, LibId, Ref, Result, Symbol, Value};
fn manifest(id: &str, version: &str) -> LibManifest {
LibManifest {
id: Symbol::new(id),
version: Version(version.to_owned()),
abi: AbiVersion { major: 0, minor: 1 },
target: LibTarget::HostRegistered,
requires: Vec::new(),
capabilities: Vec::new(),
exports: Vec::new(),
}
}
fn bool_value(cx: &mut Cx, value: bool) -> Value {
cx.factory().bool(value).unwrap()
}
fn load_value_lib_with_id(
registry: &mut Registry,
cx: &mut Cx,
lib: &str,
export: &str,
) -> (LibId, Symbol, Value) {
let export = Symbol::new(export);
let value = bool_value(cx, true);
let mut txn = registry.begin_load(
LibManifest {
exports: vec![Export::Value {
symbol: export.clone(),
}],
..manifest(lib, "0.1.0")
},
true,
);
txn.linker().value(export.clone(), value.clone()).unwrap();
let lib_id = registry.commit_load(txn).unwrap();
(lib_id, export, value)
}
fn load_function_lib(
registry: &mut Registry,
cx: &mut Cx,
lib: &str,
export: &str,
) -> (LibId, Symbol, FunctionId, Value) {
let export = Symbol::new(export);
let value = bool_value(cx, true);
let mut txn = registry.begin_load(
LibManifest {
exports: vec![Export::Function {
symbol: export.clone(),
function_id: None,
}],
..manifest(lib, "0.1.0")
},
true,
);
let function_id = txn
.linker()
.function_value(export.clone(), value.clone())
.unwrap();
let lib_id = registry.commit_load(txn).unwrap();
(lib_id, export, function_id, value)
}
fn load_manifest_only_lib(registry: &mut Registry, lib: LibManifest) -> LibId {
let txn = registry.begin_load(lib, true);
registry.commit_load(txn).unwrap()
}
struct SimpleValueLib;
impl Lib for SimpleValueLib {
fn manifest(&self) -> LibManifest {
LibManifest {
exports: vec![Export::Value {
symbol: Symbol::new("cx-unload-value"),
}],
..manifest("cx-unload-lib", "0.1.0")
}
}
fn load(&self, cx: &mut LoadCx, linker: &mut Linker) -> Result<()> {
linker.value(
Symbol::new("cx-unload-value"),
cx.factory().bool(true).unwrap(),
)
}
}
#[test]
fn unload_removes_resolution_for_every_loaded_export_kind() {
let mut cx = Cx::stub();
let mut registry = Registry::default();
let class_symbol = Symbol::new("unload-class");
let function_symbol = Symbol::new("unload-function");
let macro_symbol = Symbol::new("unload-macro");
let shape_symbol = Symbol::new("unload-shape");
let codec_symbol = Symbol::new("unload-codec");
let domain_symbol = Symbol::new("unload-domain");
let value_symbol = Symbol::new("unload-value");
let mut txn = registry.begin_load(
LibManifest {
exports: vec![
Export::Class {
symbol: class_symbol.clone(),
class_id: None,
},
Export::Function {
symbol: function_symbol.clone(),
function_id: None,
},
Export::Macro {
symbol: macro_symbol.clone(),
macro_id: None,
},
Export::Shape {
symbol: shape_symbol.clone(),
shape_id: None,
},
Export::Codec {
symbol: codec_symbol.clone(),
codec_id: None,
},
Export::NumberDomain {
symbol: domain_symbol.clone(),
number_domain_id: None,
},
Export::Value {
symbol: value_symbol.clone(),
},
],
..manifest("unload-all-lib", "0.1.0")
},
true,
);
{
let mut linker = txn.linker();
linker
.class_value(class_symbol.clone(), bool_value(&mut cx, true))
.unwrap();
linker
.function_value(function_symbol.clone(), bool_value(&mut cx, true))
.unwrap();
linker
.macro_value(macro_symbol.clone(), bool_value(&mut cx, true))
.unwrap();
linker
.shape_value(shape_symbol.clone(), bool_value(&mut cx, true))
.unwrap();
linker
.codec_value(codec_symbol.clone(), bool_value(&mut cx, true))
.unwrap();
linker
.number_domain_value(domain_symbol.clone(), bool_value(&mut cx, true))
.unwrap();
linker
.value(value_symbol.clone(), bool_value(&mut cx, true))
.unwrap();
}
let lib_id = registry.commit_load(txn).unwrap();
assert_eq!(registry.unload(lib_id).unwrap(), vec![lib_id]);
assert!(registry.class_by_symbol(&class_symbol).is_none());
assert!(registry.function_by_symbol(&function_symbol).is_none());
assert!(registry.macro_by_symbol(¯o_symbol).is_none());
assert!(registry.shape_by_symbol(&shape_symbol).is_none());
assert!(registry.codec_by_symbol(&codec_symbol).is_none());
assert!(registry.number_domain_by_symbol(&domain_symbol).is_none());
assert!(registry.value_by_symbol(&value_symbol).is_none());
assert!(registry.lib(&Symbol::new("unload-all-lib")).is_none());
registry.assert_projection_caches_match_catalog_for_tests();
}
#[test]
fn unload_refuses_loaded_dependents() {
let mut registry = Registry::default();
let dep_id = load_manifest_only_lib(&mut registry, manifest("dep-lib", "0.1.0"));
let mut user = manifest("user-lib", "0.1.0");
user.requires.push(Dependency {
id: Symbol::new("dep-lib"),
minimum_version: None,
});
load_manifest_only_lib(&mut registry, user);
let err = registry.unload(dep_id).unwrap_err();
assert!(matches!(
err,
Error::LibHasDependents { lib, dependents }
if lib == Symbol::new("dep-lib")
&& dependents == vec![Symbol::new("user-lib")]
));
assert!(registry.lib(&Symbol::new("dep-lib")).is_some());
assert!(registry.lib(&Symbol::new("user-lib")).is_some());
}
#[test]
fn unload_cascade_runs_dependents_before_dependencies() {
let mut registry = Registry::default();
let dep_id = load_manifest_only_lib(&mut registry, manifest("dep-lib", "0.1.0"));
let mut mid = manifest("mid-lib", "0.1.0");
mid.requires.push(Dependency {
id: Symbol::new("dep-lib"),
minimum_version: None,
});
let mid_id = load_manifest_only_lib(&mut registry, mid);
let mut top = manifest("top-lib", "0.1.0");
top.requires.push(Dependency {
id: Symbol::new("mid-lib"),
minimum_version: None,
});
let top_id = load_manifest_only_lib(&mut registry, top);
assert_eq!(
registry.unload_cascade(dep_id).unwrap(),
vec![top_id, mid_id, dep_id]
);
assert!(registry.libs().is_empty());
}
#[test]
fn unload_absent_lib_is_noop() {
let mut cx = Cx::stub();
let mut registry = Registry::default();
let (loaded, symbol, _) =
load_value_lib_with_id(&mut registry, &mut cx, "present-lib", "present-value");
assert!(registry.unload(LibId(999)).unwrap().is_empty());
assert_eq!(registry.libs().len(), 1);
assert!(registry.lib(&Symbol::new("present-lib")).is_some());
assert!(registry.value_by_symbol(&symbol).is_some());
assert!(registry.unload_cascade(LibId(999)).unwrap().is_empty());
assert_eq!(registry.unload(loaded).unwrap(), vec![loaded]);
}
#[test]
fn unload_then_reload_reuses_stable_ids_for_same_load() {
let mut cx = Cx::stub();
let mut registry = Registry::default();
let (first_lib, symbol, first_function, _) =
load_function_lib(&mut registry, &mut cx, "reload-lib", "reload-function");
assert_eq!(registry.unload(first_lib).unwrap(), vec![first_lib]);
let (second_lib, second_symbol, second_function, _) =
load_function_lib(&mut registry, &mut cx, "reload-lib", "reload-function");
assert_eq!(second_lib, first_lib);
assert_eq!(second_symbol, symbol);
assert_eq!(second_function, first_function);
assert!(registry.function_by_symbol(&symbol).is_some());
}
#[test]
fn unload_removes_fresh_resolution_but_live_value_survives() {
let mut cx = Cx::stub();
let mut registry = Registry::default();
let (lib_id, symbol, live_value) =
load_value_lib_with_id(&mut registry, &mut cx, "live-lib", "live-value");
assert_eq!(registry.value_by_symbol(&symbol), Some(&live_value));
registry.unload(lib_id).unwrap();
assert!(registry.value_by_symbol(&symbol).is_none());
assert_eq!(
live_value.object().as_expr(&mut cx).unwrap(),
crate::Expr::Bool(true)
);
}
#[test]
fn cx_unload_retracts_published_load_claims() {
let mut cx = Cx::stub();
let lib = SimpleValueLib;
let lib_id = cx.load_lib(&lib).unwrap();
let subject = Ref::Symbol(Symbol::new("cx-unload-value"));
let predicate = Symbol::qualified("core", "exported-by");
let object = Ref::Symbol(Symbol::new("cx-unload-lib"));
let pattern = ClaimPattern::exact(subject.clone(), predicate.clone(), object.clone());
let extra_subject = Ref::Symbol(Symbol::new("cx-unload-extra"));
let extra_predicate = Symbol::qualified("core", "extra");
let extra_object = Ref::Symbol(Symbol::new("cx-unload-value"));
let extra_pattern = ClaimPattern::exact(
extra_subject.clone(),
extra_predicate.clone(),
extra_object.clone(),
);
assert_eq!(cx.query_facts(pattern.clone()).unwrap().len(), 1);
cx.insert_fact_for_lib(
lib_id,
Claim::public(extra_subject, extra_predicate, extra_object),
)
.unwrap();
assert_eq!(cx.query_facts(extra_pattern.clone()).unwrap().len(), 1);
assert_eq!(cx.unload_lib(lib_id).unwrap(), vec![lib_id]);
assert!(cx.query_facts(pattern).unwrap().is_empty());
assert!(cx.query_facts(extra_pattern).unwrap().is_empty());
assert!(
cx.registry()
.value_by_symbol(&Symbol::new("cx-unload-value"))
.is_none()
);
}
#[test]
fn cx_unload_preserves_duplicate_claims_that_predated_load() {
let mut cx = Cx::stub();
let subject = Ref::Symbol(Symbol::new("cx-unload-value"));
let predicate = Symbol::qualified("core", "exported-by");
let object = Ref::Symbol(Symbol::new("cx-unload-lib"));
let pattern = ClaimPattern::exact(subject.clone(), predicate.clone(), object.clone());
cx.insert_fact(Claim::public(subject, predicate, object))
.unwrap();
let lib = SimpleValueLib;
let lib_id = cx.load_lib(&lib).unwrap();
assert_eq!(cx.query_facts(pattern.clone()).unwrap().len(), 1);
cx.unload_lib(lib_id).unwrap();
assert_eq!(cx.query_facts(pattern).unwrap().len(), 1);
}