diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 22884690..9aa5f62d 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -96,14 +96,15 @@ fn main() -> ExitCode { Some(Commands::Eval { code, trace }) => { let kernel = make_kernel(cli.verbose, cli.quiet); let pool = make_pool(&kernel); - eval(&code, &kernel, &pool, trace) + eval(&code, &kernel, &pool, trace, true) } Some(Commands::Run { file, repeat, trace }) => { let kernel = make_kernel(cli.verbose, cli.quiet); let pool = make_pool(&kernel); for n in 0..repeat { let code = run(&file, &kernel, &pool, trace); - if code != ExitCode::SUCCESS && code != ExitCode::FAILURE { + // 失败即终止并传播退出码,供脚本与 CI 区分成败。 + if code != ExitCode::SUCCESS { return code; } if n + 1 < repeat { @@ -161,7 +162,7 @@ fn make_pool(kernel: &Arc) -> Arc { VmPool::new(Arc::clone(kernel), kernel.config.min_pool_size, kernel.config.max_pool_size) } -fn eval(code: &str, kernel: &Arc, pool: &Arc, trace: bool) -> ExitCode { +fn eval(code: &str, kernel: &Arc, pool: &Arc, trace: bool, print_result: bool) -> ExitCode { let allocator = Allocator::default(); let program = match oxide_parser::parse(&allocator, code) { Ok(p) => p, @@ -189,7 +190,10 @@ fn eval(code: &str, kernel: &Arc, pool: &Arc, trace: bool) - guard.vm_mut().set_instruction_trace(trace); match guard.vm_mut().run(&module) { Ok(result) => { - format_result(guard.vm(), kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result); + // eval 臂按 REPL 语义打印完成值;run 臂脚本只输出自身产生内容。 + if print_result { + format_result(guard.vm(), kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result); + } ExitCode::SUCCESS } Err(err) => { @@ -293,7 +297,7 @@ fn format_array( fn run(file: &str, kernel: &Arc, pool: &Arc, trace: bool) -> ExitCode { match fs::read_to_string(file) { - Ok(source) => eval(&source, kernel, pool, trace), + Ok(source) => eval(&source, kernel, pool, trace, false), Err(err) => { kernel_error!("cannot read {}: {}", file, err); eprintln!("{}", Red.paint(format!("Cannot read {file}: {err}"))); diff --git a/crates/oxide_cli/tests/cli_smoke.rs b/crates/oxide_cli/tests/cli_smoke.rs index 56f5692f..8878573b 100644 --- a/crates/oxide_cli/tests/cli_smoke.rs +++ b/crates/oxide_cli/tests/cli_smoke.rs @@ -33,8 +33,22 @@ fn run_file() { .expect("failed to run oxide run"); assert!(output.status.success(), "run hello.js should exit 0"); + // run 臂不打印脚本完成值,stdout 应只含脚本自身输出(hello.js 无输出)。 let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.trim() == "3", "run output should be '3': {stdout}"); + assert!(stdout.trim().is_empty(), "run 不应打印完成值: {stdout}"); +} + +#[test] +fn run_throw_script() { + let fixture = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/throw.js"); + let output = Command::new(env!("CARGO_BIN_EXE_oxide")) + .args(["run", fixture]) + .output() + .expect("failed to run oxide run"); + + assert_eq!(output.status.code(), Some(1), "run throw.js should exit 1"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(!stderr.is_empty(), "run 失败应输出 stderr: {stderr}"); } #[test] diff --git a/crates/oxide_cli/tests/fixtures/throw.js b/crates/oxide_cli/tests/fixtures/throw.js new file mode 100644 index 00000000..c3fc99d2 --- /dev/null +++ b/crates/oxide_cli/tests/fixtures/throw.js @@ -0,0 +1 @@ +throw new Error("boom");