From f698decf3701cc51a61bbc3e36971898339ba91e Mon Sep 17 00:00:00 2001 From: Matthew Iselin Date: Tue, 12 Nov 2013 11:37:14 +1000 Subject: [PATCH] Implemented a ProcessExit enum and helper methods to std::rt::io::process for getting process termination status, or the signal that terminated a process. A test has been added to rtio-processes.rs to ensure signal termination is picked up correctly. --- src/compiletest/procsrv.rs | 3 +- src/compiletest/runtest.rs | 58 ++++++++++++++----------- src/librustc/back/link.rs | 13 +++--- src/librustpkg/api.rs | 9 ++-- src/librustpkg/conditions.rs | 3 +- src/librustpkg/lib.rs | 9 ++-- src/librustpkg/source_control.rs | 10 ++--- src/librustpkg/tests.rs | 49 ++++++++++++--------- src/librustpkg/version.rs | 4 +- src/librustuv/process.rs | 14 +++--- src/libstd/rt/io/process.rs | 38 +++++++++++++++- src/libstd/rt/rtio.rs | 4 +- src/libstd/run.rs | 30 +++++++------ src/test/run-pass/rtio-processes.rs | 29 +++++++++++-- src/test/run-pass/signal-exit-status.rs | 29 +++++++++++++ 15 files changed, 202 insertions(+), 100 deletions(-) create mode 100644 src/test/run-pass/signal-exit-status.rs diff --git a/src/compiletest/procsrv.rs b/src/compiletest/procsrv.rs index 6b15e21b70a..32ee19badd2 100644 --- a/src/compiletest/procsrv.rs +++ b/src/compiletest/procsrv.rs @@ -11,6 +11,7 @@ use std::os; use std::run; use std::str; +use std::rt::io::process::ProcessExit; #[cfg(target_os = "win32")] fn target_env(lib_path: &str, prog: &str) -> ~[(~str,~str)] { @@ -39,7 +40,7 @@ fn target_env(_lib_path: &str, _prog: &str) -> ~[(~str,~str)] { os::env() } -pub struct Result {status: int, out: ~str, err: ~str} +pub struct Result {status: ProcessExit, out: ~str, err: ~str} pub fn run(lib_path: &str, prog: &str, diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index 1701961b293..ffd5e721d67 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -23,6 +23,8 @@ use util::logv; use std::rt::io; use std::rt::io::fs; use std::rt::io::File; +use std::rt::io::process; +use std::rt::io::process::ProcessExit; use std::os; use std::str; use std::vec; @@ -60,7 +62,7 @@ pub fn run_metrics(config: config, testfile: ~str, mm: &mut MetricMap) { fn run_cfail_test(config: &config, props: &TestProps, testfile: &Path) { let ProcRes = compile_test(config, props, testfile); - if ProcRes.status == 0 { + if ProcRes.status.success() { fatal_ProcRes(~"compile-fail test compiled successfully!", &ProcRes); } @@ -81,7 +83,7 @@ fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) { let ProcRes = if !config.jit { let ProcRes = compile_test(config, props, testfile); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"compilation failed!", &ProcRes); } @@ -92,7 +94,7 @@ fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) { // The value our Makefile configures valgrind to return on failure static VALGRIND_ERR: int = 100; - if ProcRes.status == VALGRIND_ERR { + if ProcRes.status.matches_exit_status(VALGRIND_ERR) { fatal_ProcRes(~"run-fail test isn't valgrind-clean!", &ProcRes); } @@ -115,10 +117,9 @@ fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) { fn check_correct_failure_status(ProcRes: &ProcRes) { // The value the rust runtime returns on failure static RUST_ERR: int = 101; - if ProcRes.status != RUST_ERR { + if !ProcRes.status.matches_exit_status(RUST_ERR) { fatal_ProcRes( - format!("failure produced the wrong error code: {}", - ProcRes.status), + format!("failure produced the wrong error: {}", ProcRes.status), ProcRes); } } @@ -127,19 +128,19 @@ fn run_rpass_test(config: &config, props: &TestProps, testfile: &Path) { if !config.jit { let mut ProcRes = compile_test(config, props, testfile); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"compilation failed!", &ProcRes); } ProcRes = exec_compiled_test(config, props, testfile); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"test run failed!", &ProcRes); } } else { let ProcRes = jit_test(config, props, testfile); - if ProcRes.status != 0 { fatal_ProcRes(~"jit failed!", &ProcRes); } + if !ProcRes.status.success() { fatal_ProcRes(~"jit failed!", &ProcRes); } } } @@ -160,7 +161,7 @@ fn run_pretty_test(config: &config, props: &TestProps, testfile: &Path) { logv(config, format!("pretty-printing round {}", round)); let ProcRes = print_source(config, testfile, srcs[round].clone()); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(format!("pretty-printing failed in round {}", round), &ProcRes); } @@ -192,7 +193,7 @@ fn run_pretty_test(config: &config, props: &TestProps, testfile: &Path) { // Finally, let's make sure it actually appears to remain valid code let ProcRes = typecheck_source(config, props, testfile, actual); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"pretty-printed source does not typecheck", &ProcRes); } @@ -264,7 +265,7 @@ fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) { // compile test file (it shoud have 'compile-flags:-g' in the header) let mut ProcRes = compile_test(config, props, testfile); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"compilation failed!", &ProcRes); } @@ -375,7 +376,7 @@ fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) { } } - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal(~"gdb failed to execute"); } let num_check_lines = check_lines.len(); @@ -431,7 +432,7 @@ fn check_error_patterns(props: &TestProps, } } - if ProcRes.status == 0 { + if ProcRes.status.success() { fatal(~"process did not return an error status"); } @@ -473,7 +474,7 @@ fn check_expected_errors(expected_errors: ~[errors::ExpectedError], let mut found_flags = vec::from_elem( expected_errors.len(), false); - if ProcRes.status == 0 { + if ProcRes.status.success() { fatal(~"process did not return an error status"); } @@ -625,7 +626,7 @@ fn scan_string(haystack: &str, needle: &str, idx: &mut uint) -> bool { struct ProcArgs {prog: ~str, args: ~[~str]} -struct ProcRes {status: int, stdout: ~str, stderr: ~str, cmdline: ~str} +struct ProcRes {status: ProcessExit, stdout: ~str, stderr: ~str, cmdline: ~str} fn compile_test(config: &config, props: &TestProps, testfile: &Path) -> ProcRes { @@ -692,7 +693,7 @@ fn compose_and_run_compiler( |a,b| make_lib_name(a, b, testfile), &abs_ab); let auxres = compose_and_run(config, &abs_ab, aux_args, ~[], config.compile_lib_path, None); - if auxres.status != 0 { + if !auxres.status.success() { fatal_ProcRes( format!("auxiliary build of {} failed to compile: ", abs_ab.display()), @@ -966,7 +967,12 @@ fn _arm_exec_compiled_test(config: &config, props: &TestProps, dump_output(config, testfile, stdout_out, stderr_out); - ProcRes {status: exitcode, stdout: stdout_out, stderr: stderr_out, cmdline: cmdline } + ProcRes { + status: process::ExitStatus(exitcode), + stdout: stdout_out, + stderr: stderr_out, + cmdline: cmdline + } } fn _dummy_exec_compiled_test(config: &config, props: &TestProps, @@ -976,9 +982,9 @@ fn _dummy_exec_compiled_test(config: &config, props: &TestProps, let cmdline = make_cmdline("", args.prog, args.args); match config.mode { - mode_run_fail => ProcRes {status: 101, stdout: ~"", + mode_run_fail => ProcRes {status: process::ExitStatus(101), stdout: ~"", stderr: ~"", cmdline: cmdline}, - _ => ProcRes {status: 0, stdout: ~"", + _ => ProcRes {status: process::ExitStatus(0), stdout: ~"", stderr: ~"", cmdline: cmdline} } } @@ -1099,33 +1105,33 @@ fn run_codegen_test(config: &config, props: &TestProps, } let mut ProcRes = compile_test_and_save_bitcode(config, props, testfile); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"compilation failed!", &ProcRes); } ProcRes = extract_function_from_bitcode(config, props, "test", testfile, ""); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"extracting 'test' function failed", &ProcRes); } ProcRes = disassemble_extract(config, props, testfile, ""); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"disassembling extract failed", &ProcRes); } let mut ProcRes = compile_cc_with_clang_and_save_bitcode(config, props, testfile); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"compilation failed!", &ProcRes); } ProcRes = extract_function_from_bitcode(config, props, "test", testfile, "clang"); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"extracting 'test' function failed", &ProcRes); } ProcRes = disassemble_extract(config, props, testfile, "clang"); - if ProcRes.status != 0 { + if !ProcRes.status.success() { fatal_ProcRes(~"disassembling extract failed", &ProcRes); } diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs index 21268c132ba..9b0a2c5fde8 100644 --- a/src/librustc/back/link.rs +++ b/src/librustc/back/link.rs @@ -378,9 +378,8 @@ pub mod write { let prog = run::process_output(cc_prog, cc_args); - if prog.status != 0 { - sess.err(format!("building with `{}` failed with code {}", - cc_prog, prog.status)); + if !prog.status.success() { + sess.err(format!("linking with `{}` failed: {}", cc_prog, prog.status)); sess.note(format!("{} arguments: {}", cc_prog, cc_args.connect(" "))); sess.note(str::from_utf8(prog.error + prog.output)); @@ -947,11 +946,11 @@ pub fn link_binary(sess: Session, // We run 'cc' here let prog = run::process_output(cc_prog, cc_args); - if 0 != prog.status { - sess.err(format!("linking with `{}` failed with code {}", - cc_prog, prog.status)); + + if !prog.status.success() { + sess.err(format!("linking with `{}` failed: {}", cc_prog, prog.status)); sess.note(format!("{} arguments: {}", - cc_prog, cc_args.connect(" "))); + cc_prog, cc_args.connect(" "))); sess.note(str::from_utf8(prog.error + prog.output)); sess.abort_if_errors(); } diff --git a/src/librustpkg/api.rs b/src/librustpkg/api.rs index c0ffd66d22e..bb73882bde8 100644 --- a/src/librustpkg/api.rs +++ b/src/librustpkg/api.rs @@ -159,17 +159,16 @@ pub fn build_library_in_workspace(exec: &mut workcache::Exec, let all_args = flags + absolute_paths + cc_args + ~[~"-o", out_name.as_str().unwrap().to_owned()]; - let exit_code = run::process_status(tool, all_args); - if exit_code != 0 { - command_failed.raise((tool.to_owned(), all_args, exit_code)) - } - else { + let exit_process = run::process_status(tool, all_args); + if exit_process.success() { let out_name_str = out_name.as_str().unwrap().to_owned(); exec.discover_output("binary", out_name_str, digest_only_date(&out_name)); context.add_library_path(out_name.dir_path()); out_name_str + } else { + command_failed.raise((tool.to_owned(), all_args, exit_process)) } } diff --git a/src/librustpkg/conditions.rs b/src/librustpkg/conditions.rs index 91edc275826..6831b6ec312 100644 --- a/src/librustpkg/conditions.rs +++ b/src/librustpkg/conditions.rs @@ -13,6 +13,7 @@ pub use std::path::Path; pub use package_id::PkgId; pub use std::rt::io::FileStat; +pub use std::rt::io::process::ProcessExit; condition! { pub bad_path: (Path, ~str) -> Path; @@ -57,5 +58,5 @@ condition! { condition! { // str is output of applying the command (first component) // to the args (second component) - pub command_failed: (~str, ~[~str], int) -> ~str; + pub command_failed: (~str, ~[~str], ProcessExit) -> ~str; } diff --git a/src/librustpkg/lib.rs b/src/librustpkg/lib.rs index f7d10d6b5ef..94978b4a7e1 100644 --- a/src/librustpkg/lib.rs +++ b/src/librustpkg/lib.rs @@ -26,6 +26,7 @@ extern mod rustc; extern mod syntax; use std::{os, result, run, str, task}; +use std::rt::io::process; use std::hashmap::HashSet; use std::rt::io; use std::rt::io::fs; @@ -164,14 +165,14 @@ impl<'self> PkgScript<'self> { /// is the command to pass to it (e.g., "build", "clean", "install") /// Returns a pair of an exit code and list of configs (obtained by /// calling the package script's configs() function if it exists - fn run_custom(exe: &Path, sysroot: &Path) -> (~[~str], int) { + fn run_custom(exe: &Path, sysroot: &Path) -> (~[~str], process::ProcessExit) { debug!("Running program: {} {} {}", exe.as_str().unwrap().to_owned(), sysroot.display(), "install"); // FIXME #7401 should support commands besides `install` // FIXME (#9639): This needs to handle non-utf8 paths let status = run::process_status(exe.as_str().unwrap(), [sysroot.as_str().unwrap().to_owned(), ~"install"]); - if status != 0 { + if !status.success() { debug!("run_custom: first pkg command failed with {:?}", status); (~[], status) } @@ -486,7 +487,7 @@ impl CtxMethods for BuildContext { // We always *run* the package script let (cfgs, hook_result) = PkgScript::run_custom(&Path::new(pkg_exe), &sysroot); debug!("Command return code = {:?}", hook_result); - if hook_result != 0 { + if !hook_result.success() { fail!("Error running custom build command") } custom = true; @@ -697,7 +698,7 @@ impl CtxMethods for BuildContext { debug!("test: test_exec = {}", test_exec.display()); // FIXME (#9639): This needs to handle non-utf8 paths let status = run::process_status(test_exec.as_str().unwrap(), [~"--test"]); - if status != 0 { + if !status.success() { fail!("Some tests failed"); } } diff --git a/src/librustpkg/source_control.rs b/src/librustpkg/source_control.rs index bcda3168bd8..6f484a7be5d 100644 --- a/src/librustpkg/source_control.rs +++ b/src/librustpkg/source_control.rs @@ -36,7 +36,7 @@ pub fn safe_git_clone(source: &Path, v: &Version, target: &Path) -> CloneResult let outp = run::process_output("git", [~"clone", source.as_str().unwrap().to_owned(), target.as_str().unwrap().to_owned()]); - if outp.status != 0 { + if !outp.status.success() { println(str::from_utf8_owned(outp.output.clone())); println(str::from_utf8_owned(outp.error)); return DirToUse(target.clone()); @@ -52,7 +52,7 @@ pub fn safe_git_clone(source: &Path, v: &Version, target: &Path) -> CloneResult [format!("--work-tree={}", target.as_str().unwrap().to_owned()), format!("--git-dir={}", git_dir.as_str().unwrap().to_owned()), ~"checkout", format!("{}", *s)]); - if outp.status != 0 { + if !outp.status.success() { println(str::from_utf8_owned(outp.output.clone())); println(str::from_utf8_owned(outp.error)); return DirToUse(target.clone()); @@ -73,7 +73,7 @@ pub fn safe_git_clone(source: &Path, v: &Version, target: &Path) -> CloneResult format!("--git-dir={}", git_dir.as_str().unwrap().to_owned()), ~"pull", ~"--no-edit", source.as_str().unwrap().to_owned()]; let outp = run::process_output("git", args); - assert!(outp.status == 0); + assert!(outp.status.success()); } CheckedOutSources } else { @@ -110,7 +110,7 @@ pub fn git_clone_url(source: &str, target: &Path, v: &Version) { // FIXME (#9639): This needs to handle non-utf8 paths let outp = run::process_output("git", [~"clone", source.to_owned(), target.as_str().unwrap().to_owned()]); - if outp.status != 0 { + if !outp.status.success() { debug!("{}", str::from_utf8_owned(outp.output.clone())); debug!("{}", str::from_utf8_owned(outp.error)); cond.raise((source.to_owned(), target.clone())) @@ -120,7 +120,7 @@ pub fn git_clone_url(source: &str, target: &Path, v: &Version) { &ExactRevision(ref s) | &Tagged(ref s) => { let outp = process_output_in_cwd("git", [~"checkout", s.to_owned()], target); - if outp.status != 0 { + if !outp.status.success() { debug!("{}", str::from_utf8_owned(outp.output.clone())); debug!("{}", str::from_utf8_owned(outp.error)); cond.raise((source.to_owned(), target.clone())) diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs index b65e1b7ba02..99910242582 100644 --- a/src/librustpkg/tests.rs +++ b/src/librustpkg/tests.rs @@ -15,6 +15,8 @@ use std::{os, run, str, task}; use std::rt::io; use std::rt::io::fs; use std::rt::io::File; +use std::rt::io::process; +use std::rt::io::process::ProcessExit; use extra::arc::Arc; use extra::arc::RWArc; use extra::tempfile::TempDir; @@ -149,7 +151,7 @@ fn run_git(args: &[~str], env: Option<~[(~str, ~str)]>, cwd: &Path, err_msg: &st err_fd: None }); let rslt = prog.finish_with_output(); - if rslt.status != 0 { + if !rslt.status.success() { fail!("{} [git returned {:?}, output = {}, error = {}]", err_msg, rslt.status, str::from_utf8(rslt.output), str::from_utf8(rslt.error)); } @@ -251,7 +253,7 @@ fn command_line_test_expect_fail(args: &[~str], expected_exitcode: int) { match command_line_test_with_env(args, cwd, env) { Success(_) => fail!("Should have failed with {}, but it succeeded", expected_exitcode), - Fail(error) if error == expected_exitcode => (), // ok + Fail(error) if error.matches_exit_status(expected_exitcode) => (), // ok Fail(other) => fail!("Expected to fail with {}, but failed with {} instead", expected_exitcode, other) } @@ -259,7 +261,7 @@ fn command_line_test_expect_fail(args: &[~str], enum ProcessResult { Success(ProcessOutput), - Fail(int) // exit code + Fail(ProcessExit) } /// Runs `rustpkg` (based on the directory that this executable was @@ -289,7 +291,7 @@ fn command_line_test_with_env(args: &[~str], cwd: &Path, env: Option<~[(~str, ~s cmd, args, str::from_utf8(output.output), str::from_utf8(output.error), output.status); - if output.status != 0 { + if !output.status.success() { debug!("Command {} {:?} failed with exit code {:?}; its output was --- {} ---", cmd, args, output.status, str::from_utf8(output.output) + str::from_utf8(output.error)); @@ -501,9 +503,9 @@ fn touch_source_file(workspace: &Path, pkgid: &PkgId) { // should be able to do this w/o a process // FIXME (#9639): This needs to handle non-utf8 paths // n.b. Bumps time up by 2 seconds to get around granularity issues - if run::process_output("touch", [~"--date", + if !run::process_output("touch", [~"--date", ~"+2 seconds", - p.as_str().unwrap().to_owned()]).status != 0 { + p.as_str().unwrap().to_owned()]).status.success() { let _ = cond.raise((pkg_src_dir.clone(), ~"Bad path")); } } @@ -520,8 +522,8 @@ fn touch_source_file(workspace: &Path, pkgid: &PkgId) { // should be able to do this w/o a process // FIXME (#9639): This needs to handle non-utf8 paths // n.b. Bumps time up by 2 seconds to get around granularity issues - if run::process_output("touch", [~"-A02", - p.as_str().unwrap().to_owned()]).status != 0 { + if !run::process_output("touch", [~"-A02", + p.as_str().unwrap().to_owned()]).status.success() { let _ = cond.raise((pkg_src_dir.clone(), ~"Bad path")); } } @@ -1091,7 +1093,8 @@ fn no_rebuilding() { match command_line_test_partial([~"build", ~"foo"], workspace) { Success(*) => (), // ok - Fail(status) if status == 65 => fail!("no_rebuilding failed: it tried to rebuild bar"), + Fail(status) if status.matches_exit_status(65) => + fail!("no_rebuilding failed: it tried to rebuild bar"), Fail(_) => fail!("no_rebuilding failed for some other reason") } } @@ -1109,7 +1112,7 @@ fn no_recopying() { match command_line_test_partial([~"install", ~"foo"], workspace) { Success(*) => (), // ok - Fail(65) => fail!("no_recopying failed: it tried to re-copy foo"), + Fail(process::ExitStatus(65)) => fail!("no_recopying failed: it tried to re-copy foo"), Fail(_) => fail!("no_copying failed for some other reason") } } @@ -1127,7 +1130,8 @@ fn no_rebuilding_dep() { assert!(chmod_read_only(&bar_lib)); match command_line_test_partial([~"build", ~"foo"], workspace) { Success(*) => (), // ok - Fail(status) if status == 65 => fail!("no_rebuilding_dep failed: it tried to rebuild bar"), + Fail(status) if status.matches_exit_status(65) => + fail!("no_rebuilding_dep failed: it tried to rebuild bar"), Fail(_) => fail!("no_rebuilding_dep failed for some other reason") } } @@ -1147,7 +1151,7 @@ fn do_rebuild_dep_dates_change() { match command_line_test_partial([~"build", ~"foo"], workspace) { Success(*) => fail!("do_rebuild_dep_dates_change failed: it didn't rebuild bar"), - Fail(status) if status == 65 => (), // ok + Fail(status) if status.matches_exit_status(65) => (), // ok Fail(_) => fail!("do_rebuild_dep_dates_change failed for some other reason") } } @@ -1168,7 +1172,7 @@ fn do_rebuild_dep_only_contents_change() { // should adjust the datestamp match command_line_test_partial([~"build", ~"foo"], workspace) { Success(*) => fail!("do_rebuild_dep_only_contents_change failed: it didn't rebuild bar"), - Fail(status) if status == 65 => (), // ok + Fail(status) if status.matches_exit_status(65) => (), // ok Fail(_) => fail!("do_rebuild_dep_only_contents_change failed for some other reason") } } @@ -1274,7 +1278,7 @@ fn test_extern_mod() { err_fd: None }); let outp = prog.finish_with_output(); - if outp.status != 0 { + if !outp.status.success() { fail!("output was {}, error was {}", str::from_utf8(outp.output), str::from_utf8(outp.error)); @@ -1329,7 +1333,7 @@ fn test_extern_mod_simpler() { err_fd: None }); let outp = prog.finish_with_output(); - if outp.status != 0 { + if !outp.status.success() { fail!("output was {}, error was {}", str::from_utf8(outp.output), str::from_utf8(outp.error)); @@ -2144,7 +2148,7 @@ fn test_rebuild_when_needed() { chmod_read_only(&test_executable); match command_line_test_partial([~"test", ~"foo"], foo_workspace) { Success(*) => fail!("test_rebuild_when_needed didn't rebuild"), - Fail(status) if status == 65 => (), // ok + Fail(status) if status.matches_exit_status(65) => (), // ok Fail(_) => fail!("test_rebuild_when_needed failed for some other reason") } } @@ -2164,7 +2168,8 @@ fn test_no_rebuilding() { chmod_read_only(&test_executable); match command_line_test_partial([~"test", ~"foo"], foo_workspace) { Success(*) => (), // ok - Fail(status) if status == 65 => fail!("test_no_rebuilding failed: it rebuilt the tests"), + Fail(status) if status.matches_exit_status(65) => + fail!("test_no_rebuilding failed: it rebuilt the tests"), Fail(_) => fail!("test_no_rebuilding failed for some other reason") } } @@ -2359,9 +2364,11 @@ fn test_c_dependency_no_rebuilding() { match command_line_test_partial([~"build", ~"cdep"], dir) { Success(*) => (), // ok - Fail(status) if status == 65 => fail!("test_c_dependency_no_rebuilding failed: \ - it tried to rebuild foo.c"), - Fail(_) => fail!("test_c_dependency_no_rebuilding failed for some other reason") + Fail(status) if status.matches_exit_status(65) => + fail!("test_c_dependency_no_rebuilding failed: \ + it tried to rebuild foo.c"), + Fail(_) => + fail!("test_c_dependency_no_rebuilding failed for some other reason") } } @@ -2396,7 +2403,7 @@ fn test_c_dependency_yes_rebuilding() { match command_line_test_partial([~"build", ~"cdep"], dir) { Success(*) => fail!("test_c_dependency_yes_rebuilding failed: \ it didn't rebuild and should have"), - Fail(status) if status == 65 => (), + Fail(status) if status.matches_exit_status(65) => (), Fail(_) => fail!("test_c_dependency_yes_rebuilding failed for some other reason") } } diff --git a/src/librustpkg/version.rs b/src/librustpkg/version.rs index eff16cb9996..d40a104ccda 100644 --- a/src/librustpkg/version.rs +++ b/src/librustpkg/version.rs @@ -109,7 +109,7 @@ pub fn try_getting_local_version(local_path: &Path) -> Option { debug!("git --git-dir={} tag -l ~~~> {:?}", git_dir.display(), outp.status); - if outp.status != 0 { + if !outp.status.success() { continue; } @@ -143,7 +143,7 @@ pub fn try_getting_version(remote_path: &Path) -> Option { let outp = run::process_output("git", [~"clone", format!("https://{}", remote_path.as_str().unwrap()), tmp_dir.as_str().unwrap().to_owned()]); - if outp.status == 0 { + if outp.status.success() { debug!("Cloned it... ( {}, {} )", str::from_utf8(outp.output), str::from_utf8(outp.error)); diff --git a/src/librustuv/process.rs b/src/librustuv/process.rs index 7e75515972c..e49930c1fc9 100644 --- a/src/librustuv/process.rs +++ b/src/librustuv/process.rs @@ -33,8 +33,7 @@ pub struct Process { to_wake: Option, /// Collected from the exit_cb - exit_status: Option, - term_signal: Option, + exit_status: Option, } impl Process { @@ -82,7 +81,6 @@ impl Process { home: get_handle_to_current_scheduler!(), to_wake: None, exit_status: None, - term_signal: None, }; match unsafe { uvll::uv_spawn(loop_.handle, handle, &options) @@ -106,9 +104,10 @@ extern fn on_exit(handle: *uvll::uv_process_t, let p: &mut Process = unsafe { UvHandle::from_uv_handle(&handle) }; assert!(p.exit_status.is_none()); - assert!(p.term_signal.is_none()); - p.exit_status = Some(exit_status as int); - p.term_signal = Some(term_signal as int); + p.exit_status = Some(match term_signal { + 0 => ExitStatus(exit_status as int), + n => ExitSignal(n as int), + }); match p.to_wake.take() { Some(task) => { @@ -209,7 +208,7 @@ impl RtioProcess for Process { } } - fn wait(&mut self) -> int { + fn wait(&mut self) -> ProcessExit { // Make sure (on the home scheduler) that we have an exit status listed let _m = self.fire_homing_missile(); match self.exit_status { @@ -223,7 +222,6 @@ impl RtioProcess for Process { } } - // FIXME(#10109): this is wrong self.exit_status.unwrap() } } diff --git a/src/libstd/rt/io/process.rs b/src/libstd/rt/io/process.rs index ae087099d1f..6b21cde2488 100644 --- a/src/libstd/rt/io/process.rs +++ b/src/libstd/rt/io/process.rs @@ -18,6 +18,8 @@ use rt::io; use rt::io::io_error; use rt::rtio::{RtioProcess, IoFactory, with_local_io}; +use fmt; + // windows values don't matter as long as they're at least one of unix's // TERM/KILL/INT signals #[cfg(windows)] pub static PleaseExitSignal: int = 15; @@ -79,6 +81,40 @@ pub enum StdioContainer { CreatePipe(bool /* readable */, bool /* writable */), } +/// Describes the result of a process after it has terminated. +#[deriving(Eq)] +pub enum ProcessExit { + /// Normal termination with an exit status. + ExitStatus(int), + + /// Termination by signal, with the signal number. + ExitSignal(int), +} + +impl fmt::Default for ProcessExit { + /// Format a ProcessExit enum, to nicely present the information. + fn fmt(obj: &ProcessExit, f: &mut fmt::Formatter) { + match *obj { + ExitStatus(code) => write!(f.buf, "exit code: {}", code), + ExitSignal(code) => write!(f.buf, "signal: {}", code), + } + } +} + +impl ProcessExit { + /// Was termination successful? Signal termination not considered a success, + /// and success is defined as a zero exit status. + pub fn success(&self) -> bool { + return self.matches_exit_status(0); + } + + /// Checks whether this ProcessExit matches the given exit status. + /// Termination by signal will never match an exit code. + pub fn matches_exit_status(&self, wanted: int) -> bool { + *self == ExitStatus(wanted) + } +} + impl Process { /// Creates a new pipe initialized, but not bound to any particular /// source/destination @@ -122,7 +158,7 @@ impl Process { /// Wait for the child to exit completely, returning the status that it /// exited with. This function will continue to have the same return value /// after it has been called at least once. - pub fn wait(&mut self) -> int { self.handle.wait() } + pub fn wait(&mut self) -> ProcessExit { self.handle.wait() } } impl Drop for Process { diff --git a/src/libstd/rt/rtio.rs b/src/libstd/rt/rtio.rs index d623914cdad..9b1103b8a74 100644 --- a/src/libstd/rt/rtio.rs +++ b/src/libstd/rt/rtio.rs @@ -18,7 +18,7 @@ use c_str::CString; use ai = rt::io::net::addrinfo; use rt::io::IoError; use rt::io::signal::Signum; -use super::io::process::ProcessConfig; +use super::io::process::{ProcessConfig, ProcessExit}; use super::io::net::ip::{IpAddr, SocketAddr}; use path::Path; use super::io::{SeekStyle}; @@ -201,7 +201,7 @@ pub trait RtioFileStream { pub trait RtioProcess { fn id(&self) -> libc::pid_t; fn kill(&mut self, signal: int) -> Result<(), IoError>; - fn wait(&mut self) -> int; + fn wait(&mut self) -> ProcessExit; } pub trait RtioPipe { diff --git a/src/libstd/run.rs b/src/libstd/run.rs index 844f61dda0b..c9b33a14c52 100644 --- a/src/libstd/run.rs +++ b/src/libstd/run.rs @@ -18,6 +18,7 @@ use libc::{pid_t, c_int}; use libc; use prelude::*; use rt::io::process; +use rt::io::process::ProcessExit; use rt::io; use rt::io::Reader; use task; @@ -100,7 +101,7 @@ impl <'self> ProcessOptions<'self> { /// The output of a finished process. pub struct ProcessOutput { /// The status (exit code) of the process. - status: int, + status: ProcessExit, /// The data that the process wrote to stdout. output: ~[u8], @@ -194,7 +195,7 @@ impl Process { * * If the child has already been finished then the exit code is returned. */ - pub fn finish(&mut self) -> int { self.inner.wait() } + pub fn finish(&mut self) -> ProcessExit { self.inner.wait() } /** * Closes the handle to stdin, waits for the child process to terminate, and @@ -296,7 +297,7 @@ impl Process { * * The process's exit code */ -pub fn process_status(prog: &str, args: &[~str]) -> int { +pub fn process_status(prog: &str, args: &[~str]) -> ProcessExit { let mut prog = Process::new(prog, args, ProcessOptions { env: None, dir: None, @@ -340,8 +341,11 @@ mod tests { #[test] #[cfg(not(target_os="android"))] // FIXME(#10380) fn test_process_status() { - assert_eq!(run::process_status("false", []), 1); - assert_eq!(run::process_status("true", []), 0); + let mut status = run::process_status("false", []); + assert!(status.matches_exit_status(1)); + + status = run::process_status("true", []); + assert!(status.success()); } #[test] @@ -352,7 +356,7 @@ mod tests { = run::process_output("echo", [~"hello"]); let output_str = str::from_utf8(output); - assert_eq!(status, 0); + assert!(status.success()); assert_eq!(output_str.trim().to_owned(), ~"hello"); // FIXME #7224 if !running_on_valgrind() { @@ -367,7 +371,7 @@ mod tests { let run::ProcessOutput {status, output, error} = run::process_output("mkdir", [~"."]); - assert_eq!(status, 1); + assert!(status.matches_exit_status(1)); assert_eq!(output, ~[]); assert!(!error.is_empty()); } @@ -424,15 +428,15 @@ mod tests { #[cfg(not(target_os="android"))] // FIXME(#10380) fn test_finish_once() { let mut prog = run::Process::new("false", [], run::ProcessOptions::new()); - assert_eq!(prog.finish(), 1); + assert!(prog.finish().matches_exit_status(1)); } #[test] #[cfg(not(target_os="android"))] // FIXME(#10380) fn test_finish_twice() { let mut prog = run::Process::new("false", [], run::ProcessOptions::new()); - assert_eq!(prog.finish(), 1); - assert_eq!(prog.finish(), 1); + assert!(prog.finish().matches_exit_status(1)); + assert!(prog.finish().matches_exit_status(1)); } #[test] @@ -444,7 +448,7 @@ mod tests { = prog.finish_with_output(); let output_str = str::from_utf8(output); - assert_eq!(status, 0); + assert!(status.success()); assert_eq!(output_str.trim().to_owned(), ~"hello"); // FIXME #7224 if !running_on_valgrind() { @@ -462,7 +466,7 @@ mod tests { let output_str = str::from_utf8(output); - assert_eq!(status, 0); + assert!(status.success()); assert_eq!(output_str.trim().to_owned(), ~"hello"); // FIXME #7224 if !running_on_valgrind() { @@ -472,7 +476,7 @@ mod tests { let run::ProcessOutput {status, output, error} = prog.finish_with_output(); - assert_eq!(status, 0); + assert!(status.success()); assert_eq!(output, ~[]); // FIXME #7224 if !running_on_valgrind() { diff --git a/src/test/run-pass/rtio-processes.rs b/src/test/run-pass/rtio-processes.rs index 65cf8722eed..2228f284fc3 100644 --- a/src/test/run-pass/rtio-processes.rs +++ b/src/test/run-pass/rtio-processes.rs @@ -24,6 +24,7 @@ // See #9341 use std::rt::io; +use std::rt::io::process; use std::rt::io::process::{Process, ProcessConfig, CreatePipe, Ignored}; use std::str; @@ -42,7 +43,7 @@ fn smoke() { let p = Process::new(args); assert!(p.is_some()); let mut p = p.unwrap(); - assert_eq!(p.wait(), 0); + assert!(p.wait().success()); } #[test] @@ -78,7 +79,27 @@ fn exit_reported_right() { let p = Process::new(args); assert!(p.is_some()); let mut p = p.unwrap(); - assert_eq!(p.wait(), 1); + assert!(p.wait().matches_exit_status(1)); +} + +#[test] +#[cfg(unix, not(target_os="android"))] +fn signal_reported_right() { + let io = ~[]; + let args = ProcessConfig { + program: "/bin/sh", + args: [~"-c", ~"kill -1 $$"], + env: None, + cwd: None, + io: io, + }; + let p = Process::new(args); + assert!(p.is_some()); + let mut p = p.unwrap(); + match p.wait() { + process::ExitSignal(1) => {}, + result => fail!("not terminated by signal 1 (instead, {})", result), + } } fn read_all(input: &mut Reader) -> ~str { @@ -100,7 +121,7 @@ fn run_output(args: ProcessConfig) -> ~str { assert!(p.io[0].is_none()); assert!(p.io[1].is_some()); let ret = read_all(p.io[1].get_mut_ref() as &mut Reader); - assert_eq!(p.wait(), 0); + assert!(p.wait().success()); return ret; } @@ -152,6 +173,6 @@ fn stdin_works() { p.io[0].get_mut_ref().write("foobar".as_bytes()); p.io[0] = None; // close stdin; let out = read_all(p.io[1].get_mut_ref() as &mut Reader); - assert_eq!(p.wait(), 0); + assert!(p.wait().success()); assert_eq!(out, ~"foobar\n"); } diff --git a/src/test/run-pass/signal-exit-status.rs b/src/test/run-pass/signal-exit-status.rs new file mode 100644 index 00000000000..e3dc4dcaadb --- /dev/null +++ b/src/test/run-pass/signal-exit-status.rs @@ -0,0 +1,29 @@ +// copyright 2013 the rust project developers. see the copyright +// file at the top-level directory of this distribution and at +// http://rust-lang.org/copyright. +// +// licensed under the apache license, version 2.0 or the mit license +// , at your +// option. this file may not be copied, modified, or distributed +// except according to those terms. + +// xfail-fast + +use std::{os, run}; +use std::rt::io::process; + +fn main() { + let args = os::args(); + if args.len() >= 2 && args[1] == ~"signal" { + // Raise a segfault. + unsafe { *(0 as *mut int) = 0; } + } else { + let status = run::process_status(args[0], [~"signal"]); + match status { + process::ExitSignal(_) => {}, + _ => fail!("invalid termination (was not signalled): {:?}", status) + } + } +} +