aperture_cli/interactive/
mock.rs1use crate::error::Error;
2use std::time::Duration;
3
4#[cfg(test)]
5use mockall::predicate::*;
6
7#[cfg_attr(test, mockall::automock)]
9pub trait InputOutput {
10 fn print(&self, text: &str) -> Result<(), Error>;
15
16 fn println(&self, text: &str) -> Result<(), Error>;
21
22 fn flush(&self) -> Result<(), Error>;
27
28 fn read_line(&self) -> Result<String, Error>;
33
34 fn read_line_with_timeout(&self, timeout: Duration) -> Result<String, Error>;
39}
40
41pub struct RealInputOutput;
43
44impl InputOutput for RealInputOutput {
45 fn print(&self, text: &str) -> Result<(), Error> {
46 print!("{text}");
47 Ok(())
48 }
49
50 fn println(&self, text: &str) -> Result<(), Error> {
51 println!("{text}");
52 Ok(())
53 }
54
55 fn flush(&self) -> Result<(), Error> {
56 use std::io::Write;
57 std::io::stdout().flush().map_err(Error::Io)
58 }
59
60 fn read_line(&self) -> Result<String, Error> {
61 use std::io::BufRead;
62 let stdin = std::io::stdin();
63 let mut line = String::new();
64 stdin.lock().read_line(&mut line).map_err(Error::Io)?;
65 Ok(line)
66 }
67
68 fn read_line_with_timeout(&self, timeout: Duration) -> Result<String, Error> {
69 use std::io::BufRead;
70 use std::sync::mpsc;
71 use std::thread;
72
73 let (tx, rx) = mpsc::channel();
74
75 let read_thread = thread::spawn(move || {
78 let stdin = std::io::stdin();
79 let mut line = String::new();
80 let result = stdin.lock().read_line(&mut line);
81 match result {
82 Ok(_) => tx.send(Ok(line)).unwrap_or(()),
83 Err(e) => tx.send(Err(Error::Io(e))).unwrap_or(()),
84 }
85 });
86
87 match rx.recv_timeout(timeout) {
89 Ok(result) => {
90 let _ = read_thread.join();
92 result
93 }
94 Err(mpsc::RecvTimeoutError::Timeout) => {
95 Err(Error::InteractiveTimeout {
102 timeout_secs: timeout.as_secs(),
103 suggestion: "Try again with a faster response or increase timeout with APERTURE_INPUT_TIMEOUT".to_string(),
104 })
105 }
106 Err(mpsc::RecvTimeoutError::Disconnected) => Err(Error::InvalidConfig {
107 reason: "Input channel disconnected".to_string(),
108 }),
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_mock_input_output() {
119 let mut mock = MockInputOutput::new();
120
121 mock.expect_print()
123 .with(eq("Hello"))
124 .times(1)
125 .returning(|_| Ok(()));
126
127 mock.expect_read_line()
128 .times(1)
129 .returning(|| Ok("test input\n".to_string()));
130
131 assert!(mock.print("Hello").is_ok());
133 assert_eq!(mock.read_line().unwrap(), "test input\n");
134 }
135}