summaryrefslogtreecommitdiff
path: root/test_util/src
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-03-08 18:15:20 -0500
committerGitHub <noreply@github.com>2023-03-08 23:15:20 +0000
commit25d98ca289af64f85759fe10c8808afbfb7011e3 (patch)
tree79f0126791ae317b030696b7c507dee6420a660e /test_util/src
parentf2e5e01832a40eb71be82f1b46304c56aa2e8dba (diff)
refactor(lsp): improve test client initialization (#18015)
Diffstat (limited to 'test_util/src')
-rw-r--r--test_util/src/builders.rs26
-rw-r--r--test_util/src/lsp.rs402
-rw-r--r--test_util/src/temp_dir.rs5
3 files changed, 393 insertions, 40 deletions
diff --git a/test_util/src/builders.rs b/test_util/src/builders.rs
index da331b62a..e72ce9b63 100644
--- a/test_util/src/builders.rs
+++ b/test_util/src/builders.rs
@@ -18,6 +18,7 @@ use crate::copy_dir_recursive;
use crate::deno_exe_path;
use crate::env_vars_for_npm_tests_no_sync_download;
use crate::http_server;
+use crate::lsp::LspClientBuilder;
use crate::new_deno_dir;
use crate::strip_ansi_codes;
use crate::testdata_path;
@@ -35,6 +36,7 @@ pub struct TestContextBuilder {
copy_temp_dir: Option<String>,
cwd: Option<String>,
envs: HashMap<String, String>,
+ deno_exe: Option<PathBuf>,
}
impl TestContextBuilder {
@@ -110,7 +112,7 @@ impl TestContextBuilder {
testdata_path()
};
- let deno_exe = deno_exe_path();
+ let deno_exe = self.deno_exe.clone().unwrap_or_else(deno_exe_path);
println!("deno_exe path {}", deno_exe.display());
let http_server_guard = if self.use_http_server {
@@ -121,6 +123,7 @@ impl TestContextBuilder {
TestContext {
cwd: self.cwd.clone(),
+ deno_exe,
envs: self.envs.clone(),
use_temp_cwd: self.use_temp_cwd,
_http_server_guard: http_server_guard,
@@ -132,6 +135,7 @@ impl TestContextBuilder {
#[derive(Clone)]
pub struct TestContext {
+ deno_exe: PathBuf,
envs: HashMap<String, String>,
use_temp_cwd: bool,
cwd: Option<String>,
@@ -161,7 +165,7 @@ impl TestContext {
pub fn new_command(&self) -> TestCommandBuilder {
TestCommandBuilder {
- command_name: Default::default(),
+ command_name: self.deno_exe.to_string_lossy().to_string(),
args: Default::default(),
args_vec: Default::default(),
stdin: Default::default(),
@@ -172,10 +176,16 @@ impl TestContext {
context: self.clone(),
}
}
+
+ pub fn new_lsp_command(&self) -> LspClientBuilder {
+ let mut builder = LspClientBuilder::new();
+ builder.deno_exe(&self.deno_exe).set_test_context(self);
+ builder
+ }
}
pub struct TestCommandBuilder {
- command_name: Option<String>,
+ command_name: String,
args: String,
args_vec: Vec<String>,
stdin: Option<String>,
@@ -188,7 +198,7 @@ pub struct TestCommandBuilder {
impl TestCommandBuilder {
pub fn command_name(&mut self, name: impl AsRef<str>) -> &mut Self {
- self.command_name = Some(name.as_ref().to_string());
+ self.command_name = name.as_ref().to_string();
self
}
@@ -284,15 +294,11 @@ impl TestCommandBuilder {
arg.replace("$TESTDATA", &self.context.testdata_dir.to_string_lossy())
})
.collect::<Vec<_>>();
- let command_name = self
- .command_name
- .as_ref()
- .cloned()
- .unwrap_or("deno".to_string());
+ let command_name = &self.command_name;
let mut command = if command_name == "deno" {
Command::new(deno_exe_path())
} else {
- Command::new(&command_name)
+ Command::new(command_name)
};
command.env("DENO_DIR", self.context.deno_dir.path());
diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs
index c4a81c63c..5798235b6 100644
--- a/test_util/src/lsp.rs
+++ b/test_util/src/lsp.rs
@@ -1,12 +1,29 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use crate::deno_exe_path;
use crate::npm_registry_url;
+use crate::TestContext;
+use crate::TestContextBuilder;
use super::new_deno_dir;
use super::TempDir;
use anyhow::Result;
use lazy_static::lazy_static;
+use lsp_types::ClientCapabilities;
+use lsp_types::ClientInfo;
+use lsp_types::CodeActionCapabilityResolveSupport;
+use lsp_types::CodeActionClientCapabilities;
+use lsp_types::CodeActionKindLiteralSupport;
+use lsp_types::CodeActionLiteralSupport;
+use lsp_types::CompletionClientCapabilities;
+use lsp_types::CompletionItemCapability;
+use lsp_types::FoldingRangeClientCapabilities;
+use lsp_types::InitializeParams;
+use lsp_types::TextDocumentClientCapabilities;
+use lsp_types::TextDocumentSyncClientCapabilities;
+use lsp_types::Url;
+use lsp_types::WorkspaceClientCapabilities;
use parking_lot::Condvar;
use parking_lot::Mutex;
use regex::Regex;
@@ -19,6 +36,7 @@ use serde_json::Value;
use std::io;
use std::io::Write;
use std::path::Path;
+use std::path::PathBuf;
use std::process::Child;
use std::process::ChildStdin;
use std::process::ChildStdout;
@@ -153,6 +171,335 @@ impl LspStdoutReader {
}
}
+pub struct InitializeParamsBuilder {
+ params: InitializeParams,
+}
+
+impl InitializeParamsBuilder {
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ Self {
+ params: InitializeParams {
+ process_id: None,
+ client_info: Some(ClientInfo {
+ name: "test-harness".to_string(),
+ version: Some("1.0.0".to_string()),
+ }),
+ root_uri: None,
+ initialization_options: Some(json!({
+ "enable": true,
+ "cache": null,
+ "certificateStores": null,
+ "codeLens": {
+ "implementations": true,
+ "references": true,
+ "test": true
+ },
+ "config": null,
+ "importMap": null,
+ "lint": true,
+ "suggest": {
+ "autoImports": true,
+ "completeFunctionCalls": false,
+ "names": true,
+ "paths": true,
+ "imports": {
+ "hosts": {}
+ }
+ },
+ "testing": {
+ "args": [
+ "--allow-all"
+ ],
+ "enable": true
+ },
+ "tlsCertificate": null,
+ "unsafelyIgnoreCertificateErrors": null,
+ "unstable": false
+ })),
+ capabilities: ClientCapabilities {
+ text_document: Some(TextDocumentClientCapabilities {
+ code_action: Some(CodeActionClientCapabilities {
+ code_action_literal_support: Some(CodeActionLiteralSupport {
+ code_action_kind: CodeActionKindLiteralSupport {
+ value_set: vec![
+ "quickfix".to_string(),
+ "refactor".to_string(),
+ ],
+ },
+ }),
+ is_preferred_support: Some(true),
+ data_support: Some(true),
+ disabled_support: Some(true),
+ resolve_support: Some(CodeActionCapabilityResolveSupport {
+ properties: vec!["edit".to_string()],
+ }),
+ ..Default::default()
+ }),
+ completion: Some(CompletionClientCapabilities {
+ completion_item: Some(CompletionItemCapability {
+ snippet_support: Some(true),
+ ..Default::default()
+ }),
+ ..Default::default()
+ }),
+ folding_range: Some(FoldingRangeClientCapabilities {
+ line_folding_only: Some(true),
+ ..Default::default()
+ }),
+ synchronization: Some(TextDocumentSyncClientCapabilities {
+ dynamic_registration: Some(true),
+ will_save: Some(true),
+ will_save_wait_until: Some(true),
+ did_save: Some(true),
+ }),
+ ..Default::default()
+ }),
+ workspace: Some(WorkspaceClientCapabilities {
+ configuration: Some(true),
+ workspace_folders: Some(true),
+ ..Default::default()
+ }),
+ experimental: Some(json!({
+ "testingApi": true
+ })),
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ }
+ }
+
+ pub fn set_maybe_root_uri(&mut self, value: Option<Url>) -> &mut Self {
+ self.params.root_uri = value;
+ self
+ }
+
+ pub fn set_root_uri(&mut self, value: Url) -> &mut Self {
+ self.set_maybe_root_uri(Some(value))
+ }
+
+ pub fn set_workspace_folders(
+ &mut self,
+ folders: Vec<lsp_types::WorkspaceFolder>,
+ ) -> &mut Self {
+ self.params.workspace_folders = Some(folders);
+ self
+ }
+
+ pub fn enable_inlay_hints(&mut self) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert(
+ "inlayHints".to_string(),
+ json!({
+ "parameterNames": {
+ "enabled": "all"
+ },
+ "parameterTypes": {
+ "enabled": true
+ },
+ "variableTypes": {
+ "enabled": true
+ },
+ "propertyDeclarationTypes": {
+ "enabled": true
+ },
+ "functionLikeReturnTypes": {
+ "enabled": true
+ },
+ "enumMemberValues": {
+ "enabled": true
+ }
+ }),
+ );
+ self
+ }
+
+ pub fn disable_testing_api(&mut self) -> &mut Self {
+ let obj = self
+ .params
+ .capabilities
+ .experimental
+ .as_mut()
+ .unwrap()
+ .as_object_mut()
+ .unwrap();
+ obj.insert("testingApi".to_string(), false.into());
+ let options = self.initialization_options_mut();
+ options.remove("testing");
+ self
+ }
+
+ pub fn set_cache(&mut self, value: impl AsRef<str>) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert("cache".to_string(), value.as_ref().to_string().into());
+ self
+ }
+
+ pub fn set_code_lens(
+ &mut self,
+ value: Option<serde_json::Value>,
+ ) -> &mut Self {
+ let options = self.initialization_options_mut();
+ if let Some(value) = value {
+ options.insert("codeLens".to_string(), value);
+ } else {
+ options.remove("codeLens");
+ }
+ self
+ }
+
+ pub fn set_config(&mut self, value: impl AsRef<str>) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert("config".to_string(), value.as_ref().to_string().into());
+ self
+ }
+
+ pub fn set_enable_paths(&mut self, value: Vec<String>) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert("enablePaths".to_string(), value.into());
+ self
+ }
+
+ pub fn set_deno_enable(&mut self, value: bool) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert("enable".to_string(), value.into());
+ self
+ }
+
+ pub fn set_import_map(&mut self, value: impl AsRef<str>) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert("importMap".to_string(), value.as_ref().to_string().into());
+ self
+ }
+
+ pub fn set_tls_certificate(&mut self, value: impl AsRef<str>) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert(
+ "tlsCertificate".to_string(),
+ value.as_ref().to_string().into(),
+ );
+ self
+ }
+
+ pub fn set_unstable(&mut self, value: bool) -> &mut Self {
+ let options = self.initialization_options_mut();
+ options.insert("unstable".to_string(), value.into());
+ self
+ }
+
+ pub fn add_test_server_suggestions(&mut self) -> &mut Self {
+ self.set_suggest_imports_hosts(vec![(
+ "http://localhost:4545/".to_string(),
+ true,
+ )])
+ }
+
+ pub fn set_suggest_imports_hosts(
+ &mut self,
+ values: Vec<(String, bool)>,
+ ) -> &mut Self {
+ let options = self.initialization_options_mut();
+ let suggest = options.get_mut("suggest").unwrap().as_object_mut().unwrap();
+ let imports = suggest.get_mut("imports").unwrap().as_object_mut().unwrap();
+ let hosts = imports.get_mut("hosts").unwrap().as_object_mut().unwrap();
+ hosts.clear();
+ for (key, value) in values {
+ hosts.insert(key, value.into());
+ }
+ self
+ }
+
+ pub fn with_capabilities(
+ &mut self,
+ mut action: impl FnMut(&mut ClientCapabilities),
+ ) -> &mut Self {
+ action(&mut self.params.capabilities);
+ self
+ }
+
+ fn initialization_options_mut(
+ &mut self,
+ ) -> &mut serde_json::Map<String, serde_json::Value> {
+ let options = self.params.initialization_options.as_mut().unwrap();
+ options.as_object_mut().unwrap()
+ }
+
+ pub fn build(&self) -> InitializeParams {
+ self.params.clone()
+ }
+}
+
+pub struct LspClientBuilder {
+ print_stderr: bool,
+ deno_exe: PathBuf,
+ context: Option<TestContext>,
+}
+
+impl LspClientBuilder {
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ Self {
+ print_stderr: false,
+ deno_exe: deno_exe_path(),
+ context: None,
+ }
+ }
+
+ pub fn deno_exe(&mut self, exe_path: impl AsRef<Path>) -> &mut Self {
+ self.deno_exe = exe_path.as_ref().to_path_buf();
+ self
+ }
+
+ pub fn print_stderr(&mut self) -> &mut Self {
+ self.print_stderr = true;
+ self
+ }
+
+ pub fn set_test_context(&mut self, test_context: &TestContext) -> &mut Self {
+ self.context = Some(test_context.clone());
+ self
+ }
+
+ pub fn build(&self) -> LspClient {
+ self.build_result().unwrap()
+ }
+
+ pub fn build_result(&self) -> Result<LspClient> {
+ let deno_dir = new_deno_dir();
+ let mut command = Command::new(&self.deno_exe);
+ command
+ .env("DENO_DIR", deno_dir.path())
+ .env("NPM_CONFIG_REGISTRY", npm_registry_url())
+ .arg("lsp")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped());
+ if !self.print_stderr {
+ command.stderr(Stdio::null());
+ }
+ let mut child = command.spawn()?;
+ let stdout = child.stdout.take().unwrap();
+ let buf_reader = io::BufReader::new(stdout);
+ let reader = LspStdoutReader::new(buf_reader);
+
+ let stdin = child.stdin.take().unwrap();
+ let writer = io::BufWriter::new(stdin);
+
+ Ok(LspClient {
+ child,
+ reader,
+ request_id: 1,
+ start: Instant::now(),
+ context: self
+ .context
+ .clone()
+ .unwrap_or_else(|| TestContextBuilder::new().build()),
+ writer,
+ deno_dir,
+ })
+ }
+}
+
pub struct LspClient {
child: Child,
reader: LspStdoutReader,
@@ -160,6 +507,7 @@ pub struct LspClient {
start: Instant,
writer: io::BufWriter<ChildStdin>,
deno_dir: TempDir,
+ context: TestContext,
}
impl Drop for LspClient {
@@ -228,36 +576,6 @@ where
}
impl LspClient {
- pub fn new(deno_exe: &Path, print_stderr: bool) -> Result<Self> {
- let deno_dir = new_deno_dir();
- let mut command = Command::new(deno_exe);
- command
- .env("DENO_DIR", deno_dir.path())
- .env("NPM_CONFIG_REGISTRY", npm_registry_url())
- .arg("lsp")
- .stdin(Stdio::piped())
- .stdout(Stdio::piped());
- if !print_stderr {
- command.stderr(Stdio::null());
- }
- let mut child = command.spawn()?;
- let stdout = child.stdout.take().unwrap();
- let buf_reader = io::BufReader::new(stdout);
- let reader = LspStdoutReader::new(buf_reader);
-
- let stdin = child.stdin.take().unwrap();
- let writer = io::BufWriter::new(stdin);
-
- Ok(Self {
- child,
- reader,
- request_id: 1,
- start: Instant::now(),
- writer,
- deno_dir,
- })
- }
-
pub fn deno_dir(&self) -> &TempDir {
&self.deno_dir
}
@@ -274,6 +592,30 @@ impl LspClient {
self.reader.pending_len()
}
+ pub fn initialize_default(&mut self) {
+ self.initialize(|_| {})
+ }
+
+ pub fn initialize(
+ &mut self,
+ do_build: impl Fn(&mut InitializeParamsBuilder),
+ ) {
+ let mut builder = InitializeParamsBuilder::new();
+ builder.set_root_uri(self.context.deno_dir().uri());
+ do_build(&mut builder);
+ self
+ .write_request::<_, _, Value>("initialize", builder.build())
+ .unwrap();
+ self.write_notification("initialized", json!({})).unwrap();
+ }
+
+ pub fn shutdown(&mut self) {
+ self
+ .write_request::<_, _, Value>("shutdown", json!(null))
+ .unwrap();
+ self.write_notification("exit", json!(null)).unwrap();
+ }
+
// it's flaky to assert for a notification because a notification
// might arrive a little later, so only provide a method for asserting
// that there is no notification
diff --git a/test_util/src/temp_dir.rs b/test_util/src/temp_dir.rs
index 01dce8f1a..b800e425d 100644
--- a/test_util/src/temp_dir.rs
+++ b/test_util/src/temp_dir.rs
@@ -9,6 +9,7 @@ use std::sync::Arc;
use std::time::SystemTime;
use anyhow::Context;
+use lsp_types::Url;
use once_cell::sync::OnceCell;
static TEMP_DIR_SESSION: OnceCell<TempDirSession> = OnceCell::new();
@@ -83,6 +84,10 @@ impl TempDir {
})
}
+ pub fn uri(&self) -> Url {
+ Url::from_directory_path(self.path()).unwrap()
+ }
+
pub fn path(&self) -> &Path {
let inner = &self.0;
inner.0.as_path()