Files
DDI/agent/role/internal/command/command_test.go
T
sir_melonchenandClaude Opus 5.5 3e957d7b33 feat(agent): command runner with stdin and optional role logger
The DNS role needs to feed nsupdate scripts on stdin and to log its
dynamic updates. command.InputRunner adds RunInput (errors never quote the
input); roles implementing core.LogSetter get the core's logger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 05:47:32 +02:00

89 lines
2.6 KiB
Go

// SPDX-License-Identifier: Apache-2.0
package command
import (
"context"
"errors"
"os/exec"
"strings"
"testing"
"time"
)
func TestMissingProgram(t *testing.T) {
_, err := Exec{Timeout: time.Second}.Run(context.Background(), "ddi-no-such-program-xyz", "-v")
var ce *Error
if !errors.As(err, &ce) || !IsNotFound(err) {
t.Fatalf("err = %v, want *Error wrapping exec.ErrNotFound", err)
}
if !strings.Contains(err.Error(), "ddi-no-such-program-xyz -v") {
t.Errorf("error does not name the command: %v", err)
}
}
func TestFailingProgramQuotesOutput(t *testing.T) {
sh, err := exec.LookPath("sh")
if err != nil {
t.Skip("sh not available")
}
out, err := Exec{Timeout: 10 * time.Second}.Run(context.Background(), sh, "-c", "echo out; echo err >&2; exit 3")
if err == nil {
t.Fatal("expected an error")
}
if !strings.Contains(string(out), "out") || !strings.Contains(string(out), "err") {
t.Errorf("output = %q", out)
}
if !strings.Contains(err.Error(), "exit status 3") || !strings.Contains(err.Error(), "err") {
t.Errorf("error = %v", err)
}
}
func TestRunInputFeedsStdin(t *testing.T) {
sh, err := exec.LookPath("sh")
if err != nil {
t.Skip("sh not available")
}
out, err := Exec{Timeout: 10 * time.Second}.RunInput(context.Background(), []byte("line one\nline two\n"), sh, "-c", "wc -l")
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(string(out)) != "2" {
t.Errorf("output = %q, want 2 lines counted", out)
}
}
func TestTimeout(t *testing.T) {
sh, err := exec.LookPath("sh")
if err != nil {
t.Skip("sh not available")
}
start := time.Now()
_, err = Exec{Timeout: 200 * time.Millisecond}.Run(context.Background(), sh, "-c", "sleep 10")
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("err = %v, want deadline exceeded", err)
}
if time.Since(start) > 8*time.Second {
t.Error("timeout did not stop the program")
}
}
func TestExcerptAndLimit(t *testing.T) {
long := strings.Repeat("a", MaxErrorOutput) + "END"
ex := Excerpt([]byte(long))
if !strings.HasPrefix(ex, "...") || !strings.HasSuffix(ex, "END") || len(ex) != MaxErrorOutput+3 {
t.Errorf("excerpt has length %d, prefix %q", len(ex), ex[:5])
}
var l limitedBuffer
_, _ = l.Write([]byte(strings.Repeat("b", MaxCapture-1)))
_, _ = l.Write([]byte("cc"))
_, _ = l.Write([]byte("dd"))
b := l.Bytes()
// Only the captured part counts: the marker itself contains letters.
kept := string(b[:MaxCapture])
if !strings.HasSuffix(string(b), "\n[output truncated]") || len(b) != MaxCapture+len("\n[output truncated]") ||
strings.Count(kept, "c") != 1 || strings.Contains(kept, "d") {
t.Errorf("limited buffer kept %d bytes", len(b))
}
}