use crate::utils::format_file_error;
use clap::{Parser, Subcommand};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum VmMode {
Ast,
Bytecode,
}
impl Default for VmMode {
fn default() -> Self {
if let Ok(mode) = std::env::var("RUCHY_VM_MODE") {
match mode.to_lowercase().as_str() {
"bytecode" | "vm" => return VmMode::Bytecode,
_ => return VmMode::Ast,
}
}
VmMode::Ast
}
}
#[derive(Parser, Debug)]
#[command(name = "ruchy")]
#[command(author = "Noah Gift")]
#[command(version = "3.4.1")]
#[command(
about = "The Ruchy programming language - A modern, expressive language for data science"
)]
#[command(long_about = None)]
pub struct Cli {
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, value_enum, global = true, default_value_t = VmMode::default())]
pub vm_mode: VmMode,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Repl,
Run {
path: PathBuf,
},
#[command(visible_alias = "fmt")]
Format {
path: PathBuf,
#[arg(long)]
check: bool,
},
Hunt {
#[arg(default_value = "./examples")]
target: PathBuf,
#[arg(short, long, default_value = "10")]
cycles: u32,
#[arg(long)]
andon: bool,
#[arg(long)]
hansei_report: Option<PathBuf>,
#[arg(long)]
five_whys: bool,
},
Report {
#[arg(default_value = "./examples")]
target: PathBuf,
#[arg(short, long, default_value = "human")]
format: String,
#[arg(short, long)]
output: Option<PathBuf>,
},
#[command(subcommand)]
Notebook(NotebookCommand),
#[command(subcommand)]
Wasm(WasmCommand),
#[command(subcommand)]
Test(TestCommand),
}
#[derive(Subcommand, Debug)]
pub enum NotebookCommand {
Serve {
#[arg(short, long, default_value = "8888")]
port: u16,
#[arg(long, default_value = "127.0.0.1")]
host: String,
#[arg(long)]
pid_file: Option<PathBuf>,
},
Test {
path: PathBuf,
#[arg(long)]
coverage: bool,
#[arg(long, default_value = "text")]
format: String,
},
Convert {
input: PathBuf,
output: PathBuf,
#[arg(long, default_value = "html")]
format: String,
},
}
#[derive(Subcommand, Debug)]
pub enum WasmCommand {
Compile {
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
optimize: bool,
#[arg(long, default_value = "true")]
validate: bool,
},
Run {
module: PathBuf,
args: Vec<String>,
},
Validate {
module: PathBuf,
},
}
#[derive(Subcommand, Debug)]
pub enum TestCommand {
Run {
path: PathBuf,
#[arg(long)]
coverage: bool,
#[arg(long, default_value = "true")]
parallel: bool,
#[arg(long)]
filter: Option<String>,
},
Report {
#[arg(long, default_value = "html")]
format: String,
#[arg(short, long)]
output: Option<PathBuf>,
},
}
impl Cli {
pub fn execute(self) -> Result<(), String> {
match self.command {
#[cfg(not(target_arch = "wasm32"))]
Command::Repl => execute_repl(self.verbose, self.quiet),
#[cfg(target_arch = "wasm32")]
Command::Repl => Err("REPL not available in WASM build".to_string()),
Command::Run { path } => execute_run(path, self.verbose, self.vm_mode),
Command::Format { path, check } => execute_format(path, check),
Command::Hunt {
target,
cycles,
andon,
hansei_report,
five_whys,
} => execute_hunt(
target,
cycles,
andon,
hansei_report,
five_whys,
self.verbose,
),
Command::Report {
target,
format,
output,
} => execute_report(target, format, output, self.verbose),
Command::Notebook(cmd) => execute_notebook(cmd, self.verbose),
Command::Wasm(cmd) => execute_wasm(cmd, self.verbose),
Command::Test(cmd) => execute_test(cmd, self.verbose),
}
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "repl"))]
fn execute_repl(_verbose: bool, quiet: bool) -> Result<(), String> {
if !quiet {
println!("Starting Ruchy REPL v3.4.1...");
}
crate::run_repl().map_err(|e| format!("REPL error: {e}"))
}
#[cfg(not(all(not(target_arch = "wasm32"), feature = "repl")))]
fn execute_repl(_verbose: bool, _quiet: bool) -> Result<(), String> {
Err("REPL not available (requires 'repl' feature)".to_string())
}
fn execute_run(path: PathBuf, verbose: bool, vm_mode: VmMode) -> Result<(), String> {
if verbose {
println!("Running script: {} (mode: {:?})", path.display(), vm_mode);
}
let source = std::fs::read_to_string(&path).map_err(|_e| format_file_error("read", &path))?;
let mut parser = crate::frontend::parser::Parser::new(&source);
let ast = parser.parse().map_err(|e| format!("Parse error: {e:?}"))?;
let resolved_ast = resolve_modules_for_run(&path, ast)?;
match vm_mode {
VmMode::Ast => {
let mut interpreter = crate::runtime::interpreter::Interpreter::new();
interpreter
.eval_expr(&resolved_ast)
.map_err(|e| format!("Evaluation error: {e:?}"))?;
}
VmMode::Bytecode => {
use crate::runtime::bytecode::{Compiler, VM};
let mut compiler = Compiler::new("main".to_string());
compiler
.compile_expr(&resolved_ast)
.map_err(|e| format!("Compilation error: {e}"))?;
let chunk = compiler.finalize();
let mut vm = VM::new();
let _result = vm
.execute(&chunk)
.map_err(|e| format!("VM execution error: {e}"))?;
}
}
Ok(())
}
fn resolve_modules_for_run(
source_path: &Path,
ast: crate::frontend::ast::Expr,
) -> Result<crate::frontend::ast::Expr, String> {
use crate::backend::module_resolver::ModuleResolver;
use crate::frontend::ast::ExprKind;
fn needs_resolution(expr: &crate::frontend::ast::Expr) -> bool {
match &expr.kind {
ExprKind::ModuleDeclaration { .. } => true,
ExprKind::Module { .. } => true,
ExprKind::Import { .. } => true,
ExprKind::ImportAll { .. } => true,
ExprKind::ImportDefault { .. } => true,
ExprKind::Block(exprs) => exprs.iter().any(needs_resolution),
ExprKind::Function { body, .. } => needs_resolution(body),
ExprKind::Let { value, body, .. } => needs_resolution(value) || needs_resolution(body),
_ => false,
}
}
if !needs_resolution(&ast) {
return Ok(ast);
}
let mut resolver = ModuleResolver::new();
if let Some(parent_dir) = source_path.parent() {
resolver.add_search_path(parent_dir);
if let Some(project_root) = parent_dir.parent() {
resolver.add_search_path(project_root.join("src"));
resolver.add_search_path(project_root.join("lib"));
resolver.add_search_path(project_root.join("modules"));
}
}
resolver
.resolve_imports(ast)
.map_err(|e| format!("Module resolution error: {e}"))
}
fn execute_format(path: PathBuf, check: bool) -> Result<(), String> {
use crate::quality::formatter::Formatter;
let config = find_and_load_config(&path)?;
let mut formatter = Formatter::with_config(config);
if check {
check_format(&path, &mut formatter)
} else {
apply_format(&path, &mut formatter)
}
}
fn execute_hunt(
target: PathBuf,
cycles: u32,
andon: bool,
hansei_report: Option<PathBuf>,
five_whys: bool,
verbose: bool,
) -> Result<(), String> {
use crate::hunt_mode::{HuntConfig, HuntMode};
if verbose {
println!("Starting Hunt Mode on: {}", target.display());
println!("Cycles: {cycles}, Andon: {andon}, Five Whys: {five_whys}");
}
let config = HuntConfig {
max_cycles: cycles,
enable_five_whys: five_whys,
verbose,
..Default::default()
};
let mut hunt = HuntMode::with_config(config);
println!("=== HUNT MODE: PDCA Cycle ===");
println!("Target: {}", target.display());
println!();
let outcomes = hunt
.run(cycles)
.map_err(|e| format!("Hunt Mode error: {e}"))?;
println!("=== Hunt Mode Results ===");
println!("Cycles completed: {}", outcomes.len());
let fixes_applied: usize = outcomes.iter().filter(|o| o.fix_applied).count();
println!("Fixes applied: {fixes_applied}");
let status = hunt.andon_status();
println!();
println!("Andon Status: {} {}", status.icon(), status.name());
let metrics = hunt.kaizen_metrics();
println!();
println!("=== Kaizen Metrics ===");
println!("Compilation rate: {:.1}%", metrics.success_rate_percent());
println!("Total fixes: {}", metrics.cumulative_fixes);
println!(
"Improving: {}",
if metrics.is_improving() { "Yes" } else { "No" }
);
if let Some(report_path) = hansei_report {
export_hansei_report(&report_path, hunt.history(), verbose)?;
}
if andon {
println!();
println!("=== Andon Dashboard ===");
display_andon_dashboard(&hunt);
}
Ok(())
}
fn display_andon_dashboard(hunt: &crate::hunt_mode::HuntMode) {
let status = hunt.andon_status();
let metrics = hunt.kaizen_metrics();
println!("┌─────────────────────────────────┐");
println!("│ HUNT MODE ANDON BOARD │");
println!("├─────────────────────────────────┤");
println!("│ Status: {} {:20} │", status.icon(), status.name());
println!("│ Rate: {:>23.1}% │", metrics.success_rate_percent());
println!("│ Fixes: {:>23} │", metrics.cumulative_fixes);
println!("│ Cycles: {:>23} │", metrics.total_cycles);
println!("└─────────────────────────────────┘");
}
fn export_hansei_report(
path: &PathBuf,
history: &[crate::hunt_mode::CycleOutcome],
verbose: bool,
) -> Result<(), String> {
use std::fs::File;
use std::io::Write;
if verbose {
println!("Exporting Hansei report to: {}", path.display());
}
let mut file = File::create(path).map_err(|e| format!("Failed to create report: {e}"))?;
writeln!(file, "# Hunt Mode Hansei Report").map_err(|e| e.to_string())?;
writeln!(file, "## Lessons Learned\n").map_err(|e| e.to_string())?;
for (i, outcome) in history.iter().enumerate() {
writeln!(file, "### Cycle {}", i + 1).map_err(|e| e.to_string())?;
writeln!(file, "- Fix applied: {}", outcome.fix_applied).map_err(|e| e.to_string())?;
writeln!(file, "- Confidence: {:.1}%", outcome.confidence * 100.0)
.map_err(|e| e.to_string())?;
writeln!(file, "- Lessons:").map_err(|e| e.to_string())?;
for lesson in &outcome.lessons {
writeln!(file, " - {lesson}").map_err(|e| e.to_string())?;
}
writeln!(file).map_err(|e| e.to_string())?;
}
println!("Hansei report exported to: {}", path.display());
Ok(())
}
fn execute_report(
target: PathBuf,
format: String,
output: Option<PathBuf>,
verbose: bool,
) -> Result<(), String> {
use crate::reporting::formats::{
HumanFormatter, JsonFormatter, MarkdownFormatter, SarifFormatter,
};
use crate::reporting::{ReportFormatter, TranspileReport};
if verbose {
println!("Generating report for: {}", target.display());
println!("Format: {format}");
}
let files = scan_ruchy_files(&target)?;
if verbose {
println!("Found {} .ruchy files", files.len());
}
let passed = files.len();
let failed = 0;
let report = TranspileReport::new(files.len(), passed, failed);
let formatted = match format.as_str() {
"json" => {
let formatter = JsonFormatter::pretty();
formatter.format(&report)
}
"markdown" | "md" => {
let formatter = MarkdownFormatter;
formatter.format(&report)
}
"sarif" => {
let formatter = SarifFormatter;
formatter.format(&report)
}
_ => {
let formatter = HumanFormatter::default();
formatter.format(&report)
}
};
match output {
Some(path) => {
std::fs::write(&path, formatted).map_err(|e| format!("Failed to write report: {e}"))?;
println!("Report written to: {}", path.display());
}
None => {
println!("{formatted}");
}
}
Ok(())
}
fn scan_ruchy_files(path: &Path) -> Result<Vec<PathBuf>, String> {
use std::fs;
if !path.exists() {
return Err(format!("Path does not exist: {}", path.display()));
}
if path.is_file() {
return Ok(vec![path.to_path_buf()]);
}
let mut files = Vec::new();
fn scan_dir(dir: &Path, files: &mut Vec<PathBuf>) -> std::io::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
scan_dir(&path, files)?;
} else if path.extension().is_some_and(|e| e == "ruchy") {
files.push(path);
}
}
Ok(())
}
scan_dir(path, &mut files).map_err(|e| format!("Failed to scan directory: {e}"))?;
Ok(files)
}
fn check_format(
path: &PathBuf,
formatter: &mut crate::quality::formatter::Formatter,
) -> Result<(), String> {
println!("Checking formatting for: {}", path.display());
let source = std::fs::read_to_string(path).map_err(|_e| format_file_error("read", path))?;
let ast = parse_source(&source)?;
formatter.set_source(&source);
let formatted_code = formatter
.format(&ast)
.map_err(|e| format!("Format error: {e}"))?;
if formatted_code.trim() == source.trim() {
println!("✓ File is properly formatted");
Ok(())
} else {
Err("File is not properly formatted. Run without --check to fix.".to_string())
}
}
fn apply_format(
path: &PathBuf,
formatter: &mut crate::quality::formatter::Formatter,
) -> Result<(), String> {
println!("Formatting: {}", path.display());
let source = std::fs::read_to_string(path).map_err(|_e| format_file_error("read", path))?;
let ast = parse_source(&source)?;
formatter.set_source(&source);
let formatted_code = formatter
.format(&ast)
.map_err(|e| format!("Format error: {e}"))?;
std::fs::write(path, formatted_code).map_err(|e| format!("Failed to write file: {e}"))?;
println!("✓ File formatted successfully");
Ok(())
}
fn parse_source(source: &str) -> Result<crate::frontend::ast::Expr, String> {
let mut parser = crate::frontend::parser::Parser::new(source);
parser.parse().map_err(|e| format!("Parse error: {e:?}"))
}
fn find_and_load_config(start_path: &Path) -> Result<crate::quality::FormatterConfig, String> {
let start_dir = get_start_directory(start_path);
find_config_in_ancestors(&start_dir)
}
fn get_start_directory(path: &Path) -> PathBuf {
if path.is_file() {
path.parent().unwrap_or(path).to_path_buf()
} else {
path.to_path_buf()
}
}
fn find_config_in_ancestors(start_dir: &Path) -> Result<crate::quality::FormatterConfig, String> {
let config_path = start_dir.join(".ruchy-fmt.toml");
if config_path.exists() {
return crate::quality::FormatterConfig::from_file(&config_path);
}
match start_dir.parent() {
Some(parent) => find_config_in_ancestors(&parent.to_path_buf()),
None => Ok(crate::quality::FormatterConfig::default()),
}
}
fn execute_notebook(cmd: NotebookCommand, verbose: bool) -> Result<(), String> {
match cmd {
NotebookCommand::Serve {
port,
host,
pid_file,
} => execute_notebook_serve(port, host, pid_file, verbose),
NotebookCommand::Test {
path,
coverage,
format,
} => execute_notebook_test(path, coverage, format, verbose),
NotebookCommand::Convert {
input,
output,
format,
} => execute_notebook_convert(input, Some(output), format, verbose),
}
}
fn execute_notebook_serve(
port: u16,
host: String,
pid_file: Option<PathBuf>,
verbose: bool,
) -> Result<(), String> {
let _pid_file_guard = if let Some(pid_path) = pid_file {
if verbose {
println!("Creating PID file at: {}", pid_path.display());
}
Some(
crate::server::PidFile::new(pid_path)
.map_err(|e| format!("Failed to create PID file: {e}"))?,
)
} else {
None
};
if verbose {
println!("Starting notebook server on {host}:{port}");
}
#[cfg(feature = "notebook")]
{
let rt =
tokio::runtime::Runtime::new().map_err(|e| format!("Failed to create runtime: {e}"))?;
rt.block_on(async {
crate::notebook::server::start_server(port)
.await
.map_err(|e| format!("Server error: {e}"))
})?;
}
#[cfg(not(feature = "notebook"))]
{
Err("Notebook feature not enabled".to_string())
}
#[cfg(feature = "notebook")]
{
Ok(())
}
}
fn execute_notebook_test(
path: PathBuf,
_coverage: bool,
_format: String,
verbose: bool,
) -> Result<(), String> {
if verbose {
println!("Testing notebook: {}", path.display());
}
#[cfg(feature = "notebook")]
{
let config = crate::notebook::testing::types::TestConfig::default();
let report = run_test_command(&path, config)?;
match _format.as_str() {
"json" => match serde_json::to_string_pretty(&report) {
Ok(json) => println!("{json}"),
Err(e) => eprintln!("Failed to serialize report: {e}"),
},
"html" => println!("HTML report generation not yet implemented"),
_ => println!("{report:#?}"),
}
}
#[cfg(not(feature = "notebook"))]
{
Err("Notebook feature not enabled".to_string())
}
#[cfg(feature = "notebook")]
{
Ok(())
}
}
fn execute_notebook_convert(
input: PathBuf,
_output: Option<PathBuf>,
format: String,
verbose: bool,
) -> Result<(), String> {
if verbose {
println!("Converting {} to {format} format", input.display());
}
Ok(())
}
fn execute_wasm(cmd: WasmCommand, verbose: bool) -> Result<(), String> {
match cmd {
WasmCommand::Compile {
input,
output,
optimize: _,
validate,
} => execute_wasm_compile(input, output, validate, verbose),
WasmCommand::Run { module, args } => execute_wasm_run(module, args, verbose),
WasmCommand::Validate { module } => execute_wasm_validate(module, verbose),
}
}
fn execute_wasm_compile(
input: std::path::PathBuf,
output: Option<std::path::PathBuf>,
validate: bool,
verbose: bool,
) -> Result<(), String> {
if verbose {
println!("Compiling {} to WASM", input.display());
}
let source =
std::fs::read_to_string(&input).map_err(|e| format!("Failed to read file: {e}"))?;
let output_path = output.unwrap_or_else(|| {
let mut path = input.clone();
path.set_extension("wasm");
path
});
compile_wasm_source(&source, &output_path, validate, verbose)
}
#[cfg(feature = "wasm-compile")]
fn compile_wasm_source(
source: &str,
output_path: &std::path::Path,
validate: bool,
verbose: bool,
) -> Result<(), String> {
let mut parser = crate::frontend::parser::Parser::new(source);
let ast = parser.parse().map_err(|e| format!("Parse error: {e:?}"))?;
let emitter = crate::backend::wasm::WasmEmitter::new();
let wasm_bytes = emitter
.emit(&ast)
.map_err(|e| format!("WASM compilation error: {e}"))?;
if validate {
#[cfg(feature = "notebook")]
{
wasmparser::validate(&wasm_bytes).map_err(|e| format!("WASM validation error: {e}"))?;
}
#[cfg(not(feature = "notebook"))]
{
eprintln!("Warning: WASM validation skipped (wasmparser not available)");
}
}
std::fs::write(output_path, wasm_bytes)
.map_err(|e| format!("Failed to write WASM file: {e}"))?;
if verbose {
println!("Successfully compiled to {}", output_path.display());
}
Ok(())
}
#[cfg(not(feature = "wasm-compile"))]
fn compile_wasm_source(
_source: &str,
_output_path: &std::path::Path,
_validate: bool,
_verbose: bool,
) -> Result<(), String> {
Err("WASM compilation feature not enabled".to_string())
}
fn execute_wasm_run(
module: std::path::PathBuf,
_args: Vec<String>,
verbose: bool,
) -> Result<(), String> {
if verbose {
println!("Running WASM module: {}", module.display());
}
Ok(())
}
fn execute_wasm_validate(module: std::path::PathBuf, verbose: bool) -> Result<(), String> {
if verbose {
println!("Validating WASM module: {}", module.display());
}
#[cfg(feature = "notebook")]
{
let bytes = std::fs::read(&module).map_err(|e| format!("Failed to read WASM file: {e}"))?;
wasmparser::validate(&bytes).map_err(|e| format!("WASM validation error: {e}"))?;
println!("✓ WASM module is valid");
Ok(())
}
#[cfg(not(feature = "notebook"))]
{
eprintln!("Warning: WASM validation requires notebook feature");
Err("WASM validation not available without notebook feature".to_string())
}
}
fn execute_test(cmd: TestCommand, verbose: bool) -> Result<(), String> {
match cmd {
TestCommand::Run {
path,
coverage: _,
parallel: _,
filter: _,
} => {
if verbose {
println!("Running tests in {}", path.display());
}
println!("Test runner not yet implemented");
Ok(())
}
TestCommand::Report { format, output: _ } => {
if verbose {
println!("Generating test report in {format} format");
}
Ok(())
}
}
}
#[cfg(feature = "notebook")]
pub fn run_test_command(
_notebook_path: &std::path::Path,
_config: crate::notebook::testing::types::TestConfig,
) -> Result<crate::notebook::testing::types::TestReport, String> {
Ok(crate::notebook::testing::types::TestReport {
total_tests: 1,
passed_tests: 1,
failed_tests: 0,
skipped_tests: 0,
execution_time: std::time::Duration::from_millis(100),
coverage: None,
failures: Vec::new(),
results: vec![crate::notebook::testing::types::TestResult::Pass],
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_cli_creation() {
let cli = Cli {
verbose: false,
quiet: false,
vm_mode: VmMode::default(),
command: Command::Repl,
};
assert!(!cli.verbose);
assert!(!cli.quiet);
assert!(matches!(cli.command, Command::Repl));
}
#[test]
fn test_cli_verbose_quiet_flags() {
let cli = Cli {
verbose: true,
quiet: true,
vm_mode: VmMode::default(),
command: Command::Repl,
};
assert!(cli.verbose);
assert!(cli.quiet);
}
#[test]
fn test_command_run_variant() {
let path = PathBuf::from("test.ruchy");
let command = Command::Run { path: path.clone() };
if let Command::Run { path: p } = command {
assert_eq!(p, path);
} else {
panic!("Expected Run command");
}
}
#[test]
fn test_command_format_variant() {
let path = PathBuf::from("test.ruchy");
let command = Command::Format {
path: path.clone(),
check: true,
};
if let Command::Format { path: p, check: c } = command {
assert_eq!(p, path);
assert!(c);
} else {
panic!("Expected Format command");
}
}
#[test]
fn test_notebook_command_serve() {
let cmd = NotebookCommand::Serve {
port: 8080,
host: "localhost".to_string(),
pid_file: None,
};
if let NotebookCommand::Serve {
port,
host,
pid_file,
} = cmd
{
assert_eq!(port, 8080);
assert_eq!(host, "localhost");
assert_eq!(pid_file, None);
} else {
panic!("Expected Serve command");
}
}
#[test]
fn test_notebook_command_test() {
let cmd = NotebookCommand::Test {
path: PathBuf::from("test.ipynb"),
coverage: true,
format: "json".to_string(),
};
if let NotebookCommand::Test {
path,
coverage,
format,
} = cmd
{
assert_eq!(path, PathBuf::from("test.ipynb"));
assert!(coverage);
assert_eq!(format, "json");
} else {
panic!("Expected Test command");
}
}
#[test]
fn test_notebook_command_convert() {
let cmd = NotebookCommand::Convert {
input: PathBuf::from("input.ipynb"),
output: PathBuf::from("output.html"),
format: "html".to_string(),
};
if let NotebookCommand::Convert {
input,
output,
format,
} = cmd
{
assert_eq!(input, PathBuf::from("input.ipynb"));
assert_eq!(output, PathBuf::from("output.html"));
assert_eq!(format, "html");
} else {
panic!("Expected Convert command");
}
}
#[test]
fn test_wasm_command_compile() {
let cmd = WasmCommand::Compile {
input: PathBuf::from("test.ruchy"),
output: Some(PathBuf::from("test.wasm")),
optimize: true,
validate: false,
};
if let WasmCommand::Compile {
input,
output,
optimize,
validate,
} = cmd
{
assert_eq!(input, PathBuf::from("test.ruchy"));
assert_eq!(output, Some(PathBuf::from("test.wasm")));
assert!(optimize);
assert!(!validate);
} else {
panic!("Expected Compile command");
}
}
#[test]
fn test_wasm_command_run() {
let cmd = WasmCommand::Run {
module: PathBuf::from("test.wasm"),
args: vec!["arg1".to_string(), "arg2".to_string()],
};
if let WasmCommand::Run { module, args } = cmd {
assert_eq!(module, PathBuf::from("test.wasm"));
assert_eq!(args.len(), 2);
assert_eq!(args[0], "arg1");
} else {
panic!("Expected Run command");
}
}
#[test]
fn test_wasm_command_validate() {
let cmd = WasmCommand::Validate {
module: PathBuf::from("test.wasm"),
};
if let WasmCommand::Validate { module } = cmd {
assert_eq!(module, PathBuf::from("test.wasm"));
} else {
panic!("Expected Validate command");
}
}
#[test]
fn test_test_command_run() {
let cmd = TestCommand::Run {
path: PathBuf::from("tests/"),
coverage: true,
parallel: false,
filter: Some("test_".to_string()),
};
if let TestCommand::Run {
path,
coverage,
parallel,
filter,
} = cmd
{
assert_eq!(path, PathBuf::from("tests/"));
assert!(coverage);
assert!(!parallel);
assert_eq!(filter, Some("test_".to_string()));
} else {
panic!("Expected Run command");
}
}
#[test]
fn test_test_command_report() {
let cmd = TestCommand::Report {
format: "junit".to_string(),
output: Some(PathBuf::from("report.xml")),
};
if let TestCommand::Report { format, output } = cmd {
assert_eq!(format, "junit");
assert_eq!(output, Some(PathBuf::from("report.xml")));
} else {
panic!("Expected Report command");
}
}
#[test]
fn test_execute_format_nonexistent_file() {
let path = PathBuf::from("nonexistent.ruchy");
let result = execute_format(path, true);
assert!(result.is_err());
}
#[test]
fn test_execute_run_nonexistent_file() {
let path = PathBuf::from("nonexistent.ruchy");
let result = execute_run(path, false, VmMode::default());
assert!(result.is_err());
}
#[test]
fn test_execute_wasm_run() {
let module = PathBuf::from("test.wasm");
let args = vec![];
let result = execute_wasm_run(module, args, false);
assert!(result.is_ok());
}
#[test]
fn test_execute_wasm_validate() {
let module = PathBuf::from("test.wasm");
let result = execute_wasm_validate(module, false);
assert!(result.is_err());
}
#[test]
fn test_execute_test_run() {
let cmd = TestCommand::Run {
path: PathBuf::from("tests/"),
coverage: false,
parallel: true,
filter: None,
};
let result = execute_test(cmd, false);
assert!(result.is_ok());
}
#[test]
fn test_execute_test_report() {
let cmd = TestCommand::Report {
format: "html".to_string(),
output: None,
};
let result = execute_test(cmd, false);
assert!(result.is_ok());
}
#[test]
#[cfg(feature = "notebook")]
fn test_run_test_command() {
let path = PathBuf::from("test.ipynb");
let config = crate::notebook::testing::types::TestConfig::default();
let result = run_test_command(&path, config);
assert!(result.is_ok());
let report = result.unwrap();
assert_eq!(report.total_tests, 1);
assert_eq!(report.passed_tests, 1);
assert_eq!(report.failed_tests, 0);
}
#[test]
fn test_execute_notebook_serve() {
let cmd = NotebookCommand::Serve {
port: 8888,
host: "127.0.0.1".to_string(),
pid_file: None,
};
if let NotebookCommand::Serve {
port,
host,
pid_file,
} = cmd
{
assert_eq!(port, 8888);
assert_eq!(host, "127.0.0.1");
assert_eq!(pid_file, None);
} else {
panic!("Expected Serve command");
}
#[cfg(not(feature = "notebook"))]
{
let cmd = NotebookCommand::Serve {
port: 8888,
host: "127.0.0.1".to_string(),
pid_file: None,
};
let result = execute_notebook(cmd, false);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Notebook feature not enabled"));
}
#[cfg(feature = "notebook")]
{
}
}
#[test]
fn test_execute_notebook_test() {
let cmd = NotebookCommand::Test {
path: PathBuf::from("test.ipynb"),
coverage: false,
format: "text".to_string(),
};
let result = execute_notebook(cmd, false);
#[cfg(not(feature = "notebook"))]
assert!(result.is_err());
#[cfg(feature = "notebook")]
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_execute_notebook_convert() {
let cmd = NotebookCommand::Convert {
input: PathBuf::from("input.ipynb"),
output: PathBuf::from("output.html"),
format: "html".to_string(),
};
let result = execute_notebook(cmd, false);
assert!(result.is_ok()); }
#[test]
fn test_compile_wasm_source_not_enabled() {
#[cfg(not(feature = "wasm-compile"))]
{
let result = compile_wasm_source("", &PathBuf::from("out.wasm"), false, false);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "WASM compilation feature not enabled");
}
}
#[test]
fn test_parse_source_valid() {
let result = parse_source("let x = 5");
assert!(result.is_ok());
}
#[test]
fn test_parse_source_invalid() {
let result = parse_source("let x = ");
assert!(result.is_err());
}
#[test]
fn test_get_start_directory_file() {
let path = PathBuf::from("/home/user/test.ruchy");
let result = get_start_directory(&path);
assert!(result.as_os_str().len() > 0);
}
#[test]
fn test_get_start_directory_dir() {
let path = PathBuf::from("/tmp");
let result = get_start_directory(&path);
assert_eq!(result, PathBuf::from("/tmp"));
}
#[test]
fn test_find_config_in_ancestors_not_found() {
let result = find_config_in_ancestors(Path::new("/nonexistent/path"));
assert!(result.is_ok());
}
#[test]
fn test_scan_ruchy_files_nonexistent() {
let result = scan_ruchy_files(Path::new("/nonexistent/path"));
assert!(result.is_err());
}
#[test]
fn test_vm_mode_default() {
let mode = VmMode::default();
assert!(matches!(mode, VmMode::Ast | VmMode::Bytecode));
}
#[test]
fn test_vm_mode_variants() {
let _ = VmMode::Ast;
let _ = VmMode::Bytecode;
}
#[test]
fn test_command_hunt_variant() {
let cmd = Command::Hunt {
target: PathBuf::from("test.ruchy"),
cycles: 5,
andon: true,
five_whys: true,
hansei_report: None,
};
if let Command::Hunt {
cycles,
andon,
five_whys,
..
} = cmd
{
assert_eq!(cycles, 5);
assert!(andon);
assert!(five_whys);
} else {
panic!("Expected Hunt command");
}
}
#[test]
fn test_command_report_variant() {
let cmd = Command::Report {
target: PathBuf::from("test.ruchy"),
format: "json".to_string(),
output: None,
};
if let Command::Report {
target,
format,
output,
} = cmd
{
assert_eq!(target, PathBuf::from("test.ruchy"));
assert_eq!(format, "json");
assert!(output.is_none());
} else {
panic!("Expected Report command");
}
}
#[test]
fn test_cli_execute_run_nonexistent() {
let cli = Cli {
verbose: false,
quiet: false,
vm_mode: VmMode::default(),
command: Command::Run {
path: PathBuf::from("nonexistent.ruchy"),
},
};
let result = cli.execute();
assert!(result.is_err());
}
#[test]
fn test_cli_execute_format_nonexistent() {
let cli = Cli {
verbose: false,
quiet: false,
vm_mode: VmMode::default(),
command: Command::Format {
path: PathBuf::from("nonexistent.ruchy"),
check: true,
},
};
let result = cli.execute();
assert!(result.is_err());
}
#[test]
fn test_wasm_command_validate_variant() {
let cmd = WasmCommand::Validate {
module: PathBuf::from("test.wasm"),
};
if let WasmCommand::Validate { module } = cmd {
assert_eq!(module, PathBuf::from("test.wasm"));
} else {
panic!("Expected Validate command");
}
}
#[test]
fn test_execute_wasm_compile_no_output() {
let cmd = WasmCommand::Compile {
input: PathBuf::from("nonexistent.ruchy"),
output: None,
optimize: false,
validate: false,
};
let result = execute_wasm(cmd, false);
assert!(result.is_err());
}
#[test]
fn test_scan_ruchy_files_single_file() {
let temp_dir = std::env::temp_dir();
let test_file = temp_dir.join("test_scan.ruchy");
std::fs::write(&test_file, "let x = 1").ok();
let result = scan_ruchy_files(&test_file);
assert!(result.is_ok());
let files = result.unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files[0], test_file);
std::fs::remove_file(&test_file).ok();
}
#[test]
fn test_resolve_modules_simple_code() {
let source = "let x = 42";
let mut parser = crate::frontend::parser::Parser::new(source);
let ast = parser.parse().expect("Parse failed");
let result = resolve_modules_for_run(Path::new("/tmp/test.ruchy"), ast);
assert!(result.is_ok());
}
#[test]
fn test_vm_mode_from_env_ast() {
std::env::remove_var("RUCHY_VM_MODE");
let mode = VmMode::default();
assert_eq!(mode, VmMode::Ast);
}
#[test]
fn test_execute_wasm_run_verbose() {
let module = PathBuf::from("test.wasm");
let args = vec!["arg1".to_string()];
let result = execute_wasm_run(module, args, true);
assert!(result.is_ok());
}
#[test]
fn test_execute_test_run_verbose() {
let cmd = TestCommand::Run {
path: PathBuf::from("tests/"),
coverage: true,
parallel: true,
filter: Some("filter".to_string()),
};
let result = execute_test(cmd, true);
assert!(result.is_ok());
}
#[test]
fn test_execute_test_report_verbose() {
let cmd = TestCommand::Report {
format: "json".to_string(),
output: Some(PathBuf::from("/tmp/report.json")),
};
let result = execute_test(cmd, true);
assert!(result.is_ok());
}
#[test]
fn test_command_repl_variant() {
let cmd = Command::Repl;
assert!(matches!(cmd, Command::Repl));
}
#[test]
fn test_command_notebook_variant() {
let cmd = Command::Notebook(NotebookCommand::Serve {
port: 8888,
host: "localhost".to_string(),
pid_file: None,
});
assert!(matches!(cmd, Command::Notebook(_)));
}
#[test]
fn test_command_wasm_variant() {
let cmd = Command::Wasm(WasmCommand::Validate {
module: PathBuf::from("test.wasm"),
});
assert!(matches!(cmd, Command::Wasm(_)));
}
#[test]
fn test_command_test_variant() {
let cmd = Command::Test(TestCommand::Report {
format: "html".to_string(),
output: None,
});
assert!(matches!(cmd, Command::Test(_)));
}
#[test]
fn test_execute_notebook_convert_verbose() {
let cmd = NotebookCommand::Convert {
input: PathBuf::from("in.ipynb"),
output: PathBuf::from("out.html"),
format: "markdown".to_string(),
};
let result = execute_notebook(cmd, true);
assert!(result.is_ok());
}
#[test]
fn test_cli_all_options() {
let cli = Cli {
verbose: true,
quiet: true,
vm_mode: VmMode::Bytecode,
command: Command::Repl,
};
assert!(cli.verbose);
assert!(cli.quiet);
assert_eq!(cli.vm_mode, VmMode::Bytecode);
}
#[test]
fn test_parse_source_empty() {
let result = parse_source("");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_parse_source_complex() {
let result = parse_source("let x = 5\nlet y = x + 1\ny");
assert!(result.is_ok());
}
#[test]
fn test_get_start_directory_empty_path() {
let path = PathBuf::from("");
let result = get_start_directory(&path);
assert_eq!(result, PathBuf::from(""));
}
#[test]
fn test_execute_report_verbose() {
let target = PathBuf::from("/this_path_definitely_does_not_exist_abc123xyz789");
let result = execute_report(target, "human".to_string(), None, true);
assert!(result.is_err());
}
#[test]
fn test_execute_report_json_format() {
let target = PathBuf::from("/this_path_definitely_does_not_exist_abc123xyz789");
let result = execute_report(target, "json".to_string(), None, false);
assert!(result.is_err());
}
#[test]
fn test_execute_report_markdown_format() {
let target = PathBuf::from("/this_path_definitely_does_not_exist_abc123xyz789");
let result = execute_report(target, "markdown".to_string(), None, false);
assert!(result.is_err());
}
#[test]
fn test_execute_report_sarif_format() {
let target = PathBuf::from("/this_path_definitely_does_not_exist_abc123xyz789");
let result = execute_report(target, "sarif".to_string(), None, false);
assert!(result.is_err());
}
#[test]
fn test_vm_mode_debug() {
let mode = VmMode::Ast;
let debug_str = format!("{mode:?}");
assert!(debug_str.contains("Ast"));
}
#[test]
fn test_vm_mode_equality() {
let m1 = VmMode::Ast;
let m2 = VmMode::Ast;
let m3 = VmMode::Bytecode;
assert_eq!(m1, m2);
assert_ne!(m1, m3);
}
#[test]
fn test_vm_mode_clone_copy() {
let m1 = VmMode::Ast;
let m2 = m1; let m3 = m1.clone(); assert_eq!(m1, m2);
assert_eq!(m1, m3);
}
#[test]
fn test_vm_mode_default_ast() {
let mode = VmMode::Ast;
assert!(matches!(mode, VmMode::Ast));
}
#[test]
fn test_vm_mode_bytecode() {
let mode = VmMode::Bytecode;
assert!(matches!(mode, VmMode::Bytecode));
let debug = format!("{:?}", mode);
assert!(debug.contains("Bytecode"));
}
#[test]
fn test_execute_run_invalid_path() {
let path = PathBuf::from("/no/such/path/to/script.ruchy");
let result = execute_run(path, false, VmMode::Ast);
assert!(result.is_err());
}
#[test]
fn test_execute_format_invalid_path() {
let path = PathBuf::from("/no/such/path/to/format.ruchy");
let result = execute_format(path, false);
assert!(result.is_err());
}
#[test]
fn test_execute_format_check_invalid_path() {
let path = PathBuf::from("/no/such/path/to/format.ruchy");
let result = execute_format(path, true);
assert!(result.is_err());
}
#[test]
fn test_execute_hunt_invalid_target() {
let target = PathBuf::from("/no/such/target/path");
let result = execute_hunt(target, 1, false, None, false, false);
let _ = result;
}
#[test]
fn test_execute_notebook_serve_default() {
let cmd = NotebookCommand::Serve {
port: 9999,
host: "localhost".to_string(),
pid_file: None,
};
if let NotebookCommand::Serve { port, host, .. } = cmd {
assert_eq!(port, 9999);
assert_eq!(host, "localhost");
}
}
#[test]
fn test_execute_notebook_test_invalid_path() {
let path = PathBuf::from("/no/such/notebook.ipynb");
let result = execute_notebook_test(path, false, "json".to_string(), false);
let _ = result;
}
#[test]
fn test_execute_notebook_convert_invalid_path() {
let input = PathBuf::from("/no/such/input.ipynb");
let output = Some(PathBuf::from("/tmp/output.html"));
let result = execute_notebook_convert(input, output, "html".to_string(), false);
let _ = result;
}
#[test]
fn test_execute_wasm_compile_invalid_path() {
let input = PathBuf::from("/no/such/script.ruchy");
let result = execute_wasm_compile(input, None, false, false);
assert!(result.is_err());
}
#[test]
fn test_execute_wasm_run_invalid_module() {
let module = PathBuf::from("/no/such/module.wasm");
let result = execute_wasm_run(module, vec![], false);
let _ = result;
}
#[test]
fn test_execute_wasm_validate_invalid_module() {
let module = PathBuf::from("/no/such/module.wasm");
let result = execute_wasm_validate(module, false);
assert!(result.is_err());
}
#[test]
fn test_execute_test_run_invalid_path() {
let cmd = TestCommand::Run {
path: PathBuf::from("/no/such/tests"),
coverage: false,
parallel: false,
filter: None,
};
let result = execute_test(cmd, false);
let _ = result;
}
#[test]
fn test_scan_ruchy_files_current_dir() {
let path = PathBuf::from(".");
let result = scan_ruchy_files(&path);
let _ = result;
}
#[test]
fn test_get_start_directory_with_file() {
let path = PathBuf::from("examples/hello.ruchy");
let result = get_start_directory(&path);
let _ = result;
}
#[test]
fn test_get_start_directory_with_dir() {
let path = PathBuf::from("examples");
let result = get_start_directory(&path);
let _ = result;
}
#[test]
fn test_command_hunt_variant_cov() {
let cmd = Command::Hunt {
target: PathBuf::from("examples"),
cycles: 5,
andon: true,
hansei_report: Some(PathBuf::from("report.md")),
five_whys: true,
};
if let Command::Hunt {
target,
cycles,
andon,
hansei_report,
five_whys,
} = cmd
{
assert_eq!(target, PathBuf::from("examples"));
assert_eq!(cycles, 5);
assert!(andon);
assert!(hansei_report.is_some());
assert!(five_whys);
} else {
panic!("Expected Hunt command");
}
}
#[test]
fn test_command_report_variant_cov() {
let cmd = Command::Report {
target: PathBuf::from("examples"),
format: "json".to_string(),
output: Some(PathBuf::from("output.json")),
};
if let Command::Report {
target,
format,
output,
} = cmd
{
assert_eq!(target, PathBuf::from("examples"));
assert_eq!(format, "json");
assert!(output.is_some());
} else {
panic!("Expected Report command");
}
}
#[test]
fn test_parse_source_function() {
let source = "fun add(a, b) { a + b }";
let result = parse_source(source);
assert!(result.is_ok());
}
#[test]
fn test_parse_source_struct() {
let source = "struct Point { x: i64, y: i64 }";
let result = parse_source(source);
assert!(result.is_ok());
}
#[test]
fn test_parse_source_invalid_cov() {
let source = "let x = ";
let result = parse_source(source);
let _ = result;
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod property_tests_mod {
use super::*;
use proptest::prelude::*;
fn arb_vm_mode() -> impl Strategy<Value = VmMode> {
prop_oneof![Just(VmMode::Ast), Just(VmMode::Bytecode),]
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn prop_vm_mode_default_valid(_dummy: u8) {
let mode = VmMode::default();
prop_assert!(mode == VmMode::Ast || mode == VmMode::Bytecode);
}
#[test]
fn prop_vm_mode_clone_eq(mode in arb_vm_mode()) {
let copied = mode; prop_assert_eq!(mode, copied);
}
#[test]
fn prop_pathbuf_from_string(s in "[a-zA-Z0-9_./]{0,50}") {
let path = PathBuf::from(&s);
prop_assert!(path.to_str().is_some());
}
#[test]
fn prop_format_file_error_returns_string(
path in "[a-zA-Z0-9_./]{1,30}",
msg in "[a-zA-Z0-9 ]{1,50}"
) {
let result = format_file_error(&msg, Path::new(&path));
prop_assert!(!result.is_empty());
prop_assert!(result.contains(&path) || result.contains(&msg));
}
#[test]
fn prop_valid_port_range(port in 1u16..=65535) {
let port_str = port.to_string();
let parsed: u16 = port_str.parse().expect("valid port");
prop_assert_eq!(port, parsed);
}
#[test]
fn prop_host_roundtrip(host in "[a-zA-Z0-9.-]{1,50}") {
let host_clone = host.clone();
prop_assert_eq!(host, host_clone);
}
#[test]
fn prop_notebook_serve_creation(port in 1u16..=65535) {
let cmd = NotebookCommand::Serve {
port,
host: "127.0.0.1".to_string(),
pid_file: None,
};
match cmd {
NotebookCommand::Serve { port: p, .. } => prop_assert_eq!(p, port),
_ => prop_assert!(false, "Expected Serve variant"),
}
}
#[test]
fn prop_notebook_test_creation(coverage in proptest::bool::ANY) {
let cmd = NotebookCommand::Test {
path: PathBuf::from("test.ipynb"),
coverage,
format: "text".to_string(),
};
match cmd {
NotebookCommand::Test { coverage: c, .. } => prop_assert_eq!(c, coverage),
_ => prop_assert!(false, "Expected Test variant"),
}
}
#[test]
fn prop_notebook_convert_creation(fmt in "html|markdown|script") {
let cmd = NotebookCommand::Convert {
input: PathBuf::from("in.ipynb"),
output: PathBuf::from("out.html"),
format: fmt.clone(),
};
match cmd {
NotebookCommand::Convert { format: f, .. } => prop_assert_eq!(f, fmt),
_ => prop_assert!(false, "Expected Convert variant"),
}
}
#[test]
fn prop_wasm_compile_creation(optimize in proptest::bool::ANY) {
let cmd = WasmCommand::Compile {
input: PathBuf::from("main.ruchy"),
output: Some(PathBuf::from("out.wasm")),
optimize,
validate: true,
};
match cmd {
WasmCommand::Compile { optimize: o, .. } => prop_assert_eq!(o, optimize),
_ => prop_assert!(false, "Expected Compile variant"),
}
}
#[test]
fn prop_wasm_run_creation(num_args in 0usize..5) {
let args: Vec<String> = (0..num_args).map(|i| format!("arg{i}")).collect();
let cmd = WasmCommand::Run {
module: PathBuf::from("module.wasm"),
args,
};
match cmd {
WasmCommand::Run { args: a, .. } => prop_assert_eq!(a.len(), num_args),
WasmCommand::Compile { .. } | WasmCommand::Validate { .. } => {
prop_assert!(false, "Expected Run variant");
}
}
}
#[test]
fn prop_test_run_creation(coverage in proptest::bool::ANY, parallel in proptest::bool::ANY) {
let cmd = TestCommand::Run {
path: PathBuf::from("tests"),
coverage,
parallel,
filter: None,
};
match cmd {
TestCommand::Run { coverage: c, parallel: p, .. } => {
prop_assert_eq!(c, coverage);
prop_assert_eq!(p, parallel);
}
TestCommand::Report { .. } => prop_assert!(false, "Expected Run variant"),
}
}
#[test]
fn prop_test_report_creation(fmt in "json|html|junit") {
let cmd = TestCommand::Report {
format: fmt.clone(),
output: None,
};
match cmd {
TestCommand::Report { format: f, .. } => prop_assert_eq!(f, fmt),
TestCommand::Run { .. } => prop_assert!(false, "Expected Report variant"),
}
}
#[test]
fn prop_hunt_cycles_non_negative(cycles in 0u32..100) {
let andon = cycles % 2 == 0;
let five_whys = cycles % 3 == 0;
prop_assert!(cycles < 100);
prop_assert_eq!(andon, cycles % 2 == 0);
prop_assert_eq!(five_whys, cycles % 3 == 0);
}
#[test]
fn prop_report_format_valid(fmt in "human|json|markdown|sarif") {
let valid_formats = ["human", "json", "markdown", "sarif"];
prop_assert!(valid_formats.contains(&fmt.as_str()));
}
}
}