//go:build integration package resolution import ( "os" "os/exec" "path/filepath" "strings" "testing" ) func TestThePOSIXEnvironmentSourcedTwiceLeavesPATHAsOnce(t *testing.T) { sh, err := exec.LookPath("sh") if err != nil { t.Skip("no sh on this machine") } script := "PATH=/usr/bin:/bin\n" + composedPOSIX + "once=$PATH\n" + composedPOSIX + `[ "$PATH" = "$once" ] || { echo "changed: $once -> $PATH"; exit 1; }` + "\n" + `echo "$PATH"; echo "$GOPATH"` out, err := exec.Command(sh, "-c", script).CombinedOutput() if err != nil { t.Fatalf("sourcing twice: %v\n%s", err, out) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") want := "/home/op/go/bin:/usr/local/go/bin:/home/op/.local/bin:/home/op/bin:/usr/bin:/bin:/opt/agent/bin:/opt/scripts" if lines[0] != want { t.Fatalf("PATH is %s, not %s", lines[0], want) } if lines[1] != "/home/op/go" { t.Fatalf("GOPATH was not exported: %q", lines[1]) } // And an entry the account already has stays where it is, and once. out, err = exec.Command(sh, "-c", "PATH=/opt/scripts:/usr/bin\n"+composedPOSIX+`echo "$PATH"`).CombinedOutput() if err != nil { t.Fatalf("%v\n%s", err, out) } if got := strings.TrimSpace(string(out)); got != "/home/op/go/bin:/usr/local/go/bin:/home/op/.local/bin:/home/op/bin:/opt/scripts:/usr/bin:/opt/agent/bin" { t.Fatalf("an entry already on PATH was added again or moved: %s", got) } } func TestTheServiceManagerReadsTheSystemdRenderingAsMeant(t *testing.T) { generator := "/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator" if _, err := os.Stat(generator); err != nil { t.Skip("NOT JUDGED: no environment.d generator on this machine, so the service manager's reading " + "of the rendering is judged only where systemd runs; the rendering itself is held byte for byte " + "by TestTheEnvironmentRendersForTheServiceManagerByteForByte") } const base = "/usr/bin:/bin" read := func(conf string) map[string]string { t.Helper() config := t.TempDir() if err := os.MkdirAll(filepath.Join(config, "environment.d"), 0o755); err != nil { t.Fatal(err) } if conf != "" { if err := os.WriteFile(filepath.Join(config, "environment.d", "50-mesh.conf"), []byte(conf), 0o644); err != nil { t.Fatal(err) } } cmd := exec.Command(generator) cmd.Env = []string{"PATH=" + base, "HOME=" + config, "XDG_CONFIG_HOME=" + config} out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("%v\n%s", err, out) } vars := map[string]string{} for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if k, v, ok := strings.Cut(line, "="); ok { vars[k] = v } } return vars } machine, read50 := read(""), read(composedSystemd) // What the machine's own files do to PATH: nothing, or append to it. One that replaces or prepends // leaves nothing this test can say about the mesh's file, and says so rather than guess. added, ok := strings.CutPrefix(machine["PATH"], base) if machine["PATH"] != "" && !ok { t.Skipf("NOT JUDGED: this machine's own environment.d sets PATH to %s, not %s with something after it, so "+ "what the mesh's file adds cannot be told apart from it", machine["PATH"], base) } want := "/home/op/go/bin:/usr/local/go/bin:/home/op/.local/bin:/home/op/bin:/usr/bin:/bin:/opt/agent/bin:/opt/scripts" + added if read50["PATH"] != want || read50["GOPATH"] != "/home/op/go" { t.Fatalf("the service manager read PATH=%s GOPATH=%s, not PATH=%s GOPATH=/home/op/go (the machine's own "+ "files add %q to PATH)", read50["PATH"], read50["GOPATH"], want, added) } }