Sprint 278: LSP Proxy Hardening (steps 1918-1922)
- LSPMessageValidator: JSON-RPC 2.0 shape validation (method, id type, params type) - LSPCapabilityNegotiator: per-source capability tracking; shouldRoute gates on declared caps - LSPRequestThrottler: per-URI/method debounce; blocks rapid didChange floods - LSPErrorRecovery: per-source health tracking; stale-cache fallback when crashed - Sprint278IntegrationSummary: poly-parse (C++ + Haskell) end-to-end integration 26/26 tests passing (5+5+5+5+6). All new headers well under 600 lines. LoRA recorded: sprint278-2026-03-01 (9 calls, 6437 tokens). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
101
editor/tests/step1921_test.cpp
Normal file
101
editor/tests/step1921_test.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
// Step 1921: LSPErrorRecovery
|
||||
// Tracks per-source health; returns stale cached diagnostics when crashed.
|
||||
//
|
||||
// t1: unknown sources are healthy by default
|
||||
// t2: markCrashed/markRecovered toggle health
|
||||
// t3: healthySources filters crashed sources
|
||||
// t4: fallbackDiagnostics returns cache only when crashed
|
||||
// t5: clearCache removes entries; crashedCount
|
||||
|
||||
#include "LSPErrorRecovery.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace ws = whetstone;
|
||||
|
||||
static int p=0,f=0;
|
||||
#define T(n) { std::cout<<" "<<#n<<"... "; }
|
||||
#define P() { std::cout<<"PASS\n"; ++p; }
|
||||
#define F(m) { std::cout<<"FAIL: "<<m<<"\n"; ++f; }
|
||||
#define C(c,m) if(!(c)){F(m);return;}
|
||||
|
||||
static ws::Diagnostic diag(const std::string& uri, int sev,
|
||||
const std::string& msg, const std::string& src) {
|
||||
ws::Diagnostic d; d.uri=uri; d.severity=sev; d.message=msg; d.source=src; return d;
|
||||
}
|
||||
|
||||
void t1(){
|
||||
T(unknown_sources_healthy);
|
||||
ws::LSPErrorRecovery r;
|
||||
C(r.isHealthy("gopls"), "gopls healthy by default");
|
||||
C(r.isHealthy("haskell-lsp"),"haskell-lsp healthy by default");
|
||||
C(r.crashedCount() == 0, "none crashed");
|
||||
P();
|
||||
}
|
||||
|
||||
void t2(){
|
||||
T(markCrashed_and_markRecovered);
|
||||
ws::LSPErrorRecovery r;
|
||||
r.markCrashed("haskell-lsp");
|
||||
C(!r.isHealthy("haskell-lsp"), "crashed after markCrashed");
|
||||
C( r.isHealthy("gopls"), "gopls still healthy");
|
||||
C(r.crashedCount() == 1, "one crashed");
|
||||
r.markRecovered("haskell-lsp");
|
||||
C( r.isHealthy("haskell-lsp"), "healthy after markRecovered");
|
||||
C(r.crashedCount() == 0, "none crashed after recovery");
|
||||
P();
|
||||
}
|
||||
|
||||
void t3(){
|
||||
T(healthySources_filters_crashed);
|
||||
ws::LSPErrorRecovery r;
|
||||
r.markCrashed("haskell-lsp");
|
||||
std::vector<std::string> all = {"gopls","haskell-lsp","tsserver"};
|
||||
auto healthy = r.healthySources(all);
|
||||
C(healthy.size() == 2, "two healthy");
|
||||
C(healthy[0] == "gopls", "gopls healthy");
|
||||
C(healthy[1] == "tsserver", "tsserver healthy");
|
||||
P();
|
||||
}
|
||||
|
||||
void t4(){
|
||||
T(fallbackDiagnostics_only_when_crashed);
|
||||
ws::LSPErrorRecovery r;
|
||||
auto d = diag("file:///Parse.hs",1,"type error","haskell-lsp");
|
||||
r.cacheDiagnostics("haskell-lsp","file:///Parse.hs",{d});
|
||||
// healthy: fallback is empty
|
||||
C(r.fallbackDiagnostics("haskell-lsp","file:///Parse.hs").empty(),
|
||||
"healthy: no fallback");
|
||||
// crash it: fallback returns cache
|
||||
r.markCrashed("haskell-lsp");
|
||||
auto fb = r.fallbackDiagnostics("haskell-lsp","file:///Parse.hs");
|
||||
C(fb.size() == 1, "one fallback diag");
|
||||
C(fb[0].message == "type error", "correct message");
|
||||
// different uri: empty
|
||||
C(r.fallbackDiagnostics("haskell-lsp","file:///Other.hs").empty(),
|
||||
"missing uri empty");
|
||||
P();
|
||||
}
|
||||
|
||||
void t5(){
|
||||
T(clearCache_and_crashedCount);
|
||||
ws::LSPErrorRecovery r;
|
||||
r.cacheDiagnostics("haskell-lsp","file:///A.hs",
|
||||
{diag("file:///A.hs",1,"E","haskell-lsp")});
|
||||
r.cacheDiagnostics("haskell-lsp","file:///B.hs",
|
||||
{diag("file:///B.hs",2,"W","haskell-lsp")});
|
||||
r.markCrashed("haskell-lsp");
|
||||
C(!r.fallbackDiagnostics("haskell-lsp","file:///A.hs").empty(), "cache present");
|
||||
r.clearCache("haskell-lsp");
|
||||
C(r.fallbackDiagnostics("haskell-lsp","file:///A.hs").empty(), "cache cleared");
|
||||
// crashedCount tracks multiple
|
||||
r.markCrashed("gopls");
|
||||
C(r.crashedCount() == 2, "two crashed");
|
||||
P();
|
||||
}
|
||||
|
||||
int main(){
|
||||
std::cout << "Step 1921: LSPErrorRecovery\n";
|
||||
t1(); t2(); t3(); t4(); t5();
|
||||
std::cout << "\n" << p << "/" << (p+f) << " passed\n";
|
||||
return f > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user