fix: miro init to create shell wrapper

This commit is contained in:
2026-03-18 13:09:11 +00:00
parent 666219edbf
commit 9ba8f1f015
8 changed files with 281 additions and 95 deletions
+35 -8
View File
@@ -33,6 +33,7 @@ func TestRunShowsHelpWhenNoArgs(t *testing.T) {
func TestRunInit(t *testing.T) {
root := t.TempDir()
addFakeRecordDependencies(t, "script", "bwrap", "bash")
withWorkingDir(t, root, func() {
stdout, stderr := captureOutput(t, func() {
@@ -52,6 +53,13 @@ func TestRunInit(t *testing.T) {
if got := mustReadFile(t, filepath.Join(root, "miro.toml")); got != "test_dir = \"e2e\"\n" {
t.Fatalf("config = %q, want %q", got, "test_dir = \"e2e\"\n")
}
info, err := os.Stat(filepath.Join(root, "e2e", "shell.sh"))
if err != nil {
t.Fatalf("Stat(%q) error = %v", filepath.Join(root, "e2e", "shell.sh"), err)
}
if info.Mode().Perm()&0o111 == 0 {
t.Fatalf("shell.sh mode = %o, want executable", info.Mode().Perm())
}
}
func TestRunRecord(t *testing.T) {
@@ -59,12 +67,20 @@ func TestRunRecord(t *testing.T) {
root := t.TempDir()
wantDir := filepath.Join(root, "e2e")
if err := os.MkdirAll(wantDir, 0o755); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"e2e\"\n")
withWorkingDir(t, root, func() {
initStdout, initStderr := captureOutput(t, func() {
if got := Run([]string{"init"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0)
}
})
if initStdout != prefixed("Done initialising...\n") {
t.Fatalf("stdout = %q, want %q", initStdout, prefixed("Done initialising...\n"))
}
if initStderr != "" {
t.Fatalf("stderr = %q, want empty", initStderr)
}
withStdin(t, "y\n", func() {})
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"record", "suite/spec"}); got != 0 {
@@ -93,12 +109,20 @@ func TestRunRecordWithExplicitTestDirPath(t *testing.T) {
root := t.TempDir()
wantDir := filepath.Join(root, "e2e")
if err := os.MkdirAll(wantDir, 0o755); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"e2e\"\n")
withWorkingDir(t, root, func() {
initStdout, initStderr := captureOutput(t, func() {
if got := Run([]string{"init"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0)
}
})
if initStdout != prefixed("Done initialising...\n") {
t.Fatalf("stdout = %q, want %q", initStdout, prefixed("Done initialising...\n"))
}
if initStderr != "" {
t.Fatalf("stderr = %q, want empty", initStderr)
}
withStdin(t, "y\n", func() {})
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"record", filepath.Join("e2e", "suite", "spec")}); got != 0 {
@@ -170,6 +194,9 @@ func TestRunInitFailsWhenDependenciesMissing(t *testing.T) {
if _, err := os.Stat(filepath.Join(root, "miro.toml")); !os.IsNotExist(err) {
t.Fatalf("Stat(%q) error = %v, want not exists", filepath.Join(root, "miro.toml"), err)
}
if _, err := os.Stat(filepath.Join(root, "e2e", "shell.sh")); !os.IsNotExist(err) {
t.Fatalf("Stat(%q) error = %v, want not exists", filepath.Join(root, "e2e", "shell.sh"), err)
}
})
}
+9 -5
View File
@@ -23,16 +23,20 @@ func Init() error {
if _, err := miroconfig.ReadConfig(configPath); err != nil {
return err
}
return nil
} else if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to check %s: %v", configPath, err)
}
} else {
if err := miroconfig.WriteConfig(configPath, miroconfig.Config{
TestDir: defaultTestDir,
}); err != nil {
return fmt.Errorf("failed to write %s: %v", configPath, err)
}
return nil
}
testDir, err := resolveTestDirFromRoot(root)
if err != nil {
return err
}
return writeRecordShell(testDir)
}
+6 -1
View File
@@ -20,11 +20,16 @@ func Record(path string) (string, error) {
return "", err
}
shellPath, err := resolveRecordShell(testDir)
if err != nil {
return "", err
}
if err := os.MkdirAll(target, 0o755); err != nil {
return "", err
}
if err := recordScenario(target, recordIO{
if err := recordScenario(target, shellPath, recordIO{
in: os.Stdin,
out: os.Stdout,
err: os.Stderr,
+5 -59
View File
@@ -2,7 +2,6 @@ package miro
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
@@ -25,7 +24,7 @@ type recordIO struct {
err io.Writer
}
func recordScenario(target string, rio recordIO) error {
func recordScenario(target, shellPath string, rio recordIO) error {
rawIn, rawOut, cleanup, err := newRecordFiles()
if err != nil {
return err
@@ -48,7 +47,7 @@ func recordScenario(target string, rio recordIO) error {
output.Fprintln(rio.err, "Run commands in the recorder shell, then type exit to finish.")
if err := runRecordSession(target, rawIn, rawOut, sandbox, rio); err != nil {
if err := runRecordSession(target, rawIn, rawOut, shellPath, sandbox, rio); err != nil {
return err
}
@@ -92,7 +91,6 @@ type recordSandbox struct {
hostHome string
hostTmp string
projectRoot string
wrapperPath string
pathEnv string
}
@@ -119,7 +117,6 @@ func newRecordSandboxForProjectRoot(root, pathEnv string) (recordSandbox, func()
hostHome: filepath.Join(dir, "home"),
hostTmp: filepath.Join(dir, "tmp"),
projectRoot: root,
wrapperPath: filepath.Join(dir, "sandbox.sh"),
pathEnv: pathEnv,
}
@@ -134,63 +131,12 @@ func newRecordSandboxForProjectRoot(root, pathEnv string) (recordSandbox, func()
}
}
body := buildRecordWrapperScript(sandbox)
if err := os.WriteFile(sandbox.wrapperPath, []byte(body), 0o755); err != nil {
cleanup()
return recordSandbox{}, nil, err
}
return sandbox, cleanup, nil
}
func buildRecordWrapperScript(sandbox recordSandbox) string {
var body strings.Builder
body.WriteString("#!/bin/sh\n")
body.WriteString("set -eu\n\n")
body.WriteString("if command -v git >/dev/null 2>&1; then\n")
body.WriteString(fmt.Sprintf(" HOME=%s GIT_CONFIG_NOSYSTEM=1 git config --global user.name 'Miro Test' >/dev/null 2>&1 || :\n", shQuote(sandbox.hostHome)))
body.WriteString(fmt.Sprintf(" HOME=%s GIT_CONFIG_NOSYSTEM=1 git config --global user.email 'miro-test@example.com' >/dev/null 2>&1 || :\n", shQuote(sandbox.hostHome)))
body.WriteString(fmt.Sprintf(" HOME=%s GIT_CONFIG_NOSYSTEM=1 git config --global init.defaultBranch main >/dev/null 2>&1 || :\n", shQuote(sandbox.hostHome)))
body.WriteString(fmt.Sprintf(" HOME=%s GIT_CONFIG_NOSYSTEM=1 git config --global advice.defaultBranchName false >/dev/null 2>&1 || :\n", shQuote(sandbox.hostHome)))
body.WriteString("fi\n\n")
body.WriteString("exec bwrap \\\n")
body.WriteString(" --ro-bind / / \\\n")
body.WriteString(" --tmpfs /home \\\n")
body.WriteString(fmt.Sprintf(" --bind %s %s \\\n", shQuote(sandbox.hostHome), shQuote(recordVisibleHome)))
body.WriteString(fmt.Sprintf(" --ro-bind %s %s \\\n", shQuote(sandbox.projectRoot), shQuote(recordVisibleRepo)))
body.WriteString(fmt.Sprintf(" --bind %s %s \\\n", shQuote(sandbox.hostTmp), shQuote(recordVisibleTmp)))
body.WriteString(" --dev /dev \\\n")
body.WriteString(" --proc /proc \\\n")
body.WriteString(" --unshare-pid \\\n")
body.WriteString(" --die-with-parent \\\n")
body.WriteString(fmt.Sprintf(" --setenv GIT_AUTHOR_DATE %s \\\n", shQuote(recordGitDate)))
body.WriteString(fmt.Sprintf(" --setenv GIT_COMMITTER_DATE %s \\\n", shQuote(recordGitDate)))
body.WriteString(" --setenv GIT_CONFIG_NOSYSTEM '1' \\\n")
body.WriteString(" --setenv GIT_PAGER 'cat' \\\n")
body.WriteString(" --setenv HISTFILE '/dev/null' \\\n")
body.WriteString(fmt.Sprintf(" --setenv HOME %s \\\n", shQuote(recordVisibleHome)))
body.WriteString(" --setenv LANG 'C' \\\n")
body.WriteString(" --setenv LC_ALL 'C' \\\n")
body.WriteString(" --setenv PAGER 'cat' \\\n")
body.WriteString(fmt.Sprintf(" --setenv PATH %s \\\n", shQuote(sandbox.pathEnv)))
body.WriteString(" --setenv PS1 '$ ' \\\n")
body.WriteString(" --setenv TERM 'xterm-256color' \\\n")
body.WriteString(fmt.Sprintf(" --setenv TMPDIR %s \\\n", shQuote(recordVisibleTmp)))
body.WriteString(" --setenv TZ 'UTC' \\\n")
body.WriteString(fmt.Sprintf(" --chdir %s \\\n", shQuote(recordVisibleHome)))
body.WriteString(" bash --noprofile --norc -i\n")
return body.String()
}
func shQuote(value string) string {
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
}
func runRecordSession(dir, rawIn, rawOut string, sandbox recordSandbox, rio recordIO) error {
cmd := exec.Command("script", "-q", "-E", "always", "-I", rawIn, "-O", rawOut, "-c", sandbox.wrapperPath)
func runRecordSession(dir, rawIn, rawOut, shellPath string, sandbox recordSandbox, rio recordIO) error {
cmd := exec.Command("script", "-q", "-E", "always", "-I", rawIn, "-O", rawOut, "-c", shellPath)
cmd.Dir = dir
cmd.Env = recordSessionEnv(sandbox)
cmd.Stdin = rio.in
cmd.Stdout = rio.out
cmd.Stderr = rio.err
+92
View File
@@ -0,0 +1,92 @@
package miro
import (
"bytes"
"embed"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
)
const recordShellName = "shell.sh"
//go:embed record_shell.sh.tmpl
var recordShellTemplateFS embed.FS
var recordShellTemplate = template.Must(
template.New("record_shell.sh.tmpl").ParseFS(recordShellTemplateFS, "record_shell.sh.tmpl"),
)
func recordShellPath(testDir string) string {
return filepath.Join(testDir, recordShellName)
}
func writeRecordShell(testDir string) error {
if err := os.MkdirAll(testDir, 0o755); err != nil {
return fmt.Errorf("failed to create test directory %q: %v", testDir, err)
}
path := recordShellPath(testDir)
if err := os.WriteFile(path, []byte(buildRecordShellScript()), 0o644); err != nil {
return fmt.Errorf("failed to write recorder shell %q: %v", path, err)
}
if err := os.Chmod(path, 0o755); err != nil {
return fmt.Errorf("failed to make recorder shell executable %q: %v", path, err)
}
return nil
}
func resolveRecordShell(testDir string) (string, error) {
path := recordShellPath(testDir)
info, err := os.Stat(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("missing recorder shell %q; rerun `miro init` or restore the file", path)
}
return "", fmt.Errorf("failed to check recorder shell %q: %v", path, err)
}
if info.IsDir() {
return "", fmt.Errorf("recorder shell %q is a directory; rerun `miro init` or restore the file", path)
}
return path, nil
}
func buildRecordShellScript() string {
var body bytes.Buffer
if err := recordShellTemplate.Execute(&body, struct {
VisibleHome string
VisibleRepo string
VisibleTmp string
GitDate string
}{
VisibleHome: shQuote(recordVisibleHome),
VisibleRepo: shQuote(recordVisibleRepo),
VisibleTmp: shQuote(recordVisibleTmp),
GitDate: shQuote(recordGitDate),
}); err != nil {
panic(fmt.Sprintf("render record shell template: %v", err))
}
return body.String()
}
func recordSessionEnv(sandbox recordSandbox) []string {
env := append([]string{}, os.Environ()...)
env = append(env,
"MIRO_RECORD_HOST_HOME="+sandbox.hostHome,
"MIRO_RECORD_HOST_TMP="+sandbox.hostTmp,
"MIRO_RECORD_PROJECT_ROOT="+sandbox.projectRoot,
"MIRO_RECORD_PATH_ENV="+sandbox.pathEnv,
)
return env
}
func shQuote(value string) string {
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
}
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
set -eu
host_home=${MIRO_RECORD_HOST_HOME:?}
host_tmp=${MIRO_RECORD_HOST_TMP:?}
project_root=${MIRO_RECORD_PROJECT_ROOT:?}
path_env=${MIRO_RECORD_PATH_ENV:?}
if command -v git >/dev/null 2>&1; then
HOME="$host_home" GIT_CONFIG_NOSYSTEM=1 git config --global user.name 'Miro Test' >/dev/null 2>&1 || :
HOME="$host_home" GIT_CONFIG_NOSYSTEM=1 git config --global user.email 'miro-test@example.com' >/dev/null 2>&1 || :
HOME="$host_home" GIT_CONFIG_NOSYSTEM=1 git config --global init.defaultBranch main >/dev/null 2>&1 || :
HOME="$host_home" GIT_CONFIG_NOSYSTEM=1 git config --global advice.defaultBranchName false >/dev/null 2>&1 || :
fi
exec bwrap \
--ro-bind / / \
--tmpfs /home \
--bind "$host_home" {{ .VisibleHome }} \
--ro-bind "$project_root" {{ .VisibleRepo }} \
--bind "$host_tmp" {{ .VisibleTmp }} \
--dev /dev \
--proc /proc \
--unshare-pid \
--die-with-parent \
--setenv GIT_AUTHOR_DATE {{ .GitDate }} \
--setenv GIT_COMMITTER_DATE {{ .GitDate }} \
--setenv GIT_CONFIG_NOSYSTEM '1' \
--setenv GIT_PAGER 'cat' \
--setenv HISTFILE '/dev/null' \
--setenv HOME {{ .VisibleHome }} \
--setenv LANG 'C' \
--setenv LC_ALL 'C' \
--setenv PAGER 'cat' \
--setenv PATH "$path_env" \
--setenv PS1 '$ ' \
--setenv TERM 'xterm-256color' \
--setenv TMPDIR {{ .VisibleTmp }} \
--setenv TZ 'UTC' \
--chdir {{ .VisibleHome }} \
bash --noprofile --norc -i
+58 -21
View File
@@ -16,6 +16,7 @@ func TestRecordCreatesRelativePath(t *testing.T) {
testDir := filepath.Join(root, "e2e")
mustMkdirAll(t, testDir)
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"e2e\"\n")
mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script")
got := withWorkingDir(t, root, func() string {
@@ -51,6 +52,7 @@ func TestRecordAcceptsExplicitTestDirPrefix(t *testing.T) {
testDir := filepath.Join(root, "e2e")
mustMkdirAll(t, testDir)
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"e2e\"\n")
mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script")
got := withWorkingDir(t, root, func() string {
@@ -101,13 +103,14 @@ func TestRecordReturnsDiscardedErrorWhenSaveDeclined(t *testing.T) {
root := t.TempDir()
testDir := filepath.Join(root, "e2e")
mustMkdirAll(t, testDir)
mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script")
err := withWorkingDir(t, root, func() error {
target := filepath.Join(testDir, "a", "b", "c")
mustMkdirAll(t, target)
return withRecordStreams(t, "n\n", func(rio recordIO) error {
return recordScenario(target, rio)
return recordScenario(target, recordShellPath(testDir), rio)
})
})
@@ -128,13 +131,14 @@ func TestRecordReturnsDiscardedErrorWhenOverwriteDeclined(t *testing.T) {
testDir := filepath.Join(root, "e2e")
target := filepath.Join(testDir, "a", "b", "c")
mustMkdirAll(t, target)
mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script")
writeFile(t, filepath.Join(target, "in"), "existing in\n")
writeFile(t, filepath.Join(target, "out"), "existing out\n")
err := withWorkingDir(t, root, func() error {
return withRecordStreams(t, "n\n", func(rio recordIO) error {
return recordScenario(target, rio)
return recordScenario(target, recordShellPath(testDir), rio)
})
})
@@ -159,23 +163,20 @@ func TestRecordReturnsDiscardedErrorWhenOverwriteDeclined(t *testing.T) {
}
}
func TestNewRecordSandboxBuildsDeterministicWrapper(t *testing.T) {
projectRoot := filepath.Join(t.TempDir(), "project root")
sandbox, cleanup, err := newRecordSandboxForProjectRoot(projectRoot, "/custom/bin:/usr/bin")
if err != nil {
t.Fatalf("newRecordSandboxForProjectRoot() error = %v", err)
}
defer cleanup()
body := readFile(t, sandbox.wrapperPath)
func TestBuildRecordShellScriptUsesExpectedCommands(t *testing.T) {
body := buildRecordShellScript()
for _, want := range []string{
"HOME=" + shQuote(sandbox.hostHome) + " GIT_CONFIG_NOSYSTEM=1 git config --global user.name 'Miro Test'",
"--bind " + shQuote(sandbox.hostHome) + " " + shQuote(recordVisibleHome),
"--ro-bind " + shQuote(projectRoot) + " " + shQuote(recordVisibleRepo),
"--bind " + shQuote(sandbox.hostTmp) + " " + shQuote(recordVisibleTmp),
"host_home=${MIRO_RECORD_HOST_HOME:?}",
"host_tmp=${MIRO_RECORD_HOST_TMP:?}",
"project_root=${MIRO_RECORD_PROJECT_ROOT:?}",
"path_env=${MIRO_RECORD_PATH_ENV:?}",
"HOME=\"$host_home\" GIT_CONFIG_NOSYSTEM=1 git config --global user.name 'Miro Test'",
"--bind \"$host_home\" " + shQuote(recordVisibleHome),
"--ro-bind \"$project_root\" " + shQuote(recordVisibleRepo),
"--bind \"$host_tmp\" " + shQuote(recordVisibleTmp),
"--setenv HOME " + shQuote(recordVisibleHome),
"--setenv PATH " + shQuote("/custom/bin:/usr/bin"),
"--setenv PATH \"$path_env\"",
"--setenv PS1 '$ '",
"--setenv TERM 'xterm-256color'",
"--setenv TZ 'UTC'",
@@ -190,6 +191,8 @@ func TestNewRecordSandboxBuildsDeterministicWrapper(t *testing.T) {
func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
root := t.TempDir()
testDir := filepath.Join(root, "e2e")
mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script")
argsPath := filepath.Join(t.TempDir(), "script.args")
@@ -203,8 +206,9 @@ func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
}
defer cleanup()
shellPath := recordShellPath(testDir)
err = withRecordStreams(t, "", func(rio recordIO) error {
return runRecordSession(root, filepath.Join(t.TempDir(), "raw.in"), filepath.Join(t.TempDir(), "raw.out"), sandbox, rio)
return runRecordSession(root, filepath.Join(t.TempDir(), "raw.in"), filepath.Join(t.TempDir(), "raw.out"), shellPath, sandbox, rio)
})
if err != nil {
t.Fatalf("runRecordSession() error = %v", err)
@@ -223,15 +227,16 @@ func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
if args[7] != "-c" {
t.Fatalf("script args[7] = %q, want %q", args[7], "-c")
}
if args[8] != sandbox.wrapperPath {
t.Fatalf("script args[8] = %q, want %q", args[8], sandbox.wrapperPath)
if args[8] != shellPath {
t.Fatalf("script args[8] = %q, want %q", args[8], shellPath)
}
body := readFile(t, commandBodyPath)
for _, want := range []string{
"host_home=${MIRO_RECORD_HOST_HOME:?}",
"--ro-bind / /",
"--tmpfs /home",
"--ro-bind " + shQuote(root) + " " + shQuote(recordVisibleRepo),
"--ro-bind \"$project_root\" " + shQuote(recordVisibleRepo),
"--setenv HOME " + shQuote(recordVisibleHome),
"--setenv TMPDIR " + shQuote(recordVisibleTmp),
"bash --noprofile --norc -i",
@@ -249,6 +254,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
testDir := filepath.Join(root, "e2e")
target := filepath.Join(testDir, "suite", "spec")
mustMkdirAll(t, target)
mustWriteRecordShell(t, testDir)
reader, writer, err := os.Pipe()
if err != nil {
@@ -281,7 +287,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
}()
err = withWorkingDir(t, root, func() error {
return recordScenario(target, recordIO{
return recordScenario(target, recordShellPath(testDir), recordIO{
in: reader,
out: ioDiscard{},
err: &bytes.Buffer{},
@@ -315,6 +321,29 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
}
}
func TestRecordFailsWhenRecorderShellMissing(t *testing.T) {
root := t.TempDir()
testDir := filepath.Join(root, "e2e")
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"e2e\"\n")
mustMkdirAll(t, testDir)
addFakeRecordDependencies(t, "script")
target := filepath.Join(testDir, "suite", "spec")
err := withWorkingDir(t, root, func() error {
_, err := Record(filepath.Join("suite", "spec"))
return err
})
if err == nil {
t.Fatal("Record() error = nil, want error")
}
if !strings.Contains(err.Error(), "rerun `miro init`") {
t.Fatalf("Record() error = %q, want rerun init hint", err.Error())
}
if _, statErr := os.Stat(target); !os.IsNotExist(statErr) {
t.Fatalf("Stat(%q) error = %v, want not exists", target, statErr)
}
}
func withRecordStreams[T any](t *testing.T, input string, fn func(recordIO) T) T {
t.Helper()
@@ -410,3 +439,11 @@ func requireCommands(t *testing.T, names ...string) {
}
}
}
func mustWriteRecordShell(t *testing.T, testDir string) {
t.Helper()
if err := writeRecordShell(testDir); err != nil {
t.Fatalf("writeRecordShell(%q) error = %v", testDir, err)
}
}
+34
View File
@@ -22,6 +22,7 @@ func TestInitCreatesConfigAtProjectRoot(t *testing.T) {
if got != "test_dir = \"e2e\"\n" {
t.Fatalf("config = %q, want %q", got, "test_dir = \"e2e\"\n")
}
assertRecordShell(t, filepath.Join(root, "e2e", recordShellName))
}
func TestInitUsesGitRoot(t *testing.T) {
@@ -40,11 +41,13 @@ func TestInitUsesGitRoot(t *testing.T) {
if _, err := os.Stat(filepath.Join(root, "miro.toml")); err != nil {
t.Fatalf("Stat(%q) error = %v", filepath.Join(root, "miro.toml"), err)
}
assertRecordShell(t, filepath.Join(root, "e2e", recordShellName))
}
func TestInitLeavesExistingValidConfigUntouched(t *testing.T) {
root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"custom/suite\"\n")
writeFile(t, filepath.Join(root, "custom", "suite", recordShellName), "outdated\n")
withWorkingDir(t, root, func() struct{} {
if err := Init(); err != nil {
@@ -57,6 +60,21 @@ func TestInitLeavesExistingValidConfigUntouched(t *testing.T) {
if got != "test_dir = \"custom/suite\"\n" {
t.Fatalf("config = %q, want %q", got, "test_dir = \"custom/suite\"\n")
}
assertRecordShell(t, filepath.Join(root, "custom", "suite", recordShellName))
}
func TestInitCreatesMissingConfiguredTestDir(t *testing.T) {
root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"custom/suite\"\n")
withWorkingDir(t, root, func() struct{} {
if err := Init(); err != nil {
t.Fatalf("Init() error = %v", err)
}
return struct{}{}
})
assertRecordShell(t, filepath.Join(root, "custom", "suite", recordShellName))
}
func TestInitFailsWhenExistingConfigInvalid(t *testing.T) {
@@ -263,3 +281,19 @@ func mustGitInit(t *testing.T, dir string) {
t.Fatalf("git init: %v\n%s", err, out)
}
}
func assertRecordShell(t *testing.T, path string) {
t.Helper()
info, err := os.Stat(path)
if err != nil {
t.Fatalf("Stat(%q) error = %v", path, err)
}
if info.Mode().Perm()&0o111 == 0 {
t.Fatalf("%q mode = %o, want executable", path, info.Mode().Perm())
}
if got := readFile(t, path); got != buildRecordShellScript() {
t.Fatalf("shell = %q, want generated recorder shell", got)
}
}