mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-13 10:19:07 +02:00
Cloning the schema from a template cut the per-test store cost on Postgres from about 0.9s to 0.3s but barely moved MySQL, which stayed at 1.5s to 1.9s per store. The time is the DDL itself: with the server defaults each CREATE TABLE fsyncs the redo log, the binary log and the doublewrite buffer, and the suite issues about 40 of them per test. Start mysqld in the test container without the binary log, without the doublewrite buffer and without a redo fsync per commit. The image is the official MySQL entrypoint, so the flags are passed as the container command. Tables stay in per-table files: with innodb_file_per_table off the packages that create hundreds of databases per run got slower, not faster, because dropped databases leave the shared system tablespace to grow and fragment. Measured on healthy runners, management/server on MySQL went from 10m16s to 6m36s and the store package from 4m20s to 2m41s, with per-test store setup down from about 1.6s to about 0.5s.
155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
//go:build !ios
|
|
|
|
package testutil
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/testcontainers/testcontainers-go"
|
|
"github.com/testcontainers/testcontainers-go/modules/mysql"
|
|
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
|
testcontainersredis "github.com/testcontainers/testcontainers-go/modules/redis"
|
|
"github.com/testcontainers/testcontainers-go/wait"
|
|
)
|
|
|
|
var (
|
|
pgContainer *postgres.PostgresContainer
|
|
mysqlContainer *mysql.MySQLContainer
|
|
)
|
|
|
|
// CreateMysqlTestContainer creates a new MySQL container for testing.
|
|
func CreateMysqlTestContainer() (func(), string, error) {
|
|
ctx := context.Background()
|
|
|
|
if mysqlContainer != nil {
|
|
connStr, err := mysqlContainer.ConnectionString(ctx)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
return noOpCleanup, connStr, nil
|
|
}
|
|
|
|
var err error
|
|
mysqlContainer, err = mysql.Run(ctx,
|
|
"mlsmaycon/warmed-mysql:8",
|
|
mysql.WithDatabase("testing"),
|
|
mysql.WithUsername("root"),
|
|
mysql.WithPassword("testing"),
|
|
// Every test creates and drops a database with about 40 tables, so with
|
|
// the server defaults the run is dominated by durability work: each
|
|
// CREATE TABLE fsyncs the redo log, the binary log and the doublewrite
|
|
// buffer. None of it protects anything in a container that is discarded
|
|
// after the run. Tables stay in per-table files on purpose: in the shared
|
|
// system tablespace the cost of every CREATE and DROP grew with the number
|
|
// of databases the run had already created.
|
|
testcontainers.WithCmd("mysqld",
|
|
"--innodb-flush-log-at-trx-commit=0",
|
|
"--innodb-doublewrite=OFF",
|
|
"--skip-log-bin",
|
|
),
|
|
testcontainers.WithWaitStrategy(
|
|
wait.ForLog("/usr/sbin/mysqld: ready for connections").
|
|
WithOccurrence(1).WithStartupTimeout(15*time.Second).WithPollInterval(100*time.Millisecond),
|
|
),
|
|
)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
cleanup := func() {
|
|
if mysqlContainer != nil {
|
|
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 1*time.Second)
|
|
defer cancelFunc()
|
|
if err = mysqlContainer.Terminate(timeoutCtx); err != nil {
|
|
log.WithContext(ctx).Warnf("failed to stop mysql container %s: %s", mysqlContainer.GetContainerID(), err)
|
|
}
|
|
mysqlContainer = nil // reset the container to allow recreation
|
|
}
|
|
}
|
|
|
|
talksConn, err := mysqlContainer.ConnectionString(ctx)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return cleanup, talksConn, nil
|
|
}
|
|
|
|
// CreatePostgresTestContainer creates a new PostgreSQL container for testing.
|
|
func CreatePostgresTestContainer() (func(), string, error) {
|
|
ctx := context.Background()
|
|
|
|
if pgContainer != nil {
|
|
connStr, err := pgContainer.ConnectionString(ctx)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
return noOpCleanup, connStr, nil
|
|
}
|
|
|
|
var err error
|
|
pgContainer, err = postgres.Run(ctx,
|
|
"postgres:16-alpine",
|
|
postgres.WithDatabase("netbird"),
|
|
postgres.WithUsername("root"),
|
|
postgres.WithPassword("netbird"),
|
|
testcontainers.WithWaitStrategy(
|
|
wait.ForLog("database system is ready to accept connections").
|
|
WithOccurrence(2).WithStartupTimeout(15*time.Second),
|
|
),
|
|
)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
cleanup := func() {
|
|
if pgContainer != nil {
|
|
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 1*time.Second)
|
|
defer cancelFunc()
|
|
if err = pgContainer.Terminate(timeoutCtx); err != nil {
|
|
log.WithContext(ctx).Warnf("failed to stop postgres container %s: %s", pgContainer.GetContainerID(), err)
|
|
}
|
|
pgContainer = nil // reset the container to allow recreation
|
|
}
|
|
|
|
}
|
|
|
|
talksConn, err := pgContainer.ConnectionString(ctx)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return cleanup, talksConn, nil
|
|
}
|
|
|
|
func noOpCleanup() {
|
|
// no-op
|
|
}
|
|
|
|
// CreateRedisTestContainer creates a new Redis container for testing.
|
|
func CreateRedisTestContainer() (func(), string, error) {
|
|
ctx := context.Background()
|
|
|
|
redisContainer, err := testcontainersredis.Run(ctx, "redis:7")
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
cleanup := func() {
|
|
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 1*time.Second)
|
|
defer cancelFunc()
|
|
if err = redisContainer.Terminate(timeoutCtx); err != nil {
|
|
log.WithContext(ctx).Warnf("failed to stop redis container %s: %s", redisContainer.GetContainerID(), err)
|
|
}
|
|
}
|
|
|
|
redisURL, err := redisContainer.ConnectionString(ctx)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return cleanup, redisURL, nil
|
|
}
|