carlos / platform Public

e2e: finish isolating carlos subprocesses from the real ~/.carlos

Two more accounts-create tests still ran the binary from the real
checkout with the real $HOME. Sweep every subprocess site in the root
package: HOME and CARLOS_CONSOLE overridden, cmd.Dir in scratch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxXEWoRXU8WVTXjTJypuJ5
Paul Campbell 5af2e30bd641753f86bab9ec8b6cd00ee2ff12c0 parent 4686aa6
2 files changed, +72 −12
  • auth_e2e_test.go +8 −0
  • e2e_test.go +64 −12
diff --git a/auth_e2e_test.go b/auth_e2e_test.go
index b62de7985..c0a3339de 100644
--- a/auth_e2e_test.go
+++ b/auth_e2e_test.go
@@ -155,7 +155,11 @@ func TestAccountsCreateWithOwnerFlag(t *testing.T) {
"CARLOS_DEPLOYMENT_DIR="+storeDir,
"CARLOS_CREDENTIALS="+filepath.Join(tmp, "credentials"),
"CARLOS_DATA_DIR="+tmp,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
)
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("carlos accounts create -owner: %v\n%s", err, out)
@@ -203,9 +207,13 @@ func TestAccountsCreateUsesTheLoggedInIdentity(t *testing.T) {
cmd.Env = append(os.Environ(),
"CARLOS_CREDENTIALS="+creds,
"CARLOS_DATA_DIR="+tmp,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
// Deliberately no CARLOS_DEPLOYMENT_DIR: the console owns the bucket
// write on this path, and a laptop has no deployment store configured.
)
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("carlos accounts create (logged in): %v\n%s", err, out)
diff --git a/e2e_test.go b/e2e_test.go
index b0ab741fb..b146aaa60 100644
--- a/e2e_test.go
+++ b/e2e_test.go
@@ -480,7 +480,11 @@ func TestStatusCLI(t *testing.T) {
if err := os.MkdirAll(dryRunDir, 0o755); err != nil {
t.Fatalf("mkdir dry-run dir: %v", err)
}
- out, err := exec.Command(carlosBin, "status", "-dry-run", dryRunDir).CombinedOutput()
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd := exec.Command(carlosBin, "status", "-dry-run", dryRunDir)
+ cmd.Env = append(os.Environ(), "HOME="+tmp, "CARLOS_CONSOLE=")
+ cmd.Dir = tmp
+ out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("carlos status -dry-run %s: %v\n%s", dryRunDir, err, out)
}
@@ -488,7 +492,10 @@ func TestStatusCLI(t *testing.T) {
t.Errorf("carlos status -dry-run output = %q, want it to contain %q", out, "dry-run: no leaks")
}
- if err := exec.Command(carlosBin, "status").Run(); err == nil {
+ cmd = exec.Command(carlosBin, "status")
+ cmd.Env = append(os.Environ(), "HOME="+tmp, "CARLOS_CONSOLE=")
+ cmd.Dir = tmp
+ if err := cmd.Run(); err == nil {
t.Error("carlos status with no -dry-run flag: want a non-zero exit, got 0")
}
}
@@ -1164,8 +1171,12 @@ func TestAddUnitBackingRequiresDBForHibernate(t *testing.T) {
regPath := filepath.Join(tmp, "registry.db")
// No -db: refused, non-zero exit, no row written.
- out, err := exec.Command(carlosBin, "add", "-registry", regPath, "-app", "kass",
- "-backing", "unit", "-hibernate", "no-db.example.com").CombinedOutput()
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd := exec.Command(carlosBin, "add", "-registry", regPath, "-app", "kass",
+ "-backing", "unit", "-hibernate", "no-db.example.com")
+ cmd.Env = append(os.Environ(), "HOME="+tmp, "CARLOS_CONSOLE=")
+ cmd.Dir = tmp
+ out, err := cmd.CombinedOutput()
if err == nil {
t.Fatalf("add -backing unit -hibernate with no -db: want non-zero exit, got 0\n%s", out)
}
@@ -1176,8 +1187,11 @@ func TestAddUnitBackingRequiresDBForHibernate(t *testing.T) {
// With -db: succeeds, and the route's DBPath is exactly what was given
// (ops.FleetDBs reads this verbatim — no default synthesized here).
dbPath := filepath.Join(tmp, "unit-state", "app.db")
- out, err = exec.Command(carlosBin, "add", "-registry", regPath, "-app", "kass",
- "-backing", "unit", "-hibernate", "-db", dbPath, "with-db.example.com").CombinedOutput()
+ cmd = exec.Command(carlosBin, "add", "-registry", regPath, "-app", "kass",
+ "-backing", "unit", "-hibernate", "-db", dbPath, "with-db.example.com")
+ cmd.Env = append(os.Environ(), "HOME="+tmp, "CARLOS_CONSOLE=")
+ cmd.Dir = tmp
+ out, err = cmd.CombinedOutput()
if err != nil {
t.Fatalf("add -backing unit -hibernate -db %s: %v\n%s", dbPath, err, out)
}
@@ -1362,11 +1376,17 @@ func TestEnvCLI(t *testing.T) {
}
storeDir := filepath.Join(tmp, "store")
regPath := filepath.Join(tmp, "registry.db")
- env := append(os.Environ(), "CARLOS_DEPLOYMENT_DIR="+storeDir)
+ env := append(os.Environ(),
+ "CARLOS_DEPLOYMENT_DIR="+storeDir,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
+ )
run := func(args ...string) string {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("carlos %s: %v\n%s", strings.Join(args, " "), err, out)
@@ -1491,11 +1511,17 @@ func TestEnvironmentBundlesCLI(t *testing.T) {
t.Fatalf("build carlos: %v\n%s", err, out)
}
storeDir := filepath.Join(tmp, "store")
- env := append(os.Environ(), "CARLOS_DEPLOYMENT_DIR="+storeDir)
+ env := append(os.Environ(),
+ "CARLOS_DEPLOYMENT_DIR="+storeDir,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
+ )
run := func(args ...string) string {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("carlos %s: %v\n%s", strings.Join(args, " "), err, out)
@@ -1506,6 +1532,8 @@ func TestEnvironmentBundlesCLI(t *testing.T) {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatalf("carlos %s: succeeded, want a refusal\n%s", strings.Join(args, " "), out)
@@ -1638,11 +1666,17 @@ func TestExecDeliveryCLI(t *testing.T) {
t.Fatalf("build carlos: %v\n%s", err, out)
}
storeDir := filepath.Join(tmp, "store")
- env := append(os.Environ(), "CARLOS_DEPLOYMENT_DIR="+storeDir)
+ env := append(os.Environ(),
+ "CARLOS_DEPLOYMENT_DIR="+storeDir,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
+ )
run := func(args ...string) string {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("carlos %s: %v\n%s", strings.Join(args, " "), err, out)
@@ -1653,6 +1687,8 @@ func TestExecDeliveryCLI(t *testing.T) {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatalf("carlos %s: succeeded, want a refusal\n%s", strings.Join(args, " "), out)
@@ -1744,11 +1780,17 @@ func TestEnvironmentMergeVisibleFromCLI(t *testing.T) {
t.Fatalf("build carlos: %v\n%s", err, out)
}
storeDir := filepath.Join(tmp, "store")
- env := append(os.Environ(), "CARLOS_DEPLOYMENT_DIR="+storeDir)
+ env := append(os.Environ(),
+ "CARLOS_DEPLOYMENT_DIR="+storeDir,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
+ )
run := func(args ...string) {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("carlos %s: %v\n%s", strings.Join(args, " "), err, out)
}
@@ -1822,11 +1864,16 @@ func TestRouteCLI(t *testing.T) {
}
env := append(os.Environ(),
"CARLOS_DEPLOYMENT_DIR="+storeDir,
- "CARLOS_RELEASE_PUBKEY="+pubB64)
+ "CARLOS_RELEASE_PUBKEY="+pubB64,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
+ )
run := func(args ...string) (string, error) {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
return string(out), err
}
@@ -2982,11 +3029,16 @@ func TestAccountsMigrateLegacyKeepsNewerQualifiedPointer(t *testing.T) {
}
env := append(os.Environ(),
"CARLOS_DEPLOYMENT_DIR="+storeDir,
- "CARLOS_RELEASE_PUBKEY="+pubB64)
+ "CARLOS_RELEASE_PUBKEY="+pubB64,
+ "HOME="+tmp,
+ "CARLOS_CONSOLE=",
+ )
run := func(args ...string) (string, error) {
t.Helper()
cmd := exec.Command(carlosBin, args...)
cmd.Env = env
+ // pick() walks cwd ancestors for .carlos/config, so the subprocess must not run from the real checkout.
+ cmd.Dir = tmp
out, err := cmd.CombinedOutput()
return string(out), err
}