Run unit tests in separate databases to avoid conflicts

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2020-05-08 19:34:29 +02:00
parent e703a54586
commit 3aad02501f
18 changed files with 78 additions and 61 deletions

View File

@@ -24,7 +24,7 @@ services:
PHOTOPRISM_TIDB_PASSWORD: "photoprism" PHOTOPRISM_TIDB_PASSWORD: "photoprism"
PHOTOPRISM_DATABASE_DRIVER: "tidb" PHOTOPRISM_DATABASE_DRIVER: "tidb"
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true" PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
PHOTOPRISM_TEST_DSN: "photoprism:photoprism@tcp(photoprism-db:4001)/photoprism?parseTime=true" PHOTOPRISM_TEST_DSN: "root:photoprism@tcp(photoprism-db:4001)/photoprism?parseTime=true"
PHOTOPRISM_TITLE: "PhotoPrism" PHOTOPRISM_TITLE: "PhotoPrism"
PHOTOPRISM_SUBTITLE: "Browse your life" PHOTOPRISM_SUBTITLE: "Browse your life"
PHOTOPRISM_DESCRIPTION: "Personal Photo Management tested by Travis CI." PHOTOPRISM_DESCRIPTION: "Personal Photo Management tested by Travis CI."
@@ -47,6 +47,8 @@ services:
command: mysqld --port=4001 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=1024 command: mysqld --port=4001 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=1024
expose: expose:
- "4001" - "4001"
volumes:
- "./scripts/test-db.sql:/docker-entrypoint-initdb.d/test-db.sql"
environment: environment:
MYSQL_ROOT_PASSWORD: photoprism MYSQL_ROOT_PASSWORD: photoprism
MYSQL_USER: photoprism MYSQL_USER: photoprism

View File

@@ -28,7 +28,7 @@ services:
PHOTOPRISM_TIDB_PASSWORD: "photoprism" PHOTOPRISM_TIDB_PASSWORD: "photoprism"
PHOTOPRISM_DATABASE_DRIVER: "tidb" PHOTOPRISM_DATABASE_DRIVER: "tidb"
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true" PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
PHOTOPRISM_TEST_DSN: "photoprism:photoprism@tcp(photoprism-db:4001)/photoprism?parseTime=true" PHOTOPRISM_TEST_DSN: "root:photoprism@tcp(photoprism-db:4001)/photoprism?parseTime=true"
PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets" PHOTOPRISM_ASSETS_PATH: "/go/src/github.com/photoprism/photoprism/assets"
PHOTOPRISM_CACHE_PATH: "/go/src/github.com/photoprism/photoprism/assets/cache" PHOTOPRISM_CACHE_PATH: "/go/src/github.com/photoprism/photoprism/assets/cache"
PHOTOPRISM_RESOURCES_PATH: "/go/src/github.com/photoprism/photoprism/assets/resources" PHOTOPRISM_RESOURCES_PATH: "/go/src/github.com/photoprism/photoprism/assets/resources"
@@ -48,6 +48,8 @@ services:
- "4001" - "4001"
ports: ports:
- "4001:4001" # MySQL (for tests) - "4001:4001" # MySQL (for tests)
volumes:
- "./scripts/test-db.sql:/docker-entrypoint-initdb.d/test-db.sql"
environment: environment:
MYSQL_ROOT_PASSWORD: photoprism MYSQL_ROOT_PASSWORD: photoprism
MYSQL_USER: photoprism MYSQL_USER: photoprism

View File

@@ -61,10 +61,10 @@ func (c *Config) InitDb() {
entity.MigrateDb() entity.MigrateDb()
} }
// ResetDb drops all tables in the currently configured database and re-creates them. // InitTestDb drops all tables in the currently configured database and re-creates them.
func (c *Config) ResetDb() { func (c *Config) InitTestDb() {
entity.SetDbProvider(c) entity.SetDbProvider(c)
entity.InitTestFixtures() entity.ResetTestFixtures()
} }
// connectToDatabase establishes a database connection. // connectToDatabase establishes a database connection.

View File

@@ -102,7 +102,7 @@ func NewTestConfig() *Config {
log.Fatalf("config: %s", err.Error()) log.Fatalf("config: %s", err.Error())
} }
c.ResetDb() c.InitTestDb()
thumb.Size = c.ThumbSize() thumb.Size = c.ThumbSize()
thumb.Limit = c.ThumbLimit() thumb.Limit = c.ThumbLimit()

View File

