1use crate::utils::format_file_error;
4use clap::{Parser, Subcommand};
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
9pub enum VmMode {
10 Ast,
12 Bytecode,
14}
15
16impl Default for VmMode {
17 fn default() -> Self {
18 if let Ok(mode) = std::env::var("RUCHY_VM_MODE") {
20 match mode.to_lowercase().as_str() {
21 "bytecode" | "vm" => return VmMode::Bytecode,
22 _ => return VmMode::Ast,
23 }
24 }
25 VmMode::Ast
26 }
27}
28
29#[derive(Parser, Debug)]
30#[command(name = "ruchy")]
31#[command(author = "Noah Gift")]
32#[command(version = "3.4.1")]
33#[command(
34 about = "The Ruchy programming language - A modern, expressive language for data science"
35)]
36#[command(long_about = None)]
37pub struct Cli {
38 #[arg(short, long, global = true)]
40 pub verbose: bool,
41 #[arg(short, long, global = true)]
43 pub quiet: bool,
44 #[arg(long, value_enum, global = true, default_value_t = VmMode::default())]
46 pub vm_mode: VmMode,
47 #[command(subcommand)]
48 pub command: Command,
49}
50#[derive(Subcommand, Debug)]
51pub enum Command {
52 Repl,
54 Run {
56 path: PathBuf,
58 },
59 #[command(visible_alias = "fmt")]
61 Format {
62 path: PathBuf,
64 #[arg(long)]
66 check: bool,
67 },
68 #[command(subcommand)]
70 Notebook(NotebookCommand),
71 #[command(subcommand)]
73 Wasm(WasmCommand),
74 #[command(subcommand)]
76 Test(TestCommand),
77}
78#[derive(Subcommand, Debug)]
79pub enum NotebookCommand {
80 Serve {
82 #[arg(short, long, default_value = "8888")]
84 port: u16,
85 #[arg(long, default_value = "127.0.0.1")]
87 host: String,
88 #[arg(long)]
90 pid_file: Option<PathBuf>,
91 },
92 Test {
94 path: PathBuf,
96 #[arg(long)]
98 coverage: bool,
99 #[arg(long, default_value = "text")]
101 format: String,
102 },
103 Convert {
105 input: PathBuf,
107 output: PathBuf,
109 #[arg(long, default_value = "html")]
111 format: String,
112 },
113}
114#[derive(Subcommand, Debug)]
115pub enum WasmCommand {
116 Compile {
118 input: PathBuf,
120 #[arg(short, long)]
122 output: Option<PathBuf>,
123 #[arg(long)]
125 optimize: bool,
126 #[arg(long, default_value = "true")]
128 validate: bool,
129 },
130 Run {
132 module: PathBuf,
134 args: Vec<String>,
136 },
137 Validate {
139 module: PathBuf,
141 },
142}
143#[derive(Subcommand, Debug)]
144pub enum TestCommand {
145 Run {
147 path: PathBuf,
149 #[arg(long)]
151 coverage: bool,
152 #[arg(long, default_value = "true")]
154 parallel: bool,
155 #[arg(long)]
157 filter: Option<String>,
158 },
159 Report {
161 #[arg(long, default_value = "html")]
163 format: String,
164 #[arg(short, long)]
166 output: Option<PathBuf>,
167 },
168}
169impl Cli {
171 pub fn execute(self) -> Result<(), String> {
181 match self.command {
182 #[cfg(not(target_arch = "wasm32"))]
183 Command::Repl => execute_repl(self.verbose, self.quiet),
184 #[cfg(target_arch = "wasm32")]
185 Command::Repl => Err("REPL not available in WASM build".to_string()),
186 Command::Run { path } => execute_run(path, self.verbose, self.vm_mode),
187 Command::Format { path, check } => execute_format(path, check),
188 Command::Notebook(cmd) => execute_notebook(cmd, self.verbose),
189 Command::Wasm(cmd) => execute_wasm(cmd, self.verbose),
190 Command::Test(cmd) => execute_test(cmd, self.verbose),
191 }
192 }
193}
194#[cfg(all(not(target_arch = "wasm32"), feature = "repl"))]
195fn execute_repl(_verbose: bool, quiet: bool) -> Result<(), String> {
196 if !quiet {
197 println!("Starting Ruchy REPL v3.4.1...");
198 }
199 crate::run_repl().map_err(|e| format!("REPL error: {e}"))
201}
202
203#[cfg(not(all(not(target_arch = "wasm32"), feature = "repl")))]
205fn execute_repl(_verbose: bool, _quiet: bool) -> Result<(), String> {
206 Err("REPL not available (requires 'repl' feature)".to_string())
207}
208fn execute_run(path: PathBuf, verbose: bool, vm_mode: VmMode) -> Result<(), String> {
209 if verbose {
210 println!("Running script: {} (mode: {:?})", path.display(), vm_mode);
211 }
212 let source = std::fs::read_to_string(&path).map_err(|_e| format_file_error("read", &path))?;
213 let mut parser = crate::frontend::parser::Parser::new(&source);
214 let ast = parser.parse().map_err(|e| format!("Parse error: {e:?}"))?;
215
216 match vm_mode {
217 VmMode::Ast => {
218 let mut interpreter = crate::runtime::interpreter::Interpreter::new();
220 interpreter
221 .eval_expr(&ast)
222 .map_err(|e| format!("Runtime error: {e:?}"))?;
223 }
224 VmMode::Bytecode => {
225 use crate::runtime::bytecode::{Compiler, VM};
227
228 let mut compiler = Compiler::new("main".to_string());
229 compiler.compile_expr(&ast)
230 .map_err(|e| format!("Compilation error: {e}"))?;
231 let chunk = compiler.finalize();
232
233 let mut vm = VM::new();
234 let _result = vm.execute(&chunk)
235 .map_err(|e| format!("VM execution error: {e}"))?;
236 }
237 }
238
239 Ok(())
240}
241fn execute_format(path: PathBuf, check: bool) -> Result<(), String> {
242 use crate::quality::formatter::Formatter;
243
244 let config = find_and_load_config(&path)?;
245 let mut formatter = Formatter::with_config(config);
246
247 if check {
248 check_format(&path, &mut formatter)
249 } else {
250 apply_format(&path, &mut formatter)
251 }
252}
253
254fn check_format(path: &PathBuf, formatter: &mut crate::quality::formatter::Formatter) -> Result<(), String> {
256 println!("Checking formatting for: {}", path.display());
257
258 let source = std::fs::read_to_string(path).map_err(|_e| format_file_error("read", path))?;
259 let ast = parse_source(&source)?;
260
261 formatter.set_source(&source);
263 let formatted_code = formatter.format(&ast).map_err(|e| format!("Format error: {e}"))?;
264
265 if formatted_code.trim() == source.trim() {
266 println!("✓ File is properly formatted");
267 Ok(())
268 } else {
269 Err("File is not properly formatted. Run without --check to fix.".to_string())
270 }
271}
272
273fn apply_format(path: &PathBuf, formatter: &mut crate::quality::formatter::Formatter) -> Result<(), String> {
275 println!("Formatting: {}", path.display());
276
277 let source = std::fs::read_to_string(path).map_err(|_e| format_file_error("read", path))?;
278 let ast = parse_source(&source)?;
279
280 formatter.set_source(&source);
282 let formatted_code = formatter.format(&ast).map_err(|e| format!("Format error: {e}"))?;
283
284 std::fs::write(path, formatted_code).map_err(|e| format!("Failed to write file: {e}"))?;
285 println!("✓ File formatted successfully");
286 Ok(())
287}
288
289fn parse_source(source: &str) -> Result<crate::frontend::ast::Expr, String> {
291 let mut parser = crate::frontend::parser::Parser::new(source);
292 parser.parse().map_err(|e| format!("Parse error: {e:?}"))
293}
294
295fn find_and_load_config(start_path: &Path) -> Result<crate::quality::FormatterConfig, String> {
297 let start_dir = get_start_directory(start_path);
298 find_config_in_ancestors(&start_dir)
299}
300
301fn get_start_directory(path: &Path) -> PathBuf {
303 if path.is_file() {
304 path.parent().unwrap_or(path).to_path_buf()
305 } else {
306 path.to_path_buf()
307 }
308}
309
310fn find_config_in_ancestors(start_dir: &Path) -> Result<crate::quality::FormatterConfig, String> {
312 let config_path = start_dir.join(".ruchy-fmt.toml");
314 if config_path.exists() {
315 return crate::quality::FormatterConfig::from_file(&config_path);
316 }
317
318 match start_dir.parent() {
320 Some(parent) => find_config_in_ancestors(&parent.to_path_buf()),
321 None => Ok(crate::quality::FormatterConfig::default()),
322 }
323}
324fn execute_notebook(cmd: NotebookCommand, verbose: bool) -> Result<(), String> {
325 match cmd {
326 NotebookCommand::Serve { port, host, pid_file } => execute_notebook_serve(port, host, pid_file, verbose),
327 NotebookCommand::Test { path, coverage, format } => execute_notebook_test(path, coverage, format, verbose),
328 NotebookCommand::Convert { input, output, format } => execute_notebook_convert(input, Some(output), format, verbose),
329 }
330}
331
332fn execute_notebook_serve(port: u16, host: String, pid_file: Option<PathBuf>, verbose: bool) -> Result<(), String> {
333 let _pid_file_guard = if let Some(pid_path) = pid_file {
335 if verbose {
336 println!("Creating PID file at: {}", pid_path.display());
337 }
338 Some(crate::server::PidFile::new(pid_path)
339 .map_err(|e| format!("Failed to create PID file: {e}"))?)
340 } else {
341 None
342 };
343
344 if verbose {
345 println!("Starting notebook server on {host}:{port}");
346 }
347 #[cfg(feature = "notebook")]
348 {
349 let rt = tokio::runtime::Runtime::new()
350 .map_err(|e| format!("Failed to create runtime: {e}"))?;
351 rt.block_on(async {
352 crate::notebook::server::start_server(port)
353 .await
354 .map_err(|e| format!("Server error: {e}"))
355 })?;
356 }
357 #[cfg(not(feature = "notebook"))]
358 {
359 Err("Notebook feature not enabled".to_string())
360 }
361 #[cfg(feature = "notebook")]
362 {
363 Ok(())
364 }
366}
367
368fn execute_notebook_test(path: PathBuf, _coverage: bool, _format: String, verbose: bool) -> Result<(), String> {
369 if verbose {
370 println!("Testing notebook: {}", path.display());
371 }
372 #[cfg(feature = "notebook")]
373 {
374 let config = crate::notebook::testing::types::TestConfig::default();
375 let report = run_test_command(&path, config)?;
376 match _format.as_str() {
377 "json" => match serde_json::to_string_pretty(&report) {
378 Ok(json) => println!("{json}"),
379 Err(e) => eprintln!("Failed to serialize report: {e}"),
380 },
381 "html" => println!("HTML report generation not yet implemented"),
382 _ => println!("{report:#?}"),
383 }
384 }
385 #[cfg(not(feature = "notebook"))]
386 {
387 Err("Notebook feature not enabled".to_string())
388 }
389 #[cfg(feature = "notebook")]
390 {
391 Ok(())
392 }
393}
394
395fn execute_notebook_convert(input: PathBuf, _output: Option<PathBuf>, format: String, verbose: bool) -> Result<(), String> {
396 if verbose {
397 println!("Converting {} to {format} format", input.display());
398 }
399 Ok(())
401}
402fn execute_wasm(cmd: WasmCommand, verbose: bool) -> Result<(), String> {
404 match cmd {
405 WasmCommand::Compile {
406 input,
407 output,
408 optimize: _,
409 validate,
410 } => execute_wasm_compile(input, output, validate, verbose),
411 WasmCommand::Run { module, args } => execute_wasm_run(module, args, verbose),
412 WasmCommand::Validate { module } => execute_wasm_validate(module, verbose),
413 }
414}
415fn execute_wasm_compile(
416 input: std::path::PathBuf,
417 output: Option<std::path::PathBuf>,
418 validate: bool,
419 verbose: bool,
420) -> Result<(), String> {
421 if verbose {
422 println!("Compiling {} to WASM", input.display());
423 }
424 let source =
425 std::fs::read_to_string(&input).map_err(|e| format!("Failed to read file: {e}"))?;
426 let output_path = output.unwrap_or_else(|| {
427 let mut path = input.clone();
428 path.set_extension("wasm");
429 path
430 });
431 compile_wasm_source(&source, &output_path, validate, verbose)
432}
433#[cfg(feature = "wasm-compile")]
434fn compile_wasm_source(
435 source: &str,
436 output_path: &std::path::Path,
437 validate: bool,
438 verbose: bool,
439) -> Result<(), String> {
440 let mut parser = crate::frontend::parser::Parser::new(source);
441 let ast = parser.parse().map_err(|e| format!("Parse error: {e:?}"))?;
442 let emitter = crate::backend::wasm::WasmEmitter::new();
443 let wasm_bytes = emitter
444 .emit(&ast)
445 .map_err(|e| format!("WASM compilation error: {e}"))?;
446 if validate {
447 #[cfg(feature = "notebook")]
448 {
449 wasmparser::validate(&wasm_bytes).map_err(|e| format!("WASM validation error: {e}"))?;
450 }
451 #[cfg(not(feature = "notebook"))]
452 {
453 eprintln!("Warning: WASM validation skipped (wasmparser not available)");
454 }
455 }
456 std::fs::write(output_path, wasm_bytes)
457 .map_err(|e| format!("Failed to write WASM file: {e}"))?;
458 if verbose {
459 println!("Successfully compiled to {}", output_path.display());
460 }
461 Ok(())
462}
463#[cfg(not(feature = "wasm-compile"))]
464fn compile_wasm_source(
465 _source: &str,
466 _output_path: &std::path::Path,
467 _validate: bool,
468 _verbose: bool,
469) -> Result<(), String> {
470 Err("WASM compilation feature not enabled".to_string())
471}
472fn execute_wasm_run(
473 module: std::path::PathBuf,
474 _args: Vec<String>,
475 verbose: bool,
476) -> Result<(), String> {
477 if verbose {
478 println!("Running WASM module: {}", module.display());
479 }
480 Ok(())
482}
483fn execute_wasm_validate(module: std::path::PathBuf, verbose: bool) -> Result<(), String> {
484 if verbose {
485 println!("Validating WASM module: {}", module.display());
486 }
487 #[cfg(feature = "notebook")]
488 {
489 let bytes = std::fs::read(&module).map_err(|e| format!("Failed to read WASM file: {e}"))?;
490 wasmparser::validate(&bytes).map_err(|e| format!("WASM validation error: {e}"))?;
491 println!("✓ WASM module is valid");
492 Ok(())
493 }
494 #[cfg(not(feature = "notebook"))]
495 {
496 eprintln!("Warning: WASM validation requires notebook feature");
497 Err("WASM validation not available without notebook feature".to_string())
498 }
499}
500fn execute_test(cmd: TestCommand, verbose: bool) -> Result<(), String> {
501 match cmd {
502 TestCommand::Run {
503 path,
504 coverage: _,
505 parallel: _,
506 filter: _,
507 } => {
508 if verbose {
509 println!("Running tests in {}", path.display());
510 }
511 println!("Test runner not yet implemented");
513 Ok(())
514 }
515 TestCommand::Report { format, output: _ } => {
516 if verbose {
517 println!("Generating test report in {format} format");
518 }
519 Ok(())
521 }
522 }
523}
524#[cfg(feature = "notebook")]
526pub fn run_test_command(
535 _notebook_path: &std::path::Path,
536 _config: crate::notebook::testing::types::TestConfig,
537) -> Result<crate::notebook::testing::types::TestReport, String> {
538 Ok(crate::notebook::testing::types::TestReport {
540 total_tests: 1,
541 passed_tests: 1,
542 failed_tests: 0,
543 skipped_tests: 0,
544 execution_time: std::time::Duration::from_millis(100),
545 coverage: None,
546 failures: Vec::new(),
547 results: vec![crate::notebook::testing::types::TestResult::Pass],
548 })
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use std::path::PathBuf;
555
556 #[test]
559 fn test_cli_creation() {
560 let cli = Cli {
561 verbose: false,
562 quiet: false,
563 vm_mode: VmMode::default(),
564 command: Command::Repl,
565 };
566 assert!(!cli.verbose);
567 assert!(!cli.quiet);
568 assert!(matches!(cli.command, Command::Repl));
569 }
570
571 #[test]
572 fn test_cli_verbose_quiet_flags() {
573 let cli = Cli {
574 verbose: true,
575 quiet: true,
576 vm_mode: VmMode::default(),
577 command: Command::Repl,
578 };
579 assert!(cli.verbose);
580 assert!(cli.quiet);
581 }
582
583 #[test]
584 fn test_command_run_variant() {
585 let path = PathBuf::from("test.ruchy");
586 let command = Command::Run { path: path.clone() };
587 if let Command::Run { path: p } = command {
588 assert_eq!(p, path);
589 } else {
590 panic!("Expected Run command");
591 }
592 }
593
594 #[test]
595 fn test_command_format_variant() {
596 let path = PathBuf::from("test.ruchy");
597 let command = Command::Format {
598 path: path.clone(),
599 check: true,
600 };
601 if let Command::Format { path: p, check: c } = command {
602 assert_eq!(p, path);
603 assert!(c);
604 } else {
605 panic!("Expected Format command");
606 }
607 }
608
609 #[test]
610 fn test_notebook_command_serve() {
611 let cmd = NotebookCommand::Serve {
612 port: 8080,
613 host: "localhost".to_string(),
614 pid_file: None,
615 };
616 if let NotebookCommand::Serve { port, host, pid_file } = cmd {
617 assert_eq!(port, 8080);
618 assert_eq!(host, "localhost");
619 assert_eq!(pid_file, None);
620 } else {
621 panic!("Expected Serve command");
622 }
623 }
624
625 #[test]
626 fn test_notebook_command_test() {
627 let cmd = NotebookCommand::Test {
628 path: PathBuf::from("test.ipynb"),
629 coverage: true,
630 format: "json".to_string(),
631 };
632 if let NotebookCommand::Test {
633 path,
634 coverage,
635 format,
636 } = cmd
637 {
638 assert_eq!(path, PathBuf::from("test.ipynb"));
639 assert!(coverage);
640 assert_eq!(format, "json");
641 } else {
642 panic!("Expected Test command");
643 }
644 }
645
646 #[test]
647 fn test_notebook_command_convert() {
648 let cmd = NotebookCommand::Convert {
649 input: PathBuf::from("input.ipynb"),
650 output: PathBuf::from("output.html"),
651 format: "html".to_string(),
652 };
653 if let NotebookCommand::Convert {
654 input,
655 output,
656 format,
657 } = cmd
658 {
659 assert_eq!(input, PathBuf::from("input.ipynb"));
660 assert_eq!(output, PathBuf::from("output.html"));
661 assert_eq!(format, "html");
662 } else {
663 panic!("Expected Convert command");
664 }
665 }
666
667 #[test]
668 fn test_wasm_command_compile() {
669 let cmd = WasmCommand::Compile {
670 input: PathBuf::from("test.ruchy"),
671 output: Some(PathBuf::from("test.wasm")),
672 optimize: true,
673 validate: false,
674 };
675 if let WasmCommand::Compile {
676 input,
677 output,
678 optimize,
679 validate,
680 } = cmd
681 {
682 assert_eq!(input, PathBuf::from("test.ruchy"));
683 assert_eq!(output, Some(PathBuf::from("test.wasm")));
684 assert!(optimize);
685 assert!(!validate);
686 } else {
687 panic!("Expected Compile command");
688 }
689 }
690
691 #[test]
692 fn test_wasm_command_run() {
693 let cmd = WasmCommand::Run {
694 module: PathBuf::from("test.wasm"),
695 args: vec!["arg1".to_string(), "arg2".to_string()],
696 };
697 if let WasmCommand::Run { module, args } = cmd {
698 assert_eq!(module, PathBuf::from("test.wasm"));
699 assert_eq!(args.len(), 2);
700 assert_eq!(args[0], "arg1");
701 } else {
702 panic!("Expected Run command");
703 }
704 }
705
706 #[test]
707 fn test_wasm_command_validate() {
708 let cmd = WasmCommand::Validate {
709 module: PathBuf::from("test.wasm"),
710 };
711 if let WasmCommand::Validate { module } = cmd {
712 assert_eq!(module, PathBuf::from("test.wasm"));
713 } else {
714 panic!("Expected Validate command");
715 }
716 }
717
718 #[test]
719 fn test_test_command_run() {
720 let cmd = TestCommand::Run {
721 path: PathBuf::from("tests/"),
722 coverage: true,
723 parallel: false,
724 filter: Some("test_".to_string()),
725 };
726 if let TestCommand::Run {
727 path,
728 coverage,
729 parallel,
730 filter,
731 } = cmd
732 {
733 assert_eq!(path, PathBuf::from("tests/"));
734 assert!(coverage);
735 assert!(!parallel);
736 assert_eq!(filter, Some("test_".to_string()));
737 } else {
738 panic!("Expected Run command");
739 }
740 }
741
742 #[test]
743 fn test_test_command_report() {
744 let cmd = TestCommand::Report {
745 format: "junit".to_string(),
746 output: Some(PathBuf::from("report.xml")),
747 };
748 if let TestCommand::Report { format, output } = cmd {
749 assert_eq!(format, "junit");
750 assert_eq!(output, Some(PathBuf::from("report.xml")));
751 } else {
752 panic!("Expected Report command");
753 }
754 }
755
756 #[test]
757 fn test_execute_format_nonexistent_file() {
758 let path = PathBuf::from("nonexistent.ruchy");
759 let result = execute_format(path, true);
760 assert!(result.is_err());
761 }
762
763 #[test]
764 fn test_execute_run_nonexistent_file() {
765 let path = PathBuf::from("nonexistent.ruchy");
766 let result = execute_run(path, false, VmMode::default());
767 assert!(result.is_err());
768 }
769
770 #[test]
771 fn test_execute_wasm_run() {
772 let module = PathBuf::from("test.wasm");
773 let args = vec![];
774 let result = execute_wasm_run(module, args, false);
775 assert!(result.is_ok());
777 }
778
779 #[test]
780 fn test_execute_wasm_validate() {
781 let module = PathBuf::from("test.wasm");
782 let result = execute_wasm_validate(module, false);
783 assert!(result.is_err());
785 }
786
787 #[test]
788 fn test_execute_test_run() {
789 let cmd = TestCommand::Run {
790 path: PathBuf::from("tests/"),
791 coverage: false,
792 parallel: true,
793 filter: None,
794 };
795 let result = execute_test(cmd, false);
796 assert!(result.is_ok());
798 }
799
800 #[test]
801 fn test_execute_test_report() {
802 let cmd = TestCommand::Report {
803 format: "html".to_string(),
804 output: None,
805 };
806 let result = execute_test(cmd, false);
807 assert!(result.is_ok());
808 }
809
810 #[test]
811 fn test_run_test_command() {
812 let path = PathBuf::from("test.ipynb");
813 let config = crate::notebook::testing::types::TestConfig::default();
814 let result = run_test_command(&path, config);
815 assert!(result.is_ok());
816 let report = result.unwrap();
817 assert_eq!(report.total_tests, 1);
818 assert_eq!(report.passed_tests, 1);
819 assert_eq!(report.failed_tests, 0);
820 }
821
822 #[test]
823
824 fn test_execute_notebook_serve() {
825 let cmd = NotebookCommand::Serve {
826 port: 8888,
827 host: "127.0.0.1".to_string(),
828 pid_file: None,
829 };
830
831 if let NotebookCommand::Serve { port, host, pid_file } = cmd {
833 assert_eq!(port, 8888);
834 assert_eq!(host, "127.0.0.1");
835 assert_eq!(pid_file, None);
836 } else {
837 panic!("Expected Serve command");
838 }
839
840 #[cfg(not(feature = "notebook"))]
842 {
843 let cmd = NotebookCommand::Serve {
844 port: 8888,
845 host: "127.0.0.1".to_string(),
846 pid_file: None,
847 };
848 let result = execute_notebook(cmd, false);
849 assert!(result.is_err());
850 assert!(result.unwrap_err().contains("Notebook feature not enabled"));
851 }
852
853 #[cfg(feature = "notebook")]
855 {
856 }
859 }
860
861 #[test]
862 fn test_execute_notebook_test() {
863 let cmd = NotebookCommand::Test {
864 path: PathBuf::from("test.ipynb"),
865 coverage: false,
866 format: "text".to_string(),
867 };
868 let result = execute_notebook(cmd, false);
869 #[cfg(not(feature = "notebook"))]
871 assert!(result.is_err());
872 #[cfg(feature = "notebook")]
873 assert!(result.is_ok() || result.is_err());
874 }
875
876 #[test]
877 fn test_execute_notebook_convert() {
878 let cmd = NotebookCommand::Convert {
879 input: PathBuf::from("input.ipynb"),
880 output: PathBuf::from("output.html"),
881 format: "html".to_string(),
882 };
883 let result = execute_notebook(cmd, false);
884 assert!(result.is_ok()); }
886
887 #[test]
888 fn test_compile_wasm_source_not_enabled() {
889 #[cfg(not(feature = "wasm-compile"))]
890 {
891 let result = compile_wasm_source("", &PathBuf::from("out.wasm"), false, false);
892 assert!(result.is_err());
893 assert_eq!(result.unwrap_err(), "WASM compilation feature not enabled");
894 }
895 }
896}
897
898#[cfg(test)]
899mod property_tests_mod {
900 use proptest::proptest;
901
902 proptest! {
903 #[test]
905 fn test_execute_never_panics(input: String) {
906 let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
908 let _ = std::panic::catch_unwind(|| {
910 });
913 }
914 }
915}