--cmd exits 0 (+ optional --matches regex) or --timeout; exit 0/3/1, last output always printed, --then hook composes with notifyr. Injectable runner for tests + real-shell integration test. Spec: doc/tool-parity.md 3.2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011KikHkfCiC3yELbsMN8fT9
93 lines
2.2 KiB
Go
93 lines
2.2 KiB
Go
package wait
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestMetOnExitZero(t *testing.T) {
|
|
calls := 0
|
|
run := func(cmd string) (string, int) {
|
|
calls++
|
|
if calls < 3 {
|
|
return "not ready", 1
|
|
}
|
|
return "ready", 0
|
|
}
|
|
res, err := Wait(Options{Cmd: "x", Interval: time.Millisecond, Timeout: time.Second}, run, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !res.Met || res.Attempts != 3 || res.LastOut != "ready" {
|
|
t.Errorf("unexpected result: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestMatchesRequiredOnTopOfExitZero(t *testing.T) {
|
|
calls := 0
|
|
run := func(cmd string) (string, int) {
|
|
calls++
|
|
if calls == 1 {
|
|
return "gitea starting", 0 // exit 0 but no match yet
|
|
}
|
|
return "gitea healthy", 0
|
|
}
|
|
res, err := Wait(Options{Cmd: "x", Matches: "healthy", Interval: time.Millisecond, Timeout: time.Second}, run, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !res.Met || res.Attempts != 2 {
|
|
t.Errorf("match should gate success: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestTimeoutReportsLastOutput(t *testing.T) {
|
|
run := func(cmd string) (string, int) { return "still broken", 7 }
|
|
res, err := Wait(Options{Cmd: "x", Interval: 5 * time.Millisecond, Timeout: 20 * time.Millisecond}, run, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Met {
|
|
t.Error("should have timed out")
|
|
}
|
|
if res.LastOut != "still broken" || res.LastExit != 7 {
|
|
t.Errorf("last output lost: %+v", res)
|
|
}
|
|
if res.Attempts < 1 {
|
|
t.Error("should have tried at least once")
|
|
}
|
|
}
|
|
|
|
func TestBadRegexRejected(t *testing.T) {
|
|
if _, err := Wait(Options{Cmd: "x", Matches: "("}, nil, nil); err == nil {
|
|
t.Error("bad regex accepted")
|
|
}
|
|
}
|
|
|
|
func TestEmptyCommandRejected(t *testing.T) {
|
|
if _, err := Wait(Options{}, nil, nil); err == nil {
|
|
t.Error("empty command accepted")
|
|
}
|
|
}
|
|
|
|
// Integration: real shell, waiting for a file to appear (the exact
|
|
// case from the agy live test in doc/tool-parity.md).
|
|
func TestShellRunnerFileAppears(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "flag")
|
|
go func() {
|
|
time.Sleep(30 * time.Millisecond)
|
|
os.WriteFile(path, []byte("x"), 0o644)
|
|
}()
|
|
res, err := Wait(Options{
|
|
Cmd: "test -f " + path, Interval: 10 * time.Millisecond, Timeout: 2 * time.Second,
|
|
}, ShellRunner, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !res.Met {
|
|
t.Errorf("file never seen: %+v", res)
|
|
}
|
|
}
|