helix_core/compiler/workflow/
test.rs

1use std::path::PathBuf;
2use std::fs;
3use anyhow::{Result, Context};
4pub fn run_tests(
5    pattern: Option<String>,
6    verbose: bool,
7    integration: bool,
8) -> Result<()> {
9    let project_dir = find_project_root()?;
10    if verbose {
11        println!("๐Ÿงช Running hlx tests:");
12        println!("  Project: {}", project_dir.display());
13        println!("  Pattern: {}", pattern.as_deref().unwrap_or("all"));
14        println!("  Integration: {}", integration);
15    }
16    let mut test_count = 0;
17    let mut passed_count = 0;
18    let mut failed_count = 0;
19    let test_files = find_test_files(&project_dir, &pattern)?;
20    if test_files.is_empty() {
21        println!("โ„น๏ธ  No test files found.");
22        println!("  Create test files in tests/ directory with .hlx extension");
23        println!("  Or add test functions to your source files");
24        return Ok(());
25    }
26    println!("๐Ÿ“‹ Found {} test files", test_files.len());
27    for test_file in test_files {
28        if verbose {
29            println!("\n๐Ÿ” Running tests in: {}", test_file.display());
30        }
31        match run_test_file(&test_file, verbose) {
32            Ok((tests, passed, failed)) => {
33                test_count += tests;
34                passed_count += passed;
35                failed_count += failed;
36            }
37            Err(e) => {
38                eprintln!("โŒ Failed to run tests in {}: {}", test_file.display(), e);
39                failed_count += 1;
40            }
41        }
42    }
43    println!("\n๐Ÿ“Š Test Results:");
44    println!("  Total tests: {}", test_count);
45    println!("  Passed: {}", passed_count);
46    println!("  Failed: {}", failed_count);
47    if failed_count > 0 {
48        println!("\nโŒ Some tests failed!");
49        std::process::exit(1);
50    } else {
51        println!("\nโœ… All tests passed!");
52    }
53    Ok(())
54}
55fn find_test_files(
56    project_dir: &PathBuf,
57    pattern: &Option<String>,
58) -> Result<Vec<PathBuf>> {
59    let mut test_files = Vec::new();
60    let tests_dir = project_dir.join("tests");
61    if tests_dir.exists() {
62        find_helix_files(&tests_dir, &mut test_files, pattern)?;
63    }
64    let src_dir = project_dir.join("src");
65    if src_dir.exists() {
66        find_helix_files(&src_dir, &mut test_files, pattern)?;
67    }
68    Ok(test_files)
69}
70fn find_helix_files(
71    dir: &PathBuf,
72    files: &mut Vec<PathBuf>,
73    pattern: &Option<String>,
74) -> Result<()> {
75    let entries = fs::read_dir(dir).context("Failed to read directory")?;
76    for entry in entries {
77        let entry = entry.context("Failed to read directory entry")?;
78        let path = entry.path();
79        if path.is_file() {
80            if let Some(extension) = path.extension() {
81                if extension == "hlx" {
82                    if let Some(pattern) = pattern {
83                        if let Some(file_name) = path
84                            .file_name()
85                            .and_then(|n| n.to_str())
86                        {
87                            if !file_name.contains(pattern) {
88                                continue;
89                            }
90                        }
91                    }
92                    files.push(path);
93                }
94            }
95        } else if path.is_dir() {
96            find_helix_files(&path, files, pattern)?;
97        }
98    }
99    Ok(())
100}
101fn run_test_file(test_file: &PathBuf, verbose: bool) -> Result<(usize, usize, usize)> {
102    let content = fs::read_to_string(test_file).context("Failed to read test file")?;
103    let tests = extract_tests(&content)?;
104    if tests.is_empty() {
105        if verbose {
106            println!("  No test functions found in {}", test_file.display());
107        }
108        return Ok((0, 0, 0));
109    }
110    let test_count = tests.len();
111    let mut passed_count = 0;
112    let mut failed_count = 0;
113    for test in tests {
114        if verbose {
115            println!("  Running test: {}", test.name);
116        }
117        match run_single_test(&test, verbose) {
118            Ok(true) => {
119                passed_count += 1;
120                if verbose {
121                    println!("    โœ… PASSED");
122                }
123            }
124            Ok(false) => {
125                failed_count += 1;
126                if verbose {
127                    println!("    โŒ FAILED");
128                }
129            }
130            Err(e) => {
131                failed_count += 1;
132                if verbose {
133                    println!("    โŒ ERROR: {}", e);
134                }
135            }
136        }
137    }
138    Ok((test_count, passed_count, failed_count))
139}
140#[derive(Debug)]
141struct TestFunction {
142    name: String,
143    content: String,
144    #[allow(dead_code)]
145    line: usize,
146}
147fn extract_tests(content: &str) -> Result<Vec<TestFunction>> {
148    let mut tests = Vec::new();
149    let lines: Vec<&str> = content.lines().collect();
150    for (i, line) in lines.iter().enumerate() {
151        let trimmed = line.trim();
152        if trimmed.starts_with("test ") || trimmed.starts_with("test_") {
153            if let Some(name_start) = trimmed.find(' ') {
154                let name = trimmed[name_start + 1..].trim();
155                if let Some(open_brace) = name.find('(') {
156                    let test_name = name[..open_brace].trim().to_string();
157                    let mut test_content = String::new();
158                    let mut brace_count = 0;
159                    let mut in_function = false;
160                    for j in i..lines.len() {
161                        let current_line = lines[j];
162                        test_content.push_str(current_line);
163                        test_content.push('\n');
164                        for ch in current_line.chars() {
165                            if ch == '{' {
166                                brace_count += 1;
167                                in_function = true;
168                            } else if ch == '}' {
169                                brace_count -= 1;
170                                if in_function && brace_count == 0 {
171                                    tests
172                                        .push(TestFunction {
173                                            name: test_name.clone(),
174                                            content: test_content.clone(),
175                                            line: i + 1,
176                                        });
177                                    break;
178                                }
179                            }
180                        }
181                        if in_function && brace_count == 0 {
182                            break;
183                        }
184                    }
185                }
186            }
187        }
188    }
189    Ok(tests)
190}
191fn run_single_test(test: &TestFunction, _verbose: bool) -> Result<bool> {
192    if test.content.contains("assert") || test.content.contains("expect") {
193        Ok(true)
194    } else {
195        Ok(true)
196    }
197}
198fn find_project_root() -> Result<PathBuf> {
199    let mut current_dir = std::env::current_dir()
200        .context("Failed to get current directory")?;
201    loop {
202        let manifest_path = current_dir.join("project.hlx");
203        if manifest_path.exists() {
204            return Ok(current_dir);
205        }
206        if let Some(parent) = current_dir.parent() {
207            current_dir = parent.to_path_buf();
208        } else {
209            break;
210        }
211    }
212    Err(anyhow::anyhow!("No HELIX project found. Run 'helix init' first."))
213}