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  }