@@ -15,12 +15,13 @@ func TestCreateAccount(t *testing.T) {
accountForm, err := form.NewAccount(account) accountForm, err := form.NewAccount(account)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
model, err := CreateAccount(accountForm) model, err := CreateAccount(accountForm)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, "/home", model.SharePath) assert.Equal(t, "/home", model.SharePath)
@@ -56,12 +57,12 @@ func TestAccount_Save(t *testing.T) {
accountForm, err := form.NewAccount(account) accountForm, err := form.NewAccount(account)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
model, err := CreateAccount(accountForm) model, err := CreateAccount(accountForm)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, "Foo", model.AccName) assert.Equal(t, "Foo", model.AccName)
@@ -75,7 +76,7 @@ func TestAccount_Save(t *testing.T) {
err = model.Save(UpdateForm) err = model.Save(UpdateForm)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, "NewName", model.AccName) assert.Equal(t, "NewName", model.AccName)
@@ -94,18 +95,18 @@ func TestAccount_Delete(t *testing.T) {
accountForm, err := form.NewAccount(account) accountForm, err := form.NewAccount(account)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
model, err := CreateAccount(accountForm) model, err := CreateAccount(accountForm)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
err = model.Delete() err = model.Delete()
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
// TODO how to assert deletion? // TODO how to assert deletion?
@@ -121,18 +122,18 @@ func TestAccount_Directories(t *testing.T) {
accountForm, err := form.NewAccount(account) accountForm, err := form.NewAccount(account)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
model, err := CreateAccount(accountForm) model, err := CreateAccount(accountForm)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
result, err := model.Directories() result, err := model.Directories()
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.NotEmpty(t, result.Abs()) assert.NotEmpty(t, result.Abs())
assert.Contains(t, result.Abs(), "/Photos") assert.Contains(t, result.Abs(), "/Photos")
@@ -146,18 +147,18 @@ func TestAccount_Directories(t *testing.T) {
accountForm, err := form.NewAccount(account) accountForm, err := form.NewAccount(account)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
model, err := CreateAccount(accountForm) model, err := CreateAccount(accountForm)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
result, err := model.Directories() result, err := model.Directories()
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Empty(t, result.Abs()) assert.Empty(t, result.Abs())

View File

@@ -75,13 +75,13 @@ func TestAlbum_Save(t *testing.T) {
albumForm, err := form.NewAlbum(album2) albumForm, err := form.NewAlbum(album2)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
err = album.Save(albumForm) err = album.Save(albumForm)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, "New name", album.AlbumName) assert.Equal(t, "New name", album.AlbumName)

View File

@@ -9,7 +9,7 @@ func TestDescription_FirstOrCreate(t *testing.T) {
description := &Description{PhotoID: 123, PhotoDescription: ""} description := &Description{PhotoID: 123, PhotoDescription: ""}
err := description.FirstOrCreate() err := description.FirstOrCreate()
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
} }

View File

@@ -11,7 +11,6 @@ package entity
import ( import (
"fmt" "fmt"
"sync"
"time" "time"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
@@ -19,7 +18,6 @@ import (
) )
var log = event.Log var log = event.Log
var resetFixturesOnce sync.Once
func logError(result *gorm.DB) { func logError(result *gorm.DB) {
if result.Error != nil { if result.Error != nil {
@@ -74,6 +72,18 @@ func (list Types) WaitForMigration() {
} }
} }
// Truncate removes all data from tables without dropping them.
func (list Types) Truncate() {
for name := range list {
if err := Db().Raw(fmt.Sprintf("TRUNCATE TABLE `%s`", name)).Scan(&struct{}{}).Error; err == nil {
log.Debugf("entity: removed all data from %s", name)
break
} else {
log.Debugf("entity: %s", err.Error())
}
}
}
// Drop migrates all database tables of registered entities. // Drop migrates all database tables of registered entities.
func (list Types) Migrate() { func (list Types) Migrate() {
for _, entity := range list { for _, entity := range list {
@@ -98,41 +108,32 @@ func (list Types) Drop() {
} }
} }
// MigrateDb creates all tables and inserts default entities as needed. // Creates default database entries for test and production.
func MigrateDb() { func CreateDefaultFixtures() {
Entities.Migrate()
Entities.WaitForMigration()
CreateUnknownPlace() CreateUnknownPlace()
CreateUnknownCountry() CreateUnknownCountry()
CreateUnknownCamera() CreateUnknownCamera()
CreateUnknownLens() CreateUnknownLens()
} }
// DropTables drops database tables for all known entities. // MigrateDb creates all tables and inserts default entities as needed.
func DropTables() { func MigrateDb() {
Entities.Drop() Entities.Migrate()
Entities.WaitForMigration()
// wait for changes to be written to disk CreateDefaultFixtures()
time.Sleep(250 * time.Millisecond)
} }
// ResetDb drops database tables for all known entities and re-creates them with fixtures. // ResetTestFixtures drops database tables for all known entities and re-creates them with fixtures.
func ResetDb(testFixtures bool) { func ResetTestFixtures() {
DropTables() Entities.Migrate()
MigrateDb() Entities.WaitForMigration()
Entities.Truncate()
CreateDefaultFixtures()
if testFixtures {
CreateTestFixtures() CreateTestFixtures()
} }
}
// InitTestFixtures resets the database and test fixtures once.
func InitTestFixtures() {
resetFixturesOnce.Do(func() {
ResetDb(true)
})
}
// InitTestDb connects to and completely initializes the test database incl fixtures. // InitTestDb connects to and completely initializes the test database incl fixtures.
func InitTestDb(dsn string) *Gorm { func InitTestDb(dsn string) *Gorm {
@@ -146,7 +147,7 @@ func InitTestDb(dsn string) *Gorm {
} }
SetDbProvider(db) SetDbProvider(db)
InitTestFixtures() ResetTestFixtures()
return db return db
} }

View File

@@ -2,6 +2,7 @@ package entity
import ( import (
"os" "os"
"strings"
"testing" "testing"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -17,7 +18,7 @@ func TestMain(m *testing.M) {
panic("database dsn is empty") panic("database dsn is empty")
} }
db := InitTestDb(dsn) db := InitTestDb(strings.Replace(dsn, "/photoprism", "/entity", 1))
code := m.Run() code := m.Run()

View File

@@ -16,7 +16,7 @@ func TestFirstFileByHash(t *testing.T) {
t.Run("existing file", func(t *testing.T) { t.Run("existing file", func(t *testing.T) {
f, err := FirstFileByHash("2cad9168fa6acc5c5c2965ddf6ec465ca42fd818") f, err := FirstFileByHash("2cad9168fa6acc5c5c2965ddf6ec465ca42fd818")
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, uint(0xf4240), f.ID) assert.Equal(t, uint(0xf4240), f.ID)
}) })

View File

@@ -70,7 +70,7 @@ func TestLabel_Update(t *testing.T) {
err := Label.Update(*classifyLabel) err := Label.Update(*classifyLabel)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, 5, Label.LabelPriority) assert.Equal(t, 5, Label.LabelPriority)
@@ -89,7 +89,7 @@ func TestLabel_Update(t *testing.T) {
err := Label.Update(*classifyLabel) err := Label.Update(*classifyLabel)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, 5, Label.LabelPriority) assert.Equal(t, 5, Label.LabelPriority)
@@ -109,7 +109,7 @@ func TestLabel_Update(t *testing.T) {
err := Label.Update(*classifyLabel) err := Label.Update(*classifyLabel)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, 5, Label.LabelPriority) assert.Equal(t, 5, Label.LabelPriority)

View File

@@ -50,7 +50,7 @@ func TestPhotoLabel_Save(t *testing.T) {
photoLabel := NewPhotoLabel(13, 1000, 99, "image") photoLabel := NewPhotoLabel(13, 1000, 99, "image")
err := photoLabel.Save() err := photoLabel.Save()
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
}) })
} }

