refactor(audit): batch 3 — decompose App::update() + collapse acrylic fork

Three P1 structural refactors from the audit; no behavior change.

1. Extract the ~180-line daemon-stdout rescan/witness parser out of the
   ~790-line App::update() into a pure, static, unit-testable
   NetworkRefreshService::parseDaemonRescanOutput() returning a
   DaemonRescanScan result struct (sits next to the existing static parse*
   siblings). The scanning half moved verbatim; App::update() keeps the
   state-application half, now reading scan.* fields. Adds
   testParseDaemonRescanOutput() covering all six daemon signals + edge
   cases against fixture log snippets — previously untestable without a
   live daemon.

2. Extract the ~95-line global keyboard-shortcut block (Ctrl+, / theme
   cycle / F5 / low-spec / effects / gradient / wizard) out of
   App::update() into App::handleGlobalShortcuts().

3. Collapse the ImGuiAcrylic frontend fork: the GLAD and DX11 namespaces
   were ~375 lines of semantically-identical code (the frontend makes zero
   direct GL/DX calls — all backend work delegates to AcrylicMaterial, which
   has a backend per API). Extended the single frontend's guard to
   #if defined(DRAGONX_HAS_GLAD) || defined(DRAGONX_USE_DX11) and deleted the
   DX11 duplicate, leaving the no-backend stub. Kills the Windows/DX11 drift
   hazard (acrylic frontend changes now made once).

Verified: full-node + Lite build clean; ctest 1/1 (incl. the new parser
tests); source-hygiene clean; and the collapsed acrylic path was
compile-verified under DX11 via the mingw-w64 Windows cross-build
(ObsidianDragon.exe links).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 16:40:40 -05:00
parent 7891c689fb
commit 280a71e973
6 changed files with 331 additions and 602 deletions

View File

@@ -1051,6 +1051,99 @@ void testNetworkRefreshSnapshotHelpers()
EXPECT_EQ(snapshot.sendTxids.count("pending-send"), static_cast<size_t>(1));
}
void testParseDaemonRescanOutput()
{
using dragonx::services::NetworkRefreshService;
// "Still rescanning ... Progress=<frac>" — fraction scaled to a percentage.
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
"2026-07-01 Still rescanning at height 500000. Progress=0.42 tip=600000\n");
EXPECT_TRUE(scan.foundRescan);
EXPECT_NEAR(scan.rescanPct, 42.0f, 0.01f);
}
// "Rescanning... <n>%" — percentage read directly (already 0..100).
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
"Rescanning... 37% done\n");
EXPECT_TRUE(scan.foundRescan);
EXPECT_NEAR(scan.rescanPct, 37.0f, 0.01f);
}
// Explicit completion line marks finished.
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput("Done rescanning\n");
EXPECT_TRUE(scan.finished);
}
// The daemon that prints no "Done rescanning" logs a rescan benchmark ending in "<digits>ms".
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
" rescan 16760577ms\n");
EXPECT_TRUE(scan.finished);
}
// "Building Witnesses for block <h> <frac> complete, <n> remaining".
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
"Building Witnesses for block 123 0.5 complete, 40 remaining\n");
EXPECT_TRUE(scan.foundWitness);
EXPECT_NEAR(scan.witnessPct, 50.0f, 0.01f);
EXPECT_EQ(scan.witnessRemaining, 40);
}
// "Reading blocks for witness rebuild: <done> / <total>" — captures total and remaining.
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
"Reading blocks for witness rebuild: 10 / 200\n");
EXPECT_TRUE(scan.foundWitness);
EXPECT_EQ(scan.witnessReadTotal, 200);
EXPECT_EQ(scan.witnessRemaining, 190); // total - done
}
// Parallel-rebuild completion line snaps witnessRebuilt=true.
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
"rebuilt 3 note witness cache(s) to height 99 in 12ms using 4 thread(s)\n");
EXPECT_TRUE(scan.foundWitness);
EXPECT_TRUE(scan.witnessRebuilt);
}
// "Setting Initial Sapling Witness for tx <hash>, <i> of <N>" — distinct hashes, max N.
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput(
"Setting Initial Sapling Witness for tx abc123, 2 of 7\n"
"Setting Initial Sapling Witness for tx def456, 3 of 7\n"
"Setting Initial Sapling Witness for tx abc123, 5 of 7\n");
EXPECT_TRUE(scan.foundWitness);
// The parser collects every occurrence (dedup happens in the apply step's set), so the
// vector has all three keys but only two distinct values.
EXPECT_EQ(scan.witnessTxids.size(), static_cast<size_t>(3));
std::set<std::string> distinct(scan.witnessTxids.begin(), scan.witnessTxids.end());
EXPECT_EQ(distinct.size(), static_cast<size_t>(2));
EXPECT_EQ(distinct.count("abc123"), static_cast<size_t>(1));
EXPECT_EQ(distinct.count("def456"), static_cast<size_t>(1));
EXPECT_EQ(scan.witnessTotalTxs, 7);
}
// Empty input yields all defaults.
{
auto scan = NetworkRefreshService::parseDaemonRescanOutput("");
EXPECT_FALSE(scan.foundRescan);
EXPECT_FALSE(scan.finished);
EXPECT_NEAR(scan.rescanPct, 0.0f, 0.0001f);
EXPECT_TRUE(scan.lastStatus.empty());
EXPECT_FALSE(scan.foundWitness);
EXPECT_NEAR(scan.witnessPct, -1.0f, 0.0001f);
EXPECT_EQ(scan.witnessRemaining, -1);
EXPECT_EQ(scan.witnessReadTotal, -1);
EXPECT_FALSE(scan.witnessRebuilt);
EXPECT_TRUE(scan.witnessTxids.empty());
EXPECT_EQ(scan.witnessTotalTxs, -1);
}
}
void testNetworkRefreshRpcCollectors()
{
using Refresh = dragonx::services::NetworkRefreshService;
@@ -5381,6 +5474,7 @@ int main()
testRefreshScheduler();
testNetworkRefreshService();
testNetworkRefreshSnapshotHelpers();
testParseDaemonRescanOutput();
testNetworkRefreshRpcCollectors();
testNetworkRefreshResultModels();
testOperationStatusPollParsing();