Skip to content

Commit 332c1cc

Browse files
committed
Test publish and pull diagnostics
1 parent 72b26f2 commit 332c1cc

File tree

4 files changed

+302
-26
lines changed

4 files changed

+302
-26
lines changed

crates/ty_server/src/server.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,9 @@ impl Drop for ServerPanicHookHandler {
301301

302302
#[cfg(test)]
303303
mod tests {
304-
use ruff_db::system::{InMemorySystem, SystemPathBuf};
304+
use anyhow::Result;
305+
use lsp_types::notification::PublishDiagnostics;
306+
use ruff_db::system::{InMemorySystem, MemoryFileSystem, SystemPathBuf};
305307

306308
use crate::session::ClientOptions;
307309
use crate::test::TestServerBuilder;
@@ -337,4 +339,58 @@ mod tests {
337339

338340
insta::assert_json_snapshot!("initialization_with_workspace", initialization_result);
339341
}
342+
343+
#[test]
344+
fn publish_diagnostics_on_did_open() -> Result<()> {
345+
let workspace_root = SystemPathBuf::from("/src");
346+
347+
let fs = MemoryFileSystem::with_current_directory(&workspace_root);
348+
let foo = workspace_root.join("foo.py");
349+
let foo_content = "\
350+
def foo() -> str:
351+
return 42
352+
";
353+
fs.write_file(&foo, foo_content)?;
354+
355+
let mut server = TestServerBuilder::new()
356+
.with_memory_system(InMemorySystem::from_memory_fs(fs))
357+
.with_workspace(&workspace_root, ClientOptions::default())
358+
.with_pull_diagnostics(false)
359+
.build()?
360+
.wait_until_workspaces_are_initialized()?;
361+
362+
server.open_text_document(&foo, &foo_content)?;
363+
let diagnostics = server.get_notification::<PublishDiagnostics>()?;
364+
365+
insta::assert_debug_snapshot!(diagnostics);
366+
367+
Ok(())
368+
}
369+
370+
#[test]
371+
fn pull_diagnostics_on_did_open() -> Result<()> {
372+
let workspace_root = SystemPathBuf::from("/src");
373+
374+
let fs = MemoryFileSystem::with_current_directory(&workspace_root);
375+
let foo = workspace_root.join("foo.py");
376+
let foo_content = "\
377+
def foo() -> str:
378+
return 42
379+
";
380+
fs.write_file(&foo, foo_content)?;
381+
382+
let mut server = TestServerBuilder::new()
383+
.with_memory_system(InMemorySystem::from_memory_fs(fs))
384+
.with_workspace(&workspace_root, ClientOptions::default())
385+
.with_pull_diagnostics(true)
386+
.build()?
387+
.wait_until_workspaces_are_initialized()?;
388+
389+
server.open_text_document(&foo, &foo_content)?;
390+
let diagnostics = server.document_diagnostic_request(&foo)?;
391+
392+
insta::assert_debug_snapshot!(diagnostics);
393+
394+
Ok(())
395+
}
340396
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
source: crates/ty_server/src/server.rs
3+
expression: diagnostics
4+
---
5+
PublishDiagnosticsParams {
6+
uri: Url {
7+
scheme: "file",
8+
cannot_be_a_base: false,
9+
username: "",
10+
password: None,
11+
host: None,
12+
port: None,
13+
path: "/src/foo.py",
14+
query: None,
15+
fragment: None,
16+
},
17+
diagnostics: [
18+
Diagnostic {
19+
range: Range {
20+
start: Position {
21+
line: 1,
22+
character: 11,
23+
},
24+
end: Position {
25+
line: 1,
26+
character: 13,
27+
},
28+
},
29+
severity: Some(
30+
Error,
31+
),
32+
code: Some(
33+
String(
34+
"invalid-return-type",
35+
),
36+
),
37+
code_description: Some(
38+
CodeDescription {
39+
href: Url {
40+
scheme: "https",
41+
cannot_be_a_base: false,
42+
username: "",
43+
password: None,
44+
host: Some(
45+
Domain(
46+
"ty.dev",
47+
),
48+
),
49+
port: None,
50+
path: "/rules",
51+
query: None,
52+
fragment: Some(
53+
"invalid-return-type",
54+
),
55+
},
56+
},
57+
),
58+
source: Some(
59+
"ty",
60+
),
61+
message: "Return type does not match returned value: expected `str`, found `Literal[42]`",
62+
related_information: Some(
63+
[
64+
DiagnosticRelatedInformation {
65+
location: Location {
66+
uri: Url {
67+
scheme: "file",
68+
cannot_be_a_base: false,
69+
username: "",
70+
password: None,
71+
host: None,
72+
port: None,
73+
path: "/src/foo.py",
74+
query: None,
75+
fragment: None,
76+
},
77+
range: Range {
78+
start: Position {
79+
line: 0,
80+
character: 13,
81+
},
82+
end: Position {
83+
line: 0,
84+
character: 16,
85+
},
86+
},
87+
},
88+
message: "Expected `str` because of return type",
89+
},
90+
],
91+
),
92+
tags: None,
93+
data: None,
94+
},
95+
],
96+
version: Some(
97+
1,
98+
),
99+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
source: crates/ty_server/src/server.rs
3+
expression: diagnostics
4+
---
5+
Report(
6+
Full(
7+
RelatedFullDocumentDiagnosticReport {
8+
related_documents: None,
9+
full_document_diagnostic_report: FullDocumentDiagnosticReport {
10+
result_id: None,
11+
items: [
12+
Diagnostic {
13+
range: Range {
14+
start: Position {
15+
line: 1,
16+
character: 11,
17+
},
18+
end: Position {
19+
line: 1,
20+
character: 13,
21+
},
22+
},
23+
severity: Some(
24+
Error,
25+
),
26+
code: Some(
27+
String(
28+
"invalid-return-type",
29+
),
30+
),
31+
code_description: Some(
32+
CodeDescription {
33+
href: Url {
34+
scheme: "https",
35+
cannot_be_a_base: false,
36+
username: "",
37+
password: None,
38+
host: Some(
39+
Domain(
40+
"ty.dev",
41+
),
42+
),
43+
port: None,
44+
path: "/rules",
45+
query: None,
46+
fragment: Some(
47+
"invalid-return-type",
48+
),
49+
},
50+
},
51+
),
52+
source: Some(
53+
"ty",
54+
),
55+
message: "Return type does not match returned value: expected `str`, found `Literal[42]`",
56+
related_information: Some(
57+
[
58+
DiagnosticRelatedInformation {
59+
location: Location {
60+
uri: Url {
61+
scheme: "file",
62+
cannot_be_a_base: false,
63+
username: "",
64+
password: None,
65+
host: None,
66+
port: None,
67+
path: "/src/foo.py",
68+
query: None,
69+
fragment: None,
70+
},
71+
range: Range {
72+
start: Position {
73+
line: 0,
74+
character: 13,
75+
},
76+
end: Position {
77+
line: 0,
78+
character: 16,
79+
},
80+
},
81+
},
82+
message: "Expected `str` because of return type",
83+
},
84+
],
85+
),
86+
tags: None,
87+
data: None,
88+
},
89+
],
90+
},
91+
},
92+
),
93+
)

crates/ty_server/src/test.rs

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,22 @@ use std::time::Duration;
1515

1616
use anyhow::Result;
1717
use lsp_server::{Connection, Message, RequestId, Response, ResponseError};
18-
use lsp_types::notification::{DidOpenTextDocument, Exit, Initialized, Notification};
19-
use lsp_types::request::{Initialize, Request, Shutdown, WorkspaceConfiguration};
18+
use lsp_types::notification::{
19+
DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument, Exit,
20+
Initialized, Notification,
21+
};
22+
use lsp_types::request::{
23+
DocumentDiagnosticRequest, Initialize, Request, Shutdown, WorkspaceConfiguration,
24+
};
2025
use lsp_types::{
2126
ClientCapabilities, ConfigurationParams, DiagnosticClientCapabilities,
22-
DidChangeWatchedFilesClientCapabilities, FileEvent, InitializeParams, InitializeResult,
23-
InitializedParams, PublishDiagnosticsClientCapabilities, RegistrationParams,
24-
TextDocumentClientCapabilities, TextDocumentContentChangeEvent, Url,
25-
WorkspaceClientCapabilities, WorkspaceFolder,
27+
DidChangeTextDocumentParams, DidChangeWatchedFilesClientCapabilities,
28+
DidChangeWatchedFilesParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
29+
DocumentDiagnosticParams, DocumentDiagnosticReportResult, FileEvent, InitializeParams,
30+
InitializeResult, InitializedParams, PartialResultParams, PublishDiagnosticsClientCapabilities,
31+
RegistrationParams, TextDocumentClientCapabilities, TextDocumentContentChangeEvent,
32+
TextDocumentIdentifier, TextDocumentItem, Url, VersionedTextDocumentIdentifier,
33+
WorkDoneProgressParams, WorkspaceClientCapabilities, WorkspaceFolder,
2634
};
2735
use ruff_db::system::{InMemorySystem, SystemPath, TestSystem};
2836
use serde::de::DeserializeOwned;
@@ -289,7 +297,6 @@ impl TestServer {
289297
/// The caller should ensure that the server is expected to send this notification type. It
290298
/// will keep polling the server for notifications up to 10 times before giving up. It can
291299
/// return an error if the notification is not received within `recv_timeout` duration.
292-
#[expect(dead_code)]
293300
pub(crate) fn get_notification<N: Notification>(&mut self) -> Result<N::Params> {
294301
for _ in 0..10 {
295302
self.receive()?;
@@ -474,14 +481,17 @@ impl TestServer {
474481
}
475482

476483
/// Send a `textDocument/didOpen` notification
477-
#[expect(dead_code)]
478-
pub(crate) fn open_text_document(&mut self, uri: Url, content: String) -> Result<()> {
479-
let params = lsp_types::DidOpenTextDocumentParams {
480-
text_document: lsp_types::TextDocumentItem {
481-
uri,
484+
pub(crate) fn open_text_document(
485+
&mut self,
486+
path: impl AsRef<SystemPath>,
487+
content: &impl ToString,
488+
) -> Result<()> {
489+
let params = DidOpenTextDocumentParams {
490+
text_document: TextDocumentItem {
491+
uri: Url::from_file_path(path.as_ref()).expect("Path must be a valid URL"),
482492
language_id: "python".to_string(),
483493
version: self.next_document_version(),
484-
text: content,
494+
text: content.to_string(),
485495
},
486496
};
487497
self.send_notification::<DidOpenTextDocument>(params)
@@ -491,33 +501,52 @@ impl TestServer {
491501
#[expect(dead_code)]
492502
pub(crate) fn change_text_document(
493503
&mut self,
494-
uri: Url,
504+
path: impl AsRef<SystemPath>,
495505
changes: Vec<TextDocumentContentChangeEvent>,
496506
) -> Result<()> {
497-
let params = lsp_types::DidChangeTextDocumentParams {
498-
text_document: lsp_types::VersionedTextDocumentIdentifier {
499-
uri,
507+
let params = DidChangeTextDocumentParams {
508+
text_document: VersionedTextDocumentIdentifier {
509+
uri: Url::from_file_path(path.as_ref()).expect("Path must be a valid URL"),
500510
version: self.next_document_version(),
501511
},
502512
content_changes: changes,
503513
};
504-
self.send_notification::<lsp_types::notification::DidChangeTextDocument>(params)
514+
self.send_notification::<DidChangeTextDocument>(params)
505515
}
506516

507517
/// Send a `textDocument/didClose` notification
508518
#[expect(dead_code)]
509-
pub(crate) fn close_text_document(&mut self, uri: Url) -> Result<()> {
510-
let params = lsp_types::DidCloseTextDocumentParams {
511-
text_document: lsp_types::TextDocumentIdentifier { uri },
519+
pub(crate) fn close_text_document(&mut self, path: impl AsRef<SystemPath>) -> Result<()> {
520+
let params = DidCloseTextDocumentParams {
521+
text_document: TextDocumentIdentifier {
522+
uri: Url::from_file_path(path.as_ref()).expect("Path must be a valid URL"),
523+
},
512524
};
513-
self.send_notification::<lsp_types::notification::DidCloseTextDocument>(params)
525+
self.send_notification::<DidCloseTextDocument>(params)
514526
}
515527

516528
/// Send a `workspace/didChangeWatchedFiles` notification with the given file events
517529
#[expect(dead_code)]
518530
pub(crate) fn did_change_watched_files(&mut self, events: Vec<FileEvent>) -> Result<()> {
519-
let params = lsp_types::DidChangeWatchedFilesParams { changes: events };
520-
self.send_notification::<lsp_types::notification::DidChangeWatchedFiles>(params)
531+
let params = DidChangeWatchedFilesParams { changes: events };
532+
self.send_notification::<DidChangeWatchedFiles>(params)
533+
}
534+
535+
/// Send a `textDocument/diagnostic` request for the document at the given path.
536+
pub(crate) fn document_diagnostic_request(
537+
&mut self,
538+
path: impl AsRef<SystemPath>,
539+
) -> Result<DocumentDiagnosticReportResult> {
540+
let uri = Url::from_file_path(path.as_ref()).expect("Path must be a valid URL");
541+
let params = DocumentDiagnosticParams {
542+
text_document: TextDocumentIdentifier { uri },
543+
identifier: Some("ty".to_string()),
544+
previous_result_id: None,
545+
work_done_progress_params: WorkDoneProgressParams::default(),
546+
partial_result_params: PartialResultParams::default(),
547+
};
548+
let id = self.send_request::<DocumentDiagnosticRequest>(params)?;
549+
self.get_response::<DocumentDiagnosticReportResult>(id)
521550
}
522551
}
523552

@@ -581,7 +610,6 @@ impl TestServerBuilder {
581610
}
582611

583612
/// Enable or disable pull diagnostics capability
584-
#[expect(dead_code)]
585613
pub(crate) fn with_pull_diagnostics(mut self, enabled: bool) -> Self {
586614
self.client_capabilities
587615
.text_document

0 commit comments

Comments
 (0)