feat: arbitrary MIRO_ envs to shell script
This commit is contained in:
+12
-4
@@ -50,8 +50,8 @@ func TestRunInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if got := mustReadFile(t, filepath.Join(root, "miro.toml")); got != "[miro]\n test_dir = \"e2e\"\n" {
|
if got := mustReadFile(t, filepath.Join(root, "miro.toml")); got != defaultWrittenConfig("e2e") {
|
||||||
t.Fatalf("config = %q, want %q", got, "[miro]\n test_dir = \"e2e\"\n")
|
t.Fatalf("config = %q, want %q", got, 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 {
|
||||||
@@ -152,7 +152,7 @@ func TestRunRecordRejectsAbsolutePathOutsideTestDir(t *testing.T) {
|
|||||||
if err := os.MkdirAll(wantDir, 0o755); err != nil {
|
if err := os.MkdirAll(wantDir, 0o755); err != nil {
|
||||||
t.Fatalf("MkdirAll() error = %v", err)
|
t.Fatalf("MkdirAll() error = %v", err)
|
||||||
}
|
}
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"e2e\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
|
||||||
|
|
||||||
outside := filepath.Join(root, "outside", "spec")
|
outside := filepath.Join(root, "outside", "spec")
|
||||||
withWorkingDir(t, root, func() {
|
withWorkingDir(t, root, func() {
|
||||||
@@ -206,7 +206,7 @@ func TestRunRecordFailsWhenDependencyMissing(t *testing.T) {
|
|||||||
if err := os.MkdirAll(wantDir, 0o755); err != nil {
|
if err := os.MkdirAll(wantDir, 0o755); err != nil {
|
||||||
t.Fatalf("MkdirAll() error = %v", err)
|
t.Fatalf("MkdirAll() error = %v", err)
|
||||||
}
|
}
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"e2e\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -446,3 +446,11 @@ func withStdin(t *testing.T, input string, fn func()) {
|
|||||||
|
|
||||||
fn()
|
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"
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,22 +5,40 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DefaultVisibleHome = "/home/test"
|
||||||
|
|
||||||
|
var (
|
||||||
|
lowerSnakeCasePattern = regexp.MustCompile(`^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$`)
|
||||||
|
requiredSandboxDefaults = map[string]string{
|
||||||
|
"visible_home": DefaultVisibleHome,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
TestDir string
|
TestDir string
|
||||||
|
Sandbox map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type tomlConfig struct {
|
type tomlConfig struct {
|
||||||
Miro tomlMiroConfig `toml:"miro"`
|
Miro tomlMiroConfig `toml:"miro"`
|
||||||
|
Sandbox map[string]string `toml:"sandbox"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tomlMiroConfig struct {
|
type tomlMiroConfig struct {
|
||||||
TestDir string `toml:"test_dir"`
|
TestDir string `toml:"test_dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DefaultSandboxConfig() map[string]string {
|
||||||
|
return cloneSandbox(requiredSandboxDefaults)
|
||||||
|
}
|
||||||
|
|
||||||
// ReadConfig reads miro.toml.
|
// ReadConfig reads miro.toml.
|
||||||
func ReadConfig(path string) (Config, error) {
|
func ReadConfig(path string) (Config, error) {
|
||||||
var raw tomlConfig
|
var raw tomlConfig
|
||||||
@@ -41,9 +59,18 @@ func ReadConfig(path string) (Config, error) {
|
|||||||
if raw.Miro.TestDir == "" {
|
if raw.Miro.TestDir == "" {
|
||||||
return Config{}, fmt.Errorf("failed to read %s: empty miro.test_dir", path)
|
return Config{}, fmt.Errorf("failed to read %s: empty miro.test_dir", path)
|
||||||
}
|
}
|
||||||
|
if !meta.IsDefined("sandbox") {
|
||||||
|
return Config{}, fmt.Errorf("failed to read %s: missing [sandbox] config", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
sandbox, err := validateSandbox(path, raw.Sandbox)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
TestDir: raw.Miro.TestDir,
|
TestDir: raw.Miro.TestDir,
|
||||||
|
Sandbox: sandbox,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,15 +79,61 @@ func WriteConfig(path string, cfg Config) error {
|
|||||||
if cfg.TestDir == "" {
|
if cfg.TestDir == "" {
|
||||||
return errors.New("empty miro.test_dir")
|
return errors.New("empty miro.test_dir")
|
||||||
}
|
}
|
||||||
|
sandbox, err := validateSandbox(path, cfg.Sandbox)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := toml.NewEncoder(&buf).Encode(tomlConfig{
|
fmt.Fprintf(&buf, "[miro]\n test_dir = %q\n\n[sandbox]\n", cfg.TestDir)
|
||||||
Miro: tomlMiroConfig{
|
|
||||||
TestDir: cfg.TestDir,
|
keys := make([]string, 0, len(sandbox))
|
||||||
},
|
for key := range sandbox {
|
||||||
}); err != nil {
|
keys = append(keys, key)
|
||||||
return fmt.Errorf("failed to encode %s: %v", path, err)
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
fmt.Fprintf(&buf, " %s = %q\n", key, sandbox[key])
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(path, buf.Bytes(), 0o644)
|
return os.WriteFile(path, buf.Bytes(), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSandbox(path string, sandbox map[string]string) (map[string]string, error) {
|
||||||
|
validated := cloneSandbox(sandbox)
|
||||||
|
|
||||||
|
for key := range validated {
|
||||||
|
if !lowerSnakeCasePattern.MatchString(key) {
|
||||||
|
return nil, fmt.Errorf("failed to read %s: invalid sandbox key %q: must be lower_snake_case", path, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range requiredSandboxDefaults {
|
||||||
|
value, ok := validated[key]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to read %s: missing required sandbox.%s", path, key)
|
||||||
|
}
|
||||||
|
if value == "" {
|
||||||
|
return nil, fmt.Errorf("failed to read %s: empty sandbox.%s", path, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filepath.IsAbs(validated["visible_home"]) {
|
||||||
|
return nil, fmt.Errorf("failed to read %s: sandbox.visible_home must be an absolute path", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return validated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneSandbox(sandbox map[string]string) map[string]string {
|
||||||
|
if len(sandbox) == 0 {
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned := make(map[string]string, len(sandbox))
|
||||||
|
for key, value := range sandbox {
|
||||||
|
cloned[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,13 +13,18 @@ func TestReadConfig(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
content string
|
content string
|
||||||
wantDir string
|
wantDir string
|
||||||
|
wantSandbox map[string]string
|
||||||
wantErr string
|
wantErr string
|
||||||
wantMissing bool
|
wantMissing bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "with test dir",
|
name: "with test dir and sandbox",
|
||||||
content: "[miro]\ntest_dir = \"custom/suite\"\n",
|
content: "[miro]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nvisible_home = \"/home/test\"\nkey_word = \"value\"\n",
|
||||||
wantDir: "custom/suite",
|
wantDir: "custom/suite",
|
||||||
|
wantSandbox: map[string]string{
|
||||||
|
"visible_home": "/home/test",
|
||||||
|
"key_word": "value",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "legacy top level key",
|
name: "legacy top level key",
|
||||||
@@ -41,6 +46,31 @@ func TestReadConfig(t *testing.T) {
|
|||||||
content: "[miro]\ntest_dir = \"\"\n",
|
content: "[miro]\ntest_dir = \"\"\n",
|
||||||
wantErr: "empty miro.test_dir",
|
wantErr: "empty miro.test_dir",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "without sandbox table",
|
||||||
|
content: "[miro]\ntest_dir = \"custom/suite\"\n",
|
||||||
|
wantErr: "missing [sandbox] config",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without required visible home",
|
||||||
|
content: "[miro]\ntest_dir = \"custom/suite\"\n\n[sandbox]\n",
|
||||||
|
wantErr: "missing required sandbox.visible_home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty required visible home",
|
||||||
|
content: "[miro]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nvisible_home = \"\"\n",
|
||||||
|
wantErr: "empty sandbox.visible_home",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "relative visible home",
|
||||||
|
content: "[miro]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nvisible_home = \"home/test\"\n",
|
||||||
|
wantErr: "sandbox.visible_home must be an absolute path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid sandbox key",
|
||||||
|
content: "[miro]\ntest_dir = \"custom/suite\"\n\n[sandbox]\nvisible_home = \"/home/test\"\nKeyWord = \"value\"\n",
|
||||||
|
wantErr: "invalid sandbox key",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "invalid toml",
|
name: "invalid toml",
|
||||||
content: "[miro]\ntest_dir = [\n",
|
content: "[miro]\ntest_dir = [\n",
|
||||||
@@ -83,6 +113,14 @@ func TestReadConfig(t *testing.T) {
|
|||||||
if got.TestDir != tt.wantDir {
|
if got.TestDir != tt.wantDir {
|
||||||
t.Fatalf("ReadConfig() TestDir = %q, want %q", got.TestDir, tt.wantDir)
|
t.Fatalf("ReadConfig() TestDir = %q, want %q", got.TestDir, tt.wantDir)
|
||||||
}
|
}
|
||||||
|
if len(got.Sandbox) != len(tt.wantSandbox) {
|
||||||
|
t.Fatalf("ReadConfig() Sandbox = %#v, want %#v", got.Sandbox, tt.wantSandbox)
|
||||||
|
}
|
||||||
|
for key, want := range tt.wantSandbox {
|
||||||
|
if got.Sandbox[key] != want {
|
||||||
|
t.Fatalf("ReadConfig() Sandbox[%q] = %q, want %q", key, got.Sandbox[key], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +128,14 @@ func TestReadConfig(t *testing.T) {
|
|||||||
func TestWriteConfig(t *testing.T) {
|
func TestWriteConfig(t *testing.T) {
|
||||||
path := filepath.Join(t.TempDir(), "miro.toml")
|
path := filepath.Join(t.TempDir(), "miro.toml")
|
||||||
|
|
||||||
if err := WriteConfig(path, Config{TestDir: "e2e"}); err != nil {
|
if err := WriteConfig(path, Config{
|
||||||
|
TestDir: "e2e",
|
||||||
|
Sandbox: map[string]string{
|
||||||
|
"visible_home": "/home/test",
|
||||||
|
"alpha_key": "a",
|
||||||
|
"zulu_key": "z",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
t.Fatalf("WriteConfig() error = %v", err)
|
t.Fatalf("WriteConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,8 +143,9 @@ func TestWriteConfig(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ReadFile() error = %v", err)
|
t.Fatalf("ReadFile() error = %v", err)
|
||||||
}
|
}
|
||||||
if string(got) != "[miro]\n test_dir = \"e2e\"\n" {
|
want := "[miro]\n test_dir = \"e2e\"\n\n[sandbox]\n alpha_key = \"a\"\n visible_home = \"/home/test\"\n zulu_key = \"z\"\n"
|
||||||
t.Fatalf("config = %q, want %q", string(got), "[miro]\n test_dir = \"e2e\"\n")
|
if string(got) != want {
|
||||||
|
t.Fatalf("config = %q, want %q", string(got), want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,3 +160,18 @@ func TestWriteConfigEmptyTestDirFails(t *testing.T) {
|
|||||||
t.Fatalf("WriteConfig() error = %q, want %q", err.Error(), "empty miro.test_dir")
|
t.Fatalf("WriteConfig() error = %q, want %q", err.Error(), "empty miro.test_dir")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteConfigMissingRequiredSandboxFails(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), "miro.toml")
|
||||||
|
|
||||||
|
err := WriteConfig(path, Config{
|
||||||
|
TestDir: "e2e",
|
||||||
|
Sandbox: map[string]string{},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("WriteConfig() error = nil, want error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "missing required sandbox.visible_home") {
|
||||||
|
t.Fatalf("WriteConfig() error = %q, want visible_home error", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func Init() error {
|
|||||||
} else {
|
} else {
|
||||||
if err := miroconfig.WriteConfig(configPath, miroconfig.Config{
|
if err := miroconfig.WriteConfig(configPath, miroconfig.Config{
|
||||||
TestDir: defaultTestDir,
|
TestDir: defaultTestDir,
|
||||||
|
Sandbox: miroconfig.DefaultSandboxConfig(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("failed to write %s: %v", configPath, err)
|
return fmt.Errorf("failed to write %s: %v", configPath, err)
|
||||||
}
|
}
|
||||||
@@ -38,5 +39,5 @@ func Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeRecordShell(testDir)
|
return ensureRecordShell(testDir)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-2
@@ -10,7 +10,17 @@ import (
|
|||||||
// Record creates the requested scenario path under the resolved test directory
|
// Record creates the requested scenario path under the resolved test directory
|
||||||
// and records an interactive shell session into in/out fixtures when saved.
|
// and records an interactive shell session into in/out fixtures when saved.
|
||||||
func Record(path string) (string, error) {
|
func Record(path string) (string, error) {
|
||||||
testDir, err := ResolveTestDir()
|
root, err := currentProjectRoot()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readConfigFromRoot(root)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to resolve test directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testDir, err := resolveTestDirFromConfig(root, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to resolve test directory: %v", err)
|
return "", fmt.Errorf("failed to resolve test directory: %v", err)
|
||||||
}
|
}
|
||||||
@@ -33,7 +43,7 @@ func Record(path string) (string, error) {
|
|||||||
in: os.Stdin,
|
in: os.Stdin,
|
||||||
out: os.Stdout,
|
out: os.Stdout,
|
||||||
err: os.Stderr,
|
err: os.Stderr,
|
||||||
}); err != nil {
|
}, cfg.Sandbox); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
recordVisibleHome = "/home/test"
|
recordGitDate = "2024-01-01T00:00:00Z"
|
||||||
recordGitDate = "2024-01-01T00:00:00Z"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type recordIO struct {
|
type recordIO struct {
|
||||||
@@ -22,7 +21,7 @@ type recordIO struct {
|
|||||||
err io.Writer
|
err io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func recordScenario(target, shellPath string, rio recordIO) error {
|
func recordScenario(target, shellPath string, rio recordIO, sandboxConfig map[string]string) error {
|
||||||
rawIn, rawOut, cleanup, err := newRecordFiles()
|
rawIn, rawOut, cleanup, err := newRecordFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -45,7 +44,7 @@ func recordScenario(target, shellPath string, rio recordIO) error {
|
|||||||
|
|
||||||
output.Fprintln(rio.err, "Run commands in the recorder shell, then type exit to finish.")
|
output.Fprintln(rio.err, "Run commands in the recorder shell, then type exit to finish.")
|
||||||
|
|
||||||
if err := runRecordSession(target, rawIn, rawOut, shellPath, sandbox, rio); err != nil {
|
if err := runRecordSession(target, rawIn, rawOut, shellPath, sandbox, rio, sandboxConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +122,11 @@ func newRecordSandboxForPathEnv(pathEnv string) (recordSandbox, func(), error) {
|
|||||||
|
|
||||||
return sandbox, cleanup, nil
|
return sandbox, cleanup, nil
|
||||||
}
|
}
|
||||||
func runRecordSession(dir, rawIn, rawOut, shellPath string, sandbox recordSandbox, rio recordIO) error {
|
|
||||||
|
func runRecordSession(dir, rawIn, rawOut, shellPath string, sandbox recordSandbox, rio recordIO, sandboxConfig map[string]string) error {
|
||||||
cmd := exec.Command("script", "-q", "-E", "always", "-I", rawIn, "-O", rawOut, "-c", shellPath)
|
cmd := exec.Command("script", "-q", "-E", "always", "-I", rawIn, "-O", rawOut, "-c", shellPath)
|
||||||
cmd.Dir = dir
|
cmd.Dir = dir
|
||||||
cmd.Env = recordSessionEnv(sandbox)
|
cmd.Env = recordSessionEnv(sandbox, sandboxConfig)
|
||||||
cmd.Stdin = rio.in
|
cmd.Stdin = rio.in
|
||||||
cmd.Stdout = rio.out
|
cmd.Stdout = rio.out
|
||||||
cmd.Stderr = rio.err
|
cmd.Stderr = rio.err
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
@@ -24,6 +25,17 @@ func recordShellPath(testDir string) string {
|
|||||||
return filepath.Join(testDir, recordShellName)
|
return filepath.Join(testDir, recordShellName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureRecordShell(testDir string) error {
|
||||||
|
path := recordShellPath(testDir)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return nil
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return fmt.Errorf("failed to check recorder shell %q: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeRecordShell(testDir)
|
||||||
|
}
|
||||||
|
|
||||||
func writeRecordShell(testDir string) error {
|
func writeRecordShell(testDir string) error {
|
||||||
if err := os.MkdirAll(testDir, 0o755); err != nil {
|
if err := os.MkdirAll(testDir, 0o755); err != nil {
|
||||||
return fmt.Errorf("failed to create test directory %q: %v", testDir, err)
|
return fmt.Errorf("failed to create test directory %q: %v", testDir, err)
|
||||||
@@ -59,11 +71,9 @@ func resolveRecordShell(testDir string) (string, error) {
|
|||||||
func buildRecordShellScript() string {
|
func buildRecordShellScript() string {
|
||||||
var body bytes.Buffer
|
var body bytes.Buffer
|
||||||
if err := recordShellTemplate.Execute(&body, struct {
|
if err := recordShellTemplate.Execute(&body, struct {
|
||||||
VisibleHome string
|
GitDate string
|
||||||
GitDate string
|
|
||||||
}{
|
}{
|
||||||
VisibleHome: shQuote(recordVisibleHome),
|
GitDate: shQuote(recordGitDate),
|
||||||
GitDate: shQuote(recordGitDate),
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
panic(fmt.Sprintf("render record shell template: %v", err))
|
panic(fmt.Sprintf("render record shell template: %v", err))
|
||||||
}
|
}
|
||||||
@@ -71,17 +81,33 @@ func buildRecordShellScript() string {
|
|||||||
return body.String()
|
return body.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func recordSessionEnv(sandbox recordSandbox) []string {
|
func recordSessionEnv(sandbox recordSandbox, sandboxConfig map[string]string) []string {
|
||||||
env := append([]string{}, os.Environ()...)
|
env := append([]string{}, os.Environ()...)
|
||||||
env = append(env,
|
env = append(env,
|
||||||
"MIRO_RECORD_HOST_HOME="+sandbox.hostHome,
|
"MIRO_HOST_HOME="+sandbox.hostHome,
|
||||||
"MIRO_RECORD_HOST_TMP="+sandbox.hostTmp,
|
"MIRO_HOST_TMP="+sandbox.hostTmp,
|
||||||
"MIRO_RECORD_PATH_ENV="+sandbox.pathEnv,
|
"MIRO_PATH_ENV="+sandbox.pathEnv,
|
||||||
)
|
)
|
||||||
|
for _, key := range sortedSandboxKeys(sandboxConfig) {
|
||||||
|
env = append(env, sandboxEnvName(key)+"="+sandboxConfig[key])
|
||||||
|
}
|
||||||
|
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sandboxEnvName(key string) string {
|
||||||
|
return "MIRO_" + strings.ToUpper(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedSandboxKeys(sandboxConfig map[string]string) []string {
|
||||||
|
keys := make([]string, 0, len(sandboxConfig))
|
||||||
|
for key := range sandboxConfig {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
func shQuote(value string) string {
|
func shQuote(value string) string {
|
||||||
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
|
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
# Host directory mounted as the sandboxed HOME.
|
# Host directory mounted as the sandboxed HOME.
|
||||||
host_home=${MIRO_RECORD_HOST_HOME:?}
|
host_home=${MIRO_HOST_HOME:?}
|
||||||
# Host temp directory mounted read-write inside the sandbox.
|
# Host temp directory mounted read-write inside the sandbox.
|
||||||
host_tmp=${MIRO_RECORD_HOST_TMP:?}
|
host_tmp=${MIRO_HOST_TMP:?}
|
||||||
# PATH value forwarded into the sandbox so required tools stay available.
|
# PATH value forwarded into the sandbox so required tools stay available.
|
||||||
path_env=${MIRO_RECORD_PATH_ENV:?}
|
path_env=${MIRO_PATH_ENV:?}
|
||||||
|
visible_home=${MIRO_VISIBLE_HOME:?}
|
||||||
|
|
||||||
if command -v git >/dev/null 2>&1; then
|
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.name 'Miro Test' >/dev/null 2>&1 || :
|
||||||
@@ -15,10 +16,10 @@ if command -v git >/dev/null 2>&1; then
|
|||||||
HOME="$host_home" GIT_CONFIG_NOSYSTEM=1 git config --global advice.defaultBranchName false >/dev/null 2>&1 || :
|
HOME="$host_home" GIT_CONFIG_NOSYSTEM=1 git config --global advice.defaultBranchName false >/dev/null 2>&1 || :
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec bwrap \
|
set -- \
|
||||||
--ro-bind / / \
|
--ro-bind / / \
|
||||||
--tmpfs /home \
|
--tmpfs /home \
|
||||||
--bind "$host_home" {{ .VisibleHome }} \
|
--bind "$host_home" "$visible_home" \
|
||||||
--bind "$host_tmp" '/tmp' \
|
--bind "$host_tmp" '/tmp' \
|
||||||
--dev /dev \
|
--dev /dev \
|
||||||
--proc /proc \
|
--proc /proc \
|
||||||
@@ -29,7 +30,7 @@ exec bwrap \
|
|||||||
--setenv GIT_CONFIG_NOSYSTEM '1' \
|
--setenv GIT_CONFIG_NOSYSTEM '1' \
|
||||||
--setenv GIT_PAGER 'cat' \
|
--setenv GIT_PAGER 'cat' \
|
||||||
--setenv HISTFILE '/dev/null' \
|
--setenv HISTFILE '/dev/null' \
|
||||||
--setenv HOME {{ .VisibleHome }} \
|
--setenv HOME "$visible_home" \
|
||||||
--setenv LANG 'C' \
|
--setenv LANG 'C' \
|
||||||
--setenv LC_ALL 'C' \
|
--setenv LC_ALL 'C' \
|
||||||
--setenv PAGER 'cat' \
|
--setenv PAGER 'cat' \
|
||||||
@@ -38,5 +39,6 @@ exec bwrap \
|
|||||||
--setenv TERM 'xterm-256color' \
|
--setenv TERM 'xterm-256color' \
|
||||||
--setenv TMPDIR '/tmp' \
|
--setenv TMPDIR '/tmp' \
|
||||||
--setenv TZ 'UTC' \
|
--setenv TZ 'UTC' \
|
||||||
--chdir {{ .VisibleHome }} \
|
--chdir "$visible_home"
|
||||||
bash --noprofile --norc -i
|
|
||||||
|
exec bwrap "$@" bash --noprofile --norc -i
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func TestRecordCreatesRelativePath(t *testing.T) {
|
|||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
testDir := filepath.Join(root, "e2e")
|
testDir := filepath.Join(root, "e2e")
|
||||||
mustMkdirAll(t, testDir)
|
mustMkdirAll(t, testDir)
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"e2e\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
|
||||||
mustWriteRecordShell(t, testDir)
|
mustWriteRecordShell(t, testDir)
|
||||||
addFakeRecordDependencies(t, "script")
|
addFakeRecordDependencies(t, "script")
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ func TestRecordAcceptsExplicitTestDirPrefix(t *testing.T) {
|
|||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
testDir := filepath.Join(root, "e2e")
|
testDir := filepath.Join(root, "e2e")
|
||||||
mustMkdirAll(t, testDir)
|
mustMkdirAll(t, testDir)
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"e2e\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
|
||||||
mustWriteRecordShell(t, testDir)
|
mustWriteRecordShell(t, testDir)
|
||||||
addFakeRecordDependencies(t, "script")
|
addFakeRecordDependencies(t, "script")
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ func TestRecordAcceptsExplicitTestDirPrefix(t *testing.T) {
|
|||||||
func TestRecordRejectsAbsolutePathOutsideTestDir(t *testing.T) {
|
func TestRecordRejectsAbsolutePathOutsideTestDir(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
mustMkdirAll(t, filepath.Join(root, "e2e"))
|
mustMkdirAll(t, filepath.Join(root, "e2e"))
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"e2e\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
|
||||||
outside := filepath.Join(root, "outside", "a", "b", "c")
|
outside := filepath.Join(root, "outside", "a", "b", "c")
|
||||||
|
|
||||||
err := withWorkingDir(t, root, func() error {
|
err := withWorkingDir(t, root, func() error {
|
||||||
@@ -110,7 +110,7 @@ func TestRecordReturnsDiscardedErrorWhenSaveDeclined(t *testing.T) {
|
|||||||
target := filepath.Join(testDir, "a", "b", "c")
|
target := filepath.Join(testDir, "a", "b", "c")
|
||||||
mustMkdirAll(t, target)
|
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)
|
return recordScenario(target, recordShellPath(testDir), rio, defaultSandboxConfig())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ func TestRecordReturnsDiscardedErrorWhenOverwriteDeclined(t *testing.T) {
|
|||||||
|
|
||||||
err := withWorkingDir(t, root, func() error {
|
err := 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)
|
return recordScenario(target, recordShellPath(testDir), rio, defaultSandboxConfig())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -167,24 +167,62 @@ func TestBuildRecordShellScriptUsesExpectedCommands(t *testing.T) {
|
|||||||
body := buildRecordShellScript()
|
body := buildRecordShellScript()
|
||||||
|
|
||||||
for _, want := range []string{
|
for _, want := range []string{
|
||||||
"host_home=${MIRO_RECORD_HOST_HOME:?}",
|
"host_home=${MIRO_HOST_HOME:?}",
|
||||||
"host_tmp=${MIRO_RECORD_HOST_TMP:?}",
|
"host_tmp=${MIRO_HOST_TMP:?}",
|
||||||
"path_env=${MIRO_RECORD_PATH_ENV:?}",
|
"path_env=${MIRO_PATH_ENV:?}",
|
||||||
|
"visible_home=${MIRO_VISIBLE_HOME:?}",
|
||||||
"HOME=\"$host_home\" GIT_CONFIG_NOSYSTEM=1 git config --global user.name 'Miro Test'",
|
"HOME=\"$host_home\" GIT_CONFIG_NOSYSTEM=1 git config --global user.name 'Miro Test'",
|
||||||
"--bind \"$host_home\" " + shQuote(recordVisibleHome),
|
"--bind \"$host_home\" \"$visible_home\"",
|
||||||
"--bind \"$host_tmp\" '/tmp'",
|
"--bind \"$host_tmp\" '/tmp'",
|
||||||
"--setenv HOME " + shQuote(recordVisibleHome),
|
"--setenv HOME \"$visible_home\"",
|
||||||
"--setenv PATH \"$path_env\"",
|
"--setenv PATH \"$path_env\"",
|
||||||
"--setenv PS1 '$ '",
|
"--setenv PS1 '$ '",
|
||||||
"--setenv TERM 'xterm-256color'",
|
"--setenv TERM 'xterm-256color'",
|
||||||
"--setenv TZ 'UTC'",
|
"--setenv TZ 'UTC'",
|
||||||
"--chdir " + shQuote(recordVisibleHome),
|
"--chdir \"$visible_home\"",
|
||||||
"bash --noprofile --norc -i",
|
"exec bwrap \"$@\" bash --noprofile --norc -i",
|
||||||
} {
|
} {
|
||||||
if !strings.Contains(body, want) {
|
if !strings.Contains(body, want) {
|
||||||
t.Fatalf("wrapper = %q, want substring %q", body, want)
|
t.Fatalf("wrapper = %q, want substring %q", body, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, unwanted := range []string{
|
||||||
|
"MIRO_SANDBOX_ENVS",
|
||||||
|
"set -- \"$@\" --setenv \"$env_name\" \"$env_value\"",
|
||||||
|
} {
|
||||||
|
if strings.Contains(body, unwanted) {
|
||||||
|
t.Fatalf("wrapper = %q, want no substring %q", body, unwanted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(body, "/home/test") {
|
||||||
|
t.Fatalf("wrapper = %q, want no hardcoded visible home", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecordSessionEnvIncludesConfiguredSandboxEnv(t *testing.T) {
|
||||||
|
env := recordSessionEnv(recordSandbox{
|
||||||
|
hostHome: "/tmp/host-home",
|
||||||
|
hostTmp: "/tmp/host-tmp",
|
||||||
|
pathEnv: "/tmp/bin",
|
||||||
|
}, map[string]string{
|
||||||
|
"visible_home": "/sandbox/home",
|
||||||
|
"key_word": "value",
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, want := range []string{
|
||||||
|
"MIRO_HOST_HOME=/tmp/host-home",
|
||||||
|
"MIRO_HOST_TMP=/tmp/host-tmp",
|
||||||
|
"MIRO_PATH_ENV=/tmp/bin",
|
||||||
|
"MIRO_KEY_WORD=value",
|
||||||
|
"MIRO_VISIBLE_HOME=/sandbox/home",
|
||||||
|
} {
|
||||||
|
if !containsEnvEntry(env, want) {
|
||||||
|
t.Fatalf("env = %#v, want entry %q", env, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if containsEnvEntry(env, "MIRO_SANDBOX_ENVS=MIRO_KEY_WORD:MIRO_VISIBLE_HOME") {
|
||||||
|
t.Fatalf("env = %#v, want MIRO_SANDBOX_ENVS to be absent", env)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
|
func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
|
||||||
@@ -205,7 +243,7 @@ func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
|
|||||||
|
|
||||||
shellPath := recordShellPath(testDir)
|
shellPath := recordShellPath(testDir)
|
||||||
err = withRecordStreams(t, "", func(rio recordIO) error {
|
err = withRecordStreams(t, "", func(rio recordIO) error {
|
||||||
return runRecordSession(t.TempDir(), filepath.Join(t.TempDir(), "raw.in"), filepath.Join(t.TempDir(), "raw.out"), shellPath, sandbox, rio)
|
return runRecordSession(t.TempDir(), filepath.Join(t.TempDir(), "raw.in"), filepath.Join(t.TempDir(), "raw.out"), shellPath, sandbox, rio, defaultSandboxConfig())
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("runRecordSession() error = %v", err)
|
t.Fatalf("runRecordSession() error = %v", err)
|
||||||
@@ -230,17 +268,21 @@ func TestRunRecordSessionUsesSandboxedScriptCommand(t *testing.T) {
|
|||||||
|
|
||||||
body := readFile(t, commandBodyPath)
|
body := readFile(t, commandBodyPath)
|
||||||
for _, want := range []string{
|
for _, want := range []string{
|
||||||
"host_home=${MIRO_RECORD_HOST_HOME:?}",
|
"host_home=${MIRO_HOST_HOME:?}",
|
||||||
|
"visible_home=${MIRO_VISIBLE_HOME:?}",
|
||||||
"--ro-bind / /",
|
"--ro-bind / /",
|
||||||
"--tmpfs /home",
|
"--tmpfs /home",
|
||||||
"--setenv HOME " + shQuote(recordVisibleHome),
|
"--setenv HOME \"$visible_home\"",
|
||||||
"--setenv TMPDIR '/tmp'",
|
"--setenv TMPDIR '/tmp'",
|
||||||
"bash --noprofile --norc -i",
|
"exec bwrap \"$@\" bash --noprofile --norc -i",
|
||||||
} {
|
} {
|
||||||
if !strings.Contains(body, want) {
|
if !strings.Contains(body, want) {
|
||||||
t.Fatalf("wrapper = %q, want substring %q", body, want)
|
t.Fatalf("wrapper = %q, want substring %q", body, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.Contains(body, "MIRO_SANDBOX_ENVS") {
|
||||||
|
t.Fatalf("wrapper = %q, want no MIRO_SANDBOX_ENVS reference", body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
|
func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
|
||||||
@@ -251,6 +293,11 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
|
|||||||
target := filepath.Join(testDir, "suite", "spec")
|
target := filepath.Join(testDir, "suite", "spec")
|
||||||
mustMkdirAll(t, target)
|
mustMkdirAll(t, target)
|
||||||
mustWriteRecordShell(t, testDir)
|
mustWriteRecordShell(t, testDir)
|
||||||
|
sandboxConfig := map[string]string{
|
||||||
|
"visible_home": "/home/test",
|
||||||
|
"key_word": "value",
|
||||||
|
}
|
||||||
|
visibleHome := sandboxConfig["visible_home"]
|
||||||
|
|
||||||
reader, writer, err := os.Pipe()
|
reader, writer, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -267,7 +314,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
|
|||||||
defer close(writeDone)
|
defer close(writeDone)
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
|
|
||||||
if _, err := writer.Write([]byte("pwd\necho \"$HOME\"\nif [ -e /home/test/repo ]; then echo FOUND; else echo MISSING; fi\npwd\nexit\n")); err != nil {
|
if _, err := writer.Write([]byte("pwd\necho \"$HOME\"\necho \"$MIRO_KEY_WORD\"\nif [ -e \"$HOME/repo\" ]; then echo FOUND; else echo MISSING; fi\npwd\nexit\n")); err != nil {
|
||||||
writeDone <- err
|
writeDone <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -287,7 +334,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
|
|||||||
in: reader,
|
in: reader,
|
||||||
out: ioDiscard{},
|
out: ioDiscard{},
|
||||||
err: &bytes.Buffer{},
|
err: &bytes.Buffer{},
|
||||||
})
|
}, sandboxConfig)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("recordScenario() error = %v", err)
|
t.Fatalf("recordScenario() error = %v", err)
|
||||||
@@ -300,7 +347,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
for _, want := range []string{"pwd\n", "echo \"$HOME\"\n", "if [ -e /home/test/repo ]; then echo FOUND; else echo MISSING; fi\n", "exit\n"} {
|
for _, want := range []string{"pwd\n", "echo \"$HOME\"\n", "echo \"$MIRO_KEY_WORD\"\n", "if [ -e \"$HOME/repo\" ]; then echo FOUND; else echo MISSING; fi\n", "exit\n"} {
|
||||||
if !strings.Contains(recordedIn, want) {
|
if !strings.Contains(recordedIn, want) {
|
||||||
t.Fatalf("saved in = %q, want substring %q", recordedIn, want)
|
t.Fatalf("saved in = %q, want substring %q", recordedIn, want)
|
||||||
}
|
}
|
||||||
@@ -310,7 +357,7 @@ func TestRecordScenarioUsesDeterministicSandbox(t *testing.T) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
for _, want := range []string{recordVisibleHome} {
|
for _, want := range []string{visibleHome, "value"} {
|
||||||
if !strings.Contains(recordedOut, want) {
|
if !strings.Contains(recordedOut, want) {
|
||||||
t.Fatalf("saved out = %q, want substring %q", recordedOut, want)
|
t.Fatalf("saved out = %q, want substring %q", recordedOut, want)
|
||||||
}
|
}
|
||||||
@@ -326,7 +373,7 @@ 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"), "[miro]\ntest_dir = \"e2e\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
|
||||||
mustMkdirAll(t, testDir)
|
mustMkdirAll(t, testDir)
|
||||||
addFakeRecordDependencies(t, "script")
|
addFakeRecordDependencies(t, "script")
|
||||||
|
|
||||||
@@ -449,3 +496,19 @@ func mustWriteRecordShell(t *testing.T, testDir string) {
|
|||||||
t.Fatalf("writeRecordShell(%q) error = %v", testDir, err)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,12 +22,20 @@ func ResolveTestDir() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resolveTestDirFromRoot(root string) (string, error) {
|
func resolveTestDirFromRoot(root string) (string, error) {
|
||||||
configPath := filepath.Join(root, "miro.toml")
|
cfg, err := readConfigFromRoot(root)
|
||||||
cfg, err := miroconfig.ReadConfig(configPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return resolveTestDirFromConfig(root, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfigFromRoot(root string) (miroconfig.Config, error) {
|
||||||
|
configPath := filepath.Join(root, "miro.toml")
|
||||||
|
return miroconfig.ReadConfig(configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveTestDirFromConfig(root string, cfg miroconfig.Config) (string, error) {
|
||||||
testDir := filepath.Join(root, cfg.TestDir)
|
testDir := filepath.Join(root, cfg.TestDir)
|
||||||
info, err := os.Stat(testDir)
|
info, err := os.Stat(testDir)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ func TestInitCreatesConfigAtProjectRoot(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
got := readFile(t, filepath.Join(root, "miro.toml"))
|
got := readFile(t, filepath.Join(root, "miro.toml"))
|
||||||
if got != "[miro]\n test_dir = \"e2e\"\n" {
|
if got != defaultWrittenConfig("e2e") {
|
||||||
t.Fatalf("config = %q, want %q", got, "[miro]\n test_dir = \"e2e\"\n")
|
t.Fatalf("config = %q, want %q", got, defaultWrittenConfig("e2e"))
|
||||||
}
|
}
|
||||||
assertRecordShell(t, filepath.Join(root, "e2e", recordShellName))
|
assertRecordShell(t, filepath.Join(root, "e2e", recordShellName))
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ func TestInitUsesGitRoot(t *testing.T) {
|
|||||||
|
|
||||||
func TestInitLeavesExistingValidConfigUntouched(t *testing.T) {
|
func TestInitLeavesExistingValidConfigUntouched(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"custom/suite\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("custom/suite"))
|
||||||
writeFile(t, filepath.Join(root, "custom", "suite", recordShellName), "outdated\n")
|
writeFile(t, filepath.Join(root, "custom", "suite", recordShellName), "outdated\n")
|
||||||
|
|
||||||
withWorkingDir(t, root, func() struct{} {
|
withWorkingDir(t, root, func() struct{} {
|
||||||
@@ -57,15 +57,17 @@ func TestInitLeavesExistingValidConfigUntouched(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
got := readFile(t, filepath.Join(root, "miro.toml"))
|
got := readFile(t, filepath.Join(root, "miro.toml"))
|
||||||
if got != "[miro]\ntest_dir = \"custom/suite\"\n" {
|
if got != validConfigContent("custom/suite") {
|
||||||
t.Fatalf("config = %q, want %q", got, "[miro]\ntest_dir = \"custom/suite\"\n")
|
t.Fatalf("config = %q, want %q", got, validConfigContent("custom/suite"))
|
||||||
|
}
|
||||||
|
if got := readFile(t, filepath.Join(root, "custom", "suite", recordShellName)); got != "outdated\n" {
|
||||||
|
t.Fatalf("shell = %q, want existing shell to stay untouched", got)
|
||||||
}
|
}
|
||||||
assertRecordShell(t, filepath.Join(root, "custom", "suite", recordShellName))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInitCreatesMissingConfiguredTestDir(t *testing.T) {
|
func TestInitCreatesMissingConfiguredTestDir(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"custom/suite\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("custom/suite"))
|
||||||
|
|
||||||
withWorkingDir(t, root, func() struct{} {
|
withWorkingDir(t, root, func() struct{} {
|
||||||
if err := Init(); err != nil {
|
if err := Init(); err != nil {
|
||||||
@@ -96,7 +98,7 @@ 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)
|
mustMkdirAll(t, configured)
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"custom/suite\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("custom/suite"))
|
||||||
|
|
||||||
got := withWorkingDir(t, root, func() string {
|
got := withWorkingDir(t, root, func() string {
|
||||||
path, err := ResolveTestDir()
|
path, err := ResolveTestDir()
|
||||||
@@ -181,7 +183,7 @@ 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"), "[miro]\ntest_dir = \"missing\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("missing"))
|
||||||
|
|
||||||
got := withWorkingDir(t, root, func() string {
|
got := withWorkingDir(t, root, func() string {
|
||||||
path, err := ResolveTestDir()
|
path, err := ResolveTestDir()
|
||||||
@@ -199,7 +201,7 @@ 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")
|
writeFile(t, filepath.Join(root, "case.txt"), "hello\n")
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"case.txt\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("case.txt"))
|
||||||
|
|
||||||
err := withWorkingDir(t, root, func() error {
|
err := withWorkingDir(t, root, func() error {
|
||||||
_, err := ResolveTestDir()
|
_, err := ResolveTestDir()
|
||||||
@@ -218,7 +220,7 @@ func TestResolveTestDirUsesGitRoot(t *testing.T) {
|
|||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
mustGitInit(t, root)
|
mustGitInit(t, root)
|
||||||
want := filepath.Join(root, "e2e")
|
want := filepath.Join(root, "e2e")
|
||||||
writeFile(t, filepath.Join(root, "miro.toml"), "[miro]\ntest_dir = \"e2e\"\n")
|
writeFile(t, filepath.Join(root, "miro.toml"), validConfigContent("e2e"))
|
||||||
subdir := filepath.Join(root, "nested", "dir")
|
subdir := filepath.Join(root, "nested", "dir")
|
||||||
mustMkdirAll(t, subdir)
|
mustMkdirAll(t, subdir)
|
||||||
|
|
||||||
@@ -297,3 +299,11 @@ func assertRecordShell(t *testing.T, path string) {
|
|||||||
t.Fatalf("shell = %q, want generated recorder shell", got)
|
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"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user