package system import ( "context" "errors" "os" "path/filepath" "reflect" "strings" "testing" ) type systemCall struct { sudo bool name string args []string } type systemStep struct { call systemCall out []byte err error } type scriptedRunner struct { t *testing.T steps []systemStep calls []systemCall } func (r *scriptedRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) { return r.next(systemCall{name: name, args: append([]string(nil), args...)}) } func (r *scriptedRunner) RunSudo(ctx context.Context, args ...string) ([]byte, error) { return r.next(systemCall{sudo: true, args: append([]string(nil), args...)}) } func (r *scriptedRunner) next(call systemCall) ([]byte, error) { r.t.Helper() r.calls = append(r.calls, call) if len(r.steps) == 0 { r.t.Fatalf("unexpected call: %+v", call) } step := r.steps[0] r.steps = r.steps[1:] if step.call.sudo != call.sudo || step.call.name != call.name || !reflect.DeepEqual(step.call.args, call.args) { r.t.Fatalf("call mismatch:\n got: %+v\n want: %+v", call, step.call) } return step.out, step.err } func (r *scriptedRunner) assertExhausted() { r.t.Helper() if len(r.steps) != 0 { r.t.Fatalf("unconsumed steps: %+v", r.steps) } } type funcRunner struct { run func(ctx context.Context, name string, args ...string) ([]byte, error) runSudo func(ctx context.Context, args ...string) ([]byte, error) } func (r funcRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) { if r.run == nil { return nil, errors.New("unexpected Run call") } return r.run(ctx, name, args...) } func (r funcRunner) RunSudo(ctx context.Context, args ...string) ([]byte, error) { if r.runSudo == nil { return nil, errors.New("unexpected RunSudo call") } return r.runSudo(ctx, args...) } func TestResizeExt4ImageStopsAtFirstFailure(t *testing.T) { t.Parallel() tests := []struct { name string setup func(t *testing.T) string steps func(path string) []systemStep wantErr string wantCalls int }{ { name: "truncate failure", setup: func(t *testing.T) string { return t.TempDir() }, wantErr: "", wantCalls: 0, }, { name: "e2fsck failure", steps: func(path string) []systemStep { return []systemStep{ {call: systemCall{name: "e2fsck", args: []string{"-p", "-f", path}}, err: errors.New("e2fsck failed")}, } }, wantErr: "e2fsck failed", wantCalls: 1, }, { name: "resize2fs failure", steps: func(path string) []systemStep { return []systemStep{ {call: systemCall{name: "e2fsck", args: []string{"-p", "-f", path}}}, {call: systemCall{name: "resize2fs", args: []string{path}}, err: errors.New("resize2fs failed")}, } }, wantErr: "resize2fs failed", wantCalls: 2, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() path := "/tmp/root.ext4" if tt.setup != nil { path = tt.setup(t) } else { path = filepath.Join(t.TempDir(), "root.ext4") if err := os.WriteFile(path, []byte("seed"), 0o644); err != nil { t.Fatalf("WriteFile(%s): %v", path, err) } } var steps []systemStep if tt.steps != nil { steps = tt.steps(path) } runner := &scriptedRunner{t: t, steps: steps} err := ResizeExt4Image(context.Background(), runner, path, 4096) if err == nil { t.Fatal("ResizeExt4Image() succeeded, want error") } if tt.wantErr != "" && !strings.Contains(err.Error(), tt.wantErr) { t.Fatalf("ResizeExt4Image() error = %v, want %q", err, tt.wantErr) } if len(runner.calls) != tt.wantCalls { t.Fatalf("call count = %d, want %d", len(runner.calls), tt.wantCalls) } runner.assertExhausted() }) } } func TestReadNormalizedLines(t *testing.T) { t.Parallel() path := filepath.Join(t.TempDir(), "packages.apt") if err := os.WriteFile(path, []byte("\n# comment\n git \nless # trailing\n\r\ntmux\r\n"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } got, err := ReadNormalizedLines(path) if err != nil { t.Fatalf("ReadNormalizedLines: %v", err) } want := []string{"git", "less", "tmux"} if !reflect.DeepEqual(got, want) { t.Fatalf("lines = %v, want %v", got, want) } } func TestBuildBootArgsOmitsKernelIPAutoconfig(t *testing.T) { t.Parallel() got := BuildBootArgs("devbox") want := "console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rootfstype=ext4 rw hostname=devbox systemd.mask=home.mount systemd.mask=var.mount" if got != want { t.Fatalf("BuildBootArgs() = %q, want %q", got, want) } } func TestBuildBootArgsWithKernelIPIncludesHostnameInIPField(t *testing.T) { t.Parallel() got := BuildBootArgsWithKernelIP("devbox", "172.16.0.2", "172.16.0.1", "1.1.1.1") want := "console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rootfstype=ext4 rw ip=172.16.0.2::172.16.0.1:255.255.255.0:devbox:eth0:off:1.1.1.1 hostname=devbox systemd.mask=home.mount systemd.mask=var.mount" if got != want { t.Fatalf("BuildBootArgsWithKernelIP() = %q, want %q", got, want) } } func TestWriteExt4FileRemovesTempFileAndReturnsCopyError(t *testing.T) { t.Parallel() copyErr := errors.New("e2cp failed") var tempPath string runner := funcRunner{ runSudo: func(ctx context.Context, args ...string) ([]byte, error) { switch args[0] { case "e2rm": return nil, errors.New("ignore remove") case "e2cp": tempPath = args[1] if _, err := os.Stat(tempPath); err != nil { t.Fatalf("temp file missing during e2cp: %v", err) } return nil, copyErr default: t.Fatalf("unexpected sudo call: %v", args) return nil, nil } }, } err := WriteExt4File(context.Background(), runner, "/tmp/root.ext4", "/etc/hostname", []byte("devbox\n")) if !errors.Is(err, copyErr) { t.Fatalf("WriteExt4File() error = %v, want %v", err, copyErr) } if tempPath == "" { t.Fatal("expected e2cp temp path to be recorded") } if _, err := os.Stat(tempPath); !errors.Is(err, os.ErrNotExist) { t.Fatalf("temp file still exists after WriteExt4File: %v", err) } } func TestWriteExt4FileModeUsesRequestedPermissions(t *testing.T) { t.Parallel() debugfsCalled := false runner := funcRunner{ runSudo: func(ctx context.Context, args ...string) ([]byte, error) { switch args[0] { case "e2rm": return nil, nil case "e2cp": info, err := os.Stat(args[1]) if err != nil { t.Fatalf("Stat(temp file): %v", err) } if got := info.Mode().Perm(); got != 0o755 { t.Fatalf("temp file mode = %o, want 755", got) } return nil, nil case "debugfs": debugfsCalled = true want := []string{"debugfs", "-w", "-R", "sif /usr/local/libexec/banger-network-bootstrap mode 0100755", "/tmp/root.ext4"} if !reflect.DeepEqual(args, want) { t.Fatalf("debugfs args = %v, want %v", args, want) } return nil, nil default: t.Fatalf("unexpected sudo call: %v", args) return nil, nil } }, } if err := WriteExt4FileMode(context.Background(), runner, "/tmp/root.ext4", "/usr/local/libexec/banger-network-bootstrap", 0o755, []byte("#!/bin/sh\n")); err != nil { t.Fatalf("WriteExt4FileMode() error = %v", err) } if !debugfsCalled { t.Fatal("expected debugfs mode fixup to run") } } func TestMountTempDirUsesLoopForRegularFilesAndCleanupUsesBackgroundContext(t *testing.T) { t.Parallel() source := filepath.Join(t.TempDir(), "root.ext4") if err := os.WriteFile(source, []byte("rootfs"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } ctx, cancel := context.WithCancel(context.Background()) var mountDir string calls := 0 runner := funcRunner{ runSudo: func(callCtx context.Context, args ...string) ([]byte, error) { calls++ switch calls { case 1: mountDir = args[len(args)-1] want := []string{"mount", "-o", "ro,loop", source, mountDir} if !reflect.DeepEqual(args, want) { t.Fatalf("mount args = %v, want %v", args, want) } case 2: if callCtx.Err() != nil { t.Fatalf("cleanup context should not be canceled: %v", callCtx.Err()) } want := []string{"umount", mountDir} if !reflect.DeepEqual(args, want) { t.Fatalf("cleanup args = %v, want %v", args, want) } default: t.Fatalf("unexpected RunSudo call %d: %v", calls, args) } return nil, nil }, } gotMountDir, cleanup, err := MountTempDir(ctx, runner, source, true) if err != nil { t.Fatalf("MountTempDir: %v", err) } if gotMountDir != mountDir { t.Fatalf("mount dir = %q, want %q", gotMountDir, mountDir) } cancel() if err := cleanup(); err != nil { t.Fatalf("cleanup: %v", err) } if _, err := os.Stat(mountDir); !errors.Is(err, os.ErrNotExist) { t.Fatalf("mount dir still exists after cleanup: %v", err) } } func TestParseProcHelpers(t *testing.T) { t.Parallel() stat, err := parseProcStat("1234 (firecracker) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22") if err != nil { t.Fatalf("parseProcStat: %v", err) } if stat.userTicks != 11 || stat.systemTicks != 12 || stat.startTicks != 19 { t.Fatalf("proc stat = %+v", stat) } statm, err := parseProcStatm("200 50 0 0 0 0 0") if err != nil { t.Fatalf("parseProcStatm: %v", err) } if statm.sizePages != 200 || statm.residentPages != 50 { t.Fatalf("proc statm = %+v", statm) } uptime, err := parseProcUptime("321.50 42.10") if err != nil { t.Fatalf("parseProcUptime: %v", err) } if uptime != 321.50 { t.Fatalf("uptime = %v, want 321.50", uptime) } } func TestParseMemTotal(t *testing.T) { t.Parallel() total, err := parseMemTotal("MemTotal: 131900020 kB\nMemFree: 1024 kB\n") if err != nil { t.Fatalf("parseMemTotal: %v", err) } if total != 131900020*1024 { t.Fatalf("total = %d, want %d", total, int64(131900020*1024)) } } func TestParseMemTotalErrorsWhenMissing(t *testing.T) { t.Parallel() if _, err := parseMemTotal("MemFree: 123 kB\n"); err == nil { t.Fatal("parseMemTotal() error = nil, want missing MemTotal") } } func TestReadFilesystemUsage(t *testing.T) { t.Parallel() usage, err := ReadFilesystemUsage(t.TempDir()) if err != nil { t.Fatalf("ReadFilesystemUsage: %v", err) } if usage.TotalBytes <= 0 { t.Fatalf("usage.TotalBytes = %d, want positive", usage.TotalBytes) } if usage.FreeBytes < 0 { t.Fatalf("usage.FreeBytes = %d, want non-negative", usage.FreeBytes) } } func TestMountTempDirRemovesTempDirWhenMountFails(t *testing.T) { t.Parallel() source := t.TempDir() var mountDir string runner := funcRunner{ runSudo: func(ctx context.Context, args ...string) ([]byte, error) { mountDir = args[len(args)-1] return nil, errors.New("mount failed") }, } _, _, err := MountTempDir(context.Background(), runner, source, false) if err == nil || !strings.Contains(err.Error(), "mount failed") { t.Fatalf("MountTempDir() error = %v, want mount failure", err) } if mountDir == "" { t.Fatal("expected mount path to be recorded") } if _, statErr := os.Stat(mountDir); !errors.Is(statErr, os.ErrNotExist) { t.Fatalf("mount dir still exists after failed mount: %v", statErr) } } func TestParseMetricsFileHandlesWholeAndLineDelimitedJSON(t *testing.T) { t.Parallel() full := filepath.Join(t.TempDir(), "metrics-full.json") if err := os.WriteFile(full, []byte(`{"uptime":1,"signals":{"sigterm":0}}`), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } got := ParseMetricsFile(full) if got["uptime"] != float64(1) { t.Fatalf("ParseMetricsFile(full) = %v", got) } lines := filepath.Join(t.TempDir(), "metrics-lines.json") if err := os.WriteFile(lines, []byte("junk\n{\"uptime\":2}\n"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } got = ParseMetricsFile(lines) if got["uptime"] != float64(2) { t.Fatalf("ParseMetricsFile(lines) = %v", got) } } func TestUpdateFSTabStripsLegacyMountsAndAddsDefaultsOnce(t *testing.T) { t.Parallel() input := strings.Join([]string{ "/dev/vda / ext4 defaults 0 1", "/dev/vdb /home ext4 defaults 0 2", "/dev/vdc /var ext4 defaults 0 2", "/dev/vdb /root ext4 defaults 0 2", "tmpfs /run tmpfs defaults,nodev,nosuid,mode=0755 0 0", "", }, "\n") got := UpdateFSTab(input) if strings.Contains(got, "/home") || strings.Contains(got, "/var") { t.Fatalf("UpdateFSTab() kept legacy mounts: %q", got) } if strings.Count(got, "/dev/vdb /root ext4 defaults 0 2") != 1 { t.Fatalf("UpdateFSTab() duplicated /root mount: %q", got) } if strings.Count(got, "tmpfs /run tmpfs defaults,nodev,nosuid,mode=0755 0 0") != 1 { t.Fatalf("UpdateFSTab() duplicated /run mount: %q", got) } if !strings.Contains(got, "tmpfs /tmp tmpfs defaults,nodev,nosuid,mode=1777 0 0") { t.Fatalf("UpdateFSTab() missing /tmp mount: %q", got) } } func TestUseLoopMount(t *testing.T) { t.Parallel() file := filepath.Join(t.TempDir(), "root.ext4") if err := os.WriteFile(file, []byte("rootfs"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } dir := t.TempDir() if !useLoopMount(file) { t.Fatalf("useLoopMount(%s) = false, want true", file) } if useLoopMount(dir) { t.Fatalf("useLoopMount(%s) = true, want false", dir) } if useLoopMount(filepath.Join(dir, "missing")) { t.Fatalf("useLoopMount(missing) = true, want false") } } func TestEstimateWorkSeedSizeFallsBackToSudoDuWhenUnreadable(t *testing.T) { t.Parallel() rootHome := filepath.Join(t.TempDir(), "root") if err := os.Mkdir(rootHome, 0o700); err != nil { t.Fatalf("Mkdir: %v", err) } if err := os.WriteFile(filepath.Join(rootHome, "visible.txt"), []byte("seed"), 0o600); err != nil { t.Fatalf("WriteFile: %v", err) } if err := os.Chmod(rootHome, 0o000); err != nil { t.Fatalf("Chmod: %v", err) } defer os.Chmod(rootHome, 0o700) var sudoCalled bool runner := funcRunner{ runSudo: func(ctx context.Context, args ...string) ([]byte, error) { sudoCalled = true want := []string{"du", "-sb", rootHome} if !reflect.DeepEqual(args, want) { t.Fatalf("RunSudo args = %v, want %v", args, want) } return []byte("4096\t" + rootHome + "\n"), nil }, } sizeBytes, err := estimateWorkSeedSize(context.Background(), runner, rootHome) if err != nil { t.Fatalf("estimateWorkSeedSize: %v", err) } if !sudoCalled { t.Fatal("estimateWorkSeedSize did not fall back to sudo du") } if sizeBytes != minWorkSeedBytes { t.Fatalf("sizeBytes = %d, want %d", sizeBytes, minWorkSeedBytes) } }