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 steps []systemStep wantErr string wantCalls int }{ { name: "truncate failure", steps: []systemStep{ {call: systemCall{name: "truncate", args: []string{"-s", "4096", "/tmp/root.ext4"}}, err: errors.New("truncate failed")}, }, wantErr: "truncate failed", wantCalls: 1, }, { name: "e2fsck failure", steps: []systemStep{ {call: systemCall{name: "truncate", args: []string{"-s", "4096", "/tmp/root.ext4"}}}, {call: systemCall{name: "e2fsck", args: []string{"-p", "-f", "/tmp/root.ext4"}}, err: errors.New("e2fsck failed")}, }, wantErr: "e2fsck failed", wantCalls: 2, }, { name: "resize2fs failure", steps: []systemStep{ {call: systemCall{name: "truncate", args: []string{"-s", "4096", "/tmp/root.ext4"}}}, {call: systemCall{name: "e2fsck", args: []string{"-p", "-f", "/tmp/root.ext4"}}}, {call: systemCall{name: "resize2fs", args: []string{"/tmp/root.ext4"}}, err: errors.New("resize2fs failed")}, }, wantErr: "resize2fs failed", wantCalls: 3, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() runner := &scriptedRunner{t: t, steps: tt.steps} err := ResizeExt4Image(context.Background(), runner, "/tmp/root.ext4", 4096) if err == nil || !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 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 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 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") } }