pacman_packages answers one package on every node, or each node's counts, its explicit packages no module declares and its foreign ones apart, the versions that differ and the packages on some nodes only, with a node over the bus's cap marked not current. pacman_updates, pacman_installed and pacman_foreign read the keeper's states instead of the machine; pacman_updates says when each node's list last changed.
161 lines
6.0 KiB
Go
161 lines
6.0 KiB
Go
package main
|
|
|
|
// The package view over the bus (novox/hq ADR 0507 §6, §7; acceptance 1840/AC6): two nodes' `installed` keys and
|
|
// their `declared-packages`, answered without asking any machine.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// viewStates is two nodes' packages on the bus: node-a current and declared, node-b over the cap since a while,
|
|
// with a foreign package, and no declaration the controller holds.
|
|
func viewStates(t *testing.T) (*fakeStates, *Machine) {
|
|
t.Helper()
|
|
states := &fakeStates{}
|
|
inst := states.store(StateInstalled)
|
|
inst.Put("node-a", InstalledValue{At: t0, Packages: []InstalledPackage{
|
|
{"glibc", "2.42-1", false, "core"}, {"tmux", "3.5-1", true, "extra"}, {"vim", "9.1-1", true, "extra"},
|
|
{"zsh", "5.9-2", true, "extra"},
|
|
}})
|
|
inst.Put("node-b", InstalledValue{At: t0.Add(-time.Hour), Packages: []InstalledPackage{
|
|
{"glibc", "2.42-1", false, "core"}, {"yay", "12.0-1", true, RepoForeign}, {"zsh", "5.9-1", false, "extra"},
|
|
}})
|
|
inst.Put("node-b.not-current", NotCurrent{Bytes: 300000, Since: t0.Add(-30 * time.Minute)})
|
|
decl := states.store(DeclaredPackagesState)
|
|
var d declaredValue
|
|
json.Unmarshal([]byte(`{"node":"node-a","packages":[{"package":"vim","modules":["editor"],"absent":false},`+
|
|
`{"package":"tmux","modules":["shell"],"absent":true},{"package":"zsh","modules":["login","shell"],"absent":false}]}`), &d)
|
|
decl.Put("node-a", d)
|
|
m := machine(func(_ context.Context, name string, args ...string) Ran {
|
|
t.Errorf("a machine was asked: %s %v", name, args)
|
|
return Ran{Status: 99}
|
|
}, 1000)
|
|
m.Node, m.States = "node-a", readerOf(states)
|
|
return states, m
|
|
}
|
|
|
|
func TestOnePackageOnEveryNodeWithWhoDeclaresIt(t *testing.T) {
|
|
_, m := viewStates(t)
|
|
r, err := m.Packages("zsh", 500)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
on := r["nodes"].([]PackageOnNode)
|
|
if len(on) != 2 {
|
|
t.Fatalf("%+v", on)
|
|
}
|
|
a, b := on[0], on[1]
|
|
if a.Node != "node-a" || !a.Installed || a.Version != "5.9-2" || !a.Explicit || a.Repository != "extra" ||
|
|
strings.Join(a.DeclaredBy, ",") != "login,shell" || !a.Current {
|
|
t.Errorf("node-a: %+v", a)
|
|
}
|
|
if b.Node != "node-b" || b.Version != "5.9-1" || b.Explicit || len(b.DeclaredBy) != 0 || b.Current {
|
|
t.Errorf("node-b: %+v", b)
|
|
}
|
|
r, _ = m.Packages("yay", 500)
|
|
on = r["nodes"].([]PackageOnNode)
|
|
if on[0].Installed || !on[1].Installed || !on[1].Foreign || on[1].Repository != "" {
|
|
t.Errorf("yay: %+v", on)
|
|
}
|
|
}
|
|
|
|
func TestTheViewSaysWhatDiffersWhatIsOnSomeNodesWhatNoModuleDeclaresAndTheForeignApart(t *testing.T) {
|
|
_, m := viewStates(t)
|
|
r, err := m.Packages("", 500)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
nodes := r["nodes"].([]NodeView)
|
|
if len(nodes) != 2 {
|
|
t.Fatalf("%+v", nodes)
|
|
}
|
|
a, b := nodes[0], nodes[1]
|
|
if a.Node != "node-a" || a.Packages != 4 || a.Explicit != 3 || !a.Current || fmt.Sprint(a.Undeclared) != "[{tmux 3.5-1}]" ||
|
|
len(a.Foreign) != 0 || !a.At.Equal(t0) {
|
|
t.Errorf("node-a: %+v", a)
|
|
}
|
|
if b.Current || b.NotCurrent == nil || b.NotCurrent.Bytes != 300000 || b.Undeclared != nil ||
|
|
fmt.Sprint(b.Foreign) != "[{yay 12.0-1}]" || !b.At.Equal(t0.Add(-time.Hour)) {
|
|
t.Errorf("node-b (not current, no declaration, a foreign package): %+v", b)
|
|
}
|
|
if d := r["differing"].([]Spread); len(d) != 1 || d[0].Name != "zsh" || d[0].Versions["node-a"] != "5.9-2" || d[0].Versions["node-b"] != "5.9-1" {
|
|
t.Errorf("differing: %+v", d)
|
|
}
|
|
some := r["some_nodes"].([]Spread)
|
|
var names []string
|
|
for _, s := range some {
|
|
names = append(names, s.Name)
|
|
}
|
|
if strings.Join(names, ",") != "tmux,vim" {
|
|
t.Errorf("on some nodes only, the foreign apart: %v", names)
|
|
}
|
|
r, _ = m.Packages("", 1)
|
|
if r["some_nodes_count"] != 2 || r["some_nodes_truncated"] != true || len(r["some_nodes"].([]Spread)) != 1 {
|
|
t.Errorf("bounded: %v", r)
|
|
}
|
|
}
|
|
|
|
func TestInstalledAndForeignAnswerFromTheBus(t *testing.T) {
|
|
_, m := viewStates(t)
|
|
r, err := m.InstalledFrom("", "", "", false, 10)
|
|
if err != nil || r["node"] != "node-a" || r["count"] != 4 || r["current"] != true {
|
|
t.Fatalf("this node's: %v %v", r, err)
|
|
}
|
|
pk := r["packages"].([]Package)
|
|
if pk[0].Reason != "dependency" || pk[0].Repository != "core" || pk[1].Reason != "explicit" {
|
|
t.Errorf("%+v", pk)
|
|
}
|
|
r, err = m.InstalledFrom("node-b", "", "", true, 10)
|
|
if err != nil || r["count"] != 1 || r["current"] != false || r["not_current"] == nil {
|
|
t.Fatalf("node-b's foreign: %v %v", r, err)
|
|
}
|
|
if tot := r["totals"].(map[string]int); tot["explicit"] != 1 || tot["dependency"] != 2 || tot["foreign"] != 1 {
|
|
t.Errorf("totals: %v", tot)
|
|
}
|
|
f, err := m.ForeignFrom("node-b")
|
|
if err != nil || f["count"] != 1 || fmt.Sprint(f["packages"]) != "[{yay 12.0-1}]" {
|
|
t.Errorf("%v %v", f, err)
|
|
}
|
|
if _, err := m.InstalledFrom("node-c", "", "", false, 10); err == nil || !strings.Contains(err.Error(), "node-c") {
|
|
t.Errorf("a node with no packages on the bus: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUpdatesAnswerEveryNodeAndWhenItsListLastChanged(t *testing.T) {
|
|
states, m := viewStates(t)
|
|
up := states.store(StateUpdates)
|
|
up.now = func() time.Time { return t0.Add(-2 * time.Hour) }
|
|
up.Put("node-a", UpdatesValue{Reachable: true, Packages: []VersionChange{{"zsh", "5.9-2", "5.9-3"}}})
|
|
up.now = func() time.Time { return t0.Add(-3 * time.Hour) }
|
|
up.Put("node-b", UpdatesValue{Reachable: false, Packages: []VersionChange{}})
|
|
r, err := m.UpdatesFor("")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
v := r["nodes"].([]UpdatesView)
|
|
if len(v) != 2 || v[0].Count != 1 || !v[0].Reachable || v[0].Changed == nil || !v[0].Changed.Equal(t0.Add(-2*time.Hour)) ||
|
|
v[1].Reachable || !v[1].Changed.Equal(t0.Add(-3*time.Hour)) {
|
|
t.Fatalf("%+v", v)
|
|
}
|
|
r, _ = m.UpdatesFor("node-b")
|
|
if v := r["nodes"].([]UpdatesView); len(v) != 1 || v[0].Node != "node-b" {
|
|
t.Errorf("one node: %+v", v)
|
|
}
|
|
}
|
|
|
|
func TestANodeArgumentIsOneKeyToken(t *testing.T) {
|
|
for _, bad := range []string{"node-a.not-current", "node-a.*", "a b"} {
|
|
if _, err := nodeOf(map[string]any{"node": bad}); err == nil {
|
|
t.Errorf("%q was taken as a node", bad)
|
|
}
|
|
}
|
|
if n, err := nodeOf(map[string]any{"node": "node-a"}); err != nil || n != "node-a" {
|
|
t.Errorf("%q %v", n, err)
|
|
}
|
|
}
|