about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-02-04 01:32:21 +0000
committerbors <bors@rust-lang.org>2017-02-04 01:32:21 +0000
commitc781fc4a6a8af287f7abe141f035a638bc0165c3 (patch)
tree1b104948b624ede568053d31b42c41176192fed3
parent0648517faf1e2cf37c8b6770cbd0180a816ed9a0 (diff)
parentb7f1d7a4bdf1a06d4ac1fc1d01eb7568608351a0 (diff)
Auto merge of #36320 - GuillaumeGomez:rustdoc_test_info, r=alexcrichton
Add information in case of markdown block code test failure

r? @steveklabnik

cc @jonathandturner
-rw-r--r--.gitmodules1
-rw-r--r--src/librustdoc/clean/mod.rs10
-rw-r--r--src/librustdoc/html/markdown.rs56
-rw-r--r--src/librustdoc/markdown.rs5
-rw-r--r--src/librustdoc/test.rs49
-rw-r--r--src/libsyntax/attr.rs12
-rw-r--r--src/libsyntax/print/pprust.rs5
-rw-r--r--src/libsyntax/std_inject.rs3
-rw-r--r--src/libsyntax/test.rs3
m---------src/rt/hoedown0
-rw-r--r--src/test/run-make/issue-22131/Makefile2
-rw-r--r--src/test/rustdoc/test_option_check/test.rs26
-rw-r--r--src/tools/cargotest/main.rs2
-rw-r--r--src/tools/compiletest/src/header.rs11
-rw-r--r--src/tools/compiletest/src/runtest.rs77
15 files changed, 206 insertions, 56 deletions
diff --git a/.gitmodules b/.gitmodules
index 39288a7ae49..86c5c780c5e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -8,6 +8,7 @@
 [submodule "src/rt/hoedown"]
 	path = src/rt/hoedown
 	url = https://github.com/rust-lang/hoedown.git
+	branch = rust-2015-09-21-do-not-delete
 [submodule "src/jemalloc"]
 	path = src/jemalloc
 	url = https://github.com/rust-lang/jemalloc.git
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index cdb24a56367..fe2edcad25c 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -521,17 +521,22 @@ impl<'a, I: IntoIterator<Item=&'a ast::NestedMetaItem>> NestedAttributesExt for
 #[derive(Clone, RustcEncodable, RustcDecodable, PartialEq, Debug, Default)]
 pub struct Attributes {
     pub doc_strings: Vec<String>,
-    pub other_attrs: Vec<ast::Attribute>
+    pub other_attrs: Vec<ast::Attribute>,
+    pub span: Option<syntax_pos::Span>,
 }
 
 impl Attributes {
     pub fn from_ast(attrs: &[ast::Attribute]) -> Attributes {
         let mut doc_strings = vec![];
+        let mut sp = None;
         let other_attrs = attrs.iter().filter_map(|attr| {
             attr.with_desugared_doc(|attr| {
                 if let Some(value) = attr.value_str() {
                     if attr.check_name("doc") {
                         doc_strings.push(value.to_string());
+                        if sp.is_none() {
+                            sp = Some(attr.span);
+                        }
                         return None;
                     }
                 }
@@ -541,7 +546,8 @@ impl Attributes {
         }).collect();
         Attributes {
             doc_strings: doc_strings,
-            other_attrs: other_attrs
+            other_attrs: other_attrs,
+            span: sp,
         }
     }
 
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 442a2f40742..e8ff8930bdd 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -71,29 +71,31 @@ const HOEDOWN_EXTENSIONS: libc::c_uint =
 enum hoedown_document {}
 
 type blockcodefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                 *const hoedown_buffer, *const hoedown_renderer_data);
+                                 *const hoedown_buffer, *const hoedown_renderer_data,
+                                 libc::size_t);
 
 type blockquotefn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                  *const hoedown_renderer_data);
+                                  *const hoedown_renderer_data, libc::size_t);
 
 type headerfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                              libc::c_int, *const hoedown_renderer_data);
+                              libc::c_int, *const hoedown_renderer_data,
+                              libc::size_t);
 
 type blockhtmlfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                 *const hoedown_renderer_data);
