Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/ty_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,16 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["chrono"] }

[dev-dependencies]
ty_server = { workspace = true, features = ["testing"] }

dunce = { workspace = true }
insta = { workspace = true, features = ["filters", "json"] }
regex = { workspace = true }
tempfile = { workspace = true }

[features]
testing = []

Comment on lines +41 to +50

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is still required because we conditionally add the serde::Serialize attribute to the ClientOptions and other nested structs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use assert_debug_snapshot instead so that we don't need the serde serialization?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That is not pretty ;)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it? Isn't it almost the same as JSON (unless we add some manual implementations):

https://insta.rs/docs/serializers/#debug

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would also be fine to simply always derive Serialize for those types. It's not that many and it simplifies the crate setup

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, I think implementing Serialize unconditionally seems fine, I'll do it as a quick follow up tomorrow morning.

[target.'cfg(target_vendor = "apple")'.dependencies]
libc = { workspace = true }

Expand Down
7 changes: 3 additions & 4 deletions crates/ty_server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use anyhow::Context;
use lsp_server::Connection;
use ruff_db::system::{OsSystem, SystemPathBuf};

use crate::server::Server;
pub use crate::logging::{LogLevel, init_logging};
pub use crate::server::Server;
pub use crate::session::ClientOptions;
Comment on lines +7 to +9

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These are required by the mock server implementation.

pub use document::{NotebookDocument, PositionEncoding, TextDocument};
pub(crate) use session::{DocumentQuery, Session};

Expand All @@ -14,9 +16,6 @@ mod server;
mod session;
mod system;

#[cfg(test)]
pub mod test;

pub(crate) const SERVER_NAME: &str = "ty";
pub(crate) const DIAGNOSTIC_NAME: &str = "ty";

Expand Down
4 changes: 2 additions & 2 deletions crates/ty_server/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tracing_subscriber::fmt::time::ChronoLocal;
use tracing_subscriber::fmt::writer::BoxMakeWriter;
use tracing_subscriber::layer::SubscriberExt;

