Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions main_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"strings"
"sync"
"testing"
"time"

"golang.org/x/net/dns/dnsmessage"

Expand All @@ -33,6 +32,39 @@ func runCLI(t *testing.T, stdin string, args ...string) (int, string) {
return code, stdout.String()
}

// runCLICapture is runCLI with a caller-supplied stdout writer.
func runCLICapture(t *testing.T, stdout io.Writer, args ...string) int {
t.Helper()
return runWith(runArgs{args: args, stdin: strings.NewReader(""), stdout: stdout, stderr: io.Discard})
}

// interruptWriter captures stdout and calls fire once, after the at-th line.
// Tests interrupt on a result event instead of a wall-clock sleep, so a slow
// runner cannot finish the scan before the interrupt lands (#260).
type interruptWriter struct {
buf bytes.Buffer
at int
lines int
fire func()
}

func newInterruptWriter(at int, fire func()) *interruptWriter {
return &interruptWriter{at: at, fire: fire}
}

func (w *interruptWriter) Write(p []byte) (int, error) {
n, err := w.buf.Write(p)
w.lines += bytes.Count(p, []byte("\n"))
if w.lines >= w.at && w.fire != nil {
fire := w.fire
w.fire = nil
fire()
}
return n, err
}

func (w *interruptWriter) String() string { return w.buf.String() }

// lockedBuffer is a bytes.Buffer safe for concurrent writers, standing in
// for one pipe that both stdout and stderr write to.
type lockedBuffer struct {
Expand Down Expand Up @@ -842,16 +874,15 @@ func TestE2EResume(t *testing.T) {
t.Fatalf("reference run found only %d names", len(want))
}

// Interrupted run: -rate 20 makes 40 names take ~2s; interrupt at ~1s.
// Interrupted run: -rate 20 makes 40 names take ~2s. Interrupt after the
// first few result lines on stdout, so the interrupt cannot miss a scan
// that a slow runner has already finished (#260).
ready := make(chan struct{})
signalReady, testSignals = ready, make(chan os.Signal, 1)
t.Cleanup(func() { signalReady, testSignals = nil, nil })
go func() {
<-ready
time.Sleep(time.Second)
testSignals <- os.Interrupt
}()
code, first := runCLI(t, "", append(append([]string{}, base...), "-rate", "20", "-state", statePath, "-o", outPath, "example.com")...)
stdout := newInterruptWriter(3, func() { testSignals <- os.Interrupt })
code := runCLICapture(t, stdout, append(append([]string{}, base...), "-rate", "20", "-state", statePath, "-o", outPath, "example.com")...)
first := stdout.String()
signalReady, testSignals = nil, nil
if code != 130 {
t.Fatalf("interrupted run: exit %d, want 130", code)
Expand Down
16 changes: 4 additions & 12 deletions main_signal_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strings"
"syscall"
"testing"
"time"
)

// TestE2ESIGTERMExits143 covers #82: SIGTERM (container stop, systemd) exits
Expand All @@ -19,17 +18,10 @@ func TestE2ESIGTERMExits143(t *testing.T) {
}
wl := writeFile(t, "wl.txt", words.String())

ready := make(chan struct{})
signalReady = ready
t.Cleanup(func() { signalReady = nil })
go func() {
<-ready
time.Sleep(200 * time.Millisecond) // let the scan start
_ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
}()

// 200 names at -rate 10 would take 20s; the signal ends it early.
code, _ := runCLI(t, "", "-simulate", "-rate", "10", "-progress=false", "-w", wl, "example.com")
// 200 names at -rate 10 would take 20s; SIGTERM after the first result
// line ends it early, without a wall-clock race (#260).
stdout := newInterruptWriter(1, func() { _ = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) })
code := runCLICapture(t, stdout, "-simulate", "-rate", "10", "-progress=false", "-w", wl, "example.com")
if code != 143 {
t.Fatalf("exit %d after SIGTERM, want 143", code)
}
Expand Down