summaryrefslogtreecommitdiff
path: root/integrationtests/dtail_timeout_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'integrationtests/dtail_timeout_test.go')
-rw-r--r--integrationtests/dtail_timeout_test.go136
1 files changed, 136 insertions, 0 deletions
diff --git a/integrationtests/dtail_timeout_test.go b/integrationtests/dtail_timeout_test.go
new file mode 100644
index 0000000..2f656ff
--- /dev/null
+++ b/integrationtests/dtail_timeout_test.go
@@ -0,0 +1,136 @@
+package integrationtests
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+ "time"
+
+ "github.com/mimecast/dtail/internal/config"
+)
+
+// TestDTailTimeoutExits guards the client-side --timeout deadline for the dtail
+// follow client (task xu0). Historically the server-side read deadline fired at
+// N seconds, but in tail+query streaming mode the session stayed alive via the
+// map/aggregate command, so the client treated the closed read as a transient
+// drop and auto-reconnected for another N-second cycle indefinitely - the
+// process never exited. Plain "dtail --timeout N" (no --query) never emitted the
+// timeout at all. Both are now covered by a client-side context deadline in
+// cmd/dtail/main.go: whichever of --timeout / --shutdownAfter elapses first
+// cancels the client context, so client.Start returns and the process exits.
+//
+// The assertion is that dtail exits well within a generous guard window. Without
+// the fix the reconnect loop keeps the process alive past the guard, failing the
+// test.
+func TestDTailTimeoutExits(t *testing.T) {
+ testLogger := NewTestLogger("TestDTailTimeoutExits")
+ defer testLogger.WriteLogFile()
+ cleanupTmpFiles(t)
+
+ if !config.Env("DTAIL_INTEGRATION_TEST_RUN_MODE") {
+ t.Log("Skipping")
+ return
+ }
+
+ t.Run("QueryTimeoutExits", func(t *testing.T) {
+ runDTailTimeoutCase(t, testLogger, "dtail.timeout.query.tmp",
+ []string{"--query", "select count($line) as cnt from STATS"})
+ })
+ t.Run("PlainTimeoutExits", func(t *testing.T) {
+ runDTailTimeoutCase(t, testLogger, "dtail.timeout.plain.tmp", nil)
+ })
+}
+
+// runDTailTimeoutCase starts a dserver, follows a growing file with
+// "dtail --timeout <timeoutSeconds>" (plus any extra args), and asserts the
+// client process exits within guardSeconds. The timeout is short relative to the
+// guard so a regression (reconnect-after-timeout hang or a silently ignored
+// --timeout) is caught by the guard firing first.
+func runDTailTimeoutCase(t *testing.T, testLogger *TestLogger, followFile string, extraArgs []string) {
+ const (
+ timeoutSeconds = 3
+ guardSeconds = 20
+ )
+
+ port := getUniquePortNumber()
+ bindAddress := "localhost"
+
+ ctx, cancel := context.WithCancel(context.Background())
+ ctx = WithTestLogger(ctx, testLogger)
+ defer cancel()
+
+ if err := startTestServer(ctx, t, &ServerConfig{
+ Port: port,
+ BindAddress: bindAddress,
+ LogLevel: "error",
+ }); err != nil {
+ t.Fatalf("unable to start dserver: %v", err)
+ }
+
+ // Keep the file growing so the follow stays active (and would keep
+ // reconnecting without the client-side deadline) until the timeout fires.
+ fd, err := os.Create(followFile)
+ if err != nil {
+ t.Fatalf("unable to create follow file: %v", err)
+ }
+ defer func() {
+ _ = fd.Close()
+ _ = os.Remove(followFile)
+ }()
+ go func() {
+ for i := 0; ; i++ {
+ select {
+ case <-time.After(200 * time.Millisecond):
+ _, _ = fd.WriteString(fmt.Sprintf("%s Hello line %d\n", time.Now(), i))
+ case <-ctx.Done():
+ return
+ }
+ }
+ }()
+
+ args := []string{
+ "--cfg", "none",
+ "--logger", "stdout",
+ "--logLevel", "error",
+ "--servers", fmt.Sprintf("%s:%d", bindAddress, port),
+ "--files", followFile,
+ "--timeout", fmt.Sprintf("%d", timeoutSeconds),
+ "--trustAllHosts",
+ "--noColor",
+ }
+ args = append(args, extraArgs...)
+
+ start := time.Now()
+ stdoutCh, stderrCh, cmdErrCh, err := startCommand(ctx, t, "", "../dtail", args...)
+ if err != nil {
+ t.Fatalf("unable to start dtail: %v", err)
+ }
+
+ guard := time.NewTimer(guardSeconds * time.Second)
+ defer guard.Stop()
+
+ for {
+ select {
+ case line, ok := <-stdoutCh:
+ if ok {
+ t.Log("client stdout:", line)
+ }
+ case line, ok := <-stderrCh:
+ if ok {
+ t.Log("client stderr:", line)
+ }
+ case cmdErr := <-cmdErrCh:
+ elapsed := time.Since(start)
+ t.Logf("dtail exited after %s (err=%v)", elapsed, cmdErr)
+ if elapsed > guardSeconds*time.Second {
+ t.Fatalf("dtail exited after %s, expected well within %ds", elapsed, guardSeconds)
+ }
+ return
+ case <-guard.C:
+ t.Fatalf("dtail did not exit within %ds after --timeout %ds; "+
+ "likely reconnecting after the timeout-induced disconnect",
+ guardSeconds, timeoutSeconds)
+ }
+ }
+}