diff --git a/test/e2e/vm_user_journey_test.go b/test/e2e/vm_user_journey_test.go index c262f46..4b27789 100644 --- a/test/e2e/vm_user_journey_test.go +++ b/test/e2e/vm_user_journey_test.go @@ -41,10 +41,10 @@ func TestVM_Journey_FirstTimeUser(t *testing.T) { vm := testutil.NewMacHost(t) vmInstallHomebrew(t, vm) - // Uninstall the minimal preset packages so openboot actually installs them - // rather than finding them pre-installed on the GHA runner image. + // Remove the brew-installed openboot (left by TestVM_Interactive_InstallScript) + // and the minimal preset packages so openboot actually installs them. vm.Run(fmt.Sprintf( - "export PATH=%q && brew uninstall --ignore-dependencies jq ripgrep fd bat fzf htop tree gh 2>/dev/null || true", + "export PATH=%q && brew uninstall --ignore-dependencies openboot jq ripgrep fd bat fzf htop tree gh 2>/dev/null || true", brewPath, )) bin := vmCopyDevBinary(t, vm) diff --git a/testutil/machost.go b/testutil/machost.go index adcdd72..c40e502 100644 --- a/testutil/machost.go +++ b/testutil/machost.go @@ -73,7 +73,10 @@ func (h *MacHost) RunInteractive(command string, steps []ExpectStep, timeoutSec var script strings.Builder fmt.Fprintf(&script, "set timeout %d\n", timeoutSec) - fmt.Fprintf(&script, "spawn bash -c %s\n", shellescape(command)) + // Use Tcl quoting so the entire command is a single word. + // shellescape produces POSIX single-quote escaping which Tcl's word + // splitter does not honour — it splits on whitespace regardless. + fmt.Fprintf(&script, "spawn bash -c %s\n", tclBrace(command)) for _, step := range steps { fmt.Fprintf(&script, "expect %q\n", step.Expect) fmt.Fprintf(&script, "send %q\n", step.Send) @@ -106,6 +109,16 @@ func shellescape(s string) string { return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'" } +// tclBrace quotes s as a single Tcl word. Uses brace quoting {s} when safe +// (no unbalanced braces), otherwise falls back to Tcl double-quote escaping. +func tclBrace(s string) string { + if !strings.ContainsAny(s, "{}") { + return "{" + s + "}" + } + r := strings.NewReplacer(`\`, `\\`, `"`, `\"`, `$`, `\$`, `[`, `\[`) + return `"` + r.Replace(s) + `"` +} + func requireEphemeralHost(t *testing.T) { t.Helper() if os.Getenv("OPENBOOT_IN_VM") == "1" {