The controller plans config, templates and moved providers' provisions, hands bus accounts to the vault and judges the new manifest words, on the feature's SDK and node-engine validator (feature 57, config and provisioning).
166 lines
7.2 KiB
Go
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)
|
|
}
|
|
}
|