github.com/tailscale/sqlite@v0.0.0-20240515181108-c667cbe57c66/sqlstats/sqlstats_test.go (about) 1 package sqlstats 2 3 import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "slices" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/tailscale/sqlite" 16 ) 17 18 func TestActiveTxs(t *testing.T) { 19 tracer := &Tracer{} 20 db := sql.OpenDB(sqlite.Connector("file:"+t.TempDir()+"/test.db", nil, tracer)) 21 defer db.Close() 22 23 ctx := context.Background() 24 tx, err := db.BeginTx(ctx, nil) 25 if err != nil { 26 t.Fatal(err) 27 } 28 defer tx.Rollback() 29 if _, err := tx.ExecContext(ctx, "CREATE TABLE t (c);"); err != nil { 30 t.Fatal(err) 31 } 32 if _, err := tx.ExecContext(ctx, "INSERT INTO t (c) VALUES (1);"); err != nil { 33 t.Fatal(err) 34 } 35 if err := tx.Commit(); err != nil { 36 t.Fatal(err) 37 } 38 39 srv := httptest.NewServer(http.HandlerFunc(tracer.Handle)) 40 defer srv.Close() 41 resp, err := srv.Client().Get(srv.URL) 42 if err != nil { 43 t.Fatal(err) 44 } 45 defer resp.Body.Close() 46 b, err := io.ReadAll(resp.Body) 47 if err != nil { 48 t.Fatal(err) 49 } 50 s := string(b) 51 if want := "CREATE TABLE t "; !strings.Contains(s, want) { 52 t.Fatalf("want %q, got:\n%s", want, s) 53 } 54 if want := "INSERT INTO t (c)"; !strings.Contains(s, want) { 55 t.Fatalf("want %q, got:\n%s", want, s) 56 } 57 } 58 59 func TestNormalizeQuery(t *testing.T) { 60 tests := []struct { 61 q, want string 62 }{ 63 {"", ""}, 64 {"SELECT 1", "SELECT 1"}, 65 {"DELETE FROM foo.Bar WHERE UnixNano in (SELECT id from FOO)", "DELETE FROM foo.Bar WHERE UnixNano in (SELECT id from FOO)"}, 66 {"DELETE FROM foo.Bar WHERE UnixNano in (1)", "DELETE FROM foo.Bar WHERE UnixNano IN (...)"}, 67 {"DELETE FROM foo.Bar WHERE UnixNano in (1, 2, 3)", "DELETE FROM foo.Bar WHERE UnixNano IN (...)"}, 68 {"DELETE FROM foo.Bar WHERE UnixNano in (1,2,3)", "DELETE FROM foo.Bar WHERE UnixNano IN (...)"}, 69 {"DELETE FROM foo.Bar WHERE UnixNano in (1,2,3 )", "DELETE FROM foo.Bar WHERE UnixNano IN (...)"}, 70 {"DELETE FROM foo.Bar WHERE UnixNano in ( 1 , 2 , 3 )", "DELETE FROM foo.Bar WHERE UnixNano IN (...)"}, 71 } 72 for _, tt := range tests { 73 if got := normalizeQuery(tt.q); got != tt.want { 74 t.Errorf("normalizeQuery(%#q) = %#q; want %#q", tt.q, got, tt.want) 75 } 76 } 77 } 78 79 func TestCollect(t *testing.T) { 80 tracer := &Tracer{} 81 db := sql.OpenDB(sqlite.Connector("file:"+t.TempDir()+"/test.db", nil, tracer)) 82 defer db.Close() 83 84 ctx := context.Background() 85 tx, err := db.BeginTx(ctx, nil) 86 if err != nil { 87 t.Fatal(err) 88 } 89 defer tx.Rollback() 90 if _, err := tx.ExecContext(ctx, "CREATE TABLE t (c);"); err != nil { 91 t.Fatal(err) 92 } 93 if _, err := tx.ExecContext(ctx, "INSERT INTO t (c) VALUES (1);"); err != nil { 94 t.Fatal(err) 95 } 96 if _, err := tx.ExecContext(ctx, "INSERT INTO t (c) VALUES (1);"); err != nil { 97 t.Fatal(err) 98 } 99 if err := tx.Commit(); err != nil { 100 t.Fatal(err) 101 } 102 103 gotStats := tracer.Collect() 104 slices.SortFunc(gotStats, func(a, b *QueryStats) int { 105 if a.Query < b.Query { 106 return -1 107 } 108 return 1 109 }) 110 111 // List containing expecting query counts in order. 112 wantCount := []int{1, 1, 1, 2} 113 114 if len(gotStats) != len(wantCount) { 115 t.Errorf("unexpected number of queries compared to defined count, queries: %d, want: %d", len(gotStats), len(wantCount)) 116 } 117 118 for idx, query := range gotStats { 119 if query.Count != int64(wantCount[idx]) { 120 t.Errorf("unexpected query count for %q, got: %d, expected: %d", query.Query, query.Count, wantCount[idx]) 121 } 122 } 123 } 124 125 func TestTracerResetRace(t *testing.T) { 126 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 127 defer cancel() 128 129 tracer := &Tracer{} 130 db := sql.OpenDB(sqlite.Connector("file:"+t.TempDir()+"/test.db", nil, tracer)) 131 defer db.Close() 132 133 tx, err := db.BeginTx(ctx, nil) 134 if err != nil { 135 t.Fatal(err) 136 } 137 defer tx.Rollback() 138 if _, err := tx.ExecContext(ctx, "CREATE TABLE t (c);"); err != nil { 139 t.Fatal(err) 140 } 141 if err := tx.Commit(); err != nil { 142 t.Fatal(err) 143 } 144 145 // Continually reset the tracer. 146 go func() { 147 for ctx.Err() == nil { 148 tracer.Reset() 149 } 150 }() 151 152 // Continually grab transactions and insert into the database. 153 errc := make(chan error) 154 go func() { 155 defer close(errc) 156 var n int64 157 for ctx.Err() == nil { 158 n++ 159 tx, err = db.BeginTx(ctx, nil) 160 if err != nil { 161 errc <- fmt.Errorf("starting tx: %w", err) 162 return 163 } 164 if _, err = tx.Exec("INSERT INTO t VALUES(?)", n); err != nil { 165 errc <- fmt.Errorf("insert: %w", err) 166 return 167 } 168 if n%2 == 0 { 169 if err := tx.Commit(); err != nil { 170 errc <- fmt.Errorf("commit: %w", err) 171 return 172 } 173 } 174 tx.Rollback() 175 } 176 }() 177 178 for err := range errc { 179 if ctx.Err() != nil { 180 return 181 } 182 t.Fatalf("unexpected error: %s", err) 183 } 184 }