test: optimize store tests performance by reusing docker image and reducing build context

This commit is contained in:
Johnny 2026-01-12 23:30:56 +08:00
parent f58533003b
commit 69b62cccdb
9 changed files with 46 additions and 8 deletions

View File

@ -1 +1,5 @@
web/node_modules
.git
build/
tmp/
memos

View File

@ -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"

View File

@ -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,

View File

@ -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"

View File

@ -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{}

View File

@ -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"

View File

@ -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{}

View File

@ -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)

View File

@ -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