1 // Copyright 2013-2014 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.
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.
13 use std
::cell
::{RefCell, Cell}
;
14 use std
::collections
::{HashSet, HashMap}
;
15 use std
::dynamic_lib
::DynamicLibrary
;
17 use std
::ffi
::OsString
;
18 use std
::io
::prelude
::*;
20 use std
::path
::PathBuf
;
21 use std
::process
::Command
;
24 use std
::sync
::{Arc, Mutex}
;
28 use rustc
::front
::map
as hir_map
;
29 use rustc
::session
::{self, config}
;
30 use rustc
::session
::config
::{get_unstable_features_setting, OutputType}
;
31 use rustc
::session
::search_paths
::{SearchPaths, PathKind}
;
32 use rustc_front
::lowering
::{lower_crate, LoweringContext}
;
33 use rustc_back
::tempdir
::TempDir
;
34 use rustc_driver
::{driver, Compilation}
;
35 use rustc_metadata
::cstore
::CStore
;
36 use syntax
::codemap
::CodeMap
;
37 use syntax
::diagnostic
;
38 use syntax
::parse
::token
;
46 use visit_ast
::RustdocVisitor
;
48 #[derive(Clone, Default)]
49 pub struct TestOptions
{
50 pub no_crate_inject
: bool
,
51 pub attrs
: Vec
<String
>,
54 pub fn run(input
: &str,
57 externs
: core
::Externs
,
58 mut test_args
: Vec
<String
>,
59 crate_name
: Option
<String
>)
61 let input_path
= PathBuf
::from(input
);
62 let input
= config
::Input
::File(input_path
.clone());
64 let sessopts
= config
::Options
{
65 maybe_sysroot
: Some(env
::current_exe().unwrap().parent().unwrap()
66 .parent().unwrap().to_path_buf()),
67 search_paths
: libs
.clone(),
68 crate_types
: vec
!(config
::CrateTypeDylib
),
69 externs
: externs
.clone(),
70 unstable_features
: get_unstable_features_setting(),
71 ..config
::basic_options().clone()
74 let codemap
= CodeMap
::new();
75 let diagnostic_handler
= diagnostic
::Handler
::new(diagnostic
::Auto
, None
, true);
76 let span_diagnostic_handler
=
77 diagnostic
::SpanHandler
::new(diagnostic_handler
, codemap
);
79 let cstore
= Rc
::new(CStore
::new(token
::get_ident_interner()));
80 let cstore_
= ::rustc_driver
::cstore_to_cratestore(cstore
.clone());
81 let sess
= session
::build_session_(sessopts
,
82 Some(input_path
.clone()),
83 span_diagnostic_handler
,
85 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
87 let mut cfg
= config
::build_configuration(&sess
);
88 cfg
.extend(config
::parse_cfgspecs(cfgs
.clone()));
89 let krate
= driver
::phase_1_parse_input(&sess
, cfg
, &input
);
90 let krate
= driver
::phase_2_configure_and_expand(&sess
, &cstore
, krate
,
92 .expect("phase_2_configure_and_expand aborted in rustdoc!");
93 let krate
= driver
::assign_node_ids(&sess
, krate
);
94 let lcx
= LoweringContext
::new(&sess
, Some(&krate
));
95 let krate
= lower_crate(&lcx
, &krate
);
97 let opts
= scrape_test_config(&krate
);
99 let mut forest
= hir_map
::Forest
::new(krate
);
100 let map
= hir_map
::map_crate(&mut forest
);
102 let ctx
= core
::DocContext
{
104 maybe_typed
: core
::NotTyped(&sess
),
106 external_paths
: RefCell
::new(Some(HashMap
::new())),
107 external_traits
: RefCell
::new(None
),
108 external_typarams
: RefCell
::new(None
),
109 inlined
: RefCell
::new(None
),
110 populated_crate_impls
: RefCell
::new(HashSet
::new()),
111 deref_trait_did
: Cell
::new(None
),
114 let mut v
= RustdocVisitor
::new(&ctx
, None
);
115 v
.visit(ctx
.map
.krate());
116 let mut krate
= v
.clean(&ctx
);
118 Some(name
) => krate
.name
= name
,
121 let (krate
, _
) = passes
::collapse_docs(krate
);
122 let (krate
, _
) = passes
::unindent_comments(krate
);
124 let mut collector
= Collector
::new(krate
.name
.to_string(),
130 collector
.fold_crate(krate
);
132 test_args
.insert(0, "rustdoctest".to_string());
134 testing
::test_main(&test_args
,
135 collector
.tests
.into_iter().collect());
139 // Look for #![doc(test(no_crate_inject))], used by crates in the std facade
140 fn scrape_test_config(krate
: &::rustc_front
::hir
::Crate
) -> TestOptions
{
141 use syntax
::attr
::AttrMetaMethods
;
142 use syntax
::print
::pprust
;
144 let mut opts
= TestOptions
{
145 no_crate_inject
: false,
149 let attrs
= krate
.attrs
.iter()
150 .filter(|a
| a
.check_name("doc"))
151 .filter_map(|a
| a
.meta_item_list())
153 .filter(|a
| a
.check_name("test"))
154 .filter_map(|a
| a
.meta_item_list())
157 if attr
.check_name("no_crate_inject") {
158 opts
.no_crate_inject
= true;
160 if attr
.check_name("attr") {
161 if let Some(l
) = attr
.meta_item_list() {
163 opts
.attrs
.push(pprust
::meta_item_to_string(item
));
172 fn runtest(test
: &str, cratename
: &str, cfgs
: Vec
<String
>, libs
: SearchPaths
,
173 externs
: core
::Externs
,
174 should_panic
: bool
, no_run
: bool
, as_test_harness
: bool
,
175 opts
: &TestOptions
) {
176 // the test harness wants its own `main` & top level functions, so
177 // never wrap the test in `fn main() { ... }`
178 let test
= maketest(test
, Some(cratename
), as_test_harness
, opts
);
179 let input
= config
::Input
::Str(test
.to_string());
180 let mut outputs
= HashMap
::new();
181 outputs
.insert(OutputType
::Exe
, None
);
183 let sessopts
= config
::Options
{
184 maybe_sysroot
: Some(env
::current_exe().unwrap().parent().unwrap()
185 .parent().unwrap().to_path_buf()),
187 crate_types
: vec
!(config
::CrateTypeExecutable
),
188 output_types
: outputs
,
190 cg
: config
::CodegenOptions
{
191 prefer_dynamic
: true,
192 .. config
::basic_codegen_options()
194 test
: as_test_harness
,
195 unstable_features
: get_unstable_features_setting(),
196 ..config
::basic_options().clone()
199 // Shuffle around a few input and output handles here. We're going to pass
200 // an explicit handle into rustc to collect output messages, but we also
201 // want to catch the error message that rustc prints when it fails.
203 // We take our thread-local stderr (likely set by the test runner) and replace
204 // it with a sink that is also passed to rustc itself. When this function
205 // returns the output of the sink is copied onto the output of our own thread.
207 // The basic idea is to not use a default Handler for rustc, and then also
208 // not print things by default to the actual stderr.
209 struct Sink(Arc
<Mutex
<Vec
<u8>>>);
210 impl Write
for Sink
{
211 fn write(&mut self, data
: &[u8]) -> io
::Result
<usize> {
212 Write
::write(&mut *self.0.lock().unwrap(), data
)
214 fn flush(&mut self) -> io
::Result
<()> { Ok(()) }
216 struct Bomb(Arc
<Mutex
<Vec
<u8>>>, Box
<Write
+Send
>);
219 let _
= self.1.write_all(&self.0.lock().unwrap());
222 let data
= Arc
::new(Mutex
::new(Vec
::new()));
223 let emitter
= diagnostic
::EmitterWriter
::new(box Sink(data
.clone()), None
);
224 let old
= io
::set_panic(box Sink(data
.clone()));
225 let _bomb
= Bomb(data
, old
.unwrap_or(box io
::stdout()));
228 let codemap
= CodeMap
::new();
229 let diagnostic_handler
= diagnostic
::Handler
::with_emitter(true, box emitter
);
230 let span_diagnostic_handler
=
231 diagnostic
::SpanHandler
::new(diagnostic_handler
, codemap
);
233 let cstore
= Rc
::new(CStore
::new(token
::get_ident_interner()));
234 let cstore_
= ::rustc_driver
::cstore_to_cratestore(cstore
.clone());
235 let sess
= session
::build_session_(sessopts
,
237 span_diagnostic_handler
,
239 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
241 let outdir
= TempDir
::new("rustdoctest").ok().expect("rustdoc needs a tempdir");
242 let out
= Some(outdir
.path().to_path_buf());
243 let mut cfg
= config
::build_configuration(&sess
);
244 cfg
.extend(config
::parse_cfgspecs(cfgs
));
245 let libdir
= sess
.target_filesearch(PathKind
::All
).get_lib_path();
246 let mut control
= driver
::CompileController
::basic();
248 control
.after_analysis
.stop
= Compilation
::Stop
;
250 driver
::compile_input(sess
, &cstore
, cfg
, &input
, &out
, &None
, None
, control
);
256 // We're careful to prepend the *target* dylib search path to the child's
257 // environment to ensure that the target loads the right libraries at
258 // runtime. It would be a sad day if the *host* libraries were loaded as a
260 let mut cmd
= Command
::new(&outdir
.path().join("rust_out"));
261 let var
= DynamicLibrary
::envvar();
263 let path
= env
::var_os(var
).unwrap_or(OsString
::new());
264 let mut path
= env
::split_paths(&path
).collect
::<Vec
<_
>>();
265 path
.insert(0, libdir
.clone());
266 env
::join_paths(path
).unwrap()
268 cmd
.env(var
, &newpath
);
271 Err(e
) => panic
!("couldn't run the test: {}{}", e
,
272 if e
.kind() == io
::ErrorKind
::PermissionDenied
{
273 " - maybe your tempdir is mounted with noexec?"
276 if should_panic
&& out
.status
.success() {
277 panic
!("test executable succeeded when it should have failed");
278 } else if !should_panic
&& !out
.status
.success() {
279 panic
!("test executable failed:\n{}\n{}",
280 str::from_utf8(&out
.stdout
).unwrap_or(""),
281 str::from_utf8(&out
.stderr
).unwrap_or(""));
287 pub fn maketest(s
: &str, cratename
: Option
<&str>, dont_insert_main
: bool
,
288 opts
: &TestOptions
) -> String
{
289 let (crate_attrs
, everything_else
) = partition_source(s
);
291 let mut prog
= String
::new();
293 // First push any outer attributes from the example, assuming they
294 // are intended to be crate attributes.
295 prog
.push_str(&crate_attrs
);
297 // Next, any attributes for other aspects such as lints.
298 for attr
in &opts
.attrs
{
299 prog
.push_str(&format
!("#![{}]\n", attr
));
302 // Don't inject `extern crate std` because it's already injected by the
304 if !s
.contains("extern crate") && !opts
.no_crate_inject
&& cratename
!= Some("std") {
307 if s
.contains(cratename
) {
308 prog
.push_str(&format
!("extern crate {};\n", cratename
));
314 if dont_insert_main
|| s
.contains("fn main") {
315 prog
.push_str(&everything_else
);
317 prog
.push_str("fn main() {\n ");
318 prog
.push_str(&everything_else
.replace("\n", "\n "));
319 prog
.push_str("\n}");
322 info
!("final test program: {}", prog
);
327 fn partition_source(s
: &str) -> (String
, String
) {
328 use rustc_unicode
::str::UnicodeStr
;
330 let mut after_header
= false;
331 let mut before
= String
::new();
332 let mut after
= String
::new();
334 for line
in s
.lines() {
335 let trimline
= line
.trim();
336 let header
= trimline
.is_whitespace() ||
337 trimline
.starts_with("#![feature");
338 if !header
|| after_header
{
340 after
.push_str(line
);
341 after
.push_str("\n");
343 before
.push_str(line
);
344 before
.push_str("\n");
348 return (before
, after
);
351 pub struct Collector
{
352 pub tests
: Vec
<testing
::TestDescAndFn
>,
356 externs
: core
::Externs
,
359 current_header
: Option
<String
>,
365 pub fn new(cratename
: String
, cfgs
: Vec
<String
>, libs
: SearchPaths
, externs
: core
::Externs
,
366 use_headers
: bool
, opts
: TestOptions
) -> Collector
{
374 use_headers
: use_headers
,
375 current_header
: None
,
376 cratename
: cratename
,
381 pub fn add_test(&mut self, test
: String
,
382 should_panic
: bool
, no_run
: bool
, should_ignore
: bool
,
383 as_test_harness
: bool
) {
384 let name
= if self.use_headers
{
385 let s
= self.current_header
.as_ref().map(|s
| &**s
).unwrap_or("");
386 format
!("{}_{}", s
, self.cnt
)
388 format
!("{}_{}", self.names
.join("::"), self.cnt
)
391 let cfgs
= self.cfgs
.clone();
392 let libs
= self.libs
.clone();
393 let externs
= self.externs
.clone();
394 let cratename
= self.cratename
.to_string();
395 let opts
= self.opts
.clone();
396 debug
!("Creating test {}: {}", name
, test
);
397 self.tests
.push(testing
::TestDescAndFn
{
398 desc
: testing
::TestDesc
{
399 name
: testing
::DynTestName(name
),
400 ignore
: should_ignore
,
401 // compiler failures are test failures
402 should_panic
: testing
::ShouldPanic
::No
,
404 testfn
: testing
::DynTestFn(Box
::new(move|| {
418 pub fn register_header(&mut self, name
: &str, level
: u32) {
419 if self.use_headers
&& level
== 1 {
420 // we use these headings as test names, so it's good if
421 // they're valid identifiers.
422 let name
= name
.chars().enumerate().map(|(i
, c
)| {
423 if (i
== 0 && c
.is_xid_start()) ||
424 (i
!= 0 && c
.is_xid_continue()) {
429 }).collect
::<String
>();
431 // new header => reset count.
433 self.current_header
= Some(name
);
438 impl DocFolder
for Collector
{
439 fn fold_item(&mut self, item
: clean
::Item
) -> Option
<clean
::Item
> {
440 let current_name
= match item
.name
{
441 Some(ref name
) if !name
.is_empty() => Some(name
.clone()),
442 _
=> typename_if_impl(&item
)
445 let pushed
= if let Some(name
) = current_name
{
446 self.names
.push(name
);
452 if let Some(doc
) = item
.doc_value() {
454 markdown
::find_testable_code(doc
, &mut *self);
457 let ret
= self.fold_item_recur(item
);
464 // FIXME: it would be better to not have the escaped version in the first place
465 fn unescape_for_testname(mut s
: String
) -> String
{
467 if s
.contains("&") {
468 s
= s
.replace("&", "&");
470 // `::&'a mut Foo::` looks weird, let's make it `::<&'a mut Foo>`::
471 if let Some('
&'
) = s
.chars().nth(0) {
472 s
= format
!("<{}>", s
);
476 // either `<..>` or `->`
477 if s
.contains(">") {
478 s
.replace(">", ">")
479 .replace("<", "<")
485 fn typename_if_impl(item
: &clean
::Item
) -> Option
<String
> {
486 if let clean
::ItemEnum
::ImplItem(ref impl_
) = item
.inner
{
487 let path
= impl_
.for_
.to_string();
488 let unescaped_path
= unescape_for_testname(path
);