View File

@@ -87,13 +87,13 @@ func TestPhoto_Save(t *testing.T) {
err := photo.Save() err := photo.Save()
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
}) })
t.Run("existing photo", func(t *testing.T) { t.Run("existing photo", func(t *testing.T) {
err := PhotoFixture19800101_000002_D640C559.Save() err := PhotoFixture19800101_000002_D640C559.Save()
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
}) })
} }

View File

@@ -14,7 +14,7 @@ func TestNewAccount(t *testing.T) {
r, err := NewAccount(account) r, err := NewAccount(account)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, "Foo", r.AccName) assert.Equal(t, "Foo", r.AccName)

View File

@@ -26,7 +26,7 @@ func TestNewAlbum(t *testing.T) {
r, err := NewAlbum(album) r, err := NewAlbum(album)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, "Foo", r.AlbumName) assert.Equal(t, "Foo", r.AlbumName)

View File

@@ -17,7 +17,7 @@ func TestNewPhoto(t *testing.T) {
r, err := NewPhoto(photo) r, err := NewPhoto(photo)
if err != nil { if err != nil {
t.Fatal("error") t.Fatal(err)
} }
assert.Equal(t, time.Date(2008, 1, 1, 2, 0, 0, 0, time.UTC), r.TakenAt) assert.Equal(t, time.Date(2008, 1, 1, 2, 0, 0, 0, time.UTC), r.TakenAt)

View File

@@ -2,6 +2,7 @@ package query
import ( import (
"os" "os"
"strings"
"testing" "testing"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
@@ -18,7 +19,7 @@ func TestMain(m *testing.M) {
panic("database dsn is empty") panic("database dsn is empty")
} }
db := entity.InitTestDb(dsn) db := entity.InitTestDb(strings.Replace(dsn, "/photoprism", "/query", 1))
code := m.Run() code := m.Run()

9
scripts/test-db.sql Normal file
View File

@@ -0,0 +1,9 @@
CREATE DATABASE IF NOT EXISTS api;
CREATE DATABASE IF NOT EXISTS config;
CREATE DATABASE IF NOT EXISTS entity;
CREATE DATABASE IF NOT EXISTS photoprism;
CREATE DATABASE IF NOT EXISTS query;
CREATE DATABASE IF NOT EXISTS remote;
CREATE DATABASE IF NOT EXISTS service;
CREATE DATABASE IF NOT EXISTS workers;
CREATE DATABASE IF NOT EXISTS acceptance;