Skip to main content

command_stream/commands/
sleep.rs

1//! Virtual `sleep` command implementation
2
3use crate::commands::CommandContext;
4use crate::utils::{trace_lazy, CommandResult};
5use tokio::time::{sleep as tokio_sleep, Duration};
6
7/// Execute the sleep command
8///
9/// Pauses for the specified number of seconds.
10pub async fn sleep(ctx: CommandContext) -> CommandResult {
11    let seconds_str = ctx.args.first().map(|s| s.as_str()).unwrap_or("0");
12
13    let seconds: f64 = match seconds_str.parse() {
14        Ok(s) => s,
15        Err(_) => {
16            return CommandResult::error(format!(
17                "sleep: invalid time interval '{}'\n",
18                seconds_str
19            ));
20        }
21    };
22
23    if seconds < 0.0 {
24        return CommandResult::error(format!("sleep: invalid time interval '{}'\n", seconds_str));
25    }
26
27    trace_lazy("VirtualCommand", || {
28        format!("sleep: starting {} seconds", seconds)
29    });
30
31    let duration = Duration::from_secs_f64(seconds);
32
33    // Check for cancellation during sleep
34    tokio::select! {
35        _ = tokio_sleep(duration) => {
36            trace_lazy("VirtualCommand", || {
37                format!("sleep: completed naturally after {} seconds", seconds)
38            });
39            CommandResult::success_empty()
40        }
41        _ = async {
42            loop {
43                tokio::time::sleep(Duration::from_millis(100)).await;
44                if ctx.is_cancelled() {
45                    break;
46                }
47            }
48        } => {
49            trace_lazy("VirtualCommand", || {
50                format!("sleep: cancelled after partial sleep")
51            });
52            CommandResult::error_with_code("", 130) // SIGINT exit code
53        }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use std::time::Instant;
61
62    #[tokio::test]
63    async fn test_sleep_short() {
64        let ctx = CommandContext::new(vec!["0.1".to_string()]);
65        let start = Instant::now();
66        let result = sleep(ctx).await;
67        let elapsed = start.elapsed();
68
69        assert!(result.is_success());
70        assert!(elapsed >= Duration::from_millis(100));
71        assert!(elapsed < Duration::from_millis(200));
72    }
73
74    #[tokio::test]
75    async fn test_sleep_zero() {
76        let ctx = CommandContext::new(vec!["0".to_string()]);
77        let result = sleep(ctx).await;
78        assert!(result.is_success());
79    }
80
81    #[tokio::test]
82    async fn test_sleep_invalid() {
83        let ctx = CommandContext::new(vec!["invalid".to_string()]);
84        let result = sleep(ctx).await;
85        assert!(!result.is_success());
86    }
87
88    #[tokio::test]
89    async fn test_sleep_negative() {
90        let ctx = CommandContext::new(vec!["-1".to_string()]);
91        let result = sleep(ctx).await;
92        assert!(!result.is_success());
93    }
94}