diff --git a/Cargo.lock b/Cargo.lock index a1f8c40b..a78b5bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1353,6 +1353,7 @@ dependencies = [ "moonutil", "n2", "openssl", + "regex", "semver", "serde", "serde_json", @@ -1842,14 +1843,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -1863,13 +1864,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1880,9 +1881,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" diff --git a/Cargo.toml b/Cargo.toml index 7acf8fbc..df48a365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ ariadne = { version = "0.4.1", features = ["auto-color"] } clap_complete = { version = "4.5.4" } schemars = "0.8" nucleo-matcher = "0.3.1" +regex = { version = "1.10.3", default-features = false, features = ["std"] } [profile.release] debug = false diff --git a/crates/moon/Cargo.toml b/crates/moon/Cargo.toml index ced0b5a0..4bc7196f 100644 --- a/crates/moon/Cargo.toml +++ b/crates/moon/Cargo.toml @@ -51,6 +51,7 @@ tokio.workspace = true futures.workspace = true clap_complete.workspace = true indexmap.workspace = true +regex.workspace = true [target.'cfg(not(windows))'.dependencies] openssl = { version = "0.10.66", features = ["vendored"] } diff --git a/crates/moon/src/cli/test.rs b/crates/moon/src/cli/test.rs index 44ef44cd..349e4a1f 100644 --- a/crates/moon/src/cli/test.rs +++ b/crates/moon/src/cli/test.rs @@ -18,14 +18,18 @@ use anyhow::Context; use colored::Colorize; +use doc_test::DocTestExtractor; +use doc_test::PatchJSON; use moonbuild::dry_run; use moonbuild::entry; use mooncake::pkg::sync::auto_sync; +use moonutil::common::backend_filter; use moonutil::common::lower_surface_targets; use moonutil::common::FileLock; use moonutil::common::GeneratedTestDriver; use moonutil::common::MooncOpt; use moonutil::common::RunMode; +use moonutil::common::MOON_DOC_TEST_POSTFIX; use moonutil::common::{MoonbuildOpt, TestOpt}; use moonutil::dirs::mk_arch_mode_dir; use moonutil::dirs::PackageDirs; @@ -82,8 +86,12 @@ pub struct TestSubcommand { pub test_failure_json: bool, /// Path to the patch file - #[clap(long)] + #[clap(long, requires("package"), conflicts_with = "update")] pub patch_file: Option, + + /// Run doc test + #[clap(long = "doc", conflicts_with = "update")] + pub doc_test: bool, } pub fn run_test(cli: UniversalFlags, cmd: TestSubcommand) -> anyhow::Result { @@ -290,6 +298,37 @@ fn run_test_internal( continue; } + pkg.patch_file = cmd.patch_file.clone(); + + if cmd.doc_test { + let mbt_files = backend_filter( + &pkg.files, + moonc_opt.build_opt.debug_flag, + moonc_opt.build_opt.target_backend, + ); + + let mut doc_tests = vec![]; + let doc_test_extractor = DocTestExtractor::new(); + for file in mbt_files { + let doc_test_in_mbt_file = doc_test_extractor.extract_from_file(&file)?; + if !doc_test_in_mbt_file.is_empty() { + doc_tests.push(doc_test_in_mbt_file); + } + } + + let pj = PatchJSON::from_doc_tests(doc_tests); + let pj_path = pkg + .artifact + .with_file_name(format!("{}.json", MOON_DOC_TEST_POSTFIX)); + if !pj_path.parent().unwrap().exists() { + std::fs::create_dir_all(pj_path.parent().unwrap())?; + } + std::fs::write(&pj_path, serde_json::to_string_pretty(&pj)?) + .context(format!("failed to write {}", &pj_path.display()))?; + + pkg.doc_test_patch_file = Some(pj_path); + } + { // test driver file will be generated via `moon generate-test-driver` command let internal_generated_file = target_dir @@ -405,3 +444,123 @@ fn do_run_test( Ok(2) } } + +mod doc_test { + use regex::Regex; + use std::fs; + use std::path::Path; + + #[derive(Debug)] + pub struct DocTest { + pub content: String, + pub file_name: String, + pub line_number: usize, + pub line_count: usize, + } + + pub struct DocTestExtractor { + test_pattern: Regex, + } + + impl DocTestExtractor { + pub fn new() -> Self { + // \r\n for windows, \n for unix + let pattern = r#"///\s*```(?:\r?\n)((?:///.*(?:\r?\n))*?)///\s*```"#; + Self { + test_pattern: Regex::new(pattern).expect("Invalid regex pattern"), + } + } + + pub fn extract_from_file(&self, file_path: &Path) -> anyhow::Result> { + let content = fs::read_to_string(file_path)?; + + let mut tests = Vec::new(); + + for cap in self.test_pattern.captures_iter(&content) { + if let Some(test_match) = cap.get(0) { + let line_number = content[..test_match.start()] + .chars() + .filter(|&c| c == '\n') + .count() + + 1; + + if let Some(test_content) = cap.get(1) { + let processed_content = test_content + .as_str() + .lines() + .map(|line| { + format!(" {}", line.trim_start_matches("/// ")).to_string() + }) + .collect::>() + .join("\n"); + + let line_count = processed_content.split('\n').count(); + + tests.push(DocTest { + content: processed_content, + file_name: file_path.file_name().unwrap().to_str().unwrap().to_string(), + line_number, + line_count, + }); + } + } + } + + Ok(tests) + } + } + + #[derive(Debug, serde::Serialize)] + pub struct PatchJSON { + pub drops: Vec, + pub patches: Vec, + } + + #[derive(Debug, serde::Serialize)] + pub struct PatchItem { + pub name: String, + pub content: String, + } + + impl PatchJSON { + pub fn from_doc_tests(doc_tests: Vec>) -> Self { + let mut patches = vec![]; + for doc_tests_in_mbt_file in doc_tests.iter() { + let mut current_line = 1; + let mut content = String::new(); + for doc_test in doc_tests_in_mbt_file { + let test_name = format!( + "{} {} {} {}", + "doc_test", doc_test.file_name, doc_test.line_number, doc_test.line_count + ); + + let start_line_number = doc_test.line_number; + let empty_lines = "\n".repeat(start_line_number - current_line); + + content.push_str(&format!( + "{}test \"{}\" {{\n{}\n}}", + empty_lines, test_name, doc_test.content + )); + + // +1 for the } + current_line = start_line_number + doc_test.line_count + 1; + } + + patches.push(PatchItem { + // xxx.mbt -> xxx_doc_test.mbt + name: format!( + "{}{}.mbt", + doc_tests_in_mbt_file[0].file_name.trim_end_matches(".mbt"), + moonutil::common::MOON_DOC_TEST_POSTFIX, + ), + content, + }); + } + + PatchJSON { + drops: vec![], + patches, + } + } + } +} diff --git a/crates/moon/tests/test_cases/mod.rs b/crates/moon/tests/test_cases/mod.rs index dacca0b2..2f5eb047 100644 --- a/crates/moon/tests/test_cases/mod.rs +++ b/crates/moon/tests/test_cases/mod.rs @@ -7898,3 +7898,54 @@ fn test_add_mi_if_self_not_set_in_test_imports() { "#]], ); } + +#[test] +fn test_run_doc_test() { + let dir = TestDir::new("run_doc_test.in"); + + // `moon test --doc` run doc test only + check( + get_err_stdout(&dir, ["test", "--sort-input", "--doc"]), + expect![[r#" + doc_test 1 from hello.mbt + doc_test 2 from hello.mbt + doc_test 3 from hello.mbt + doc_test + doc_test 1 from greet.mbt + doc_test 2 from greet.mbt + doc_test 3 from greet.mbt + doc_test from greet.mbt + test username/hello/lib/hello.mbt::2 failed: FAILED: $ROOT/src/lib/hello.mbt:22:5-22:31 this is a failure + test username/hello/lib/greet.mbt::3 failed + expect test failed at $ROOT/src/lib/greet.mbt:33:5-33:18 + Diff: + ---- + 423 + ---- + + Total tests: 8, passed: 6, failed: 2. + "#]], + ); + + // --doc conflicts with --update + #[cfg(unix)] + check( + get_err_stderr(&dir, ["test", "--doc", "--update"]), + expect![[r#" + error: the argument '--doc' cannot be used with '--update' + + Usage: moon test --doc + + For more information, try '--help'. + "#]], + ); + + // `moon test` will not run doc test + check( + get_stdout(&dir, ["test", "--sort-input"]), + expect![[r#" + hello from hello_test.mbt + Total tests: 1, passed: 1, failed: 0. + "#]], + ); +} diff --git a/crates/moon/tests/test_cases/run_doc_test.in/README.md b/crates/moon/tests/test_cases/run_doc_test.in/README.md new file mode 100644 index 00000000..ae00983f --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/README.md @@ -0,0 +1 @@ +# username/hello \ No newline at end of file diff --git a/crates/moon/tests/test_cases/run_doc_test.in/moon.mod.json b/crates/moon/tests/test_cases/run_doc_test.in/moon.mod.json new file mode 100644 index 00000000..6c31689d --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/moon.mod.json @@ -0,0 +1,10 @@ +{ + "name": "username/hello", + "version": "0.1.0", + "readme": "README.md", + "repository": "", + "license": "Apache-2.0", + "keywords": [], + "description": "", + "source": "src" +} \ No newline at end of file diff --git a/crates/moon/tests/test_cases/run_doc_test.in/src/lib/greet.mbt b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/greet.mbt new file mode 100644 index 00000000..f6d3694e --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/greet.mbt @@ -0,0 +1,38 @@ +/// ``` +/// println("doc_test 1 from greet.mbt") +/// ``` +pub fn greet1() -> String { + "greet, world!" +} + + +/// ``` +/// println("doc_test 2 from greet.mbt") +/// +/// +/// +/// ``` +pub fn greet2() -> String { + "greet, world!" +} + +/// ``` +/// println("doc_test 3 from greet.mbt") +/// +/// +/// +/// ``` +pub fn greet3() -> String { + "greet, wor1ld!" +} + +/// ``` +/// println("doc_test from greet.mbt") +/// +/// +/// inspect!(423) +/// +/// ``` +pub fn greet() -> String { + "greet, wor1ld!" +} diff --git a/crates/moon/tests/test_cases/run_doc_test.in/src/lib/hello.mbt b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/hello.mbt new file mode 100644 index 00000000..c8b98188 --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/hello.mbt @@ -0,0 +1,40 @@ +/// ``` +/// println("doc_test 1 from hello.mbt") +/// ``` +pub fn hello1() -> String { + "Hello, world!" +} + + +/// ``` +/// println("doc_test 2 from hello.mbt") +/// +/// +/// +/// ``` +pub fn hello2() -> String { + "Hello, world!" +} + +/// ``` +/// println("doc_test 3 from hello.mbt") +/// +/// fail!("this is a failure") +/// +/// ``` +pub fn hello3() -> String { + "Hello, wor1ld!" +} + +/// ``` +/// println("doc_test") +/// +/// +/// +/// +/// +/// assert_true!(true) +/// ``` +pub fn hello() -> String { + "Hello, wor1ld!" +} diff --git a/crates/moon/tests/test_cases/run_doc_test.in/src/lib/hello_test.mbt b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/hello_test.mbt new file mode 100644 index 00000000..e2615592 --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/hello_test.mbt @@ -0,0 +1,3 @@ +test "hello" { + println("hello from hello_test.mbt") +} diff --git a/crates/moon/tests/test_cases/run_doc_test.in/src/lib/moon.pkg.json b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/moon.pkg.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/src/lib/moon.pkg.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/crates/moon/tests/test_cases/run_doc_test.in/src/main/main.mbt b/crates/moon/tests/test_cases/run_doc_test.in/src/main/main.mbt new file mode 100644 index 00000000..7171f862 --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/src/main/main.mbt @@ -0,0 +1,4 @@ +///| +fn main { + println(@lib.hello()) +} diff --git a/crates/moon/tests/test_cases/run_doc_test.in/src/main/moon.pkg.json b/crates/moon/tests/test_cases/run_doc_test.in/src/main/moon.pkg.json new file mode 100644 index 00000000..48783525 --- /dev/null +++ b/crates/moon/tests/test_cases/run_doc_test.in/src/main/moon.pkg.json @@ -0,0 +1,6 @@ +{ + "is-main": true, + "import": [ + "username/hello/lib" + ] +} \ No newline at end of file diff --git a/crates/moonbuild/src/entry.rs b/crates/moonbuild/src/entry.rs index 6a59265b..2e83598a 100644 --- a/crates/moonbuild/src/entry.rs +++ b/crates/moonbuild/src/entry.rs @@ -41,8 +41,8 @@ use crate::runtest::TestStatistics; use moonutil::common::{ DriverKind, FileLock, FileName, MoonbuildOpt, MooncGenTestInfo, MooncOpt, TargetBackend, - TestArtifacts, TestBlockIndex, TestName, BLACKBOX_TEST_PATCH, TEST_INFO_FILE, - WHITEBOX_TEST_PATCH, + TestArtifacts, TestBlockIndex, TestName, BLACKBOX_TEST_PATCH, MOON_DOC_TEST_POSTFIX, + TEST_INFO_FILE, WHITEBOX_TEST_PATCH, }; use std::sync::{Arc, Mutex}; @@ -483,7 +483,7 @@ fn convert_moonc_test_info( output_format: &str, filter_file: Option<&String>, sort_input: bool, - patch_file: Option<&PathBuf>, + patch_file: &Option, ) -> anyhow::Result> { let mut test_info_files = vec![]; for (files, driver_kind) in [ @@ -542,6 +542,15 @@ fn convert_moonc_test_info( continue; } } + // if doc test patch json is given, we should just running doc test + // so tests in other files should be filtered out + if let Some(patch_json) = patch_file { + if patch_json.to_str().unwrap().contains(MOON_DOC_TEST_POSTFIX) + && !filename.contains(MOON_DOC_TEST_POSTFIX) + { + continue; + } + } let test_type = if filename.ends_with("_test.mbt") { DriverKind::Blackbox.to_string() } else if filename.ends_with("_wbtest.mbt") { @@ -616,10 +625,7 @@ pub fn run_test( moonc_opt.link_opt.output_format.to_str(), filter_file, moonbuild_opt.sort_input, - moonbuild_opt - .test_opt - .as_ref() - .and_then(|it| it.patch_file.as_ref()), + &pkg.patch_file.clone().or(pkg.doc_test_patch_file.clone()), )?; for (artifact_path, file_test_info_map) in current_pkg_test_info { diff --git a/crates/moonbuild/src/gen/gen_runtest.rs b/crates/moonbuild/src/gen/gen_runtest.rs index b8b660ad..8f42b2fd 100644 --- a/crates/moonbuild/src/gen/gen_runtest.rs +++ b/crates/moonbuild/src/gen/gen_runtest.rs @@ -68,6 +68,7 @@ pub struct RuntestDriverItem { pub package_name: String, pub driver_file: String, pub files_may_contain_test_block: Vec, + pub patch_file: Option, } #[derive(Debug)] @@ -149,6 +150,7 @@ pub fn gen_package_test_driver( driver_file, files_may_contain_test_block, driver_kind: DriverKind::Internal, + patch_file: pkg.patch_file.clone(), }) } GeneratedTestDriver::BlackboxTest(it) => { @@ -164,6 +166,7 @@ pub fn gen_package_test_driver( driver_file, files_may_contain_test_block, driver_kind: DriverKind::Blackbox, + patch_file: pkg.patch_file.clone().or(pkg.doc_test_patch_file.clone()), }) } GeneratedTestDriver::WhiteboxTest(it) => { @@ -180,6 +183,7 @@ pub fn gen_package_test_driver( driver_file, files_may_contain_test_block, driver_kind: DriverKind::Whitebox, + patch_file: pkg.patch_file.clone(), }) } } @@ -812,7 +816,10 @@ pub fn gen_runtest( } } - if !pkg.test_files.is_empty() || blackbox_patch_file.is_some() { + if !pkg.test_files.is_empty() + || blackbox_patch_file.is_some() + || pkg.doc_test_patch_file.is_some() + { for item in pkg.generated_test_drivers.iter() { if let GeneratedTestDriver::BlackboxTest(_) = item { test_drivers.push(gen_package_test_driver(item, pkg)?); @@ -820,7 +827,9 @@ pub fn gen_runtest( m, pkg, moonc_opt, - blackbox_patch_file.clone(), + blackbox_patch_file + .clone() + .or(pkg.doc_test_patch_file.clone()), )?); link_items.push(gen_link_blackbox_test(m, pkg, moonc_opt)?); } @@ -1145,24 +1154,20 @@ fn gen_generate_test_driver_command( let mut build = Build::new(loc, ins, outs); - let patch_file = moonbuild_opt - .test_opt - .as_ref() - .and_then(|opt| opt.patch_file.as_ref()) - .and_then(|p| { - let filename = p.to_str().unwrap(); - match item.driver_kind { - DriverKind::Whitebox if filename.ends_with(WHITEBOX_TEST_PATCH) => Some(p), - DriverKind::Blackbox if filename.ends_with(BLACKBOX_TEST_PATCH) => Some(p), - DriverKind::Internal - if !filename.ends_with(WHITEBOX_TEST_PATCH) - && !filename.ends_with(BLACKBOX_TEST_PATCH) => - { - Some(p) - } - _ => None, + let patch_file = item.patch_file.as_ref().and_then(|p| { + let filename = p.to_str().unwrap(); + match item.driver_kind { + DriverKind::Whitebox if filename.ends_with(WHITEBOX_TEST_PATCH) => Some(p), + DriverKind::Blackbox if filename.ends_with(BLACKBOX_TEST_PATCH) => Some(p), + DriverKind::Internal + if !filename.ends_with(WHITEBOX_TEST_PATCH) + && !filename.ends_with(BLACKBOX_TEST_PATCH) => + { + Some(p) } - }); + _ => None, + } + }); let command = CommandBuilder::new( &std::env::current_exe() diff --git a/crates/moonbuild/src/runtest.rs b/crates/moonbuild/src/runtest.rs index d8ebe79e..565c91e3 100644 --- a/crates/moonbuild/src/runtest.rs +++ b/crates/moonbuild/src/runtest.rs @@ -184,9 +184,20 @@ async fn run( if s.is_empty() { continue; } - let a = serde_json_lenient::from_str(s.trim()) + let mut ts: TestStatistics = serde_json_lenient::from_str(s.trim()) .context(format!("failed to parse test summary: {}", s))?; - test_statistics.push(a); + + // this is a hack for doc test, make the doc test patch filename be the original file name + if ts.test_name.starts_with("doc_test") { + let temp = ts.test_name.split(" ").collect::>(); + let original_file_name = temp[1].to_string(); + ts.filename = original_file_name.clone(); + ts.message = ts + .message + .replace(moonutil::common::MOON_DOC_TEST_POSTFIX, ""); + } + + test_statistics.push(ts); } for mut test_statistic in test_statistics { diff --git a/crates/moonutil/src/common.rs b/crates/moonutil/src/common.rs index 232cfd18..e46d1b9d 100644 --- a/crates/moonutil/src/common.rs +++ b/crates/moonutil/src/common.rs @@ -56,6 +56,8 @@ pub const TEST_INFO_FILE: &str = "test_info.json"; pub const WHITEBOX_TEST_PATCH: &str = "_wbtest.json"; pub const BLACKBOX_TEST_PATCH: &str = "_test.json"; +pub const MOON_DOC_TEST_POSTFIX: &str = "__moonbit_internal_doc_test"; + #[derive(Debug, thiserror::Error)] pub enum SourceError { #[error("`source` should not contain invalid chars `{0:?}`")] diff --git a/crates/moonutil/src/package.rs b/crates/moonutil/src/package.rs index 081eb569..2ca13c95 100644 --- a/crates/moonutil/src/package.rs +++ b/crates/moonutil/src/package.rs @@ -69,6 +69,8 @@ pub struct Package { pub patch_file: Option, pub no_mi: bool, + + pub doc_test_patch_file: Option, } impl Package { diff --git a/crates/moonutil/src/render.rs b/crates/moonutil/src/render.rs index a16af257..d5085113 100644 --- a/crates/moonutil/src/render.rs +++ b/crates/moonutil/src/render.rs @@ -19,7 +19,7 @@ use ariadne::Fmt; use serde::{Deserialize, Serialize}; -use crate::common::line_col_to_byte_idx; +use crate::common::{line_col_to_byte_idx, MOON_DOC_TEST_POSTFIX}; #[derive(Debug, Serialize, Deserialize)] pub struct MooncDiagnostic { @@ -75,7 +75,12 @@ impl MooncDiagnostic { .fg(color) ); } else { - let source_file_path = &diagnostic.location.path; + let source_file_path = + &if diagnostic.location.path.contains(MOON_DOC_TEST_POSTFIX) { + diagnostic.location.path.replace(MOON_DOC_TEST_POSTFIX, "") + } else { + diagnostic.location.path.clone() + }; let source_file = match std::fs::read_to_string(source_file_path) { Ok(content) => content, Err(_) => { diff --git a/crates/moonutil/src/scan.rs b/crates/moonutil/src/scan.rs index ca48db88..eebfa300 100644 --- a/crates/moonutil/src/scan.rs +++ b/crates/moonutil/src/scan.rs @@ -357,6 +357,7 @@ fn scan_one_package( pre_build: pkg.pre_build, patch_file: None, no_mi: false, + doc_test_patch_file: None, }; if doc_mode { // -o diff --git a/docs/manual-zh/src/commands.md b/docs/manual-zh/src/commands.md index 381218df..c2cea4dc 100644 --- a/docs/manual-zh/src/commands.md +++ b/docs/manual-zh/src/commands.md @@ -213,6 +213,7 @@ Test the current package * `--no-parallelize` — Run the tests in a target backend sequentially * `--test-failure-json` — Print failure message in JSON format * `--patch-file ` — Path to the patch file +* `--doc` — Run doc test diff --git a/docs/manual/src/commands.md b/docs/manual/src/commands.md index 381218df..c2cea4dc 100644 --- a/docs/manual/src/commands.md +++ b/docs/manual/src/commands.md @@ -213,6 +213,7 @@ Test the current package * `--no-parallelize` — Run the tests in a target backend sequentially * `--test-failure-json` — Print failure message in JSON format * `--patch-file ` — Path to the patch file +* `--doc` — Run doc test