| use std::path::PathBuf; | |
| use rustc_span::edition::Edition; | |
| use rustc_span::{DUMMY_SP, FileName}; | |
| use super::extracted::ExtractedDocTests; | |
| use super::{BuildDocTestBuilder, GlobalTestOptions, ScrapedDocTest}; | |
| use crate::html::markdown::LangString; | |
| fn make_test( | |
| test_code: &str, | |
| crate_name: Option<&str>, | |
| dont_insert_main: bool, | |
| opts: &GlobalTestOptions, | |
| global_crate_attrs: Vec<&str>, | |
| test_id: Option<&str>, | |
| ) -> (String, usize) { | |
| let mut builder = BuildDocTestBuilder::new(test_code) | |
| .global_crate_attrs(global_crate_attrs.into_iter().map(|a| a.to_string()).collect()); | |
| if let Some(crate_name) = crate_name { | |
| builder = builder.crate_name(crate_name); | |
| } | |
| if let Some(test_id) = test_id { | |
| builder = builder.test_id(test_id.to_string()); | |
| } | |
| let doctest = builder.build(None); | |
| let (wrapped, line_offset) = | |
| doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name); | |
| (wrapped.to_string(), line_offset) | |
| } | |
| /// Default [`GlobalTestOptions`] for these unit tests. | |
| fn default_global_opts(crate_name: impl Into<String>) -> GlobalTestOptions { | |
| GlobalTestOptions { | |
| crate_name: crate_name.into(), | |
| no_crate_inject: false, | |
| insert_indent_space: false, | |
| args_file: PathBuf::new(), | |
| } | |
| } | |
| #[test] | |
| fn make_test_basic() { | |
| //basic use: wraps with `fn main`, adds `#![allow(unused)]` | |
| let opts = default_global_opts(""); | |
| let input = "assert_eq!(2+2, 4);"; | |
| let expected = "#![allow(unused)] | |
| fn main() { | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 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 = default_global_opts("asdf"); | |
| let input = "assert_eq!(2+2, 4);"; | |
| let expected = "#![allow(unused)] | |
| fn main() { | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (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 = default_global_opts("asdf"); | |
| let input = "use asdf::qwop; | |
| assert_eq!(2+2, 4);"; | |
| let expected = "#![allow(unused)] | |
| #[allow(unused_extern_crates)] | |
| extern crate r#asdf; | |
| fn main() { | |
| use asdf::qwop; | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (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 = GlobalTestOptions { no_crate_inject: true, ..default_global_opts("asdf") }; | |
| 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, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (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 = default_global_opts("std"); | |
| 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, len) = make_test(input, Some("std"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (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 = default_global_opts("asdf"); | |
| let input = "extern crate asdf; | |
| 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, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 2)); | |
| } | |
| #[test] | |
| fn make_test_manual_extern_crate_with_macro_use() { | |
| let opts = default_global_opts("asdf"); | |
| let input = "#[macro_use] extern crate asdf; | |
| use asdf::qwop; | |
| assert_eq!(2+2, 4);"; | |
| let expected = "#![allow(unused)] | |
| #[macro_use] extern crate asdf; | |
| fn main() { | |
| use asdf::qwop; | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (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 opts = default_global_opts("asdf"); | |
| let input = "use asdf::qwop; | |
| assert_eq!(2+2, 4);"; | |
| let expected = "#![feature(sick_rad)] | |
| #[allow(unused_extern_crates)] | |
| extern crate r#asdf; | |
| fn main() { | |
| use asdf::qwop; | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = | |
| make_test(input, Some("asdf"), false, &opts, vec!["feature(sick_rad)"], None); | |
| assert_eq!((output, len), (expected, 3)); | |
| let expected = "#![feature(sick_rad)] | |
| #![feature(hella_dope)] | |
| #[allow(unused_extern_crates)] | |
| extern crate r#asdf; | |
| fn main() { | |
| use asdf::qwop; | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test( | |
| input, | |
| Some("asdf"), | |
| false, | |
| &opts, | |
| vec![ | |
| "feature(sick_rad)", | |
| // Adding more will also bump the returned line offset. | |
| "feature(hella_dope)", | |
| ], | |
| None, | |
| ); | |
| assert_eq!((output, len), (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 = default_global_opts(""); | |
| 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, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 2)); | |
| } | |
| #[test] | |
| fn make_test_with_main() { | |
| // Including your own `fn main` wrapper lets the test use it verbatim. | |
| let opts = default_global_opts(""); | |
| let input = "fn main() { | |
| assert_eq!(2+2, 4); | |
| }"; | |
| let expected = "#![allow(unused)] | |
| fn main() { | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 1)); | |
| } | |
| #[test] | |
| fn make_test_fake_main() { | |
| // ... but putting it in a comment will still provide a wrapper. | |
| let opts = default_global_opts(""); | |
| 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, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 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 = default_global_opts(""); | |
| 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, len) = make_test(input, None, true, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 1)); | |
| } | |
| #[test] | |
| fn make_test_issues_21299() { | |
| let opts = default_global_opts(""); | |
| let input = "// fn main | |
| assert_eq!(2+2, 4);"; | |
| let expected = "#![allow(unused)] | |
| fn main() { | |
| // fn main | |
| assert_eq!(2+2, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 2)); | |
| } | |
| #[test] | |
| fn make_test_issues_33731() { | |
| let opts = default_global_opts("asdf"); | |
| let input = "extern crate hella_qwop; | |
| assert_eq!(asdf::foo, 4);"; | |
| let expected = "#![allow(unused)] | |
| extern crate hella_qwop; | |
| #[allow(unused_extern_crates)] | |
| extern crate r#asdf; | |
| fn main() { | |
| assert_eq!(asdf::foo, 4); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 3)); | |
| } | |
| #[test] | |
| fn make_test_main_in_macro() { | |
| let opts = default_global_opts("my_crate"); | |
| let input = "#[macro_use] extern crate my_crate; | |
| test_wrapper! { | |
| fn main() {} | |
| }"; | |
| let expected = "#![allow(unused)] | |
| #[macro_use] extern crate my_crate; | |
| test_wrapper! { | |
| fn main() {} | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, Some("my_crate"), false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 1)); | |
| } | |
| #[test] | |
| fn make_test_returns_result() { | |
| // creates an inner function and unwraps it | |
| let opts = default_global_opts(""); | |
| let input = "use std::io; | |
| let mut input = String::new(); | |
| io::stdin().read_line(&mut input)?; | |
| Ok::<(), io:Error>(())"; | |
| let expected = "#![allow(unused)] | |
| fn main() { fn _inner() -> core::result::Result<(), impl core::fmt::Debug> { | |
| use std::io; | |
| let mut input = String::new(); | |
| io::stdin().read_line(&mut input)?; | |
| Ok::<(), io:Error>(()) | |
| } _inner().unwrap() }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 2)); | |
| } | |
| #[test] | |
| fn make_test_named_wrapper() { | |
| // creates an inner function with a specific name | |
| let opts = default_global_opts(""); | |
| let input = "assert_eq!(2+2, 4);"; | |
| let expected = "#![allow(unused)] | |
| fn main() { #[allow(non_snake_case)] fn _doctest_main__some_unique_name() { | |
| assert_eq!(2+2, 4); | |
| } _doctest_main__some_unique_name() }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), Some("_some_unique_name")); | |
| assert_eq!((output, len), (expected, 2)); | |
| } | |
| #[test] | |
| fn make_test_insert_extra_space() { | |
| // will insert indent spaces in the code block if `insert_indent_space` is true | |
| let opts = GlobalTestOptions { insert_indent_space: true, ..default_global_opts("") }; | |
| let input = "use std::*; | |
| assert_eq!(2+2, 4); | |
| eprintln!(\"hello anan\"); | |
| "; | |
| let expected = "#![allow(unused)] | |
| fn main() { | |
| use std::*; | |
| assert_eq!(2+2, 4); | |
| eprintln!(\"hello anan\"); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 2)); | |
| } | |
| #[test] | |
| fn make_test_insert_extra_space_fn_main() { | |
| // if input already has a fn main, it should insert a space before it | |
| let opts = GlobalTestOptions { insert_indent_space: true, ..default_global_opts("") }; | |
| let input = "use std::*; | |
| fn main() { | |
| assert_eq!(2+2, 4); | |
| eprintln!(\"hello anan\"); | |
| }"; | |
| let expected = "#![allow(unused)] | |
| use std::*; | |
| fn main() { | |
| assert_eq!(2+2, 4); | |
| eprintln!(\"hello anan\"); | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 1)); | |
| } | |
| #[test] | |
| fn comment_in_attrs() { | |
| // If there is an inline code comment after attributes, we need to ensure that | |
| // a backline will be added to prevent generating code "inside" it (and thus generating) | |
| // invalid code. | |
| let opts = default_global_opts(""); | |
| let input = "\ | |
| #![feature(rustdoc_internals)] | |
| #![allow(internal_features)] | |
| #![doc(rust_logo)] | |
| //! This crate has the Rust(tm) branding on it."; | |
| let expected = "\ | |
| #![allow(unused)] | |
| #![feature(rustdoc_internals)] | |
| #![allow(internal_features)] | |
| #![doc(rust_logo)] | |
| //! This crate has the Rust(tm) branding on it. | |
| fn main() { | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 2)); | |
| // And same, if there is a `main` function provided by the user, we ensure that it's | |
| // correctly separated. | |
| let input = "\ | |
| #![feature(rustdoc_internals)] | |
| #![allow(internal_features)] | |
| #![doc(rust_logo)] | |
| //! This crate has the Rust(tm) branding on it. | |
| fn main() {}"; | |
| let expected = "\ | |
| #![allow(unused)] | |
| #![feature(rustdoc_internals)] | |
| #![allow(internal_features)] | |
| #![doc(rust_logo)] | |
| //! This crate has the Rust(tm) branding on it. | |
| fn main() {}" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 1)); | |
| } | |
| // This test ensures that the only attributes taken into account when we switch between | |
| // "crate level" content and the rest doesn't include inner attributes span, as it would | |
| // include part of the item and generate broken code. | |
| #[test] | |
| fn inner_attributes() { | |
| let opts = default_global_opts(""); | |
| let input = r#" | |
| //! A doc comment that applies to the implicit anonymous module of this crate | |
| pub mod outer_module { | |
| //!! - Still an inner line doc (but with a bang at the beginning) | |
| } | |
| "#; | |
| let expected = "#![allow(unused)] | |
| //! A doc comment that applies to the implicit anonymous module of this crate | |
| fn main() { | |
| pub mod outer_module { | |
| //!! - Still an inner line doc (but with a bang at the beginning) | |
| } | |
| }" | |
| .to_string(); | |
| let (output, len) = make_test(input, None, false, &opts, Vec::new(), None); | |
| assert_eq!((output, len), (expected, 2)); | |
| } | |
| fn get_extracted_doctests(code: &str) -> ExtractedDocTests { | |
| let opts = default_global_opts(""); | |
| let mut extractor = ExtractedDocTests::new(); | |
| extractor.add_test_with_edition( | |
| ScrapedDocTest::new( | |
| FileName::Custom(String::new()), | |
| 0, | |
| Vec::new(), | |
| LangString::default(), | |
| code.to_string(), | |
| DUMMY_SP, | |
| Vec::new(), | |
| Vec::new(), | |
| ), | |
| &opts, | |
| Edition::Edition2018, | |
| ); | |
| extractor | |
| } | |
| // Test that `extracted::DocTest::wrapper` is `None` if the doctest has a `main` function. | |
| #[test] | |
| fn test_extracted_doctest_wrapper_field() { | |
| let extractor = get_extracted_doctests("fn main() {}"); | |
| assert_eq!(extractor.doctests().len(), 1); | |
| let doctest_code = extractor.doctests()[0].doctest_code.as_ref().unwrap(); | |
| assert!(doctest_code.wrapper.is_none()); | |
| } | |
| // Test that `ExtractedDocTest::doctest_code` is `None` if the doctest has syntax error. | |
| #[test] | |
| fn test_extracted_doctest_doctest_code_field() { | |
| let extractor = get_extracted_doctests("let x +="); | |
| assert_eq!(extractor.doctests().len(), 1); | |
| assert!(extractor.doctests()[0].doctest_code.is_none()); | |
| } | |
| // Test that `extracted::DocTest::wrapper` is `Some` if the doctest needs wrapping. | |
| #[test] | |
| fn test_extracted_doctest_wrapper_field_with_info() { | |
| let extractor = get_extracted_doctests("let x = 12;"); | |
| assert_eq!(extractor.doctests().len(), 1); | |
| let doctest_code = extractor.doctests()[0].doctest_code.as_ref().unwrap(); | |
| assert!(doctest_code.wrapper.is_some()); | |
| } |