From aa4883e038af61a5f0840e64524fbef289345b18 Mon Sep 17 00:00:00 2001 From: kbwo Date: Sat, 30 Nov 2024 18:19:28 +0900 Subject: [PATCH] feat(adapter): add diagnostics to show which test failed --- Cargo.lock | 55 ++++++++++---------- crates/adapter/Cargo.toml | 4 +- crates/adapter/src/runner/cargo_nextest.rs | 58 +++++++++++++-------- crates/adapter/src/runner/cargo_test.rs | 57 ++++++++++++-------- crates/adapter/src/runner/util.rs | 60 +++++++++++++++++++--- demo/rust/src/lib.rs | 9 ++++ 6 files changed, 167 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4783bd6..8b06e43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,31 +721,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "testing-language-server" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1a48aec4305834e4f80ad28dfe5bea8c98c0bb215a092eb2af15c4ac86efb7" -dependencies = [ - "anyhow", - "chrono", - "clap", - "dirs", - "glob", - "globwalk", - "lsp-types", - "once_cell", - "regex", - "serde", - "serde_json", - "strum", - "thiserror", - "tracing", - "tracing-appender", - "tracing-subscriber", - "tree-sitter-php", -] - [[package]] name = "testing-language-server" version = "0.1.7" @@ -770,9 +745,35 @@ dependencies = [ "tree-sitter-php", ] +[[package]] +name = "testing-language-server" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fae6ce9cb290e80814d1796e4207a0a0dc21c216807136d48b68c0f27be29b9" +dependencies = [ + "anyhow", + "chrono", + "clap", + "dirs", + "glob", + "globwalk", + "lsp-types", + "once_cell", + "regex", + "serde", + "serde_json", + "strum", + "thiserror", + "toml", + "tracing", + "tracing-appender", + "tracing-subscriber", + "tree-sitter-php", +] + [[package]] name = "testing-ls-adapter" -version = "0.0.10" +version = "0.0.11" dependencies = [ "anyhow", "clap", @@ -782,7 +783,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "testing-language-server 0.0.11", + "testing-language-server 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", "tracing-appender", "tracing-subscriber", diff --git a/crates/adapter/Cargo.toml b/crates/adapter/Cargo.toml index 50162a7..5148bd1 100644 --- a/crates/adapter/Cargo.toml +++ b/crates/adapter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "testing-ls-adapter" -version = "0.0.10" +version = "0.0.11" edition = "2021" description = "testing-language-server adapter" license = "MIT" @@ -8,7 +8,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -testing-language-server = "0.0.11" +testing-language-server = "0.1.7" lsp-types = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } diff --git a/crates/adapter/src/runner/cargo_nextest.rs b/crates/adapter/src/runner/cargo_nextest.rs index fea1680..202a2b9 100644 --- a/crates/adapter/src/runner/cargo_nextest.rs +++ b/crates/adapter/src/runner/cargo_nextest.rs @@ -8,6 +8,7 @@ use testing_language_server::spec::RunFileTestResult; use testing_language_server::spec::DiscoverResult; use testing_language_server::spec::DiscoverResultItem; +use testing_language_server::spec::TestItem; use crate::model::Runner; @@ -16,14 +17,6 @@ use super::util::discover_rust_tests; use super::util::parse_cargo_diagnostics; use super::util::write_result_log; -fn parse_diagnostics( - contents: &str, - workspace_root: PathBuf, - file_paths: &[String], -) -> RunFileTestResult { - parse_cargo_diagnostics(contents, workspace_root, file_paths) -} - fn detect_workspaces(file_paths: &[String]) -> DetectWorkspaceResult { detect_workspaces_from_file_list(file_paths, &["Cargo.toml".to_string()]) } @@ -54,19 +47,16 @@ impl Runner for CargoNextestRunner { args: testing_language_server::spec::RunFileTestArgs, ) -> Result<(), LSError> { let file_paths = args.file_paths; - let tests = file_paths + let discovered_tests: Vec = file_paths .iter() - .map(|path| { - discover_rust_tests(path).map(|test_items| { - test_items - .into_iter() - .map(|item| item.id) - .collect::>() - }) - }) + .map(|path| discover_rust_tests(path)) .filter_map(Result::ok) .flatten() .collect::>(); + let test_ids = discovered_tests + .iter() + .map(|item| item.id.clone()) + .collect::>(); let workspace_root = args.workspace; let test_result = std::process::Command::new("cargo") .current_dir(&workspace_root) @@ -76,7 +66,7 @@ impl Runner for CargoNextestRunner { .arg("--no-fail-fast") .args(args.extra) .arg("--") - .args(tests) + .args(&test_ids) .output() .unwrap(); let output = test_result; @@ -91,10 +81,11 @@ impl Runner for CargoNextestRunner { return Err(LSError::Adapter(String::from_utf8(stderr).unwrap())); } let test_result = String::from_utf8(stderr)?; - let diagnostics: RunFileTestResult = parse_diagnostics( + let diagnostics: RunFileTestResult = parse_cargo_diagnostics( &test_result, PathBuf::from_str(&workspace_root).unwrap(), &file_paths, + &discovered_tests, ); send_stdout(&diagnostics)?; Ok(()) @@ -115,7 +106,7 @@ impl Runner for CargoNextestRunner { #[cfg(test)] mod tests { use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; - use testing_language_server::spec::RunFileTestResultItem; + use testing_language_server::spec::{RunFileTestResultItem, TestItem}; use crate::runner::util::MAX_CHAR_LENGTH; @@ -142,10 +133,35 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace "#; let file_paths = vec!["/home/example/projects/rocks-lib/src/rocks/dependency.rs".to_string()]; - let diagnostics: RunFileTestResult = parse_diagnostics( + let test_items: Vec = vec![TestItem { + id: "rocks::dependency::tests::parse_dependency".to_string(), + name: "rocks::dependency::tests::parse_dependency".to_string(), + start_position: Range { + start: Position { + line: 85, + character: 63, + }, + end: Position { + line: 85, + character: MAX_CHAR_LENGTH, + }, + }, + end_position: Range { + start: Position { + line: 85, + character: 63, + }, + end: Position { + line: 85, + character: MAX_CHAR_LENGTH, + }, + }, + }]; + let diagnostics: RunFileTestResult = parse_cargo_diagnostics( fixture, PathBuf::from_str("/home/example/projects").unwrap(), &file_paths, + &test_items, ); let message = r#"called `Result::unwrap()` on an `Err` value: unexpected end of input while parsing min or version number Location: diff --git a/crates/adapter/src/runner/cargo_test.rs b/crates/adapter/src/runner/cargo_test.rs index e391911..bea62bd 100644 --- a/crates/adapter/src/runner/cargo_test.rs +++ b/crates/adapter/src/runner/cargo_test.rs @@ -8,6 +8,7 @@ use testing_language_server::spec::RunFileTestResult; use testing_language_server::spec::DiscoverResult; use testing_language_server::spec::DiscoverResultItem; +use testing_language_server::spec::TestItem; use crate::model::Runner; @@ -16,14 +17,6 @@ use super::util::discover_rust_tests; use super::util::parse_cargo_diagnostics; use super::util::write_result_log; -fn parse_diagnostics( - contents: &str, - workspace_root: PathBuf, - file_paths: &[String], -) -> RunFileTestResult { - parse_cargo_diagnostics(contents, workspace_root, file_paths) -} - fn detect_workspaces(file_paths: &[String]) -> DetectWorkspaceResult { detect_workspaces_from_file_list(file_paths, &["Cargo.toml".to_string()]) } @@ -54,26 +47,23 @@ impl Runner for CargoTestRunner { args: testing_language_server::spec::RunFileTestArgs, ) -> Result<(), LSError> { let file_paths = args.file_paths; - let tests = file_paths + let discovered_tests: Vec = file_paths .iter() - .map(|path| { - discover_rust_tests(path).map(|test_items| { - test_items - .into_iter() - .map(|item| item.id) - .collect::>() - }) - }) + .map(|path| discover_rust_tests(path)) .filter_map(Result::ok) .flatten() .collect::>(); + let test_ids = discovered_tests + .iter() + .map(|item| item.id.clone()) + .collect::>(); let workspace_root = args.workspace; let test_result = std::process::Command::new("cargo") .current_dir(&workspace_root) .arg("test") .args(args.extra) .arg("--") - .args(tests) + .args(&test_ids) .output() .unwrap(); let output = test_result; @@ -85,10 +75,12 @@ impl Runner for CargoTestRunner { // When `--nocapture` option is set, stderr has some important information // to parse test result let test_result = String::from_utf8(stderr)? + &String::from_utf8(stdout)?; - let diagnostics: RunFileTestResult = parse_diagnostics( + + let diagnostics: RunFileTestResult = parse_cargo_diagnostics( &test_result, PathBuf::from_str(&workspace_root).unwrap(), &file_paths, + &discovered_tests, ); send_stdout(&diagnostics)?; Ok(()) @@ -136,10 +128,35 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace "#; let file_paths = vec!["/home/example/projects/rocks-lib/src/rocks/dependency.rs".to_string()]; - let diagnostics: RunFileTestResult = parse_diagnostics( + let test_items: Vec = vec![TestItem { + id: "rocks::dependency::tests::parse_dependency".to_string(), + name: "rocks::dependency::tests::parse_dependency".to_string(), + start_position: Range { + start: Position { + line: 85, + character: 63, + }, + end: Position { + line: 85, + character: MAX_CHAR_LENGTH, + }, + }, + end_position: Range { + start: Position { + line: 85, + character: 63, + }, + end: Position { + line: 85, + character: MAX_CHAR_LENGTH, + }, + }, + }]; + let diagnostics: RunFileTestResult = parse_cargo_diagnostics( fixture, PathBuf::from_str("/home/example/projects").unwrap(), &file_paths, + &test_items, ); let message = r#"called `Result::unwrap()` on an `Err` value: unexpected end of input while parsing min or version number Location: diff --git a/crates/adapter/src/runner/util.rs b/crates/adapter/src/runner/util.rs index b93a7ce..b598214 100644 --- a/crates/adapter/src/runner/util.rs +++ b/crates/adapter/src/runner/util.rs @@ -276,19 +276,35 @@ pub fn parse_cargo_diagnostics( contents: &str, workspace_root: PathBuf, file_paths: &[String], + test_items: &[TestItem], ) -> RunFileTestResult { let contents = contents.replace("\r\n", "\n"); let lines = contents.lines(); let mut result_map: HashMap> = HashMap::new(); for (i, line) in lines.clone().enumerate() { + // Example: + // thread 'server::tests::test_panic' panicked at src/server.rs:584:9: let re = Regex::new(r"thread '([^']+)' panicked at ([^:]+):(\d+):(\d+):").unwrap(); if let Some(m) = re.captures(line) { let mut message = String::new(); - let file = m.get(2).unwrap().as_str().to_string(); - if let Some(file_path) = file_paths - .iter() - .find(|path| path.contains(workspace_root.join(&file).to_str().unwrap())) - { + // :: + let id_with_file = m.get(1).unwrap().as_str().to_string(); + + // relaive path + let relative_file_path = m.get(2).unwrap().as_str().to_string(); + // name of the file without extension + let file_stem = Path::new(&relative_file_path) + .file_stem() + .unwrap() + .to_str() + .unwrap(); + let executed_test_id = id_with_file.replace(&(file_stem.to_string() + "::"), ""); + + if let Some(file_path) = file_paths.iter().find(|path| { + path.contains(workspace_root.join(&relative_file_path).to_str().unwrap()) + }) { + let matched_test_item = test_items.iter().find(|item| item.id == executed_test_id); + let lnum = m.get(3).unwrap().as_str().parse::().unwrap() - 1; let col = m.get(4).unwrap().as_str().parse::().unwrap() - 1; let mut next_i = i + 1; @@ -309,10 +325,42 @@ pub fn parse_cargo_diagnostics( character: MAX_CHAR_LENGTH, }, }, - message, + message: message.clone(), severity: Some(DiagnosticSeverity::ERROR), ..Diagnostic::default() }; + + // if the test item is matched, + // add a diagnostic to the beginning of the test item + // in order to show which test failed. + // If this code does not exist, only panicked positions are shown + if let Some(test_item) = matched_test_item { + let message = format!( + "`{}` failed at {relative_file_path}:{lnum}:{col}\nMessage:\n{message}", + test_item.name + ); + let lnum = test_item.start_position.start.line; + let col = test_item.start_position.start.character; + let diagnostic = Diagnostic { + range: Range { + start: Position { + line: lnum, + character: col, + }, + end: Position { + line: lnum, + character: MAX_CHAR_LENGTH, + }, + }, + message, + severity: Some(DiagnosticSeverity::ERROR), + ..Diagnostic::default() + }; + result_map + .entry(file_path.to_string()) + .or_default() + .push(diagnostic); + } result_map .entry(file_path.to_string()) .or_default() diff --git a/demo/rust/src/lib.rs b/demo/rust/src/lib.rs index 60b0199..c3b5f5f 100644 --- a/demo/rust/src/lib.rs +++ b/demo/rust/src/lib.rs @@ -53,4 +53,13 @@ mod tests { } } } + + fn p() { + panic!("test failed"); + } + + #[test] + fn test_panic() { + p(); + } }