github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/tests/parallel/parallel_test.go (about)

     1  package tests
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"testing"
     9  	"time"
    10  
    11  	"golang.org/x/sync/errgroup"
    12  
    13  	"github.com/ncruces/go-sqlite3"
    14  	_ "github.com/ncruces/go-sqlite3/embed"
    15  	_ "github.com/ncruces/go-sqlite3/tests/testcfg"
    16  	"github.com/ncruces/go-sqlite3/vfs"
    17  	_ "github.com/ncruces/go-sqlite3/vfs/adiantum"
    18  	"github.com/ncruces/go-sqlite3/vfs/memdb"
    19  )
    20  
    21  func Test_parallel(t *testing.T) {
    22  	if !vfs.SupportsFileLocking {
    23  		t.Skip("skipping without locks")
    24  	}
    25  
    26  	var iter int
    27  	if testing.Short() {
    28  		iter = 1000
    29  	} else {
    30  		iter = 5000
    31  	}
    32  
    33  	name := "file:" +
    34  		filepath.Join(t.TempDir(), "test.db") +
    35  		"?_pragma=busy_timeout(10000)" +
    36  		"&_pragma=journal_mode(truncate)" +
    37  		"&_pragma=synchronous(off)"
    38  	testParallel(t, name, iter)
    39  	testIntegrity(t, name)
    40  }
    41  
    42  func Test_wal(t *testing.T) {
    43  	if !vfs.SupportsSharedMemory {
    44  		t.Skip("skipping without shared memory")
    45  	}
    46  
    47  	name := "file:" +
    48  		filepath.Join(t.TempDir(), "test.db") +
    49  		"?_pragma=busy_timeout(10000)" +
    50  		"&_pragma=journal_mode(wal)" +
    51  		"&_pragma=synchronous(off)"
    52  	testParallel(t, name, 1000)
    53  	testIntegrity(t, name)
    54  }
    55  
    56  func Test_memdb(t *testing.T) {
    57  	var iter int
    58  	if testing.Short() {
    59  		iter = 1000
    60  	} else {
    61  		iter = 5000
    62  	}
    63  
    64  	memdb.Delete("test.db")
    65  	memdb.Create("test.db", nil)
    66  	name := "file:/test.db?vfs=memdb"
    67  	testParallel(t, name, iter)
    68  	testIntegrity(t, name)
    69  }
    70  
    71  func Test_adiantum(t *testing.T) {
    72  	if !vfs.SupportsFileLocking {
    73  		t.Skip("skipping without locks")
    74  	}
    75  
    76  	var iter int
    77  	if testing.Short() {
    78  		iter = 1000
    79  	} else {
    80  		iter = 5000
    81  	}
    82  
    83  	name := "file:" +
    84  		filepath.ToSlash(filepath.Join(t.TempDir(), "test.db")) +
    85  		"?vfs=adiantum" +
    86  		"&_pragma=hexkey(e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855)"
    87  	testParallel(t, name, iter)
    88  	testIntegrity(t, name)
    89  }
    90  
    91  func TestMultiProcess(t *testing.T) {
    92  	if !vfs.SupportsFileLocking {
    93  		t.Skip("skipping without locks")
    94  	}
    95  	if testing.Short() {
    96  		t.Skip("skipping in short mode")
    97  	}
    98  
    99  	file := filepath.Join(t.TempDir(), "test.db")
   100  	t.Setenv("TestMultiProcess_dbfile", file)
   101  
   102  	name := "file:" + file +
   103  		"?_pragma=busy_timeout(10000)" +
   104  		"&_pragma=journal_mode(truncate)" +
   105  		"&_pragma=synchronous(off)"
   106  
   107  	cmd := exec.Command(os.Args[0], append(os.Args[1:], "-test.v", "-test.run=TestChildProcess")...)
   108  	out, err := cmd.StdoutPipe()
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	if err := cmd.Start(); err != nil {
   113  		t.Fatal(err)
   114  	}
   115  
   116  	var buf [3]byte
   117  	// Wait for child to start.
   118  	if _, err := io.ReadFull(out, buf[:]); err != nil {
   119  		t.Fatal(err)
   120  	} else if str := string(buf[:]); str != "===" {
   121  		t.Fatal(str)
   122  	}
   123  
   124  	testParallel(t, name, 1000)
   125  	if err := cmd.Wait(); err != nil {
   126  		t.Error(err)
   127  	}
   128  	testIntegrity(t, name)
   129  }
   130  
   131  func TestChildProcess(t *testing.T) {
   132  	file := os.Getenv("TestMultiProcess_dbfile")
   133  	if file == "" || testing.Short() {
   134  		t.SkipNow()
   135  	}
   136  
   137  	name := "file:" + file +
   138  		"?_pragma=busy_timeout(10000)" +
   139  		"&_pragma=journal_mode(truncate)" +
   140  		"&_pragma=synchronous(off)"
   141  
   142  	testParallel(t, name, 1000)
   143  }
   144  
   145  func Benchmark_memdb(b *testing.B) {
   146  	sqlite3.Initialize()
   147  	b.ResetTimer()
   148  
   149  	memdb.Delete("test.db")
   150  	memdb.Create("test.db", nil)
   151  	name := "file:/test.db?vfs=memdb"
   152  	testParallel(b, name, b.N)
   153  }
   154  
   155  func testParallel(t testing.TB, name string, n int) {
   156  	writer := func() error {
   157  		db, err := sqlite3.Open(name)
   158  		if err != nil {
   159  			return err
   160  		}
   161  		defer db.Close()
   162  
   163  		err = db.BusyHandler(func(count int) (retry bool) {
   164  			time.Sleep(time.Millisecond)
   165  			return true
   166  		})
   167  		if err != nil {
   168  			return err
   169  		}
   170  
   171  		err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`)
   172  		if err != nil {
   173  			return err
   174  		}
   175  
   176  		err = db.Exec(`INSERT INTO users (id, name) VALUES (0, 'go'), (1, 'zig'), (2, 'whatever')`)
   177  		if err != nil {
   178  			return err
   179  		}
   180  
   181  		return db.Close()
   182  	}
   183  
   184  	reader := func() error {
   185  		db, err := sqlite3.Open(name)
   186  		if err != nil {
   187  			return err
   188  		}
   189  		defer db.Close()
   190  
   191  		err = db.BusyTimeout(10 * time.Second)
   192  		if err != nil {
   193  			return err
   194  		}
   195  
   196  		stmt, _, err := db.Prepare(`SELECT id, name FROM users`)
   197  		if err != nil {
   198  			return err
   199  		}
   200  		defer stmt.Close()
   201  
   202  		row := 0
   203  		for stmt.Step() {
   204  			row++
   205  		}
   206  		if err := stmt.Err(); err != nil {
   207  			return err
   208  		}
   209  		if row%3 != 0 {
   210  			t.Errorf("got %d rows, want multiple of 3", row)
   211  		}
   212  
   213  		err = stmt.Close()
   214  		if err != nil {
   215  			return err
   216  		}
   217  
   218  		return db.Close()
   219  	}
   220  
   221  	err := writer()
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  
   226  	var group errgroup.Group
   227  	group.SetLimit(6)
   228  	for i := 0; i < n; i++ {
   229  		if i&7 != 7 {
   230  			group.Go(reader)
   231  		} else {
   232  			group.Go(writer)
   233  		}
   234  	}
   235  	err = group.Wait()
   236  	if err != nil {
   237  		t.Error(err)
   238  	}
   239  }
   240  
   241  func testIntegrity(t testing.TB, name string) {
   242  	db, err := sqlite3.Open(name)
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  	defer db.Close()
   247  
   248  	test := `PRAGMA integrity_check`
   249  	if testing.Short() {
   250  		test = `PRAGMA quick_check`
   251  	}
   252  
   253  	stmt, _, err := db.Prepare(test)
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  	defer stmt.Close()
   258  
   259  	for stmt.Step() {
   260  		if row := stmt.ColumnText(0); row != "ok" {
   261  			t.Error(row)
   262  		}
   263  	}
   264  	if err := stmt.Err(); err != nil {
   265  		t.Fatal(err)
   266  	}
   267  
   268  	err = stmt.Close()
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	err = db.Close()
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  }