Demilade Sonuga's blog
All postsEvent Handling III
In this post, we're going to test the event hooker.
What Exactly Are We Testing?
Does send_event
result in the calling of associated registered functions and those functions only?
Does hook_event
result in the registering of functions to be called when certain events occur?
Does a function still get called when an event occurs after unregistering it with unhook_event
?
Dummy Allocators
Once again, we're going to need dummy allocators. For now, we'll just copy and paste (we'll deal with the resulting mess when we're refactoring).
In event_hook.rs
#[cfg(test)]
mod tests {
use super::*;
use crate::allocator::Allocator;
// Convenience function for getting the always successful allocator
fn successful_allocator() -> *mut SuccessfulAllocator {
&mut SuccessfulAllocator as *mut _
}
// Convenience function for getting the always fail allocator
fn failing_allocator() -> *mut FailingAllocator {
&mut FailingAllocator as *mut _
}
// Dummy allocator that we can depend on to always succeed
struct SuccessfulAllocator;
use std::alloc::Global as PlatformAllocator;
use std::alloc::Layout;
use std::ptr::NonNull;
use std::alloc::Allocator as StdAllocator;
// Use your computer's allocator to allocate and deallocate memory
// Much more reliable than using our own custom allocator,
// so we can depend on it succeeding (under normal circumstances)
unsafe impl Allocator for SuccessfulAllocator {
unsafe fn alloc(&mut self, size: usize, alignment: usize) -> Option<*mut u8> {
let mem_layout = Layout::from_size_align(size, alignment).unwrap();
let mem = PlatformAllocator.allocate(mem_layout).unwrap();
let ptr = mem.as_ptr() as *mut u8;
Some(ptr)
}
unsafe fn dealloc(&mut self, ptr: *mut u8, size_to_dealloc: usize) {
// Using an alignment of 1 here because I think the alignment no
// longer matters here. We're deallocating memory because we're
// done using it
let mem_layout = Layout::from_size_align(size_to_dealloc, 1).unwrap();
PlatformAllocator.deallocate(NonNull::new(ptr).unwrap(), mem_layout);
}
}
// Dummy allocator we can depend on to always fail
struct FailingAllocator;
unsafe impl Allocator for FailingAllocator {
unsafe fn alloc(&mut self, size: usize, alignment: usize) -> Option<*mut u8> {
None
}
unsafe fn dealloc(&mut self, ptr: *mut u8, size_to_dealloc: usize) {}
}
}
Testing
The thing about the tests we're going to write here is that the send_event
, hook_event
and unhook_event
functions can't be tested individually. To verify that send_event
is working, we first need to register a
function with hook_event
. And to verify that hook_event
is working, we'll need to call send_event
to
see if the registered function gets called. To verify that unhook_event
is working, we first need to register
the function with hook_event
, check if it runs with send_event
and verify that it doesn't run anymore
after unhook_event
is called.
So, instead of writing tests for testing those individual functions, we're going to write a test that will test everything working together as a whole.
#[cfg(test)]
mod tests {
// ...Others
use crate::keyboard::{KeyCode, KeyDirection};
#[test]
fn test_event_hooker1() {
let allocator = successful_allocator();
let mut event_hooker = EventHooker::new(allocator);
// This variable is increased everytime a timer handler is called
let mut timer_no = 0;
// This variable is increased everytime a keyboard handler is called
let mut keyboard_no = 0;
// Registering the timer and keyboard handlers
let timer_handler_hook_id = event_hooker.hook_event(
EventKind::Timer,
BoxedFn::new(|_| timer_no += 1, allocator)
);
let keyboard_handler_hook_id = event_hooker.hook_event(
EventKind::Keyboard,
BoxedFn::new(|_| keyboard_no += 1, allocator)
);
// Send the timer event 3 times
for _ in 0..3 {
event_hooker.send_event(EventInfo::Timer);
}
// Since the timer event was sent 3 times and no keyboard
// event has been sent, then the timer_no should have increased
// to 3 and the keyboard_no should have remained 0
assert_eq!(timer_no, 3);
assert_eq!(keyboard_no, 0);
// Send the keyboard event 3 times
for _ in 0..3 {
event_hooker.send_event(EventInfo::Keyboard(KeyEvent {
keycode: KeyCode::A,
direction: KeyDirection::Down
}));
}
// The timer_no should still be 3 and the keyboard_no should increase to 3
assert_eq!(timer_no, 3);
assert_eq!(keyboard_no, 3);
}
// ...Others
}
The above test initializes two variables, timer_no
and keyboard_no
, to 0. When the timer event handler
is called, timer_no
increases by 1 and when the keyboard handler is called, keyboard_no
increases by 1.
This test answers the first two questions under the What Exactly Are We Testing section.
It does that by sending the timer event 3 times, checking the timer_no
to see if it has indeed increased
by 3 and verifying that the keyboard_no
has not increased at all. This verifies that sending the timer
event resulted only in calling the function registered as the timer event handler.
It then sends the keyboard event 3 times and checks to make sure that timer_no
doesn't increase at all
and that keyboard_no
increases 3 times. This verifies that sending the keyboard event results in
invoking only functions registered as keyboard handlers.
To test out the unhook_event
behavior, we can just write another test.
#[cfg(test)]
mod tests {
// ...Others
#[test]
fn test_event_hooker2() {
let allocator = successful_allocator();
let mut event_hooker = EventHooker::new(allocator);
// The number of times the timer handler was called
let mut no_of_calls = 0;
let hook_id = event_hooker.hook_event(
EventKind::Timer,
BoxedFn::new(|_| no_of_calls += 1, allocator)
);
// Send the timer event 5 times
for _ in 0..5 {
event_hooker.send_event(EventInfo::Timer);
}
// Verify that no_of_calls increased to 5
// (no_of_calls is incremented with every invocation
// of the timer handler)
assert_eq!(no_of_calls, 5);
// Unregister the timer handler
event_hooker.unhook_event(EventKind::Timer, hook_id).unwrap();
// Send the timer event 5 times and verify that
// no_of_calls remained the same
for _ in 0..5 {
event_hooker.send_event(EventInfo::Timer);
}
assert_eq!(no_of_calls, 5);
}
// ...Others
}
The above verifies that unhook_event
truly results in the unregistering of an event handler. It does this
by first registering a function to be called when timer events occur. Invoking this function results in the
increase of no_of_calls
by 1. Timer events are sent and no_of_calls
is checked to verify that the expected
effect of calling the function is made.
unhook_event
is then called with the function's id and timer events are sent again. no_of_calls
is then
checked to verify that the function's effects are no longer there, meaning that it's no longer being invoked
when timer events are sent.
These are just a few cases and there are a lot of other things that could go wrong with the EventHooker
. You
should add those tests yourself.
Anyways, this concludes our EventHooker
testing. Next, we're going to check it out in live action
from the efi_main
function in main.rs
.
Take Away
For the full code, go to the repo
In The Next Post
We'll be checking out the EventHooker
in action.