Skip to main content

tui_test/
config.rs

1use std::path::PathBuf;
2
3pub const DEFAULT_COLS: u16 = 80;
4pub const DEFAULT_ROWS: u16 = 30;
5pub const POLL_DELAY_MS: u64 = 50;
6/// Cap for `open`'s implicit prompt wait when no `ready` budget is configured.
7pub const OPEN_READY_CAP_MS: u64 = 8_000;
8
9/// The kind of thing a wait or assertion is blocking on.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TimeoutClass {
12    /// Text appearing on (or leaving) the screen: `expect text`, `wait text`.
13    Text,
14    /// The screen going quiet: `wait idle`.
15    Idle,
16    /// A foreground command finishing: `wait command`, `expect exit-code`.
17    Command,
18    /// The session's program exiting: `wait exit`.
19    Exit,
20    /// The shell reporting a prompt: `wait ready`, and `open`'s implicit wait.
21    Ready,
22}
23
24impl TimeoutClass {
25    /// The built-in budget when nothing else is configured.
26    pub fn built_in_ms(self) -> u64 {
27        match self {
28            TimeoutClass::Text | TimeoutClass::Idle => 5_000,
29            TimeoutClass::Command | TimeoutClass::Exit | TimeoutClass::Ready => 30_000,
30        }
31    }
32
33    /// The environment variable consulted for this class.
34    pub fn env_var(self) -> &'static str {
35        match self {
36            TimeoutClass::Text => "TUI_TEST_TIMEOUT_TEXT_MS",
37            TimeoutClass::Idle => "TUI_TEST_TIMEOUT_IDLE_MS",
38            TimeoutClass::Command => "TUI_TEST_TIMEOUT_COMMAND_MS",
39            TimeoutClass::Exit => "TUI_TEST_TIMEOUT_EXIT_MS",
40            TimeoutClass::Ready => "TUI_TEST_TIMEOUT_READY_MS",
41        }
42    }
43
44    /// The environment override, read at call time so tests can vary it.
45    pub fn env_ms(self) -> Option<u64> {
46        env_timeout_ms(self.env_var())
47    }
48
49    /// Resolve this class's default without a session.
50    pub fn default_ms(self) -> u64 {
51        self.env_ms().unwrap_or_else(|| self.built_in_ms())
52    }
53}
54
55fn env_timeout_ms(key: &str) -> Option<u64> {
56    parse_timeout_ms(&std::env::var(key).ok()?)
57}
58
59/// Parse a positive millisecond duration.
60fn parse_timeout_ms(raw: &str) -> Option<u64> {
61    raw.trim().parse::<u64>().ok().filter(|ms| *ms > 0)
62}
63
64/// Root directory for tui-test runtime data.
65/// Override with `TUI_TEST_HOME`.
66pub fn home_dir() -> PathBuf {
67    if let Ok(dir) = std::env::var("TUI_TEST_HOME") {
68        return PathBuf::from(dir);
69    }
70    let base = dirs::home_dir().unwrap_or_else(std::env::temp_dir);
71    base.join(".tui-test")
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn timeout_overrides_accept_positive_integers() {
80        assert_eq!(parse_timeout_ms("1"), Some(1));
81        assert_eq!(parse_timeout_ms("30000"), Some(30_000));
82        assert_eq!(parse_timeout_ms("  2500  "), Some(2_500));
83    }
84
85    #[test]
86    fn timeout_overrides_reject_junk_so_the_default_wins() {
87        for raw in ["", "0", "-1", "abc", "1.5", "5s"] {
88            assert_eq!(
89                parse_timeout_ms(raw),
90                None,
91                "expected {raw:?} to be ignored"
92            );
93        }
94    }
95
96    #[test]
97    fn built_in_defaults_split_screen_from_process() {
98        assert_eq!(TimeoutClass::Text.built_in_ms(), 5_000);
99        assert_eq!(TimeoutClass::Idle.built_in_ms(), 5_000);
100        assert_eq!(TimeoutClass::Command.built_in_ms(), 30_000);
101        assert_eq!(TimeoutClass::Exit.built_in_ms(), 30_000);
102        assert_eq!(TimeoutClass::Ready.built_in_ms(), 30_000);
103    }
104
105    #[test]
106    fn each_class_has_a_distinct_env_var() {
107        let classes = [
108            TimeoutClass::Text,
109            TimeoutClass::Idle,
110            TimeoutClass::Command,
111            TimeoutClass::Exit,
112            TimeoutClass::Ready,
113        ];
114        let mut seen = std::collections::HashSet::new();
115        for class in classes {
116            let name = class.env_var();
117            assert!(name.starts_with("TUI_TEST_TIMEOUT_"));
118            assert!(name.ends_with("_MS"));
119            assert!(seen.insert(name), "duplicate env var {name}");
120        }
121    }
122
123    #[test]
124    fn class_default_falls_back_to_the_built_in() {
125        static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
126        let _guard = ENV_LOCK.lock().unwrap();
127        for class in [TimeoutClass::Text, TimeoutClass::Command] {
128            let key = class.env_var();
129            let old = std::env::var_os(key);
130            std::env::remove_var(key);
131            let result = std::panic::catch_unwind(|| {
132                assert_eq!(class.default_ms(), class.built_in_ms());
133            });
134            if let Some(value) = old {
135                std::env::set_var(key, value);
136            }
137            result.unwrap();
138        }
139    }
140}