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