use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use serde_json::json;
#[allow(unused_imports)]
use lspf::{
BuildError, CallKind, CancellationToken, ClientError, ClientHandle, ConnectionDirection,
ConnectionFailure, ConnectionFailureCategory, ConnectionFailureContext, ConnectionRequestId,
DEFAULT_CONCURRENCY_LIMIT, Document, DocumentsView, Error, FeatureSpec, FileProvider,
IncomingCall, JsonRpcError, Layer, LspError, MemoryFileProvider, Next, Notebook, NotebooksView,
NotificationFeatureSpec, PartialResultRequest, PartialResultSink, PositionEncoding,
ProgressError, ProgressHandle, ProgressOptions, RawMessage, RequestId, ResourcePolicy,
ResourcePolicyField, Result, Server, ServerBuilder, ServerContext, ServiceFuture,
ServiceResult, SharedHandler, TaskFuture, TaskSend, TelemetryEventParams, Transport,
TransportError, TransportReader, TransportWriter, Workspace, WorkspaceError, features, types,
};
#[allow(unused_imports)]
use lspf::InitializeRegistrar;
#[allow(unused_imports)]
#[cfg(feature = "runtime-tokio")]
use lspf::{Client, ClientBuilder, ClientConnection, ClientContext, Outcome, ServerHandle};
#[allow(unused_imports)]
#[cfg(feature = "runtime-tokio")]
use lspf::{OsFileProvider, OsFileProviderBuilder};
#[allow(unused_imports)]
#[cfg(feature = "stdio")]
use lspf::{
ChildConnection, ChildError, ChildOutput, StdioBuilder, StdioReader, StdioTransport,
StdioWriter, stdio,
};
#[allow(unused_imports)]
#[cfg(feature = "tcp")]
use lspf::{TcpBuilder, TcpReader, TcpTransport, TcpWriter, tcp};
#[allow(unused_imports)]
#[cfg(feature = "websocket")]
use lspf::{WebSocketBuilder, WebSocketReader, WebSocketTransport, WebSocketWriter, websocket};
use lspf::testing::{
ClientJourney, JourneyError, MemoryReader, MemoryTransport, MemoryWriter, ScriptedPeer,
ServerJourney, VirtualClock, WireCapture, WireDirection, WireEvent,
};
use lspf::types::request::Request;
#[allow(unused_imports)]
use lspf::types::{
ApplyWorkspaceEditResponse, CodeActionOrCommand, ColorProviderOptions,
DiagnosticServerCapabilities, DocumentDiagnosticReportResult, GotoDefinitionParams,
GotoDefinitionResponse, InlayHintLabel, InlayHintTooltip, PrepareRenameResponse,
ReferencesOptions, SemanticTokenModifier, SemanticTokenType, SemanticTokensFullDeltaResult,
SemanticTokensRangeResult, SemanticTokensResult, TextDocumentSyncCapability,
TextDocumentSyncSaveOptions, WorkspaceDiagnosticReportResult, WorkspaceServerCapabilities,
};
use lspf::types::{
ClientCapabilities, Hover, HoverContents, HoverParams, MarkupContent, MarkupKind,
};
struct FrozenState;
struct CountingLayer {
requests: Arc<AtomicUsize>,
}
impl Layer<FrozenState> for CountingLayer {
fn call(&self, call: IncomingCall<FrozenState>, next: Next<FrozenState>) -> ServiceFuture {
if call.kind() == CallKind::Request {
self.requests.fetch_add(1, Ordering::SeqCst);
}
Box::pin(async move { next.call(call).await })
}
}
async fn hover(
_state: Arc<FrozenState>,
ctx: ServerContext,
params: HoverParams,
_cancel: CancellationToken,
) -> std::result::Result<Option<Hover>, LspError> {
let uri = params.text_document_position_params.text_document.uri;
let documents: DocumentsView = ctx.documents();
let words = documents.get(&uri).map_or(0, |document: Document| {
document.text().split_whitespace().count()
});
Ok(Some(Hover {
contents: HoverContents::MarkupContent(MarkupContent {
kind: MarkupKind::PlainText,
value: words.to_string(),
}),
range: None,
}))
}
#[tokio::test]
async fn a_downstream_crate_drives_the_frozen_server_surface() {
let layered_requests = Arc::new(AtomicUsize::new(0));
let server = Server::builder(FrozenState)
.file_provider(MemoryFileProvider::new())
.resource_policy(ResourcePolicy {
max_outbound_messages: 32,
..ResourcePolicy::default()
})
.concurrency_limit(DEFAULT_CONCURRENCY_LIMIT)
.layer(CountingLayer {
requests: Arc::clone(&layered_requests),
})
.feature(features::hover(), hover)
.build()
.expect("the frozen registration surface builds a server");
let mut journey = ServerJourney::start(server)
.await
.expect("the frozen journey initializes the server");
let capture: WireCapture = journey.capture();
let peer: &mut ScriptedPeer = journey.peer();
peer.send(RawMessage::Request {
id: RequestId::Number(42),
method: std::borrow::Cow::Borrowed(<types::request::HoverRequest as Request>::METHOD),
params: bytes::Bytes::from(
serde_json::to_vec(&json!({
"textDocument": {"uri": "file:///frozen.txt"},
"position": {"line": 0, "character": 0}
}))
.expect("hover params serialize"),
),
})
.expect("the scripted peer sends a hover request");
let response = peer.recv().await.expect("the server answers the request");
let RawMessage::Response { id, result } = response else {
panic!("a hover request is answered with a response");
};
assert_eq!(
id,
RequestId::Number(42),
"the response echoes the request id"
);
let hover: Hover = serde_json::from_slice(&result.expect("the handler succeeded"))
.expect("the response carries the handler's typed Hover");
assert!(
matches!(hover.contents, HoverContents::MarkupContent(markup) if markup.value == "0"),
"an unopened document has no text, so the handler counts zero words"
);
let outcome: Outcome = journey
.finish()
.await
.expect("shutdown and exit complete the journey");
assert_eq!(outcome.code(), 0, "a clean shutdown reports exit code 0");
let traffic: Vec<WireEvent> = capture.snapshot();
assert_eq!(
traffic.first().map(WireEvent::direction),
Some(WireDirection::PeerToEndpoint),
"the initialize request is the first message across the seam"
);
assert!(
traffic.iter().any(|event| event.sequence() > 0),
"the capture preserves ordering across both directions"
);
assert_eq!(
layered_requests.load(Ordering::SeqCst),
1,
"the registered Layer wrapped the hover request and nothing else"
);
}
#[tokio::test]
async fn a_downstream_crate_drives_the_frozen_client_surface() {
let builder: ClientBuilder = Client::builder(ClientCapabilities::default());
let mut journey = ClientJourney::start(builder)
.await
.expect("the frozen journey initializes the client");
let handle: ServerHandle = journey.server();
let capture: WireCapture = journey.capture();
handle
.notify::<types::notification::DidChangeConfiguration>(
types::DidChangeConfigurationParams {
settings: json!({"frozen": true}),
},
)
.expect("a typed notification enqueues through the frozen handle");
let forwarded = journey
.peer()
.recv()
.await
.expect("the peer observes the notification");
assert_eq!(
forwarded.method(),
Some(<types::notification::DidChangeConfiguration as lspf::types::notification::Notification>::METHOD)
);
let outcome: Outcome = journey
.finish()
.await
.expect("shutdown and exit complete the client journey");
assert_eq!(outcome.code(), 0);
assert!(!capture.snapshot().is_empty());
}
#[test]
fn frozen_values_are_constructible_from_downstream_code() {
assert_eq!(DEFAULT_CONCURRENCY_LIMIT, 64);
assert_eq!(PositionEncoding::default(), PositionEncoding::Utf16);
let error = LspError::internal("frozen");
assert_eq!(error.code(), -32603);
assert_eq!(error.message(), "frozen");
let wire = RawMessage::ProtocolError {
error: JsonRpcError {
code: -32700,
message: "frozen".to_string(),
data: Some(json!({"kind": "parse"})),
},
};
assert_eq!(wire.method(), None);
let policy = ResourcePolicy::default();
assert!(policy.max_inbound_requests > 0);
let build_error = Server::builder(FrozenState)
.resource_policy(ResourcePolicy {
max_outbound_messages: 0,
..ResourcePolicy::default()
})
.build()
.err()
.expect("a zero outbound budget is rejected at build time");
assert_eq!(
build_error,
BuildError::InvalidResourcePolicy {
field: ResourcePolicyField::MaxOutboundMessages
}
);
}
#[tokio::test]
async fn the_frozen_transport_seam_splits_into_named_halves() {
let (transport, mut peer): (MemoryTransport, ScriptedPeer) = MemoryTransport::pair();
let (mut reader, mut writer): (MemoryReader, MemoryWriter) = transport.split();
peer.send(RawMessage::Notification {
method: std::borrow::Cow::Borrowed("frozen/inbound"),
params: bytes::Bytes::from_static(b"{}"),
})
.expect("the peer writes into the seam");
let inbound = reader.recv().await.expect("the reader half receives it");
assert_eq!(inbound.method(), Some("frozen/inbound"));
writer
.send(RawMessage::Notification {
method: std::borrow::Cow::Borrowed("frozen/outbound"),
params: bytes::Bytes::from_static(b"{}"),
})
.await
.expect("the writer half sends back");
assert_eq!(
peer.recv().await.expect("the peer receives it").method(),
Some("frozen/outbound")
);
let failure: TransportError = TransportError::Closed;
assert!(!failure.to_string().is_empty());
}
#[tokio::test(flavor = "current_thread", start_paused = false)]
async fn the_frozen_testing_clock_controls_connection_deadlines() {
let clock = VirtualClock::pause();
clock.advance(std::time::Duration::from_millis(50)).await;
clock.resume();
let error: JourneyError = JourneyError::from(lspf::Error::from(TransportError::Closed));
assert!(!error.to_string().is_empty());
}