test.rs 8.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

11
use std::cell::RefCell;
12
use std::io;
13
use std::io::Process;
14 15 16 17
use std::local_data;
use std::os;
use std::str;

18
use collections::HashSet;
L
Liigo Zhuang 已提交
19
use testing;
20
use extra::tempfile::TempDir;
A
Alex Crichton 已提交
21
use rustc::back::link;
22 23
use rustc::driver::driver;
use rustc::driver::session;
24
use rustc::metadata::creader::Loader;
A
Arcterus 已提交
25
use getopts;
26 27
use syntax::diagnostic;
use syntax::parse;
28
use syntax::codemap::CodeMap;
29 30 31 32 33 34 35 36 37 38

use core;
use clean;
use clean::Clean;
use fold::DocFolder;
use html::markdown;
use passes;
use visit_ast::RustdocVisitor;

pub fn run(input: &str, matches: &getopts::Matches) -> int {
39 40
    let input_path = Path::new(input);
    let input = driver::FileInput(input_path.clone());
41
    let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice()));
42
    let libs = @RefCell::new(libs.move_iter().collect());
43

44
    let sessopts = @session::Options {
45 46
        maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()),
        addl_lib_search_paths: libs,
47
        crate_types: vec!(session::CrateTypeDylib),
48 49 50 51
        .. (*session::basic_options()).clone()
    };


52
    let cm = @CodeMap::new();
53
    let diagnostic_handler = diagnostic::default_handler();
54
    let span_diagnostic_handler =
55 56 57
        diagnostic::mk_span_handler(diagnostic_handler, cm);
    let parsesess = parse::new_parse_sess_special_handler(span_diagnostic_handler,
                                                          cm);
58 59

    let sess = driver::build_session_(sessopts,
60
                                      Some(input_path),
61 62 63 64
                                      parsesess.cm,
                                      span_diagnostic_handler);

    let cfg = driver::build_configuration(sess);
65
    let krate = driver::phase_1_parse_input(sess, cfg, &input);
66
    let loader = &mut Loader::new(sess);
67
    let (krate, _) = driver::phase_2_configure_and_expand(sess, loader, krate);
68 69

    let ctx = @core::DocContext {
70
        krate: krate,
71 72 73 74 75
        tycx: None,
        sess: sess,
    };
    local_data::set(super::ctxtkey, ctx);

A
Alex Crichton 已提交
76
    let mut v = RustdocVisitor::new(ctx, None);
77 78 79 80
    v.visit(&ctx.krate);
    let krate = v.clean();
    let (krate, _) = passes::unindent_comments(krate);
    let (krate, _) = passes::collapse_docs(krate);
81 82 83 84 85 86

    let mut collector = Collector {
        tests: ~[],
        names: ~[],
        cnt: 0,
        libs: libs,
87
        cratename: krate.name.to_owned(),
88
    };
89
    collector.fold_crate(krate);
90 91 92 93 94 95

    let args = matches.opt_strs("test-args");
    let mut args = args.iter().flat_map(|s| s.words()).map(|s| s.to_owned());
    let mut args = args.to_owned_vec();
    args.unshift(~"rustdoctest");

L
Liigo Zhuang 已提交
96
    testing::test_main(args, collector.tests);
97 98 99 100

    0
}

101 102
fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
           no_run: bool) {
103
    let test = maketest(test, cratename);
E
Eduard Burtescu 已提交
104
    let parsesess = parse::new_parse_sess();
105
    let input = driver::StrInput(test);
106

107
    let sessopts = @session::Options {
108
        maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()),
109
        addl_lib_search_paths: @RefCell::new(libs),
110 111
        crate_types: vec!(session::CrateTypeExecutable),
        output_types: vec!(link::OutputTypeExe),
112 113 114 115
        cg: session::CodegenOptions {
            prefer_dynamic: true,
            .. session::basic_codegen_options()
        },
116 117 118
        .. (*session::basic_options()).clone()
    };

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
    // Shuffle around a few input and output handles here. We're going to pass
    // an explicit handle into rustc to collect output messages, but we also
    // want to catch the error message that rustc prints when it fails.
    //
    // We take our task-local stderr (likely set by the test runner), and move
    // it into another task. This helper task then acts as a sink for both the
    // stderr of this task and stderr of rustc itself, copying all the info onto
    // the stderr channel we originally started with.
    //
    // The basic idea is to not use a default_handler() for rustc, and then also
    // not print things by default to the actual stderr.
    let (p, c) = Chan::new();
    let w1 = io::ChanWriter::new(c);
    let w2 = w1.clone();
    let old = io::stdio::set_stderr(~w1);
    spawn(proc() {
        let mut p = io::PortReader::new(p);
        let mut err = old.unwrap_or(~io::stderr() as ~Writer);
        io::util::copy(&mut p, &mut err).unwrap();
    });
    let emitter = diagnostic::EmitterWriter::new(~w2);

    // Compile the code
    let diagnostic_handler = diagnostic::mk_handler(~emitter);