pub(crate) fn init_logging(log_level: LogLevel, log_file: Option<&SystemPath>) {
pub fn init_logging(log_level: LogLevel, log_file: Option<&SystemPath>) {
let log_file = log_file
.map(|path| {
// this expands `logFile` so that tildes and environment variables
Expand Down Expand Up @@ -66,7 +66,7 @@ pub(crate) fn init_logging(log_level: LogLevel, log_file: Option<&SystemPath>) {
/// The default log level is `info`.
#[derive(Clone, Copy, Debug, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub(crate) enum LogLevel {
pub enum LogLevel {
Error,
Warn,
#[default]
Expand Down
92 changes: 3 additions & 89 deletions crates/ty_server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub(crate) use api::publish_settings_diagnostics;
pub(crate) use main_loop::{Action, ConnectionSender, Event, MainLoopReceiver, MainLoopSender};
pub(crate) type Result<T> = std::result::Result<T, api::Error>;

pub(crate) struct Server {
pub struct Server {
connection: Connection,
client_capabilities: ClientCapabilities,
worker_threads: NonZeroUsize,
Expand All @@ -36,7 +36,7 @@ pub(crate) struct Server {
}

impl Server {
pub(crate) fn new(
pub fn new(
worker_threads: NonZeroUsize,
connection: Connection,
native_system: Arc<dyn System + 'static + Send + Sync + RefUnwindSafe>,
Expand Down Expand Up @@ -161,7 +161,7 @@ impl Server {
})
}

pub(crate) fn run(mut self) -> crate::Result<()> {
pub fn run(mut self) -> crate::Result<()> {
let client = Client::new(
self.main_loop_sender.clone(),
self.connection.sender.clone(),
Expand Down Expand Up @@ -302,89 +302,3 @@ impl Drop for ServerPanicHookHandler {
}
}
}

#[cfg(test)]
mod tests {
use anyhow::Result;
use lsp_types::notification::PublishDiagnostics;
use ruff_db::system::SystemPath;

use crate::session::ClientOptions;
use crate::test::TestServerBuilder;

#[test]
fn initialization() -> Result<()> {
let server = TestServerBuilder::new()?
.build()?
.wait_until_workspaces_are_initialized()?;

let initialization_result = server.initialization_result().unwrap();

insta::assert_json_snapshot!("initialization", initialization_result);

Ok(())
}

#[test]
fn initialization_with_workspace() -> Result<()> {
let workspace_root = SystemPath::new("foo");
let server = TestServerBuilder::new()?
.with_workspace(workspace_root, ClientOptions::default())?
.build()?
.wait_until_workspaces_are_initialized()?;

let initialization_result = server.initialization_result().unwrap();

insta::assert_json_snapshot!("initialization_with_workspace", initialization_result);

Ok(())
}

#[test]
fn publish_diagnostics_on_did_open() -> Result<()> {
let workspace_root = SystemPath::new("src");
let foo = SystemPath::new("src/foo.py");
let foo_content = "\
def foo() -> str:
return 42
";

let mut server = TestServerBuilder::new()?
.with_workspace(workspace_root, ClientOptions::default())?
.with_file(foo, foo_content)?
.enable_pull_diagnostics(false)
.build()?
.wait_until_workspaces_are_initialized()?;

server.open_text_document(foo, &foo_content, 1);
let diagnostics = server.await_notification::<PublishDiagnostics>()?;

insta::assert_debug_snapshot!(diagnostics);

Ok(())
}

#[test]
fn pull_diagnostics_on_did_open() -> Result<()> {
let workspace_root = SystemPath::new("src");
let foo = SystemPath::new("src/foo.py");
let foo_content = "\
def foo() -> str:
return 42
";

let mut server = TestServerBuilder::new()?
.with_workspace(workspace_root, ClientOptions::default())?
.with_file(foo, foo_content)?
.enable_pull_diagnostics(true)
.build()?
.wait_until_workspaces_are_initialized()?;

server.open_text_document(foo, &foo_content, 1);
let diagnostics = server.document_diagnostic_request(foo)?;

insta::assert_debug_snapshot!(diagnostics);

Ok(())
}
}
3 changes: 2 additions & 1 deletion crates/ty_server/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use ty_project::{ChangeResult, Db as _, ProjectDatabase, ProjectMetadata};

pub(crate) use self::capabilities::ResolvedClientCapabilities;
pub(crate) use self::index::DocumentQuery;
pub(crate) use self::options::{AllOptions, ClientOptions, DiagnosticMode};
pub use self::options::ClientOptions;
pub(crate) use self::options::{AllOptions, DiagnosticMode};
pub(crate) use self::settings::ClientSettings;
use crate::document::{DocumentKey, DocumentVersion, NotebookDocument};
use crate::server::publish_settings_diagnostics;
Expand Down
29 changes: 19 additions & 10 deletions crates/ty_server/src/session/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ struct WorkspaceOptions {

/// This is a direct representation of the settings schema sent by the client.
#[derive(Clone, Debug, Deserialize, Default)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
pub(crate) struct ClientOptions {
pub struct ClientOptions {
/// Settings under the `python.*` namespace in VS Code that are useful for the ty language
/// server.
python: Option<Python>,
Expand All @@ -63,7 +64,8 @@ pub(crate) struct ClientOptions {

/// Diagnostic mode for the language server.
#[derive(Clone, Copy, Debug, Default, Deserialize)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
pub(crate) enum DiagnosticMode {
/// Check only currently open files.
Expand Down Expand Up @@ -147,21 +149,24 @@ impl ClientOptions {
// all settings and not just the ones in "python.*".

#[derive(Clone, Debug, Deserialize, Default)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
struct Python {
ty: Option<Ty>,
}

#[derive(Clone, Debug, Deserialize, Default)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
struct PythonExtension {
active_environment: Option<ActiveEnvironment>,
}

#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
pub(crate) struct ActiveEnvironment {
pub(crate) executable: PythonExecutable,
Expand All @@ -170,7 +175,8 @@ pub(crate) struct ActiveEnvironment {
}

#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
pub(crate) struct EnvironmentVersion {
pub(crate) major: i64,
Expand All @@ -182,7 +188,8 @@ pub(crate) struct EnvironmentVersion {
}

#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
pub(crate) struct PythonEnvironment {
pub(crate) folder_uri: Url,
Expand All @@ -194,7 +201,8 @@ pub(crate) struct PythonEnvironment {
}

#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
pub(crate) struct PythonExecutable {
#[allow(dead_code)]
Expand All @@ -203,7 +211,8 @@ pub(crate) struct PythonExecutable {
}

#[derive(Clone, Debug, Deserialize, Default)]
#[cfg_attr(test, derive(serde::Serialize, PartialEq, Eq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "testing", derive(serde::Serialize))]
#[serde(rename_all = "camelCase")]
struct Ty {
disable_language_services: Option<bool>,
Expand Down
33 changes: 33 additions & 0 deletions crates/ty_server/tests/e2e/initialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use anyhow::Result;
use ruff_db::system::SystemPath;
use ty_server::ClientOptions;

use crate::TestServerBuilder;

#[test]
fn empty_workspace_folders() -> Result<()> {
let server = TestServerBuilder::new()?
.build()?
.wait_until_workspaces_are_initialized()?;

let initialization_result = server.initialization_result().unwrap();

insta::assert_json_snapshot!("initialization", initialization_result);

Ok(())
}

#[test]
fn single_workspace_folder() -> Result<()> {
let workspace_root = SystemPath::new("foo");
let server = TestServerBuilder::new()?
.with_workspace(workspace_root, ClientOptions::default())?
.build()?
.wait_until_workspaces_are_initialized()?;

let initialization_result = server.initialization_result().unwrap();

insta::assert_json_snapshot!("initialization_with_workspace", initialization_result);

Ok(())
}
Loading
Loading