test: refactor tests for a leaner test setup

This commit is contained in:
2026-03-21 08:53:05 +00:00
parent f4820d8099
commit d56e9f3204
8 changed files with 501 additions and 964 deletions
+47 -579
View File
@@ -1,18 +1,18 @@
package cmd package cmd
import ( import (
"bytes"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"miro/internal/testutil"
) )
func TestRunShowsHelpWhenNoArgs(t *testing.T) { func TestRunShowsHelpWhenNoArgs(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run(nil); got != 0 { if got := Run(nil); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0) t.Fatalf("Run() code = %d, want %d", got, 0)
} }
@@ -31,10 +31,10 @@ func TestRunShowsHelpWhenNoArgs(t *testing.T) {
func TestRunInit(t *testing.T) { func TestRunInit(t *testing.T) {
root := t.TempDir() root := t.TempDir()
addFakeRecordDependencies(t, "script", "bwrap", "bash") testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
withWorkingDir(t, root, func() { testutil.WithWorkingDir(t, root, func() struct{} {
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"init"}); got != 0 { if got := Run([]string{"init"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0) t.Fatalf("Run() code = %d, want %d", got, 0)
} }
@@ -46,10 +46,11 @@ func TestRunInit(t *testing.T) {
if stderr != "" { if stderr != "" {
t.Fatalf("stderr = %q, want empty", stderr) t.Fatalf("stderr = %q, want empty", stderr)
} }
return struct{}{}
}) })
if got := mustReadFile(t, filepath.Join(root, "miro.toml")); got != defaultWrittenConfig("e2e") { if got := testutil.ReadFile(t, filepath.Join(root, "miro.toml")); got != testutil.DefaultWrittenConfig("e2e") {
t.Fatalf("config = %q, want %q", got, defaultWrittenConfig("e2e")) t.Fatalf("config = %q, want %q", got, testutil.DefaultWrittenConfig("e2e"))
} }
info, err := os.Stat(filepath.Join(root, "e2e", "shell.sh")) info, err := os.Stat(filepath.Join(root, "e2e", "shell.sh"))
if err != nil { if err != nil {
@@ -61,13 +62,13 @@ func TestRunInit(t *testing.T) {
} }
func TestRunRecord(t *testing.T) { func TestRunRecord(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
root := t.TempDir() root := t.TempDir()
wantDir := filepath.Join(root, "e2e") wantDir := filepath.Join(root, "e2e")
withWorkingDir(t, root, func() { testutil.WithWorkingDir(t, root, func() struct{} {
initStdout, initStderr := captureOutput(t, func() { initStdout, initStderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"init"}); got != 0 { if got := Run([]string{"init"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0) t.Fatalf("Run() code = %d, want %d", got, 0)
} }
@@ -79,8 +80,8 @@ func TestRunRecord(t *testing.T) {
t.Fatalf("stderr = %q, want empty", initStderr) t.Fatalf("stderr = %q, want empty", initStderr)
} }
withStdin(t, "y\n", func() {}) testutil.WithStdin(t, "y\n", func() {})
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"record", "suite/spec"}); got != 0 { if got := Run([]string{"record", "suite/spec"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0) t.Fatalf("Run() code = %d, want %d", got, 0)
} }
@@ -99,6 +100,7 @@ func TestRunRecord(t *testing.T) {
t.Fatalf("Stat(%q) error = %v", filepath.Join(createdPath, name), err) t.Fatalf("Stat(%q) error = %v", filepath.Join(createdPath, name), err)
} }
} }
return struct{}{}
}) })
} }
@@ -106,8 +108,8 @@ func TestRunInitFailsWhenDependenciesMissing(t *testing.T) {
root := t.TempDir() root := t.TempDir()
t.Setenv("PATH", "") t.Setenv("PATH", "")
withWorkingDir(t, root, func() { testutil.WithWorkingDir(t, root, func() struct{} {
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"init"}); got != 1 { if got := Run([]string{"init"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1) t.Fatalf("Run() code = %d, want %d", got, 1)
} }
@@ -125,82 +127,40 @@ func TestRunInitFailsWhenDependenciesMissing(t *testing.T) {
if _, err := os.Stat(filepath.Join(root, "e2e", "shell.sh")); !os.IsNotExist(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) t.Fatalf("Stat(%q) error = %v, want not exists", filepath.Join(root, "e2e", "shell.sh"), err)
} }
return struct{}{}
}) })
} }
func TestRunRecordFailsWhenDependencyMissing(t *testing.T) { func TestRunRecordMissingPath(t *testing.T) {
root := t.TempDir() testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
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"), validConfigContent("e2e"))
for _, tc := range []struct { stdout, stderr := testutil.CaptureOutput(t, func() {
name string if got := Run([]string{"record"}); got != 1 {
deps []string t.Fatalf("Run() code = %d, want %d", got, 1)
missing string
}{
{name: "script", deps: []string{"bwrap", "bash"}, missing: "script"},
{name: "bwrap", deps: []string{"script", "bash"}, missing: "bwrap"},
{name: "bash", deps: []string{"script", "bwrap"}, missing: "bash"},
} {
t.Run(tc.name, func(t *testing.T) {
addFakeRecordDependencies(t, tc.deps...)
withWorkingDir(t, root, func() {
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"record", "suite/spec"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1)
}
})
if stdout != "" {
t.Fatalf("stdout = %q, want empty", stdout)
}
want := `required command "` + tc.missing + `" not found in PATH`
if !strings.Contains(stderr, want) {
t.Fatalf("stderr = %q, want missing dependency error %q", stderr, want)
}
if _, err := os.Stat(filepath.Join(wantDir, "suite", "spec", "in")); !os.IsNotExist(err) {
t.Fatalf("Stat() error = %v, want not exists", err)
}
})
})
}
}
func TestRunRecordMissingConfigFails(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
root := t.TempDir()
withWorkingDir(t, root, func() {
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"record", "suite/spec"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1)
}
})
if stdout != "" {
t.Fatalf("stdout = %q, want empty", stdout)
}
if !strings.Contains(stderr, "failed to resolve test directory") || !strings.Contains(stderr, "miro.toml") {
t.Fatalf("stderr = %q, want missing config error", stderr)
} }
}) })
if stdout != "" {
t.Fatalf("stdout = %q, want empty", stdout)
}
if !strings.Contains(stderr, "accepts 1 arg(s), received 0") {
t.Fatalf("stderr = %q, want argument error", stderr)
}
if !strings.Contains(stderr, "Usage:") || !strings.Contains(stderr, "miro record <path>") {
t.Fatalf("stderr = %q, want record usage", stderr)
}
} }
func TestRunTest(t *testing.T) { func TestRunTest(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1") t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
root := t.TempDir() root := t.TempDir()
writeScenarioFixtures(t, filepath.Join(root, "e2e", "a"), "echo one\n", "echo one\n") testutil.WriteScenarioFixtures(t, filepath.Join(root, "e2e", "a"), "echo one\n", "echo one\n")
writeScenarioFixtures(t, filepath.Join(root, "e2e", "nested", "b"), "echo two\n", "echo two\n") testutil.WriteScenarioFixtures(t, filepath.Join(root, "e2e", "nested", "b"), "echo two\n", "echo two\n")
withWorkingDir(t, root, func() { testutil.WithWorkingDir(t, root, func() struct{} {
initStdout, initStderr := captureOutput(t, func() { initStdout, initStderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"init"}); got != 0 { if got := Run([]string{"init"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0) t.Fatalf("Run() code = %d, want %d", got, 0)
} }
@@ -212,7 +172,7 @@ func TestRunTest(t *testing.T) {
t.Fatalf("stderr = %q, want empty", initStderr) t.Fatalf("stderr = %q, want empty", initStderr)
} }
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"test"}); got != 0 { if got := Run([]string{"test"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0) t.Fatalf("Run() code = %d, want %d", got, 0)
} }
@@ -233,270 +193,14 @@ func TestRunTest(t *testing.T) {
if stderr != "" { if stderr != "" {
t.Fatalf("stderr = %q, want empty", stderr) t.Fatalf("stderr = %q, want empty", stderr)
} }
return struct{}{}
}) })
} }
func TestRunTestScopedDirectory(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
root := t.TempDir()
writeScenarioFixtures(t, filepath.Join(root, "e2e", "a"), "echo one\n", "echo one\n")
writeScenarioFixtures(t, filepath.Join(root, "e2e", "nested", "b"), "echo two\n", "echo two\n")
writeScenarioFixtures(t, filepath.Join(root, "e2e", "nested", "c"), "echo three\n", "echo three\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)
}
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test", "nested"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0)
}
})
for _, want := range []string{
"RUN nested/b",
"PASS nested/b (",
"RUN nested/c",
"PASS nested/c (",
"Summary: total=2 passed=2 failed=0",
} {
if !strings.Contains(stdout, want) {
t.Fatalf("stdout = %q, want substring %q", stdout, want)
}
}
if strings.Contains(stdout, "RUN a") {
t.Fatalf("stdout = %q, want scoped run to exclude scenario outside nested", stdout)
}
if stderr != "" {
t.Fatalf("stderr = %q, want empty", stderr)
}
})
}
func TestRunTestScopedLeafDirectory(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
root := t.TempDir()
writeScenarioFixtures(t, filepath.Join(root, "e2e", "a"), "echo one\n", "echo one\n")
writeScenarioFixtures(t, filepath.Join(root, "e2e", "nested", "b"), "echo two\n", "echo two\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)
}
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test", filepath.Join("nested", "b")}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0)
}
})
for _, want := range []string{
"RUN nested/b",
"PASS nested/b (",
"Summary: total=1 passed=1 failed=0",
} {
if !strings.Contains(stdout, want) {
t.Fatalf("stdout = %q, want substring %q", stdout, want)
}
}
if strings.Contains(stdout, "RUN a") {
t.Fatalf("stdout = %q, want scoped run to exclude scenario outside nested/b", stdout)
}
if stderr != "" {
t.Fatalf("stderr = %q, want empty", stderr)
}
})
}
func TestRunTestReturnsZeroWhenScenarioMismatches(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
root := t.TempDir()
writeScenarioFixtures(t, filepath.Join(root, "e2e", "a"), "echo one\n", "echo one\n")
writeScenarioFixtures(t, filepath.Join(root, "e2e", "b"), "echo two\n", "different output\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)
}
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0)
}
})
for _, want := range []string{
"RUN a",
"PASS a (",
"RUN b",
"FAIL b (",
"output differed",
"Summary: total=2 passed=1 failed=1",
} {
if !strings.Contains(stdout, want) {
t.Fatalf("stdout = %q, want substring %q", stdout, want)
}
}
if stderr != "" {
t.Fatalf("stderr = %q, want empty", stderr)
}
})
}
func TestRunTestMissingConfigFails(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
root := t.TempDir()
withWorkingDir(t, root, func() {
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1)
}
})
if stdout != "" {
t.Fatalf("stdout = %q, want empty", stdout)
}
if !strings.Contains(stderr, "failed to resolve test directory") || !strings.Contains(stderr, "miro.toml") {
t.Fatalf("stderr = %q, want missing config error", stderr)
}
})
}
func TestRunTestFailsWhenRecorderShellMissing(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
withWorkingDir(t, root, func() {
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1)
}
})
if stdout != "" {
t.Fatalf("stdout = %q, want empty", stdout)
}
if !strings.Contains(stderr, "rerun `miro init`") {
t.Fatalf("stderr = %q, want rerun init hint", stderr)
}
})
}
func TestRunTestFailsWhenFixtureMalformed(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
writeFile(t, filepath.Join(root, "e2e", "shell.sh"), "#!/bin/sh\n")
writeFile(t, filepath.Join(root, "e2e", "broken", "in"), "echo broken\n")
withWorkingDir(t, root, func() {
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1)
}
})
if stdout != "" {
t.Fatalf("stdout = %q, want empty", stdout)
}
if !strings.Contains(stderr, `malformed scenario "broken": missing out fixture`) {
t.Fatalf("stderr = %q, want malformed fixture error", stderr)
}
})
}
func TestRunTestReturnsZeroWhenCompareMarkerMissing(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
writeFile(t, filepath.Join(root, "e2e", "shell.sh"), "#!/bin/sh\n")
writeScenarioFixtures(t, filepath.Join(root, "e2e", "some"), "echo one\n", "echo one\n")
withWorkingDir(t, root, func() {
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test"}); got != 0 {
t.Fatalf("Run() code = %d, want %d", got, 0)
}
})
for _, want := range []string{
"RUN some",
"FAIL some (",
"missing compare marker",
"Summary: total=1 passed=0 failed=1",
} {
if !strings.Contains(stdout, want) {
t.Fatalf("stdout = %q, want substring %q", stdout, want)
}
}
if stderr != "" {
t.Fatalf("stderr = %q, want empty", stderr)
}
})
}
func TestRunRecordMissingPath(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"record"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1)
}
})
if stdout != "" {
t.Fatalf("stdout = %q, want empty", stdout)
}
if !strings.Contains(stderr, "accepts 1 arg(s), received 0") {
t.Fatalf("stderr = %q, want argument error", stderr)
}
if !strings.Contains(stderr, "Usage:") || !strings.Contains(stderr, "miro record <path>") {
t.Fatalf("stderr = %q, want record usage", stderr)
}
}
func TestRunTestExtraArgs(t *testing.T) { func TestRunTestExtraArgs(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"test", "a", "b"}); got != 1 { if got := Run([]string{"test", "a", "b"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1) t.Fatalf("Run() code = %d, want %d", got, 1)
} }
@@ -514,9 +218,9 @@ func TestRunTestExtraArgs(t *testing.T) {
} }
func TestRunInitExtraArgs(t *testing.T) { func TestRunInitExtraArgs(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"init", "extra"}); got != 1 { if got := Run([]string{"init", "extra"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1) t.Fatalf("Run() code = %d, want %d", got, 1)
} }
@@ -534,9 +238,9 @@ func TestRunInitExtraArgs(t *testing.T) {
} }
func TestRunUnknownCommand(t *testing.T) { func TestRunUnknownCommand(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") testutil.AddFakeRecordDependencies(t, "script", "bwrap", "bash")
stdout, stderr := captureOutput(t, func() { stdout, stderr := testutil.CaptureOutput(t, func() {
if got := Run([]string{"wat"}); got != 1 { if got := Run([]string{"wat"}); got != 1 {
t.Fatalf("Run() code = %d, want %d", got, 1) t.Fatalf("Run() code = %d, want %d", got, 1)
} }
@@ -550,242 +254,6 @@ func TestRunUnknownCommand(t *testing.T) {
} }
} }
func captureOutput(t *testing.T, fn func()) (string, string) {
t.Helper()
t.Setenv("NO_COLOR", "1")
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
t.Fatalf("os.Pipe() stdout error = %v", err)
}
stderrReader, stderrWriter, err := os.Pipe()
if err != nil {
t.Fatalf("os.Pipe() stderr error = %v", err)
}
oldStdout := os.Stdout
oldStderr := os.Stderr
os.Stdout = stdoutWriter
os.Stderr = stderrWriter
t.Cleanup(func() {
os.Stdout = oldStdout
os.Stderr = oldStderr
})
fn()
if err := stdoutWriter.Close(); err != nil {
t.Fatalf("stdout close error = %v", err)
}
if err := stderrWriter.Close(); err != nil {
t.Fatalf("stderr close error = %v", err)
}
var stdoutBuf bytes.Buffer
if _, err := io.Copy(&stdoutBuf, stdoutReader); err != nil {
t.Fatalf("stdout copy error = %v", err)
}
var stderrBuf bytes.Buffer
if _, err := io.Copy(&stderrBuf, stderrReader); err != nil {
t.Fatalf("stderr copy error = %v", err)
}
return stdoutBuf.String(), stderrBuf.String()
}
func prefixed(msg string) string { func prefixed(msg string) string {
return "miro " + msg return "miro " + msg
} }
func writeFile(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", filepath.Dir(path), err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}
func writeScenarioFixtures(t *testing.T, dir, in, out string) {
t.Helper()
writeFile(t, filepath.Join(dir, "in"), in)
writeFile(t, filepath.Join(dir, "out"), out)
}
func mustReadFile(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile(%q) error = %v", path, err)
}
return string(data)
}
func withWorkingDir(t *testing.T, dir string, fn func()) {
t.Helper()
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd() error = %v", err)
}
if err := os.Chdir(dir); err != nil {
t.Fatalf("Chdir(%q) error = %v", dir, err)
}
t.Cleanup(func() {
if err := os.Chdir(wd); err != nil {
t.Fatalf("restore working directory: %v", err)
}
})
fn()
}
func addFakeRecordDependencies(t *testing.T, names ...string) {
t.Helper()
binDir := t.TempDir()
for _, name := range names {
path := filepath.Join(binDir, name)
body := "#!/bin/sh\nexit 0\n"
if name == "script" {
body = `#!/bin/sh
if [ -n "${FAKE_SCRIPT_ARGS_FILE:-}" ]; then
: > "$FAKE_SCRIPT_ARGS_FILE"
for arg in "$@"; do
printf '%s\n' "$arg" >> "$FAKE_SCRIPT_ARGS_FILE"
done
fi
in=''
out=''
cmd=''
while [ "$#" -gt 0 ]; do
case "$1" in
-I)
in="$2"
shift 2
;;
-O)
out="$2"
shift 2
;;
-c)
cmd="$2"
shift 2
;;
*)
shift
;;
esac
done
if [ -n "${FAKE_SCRIPT_COMMAND_BODY_FILE:-}" ] && [ -n "$cmd" ]; then
: > "$FAKE_SCRIPT_COMMAND_BODY_FILE"
while IFS= read -r line || [ -n "$line" ]; do
printf '%s\n' "$line" >> "$FAKE_SCRIPT_COMMAND_BODY_FILE"
done < "$cmd"
fi
command_has_compare_marker=0
if [ -n "$cmd" ] && /bin/grep -q '__MIRO_E2E_BEGIN__' "$cmd" 2>/dev/null; then
command_has_compare_marker=1
fi
stdin_file=''
if [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] || [ -n "${FAKE_SCRIPT_CAPTURE_STDIN_FILE:-}" ]; then
stdin_file="${TMPDIR:-/tmp}/miro-fake-script-stdin-$$"
/bin/cat > "$stdin_file"
fi
if [ -n "${FAKE_SCRIPT_CAPTURE_STDIN_FILE:-}" ] && [ -n "$stdin_file" ]; then
/bin/cp "$stdin_file" "$FAKE_SCRIPT_CAPTURE_STDIN_FILE"
fi
if [ -n "$in" ] && [ -n "${FAKE_SCRIPT_LOG_IN+x}" ]; then
printf '%s' "$FAKE_SCRIPT_LOG_IN" > "$in"
elif [ -n "$in" ] && [ -n "$stdin_file" ]; then
/bin/cp "$stdin_file" "$in"
elif [ -n "$in" ]; then
printf '%s' 'fake recorded input
' > "$in"
fi
if [ -n "${FAKE_SCRIPT_STREAM_OUT+x}" ]; then
printf '%s' "$FAKE_SCRIPT_STREAM_OUT"
elif [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] && [ -n "$stdin_file" ]; then
/bin/cat "$stdin_file"
if [ "${MIRO_COMPARE_MARKER:-0}" = "1" ] && [ "$command_has_compare_marker" = "1" ]; then
printf '%s\n' '__MIRO_E2E_BEGIN__'
/bin/cat "$stdin_file"
fi
fi
if [ -n "${FAKE_SCRIPT_STREAM_ERR+x}" ]; then
printf '%s' "$FAKE_SCRIPT_STREAM_ERR" >&2
fi
if [ -n "$out" ] && [ -n "${FAKE_SCRIPT_LOG_OUT+x}" ]; then
printf '%s' "$FAKE_SCRIPT_LOG_OUT" > "$out"
elif [ -n "$out" ] && [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] && [ -n "$stdin_file" ]; then
{
printf '%s' 'Script started on 2026-03-18 11:13:38+00:00 [TERM="xterm-256color"]
'
/bin/cat "$stdin_file"
if [ "${MIRO_COMPARE_MARKER:-0}" = "1" ] && [ "$command_has_compare_marker" = "1" ]; then
printf '%s\n' '__MIRO_E2E_BEGIN__'
/bin/cat "$stdin_file"
fi
printf '%s' 'Script done on 2026-03-18 11:13:44+00:00 [COMMAND_EXIT_CODE="0"]
'
} > "$out"
elif [ -n "$out" ]; then
printf '%s' 'Script started on 2026-03-18 11:13:38+00:00 [TERM="xterm-256color"]
fake recorded output
Script done on 2026-03-18 11:13:44+00:00 [COMMAND_EXIT_CODE="0"]
' > "$out"
fi
if [ -n "$stdin_file" ]; then
/bin/rm -f "$stdin_file"
fi
if [ -n "${FAKE_SCRIPT_EXIT_CODE:-}" ]; then
exit "$FAKE_SCRIPT_EXIT_CODE"
fi
exit 0
`
}
if err := os.WriteFile(path, []byte(body), 0o755); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}
t.Setenv("PATH", binDir)
}
func withStdin(t *testing.T, input string, fn func()) {
t.Helper()
path := filepath.Join(t.TempDir(), "stdin.txt")
if err := os.WriteFile(path, []byte(input), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
file, err := os.Open(path)
if err != nil {
t.Fatalf("Open(%q) error = %v", path, err)
}
oldStdin := os.Stdin
os.Stdin = file
t.Cleanup(func() {
os.Stdin = oldStdin
if err := file.Close(); err != nil {
t.Fatalf("close stdin file: %v", err)
}
})
fn()
}
func defaultWrittenConfig(testDir string) string {
return "[miro]\n test_dir = \"" + testDir + "\"\n\n[sandbox]\n visible_home = \"/home/test\"\n"
}
func validConfigContent(testDir string) string {
return "[miro]\ntest_dir = \"" + testDir + "\"\n\n[sandbox]\nvisible_home = \"/home/test\"\n"
}
+6 -4
View File
@@ -5,13 +5,15 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"miro/internal/testutil"
) )
func TestResolvePathWithinTestDirAcceptsRelativePath(t *testing.T) { func TestResolvePathWithinTestDirAcceptsRelativePath(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
got := withWorkingDir(t, root, func() string { got := testutil.WithWorkingDir(t, root, func() string {
path, err := resolvePathWithinTestDir(testDir, filepath.Join("suite", "spec"), "record") path, err := resolvePathWithinTestDir(testDir, filepath.Join("suite", "spec"), "record")
if err != nil { if err != nil {
t.Fatalf("resolvePathWithinTestDir() error = %v", err) t.Fatalf("resolvePathWithinTestDir() error = %v", err)
@@ -29,7 +31,7 @@ func TestResolvePathWithinTestDirAcceptsExplicitTestDirPrefix(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
got := withWorkingDir(t, root, func() string { got := testutil.WithWorkingDir(t, root, func() string {
path, err := resolvePathWithinTestDir(testDir, filepath.Join("e2e", "suite", "spec"), "record") path, err := resolvePathWithinTestDir(testDir, filepath.Join("e2e", "suite", "spec"), "record")
if err != nil { if err != nil {
t.Fatalf("resolvePathWithinTestDir() error = %v", err) t.Fatalf("resolvePathWithinTestDir() error = %v", err)
@@ -74,9 +76,9 @@ func TestResolvePathWithinTestDirRejectsAbsolutePathOutsideTestDir(t *testing.T)
func TestResolvePathWithinTestDirAllowsPathCurrentDirectory(t *testing.T) { func TestResolvePathWithinTestDirAllowsPathCurrentDirectory(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
mustMkdirAll(t, testDir) testutil.MustMkdirAll(t, testDir)
got := withWorkingDir(t, testDir, func() string { got := testutil.WithWorkingDir(t, testDir, func() string {
path, err := resolvePathWithinTestDir(testDir, ".", "record") path, err := resolvePathWithinTestDir(testDir, ".", "record")
if err != nil { if err != nil {
t.Fatalf("resolvePathWithinTestDir() error = %v", err) t.Fatalf("resolvePathWithinTestDir() error = %v", err)
+5 -3
View File
@@ -3,11 +3,13 @@ package miro
import ( import (
"path/filepath" "path/filepath"
"testing" "testing"
"miro/internal/testutil"
) )
func TestLoadRecordedInputTrimsTrailingNewlineAfterEOF(t *testing.T) { func TestLoadRecordedInputTrimsTrailingNewlineAfterEOF(t *testing.T) {
path := filepath.Join(t.TempDir(), "in") path := filepath.Join(t.TempDir(), "in")
writeFile(t, path, "echo hi\r"+string([]byte{eofByte})+"\n") testutil.WriteFile(t, path, "echo hi\r"+string([]byte{eofByte})+"\n")
got, err := loadRecordedInput(path) got, err := loadRecordedInput(path)
if err != nil { if err != nil {
@@ -22,7 +24,7 @@ func TestLoadRecordedInputTrimsTrailingNewlineAfterEOF(t *testing.T) {
func TestLoadRecordedInputTrimsTrailingNewlineAfterCarriageReturn(t *testing.T) { func TestLoadRecordedInputTrimsTrailingNewlineAfterCarriageReturn(t *testing.T) {
path := filepath.Join(t.TempDir(), "in") path := filepath.Join(t.TempDir(), "in")
writeFile(t, path, "echo hi\rexit\r\n") testutil.WriteFile(t, path, "echo hi\rexit\r\n")
got, err := loadRecordedInput(path) got, err := loadRecordedInput(path)
if err != nil { if err != nil {
@@ -37,7 +39,7 @@ func TestLoadRecordedInputTrimsTrailingNewlineAfterCarriageReturn(t *testing.T)
func TestLoadRecordedInputLeavesNormalTrailingNewline(t *testing.T) { func TestLoadRecordedInputLeavesNormalTrailingNewline(t *testing.T) {
path := filepath.Join(t.TempDir(), "in") path := filepath.Join(t.TempDir(), "in")
writeFile(t, path, "echo hi\n") testutil.WriteFile(t, path, "echo hi\n")
got, err := loadRecordedInput(path) got, err := loadRecordedInput(path)
if err != nil { if err != nil {
+30 -243
View File
@@ -4,23 +4,24 @@ import (
"bytes" "bytes"
"errors" "errors"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"time" "time"
"miro/internal/testutil"
) )
func TestRecordCreatesRelativePath(t *testing.T) { func TestRecordCreatesRelativePath(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
mustMkdirAll(t, testDir) testutil.MustMkdirAll(t, testDir)
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("e2e"))
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script") testutil.AddFakeRecordDependencies(t, "script")
got := withWorkingDir(t, root, func() string { got := testutil.WithWorkingDir(t, root, func() string {
withStdin(t, "y\n", func() {}) testutil.WithStdin(t, "y\n", func() {})
path, err := Record(filepath.Join("a", "b", "c") + string(os.PathSeparator)) path, err := Record(filepath.Join("a", "b", "c") + string(os.PathSeparator))
if err != nil { if err != nil {
t.Fatalf("Record() error = %v", err) t.Fatalf("Record() error = %v", err)
@@ -39,10 +40,10 @@ func TestRecordCreatesRelativePath(t *testing.T) {
} }
} }
if got := readFile(t, filepath.Join(want, "in")); got != "fake recorded input\n" { if got := testutil.ReadFile(t, filepath.Join(want, "in")); got != "fake recorded input\n" {
t.Fatalf("saved in = %q, want %q", got, "fake recorded input\n") t.Fatalf("saved in = %q, want %q", got, "fake recorded input\n")
} }
if got := readFile(t, filepath.Join(want, "out")); got != "fake recorded output\n" { if got := testutil.ReadFile(t, filepath.Join(want, "out")); got != "fake recorded output\n" {
t.Fatalf("saved out = %q, want %q", got, "fake recorded output\n") t.Fatalf("saved out = %q, want %q", got, "fake recorded output\n")
} }
} }
@@ -50,13 +51,13 @@ func TestRecordCreatesRelativePath(t *testing.T) {
func TestRecordReturnsDiscardedErrorWhenSaveDeclined(t *testing.T) { func TestRecordReturnsDiscardedErrorWhenSaveDeclined(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
mustMkdirAll(t, testDir) testutil.MustMkdirAll(t, testDir)
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script") testutil.AddFakeRecordDependencies(t, "script")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
target := filepath.Join(testDir, "a", "b", "c") target := filepath.Join(testDir, "a", "b", "c")
mustMkdirAll(t, target) testutil.MustMkdirAll(t, target)
return withRecordStreams(t, "n\n", func(rio recordIO) error { return withRecordStreams(t, "n\n", func(rio recordIO) error {
return recordScenario(target, recordShellPath(testDir), rio, defaultSandboxConfig()) return recordScenario(target, recordShellPath(testDir), rio, defaultSandboxConfig())
}) })
@@ -78,13 +79,13 @@ func TestRecordReturnsDiscardedErrorWhenOverwriteDeclined(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
target := filepath.Join(testDir, "a", "b", "c") target := filepath.Join(testDir, "a", "b", "c")
mustMkdirAll(t, target) testutil.MustMkdirAll(t, target)
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script") testutil.AddFakeRecordDependencies(t, "script")
writeFile(t, filepath.Join(target, "in"), "existing in\n") testutil.WriteFile(t, filepath.Join(target, "in"), "existing in\n")
writeFile(t, filepath.Join(target, "out"), "existing out\n") testutil.WriteFile(t, filepath.Join(target, "out"), "existing out\n")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
return withRecordStreams(t, "n\n", func(rio recordIO) error { return withRecordStreams(t, "n\n", func(rio recordIO) error {
return recordScenario(target, recordShellPath(testDir), rio, defaultSandboxConfig()) return recordScenario(target, recordShellPath(testDir), rio, defaultSandboxConfig())
}) })
@@ -187,7 +188,7 @@ func TestRecordSessionEnvWithExtraIncludesAdditionalEntries(t *testing.T) {
func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) { func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
addFakeRecordDependencies(t, "script") testutil.AddFakeRecordDependencies(t, "script")
argsPath := filepath.Join(t.TempDir(), "script.args") argsPath := filepath.Join(t.TempDir(), "script.args")
commandBodyPath := filepath.Join(t.TempDir(), "script.command") commandBodyPath := filepath.Join(t.TempDir(), "script.command")
@@ -208,7 +209,7 @@ func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
t.Fatalf("runRecordSession() error = %v", err) t.Fatalf("runRecordSession() error = %v", err)
} }
args := strings.Split(strings.TrimSpace(readFile(t, argsPath)), "\n") args := strings.Split(strings.TrimSpace(testutil.ReadFile(t, argsPath)), "\n")
if len(args) != 9 { if len(args) != 9 {
t.Fatalf("script args = %q, want 9 args", args) t.Fatalf("script args = %q, want 9 args", args)
} }
@@ -225,7 +226,7 @@ func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
t.Fatalf("script args[8] = %q, want %q", args[8], shellPath) t.Fatalf("script args[8] = %q, want %q", args[8], shellPath)
} }
body := readFile(t, commandBodyPath) body := testutil.ReadFile(t, commandBodyPath)
for _, want := range []string{ for _, want := range []string{
"host_home=${MIRO_HOST_HOME:?}", "host_home=${MIRO_HOST_HOME:?}",
"visible_home=${MIRO_VISIBLE_HOME:?}", "visible_home=${MIRO_VISIBLE_HOME:?}",
@@ -244,12 +245,12 @@ func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
} }
func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) { func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
requireCommands(t, "script", "bwrap", "bash") testutil.RequireCommands(t, "script", "bwrap", "bash")
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
target := filepath.Join(testDir, "suite", "spec") target := filepath.Join(testDir, "suite", "spec")
mustMkdirAll(t, target) testutil.MustMkdirAll(t, target)
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
sandboxConfig := map[string]string{ sandboxConfig := map[string]string{
"visible_home": "/home/test", "visible_home": "/home/test",
@@ -287,7 +288,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
writeDone <- nil writeDone <- nil
}() }()
err = withWorkingDir(t, root, func() error { err = testutil.WithWorkingDir(t, root, func() error {
return recordScenario(target, recordShellPath(testDir), recordIO{ return recordScenario(target, recordShellPath(testDir), recordIO{
in: reader, in: reader,
out: ioDiscard{}, out: ioDiscard{},
@@ -301,7 +302,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
t.Fatalf("write session input: %v", err) t.Fatalf("write session input: %v", err)
} }
recordedIn := readFile(t, filepath.Join(target, "in")) recordedIn := testutil.ReadFile(t, filepath.Join(target, "in"))
if strings.Contains(recordedIn, "Script started on ") { if strings.Contains(recordedIn, "Script started on ") {
t.Fatalf("saved in = %q, want stripped script wrapper", recordedIn) t.Fatalf("saved in = %q, want stripped script wrapper", recordedIn)
} }
@@ -311,7 +312,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
} }
} }
recordedOut := readFile(t, filepath.Join(target, "out")) recordedOut := testutil.ReadFile(t, filepath.Join(target, "out"))
if strings.Contains(recordedOut, "Script started on ") { if strings.Contains(recordedOut, "Script started on ") {
t.Fatalf("saved out = %q, want stripped script wrapper", recordedOut) t.Fatalf("saved out = %q, want stripped script wrapper", recordedOut)
} }
@@ -331,12 +332,12 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
func TestRecordFailsWhenRecorderShellMissing(t *testing.T) { func TestRecordFailsWhenRecorderShellMissing(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("e2e"))
mustMkdirAll(t, testDir) testutil.MustMkdirAll(t, testDir)
addFakeRecordDependencies(t, "script") testutil.AddFakeRecordDependencies(t, "script")
target := filepath.Join(testDir, "suite", "spec") target := filepath.Join(testDir, "suite", "spec")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
_, err := Record(filepath.Join("suite", "spec")) _, err := Record(filepath.Join("suite", "spec"))
return err return err
}) })
@@ -350,217 +351,3 @@ func TestRecordFailsWhenRecorderShellMissing(t *testing.T) {
t.Fatalf("Stat(%q) error = %v, want not exists", target, 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()
path := filepath.Join(t.TempDir(), "stdin.txt")
if err := os.WriteFile(path, []byte(input), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
file, err := os.Open(path)
if err != nil {
t.Fatalf("Open(%q) error = %v", path, err)
}
t.Cleanup(func() {
if err := file.Close(); err != nil {
t.Fatalf("close record input: %v", err)
}
})
return fn(recordIO{
in: file,
out: ioDiscard{},
err: &bytes.Buffer{},
})
}
func withStdin(t *testing.T, input string, fn func()) {
t.Helper()
path := filepath.Join(t.TempDir(), "stdin.txt")
if err := os.WriteFile(path, []byte(input), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
file, err := os.Open(path)
if err != nil {
t.Fatalf("Open(%q) error = %v", path, err)
}
oldStdin := os.Stdin
os.Stdin = file
t.Cleanup(func() {
os.Stdin = oldStdin
if err := file.Close(); err != nil {
t.Fatalf("close stdin file: %v", err)
}
})
fn()
}
func readFile(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile(%q) error = %v", path, err)
}
return string(data)
}
type ioDiscard struct{}
func (ioDiscard) Write(p []byte) (int, error) {
return len(p), nil
}
func addFakeRecordDependencies(t *testing.T, names ...string) {
t.Helper()
binDir := t.TempDir()
for _, name := range names {
path := filepath.Join(binDir, name)
body := "#!/bin/sh\nexit 0\n"
if name == "script" {
body = `#!/bin/sh
if [ -n "${FAKE_SCRIPT_ARGS_FILE:-}" ]; then
: > "$FAKE_SCRIPT_ARGS_FILE"
for arg in "$@"; do
printf '%s\n' "$arg" >> "$FAKE_SCRIPT_ARGS_FILE"
done
fi
in=''
out=''
cmd=''
while [ "$#" -gt 0 ]; do
case "$1" in
-I)
in="$2"
shift 2
;;
-O)
out="$2"
shift 2
;;
-c)
cmd="$2"
shift 2
;;
*)
shift
;;
esac
done
if [ -n "${FAKE_SCRIPT_COMMAND_BODY_FILE:-}" ] && [ -n "$cmd" ]; then
: > "$FAKE_SCRIPT_COMMAND_BODY_FILE"
while IFS= read -r line || [ -n "$line" ]; do
printf '%s\n' "$line" >> "$FAKE_SCRIPT_COMMAND_BODY_FILE"
done < "$cmd"
fi
command_has_compare_marker=0
if [ -n "$cmd" ] && /bin/grep -q '__MIRO_E2E_BEGIN__' "$cmd" 2>/dev/null; then
command_has_compare_marker=1
fi
stdin_file=''
if [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] || [ -n "${FAKE_SCRIPT_CAPTURE_STDIN_FILE:-}" ]; then
stdin_file="${TMPDIR:-/tmp}/miro-fake-script-stdin-$$"
/bin/cat > "$stdin_file"
fi
if [ -n "${FAKE_SCRIPT_CAPTURE_STDIN_FILE:-}" ] && [ -n "$stdin_file" ]; then
/bin/cp "$stdin_file" "$FAKE_SCRIPT_CAPTURE_STDIN_FILE"
fi
if [ -n "$in" ] && [ -n "${FAKE_SCRIPT_LOG_IN+x}" ]; then
printf '%s' "$FAKE_SCRIPT_LOG_IN" > "$in"
elif [ -n "$in" ] && [ -n "$stdin_file" ]; then
/bin/cp "$stdin_file" "$in"
elif [ -n "$in" ]; then
printf '%s' 'fake recorded input
' > "$in"
fi
if [ -n "${FAKE_SCRIPT_STREAM_OUT+x}" ]; then
printf '%s' "$FAKE_SCRIPT_STREAM_OUT"
elif [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] && [ -n "$stdin_file" ]; then
/bin/cat "$stdin_file"
if [ "${MIRO_COMPARE_MARKER:-0}" = "1" ] && [ "$command_has_compare_marker" = "1" ]; then
printf '%s\n' '__MIRO_E2E_BEGIN__'
/bin/cat "$stdin_file"
fi
fi
if [ -n "${FAKE_SCRIPT_STREAM_ERR+x}" ]; then
printf '%s' "$FAKE_SCRIPT_STREAM_ERR" >&2
fi
if [ -n "$out" ] && [ -n "${FAKE_SCRIPT_LOG_OUT+x}" ]; then
printf '%s' "$FAKE_SCRIPT_LOG_OUT" > "$out"
elif [ -n "$out" ] && [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] && [ -n "$stdin_file" ]; then
{
printf '%s' 'Script started on 2026-03-18 11:13:38+00:00 [TERM="xterm-256color"]
'
/bin/cat "$stdin_file"
if [ "${MIRO_COMPARE_MARKER:-0}" = "1" ] && [ "$command_has_compare_marker" = "1" ]; then
printf '%s\n' '__MIRO_E2E_BEGIN__'
/bin/cat "$stdin_file"
fi
printf '%s' 'Script done on 2026-03-18 11:13:44+00:00 [COMMAND_EXIT_CODE="0"]
'
} > "$out"
elif [ -n "$out" ]; then
printf '%s' 'Script started on 2026-03-18 11:13:38+00:00 [TERM="xterm-256color"]
fake recorded output
Script done on 2026-03-18 11:13:44+00:00 [COMMAND_EXIT_CODE="0"]
' > "$out"
fi
if [ -n "$stdin_file" ]; then
/bin/rm -f "$stdin_file"
fi
if [ -n "${FAKE_SCRIPT_EXIT_CODE:-}" ]; then
exit "$FAKE_SCRIPT_EXIT_CODE"
fi
exit 0
`
}
if err := os.WriteFile(path, []byte(body), 0o755); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}
t.Setenv("PATH", binDir)
}
func requireCommands(t *testing.T, names ...string) {
t.Helper()
for _, name := range names {
if _, err := exec.LookPath(name); err != nil {
t.Skipf("missing command %q: %v", name, err)
}
}
}
func mustWriteRecordShell(t *testing.T, testDir string) {
t.Helper()
if err := writeRecordShell(testDir); err != nil {
t.Fatalf("writeRecordShell(%q) error = %v", testDir, err)
}
}
func defaultSandboxConfig() map[string]string {
return map[string]string{
"visible_home": "/home/test",
}
}
func containsEnvEntry(env []string, want string) bool {
for _, entry := range env {
if entry == want {
return true
}
}
return false
}
+21 -26
View File
@@ -6,6 +6,8 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"miro/internal/testutil"
) )
func TestFormatElapsed(t *testing.T) { func TestFormatElapsed(t *testing.T) {
@@ -30,10 +32,10 @@ func TestFormatElapsed(t *testing.T) {
func TestDiscoverTestScenariosFindsNestedFixturesAndSorts(t *testing.T) { func TestDiscoverTestScenariosFindsNestedFixturesAndSorts(t *testing.T) {
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
writeScenarioFixtures(t, filepath.Join(testDir, "nested", "b"), "echo two\n", "echo two\n") testutil.WriteScenarioFixtures(t, filepath.Join(testDir, "nested", "b"), "echo two\n", "echo two\n")
writeScenarioFixtures(t, filepath.Join(testDir, "a"), "echo one\n", "echo one\n") testutil.WriteScenarioFixtures(t, filepath.Join(testDir, "a"), "echo one\n", "echo one\n")
writeFile(t, filepath.Join(testDir, "shell.sh"), "#!/bin/sh\n") testutil.WriteFile(t, filepath.Join(testDir, "shell.sh"), "#!/bin/sh\n")
writeFile(t, filepath.Join(testDir, "notes.txt"), "ignore me\n") testutil.WriteFile(t, filepath.Join(testDir, "notes.txt"), "ignore me\n")
got, err := discoverTestScenarios(testDir, testDir) got, err := discoverTestScenarios(testDir, testDir)
if err != nil { if err != nil {
@@ -63,7 +65,7 @@ func TestDiscoverTestScenariosFindsNestedFixturesAndSorts(t *testing.T) {
func TestDiscoverTestScenariosRejectsMissingOutFixture(t *testing.T) { func TestDiscoverTestScenariosRejectsMissingOutFixture(t *testing.T) {
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
writeFile(t, filepath.Join(testDir, "broken", "in"), "echo broken\n") testutil.WriteFile(t, filepath.Join(testDir, "broken", "in"), "echo broken\n")
_, err := discoverTestScenarios(testDir, testDir) _, err := discoverTestScenarios(testDir, testDir)
if err == nil { if err == nil {
@@ -76,7 +78,7 @@ func TestDiscoverTestScenariosRejectsMissingOutFixture(t *testing.T) {
func TestDiscoverTestScenariosRejectsMissingInFixture(t *testing.T) { func TestDiscoverTestScenariosRejectsMissingInFixture(t *testing.T) {
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
writeFile(t, filepath.Join(testDir, "broken", "out"), "echo broken\n") testutil.WriteFile(t, filepath.Join(testDir, "broken", "out"), "echo broken\n")
_, err := discoverTestScenarios(testDir, testDir) _, err := discoverTestScenarios(testDir, testDir)
if err == nil { if err == nil {
@@ -88,14 +90,14 @@ func TestDiscoverTestScenariosRejectsMissingInFixture(t *testing.T) {
} }
func TestReplayScenarioUsesRecordedInputAndKeepsReplayOutputQuiet(t *testing.T) { func TestReplayScenarioUsesRecordedInputAndKeepsReplayOutputQuiet(t *testing.T) {
addFakeRecordDependencies(t, "script") testutil.AddFakeRecordDependencies(t, "script")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1") t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
shellPath := filepath.Join(testDir, "shell.sh") shellPath := filepath.Join(testDir, "shell.sh")
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
scenarioDir := filepath.Join(testDir, "suite", "spec") scenarioDir := filepath.Join(testDir, "suite", "spec")
writeScenarioFixtures(t, scenarioDir, "echo replay\nexit\n", "echo replay\nexit\n") testutil.WriteScenarioFixtures(t, scenarioDir, "echo replay\nexit\n", "echo replay\nexit\n")
capturedInput := filepath.Join(t.TempDir(), "stdin.capture") capturedInput := filepath.Join(t.TempDir(), "stdin.capture")
t.Setenv("FAKE_SCRIPT_CAPTURE_STDIN_FILE", capturedInput) t.Setenv("FAKE_SCRIPT_CAPTURE_STDIN_FILE", capturedInput)
@@ -115,7 +117,7 @@ func TestReplayScenarioUsesRecordedInputAndKeepsReplayOutputQuiet(t *testing.T)
t.Fatalf("replayScenario() error = %v", err) t.Fatalf("replayScenario() error = %v", err)
} }
if got := readFile(t, capturedInput); got != "echo replay\nexit\n" { if got := testutil.ReadFile(t, capturedInput); got != "echo replay\nexit\n" {
t.Fatalf("captured stdin = %q, want recorded input", got) t.Fatalf("captured stdin = %q, want recorded input", got)
} }
if stdout.String() != "" { if stdout.String() != "" {
@@ -127,14 +129,14 @@ func TestReplayScenarioUsesRecordedInputAndKeepsReplayOutputQuiet(t *testing.T)
} }
func TestReplayScenarioFailsWhenCompareMarkerMissing(t *testing.T) { func TestReplayScenarioFailsWhenCompareMarkerMissing(t *testing.T) {
addFakeRecordDependencies(t, "script") testutil.AddFakeRecordDependencies(t, "script")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1") t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
shellPath := filepath.Join(testDir, "shell.sh") shellPath := filepath.Join(testDir, "shell.sh")
writeFile(t, shellPath, "#!/bin/sh\n") testutil.WriteFile(t, shellPath, "#!/bin/sh\n")
scenarioDir := filepath.Join(testDir, "suite", "spec") scenarioDir := filepath.Join(testDir, "suite", "spec")
writeScenarioFixtures(t, scenarioDir, "echo replay\nexit\n", "echo replay\nexit\n") testutil.WriteScenarioFixtures(t, scenarioDir, "echo replay\nexit\n", "echo replay\nexit\n")
err := replayScenario(testScenario{ err := replayScenario(testScenario{
dir: scenarioDir, dir: scenarioDir,
@@ -156,7 +158,7 @@ func TestReplayScenarioFailsWhenCompareMarkerMissing(t *testing.T) {
func TestDiscoverTestScenariosUsesDisplayRootForRelativePaths(t *testing.T) { func TestDiscoverTestScenariosUsesDisplayRootForRelativePaths(t *testing.T) {
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
scopedDir := filepath.Join(testDir, "nested") scopedDir := filepath.Join(testDir, "nested")
writeScenarioFixtures(t, filepath.Join(scopedDir, "b"), "echo two\n", "echo two\n") testutil.WriteScenarioFixtures(t, filepath.Join(scopedDir, "b"), "echo two\n", "echo two\n")
got, err := discoverTestScenarios(scopedDir, testDir) got, err := discoverTestScenarios(scopedDir, testDir)
if err != nil { if err != nil {
@@ -172,7 +174,7 @@ func TestDiscoverTestScenariosUsesDisplayRootForRelativePaths(t *testing.T) {
func TestResolveTestDiscoveryRootRejectsMissingPath(t *testing.T) { func TestResolveTestDiscoveryRootRejectsMissingPath(t *testing.T) {
testDir := filepath.Join(t.TempDir(), "e2e") testDir := filepath.Join(t.TempDir(), "e2e")
mustMkdirAll(t, testDir) testutil.MustMkdirAll(t, testDir)
_, err := resolveTestDiscoveryRoot(testDir, "missing") _, err := resolveTestDiscoveryRoot(testDir, "missing")
if err == nil { if err == nil {
@@ -186,9 +188,9 @@ func TestResolveTestDiscoveryRootRejectsMissingPath(t *testing.T) {
func TestResolveTestDiscoveryRootRejectsFile(t *testing.T) { func TestResolveTestDiscoveryRootRejectsFile(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
writeFile(t, filepath.Join(testDir, "case.txt"), "hello\n") testutil.WriteFile(t, filepath.Join(testDir, "case.txt"), "hello\n")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
_, err := resolveTestDiscoveryRoot(testDir, filepath.Join("e2e", "case.txt")) _, err := resolveTestDiscoveryRoot(testDir, filepath.Join("e2e", "case.txt"))
return err return err
}) })
@@ -204,11 +206,11 @@ func TestRunTestsScopedRunEmptyDirectoryFails(t *testing.T) {
root := t.TempDir() root := t.TempDir()
testDir := filepath.Join(root, "e2e") testDir := filepath.Join(root, "e2e")
emptyDir := filepath.Join(testDir, "empty") emptyDir := filepath.Join(testDir, "empty")
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("e2e"))
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
mustMkdirAll(t, emptyDir) testutil.MustMkdirAll(t, emptyDir)
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
return runTests("empty", testIO{ return runTests("empty", testIO{
out: &bytes.Buffer{}, out: &bytes.Buffer{},
err: &bytes.Buffer{}, err: &bytes.Buffer{},
@@ -221,10 +223,3 @@ func TestRunTestsScopedRunEmptyDirectoryFails(t *testing.T) {
t.Fatalf("runTests() error = %q, want empty-directory error", err.Error()) t.Fatalf("runTests() error = %q, want empty-directory error", err.Error())
} }
} }
func writeScenarioFixtures(t *testing.T, dir, in, out string) {
t.Helper()
writeFile(t, filepath.Join(dir, "in"), in)
writeFile(t, filepath.Join(dir, "out"), out)
}
+39 -109
View File
@@ -2,36 +2,37 @@ package miro
import ( import (
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
"miro/internal/testutil"
) )
func TestInitCreatesConfigAtProjectRoot(t *testing.T) { func TestInitCreatesConfigAtProjectRoot(t *testing.T) {
root := t.TempDir() root := t.TempDir()
withWorkingDir(t, root, func() struct{} { testutil.WithWorkingDir(t, root, func() struct{} {
if err := Init(); err != nil { if err := Init(); err != nil {
t.Fatalf("Init() error = %v", err) t.Fatalf("Init() error = %v", err)
} }
return struct{}{} return struct{}{}
}) })
got := readFile(t, filepath.Join(root, "miro.toml")) got := testutil.ReadFile(t, filepath.Join(root, "miro.toml"))
if got != defaultWrittenConfig("e2e") { if got != testutil.DefaultWrittenConfig("e2e") {
t.Fatalf("config = %q, want %q", got, defaultWrittenConfig("e2e")) t.Fatalf("config = %q, want %q", got, testutil.DefaultWrittenConfig("e2e"))
} }
assertRecordShell(t, filepath.Join(root, "e2e", recordShellName)) assertRecordShell(t, filepath.Join(root, "e2e", recordShellName))
} }
func TestInitUsesGitRoot(t *testing.T) { func TestInitUsesGitRoot(t *testing.T) {
root := t.TempDir() root := t.TempDir()
mustGitInit(t, root) testutil.MustGitInit(t, root)
subdir := filepath.Join(root, "nested", "dir") subdir := filepath.Join(root, "nested", "dir")
mustMkdirAll(t, subdir) testutil.MustMkdirAll(t, subdir)
withWorkingDir(t, subdir, func() struct{} { testutil.WithWorkingDir(t, subdir, func() struct{} {
if err := Init(); err != nil { if err := Init(); err != nil {
t.Fatalf("Init() error = %v", err) t.Fatalf("Init() error = %v", err)
} }
@@ -46,30 +47,30 @@ func TestInitUsesGitRoot(t *testing.T) {
func TestInitLeavesExistingValidConfigUntouchedAndRefreshesShell(t *testing.T) { func TestInitLeavesExistingValidConfigUntouchedAndRefreshesShell(t *testing.T) {
root := t.TempDir() root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("custom/suite")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("custom/suite"))
writeFile(t, filepath.Join(root, "custom", "suite", recordShellName), "outdated\n") testutil.WriteFile(t, filepath.Join(root, "custom", "suite", recordShellName), "outdated\n")
withWorkingDir(t, root, func() struct{} { testutil.WithWorkingDir(t, root, func() struct{} {
if err := Init(); err != nil { if err := Init(); err != nil {
t.Fatalf("Init() error = %v", err) t.Fatalf("Init() error = %v", err)
} }
return struct{}{} return struct{}{}
}) })
got := readFile(t, filepath.Join(root, "miro.toml")) got := testutil.ReadFile(t, filepath.Join(root, "miro.toml"))
if got != validConfigContent("custom/suite") { if got != testutil.ValidConfigContent("custom/suite") {
t.Fatalf("config = %q, want %q", got, validConfigContent("custom/suite")) t.Fatalf("config = %q, want %q", got, testutil.ValidConfigContent("custom/suite"))
} }
if got := readFile(t, filepath.Join(root, "custom", "suite", recordShellName)); got != buildRecordShellScript() { if got := testutil.ReadFile(t, filepath.Join(root, "custom", "suite", recordShellName)); got != buildRecordShellScript() {
t.Fatalf("shell = %q, want refreshed recorder shell", got) t.Fatalf("shell = %q, want refreshed recorder shell", got)
} }
} }
func TestInitCreatesMissingConfiguredTestDir(t *testing.T) { func TestInitCreatesMissingConfiguredTestDir(t *testing.T) {
root := t.TempDir() root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("custom/suite")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("custom/suite"))
withWorkingDir(t, root, func() struct{} { testutil.WithWorkingDir(t, root, func() struct{} {
if err := Init(); err != nil { if err := Init(); err != nil {
t.Fatalf("Init() error = %v", err) t.Fatalf("Init() error = %v", err)
} }
@@ -81,9 +82,9 @@ func TestInitCreatesMissingConfiguredTestDir(t *testing.T) {
func TestInitFailsWhenExistingConfigInvalid(t *testing.T) { func TestInitFailsWhenExistingConfigInvalid(t *testing.T) {
root := t.TempDir() root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"e2e\"\n") testutil.WriteFile(t, filepath.Join(root, "miro.toml"), "test_dir = \"e2e\"\n")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
return Init() return Init()
}) })
if err == nil { if err == nil {
@@ -97,10 +98,10 @@ func TestInitFailsWhenExistingConfigInvalid(t *testing.T) {
func TestResolveTestDirFromConfig(t *testing.T) { func TestResolveTestDirFromConfig(t *testing.T) {
root := t.TempDir() root := t.TempDir()
configured := filepath.Join(root, "custom", "suite") configured := filepath.Join(root, "custom", "suite")
mustMkdirAll(t, configured) testutil.MustMkdirAll(t, configured)
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("custom/suite")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("custom/suite"))
got := withWorkingDir(t, root, func() string { got := testutil.WithWorkingDir(t, root, func() string {
path, err := ResolveTestDir() path, err := ResolveTestDir()
if err != nil { if err != nil {
t.Fatalf("ResolveTestDir() error = %v", err) t.Fatalf("ResolveTestDir() error = %v", err)
@@ -116,7 +117,7 @@ func TestResolveTestDirFromConfig(t *testing.T) {
func TestResolveTestDirMissingConfigFails(t *testing.T) { func TestResolveTestDirMissingConfigFails(t *testing.T) {
root := t.TempDir() root := t.TempDir()
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
_, err := ResolveTestDir() _, err := ResolveTestDir()
return err return err
}) })
@@ -131,9 +132,9 @@ func TestResolveTestDirMissingConfigFails(t *testing.T) {
func TestResolveTestDirMissingTestDirFails(t *testing.T) { func TestResolveTestDirMissingTestDirFails(t *testing.T) {
root := t.TempDir() root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\n") testutil.WriteFile(t, filepath.Join(root, "miro.toml"), "[miro]\n")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
_, err := ResolveTestDir() _, err := ResolveTestDir()
return err return err
}) })
@@ -148,9 +149,9 @@ func TestResolveTestDirMissingTestDirFails(t *testing.T) {
func TestResolveTestDirEmptyTestDirFails(t *testing.T) { func TestResolveTestDirEmptyTestDirFails(t *testing.T) {
root := t.TempDir() root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"\"\n") testutil.WriteFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"\"\n")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
_, err := ResolveTestDir() _, err := ResolveTestDir()
return err return err
}) })
@@ -165,9 +166,9 @@ func TestResolveTestDirEmptyTestDirFails(t *testing.T) {
func TestResolveTestDirMalformedConfigFails(t *testing.T) { func TestResolveTestDirMalformedConfigFails(t *testing.T) {
root := t.TempDir() root := t.TempDir()
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = [\n") testutil.WriteFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = [\n")
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
_, err := ResolveTestDir() _, err := ResolveTestDir()
return err return err
}) })
@@ -183,9 +184,9 @@ func TestResolveTestDirMalformedConfigFails(t *testing.T) {
func TestResolveTestDirConfiguredMissingDirectoryReturnsConfiguredPath(t *testing.T) { func TestResolveTestDirConfiguredMissingDirectoryReturnsConfiguredPath(t *testing.T) {
root := t.TempDir() root := t.TempDir()
want := filepath.Join(root, "missing") want := filepath.Join(root, "missing")
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("missing")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("missing"))
got := withWorkingDir(t, root, func() string { got := testutil.WithWorkingDir(t, root, func() string {
path, err := ResolveTestDir() path, err := ResolveTestDir()
if err != nil { if err != nil {
t.Fatalf("ResolveTestDir() error = %v", err) t.Fatalf("ResolveTestDir() error = %v", err)
@@ -200,10 +201,10 @@ func TestResolveTestDirConfiguredMissingDirectoryReturnsConfiguredPath(t *testin
func TestResolveTestDirConfiguredFileFails(t *testing.T) { func TestResolveTestDirConfiguredFileFails(t *testing.T) {
root := t.TempDir() root := t.TempDir()
writeFile(t, filepath.Join(root, "case.txt"), "hello\n") testutil.WriteFile(t, filepath.Join(root, "case.txt"), "hello\n")
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("case.txt")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("case.txt"))
err := withWorkingDir(t, root, func() error { err := testutil.WithWorkingDir(t, root, func() error {
_, err := ResolveTestDir() _, err := ResolveTestDir()
return err return err
}) })
@@ -218,13 +219,13 @@ func TestResolveTestDirConfiguredFileFails(t *testing.T) {
func TestResolveTestDirUsesGitRoot(t *testing.T) { func TestResolveTestDirUsesGitRoot(t *testing.T) {
root := t.TempDir() root := t.TempDir()
mustGitInit(t, root) testutil.MustGitInit(t, root)
want := filepath.Join(root, "e2e") want := filepath.Join(root, "e2e")
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e")) testutil.WriteFile(t, filepath.Join(root, "miro.toml"), testutil.ValidConfigContent("e2e"))
subdir := filepath.Join(root, "nested", "dir") subdir := filepath.Join(root, "nested", "dir")
mustMkdirAll(t, subdir) testutil.MustMkdirAll(t, subdir)
got := withWorkingDir(t, subdir, func() string { got := testutil.WithWorkingDir(t, subdir, func() string {
path, err := ResolveTestDir() path, err := ResolveTestDir()
if err != nil { if err != nil {
t.Fatalf("ResolveTestDir() error = %v", err) t.Fatalf("ResolveTestDir() error = %v", err)
@@ -236,74 +237,3 @@ func TestResolveTestDirUsesGitRoot(t *testing.T) {
t.Fatalf("ResolveTestDir() = %q, want %q", got, want) t.Fatalf("ResolveTestDir() = %q, want %q", got, want)
} }
} }
func withWorkingDir[T any](t *testing.T, dir string, fn func() T) T {
t.Helper()
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd() error = %v", err)
}
if err := os.Chdir(dir); err != nil {
t.Fatalf("Chdir(%q) error = %v", dir, err)
}
t.Cleanup(func() {
if err := os.Chdir(wd); err != nil {
t.Fatalf("restore working directory: %v", err)
}
})
return fn()
}
func writeFile(t *testing.T, path, content string) {
t.Helper()
mustMkdirAll(t, filepath.Dir(path))
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}
func mustMkdirAll(t *testing.T, path string) {
t.Helper()
if err := os.MkdirAll(path, 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", path, err)
}
}
func mustGitInit(t *testing.T, dir string) {
t.Helper()
cmd := exec.Command("git", "init")
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
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)
}
}
func defaultWrittenConfig(testDir string) string {
return "[miro]\n test_dir = \"" + testDir + "\"\n\n[sandbox]\n visible_home = \"/home/test\"\n"
}
func validConfigContent(testDir string) string {
return "[miro]\ntest_dir = \"" + testDir + "\"\n\n[sandbox]\nvisible_home = \"/home/test\"\n"
}
+80
View File
@@ -0,0 +1,80 @@
package miro
import (
"bytes"
"os"
"path/filepath"
"testing"
"miro/internal/testutil"
)
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 := testutil.ReadFile(t, path); got != buildRecordShellScript() {
t.Fatalf("shell = %q, want generated recorder shell", got)
}
}
func withRecordStreams[T any](t *testing.T, input string, fn func(recordIO) T) T {
t.Helper()
path := filepath.Join(t.TempDir(), "stdin.txt")
testutil.WriteFile(t, path, input)
file, err := os.Open(path)
if err != nil {
t.Fatalf("Open(%q) error = %v", path, err)
}
t.Cleanup(func() {
if err := file.Close(); err != nil {
t.Fatalf("close record input: %v", err)
}
})
return fn(recordIO{
in: file,
out: ioDiscard{},
err: &bytes.Buffer{},
})
}
type ioDiscard struct{}
func (ioDiscard) Write(p []byte) (int, error) {
return len(p), nil
}
func mustWriteRecordShell(t *testing.T, testDir string) {
t.Helper()
if err := writeRecordShell(testDir); err != nil {
t.Fatalf("writeRecordShell(%q) error = %v", testDir, err)
}
}
func defaultSandboxConfig() map[string]string {
return map[string]string{
"visible_home": "/home/test",
}
}
func containsEnvEntry(env []string, want string) bool {
for _, entry := range env {
if entry == want {
return true
}
}
return false
}
+273
View File
@@ -0,0 +1,273 @@
package testutil
import (
"bytes"
"io"
"os"
"os/exec"
"path/filepath"
"testing"
)
func CaptureOutput(t *testing.T, fn func()) (string, string) {
t.Helper()
t.Setenv("NO_COLOR", "1")
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
t.Fatalf("os.Pipe() stdout error = %v", err)
}
stderrReader, stderrWriter, err := os.Pipe()
if err != nil {
t.Fatalf("os.Pipe() stderr error = %v", err)
}
oldStdout := os.Stdout
oldStderr := os.Stderr
os.Stdout = stdoutWriter
os.Stderr = stderrWriter
t.Cleanup(func() {
os.Stdout = oldStdout
os.Stderr = oldStderr
})
fn()
if err := stdoutWriter.Close(); err != nil {
t.Fatalf("stdout close error = %v", err)
}
if err := stderrWriter.Close(); err != nil {
t.Fatalf("stderr close error = %v", err)
}
var stdoutBuf bytes.Buffer
if _, err := io.Copy(&stdoutBuf, stdoutReader); err != nil {
t.Fatalf("stdout copy error = %v", err)
}
var stderrBuf bytes.Buffer
if _, err := io.Copy(&stderrBuf, stderrReader); err != nil {
t.Fatalf("stderr copy error = %v", err)
}
return stdoutBuf.String(), stderrBuf.String()
}
func WriteFile(t *testing.T, path, content string) {
t.Helper()
MustMkdirAll(t, filepath.Dir(path))
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}
func WriteScenarioFixtures(t *testing.T, dir, in, out string) {
t.Helper()
WriteFile(t, filepath.Join(dir, "in"), in)
WriteFile(t, filepath.Join(dir, "out"), out)
}
func ReadFile(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile(%q) error = %v", path, err)
}
return string(data)
}
func MustMkdirAll(t *testing.T, path string) {
t.Helper()
if err := os.MkdirAll(path, 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", path, err)
}
}
func WithWorkingDir[T any](t *testing.T, dir string, fn func() T) T {
t.Helper()
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd() error = %v", err)
}
if err := os.Chdir(dir); err != nil {
t.Fatalf("Chdir(%q) error = %v", dir, err)
}
t.Cleanup(func() {
if err := os.Chdir(wd); err != nil {
t.Fatalf("restore working directory: %v", err)
}
})
return fn()
}
func WithStdin(t *testing.T, input string, fn func()) {
t.Helper()
path := filepath.Join(t.TempDir(), "stdin.txt")
if err := os.WriteFile(path, []byte(input), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
file, err := os.Open(path)
if err != nil {
t.Fatalf("Open(%q) error = %v", path, err)
}
oldStdin := os.Stdin
os.Stdin = file
t.Cleanup(func() {
os.Stdin = oldStdin
if err := file.Close(); err != nil {
t.Fatalf("close stdin file: %v", err)
}
})
fn()
}
func AddFakeRecordDependencies(t *testing.T, names ...string) {
t.Helper()
binDir := t.TempDir()
for _, name := range names {
path := filepath.Join(binDir, name)
body := "#!/bin/sh\nexit 0\n"
if name == "script" {
body = `#!/bin/sh
if [ -n "${FAKE_SCRIPT_ARGS_FILE:-}" ]; then
: > "$FAKE_SCRIPT_ARGS_FILE"
for arg in "$@"; do
printf '%s\n' "$arg" >> "$FAKE_SCRIPT_ARGS_FILE"
done
fi
in=''
out=''
cmd=''
while [ "$#" -gt 0 ]; do
case "$1" in
-I)
in="$2"
shift 2
;;
-O)
out="$2"
shift 2
;;
-c)
cmd="$2"
shift 2
;;
*)
shift
;;
esac
done
if [ -n "${FAKE_SCRIPT_COMMAND_BODY_FILE:-}" ] && [ -n "$cmd" ]; then
: > "$FAKE_SCRIPT_COMMAND_BODY_FILE"
while IFS= read -r line || [ -n "$line" ]; do
printf '%s\n' "$line" >> "$FAKE_SCRIPT_COMMAND_BODY_FILE"
done < "$cmd"
fi
command_has_compare_marker=0
if [ -n "$cmd" ] && /bin/grep -q '__MIRO_E2E_BEGIN__' "$cmd" 2>/dev/null; then
command_has_compare_marker=1
fi
stdin_file=''
if [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] || [ -n "${FAKE_SCRIPT_CAPTURE_STDIN_FILE:-}" ]; then
stdin_file="${TMPDIR:-/tmp}/miro-fake-script-stdin-$$"
/bin/cat > "$stdin_file"
fi
if [ -n "${FAKE_SCRIPT_CAPTURE_STDIN_FILE:-}" ] && [ -n "$stdin_file" ]; then
/bin/cp "$stdin_file" "$FAKE_SCRIPT_CAPTURE_STDIN_FILE"
fi
if [ -n "$in" ] && [ -n "${FAKE_SCRIPT_LOG_IN+x}" ]; then
printf '%s' "$FAKE_SCRIPT_LOG_IN" > "$in"
elif [ -n "$in" ] && [ -n "$stdin_file" ]; then
/bin/cp "$stdin_file" "$in"
elif [ -n "$in" ]; then
printf '%s' 'fake recorded input
' > "$in"
fi
if [ -n "${FAKE_SCRIPT_STREAM_OUT+x}" ]; then
printf '%s' "$FAKE_SCRIPT_STREAM_OUT"
elif [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] && [ -n "$stdin_file" ]; then
/bin/cat "$stdin_file"
if [ "${MIRO_COMPARE_MARKER:-0}" = "1" ] && [ "$command_has_compare_marker" = "1" ]; then
printf '%s\n' '__MIRO_E2E_BEGIN__'
/bin/cat "$stdin_file"
fi
fi
if [ -n "${FAKE_SCRIPT_STREAM_ERR+x}" ]; then
printf '%s' "$FAKE_SCRIPT_STREAM_ERR" >&2
fi
if [ -n "$out" ] && [ -n "${FAKE_SCRIPT_LOG_OUT+x}" ]; then
printf '%s' "$FAKE_SCRIPT_LOG_OUT" > "$out"
elif [ -n "$out" ] && [ "${FAKE_SCRIPT_ECHO_STDIN:-}" = "1" ] && [ -n "$stdin_file" ]; then
{
printf '%s' 'Script started on 2026-03-18 11:13:38+00:00 [TERM="xterm-256color"]
'
/bin/cat "$stdin_file"
if [ "${MIRO_COMPARE_MARKER:-0}" = "1" ] && [ "$command_has_compare_marker" = "1" ]; then
printf '%s\n' '__MIRO_E2E_BEGIN__'
/bin/cat "$stdin_file"
fi
printf '%s' 'Script done on 2026-03-18 11:13:44+00:00 [COMMAND_EXIT_CODE="0"]
'
} > "$out"
elif [ -n "$out" ]; then
printf '%s' 'Script started on 2026-03-18 11:13:38+00:00 [TERM="xterm-256color"]
fake recorded output
Script done on 2026-03-18 11:13:44+00:00 [COMMAND_EXIT_CODE="0"]
' > "$out"
fi
if [ -n "$stdin_file" ]; then
/bin/rm -f "$stdin_file"
fi
if [ -n "${FAKE_SCRIPT_EXIT_CODE:-}" ]; then
exit "$FAKE_SCRIPT_EXIT_CODE"
fi
exit 0
`
}
if err := os.WriteFile(path, []byte(body), 0o755); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}
t.Setenv("PATH", binDir)
}
func RequireCommands(t *testing.T, names ...string) {
t.Helper()
for _, name := range names {
if _, err := exec.LookPath(name); err != nil {
t.Skipf("missing command %q: %v", name, err)
}
}
}
func MustGitInit(t *testing.T, dir string) {
t.Helper()
cmd := exec.Command("git", "init")
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git init: %v\n%s", err, out)
}
}
func DefaultWrittenConfig(testDir string) string {
return "[miro]\n test_dir = \"" + testDir + "\"\n\n[sandbox]\n visible_home = \"/home/test\"\n"
}
func ValidConfigContent(testDir string) string {
return "[miro]\ntest_dir = \"" + testDir + "\"\n\n[sandbox]\nvisible_home = \"/home/test\"\n"
}