From 11a83d034b7b7328fc4c8c914fadd10ac93c9c91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:48:13 +0000 Subject: [PATCH 1/5] Initial plan From e227587cae2d68bae1fbcb90627161c6bff5ab54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:52:16 +0000 Subject: [PATCH 2/5] Add defer rows.Close() to fix timeout hang on Linux Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com> --- pkg/sqlcmd/sqlcmd.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/sqlcmd/sqlcmd.go b/pkg/sqlcmd/sqlcmd.go index 5e572a94..ba6a74e4 100644 --- a/pkg/sqlcmd/sqlcmd.go +++ b/pkg/sqlcmd/sqlcmd.go @@ -431,6 +431,9 @@ func (s *Sqlcmd) runQuery(query string) (int, error) { } retmsg := &sqlexp.ReturnMessage{} rows, qe := s.db.QueryContext(ctx, query, retmsg) + if rows != nil { + defer rows.Close() + } if qe != nil { s.Format.AddError(qe) } From 2245e0d81e5b1aa52962b183c2aa27864ff6eaee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:45:13 +0000 Subject: [PATCH 3/5] Add e2e test for query timeout to verify fix Co-authored-by: shueybubbles <2224906+shueybubbles@users.noreply.github.com> --- cmd/modern/e2e_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/cmd/modern/e2e_test.go b/cmd/modern/e2e_test.go index 9c3d14e6..67385fdf 100644 --- a/cmd/modern/e2e_test.go +++ b/cmd/modern/e2e_test.go @@ -312,6 +312,32 @@ func TestE2E_PipedInput_WithBytesBuffer_NoPanic(t *testing.T) { assert.NotContains(t, outputStr, "nil pointer", "should not have nil pointer error") } +// TestE2E_QueryTimeout_LiveConnection tests that the -t flag properly times out queries +// and doesn't hang for 10 minutes on Linux. This is a regression test for the issue +// where sqlcmd would print "Timeout expired" but then hang for ~10 minutes before exiting. +func TestE2E_QueryTimeout_LiveConnection(t *testing.T) { + skipIfNoLiveConnection(t) + binary := buildBinary(t) + + // Use a 1 second timeout with a query that would take 10 seconds + args := append([]string{"-C", "-t", "1", "-Q", "WAITFOR DELAY '00:00:10'"}, getAuthArgs(t)...) + cmd := exec.Command(binary, args...) + cmd.Env = os.Environ() + + output, err := cmd.CombinedOutput() + outputStr := string(output) + + // The command should fail due to timeout + assert.Error(t, err, "query should timeout and return an error") + + // Output should contain timeout message + assert.Contains(t, outputStr, "Timeout expired", "output should contain 'Timeout expired' message") + + // The key validation: the test itself should complete quickly (not hang for 10 minutes) + // If this test completes in a reasonable time, the fix is working + t.Logf("Query timed out correctly: %s", outputStr) +} + // cleanupBinary removes the temporary build directory containing the test binary. // TestMain calls this to ensure deterministic cleanup instead of relying on // eventual OS temp directory maintenance. From 5219e2afa4e66d1e267bc11242d26f25844b3e48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:05:50 +0000 Subject: [PATCH 4/5] Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com> --- cmd/modern/e2e_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/modern/e2e_test.go b/cmd/modern/e2e_test.go index 67385fdf..1adb8469 100644 --- a/cmd/modern/e2e_test.go +++ b/cmd/modern/e2e_test.go @@ -329,10 +329,10 @@ func TestE2E_QueryTimeout_LiveConnection(t *testing.T) { // The command should fail due to timeout assert.Error(t, err, "query should timeout and return an error") - + // Output should contain timeout message assert.Contains(t, outputStr, "Timeout expired", "output should contain 'Timeout expired' message") - + // The key validation: the test itself should complete quickly (not hang for 10 minutes) // If this test completes in a reasonable time, the fix is working t.Logf("Query timed out correctly: %s", outputStr) From 44668cdab31bdbe65c749168160c91572b8b36b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:08:26 +0000 Subject: [PATCH 5/5] Add timing measurement to e2e timeout test to verify quick exit Co-authored-by: dlevy-msft-sql <194277063+dlevy-msft-sql@users.noreply.github.com> --- cmd/modern/e2e_test.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cmd/modern/e2e_test.go b/cmd/modern/e2e_test.go index 1adb8469..75c07e34 100644 --- a/cmd/modern/e2e_test.go +++ b/cmd/modern/e2e_test.go @@ -12,6 +12,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -324,7 +325,11 @@ func TestE2E_QueryTimeout_LiveConnection(t *testing.T) { cmd := exec.Command(binary, args...) cmd.Env = os.Environ() + // Measure execution time - this is the key validation + // The command should complete in a few seconds, not 10 minutes + start := time.Now() output, err := cmd.CombinedOutput() + elapsed := time.Since(start) outputStr := string(output) // The command should fail due to timeout @@ -333,9 +338,14 @@ func TestE2E_QueryTimeout_LiveConnection(t *testing.T) { // Output should contain timeout message assert.Contains(t, outputStr, "Timeout expired", "output should contain 'Timeout expired' message") - // The key validation: the test itself should complete quickly (not hang for 10 minutes) - // If this test completes in a reasonable time, the fix is working - t.Logf("Query timed out correctly: %s", outputStr) + // Critical: verify the command completed quickly (within 30 seconds, not 10 minutes) + // If the bug exists, this would take ~10 minutes on Linux + maxDuration := 30 * time.Second + if elapsed > maxDuration { + t.Errorf("Command took too long to complete: %v (expected < %v). The timeout hang bug may still exist.", elapsed, maxDuration) + } + + t.Logf("Query timed out correctly in %v: %s", elapsed, outputStr) } // cleanupBinary removes the temporary build directory containing the test binary.