Files
whetstone_DSL/editor/tests/step1921_test.cpp
Bill 4d47e894c1 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>
2026-03-01 20:28:31 -07:00

102 lines
3.5 KiB
C++

// 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;
}