#![allow(clippy::expect_used, clippy::panic)]
use std::path::PathBuf;
use lean_rs::module::{LeanIo, LeanLibrary};
use lean_rs::{DiagnosticCapture, LeanDiagnosticCode, LeanError, LeanRuntime};
use lean_rs_host::meta::{MetaCallStatus, infer_type};
use lean_rs_host::{LeanCapabilities, LeanElabOptions, LeanHost, LeanSession};
fn fixture_lake_root() -> PathBuf {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace = manifest_dir
.parent()
.and_then(std::path::Path::parent)
.expect("crates/<name>/ lives two directories beneath the workspace root");
workspace.join("fixtures").join("lean")
}
fn fixture_dylib_path() -> PathBuf {
let dylib_ext = if cfg!(target_os = "macos") { "dylib" } else { "so" };
let lib_dir = fixture_lake_root().join(".lake").join("build").join("lib");
let new_style = lib_dir.join(format!("liblean__rs__fixture_LeanRsFixture.{dylib_ext}"));
let old_style = lib_dir.join(format!("libLeanRsFixture.{dylib_ext}"));
if old_style.is_file() && !new_style.is_file() {
old_style
} else {
new_style
}
}
fn runtime() -> &'static LeanRuntime {
LeanRuntime::init().expect("Lean runtime initialisation must succeed")
}
fn fixture_host() -> LeanHost<'static> {
LeanHost::from_lake_project(runtime(), fixture_lake_root()).expect("host opens cleanly")
}
fn fixture_caps<'lean, 'h>(host: &'h LeanHost<'lean>) -> LeanCapabilities<'lean, 'h> {
host.load_capabilities("lean_rs_fixture", "LeanRsFixture")
.expect("load caps")
}
fn session_over_elaboration<'lean, 'c>(caps: &'c LeanCapabilities<'lean, 'c>) -> LeanSession<'lean, 'c> {
caps.session(&["LeanRsHostShims.Elaboration"])
.expect("session imports cleanly")
}
fn session_over_handles<'lean, 'c>(caps: &'c LeanCapabilities<'lean, 'c>) -> LeanSession<'lean, 'c> {
caps.session(&["LeanRsFixture.Handles"])
.expect("session imports cleanly")
}
#[test]
fn module_init_code_on_missing_lake_project() {
let bogus = std::env::temp_dir().join("lean_rs_definitely_not_a_lake_project_for_prompt_25");
let err = LeanHost::from_lake_project(runtime(), &bogus).expect_err("missing project must fail");
assert_eq!(err.code(), LeanDiagnosticCode::ModuleInit, "got {err:?}");
}
#[test]
fn module_init_code_on_missing_dylib() {
let host = fixture_host();
let err = host
.load_capabilities("lean_rs_fixture", "DefinitelyMissingLib")
.expect_err("missing capability dylib must fail");
assert_eq!(err.code(), LeanDiagnosticCode::ModuleInit, "got {err:?}");
}
#[test]
fn linking_code_on_invalid_lake_identifier() {
let library = LeanLibrary::open(runtime(), fixture_dylib_path()).expect("fixture dylib opens");
let err = library
.initialize_module("lean_rs_fixture", "Bad Module")
.expect_err("invalid Lake module name must fail");
assert_eq!(err.code(), LeanDiagnosticCode::Linking, "got {err:?}");
}
#[test]
fn linking_code_on_missing_initializer_symbol() {
let library = LeanLibrary::open(runtime(), fixture_dylib_path()).expect("fixture dylib opens");
let err = library
.initialize_module("lean_rs_fixture", "DefinitelyNotAModule")
.expect_err("missing initializer symbol must fail");
assert_eq!(err.code(), LeanDiagnosticCode::Linking, "got {err:?}");
}
#[test]
fn symbol_lookup_code_on_missing_capability_export() {
let host = fixture_host();
let caps = fixture_caps(&host);
let mut session = session_over_handles(&caps);
let err = session
.call_capability::<(), LeanIo<u64>>("lean_rs_no_such_capability_export", ())
.expect_err("missing capability symbol must fail");
assert_eq!(err.code(), LeanDiagnosticCode::SymbolLookup, "got {err:?}");
}
#[test]
fn abi_conversion_code_on_missing_declaration() {
let host = fixture_host();
let caps = fixture_caps(&host);
let mut session = session_over_handles(&caps);
let err = session
.query_declaration("LeanRsFixture.Handles.definitely_does_not_exist")
.expect_err("unknown name must fail at the conversion boundary");
assert_eq!(err.code(), LeanDiagnosticCode::AbiConversion, "got {err:?}");
}
#[test]
fn lean_exception_code_from_lean_throw() {
let host = fixture_host();
let caps = fixture_caps(&host);
let mut session = caps
.session(&["LeanRsFixture.Effects"])
.expect("session imports cleanly");
let err = session
.call_capability::<(), LeanIo<u64>>("lean_rs_fixture_io_throw", ())
.expect_err("fixture export raises through IO");
assert_eq!(err.code(), LeanDiagnosticCode::LeanException, "got {err:?}");
let LeanError::LeanException(exc) = err else {
panic!("expected LeanException variant");
};
assert!(
exc.message().contains("deliberate IO exception"),
"got message {:?}",
exc.message()
);
}
#[test]
fn elaboration_code_on_parse_error() {
let host = fixture_host();
let caps = fixture_caps(&host);
let mut session = session_over_elaboration(&caps);
let opts = LeanElabOptions::new();
let outcome = session
.elaborate("(1 +", None, &opts)
.expect("host stack reports no exception while elaborating a malformed term");
let failure = outcome.expect_err("malformed term must elaborate to a failure");
assert_eq!(failure.code(), LeanDiagnosticCode::Elaboration);
assert!(
!failure.diagnostics().is_empty(),
"Lean must have produced at least one diagnostic"
);
}
#[test]
fn unsupported_code_on_absent_meta_service() {
let host = fixture_host();
let caps = fixture_caps(&host);
let mut session = caps
.session(&["LeanRsFixture.Handles", "LeanRsHostShims.Meta"])
.expect("session imports cleanly");
let expr = session
.declaration_type("Nat.zero")
.expect("Nat.zero is available")
.expect("Nat.zero has a type");
let response = session
.run_meta(&infer_type(), expr, &lean_rs_host::meta::LeanMetaOptions::new())
.expect("run_meta dispatches cleanly");
if matches!(response.status(), MetaCallStatus::Ok) {
assert_eq!(response.code(), None, "Ok response projects to no code");
} else {
let code = response.code().expect("non-Ok response must project a code");
assert!(
matches!(code, LeanDiagnosticCode::Unsupported | LeanDiagnosticCode::Elaboration),
"unexpected code {code:?} on non-Ok response"
);
}
}
#[test]
fn capture_records_runtime_init_span() {
let capture = DiagnosticCapture::install();
let _runtime = LeanRuntime::init().expect("runtime init");
let events = capture.events();
assert!(
events.iter().any(|e| e.span.as_deref() == Some("lean_rs.runtime.init")),
"DiagnosticCapture must record the runtime.init span on its install thread; got {events:?}",
);
}
#[test]
fn capture_records_explicit_diagnostic_event() {
let capture = DiagnosticCapture::install();
tracing::error!(
target: "lean_rs",
code = LeanDiagnosticCode::Linking.as_str(),
"synthetic linker failure",
);
let events = capture.events();
assert!(
events
.iter()
.any(|e| e.code == Some(LeanDiagnosticCode::Linking) && e.message == "synthetic linker failure"),
"DiagnosticCapture must project a `code` field back to LeanDiagnosticCode; got {events:?}",
);
}
#[test]
fn all_codes_have_distinct_stable_ids() {
let codes = [
LeanDiagnosticCode::RuntimeInit,
LeanDiagnosticCode::Linking,
LeanDiagnosticCode::ModuleInit,
LeanDiagnosticCode::SymbolLookup,
LeanDiagnosticCode::AbiConversion,
LeanDiagnosticCode::LeanException,
LeanDiagnosticCode::Elaboration,
LeanDiagnosticCode::Unsupported,
LeanDiagnosticCode::Internal,
];
let mut seen = std::collections::HashSet::new();
for code in codes {
let id = code.as_str();
assert!(
id.starts_with("lean_rs."),
"code {code:?} id {id} does not have the 'lean_rs.' prefix"
);
assert!(seen.insert(id), "duplicate id {id} on {code:?}");
assert!(
!code.description().is_empty(),
"code {code:?} must have a non-empty description"
);
}
}