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>
89 lines
2.6 KiB
Go
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))
|
|
}
|
|
}
|