From 514929c3b8ee0d4f540be5eb4bc8c1a10e62095d Mon Sep 17 00:00:00 2001 From: Weicao-CatilGrass <1992414357@qq.com> Date: Tue, 9 Jun 2026 21:08:20 +0800 Subject: Add unit and integration tests for mingling_core --- mingling_core/src/program/config.rs | 142 +++++++++++++++++ mingling_core/src/program/error.rs | 222 +++++++++++++++++++++++++++ mingling_core/src/program/flag.rs | 53 +++++++ mingling_core/src/program/hook.rs | 208 +++++++++++++++++++++++++ mingling_core/src/program/setup.rs | 82 ++++++++++ mingling_core/src/program/single_instance.rs | 46 ++++++ mingling_core/src/program/string_vec.rs | 59 +++++++ 7 files changed, 812 insertions(+) (limited to 'mingling_core/src/program') diff --git a/mingling_core/src/program/config.rs b/mingling_core/src/program/config.rs index 42603a5..4e193f2 100644 --- a/mingling_core/src/program/config.rs +++ b/mingling_core/src/program/config.rs @@ -206,3 +206,145 @@ impl std::fmt::Display for GeneralRendererSetting { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn program_stdout_setting_default() { + let s = ProgramStdoutSetting::default(); + assert!(s.error_output); + assert!(s.render_output); + assert!(!s.silence_panic); + assert!(!s.verbose); + assert!(!s.quiet); + assert!(!s.debug); + assert!(s.color); + assert!(s.progress); + } + + #[test] + fn program_user_context_default() { + let ctx = ProgramUserContext::default(); + assert!(!ctx.help); + assert!(ctx.run_hook); + assert!(!ctx.confirm); + assert!(!ctx.dry_run); + assert!(!ctx.force); + assert!(!ctx.interactive); + assert!(!ctx.assume_yes); + } + + #[cfg(feature = "general_renderer")] + mod general_renderer_tests { + use super::*; + + #[test] + fn from_str_disable() { + let val: GeneralRendererSetting = "disable".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::Disable)); + } + + #[cfg(feature = "json_serde_fmt")] + #[test] + fn from_str_json() { + let val: GeneralRendererSetting = "json".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::Json)); + } + + #[cfg(feature = "json_serde_fmt")] + #[test] + fn from_str_json_pretty() { + let val: GeneralRendererSetting = "json-pretty".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::JsonPretty)); + } + + #[cfg(feature = "yaml_serde_fmt")] + #[test] + fn from_str_yaml() { + let val: GeneralRendererSetting = "yaml".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::Yaml)); + } + + #[cfg(feature = "toml_serde_fmt")] + #[test] + fn from_str_toml() { + let val: GeneralRendererSetting = "toml".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::Toml)); + } + + #[cfg(feature = "ron_serde_fmt")] + #[test] + fn from_str_ron() { + let val: GeneralRendererSetting = "ron".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::Ron)); + } + + #[cfg(feature = "ron_serde_fmt")] + #[test] + fn from_str_ron_pretty() { + let val: GeneralRendererSetting = "ron-pretty".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::RonPretty)); + } + + #[test] + fn from_str_invalid() { + let res: Result = "invalid".parse(); + assert!(res.is_err()); + } + + #[test] + fn from_str_kebab_case() { + let val: GeneralRendererSetting = "JsonPretty".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::JsonPretty)); + } + + #[test] + fn from_str_case_insensitive() { + let val: GeneralRendererSetting = "JSON".parse().unwrap(); + assert!(matches!(val, GeneralRendererSetting::Json)); + } + + #[test] + fn from_and_str() { + let val = >::from("json"); + assert!( + matches!(val, GeneralRendererSetting::Disable) + || matches!(val, GeneralRendererSetting::Json) + ); + + let val = >::from("invalid"); + assert!(matches!(val, GeneralRendererSetting::Disable)); + } + + #[test] + fn from_string() { + let val = >::from("json-pretty".to_string()); + assert!( + matches!(val, GeneralRendererSetting::Disable) + || matches!(val, GeneralRendererSetting::JsonPretty) + ); + } + + #[test] + fn display_disable() { + assert_eq!(GeneralRendererSetting::Disable.to_string(), "disable"); + } + + #[cfg(feature = "json_serde_fmt")] + #[test] + fn display_json() { + assert_eq!(GeneralRendererSetting::Json.to_string(), "json"); + } + + #[cfg(feature = "json_serde_fmt")] + #[test] + fn display_json_pretty() { + assert_eq!( + GeneralRendererSetting::JsonPretty.to_string(), + "json-pretty" + ); + } + } +} diff --git a/mingling_core/src/program/error.rs b/mingling_core/src/program/error.rs index 822e429..144b5ab 100644 --- a/mingling_core/src/program/error.rs +++ b/mingling_core/src/program/error.rs @@ -30,3 +30,225 @@ impl fmt::Debug for ProgramPanic { write!(f, "{:?}", self.payload) } } + +#[cfg(test)] +mod tests { + use crate::error::ChainProcessError; + use crate::error::ProgramPanic; + use crate::program::exec::error::ProgramExecuteError; + use crate::program::exec::error::ProgramInternalExecuteError; + + // ProgramPanic + + #[test] + fn test_program_panic_new_with_str() { + let panic = ProgramPanic::new(Box::new("hello world")); + assert_eq!(format!("{panic}"), "hello world"); + } + + #[test] + fn test_program_panic_new_with_string() { + let panic = ProgramPanic::new(Box::new("owned string".to_string())); + assert_eq!(format!("{panic}"), "owned string"); + } + + #[test] + fn test_program_panic_new_with_i32() { + let panic = ProgramPanic::new(Box::new(42)); + assert_eq!(format!("{panic}"), ""); + } + + #[test] + fn test_program_panic_debug() { + let panic = ProgramPanic::new(Box::new("debug me")); + let debug = format!("{panic:?}"); + assert_eq!(debug, "Any { .. }"); + } + + // ProgramExecuteError + + #[test] + fn test_program_execute_error_display_dispatcher_not_found() { + let err = ProgramExecuteError::DispatcherNotFound; + assert_eq!(format!("{err}"), "No Dispatcher Found"); + } + + #[test] + fn test_program_execute_error_display_renderer_not_found() { + let err = ProgramExecuteError::RendererNotFound("markdown".into()); + assert_eq!(format!("{err}"), "No Renderer (`markdown`) Found"); + } + + #[test] + fn test_program_execute_error_display_panic() { + let p = ProgramPanic::new(Box::new("oops")); + let err = ProgramExecuteError::Panic(p); + let display = format!("{err}"); + assert!(display.starts_with("Panic: ")); + } + + #[test] + fn test_program_execute_error_display_other() { + let err = ProgramExecuteError::Other("something went wrong".into()); + assert_eq!(format!("{err}"), "Other error: something went wrong"); + } + + #[test] + fn test_program_execute_error_error_trait_no_source() { + use std::error::Error; + let err = ProgramExecuteError::Other("msg".into()); + assert!(err.source().is_none()); + + let err = ProgramExecuteError::DispatcherNotFound; + assert!(err.source().is_none()); + + let err = ProgramExecuteError::RendererNotFound("json".into()); + assert!(err.source().is_none()); + + let panic = ProgramPanic::new(Box::new("panic")); + let err = ProgramExecuteError::Panic(panic); + assert!(err.source().is_none()); + } + + #[test] + fn test_from_program_panic_into_program_execute_error() { + let panic = ProgramPanic::new(Box::new("from panic")); + let err: ProgramExecuteError = panic.into(); + assert!(matches!(err, ProgramExecuteError::Panic(_))); + } + + // ProgramInternalExecuteError + + #[test] + fn test_program_internal_execute_error_display_dispatcher_not_found() { + let err = ProgramInternalExecuteError::DispatcherNotFound; + assert_eq!(format!("{err}"), "No Dispatcher Found"); + } + + #[test] + fn test_program_internal_execute_error_display_renderer_not_found() { + let err = ProgramInternalExecuteError::RendererNotFound("html".into()); + assert_eq!(format!("{err}"), "No Renderer (`html`) Found"); + } + + #[test] + fn test_program_internal_execute_error_display_other() { + let err = ProgramInternalExecuteError::Other("internal issue".into()); + assert_eq!(format!("{err}"), "Other error: internal issue"); + } + + #[test] + fn test_program_internal_execute_error_display_io() { + let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing"); + let err = ProgramInternalExecuteError::IO(io_err); + let display = format!("{err}"); + assert!(display.contains("IO error")); + assert!(display.contains("file missing")); + } + + #[test] + fn test_program_internal_execute_error_display_repl_panic() { + let p = ProgramPanic::new(Box::new("repl crash")); + let err = ProgramInternalExecuteError::REPLPanic(p); + let display = format!("{err}"); + assert!(display.starts_with("A single REPL execution failed: ")); + assert!(display.contains("repl crash")); + } + + #[test] + fn test_program_internal_execute_error_error_trait_source() { + use std::error::Error; + + let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied"); + let err = ProgramInternalExecuteError::IO(io_err); + assert!(err.source().is_some()); + + let err = ProgramInternalExecuteError::DispatcherNotFound; + assert!(err.source().is_none()); + + let err = ProgramInternalExecuteError::RendererNotFound("txt".into()); + assert!(err.source().is_none()); + + let err = ProgramInternalExecuteError::Other("msg".into()); + assert!(err.source().is_none()); + + let p = ProgramPanic::new(Box::new("msg")); + let err = ProgramInternalExecuteError::REPLPanic(p); + assert!(err.source().is_none()); + } + + #[test] + fn test_from_io_error_into_program_internal_execute_error() { + let io_err = std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"); + let err: ProgramInternalExecuteError = io_err.into(); + assert!(matches!(err, ProgramInternalExecuteError::IO(_))); + } + + // From for ProgramExecuteError + + #[test] + fn test_from_internal_to_execute_dispatcher_not_found() { + let internal = ProgramInternalExecuteError::DispatcherNotFound; + let err: ProgramExecuteError = internal.into(); + assert!(matches!(err, ProgramExecuteError::DispatcherNotFound)); + } + + #[test] + fn test_from_internal_to_execute_renderer_not_found() { + let internal = ProgramInternalExecuteError::RendererNotFound("yaml".into()); + let err: ProgramExecuteError = internal.into(); + assert!(matches!(err, ProgramExecuteError::RendererNotFound(_))); + assert_eq!(format!("{err}"), "No Renderer (`yaml`) Found"); + } + + #[test] + fn test_from_internal_to_execute_other() { + let internal = ProgramInternalExecuteError::Other("custom".into()); + let err: ProgramExecuteError = internal.into(); + assert!(matches!(err, ProgramExecuteError::Other(_))); + assert_eq!(format!("{err}"), "Other error: custom"); + } + + #[test] + fn test_from_internal_to_execute_io_becomes_other() { + let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused"); + let internal = ProgramInternalExecuteError::IO(io_err); + let err: ProgramExecuteError = internal.into(); + assert!(matches!(err, ProgramExecuteError::Other(_))); + let display = format!("{err}"); + assert!(display.starts_with("Other error: ")); + assert!(display.contains("refused")); + } + + #[test] + fn test_from_internal_to_execute_repl_panic_becomes_other() { + let p = ProgramPanic::new(Box::new("panic in repl")); + let internal = ProgramInternalExecuteError::REPLPanic(p); + let err: ProgramExecuteError = internal.into(); + assert!(matches!(err, ProgramExecuteError::Other(_))); + let display = format!("{err}"); + assert!(display.contains("A single REPL execution failed")); + assert!(display.contains("panic in repl")); + } + + // From for ProgramInternalExecuteError + + #[test] + fn test_from_chain_process_error_other_into_internal() { + let chain_err = ChainProcessError::Other("chain other".into()); + let err: ProgramInternalExecuteError = chain_err.into(); + assert!(matches!(err, ProgramInternalExecuteError::Other(_))); + assert_eq!(format!("{err}"), "Other error: chain other"); + } + + #[test] + fn test_from_chain_process_error_io_into_internal() { + let io_err = std::io::Error::new(std::io::ErrorKind::BrokenPipe, "broken"); + let chain_err = ChainProcessError::IO(io_err); + let err: ProgramInternalExecuteError = chain_err.into(); + assert!(matches!(err, ProgramInternalExecuteError::IO(_))); + let display = format!("{err}"); + assert!(display.contains("IO error")); + assert!(display.contains("broken")); + } +} diff --git a/mingling_core/src/program/flag.rs b/mingling_core/src/program/flag.rs index bc1c922..0865414 100644 --- a/mingling_core/src/program/flag.rs +++ b/mingling_core/src/program/flag.rs @@ -161,6 +161,8 @@ macro_rules! special_arguments { #[cfg(test)] mod tests { + use crate::Flag; + #[test] fn test_special_flag() { // Test flag found and removed @@ -466,6 +468,57 @@ mod tests { assert_eq!(result, vec!["a", "b"]); assert_eq!(args, vec!["--next", "1"]); } + + #[test] + fn test_flag_from_empty_tuple() { + let flag = Flag::from(()); + assert_eq!(flag.as_ref(), &[] as &[&str]); + } + + #[test] + fn test_flag_from_static_str() { + let flag = Flag::from("-h"); + assert_eq!(flag.as_ref(), &["-h"]); + } + + #[test] + fn test_flag_from_slice() { + let flag = Flag::from(&["-h", "--help"][..]); + assert_eq!(flag.as_ref(), &["-h", "--help"]); + } + + #[test] + fn test_flag_from_array() { + let flag = Flag::from(["-v", "--verbose"]); + assert_eq!(flag.as_ref(), &["-v", "--verbose"]); + } + + #[test] + fn test_flag_from_ref_array() { + let flag = Flag::from(&["-f", "--file"]); + assert_eq!(flag.as_ref(), &["-f", "--file"]); + } + + #[test] + fn test_flag_from_ref_flag() { + let original = Flag::from("-x"); + let cloned = Flag::from(&original); + assert_eq!(cloned.as_ref(), &["-x"]); + } + + #[test] + fn test_flag_as_ref() { + let flag = Flag::from("-h"); + let r: &[&str] = flag.as_ref(); + assert_eq!(r, &["-h"]); + } + + #[test] + fn test_flag_deref() { + let flag = Flag::from(["-a", "-b"]); + let collected: Vec<&&str> = flag.iter().collect(); + assert_eq!(collected, vec![&"-a", &"-b"]); + } } impl Program diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs index 929eac2..f6df2e2 100644 --- a/mingling_core/src/program/hook.rs +++ b/mingling_core/src/program/hook.rs @@ -543,3 +543,211 @@ where self } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Groupped; + use std::sync::atomic::{AtomicBool, Ordering}; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "general_renderer", derive(serde::Serialize))] + enum MockHookEnum { + A, + B, + } + + impl std::fmt::Display for MockHookEnum { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } + } + + impl Groupped for MockHookEnum { + fn member_id() -> MockHookEnum { + MockHookEnum::A + } + } + + impl ProgramCollect for MockHookEnum { + type Enum = MockHookEnum; + type ErrorDispatcherNotFound = MockHookEnum; + type ErrorRendererNotFound = MockHookEnum; + type ResultEmpty = MockHookEnum; + + fn build_renderer_not_found(_member_id: MockHookEnum) -> AnyOutput { + unreachable!() + } + + fn build_dispatcher_not_found(_args: Vec) -> AnyOutput { + unreachable!() + } + + fn build_empty_result() -> AnyOutput { + unreachable!() + } + + fn render(_any: AnyOutput, _r: &mut RenderResult) { + unreachable!() + } + + fn render_help(_any: AnyOutput, _r: &mut RenderResult) { + unreachable!() + } + + fn do_chain(_any: AnyOutput) -> crate::ChainProcess { + unreachable!() + } + + fn has_renderer(_any: &AnyOutput) -> bool { + unreachable!() + } + + fn has_chain(_any: &AnyOutput) -> bool { + unreachable!() + } + + #[cfg(feature = "comp")] + fn do_comp(_any: &AnyOutput, _ctx: &crate::ShellContext) -> crate::Suggest { + unreachable!() + } + + #[cfg(feature = "general_renderer")] + fn general_render( + _any: AnyOutput, + _setting: &crate::GeneralRendererSetting, + ) -> Result { + unreachable!() + } + } + + #[test] + fn test_hook_empty() { + let hook = ProgramHook::::empty(); + assert!(hook.begin.is_none()); + assert!(hook.pre_dispatch.is_none()); + assert!(hook.post_dispatch.is_none()); + assert!(hook.pre_chain.is_none()); + assert!(hook.post_chain.is_none()); + assert!(hook.pre_render.is_none()); + assert!(hook.post_render.is_none()); + assert!(hook.finish.is_none()); + } + + #[test] + fn test_hook_on_begin() { + static CALLED: AtomicBool = AtomicBool::new(false); + let hook = ProgramHook::::empty().on_begin(|| { + CALLED.store(true, Ordering::SeqCst); + }); + assert!(hook.begin.is_some()); + (hook.begin.unwrap())(); + assert!(CALLED.load(Ordering::SeqCst)); + } + + #[test] + fn test_hook_on_pre_dispatch() { + static CALLED: AtomicBool = AtomicBool::new(false); + let hook = ProgramHook::::empty().on_pre_dispatch(|args| { + assert_eq!(args, &["a", "b"]); + CALLED.store(true, Ordering::SeqCst); + }); + assert!(hook.pre_dispatch.is_some()); + (hook.pre_dispatch.unwrap())(&["a".to_string(), "b".to_string()]); + assert!(CALLED.load(Ordering::SeqCst)); + } + + #[test] + fn test_hook_on_post_dispatch() { + static CALLED: AtomicBool = AtomicBool::new(false); + let hook = ProgramHook::::empty().on_post_dispatch(|entry| { + assert_eq!(*entry, MockHookEnum::A); + CALLED.store(true, Ordering::SeqCst); + }); + assert!(hook.post_dispatch.is_some()); + (hook.post_dispatch.unwrap())(&MockHookEnum::A); + assert!(CALLED.load(Ordering::SeqCst)); + } + + #[test] + fn test_hook_on_pre_chain() { + static CALLED: AtomicBool = AtomicBool::new(false); + let hook = ProgramHook::::empty().on_pre_chain( + |input: &MockHookEnum, _raw: &dyn Any| { + assert_eq!(*input, MockHookEnum::A); + CALLED.store(true, Ordering::SeqCst); + }, + ); + assert!(hook.pre_chain.is_some()); + (hook.pre_chain.unwrap())(&MockHookEnum::A, &42); + assert!(CALLED.load(Ordering::SeqCst)); + } + + #[test] + fn test_hook_on_post_chain() { + static CALLED: AtomicBool = AtomicBool::new(false); + let hook = ProgramHook::::empty().on_post_chain( + |_output: &AnyOutput| { + CALLED.store(true, Ordering::SeqCst); + }, + ); + assert!(hook.post_chain.is_some()); + let output = AnyOutput::new(MockHookEnum::A); + (hook.post_chain.unwrap())(&output); + assert!(CALLED.load(Ordering::SeqCst)); + } + + #[test] + fn test_hook_on_pre_render() { + static CALLED: AtomicBool = AtomicBool::new(false); + let hook = ProgramHook::::empty().on_pre_render( + |input: &MockHookEnum, _raw: &dyn Any| { + assert_eq!(*input, MockHookEnum::A); + CALLED.store(true, Ordering::SeqCst); + }, + ); + assert!(hook.pre_render.is_some()); + (hook.pre_render.unwrap())(&MockHookEnum::A, &42); + assert!(CALLED.load(Ordering::SeqCst)); + } + + #[test] + fn test_hook_on_post_render() { + static CALLED: AtomicBool = AtomicBool::new(false); + let hook = ProgramHook::::empty().on_post_render(|_result: &RenderResult| { + CALLED.store(true, Ordering::SeqCst); + }); + assert!(hook.post_render.is_some()); + let result = RenderResult::default(); + (hook.post_render.unwrap())(&result); + assert!(CALLED.load(Ordering::SeqCst)); + } + + #[test] + fn test_hook_on_finish() { + let hook = ProgramHook::::empty().on_finish(|| 42); + assert!(hook.finish.is_some()); + assert_eq!((hook.finish.unwrap())(), 42); + } + + #[test] + fn test_hook_builder_chaining() { + let hook = ProgramHook::::empty() + .on_begin(|| {}) + .on_pre_dispatch(|_| {}) + .on_post_dispatch(|_| {}) + .on_pre_chain(|_, _| {}) + .on_post_chain(|_| {}) + .on_pre_render(|_, _| {}) + .on_post_render(|_| {}) + .on_finish(|| 0); + assert!(hook.begin.is_some()); + assert!(hook.pre_dispatch.is_some()); + assert!(hook.post_dispatch.is_some()); + assert!(hook.pre_chain.is_some()); + assert!(hook.post_chain.is_some()); + assert!(hook.pre_render.is_some()); + assert!(hook.post_render.is_some()); + assert!(hook.finish.is_some()); + } +} diff --git a/mingling_core/src/program/setup.rs b/mingling_core/src/program/setup.rs index fa9d0eb..2bfced1 100644 --- a/mingling_core/src/program/setup.rs +++ b/mingling_core/src/program/setup.rs @@ -16,3 +16,85 @@ where S::setup(setup, self); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{AnyOutput, ChainProcess, Groupped, RenderResult}; + + /// Minimal mock collector that satisfies `C: ProgramCollect` + /// by setting `Enum = Self`. + #[derive(Debug, Clone, PartialEq)] + struct MockCollect; + + impl Groupped for MockCollect { + fn member_id() -> MockCollect { + MockCollect + } + } + + impl ProgramCollect for MockCollect { + type Enum = MockCollect; + type ErrorDispatcherNotFound = MockCollect; + type ErrorRendererNotFound = MockCollect; + type ResultEmpty = MockCollect; + + fn build_renderer_not_found(_member_id: MockCollect) -> AnyOutput { + unimplemented!() + } + fn build_dispatcher_not_found(_args: Vec) -> AnyOutput { + unimplemented!() + } + fn build_empty_result() -> AnyOutput { + unimplemented!() + } + fn render(_any: AnyOutput, _r: &mut RenderResult) { + unimplemented!() + } + fn render_help(_any: AnyOutput, _r: &mut RenderResult) { + unimplemented!() + } + fn do_chain(_any: AnyOutput) -> ChainProcess { + unimplemented!() + } + #[cfg(feature = "comp")] + fn do_comp(_any: &AnyOutput, _ctx: &crate::ShellContext) -> crate::Suggest { + unimplemented!() + } + fn has_renderer(_any: &AnyOutput) -> bool { + unimplemented!() + } + fn has_chain(_any: &AnyOutput) -> bool { + unimplemented!() + } + + #[cfg(feature = "general_renderer")] + fn general_render( + _any: AnyOutput, + _setting: &crate::GeneralRendererSetting, + ) -> Result { + unimplemented!() + } + } + + struct TestSetup { + called: std::rc::Rc>, + } + + impl ProgramSetup for TestSetup { + fn setup(self, _program: &mut Program) { + self.called.set(true); + } + } + + #[test] + fn test_with_setup_calls_setup() { + let called = std::rc::Rc::new(std::cell::Cell::new(false)); + let setup = TestSetup { + called: std::rc::Rc::clone(&called), + }; + let mut program: Program = Program::new_with_args(["test"]); + program.with_setup(setup); + assert!(called.get()); + } +} diff --git a/mingling_core/src/program/single_instance.rs b/mingling_core/src/program/single_instance.rs index d16bcf5..8b165bf 100644 --- a/mingling_core/src/program/single_instance.rs +++ b/mingling_core/src/program/single_instance.rs @@ -110,3 +110,49 @@ where { THIS_PROGRAM.get_raw()?.downcast_ref::>() } + +#[cfg(test)] +mod tests { + use super::ProgramCell; + + #[test] + fn test_program_cell_set_and_get_raw() { + let cell = ProgramCell::new(); + cell.set(Box::new(42_i32)); + let val = cell.get_raw(); + assert!(val.is_some()); + assert_eq!(*val.unwrap().downcast_ref::().unwrap(), 42); + } + + #[test] + fn test_program_cell_get_raw_uninitialized() { + let cell = ProgramCell::new(); + assert!(cell.get_raw().is_none()); + } + + #[test] + #[should_panic(expected = "ProgramCell already initialized")] + fn test_program_cell_set_twice_panics() { + let cell = ProgramCell::new(); + cell.set(Box::new(1_i32)); + cell.set(Box::new(2_i32)); + } + + #[test] + fn test_program_cell_take() { + let cell = ProgramCell::new(); + cell.set(Box::new(99_i32)); + + // SAFETY: test-local cell, no outstanding references. + let taken = unsafe { cell.take() }; + assert!(taken.is_some()); + assert_eq!(*taken.unwrap().downcast_ref::().unwrap(), 99); + + // After take, get_raw returns None. + assert!(cell.get_raw().is_none()); + + // Calling take again returns None. + let taken_again = unsafe { cell.take() }; + assert!(taken_again.is_none()); + } +} diff --git a/mingling_core/src/program/string_vec.rs b/mingling_core/src/program/string_vec.rs index fd0e2cb..1ccedf4 100644 --- a/mingling_core/src/program/string_vec.rs +++ b/mingling_core/src/program/string_vec.rs @@ -55,3 +55,62 @@ impl From> for StringVec { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_string_vec_from_array() { + let sv = StringVec::from(["a", "b", "c"]); + assert_eq!(sv.vec, vec!["a", "b", "c"]); + } + + #[test] + fn test_string_vec_from_slice_ref() { + let arr = ["x", "y"]; + let sv = StringVec::from(&arr[..]); + assert_eq!(sv.vec, vec!["x", "y"]); + } + + #[test] + fn test_string_vec_from_vec_string() { + let original = vec!["one".to_string(), "two".to_string()]; + let sv = StringVec::from(original.clone()); + assert_eq!(sv.vec, original); + } + + #[test] + fn test_string_vec_from_slice_string() { + let original = vec!["a".to_string(), "b".to_string()]; + let sv = StringVec::from(&original[..]); + assert_eq!(sv.vec, original); + } + + #[test] + fn test_string_vec_from_vec_str() { + let sv = StringVec::from(vec!["hello", "world"]); + assert_eq!(sv.vec, vec!["hello", "world"]); + } + + #[test] + fn test_string_vec_deref() { + let sv = StringVec::from(["alpha", "beta"]); + let inner: &Vec = &*sv; + assert_eq!(inner.len(), 2); + assert_eq!(inner[0], "alpha"); + } + + #[test] + fn test_string_vec_into_vec() { + let sv = StringVec::from(["foo", "bar"]); + let v: Vec = sv.into(); + assert_eq!(v, vec!["foo", "bar"]); + } + + #[test] + fn test_string_vec_empty_from_empty_array() { + let sv = StringVec::from([] as [&str; 0]); + assert!(sv.vec.is_empty()); + } +} -- cgit