205 lines
8.1 KiB
Go
205 lines
8.1 KiB
Go
//go:build smoke
|
|
|
|
package smoketest
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// testWorkspaceRun ports scenario_workspace_run. Ships the throwaway
|
|
// git repo to a fresh VM and reads the marker file from the guest.
|
|
func testWorkspaceRun(t *testing.T) {
|
|
out := mustBanger(t, "vm", "run", "--rm", repoDir, "--", "cat", "/root/repo/smoke-file.txt")
|
|
wantContains(t, out, "smoke-workspace-marker", "workspace vm run guest read")
|
|
}
|
|
|
|
// testWorkspaceDryrun ports scenario_workspace_dryrun. `--dry-run`
|
|
// lists the tracked files and the resolved transfer mode without
|
|
// creating a VM.
|
|
func testWorkspaceDryrun(t *testing.T) {
|
|
out := mustBanger(t, "vm", "run", "--dry-run", repoDir)
|
|
wantContains(t, out, "smoke-file.txt", "dry-run file list")
|
|
wantContains(t, out, "mode: tracked only", "dry-run mode line")
|
|
}
|
|
|
|
// testIncludeUntracked ports scenario_include_untracked. Drops an
|
|
// untracked file in the fixture and asserts --include-untracked picks
|
|
// it up. The cleanup hook removes the file even if the scenario fails
|
|
// so downstream repodir scenarios see the original tree.
|
|
func testIncludeUntracked(t *testing.T) {
|
|
untracked := filepath.Join(repoDir, "smoke-untracked.txt")
|
|
if err := os.WriteFile(untracked, []byte("untracked-marker\n"), 0o644); err != nil {
|
|
t.Fatalf("write untracked file: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = os.Remove(untracked) })
|
|
|
|
out := mustBanger(t, "vm", "run", "--rm", "--include-untracked", repoDir,
|
|
"--", "cat", "/root/repo/smoke-untracked.txt")
|
|
wantContains(t, out, "untracked-marker", "include-untracked guest read")
|
|
}
|
|
|
|
// testWorkspaceExport ports scenario_workspace_export. Round-trips a
|
|
// guest-side edit back out as a patch via `vm workspace export`.
|
|
func testWorkspaceExport(t *testing.T) {
|
|
const name = "smoke-export"
|
|
vmCreate(t, name, "--image", "debian-bookworm")
|
|
mustBanger(t, "vm", "workspace", "prepare", name, repoDir)
|
|
mustBanger(t, "vm", "ssh", name, "--", "sh", "-c",
|
|
"echo guest-edit > /root/repo/new-guest-file.txt")
|
|
|
|
patch := filepath.Join(runtimeDir, "smoke-export.diff")
|
|
mustBanger(t, "vm", "workspace", "export", name, "--output", patch)
|
|
|
|
st, err := os.Stat(patch)
|
|
if err != nil {
|
|
t.Fatalf("export: stat patch %s: %v", patch, err)
|
|
}
|
|
if st.Size() == 0 {
|
|
t.Fatalf("export: patch file empty at %s", patch)
|
|
}
|
|
body, err := os.ReadFile(patch)
|
|
if err != nil {
|
|
t.Fatalf("export: read patch: %v", err)
|
|
}
|
|
wantContains(t, string(body), "new-guest-file.txt", "export: patch must reference new-guest-file.txt")
|
|
}
|
|
|
|
// testWorkspaceFullCopy ports scenario_workspace_full_copy. Verifies
|
|
// the alternate transfer path (--mode full_copy) lands the same fixture
|
|
// in the guest.
|
|
func testWorkspaceFullCopy(t *testing.T) {
|
|
const name = "smoke-fc"
|
|
vmCreate(t, name)
|
|
mustBanger(t, "vm", "workspace", "prepare", name, repoDir, "--mode", "full_copy")
|
|
|
|
out := mustBanger(t, "vm", "ssh", name, "--", "cat", "/root/repo/smoke-file.txt")
|
|
wantContains(t, out, "smoke-workspace-marker", "full_copy: marker missing in guest")
|
|
}
|
|
|
|
// testWorkspaceBasecommit ports scenario_workspace_basecommit. Confirms
|
|
// that `vm workspace export` without --base-commit captures only the
|
|
// working-copy diff, while --base-commit also captures guest-side
|
|
// commits made on top of HEAD.
|
|
func testWorkspaceBasecommit(t *testing.T) {
|
|
const name = "smoke-basecommit"
|
|
vmCreate(t, name)
|
|
mustBanger(t, "vm", "workspace", "prepare", name, repoDir)
|
|
|
|
baseSHA := strings.TrimSpace(mustBanger(t, "vm", "ssh", name, "--",
|
|
"sh", "-c", "cd /root/repo && git rev-parse HEAD"))
|
|
if len(baseSHA) != 40 {
|
|
t.Fatalf("export base: bad base sha: %q", baseSHA)
|
|
}
|
|
|
|
mustBanger(t, "vm", "ssh", name, "--", "sh", "-c",
|
|
"cd /root/repo && "+
|
|
"git -c user.email=smoke@smoke -c user.name=smoke checkout -b smoke-branch >/dev/null 2>&1 && "+
|
|
"echo committed-marker > smoke-committed.txt && "+
|
|
"git add smoke-committed.txt && "+
|
|
"git -c user.email=smoke@smoke -c user.name=smoke commit -q -m 'guest side'")
|
|
|
|
plain := filepath.Join(runtimeDir, "smoke-plain.diff")
|
|
mustBanger(t, "vm", "workspace", "export", name, "--output", plain)
|
|
if body, err := os.ReadFile(plain); err == nil {
|
|
wantNotContains(t, string(body), "smoke-committed.txt",
|
|
"export base: plain export must NOT capture guest-side commit")
|
|
}
|
|
|
|
base := filepath.Join(runtimeDir, "smoke-base.diff")
|
|
mustBanger(t, "vm", "workspace", "export", name, "--base-commit", baseSHA, "--output", base)
|
|
st, err := os.Stat(base)
|
|
if err != nil || st.Size() == 0 {
|
|
t.Fatalf("export base: --base-commit patch empty/missing: stat=%v err=%v", st, err)
|
|
}
|
|
body, _ := os.ReadFile(base)
|
|
wantContains(t, string(body), "smoke-committed.txt",
|
|
"export base: --base-commit patch must include committed marker")
|
|
}
|
|
|
|
// testWorkspaceRestart ports scenario_workspace_restart. Verifies the
|
|
// workspace marker survives a stop/start cycle (rootfs persistence).
|
|
func testWorkspaceRestart(t *testing.T) {
|
|
const name = "smoke-wsrestart"
|
|
vmCreate(t, name)
|
|
mustBanger(t, "vm", "workspace", "prepare", name, repoDir)
|
|
|
|
pre := mustBanger(t, "vm", "ssh", name, "--", "cat", "/root/repo/smoke-file.txt")
|
|
wantContains(t, pre, "smoke-workspace-marker", "workspace stop/start: pre-cycle marker")
|
|
|
|
mustBanger(t, "vm", "stop", name)
|
|
mustBanger(t, "vm", "start", name)
|
|
waitForSSH(t, name)
|
|
|
|
post := mustBanger(t, "vm", "ssh", name, "--", "cat", "/root/repo/smoke-file.txt")
|
|
wantContains(t, post, "smoke-workspace-marker", "workspace stop/start: post-cycle marker")
|
|
}
|
|
|
|
// testVMExec ports scenario_vm_exec. The longest scenario in the suite
|
|
// — covers auto-cd, exit-code propagation, stale-workspace detection,
|
|
// --auto-prepare resync, and the not-running refusal. The repodir
|
|
// commit added mid-scenario is rolled back via t.Cleanup so subsequent
|
|
// repodir-chain scenarios see the original fixture state.
|
|
func testVMExec(t *testing.T) {
|
|
const name = "smoke-exec"
|
|
vmCreate(t, name)
|
|
mustBanger(t, "vm", "workspace", "prepare", name, repoDir)
|
|
|
|
show := mustBanger(t, "vm", "show", name)
|
|
wantContains(t, show, `"guest_path": "/root/repo"`,
|
|
"vm exec: workspace.guest_path not persisted")
|
|
|
|
out := mustBanger(t, "vm", "exec", name, "--", "cat", "smoke-file.txt")
|
|
wantContains(t, out, "smoke-workspace-marker", "vm exec: workspace marker")
|
|
|
|
if got := strings.TrimSpace(mustBanger(t, "vm", "exec", name, "--", "pwd")); got != "/root/repo" {
|
|
t.Fatalf("vm exec: pwd got %q, want /root/repo (auto-cd didn't happen)", got)
|
|
}
|
|
|
|
res := banger(t, "vm", "exec", name, "--", "sh", "-c", "exit 17")
|
|
wantExit(t, res, 17, "vm exec: exit-code propagation")
|
|
|
|
// Advance host HEAD so the workspace goes stale, register the
|
|
// rollback before mutating so a Fatal anywhere below still
|
|
// restores the fixture.
|
|
t.Cleanup(func() {
|
|
cmd := exec.Command("git", "reset", "--hard", "HEAD~1", "-q")
|
|
cmd.Dir = repoDir
|
|
_ = cmd.Run()
|
|
})
|
|
for _, args := range [][]string{
|
|
{"sh", "-c", "echo post-prepare-marker > smoke-exec-new.txt"},
|
|
{"git", "add", "smoke-exec-new.txt"},
|
|
{"git", "commit", "-q", "-m", "add smoke-exec-new.txt after prepare"},
|
|
} {
|
|
cmd := exec.Command(args[0], args[1:]...)
|
|
cmd.Dir = repoDir
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("vm exec: stage host commit: %s: %v\n%s", args, err, out)
|
|
}
|
|
}
|
|
|
|
stale := banger(t, "vm", "exec", name, "--", "ls", "smoke-exec-new.txt")
|
|
if stale.rc == 0 {
|
|
t.Fatalf("vm exec: stale workspace already had the new file (dirty path didn't take effect)")
|
|
}
|
|
wantContains(t, stale.stderr, "workspace stale", "vm exec: stale-workspace warning on stderr")
|
|
wantContains(t, stale.stderr, "--auto-prepare", "vm exec: stale warning must mention --auto-prepare")
|
|
|
|
auto := mustBanger(t, "vm", "exec", name, "--auto-prepare", "--", "cat", "smoke-exec-new.txt")
|
|
wantContains(t, auto, "post-prepare-marker", "vm exec: --auto-prepare didn't re-sync new file")
|
|
|
|
clean := banger(t, "vm", "exec", name, "--", "true")
|
|
wantExit(t, clean, 0, "vm exec: post-auto-prepare run")
|
|
wantNotContains(t, clean.stderr, "workspace stale", "vm exec: stale warning persisted after --auto-prepare")
|
|
|
|
mustBanger(t, "vm", "stop", name)
|
|
stopped := banger(t, "vm", "exec", name, "--", "true")
|
|
if stopped.rc == 0 {
|
|
t.Fatalf("vm exec: exec on stopped VM unexpectedly succeeded")
|
|
}
|
|
wantContains(t, stopped.stderr, "not running", "vm exec: stopped-VM error message")
|
|
}
|