-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcancel_test.go
More file actions
105 lines (96 loc) · 3.2 KB
/
Copy pathcancel_test.go
File metadata and controls
105 lines (96 loc) · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package sqlite
import (
"context"
"database/sql"
"errors"
"strings"
"testing"
"time"
)
// TestContext_CancelInterruptsRecursiveCTE asserts that canceling the context
// mid-query actually interrupts SQLite's execution rather than blocking
// until the (potentially astronomical) result set completes.
//
// We use a long recursive CTE that produces 10 million rows; without
// interrupt the QueryContext call would burn CPU for seconds. With the
// driver's sqlite3_interrupt plumbing wired in correctly, canceling after
// 50ms returns an error within ~200ms.
func TestContext_CancelInterruptsRecursiveCTE(t *testing.T) {
db, err := sql.Open(DriverNameSQLite3, ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
ctx, cancel := context.WithCancel(context.Background())
// Cancel 50ms in — long enough for the query to start, short enough
// that 10M-row generation hasn't completed.
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
start := time.Now()
// Recursive CTE that counts up to 10M.
rows, err := db.QueryContext(ctx, `
WITH RECURSIVE c(n) AS (
SELECT 1 UNION ALL SELECT n + 1 FROM c WHERE n < 10000000
)
SELECT count(*) FROM c
`)
elapsed := time.Since(start)
if err == nil {
// Some sqlite versions return rows but with an error on first Next().
var n int
if rows.Next() {
rows.Scan(&n)
}
rows.Close()
// Even if the query "succeeded" we should not have taken longer than
// a couple hundred ms — that would indicate interrupt did nothing.
if elapsed > 500*time.Millisecond {
t.Errorf("query completed without cancellation in %v (interrupt failed?)", elapsed)
}
return
}
// Happy path: error returned. Make sure it surfaces the cancellation,
// not some other failure, and that we didn't burn through the whole
// 10M-row generation.
if elapsed > 500*time.Millisecond {
t.Errorf("cancellation took %v to surface; expected ≤ 500ms", elapsed)
}
// The error should be related to interruption. The driver may surface
// context.Canceled, context.DeadlineExceeded, or an interrupt-flavored
// SQLite error string depending on timing. Match on any of those.
msg := strings.ToLower(err.Error())
if !errors.Is(err, context.Canceled) &&
!errors.Is(err, context.DeadlineExceeded) &&
!strings.Contains(msg, "interrupt") &&
!strings.Contains(msg, "canceled") {
t.Errorf("unexpected error after cancellation: %v", err)
}
}
// TestContext_DeadlineExceeded is the equivalent test using a context
// deadline instead of explicit cancellation. Same expectations: error
// within ~200ms, related to the deadline.
func TestContext_DeadlineExceeded(t *testing.T) {
db, err := sql.Open(DriverNameSQLite3, ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
start := time.Now()
_, err = db.QueryContext(ctx, `
WITH RECURSIVE c(n) AS (
SELECT 1 UNION ALL SELECT n + 1 FROM c WHERE n < 10000000
)
SELECT count(*) FROM c
`)
elapsed := time.Since(start)
if err == nil {
t.Fatalf("expected error from deadline; query finished in %v", elapsed)
}
if elapsed > 500*time.Millisecond {
t.Errorf("deadline took %v to surface; expected ≤ 500ms", elapsed)
}
}