feat: add support for path arg in miro test

This commit is contained in:
2026-03-20 21:43:50 +00:00
parent b1061ee1af
commit 705933726e
4 changed files with 221 additions and 48 deletions
+113
View File
@@ -239,6 +239,99 @@ func TestRunTest(t *testing.T) {
}) })
} }
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 TestRunTestReturnsOneWhenScenarioMismatches(t *testing.T) { func TestRunTestReturnsOneWhenScenarioMismatches(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") addFakeRecordDependencies(t, "script", "bwrap", "bash")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1") t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1")
@@ -401,6 +494,26 @@ func TestRunRecordMissingPath(t *testing.T) {
} }
} }
func TestRunTestExtraArgs(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash")
stdout, stderr := captureOutput(t, func() {
if got := Run([]string{"test", "a", "b"}); 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 at most 1 arg(s), received 2") {
t.Fatalf("stderr = %q, want extra-arg error", stderr)
}
if !strings.Contains(stderr, "Usage:") || !strings.Contains(stderr, "miro test [path]") {
t.Fatalf("stderr = %q, want test usage", stderr)
}
}
func TestRunInitExtraArgs(t *testing.T) { func TestRunInitExtraArgs(t *testing.T) {
addFakeRecordDependencies(t, "script", "bwrap", "bash") addFakeRecordDependencies(t, "script", "bwrap", "bash")
+11 -4
View File
@@ -1,6 +1,8 @@
package cmd package cmd
import ( import (
"path/filepath"
"miro/internal/miro" "miro/internal/miro"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -8,10 +10,15 @@ import (
func newTestCommand() *cobra.Command { func newTestCommand() *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "test", Use: "test [path]",
Args: cobra.NoArgs, Args: cobra.MaximumNArgs(1),
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(_ *cobra.Command, args []string) error {
return miro.RunTests() path := ""
if len(args) == 1 {
path = filepath.Clean(args[0])
}
return miro.RunTests(path)
}, },
} }
} }
+38 -9
View File
@@ -45,14 +45,14 @@ func (e TestSuiteFailedError) Error() string {
} }
// RunTests replays all scenarios under the configured test directory. // RunTests replays all scenarios under the configured test directory.
func RunTests() error { func RunTests(path string) error {
return runTests(testIO{ return runTests(path, testIO{
out: os.Stdout, out: os.Stdout,
err: os.Stderr, err: os.Stderr,
}) })
} }
func runTests(tio testIO) error { func runTests(path string, tio testIO) error {
root, err := currentProjectRoot() root, err := currentProjectRoot()
if err != nil { if err != nil {
return err return err
@@ -68,17 +68,22 @@ func runTests(tio testIO) error {
return fmt.Errorf("failed to resolve test directory: %v", err) return fmt.Errorf("failed to resolve test directory: %v", err)
} }
discoveryRoot, err := resolveTestDiscoveryRoot(testDir, path)
if err != nil {
return err
}
shellPath, err := resolveRecordShell(testDir) shellPath, err := resolveRecordShell(testDir)
if err != nil { if err != nil {
return err return err
} }
scenarios, err := discoverTestScenarios(testDir) scenarios, err := discoverTestScenarios(discoveryRoot, testDir)
if err != nil { if err != nil {
return err return err
} }
if len(scenarios) == 0 { if len(scenarios) == 0 {
return fmt.Errorf("no test scenarios found in %q", testDir) return fmt.Errorf("no test scenarios found in %q", discoveryRoot)
} }
summary := testSummary{total: len(scenarios)} summary := testSummary{total: len(scenarios)}
@@ -110,10 +115,34 @@ func runTests(tio testIO) error {
return nil return nil
} }
func discoverTestScenarios(testDir string) ([]testScenario, error) { func resolveTestDiscoveryRoot(testDir, path string) (string, error) {
if path == "" {
return testDir, nil
}
target, err := resolvePathWithinTestDir(testDir, path, "test")
if err != nil {
return "", err
}
info, err := os.Stat(target)
if err != nil {
if os.IsNotExist(err) {
return "", fmt.Errorf("test path %q does not exist", target)
}
return "", fmt.Errorf("failed to check test path %q: %v", target, err)
}
if !info.IsDir() {
return "", fmt.Errorf("test path %q is not a directory", target)
}
return target, nil
}
func discoverTestScenarios(discoveryRoot, displayRoot string) ([]testScenario, error) {
fixturesByDir := map[string]testFixtureFiles{} fixturesByDir := map[string]testFixtureFiles{}
if err := filepath.WalkDir(testDir, func(path string, d fs.DirEntry, walkErr error) error { if err := filepath.WalkDir(discoveryRoot, func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil { if walkErr != nil {
return walkErr return walkErr
} }
@@ -137,12 +166,12 @@ func discoverTestScenarios(testDir string) ([]testScenario, error) {
return nil return nil
}); err != nil { }); err != nil {
return nil, fmt.Errorf("failed to scan test directory %q: %v", testDir, err) return nil, fmt.Errorf("failed to scan test directory %q: %v", discoveryRoot, err)
} }
scenarios := make([]testScenario, 0, len(fixturesByDir)) scenarios := make([]testScenario, 0, len(fixturesByDir))
for dir, files := range fixturesByDir { for dir, files := range fixturesByDir {
relPath, err := filepath.Rel(testDir, dir) relPath, err := filepath.Rel(displayRoot, dir)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to resolve scenario path for %q: %v", dir, err) return nil, fmt.Errorf("failed to resolve scenario path for %q: %v", dir, err)
} }
+59 -35
View File
@@ -2,7 +2,6 @@ package miro
import ( import (
"bytes" "bytes"
"errors"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@@ -15,7 +14,7 @@ func TestDiscoverTestScenariosFindsNestedFixturesAndSorts(t *testing.T) {
writeFile(t, filepath.Join(testDir, "shell.sh"), "#!/bin/sh\n") writeFile(t, filepath.Join(testDir, "shell.sh"), "#!/bin/sh\n")
writeFile(t, filepath.Join(testDir, "notes.txt"), "ignore me\n") writeFile(t, filepath.Join(testDir, "notes.txt"), "ignore me\n")
got, err := discoverTestScenarios(testDir) got, err := discoverTestScenarios(testDir, testDir)
if err != nil { if err != nil {
t.Fatalf("discoverTestScenarios() error = %v", err) t.Fatalf("discoverTestScenarios() error = %v", err)
} }
@@ -45,7 +44,7 @@ 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") writeFile(t, filepath.Join(testDir, "broken", "in"), "echo broken\n")
_, err := discoverTestScenarios(testDir) _, err := discoverTestScenarios(testDir, testDir)
if err == nil { if err == nil {
t.Fatal("discoverTestScenarios() error = nil, want error") t.Fatal("discoverTestScenarios() error = nil, want error")
} }
@@ -58,7 +57,7 @@ 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") writeFile(t, filepath.Join(testDir, "broken", "out"), "echo broken\n")
_, err := discoverTestScenarios(testDir) _, err := discoverTestScenarios(testDir, testDir)
if err == nil { if err == nil {
t.Fatal("discoverTestScenarios() error = nil, want error") t.Fatal("discoverTestScenarios() error = nil, want error")
} }
@@ -133,47 +132,72 @@ func TestReplayScenarioFailsWhenCompareMarkerMissing(t *testing.T) {
} }
} }
func TestRunTestsRunsFullSuiteAndSummarizesFailures(t *testing.T) { func TestDiscoverTestScenariosUsesDisplayRootForRelativePaths(t *testing.T) {
addFakeRecordDependencies(t, "script") testDir := filepath.Join(t.TempDir(), "e2e")
t.Setenv("FAKE_SCRIPT_ECHO_STDIN", "1") scopedDir := filepath.Join(testDir, "nested")
writeScenarioFixtures(t, filepath.Join(scopedDir, "b"), "echo two\n", "echo two\n")
got, err := discoverTestScenarios(scopedDir, testDir)
if err != nil {
t.Fatalf("discoverTestScenarios() error = %v", err)
}
if len(got) != 1 {
t.Fatalf("len(discoverTestScenarios()) = %d, want 1", len(got))
}
if got[0].relPath != filepath.Join("nested", "b") {
t.Fatalf("scenario relPath = %q, want %q", got[0].relPath, filepath.Join("nested", "b"))
}
}
func TestResolveTestDiscoveryRootRejectsMissingPath(t *testing.T) {
testDir := filepath.Join(t.TempDir(), "e2e")
mustMkdirAll(t, testDir)
_, err := resolveTestDiscoveryRoot(testDir, "missing")
if err == nil {
t.Fatal("resolveTestDiscoveryRoot() error = nil, want error")
}
if !strings.Contains(err.Error(), `does not exist`) {
t.Fatalf("resolveTestDiscoveryRoot() error = %q, want missing-path error", err.Error())
}
}
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")
err := withWorkingDir(t, root, func() error {
_, err := resolveTestDiscoveryRoot(testDir, filepath.Join("e2e", "case.txt"))
return err
})
if err == nil {
t.Fatal("resolveTestDiscoveryRoot() error = nil, want error")
}
if !strings.Contains(err.Error(), `is not a directory`) {
t.Fatalf("resolveTestDiscoveryRoot() error = %q, want directory error", err.Error())
}
}
func TestRunTestsScopedRunEmptyDirectoryFails(t *testing.T) {
root := t.TempDir()
testDir := filepath.Join(root, "e2e")
emptyDir := filepath.Join(testDir, "empty")
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e")) writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
mustWriteRecordShell(t, testDir) mustWriteRecordShell(t, testDir)
writeScenarioFixtures(t, filepath.Join(testDir, "a"), "echo one\n", "echo one\n") mustMkdirAll(t, emptyDir)
writeScenarioFixtures(t, filepath.Join(testDir, "b"), "echo two\n", "different output\n")
var stdout bytes.Buffer
var stderr bytes.Buffer
err := withWorkingDir(t, root, func() error { err := withWorkingDir(t, root, func() error {
return runTests(testIO{ return runTests("empty", testIO{
out: &stdout, out: &bytes.Buffer{},
err: &stderr, err: &bytes.Buffer{},
}) })
}) })
if err == nil {
var suiteErr TestSuiteFailedError t.Fatal("runTests() error = nil, want error")
if !errors.As(err, &suiteErr) {
t.Fatalf("runTests() error = %v, want TestSuiteFailedError", err)
} }
if suiteErr.Failed != 1 { if !strings.Contains(err.Error(), `no test scenarios found in "`) || !strings.Contains(err.Error(), filepath.Join("e2e", "empty")) {
t.Fatalf("TestSuiteFailedError.Failed = %d, want 1", suiteErr.Failed) t.Fatalf("runTests() error = %q, want empty-directory error", err.Error())
}
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.String(), want) {
t.Fatalf("stdout = %q, want substring %q", stdout.String(), want)
}
}
if stderr.String() != "" {
t.Fatalf("stderr = %q, want empty", stderr.String())
} }
} }