rustdoc: Add the ability to test code in comments
This adds support for the `--test` flag to rustdoc which will parse a crate, extract all code examples in doc comments, and then run each test in the extra::test driver.
This commit is contained in:
parent
f71c0dc2cd
commit
6c9c045064
6 changed files with 335 additions and 40 deletions
|
@ -1147,13 +1147,17 @@ fn name_from_pat(p: &ast::Pat) -> ~str {
|
||||||
fn resolve_type(path: Path, tpbs: Option<~[TyParamBound]>,
|
fn resolve_type(path: Path, tpbs: Option<~[TyParamBound]>,
|
||||||
id: ast::NodeId) -> Type {
|
id: ast::NodeId) -> Type {
|
||||||
let cx = local_data::get(super::ctxtkey, |x| *x.unwrap());
|
let cx = local_data::get(super::ctxtkey, |x| *x.unwrap());
|
||||||
|
let tycx = match cx.tycx {
|
||||||
|
Some(tycx) => tycx,
|
||||||
|
// If we're extracting tests, this return value doesn't matter.
|
||||||
|
None => return Bool
|
||||||
|
};
|
||||||
debug!("searching for {:?} in defmap", id);
|
debug!("searching for {:?} in defmap", id);
|
||||||
let d = match cx.tycx.def_map.find(&id) {
|
let d = match tycx.def_map.find(&id) {
|
||||||
Some(k) => k,
|
Some(k) => k,
|
||||||
None => {
|
None => {
|
||||||
let ctxt = local_data::get(super::ctxtkey, |x| *x.unwrap());
|
|
||||||
debug!("could not find {:?} in defmap (`{}`)", id,
|
debug!("could not find {:?} in defmap (`{}`)", id,
|
||||||
syntax::ast_map::node_id_to_str(ctxt.tycx.items, id, ctxt.sess.intr()));
|
syntax::ast_map::node_id_to_str(tycx.items, id, cx.sess.intr()));
|
||||||
fail!("Unexpected failure: unresolved id not in defmap (this is a bug!)")
|
fail!("Unexpected failure: unresolved id not in defmap (this is a bug!)")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1182,7 +1186,7 @@ fn resolve_type(path: Path, tpbs: Option<~[TyParamBound]>,
|
||||||
if ast_util::is_local(def_id) {
|
if ast_util::is_local(def_id) {
|
||||||
ResolvedPath{ path: path, typarams: tpbs, id: def_id.node }
|
ResolvedPath{ path: path, typarams: tpbs, id: def_id.node }
|
||||||
} else {
|
} else {
|
||||||
let fqn = csearch::get_item_path(cx.tycx, def_id);
|
let fqn = csearch::get_item_path(tycx, def_id);
|
||||||
let fqn = fqn.move_iter().map(|i| {
|
let fqn = fqn.move_iter().map(|i| {
|
||||||
match i {
|
match i {
|
||||||
ast_map::path_mod(id) |
|
ast_map::path_mod(id) |
|
||||||
|
@ -1203,6 +1207,11 @@ fn resolve_use_source(path: Path, id: ast::NodeId) -> ImportSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_def(id: ast::NodeId) -> Option<ast::DefId> {
|
fn resolve_def(id: ast::NodeId) -> Option<ast::DefId> {
|
||||||
let dm = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.def_map;
|
let cx = local_data::get(super::ctxtkey, |x| *x.unwrap());
|
||||||
dm.find(&id).map(|&d| ast_util::def_id_of_def(d))
|
match cx.tycx {
|
||||||
|
Some(tcx) => {
|
||||||
|
tcx.def_map.find(&id).map(|&d| ast_util::def_id_of_def(d))
|
||||||
|
}
|
||||||
|
None => None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use clean::Clean;
|
||||||
|
|
||||||
pub struct DocContext {
|
pub struct DocContext {
|
||||||
crate: ast::Crate,
|
crate: ast::Crate,
|
||||||
tycx: middle::ty::ctxt,
|
tycx: Option<middle::ty::ctxt>,
|
||||||
sess: driver::session::Session
|
sess: driver::session::Session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,17 +78,13 @@ fn get_ast_and_resolve(cpath: &Path,
|
||||||
} = phase_3_run_analysis_passes(sess, &crate);
|
} = phase_3_run_analysis_passes(sess, &crate);
|
||||||
|
|
||||||
debug!("crate: {:?}", crate);
|
debug!("crate: {:?}", crate);
|
||||||
return (DocContext { crate: crate, tycx: ty_cx, sess: sess },
|
return (DocContext { crate: crate, tycx: Some(ty_cx), sess: sess },
|
||||||
CrateAnalysis { exported_items: exported_items });
|
CrateAnalysis { exported_items: exported_items });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_core (libs: HashSet<Path>, cfgs: ~[~str], path: &Path) -> (clean::Crate, CrateAnalysis) {
|
pub fn run_core (libs: HashSet<Path>, cfgs: ~[~str], path: &Path) -> (clean::Crate, CrateAnalysis) {
|
||||||
let (ctxt, analysis) = get_ast_and_resolve(path, libs, cfgs);
|
let (ctxt, analysis) = get_ast_and_resolve(path, libs, cfgs);
|
||||||
let ctxt = @ctxt;
|
let ctxt = @ctxt;
|
||||||
debug!("defmap:");
|
|
||||||
for (k, v) in ctxt.tycx.def_map.iter() {
|
|
||||||
debug!("{:?}: {:?}", k, v);
|
|
||||||
}
|
|
||||||
local_data::set(super::ctxtkey, ctxt);
|
local_data::set(super::ctxtkey, ctxt);
|
||||||
|
|
||||||
let v = @mut RustdocVisitor::new();
|
let v = @mut RustdocVisitor::new();
|
||||||
|
|
|
@ -22,9 +22,12 @@
|
||||||
//! // ... something using html
|
//! // ... something using html
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
use std::cast;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::libc;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::libc;
|
||||||
|
use std::str;
|
||||||
|
use std::unstable::intrinsics;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
/// A unit struct which has the `fmt::Default` trait implemented. When
|
/// A unit struct which has the `fmt::Default` trait implemented. When
|
||||||
|
@ -41,8 +44,10 @@ static MKDEXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
|
||||||
|
|
||||||
type sd_markdown = libc::c_void; // this is opaque to us
|
type sd_markdown = libc::c_void; // this is opaque to us
|
||||||
|
|
||||||
// this is a large struct of callbacks we don't use
|
struct sd_callbacks {
|
||||||
type sd_callbacks = [libc::size_t, ..26];
|
blockcode: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
|
||||||
|
other: [libc::size_t, ..25],
|
||||||
|
}
|
||||||
|
|
||||||
struct html_toc_data {
|
struct html_toc_data {
|
||||||
header_count: libc::c_int,
|
header_count: libc::c_int,
|
||||||
|
@ -56,6 +61,11 @@ struct html_renderopt {
|
||||||
link_attributes: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
|
link_attributes: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct my_opaque {
|
||||||
|
opt: html_renderopt,
|
||||||
|
dfltblk: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
|
||||||
|
}
|
||||||
|
|
||||||
struct buf {
|
struct buf {
|
||||||
data: *u8,
|
data: *u8,
|
||||||
size: libc::size_t,
|
size: libc::size_t,
|
||||||
|
@ -84,7 +94,28 @@ extern {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(w: &mut io::Writer, s: &str) {
|
pub fn render(w: &mut io::Writer, s: &str) {
|
||||||
|
extern fn block(ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
|
||||||
|
unsafe {
|
||||||
|
let my_opaque: &my_opaque = cast::transmute(opaque);
|
||||||
|
vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
|
||||||
|
let text = str::from_utf8(text);
|
||||||
|
let mut lines = text.lines().filter(|l| {
|
||||||
|
!l.trim().starts_with("#")
|
||||||
|
});
|
||||||
|
let text = lines.to_owned_vec().connect("\n");
|
||||||
|
|
||||||
|
let buf = buf {
|
||||||
|
data: text.as_bytes().as_ptr(),
|
||||||
|
size: text.len() as libc::size_t,
|
||||||
|
asize: text.len() as libc::size_t,
|
||||||
|
unit: 0,
|
||||||
|
};
|
||||||
|
(my_opaque.dfltblk)(ob, &buf, lang, opaque);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This code is all lifted from examples/sundown.c in the sundown repo
|
// This code is all lifted from examples/sundown.c in the sundown repo
|
||||||
unsafe {
|
unsafe {
|
||||||
let ob = bufnew(OUTPUT_UNIT);
|
let ob = bufnew(OUTPUT_UNIT);
|
||||||
|
@ -100,11 +131,16 @@ fn render(w: &mut io::Writer, s: &str) {
|
||||||
flags: 0,
|
flags: 0,
|
||||||
link_attributes: None,
|
link_attributes: None,
|
||||||
};
|
};
|
||||||
let callbacks: sd_callbacks = [0, ..26];
|
let mut callbacks: sd_callbacks = intrinsics::init();
|
||||||
|
|
||||||
sdhtml_renderer(&callbacks, &options, 0);
|
sdhtml_renderer(&callbacks, &options, 0);
|
||||||
|
let opaque = my_opaque {
|
||||||
|
opt: options,
|
||||||
|
dfltblk: callbacks.blockcode,
|
||||||
|
};
|
||||||
|
callbacks.blockcode = block;
|
||||||
let markdown = sd_markdown_new(extensions, 16, &callbacks,
|
let markdown = sd_markdown_new(extensions, 16, &callbacks,
|
||||||
&options as *html_renderopt as *libc::c_void);
|
&opaque as *my_opaque as *libc::c_void);
|
||||||
|
|
||||||
|
|
||||||
sd_markdown_render(ob, s.as_ptr(), s.len() as libc::size_t, markdown);
|
sd_markdown_render(ob, s.as_ptr(), s.len() as libc::size_t, markdown);
|
||||||
|
@ -118,6 +154,48 @@ fn render(w: &mut io::Writer, s: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
|
||||||
|
extern fn block(_ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
|
||||||
|
unsafe {
|
||||||
|
if text.is_null() || lang.is_null() { return }
|
||||||
|
let (test, shouldfail, ignore) =
|
||||||
|
vec::raw::buf_as_slice((*lang).data,
|
||||||
|
(*lang).size as uint, |lang| {
|
||||||
|
let s = str::from_utf8(lang);
|
||||||
|
(s.contains("rust"), s.contains("should_fail"),
|
||||||
|
s.contains("ignore"))
|
||||||
|
});
|
||||||
|
if !test { return }
|
||||||
|
vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
|
||||||
|
let tests: &mut ::test::Collector = intrinsics::transmute(opaque);
|
||||||
|
let text = str::from_utf8(text);
|
||||||
|
let mut lines = text.lines().map(|l| l.trim_chars(&'#'));
|
||||||
|
let text = lines.to_owned_vec().connect("\n");
|
||||||
|
tests.add_test(text, ignore, shouldfail);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ob = bufnew(OUTPUT_UNIT);
|
||||||
|
let extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_TABLES |
|
||||||
|
MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK |
|
||||||
|
MKDEXT_STRIKETHROUGH;
|
||||||
|
let callbacks = sd_callbacks {
|
||||||
|
blockcode: block,
|
||||||
|
other: intrinsics::init()
|
||||||
|
};
|
||||||
|
|
||||||
|
let tests = tests as *mut ::test::Collector as *libc::c_void;
|
||||||
|
let markdown = sd_markdown_new(extensions, 16, &callbacks, tests);
|
||||||
|
|
||||||
|
sd_markdown_render(ob, doc.as_ptr(), doc.len() as libc::size_t,
|
||||||
|
markdown);
|
||||||
|
sd_markdown_free(markdown);
|
||||||
|
bufrelease(ob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Default for Markdown<'a> {
|
impl<'a> fmt::Default for Markdown<'a> {
|
||||||
fn fmt(md: &Markdown<'a>, fmt: &mut fmt::Formatter) {
|
fn fmt(md: &Markdown<'a>, fmt: &mut fmt::Formatter) {
|
||||||
// This is actually common enough to special-case
|
// This is actually common enough to special-case
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub mod html {
|
||||||
pub mod passes;
|
pub mod passes;
|
||||||
pub mod plugins;
|
pub mod plugins;
|
||||||
pub mod visit_ast;
|
pub mod visit_ast;
|
||||||
|
pub mod test;
|
||||||
|
|
||||||
pub static SCHEMA_VERSION: &'static str = "0.8.1";
|
pub static SCHEMA_VERSION: &'static str = "0.8.1";
|
||||||
|
|
||||||
|
@ -100,6 +101,9 @@ pub fn opts() -> ~[groups::OptGroup] {
|
||||||
optmulti("", "plugins", "space separated list of plugins to also load",
|
optmulti("", "plugins", "space separated list of plugins to also load",
|
||||||
"PLUGINS"),
|
"PLUGINS"),
|
||||||
optflag("", "no-defaults", "don't run the default passes"),
|
optflag("", "no-defaults", "don't run the default passes"),
|
||||||
|
optflag("", "test", "run code examples as tests"),
|
||||||
|
optmulti("", "test-args", "arguments to pass to the test runner",
|
||||||
|
"ARGS"),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +118,19 @@ pub fn main_args(args: &[~str]) -> int {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches.free.len() == 0 {
|
||||||
|
println("expected an input file to act on");
|
||||||
|
return 1;
|
||||||
|
} if matches.free.len() > 1 {
|
||||||
|
println("only one input file may be specified");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let input = matches.free[0].as_slice();
|
||||||
|
|
||||||
|
if matches.opt_present("test") {
|
||||||
|
return test::run(input, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
if matches.opt_strs("passes") == ~[~"list"] {
|
if matches.opt_strs("passes") == ~[~"list"] {
|
||||||
println("Available passes for running rustdoc:");
|
println("Available passes for running rustdoc:");
|
||||||
for &(name, _, description) in PASSES.iter() {
|
for &(name, _, description) in PASSES.iter() {
|
||||||
|
@ -126,7 +143,7 @@ pub fn main_args(args: &[~str]) -> int {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (crate, res) = match acquire_input(&matches) {
|
let (crate, res) = match acquire_input(input, &matches) {
|
||||||
Ok(pair) => pair,
|
Ok(pair) => pair,
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
println!("input error: {}", s);
|
println!("input error: {}", s);
|
||||||
|
@ -157,14 +174,8 @@ pub fn main_args(args: &[~str]) -> int {
|
||||||
|
|
||||||
/// Looks inside the command line arguments to extract the relevant input format
|
/// Looks inside the command line arguments to extract the relevant input format
|
||||||
/// and files and then generates the necessary rustdoc output for formatting.
|
/// and files and then generates the necessary rustdoc output for formatting.
|
||||||
fn acquire_input(matches: &getopts::Matches) -> Result<Output, ~str> {
|
fn acquire_input(input: &str,
|
||||||
if matches.free.len() == 0 {
|
matches: &getopts::Matches) -> Result<Output, ~str> {
|
||||||
return Err(~"expected an input file to act on");
|
|
||||||
} if matches.free.len() > 1 {
|
|
||||||
return Err(~"only one input file may be specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = matches.free[0].as_slice();
|
|
||||||
match matches.opt_str("r") {
|
match matches.opt_str("r") {
|
||||||
Some(~"rust") => Ok(rust_input(input, matches)),
|
Some(~"rust") => Ok(rust_input(input, matches)),
|
||||||
Some(~"json") => json_input(input),
|
Some(~"json") => json_input(input),
|
||||||
|
|
207
src/librustdoc/test.rs
Normal file
207
src/librustdoc/test.rs
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use std::hashmap::HashSet;
|
||||||
|
use std::local_data;
|
||||||
|
use std::os;
|
||||||
|
use std::run;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use extra::tempfile::TempDir;
|
||||||
|
use extra::getopts;
|
||||||
|
use extra::test;
|
||||||
|
use rustc::driver::driver;
|
||||||
|
use rustc::driver::session;
|
||||||
|
use syntax::diagnostic;
|
||||||
|
use syntax::parse;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
let parsesess = parse::new_parse_sess(None);
|
||||||
|
let input = driver::file_input(Path::new(input));
|
||||||
|
let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice()));
|
||||||
|
let libs = @mut libs.move_iter().collect();
|
||||||
|
|
||||||
|
let sessopts = @session::options {
|
||||||
|
binary: @"rustdoc",
|
||||||
|
maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()),
|
||||||
|
addl_lib_search_paths: libs,
|
||||||
|
outputs: ~[session::OutputDylib],
|
||||||
|
.. (*session::basic_options()).clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let diagnostic_handler = diagnostic::mk_handler(None);
|
||||||
|
let span_diagnostic_handler =
|
||||||
|
diagnostic::mk_span_handler(diagnostic_handler, parsesess.cm);
|
||||||
|
|
||||||
|
let sess = driver::build_session_(sessopts,
|
||||||
|
parsesess.cm,
|
||||||
|
@diagnostic::DefaultEmitter as
|
||||||
|
@diagnostic::Emitter,
|
||||||
|
span_diagnostic_handler);
|
||||||
|
|
||||||
|
let cfg = driver::build_configuration(sess);
|
||||||
|
let mut crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
|
||||||
|
crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
|
||||||
|
|
||||||
|
let ctx = @core::DocContext {
|
||||||
|
crate: crate,
|
||||||
|
tycx: None,
|
||||||
|
sess: sess,
|
||||||
|
};
|
||||||
|
local_data::set(super::ctxtkey, ctx);
|
||||||
|
|
||||||
|
let v = @mut RustdocVisitor::new();
|
||||||
|
v.visit(&ctx.crate);
|
||||||
|
let crate = v.clean();
|
||||||
|
let (crate, _) = passes::unindent_comments(crate);
|
||||||
|
let (crate, _) = passes::collapse_docs(crate);
|
||||||
|
|
||||||
|
let mut collector = Collector {
|
||||||
|
tests: ~[],
|
||||||
|
names: ~[],
|
||||||
|
cnt: 0,
|
||||||
|
libs: libs,
|
||||||
|
cratename: crate.name.to_owned(),
|
||||||
|
};
|
||||||
|
collector.fold_crate(crate);
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
test::test_main(args, collector.tests);
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtest(test: &str, cratename: &str, libs: HashSet<Path>) {
|
||||||
|
let test = maketest(test, cratename);
|
||||||
|
let parsesess = parse::new_parse_sess(None);
|
||||||
|
let input = driver::str_input(test);
|
||||||
|
|
||||||
|
let sessopts = @session::options {
|
||||||
|
binary: @"rustdoctest",
|
||||||
|
maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()),
|
||||||
|
addl_lib_search_paths: @mut libs,
|
||||||
|
outputs: ~[session::OutputExecutable],
|
||||||
|
debugging_opts: session::prefer_dynamic,
|
||||||
|
.. (*session::basic_options()).clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let diagnostic_handler = diagnostic::mk_handler(None);
|
||||||
|
let span_diagnostic_handler =
|
||||||
|
diagnostic::mk_span_handler(diagnostic_handler, parsesess.cm);
|
||||||
|
|
||||||
|
let sess = driver::build_session_(sessopts,
|
||||||
|
parsesess.cm,
|
||||||
|
@diagnostic::DefaultEmitter as
|
||||||
|
@diagnostic::Emitter,
|
||||||
|
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);
|
||||||
|
|
||||||
|
let exe = outdir.path().join("rust_out");
|
||||||
|
let out = run::process_output(exe.as_str().unwrap(), []);
|
||||||
|
match out {
|
||||||
|
None => fail!("couldn't run the test"),
|
||||||
|
Some(out) => {
|
||||||
|
if !out.status.success() {
|
||||||
|
fail!("test executable failed:\n{}",
|
||||||
|
str::from_utf8(out.error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maketest(s: &str, cratename: &str) -> @str {
|
||||||
|
let mut prog = ~r"
|
||||||
|
#[deny(warnings)];
|
||||||
|
#[allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)];
|
||||||
|
#[feature(macro_rules, globs, struct_variant, managed_boxes)];
|
||||||
|
";
|
||||||
|
if s.contains("extra") {
|
||||||
|
prog.push_str("extern mod extra;\n");
|
||||||
|
}
|
||||||
|
if s.contains(cratename) {
|
||||||
|
prog.push_str(format!("extern mod {};\n", cratename));
|
||||||
|
}
|
||||||
|
if s.contains("fn main") {
|
||||||
|
prog.push_str(s);
|
||||||
|
} else {
|
||||||
|
prog.push_str("fn main() {\n");
|
||||||
|
prog.push_str(s);
|
||||||
|
prog.push_str("\n}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return prog.to_managed();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Collector {
|
||||||
|
priv tests: ~[test::TestDescAndFn],
|
||||||
|
priv names: ~[~str],
|
||||||
|
priv libs: @mut HashSet<Path>,
|
||||||
|
priv cnt: uint,
|
||||||
|
priv cratename: ~str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Collector {
|
||||||
|
pub fn add_test(&mut self, test: &str, ignore: bool, should_fail: bool) {
|
||||||
|
let test = test.to_owned();
|
||||||
|
let name = format!("{}_{}", self.names.connect("::"), self.cnt);
|
||||||
|
self.cnt += 1;
|
||||||
|
let libs = (*self.libs).clone();
|
||||||
|
let cratename = self.cratename.to_owned();
|
||||||
|
self.tests.push(test::TestDescAndFn {
|
||||||
|
desc: test::TestDesc {
|
||||||
|
name: test::DynTestName(name),
|
||||||
|
ignore: ignore,
|
||||||
|
should_fail: should_fail,
|
||||||
|
},
|
||||||
|
testfn: test::DynTestFn(proc() {
|
||||||
|
runtest(test, cratename, libs);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,11 +12,10 @@
|
||||||
//! usable for clean
|
//! usable for clean
|
||||||
|
|
||||||
use syntax::abi::AbiSet;
|
use syntax::abi::AbiSet;
|
||||||
use syntax::{ast, ast_map};
|
use syntax::ast;
|
||||||
use syntax::codemap::Span;
|
use syntax::codemap::Span;
|
||||||
|
|
||||||
use doctree::*;
|
use doctree::*;
|
||||||
use std::local_data;
|
|
||||||
|
|
||||||
pub struct RustdocVisitor {
|
pub struct RustdocVisitor {
|
||||||
module: Module,
|
module: Module,
|
||||||
|
@ -91,15 +90,8 @@ impl RustdocVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_mod_contents(span: Span, attrs: ~[ast::Attribute], vis:
|
fn visit_mod_contents(span: Span, attrs: ~[ast::Attribute], vis:
|
||||||
ast::visibility, id: ast::NodeId, m: &ast::_mod) -> Module {
|
ast::visibility, id: ast::NodeId, m: &ast::_mod,
|
||||||
let am = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.items;
|
name: Option<ast::Ident>) -> Module {
|
||||||
let name = match am.find(&id) {
|
|
||||||
Some(m) => match m {
|
|
||||||
&ast_map::node_item(ref it, _) => Some(it.ident),
|
|
||||||
_ => fail!("mod id mapped to non-item in the ast map")
|
|
||||||
},
|
|
||||||
None => None
|
|
||||||
};
|
|
||||||
let mut om = Module::new(name);
|
let mut om = Module::new(name);
|
||||||
om.view_items = m.view_items.clone();
|
om.view_items = m.view_items.clone();
|
||||||
om.where = span;
|
om.where = span;
|
||||||
|
@ -117,7 +109,8 @@ impl RustdocVisitor {
|
||||||
match item.node {
|
match item.node {
|
||||||
ast::item_mod(ref m) => {
|
ast::item_mod(ref m) => {
|
||||||
om.mods.push(visit_mod_contents(item.span, item.attrs.clone(),
|
om.mods.push(visit_mod_contents(item.span, item.attrs.clone(),
|
||||||
item.vis, item.id, m));
|
item.vis, item.id, m,
|
||||||
|
Some(item.ident)));
|
||||||
},
|
},
|
||||||
ast::item_enum(ref ed, ref gen) => om.enums.push(visit_enum_def(item, ed, gen)),
|
ast::item_enum(ref ed, ref gen) => om.enums.push(visit_enum_def(item, ed, gen)),
|
||||||
ast::item_struct(sd, ref gen) => om.structs.push(visit_struct_def(item, sd, gen)),
|
ast::item_struct(sd, ref gen) => om.structs.push(visit_struct_def(item, sd, gen)),
|
||||||
|
@ -182,6 +175,7 @@ impl RustdocVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.module = visit_mod_contents(crate.span, crate.attrs.clone(),
|
self.module = visit_mod_contents(crate.span, crate.attrs.clone(),
|
||||||
ast::public, ast::CRATE_NODE_ID, &crate.module);
|
ast::public, ast::CRATE_NODE_ID,
|
||||||
|
&crate.module, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue