diff options
Diffstat (limited to 'test_util')
-rw-r--r-- | test_util/Cargo.toml | 1 | ||||
-rw-r--r-- | test_util/src/builders.rs | 26 | ||||
-rw-r--r-- | test_util/src/lsp.rs | 402 | ||||
-rw-r--r-- | test_util/src/temp_dir.rs | 5 |
4 files changed, 394 insertions, 40 deletions
diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml index 93bad8594..705ccda40 100644 --- a/test_util/Cargo.toml +++ b/test_util/Cargo.toml @@ -23,6 +23,7 @@ flate2.workspace = true futures.workspace = true hyper = { workspace = true, features = ["server", "http1", "http2", "runtime"] } lazy_static = "1.4.0" +lsp-types.workspace = true once_cell.workspace = true os_pipe.workspace = true parking_lot.workspace = true 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() |