143 144 145 146
    let span_diagnostic_handler =
        diagnostic::mk_span_handler(diagnostic_handler, parsesess.cm);

    let sess = driver::build_session_(sessopts,
147
                                      None,
148 149 150 151 152 153 154 155
                                      parsesess.cm,
                                      span_diagnostic_handler);

    let outdir = TempDir::new("rustdoctest").expect("rustdoc needs a tempdir");
    let out = Some(outdir.path().clone());
    let cfg = driver::build_configuration(sess);
    driver::compile_input(sess, cfg, &input, &out, &None);

156 157
    if no_run { return }

158
    // Run the code!
159
    let exe = outdir.path().join("rust_out");
160
    let out = Process::output(exe.as_str().unwrap(), []);
161
    match out {
162 163 164 165
        Err(e) => fail!("couldn't run the test: {}{}", e,
                        if e.kind == io::PermissionDenied {
                            " - maybe your tempdir is mounted with noexec?"
                        } else { "" }),
A
Alex Crichton 已提交
166
        Ok(out) => {
167 168 169 170
            if should_fail && out.status.success() {
                fail!("test executable succeeded when it should have failed");
            } else if !should_fail && !out.status.success() {
                fail!("test executable failed:\n{}", str::from_utf8(out.error));
171 172 173 174 175
            }
        }
    }
}

176
fn maketest(s: &str, cratename: &str) -> ~str {
177 178 179 180
    let mut prog = ~r"
#[deny(warnings)];
#[allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)];
";
181 182 183 184 185 186 187
    if !s.contains("extern crate") {
        if s.contains("extra") {
            prog.push_str("extern crate extra;\n");
        }
        if s.contains(cratename) {
            prog.push_str(format!("extern crate {};\n", cratename));
        }
188 189 190 191 192 193 194 195 196
    }
    if s.contains("fn main") {
        prog.push_str(s);
    } else {
        prog.push_str("fn main() {\n");
        prog.push_str(s);
        prog.push_str("\n}");
    }

197
    return prog;
198 199 200
}

pub struct Collector {
L
Liigo Zhuang 已提交
201
    priv tests: ~[testing::TestDescAndFn],
202
    priv names: ~[~str],
203
    priv libs: @RefCell<HashSet<Path>>,
204 205 206 207 208
    priv cnt: uint,
    priv cratename: ~str,
}

impl Collector {
209
    pub fn add_test(&mut self, test: &str, should_fail: bool, no_run: bool) {
210 211 212
        let test = test.to_owned();
        let name = format!("{}_{}", self.names.connect("::"), self.cnt);
        self.cnt += 1;
213 214
        let libs = self.libs.borrow();
        let libs = (*libs.get()).clone();
215
        let cratename = self.cratename.to_owned();
216
        debug!("Creating test {}: {}", name, test);
L
Liigo Zhuang 已提交
217 218 219
        self.tests.push(testing::TestDescAndFn {
            desc: testing::TestDesc {
                name: testing::DynTestName(name),
220 221
                ignore: false,
                should_fail: false, // compiler failures are test failures
222
            },
L
Liigo Zhuang 已提交
223
            testfn: testing::DynTestFn(proc() {
224
                runtest(test, cratename, libs, should_fail, no_run);
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
            }),
        });
    }
}

impl DocFolder for Collector {
    fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
        let pushed = match item.name {
            Some(ref name) if name.len() == 0 => false,
            Some(ref name) => { self.names.push(name.to_owned()); true }
            None => false
        };
        match item.doc_value() {
            Some(doc) => {
                self.cnt = 0;
                markdown::find_testable_code(doc, self);
            }
            None => {}
        }
        let ret = self.fold_item_recur(item);
        if pushed {
            self.names.pop();
        }
        return ret;
    }
}