From 3ee5471ca2dc810d43863e5ddf0296e19ee7b6d3 Mon Sep 17 00:00:00 2001 From: Arnaud Ritti <77437157+arnaud-ritti@users.noreply.github.com> Date: Sat, 7 Nov 2026 22:29:07 +0100 Subject: [PATCH] fix(test): synchronize plugin diagnostics deadline coverage (#2069) A diagnostics test assumed a cold plugin actor would enter its provider within 10 ms, which failed on slower CI. The fixture is prewarmed and uses synchronous entry gates, so the tests tell a skipped request from a provider that started and hold the provider past the unchanged deadline; adds a test that cancelling a running provider doesn't affect another window's command. Co-authored-by: arnaud-ritti <77437157+arnaud-ritti@users.noreply.github.com> --- crates/ide-host/tests/plugin_diagnostics.rs | 120 +++++++++++++++--- .../fixtures/diagnostics/dist/plugin.wasm | Bin 97682 -> 102194 bytes .../diagnostics/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/diagnostics/src/lib.rs | 6 +- 4 files changed, 105 insertions(+), 23 deletions(-) diff --git a/crates/ide-host/tests/plugin_diagnostics.rs b/crates/ide-host/tests/plugin_diagnostics.rs index 379296785..e12e40b04 100644 --- a/crates/ide-host/tests/plugin_diagnostics.rs +++ b/crates/ide-host/tests/plugin_diagnostics.rs @@ -1,11 +1,11 @@ //! Real Wasm provider dispatch and the document-edit path used locally and over RPC. use ide_core::{CancellationToken, ChangeSet, Diagnostic, IdeEvent, Pos, Range, TextChange}; -use ide_host::plugins::{Consent, HostWorkspace, PluginHost, set_editor_providers}; +use ide_host::plugins::{Consent, HostWorkspace, PluginEvent, PluginHost, set_editor_providers}; use ide_host::{Host, HostConfig, ops}; use ide_plugin::{Invocation, Limits, ProviderDocument}; use parking_lot::Mutex; use std::path::{Path, PathBuf}; -use std::sync::Arc; +use std::sync::{Arc, mpsc}; use std::time::{Duration, Instant}; const ID: &str = "test.diagnostics"; @@ -86,27 +86,110 @@ fn diagnostics_is_an_api_1_3_addition() { assert!(host.install_from_dir(&package, &Consent::all()).unwrap_err().message.contains("new in plugin API 1.3")); } +// The fixture signals from inside a host import, so the actor cannot finish +// until this test releases it. A dropped guard also releases a failing test. +enum FixtureEvent { + Entered, + Returned, +} + +struct FixtureGate { + events: mpsc::Receiver, + finished: mpsc::Sender, + release: mpsc::Sender<()>, +} + +impl FixtureGate { + fn new(dir: &Path, limits: Limits) -> (Arc, Self) { + let (started, events) = mpsc::channel(); + let finished = started.clone(); + let (release, released) = mpsc::channel(); + let released = Mutex::new(released); + let sink = Arc::new(move |_: Option<&str>, event| { + if matches!(event, PluginEvent::ContextChanged { ref plugin_id, ref key, value: Some(_), .. } if plugin_id == ID && key == "fixture-gate") + { + started.send(FixtureEvent::Entered).unwrap(); + released.lock().recv_timeout(Duration::from_secs(5)).unwrap(); + } + }); + let host = Arc::new(PluginHost::new(dir.join("plugins"), sink, limits).unwrap()); + host.install_from_dir(&fixture(), &Consent::all()).unwrap(); + assert_eq!(host.run_command(ID, "calls", None, Invocation::default()).unwrap().as_deref(), Some("0")); + (host, Self { events, finished, release }) + } + + fn entered(&self) { + assert!(matches!(self.events.recv_timeout(Duration::from_secs(5)).unwrap(), FixtureEvent::Entered)); + } + + fn provider( + &self, + host: &Arc, + document: ProviderDocument, + cancel: &CancellationToken, + ) -> std::thread::JoinHandle)>> { + let start = Instant::now(); + loop { + let (host, document, cancel, finished) = + (host.clone(), document.clone(), cancel.clone(), self.finished.clone()); + let call = std::thread::spawn(move || { + let result = host.provide_diagnostics(&document, &cancel); + let _ = finished.send(FixtureEvent::Returned); + result + }); + if matches!(self.events.recv_timeout(Duration::from_secs(5)).unwrap(), FixtureEvent::Entered) { + return call; + } + // Expiry before entry is valid, but cannot exercise a late answer. + assert!(call.join().unwrap().is_empty()); + assert!(start.elapsed() < Duration::from_secs(5), "provider never entered the fixture"); + } + } +} + +impl Drop for FixtureGate { + fn drop(&mut self) { + let _ = self.release.send(()); + } +} + #[test] fn late_and_cancelled_answers_are_dropped_without_disabling_the_plugin() { let tmp = tempfile::tempdir().unwrap(); - let host = plugins(tmp.path(), Limits { edit_soft: Duration::from_millis(10), ..generous() }); - assert!(host.provide_diagnostics(&doc(tmp.path(), "text", "slow BAD"), &CancellationToken::new()).is_empty()); + let soft = Duration::from_millis(10); + let (host, gate) = FixtureGate::new(tmp.path(), Limits { edit_soft: soft, ..generous() }); + let call = gate.provider(&host, doc(tmp.path(), "text", "gate BAD"), &CancellationToken::new()); + // Entry was observed: hold this actual provider beyond its soft deadline. + assert!(matches!(gate.events.recv_timeout(soft), Err(mpsc::RecvTimeoutError::Timeout))); + drop(gate); + assert!(call.join().unwrap().is_empty()); let cancel = CancellationToken::new(); cancel.cancel(); assert!(host.provide_diagnostics(&doc(tmp.path(), "text", "BAD"), &cancel).is_empty()); assert_eq!(host.run_command(ID, "calls", None, Invocation::default()).unwrap().as_deref(), Some("1")); } +#[test] +fn cancelling_a_started_provider_drops_its_answer_without_disabling_the_plugin() { + let tmp = tempfile::tempdir().unwrap(); + let (host, gate) = FixtureGate::new(tmp.path(), generous()); + let cancel = CancellationToken::new(); + let call = gate.provider(&host, doc(tmp.path(), "text", "gate BAD"), &cancel); + cancel.cancel(); + drop(gate); + assert!(call.join().unwrap().is_empty()); + assert_eq!(host.run_command(ID, "calls", None, Invocation::default()).unwrap().as_deref(), Some("1")); +} + #[test] fn cancelling_a_queued_provider_does_not_interrupt_another_windows_command() { let tmp = tempfile::tempdir().unwrap(); - let host = plugins(tmp.path(), generous()); - host.run_command(ID, "calls", None, Invocation::default()).unwrap(); + let (host, gate) = FixtureGate::new(tmp.path(), generous()); let waiter = { let host = host.clone(); std::thread::spawn(move || host.run_command(ID, "wait", None, Invocation::default())) }; - std::thread::sleep(Duration::from_millis(50)); + gate.entered(); let cancel = CancellationToken::new(); let canceller = { let cancel = cancel.clone(); @@ -117,6 +200,7 @@ fn cancelling_a_queued_provider_does_not_interrupt_another_windows_command() { }; assert!(host.provide_diagnostics(&doc(tmp.path(), "text", "BAD"), &cancel).is_empty()); canceller.join().unwrap(); + drop(gate); assert_eq!(waiter.join().unwrap().unwrap().as_deref(), Some("finished")); assert_eq!(host.run_command(ID, "calls", None, Invocation::default()).unwrap().as_deref(), Some("0")); } @@ -124,14 +208,14 @@ fn cancelling_a_queued_provider_does_not_interrupt_another_windows_command() { #[test] fn editor_requests_expire_in_the_queue_without_running_after_a_command() { let tmp = tempfile::tempdir().unwrap(); - let host = plugins(tmp.path(), Limits { edit_soft: Duration::from_millis(10), ..generous() }); - host.run_command(ID, "calls", None, Invocation::default()).unwrap(); + let (host, gate) = FixtureGate::new(tmp.path(), Limits { edit_soft: Duration::from_millis(10), ..generous() }); let waiter = { let host = host.clone(); std::thread::spawn(move || host.run_command(ID, "wait", None, Invocation::default())) }; - std::thread::sleep(Duration::from_millis(50)); + gate.entered(); assert!(host.provide_diagnostics(&doc(tmp.path(), "text", "BAD"), &CancellationToken::new()).is_empty()); + drop(gate); assert_eq!(waiter.join().unwrap().unwrap().as_deref(), Some("finished")); assert_eq!(host.run_command(ID, "calls", None, Invocation::default()).unwrap().as_deref(), Some("0")); assert_eq!(host.info_of(ID).unwrap().stats.slow_calls, 1); @@ -140,20 +224,14 @@ fn editor_requests_expire_in_the_queue_without_running_after_a_command() { #[test] fn typing_on_cancels_a_running_provider_before_its_hard_deadline() { let tmp = tempfile::tempdir().unwrap(); - let host = plugins(tmp.path(), Limits { fuel: u64::MAX, ..generous() }); - host.run_command(ID, "calls", None, Invocation::default()).unwrap(); + let (host, gate) = FixtureGate::new(tmp.path(), Limits { fuel: u64::MAX, ..generous() }); let cancel = CancellationToken::new(); - let canceller = { - let cancel = cancel.clone(); - std::thread::spawn(move || { - std::thread::sleep(Duration::from_millis(30)); - cancel.cancel(); - }) - }; + let call = gate.provider(&host, doc(tmp.path(), "text", "gate spin BAD"), &cancel); let started = Instant::now(); - assert!(host.provide_diagnostics(&doc(tmp.path(), "text", "spin BAD"), &cancel).is_empty()); + cancel.cancel(); + drop(gate); + assert!(call.join().unwrap().is_empty()); assert!(started.elapsed() < Duration::from_secs(2)); - canceller.join().unwrap(); } // This executable has one edit-path test because editor providers are installed once per process. diff --git a/crates/ide-plugin/tests/fixtures/diagnostics/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/diagnostics/dist/plugin.wasm index 3511b2b86bbc177fcd74835a16343145d4f4bdef..9784a1227e1afed3295f8e51b3d5deb8d81f1c55 100644 GIT binary patch delta 23422 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 r$VDojitQj0zK2~To#yR-8qALm1Y0WsF))O zM4fCBLBPIy8qab6@LHl2WY;>STAvGkE!{18kg4{IaX8c}5gzj;&_X=Q#} z6%8wkJC-I298(bmQ`@U22xeP9h2(voxD<79e6G|*)<2FnuV)t|XZ zt~u4U^~rH?3Cy2c0s+vJ;+Anb;&EqNbRn5p#~rH1`dM}5VAZ5rzt1P!-Dw@33$^PJ zuetKmy*ADrG;?`RjQK)J&-AAq3-$7zaTZ-f=^23zBXbmQdC#C#b`j;IhuWwSQ`Y4@ zr3TKU1nY-+kQDZ4SIveN{wq{Glfb8y_e|o_WD7(HsmcB1C70IvMv8|xW$_I@pM5cv zs%cVKs_EUWTjo%%_5Q^auV&=Rgqm5-V!|rD1bJCfWO!(~M(K()Vy$nR=%SSNN<2;) zuCQd-xTK)e$}k$BhYRD6*1QQxM_X96^;Y#+lsYc)SXwqIFuk^VMtwt5bM3UIWM5P_ zq{Ox9iBzp|etnu}W_A7a*){Z=SL>_fHeXO;&6`U(*24KTxX8Xr*OQIh=iVKc#$bP#OUVg=n28@Oly&g+XUH12fHKnJ?cNe%HS|nw zRy7Zz<;ewPWJS5Syk~MpTRz3wyMVHaQ#;(+o0fsr?KvsND(RNKkn&)(lr4n1&a^fx zL=Te};&d`AVr^WAYkv3nLW$W&UF4g?^&-|U3$f&xV^Yg|<{fRx8?t={grKjbyk|JB zZ3^D!`QlY9DnztP%Qyo!<|crK{RZ7$^fXT zmUnRFZfWB5w1Zy0s8!S3hud1EP2MLKt4Ox!TuRP0b!MN2`jWXdjpsKs&Z(I`q;0N; WT6q`HhpzuG6q54(yFx*3uKy3Vo^fsf delta 19689 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 -> Result, Error> { match id { "wait" => { + host::set_context("fixture-gate", "wait").unwrap(); std::thread::sleep(Duration::from_millis(400)); Ok(Some("finished".into())) } @@ -28,6 +29,9 @@ impl Plugin for Fixture { impl DiagnosticsProvider for Fixture { fn diagnostics(&mut self, doc: &Document) -> Vec { self.calls += 1; + if doc.text.starts_with("gate") { + host::set_context("fixture-gate", self.calls.to_string()).unwrap(); + } if doc.text == "limits" { let mut diagnostic = Diagnostic::new(Range::default(), Severity::Hint, "m".repeat(5000)); diagnostic.source = Some("s".repeat(200)); @@ -39,7 +43,7 @@ impl DiagnosticsProvider for Fixture { if doc.text.starts_with("slow") { std::thread::sleep(Duration::from_millis(200)); } - if doc.text.starts_with("spin") { + if doc.text.starts_with("spin") || doc.text.starts_with("gate spin") { loop { std::hint::spin_loop(); }