143 lines
4.4 KiB
Go
143 lines
4.4 KiB
Go
package migration
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
type migrationRow struct {
|
|
ID uint `gorm:"primaryKey"`
|
|
Version string `gorm:"size:255"`
|
|
}
|
|
|
|
func (migrationRow) TableName() string { return "sys_migration" }
|
|
|
|
// captureOutput 同时捕获 stdout 和标准 log 的输出。
|
|
//
|
|
// `[必须]` 必须包含 log:本来要防的那个回归(每个迁移打一个裸 "1")走的正是
|
|
// log 的默认输出 stderr,只盯 stdout 的断言在它面前是空转的。而且 stderr 才是
|
|
// 启动脚本 `2>&1 |` 变成红色错误块的那一路。
|
|
func captureOutput(t *testing.T, run func()) string {
|
|
t.Helper()
|
|
originalStdout, originalLogOutput := os.Stdout, log.Writer()
|
|
reader, writer, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("建管道失败: %v", err)
|
|
}
|
|
os.Stdout = writer
|
|
log.SetOutput(writer)
|
|
run()
|
|
writer.Close()
|
|
os.Stdout = originalStdout
|
|
log.SetOutput(originalLogOutput)
|
|
var builder strings.Builder
|
|
buffer := make([]byte, 4096)
|
|
for {
|
|
n, err := reader.Read(buffer)
|
|
builder.Write(buffer[:n])
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return builder.String()
|
|
}
|
|
|
|
func newMigrationTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"),
|
|
&gorm.Config{Logger: logger.Default.LogMode(logger.Info)})
|
|
if err != nil {
|
|
t.Fatalf("打开数据库失败: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(&migrationRow{}); err != nil {
|
|
t.Fatalf("建表失败: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// 已应用的迁移只汇总成一行,不再每个打印一个裸 "1"。
|
|
func TestMigrateSummarisesInsteadOfPrintingBareCounts(t *testing.T) {
|
|
db := newMigrationTestDB(t)
|
|
db.Create(&migrationRow{Version: "0000000000001"})
|
|
db.Create(&migrationRow{Version: "0000000000002"})
|
|
|
|
migration := &Migration{version: map[string]func(*gorm.DB, string) error{}}
|
|
migration.SetDb(db)
|
|
for _, version := range []string{"0000000000001", "0000000000002"} {
|
|
migration.SetVersion(version, func(*gorm.DB, string) error { return nil })
|
|
}
|
|
|
|
output := captureOutput(t, migration.Migrate)
|
|
|
|
if !strings.Contains(output, "跳过 2 个") {
|
|
t.Fatalf("应汇总跳过数量: %q", output)
|
|
}
|
|
if !strings.Contains(output, "新执行 0 个") {
|
|
t.Fatalf("应汇总新执行数量: %q", output)
|
|
}
|
|
// 上游那些孤零零的计数行不该再出现。
|
|
//
|
|
// `[必须]` 匹配时必须允许 log 的时间戳前缀。实际输出是
|
|
// "2026/08/20 09:48:41 1",拿 == "1" 去比永远不成立——这条断言最初就是
|
|
// 这么写的,变异测试才发现它根本不会失败。
|
|
bareCount := regexp.MustCompile(`(?m)^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} )?\d+\s*$`)
|
|
if bareCount.MatchString(output) {
|
|
t.Fatalf("仍在打印裸计数: %q", output)
|
|
}
|
|
// 存在性检查用 Warn 级会话,不该回显 SELECT。
|
|
if strings.Contains(output, "SELECT count(*)") {
|
|
t.Fatalf("版本存在性检查不应回显 SQL: %q", output)
|
|
}
|
|
}
|
|
|
|
// 没跑过的迁移要真的执行,并计入汇总。
|
|
func TestMigrateRunsPendingVersions(t *testing.T) {
|
|
db := newMigrationTestDB(t)
|
|
db.Create(&migrationRow{Version: "0000000000001"})
|
|
|
|
executed := make([]string, 0, 2)
|
|
migration := &Migration{version: map[string]func(*gorm.DB, string) error{}}
|
|
migration.SetDb(db)
|
|
migration.SetVersion("0000000000001", func(*gorm.DB, string) error {
|
|
executed = append(executed, "one")
|
|
return nil
|
|
})
|
|
migration.SetVersion("0000000000002", func(*gorm.DB, string) error {
|
|
executed = append(executed, "two")
|
|
return nil
|
|
})
|
|
|
|
output := captureOutput(t, migration.Migrate)
|
|
|
|
if len(executed) != 1 || executed[0] != "two" {
|
|
t.Fatalf("只应执行未应用的版本,实际执行 %v", executed)
|
|
}
|
|
if !strings.Contains(output, "新执行 1 个") || !strings.Contains(output, "跳过 1 个") {
|
|
t.Fatalf("汇总不对: %q", output)
|
|
}
|
|
}
|
|
|
|
func TestSetVersionRejectsDuplicateRegistration(t *testing.T) {
|
|
migration := &Migration{version: map[string]func(*gorm.DB, string) error{}}
|
|
migration.SetVersion("0000000000001", func(*gorm.DB, string) error { return nil })
|
|
|
|
defer func() {
|
|
value := recover()
|
|
if value == nil {
|
|
t.Fatal("重复迁移版本不应静默覆盖")
|
|
}
|
|
if !strings.Contains(fmt.Sprint(value), "duplicate migration version registered: 0000000000001") {
|
|
t.Fatalf("重复版本错误不明确: %v", value)
|
|
}
|
|
}()
|
|
migration.SetVersion("0000000000001", func(*gorm.DB, string) error { return nil })
|
|
}
|