Add subprocess runner and generated config helpers
This commit is contained in:
196
internal/adapters/subprocess/run_test.go
Normal file
196
internal/adapters/subprocess/run_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package subprocess
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestRunSuccessCapturesStdoutStderr(t *testing.T) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("os.Executable() error = %v", err)
|
||||
}
|
||||
|
||||
dir := t.TempDir()
|
||||
stdoutPath := filepath.Join(dir, "stdout.log")
|
||||
stderrPath := filepath.Join(dir, "stderr.log")
|
||||
req := RunRequest{
|
||||
Executable: exe,
|
||||
Args: []string{"-test.run=TestSubprocessHelper", "--", "success"},
|
||||
EnvOverrides: map[string]string{"GO_WANT_SUBPROCESS_HELPER": "1", "SUBPROCESS_HELPER_STDOUT": "hello-out", "SUBPROCESS_HELPER_STDERR": "hello-err"},
|
||||
StdoutLogPath: stdoutPath,
|
||||
StderrLogPath: stderrPath,
|
||||
}
|
||||
|
||||
res, err := Run(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
if res.ExitCode != 0 {
|
||||
t.Fatalf("ExitCode = %d, want 0", res.ExitCode)
|
||||
}
|
||||
|
||||
stdoutBytes, err := os.ReadFile(stdoutPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read stdout log: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(stdoutBytes), "hello-out") {
|
||||
t.Fatalf("stdout log = %q, want hello-out", string(stdoutBytes))
|
||||
}
|
||||
|
||||
stderrBytes, err := os.ReadFile(stderrPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read stderr log: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(stderrBytes), "hello-err") {
|
||||
t.Fatalf("stderr log = %q, want hello-err", string(stderrBytes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFailureReturnsUsefulError(t *testing.T) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("os.Executable() error = %v", err)
|
||||
}
|
||||
|
||||
req := RunRequest{
|
||||
Executable: exe,
|
||||
Args: []string{"-test.run=TestSubprocessHelper", "--", "fail"},
|
||||
EnvOverrides: map[string]string{
|
||||
"GO_WANT_SUBPROCESS_HELPER": "1",
|
||||
},
|
||||
}
|
||||
|
||||
res, err := Run(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatal("Run() error = nil, want non-nil")
|
||||
}
|
||||
if res.ExitCode == 0 {
|
||||
t.Fatalf("ExitCode = %d, want non-zero", res.ExitCode)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "exit code") {
|
||||
t.Fatalf("error = %q, want exit code context", err.Error())
|
||||
}
|
||||
if !strings.Contains(err.Error(), exe) {
|
||||
t.Fatalf("error = %q, want executable context", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunTimeout(t *testing.T) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatalf("os.Executable() error = %v", err)
|
||||
}
|
||||
|
||||
req := RunRequest{
|
||||
Executable: exe,
|
||||
Args: []string{"-test.run=TestSubprocessHelper", "--", "sleep"},
|
||||
EnvOverrides: map[string]string{
|
||||
"GO_WANT_SUBPROCESS_HELPER": "1",
|
||||
},
|
||||
Timeout: 50 * time.Millisecond,
|
||||
}
|
||||
|
||||
res, err := Run(context.Background(), req)
|
||||
if err == nil {
|
||||
t.Fatal("Run() error = nil, want timeout error")
|
||||
}
|
||||
if !res.TimedOut {
|
||||
t.Fatalf("TimedOut = %v, want true", res.TimedOut)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "timed out") {
|
||||
t.Fatalf("error = %q, want timeout context", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteYAMLAtomic(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "config.generated.yml")
|
||||
|
||||
if err := WriteYAMLAtomic(path, map[string]any{"name": "narratio", "stage": "merge"}, 0o644); err != nil {
|
||||
t.Fatalf("WriteYAMLAtomic() error = %v", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read yaml: %v", err)
|
||||
}
|
||||
|
||||
var got map[string]any
|
||||
if err := yaml.Unmarshal(data, &got); err != nil {
|
||||
t.Fatalf("yaml unmarshal: %v", err)
|
||||
}
|
||||
if got["name"] != "narratio" {
|
||||
t.Fatalf("name = %#v, want narratio", got["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteYAMLAtomicOverwriteNoTempResidue(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "config.generated.yml")
|
||||
|
||||
if err := WriteYAMLAtomic(path, map[string]any{"value": "one"}, 0o644); err != nil {
|
||||
t.Fatalf("first write: %v", err)
|
||||
}
|
||||
if err := WriteYAMLAtomic(path, map[string]any{"value": "two"}, 0o644); err != nil {
|
||||
t.Fatalf("second write: %v", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read yaml: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(data), "two") {
|
||||
t.Fatalf("yaml = %q, want overwritten value", string(data))
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadDir() error = %v", err)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if strings.Contains(name, ".tmp-") {
|
||||
t.Fatalf("temp file residue found: %q", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubprocessHelper(t *testing.T) {
|
||||
if os.Getenv("GO_WANT_SUBPROCESS_HELPER") != "1" {
|
||||
return
|
||||
}
|
||||
|
||||
args := os.Args
|
||||
mode := ""
|
||||
for i := range args {
|
||||
if args[i] == "--" && i+1 < len(args) {
|
||||
mode = args[i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if mode == "" {
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "success":
|
||||
_, _ = os.Stdout.WriteString(os.Getenv("SUBPROCESS_HELPER_STDOUT") + "\n")
|
||||
_, _ = os.Stderr.WriteString(os.Getenv("SUBPROCESS_HELPER_STDERR") + "\n")
|
||||
os.Exit(0)
|
||||
case "fail":
|
||||
_, _ = os.Stderr.WriteString("intentional failure\n")
|
||||
os.Exit(3)
|
||||
case "sleep":
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
default:
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user