+                                 *const hoedown_renderer_data, libc::size_t);
 
 type codespanfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                *const hoedown_renderer_data) -> libc::c_int;
+                                *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
 
 type linkfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
                              *const hoedown_buffer, *const hoedown_buffer,
-                             *const hoedown_renderer_data) -> libc::c_int;
+                             *const hoedown_renderer_data, libc::size_t) -> libc::c_int;
 
 type entityfn = extern "C" fn (*mut hoedown_buffer, *const hoedown_buffer,
-                               *const hoedown_renderer_data);
+                               *const hoedown_renderer_data, libc::size_t);
 
 type normaltextfn = extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                                  *const hoedown_renderer_data);
+                                  *const hoedown_renderer_data, libc::size_t);
 
 #[repr(C)]
 struct hoedown_renderer_data {
@@ -147,7 +149,8 @@ struct html_toc_data {
 
 struct MyOpaque {
     dfltblk: extern "C" fn(*mut hoedown_buffer, *const hoedown_buffer,
-                           *const hoedown_buffer, *const hoedown_renderer_data),
+                           *const hoedown_buffer, *const hoedown_renderer_data,
+                           libc::size_t),
     toc_builder: Option<TocBuilder>,
 }
 
@@ -229,7 +232,8 @@ pub fn render(w: &mut fmt::Formatter,
               print_toc: bool,
               html_flags: libc::c_uint) -> fmt::Result {
     extern fn block(ob: *mut hoedown_buffer, orig_text: *const hoedown_buffer,
-                    lang: *const hoedown_buffer, data: *const hoedown_renderer_data) {
+                    lang: *const hoedown_buffer, data: *const hoedown_renderer_data,
+                    line: libc::size_t) {
         unsafe {
             if orig_text.is_null() { return }
 
@@ -246,7 +250,8 @@ pub fn render(w: &mut fmt::Formatter,
                 let rlang = str::from_utf8(rlang).unwrap();
                 if !LangString::parse(rlang).rust {
                     (my_opaque.dfltblk)(ob, orig_text, lang,
-                                        opaque as *const hoedown_renderer_data);
+                                        opaque as *const hoedown_renderer_data,
+                                        line);
                     true
                 } else {
                     false
@@ -312,7 +317,8 @@ pub fn render(w: &mut fmt::Formatter,
     }
 
     extern fn header(ob: *mut hoedown_buffer, text: *const hoedown_buffer,
-                     level: libc::c_int, data: *const hoedown_renderer_data) {
+                     level: libc::c_int, data: *const hoedown_renderer_data,
+                     _: libc::size_t) {
         // hoedown does this, we may as well too
         unsafe { hoedown_buffer_puts(ob, "\n\0".as_ptr() as *const _); }
 
@@ -373,6 +379,7 @@ pub fn render(w: &mut fmt::Formatter,
         ob: *mut hoedown_buffer,
         text: *const hoedown_buffer,
         _: *const hoedown_renderer_data,
+        _: libc::size_t
     ) -> libc::c_int {
         let content = if text.is_null() {
             "".to_owned()
@@ -422,11 +429,12 @@ pub fn render(w: &mut fmt::Formatter,
     }
 }
 
-pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
+pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector, start_line: usize) {
     extern fn block(_ob: *mut hoedown_buffer,
                     text: *const hoedown_buffer,
                     lang: *const hoedown_buffer,
-                    data: *const hoedown_renderer_data) {
+                    data: *const hoedown_renderer_data,
+                    line: libc::size_t) {
         unsafe {
             if text.is_null() { return }
             let block_info = if lang.is_null() {
@@ -445,16 +453,19 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
                 stripped_filtered_line(l).unwrap_or(l)
             });
             let text = lines.collect::<Vec<&str>>().join("\n");
+            let line = tests.get_line() + line;
             tests.add_test(text.to_owned(),
                            block_info.should_panic, block_info.no_run,
                            block_info.ignore, block_info.test_harness,
-                           block_info.compile_fail, block_info.error_codes);
+                           block_info.compile_fail, block_info.error_codes,
+                           line);
         }
     }
 
     extern fn header(_ob: *mut hoedown_buffer,
                      text: *const hoedown_buffer,
-                     level: libc::c_int, data: *const hoedown_renderer_data) {
+                     level: libc::c_int, data: *const hoedown_renderer_data,
+                     _: libc::size_t) {
         unsafe {
             let opaque = (*data).opaque as *mut hoedown_html_renderer_state;
             let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
@@ -468,6 +479,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
         }
     }
 
+    tests.set_line(start_line);
     unsafe {
         let ob = hoedown_buffer_new(DEF_OUNIT);
         let renderer = hoedown_html_renderer_new(0, 0);
@@ -488,6 +500,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
 
 #[derive(Eq, PartialEq, Clone, Debug)]
 struct LangString {
+    original: String,
     should_panic: bool,
     no_run: bool,
     ignore: bool,
@@ -500,6 +513,7 @@ struct LangString {
 impl LangString {
     fn all_false() -> LangString {
         LangString {
+            original: String::new(),
             should_panic: false,
             no_run: false,
             ignore: false,
@@ -521,6 +535,7 @@ impl LangString {
             allow_error_code_check = true;
         }
 
+        data.original = string.to_owned();
         let tokens = string.split(|c: char|
             !(c == '_' || c == '-' || c.is_alphanumeric())
         );
@@ -586,7 +601,8 @@ pub fn plain_summary_line(md: &str) -> String {
                        _link: *const hoedown_buffer,
                        _title: *const hoedown_buffer,
                        content: *const hoedown_buffer,
-                       data: *const hoedown_renderer_data) -> libc::c_int
+                       data: *const hoedown_renderer_data,
+                       _: libc::size_t) -> libc::c_int
     {
         unsafe {
             if !content.is_null() && (*content).size > 0 {
@@ -599,8 +615,9 @@ pub fn plain_summary_line(md: &str) -> String {
     }
 
     extern fn normal_text(_ob: *mut hoedown_buffer,
-                              text: *const hoedown_buffer,
-                              data: *const hoedown_renderer_data)
+                          text: *const hoedown_buffer,
+                          data: *const hoedown_renderer_data,
+                          _: libc::size_t)
     {
         unsafe {
             let ob = (*data).opaque as *mut hoedown_buffer;
@@ -647,6 +664,7 @@ mod tests {
                 test_harness: test_harness,
                 compile_fail: compile_fail,
                 error_codes: error_codes,
+                original: s.to_owned(),
             })
         }
 
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index 369e18948ad..49497957be9 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -154,8 +154,9 @@ pub fn test(input: &str, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
     let mut opts = TestOptions::default();
     opts.no_crate_inject = true;
     let mut collector = Collector::new(input.to_string(), cfgs, libs, externs,
-                                       true, opts, maybe_sysroot);
-    find_testable_code(&input_str, &mut collector);
+                                       true, opts, maybe_sysroot, "input".to_string(),
+                                       None);
+    find_testable_code(&input_str, &mut collector, 0);
     test_args.insert(0, "rustdoctest".to_string());
     testing::test_main(&test_args, collector.tests);
     0
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index ab0ac02fd88..6b6330ef12a 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -37,6 +37,7 @@ use rustc_trans::back::link;
 use syntax::ast;
 use syntax::codemap::CodeMap;
 use syntax::feature_gate::UnstableFeatures;
+use syntax_pos::{BytePos, DUMMY_SP, Pos};
 use errors;
 use errors::emitter::ColorConfig;
 
@@ -79,7 +80,7 @@ pub fn run(input: &str,
     let _ignore = dep_graph.in_ignore();
     let cstore = Rc::new(CStore::new(&dep_graph));
     let mut sess = session::build_session_(
-        sessopts, &dep_graph, Some(input_path.clone()), handler, codemap, cstore.clone(),
+        sessopts, &dep_graph, Some(input_path.clone()), handler, codemap.clone(), cstore.clone(),
     );
     rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess));
     sess.parse_sess.config =
@@ -96,13 +97,16 @@ pub fn run(input: &str,
         link::find_crate_name(None, &hir_forest.krate().attrs, &input)
     });
     let opts = scrape_test_config(hir_forest.krate());
+    let filename = input_path.to_str().unwrap_or("").to_owned();
     let mut collector = Collector::new(crate_name,
                                        cfgs,
                                        libs,
                                        externs,
                                        false,
                                        opts,
-                                       maybe_sysroot);
+                                       maybe_sysroot,
+                                       filename,
+                                       Some(codemap));
 
     {
         let dep_graph = DepGraph::new(false);
@@ -256,7 +260,9 @@ fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths,
                         error_codes.retain(|err| !out.contains(err));
                     }
                 }
-                Ok(()) if compile_fail => panic!("test compiled while it wasn't supposed to"),
+                Ok(()) if compile_fail => {
+                    panic!("test compiled while it wasn't supposed to")
+                }
                 _ => {}
             }
         }
@@ -302,7 +308,7 @@ fn runtest(test: &str, cratename: &str, cfgs: Vec<String>, libs: SearchPaths,
             if should_panic && out.status.success() {
                 panic!("test executable succeeded when it should have failed");
             } else if !should_panic && !out.status.success() {
-                panic!("test executable failed:\n{}\n{}",
+                panic!("test executable failed:\n{}\n{}\n",
                        str::from_utf8(&out.stdout).unwrap_or(""),
                        str::from_utf8(&out.stderr).unwrap_or(""));
             }
@@ -384,11 +390,15 @@ pub struct Collector {
     cratename: String,
     opts: TestOptions,
     maybe_sysroot: Option<PathBuf>,
+    filename: String,
+    start_line: usize,
+    codemap: Option<Rc<CodeMap>>,
 }
 
 impl Collector {
     pub fn new(cratename: String, cfgs: Vec<String>, libs: SearchPaths, externs: Externs,
-               use_headers: bool, opts: TestOptions, maybe_sysroot: Option<PathBuf>) -> Collector {
+               use_headers: bool, opts: TestOptions, maybe_sysroot: Option<PathBuf>,
+               filename: String, codemap: Option<Rc<CodeMap>>) -> Collector {
         Collector {
             tests: Vec::new(),
             names: Vec::new(),
@@ -401,18 +411,17 @@ impl Collector {
             cratename: cratename,
             opts: opts,
             maybe_sysroot: maybe_sysroot,
+            filename: filename,
+            start_line: 0,
+            codemap: codemap,
         }
     }
 
     pub fn add_test(&mut self, test: String,
                     should_panic: bool, no_run: bool, should_ignore: bool,
-                    as_test_harness: bool, compile_fail: bool, error_codes: Vec<String>) {
-        let name = if self.use_headers {
-            let s = self.current_header.as_ref().map(|s| &**s).unwrap_or("");
-            format!("{}_{}", s, self.cnt)
-        } else {
-            format!("{}_{}", self.names.join("::"), self.cnt)
-        };
+                    as_test_harness: bool, compile_fail: bool, error_codes: Vec<String>,
+                    line: usize) {
+        let name = format!("{} - line {}", self.filename, line);
         self.cnt += 1;
         let cfgs = self.cfgs.clone();
         let libs = self.libs.clone();
@@ -456,6 +465,19 @@ impl Collector {
         });
     }
 
+    pub fn get_line(&self) -> usize {
+        if let Some(ref codemap) = self.codemap{
+            let line = codemap.lookup_char_pos(BytePos(self.start_line as u32)).line;
+            if line > 0 { line - 1 } else { line }
+        } else {
+            self.start_line
+        }
+    }
+
+    pub fn set_line(&mut self, start_line: usize) {
+        self.start_line = start_line;
+    }
+
     pub fn register_header(&mut self, name: &str, level: u32) {
         if self.use_headers && level == 1 {
             // we use these headings as test names, so it's good if
@@ -496,7 +518,8 @@ impl<'a, 'hir> HirCollector<'a, 'hir> {
         attrs.unindent_doc_comments();
         if let Some(doc) = attrs.doc_value() {
             self.collector.cnt = 0;
-            markdown::find_testable_code(doc, self.collector);
+            markdown::find_testable_code(doc, self.collector,
+                                         attrs.span.unwrap_or(DUMMY_SP).lo.to_usize());
         }
 
         nested(self);
diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs
index 099ca8f02d2..455a6a0fb32 100644
--- a/src/libsyntax/attr.rs
+++ b/src/libsyntax/attr.rs
@@ -280,9 +280,9 @@ impl Attribute {
                 Symbol::intern("doc"),
                 Symbol::intern(&strip_doc_comment_decoration(&comment.as_str())));
             if self.style == ast::AttrStyle::Outer {
-                f(&mk_attr_outer(self.id, meta))
+                f(&mk_attr_outer(self.span, self.id, meta))
             } else {
-                f(&mk_attr_inner(self.id, meta))
+                f(&mk_attr_inner(self.span, self.id, meta))
             }
         } else {
             f(self)
@@ -339,8 +339,8 @@ pub fn mk_attr_id() -> AttrId {
 }
 
 /// Returns an inner attribute with the given value.
-pub fn mk_attr_inner(id: AttrId, item: MetaItem) -> Attribute {
-    mk_spanned_attr_inner(DUMMY_SP, id, item)
+pub fn mk_attr_inner(span: Span, id: AttrId, item: MetaItem) -> Attribute {
+    mk_spanned_attr_inner(span, id, item)
 }
 
 /// Returns an innter attribute with the given value and span.
@@ -356,8 +356,8 @@ pub fn mk_spanned_attr_inner(sp: Span, id: AttrId, item: MetaItem) -> Attribute
 
 
 /// Returns an outer attribute with the given value.
-pub fn mk_attr_outer(id: AttrId, item: MetaItem) -> Attribute {
-    mk_spanned_attr_outer(DUMMY_SP, id, item)
+pub fn mk_attr_outer(span: Span, id: AttrId, item: MetaItem) -> Attribute {
+    mk_spanned_attr_outer(span, id, item)
 }
 
 /// Returns an outer attribute with the given value and span.
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index ffdd3b2e93e..f8f1820d0b9 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -27,6 +27,7 @@ use print::pp::Breaks::{Consistent, Inconsistent};
 use ptr::P;
 use std_inject;
 use symbol::{Symbol, keywords};
+use syntax_pos::DUMMY_SP;
 use tokenstream::{self, TokenTree};
 
 use std::ascii;
@@ -116,12 +117,12 @@ pub fn print_crate<'a>(cm: &'a CodeMap,
         // #![feature(prelude_import)]
         let prelude_import_meta = attr::mk_list_word_item(Symbol::intern("prelude_import"));
         let list = attr::mk_list_item(Symbol::intern("feature"), vec![prelude_import_meta]);
-        let fake_attr = attr::mk_attr_inner(attr::mk_attr_id(), list);
+        let fake_attr = attr::mk_attr_inner(DUMMY_SP, attr::mk_attr_id(), list);
         s.print_attribute(&fake_attr)?;
 
         // #![no_std]
         let no_std_meta = attr::mk_word_item(Symbol::intern("no_std"));
-        let fake_attr = attr::mk_attr_inner(attr::mk_attr_id(), no_std_meta);
+        let fake_attr = attr::mk_attr_inner(DUMMY_SP, attr::mk_attr_id(), no_std_meta);
         s.print_attribute(&fake_attr)?;
     }
 
diff --git a/src/libsyntax/std_inject.rs b/src/libsyntax/std_inject.rs
index 68d807b24a7..4a2dfaf6124 100644
--- a/src/libsyntax/std_inject.rs
+++ b/src/libsyntax/std_inject.rs
@@ -56,7 +56,8 @@ pub fn maybe_inject_crates_ref(sess: &ParseSess,
     let crate_name = Symbol::intern(&alt_std_name.unwrap_or(name.to_string()));
 
     krate.module.items.insert(0, P(ast::Item {
-        attrs: vec![attr::mk_attr_outer(attr::mk_attr_id(),
+        attrs: vec![attr::mk_attr_outer(DUMMY_SP,
+                                        attr::mk_attr_id(),
                                         attr::mk_word_item(Symbol::intern("macro_use")))],
         vis: ast::Visibility::Inherited,
         node: ast::ItemKind::ExternCrate(Some(crate_name)),
diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs
index 74ec33fdd2a..dd2756cd2b2 100644
--- a/src/libsyntax/test.rs
+++ b/src/libsyntax/test.rs
@@ -195,7 +195,8 @@ impl fold::Folder for EntryPointCleaner {
                     let dead_code_str = Symbol::intern("dead_code");
                     let word_vec = vec![attr::mk_list_word_item(dead_code_str)];
                     let allow_dead_code_item = attr::mk_list_item(allow_str, word_vec);
-                    let allow_dead_code = attr::mk_attr_outer(attr::mk_attr_id(),
+                    let allow_dead_code = attr::mk_attr_outer(DUMMY_SP,
+                                                              attr::mk_attr_id(),
                                                               allow_dead_code_item);
 
                     ast::Item {
diff --git a/src/rt/hoedown b/src/rt/hoedown
-Subproject a3736a0a1907cbc8bf619708738815a5fd789c8
+Subproject da282f1bb7277b4d30fa1599ee29ad8eb4dd2a9
diff --git a/src/test/run-make/issue-22131/Makefile b/src/test/run-make/issue-22131/Makefile
index 1e8568626a6..f65cc9e06a3 100644
--- a/src/test/run-make/issue-22131/Makefile
+++ b/src/test/run-make/issue-22131/Makefile
@@ -4,4 +4,4 @@ all: foo.rs
 	$(RUSTC) --cfg 'feature="bar"' --crate-type lib foo.rs
 	$(HOST_RPATH_ENV) '$(RUSTDOC)' --test --cfg 'feature="bar"' \
 		-L $(TMPDIR) foo.rs |\
-		grep -q 'test foo_0 ... ok'
+		grep -q 'foo.rs - line 11 ... ok'
diff --git a/src/test/rustdoc/test_option_check/test.rs b/src/test/rustdoc/test_option_check/test.rs
new file mode 100644
index 00000000000..b2afe43204d
--- /dev/null
+++ b/src/test/rustdoc/test_option_check/test.rs
@@ -0,0 +1,26 @@
+// Copyright 2017 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.
+
+// compile-flags: --test
+// check-test-line-numbers-match
+
+/// This is a Foo;
+///
+/// ```
+/// println!("baaaaaar");
+/// ```
+pub struct Foo;
+
+/// This is a Bar;
+///
+/// ```
+/// println!("fooooo");
+/// ```
+pub struct Bar;
diff --git a/src/tools/cargotest/main.rs b/src/tools/cargotest/main.rs
index 83fd766c547..786b3192e06 100644
--- a/src/tools/cargotest/main.rs
+++ b/src/tools/cargotest/main.rs
@@ -25,7 +25,7 @@ const TEST_REPOS: &'static [Test] = &[
     Test {
         name: "cargo",
         repo: "https://github.com/rust-lang/cargo",
-        sha: "2324c2bbaf7fc6ea9cbdd77c034ef1af769cb617",
+        sha: "0e1e34be7540bdaed4918457654fbf028cf69e56",
         lock: None,
     },
     Test {
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index ac1ac1c2f6c..71d8d62c75b 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -224,6 +224,8 @@ pub struct TestProps {
     pub incremental_dir: Option<PathBuf>,
     // Specifies that a cfail test must actually compile without errors.
     pub must_compile_successfully: bool,
+    // rustdoc will test the output of the `--test` option
+    pub check_test_line_numbers_match: bool,
 }
 
 impl TestProps {
@@ -248,6 +250,7 @@ impl TestProps {
             forbid_output: vec![],
             incremental_dir: None,
             must_compile_successfully: false,
+            check_test_line_numbers_match: false,
         }
     }
 
@@ -347,6 +350,10 @@ impl TestProps {
             if !self.must_compile_successfully {
                 self.must_compile_successfully = parse_must_compile_successfully(ln);
             }
+
+            if !self.check_test_line_numbers_match {
+                self.check_test_line_numbers_match = parse_check_test_line_numbers_match(ln);
+            }
         });
 
         for key in vec!["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
@@ -458,6 +465,10 @@ fn parse_must_compile_successfully(line: &str) -> bool {
     parse_name_directive(line, "must-compile-successfully")
 }
 
+fn parse_check_test_line_numbers_match(line: &str) -> bool {
+    parse_name_directive(line, "check-test-line-numbers-match")
+}
+
 fn parse_env(line: &str, name: &str) -> Option<(String, String)> {
     parse_name_value_directive(line, name).map(|nv| {
         // nv is either FOO or FOO=BAR
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 86fa5e70c9c..a8c46722e16 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -43,7 +43,7 @@ pub fn run(config: Config, testpaths: &TestPaths) {
         }
 
         _ => {
-            // android has it's own gdb handling
+            // android has its own gdb handling
             if config.mode == DebugInfoGdb && config.gdb.is_none() {
                 panic!("gdb not available but debuginfo gdb debuginfo test requested");
             }
@@ -1887,14 +1887,75 @@ actual:\n\
         if !proc_res.status.success() {
             self.fatal_proc_rec("rustdoc failed!", &proc_res);
         }
-        let root = self.find_rust_src_root().unwrap();
 
-        let res = self.cmd2procres(Command::new(&self.config.docck_python)
-                                   .arg(root.join("src/etc/htmldocck.py"))
-                                   .arg(out_dir)
-                                   .arg(&self.testpaths.file));
-        if !res.status.success() {
-            self.fatal_proc_rec("htmldocck failed!", &res);
+        if self.props.check_test_line_numbers_match == true {
+            self.check_rustdoc_test_option(proc_res);
+        } else {
+            let root = self.find_rust_src_root().unwrap();
+            let res = self.cmd2procres(Command::new(&self.config.docck_python)
+                                       .arg(root.join("src/etc/htmldocck.py"))
+                                       .arg(out_dir)
+                                       .arg(&self.testpaths.file));
+            if !res.status.success() {
+                self.fatal_proc_rec("htmldocck failed!", &res);
+            }
+        }
+    }
+
+    fn check_rustdoc_test_option(&self, res: ProcRes) {
+        let mut file = fs::File::open(&self.testpaths.file)
+                                .expect("markdown_test_output_check_entry File::open failed");
+        let mut content = String::new();
+        file.read_to_string(&mut content)
+            .expect("markdown_test_output_check_entry read_to_string failed");
+        let mut ignore = false;
+        let mut v: Vec<usize> =
+            content.lines()
+                   .enumerate()
+                   .filter_map(|(line_nb, line)| {
+                       let sline = line.split("///").last().unwrap_or("");
+                       let line = sline.trim_left();
+                       if line.starts_with("```") {
+                           if ignore {
+                               ignore = false;
+                               None
+                           } else {
+                               ignore = true;
+                               Some(line_nb + 1)
+                           }
+                       } else {
+                           None
+                       }
+                   })
+                   .collect();
+
+        let mut tested = 0;
+        for _ in res.stdout.split("\n")
+                           .filter(|s| s.starts_with("test "))
+                           .inspect(|s| {
+                               let tmp: Vec<&str> = s.split(" - line ").collect();
+                               if tmp.len() == 2 {
+                                   tested += 1;
+                                   let line = tmp[1].split(" ...")
+                                                    .next()
+                                                    .unwrap_or("0")
+                                                    .parse()
+                                                    .unwrap_or(0);
+                                   if let Ok(pos) = v.binary_search(&line) {
+                                       v.remove(pos);
+                                   } else {
+                                       self.fatal_proc_rec(
+                                           &format!("Not found doc test: \"{}\" in {:?}", s, v),
+                                           &res);
+                                   }
+                               }
+                           }) {}
+        if tested == 0 {
+            self.fatal_proc_rec("No test has been found", &res);
+        } else if v.len() != 0 {
+            self.fatal_proc_rec(&format!("Not found test at line{} {:?}",
+                                         if v.len() > 1 { "s" } else { "" }, v),
+                                &res);
         }
     }