about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2018-02-17 14:45:20 +0100
committerGitHub <noreply@github.com>2018-02-17 14:45:20 +0100
commit8aa2852399667b8aa705d2eae066a493bdf54ede (patch)
treea9e6cb906f5948739d669a806e0d3605bda2dfbb
parentb85bd51c944f8cbe3a9c4cc95b61e08e5f338052 (diff)
parent72779936ec89a92731e40e0c406c4620a7c33d7d (diff)
downloadrust-8aa2852399667b8aa705d2eae066a493bdf54ede.tar.gz
rust-8aa2852399667b8aa705d2eae066a493bdf54ede.zip
Rollup merge of #48095 - QuietMisdreavus:doctest-assembly, r=GuillaumeGomez
add unit tests for rustdoc's processing of doctests

cc #42018

There's a lot of things that rustdoc will do to massage doctests into something that can be compiled, and a lot of options that can be toggled to affect this. Hopefully this list of tests can show off that functionality.

The first commit is slightly unrelated but doesn't touch public functionality, because i found that if you have a manual `fn main`, it adds an extra line break at the end, whereas it would trim this extra line break if it were putting a `fn main` in automatically. That first commit makes it trim out that whitespace ahead of time.
-rw-r--r--src/librustdoc/test.rs220
-rw-r--r--src/test/rustdoc/playground.rs4
2 files changed, 219 insertions, 5 deletions
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index 087d88419bc..08258489a2e 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -345,6 +345,7 @@ pub fn make_test(s: &str,
                  opts: &TestOptions)
                  -> (String, usize) {
     let (crate_attrs, everything_else) = partition_source(s);
+    let everything_else = everything_else.trim();
     let mut line_offset = 0;
     let mut prog = String::new();
 
@@ -392,12 +393,11 @@ pub fn make_test(s: &str,
         .any(|code| code.contains("fn main"));
 
     if dont_insert_main || already_has_main {
-        prog.push_str(&everything_else);
+        prog.push_str(everything_else);
     } else {
         prog.push_str("fn main() {\n");
         line_offset += 1;
-        prog.push_str(&everything_else);
-        prog = prog.trim().into();
+        prog.push_str(everything_else);
         prog.push_str("\n}");
     }
 
@@ -753,3 +753,217 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for HirCollector<'a, 'hir> {
         self.visit_testable(macro_def.name.to_string(), &macro_def.attrs, |_| ());
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::{TestOptions, make_test};
+
+    #[test]
+    fn make_test_basic() {
+        //basic use: wraps with `fn main`, adds `#![allow(unused)]`
+        let opts = TestOptions::default();
+        let input =
+"assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+fn main() {
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, None, false, &opts);
+        assert_eq!(output, (expected.clone(), 2));
+    }
+
+    #[test]
+    fn make_test_crate_name_no_use() {
+        //if you give a crate name but *don't* use it within the test, it won't bother inserting
+        //the `extern crate` statement
+        let opts = TestOptions::default();
+        let input =
+"assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+fn main() {
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, Some("asdf"), false, &opts);
+        assert_eq!(output, (expected, 2));
+    }
+
+    #[test]
+    fn make_test_crate_name() {
+        //if you give a crate name and use it within the test, it will insert an `extern crate`
+        //statement before `fn main`
+        let opts = TestOptions::default();
+        let input =
+"use asdf::qwop;
+assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, Some("asdf"), false, &opts);
+        assert_eq!(output, (expected, 3));
+    }
+
+    #[test]
+    fn make_test_no_crate_inject() {
+        //even if you do use the crate within the test, setting `opts.no_crate_inject` will skip
+        //adding it anyway
+        let opts = TestOptions {
+            no_crate_inject: true,
+            attrs: vec![],
+        };
+        let input =
+"use asdf::qwop;
+assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, Some("asdf"), false, &opts);
+        assert_eq!(output, (expected, 2));
+    }
+
+    #[test]
+    fn make_test_ignore_std() {
+        //even if you include a crate name, and use it in the doctest, we still won't include an
+        //`extern crate` statement if the crate is "std" - that's included already by the compiler!
+        let opts = TestOptions::default();
+        let input =
+"use std::*;
+assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+fn main() {
+use std::*;
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, Some("std"), false, &opts);
+        assert_eq!(output, (expected, 2));
+    }
+
+    #[test]
+    fn make_test_manual_extern_crate() {
+        //when you manually include an `extern crate` statement in your doctest, make_test assumes
+        //you've included one for your own crate too
+        let opts = TestOptions::default();
+        let input =
+"extern crate asdf;
+use asdf::qwop;
+assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+fn main() {
+extern crate asdf;
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, Some("asdf"), false, &opts);
+        assert_eq!(output, (expected, 2));
+    }
+
+    #[test]
+    fn make_test_opts_attrs() {
+        //if you supplied some doctest attributes with #![doc(test(attr(...)))], it will use those
+        //instead of the stock #![allow(unused)]
+        let mut opts = TestOptions::default();
+        opts.attrs.push("feature(sick_rad)".to_string());
+        let input =
+"use asdf::qwop;
+assert_eq!(2+2, 4);";
+        let expected =
+"#![feature(sick_rad)]
+extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, Some("asdf"), false, &opts);
+        assert_eq!(output, (expected, 3));
+
+        //adding more will also bump the returned line offset
+        opts.attrs.push("feature(hella_dope)".to_string());
+        let expected =
+"#![feature(sick_rad)]
+#![feature(hella_dope)]
+extern crate asdf;
+fn main() {
+use asdf::qwop;
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, Some("asdf"), false, &opts);
+        assert_eq!(output, (expected, 4));
+    }
+
+    #[test]
+    fn make_test_crate_attrs() {
+        //including inner attributes in your doctest will apply them to the whole "crate", pasting
+        //them outside the generated main function
+        let opts = TestOptions::default();
+        let input =
+"#![feature(sick_rad)]
+assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+#![feature(sick_rad)]
+fn main() {
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, None, false, &opts);
+        assert_eq!(output, (expected, 2));
+    }
+
+    #[test]
+    fn make_test_with_main() {
+        //including your own `fn main` wrapper lets the test use it verbatim
+        let opts = TestOptions::default();
+        let input =
+"fn main() {
+    assert_eq!(2+2, 4);
+}";
+        let expected =
+"#![allow(unused)]
+fn main() {
+    assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, None, false, &opts);
+        assert_eq!(output, (expected, 1));
+    }
+
+    #[test]
+    fn make_test_fake_main() {
+        //...but putting it in a comment will still provide a wrapper
+        let opts = TestOptions::default();
+        let input =
+"//Ceci n'est pas une `fn main`
+assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+fn main() {
+//Ceci n'est pas une `fn main`
+assert_eq!(2+2, 4);
+}".to_string();
+        let output = make_test(input, None, false, &opts);
+        assert_eq!(output, (expected.clone(), 2));
+    }
+
+    #[test]
+    fn make_test_dont_insert_main() {
+        //even with that, if you set `dont_insert_main`, it won't create the `fn main` wrapper
+        let opts = TestOptions::default();
+        let input =
+"//Ceci n'est pas une `fn main`
+assert_eq!(2+2, 4);";
+        let expected =
+"#![allow(unused)]
+//Ceci n'est pas une `fn main`
+assert_eq!(2+2, 4);".to_string();
+        let output = make_test(input, None, true, &opts);
+        assert_eq!(output, (expected.clone(), 1));
+    }
+}
diff --git a/src/test/rustdoc/playground.rs b/src/test/rustdoc/playground.rs
index 8e193efaf85..9e7c3aa4901 100644
--- a/src/test/rustdoc/playground.rs
+++ b/src/test/rustdoc/playground.rs
@@ -34,6 +34,6 @@
 //! }
 //! ```
 
-// @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0Afn%20main()%20%7B%0A%20%20%20%20println!(%22Hello%2C%20world!%22)%3B%0A%7D%0A"]' "Run"
+// @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0Afn%20main()%20%7B%0A%20%20%20%20println!(%22Hello%2C%20world!%22)%3B%0A%7D"]' "Run"
 // @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0Afn%20main()%20%7B%0Aprintln!(%22Hello%2C%20world!%22)%3B%0A%7D"]' "Run"
-// @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0A%23!%5Bfeature(something)%5D%0A%0Afn%20main()%20%7B%0A%20%20%20%20println!(%22Hello%2C%20world!%22)%3B%0A%7D%0A&version=nightly"]' "Run"
+// @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Ballow(unused)%5D%0A%23!%5Bfeature(something)%5D%0A%0Afn%20main()%20%7B%0A%20%20%20%20println!(%22Hello%2C%20world!%22)%3B%0A%7D&version=nightly"]' "Run"