pub mod errors;
mod hook;
mod hookable;
pub use hook::*;
pub use hookable::*;
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::HookableError;
use hookable::Hookable;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::time::sleep;
fn base_hooks() -> Hookable {
let hooks = Hookable::new();
hooks.hook("finishLoad", || {
println!("finishLoad executed");
});
hooks
}
fn async_hooks() -> Hookable {
let hooks = Hookable::new();
hooks.hook_async("asyncEvent", || {
Box::pin(async {
sleep(Duration::from_millis(100)).await;
println!("Async hook executed");
})
});
hooks
}
#[test]
fn test_basic_sync_hook() {
let hooks = base_hooks();
let result = hooks.call("finishLoad");
assert!(result.is_ok());
}
#[test]
fn test_hook_not_found() {
let hooks = Hookable::new();
let result = hooks.call("nonExistentHook");
assert!(result.is_err());
if let Err(e) = result {
match e {
HookableError::NoHookFound(name) => {
assert_eq!(name, "nonExistentHook");
}
_ => panic!("Expected NoHookFound error"),
}
}
}
#[test]
fn test_multiple_sync_hooks() {
let hooks = Hookable::new();
let counter = Arc::new(Mutex::new(0));
let counter1 = counter.clone();
hooks.hook("multiHook", move || {
let mut count = counter1.lock().unwrap();
*count += 1;
});
let counter2 = counter.clone();
hooks.hook("multiHook", move || {
let mut count = counter2.lock().unwrap();
*count += 10;
});
let counter3 = counter.clone();
hooks.hook("multiHook", move || {
let mut count = counter3.lock().unwrap();
*count += 100;
});
hooks.call("multiHook").expect("Failed to call multiHook");
let final_count = *counter.lock().unwrap();
assert_eq!(final_count, 111); }
#[test]
fn test_sync_hook_called_with_async_method() {
let hooks = base_hooks();
let rt = Runtime::new().unwrap();
let result = rt.block_on(hooks.call_async("finishLoad"));
assert!(result.is_ok());
}
#[test]
fn test_async_hook_called_with_sync_method() {
let hooks = async_hooks();
let result = hooks.call("asyncEvent");
assert!(result.is_err());
if let Err(e) = result {
match e {
HookableError::AsyncHookCalledSync(name) => {
assert_eq!(name, "asyncEvent");
}
_ => panic!("Expected AsyncHookCalledSync error"),
}
}
}
#[tokio::test]
async fn test_basic_async_hook() {
let hooks = async_hooks();
let result = hooks.call_async("asyncEvent").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_mixed_sync_and_async_hooks() {
let hooks = Hookable::new();
let counter = Arc::new(Mutex::new(0));
let counter1 = counter.clone();
hooks.hook("mixedEvent", move || {
let mut count = counter1.lock().unwrap();
*count += 1;
});
let counter2 = counter.clone();
hooks.hook_async("mixedEvent", move || {
let counter2 = counter2.clone();
Box::pin(async move {
sleep(Duration::from_millis(50)).await;
let mut count = counter2.lock().unwrap();
*count += 2;
})
});
let counter3 = counter.clone();
hooks.hook("mixedEvent", move || {
let mut count = counter3.lock().unwrap();
*count += 4;
});
hooks
.call_async("mixedEvent")
.await
.expect("Failed to call mixedEvent");
let final_count = *counter.lock().unwrap();
assert_eq!(final_count, 7); }
#[tokio::test]
async fn test_async_hook_not_found() {
let hooks = Hookable::new();
let result = hooks.call_async("nonExistentAsyncHook").await;
assert!(result.is_err());
if let Err(e) = result {
match e {
HookableError::NoHookFound(name) => {
assert_eq!(name, "nonExistentAsyncHook");
}
_ => panic!("Expected NoHookFound error"),
}
}
}
#[tokio::test]
async fn test_multiple_async_hooks() {
let hooks = Hookable::new();
let results = Arc::new(Mutex::new(Vec::new()));
let results1 = results.clone();
hooks.hook_async("sequentialAsync", move || {
let results1 = results1.clone();
Box::pin(async move {
sleep(Duration::from_millis(10)).await;
results1.lock().unwrap().push("first");
})
});
let results2 = results.clone();
hooks.hook_async("sequentialAsync", move || {
let results2 = results2.clone();
Box::pin(async move {
sleep(Duration::from_millis(20)).await;
results2.lock().unwrap().push("second");
})
});
let results3 = results.clone();
hooks.hook_async("sequentialAsync", move || {
let results3 = results3.clone();
Box::pin(async move {
sleep(Duration::from_millis(5)).await;
results3.lock().unwrap().push("third");
})
});
hooks
.call_async("sequentialAsync")
.await
.expect("Failed to call sequentialAsync");
let final_results = results.lock().unwrap();
assert_eq!(final_results.len(), 3);
assert_eq!(*final_results, vec!["first", "second", "third"]);
}
#[test]
fn test_hookable_clone() {
let hooks = Hookable::new();
let counter = Arc::new(Mutex::new(0));
let counter_clone = counter.clone();
hooks.hook("cloneTest", move || {
let mut count = counter_clone.lock().unwrap();
*count += 1;
});
let hooks_clone = hooks.clone();
hooks.call("cloneTest").expect("Failed to call on original");
hooks_clone
.call("cloneTest")
.expect("Failed to call on clone");
let final_count = *counter.lock().unwrap();
assert_eq!(final_count, 2);
}
#[test]
fn test_hookable_default() {
let hooks = Hookable::default();
hooks.hook("defaultTest", || {
println!("Default hookable works");
});
let result = hooks.call("defaultTest");
assert!(result.is_ok());
}
#[tokio::test]
async fn test_concurrent_hook_execution() {
let hooks = Arc::new(Hookable::new());
let counter = Arc::new(Mutex::new(0));
let counter_clone = counter.clone();
hooks.hook("concurrentTest", move || {
let mut count = counter_clone.lock().unwrap();
*count += 1;
});
let mut handles = vec![];
for _ in 0..10 {
let hooks_clone = hooks.clone();
let handle = tokio::spawn(async move {
hooks_clone
.call("concurrentTest")
.expect("Failed to call hook");
});
handles.push(handle);
}
for handle in handles {
handle.await.expect("Task failed");
}
let final_count = *counter.lock().unwrap();
assert_eq!(final_count, 10);
}
#[test]
fn test_hook_with_string_types() {
let hooks = Hookable::new();
hooks.hook("string_literal", || println!("String literal"));
hooks.hook(String::from("owned_string"), || println!("Owned string"));
hooks.hook("str_slice".to_string(), || println!("String slice"));
assert!(hooks.call("string_literal").is_ok());
assert!(hooks.call(String::from("owned_string")).is_ok());
assert!(hooks.call("str_slice").is_ok());
}
}