From ffc8240436607783fde298446b9f5fbaecb09c35 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Tue, 3 Dec 2024 15:33:55 +0800 Subject: [PATCH 1/6] internal: move doc_test mod into moonutil --- Cargo.lock | 2 +- crates/moon/Cargo.toml | 1 - crates/moon/src/cli/test.rs | 152 +------------------------------- crates/moonutil/Cargo.toml | 1 + crates/moonutil/src/doc_test.rs | 150 +++++++++++++++++++++++++++++++ crates/moonutil/src/lib.rs | 1 + 6 files changed, 155 insertions(+), 152 deletions(-) create mode 100644 crates/moonutil/src/doc_test.rs diff --git a/Cargo.lock b/Cargo.lock index a78b5bb3..d4dcdbdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1353,7 +1353,6 @@ dependencies = [ "moonutil", "n2", "openssl", - "regex", "semver", "serde", "serde_json", @@ -1478,6 +1477,7 @@ dependencies = [ "log", "nucleo-matcher", "petgraph", + "regex", "schemars", "semver", "serde", diff --git a/crates/moon/Cargo.toml b/crates/moon/Cargo.toml index 4bc7196f..ced0b5a0 100644 --- a/crates/moon/Cargo.toml +++ b/crates/moon/Cargo.toml @@ -51,7 +51,6 @@ 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 32f008af..bcc7bcf7 100644 --- a/crates/moon/src/cli/test.rs +++ b/crates/moon/src/cli/test.rs @@ -18,18 +18,14 @@ 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; @@ -90,7 +86,7 @@ pub struct TestSubcommand { pub patch_file: Option, /// Run doc test - #[clap(long = "doc", conflicts_with = "update")] + #[clap(long = "doc")] pub doc_test: bool, } @@ -302,31 +298,7 @@ fn run_test_internal( 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()))?; - + let pj_path = moonutil::doc_test::gen_doc_test_patch(&pkg, &moonc_opt)?; pkg.doc_test_patch_file = Some(pj_path); } @@ -434,123 +406,3 @@ 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/moonutil/Cargo.toml b/crates/moonutil/Cargo.toml index 1e28a217..6f5d2c12 100644 --- a/crates/moonutil/Cargo.toml +++ b/crates/moonutil/Cargo.toml @@ -47,6 +47,7 @@ schemars.workspace = true line-index.workspace = true nucleo-matcher.workspace = true which.workspace = true +regex.workspace = true [dev-dependencies] expect-test.workspace = true diff --git a/crates/moonutil/src/doc_test.rs b/crates/moonutil/src/doc_test.rs new file mode 100644 index 00000000..9b30441f --- /dev/null +++ b/crates/moonutil/src/doc_test.rs @@ -0,0 +1,150 @@ +use anyhow::Context; +use regex::Regex; +use std::fs; +use std::path::{Path, PathBuf}; + +use crate::common::{backend_filter, MooncOpt}; +use crate::package::Package; + +#[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 + )); + + std::fs::write(format!("__doc_test_{}.mbt", doc_test.file_name), &content).unwrap(); + + // +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"), + crate::common::MOON_DOC_TEST_POSTFIX, + ), + content, + }); + } + + PatchJSON { + drops: vec![], + patches, + } + } +} + +pub fn gen_doc_test_patch(pkg: &Package, moonc_opt: &MooncOpt) -> anyhow::Result { + 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", crate::common::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_lenient::to_string_pretty(&pj)?) + .context(format!("failed to write {}", &pj_path.display()))?; + + Ok(pj_path) +} diff --git a/crates/moonutil/src/lib.rs b/crates/moonutil/src/lib.rs index e9dc494d..8b0d2427 100644 --- a/crates/moonutil/src/lib.rs +++ b/crates/moonutil/src/lib.rs @@ -35,3 +35,4 @@ pub mod path; pub mod render; pub mod scan; pub mod version; +pub mod doc_test; From 847290b3a585aca65772d6031115837832f18352 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Tue, 3 Dec 2024 15:35:37 +0800 Subject: [PATCH 2/6] fix: doc test patch json file should be input in gen_generate_test_driver build graph --- crates/moonbuild/src/gen/gen_runtest.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/moonbuild/src/gen/gen_runtest.rs b/crates/moonbuild/src/gen/gen_runtest.rs index 8e5a288f..96f49f0c 100644 --- a/crates/moonbuild/src/gen/gen_runtest.rs +++ b/crates/moonbuild/src/gen/gen_runtest.rs @@ -158,11 +158,14 @@ pub fn gen_package_test_driver( GeneratedTestDriver::BlackboxTest(it) => { let package_name = pkg.full_name(); let driver_file = it.display().to_string(); - let files_may_contain_test_block = pkg + let mut files_may_contain_test_block: Vec = pkg .test_files .iter() .map(|(f, _)| f.display().to_string()) .collect(); + if let Some(doc_test_patch_file) = pkg.doc_test_patch_file.clone() { + files_may_contain_test_block.push(doc_test_patch_file.display().to_string()); + } Ok(RuntestDriverItem { package_name, driver_file, From d50d06a8c2b8a6b3966e2edee15058ced666a9f4 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Tue, 3 Dec 2024 15:47:38 +0800 Subject: [PATCH 3/6] feat: support update doc test --- crates/moonbuild/src/entry.rs | 25 ++++++++++++++++++++++++- crates/moonbuild/src/runtest.rs | 18 ++++++++++-------- crates/moonutil/src/lib.rs | 2 +- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/moonbuild/src/entry.rs b/crates/moonbuild/src/entry.rs index 2e83598a..f1d91a2e 100644 --- a/crates/moonbuild/src/entry.rs +++ b/crates/moonbuild/src/entry.rs @@ -987,9 +987,17 @@ async fn handle_test_result( // here need to rerun the test to get the new error message // since the previous apply expect may add or delete some line, which make the error message out of date let index = origin_err.index.clone().parse::().unwrap(); + let filename = if origin_err.is_doc_test { + origin_err + .filename + .replace(".mbt", &format!("{}.mbt", MOON_DOC_TEST_POSTFIX)) + } else { + origin_err.filename.clone() + }; + let test_args = TestArgs { package: origin_err.package.clone(), - file_and_index: vec![(origin_err.filename.clone(), index..(index + 1))], + file_and_index: vec![(filename, index..(index + 1))], }; let rerun = execute_test( moonc_opt.build_opt.target_backend, @@ -1012,9 +1020,17 @@ async fn handle_test_result( Err(TestFailedStatus::ExpectTestFailed(cur_err)) => &[cur_err.message], _ => &[origin_err.message.clone()], }; + if let Err(e) = crate::expect::apply_expect(update_msg) { eprintln!("{}: {:?}", "apply expect failed".red().bold(), e); } + // if is doc test, after apply_expect, we need to update the doc test patch file + if origin_err.is_doc_test { + moonutil::doc_test::gen_doc_test_patch( + module.get_package_by_name(&origin_err.package), + moonc_opt, + )?; + } // recompile after apply expect { @@ -1050,6 +1066,13 @@ async fn handle_test_result( if let Err(e) = crate::expect::apply_expect(&[etf.message.clone()]) { eprintln!("{}: {:?}", "failed".red().bold(), e); } + // if is doc test, after apply_expect, we need to update the doc test patch file + if origin_err.is_doc_test { + moonutil::doc_test::gen_doc_test_patch( + module.get_package_by_name(&origin_err.package), + moonc_opt, + )?; + } // recompile after apply expect { diff --git a/crates/moonbuild/src/runtest.rs b/crates/moonbuild/src/runtest.rs index 565c91e3..a1824e67 100644 --- a/crates/moonbuild/src/runtest.rs +++ b/crates/moonbuild/src/runtest.rs @@ -24,7 +24,7 @@ use super::gen; use anyhow::{bail, Context}; use moonutil::common::{ MoonbuildOpt, MooncOpt, MOON_COVERAGE_DELIMITER_BEGIN, MOON_COVERAGE_DELIMITER_END, - MOON_TEST_DELIMITER_BEGIN, MOON_TEST_DELIMITER_END, + MOON_DOC_TEST_POSTFIX, MOON_TEST_DELIMITER_BEGIN, MOON_TEST_DELIMITER_END, }; use moonutil::module::ModuleDB; use n2::load::State; @@ -49,6 +49,9 @@ pub struct TestStatistics { pub index: String, pub test_name: String, pub message: String, + #[serde(skip_serializing)] + #[serde(default)] + pub is_doc_test: bool, } impl std::fmt::Display for TestStatistics { @@ -187,14 +190,13 @@ async fn run( let mut ts: TestStatistics = serde_json_lenient::from_str(s.trim()) .context(format!("failed to parse test summary: {}", s))?; + if ts.filename.contains(MOON_DOC_TEST_POSTFIX) { + ts.is_doc_test = true; + } // 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, ""); + if ts.is_doc_test { + ts.filename = ts.filename.replace(MOON_DOC_TEST_POSTFIX, ""); + ts.message = ts.message.replace(MOON_DOC_TEST_POSTFIX, ""); } test_statistics.push(ts); diff --git a/crates/moonutil/src/lib.rs b/crates/moonutil/src/lib.rs index 8b0d2427..7cd20bc2 100644 --- a/crates/moonutil/src/lib.rs +++ b/crates/moonutil/src/lib.rs @@ -23,6 +23,7 @@ pub mod common; pub mod cond_expr; pub mod dependency; pub mod dirs; +pub mod doc_test; pub mod fuzzy_match; pub mod git; pub mod graph; @@ -35,4 +36,3 @@ pub mod path; pub mod render; pub mod scan; pub mod version; -pub mod doc_test; From c954757deed1fce6d5806275fdcea0d54460a4c5 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Tue, 3 Dec 2024 15:48:13 +0800 Subject: [PATCH 4/6] test: add test case for update doc test --- crates/moon/tests/test_cases/mod.rs | 31 ++++++++++++++----- .../run_doc_test.in/src/lib/hello.mbt | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/crates/moon/tests/test_cases/mod.rs b/crates/moon/tests/test_cases/mod.rs index f82c5709..b6ed4fdb 100644 --- a/crates/moon/tests/test_cases/mod.rs +++ b/crates/moon/tests/test_cases/mod.rs @@ -7978,6 +7978,13 @@ fn test_run_doc_test() { doc_test 2 from greet.mbt doc_test 3 from greet.mbt doc_test from greet.mbt + test username/hello/lib/hello.mbt::1 failed + expect test failed at $ROOT/src/lib/hello.mbt:12:5-12:19 + Diff: + ---- + 1256 + ---- + 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 @@ -7986,20 +7993,30 @@ fn test_run_doc_test() { 423 ---- - Total tests: 8, passed: 6, failed: 2. + Total tests: 8, passed: 5, failed: 3. "#]], ); - // --doc conflicts with --update - #[cfg(unix)] check( - get_err_stderr(&dir, ["test", "--doc", "--update"]), + get_err_stdout(&dir, ["test", "--sort-input", "--doc", "--update"]), expect![[r#" - error: the argument '--doc' cannot be used with '--update' + 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 - Usage: moon test --doc + Auto updating expect tests and retesting ... - For more information, try '--help'. + doc_test 2 from hello.mbt + doc_test 2 from hello.mbt + test username/hello/lib/hello.mbt::2 failed: FAILED: $ROOT/src/lib/hello.mbt:22:5-22:31 this is a failure + doc_test from greet.mbt + doc_test from greet.mbt + Total tests: 8, passed: 7, failed: 1. "#]], ); 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 index c8b98188..89e501f4 100644 --- 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 @@ -9,7 +9,7 @@ pub fn hello1() -> String { /// ``` /// println("doc_test 2 from hello.mbt") /// -/// +/// inspect!(1256) /// /// ``` pub fn hello2() -> String { From b656124a7d40b3118855d7af40a296e30d68ca2d Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Wed, 4 Dec 2024 10:58:21 +0800 Subject: [PATCH 5/6] feat: support ``` mbt | moonbit for doc test --- .../tests/test_cases/run_doc_test.in/src/lib/greet.mbt | 2 +- .../tests/test_cases/run_doc_test.in/src/lib/hello.mbt | 2 +- crates/moonutil/src/doc_test.rs | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) 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 index f6d3694e..8ea288ba 100644 --- 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 @@ -16,7 +16,7 @@ pub fn greet2() -> String { "greet, world!" } -/// ``` +/// ```moonbit /// println("doc_test 3 from greet.mbt") /// /// 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 index 89e501f4..45584e86 100644 --- 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 @@ -6,7 +6,7 @@ pub fn hello1() -> String { } -/// ``` +/// ```mbt /// println("doc_test 2 from hello.mbt") /// /// inspect!(1256) diff --git a/crates/moonutil/src/doc_test.rs b/crates/moonutil/src/doc_test.rs index 9b30441f..0880db30 100644 --- a/crates/moonutil/src/doc_test.rs +++ b/crates/moonutil/src/doc_test.rs @@ -21,7 +21,7 @@ pub struct DocTestExtractor { impl DocTestExtractor { pub fn new() -> Self { // \r\n for windows, \n for unix - let pattern = r#"///\s*```(?:\r?\n)((?:///.*(?:\r?\n))*?)///\s*```"#; + let pattern = r#"///\s*```(?:mbt|moonbit)?\s*(?:\r?\n)((?:///.*(?:\r?\n))*?)///\s*```"#; Self { test_pattern: Regex::new(pattern).expect("Invalid regex pattern"), } @@ -96,10 +96,11 @@ impl PatchJSON { empty_lines, test_name, doc_test.content )); - std::fs::write(format!("__doc_test_{}.mbt", doc_test.file_name), &content).unwrap(); - // +1 for the } current_line = start_line_number + doc_test.line_count + 1; + + // this is for debug + // std::fs::write(format!("__doc_test_{}.mbt", doc_test.file_name), &content).unwrap(); } patches.push(PatchItem { From d0522bf2fc597117bb0f360a79d5e488befdc188 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Wed, 4 Dec 2024 12:27:04 +0800 Subject: [PATCH 6/6] internal: support test {} inside doc test --- crates/moon/src/cli/test.rs | 2 +- crates/moon/tests/test_cases/mod.rs | 32 ++++++--- .../run_doc_test.in/src/lib/greet.mbt | 62 ++++++++++++++-- crates/moonutil/src/doc_test.rs | 71 +++++++++++++++---- 4 files changed, 137 insertions(+), 30 deletions(-) diff --git a/crates/moon/src/cli/test.rs b/crates/moon/src/cli/test.rs index bcc7bcf7..8005ca05 100644 --- a/crates/moon/src/cli/test.rs +++ b/crates/moon/src/cli/test.rs @@ -298,7 +298,7 @@ fn run_test_internal( pkg.patch_file = cmd.patch_file.clone(); if cmd.doc_test { - let pj_path = moonutil::doc_test::gen_doc_test_patch(&pkg, &moonc_opt)?; + let pj_path = moonutil::doc_test::gen_doc_test_patch(pkg, &moonc_opt)?; pkg.doc_test_patch_file = Some(pj_path); } diff --git a/crates/moon/tests/test_cases/mod.rs b/crates/moon/tests/test_cases/mod.rs index b6ed4fdb..385440d7 100644 --- a/crates/moon/tests/test_cases/mod.rs +++ b/crates/moon/tests/test_cases/mod.rs @@ -7975,9 +7975,13 @@ fn test_run_doc_test() { doc_test 3 from hello.mbt doc_test doc_test 1 from greet.mbt - doc_test 2 from greet.mbt + test block 1 + test block 2 + test block 3 doc_test 3 from greet.mbt - doc_test from greet.mbt + test block 4 + test block 5 + doc_test 5 from greet.mbt test username/hello/lib/hello.mbt::1 failed expect test failed at $ROOT/src/lib/hello.mbt:12:5-12:19 Diff: @@ -7986,14 +7990,15 @@ fn test_run_doc_test() { ---- 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 + test username/hello/lib/greet.mbt::2 failed + expect test failed at $ROOT/src/lib/greet.mbt:22:7-22:21 Diff: ---- - 423 + 1256 ---- - Total tests: 8, passed: 5, failed: 3. + test username/hello/lib/greet.mbt::3 failed: FAILED: $ROOT/src/lib/greet.mbt:31:7-31:31 another failure + Total tests: 12, passed: 8, failed: 4. "#]], ); @@ -8005,18 +8010,23 @@ fn test_run_doc_test() { doc_test 3 from hello.mbt doc_test doc_test 1 from greet.mbt - doc_test 2 from greet.mbt + test block 1 + test block 2 + test block 3 doc_test 3 from greet.mbt - doc_test from greet.mbt + test block 4 + test block 5 + doc_test 5 from greet.mbt Auto updating expect tests and retesting ... doc_test 2 from hello.mbt doc_test 2 from hello.mbt test username/hello/lib/hello.mbt::2 failed: FAILED: $ROOT/src/lib/hello.mbt:22:5-22:31 this is a failure - doc_test from greet.mbt - doc_test from greet.mbt - Total tests: 8, passed: 7, failed: 1. + test block 2 + test block 2 + test username/hello/lib/greet.mbt::3 failed: FAILED: $ROOT/src/lib/greet.mbt:31:7-31:31 another failure + Total tests: 12, passed: 10, failed: 2. "#]], ); 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 index 8ea288ba..492a42c5 100644 --- 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 @@ -6,10 +6,30 @@ pub fn greet1() -> String { } -/// ``` -/// println("doc_test 2 from greet.mbt") +/// ```mbt +/// +/// +/// +/// test { +/// println("test block 1") +/// } +/// +/// +/// test { +/// +/// println("test block 2") +/// +/// inspect!(1256) +/// +/// /// +/// } /// +/// test { +/// println("test block 3") +/// +/// fail!("another failure") +/// } /// /// ``` pub fn greet2() -> String { @@ -17,6 +37,7 @@ pub fn greet2() -> String { } /// ```moonbit +/// /// println("doc_test 3 from greet.mbt") /// /// @@ -26,13 +47,44 @@ pub fn greet3() -> String { "greet, wor1ld!" } -/// ``` -/// println("doc_test from greet.mbt") +/// ```moonbit +/// test { +/// /// /// -/// inspect!(423) +/// println("test block 4") +/// +/// } +/// /// /// ``` pub fn greet() -> String { "greet, wor1ld!" } + + +/// ``` +/// test { +/// +/// println("test block 5") +/// +/// +/// } +/// +/// +/// +/// ``` +pub fn greet4() -> String { + "greet, wor1ld!" +} + + +/// ``` +/// println("doc_test 5 from greet.mbt") +/// +/// +/// +/// ``` +pub fn greet5() -> String { + "greet, wor1ld!" +} \ No newline at end of file diff --git a/crates/moonutil/src/doc_test.rs b/crates/moonutil/src/doc_test.rs index 0880db30..0d079b6d 100644 --- a/crates/moonutil/src/doc_test.rs +++ b/crates/moonutil/src/doc_test.rs @@ -1,3 +1,21 @@ +// moon: The build system and package manager for MoonBit. +// Copyright (C) 2024 International Digital Economy Academy +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// For inquiries, you can contact us via e-mail at jichuruanjian@idea.edu.cn. + use anyhow::Context; use regex::Regex; use std::fs; @@ -18,6 +36,12 @@ pub struct DocTestExtractor { test_pattern: Regex, } +impl Default for DocTestExtractor { + fn default() -> Self { + Self::new() + } +} + impl DocTestExtractor { pub fn new() -> Self { // \r\n for windows, \n for unix @@ -41,17 +65,10 @@ impl DocTestExtractor { + 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(); + let line_count = test_content.as_str().lines().count(); tests.push(DocTest { - content: processed_content, + content: test_content.as_str().to_string(), file_name: file_path.file_name().unwrap().to_str().unwrap().to_string(), line_number, line_count, @@ -88,13 +105,41 @@ impl PatchJSON { "doc_test", doc_test.file_name, doc_test.line_number, doc_test.line_count ); + let already_wrapped = doc_test + .content + .lines() + .any(|line| line.replace("///", "").trim_start().starts_with("test")); + + let processed_content = doc_test + .content + .as_str() + .lines() + .map(|line| { + if already_wrapped { + let remove_slash = line.replace("///", "").trim_start().to_string(); + if remove_slash.starts_with("test") || remove_slash.starts_with("}") { + remove_slash + } else { + line.to_string().replace("///", " ") + } + } else { + format!(" {}", line.trim_start_matches("///")).to_string() + } + }) + .collect::>() + .join("\n"); + 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 - )); + if already_wrapped { + content.push_str(&format!("\n{}{}\n", empty_lines, processed_content)); + } else { + content.push_str(&format!( + "{}test \"{}\" {{\n{}\n}}", + empty_lines, test_name, processed_content + )); + } // +1 for the } current_line = start_line_number + doc_test.line_count + 1;