Add some tests

This commit is contained in:
Shuhei Takahashi 2025-01-22 23:54:34 +09:00
parent 087a41e264
commit 8767ee811d
13 changed files with 325 additions and 6 deletions

View file

@ -1371,3 +1371,33 @@ impl Analyzer {
}
}
}
#[cfg(test)]
mod tests {
use crate::testutil::testdata;
use super::*;
#[test]
fn test_analyze_smoke() {
let storage = Arc::new(Mutex::new(DocumentStorage::new()));
let mut analyzer = Analyzer::new(&storage);
let file = analyzer
.analyze(&testdata("workspaces/smoke/BUILD.gn"))
.unwrap();
// No parse error.
assert!(file
.ast_root
.statements
.iter()
.all(|s| !matches!(s, Statement::Unknown(_) | Statement::UnmatchedBrace(_))));
// Inspect the top-level scope.
let scope = file.scope_at(0);
assert!(scope.get("enable_opt").is_some());
assert!(scope.get("_lib").is_some());
assert!(scope.get("is_linux").is_some());
}
}

52
src/client.rs Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::fmt::Display;
use serde_json::Value;
use tower_lsp::lsp_types::{ConfigurationItem, MessageType};
use crate::providers::RpcResult;
#[derive(Clone)]
pub struct TestableClient {
client: Option<tower_lsp::Client>,
}
impl TestableClient {
pub fn new(client: tower_lsp::Client) -> Self {
Self {
client: Some(client),
}
}
#[cfg(test)]
pub fn new_for_testing() -> Self {
Self { client: None }
}
pub async fn log_message<M: Display>(&self, typ: MessageType, message: M) {
if let Some(client) = &self.client {
client.log_message(typ, message).await;
}
}
pub async fn configuration(&self, items: Vec<ConfigurationItem>) -> RpcResult<Vec<Value>> {
if let Some(client) = &self.client {
client.configuration(items).await
} else {
Ok(Vec::new())
}
}
}

View file

@ -15,10 +15,12 @@
mod analyze;
mod ast;
mod builtins;
mod client;
mod config;
mod providers;
mod server;
mod storage;
mod testutil;
mod util;
#[tokio::main]

View file

@ -172,3 +172,57 @@ pub async fn hover(context: &ProviderContext, params: HoverParams) -> RpcResult<
range: Some(current_file.document.line_index.range(ident.span)),
}))
}
#[cfg(test)]
mod tests {
use tower_lsp::lsp_types::{
Position, Range, TextDocumentIdentifier, TextDocumentPositionParams, WorkDoneProgressParams,
};
use crate::testutil::testdata;
use super::*;
#[tokio::test]
async fn test_hover() {
let uri = Url::from_file_path(testdata("workspaces/hover/BUILD.gn")).unwrap();
let params = HoverParams {
text_document_position_params: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: uri.clone() },
position: Position {
line: 17,
character: 0,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
};
let response = hover(&ProviderContext::new_for_testing(), params)
.await
.unwrap();
assert_eq!(
response,
Some(Hover {
contents: HoverContents::Array(vec![
MarkedString::from_language_code("gn".to_string(), "a = 1".to_string()),
MarkedString::from_language_code("text".to_string(), "".to_string()),
MarkedString::from_markdown(format!(
"Defined at [//BUILD.gn:18:1]({}#L18,1)",
uri
)),
]),
range: Some(Range {
start: Position {
line: 17,
character: 0,
},
end: Position {
line: 17,
character: 1,
},
}),
})
);
}
}

View file

@ -17,11 +17,12 @@ use std::{
sync::{Arc, Mutex},
};
use tower_lsp::{lsp_types::Position, Client};
use tower_lsp::lsp_types::Position;
use crate::{
analyze::{AnalyzedFile, Analyzer},
ast::{Identifier, Node},
client::TestableClient,
storage::DocumentStorage,
};
@ -39,7 +40,20 @@ pub type RpcResult<T> = tower_lsp::jsonrpc::Result<T>;
pub struct ProviderContext {
pub storage: Arc<Mutex<DocumentStorage>>,
pub analyzer: Arc<Mutex<Analyzer>>,
pub client: Client,
pub client: TestableClient,
}
impl ProviderContext {
#[cfg(test)]
pub fn new_for_testing() -> Self {
let storage = Arc::new(Mutex::new(DocumentStorage::new()));
let analyzer = Arc::new(Mutex::new(Analyzer::new(&storage)));
Self {
storage,
analyzer,
client: TestableClient::new_for_testing(),
}
}
}
pub fn into_rpc_error(err: std::io::Error) -> tower_lsp::jsonrpc::Error {

View file

@ -29,11 +29,12 @@ use tower_lsp::{
HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams,
MessageType, OneOf, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
},
Client, LanguageServer, LspService, Server,
LanguageServer, LspService, Server,
};
use crate::{
analyze::{find_workspace_root, Analyzer},
client::TestableClient,
config::Configurations,
providers::{ProviderContext, RpcResult},
storage::DocumentStorage,
@ -48,7 +49,7 @@ impl Backend {
pub fn new(
storage: &Arc<Mutex<DocumentStorage>>,
analyzer: &Arc<Mutex<Analyzer>>,
client: Client,
client: TestableClient,
) -> Self {
Self {
context: ProviderContext {
@ -180,8 +181,9 @@ impl LanguageServer for Backend {
pub async fn run() {
let storage = Arc::new(Mutex::new(DocumentStorage::new()));
let analyzer = Arc::new(Mutex::new(Analyzer::new(&storage)));
let (service, socket) =
LspService::new(move |client| Backend::new(&storage, &analyzer, client));
let (service, socket) = LspService::new(move |client| {
Backend::new(&storage, &analyzer, TestableClient::new(client))
});
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();

24
src/testutil.rs Normal file
View file

@ -0,0 +1,24 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg(test)]
use std::path::{Path, PathBuf};
pub fn testdata(name: impl AsRef<Path>) -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("testdata");
path.push(name.as_ref());
path
}

15
testdata/workspaces/hover/.gn vendored Normal file
View file

@ -0,0 +1,15 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
buildconfig = "//BUILDCONFIG.gn"

36
testdata/workspaces/hover/BUILD.gn vendored Normal file
View file

@ -0,0 +1,36 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Avoid attaching the copyright comment to the first assignment.
start = true
a = 1
b = 1
b = 2
b = 3
template("foo") {
}
if (true) {
template("bar") {
}
} else {
template("bar") {
}
}
foo("foo")
bar("bar")

View file

@ -0,0 +1,13 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

15
testdata/workspaces/smoke/.gn vendored Normal file
View file

@ -0,0 +1,15 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
buildconfig = "//BUILDCONFIG.gn"

47
testdata/workspaces/smoke/BUILD.gn vendored Normal file
View file

@ -0,0 +1,47 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
declare_args() {
enable_opt = false
}
static_library("lib") {
sources = [ "lib.cc" ]
opt = enable_opt
}
if (is_linux) {
static_library("lib_linux") {
sources = [ "lib_linux.cc" ]
opt = enable_opt
}
_lib = ":lib_linux"
} else if (is_win) {
static_library("lib_win") {
sources = [ "lib_win.cc" ]
opt = enable_opt
}
_lib = ":lib_win"
} else {
assert(false, "Unsupported platform")
}
executable("main") {
sources = [ "main.cc" ]
deps = [
":lib",
_lib,
]
opt = enable_opt
}

View file

@ -0,0 +1,15 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
is_linux = current_os == "linux"