Files
mesh-catalog/modules/snapd/cmd/snapd-tools/snapd_test.go
T
jochen db297e8bdd snapd: tools for the snaps, the package blocked on the mesh's AUR repository (hq to-be 42 phase 2.9)
snapd is not in the official repositories, and ADR 0205's archive does not
fit a daemon with setuid helpers, so the module declares nothing until
research 027 question 1 (P2) builds it into the mesh's own repository. Not
even its units: on the laptop they do not exist, and the module would fail
there.

Ten Go tools that work wherever snapd is installed and say so where it is
not: status (with AppArmor's absence from the kernel named), list, info,
updates, disk usage with the disabled revisions' share, services, changes,
and install, remove and refresh through sudo -n with --no-wait, answering
snapd's change id.
2026-10-04 13:02:23 +02:00

204 lines
7.4 KiB
Go

package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestTheManifestDeclaresNothingTheHostCouldNotInstall(t *testing.T) {
m := readManifest(t)
holdsTheBundle(t, m, "snapd")
// snapd is not in the official repositories: no package, and no unit that only that package
// brings, until the mesh's package repository builds it (research 027 question 1).
if len(m.Resources) != 0 {
t.Errorf("resources %v", m.Resources)
}
}
const listAll = `Name Version Rev Tracking Publisher Notes
bare 1.0 5 latest/stable canonical** base
code 04c0d99f 266 latest/stable vscode** disabled,classic
code 07f806f9 267 latest/stable vscode** classic
gtk-common-themes 0.1-81-g442e511 1535 latest/stable canonical** -
`
func TestListReadsEachRevisionAndMarksTheDisabledOnes(t *testing.T) {
f := using(t, func(string, Cmd) Result { return ok(listAll) })
got, err := List()
if err != nil || len(got.Snaps) != 4 || got.Disabled != 1 || got.Active != 3 {
t.Fatalf("%+v %v", got, err)
}
if s := got.Snaps[1]; s.Name != "code" || s.Revision != "266" || !s.Disabled || s.Publisher != "vscode" || strings.Join(s.Notes, ",") != "disabled,classic" {
t.Errorf("%+v", s)
}
if s := got.Snaps[3]; len(s.Notes) != 0 || s.Disabled {
t.Errorf("%+v", s)
}
if f.lines()[0] != "snap list --all --unicode=never --color=never" {
t.Errorf("%v", f.lines())
}
}
func TestToolsSayWhenSnapdIsNotInstalled(t *testing.T) {
using(t, func(string, Cmd) Result { return Result{Status: 127, Error: "not-found"} })
if _, err := List(); err == nil || !strings.Contains(err.Error(), "not in the official repositories") {
t.Fatalf("%v", err)
}
got, err := Status()
if err != nil || got.Installed || !strings.Contains(strings.Join(got.Findings, ";"), "not installed") {
t.Fatalf("%+v %v", got, err)
}
}
func TestStatusNamesWhatIsWrongWithTheInstallation(t *testing.T) {
dir := t.TempDir()
wasLSM, wasRoot := lsmFile, snapRoot
lsmFile, snapRoot = filepath.Join(dir, "lsm"), filepath.Join(dir, "snap")
defer func() { lsmFile, snapRoot = wasLSM, wasRoot }()
_ = os.WriteFile(lsmFile, []byte("capability,landlock,lockdown,yama,bpf\n"), 0o644)
_ = os.Mkdir(snapRoot, 0o755)
using(t, func(line string, c Cmd) Result {
switch {
case line == "snap version":
return ok("snap 2.76.2-2\nsnapd 2.76.2-2\nseries 16\n")
case strings.HasSuffix(line, "is-enabled snapd.service"), strings.HasSuffix(line, "is-enabled apparmor.service"):
return Result{Status: 1, Stdout: "disabled\n"}
case strings.Contains(line, "is-enabled"):
return ok("enabled\n")
case strings.Contains(line, "is-active snapd.service"):
return ok("active\n")
}
return Result{Status: 3, Stdout: "inactive\n"}
})
got, err := Status()
if err != nil || !got.Installed || got.Version != "2.76.2-2" || got.AppArmor || !got.ClassicRoot || len(got.Units) != 4 {
t.Fatalf("%+v %v", got, err)
}
if got.Units[1].Enabled != "disabled" || got.Units[1].Active != "active" {
t.Errorf("socket-activated service: %+v", got.Units[1])
}
all := strings.Join(got.Findings, ";")
if !strings.Contains(all, "without their confinement") || !strings.Contains(all, "nothing enforces") || strings.Contains(all, "/snap does not exist") {
t.Errorf("%s", all)
}
}
func TestInfoReadsFieldsCommandsAndChannels(t *testing.T) {
using(t, func(string, Cmd) Result {
return ok(`name: code
summary: Code editing. Redefined.
publisher: Visual Studio Code (vscode**)
license: unset
description: |
Visual Studio Code is a new choice
of tool.
commands:
- code
- code.url-handler
tracking: latest/stable
channels:
latest/stable: 07f806f9 2026-09-30 (267) 543MB classic
latest/candidate: ^
`)
})
got, err := Info("code")
if err != nil || got.Fields["summary"] != "Code editing. Redefined." || len(got.Commands) != 2 || got.Fields["tracking"] != "latest/stable" {
t.Fatalf("%+v %v", got, err)
}
if got.Channels["latest/stable"] != "07f806f9 2026-09-30 (267) 543MB classic" || got.Fields["description"] != "Visual Studio Code is a new choice of tool." {
t.Errorf("%+v", got)
}
}
func TestUpdatesAndChangesReadNothingToDoAsEmpty(t *testing.T) {
using(t, func(line string, c Cmd) Result {
if strings.Contains(line, "refresh --list") {
return Result{Stderr: "All snaps up to date.\n"}
}
return Result{Status: 1, Stderr: "error: no changes found\n"}
})
u, err := Updates()
if err != nil || u["count"] != 0 {
t.Fatalf("%v %v", u, err)
}
c, err := Changes("")
if err != nil || len(c["changes"].([]Change)) != 0 {
t.Fatalf("%v %v", c, err)
}
if _, err := Changes("12; reboot"); err == nil {
t.Error("a change id that is not a number")
}
}
func TestChangesAndTasksAreReadByColumn(t *testing.T) {
using(t, func(line string, c Cmd) Result {
if strings.Contains(line, "tasks") {
return ok("Status Spawn Ready Summary\nDone 2026-10-01T18:57:00+02:00 2026-10-01T18:57:10+02:00 Download snap \"code\" (267)\n")
}
return ok("ID Status Spawn Ready Summary\n42 Doing 2026-10-04T10:00:00+02:00 - Install \"hello\" snap\n")
})
c, err := Changes("")
ch := c["changes"].([]Change)
if err != nil || len(ch) != 1 || ch[0].ID != "42" || ch[0].Ready != "" || ch[0].Summary != `Install "hello" snap` {
t.Fatalf("%+v %v", c, err)
}
c, _ = Changes("42")
if tasks := c["tasks"].([]Change); len(tasks) != 1 || tasks[0].Status != "Done" || tasks[0].Summary != `Download snap "code" (267)` {
t.Errorf("%+v", c)
}
}
func TestActsGoThroughSudoWithoutWaitingAndAnswerTheChange(t *testing.T) {
f := using(t, func(line string, c Cmd) Result {
if strings.Contains(line, "refresh") {
return Result{Stderr: "All snaps up to date.\n"}
}
return ok("57\n")
})
got, err := Install("hello-world", "latest/edge", true)
if err != nil || got.Change != "57" || !strings.Contains(got.Follow, "57") {
t.Fatalf("%+v %v", got, err)
}
if _, err := Remove("hello-world", true); err != nil {
t.Fatal(err)
}
r, err := Refresh("")
if err != nil || r.Change != "" || !strings.Contains(r.Said, "up to date") {
t.Fatalf("%+v %v", r, err)
}
want := "sudo -n snap install --no-wait --channel=latest/edge --classic hello-world\n" +
"sudo -n snap remove --no-wait --purge hello-world\n" +
"sudo -n snap refresh --no-wait"
if got := strings.Join(f.lines(), "\n"); got != want {
t.Errorf("asked\n%s", got)
}
for _, bad := range []string{"--classic", "Hello", "a b", "../x"} {
if err := checkSnapName(bad); err == nil {
t.Errorf("%q accepted as a snap name", bad)
}
}
if _, err := Install("x", "--dangerous", false); err == nil {
t.Error("an option as a channel")
}
}
func TestDiskUsageMeasuresEachRevisionAndWhatDisabledOnesTake(t *testing.T) {
dir := t.TempDir()
was := snapsDir
snapsDir = dir
defer func() { snapsDir = was }()
for name, size := range map[string]int{"code_266.snap": 500, "code_267.snap": 510, "bare_5.snap": 4} {
_ = os.WriteFile(filepath.Join(dir, name), make([]byte, size), 0o600)
}
using(t, func(string, Cmd) Result { return ok(listAll) })
got, err := DiskUsage()
if err != nil || got.TotalBytes != 1014 || got.Reclaimable != 500 || len(got.Snaps) != 2 {
t.Fatalf("%+v %v", got, err)
}
if c := got.Snaps[0]; c.Name != "code" || c.Bytes != 1010 || !c.Revisions[0].Disabled || c.Revisions[1].Disabled {
t.Errorf("%+v", c)
}
}