hojicha_runtime/testing/
async_test_utils.rs1use hojicha_core::core::{Cmd, Message};
4use hojicha_core::event::Event;
5use std::sync::mpsc;
6use std::time::Duration;
7use tokio::runtime::Runtime;
8
9pub struct AsyncTestHarness {
11 runtime: Runtime,
12}
13
14impl AsyncTestHarness {
15 pub fn new() -> Self {
17 Self {
18 runtime: Runtime::new().expect("Failed to create runtime"),
19 }
20 }
21
22 pub fn execute_command<M: Message + Clone + Send + 'static>(
24 &self,
25 cmd: Cmd<M>,
26 ) -> Vec<M> {
27 let (tx, rx) = mpsc::sync_channel(100);
28
29 use crate::program::CommandExecutor;
31 let executor = CommandExecutor::new().expect("Failed to create executor");
32
33 executor.execute(cmd, tx);
35
36 let mut messages = Vec::new();
38 let start = std::time::Instant::now();
39 let timeout = Duration::from_secs(1);
40
41 while start.elapsed() < timeout {
42 match rx.try_recv() {
43 Ok(Event::User(msg)) => messages.push(msg),
44 Ok(_) => {} Err(mpsc::TryRecvError::Empty) => {
46 std::thread::sleep(Duration::from_millis(1));
48 }
49 Err(mpsc::TryRecvError::Disconnected) => break,
50 }
51 }
52
53 messages
54 }
55
56 pub fn execute_tick_now<M: Message, F>(&self, _duration: Duration, f: F) -> M
58 where
59 F: FnOnce() -> M,
60 {
61 f()
63 }
64
65 pub fn block_on_async<M: Message + Send + 'static>(
67 &self,
68 future: impl std::future::Future<Output = Option<M>> + Send + 'static,
69 ) -> Option<M> {
70 self.runtime.block_on(future)
71 }
72
73 pub fn execute_and_wait<M: Message + Clone + Send + 'static>(
75 &self,
76 cmd: Cmd<M>,
77 wait_duration: Duration,
78 ) -> Vec<M> {
79 let (tx, rx) = mpsc::sync_channel(100);
80
81 use crate::program::CommandExecutor;
82 let executor = CommandExecutor::new().expect("Failed to create executor");
83
84 executor.execute(cmd, tx);
86
87 std::thread::sleep(wait_duration);
89
90 let mut messages = Vec::new();
92 while let Ok(Event::User(msg)) = rx.try_recv() {
93 messages.push(msg);
94 }
95
96 messages
97 }
98}
99
100pub trait CmdTestExt<M: Message> {
102 fn execute_sync(self) -> Option<M>;
104
105 fn execute_with_harness(self, harness: &AsyncTestHarness) -> Vec<M>;
107}
108
109impl<M: Message + Clone + Send + 'static> CmdTestExt<M> for Cmd<M> {
110 fn execute_sync(self) -> Option<M> {
111 if !self.is_tick() && !self.is_every() && !self.is_async() {
113 self.test_execute().ok().flatten()
114 } else {
115 let harness = AsyncTestHarness::new();
117 harness.execute_command(self).into_iter().next()
118 }
119 }
120
121 fn execute_with_harness(self, harness: &AsyncTestHarness) -> Vec<M> {
122 harness.execute_command(self)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use hojicha_core::commands;
130
131 #[derive(Debug, Clone, PartialEq)]
132 enum TestMsg {
133 Tick,
134 Every,
135 Async,
136 }
137
138 #[test]
139 fn test_async_harness() {
140 let harness = AsyncTestHarness::new();
141
142 let tick_cmd = commands::tick(Duration::from_millis(10), || TestMsg::Tick);
144 let messages = harness.execute_command(tick_cmd);
145 assert_eq!(messages, vec![TestMsg::Tick]);
146 }
147
148 #[test]
149 fn test_execute_tick_now() {
150 let harness = AsyncTestHarness::new();
151 let msg = harness.execute_tick_now(Duration::from_secs(100), || TestMsg::Tick);
152 assert_eq!(msg, TestMsg::Tick);
153 }
154
155 #[test]
156 fn test_block_on_async() {
157 let harness = AsyncTestHarness::new();
158 let result = harness.block_on_async(async {
159 tokio::time::sleep(Duration::from_millis(1)).await;
160 Some(TestMsg::Async)
161 });
162 assert_eq!(result, Some(TestMsg::Async));
163 }
164
165 #[test]
166 fn test_cmd_sync_execution() {
167 let cmd = commands::custom(|| Some(TestMsg::Async));
169 let result = cmd.execute_sync();
170 assert_eq!(result, Some(TestMsg::Async));
171 }
172}