implement adapter error bubbling and send warning diagnostic

fix #2
This commit is contained in:
kbwo 2024-06-13 00:04:14 +09:00
parent d2f262056a
commit 607f43aa39
3 changed files with 137 additions and 32 deletions

View file

@ -1,7 +1,10 @@
use crate::model::AvailableTestKind;
use crate::model::Runner;
use clap::Parser;
use std::io;
use std::io::Write;
use std::str::FromStr;
use testing_language_server::error::LSError;
use testing_language_server::spec::AdapterCommands;
use testing_language_server::spec::DetectWorkspaceRootArgs;
use testing_language_server::spec::DiscoverArgs;
@ -24,25 +27,32 @@ fn pick_test_from_extra(
Ok((extra, AvailableTestKind::from_str(&language)?))
}
fn handle(commands: AdapterCommands) -> Result<(), LSError> {
match commands {
AdapterCommands::Discover(mut commands) => {
let (extra, test_kind) = pick_test_from_extra(&mut commands.extra).unwrap();
test_kind.disover(DiscoverArgs { extra, ..commands })?;
Ok(())
}
AdapterCommands::RunFileTest(mut commands) => {
let (extra, test_kind) = pick_test_from_extra(&mut commands.extra)?;
test_kind.run_file_test(RunFileTestArgs { extra, ..commands })?;
Ok(())
}
AdapterCommands::DetectWorkspaceRoot(mut commands) => {
let (extra, test_kind) = pick_test_from_extra(&mut commands.extra)?;
test_kind.detect_workspaces_root(DetectWorkspaceRootArgs { extra, ..commands })?;
Ok(())
}
}
}
fn main() {
let args = AdapterCommands::parse();
match args {
AdapterCommands::Discover(mut args) => {
let (extra, test_kind) = pick_test_from_extra(&mut args.extra).unwrap();
test_kind.disover(DiscoverArgs { extra, ..args }).unwrap();
}
AdapterCommands::RunFileTest(mut args) => {
let (extra, test_kind) = pick_test_from_extra(&mut args.extra).unwrap();
test_kind
.run_file_test(RunFileTestArgs { extra, ..args })
.unwrap();
}
AdapterCommands::DetectWorkspaceRoot(mut args) => {
let (extra, test_kind) = pick_test_from_extra(&mut args.extra).unwrap();
test_kind
.detect_workspaces_root(DetectWorkspaceRootArgs { extra, ..args })
.unwrap();
}
if let Err(error) = handle(args) {
io::stderr()
.write_all(format!("{:#?}", error).as_bytes())
.unwrap();
}
}

View file

@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Output;
use std::str::FromStr;
use testing_language_server::error::LSError;
use testing_language_server::spec::DetectWorkspaceRootResult;
@ -248,7 +249,11 @@ impl Runner for CargoTestRunner {
.args(args.extra)
.output()
.unwrap();
let test_result = String::from_utf8(test_result.stdout)?;
let Output { stdout, stderr, .. } = test_result;
if !stderr.is_empty() {
return Err(LSError::Adapter(String::from_utf8(stderr).unwrap()));
}
let test_result = String::from_utf8(stdout)?;
let diagnostics: RunFileTestResult = parse_diagnostics(
&test_result,
PathBuf::from_str(&workspace_root).unwrap(),

View file

@ -11,9 +11,12 @@ use crate::util::send_stdout;
use lsp_types::Diagnostic;
use lsp_types::DiagnosticOptions;
use lsp_types::DiagnosticServerCapabilities;
use lsp_types::DiagnosticSeverity;
use lsp_types::InitializeParams;
use lsp_types::InitializeResult;
use lsp_types::Position;
use lsp_types::PublishDiagnosticsParams;
use lsp_types::Range;
use lsp_types::ServerCapabilities;
use lsp_types::TextDocumentSyncCapability;
use lsp_types::TextDocumentSyncKind;
@ -29,6 +32,7 @@ use std::io::BufRead;
use std::io::{self, Read};
use std::path::PathBuf;
use std::process::Command;
use std::process::Output;
#[derive(Deserialize, Default)]
#[serde(rename_all = "camelCase")]
@ -137,7 +141,6 @@ impl TestingLS {
.as_str()
.ok_or(serde_json::Error::custom("`uri` is not set"))?;
let result = self.discover_file(uri)?;
tracing::info!("DEBUGPRINT[6]: server.rs:139: result={:#?}", result);
send_stdout(&json!({
"jsonrpc": "2.0",
"id": id,
@ -325,13 +328,14 @@ impl TestingLS {
Ok(())
}
fn check(
fn get_diagnostics(
&self,
adapter: &AdapterConfiguration,
workspace_root: &str,
paths: &[String],
) -> Result<impl Serialize, LSError> {
) -> Result<Vec<(String, Vec<Diagnostic>)>, LSError> {
let mut adapter_command = Command::new(&adapter.path);
let mut diagnostics: Vec<(String, Vec<Diagnostic>)> = vec![];
let cwd = PathBuf::from(workspace_root);
let adapter_command = adapter_command.current_dir(&cwd);
let mut args: Vec<&str> = vec!["--workspace-root", cwd.to_str().unwrap()];
@ -348,19 +352,64 @@ impl TestingLS {
.envs(&adapter.envs)
.output()
.map_err(|err| LSError::Adapter(err.to_string()))?;
let Output { stdout, stderr, .. } = output;
if !stderr.is_empty() {
let message = "Cannot run test command: \n".to_string()
+ &String::from_utf8(stderr.clone()).unwrap();
let placeholder_diagnostic = Diagnostic {
range: Range {
start: Position {
line: 0,
character: 0,
},
end: Position {
line: 0,
character: 0,
},
},
message,
severity: Some(DiagnosticSeverity::WARNING),
code_description: None,
code: None,
source: None,
tags: None,
related_information: None,
data: None,
};
for path in paths {
diagnostics.push((path.to_string(), vec![placeholder_diagnostic.clone()]));
}
}
let adapter_result =
String::from_utf8(output.stdout).map_err(|err| LSError::Adapter(err.to_string()))?;
let diagnostics: RunFileTestResult = serde_json::from_str(&adapter_result)?;
for target_file in paths {
let diagnostics_for_file: Vec<Diagnostic> = diagnostics
.clone()
.iter()
.filter(|RunFileTestResultItem { path, .. }| path == target_file)
.flat_map(|RunFileTestResultItem { diagnostics, .. }| diagnostics.clone())
.collect();
let uri = Url::from_file_path(target_file.replace("file://", "")).unwrap();
self.send_diagnostics(uri, diagnostics_for_file)?;
String::from_utf8(stdout).map_err(|err| LSError::Adapter(err.to_string()))?;
if let Ok(res) = serde_json::from_str::<RunFileTestResult>(&adapter_result) {
for target_file in paths {
let diagnostics_for_file: Vec<Diagnostic> = res
.clone()
.iter()
.filter(|RunFileTestResultItem { path, .. }| path == target_file)
.flat_map(|RunFileTestResultItem { diagnostics, .. }| diagnostics.clone())
.collect();
let uri = Url::from_file_path(target_file.replace("file://", "")).unwrap();
diagnostics.push((uri.to_string(), diagnostics_for_file));
}
}
Ok(diagnostics)
}
fn check(
&self,
adapter: &AdapterConfiguration,
workspace_root: &str,
paths: &[String],
) -> Result<impl Serialize, LSError> {
let diagnostics = self.get_diagnostics(adapter, workspace_root, paths)?;
for (path, diagnostics) in diagnostics {
self.send_diagnostics(
Url::from_file_path(path.replace("file://", "")).unwrap(),
diagnostics,
)?;
}
Ok(())
}
@ -495,4 +544,45 @@ mod tests {
assert_eq!(extension_from_url_str(file).unwrap(), ".js");
});
}
#[test]
fn bubble_adapter_error() {
let adapter_conf: AdapterConfiguration = AdapterConfiguration {
path: std::env::current_dir()
.unwrap()
.join("target/debug/testing-ls-adapter")
.to_str()
.unwrap()
.to_string(),
extra_args: vec!["--invalid-arg".to_string()],
envs: HashMap::new(),
};
let abs_path_of_test_proj = std::env::current_dir().unwrap().join("test_proj/rust");
let files = TestingLS::project_files(".rs", abs_path_of_test_proj.clone());
let server = TestingLS {
initialize_params: InitializeParams {
workspace_folders: Some(vec![WorkspaceFolder {
uri: Url::from_file_path(&abs_path_of_test_proj).unwrap(),
name: "test_proj".to_string(),
}]),
..InitializeParams::default()
},
options: InitializedOptions {
adapter_command: HashMap::from([(String::from(".rs"), vec![adapter_conf.clone()])]),
},
workspace_root_cache: HashMap::new(),
};
let diagnostics = server
.get_diagnostics(
&adapter_conf,
abs_path_of_test_proj.to_str().unwrap(),
&files,
)
.unwrap();
assert_eq!(diagnostics.len(), 1);
let diagnostic = diagnostics.first().unwrap().1.first().unwrap();
assert_eq!(diagnostic.severity.unwrap(), DiagnosticSeverity::WARNING);
assert!(diagnostic.message.contains("Cannot run test command:"));
}
}