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.
11 use std
::cell
::{RefCell, Cell}
;
12 use std
::collections
::{HashSet, HashMap}
;
13 use std
::dynamic_lib
::DynamicLibrary
;
15 use std
::ffi
::OsString
;
16 use std
::io
::prelude
::*;
18 use std
::path
::PathBuf
;
19 use std
::process
::Command
;
21 use std
::sync
::{Arc, Mutex}
;
25 use rustc
::session
::{self, config}
;
26 use rustc
::session
::config
::get_unstable_features_setting
;
27 use rustc
::session
::search_paths
::{SearchPaths, PathKind}
;
28 use rustc_front
::lowering
::lower_crate
;
29 use rustc_back
::tempdir
::TempDir
;
30 use rustc_driver
::{driver, Compilation}
;
31 use syntax
::codemap
::CodeMap
;
32 use syntax
::diagnostic
;
40 use visit_ast
::RustdocVisitor
;
42 #[derive(Clone, Default)]
43 pub struct TestOptions
{
44 pub no_crate_inject
: bool
,
45 pub attrs
: Vec
<String
>,
48 pub fn run(input
: &str,
51 externs
: core
::Externs
,
52 mut test_args
: Vec
<String
>,
53 crate_name
: Option
<String
>)
55 let input_path
= PathBuf
::from(input
);
56 let input
= config
::Input
::File(input_path
.clone());
58 let sessopts
= config
::Options
{
59 maybe_sysroot
: Some(env
::current_exe().unwrap().parent().unwrap()
60 .parent().unwrap().to_path_buf()),
61 search_paths
: libs
.clone(),
62 crate_types
: vec
!(config
::CrateTypeDylib
),
63 externs
: externs
.clone(),
64 unstable_features
: get_unstable_features_setting(),
65 ..config
::basic_options().clone()
68 let codemap
= CodeMap
::new();
69 let diagnostic_handler
= diagnostic
::Handler
::new(diagnostic
::Auto
, None
, true);
70 let span_diagnostic_handler
=
71 diagnostic
::SpanHandler
::new(diagnostic_handler
, codemap
);
73 let sess
= session
::build_session_(sessopts
,
74 Some(input_path
.clone()),
75 span_diagnostic_handler
);
76 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
78 let mut cfg
= config
::build_configuration(&sess
);
79 cfg
.extend(config
::parse_cfgspecs(cfgs
));
80 let krate
= driver
::phase_1_parse_input(&sess
, cfg
, &input
);
81 let krate
= driver
::phase_2_configure_and_expand(&sess
, krate
,
83 .expect("phase_2_configure_and_expand aborted in rustdoc!");
84 let krate
= driver
::assign_node_ids(&sess
, krate
);
85 let krate
= lower_crate(&krate
);
87 let opts
= scrape_test_config(&krate
);
89 let ctx
= core
::DocContext
{
91 maybe_typed
: core
::NotTyped(sess
),
93 external_paths
: RefCell
::new(Some(HashMap
::new())),
94 external_traits
: RefCell
::new(None
),
95 external_typarams
: RefCell
::new(None
),
96 inlined
: RefCell
::new(None
),
97 populated_crate_impls
: RefCell
::new(HashSet
::new()),
98 deref_trait_did
: Cell
::new(None
),
101 let mut v
= RustdocVisitor
::new(&ctx
, None
);
103 let mut krate
= v
.clean(&ctx
);
105 Some(name
) => krate
.name
= name
,
108 let (krate
, _
) = passes
::collapse_docs(krate
);
109 let (krate
, _
) = passes
::unindent_comments(krate
);
111 let mut collector
= Collector
::new(krate
.name
.to_string(),
116 collector
.fold_crate(krate
);
118 test_args
.insert(0, "rustdoctest".to_string());
120 testing
::test_main(&test_args
,
121 collector
.tests
.into_iter().collect());
125 // Look for #![doc(test(no_crate_inject))], used by crates in the std facade
126 fn scrape_test_config(krate
: &::rustc_front
::hir
::Crate
) -> TestOptions
{
127 use rustc_front
::attr
::AttrMetaMethods
;
128 use rustc_front
::print
::pprust
;
130 let mut opts
= TestOptions
{
131 no_crate_inject
: false,
135 let attrs
= krate
.attrs
.iter()
136 .filter(|a
| a
.check_name("doc"))
137 .filter_map(|a
| a
.meta_item_list())
139 .filter(|a
| a
.check_name("test"))
140 .filter_map(|a
| a
.meta_item_list())
143 if attr
.check_name("no_crate_inject") {
144 opts
.no_crate_inject
= true;
146 if attr
.check_name("attr") {
147 if let Some(l
) = attr
.meta_item_list() {
149 opts
.attrs
.push(pprust
::meta_item_to_string(item
));
158 fn runtest(test
: &str, cratename
: &str, libs
: SearchPaths
,
159 externs
: core
::Externs
,
160 should_panic
: bool
, no_run
: bool
, as_test_harness
: bool
,
161 opts
: &TestOptions
) {
162 // the test harness wants its own `main` & top level functions, so
163 // never wrap the test in `fn main() { ... }`
164 let test
= maketest(test
, Some(cratename
), as_test_harness
, opts
);
165 let input
= config
::Input
::Str(test
.to_string());
167 let sessopts
= config
::Options
{
168 maybe_sysroot
: Some(env
::current_exe().unwrap().parent().unwrap()
169 .parent().unwrap().to_path_buf()),
171 crate_types
: vec
!(config
::CrateTypeExecutable
),
172 output_types
: vec
!(config
::OutputTypeExe
),
174 cg
: config
::CodegenOptions
{
175 prefer_dynamic
: true,
176 .. config
::basic_codegen_options()
178 test
: as_test_harness
,
179 unstable_features
: get_unstable_features_setting(),
180 ..config
::basic_options().clone()
183 // Shuffle around a few input and output handles here. We're going to pass
184 // an explicit handle into rustc to collect output messages, but we also
185 // want to catch the error message that rustc prints when it fails.
187 // We take our thread-local stderr (likely set by the test runner) and replace
188 // it with a sink that is also passed to rustc itself. When this function
189 // returns the output of the sink is copied onto the output of our own thread.
191 // The basic idea is to not use a default Handler for rustc, and then also
192 // not print things by default to the actual stderr.
193 struct Sink(Arc
<Mutex
<Vec
<u8>>>);
194 impl Write
for Sink
{
195 fn write(&mut self, data
: &[u8]) -> io
::Result
<usize> {
196 Write
::write(&mut *self.0.lock().unwrap(), data
)
198 fn flush(&mut self) -> io
::Result
<()> { Ok(()) }
200 struct Bomb(Arc
<Mutex
<Vec
<u8>>>, Box
<Write
+Send
>);
203 let _
= self.1.write_all(&self.0.lock().unwrap());
206 let data
= Arc
::new(Mutex
::new(Vec
::new()));
207 let emitter
= diagnostic
::EmitterWriter
::new(box Sink(data
.clone()), None
);
208 let old
= io
::set_panic(box Sink(data
.clone()));
209 let _bomb
= Bomb(data
, old
.unwrap_or(box io
::stdout()));
212 let codemap
= CodeMap
::new();
213 let diagnostic_handler
= diagnostic
::Handler
::with_emitter(true, box emitter
);
214 let span_diagnostic_handler
=
215 diagnostic
::SpanHandler
::new(diagnostic_handler
, codemap
);
217 let sess
= session
::build_session_(sessopts
,
219 span_diagnostic_handler
);
220 rustc_lint
::register_builtins(&mut sess
.lint_store
.borrow_mut(), Some(&sess
));
222 let outdir
= TempDir
::new("rustdoctest").ok().expect("rustdoc needs a tempdir");
223 let out
= Some(outdir
.path().to_path_buf());
224 let cfg
= config
::build_configuration(&sess
);
225 let libdir
= sess
.target_filesearch(PathKind
::All
).get_lib_path();
226 let mut control
= driver
::CompileController
::basic();
228 control
.after_analysis
.stop
= Compilation
::Stop
;
230 driver
::compile_input(sess
, cfg
, &input
, &out
, &None
, None
, control
);
236 // We're careful to prepend the *target* dylib search path to the child's
237 // environment to ensure that the target loads the right libraries at
238 // runtime. It would be a sad day if the *host* libraries were loaded as a
240 let mut cmd
= Command
::new(&outdir
.path().join("rust_out"));
241 let var
= DynamicLibrary
::envvar();
243 let path
= env
::var_os(var
).unwrap_or(OsString
::new());
244 let mut path
= env
::split_paths(&path
).collect
::<Vec
<_
>>();
245 path
.insert(0, libdir
.clone());
246 env
::join_paths(path
).unwrap()
248 cmd
.env(var
, &newpath
);
251 Err(e
) => panic
!("couldn't run the test: {}{}", e
,
252 if e
.kind() == io
::ErrorKind
::PermissionDenied
{
253 " - maybe your tempdir is mounted with noexec?"
256 if should_panic
&& out
.status
.success() {
257 panic
!("test executable succeeded when it should have failed");
258 } else if !should_panic
&& !out
.status
.success() {
259 panic
!("test executable failed:\n{}\n{}",
260 str::from_utf8(&out
.stdout
).unwrap_or(""),
261 str::from_utf8(&out
.stderr
).unwrap_or(""));
267 pub fn maketest(s
: &str, cratename
: Option
<&str>, dont_insert_main
: bool
,
268 opts
: &TestOptions
) -> String
{
269 let (crate_attrs
, everything_else
) = partition_source(s
);
271 let mut prog
= String
::new();
273 // First push any outer attributes from the example, assuming they
274 // are intended to be crate attributes.
275 prog
.push_str(&crate_attrs
);
277 // Next, any attributes for other aspects such as lints.
278 for attr
in &opts
.attrs
{
279 prog
.push_str(&format
!("#![{}]\n", attr
));
282 // Don't inject `extern crate std` because it's already injected by the
284 if !s
.contains("extern crate") && !opts
.no_crate_inject
&& cratename
!= Some("std") {
287 if s
.contains(cratename
) {
288 prog
.push_str(&format
!("extern crate {};\n", cratename
));
294 if dont_insert_main
|| s
.contains("fn main") {
295 prog
.push_str(&everything_else
);
297 prog
.push_str("fn main() {\n ");
298 prog
.push_str(&everything_else
.replace("\n", "\n "));
299 prog
.push_str("\n}");
302 info
!("final test program: {}", prog
);
307 fn partition_source(s
: &str) -> (String
, String
) {
308 use rustc_unicode
::str::UnicodeStr
;
310 let mut after_header
= false;
311 let mut before
= String
::new();
312 let mut after
= String
::new();
314 for line
in s
.lines() {
315 let trimline
= line
.trim();
316 let header
= trimline
.is_whitespace() ||
317 trimline
.starts_with("#![feature");
318 if !header
|| after_header
{
320 after
.push_str(line
);
321 after
.push_str("\n");
323 before
.push_str(line
);
324 before
.push_str("\n");
328 return (before
, after
);
331 pub struct Collector
{
332 pub tests
: Vec
<testing
::TestDescAndFn
>,
335 externs
: core
::Externs
,
338 current_header
: Option
<String
>,
344 pub fn new(cratename
: String
, libs
: SearchPaths
, externs
: core
::Externs
,
345 use_headers
: bool
, opts
: TestOptions
) -> Collector
{
352 use_headers
: use_headers
,
353 current_header
: None
,
354 cratename
: cratename
,
359 pub fn add_test(&mut self, test
: String
,
360 should_panic
: bool
, no_run
: bool
, should_ignore
: bool
,
361 as_test_harness
: bool
) {
362 let name
= if self.use_headers
{
363 let s
= self.current_header
.as_ref().map(|s
| &**s
).unwrap_or("");
364 format
!("{}_{}", s
, self.cnt
)
366 format
!("{}_{}", self.names
.join("::"), self.cnt
)
369 let libs
= self.libs
.clone();
370 let externs
= self.externs
.clone();
371 let cratename
= self.cratename
.to_string();
372 let opts
= self.opts
.clone();
373 debug
!("Creating test {}: {}", name
, test
);
374 self.tests
.push(testing
::TestDescAndFn
{
375 desc
: testing
::TestDesc
{
376 name
: testing
::DynTestName(name
),
377 ignore
: should_ignore
,
378 // compiler failures are test failures
379 should_panic
: testing
::ShouldPanic
::No
,
381 testfn
: testing
::DynTestFn(Box
::new(move|| {
394 pub fn register_header(&mut self, name
: &str, level
: u32) {
395 if self.use_headers
&& level
== 1 {
396 // we use these headings as test names, so it's good if
397 // they're valid identifiers.
398 let name
= name
.chars().enumerate().map(|(i
, c
)| {
399 if (i
== 0 && c
.is_xid_start()) ||
400 (i
!= 0 && c
.is_xid_continue()) {
405 }).collect
::<String
>();
407 // new header => reset count.
409 self.current_header
= Some(name
);
414 impl DocFolder
for Collector
{
415 fn fold_item(&mut self, item
: clean
::Item
) -> Option
<clean
::Item
> {
416 let pushed
= match item
.name
{
417 Some(ref name
) if name
.is_empty() => false,
418 Some(ref name
) => { self.names.push(name.to_string()); true }
421 match item
.doc_value() {
424 markdown
::find_testable_code(doc
, &mut *self);
428 let ret
= self.fold_item_recur(item
);