Add compile-fail test in rustdoc

This commit is contained in:
Guillaume Gomez 2016-01-05 23:38:11 +01:00
parent d0ef740266
commit eb5b9037ad
7 changed files with 200 additions and 80 deletions

View File

@ -21,11 +21,123 @@ use std::env;
use std::path::Path;
use std::error::Error;
use syntax::diagnostics::metadata::{get_metadata_dir, ErrorMetadataMap};
use syntax::diagnostics::metadata::{get_metadata_dir, ErrorMetadataMap, ErrorMetadata};
use rustdoc::html::markdown::Markdown;
use rustc_serialize::json;
enum OutputFormat {
HTML(HTMLFormatter),
Markdown(MarkdownFormatter),
Unknown(String),
}
impl OutputFormat {
fn from(format: &str) -> OutputFormat {
match &*format.to_lowercase() {
"html" => OutputFormat::HTML(HTMLFormatter),
"markdown" => OutputFormat::Markdown(MarkdownFormatter),
s => OutputFormat::Unknown(s.to_owned()),
}
}
}
trait Formatter {
fn header(&self, output: &mut Write) -> Result<(), Box<Error>>;
fn title(&self, output: &mut Write) -> Result<(), Box<Error>>;
fn error_code_block(&self, output: &mut Write, info: &ErrorMetadata,
err_code: &str) -> Result<(), Box<Error>>;
fn footer(&self, output: &mut Write) -> Result<(), Box<Error>>;
}
struct HTMLFormatter;
struct MarkdownFormatter;
impl Formatter for HTMLFormatter {
fn header(&self, output: &mut Write) -> Result<(), Box<Error>> {
try!(write!(output, r##"<!DOCTYPE html>
<html>
<head>
<title>Rust Compiler Error Index</title>
<meta charset="utf-8">
<!-- Include rust.css after main.css so its rules take priority. -->
<link rel="stylesheet" type="text/css" href="main.css"/>
<link rel="stylesheet" type="text/css" href="rust.css"/>
<style>
.error-undescribed {{
display: none;
}}
</style>
</head>
<body>
"##));
Ok(())
}
fn title(&self, output: &mut Write) -> Result<(), Box<Error>> {
try!(write!(output, "<h1>Rust Compiler Error Index</h1>\n"));
Ok(())
}
fn error_code_block(&self, output: &mut Write, info: &ErrorMetadata,
err_code: &str) -> Result<(), Box<Error>> {
// Enclose each error in a div so they can be shown/hidden en masse.
let desc_desc = match info.description {
Some(_) => "error-described",
None => "error-undescribed",
};
let use_desc = match info.use_site {
Some(_) => "error-used",
None => "error-unused",
};
try!(write!(output, "<div class=\"{} {}\">", desc_desc, use_desc));
// Error title (with self-link).
try!(write!(output,
"<h2 id=\"{0}\" class=\"section-header\"><a href=\"#{0}\">{0}</a></h2>\n",
err_code));
// Description rendered as markdown.
match info.description {
Some(ref desc) => try!(write!(output, "{}", Markdown(desc))),
None => try!(write!(output, "<p>No description.</p>\n")),
}
try!(write!(output, "</div>\n"));
Ok(())
}
fn footer(&self, output: &mut Write) -> Result<(), Box<Error>> {
try!(write!(output, "</body>\n</html>"));
Ok(())
}
}
impl Formatter for MarkdownFormatter {
#[allow(unused_variables)]
fn header(&self, output: &mut Write) -> Result<(), Box<Error>> {
Ok(())
}
fn title(&self, output: &mut Write) -> Result<(), Box<Error>> {
try!(write!(output, "# Rust Compiler Error Index\n"));
Ok(())
}
fn error_code_block(&self, output: &mut Write, info: &ErrorMetadata,
err_code: &str) -> Result<(), Box<Error>> {
Ok(match info.description {
Some(ref desc) => try!(write!(output, "## {}\n{}\n", err_code, desc)),
None => (),
})
}
#[allow(unused_variables)]
fn footer(&self, output: &mut Write) -> Result<(), Box<Error>> {
Ok(())
}
}
/// Load all the metadata files from `metadata_dir` into an in-memory map.
fn load_all_errors(metadata_dir: &Path) -> Result<ErrorMetadataMap, Box<Error>> {
let mut all_errors = BTreeMap::new();
@ -47,71 +159,45 @@ fn load_all_errors(metadata_dir: &Path) -> Result<ErrorMetadataMap, Box<Error>>
}
/// Output an HTML page for the errors in `err_map` to `output_path`.
fn render_error_page(err_map: &ErrorMetadataMap, output_path: &Path) -> Result<(), Box<Error>> {
fn render_error_page<T: Formatter>(err_map: &ErrorMetadataMap, output_path: &Path,
formatter: T) -> Result<(), Box<Error>> {
let mut output_file = try!(File::create(output_path));
try!(write!(&mut output_file,
r##"<!DOCTYPE html>
<html>
<head>
<title>Rust Compiler Error Index</title>
<meta charset="utf-8">
<!-- Include rust.css after main.css so its rules take priority. -->
<link rel="stylesheet" type="text/css" href="main.css"/>
<link rel="stylesheet" type="text/css" href="rust.css"/>
<style>
.error-undescribed {{
display: none;
}}
</style>
</head>
<body>
"##
));
try!(write!(&mut output_file, "<h1>Rust Compiler Error Index</h1>\n"));
try!(formatter.header(&mut output_file));
try!(formatter.title(&mut output_file));
for (err_code, info) in err_map {
// Enclose each error in a div so they can be shown/hidden en masse.
let desc_desc = match info.description {
Some(_) => "error-described",
None => "error-undescribed",
};
let use_desc = match info.use_site {
Some(_) => "error-used",
None => "error-unused",
};
try!(write!(&mut output_file, "<div class=\"{} {}\">", desc_desc, use_desc));
// Error title (with self-link).
try!(write!(&mut output_file,
"<h2 id=\"{0}\" class=\"section-header\"><a href=\"#{0}\">{0}</a></h2>\n",
err_code));
// Description rendered as markdown.
match info.description {
Some(ref desc) => try!(write!(&mut output_file, "{}", Markdown(desc))),
None => try!(write!(&mut output_file, "<p>No description.</p>\n")),
try!(formatter.error_code_block(&mut output_file, info, err_code));
}
try!(write!(&mut output_file, "</div>\n"));
formatter.footer(&mut output_file)
}
try!(write!(&mut output_file, "</body>\n</html>"));
Ok(())
}
fn main_with_result() -> Result<(), Box<Error>> {
fn main_with_result(format: OutputFormat) -> Result<(), Box<Error>> {
let build_arch = try!(env::var("CFG_BUILD"));
let metadata_dir = get_metadata_dir(&build_arch);
let err_map = try!(load_all_errors(&metadata_dir));
try!(render_error_page(&err_map, Path::new("doc/error-index.html")));
match format {
OutputFormat::Unknown(s) => panic!("Unknown output format: {}", s),
OutputFormat::HTML(h) => try!(render_error_page(&err_map,
Path::new("doc/error-index.html"),
h)),
OutputFormat::Markdown(m) => try!(render_error_page(&err_map,
Path::new("doc/error-index.html"),
m)),
}
Ok(())
}
fn parse_args() -> OutputFormat {
for arg in env::args().skip(1) {
return OutputFormat::from(&arg);
}
OutputFormat::from("html")
}
fn main() {
if let Err(e) = main_with_result() {
if let Err(e) = main_with_result(parse_args()) {
panic!("{}", e.description());
}
}

View File

@ -65,7 +65,7 @@ pub fn compile_input(sess: &Session,
outdir: &Option<PathBuf>,
output: &Option<PathBuf>,
addl_plugins: Option<Vec<String>>,
control: CompileController) -> CompileResult {
control: &CompileController) -> CompileResult {
macro_rules! controller_entry_point {
($point: ident, $tsess: expr, $make_state: expr, $phase_result: expr) => {{
let state = $make_state;

View File

@ -199,7 +199,7 @@ pub fn run_compiler<'a>(args: &[String],
let plugins = sess.opts.debugging_opts.extra_plugins.clone();
let control = callbacks.build_controller(&sess);
(driver::compile_input(&sess, &cstore, cfg, &input, &odir, &ofile,
Some(plugins), control),
Some(plugins), &control),
Some(sess))
}

View File

@ -400,7 +400,8 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
let text = lines.collect::<Vec<&str>>().join("\n");
tests.add_test(text.to_owned(),
block_info.should_panic, block_info.no_run,
block_info.ignore, block_info.test_harness);
block_info.ignore, block_info.test_harness,
block_info.compile_fail);
}
}
@ -445,6 +446,7 @@ struct LangString {
ignore: bool,
rust: bool,
test_harness: bool,
compile_fail: bool,
}
impl LangString {
@ -455,6 +457,7 @@ impl LangString {
ignore: false,
rust: true, // NB This used to be `notrust = false`
test_harness: false,
compile_fail: false,
}
}
@ -474,7 +477,9 @@ impl LangString {
"no_run" => { data.no_run = true; seen_rust_tags = true; },
"ignore" => { data.ignore = true; seen_rust_tags = true; },
"rust" => { data.rust = true; seen_rust_tags = true; },
"test_harness" => { data.test_harness = true; seen_rust_tags = true; }
"test_harness" => { data.test_harness = true; seen_rust_tags = true; },
"compile_fail" => { data.compile_fail = true; seen_rust_tags = true;
data.no_run = true; },
_ => { seen_other_tags = true }
}
}
@ -557,28 +562,31 @@ mod tests {
#[test]
fn test_lang_string_parse() {
fn t(s: &str,
should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool) {
should_panic: bool, no_run: bool, ignore: bool, rust: bool, test_harness: bool,
compile_fail: bool) {
assert_eq!(LangString::parse(s), LangString {
should_panic: should_panic,
no_run: no_run,
ignore: ignore,
rust: rust,
test_harness: test_harness,
compile_fail: compile_fail,
})
}
// marker | should_panic| no_run | ignore | rust | test_harness
t("", false, false, false, true, false);
t("rust", false, false, false, true, false);
t("sh", false, false, false, false, false);
t("ignore", false, false, true, true, false);
t("should_panic", true, false, false, true, false);
t("no_run", false, true, false, true, false);
t("test_harness", false, false, false, true, true);
t("{.no_run .example}", false, true, false, true, false);
t("{.sh .should_panic}", true, false, false, true, false);
t("{.example .rust}", false, false, false, true, false);
t("{.test_harness .rust}", false, false, false, true, true);
// marker | should_panic| no_run| ignore| rust | test_harness| compile_fail
t("", false, false, false, true, false, false);
t("rust", false, false, false, true, false, false);
t("sh", false, false, false, false, false, false);
t("ignore", false, false, true, true, false, false);
t("should_panic", true, false, false, true, false, false);
t("no_run", false, true, false, true, false, false);
t("test_harness", false, false, false, true, true, false);
t("compile_fail", false, false, false, true, false, true);
t("{.no_run .example}", false, true, false, true, false, false);
t("{.sh .should_panic}", true, false, false, true, false, false);
t("{.example .rust}", false, false, false, true, false, false);
t("{.test_harness .rust}", false, false, false, true, true, false);
}
#[test]

View File

@ -22,10 +22,12 @@
#![feature(box_syntax)]
#![feature(dynamic_lib)]
#![feature(libc)]
#![feature(recover)]
#![feature(rustc_private)]
#![feature(set_stdio)]
#![feature(slice_patterns)]
#![feature(staged_api)]
#![feature(std_panic)]
#![feature(test)]
#![feature(unicode)]

View File

@ -18,6 +18,7 @@ use std::ffi::OsString;
use std::io::prelude::*;
use std::io;
use std::path::PathBuf;
use std::panic::{self, AssertRecoverSafe};
use std::process::Command;
use std::rc::Rc;
use std::str;
@ -175,7 +176,7 @@ fn scrape_test_config(krate: &::rustc_front::hir::Crate) -> TestOptions {
fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths,
externs: core::Externs,
should_panic: bool, no_run: bool, as_test_harness: bool,
opts: &TestOptions) {
compile_fail: bool, opts: &TestOptions) {
// the test harness wants its own `main` & top level functions, so
// never wrap the test in `fn main() { ... }`
let test = maketest(test, Some(cratename), as_test_harness, opts);
@ -241,19 +242,41 @@ fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths,
cstore.clone());
rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
let outdir = TempDir::new("rustdoctest").ok().expect("rustdoc needs a tempdir");
let out = Some(outdir.path().to_path_buf());
let mut cfg = config::build_configuration(&sess);
cfg.extend(config::parse_cfgspecs(cfgs));
let outdir = Mutex::new(TempDir::new("rustdoctest").ok().expect("rustdoc needs a tempdir"));
let libdir = sess.target_filesearch(PathKind::All).get_lib_path();
let mut control = driver::CompileController::basic();
let mut cfg = config::build_configuration(&sess);
cfg.extend(config::parse_cfgspecs(cfgs.clone()));
let out = Some(outdir.lock().unwrap().path().to_path_buf());
if no_run {
control.after_analysis.stop = Compilation::Stop;
}
let result = driver::compile_input(&sess, &cstore, cfg, &input,
&out, &None, None, control);
match result {
Err(count) if count > 0 => sess.fatal("aborting due to previous error(s)"),
match {
let b_sess = AssertRecoverSafe::new(&sess);
let b_cstore = AssertRecoverSafe::new(&cstore);
let b_cfg = AssertRecoverSafe::new(cfg.clone());
let b_input = AssertRecoverSafe::new(&input);
let b_out = AssertRecoverSafe::new(&out);
let b_control = AssertRecoverSafe::new(&control);
panic::recover(|| {
AssertRecoverSafe::new(driver::compile_input(&b_sess, &b_cstore, (*b_cfg).clone(),
&b_input, &b_out,
&None, None, &b_control))
})
} {
Ok(r) => {
match *r {
Err(count) if count > 0 && compile_fail == false => {
sess.fatal("aborting due to previous error(s)")
}
Ok(()) if compile_fail => panic!("test compiled while it wasn't supposed to"),
_ => {}
}
}
Err(_) if compile_fail == false => panic!("couldn't compile the test"),
_ => {}
}
@ -265,7 +288,7 @@ fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths,
// environment to ensure that the target loads the right libraries at
// runtime. It would be a sad day if the *host* libraries were loaded as a
// mistake.
let mut cmd = Command::new(&outdir.path().join("rust_out"));
let mut cmd = Command::new(&outdir.lock().unwrap().path().join("rust_out"));
let var = DynamicLibrary::envvar();
let newpath = {
let path = env::var_os(var).unwrap_or(OsString::new());
@ -389,7 +412,7 @@ impl Collector {
pub fn add_test(&mut self, test: String,
should_panic: bool, no_run: bool, should_ignore: bool,
as_test_harness: bool) {
as_test_harness: bool, compile_fail: bool) {
let name = if self.use_headers {
let s = self.current_header.as_ref().map(|s| &**s).unwrap_or("");
format!("{}_{}", s, self.cnt)
@ -419,6 +442,7 @@ impl Collector {
should_panic,
no_run,
as_test_harness,
compile_fail,
&opts);
}))
});

View File

@ -71,5 +71,5 @@ fn compile(code: String, output: PathBuf, sysroot: PathBuf) {
&None,
&Some(output),
None,
control);
&control);
}