Follow bar values so a dead watch speaks, date them by their reading, and widen the path check (hq ADR 0260)
A watch that died left the block silent, a removed block kept its value, and a value frozen at its source looked fresh because its file was new. The follower now rereads a silent value and says so, drops removed blocks, and dates each file by the reading's own time. The path check takes /etc and /usr/local/lib in, ignores what a container sees inside itself, and lists the five findings standing today, which may only go.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -8,56 +9,128 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// Defends novox/hq ADR 0260: a value another module keeps reaches the bar over the bus, through this
|
||||
// module's own bundle and directory, and an old one is not shown as if it were now.
|
||||
// module's own bundle and directory; a dead watch is never silent; a removed block is no longer kept;
|
||||
// and a value frozen at its source hides.
|
||||
|
||||
func TestAValueIsKeptAsItsTextAndGoneWhenDeleted(t *testing.T) {
|
||||
func TestAValueIsKeptAsItsTextDatedByItsAtAndGoneWhenDeleted(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
w := Wanted{"power.draw", "laptop"}
|
||||
if err := keep(dir, w, stdio.StateChange{Op: "put", Value: []byte(`{"text":"18.2 W machine","watts":18.2}`)}); err != nil {
|
||||
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
||||
if err := keep(dir, w, "put", json.RawMessage(`{"text":"18.2 W machine","at":"2026-10-08T12:00:00Z"}`)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := os.ReadFile(filepath.Join(dir, "power.draw@laptop"))
|
||||
if err != nil || string(b) != "18.2 W machine\n" {
|
||||
t.Fatalf("%q %v", b, err)
|
||||
path := filepath.Join(dir, "power.draw@laptop")
|
||||
b, err := os.ReadFile(path)
|
||||
info, _ := os.Stat(path)
|
||||
if err != nil || string(b) != "18.2 W machine\n" || !info.ModTime().Equal(at) {
|
||||
t.Fatalf("%q %v, dated %v", b, err, info.ModTime())
|
||||
}
|
||||
if err := keep(dir, w, stdio.StateChange{Op: "delete"}); err != nil {
|
||||
if err := keep(dir, w, "delete", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, "power.draw@laptop")); !os.IsNotExist(err) {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
t.Fatal("a deleted value is still shown")
|
||||
}
|
||||
if err := keep(dir, w, stdio.StateChange{Op: "put", Value: []byte(`"no text"`)}); err == nil {
|
||||
if err := keep(dir, w, "put", json.RawMessage(`"no text"`)); err == nil {
|
||||
t.Fatal("a value with no text was kept")
|
||||
}
|
||||
}
|
||||
|
||||
func TestARefusedWatchIsTriedAgainAndAWatchedOneIsNot(t *testing.T) {
|
||||
bar := filepath.Join(t.TempDir(), "bottom-bar.toml")
|
||||
if err := os.WriteFile(bar, []byte(`command = "~/.local/bin/i3status-value power.draw laptop"`+"\n"), 0o644); err != nil {
|
||||
type fakeBus struct {
|
||||
refuse bool
|
||||
watched []Wanted
|
||||
handles map[Wanted]func(string, json.RawMessage) error
|
||||
gets int
|
||||
value json.RawMessage
|
||||
}
|
||||
|
||||
func (b *fakeBus) bus() Bus {
|
||||
return Bus{
|
||||
Watch: func(state, key string, h func(string, json.RawMessage) error) error {
|
||||
if b.refuse {
|
||||
return errors.New("not granted")
|
||||
}
|
||||
w := Wanted{state, key}
|
||||
b.watched = append(b.watched, w)
|
||||
if b.handles == nil {
|
||||
b.handles = map[Wanted]func(string, json.RawMessage) error{}
|
||||
}
|
||||
b.handles[w] = h
|
||||
return nil
|
||||
},
|
||||
Get: func(string, string) (json.RawMessage, bool, error) { b.gets++; return b.value, b.value != nil, nil },
|
||||
}
|
||||
}
|
||||
|
||||
func barFiles(t *testing.T, content string) []string {
|
||||
t.Helper()
|
||||
f := filepath.Join(t.TempDir(), "bottom-bar.toml")
|
||||
if err := os.WriteFile(f, []byte(content), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
watching := map[Wanted]bool{}
|
||||
calls := 0
|
||||
refuse := func(string, string, func(stdio.StateChange) error) error { calls++; return errors.New("not granted") }
|
||||
if said := followValues([]string{bar}, t.TempDir(), watching, refuse); len(said) != 1 || !strings.Contains(said[0], "not granted") {
|
||||
return []string{f}
|
||||
}
|
||||
|
||||
func TestAFollowerSaysARefusedWatchRereadsASilentOneAndDropsARemovedBlock(t *testing.T) {
|
||||
now := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
||||
var said []string
|
||||
b := &fakeBus{refuse: true}
|
||||
dir := t.TempDir()
|
||||
f := &Follower{Dir: dir, Bus: b.bus(), Now: func() time.Time { return now }, Say: func(s string) { said = append(said, s) }}
|
||||
bar := barFiles(t, `command = "~/.local/bin/i3status-value power.draw laptop"`)
|
||||
|
||||
// Refused: said, and tried again the next round.
|
||||
f.Round(bar)
|
||||
if len(said) != 1 || !strings.Contains(said[0], "cannot watch power.draw laptop: not granted") {
|
||||
t.Fatalf("%v", said)
|
||||
}
|
||||
accept := func(state, key string, _ func(stdio.StateChange) error) error {
|
||||
calls++
|
||||
if state != "power.draw" || key != "laptop" {
|
||||
t.Fatalf("watched %s %s", state, key)
|
||||
}
|
||||
return nil
|
||||
b.refuse = false
|
||||
f.Round(bar)
|
||||
if len(b.watched) != 1 {
|
||||
t.Fatalf("watched %v", b.watched)
|
||||
}
|
||||
followValues([]string{bar}, t.TempDir(), watching, accept)
|
||||
followValues([]string{bar}, t.TempDir(), watching, accept)
|
||||
if calls != 2 {
|
||||
t.Fatalf("%d watches; once refused, once taken, then left alone", calls)
|
||||
|
||||
// Silent for longer than Silent: said, and read again.
|
||||
b.value = json.RawMessage(`{"text":"9.0 W CPU","at":"2026-10-08T12:01:00Z"}`)
|
||||
now = now.Add(Silent + time.Second)
|
||||
f.Round(bar)
|
||||
if b.gets != 1 || !strings.Contains(said[len(said)-1], "has said nothing") {
|
||||
t.Fatalf("gets %d, said %v", b.gets, said)
|
||||
}
|
||||
if got, _ := os.ReadFile(filepath.Join(dir, "power.draw@laptop")); string(got) != "9.0 W CPU\n" {
|
||||
t.Fatalf("%q", got)
|
||||
}
|
||||
// Not again while the watch speaks.
|
||||
if err := b.handles[Wanted{"power.draw", "laptop"}]("put", b.value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Round(bar)
|
||||
if b.gets != 1 {
|
||||
t.Fatalf("read again while the watch speaks: %d", b.gets)
|
||||
}
|
||||
|
||||
// Its block removed: the file goes, and what the watch still hands over is dropped.
|
||||
f.Round(barFiles(t, "no values here"))
|
||||
if _, err := os.Stat(filepath.Join(dir, "power.draw@laptop")); !os.IsNotExist(err) {
|
||||
t.Fatal("a value no bar shows is still kept")
|
||||
}
|
||||
if err := b.handles[Wanted{"power.draw", "laptop"}]("put", b.value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, "power.draw@laptop")); !os.IsNotExist(err) {
|
||||
t.Fatal("a stopped watch still writes")
|
||||
}
|
||||
if !strings.Contains(said[len(said)-1], "no longer kept") {
|
||||
t.Fatalf("%v", said)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAKeyIsAMachinesName(t *testing.T) {
|
||||
if w := wantedIn(barFiles(t, `i3status-value power.draw ../etc`+"\n"+`i3status-value power.draw Laptop`)); len(w) != 0 {
|
||||
t.Fatalf("%v", w)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,17 +149,19 @@ func TestTheBlocksCommandShowsOnlyAFreshValue(t *testing.T) {
|
||||
if got := run(); got != "" {
|
||||
t.Fatalf("no value: %q", got)
|
||||
}
|
||||
if err := keep(dir, Wanted{"power.draw", "laptop"}, stdio.StateChange{Op: "put", Value: []byte(`{"text":"8.0 W CPU"}`)}); err != nil {
|
||||
fresh := time.Now().UTC().Format(time.RFC3339)
|
||||
if err := keep(dir, Wanted{"power.draw", "laptop"}, "put", json.RawMessage(`{"text":"8.0 W CPU","at":"`+fresh+`"}`)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := run(); got != "8.0 W CPU" {
|
||||
t.Fatalf("fresh: %q", got)
|
||||
}
|
||||
old := time.Now().Add(-31 * time.Second)
|
||||
if err := os.Chtimes(filepath.Join(dir, "power.draw@laptop"), old, old); err != nil {
|
||||
// A value measured 31 s ago hides, however recently it arrived.
|
||||
old := time.Now().Add(-31 * time.Second).UTC().Format(time.RFC3339)
|
||||
if err := keep(dir, Wanted{"power.draw", "laptop"}, "put", json.RawMessage(`{"text":"8.0 W CPU","at":"`+old+`"}`)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := run(); got != "" {
|
||||
t.Fatalf("31 s old: %q", got)
|
||||
t.Fatalf("measured 31 s ago: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user