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) -> 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(), ), &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()); }