Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions common/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ mod tests {
}

#[tokio::test]
async fn test_from_file_successfully_reads_correct_config_structure() -> Result<()> {
async fn test_from_file_success() -> Result<()> {
zed_settings_file().write_str(
r#"
{
Expand All @@ -164,7 +164,7 @@ mod tests {
}

#[tokio::test]
async fn test_from_file_fails_when_settings_file_is_missing() {
async fn test_from_file_failure_when_settings_file_is_missing() {
let config = Config::from_settings_file();

assert_eq!(
Expand Down Expand Up @@ -283,7 +283,7 @@ mod tests {
}

#[tokio::test]
async fn test_from_user_input_successfully_reads_config() -> Result<()> {
async fn test_from_user_input_success() -> Result<()> {
let input_lines = "\nabcdef1234567890\n"; // empty line followed by fake gist id
let mut io = CursorInteractiveIO::new(input_lines);

Expand Down
3 changes: 3 additions & 0 deletions lsp/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use tower_lsp::Client as LspClient;

#[cfg(test)]
use crate::mocks::MockLspClient as LspClient;
#[cfg(test)]
use crate::watching::MockPathStore as PathStore;
#[cfg(not(test))]
use crate::watching::PathStore;

#[derive(Debug)]
Expand Down
232 changes: 228 additions & 4 deletions lsp/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Backend {
#[allow(clippy::expect_used)]
self.app_state
.get()
.expect("App state must already be initialized")
.expect("App state must be initialized")
.lock()
.expect("Watched paths store mutex is poisoned")
.watched_paths
Expand All @@ -65,7 +65,7 @@ impl Backend {
#[allow(clippy::expect_used)]
self.app_state
.get()
.expect("App state must be already initialized")
.expect("App state must be initialized")
.lock()
.expect("Watched paths store mutex is poisoned")
.watched_paths
Expand Down Expand Up @@ -104,12 +104,12 @@ impl LanguageServer for Backend {
#[allow(clippy::expect_used)]
self.app_state
.set(Mutex::new(app_state))
.expect("AppState should not yet be initialized");
.expect("AppState was already initialized");

#[allow(clippy::expect_used)]
self.app_state
.get()
.expect("App state should have been already initialized")
.expect("App state should be initialized")
.lock()
.expect("Watched paths store mutex is poisoned")
.watched_paths
Expand Down Expand Up @@ -195,3 +195,227 @@ impl LanguageServer for Backend {
}
}
}

#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]

use std::path::Path;
use std::path::PathBuf;

use anyhow::Result;
use anyhow::anyhow;
use mockall::{Sequence, predicate};
use tower_lsp::{LanguageServer, lsp_types::InitializeParams};
use zed_extension_api::serde_json::{Value, json};

use crate::{backend::Backend, mocks::MockLspClient, watching::MockPathStore};

async fn init_lsp_backend(initialization_options: Option<Value>) -> Result<Backend> {
let mut mock_lsp_client = MockLspClient::default();
mock_lsp_client
.expect_clone()
.returning(MockLspClient::default);

let backend = Backend::new(mock_lsp_client);
let initialize_params = InitializeParams {
initialization_options,
..Default::default()
};
backend.initialize(initialize_params).await?;

Ok(backend)
}

async fn init_lsp_backend_default() -> Result<Backend> {
init_lsp_backend(Some(json!({
"github_token": "gho_my-shiny-token",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these keys be constants? They seem common and expected by the initialization code?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are the field names of the Config type. So I had no need to extract them into constants.

"gist_id": "deadbeefdeadbeefdeadbeefdeadbeef"
})))
.await
}

#[tokio::test]
async fn test_initialize_success() -> Result<()> {
let ctx = MockPathStore::new_context();
ctx.expect().returning(|_, _| {
let mut mock_path_store = MockPathStore::default();
mock_path_store.expect_start_watcher().returning(|| ());
Ok(mock_path_store)
});

init_lsp_backend_default().await?;

Ok(())
}

#[tokio::test]
async fn test_initialize_failure_missing_initialization_options() -> Result<()> {
let ctx = MockPathStore::new_context();
ctx.expect().returning(|_, _| {
let mut mock_path_store = MockPathStore::default();
mock_path_store.expect_start_watcher().returning(|| ());
Ok(mock_path_store)
});

assert!(init_lsp_backend(None).await.is_err());

Ok(())
}

#[tokio::test]
async fn test_initialize_failure_invalid_initialization_options() -> Result<()> {
let ctx = MockPathStore::new_context();
ctx.expect().returning(|_, _| {
let mut mock_path_store = MockPathStore::default();
mock_path_store.expect_start_watcher().returning(|| ());
Ok(mock_path_store)
});

let test_cases = [
json!({
"hello": "world"
}),
json!({
"gist_id": "1234"
}),
json!({
"github_token": "tok"
}),
json!({
"gist_id": "1234",
"random_key": "value"
}),
json!({
"github_token": "5678",
"random_prop": "val"
}),
];

for test in test_cases {
assert!(init_lsp_backend(Some(test)).await.is_err());
}

Ok(())
}

#[tokio::test]
async fn test_watch_path_success() -> Result<()> {
let path = "/path/to/watch";

let ctx = MockPathStore::new_context();
ctx.expect().returning(|_, _| {
let mut seq = Sequence::new();
let mut mock_path_store = MockPathStore::default();
mock_path_store
.expect_start_watcher()
.in_sequence(&mut seq)
.returning(|| ());
mock_path_store
.expect_watch()
.in_sequence(&mut seq)
.with(predicate::eq(PathBuf::from(path.to_string())))
.returning(|_| Ok(()));
Ok(mock_path_store)
});

let backend = init_lsp_backend_default().await?;

backend.watch_path(PathBuf::from(path))?;

Ok(())
}

#[tokio::test]
async fn test_watch_path_failure_path_store_watch_failed() -> Result<()> {
let path = "/path/to/watch";

let ctx = MockPathStore::new_context();
ctx.expect().returning(|_, _| {
let mut seq = Sequence::new();
let mut mock_path_store = MockPathStore::default();
mock_path_store
.expect_start_watcher()
.in_sequence(&mut seq)
.returning(|| ());
mock_path_store
.expect_watch()
.in_sequence(&mut seq)
.with(predicate::eq(PathBuf::from(path.to_string())))
.returning(|_| Err(anyhow!("Failed to watch path")));
Ok(mock_path_store)
});

let backend = init_lsp_backend_default().await?;

assert_eq!(
backend
.watch_path(PathBuf::from(path))
.unwrap_err()
.to_string(),
"Failed to watch path"
);

Ok(())
}

#[tokio::test]
async fn test_unwatch_path_success() -> Result<()> {
let path = "/path/to/watch";

let ctx = MockPathStore::new_context();
ctx.expect().returning(|_, _| {
let mut seq = Sequence::new();
let mut mock_path_store = MockPathStore::default();
mock_path_store
.expect_start_watcher()
.in_sequence(&mut seq)
.returning(|| ());
mock_path_store
.expect_unwatch()
.in_sequence(&mut seq)
.with(predicate::eq(PathBuf::from(path.to_string())))
.returning(|_| Ok(()));
Ok(mock_path_store)
});

let backend = init_lsp_backend_default().await?;

backend.unwatch_path(Path::new(path))?;

Ok(())
}

#[tokio::test]
async fn test_unwatch_path_failure_path_store_watch_failed() -> Result<()> {
let path = "/path/to/watch";

let ctx = MockPathStore::new_context();
ctx.expect().returning(|_, _| {
let mut seq = Sequence::new();
let mut mock_path_store = MockPathStore::default();
mock_path_store
.expect_start_watcher()
.in_sequence(&mut seq)
.returning(|| ());
mock_path_store
.expect_unwatch()
.in_sequence(&mut seq)
.with(predicate::eq(PathBuf::from(path.to_string())))
.returning(|_| Err(anyhow!("Failed to unwatch path")));
Ok(mock_path_store)
});

let backend = init_lsp_backend_default().await?;

assert_eq!(
backend
.unwatch_path(Path::new(path))
.unwrap_err()
.to_string(),
"Failed to unwatch path"
);

Ok(())
}
}
12 changes: 6 additions & 6 deletions lsp/src/mocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ mock! {
pub fn show_message(&self, msg_type: MessageType, message: String) -> impl Future<Output = ()> + Send + Sync;
}

impl fmt::Debug for LspClient {
fn fmt<'a>(&self, f: &mut std::fmt::Formatter<'a>) -> std::fmt::Result {
f.debug_struct("LspClient").finish()
}
}

impl Clone for LspClient {
fn clone(&self) -> Self {
Self::default()
}
}
}

impl fmt::Debug for MockLspClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LspClient").finish()
}
}
17 changes: 9 additions & 8 deletions lsp/src/watching/path_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct PathStore {
watched_set: WatchedSet,
}

#[cfg_attr(test, mockall::automock)]
impl PathStore {
pub fn new(sync_client: Arc<dyn SyncClient>, lsp_client: Arc<LspClient>) -> Result<Self> {
let event_handler = Box::new(move |event| {
Expand Down Expand Up @@ -112,7 +113,7 @@ mod tests {
};

#[test]
fn test_successful_creation() {
fn test_creation_success() {
let ctx = MockWatchedSet::new_context();
ctx.expect().returning(|_| Ok(MockWatchedSet::default()));

Expand All @@ -126,7 +127,7 @@ mod tests {
}

#[test]
fn test_unsuccessful_creation_when_watched_set_creation_failed() {
fn test_creation_failure_when_watched_set_creation_failed() {
let ctx = MockWatchedSet::new_context();
ctx.expect()
.returning(|_| Err(anyhow!("Failed to create watched set")));
Expand Down Expand Up @@ -184,7 +185,7 @@ mod tests {
}

#[test]
fn test_successful_watch_path() -> Result<()> {
fn test_watch_path_success() -> Result<()> {
let dir = TempDir::new()?;
dir.child("foobar").touch()?;
let path = dir.path().to_path_buf();
Expand All @@ -203,7 +204,7 @@ mod tests {
}

#[test]
fn test_unsuccessful_watch_path() -> Result<()> {
fn test_watch_path_failure() -> Result<()> {
let dir = TempDir::new()?;
dir.child("foobar").touch()?;
let path = dir.path().to_path_buf();
Expand All @@ -226,7 +227,7 @@ mod tests {
}

#[test]
fn test_successful_unwatch_path() -> Result<()> {
fn test_unwatch_path_success() -> Result<()> {
let dir = TempDir::new()?;
dir.child("foobar").touch()?;
let path = dir.path().to_path_buf();
Expand All @@ -245,7 +246,7 @@ mod tests {
}

#[test]
fn test_unsuccessful_unwatch_path() -> Result<()> {
fn test_unwatch_path_failure() -> Result<()> {
let dir = TempDir::new()?;
dir.child("foobar").touch()?;
let path = dir.path().to_path_buf();
Expand Down Expand Up @@ -303,7 +304,7 @@ mod tests {
}

#[test]
fn test_modify_event_handling_without_modified_path() -> Result<()> {
fn test_modify_event_without_modified_path_notification() -> Result<()> {
let ctx = MockWatchedSet::new_context();
ctx.expect().returning(move |event_handler: EventHandler| {
let event = Event::new(EventKind::Modify(ModifyKind::Data(DataChange::Any)));
Expand Down Expand Up @@ -335,7 +336,7 @@ mod tests {
}

#[test]
fn test_modify_event_handling_with_file_read_error() -> Result<()> {
fn test_modify_event_with_file_read_error_notification() -> Result<()> {
let ctx = MockWatchedSet::new_context();
ctx.expect().returning(move |event_handler: EventHandler| {
let mut event = Event::new(EventKind::Modify(ModifyKind::Data(DataChange::Any)));
Expand Down
Loading