use std::io::Write; use std::process::{Command, Output, Stdio}; fn stdin_command(arguments: &[&str], source: &[u8]) -> Output { let mut child = Command::new(env!("CARGO_BIN_EXE_cli")) .args(arguments) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); child.stdin.take().unwrap().write_all(source).unwrap(); child.wait_with_output().unwrap() } #[test] fn run_executes_real_endor_files_and_prints_entry_results() { for (file, expected) in [ ("hello.endor", "你好,Endor 🌱\n"), ("calls.endor", "函数调用已接通\n"), ("shadowing.endor", "局部名称优先\n"), ("number.endor", "-123456789012345678901234567890.00100\n"), ] { let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .join("../compiler/examples") .join(file); let output = Command::new(env!("CARGO_BIN_EXE_cli")) .arg("run") .arg(path) .output() .unwrap(); assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); assert_eq!(String::from_utf8(output.stdout).unwrap(), expected); assert!(output.stderr.is_empty()); } } #[test] fn eval_and_run_read_stdin_and_support_explicit_entry_names() { let output = stdin_command(&["eval", "-"], "\"表达式🌱\"".as_bytes()); assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); assert_eq!(String::from_utf8(output.stdout).unwrap(), "表达式🌱\n"); let output = stdin_command( &["run", "--entry", "greet", "-"], b"function greet(): String { return \"ok\" }", ); assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); assert_eq!(String::from_utf8(output.stdout).unwrap(), "ok\n"); let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../compiler/examples/literal.endor"); let output = Command::new(env!("CARGO_BIN_EXE_cli")) .arg("eval") .arg(path) .output() .unwrap(); assert!(output.status.success()); assert_eq!( String::from_utf8(output.stdout).unwrap(), "表达式 → 字节码 → VM\n" ); } #[test] fn execution_errors_fail_without_success_output_and_report_their_stage() { for (arguments, source, expected) in [ ( vec!["run", "-"], "function main(): T {\n return missing\n}", ":2:10: undefined name: missing", ), ( vec!["run", "-"], "function main(): T { return f() } function f(x: T): T { return x }", "positional arguments", ), ( vec!["run", "-"], "function other(): T { return 1 }", "entry function not found: main", ), ( vec!["eval", "-"], "1 + 2", "arithmetic bytecode is not implemented", ), (vec!["eval", "-"], "1()", "does not support call"), ( vec!["run", "-"], "function main(): T { return ) }", "expected", ), ] { let output = stdin_command(&arguments, source.as_bytes()); assert!(!output.status.success()); assert!(output.stdout.is_empty()); let error = String::from_utf8(output.stderr).unwrap(); assert!(error.contains(expected), "{error}"); if expected == "positional arguments" { assert!(error.contains("at main (:1, code unit")); } } let output = stdin_command(&["eval", "-"], &[0xff]); assert!(!output.status.success()); assert!( String::from_utf8(output.stderr) .unwrap() .contains("invalid UTF-8") ); for arguments in [ vec!["run"], vec!["run", "--entry"], vec!["run", "--unknown", "-"], vec!["eval"], vec!["eval", "-", "extra"], ] { let output = Command::new(env!("CARGO_BIN_EXE_cli")) .args(arguments) .output() .unwrap(); assert!(!output.status.success()); assert!(output.stdout.is_empty()); assert!(String::from_utf8(output.stderr).unwrap().contains("Usage:")); } }