Speed up MySQL tests (#585)
Improves MySQL test time (on my 2020 MBP) to ~18s from ~125s. - Use separate databases for each test to allow parallelization. - Run migrations only once at beginning of tests and then reload generated schema. - Add `--innodb-file-per-table=OFF` for ~20% additional speedup.
This commit is contained in:
@@ -42,10 +42,6 @@ ifdef CIRCLE_TAG
|
||||
DOCKER_IMAGE_TAG = ${CIRCLE_TAG}
|
||||
endif
|
||||
|
||||
ifndef MYSQL_PORT_3306_TCP_ADDR
|
||||
MYSQL_PORT_3306_TCP_ADDR = 127.0.0.1
|
||||
endif
|
||||
|
||||
KIT_VERSION = "\
|
||||
-X github.com/kolide/kit/version.appName=${APP_NAME} \
|
||||
-X github.com/kolide/kit/version.version=${VERSION} \
|
||||
@@ -123,7 +119,7 @@ lint-go:
|
||||
lint: lint-go lint-js
|
||||
|
||||
test-go:
|
||||
go test -tags full ./...
|
||||
go test -tags full -parallel 8 ./...
|
||||
|
||||
analyze-go:
|
||||
go test -tags full -race -cover ./...
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@ services:
|
||||
|
||||
mysql_test:
|
||||
image: mysql:5.7
|
||||
command: mysqld --datadir=/tmpfs --slow_query_log=1 --log_output=TABLE --log-queries-not-using-indexes --event-scheduler=ON
|
||||
# innodb-file-per-table=OFF gives ~20% speedup for test runs.
|
||||
command: mysqld --datadir=/tmpfs --slow_query_log=1 --log_output=TABLE --log-queries-not-using-indexes --event-scheduler=ON --innodb-file-per-table=OFF
|
||||
tmpfs: /tmpfs
|
||||
environment: *mysql-default-environment
|
||||
ports:
|
||||
|
||||
@@ -34,7 +34,6 @@ var testFunctions = [...]func(*testing.T, kolide.Datastore){
|
||||
testCreateUser,
|
||||
testSaveUser,
|
||||
testUserByID,
|
||||
testPasswordResetRequests,
|
||||
testSearchHosts,
|
||||
testSearchHostsLimit,
|
||||
testSearchLabels,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/WatchBeam/clock"
|
||||
@@ -13,46 +16,115 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupMySQL(t *testing.T) (ds *mysql.Datastore, teardown func()) {
|
||||
const (
|
||||
schemaDbName = "schemadb"
|
||||
dumpfile = "dump.sql"
|
||||
testUsername = "root"
|
||||
testPassword = "toor"
|
||||
testAddress = "localhost:3307"
|
||||
)
|
||||
|
||||
func connectMySQL(t *testing.T, testName string) *mysql.Datastore {
|
||||
config := config.MysqlConfig{
|
||||
Username: "fleet",
|
||||
Password: "insecure",
|
||||
Database: "fleet",
|
||||
// When using docker-compose.yml for local testing
|
||||
Address: "127.0.0.1:3307",
|
||||
}
|
||||
|
||||
// When using Docker link on CI
|
||||
if h, ok := os.LookupEnv("MYSQL_PORT_3306_TCP_ADDR"); ok {
|
||||
config.Address = h + ":3306"
|
||||
Username: testUsername,
|
||||
Password: testPassword,
|
||||
Database: testName,
|
||||
Address: testAddress,
|
||||
}
|
||||
|
||||
// Create datastore client
|
||||
ds, err := mysql.New(config, clock.NewMockClock(), mysql.Logger(log.NewNopLogger()), mysql.LimitAttempts(1))
|
||||
require.Nil(t, err)
|
||||
teardown = func() {
|
||||
ds.Close()
|
||||
return ds
|
||||
}
|
||||
|
||||
// initializeSchema initializes a database schema using the normal Fleet
|
||||
// migrations, then outputs the schema with mysqldump within the MySQL Docker
|
||||
// container.
|
||||
func initializeSchema(t *testing.T) {
|
||||
// Create the database (must use raw MySQL client to do this)
|
||||
db, err := sql.Open(
|
||||
"mysql",
|
||||
fmt.Sprintf("%s:%s@tcp(%s)/?multiStatements=true", testUsername, testPassword, testAddress),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
_, err = db.Exec("DROP DATABASE IF EXISTS schemadb; CREATE DATABASE schemadb;")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a datastore client in order to run migrations as usual
|
||||
config := config.MysqlConfig{
|
||||
Username: testUsername,
|
||||
Password: testPassword,
|
||||
Address: testAddress,
|
||||
Database: schemaDbName,
|
||||
}
|
||||
ds, err := mysql.New(config, clock.NewMockClock(), mysql.Logger(log.NewNopLogger()), mysql.LimitAttempts(1))
|
||||
require.Nil(t, err)
|
||||
defer ds.Close()
|
||||
require.Nil(t, ds.MigrateTables())
|
||||
|
||||
// Dump schema to dumpfile
|
||||
if out, err := exec.Command(
|
||||
"docker-compose", "exec", "-T", "mysql_test",
|
||||
// Command run inside container
|
||||
"mysqldump",
|
||||
"-u"+testUsername, "-p"+testPassword,
|
||||
"schemadb",
|
||||
"--compact", "--skip-comments",
|
||||
"--result-file="+dumpfile,
|
||||
).CombinedOutput(); err != nil {
|
||||
t.Error(err)
|
||||
t.Error(string(out))
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// initializeDatabase loads the dumped schema into a newly created database in
|
||||
// MySQL. This is much faster than running the full set of migrations on each
|
||||
// test.
|
||||
func initializeDatabase(t *testing.T, dbName string) {
|
||||
// Load schema from dumpfile
|
||||
if out, err := exec.Command(
|
||||
"docker-compose", "exec", "-T", "mysql_test",
|
||||
// Command run inside container
|
||||
"mysql",
|
||||
"-u"+testUsername, "-p"+testPassword,
|
||||
"-e",
|
||||
fmt.Sprintf(
|
||||
"DROP DATABASE IF EXISTS %s; CREATE DATABASE %s; USE %s; SET FOREIGN_KEY_CHECKS=0; SOURCE %s;",
|
||||
dbName, dbName, dbName, dumpfile,
|
||||
),
|
||||
).CombinedOutput(); err != nil {
|
||||
t.Error(err)
|
||||
t.Error(string(out))
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return ds, teardown
|
||||
}
|
||||
|
||||
func TestMySQL(t *testing.T) {
|
||||
if _, ok := os.LookupEnv("MYSQL_TEST"); !ok {
|
||||
t.SkipNow()
|
||||
t.Skip("MySQL tests are disabled")
|
||||
}
|
||||
|
||||
ds, teardown := setupMySQL(t)
|
||||
defer teardown()
|
||||
// get rid of database if it is hanging around
|
||||
err := ds.Drop()
|
||||
require.Nil(t, err)
|
||||
// Initialize the schema once for the entire test run.
|
||||
initializeSchema(t)
|
||||
|
||||
for _, f := range testFunctions {
|
||||
// Copy test function to a variable scoped within the loop so we don't
|
||||
// run the same test a bunch of times.
|
||||
testFunc := f
|
||||
t.Run(test.FunctionName(testFunc), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run(test.FunctionName(f), func(t *testing.T) {
|
||||
defer func() { require.Nil(t, ds.Drop()) }()
|
||||
require.Nil(t, ds.MigrateTables())
|
||||
f(t, ds)
|
||||
// Create a new database and load the schema for each test
|
||||
initializeDatabase(t, test.FunctionName(testFunc))
|
||||
|
||||
ds := connectMySQL(t, test.FunctionName(testFunc))
|
||||
defer ds.Close()
|
||||
|
||||
testFunc(t, ds)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package live_query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -33,10 +32,6 @@ func setupRedisLiveQuery(t *testing.T) (store *redisLiveQuery, teardown func())
|
||||
useTLS = false
|
||||
)
|
||||
|
||||
if a, ok := os.LookupEnv("REDIS_PORT_6379_TCP_ADDR"); ok {
|
||||
addr = fmt.Sprintf("%s:6379", a)
|
||||
}
|
||||
|
||||
store = NewRedisLiveQuery(pubsub.NewRedisPool(addr, password, database, useTLS))
|
||||
|
||||
_, err := store.pool.Get().Do("PING")
|
||||
|
||||
@@ -2,7 +2,6 @@ package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -68,10 +67,6 @@ func setupRedis(t *testing.T) (store *redisQueryResults, teardown func()) {
|
||||
useTLS = false
|
||||
)
|
||||
|
||||
if a, ok := os.LookupEnv("REDIS_PORT_6379_TCP_ADDR"); ok {
|
||||
addr = fmt.Sprintf("%s:6379", a)
|
||||
}
|
||||
|
||||
store = NewRedisQueryResults(NewRedisPool(addr, password, database, useTLS))
|
||||
|
||||
_, err := store.pool.Get().Do("PING")
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package sso
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/fleetdm/fleet/server/pubsub"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -20,9 +19,6 @@ func newPool(t *testing.T) *redis.Pool {
|
||||
database = 0
|
||||
useTLS = false
|
||||
)
|
||||
if a, ok := os.LookupEnv("REDIS_PORT_6379_TCP_ADDR"); ok {
|
||||
addr = fmt.Sprintf("%s:6379", a)
|
||||
}
|
||||
|
||||
p := pubsub.NewRedisPool(addr, password, database, useTLS)
|
||||
_, err := p.Get().Do("PING")
|
||||
|
||||
Reference in New Issue
Block a user