Files
mesh-catalog/modules/pacman/cmd/pacman-tools/view_test.go
T

212 lines
8.4 KiB
Go

package main
// The package view over the bus (novox/hq ADR 0507 §6, §7; acceptance 1840/AC6): two nodes' `installed` keys and
// the node-engine's `packages`, answered without asking any machine.
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// 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 node-engine published.
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) {
states, 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.DeclaredFrom != SourceEngine || b.DeclaredFrom != "" {
t.Errorf("sources: %q, %q", a.DeclaredFrom, b.DeclaredFrom)
}
// A node whose engine has written no key reads the controller's mirror, said so; a node with both reads its engine's.
var legacy, stale declaredValue
json.Unmarshal([]byte(`{"node":"node-b","packages":[{"package":"zsh","modules":["legacy"],"absent":false}]}`), &legacy)
json.Unmarshal([]byte(`{"node":"node-a","packages":[{"package":"zsh","modules":["stale"],"absent":false}]}`), &stale)
mirror := states.store(ControllerDeclaredState)
mirror.Put("node-b", legacy)
mirror.Put("node-a", stale)
r2, _ := m.Packages("zsh", 500)
on2 := r2["nodes"].([]PackageOnNode)
if on2[0].DeclaredFrom != SourceEngine || strings.Join(on2[0].DeclaredBy, ",") != "login,shell" ||
on2[1].DeclaredFrom != SourceController || strings.Join(on2[1].DeclaredBy, ",") != "legacy" {
t.Errorf("fallback: %+v", on2)
}
// The engine's state cannot be read (its bucket is not on the bus yet): the mirror answers for every node, and the
// answer says why beside declared_from; a mirror that cannot be read either is an error.
read := m.States
m.States = func(name string) StateReader {
if name == SourceEngine {
return failingReader{errors.New("is issued and its bucket is not on the bus yet")}
}
return read(name)
}
r3, err3 := m.Packages("zsh", 500)
on3, _ := r3["nodes"].([]PackageOnNode)
if err3 != nil || len(on3) != 2 || on3[0].DeclaredFrom != SourceController || strings.Join(on3[0].DeclaredBy, ",") != "stale" ||
on3[1].DeclaredFrom != SourceController || !strings.Contains(fmt.Sprint(r3["declared_engine_error"]), "not on the bus yet") {
t.Errorf("engine unreadable: %+v %v", r3, err3)
}
m.States = func(name string) StateReader {
if name == SourceEngine || name == SourceController {
return failingReader{errors.New("refused")}
}
return read(name)
}
if _, err := m.Packages("zsh", 500); err == nil {
t.Error("neither state could be read and no error was said")
}
m.States = read
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)
}
}
// failingReader is a state that cannot be read.
type failingReader struct{ err error }
func (f failingReader) Get(string) (*stdio.StateEntry, error) { return nil, f.err }
func (f failingReader) Keys() ([]string, error) { return nil, f.err }
func (f failingReader) History(string) ([]stdio.StateRevision, error) { return nil, f.err }