Files
mesh-controller/internal/catalogue/environment_items_test.go
T

166 lines
7.2 KiB
Go

package catalogue
import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
// The session environment is a collection of typed items (design 57 L3, issue 795): a variable is an item with
// kind=variable and name=<NAME>, a PATH entry one with kind=path and at=start|end, each a template whose text is the
// value or the entry. Where node-env is not handed the controller renders the items as the old form was rendered;
// where it is, it names the collection for the client, which fills the same files (mesh-catalog's provisioner client
// holds that half against the same golden files). testdata/environment holds the environment of the catalogue's five
// contributors as the old form composed it, with all five and with zsh alone.
type envDecl struct{ module, item, name, value string }
func environmentOfTheCatalogue() []envDecl {
return []envDecl{
{"adwaita", "gtk-theme", "GTK_THEME", "Adwaita:dark"},
{"adwaita", "gtk2-rc-files", "GTK2_RC_FILES", "/usr/share/themes/Adwaita-dark/gtk-2.0/gtkrc"},
{"adwaita", "qt-qpa-platformtheme", "QT_QPA_PLATFORMTHEME", "qt6ct"},
{"adwaita", "qt-style-override", "QT_STYLE_OVERRIDE", "Fusion"},
{"adwaita", "qt-select", "QT_SELECT", "6"},
{"adwaita", "xcursor-theme", "XCURSOR_THEME", "Adwaita"},
{"adwaita", "xcursor-size", "XCURSOR_SIZE", "24"},
{"clipmenu", "cm-selections", "CM_SELECTIONS", "clipboard"},
{"clipmenu", "cm-max-clips", "CM_MAX_CLIPS", "500"},
{"clipmenu", "cm-histlength", "CM_HISTLENGTH", "15"},
{"i3", "xdg-current-desktop", "XDG_CURRENT_DESKTOP", "i3"},
{"i3", "xdg-session-desktop", "XDG_SESSION_DESKTOP", "i3"},
{"xterm", "terminal", "TERMINAL", "xterm"},
{"zsh", "editor", "EDITOR", "vim"},
{"zsh", "visual", "VISUAL", "vim"},
{"zsh", "xdg-config-home", "XDG_CONFIG_HOME", "${machine:account-home}/.config"},
{"zsh", "path-local-bin", "path:10", "${machine:account-home}/.local/bin"},
{"zsh", "path-scripts", "path:20", "${machine:account-home}/scripts"},
{"zsh", "path-scripts-bin", "path:30", "${machine:account-home}/scripts/bin"},
}
}
const environmentCollector = `{"module":"node-env","version":"1","moved":true,
"claims":[{"name":"node-environment","scope":"node"}],
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
"resources":[{"id":"posix","type":"file","path":"/home/op/.config/mesh/environment.sh","template":"files/posix"},
{"id":"systemd","type":"file","path":"/home/op/.config/environment.d/50-mesh.conf","template":"files/systemd"}]}`
// nodeEnvWith is node-env, its file placing the environment as plan says, with the declared contributors' items.
func nodeEnvWith(t *testing.T, decls []envDecl, plan string) Resolution {
t.Helper()
collector := held(t, environmentCollector, builtWith("node-env", map[string]string{
"posix": "# header\n" + plan, "systemd": "${environment:systemd}"}))
mods := []Manifest{collector}
order := []string{"node-env"}
byModule := map[string][]envDecl{}
var modules []string
for _, d := range decls {
if byModule[d.module] == nil {
modules = append(modules, d.module)
}
byModule[d.module] = append(byModule[d.module], d)
}
for _, name := range modules {
var items []string
texts := map[string]string{}
for _, d := range byModule[name] {
attrs := fmt.Sprintf(`{"kind":"variable","name":%q}`, d.name)
if order, isPath := strings.CutPrefix(d.name, "path:"); isPath {
attrs = fmt.Sprintf(`{"kind":"path","at":"start","order":%q}`, order)
}
items = append(items, fmt.Sprintf(`{"collection":"environment","name":%q,"attributes":%s,"template":"files/env-%s"}`,
d.item, attrs, d.item))
texts["env-"+d.item] = d.value + "\n"
}
raw := fmt.Sprintf(`{"module":%q,"version":"1","moved":true,"items":[%s],
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]}}`, name, strings.Join(items, ","))
mods = append(mods, held(t, raw, builtWith(name, texts)))
order = append(order, name)
}
r, err := Resolve(shelf(mods...), order, workstation(), World{})
if err != nil {
t.Fatal(err)
}
r.Account = "op"
return r
}
func TestTheEnvironmentOfTheCatalogueIsTheSameHandedOrNot(t *testing.T) {
read := func(name string) string {
b, err := os.ReadFile(filepath.Join("testdata", "environment", name))
if err != nil {
t.Fatal(err)
}
return string(b)
}
for name, only := range map[string]string{"all": "", "zsh": "zsh"} {
var decls []envDecl
for _, d := range environmentOfTheCatalogue() {
if only == "" || d.module == only {
decls = append(decls, d)
}
}
// Named as the collector names it, and as the old placeholder does.
for _, plan := range []string{"${collected:environment:posix}", "${environment:posix}"} {
r := nodeEnvWith(t, decls, plan)
// Not handed: the controller renders each moved contributor's items as the old form was rendered.
if got, want := composedFile(t, r, "node-env.posix"), "# header\n"+read(name+".posix"); got != want {
t.Errorf("%s, %s, not handed, the POSIX file is\n%s\nwant\n%s", name, plan, got, want)
}
if got, want := composedFile(t, r, "node-env.systemd"), read(name+".systemd"); got != want {
t.Errorf("%s, %s, not handed, the environment.d file is\n%s\nwant\n%s", name, plan, got, want)
}
// Handed: one collection, named for the client; each contributor's plan names its items.
c, _ := composeWith(t, r, handingTo("store:5000"))
if got := c.Plans["node-env"].Collections; !reflect.DeepEqual(got, []PlanCollection{{Name: "environment", Comment: "#"}}) {
t.Errorf("%s: the plan collects %+v", name, got)
}
items, got := len(decls), 0
for m := range c.Plans {
got += len(c.Plans[m].Items)
}
if got != items {
t.Errorf("%s: the plans name %d items, want %d", name, got, items)
}
}
}
}
func TestAnEnvironmentItemIsTypedAndNamesWhatItNeeds(t *testing.T) {
item := func(attrs map[string]string) []string {
return environmentItemProblems("x", Item{Collection: "environment", Attributes: attrs})
}
for _, ok := range []map[string]string{
{"kind": "variable", "name": "GTK_THEME"},
{"kind": "path", "at": "end", "order": "10"},
} {
if p := item(ok); len(p) != 0 {
t.Errorf("%v is refused: %v", ok, p)
}
}
for _, bad := range []map[string]string{
nil, {"kind": "other"}, {"kind": "variable"}, {"kind": "variable", "name": "PATH"},
{"kind": "path"}, {"kind": "path", "at": "middle"}, {"kind": "path", "at": "start", "name": "X"},
{"kind": "variable", "name": "A", "color": "red"},
} {
if p := item(bad); len(p) == 0 {
t.Errorf("%v is taken", bad)
}
}
if !attributeFits("environment", "name", "GTK_THEME") || attributeFits("shell:zsh:normal", "name", "GTK_THEME") ||
attributeFits("environment", "kind", "Variable") {
t.Error("an upper-case name is the environment's name alone")
}
// A module that sets a variable twice, in two modules, is refused as the old form refused it.
a := Manifest{Module: "a", Items: []Item{{Collection: "environment", Content: "1\n",
Attributes: map[string]string{"kind": "variable", "name": "X"}}}}
b := Manifest{Module: "b", Items: []Item{{Collection: "environment", Content: "2\n",
Attributes: map[string]string{"kind": "variable", "name": "X"}}}}
if err := variablesSetOnce([]Manifest{a, b}); err == nil || !strings.Contains(err.Error(), "both set X") {
t.Errorf("two modules setting X: %v", err)
}
}