mesh/merge-gate head c7a4bf5c11d6 merged into trunk d8e785bb4b4b — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head c7a4bf5c11d6 merged into trunk d8e785bb4b4b — pass: its merge-check.sh passed
mesh/delivery delivered
Go's monotonic clock stops while a laptop is suspended, so a day judged by it could pass a day and a half of wall time before the machine said what it is. And a hung statfs left a goroutine and thread blocked per apply; now one check runs at a time and reports after it say the sizes unknown until it ends.
108 lines
4.2 KiB
Go
108 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/link"
|
|
)
|
|
|
|
// Defends novox/hq issue 160: a machine nobody pushed to still says what it is made of once a day, so the
|
|
// newest of it the mesh holds is no older than a day. Before, the reconcile said it only with something
|
|
// else (a hold, a firewall, an outward link changed), and a converged machine's picture was as old as the
|
|
// last delivery to it.
|
|
func TestAMachineNobodyPushedToSaysWhatItIsOnceADay(t *testing.T) {
|
|
at := time.Date(2026, 10, 12, 9, 0, 0, 0, time.UTC)
|
|
w := &adoptionWatch{now: func() time.Time { return at }}
|
|
quiet := link.Report{Outward: []string{"wlan0"},
|
|
Profile: map[string]any{"machine": map[string]any{"observed_at": at}}}
|
|
|
|
if !w.news(quiet) {
|
|
t.Fatal("the first reconcile after the engine started did not say what the machine is")
|
|
}
|
|
w.said(quiet)
|
|
|
|
for _, after := range []time.Duration{5 * time.Minute, 12 * time.Hour, 22*time.Hour + 55*time.Minute} {
|
|
at = time.Date(2026, 10, 12, 9, 0, 0, 0, time.UTC).Add(after)
|
|
if w.news(quiet) {
|
|
t.Errorf("%s after the mesh heard it, an unchanged reconcile was said again", after)
|
|
}
|
|
}
|
|
// The reconcile that falls past 23 hours says it, so it reaches the mesh inside the day.
|
|
at = time.Date(2026, 10, 12, 9, 0, 0, 0, time.UTC).Add(23*time.Hour + 5*time.Minute)
|
|
if !w.news(quiet) {
|
|
t.Fatal("a day on, the reconcile did not say what the machine is")
|
|
}
|
|
w.said(quiet)
|
|
at = at.Add(5 * time.Minute)
|
|
if w.news(quiet) {
|
|
t.Error("once said, it was said again at the next reconcile")
|
|
}
|
|
}
|
|
|
|
// A delivery's account carries the profile too: heard, it starts the day again.
|
|
func TestADeliveryHeardStartsTheDayAgain(t *testing.T) {
|
|
at := time.Date(2026, 10, 12, 9, 0, 0, 0, time.UTC)
|
|
w := &adoptionWatch{now: func() time.Time { return at }}
|
|
report := link.Report{Outward: []string{"eth0"}, Profile: map[string]any{"capabilities": []any{}}}
|
|
w.said(report)
|
|
at = at.Add(20 * time.Hour)
|
|
w.said(report) // a delivery's apply
|
|
at = at.Add(20 * time.Hour)
|
|
if w.news(report) {
|
|
t.Error("said again unasked 20 hours after a delivery told the mesh")
|
|
}
|
|
}
|
|
|
|
// A refusal carries nothing about the machine, so it is never said for being a day old.
|
|
func TestARefusalIsNotSaidForTheMachinesSake(t *testing.T) {
|
|
at := time.Date(2026, 10, 12, 9, 0, 0, 0, time.UTC)
|
|
w := &adoptionWatch{now: func() time.Time { return at }}
|
|
if w.machineDue(link.Report{Refused: "no"}) {
|
|
t.Error("a refusal was due for what the machine is made of")
|
|
}
|
|
}
|
|
|
|
// A laptop that slept still says what it is within a day of wall time (review of mesh-host#92). Go's monotonic
|
|
// clock stops while a machine is suspended, and Sub between two times that both carry a monotonic reading uses
|
|
// it, so a watch that kept one would count a day asleep as almost nothing. Simulated with a clock that first
|
|
// reads now (a monotonic reading), and after the sleep reads the wall time a day on: the watch must keep no
|
|
// monotonic reading, and judge the day as the wall clock says it.
|
|
func TestALaptopThatSleptStillSaysWhatItIsWithinADay(t *testing.T) {
|
|
before := time.Now() // carries a monotonic reading
|
|
asleep := before.Add(3 * time.Second)
|
|
if asleep.Sub(before) != 3*time.Second {
|
|
t.Fatal("the monotonic reading is not what this test simulates with")
|
|
}
|
|
calls := 0
|
|
w := &adoptionWatch{now: func() time.Time {
|
|
calls++
|
|
if calls == 1 {
|
|
return before
|
|
}
|
|
// The wall clock a day on, as a machine's clock reads after waking.
|
|
return asleep.Round(0).Add(24 * time.Hour)
|
|
}}
|
|
report := link.Report{Outward: []string{"wlan0"}, Profile: map[string]any{"capabilities": []any{}}}
|
|
w.said(report)
|
|
if strings.Contains(w.machineSaid.String(), "m=") {
|
|
t.Fatalf("the watch kept a monotonic reading (%s): a sleep would not count toward the day", w.machineSaid)
|
|
}
|
|
if !w.news(report) {
|
|
t.Error("a day of wall time on, a laptop that slept did not say what it is")
|
|
}
|
|
}
|
|
|
|
// And the watch's own clock never carries a monotonic reading.
|
|
func TestTheWatchClockIsWallTime(t *testing.T) {
|
|
w := &adoptionWatch{}
|
|
if s := w.clock().String(); strings.Contains(s, "m=") {
|
|
t.Errorf("the watch's clock carries a monotonic reading: %s", s)
|
|
}
|
|
w = &adoptionWatch{now: time.Now}
|
|
if s := w.clock().String(); strings.Contains(s, "m=") {
|
|
t.Errorf("a given clock's time kept its monotonic reading: %s", s)
|
|
}
|
|
}
|