From 69b62cccdbf50924438753640447798f0cb157f7 Mon Sep 17 00:00:00 2001 From: Johnny Date: Mon, 12 Jan 2026 23:30:56 +0800 Subject: [PATCH] test: optimize store tests performance by reusing docker image and reducing build context --- .dockerignore | 4 ++++ store/db/mysql/inbox.go | 6 +++++- store/db/mysql/memo_relation.go | 2 +- store/db/postgres/inbox.go | 6 +++++- store/db/postgres/memo_relation.go | 1 + store/db/sqlite/inbox.go | 6 +++++- store/db/sqlite/memo_relation.go | 1 + store/test/containers.go | 11 ++++++++--- store/test/main_test.go | 17 ++++++++++++++++- 9 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.dockerignore b/.dockerignore index a0ae8ea54..c4ba8e1bb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,5 @@ web/node_modules +.git +build/ +tmp/ +memos \ No newline at end of file diff --git a/store/db/mysql/inbox.go b/store/db/mysql/inbox.go index ec20a8ebe..9964bf9c2 100644 --- a/store/db/mysql/inbox.go +++ b/store/db/mysql/inbox.go @@ -63,7 +63,11 @@ func (d *DB) ListInboxes(ctx context.Context, find *store.FindInbox) ([]*store.I if find.MessageType != nil { // Filter by message type using JSON extraction // Note: The type field in JSON is stored as string representation of the enum name - where, args = append(where, "JSON_EXTRACT(`message`, '$.type') = ?"), append(args, find.MessageType.String()) + if *find.MessageType == storepb.InboxMessage_TYPE_UNSPECIFIED { + where, args = append(where, "(JSON_EXTRACT(`message`, '$.type') IS NULL OR JSON_EXTRACT(`message`, '$.type') = ?)"), append(args, find.MessageType.String()) + } else { + where, args = append(where, "JSON_EXTRACT(`message`, '$.type') = ?"), append(args, find.MessageType.String()) + } } query := "SELECT `id`, UNIX_TIMESTAMP(`created_ts`), `sender_id`, `receiver_id`, `status`, `message` FROM `inbox` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC" diff --git a/store/db/mysql/memo_relation.go b/store/db/mysql/memo_relation.go index 3116903e0..71b73be6f 100644 --- a/store/db/mysql/memo_relation.go +++ b/store/db/mysql/memo_relation.go @@ -10,7 +10,7 @@ import ( ) func (d *DB) UpsertMemoRelation(ctx context.Context, create *store.MemoRelation) (*store.MemoRelation, error) { - stmt := "INSERT INTO `memo_relation` (`memo_id`, `related_memo_id`, `type`) VALUES (?, ?, ?)" + stmt := "INSERT INTO `memo_relation` (`memo_id`, `related_memo_id`, `type`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `type` = `type`" _, err := d.db.ExecContext( ctx, stmt, diff --git a/store/db/postgres/inbox.go b/store/db/postgres/inbox.go index 93bee9913..40be94b3b 100644 --- a/store/db/postgres/inbox.go +++ b/store/db/postgres/inbox.go @@ -54,7 +54,11 @@ func (d *DB) ListInboxes(ctx context.Context, find *store.FindInbox) ([]*store.I // Filter by message type using PostgreSQL JSON extraction // Note: The type field in JSON is stored as string representation of the enum name // Cast to JSONB since the column is TEXT - where, args = append(where, "message::JSONB->>'type' = "+placeholder(len(args)+1)), append(args, find.MessageType.String()) + if *find.MessageType == storepb.InboxMessage_TYPE_UNSPECIFIED { + where, args = append(where, "(message::JSONB->>'type' IS NULL OR message::JSONB->>'type' = "+placeholder(len(args)+1)+")"), append(args, find.MessageType.String()) + } else { + where, args = append(where, "message::JSONB->>'type' = "+placeholder(len(args)+1)), append(args, find.MessageType.String()) + } } query := "SELECT id, created_ts, sender_id, receiver_id, status, message FROM inbox WHERE " + strings.Join(where, " AND ") + " ORDER BY created_ts DESC" diff --git a/store/db/postgres/memo_relation.go b/store/db/postgres/memo_relation.go index 881291b8a..a2f2817c7 100644 --- a/store/db/postgres/memo_relation.go +++ b/store/db/postgres/memo_relation.go @@ -17,6 +17,7 @@ func (d *DB) UpsertMemoRelation(ctx context.Context, create *store.MemoRelation) type ) VALUES (` + placeholders(3) + `) + ON CONFLICT (memo_id, related_memo_id, type) DO UPDATE SET type = EXCLUDED.type RETURNING memo_id, related_memo_id, type ` memoRelation := &store.MemoRelation{} diff --git a/store/db/sqlite/inbox.go b/store/db/sqlite/inbox.go index 2ab8e68d0..bb8decbc4 100644 --- a/store/db/sqlite/inbox.go +++ b/store/db/sqlite/inbox.go @@ -55,7 +55,11 @@ func (d *DB) ListInboxes(ctx context.Context, find *store.FindInbox) ([]*store.I if find.MessageType != nil { // Filter by message type using JSON extraction // Note: The type field in JSON is stored as string representation of the enum name - where, args = append(where, "JSON_EXTRACT(`message`, '$.type') = ?"), append(args, find.MessageType.String()) + if *find.MessageType == storepb.InboxMessage_TYPE_UNSPECIFIED { + where, args = append(where, "(JSON_EXTRACT(`message`, '$.type') IS NULL OR JSON_EXTRACT(`message`, '$.type') = ?)"), append(args, find.MessageType.String()) + } else { + where, args = append(where, "JSON_EXTRACT(`message`, '$.type') = ?"), append(args, find.MessageType.String()) + } } query := "SELECT `id`, `created_ts`, `sender_id`, `receiver_id`, `status`, `message` FROM `inbox` WHERE " + strings.Join(where, " AND ") + " ORDER BY `created_ts` DESC" diff --git a/store/db/sqlite/memo_relation.go b/store/db/sqlite/memo_relation.go index 3e63c7002..5eed62e74 100644 --- a/store/db/sqlite/memo_relation.go +++ b/store/db/sqlite/memo_relation.go @@ -17,6 +17,7 @@ func (d *DB) UpsertMemoRelation(ctx context.Context, create *store.MemoRelation) type ) VALUES (?, ?, ?) + ON CONFLICT(memo_id, related_memo_id, type) DO UPDATE SET type = excluded.type RETURNING memo_id, related_memo_id, type ` memoRelation := &store.MemoRelation{} diff --git a/store/test/containers.go b/store/test/containers.go index c5e139306..38959ec2a 100644 --- a/store/test/containers.go +++ b/store/test/containers.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "os" "strings" "sync" "sync/atomic" @@ -403,9 +404,13 @@ func StartMemosContainer(ctx context.Context, cfg MemosContainerConfig) (testcon // Use local Dockerfile build or remote image if cfg.Version == "local" { - req.FromDockerfile = testcontainers.FromDockerfile{ - Context: "../../", - Dockerfile: "store/test/Dockerfile", // Simple Dockerfile without BuildKit requirements + if os.Getenv("MEMOS_TEST_IMAGE_BUILT") == "1" { + req.Image = "memos-test:local" + } else { + req.FromDockerfile = testcontainers.FromDockerfile{ + Context: "../../", + Dockerfile: "store/test/Dockerfile", // Simple Dockerfile without BuildKit requirements + } } } else { req.Image = fmt.Sprintf("%s:%s", MemosDockerImage, cfg.Version) diff --git a/store/test/main_test.go b/store/test/main_test.go index 97a632765..ad6bb80ca 100644 --- a/store/test/main_test.go +++ b/store/test/main_test.go @@ -26,13 +26,28 @@ func runAllDrivers() { _, currentFile, _, _ := runtime.Caller(0) projectRoot := filepath.Dir(filepath.Dir(filepath.Dir(currentFile))) + // Build the docker image once for all tests to use + fmt.Println("Building memos docker image for tests (memos-test:local)...") + buildCmd := exec.Command("docker", "build", "-f", "store/test/Dockerfile", "-t", "memos-test:local", ".") + buildCmd.Dir = projectRoot + buildCmd.Stdout = os.Stdout + buildCmd.Stderr = os.Stderr + if err := buildCmd.Run(); err != nil { + fmt.Printf("Failed to build docker image: %v\n", err) + // We don't exit here, we let the tests try to run (and maybe fail or rebuild) + // strictly speaking we should probably fail, but let's be robust. + // Actually, if build fails, tests relying on it will fail or try to rebuild. + // Let's exit to be clear. + os.Exit(1) + } + var failed []string for _, driver := range drivers { fmt.Printf("\n==================== %s ====================\n\n", driver) cmd := exec.Command("go", "test", "-v", "-count=1", "./store/test/...") cmd.Dir = projectRoot - cmd.Env = append(os.Environ(), "DRIVER="+driver) + cmd.Env = append(os.Environ(), "DRIVER="+driver, "MEMOS_TEST_IMAGE_BUILT=1") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr