leetcode_cli/cmd/
test.rs

1//! Test command
2use crate::{Error, Result};
3use clap::Args;
4
5/// Test command arguments
6#[derive(Args)]
7#[command(group = clap::ArgGroup::new("question-id").args(&["id", "daily"]).required(true))]
8pub struct TestArgs {
9    /// Question id
10    #[arg(value_parser = clap::value_parser!(i32))]
11    pub id: Option<i32>,
12
13    /// Custom testcase
14    pub testcase: Option<String>,
15
16    /// Test today's daily challenge
17    #[arg(short = 'd', long)]
18    pub daily: bool,
19
20    /// Watch for file changes and test automatically
21    #[arg(short, long)]
22    pub watch: bool,
23}
24
25impl TestArgs {
26    /// `test` handler
27    pub async fn run(&self) -> Result<()> {
28        use crate::cache::{Cache, Run};
29        use crate::helper::code_path;
30        use notify::{Config as NotifyConfig, RecommendedWatcher, RecursiveMode, Watcher};
31        use std::path::Path;
32        use std::sync::mpsc::channel;
33        use std::time::{Duration, Instant};
34
35        let cache = Cache::new()?;
36        let daily_id = if self.daily {
37            Some(cache.get_daily_problem_id().await?)
38        } else {
39            None
40        };
41
42        let id = self.id.or(daily_id).ok_or(Error::NoneError)?;
43
44        let case_str: Option<String> = self.testcase.as_ref().map(|case| case.replace("\\n", "\n"));
45
46        if self.watch {
47            let problem = cache.get_problem(id)?;
48            let path_str = code_path(&problem, None)?;
49            let path = Path::new(&path_str);
50            let parent = path
51                .parent()
52                .ok_or_else(|| anyhow::anyhow!("Could not get parent directory"))?;
53
54            let (tx, rx) = channel();
55            let mut watcher = RecommendedWatcher::new(tx, NotifyConfig::default())
56                .map_err(|e| anyhow::anyhow!("Failed to create watcher: {}", e))?;
57
58            // Watch parent directory to handle file creation and atomic saves (which may delete/rename)
59            watcher
60                .watch(parent, RecursiveMode::NonRecursive)
61                .map_err(|e| anyhow::anyhow!("Failed to watch parent directory: {}", e))?;
62
63            if path.exists() {
64                println!("Watching for changes in {}...", path_str);
65                let res = cache.exec_problem(id, Run::Test, case_str.clone()).await?;
66                println!("{}", res);
67            } else {
68                println!("File {} does not exist. Waiting for creation...", path_str);
69            }
70
71            let mut last_run = Instant::now() - Duration::from_secs(1);
72            while let Ok(res_event) = rx.recv() {
73                match res_event {
74                    Ok(event) if event.paths.iter().any(|p| p == path) => {
75                        if event.kind.is_create() {
76                            println!("File created, watching for changes...");
77                        } else if event.kind.is_modify() {
78                            // Debounce: ignore events within 500ms of the last run
79                            if last_run.elapsed() < Duration::from_millis(500) {
80                                continue;
81                            }
82
83                            // Wait for atomic save to complete (e.g. file content flush)
84                            std::thread::sleep(Duration::from_millis(200));
85                            // Drain any subsequent events generated during the sleep
86                            while rx.try_recv().is_ok() {}
87
88                            if !path.exists() {
89                                continue;
90                            }
91                            println!("File changed, testing again...");
92                            match cache.exec_problem(id, Run::Test, case_str.clone()).await {
93                                Ok(res) => println!("{}", res),
94                                Err(e) => println!("Error: {}", e),
95                            }
96                            last_run = Instant::now();
97                        }
98                    }
99                    Err(e) => println!("watch error: {:?}", e),
100                    _ => {}
101                }
102            }
103        } else {
104            let res = cache.exec_problem(id, Run::Test, case_str).await?;
105            println!("{}", res);
106        }
107        Ok(())
108    }
109}