use hojicha_core::core::{Cmd, Message};
use hojicha_core::event::Event;
use std::sync::mpsc;
use std::time::Duration;
pub struct AsyncTestHarness;
impl Default for AsyncTestHarness {
fn default() -> Self {
Self::new()
}
}
impl AsyncTestHarness {
pub fn new() -> Self {
Self
}
pub fn execute_command<M: Message + Clone + Send + 'static>(&self, cmd: Cmd<M>) -> Vec<M> {
let (tx, rx) = mpsc::sync_channel(100);
use crate::program::CommandExecutor;
let executor = CommandExecutor::new().expect("Failed to create executor");
executor.execute(cmd, &tx);
let mut messages = Vec::new();
let start = std::time::Instant::now();
let timeout = Duration::from_secs(1);
while start.elapsed() < timeout {
match rx.try_recv() {
Ok(Event::User(msg)) => messages.push(msg),
Ok(_) => {} Err(mpsc::TryRecvError::Empty) => {
std::thread::sleep(Duration::from_millis(1));
}
Err(mpsc::TryRecvError::Disconnected) => break,
}
}
messages
}
pub fn execute_tick_now<M: Message, F>(&self, _duration: Duration, f: F) -> M
where
F: FnOnce() -> M,
{
f()
}
pub fn block_on_async<M: Message + Send + 'static>(
&self,
future: impl std::future::Future<Output = Option<M>> + Send + 'static,
) -> Option<M> {
use crate::shared_runtime::shared_runtime;
shared_runtime().block_on(future)
}
pub fn execute_and_wait<M: Message + Clone + Send + 'static>(
&self,
cmd: Cmd<M>,
wait_duration: Duration,
) -> Vec<M> {
let (tx, rx) = mpsc::sync_channel(100);
use crate::program::CommandExecutor;
let executor = CommandExecutor::new().expect("Failed to create executor");
executor.execute(cmd, &tx);
std::thread::sleep(wait_duration);
let mut messages = Vec::new();
while let Ok(Event::User(msg)) = rx.try_recv() {
messages.push(msg);
}
messages
}
}
pub trait CmdTestExt<M: Message> {
fn execute_sync(self) -> Option<M>;
fn execute_with_harness(self, harness: &AsyncTestHarness) -> Vec<M>;
}
impl<M: Message + Clone + Send + 'static> CmdTestExt<M> for Cmd<M> {
fn execute_sync(self) -> Option<M> {
if !self.is_tick() && !self.is_every() && !self.is_async() {
self.test_execute().ok().flatten()
} else {
let harness = AsyncTestHarness::new();
harness.execute_command(self).into_iter().next()
}
}
fn execute_with_harness(self, harness: &AsyncTestHarness) -> Vec<M> {
harness.execute_command(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use hojicha_core::commands;
#[derive(Debug, Clone, PartialEq)]
enum TestMsg {
Tick,
#[allow(dead_code)]
Every,
Async,
}
#[test]
fn test_async_harness() {
let harness = AsyncTestHarness::new();
let tick_cmd = commands::tick(Duration::from_millis(10), || TestMsg::Tick);
let messages = harness.execute_command(tick_cmd);
assert_eq!(messages, vec![TestMsg::Tick]);
}
#[test]
fn test_execute_tick_now() {
let harness = AsyncTestHarness::new();
let msg = harness.execute_tick_now(Duration::from_secs(100), || TestMsg::Tick);
assert_eq!(msg, TestMsg::Tick);
}
#[test]
fn test_block_on_async() {
let harness = AsyncTestHarness::new();
let result = harness.block_on_async(async {
tokio::time::sleep(Duration::from_millis(1)).await;
Some(TestMsg::Async)
});
assert_eq!(result, Some(TestMsg::Async));
}
#[test]
fn test_cmd_sync_execution() {
let cmd = commands::custom(|| Some(TestMsg::Async));
let result = cmd.execute_sync();
assert_eq!(result, Some(TestMsg::Async));
}
}