diff --git a/crates/mun_libloader/Cargo.toml b/crates/mun_libloader/Cargo.toml index 09b127b91..dff49a64a 100644 --- a/crates/mun_libloader/Cargo.toml +++ b/crates/mun_libloader/Cargo.toml @@ -17,3 +17,4 @@ mun_abi = { version = "0.4.0", path = "../mun_abi" } anyhow = { version = "1.0", default-features = false, features = ["std"] } libloading = { version = "0.7", default-features = false } tempfile = { version = "3", default-features = false } +thiserror = { version = "1.0.38", default-features = false } diff --git a/crates/mun_libloader/src/lib.rs b/crates/mun_libloader/src/lib.rs index 51b50e68e..6907db5cb 100644 --- a/crates/mun_libloader/src/lib.rs +++ b/crates/mun_libloader/src/lib.rs @@ -5,6 +5,19 @@ pub use temp_library::TempLibrary; mod temp_library; +/// An error that occurs upon construction of a [`MunLibrary`]. +#[derive(Debug, thiserror::Error)] +pub enum InitError { + #[error(transparent)] + FailedToCreateTempLibrary(#[from] temp_library::InitError), + #[error("Missing symbol for retrieving ABI version: {0}")] + MissingGetAbiVersionFn(libloading::Error), + #[error("Missing symbol for retrieving ABI information: {0}")] + MissingGetInfoFn(libloading::Error), + #[error("Missing symbol for setting allocator handle: {0}")] + MissingSetAllocatorHandleFn(libloading::Error), +} + pub struct MunLibrary(TempLibrary); impl MunLibrary { @@ -22,7 +35,7 @@ impl MunLibrary { /// executed when the library is unloaded. /// /// See [`libloading::Library::new`] for more information. - pub unsafe fn new(library_path: &Path) -> Result { + pub unsafe fn new(library_path: &Path) -> Result { // Although loading a library is technically unsafe, we assume here that this is not the // case for munlibs. let library = TempLibrary::new(library_path)?; @@ -30,15 +43,21 @@ impl MunLibrary { // Verify that the `*.munlib` contains all required functions. Note that this is an unsafe // operation because the loaded symbols don't actually contain type information. Casting // is therefore unsafe. - let _get_abi_version_fn: libloading::Symbol<'_, extern "C" fn() -> u32> = - library.library().get(abi::GET_VERSION_FN_NAME.as_bytes())?; + let _get_abi_version_fn: libloading::Symbol<'_, extern "C" fn() -> u32> = library + .library() + .get(abi::GET_VERSION_FN_NAME.as_bytes()) + .map_err(InitError::MissingGetAbiVersionFn)?; let _get_info_fn: libloading::Symbol<'_, extern "C" fn() -> abi::AssemblyInfo<'static>> = - library.library().get(abi::GET_INFO_FN_NAME.as_bytes())?; + library + .library() + .get(abi::GET_INFO_FN_NAME.as_bytes()) + .map_err(InitError::MissingGetInfoFn)?; let _set_allocator_handle_fn: libloading::Symbol<'_, extern "C" fn(*mut c_void)> = library .library() - .get(abi::SET_ALLOCATOR_HANDLE_FN_NAME.as_bytes())?; + .get(abi::SET_ALLOCATOR_HANDLE_FN_NAME.as_bytes()) + .map_err(InitError::MissingSetAllocatorHandleFn)?; Ok(MunLibrary(library)) } diff --git a/crates/mun_libloader/src/temp_library.rs b/crates/mun_libloader/src/temp_library.rs index 419cac0a0..009af9229 100644 --- a/crates/mun_libloader/src/temp_library.rs +++ b/crates/mun_libloader/src/temp_library.rs @@ -1,9 +1,18 @@ -use std::fs; -use std::path::Path; +use std::{fs, io, path::Path}; -use anyhow::Error; use libloading::Library; +/// An error that occurs upon construction of a [`TempLibrary`]. +#[derive(Debug, thiserror::Error)] +pub enum InitError { + #[error("Failed to create a named temp file: {0}.")] + CreateTempFile(io::Error), + #[error("Failed to copy shared library: {0}.")] + CopyLibrary(io::Error), + #[error("Failed to load temp shared library: {0}")] + LoadTempLibrary(#[from] libloading::Error), +} + /// A structure that holds a `Library` instance but creates a unique file per load. This enables /// writing to the original library and ensures that each shared object on Linux is loaded /// separately. @@ -36,9 +45,11 @@ impl TempLibrary { /// executed when the library is unloaded. /// /// See [`libloading::Library::new`] for more information. - pub unsafe fn new(path: &Path) -> Result { - let tmp_path = tempfile::NamedTempFile::new()?.into_temp_path(); - fs::copy(path, &tmp_path)?; + pub unsafe fn new(path: &Path) -> Result { + let tmp_path = tempfile::NamedTempFile::new() + .map_err(InitError::CreateTempFile)? + .into_temp_path(); + fs::copy(path, &tmp_path).map_err(InitError::CopyLibrary)?; let library = Library::new(&tmp_path)?; Ok(TempLibrary { _tmp_path: tmp_path, diff --git a/crates/mun_memory/src/lib.rs b/crates/mun_memory/src/lib.rs index 1515c2ed0..70a144cf3 100644 --- a/crates/mun_memory/src/lib.rs +++ b/crates/mun_memory/src/lib.rs @@ -16,7 +16,6 @@ pub mod mapping; mod r#type; pub mod type_table; use mun_abi as abi; -use thiserror::Error; pub mod prelude { pub use crate::diff::{compute_struct_diff, FieldDiff, FieldEditKind, StructDiff}; @@ -25,7 +24,7 @@ pub mod prelude { } /// An error that can occur when trying to convert from an abi type to an internal type. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum TryFromAbiError<'a> { #[error("unknown TypeId '{0}'")] UnknownTypeId(abi::TypeId<'a>), diff --git a/crates/mun_memory/src/type/mod.rs b/crates/mun_memory/src/type/mod.rs index 4bc716f7d..977a33b7b 100644 --- a/crates/mun_memory/src/type/mod.rs +++ b/crates/mun_memory/src/type/mod.rs @@ -946,7 +946,7 @@ impl Type { } /// Tries to convert multiple [`abi::TypeDefinition`] to internal type representations. If - /// the conversion succeeds an updated [`TypeTable`] is returned + /// the conversion succeeds an updated [`TypeTable`] is returned. pub fn try_from_abi<'abi>( type_info: impl IntoIterator>, type_table: TypeTable, diff --git a/crates/mun_runtime/Cargo.toml b/crates/mun_runtime/Cargo.toml index 62e21e128..cfded53b0 100644 --- a/crates/mun_runtime/Cargo.toml +++ b/crates/mun_runtime/Cargo.toml @@ -18,7 +18,6 @@ mun_libloader = { version = "0.4.0", path = "../mun_libloader" } mun_capi_utils = { version = "0.4.0", path = "../mun_capi_utils"} mun_memory = { version = "0.4.0", path = "../mun_memory" } mun_project = { version = "0.4.0", path = "../mun_project" } -anyhow = { version = "1.0", default-features = false } itertools = { version = "0.10.3", default-features = false, features = ["use_alloc"] } log = { version = "0.4", default-features = false } notify = "5.0.0" @@ -26,6 +25,7 @@ once_cell = { version = "1.4.0", default-features = false } parking_lot = { version = "0.12.0", default-features = false } rustc-hash = { version = "1.1", default-features = false } seq-macro = { version = "0.3.0", default-features = false } +thiserror = { version = "1.0.38", default-features = false } [dev-dependencies] mun_compiler = { path="../mun_compiler" } diff --git a/crates/mun_runtime/src/assembly.rs b/crates/mun_runtime/src/assembly.rs index 039748242..e8ce120e8 100644 --- a/crates/mun_runtime/src/assembly.rs +++ b/crates/mun_runtime/src/assembly.rs @@ -1,11 +1,11 @@ use std::{ collections::{HashMap, VecDeque}, ffi::c_void, + io, path::{Path, PathBuf}, sync::Arc, }; -use anyhow::{anyhow, Context}; use itertools::Itertools; use log::error; @@ -19,6 +19,73 @@ use mun_memory::{ use crate::{garbage_collector::GarbageCollector, DispatchTable}; +/// An error that occurs upon loading of a Mun library. +#[derive(Debug, thiserror::Error)] +pub enum LoadError { + #[error("An assembly with the same name already exists")] + AlreadyExists, + #[error(transparent)] + FailedToLoadSharedLibrary(#[from] mun_libloader::InitError), + #[error("ABI version mismatch. munlib is `{actual}` but runtime is `{expected}`")] + MismatchedAbiVersions { expected: u32, actual: u32 }, + #[error(transparent)] + Other(#[from] io::Error), +} + +/// An error that occurs upon linking of a Mun assembly. +#[derive(Debug, thiserror::Error)] +pub enum LinkError { + /// Failed to load assembly + #[error(transparent)] + LoadAssembly(#[from] LoadError), + /// Failed to load type + #[error("Failed to load type with id `{0}`")] + LoadType(String), + /// Failed to link function + #[error(transparent)] + Function(#[from] LinkFunctionsError), + /// Failed to link assembly's types + #[error("Failed to link types: {0:?}")] + MissingTypes(Vec), +} + +/// An error that occurs upon linking of a Mun function prototype. +#[derive(Debug, thiserror::Error)] +pub enum LinkFunctionsError { + /// Failed to resolve function argument + #[error("Could not resolve function `{fn_name}`'s argument type #{idx}: {type_id}")] + UnresolvedArgument { + /// Function name + fn_name: String, + /// Argument index + idx: usize, + /// Argument type ID + type_id: String, + }, + /// Failed to resolve function return type + #[error("Could not resolve function `{fn_name}`'s result type: {type_id}")] + UnresolvedResult { + /// Function name + fn_name: String, + /// Result type ID + type_id: String, + }, + /// Failed to retrieve function pointer due to mismatched function signature + #[error("The function signature in the dispatch table does not match.\nExpected:\n\tfn {expected}\n\nFound:\n\tfn {found}")] + MismatchedSignature { + /// Expected function signature + expected: String, + /// Function signature found in dispatch table + found: String, + }, + /// Failed to load functions due to missing dependencies. + #[error("Missing dependencies for functions: {functions:?}")] + MissingDependencies { + /// Function names for which dependencies were missing + functions: Vec, + }, +} + /// An assembly is a hot reloadable compilation unit, consisting of one or more Mun modules. pub struct Assembly { library_path: PathBuf, @@ -43,19 +110,15 @@ impl Assembly { /// executed when the library is unloaded. /// /// See [`libloading::Library::new`] for more information. - pub unsafe fn load( - library_path: &Path, - gc: Arc, - ) -> Result { + pub unsafe fn load(library_path: &Path, gc: Arc) -> Result { let mut library = MunLibrary::new(library_path)?; let version = library.get_abi_version(); if abi::ABI_VERSION != version { - return Err(anyhow::anyhow!( - "ABI version mismatch. munlib is `{}` but runtime is `{}`", - version, - abi::ABI_VERSION - )); + return Err(LoadError::MismatchedAbiVersions { + expected: abi::ABI_VERSION, + actual: version, + }); } let allocator_ptr = Arc::into_raw(gc.clone()) as *mut std::ffi::c_void; @@ -71,28 +134,30 @@ impl Assembly { Ok(assembly) } + /// On failure, returns debug names of all missing types. fn link_all_types<'abi>( type_table: &TypeTable, to_link: impl Iterator, &'abi mut *const c_void, &'abi str)>, - ) -> anyhow::Result<()> { + ) -> Result<(), Vec> { // Try to link all LUT entries - let mut failed_to_link = false; - for (type_id, type_info_ptr, _debug_name) in to_link { - // Ensure that the function is in the runtime dispatch table - if let Some(ty) = type_table.find_type_info_by_id(type_id) { - *type_info_ptr = Type::into_raw(ty); - } else { - failed_to_link = true; - } - } + let failed_to_link = to_link + .filter_map(|(type_id, type_info_ptr, debug_name)| { + // Ensure that the function is in the runtime dispatch table + if let Some(ty) = type_table.find_type_info_by_id(type_id) { + *type_info_ptr = Type::into_raw(ty); + None + } else { + Some(debug_name) + } + }) + .map(ToString::to_string) + .collect::>(); - if failed_to_link { - return Err(anyhow!( - "Failed to link types due to missing type dependencies." - )); + if failed_to_link.is_empty() { + Ok(()) + } else { + Err(failed_to_link) } - - Ok(()) } /// Private implementation of runtime linking @@ -100,7 +165,7 @@ impl Assembly { dispatch_table: &DispatchTable, type_table: &TypeTable, to_link: impl Iterator)>, - ) -> anyhow::Result<()> { + ) -> Result<(), LinkFunctionsError> { let mut to_link: Vec<_> = to_link.collect(); let mut retry = true; @@ -119,30 +184,20 @@ impl Assembly { .map(|(idx, fn_arg_type_id)| { type_table .find_type_info_by_id(fn_arg_type_id) - .ok_or_else(|| { - anyhow!( - "could not resolve type of argument #{}: {}", - idx + 1, - fn_arg_type_id - ) + .ok_or_else(|| LinkFunctionsError::UnresolvedArgument { + fn_name: fn_prototype.name().to_string(), + idx: idx + 1, + type_id: fn_arg_type_id.to_string(), }) }) - .collect::, _>>() - .with_context(|| { - format!("failed to link function '{}'", fn_prototype.name()) - })?; + .collect::, _>>()?; // Get the return type info let fn_proto_ret_type_info = type_table .find_type_info_by_id(&fn_prototype.signature.return_type) - .ok_or_else(|| { - anyhow!( - "could not resolve type of return type: {}", - &fn_prototype.signature.return_type - ) - }) - .with_context(|| { - format!("failed to link function '{}'", fn_prototype.name()) + .ok_or_else(|| LinkFunctionsError::UnresolvedResult { + fn_name: fn_prototype.name().to_string(), + type_id: fn_prototype.signature.return_type.to_string(), })?; // Ensure that the function is in the runtime dispatch table @@ -163,10 +218,17 @@ impl Assembly { .join(", "); let fn_name = fn_prototype.name(); - return Err(anyhow!("a function with the same name does exist, but the signatures do not match.\nExpected:\n\tfn {fn_name}({expected}) -> {}\n\nFunction that exists:\n\tfn {fn_name}({found}) -> {}", - &fn_proto_ret_type_info.name(), - &existing_fn_def.prototype.signature.return_type.name())) - .with_context(|| format!("failed to link function '{}'", fn_prototype.name())); + + return Err(LinkFunctionsError::MismatchedSignature { + expected: format!( + "{fn_name}({expected}) -> {}", + fn_proto_ret_type_info.name() + ), + found: format!( + "{fn_name}({found}) -> {}", + existing_fn_def.prototype.signature.return_type.name() + ), + }); } *dispatch_ptr = existing_fn_def.fn_ptr; @@ -180,23 +242,16 @@ impl Assembly { to_link = failed_to_link; } - if !to_link.is_empty() { - let mut missing_functions = vec![]; - for (_, fn_prototype) in to_link { - error!( - "Failed to link: function `{}` is missing.", - fn_prototype.name() - ); - missing_functions.push(format!("- {}", fn_prototype.name())); - } - - return Err(anyhow!( - "Failed to link due to missing dependencies.\n{}", - missing_functions.join("\n") - )); + if to_link.is_empty() { + Ok(()) + } else { + Err(LinkFunctionsError::MissingDependencies { + functions: to_link + .into_iter() + .map(|(_, fn_prototype)| fn_prototype.name().to_string()) + .collect(), + }) } - - Ok(()) } /// Tries to link the `assemblies`, resulting in a new [`DispatchTable`] on success. This leaves @@ -205,7 +260,7 @@ impl Assembly { assemblies: impl Iterator, dispatch_table: &DispatchTable, type_table: &TypeTable, - ) -> anyhow::Result<(DispatchTable, TypeTable)> { + ) -> Result<(DispatchTable, TypeTable), LinkError> { let mut assemblies: Vec<&'a mut _> = assemblies.collect(); // Load all types, this creates a new type table that contains the types loaded @@ -215,7 +270,7 @@ impl Assembly { .flat_map(|asm| asm.info().symbols.types().iter()), type_table.clone(), ) - .map_err(|e| anyhow::anyhow!("failed to link types from assembly: {}", e))?; + .map_err(|e| LinkError::LoadType(e.to_string()))?; let types_to_link = assemblies .iter_mut() @@ -224,7 +279,7 @@ impl Assembly { // by the compiler. .filter(|(_, ptr, _)| ptr.is_null()); - Assembly::link_all_types(&type_table, types_to_link)?; + Assembly::link_all_types(&type_table, types_to_link).map_err(LinkError::MissingTypes)?; // Clone the dispatch table, such that we can roll back if linking fails let mut dispatch_table = dispatch_table.clone(); @@ -256,7 +311,7 @@ impl Assembly { linked_assemblies: &mut HashMap, dispatch_table: &DispatchTable, type_table: &TypeTable, - ) -> anyhow::Result<(DispatchTable, TypeTable)> { + ) -> Result<(DispatchTable, TypeTable), LinkError> { let mut dependencies: HashMap> = unlinked_assemblies .values() .map(|assembly| { @@ -312,7 +367,7 @@ impl Assembly { // Collect all types that need to be loaded let (updated_type_table, new_types) = Type::try_from_abi(new_assembly.info.symbols.types(), type_table) - .map_err(|e| anyhow::anyhow!("failed to load assembly types: {}", e))?; + .map_err(|e| LinkError::LoadType(e.to_string()))?; type_table = updated_type_table; // Load all types, retrying types that depend on other unloaded types within the module @@ -324,7 +379,8 @@ impl Assembly { // by the compiler. .filter(|(_, ptr, _)| ptr.is_null()); - Assembly::link_all_types(&type_table, types_to_link)?; + Assembly::link_all_types(&type_table, types_to_link) + .map_err(LinkError::MissingTypes)?; // Memory map allocated object if let Some((old_assembly, old_types)) = old_types { diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 183220b80..b71f22d93 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -14,7 +14,7 @@ mod function_info; mod marshal; mod reflection; -use anyhow::Result; +use assembly::LoadError; use dispatch_table::DispatchTable; use garbage_collector::GarbageCollector; use log::{debug, error, info}; @@ -26,11 +26,10 @@ use mun_memory::{ use mun_project::LOCKFILE_NAME; use notify::{event::ModifyKind, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use std::{ - collections::{HashMap, VecDeque}, + collections::{BTreeMap, HashMap, VecDeque}, ffi, ffi::c_void, fmt::{Debug, Display, Formatter}, - io, mem::ManuallyDrop, path::{Path, PathBuf}, ptr::NonNull, @@ -42,8 +41,8 @@ use std::{ pub use crate::{ adt::{RootedStruct, StructRef}, - array::{ArrayRef, RootedArray}, - assembly::Assembly, + array::{ArrayRef, RawArray, RootedArray}, + assembly::{Assembly, LinkError, LinkFunctionsError}, function_info::{ FunctionDefinition, FunctionPrototype, FunctionSignature, IntoFunctionDefinition, }, @@ -51,7 +50,6 @@ pub use crate::{ reflection::{ArgumentReflection, ReturnTypeReflection}, }; // Re-export some useful types so crates dont have to depend on mun_memory as well. -use crate::array::RawArray; pub use mun_memory::{Field, FieldData, HasStaticType, PointerType, StructType, Type}; /// Options for the construction of a [`Runtime`]. @@ -162,11 +160,22 @@ impl RuntimeBuilder { /// executed when the library is unloaded. /// /// See [`Assembly::load`] for more information. - pub unsafe fn finish(self) -> anyhow::Result { + pub unsafe fn finish(self) -> Result { Runtime::new(self.options) } } +/// An error that occurs upon construction of a [`Runtime`]. +#[derive(Debug, thiserror::Error)] +pub enum InitError { + /// Failed to link assembly + #[error(transparent)] + LinkAssembly(#[from] LinkError), + /// Failed to construct watcher + #[error(transparent)] + Watcher(#[from] notify::Error), +} + /// A runtime for the Mun language. /// /// # Logging @@ -179,7 +188,7 @@ impl RuntimeBuilder { pub struct Runtime { assemblies: HashMap, /// Assemblies that have changed and thus need to be relinked. Maps the old to the (potentially) new path. - assemblies_to_relink: VecDeque<(PathBuf, PathBuf)>, + assemblies_to_relink: BTreeMap, dispatch_table: DispatchTable, type_table: TypeTable, watcher: RecommendedWatcher, @@ -210,7 +219,7 @@ impl Runtime { /// executed when the library is unloaded. /// /// See [`Assembly::load`] for more information. - pub unsafe fn new(mut options: RuntimeOptions) -> anyhow::Result { + pub unsafe fn new(mut options: RuntimeOptions) -> Result { let (tx, rx) = channel(); let mut dispatch_table = DispatchTable::default(); @@ -241,7 +250,7 @@ impl Runtime { })?; let mut runtime = Runtime { assemblies: HashMap::new(), - assemblies_to_relink: VecDeque::new(), + assemblies_to_relink: BTreeMap::new(), dispatch_table, type_table, watcher, @@ -268,14 +277,13 @@ impl Runtime { /// executed when the library is unloaded. /// /// See [`Assembly::load`] for more information. - unsafe fn add_assembly(&mut self, library_path: &Path) -> anyhow::Result<()> { - let library_path = library_path.canonicalize()?; + unsafe fn add_assembly(&mut self, library_path: &Path) -> Result<(), LinkError> { + let library_path = library_path + .canonicalize() + .map_err(|e| LinkError::LoadAssembly(LoadError::Other(e)))?; + if self.assemblies.contains_key(&library_path) { - return Err(io::Error::new( - io::ErrorKind::AlreadyExists, - "An assembly with the same name already exists.", - ) - .into()); + return Err(LoadError::AlreadyExists.into()); } let mut loaded = HashMap::new(); @@ -315,7 +323,8 @@ impl Runtime { for (library_path, assembly) in loaded.into_iter() { self.watcher - .watch(library_path.parent().unwrap(), RecursiveMode::NonRecursive)?; + .watch(library_path.parent().unwrap(), RecursiveMode::NonRecursive) + .expect("Path must exist as we just loaded the library"); self.assemblies.insert(library_path, assembly); } @@ -360,7 +369,7 @@ impl Runtime { unsafe fn relink_assemblies( runtime: &mut Runtime, - ) -> anyhow::Result<(DispatchTable, TypeTable)> { + ) -> Result<(DispatchTable, TypeTable), LinkError> { let mut loaded = HashMap::new(); let to_load = &mut runtime.assemblies_to_relink; @@ -374,7 +383,7 @@ impl Runtime { } // Load all assemblies and their dependencies - while let Some((old_path, new_path)) = to_load.pop_front() { + while let Some((old_path, new_path)) = to_load.pop_first() { // A dependency can be added by multiple dependants, so check that we didn't load it yet if loaded.contains_key(&old_path) { continue; @@ -398,7 +407,7 @@ impl Runtime { if !loaded.contains_key(&library_path) && !runtime.assemblies.contains_key(&library_path) { - to_load.push_back((old_path.clone(), library_path)); + to_load.insert(old_path.clone(), library_path); } } } @@ -433,7 +442,7 @@ impl Runtime { EventKind::Modify(ModifyKind::Name(_)) => { let tracker = event.attrs.tracker().expect("Invalid RENAME event."); if let Some(old_path) = self.renamed_files.remove(&tracker) { - self.assemblies_to_relink.push_back((old_path, path)); + self.assemblies_to_relink.insert(old_path, path); // on_file_changed(self, &old_path, &path); } else { self.renamed_files.insert(tracker, path); @@ -441,7 +450,7 @@ impl Runtime { } EventKind::Modify(_) => { // TODO: don't overwrite existing - self.assemblies_to_relink.push_back((path.clone(), path)); + self.assemblies_to_relink.insert(path.clone(), path); } _ => (), } @@ -463,7 +472,7 @@ impl Runtime { return true; } - Err(e) => error!("Failed to relink assemblies, due to {}.", e), + Err(e) => error!("Failed to relink assemblies: {e}"), } } } diff --git a/crates/mun_runtime/tests/runtime.rs b/crates/mun_runtime/tests/runtime.rs index fdaa72582..c005aa8b2 100644 --- a/crates/mun_runtime/tests/runtime.rs +++ b/crates/mun_runtime/tests/runtime.rs @@ -1,5 +1,5 @@ +use mun_runtime::LinkFunctionsError; use mun_test::CompileAndRunTestDriver; -use std::io; #[macro_use] mod util; @@ -155,23 +155,24 @@ fn from_fixture() { #[test] fn error_assembly_not_linkable() { + const EXPECTED_FN_NAME: &str = "dependency"; + let driver = CompileAndRunTestDriver::new( - r" - extern fn dependency() -> i32; + &format!( + r" + extern fn {EXPECTED_FN_NAME}() -> i32; - pub fn main() -> i32 { dependency() } - ", + pub fn main() -> i32 {{ {EXPECTED_FN_NAME}() }} + " + ), |builder| builder, ); assert_eq!( - format!("{}", driver.unwrap_err()), - format!( - "{}", - io::Error::new( - io::ErrorKind::NotFound, - "Failed to link due to missing dependencies.\n- dependency".to_string(), - ) - ) + driver.unwrap_err().to_string(), + LinkFunctionsError::MissingDependencies { + functions: vec![EXPECTED_FN_NAME.to_string()] + } + .to_string() ); } diff --git a/crates/mun_test/src/driver.rs b/crates/mun_test/src/driver.rs index 8c6691d58..9e08073fd 100644 --- a/crates/mun_test/src/driver.rs +++ b/crates/mun_test/src/driver.rs @@ -1,5 +1,5 @@ use mun_compiler::{Config, DisplayColor, Driver, PathOrInline, RelativePathBuf}; -use mun_runtime::{Runtime, RuntimeBuilder}; +use mun_runtime::{InitError, Runtime, RuntimeBuilder}; use std::{ path::{Path, PathBuf}, thread::sleep, @@ -144,7 +144,7 @@ impl CompileAndRunTestDriver { pub fn from_fixture( fixture: &str, config_fn: impl FnOnce(RuntimeBuilder) -> RuntimeBuilder, - ) -> Result { + ) -> Result { let driver = CompileTestDriver::from_fixture(fixture); let builder = Runtime::builder(driver.lib_path()); @@ -160,7 +160,7 @@ impl CompileAndRunTestDriver { pub fn new( text: &str, config_fn: impl FnOnce(RuntimeBuilder) -> RuntimeBuilder, - ) -> Result { + ) -> Result { let driver = CompileTestDriver::from_file(text); let builder = Runtime::builder(driver.lib_path());