mirror of
https://github.com/kbwo/testing-language-server.git
synced 2025-07-23 19:05:16 +00:00
feat(adapter): add diagnostics to show which test failed
This commit is contained in:
parent
e09a50d60c
commit
aa4883e038
6 changed files with 167 additions and 76 deletions
55
Cargo.lock
generated
55
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<TestItem> = file_paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
discover_rust_tests(path).map(|test_items| {
|
||||
test_items
|
||||
.into_iter()
|
||||
.map(|item| item.id)
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
})
|
||||
.map(|path| discover_rust_tests(path))
|
||||
.filter_map(Result::ok)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let test_ids = discovered_tests
|
||||
.iter()
|
||||
.map(|item| item.id.clone())
|
||||
.collect::<Vec<String>>();
|
||||
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<TestItem> = 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:
|
||||
|
|
|
@ -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<TestItem> = file_paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
discover_rust_tests(path).map(|test_items| {
|
||||
test_items
|
||||
.into_iter()
|
||||
.map(|item| item.id)
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
})
|
||||
.map(|path| discover_rust_tests(path))
|
||||
.filter_map(Result::ok)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
let test_ids = discovered_tests
|
||||
.iter()
|
||||
.map(|item| item.id.clone())
|
||||
.collect::<Vec<String>>();
|
||||
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<TestItem> = 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:
|
||||
|
|
|
@ -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<String, Vec<Diagnostic>> = 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()))
|
||||
{
|
||||
// <filename>::<id>
|
||||
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::<u32>().unwrap() - 1;
|
||||
let col = m.get(4).unwrap().as_str().parse::<u32>().unwrap() - 1;
|
||||
let mut next_i = i + 1;
|
||||
|
@ -298,6 +314,33 @@ pub fn parse_cargo_diagnostics(
|
|||
message = format!("{}{}\n", message, lines.clone().nth(next_i).unwrap());
|
||||
next_i += 1;
|
||||
}
|
||||
let diagnostic = Diagnostic {
|
||||
range: Range {
|
||||
start: Position {
|
||||
line: lnum,
|
||||
character: col,
|
||||
},
|
||||
end: Position {
|
||||
line: lnum,
|
||||
character: MAX_CHAR_LENGTH,
|
||||
},
|
||||
},
|
||||
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 {
|
||||
|
@ -317,6 +360,11 @@ pub fn parse_cargo_diagnostics(
|
|||
.entry(file_path.to_string())
|
||||
.or_default()
|
||||
.push(diagnostic);
|
||||
}
|
||||
result_map
|
||||
.entry(file_path.to_string())
|
||||
.or_default()
|
||||
.push(diagnostic);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -53,4 +53,13 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn p() {
|
||||
panic!("test failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_panic() {
|
||||
p();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue