Files
pangolin/server/internal/nodes/testmain_test.go
T
wangjia 4783f3e32e feat(nodes): 节点生命周期状态机 [tsk_aG4yOS90inF_]
实现 Lifecycle 类型及其全部公开方法,覆盖 6 个状态迁移动作:
MarkProbing / MarkUp / MarkDraining / MarkDown / MarkDestroyed /
MarkBlockedSuspect。每次合法迁移在同一个 SQL 事务中原子执行
UPDATE nodes + INSERT node_events + BumpVersion;MarkDestroyed
事务提交后异步调用 mtls.CRL.Revoke() 并清除 Redis 负载缓存与
命令队列键。配套集成测试覆盖 5 类场景(合法迁移、非法迁移、
并发写、后置钩子、blocked_suspect 幂等),全部通过。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:49:45 +08:00

87 lines
2.5 KiB
Go

//go:build integration
package nodes_test
import (
"context"
"fmt"
"os"
"os/exec"
"testing"
"time"
)
// TestMain starts a throw-away MySQL 8 Docker container when PANGOLIN_TEST_DSN
// is not already set, runs all tests, and tears the container down on exit.
//
// If Docker is unavailable the tests are skipped gracefully (requireDSN in
// lifecycle_test.go handles the skip).
func TestMain(m *testing.M) {
if os.Getenv("PANGOLIN_TEST_DSN") != "" {
// Caller already provided a DSN — run directly.
os.Exit(m.Run())
}
const (
containerName = "pangolin-lc-test-mysql"
hostPort = "13399"
dbName = "pangolin_test"
rootPwd = "secret"
)
ctx := context.Background()
// Remove any leftover container from a previous crashed run.
_ = exec.CommandContext(ctx, "docker", "rm", "-f", containerName).Run()
// Start MySQL 8.
startCmd := exec.CommandContext(ctx, "docker", "run", "-d",
"--name", containerName,
"-e", "MYSQL_ROOT_PASSWORD="+rootPwd,
"-e", "MYSQL_DATABASE="+dbName,
"-p", hostPort+":3306",
"mysql:8",
)
if out, err := startCmd.CombinedOutput(); err != nil {
fmt.Fprintf(os.Stderr, "docker run failed: %v\n%s\n", err, out)
fmt.Fprintln(os.Stderr, "skipping lifecycle integration tests (no MySQL)")
os.Exit(0) // graceful skip
}
// Tear down on exit regardless of test outcome.
defer func() {
_ = exec.Command("docker", "stop", containerName).Run()
_ = exec.Command("docker", "rm", containerName).Run()
}()
// multiStatements=true is required by golang-migrate's MySQL driver so it
// can execute migration files containing multiple SQL statements in one call.
// store.Open / buildDSN preserves unrecognised params, so this flag
// survives the DSN round-trip through buildDSN.
dsn := fmt.Sprintf("root:%s@tcp(127.0.0.1:%s)/%s?multiStatements=true",
rootPwd, hostPort, dbName)
// Wait until MySQL accepts connections (up to 60 s).
deadline := time.Now().Add(60 * time.Second)
for time.Now().Before(deadline) {
ping := exec.CommandContext(ctx, "docker", "exec", containerName,
"mysqladmin", "ping", "--silent")
if err := ping.Run(); err == nil {
break
}
time.Sleep(2 * time.Second)
}
// Verify connectivity.
check := exec.CommandContext(ctx, "docker", "exec", containerName,
"mysqladmin", "ping", "--silent")
if err := check.Run(); err != nil {
fmt.Fprintln(os.Stderr, "MySQL did not become ready within 60 s; skipping tests")
os.Exit(0)
}
os.Setenv("PANGOLIN_TEST_DSN", dsn)
os.Exit(m.Run())
}