import io::writer_util; import common::mode_run_pass; import common::mode_run_fail; import common::mode_compile_fail; import common::mode_pretty; import common::config; import header::load_props; import header::test_props; import util::logv; export run; fn run(config: config, testfile: str) { if config.verbose { // We're going to be dumping a lot of info. Start on a new line. io::stdout().write_str("\n\n"); } #debug("running %s", testfile); let props = load_props(testfile); alt config.mode { mode_compile_fail { run_cfail_test(config, props, testfile); } mode_run_fail { run_rfail_test(config, props, testfile); } mode_run_pass { run_rpass_test(config, props, testfile); } mode_pretty { run_pretty_test(config, props, testfile); } } } fn run_cfail_test(config: config, props: test_props, testfile: str) { let procres = compile_test(config, props, testfile); if procres.status == 0 { fatal_procres("compile-fail test compiled successfully!", procres); } check_correct_failure_status(procres); let expected_errors = errors::load_errors(testfile); if vec::is_not_empty(expected_errors) { if vec::is_not_empty(props.error_patterns) { fatal("both error pattern and expected errors specified"); } check_expected_errors(expected_errors, testfile, procres); } else { check_error_patterns(props, testfile, procres); } } fn run_rfail_test(config: config, props: test_props, testfile: str) { let mut procres = compile_test(config, props, testfile); if procres.status != 0 { fatal_procres("compilation failed!", procres); } procres = exec_compiled_test(config, props, testfile); // The value our Makefile configures valgrind to return on failure const valgrind_err: int = 100; if procres.status == valgrind_err { fatal_procres("run-fail test isn't valgrind-clean!", procres); } check_correct_failure_status(procres); check_error_patterns(props, testfile, procres); } fn check_correct_failure_status(procres: procres) { // The value the rust runtime returns on failure const rust_err: int = 101; if procres.status != rust_err { fatal_procres( #fmt("failure produced the wrong error code: %d", procres.status), procres); } } fn run_rpass_test(config: config, props: test_props, testfile: str) { let mut procres = compile_test(config, props, testfile); if procres.status != 0 { fatal_procres("compilation failed!", procres); } procres = exec_compiled_test(config, props, testfile); if procres.status != 0 { fatal_procres("test run failed!", procres); } } fn run_pretty_test(config: config, props: test_props, testfile: str) { if option::is_some(props.pp_exact) { logv(config, "testing for exact pretty-printing"); } else { logv(config, "testing for converging pretty-printing"); } let rounds = alt props.pp_exact { option::some(_) { 1 } option::none { 2 } }; let mut srcs = [result::get(io::read_whole_file_str(testfile))]; let mut round = 0; while round < rounds { logv(config, #fmt["pretty-printing round %d", round]); let procres = print_source(config, testfile, srcs[round]); if procres.status != 0 { fatal_procres(#fmt["pretty-printing failed in round %d", round], procres); } srcs += [procres.stdout]; round += 1; } let mut expected = alt props.pp_exact { option::some(file) { let filepath = path::connect(path::dirname(testfile), file); result::get(io::read_whole_file_str(filepath)) } option::none { srcs[vec::len(srcs) - 2u] } }; let mut actual = srcs[vec::len(srcs) - 1u]; if option::is_some(props.pp_exact) { // Now we have to care about line endings let cr = "\r"; check (str::is_not_empty(cr)); actual = str::replace(actual, cr, ""); expected = str::replace(expected, cr, ""); } compare_source(expected, actual); // Finally, let's make sure it actually appears to remain valid code let procres = typecheck_source(config, props, testfile, actual); if procres.status != 0 { fatal_procres("pretty-printed source does not typecheck", procres); } ret; fn print_source(config: config, testfile: str, src: str) -> procres { compose_and_run(config, testfile, make_pp_args, config.compile_lib_path, option::some(src)) } fn make_pp_args(config: config, _testfile: str) -> procargs { let prog = config.rustc_path; let args = ["-", "--pretty", "normal"]; ret {prog: prog, args: args}; } fn compare_source(expected: str, actual: str) { if expected != actual { error("pretty-printed source does not match expected source"); let msg = #fmt["\n\ expected:\n\ ------------------------------------------\n\ %s\n\ ------------------------------------------\n\ actual:\n\ ------------------------------------------\n\ %s\n\ ------------------------------------------\n\ \n", expected, actual]; io::stdout().write_str(msg); fail; } } fn typecheck_source(config: config, props: test_props, testfile: str, src: str) -> procres { compose_and_run_compiler( config, props, testfile, make_typecheck_args, option::some(src)) } fn make_typecheck_args(config: config, _testfile: str) -> procargs { let prog = config.rustc_path; let mut args = ["-", "--no-trans", "--lib", "-L", config.build_base]; args += split_maybe_args(config.rustcflags); ret {prog: prog, args: args}; } } fn check_error_patterns(props: test_props, testfile: str, procres: procres) { if vec::is_empty(props.error_patterns) { fatal("no error pattern specified in " + testfile); } if procres.status == 0 { fatal("process did not return an error status"); } let mut next_err_idx = 0u; let mut next_err_pat = props.error_patterns[next_err_idx]; for line: str in str::split_char(procres.stderr, '\n') { if str::contains(line, next_err_pat) { #debug("found error pattern %s", next_err_pat); next_err_idx += 1u; if next_err_idx == vec::len(props.error_patterns) { #debug("found all error patterns"); ret; } next_err_pat = props.error_patterns[next_err_idx]; } } let missing_patterns = vec::slice(props.error_patterns, next_err_idx, vec::len(props.error_patterns)); if vec::len(missing_patterns) == 1u { fatal_procres(#fmt["error pattern '%s' not found!", missing_patterns[0]], procres); } else { for pattern: str in missing_patterns { error(#fmt["error pattern '%s' not found!", pattern]); } fatal_procres("multiple error patterns not found", procres); } } fn check_expected_errors(expected_errors: [errors::expected_error], testfile: str, procres: procres) { // true if we found the error in question let found_flags = vec::to_mut(vec::from_elem( vec::len(expected_errors), false)); if procres.status == 0 { fatal("process did not return an error status"); } let prefixes = vec::map(expected_errors, {|ee| #fmt("%s:%u:", testfile, ee.line) }); // Scan and extract our error/warning messages, // which look like: // filename:line1:col1: line2:col2: *error:* msg // filename:line1:col1: line2:col2: *warning:* msg // where line1:col1: is the starting point, line2:col2: // is the ending point, and * represents ANSI color codes. for line: str in str::split_char(procres.stderr, '\n') { let mut was_expected = false; vec::iteri(expected_errors) {|i, ee| if !found_flags[i] { #debug["prefix=%s ee.kind=%s ee.msg=%s line=%s", prefixes[i], ee.kind, ee.msg, line]; if (str::starts_with(line, prefixes[i]) && str::contains(line, ee.kind) && str::contains(line, ee.msg)) { found_flags[i] = true; was_expected = true; } } } // ignore this msg which gets printed at the end if str::contains(line, "aborting due to previous errors") { was_expected = true; } if !was_expected && (str::contains(line, "error") || str::contains(line, "warning")) { fatal_procres(#fmt["unexpected error pattern '%s'!", line], procres); } } uint::range(0u, vec::len(found_flags)) {|i| if !found_flags[i] { let ee = expected_errors[i]; fatal_procres(#fmt["expected %s on line %u not found: %s", ee.kind, ee.line, ee.msg], procres); } } } type procargs = {prog: str, args: [str]}; type procres = {status: int, stdout: str, stderr: str, cmdline: str}; fn compile_test(config: config, props: test_props, testfile: str) -> procres { compose_and_run_compiler(config, props, testfile, make_compile_args(_, props, [], make_exe_name, _), none) } fn exec_compiled_test(config: config, props: test_props, testfile: str) -> procres { compose_and_run(config, testfile, bind make_run_args(_, props, _), config.run_lib_path, option::none) } fn compose_and_run_compiler( config: config, props: test_props, testfile: str, mk_args: fn(config: config, _testfile: str) -> procargs, input: option) -> procres { vec::iter(props.aux_builds) {|rel_ab| let abs_ab = path::connect(config.aux_base, rel_ab); let auxres = compose_and_run(config, abs_ab, make_compile_args(_, props, ["--lib"], make_lib_name, _), config.compile_lib_path, option::none); if auxres.status != 0 { fatal_procres( #fmt["auxiliary build of %s failed to compile: ", abs_ab], auxres); } } compose_and_run(config, testfile, mk_args, config.compile_lib_path, input) } fn compose_and_run(config: config, testfile: str, make_args: fn(config, str) -> procargs, lib_path: str, input: option) -> procres { let procargs = make_args(config, testfile); ret program_output(config, testfile, lib_path, procargs.prog, procargs.args, input); } fn make_compile_args(config: config, props: test_props, extras: [str], xform: fn(config, str) -> str, testfile: str) -> procargs { let prog = config.rustc_path; let mut args = [testfile, "-o", xform(config, testfile), "-L", config.build_base] + extras; args += split_maybe_args(config.rustcflags); args += split_maybe_args(props.compile_flags); ret {prog: prog, args: args}; } fn make_lib_name(config: config, testfile: str) -> str { // what we return here is not particularly important, as it // happens; rustc ignores everything except for the directory. output_base_name(config, testfile) } fn make_exe_name(config: config, testfile: str) -> str { output_base_name(config, testfile) + os::exe_suffix() } fn make_run_args(config: config, _props: test_props, testfile: str) -> procargs { let toolargs = { // If we've got another tool to run under (valgrind), // then split apart its command let runtool = alt config.runtool { option::some(s) { option::some(s) } option::none { option::none } }; split_maybe_args(runtool) }; let args = toolargs + [make_exe_name(config, testfile)]; ret {prog: args[0], args: vec::slice(args, 1u, vec::len(args))}; } fn split_maybe_args(argstr: option) -> [str] { fn rm_whitespace(v: [str]) -> [str] { fn flt(&&s: str) -> option { if !str::is_whitespace(s) { option::some(s) } else { option::none } } vec::filter_map(v, flt) } alt argstr { option::some(s) { rm_whitespace(str::split_char(s, ' ')) } option::none { [] } } } fn program_output(config: config, testfile: str, lib_path: str, prog: str, args: [str], input: option) -> procres { let cmdline = { let cmdline = make_cmdline(lib_path, prog, args); logv(config, #fmt["executing %s", cmdline]); cmdline }; let res = procsrv::run(lib_path, prog, args, input); dump_output(config, testfile, res.out, res.err); ret {status: res.status, stdout: res.out, stderr: res.err, cmdline: cmdline}; } // Linux and mac don't require adjusting the library search path #[cfg(target_os = "linux")] #[cfg(target_os = "macos")] #[cfg(target_os = "freebsd")] fn make_cmdline(_libpath: str, prog: str, args: [str]) -> str { #fmt["%s %s", prog, str::connect(args, " ")] } #[cfg(target_os = "win32")] fn make_cmdline(libpath: str, prog: str, args: [str]) -> str { #fmt["%s %s %s", lib_path_cmd_prefix(libpath), prog, str::connect(args, " ")] } // Build the LD_LIBRARY_PATH variable as it would be seen on the command line // for diagnostic purposes fn lib_path_cmd_prefix(path: str) -> str { #fmt["%s=\"%s\"", util::lib_path_env_var(), util::make_new_path(path)] } fn dump_output(config: config, testfile: str, out: str, err: str) { dump_output_file(config, testfile, out, "out"); dump_output_file(config, testfile, err, "err"); maybe_dump_to_stdout(config, out, err); } fn dump_output_file(config: config, testfile: str, out: str, extension: str) { let outfile = make_out_name(config, testfile, extension); let writer = result::get( io::file_writer(outfile, [io::create, io::truncate])); writer.write_str(out); } fn make_out_name(config: config, testfile: str, extension: str) -> str { output_base_name(config, testfile) + "." + extension } fn output_base_name(config: config, testfile: str) -> str { let base = config.build_base; let filename = { let parts = str::split_char(path::basename(testfile), '.'); str::connect(vec::slice(parts, 0u, vec::len(parts) - 1u), ".") }; #fmt["%s%s.%s", base, filename, config.stage_id] } fn maybe_dump_to_stdout(config: config, out: str, err: str) { if config.verbose { let sep1 = #fmt["------%s------------------------------", "stdout"]; let sep2 = #fmt["------%s------------------------------", "stderr"]; let sep3 = "------------------------------------------"; io::stdout().write_line(sep1); io::stdout().write_line(out); io::stdout().write_line(sep2); io::stdout().write_line(err); io::stdout().write_line(sep3); } } fn error(err: str) { io::stdout().write_line(#fmt["\nerror: %s", err]); } fn fatal(err: str) -> ! { error(err); fail; } fn fatal_procres(err: str, procres: procres) -> ! { let msg = #fmt["\n\ error: %s\n\ command: %s\n\ stdout:\n\ ------------------------------------------\n\ %s\n\ ------------------------------------------\n\ stderr:\n\ ------------------------------------------\n\ %s\n\ ------------------------------------------\n\ \n", err, procres.cmdline, procres.stdout, procres.stderr]; io::stdout().write_str(msg); fail; }