fix: miro init to create shell wrapper
This commit is contained in:
+35
-8
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, "'", `'"'"'`) + "'"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user