mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-10-15 04:40:05 +00:00
Rebase of #1881 by @SaarMesh-Bot onto current master. Their three commits are preserved, two of them cherry-picked with authorship intact; the sweep itself had to be regenerated. Opened as a new PR rather than force-pushing their branch. Closes #1881 once merged. Addresses parts 1 and 3 of #1859; part 2 landed as #1937. ## Why regenerated rather than merged The sweep in #1881 was cut on 2026-09-02 07:13 and roughly forty PRs landed after it, so it went `CONFLICTING/DIRTY`. Re-running `gofmt` on current master is cheaper and less error-prone than resolving 72 conflicts that are all whitespace. The drift it fixes also grew in the meantime: 66 files now, against 72 then, but spread differently. ## The three commits 1. **`style(#1859)`** — `gofmt -w` across the 14 modules. 66 files. 2. **`test(#1859)`** — @SaarMesh-Bot's fix for the one `go vet` copylocks finding, `cmd/ingestor/coverage_boost_test.go`: the range variable copied a `Config` embedding `sync.Once`. Cherry-picked unchanged. 3. **`ci(#1859)`** — @SaarMesh-Bot's CI step that fails on gofmt drift or vet findings, plus `.git-blame-ignore-revs`. Cherry-picked with one change, noted in the commit message: the ignore file pointed at `04bc80ee`, the sweep commit on their branch, which does not exist on this base and would make `git blame --ignore-revs-file` error. Repointed at `d3a02599`, the sweep here. ## Verification The claim "formatting only" is checked twice rather than asserted: - Every changed file is byte-identical to `gofmt(previous content)`. 0 of 66 deviate. - With line comments and all whitespace stripped, 0 of 66 files differ, so no code outside comments changed. 14 of the 66 also show doc-comment reflow. Since Go 1.19 `gofmt` re-indents indented comment blocks to tabs and inserts a blank comment line before them; the behavior matrix above `resolveHopWithContext` in `cmd/ingestor/path_resolver.go` is a clear example. That is gofmt's own output, not an edit, but it is worth naming because it makes the diff look larger than "whitespace" suggests. The gate was run locally exactly as the workflow runs it: `gofmt` clean, and `go vet` clean in all 14 modules, including `cmd/ingestor` which is what commit 2 fixes. Suites: `cmd/server` ok (80.7s), `internal/packetpath` ok (2.3s), `cmd/ingestor` passes except `TestWriteStatsAtomic_SymlinkAtDestIsReplaced`, which fails identically on bare master with "A required privilege is not held by the client" (Windows symlink privilege on my host, not code). ## Sequencing This should go last in the queue. The sweep touches 66 files, so merging it before the remaining open Go PRs gives each of them a conflict about nothing but formatting. After it lands the gate is active, and any PR with drift fails CI until it runs `gofmt -w`. Excluded from the sweep: the misnamed `Dockerfile.go`, which is a Dockerfile that gofmt cannot parse (the workflow excludes it too), and `docs/DEPLOYMENT.md`, which a case-insensitive filesystem surfaces as a spurious modification against `docs/deployment.md` and is unrelated. --------- Co-authored-by: SaarMesh-Bot <300107934+SaarMesh-Bot@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
132 lines
4.6 KiB
Go
132 lines
4.6 KiB
Go
package main
|
||
|
||
// Regression tests for #1285:
|
||
//
|
||
// Bug A: per-hash evidence's MedianCorrectedSkewSec includes 700-day RTC-reset
|
||
// outliers, dragging the displayed median into "−704d 18h" garbage even
|
||
// though every recent sample is small (< 30s).
|
||
//
|
||
// Bug B: RecentBadSampleCount counts samples outside the *displayed* recent
|
||
// window (or counts against raw skew, not corrected) → "3 of last 5
|
||
// adverts had nonsense timestamps" warning fires on healthy nodes.
|
||
//
|
||
// Both must pass without disturbing the existing bimodal / no-clock logic.
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// Synthesizes the repro from issue #1285:
|
||
//
|
||
// 30 healthy adverts (skew ~−20s) + 1 historical advert with an RTC reset
|
||
// (advertTS = 2024-06-13, observed today → −705d skew).
|
||
//
|
||
// Returns the populated store.
|
||
func seedIssue1285Repro(t *testing.T) *PacketStore {
|
||
t.Helper()
|
||
ps := NewPacketStore(nil, nil)
|
||
pt := 4 // ADVERT
|
||
|
||
const pubkey = "RTCRESET"
|
||
const skewSec = int64(-20) // node clock is 20s BEHIND wall-clock
|
||
|
||
baseObs := int64(1779000000) // ~mid-2026
|
||
rtcResetAdv := int64(1718281640) // 2024-06-13 (from the issue repro)
|
||
|
||
var txs []*StoreTx
|
||
|
||
// 30 healthy adverts spanning the older end of the recent window.
|
||
for i := 0; i < 30; i++ {
|
||
obsTS := baseObs + int64(i)*60
|
||
advTS := obsTS + skewSec
|
||
tx := &StoreTx{
|
||
Hash: "healthy-" + formatInt64(int64(i)),
|
||
PayloadType: &pt,
|
||
DecodedJSON: `{"payload":{"timestamp":` + formatInt64(advTS) + `},"pubKey":"RTCRESET"}`,
|
||
Observations: []*StoreObs{
|
||
{ObserverID: "obs1", Timestamp: time.Unix(obsTS, 0).UTC().Format(time.RFC3339)},
|
||
},
|
||
}
|
||
txs = append(txs, tx)
|
||
}
|
||
|
||
// One RTC-reset packet observed MOST RECENTLY (so it sits in the
|
||
// per-hash evidence list AND is included in the recent-window count
|
||
// on master). Its advertTS is from 2024 → corrected skew ≈ -60M sec.
|
||
rtcResetObs := baseObs + int64(30*60) + 60
|
||
rtcTx := &StoreTx{
|
||
Hash: "rtc-reset-0001",
|
||
PayloadType: &pt,
|
||
DecodedJSON: `{"payload":{"timestamp":` + formatInt64(rtcResetAdv) + `},"pubKey":"RTCRESET"}`,
|
||
Observations: []*StoreObs{
|
||
{ObserverID: "obs1", Timestamp: time.Unix(rtcResetObs, 0).UTC().Format(time.RFC3339)},
|
||
},
|
||
}
|
||
txs = append(txs, rtcTx)
|
||
|
||
ps.mu.Lock()
|
||
ps.byNode[pubkey] = txs
|
||
for _, tx := range txs {
|
||
ps.byPayloadType[4] = append(ps.byPayloadType[4], tx)
|
||
}
|
||
ps.clockSkew.computeInterval = 0
|
||
ps.mu.Unlock()
|
||
return ps
|
||
}
|
||
|
||
// Bug A — per-hash evidence median must EXCLUDE the 705-day RTC-reset outlier.
|
||
// On master this asserts on the RTC-reset hash's MedianCorrectedSkewSec being
|
||
// ≈ −60M sec ("−704d 18h"); after the fix the field is suppressed (0) or
|
||
// otherwise marked insufficient, never displayed as the garbage value.
|
||
func TestIssue1285_HashEvidence_OutlierExcludedFromMedian(t *testing.T) {
|
||
ps := seedIssue1285Repro(t)
|
||
r := ps.GetNodeClockSkew("RTCRESET")
|
||
if r == nil {
|
||
t.Fatal("expected clock skew result")
|
||
}
|
||
|
||
// The recent-hash evidence list is the source of the "median corrected:
|
||
// −704d 18h" string in the UI. After the fix, NO entry in this list
|
||
// should report a |median| above the 24h sanity threshold — the fix is
|
||
// to drop outlier samples (or flag the hash as insufficient-data) before
|
||
// publishing the median.
|
||
const maxSaneAbsSec = float64(24 * 3600)
|
||
for _, ev := range r.RecentHashEvidence {
|
||
if abs(ev.MedianCorrectedSkewSec) > maxSaneAbsSec {
|
||
t.Errorf("hash %s exposes outlier-dominated median %.0fs (~%.1fd); "+
|
||
"expected entry to be filtered out or marked insufficient (|median| <= %.0fs)",
|
||
ev.Hash, ev.MedianCorrectedSkewSec,
|
||
ev.MedianCorrectedSkewSec/86400, maxSaneAbsSec)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Bug B — RecentBadSampleCount must be 0 when every sample in the recent
|
||
// window is healthy (<30s |corrected skew|). On master this fires because
|
||
// "recent" is computed over the wrong set (or against raw skew).
|
||
func TestIssue1285_RecentBadCount_NotPollutedByOldOutlier(t *testing.T) {
|
||
ps := seedIssue1285Repro(t)
|
||
r := ps.GetNodeClockSkew("RTCRESET")
|
||
if r == nil {
|
||
t.Fatal("expected clock skew result")
|
||
}
|
||
if r.RecentBadSampleCount != 0 {
|
||
t.Errorf("RecentBadSampleCount = %d, want 0 — recent samples are all "+
|
||
"~−20s (healthy); the historical RTC-reset outlier is outside the "+
|
||
"recent window and must not be counted", r.RecentBadSampleCount)
|
||
}
|
||
if r.Severity == SkewBimodalClock || r.Severity == SkewNoClock {
|
||
t.Errorf("severity = %v, want ok/warning — recent samples are all "+
|
||
"healthy (~−20s skew), one historical outlier must not flip the node "+
|
||
"to bimodal/no-clock", r.Severity)
|
||
}
|
||
}
|
||
|
||
func abs(v float64) float64 {
|
||
if v < 0 {
|
||
return -v
|
||
}
|
||
return v
|
||
}
|