mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-23 05:13:00 -05:00
Merge pull request #2934 from dolthub/andy/tx-integration-tests
integration-tests/transactions: Added Concurrent Transaction Tests
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
// Copyright 2022 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package transactions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/gocraft/dbr/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var defaultConfig = ServerConfig{
|
||||
database: "mysql",
|
||||
host: "127.0.0.1",
|
||||
port: 3316,
|
||||
user: "root",
|
||||
password: "toor",
|
||||
}
|
||||
|
||||
func TestConcurrentTransactions(t *testing.T) {
|
||||
sequential := &sync.Mutex{}
|
||||
for _, test := range txTests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sequential.Lock()
|
||||
defer sequential.Unlock()
|
||||
testConcurrentTx(t, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type ConcurrentTxTest struct {
|
||||
name string
|
||||
queries []concurrentQuery
|
||||
}
|
||||
|
||||
type concurrentQuery struct {
|
||||
conn string
|
||||
query string
|
||||
assertion selector
|
||||
expected []testRow
|
||||
}
|
||||
|
||||
type selector func(s *dbr.Session) *dbr.SelectStmt
|
||||
|
||||
type testRow struct {
|
||||
Pk, C0 int
|
||||
}
|
||||
|
||||
const (
|
||||
one = "one"
|
||||
two = "two"
|
||||
)
|
||||
|
||||
var txTests = []ConcurrentTxTest{
|
||||
{
|
||||
name: "smoke test",
|
||||
queries: []concurrentQuery{
|
||||
{
|
||||
conn: one,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1},
|
||||
{2, 2},
|
||||
{3, 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "concurrent transactions",
|
||||
queries: []concurrentQuery{
|
||||
{
|
||||
conn: one,
|
||||
query: "BEGIN;",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
query: "BEGIN;",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1}, {2, 2}, {3, 3},
|
||||
},
|
||||
},
|
||||
{
|
||||
conn: one,
|
||||
query: "INSERT INTO tx.data VALUES (4,4)",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1}, {2, 2}, {3, 3},
|
||||
},
|
||||
},
|
||||
{
|
||||
conn: one,
|
||||
query: "COMMIT",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1}, {2, 2}, {3, 3},
|
||||
},
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
query: "COMMIT",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1}, {2, 2}, {3, 3}, {4, 4},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "concurrent updates",
|
||||
queries: []concurrentQuery{
|
||||
{
|
||||
conn: one,
|
||||
query: "BEGIN;",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
query: "BEGIN;",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data").Where("pk = 1")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
conn: one,
|
||||
query: "UPDATE tx.data SET c0 = c0 + 10 WHERE pk = 1;",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data").Where("pk = 1")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
conn: one,
|
||||
query: "COMMIT",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data").Where("pk = 1")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
query: "UPDATE tx.data SET c0 = c0 + 10 WHERE pk = 1;",
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
assertion: func(s *dbr.Session) *dbr.SelectStmt {
|
||||
return s.Select("*").From("tx.data").Where("pk = 1")
|
||||
},
|
||||
expected: []testRow{
|
||||
{1, 21},
|
||||
},
|
||||
},
|
||||
{
|
||||
conn: two,
|
||||
query: "COMMIT",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func setupCommon(sess *dbr.Session) (err error) {
|
||||
queries := []string{
|
||||
"DROP DATABASE IF EXISTS tx;",
|
||||
"CREATE DATABASE IF NOT EXISTS tx;",
|
||||
"USE tx;",
|
||||
"CREATE TABLE data (pk int primary key, c0 int);",
|
||||
"INSERT INTO data VALUES (1,1),(2,2),(3,3);",
|
||||
}
|
||||
|
||||
for _, q := range queries {
|
||||
if _, err = sess.Exec(q); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func testConcurrentTx(t *testing.T, test ConcurrentTxTest) {
|
||||
conns, err := createNamedConnections(defaultConfig, one, two)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, closeNamedConnections(conns)) }()
|
||||
|
||||
err = setupCommon(conns[one])
|
||||
defer func() { require.NoError(t, teardownCommon(conns[one])) }()
|
||||
|
||||
for _, q := range test.queries {
|
||||
conn := conns[q.conn]
|
||||
if q.query != "" {
|
||||
_, err = conn.Exec(q.query)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if q.assertion == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var actual []testRow
|
||||
_, err = q.assertion(conn).Load(&actual)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, q.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func teardownCommon(sess *dbr.Session) (err error) {
|
||||
_, err = sess.Exec("DROP DATABASE tx;")
|
||||
return
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
database string
|
||||
host string
|
||||
port int
|
||||
user string
|
||||
password string
|
||||
}
|
||||
|
||||
type namedConnections map[string]*dbr.Session
|
||||
|
||||
// ConnectionString returns a Data Source Name (DSN) to be used by go clients for connecting to a running server.
|
||||
func ConnectionString(config ServerConfig) string {
|
||||
return fmt.Sprintf("%v:%v@tcp(%v:%v)/%s",
|
||||
config.user,
|
||||
config.password,
|
||||
config.host,
|
||||
config.port,
|
||||
config.database,
|
||||
)
|
||||
}
|
||||
|
||||
func createNamedConnections(config ServerConfig, names ...string) (nc namedConnections, err error) {
|
||||
nc = make(namedConnections, len(names))
|
||||
for _, name := range names {
|
||||
var c *dbr.Connection
|
||||
if c, err = dbr.Open("mysql", ConnectionString(config), nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nc[name] = c.NewSession(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func closeNamedConnections(nc namedConnections) (err error) {
|
||||
for _, conn := range nc {
|
||||
if err = conn.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
module github.com/dolthub/dolt/integration-tests/transactions
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/go-sql-driver/mysql v1.6.0
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/gocraft/dbr/v2 v2.7.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gocraft/dbr/v2 v2.7.3 h1:5/PTRiBkdD2FoHpnrCMoEUw5Wf/Cl3l3PjJ02Wm+pwM=
|
||||
github.com/gocraft/dbr/v2 v2.7.3/go.mod h1:8IH98S8M8J0JSEiYk0MPH26ZDUKemiQ/GvmXL5jo+Uw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Reference in New Issue
Block a user