1use std::time::Duration;
7
8use tokio::runtime::{Builder, Runtime};
9
10#[cfg(unix)]
11use crate::backend::AsyncPty;
12#[cfg(windows)]
13use crate::backend::WindowsAsyncPty;
14use crate::config::SessionConfig;
15use crate::error::Result;
16use crate::expect::Pattern;
17use crate::session::Session;
18use crate::types::{ControlChar, Match};
19
20#[cfg(unix)]
25pub struct SyncSession {
26 runtime: Runtime,
28 inner: Session<AsyncPty>,
30}
31
32#[cfg(windows)]
37pub struct SyncSession {
38 runtime: Runtime,
40 inner: Session<WindowsAsyncPty>,
42}
43
44#[cfg(unix)]
45impl SyncSession {
46 pub fn spawn(command: &str, args: &[&str]) -> Result<Self> {
52 let runtime = Builder::new_current_thread()
53 .enable_all()
54 .build()
55 .map_err(|e| crate::error::ExpectError::io_context("creating tokio runtime", e))?;
56
57 let inner = runtime.block_on(Session::spawn(command, args))?;
58
59 Ok(Self { runtime, inner })
60 }
61
62 pub fn spawn_with_config(command: &str, args: &[&str], config: SessionConfig) -> Result<Self> {
68 let runtime = Builder::new_current_thread()
69 .enable_all()
70 .build()
71 .map_err(|e| crate::error::ExpectError::io_context("creating tokio runtime", e))?;
72
73 let inner = runtime.block_on(Session::spawn_with_config(command, args, config))?;
74
75 Ok(Self { runtime, inner })
76 }
77
78 #[must_use]
80 pub const fn config(&self) -> &SessionConfig {
81 self.inner.config()
82 }
83
84 #[must_use]
86 pub const fn is_active(&self) -> bool {
87 !self.inner.is_eof()
88 }
89
90 #[must_use]
92 pub fn pid(&self) -> u32 {
93 self.inner.pid()
94 }
95
96 pub fn send(&mut self, data: &[u8]) -> Result<()> {
102 self.runtime.block_on(self.inner.send(data))
103 }
104
105 pub fn send_str(&mut self, s: &str) -> Result<()> {
111 self.runtime.block_on(self.inner.send_str(s))
112 }
113
114 pub fn send_line(&mut self, line: &str) -> Result<()> {
120 self.runtime.block_on(self.inner.send_line(line))
121 }
122
123 pub fn send_control(&mut self, ctrl: ControlChar) -> Result<()> {
129 self.runtime.block_on(self.inner.send_control(ctrl))
130 }
131
132 pub fn expect(&mut self, pattern: impl Into<Pattern>) -> Result<Match> {
138 self.runtime.block_on(self.inner.expect(pattern))
139 }
140
141 pub fn expect_timeout(
147 &mut self,
148 pattern: impl Into<Pattern>,
149 timeout: Duration,
150 ) -> Result<Match> {
151 self.runtime
152 .block_on(self.inner.expect_timeout(pattern, timeout))
153 }
154
155 #[must_use]
157 pub fn buffer(&mut self) -> String {
158 self.inner.buffer()
159 }
160
161 pub fn clear_buffer(&mut self) {
163 self.inner.clear_buffer();
164 }
165
166 pub fn resize(&mut self, cols: u16, rows: u16) -> Result<()> {
172 self.runtime.block_on(self.inner.resize_pty(cols, rows))
173 }
174
175 pub fn signal(&self, signal: i32) -> Result<()> {
181 self.inner.signal(signal)
182 }
183
184 pub fn kill(&self) -> Result<()> {
190 self.inner.kill()
191 }
192
193 pub fn block_on<F, T>(&self, future: F) -> T
195 where
196 F: std::future::Future<Output = T>,
197 {
198 self.runtime.block_on(future)
199 }
200}
201
202#[cfg(windows)]
203impl SyncSession {
204 pub fn spawn(command: &str, args: &[&str]) -> Result<Self> {
210 let runtime = Builder::new_current_thread()
211 .enable_all()
212 .build()
213 .map_err(|e| crate::error::ExpectError::io_context("creating tokio runtime", e))?;
214
215 let inner = runtime.block_on(Session::spawn(command, args))?;
216
217 Ok(Self { runtime, inner })
218 }
219
220 pub fn spawn_with_config(command: &str, args: &[&str], config: SessionConfig) -> Result<Self> {
226 let runtime = Builder::new_current_thread()
227 .enable_all()
228 .build()
229 .map_err(|e| crate::error::ExpectError::io_context("creating tokio runtime", e))?;
230
231 let inner = runtime.block_on(Session::spawn_with_config(command, args, config))?;
232
233 Ok(Self { runtime, inner })
234 }
235
236 #[must_use]
238 pub const fn config(&self) -> &SessionConfig {
239 self.inner.config()
240 }
241
242 #[must_use]
244 pub fn is_active(&self) -> bool {
245 !self.inner.is_eof()
246 }
247
248 #[must_use]
250 pub fn pid(&self) -> u32 {
251 self.inner.pid()
252 }
253
254 pub fn send(&mut self, data: &[u8]) -> Result<()> {
260 self.runtime.block_on(self.inner.send(data))
261 }
262
263 pub fn send_str(&mut self, s: &str) -> Result<()> {
269 self.runtime.block_on(self.inner.send_str(s))
270 }
271
272 pub fn send_line(&mut self, line: &str) -> Result<()> {
278 self.runtime.block_on(self.inner.send_line(line))
279 }
280
281 pub fn send_control(&mut self, ctrl: ControlChar) -> Result<()> {
287 self.runtime.block_on(self.inner.send_control(ctrl))
288 }
289
290 pub fn expect(&mut self, pattern: impl Into<Pattern>) -> Result<Match> {
296 self.runtime.block_on(self.inner.expect(pattern))
297 }
298
299 pub fn expect_timeout(
305 &mut self,
306 pattern: impl Into<Pattern>,
307 timeout: Duration,
308 ) -> Result<Match> {
309 self.runtime
310 .block_on(self.inner.expect_timeout(pattern, timeout))
311 }
312
313 #[must_use]
315 pub fn buffer(&mut self) -> String {
316 self.inner.buffer()
317 }
318
319 pub fn clear_buffer(&mut self) {
321 self.inner.clear_buffer();
322 }
323
324 pub fn resize(&mut self, cols: u16, rows: u16) -> Result<()> {
330 self.runtime.block_on(self.inner.resize_pty(cols, rows))
331 }
332
333 #[must_use]
335 pub fn is_running(&self) -> bool {
336 self.inner.is_running()
337 }
338
339 pub fn kill(&self) -> Result<()> {
345 self.inner.kill()
346 }
347
348 pub fn block_on<F, T>(&self, future: F) -> T
350 where
351 F: std::future::Future<Output = T>,
352 {
353 self.runtime.block_on(future)
354 }
355}
356
357impl std::fmt::Debug for SyncSession {
358 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359 f.debug_struct("SyncSession").finish_non_exhaustive()
360 }
361}
362
363pub struct BlockingExpect<'a> {
365 session: &'a mut SyncSession,
366 timeout: Duration,
367}
368
369impl<'a> BlockingExpect<'a> {
370 pub const fn new(session: &'a mut SyncSession) -> Self {
372 let timeout = session.config().timeout.default;
373 Self { session, timeout }
374 }
375
376 #[must_use]
378 pub const fn timeout(mut self, timeout: Duration) -> Self {
379 self.timeout = timeout;
380 self
381 }
382
383 pub fn pattern(self, pattern: impl Into<Pattern>) -> Result<Match> {
389 self.session.expect_timeout(pattern, self.timeout)
390 }
391}
392
393pub fn block_on<F, T>(future: F) -> Result<T>
402where
403 F: std::future::Future<Output = T>,
404{
405 let runtime = Builder::new_current_thread()
406 .enable_all()
407 .build()
408 .map_err(|e| {
409 crate::error::ExpectError::io_context("creating tokio runtime for block_on", e)
410 })?;
411
412 Ok(runtime.block_on(future))
413}
414
415pub fn spawn(command: &str, args: &[&str]) -> Result<SyncSession> {
421 SyncSession::spawn(command, args)
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn block_on_simple() {
430 let result = block_on(async { 42 });
431 assert!(result.is_ok());
432 assert_eq!(result.unwrap(), 42);
433 }
434
435 #[cfg(unix)]
436 #[test]
437 fn sync_session_spawn_echo() {
438 let mut session =
439 SyncSession::spawn("/bin/echo", &["hello"]).expect("Failed to spawn echo");
440
441 assert!(session.pid() > 0);
443
444 let m = session.expect("hello").expect("Failed to expect hello");
446 assert!(m.matched.contains("hello"));
447 }
448}