1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Command {
11 pub index: u32,
13
14 pub command: String,
16
17 pub cwd: PathBuf,
19
20 pub started_at: f64,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub ended_at: Option<f64>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub exit_code: Option<i32>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub duration_ms: Option<u64>,
34}
35
36impl Command {
37 #[must_use]
46 pub fn new(index: u32, command: String, cwd: PathBuf) -> Self {
47 let started_at = SystemTime::now()
48 .duration_since(UNIX_EPOCH)
49 .expect("Time went backwards")
50 .as_secs_f64();
51
52 Self {
53 index,
54 command,
55 cwd,
56 started_at,
57 ended_at: None,
58 exit_code: None,
59 duration_ms: None,
60 }
61 }
62
63 pub fn complete(&mut self, exit_code: i32) {
72 let ended_at = SystemTime::now()
73 .duration_since(UNIX_EPOCH)
74 .expect("Time went backwards")
75 .as_secs_f64();
76
77 self.ended_at = Some(ended_at);
78 self.exit_code = Some(exit_code);
79 self.duration_ms = Some(((ended_at - self.started_at) * 1000.0) as u64);
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use std::thread::sleep;
87 use std::time::Duration;
88
89 #[test]
90 fn test_command_new() {
91 let cmd = Command::new(0, "echo hello".to_string(), PathBuf::from("/home/user"));
92
93 assert_eq!(cmd.index, 0);
94 assert_eq!(cmd.command, "echo hello");
95 assert_eq!(cmd.cwd, PathBuf::from("/home/user"));
96 assert!(cmd.started_at > 0.0);
97 assert!(cmd.ended_at.is_none());
98 assert!(cmd.exit_code.is_none());
99 assert!(cmd.duration_ms.is_none());
100 }
101
102 #[test]
103 fn test_command_complete() {
104 let mut cmd = Command::new(0, "echo hello".to_string(), PathBuf::from("/home/user"));
105
106 sleep(Duration::from_millis(10));
108
109 cmd.complete(0);
110
111 assert!(cmd.ended_at.is_some());
112 assert_eq!(cmd.exit_code, Some(0));
113 assert!(cmd.duration_ms.is_some());
114 assert!(cmd.duration_ms.unwrap() >= 10);
115 }
116
117 #[test]
118 fn test_command_serialization() {
119 let mut cmd = Command::new(0, "echo hello".to_string(), PathBuf::from("/home/user"));
120 cmd.complete(0);
121
122 let json = serde_json::to_string(&cmd).expect("Failed to serialize");
123 assert!(json.contains("\"command\":\"echo hello\""));
124 assert!(json.contains("\"exit_code\":0"));
125
126 let deserialized: Command = serde_json::from_str(&json).expect("Failed to deserialize");
127 assert_eq!(deserialized.command, cmd.command);
128 assert_eq!(deserialized.exit_code, cmd.exit_code);
129 }
130}