]> git.lizzy.rs Git - rust.git/blob - src/compiletest/runtest.rs
796fc2c802b58ce6ad199ed5bf09a495cf0a6fdf
[rust.git] / src / compiletest / runtest.rs
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use common::config;
12 use common::mode_compile_fail;
13 use common::mode_pretty;
14 use common::mode_run_fail;
15 use common::mode_run_pass;
16 use errors;
17 use header::TestProps;
18 use header::load_props;
19 use procsrv;
20 use util::logv;
21 #[cfg(target_os = "win32")]
22 use util;
23
24 use std::io::File;
25 use std::io::fs;
26 use std::io::net::ip::{Ipv4Addr, SocketAddr};
27 use std::io::net::tcp;
28 use std::io::process::ProcessExit;
29 use std::io::process;
30 use std::io::timer;
31 use std::io;
32 use std::os;
33 use std::str;
34 use std::task;
35 use std::slice;
36
37 use test::MetricMap;
38
39 pub fn run(config: config, testfile: ~str) {
40
41     match config.target.as_slice() {
42
43         "arm-linux-androideabi" => {
44             if !config.adb_device_status {
45                 fail!("android device not available");
46             }
47         }
48
49         _=> { }
50     }
51
52     let mut _mm = MetricMap::new();
53     run_metrics(config, testfile, &mut _mm);
54 }
55
56 pub fn run_metrics(config: config, testfile: ~str, mm: &mut MetricMap) {
57     if config.verbose {
58         // We're going to be dumping a lot of info. Start on a new line.
59         print!("\n\n");
60     }
61     let testfile = Path::new(testfile);
62     debug!("running {}", testfile.display());
63     let props = load_props(&testfile);
64     debug!("loaded props");
65     match config.mode {
66       mode_compile_fail => run_cfail_test(&config, &props, &testfile),
67       mode_run_fail => run_rfail_test(&config, &props, &testfile),
68       mode_run_pass => run_rpass_test(&config, &props, &testfile),
69       mode_pretty => run_pretty_test(&config, &props, &testfile),
70       mode_debug_info => run_debuginfo_test(&config, &props, &testfile),
71       mode_codegen => run_codegen_test(&config, &props, &testfile, mm)
72     }
73 }
74
75 fn run_cfail_test(config: &config, props: &TestProps, testfile: &Path) {
76     let proc_res = compile_test(config, props, testfile);
77
78     if proc_res.status.success() {
79         fatal_ProcRes(~"compile-fail test compiled successfully!", &proc_res);
80     }
81
82     check_correct_failure_status(&proc_res);
83
84     let expected_errors = errors::load_errors(testfile);
85     if !expected_errors.is_empty() {
86         if !props.error_patterns.is_empty() {
87             fatal(~"both error pattern and expected errors specified");
88         }
89         check_expected_errors(expected_errors, testfile, &proc_res);
90     } else {
91         check_error_patterns(props, testfile, &proc_res);
92     }
93 }
94
95 fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) {
96     let proc_res = if !config.jit {
97         let proc_res = compile_test(config, props, testfile);
98
99         if !proc_res.status.success() {
100             fatal_ProcRes(~"compilation failed!", &proc_res);
101         }
102
103         exec_compiled_test(config, props, testfile)
104     } else {
105         jit_test(config, props, testfile)
106     };
107
108     // The value our Makefile configures valgrind to return on failure
109     static VALGRIND_ERR: int = 100;
110     if proc_res.status.matches_exit_status(VALGRIND_ERR) {
111         fatal_ProcRes(~"run-fail test isn't valgrind-clean!", &proc_res);
112     }
113
114     check_correct_failure_status(&proc_res);
115     check_error_patterns(props, testfile, &proc_res);
116 }
117
118 fn check_correct_failure_status(proc_res: &ProcRes) {
119     // The value the rust runtime returns on failure
120     static RUST_ERR: int = 101;
121     if !proc_res.status.matches_exit_status(RUST_ERR) {
122         fatal_ProcRes(
123             format!("failure produced the wrong error: {}", proc_res.status),
124             proc_res);
125     }
126 }
127
128 fn run_rpass_test(config: &config, props: &TestProps, testfile: &Path) {
129     if !config.jit {
130         let mut proc_res = compile_test(config, props, testfile);
131
132         if !proc_res.status.success() {
133             fatal_ProcRes(~"compilation failed!", &proc_res);
134         }
135
136         proc_res = exec_compiled_test(config, props, testfile);
137
138         if !proc_res.status.success() {
139             fatal_ProcRes(~"test run failed!", &proc_res);
140         }
141     } else {
142         let proc_res = jit_test(config, props, testfile);
143
144         if !proc_res.status.success() { fatal_ProcRes(~"jit failed!", &proc_res); }
145     }
146 }
147
148 fn run_pretty_test(config: &config, props: &TestProps, testfile: &Path) {
149     if props.pp_exact.is_some() {
150         logv(config, ~"testing for exact pretty-printing");
151     } else { logv(config, ~"testing for converging pretty-printing"); }
152
153     let rounds =
154         match props.pp_exact { Some(_) => 1, None => 2 };
155
156     let src = File::open(testfile).read_to_end().unwrap();
157     let src = str::from_utf8_owned(src).unwrap();
158     let mut srcs = ~[src];
159
160     let mut round = 0;
161     while round < rounds {
162         logv(config, format!("pretty-printing round {}", round));
163         let proc_res = print_source(config, testfile, srcs[round].clone());
164
165         if !proc_res.status.success() {
166             fatal_ProcRes(format!("pretty-printing failed in round {}", round),
167                           &proc_res);
168         }
169
170         let ProcRes{ stdout, .. } = proc_res;
171         srcs.push(stdout);
172         round += 1;
173     }
174
175     let mut expected = match props.pp_exact {
176         Some(ref file) => {
177             let filepath = testfile.dir_path().join(file);
178             let s = File::open(&filepath).read_to_end().unwrap();
179             str::from_utf8_owned(s).unwrap()
180           }
181           None => { srcs[srcs.len() - 2u].clone() }
182         };
183     let mut actual = srcs[srcs.len() - 1u].clone();
184
185     if props.pp_exact.is_some() {
186         // Now we have to care about line endings
187         let cr = ~"\r";
188         actual = actual.replace(cr, "");
189         expected = expected.replace(cr, "");
190     }
191
192     compare_source(expected, actual);
193
194     // Finally, let's make sure it actually appears to remain valid code
195     let proc_res = typecheck_source(config, props, testfile, actual);
196
197     if !proc_res.status.success() {
198         fatal_ProcRes(~"pretty-printed source does not typecheck", &proc_res);
199     }
200
201     return;
202
203     fn print_source(config: &config, testfile: &Path, src: ~str) -> ProcRes {
204         compose_and_run(config, testfile, make_pp_args(config, testfile),
205                         ~[], config.compile_lib_path, Some(src))
206     }
207
208     fn make_pp_args(config: &config, _testfile: &Path) -> ProcArgs {
209         let args = ~[~"-", ~"--pretty", ~"normal",
210                      ~"--target=" + config.target];
211         // FIXME (#9639): This needs to handle non-utf8 paths
212         return ProcArgs {prog: config.rustc_path.as_str().unwrap().to_owned(), args: args};
213     }
214
215     fn compare_source(expected: &str, actual: &str) {
216         if expected != actual {
217             error(~"pretty-printed source does not match expected source");
218             println!("\n\
219 expected:\n\
220 ------------------------------------------\n\
221 {}\n\
222 ------------------------------------------\n\
223 actual:\n\
224 ------------------------------------------\n\
225 {}\n\
226 ------------------------------------------\n\
227 \n",
228                      expected, actual);
229             fail!();
230         }
231     }
232
233     fn typecheck_source(config: &config, props: &TestProps,
234                         testfile: &Path, src: ~str) -> ProcRes {
235         let args = make_typecheck_args(config, props, testfile);
236         compose_and_run_compiler(config, props, testfile, args, Some(src))
237     }
238
239     fn make_typecheck_args(config: &config, props: &TestProps, testfile: &Path) -> ProcArgs {
240         let aux_dir = aux_output_dir_name(config, testfile);
241         let target = if props.force_host {
242             config.host.as_slice()
243         } else {
244             config.target.as_slice()
245         };
246         // FIXME (#9639): This needs to handle non-utf8 paths
247         let mut args = ~[~"-",
248                          ~"--no-trans", ~"--crate-type=lib",
249                          ~"--target=" + target,
250                          ~"-L", config.build_base.as_str().unwrap().to_owned(),
251                          ~"-L",
252                          aux_dir.as_str().unwrap().to_owned()];
253         args.push_all_move(split_maybe_args(&config.target_rustcflags));
254         args.push_all_move(split_maybe_args(&props.compile_flags));
255         // FIXME (#9639): This needs to handle non-utf8 paths
256         return ProcArgs {prog: config.rustc_path.as_str().unwrap().to_owned(), args: args};
257     }
258 }
259
260 fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) {
261     let mut config = config {
262         target_rustcflags: cleanup_debug_info_options(&config.target_rustcflags),
263         host_rustcflags: cleanup_debug_info_options(&config.host_rustcflags),
264         .. config.clone()
265     };
266
267     let config = &mut config;
268     let check_lines = &props.check_lines;
269     let mut cmds = props.debugger_cmds.connect("\n");
270
271     // compile test file (it shoud have 'compile-flags:-g' in the header)
272     let mut proc_res = compile_test(config, props, testfile);
273     if !proc_res.status.success() {
274         fatal_ProcRes(~"compilation failed!", &proc_res);
275     }
276
277     let exe_file = make_exe_name(config, testfile);
278
279     let mut proc_args;
280     match config.target.as_slice() {
281         "arm-linux-androideabi" => {
282
283             cmds = cmds.replace("run","continue");
284
285             // write debugger script
286             let script_str = [~"set charset UTF-8",
287                               format!("file {}",exe_file.as_str().unwrap().to_owned()),
288                               ~"target remote :5039",
289                               cmds,
290                               ~"quit"].connect("\n");
291             debug!("script_str = {}", script_str);
292             dump_output_file(config, testfile, script_str, "debugger.script");
293
294
295             procsrv::run("", config.adb_path,
296                          [~"push", exe_file.as_str().unwrap().to_owned(),
297                           config.adb_test_dir.clone()],
298                          ~[(~"",~"")], Some(~""))
299                 .expect(format!("failed to exec `{}`", config.adb_path));
300
301             procsrv::run("", config.adb_path,
302                          [~"forward", ~"tcp:5039", ~"tcp:5039"],
303                          ~[(~"",~"")], Some(~""))
304                 .expect(format!("failed to exec `{}`", config.adb_path));
305
306             let adb_arg = format!("export LD_LIBRARY_PATH={}; gdbserver :5039 {}/{}",
307                                   config.adb_test_dir.clone(), config.adb_test_dir.clone(),
308                                   str::from_utf8(exe_file.filename().unwrap()).unwrap());
309
310             let mut process = procsrv::run_background("", config.adb_path,
311                                                       [~"shell",adb_arg.clone()],
312                                                       ~[(~"",~"")], Some(~""))
313                 .expect(format!("failed to exec `{}`", config.adb_path));
314             loop {
315                 //waiting 1 second for gdbserver start
316                 timer::sleep(1000);
317                 let result = task::try(proc() {
318                     tcp::TcpStream::connect(SocketAddr {
319                         ip: Ipv4Addr(127, 0, 0, 1),
320                         port: 5039,
321                     }).unwrap();
322                 });
323                 if result.is_err() {
324                     continue;
325                 }
326                 break;
327             }
328
329             let args = split_maybe_args(&config.target_rustcflags);
330             let mut tool_path:~str = ~"";
331             for arg in args.iter() {
332                 if arg.contains("android-cross-path=") {
333                     tool_path = arg.replace("android-cross-path=","");
334                     break;
335                 }
336             }
337
338             if tool_path.equals(&~"") {
339                 fatal(~"cannot found android cross path");
340             }
341
342             let debugger_script = make_out_name(config, testfile, "debugger.script");
343             // FIXME (#9639): This needs to handle non-utf8 paths
344             let debugger_opts = ~[~"-quiet", ~"-batch", ~"-nx",
345                                   "-command=" + debugger_script.as_str().unwrap().to_owned()];
346
347             let gdb_path = tool_path.append("/bin/arm-linux-androideabi-gdb");
348             let procsrv::Result{ out, err, status }=
349                 procsrv::run("",
350                              gdb_path,
351                              debugger_opts, ~[(~"",~"")], None)
352                 .expect(format!("failed to exec `{}`", gdb_path));
353             let cmdline = {
354                 let cmdline = make_cmdline("", "arm-linux-androideabi-gdb", debugger_opts);
355                 logv(config, format!("executing {}", cmdline));
356                 cmdline
357             };
358
359             proc_res = ProcRes {status: status,
360                                stdout: out,
361                                stderr: err,
362                                cmdline: cmdline};
363             process.signal_kill().unwrap();
364         }
365
366         _=> {
367             // write debugger script
368             let script_str = [~"set charset UTF-8",
369                 cmds,
370                 ~"quit\n"].connect("\n");
371             debug!("script_str = {}", script_str);
372             dump_output_file(config, testfile, script_str, "debugger.script");
373
374             // run debugger script with gdb
375             #[cfg(windows)]
376             fn debugger() -> ~str { ~"gdb.exe" }
377             #[cfg(unix)]
378             fn debugger() -> ~str { ~"gdb" }
379
380             let debugger_script = make_out_name(config, testfile, "debugger.script");
381
382             // FIXME (#9639): This needs to handle non-utf8 paths
383             let debugger_opts = ~[~"-quiet", ~"-batch", ~"-nx",
384                 "-command=" + debugger_script.as_str().unwrap().to_owned(),
385                 exe_file.as_str().unwrap().to_owned()];
386             proc_args = ProcArgs {prog: debugger(), args: debugger_opts};
387             proc_res = compose_and_run(config, testfile, proc_args, ~[], "", None);
388         }
389     }
390
391     if !proc_res.status.success() {
392         fatal(~"gdb failed to execute");
393     }
394     let num_check_lines = check_lines.len();
395     if num_check_lines > 0 {
396         // Allow check lines to leave parts unspecified (e.g., uninitialized
397         // bits in the wrong case of an enum) with the notation "[...]".
398         let check_fragments: ~[~[&str]] = check_lines.map(|s| s.split_str("[...]").collect());
399         // check if each line in props.check_lines appears in the
400         // output (in order)
401         let mut i = 0u;
402         for line in proc_res.stdout.lines() {
403             let mut rest = line.trim();
404             let mut first = true;
405             let mut failed = false;
406             for &frag in check_fragments[i].iter() {
407                 let found = if first {
408                     if rest.starts_with(frag) { Some(0) } else { None }
409                 } else {
410                     rest.find_str(frag)
411                 };
412                 match found {
413                     None => {
414                         failed = true;
415                         break;
416                     }
417                     Some(i) => {
418                         rest = rest.slice_from(i + frag.len());
419                     }
420                 }
421                 first = false;
422             }
423             if !failed && rest.len() == 0 {
424                 i += 1u;
425             }
426             if i == num_check_lines {
427                 // all lines checked
428                 break;
429             }
430         }
431         if i != num_check_lines {
432             fatal_ProcRes(format!("line not found in debugger output: {}",
433                                   check_lines[i]), &proc_res);
434         }
435     }
436
437     fn cleanup_debug_info_options(options: &Option<~str>) -> Option<~str> {
438         if options.is_none() {
439             return None;
440         }
441
442         // Remove options that are either unwanted (-O) or may lead to duplicates due to RUSTFLAGS.
443         let options_to_remove = [~"-O", ~"-g", ~"--debuginfo"];
444         let new_options = split_maybe_args(options).move_iter()
445                                                    .filter(|x| !options_to_remove.contains(x))
446                                                    .to_owned_vec()
447                                                    .connect(" ");
448         Some(new_options)
449     }
450 }
451
452 fn check_error_patterns(props: &TestProps,
453                         testfile: &Path,
454                         proc_res: &ProcRes) {
455     if props.error_patterns.is_empty() {
456         fatal(~"no error pattern specified in " + testfile.display().as_maybe_owned().as_slice());
457     }
458
459     if proc_res.status.success() {
460         fatal(~"process did not return an error status");
461     }
462
463     let mut next_err_idx = 0u;
464     let mut next_err_pat = &props.error_patterns[next_err_idx];
465     let mut done = false;
466     let output_to_check = if props.check_stdout {
467         proc_res.stdout + proc_res.stderr
468     } else {
469         proc_res.stderr.clone()
470     };
471     for line in output_to_check.lines() {
472         if line.contains(*next_err_pat) {
473             debug!("found error pattern {}", *next_err_pat);
474             next_err_idx += 1u;
475             if next_err_idx == props.error_patterns.len() {
476                 debug!("found all error patterns");
477                 done = true;
478                 break;
479             }
480             next_err_pat = &props.error_patterns[next_err_idx];
481         }
482     }
483     if done { return; }
484
485     let missing_patterns =
486         props.error_patterns.slice(next_err_idx, props.error_patterns.len());
487     if missing_patterns.len() == 1u {
488         fatal_ProcRes(format!("error pattern '{}' not found!",
489                               missing_patterns[0]), proc_res);
490     } else {
491         for pattern in missing_patterns.iter() {
492             error(format!("error pattern '{}' not found!", *pattern));
493         }
494         fatal_ProcRes(~"multiple error patterns not found", proc_res);
495     }
496 }
497
498 fn check_expected_errors(expected_errors: ~[errors::ExpectedError],
499                          testfile: &Path,
500                          proc_res: &ProcRes) {
501
502     // true if we found the error in question
503     let mut found_flags = slice::from_elem(
504         expected_errors.len(), false);
505
506     if proc_res.status.success() {
507         fatal(~"process did not return an error status");
508     }
509
510     let prefixes = expected_errors.iter().map(|ee| {
511         format!("{}:{}:", testfile.display(), ee.line)
512     }).collect::<~[~str]>();
513
514     #[cfg(target_os = "win32")]
515     fn to_lower( s : &str ) -> ~str {
516         let i = s.chars();
517         let c : ~[char] = i.map( |c| {
518             if c.is_ascii() {
519                 c.to_ascii().to_lower().to_char()
520             } else {
521                 c
522             }
523         } ).collect();
524         str::from_chars( c )
525     }
526
527     #[cfg(target_os = "win32")]
528     fn prefix_matches( line : &str, prefix : &str ) -> bool {
529         to_lower(line).starts_with( to_lower(prefix) )
530     }
531
532     #[cfg(target_os = "linux")]
533     #[cfg(target_os = "macos")]
534     #[cfg(target_os = "freebsd")]
535     fn prefix_matches( line : &str, prefix : &str ) -> bool {
536         line.starts_with( prefix )
537     }
538
539     // Scan and extract our error/warning messages,
540     // which look like:
541     //    filename:line1:col1: line2:col2: *error:* msg
542     //    filename:line1:col1: line2:col2: *warning:* msg
543     // where line1:col1: is the starting point, line2:col2:
544     // is the ending point, and * represents ANSI color codes.
545     for line in proc_res.stderr.lines() {
546         let mut was_expected = false;
547         for (i, ee) in expected_errors.iter().enumerate() {
548             if !found_flags[i] {
549                 debug!("prefix={} ee.kind={} ee.msg={} line={}",
550                        prefixes[i], ee.kind, ee.msg, line);
551                 if prefix_matches(line, prefixes[i]) &&
552                     line.contains(ee.kind) &&
553                     line.contains(ee.msg) {
554                     found_flags[i] = true;
555                     was_expected = true;
556                     break;
557                 }
558             }
559         }
560
561         // ignore this msg which gets printed at the end
562         if line.contains("aborting due to") {
563             was_expected = true;
564         }
565
566         if !was_expected && is_compiler_error_or_warning(line) {
567             fatal_ProcRes(format!("unexpected compiler error or warning: '{}'",
568                                line),
569                           proc_res);
570         }
571     }
572
573     for (i, &flag) in found_flags.iter().enumerate() {
574         if !flag {
575             let ee = &expected_errors[i];
576             fatal_ProcRes(format!("expected {} on line {} not found: {}",
577                                ee.kind, ee.line, ee.msg), proc_res);
578         }
579     }
580 }
581
582 fn is_compiler_error_or_warning(line: &str) -> bool {
583     let mut i = 0u;
584     return
585         scan_until_char(line, ':', &mut i) &&
586         scan_char(line, ':', &mut i) &&
587         scan_integer(line, &mut i) &&
588         scan_char(line, ':', &mut i) &&
589         scan_integer(line, &mut i) &&
590         scan_char(line, ':', &mut i) &&
591         scan_char(line, ' ', &mut i) &&
592         scan_integer(line, &mut i) &&
593         scan_char(line, ':', &mut i) &&
594         scan_integer(line, &mut i) &&
595         scan_char(line, ' ', &mut i) &&
596         (scan_string(line, "error", &mut i) ||
597          scan_string(line, "warning", &mut i));
598 }
599
600 fn scan_until_char(haystack: &str, needle: char, idx: &mut uint) -> bool {
601     if *idx >= haystack.len() {
602         return false;
603     }
604     let opt = haystack.slice_from(*idx).find(needle);
605     if opt.is_none() {
606         return false;
607     }
608     *idx = opt.unwrap();
609     return true;
610 }
611
612 fn scan_char(haystack: &str, needle: char, idx: &mut uint) -> bool {
613     if *idx >= haystack.len() {
614         return false;
615     }
616     let range = haystack.char_range_at(*idx);
617     if range.ch != needle {
618         return false;
619     }
620     *idx = range.next;
621     return true;
622 }
623
624 fn scan_integer(haystack: &str, idx: &mut uint) -> bool {
625     let mut i = *idx;
626     while i < haystack.len() {
627         let range = haystack.char_range_at(i);
628         if range.ch < '0' || '9' < range.ch {
629             break;
630         }
631         i = range.next;
632     }
633     if i == *idx {
634         return false;
635     }
636     *idx = i;
637     return true;
638 }
639
640 fn scan_string(haystack: &str, needle: &str, idx: &mut uint) -> bool {
641     let mut haystack_i = *idx;
642     let mut needle_i = 0u;
643     while needle_i < needle.len() {
644         if haystack_i >= haystack.len() {
645             return false;
646         }
647         let range = haystack.char_range_at(haystack_i);
648         haystack_i = range.next;
649         if !scan_char(needle, range.ch, &mut needle_i) {
650             return false;
651         }
652     }
653     *idx = haystack_i;
654     return true;
655 }
656
657 struct ProcArgs {prog: ~str, args: ~[~str]}
658
659 struct ProcRes {status: ProcessExit, stdout: ~str, stderr: ~str, cmdline: ~str}
660
661 fn compile_test(config: &config, props: &TestProps,
662                 testfile: &Path) -> ProcRes {
663     compile_test_(config, props, testfile, [])
664 }
665
666 fn jit_test(config: &config, props: &TestProps, testfile: &Path) -> ProcRes {
667     compile_test_(config, props, testfile, [~"--jit"])
668 }
669
670 fn compile_test_(config: &config, props: &TestProps,
671                  testfile: &Path, extra_args: &[~str]) -> ProcRes {
672     let aux_dir = aux_output_dir_name(config, testfile);
673     // FIXME (#9639): This needs to handle non-utf8 paths
674     let link_args = ~[~"-L", aux_dir.as_str().unwrap().to_owned()];
675     let args = make_compile_args(config, props, link_args + extra_args,
676                                  |a, b| ThisFile(make_exe_name(a, b)), testfile);
677     compose_and_run_compiler(config, props, testfile, args, None)
678 }
679
680 fn exec_compiled_test(config: &config, props: &TestProps,
681                       testfile: &Path) -> ProcRes {
682
683     let env = props.exec_env.clone();
684
685     match config.target.as_slice() {
686
687         "arm-linux-androideabi" => {
688             _arm_exec_compiled_test(config, props, testfile, env)
689         }
690
691         _=> {
692             compose_and_run(config, testfile,
693                             make_run_args(config, props, testfile),
694                             env,
695                             config.run_lib_path, None)
696         }
697     }
698 }
699
700 fn compose_and_run_compiler(
701     config: &config,
702     props: &TestProps,
703     testfile: &Path,
704     args: ProcArgs,
705     input: Option<~str>) -> ProcRes {
706
707     if !props.aux_builds.is_empty() {
708         ensure_dir(&aux_output_dir_name(config, testfile));
709     }
710
711     let aux_dir = aux_output_dir_name(config, testfile);
712     // FIXME (#9639): This needs to handle non-utf8 paths
713     let extra_link_args = ~[~"-L", aux_dir.as_str().unwrap().to_owned()];
714
715     for rel_ab in props.aux_builds.iter() {
716         let abs_ab = config.aux_base.join(rel_ab.as_slice());
717         let aux_props = load_props(&abs_ab);
718         let crate_type = if aux_props.no_prefer_dynamic {
719             ~[]
720         } else {
721             ~[~"--crate-type=dylib"]
722         };
723         let aux_args =
724             make_compile_args(config, &aux_props, crate_type + extra_link_args,
725                               |a,b| {
726                                   let f = make_lib_name(a, b, testfile);
727                                   ThisDirectory(f.dir_path())
728                               }, &abs_ab);
729         let auxres = compose_and_run(config, &abs_ab, aux_args, ~[],
730                                      config.compile_lib_path, None);
731         if !auxres.status.success() {
732             fatal_ProcRes(
733                 format!("auxiliary build of {} failed to compile: ",
734                      abs_ab.display()),
735                 &auxres);
736         }
737
738         match config.target.as_slice() {
739
740             "arm-linux-androideabi" => {
741                 _arm_push_aux_shared_library(config, testfile);
742             }
743
744             _=> { }
745         }
746     }
747
748     compose_and_run(config, testfile, args, ~[],
749                     config.compile_lib_path, input)
750 }
751
752 fn ensure_dir(path: &Path) {
753     if path.is_dir() { return; }
754     fs::mkdir(path, io::UserRWX).unwrap();
755 }
756
757 fn compose_and_run(config: &config, testfile: &Path,
758                    ProcArgs{ args, prog }: ProcArgs,
759                    procenv: ~[(~str, ~str)],
760                    lib_path: &str,
761                    input: Option<~str>) -> ProcRes {
762     return program_output(config, testfile, lib_path,
763                           prog, args, procenv, input);
764 }
765
766 enum TargetLocation {
767     ThisFile(Path),
768     ThisDirectory(Path),
769 }
770
771 fn make_compile_args(config: &config,
772                      props: &TestProps,
773                      extras: ~[~str],
774                      xform: |&config, &Path| -> TargetLocation,
775                      testfile: &Path)
776                      -> ProcArgs {
777     let xform_file = xform(config, testfile);
778     let target = if props.force_host {
779         config.host.as_slice()
780     } else {
781         config.target.as_slice()
782     };
783     // FIXME (#9639): This needs to handle non-utf8 paths
784     let mut args = ~[testfile.as_str().unwrap().to_owned(),
785                      ~"-L", config.build_base.as_str().unwrap().to_owned(),
786                      ~"--target=" + target]
787         + extras;
788     if !props.no_prefer_dynamic {
789         args.push(~"-C");
790         args.push(~"prefer-dynamic");
791     }
792     let path = match xform_file {
793         ThisFile(path) => { args.push(~"-o"); path }
794         ThisDirectory(path) => { args.push(~"--out-dir"); path }
795     };
796     args.push(path.as_str().unwrap().to_owned());
797     if props.force_host {
798         args.push_all_move(split_maybe_args(&config.host_rustcflags));
799     } else {
800         args.push_all_move(split_maybe_args(&config.target_rustcflags));
801     }
802     args.push_all_move(split_maybe_args(&props.compile_flags));
803     return ProcArgs {prog: config.rustc_path.as_str().unwrap().to_owned(), args: args};
804 }
805
806 fn make_lib_name(config: &config, auxfile: &Path, testfile: &Path) -> Path {
807     // what we return here is not particularly important, as it
808     // happens; rustc ignores everything except for the directory.
809     let auxname = output_testname(auxfile);
810     aux_output_dir_name(config, testfile).join(&auxname)
811 }
812
813 fn make_exe_name(config: &config, testfile: &Path) -> Path {
814     let mut f = output_base_name(config, testfile);
815     if !os::consts::EXE_SUFFIX.is_empty() {
816         match f.filename().map(|s| s + os::consts::EXE_SUFFIX.as_bytes()) {
817             Some(v) => f.set_filename(v),
818             None => ()
819         }
820     }
821     f
822 }
823
824 fn make_run_args(config: &config, _props: &TestProps, testfile: &Path) ->
825    ProcArgs {
826     // If we've got another tool to run under (valgrind),
827     // then split apart its command
828     let mut args = split_maybe_args(&config.runtool);
829     let exe_file = make_exe_name(config, testfile);
830     // FIXME (#9639): This needs to handle non-utf8 paths
831     args.push(exe_file.as_str().unwrap().to_owned());
832     let prog = args.shift().unwrap();
833     return ProcArgs {prog: prog, args: args};
834 }
835
836 fn split_maybe_args(argstr: &Option<~str>) -> ~[~str] {
837     match *argstr {
838         Some(ref s) => {
839             s.split(' ')
840                 .filter_map(|s| if s.is_whitespace() {None} else {Some(s.to_owned())})
841                 .collect()
842         }
843         None => ~[]
844     }
845 }
846
847 fn program_output(config: &config, testfile: &Path, lib_path: &str, prog: ~str,
848                   args: ~[~str], env: ~[(~str, ~str)],
849                   input: Option<~str>) -> ProcRes {
850     let cmdline =
851         {
852             let cmdline = make_cmdline(lib_path, prog, args);
853             logv(config, format!("executing {}", cmdline));
854             cmdline
855         };
856     let procsrv::Result{ out, err, status } =
857             procsrv::run(lib_path, prog, args, env, input)
858             .expect(format!("failed to exec `{}`", prog));
859     dump_output(config, testfile, out, err);
860     return ProcRes {status: status,
861          stdout: out,
862          stderr: err,
863          cmdline: cmdline};
864 }
865
866 // Linux and mac don't require adjusting the library search path
867 #[cfg(target_os = "linux")]
868 #[cfg(target_os = "macos")]
869 #[cfg(target_os = "freebsd")]
870 fn make_cmdline(_libpath: &str, prog: &str, args: &[~str]) -> ~str {
871     format!("{} {}", prog, args.connect(" "))
872 }
873
874 #[cfg(target_os = "win32")]
875 fn make_cmdline(libpath: &str, prog: &str, args: &[~str]) -> ~str {
876     format!("{} {} {}", lib_path_cmd_prefix(libpath), prog,
877          args.connect(" "))
878 }
879
880 // Build the LD_LIBRARY_PATH variable as it would be seen on the command line
881 // for diagnostic purposes
882 #[cfg(target_os = "win32")]
883 fn lib_path_cmd_prefix(path: &str) -> ~str {
884     format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
885 }
886
887 fn dump_output(config: &config, testfile: &Path, out: &str, err: &str) {
888     dump_output_file(config, testfile, out, "out");
889     dump_output_file(config, testfile, err, "err");
890     maybe_dump_to_stdout(config, out, err);
891 }
892
893 fn dump_output_file(config: &config, testfile: &Path,
894                     out: &str, extension: &str) {
895     let outfile = make_out_name(config, testfile, extension);
896     File::create(&outfile).write(out.as_bytes()).unwrap();
897 }
898
899 fn make_out_name(config: &config, testfile: &Path, extension: &str) -> Path {
900     output_base_name(config, testfile).with_extension(extension)
901 }
902
903 fn aux_output_dir_name(config: &config, testfile: &Path) -> Path {
904     let mut f = output_base_name(config, testfile);
905     match f.filename().map(|s| s + bytes!(".libaux")) {
906         Some(v) => f.set_filename(v),
907         None => ()
908     }
909     f
910 }
911
912 fn output_testname(testfile: &Path) -> Path {
913     Path::new(testfile.filestem().unwrap())
914 }
915
916 fn output_base_name(config: &config, testfile: &Path) -> Path {
917     config.build_base
918         .join(&output_testname(testfile))
919         .with_extension(config.stage_id.as_slice())
920 }
921
922 fn maybe_dump_to_stdout(config: &config, out: &str, err: &str) {
923     if config.verbose {
924         println!("------{}------------------------------", "stdout");
925         println!("{}", out);
926         println!("------{}------------------------------", "stderr");
927         println!("{}", err);
928         println!("------------------------------------------");
929     }
930 }
931
932 fn error(err: ~str) { println!("\nerror: {}", err); }
933
934 fn fatal(err: ~str) -> ! { error(err); fail!(); }
935
936 fn fatal_ProcRes(err: ~str, proc_res: &ProcRes) -> ! {
937     print!("\n\
938 error: {}\n\
939 command: {}\n\
940 stdout:\n\
941 ------------------------------------------\n\
942 {}\n\
943 ------------------------------------------\n\
944 stderr:\n\
945 ------------------------------------------\n\
946 {}\n\
947 ------------------------------------------\n\
948 \n",
949              err, proc_res.cmdline, proc_res.stdout, proc_res.stderr);
950     fail!();
951 }
952
953 fn _arm_exec_compiled_test(config: &config, props: &TestProps,
954                       testfile: &Path, env: ~[(~str, ~str)]) -> ProcRes {
955
956     let args = make_run_args(config, props, testfile);
957     let cmdline = make_cmdline("", args.prog, args.args);
958
959     // get bare program string
960     let mut tvec: ~[~str] = args.prog.split('/').map(|ts| ts.to_owned()).collect();
961     let prog_short = tvec.pop().unwrap();
962
963     // copy to target
964     let copy_result = procsrv::run("", config.adb_path,
965         [~"push", args.prog.clone(), config.adb_test_dir.clone()],
966         ~[(~"",~"")], Some(~""))
967         .expect(format!("failed to exec `{}`", config.adb_path));
968
969     if config.verbose {
970         println!("push ({}) {} {} {}",
971             config.target, args.prog,
972             copy_result.out, copy_result.err);
973     }
974
975     logv(config, format!("executing ({}) {}", config.target, cmdline));
976
977     let mut runargs = ~[];
978
979     // run test via adb_run_wrapper
980     runargs.push(~"shell");
981     for (key, val) in env.move_iter() {
982         runargs.push(format!("{}={}", key, val));
983     }
984     runargs.push(format!("{}/adb_run_wrapper.sh", config.adb_test_dir));
985     runargs.push(format!("{}", config.adb_test_dir));
986     runargs.push(format!("{}", prog_short));
987
988     for tv in args.args.iter() {
989         runargs.push(tv.to_owned());
990     }
991     procsrv::run("", config.adb_path, runargs, ~[(~"",~"")], Some(~""))
992         .expect(format!("failed to exec `{}`", config.adb_path));
993
994     // get exitcode of result
995     runargs = ~[];
996     runargs.push(~"shell");
997     runargs.push(~"cat");
998     runargs.push(format!("{}/{}.exitcode", config.adb_test_dir, prog_short));
999
1000     let procsrv::Result{ out: exitcode_out, err: _, status: _ } =
1001         procsrv::run("", config.adb_path, runargs, ~[(~"",~"")],
1002                      Some(~""))
1003         .expect(format!("failed to exec `{}`", config.adb_path));
1004
1005     let mut exitcode : int = 0;
1006     for c in exitcode_out.chars() {
1007         if !c.is_digit() { break; }
1008         exitcode = exitcode * 10 + match c {
1009             '0' .. '9' => c as int - ('0' as int),
1010             _ => 101,
1011         }
1012     }
1013
1014     // get stdout of result
1015     runargs = ~[];
1016     runargs.push(~"shell");
1017     runargs.push(~"cat");
1018     runargs.push(format!("{}/{}.stdout", config.adb_test_dir, prog_short));
1019
1020     let procsrv::Result{ out: stdout_out, err: _, status: _ } =
1021         procsrv::run("", config.adb_path, runargs, ~[(~"",~"")], Some(~""))
1022         .expect(format!("failed to exec `{}`", config.adb_path));
1023
1024     // get stderr of result
1025     runargs = ~[];
1026     runargs.push(~"shell");
1027     runargs.push(~"cat");
1028     runargs.push(format!("{}/{}.stderr", config.adb_test_dir, prog_short));
1029
1030     let procsrv::Result{ out: stderr_out, err: _, status: _ } =
1031         procsrv::run("", config.adb_path, runargs, ~[(~"",~"")], Some(~""))
1032         .expect(format!("failed to exec `{}`", config.adb_path));
1033
1034     dump_output(config, testfile, stdout_out, stderr_out);
1035
1036     ProcRes {
1037         status: process::ExitStatus(exitcode),
1038         stdout: stdout_out,
1039         stderr: stderr_out,
1040         cmdline: cmdline
1041     }
1042 }
1043
1044 fn _arm_push_aux_shared_library(config: &config, testfile: &Path) {
1045     let tdir = aux_output_dir_name(config, testfile);
1046
1047     let dirs = fs::readdir(&tdir).unwrap();
1048     for file in dirs.iter() {
1049         if file.extension_str() == Some("so") {
1050             // FIXME (#9639): This needs to handle non-utf8 paths
1051             let copy_result = procsrv::run("", config.adb_path,
1052                 [~"push", file.as_str().unwrap().to_owned(), config.adb_test_dir.clone()],
1053                 ~[(~"",~"")], Some(~""))
1054                 .expect(format!("failed to exec `{}`", config.adb_path));
1055
1056             if config.verbose {
1057                 println!("push ({}) {} {} {}",
1058                     config.target, file.display(),
1059                     copy_result.out, copy_result.err);
1060             }
1061         }
1062     }
1063 }
1064
1065 // codegen tests (vs. clang)
1066
1067 fn make_o_name(config: &config, testfile: &Path) -> Path {
1068     output_base_name(config, testfile).with_extension("o")
1069 }
1070
1071 fn append_suffix_to_stem(p: &Path, suffix: &str) -> Path {
1072     if suffix.len() == 0 {
1073         (*p).clone()
1074     } else {
1075         let stem = p.filestem().unwrap();
1076         p.with_filename(stem + bytes!("-") + suffix.as_bytes())
1077     }
1078 }
1079
1080 fn compile_test_and_save_bitcode(config: &config, props: &TestProps,
1081                                  testfile: &Path) -> ProcRes {
1082     let aux_dir = aux_output_dir_name(config, testfile);
1083     // FIXME (#9639): This needs to handle non-utf8 paths
1084     let link_args = ~[~"-L", aux_dir.as_str().unwrap().to_owned()];
1085     let llvm_args = ~[~"--emit=obj", ~"--crate-type=lib", ~"-C", ~"save-temps"];
1086     let args = make_compile_args(config, props,
1087                                  link_args + llvm_args,
1088                                  |a, b| ThisFile(make_o_name(a, b)), testfile);
1089     compose_and_run_compiler(config, props, testfile, args, None)
1090 }
1091
1092 fn compile_cc_with_clang_and_save_bitcode(config: &config, _props: &TestProps,
1093                                           testfile: &Path) -> ProcRes {
1094     let bitcodefile = output_base_name(config, testfile).with_extension("bc");
1095     let bitcodefile = append_suffix_to_stem(&bitcodefile, "clang");
1096     let testcc = testfile.with_extension("cc");
1097     let proc_args = ProcArgs {
1098         // FIXME (#9639): This needs to handle non-utf8 paths
1099         prog: config.clang_path.get_ref().as_str().unwrap().to_owned(),
1100         args: ~[~"-c",
1101                 ~"-emit-llvm",
1102                 ~"-o", bitcodefile.as_str().unwrap().to_owned(),
1103                 testcc.as_str().unwrap().to_owned() ]
1104     };
1105     compose_and_run(config, testfile, proc_args, ~[], "", None)
1106 }
1107
1108 fn extract_function_from_bitcode(config: &config, _props: &TestProps,
1109                                  fname: &str, testfile: &Path,
1110                                  suffix: &str) -> ProcRes {
1111     let bitcodefile = output_base_name(config, testfile).with_extension("bc");
1112     let bitcodefile = append_suffix_to_stem(&bitcodefile, suffix);
1113     let extracted_bc = append_suffix_to_stem(&bitcodefile, "extract");
1114     let prog = config.llvm_bin_path.get_ref().join("llvm-extract");
1115     let proc_args = ProcArgs {
1116         // FIXME (#9639): This needs to handle non-utf8 paths
1117         prog: prog.as_str().unwrap().to_owned(),
1118         args: ~["-func=" + fname,
1119                 "-o=" + extracted_bc.as_str().unwrap(),
1120                 bitcodefile.as_str().unwrap().to_owned() ]
1121     };
1122     compose_and_run(config, testfile, proc_args, ~[], "", None)
1123 }
1124
1125 fn disassemble_extract(config: &config, _props: &TestProps,
1126                        testfile: &Path, suffix: &str) -> ProcRes {
1127     let bitcodefile = output_base_name(config, testfile).with_extension("bc");
1128     let bitcodefile = append_suffix_to_stem(&bitcodefile, suffix);
1129     let extracted_bc = append_suffix_to_stem(&bitcodefile, "extract");
1130     let extracted_ll = extracted_bc.with_extension("ll");
1131     let prog = config.llvm_bin_path.get_ref().join("llvm-dis");
1132     let proc_args = ProcArgs {
1133         // FIXME (#9639): This needs to handle non-utf8 paths
1134         prog: prog.as_str().unwrap().to_owned(),
1135         args: ~["-o=" + extracted_ll.as_str().unwrap(),
1136                 extracted_bc.as_str().unwrap().to_owned() ]
1137     };
1138     compose_and_run(config, testfile, proc_args, ~[], "", None)
1139 }
1140
1141
1142 fn count_extracted_lines(p: &Path) -> uint {
1143     let x = File::open(&p.with_extension("ll")).read_to_end().unwrap();
1144     let x = str::from_utf8_owned(x).unwrap();
1145     x.lines().len()
1146 }
1147
1148
1149 fn run_codegen_test(config: &config, props: &TestProps,
1150                     testfile: &Path, mm: &mut MetricMap) {
1151
1152     if config.llvm_bin_path.is_none() {
1153         fatal(~"missing --llvm-bin-path");
1154     }
1155
1156     if config.clang_path.is_none() {
1157         fatal(~"missing --clang-path");
1158     }
1159
1160     let mut proc_res = compile_test_and_save_bitcode(config, props, testfile);
1161     if !proc_res.status.success() {
1162         fatal_ProcRes(~"compilation failed!", &proc_res);
1163     }
1164
1165     proc_res = extract_function_from_bitcode(config, props, "test", testfile, "");
1166     if !proc_res.status.success() {
1167         fatal_ProcRes(~"extracting 'test' function failed", &proc_res);
1168     }
1169
1170     proc_res = disassemble_extract(config, props, testfile, "");
1171     if !proc_res.status.success() {
1172         fatal_ProcRes(~"disassembling extract failed", &proc_res);
1173     }
1174
1175
1176     let mut proc_res = compile_cc_with_clang_and_save_bitcode(config, props, testfile);
1177     if !proc_res.status.success() {
1178         fatal_ProcRes(~"compilation failed!", &proc_res);
1179     }
1180
1181     proc_res = extract_function_from_bitcode(config, props, "test", testfile, "clang");
1182     if !proc_res.status.success() {
1183         fatal_ProcRes(~"extracting 'test' function failed", &proc_res);
1184     }
1185
1186     proc_res = disassemble_extract(config, props, testfile, "clang");
1187     if !proc_res.status.success() {
1188         fatal_ProcRes(~"disassembling extract failed", &proc_res);
1189     }
1190
1191     let base = output_base_name(config, testfile);
1192     let base_extract = append_suffix_to_stem(&base, "extract");
1193
1194     let base_clang = append_suffix_to_stem(&base, "clang");
1195     let base_clang_extract = append_suffix_to_stem(&base_clang, "extract");
1196
1197     let base_lines = count_extracted_lines(&base_extract);
1198     let clang_lines = count_extracted_lines(&base_clang_extract);
1199
1200     mm.insert_metric("clang-codegen-ratio",
1201                      (base_lines as f64) / (clang_lines as f64),
1202                      0.001);
1203 }