隔离原生测试数据目录并记录全后端及恢复竞争检测结果

This commit is contained in:
xuans 2026-10-02 14:01:51 +08:00
parent 3b254496b0
commit 7ba4046135
13 changed files with 54 additions and 0 deletions

View File

@ -60,6 +60,7 @@ func TestScertAdminSystemdUnitKeepsManualOnlyPolicy(t *testing.T) {
func TestControllerActivatesAndStopsResetPath(t *testing.T) { func TestControllerActivatesAndStopsResetPath(t *testing.T) {
cfg := config.Default(t.TempDir()) cfg := config.Default(t.TempDir())
cfg.Paths.Data = t.TempDir()
cfg.SetupReady = true cfg.SetupReady = true
cfg.Web.Hostname = "scert.example.test" cfg.Web.Hostname = "scert.example.test"
cfg.Server.Port = 8443 cfg.Server.Port = 8443
@ -111,6 +112,7 @@ func TestExpiredLeaseIsInactive(t *testing.T) {
func TestResetCodeCanOnlyBeConsumedOnce(t *testing.T) { func TestResetCodeCanOnlyBeConsumedOnce(t *testing.T) {
cfg := config.Default(t.TempDir()) cfg := config.Default(t.TempDir())
cfg.Paths.Data = t.TempDir()
cfg.SetupReady = true cfg.SetupReady = true
cfg.Web.Hostname = "scert.example.test" cfg.Web.Hostname = "scert.example.test"
controller := New(cfg) controller := New(cfg)
@ -137,6 +139,7 @@ func TestResetCodeCanOnlyBeConsumedOnce(t *testing.T) {
func TestControllerWritesAndDestroysProtectedCodeFile(t *testing.T) { func TestControllerWritesAndDestroysProtectedCodeFile(t *testing.T) {
cfg := config.Default(t.TempDir()) cfg := config.Default(t.TempDir())
cfg.Paths.Data = t.TempDir()
cfg.SetupReady = true cfg.SetupReady = true
cfg.Web.Hostname = "scert.example.test" cfg.Web.Hostname = "scert.example.test"
codePath := filepath.Join(t.TempDir(), "runtime", "reset-code") codePath := filepath.Join(t.TempDir(), "runtime", "reset-code")
@ -172,6 +175,7 @@ func TestControllerWritesAndDestroysProtectedCodeFile(t *testing.T) {
func TestConsumeDestroysCodeFile(t *testing.T) { func TestConsumeDestroysCodeFile(t *testing.T) {
cfg := config.Default(t.TempDir()) cfg := config.Default(t.TempDir())
cfg.Paths.Data = t.TempDir()
cfg.SetupReady = true cfg.SetupReady = true
cfg.Web.Hostname = "scert.example.test" cfg.Web.Hostname = "scert.example.test"
codePath := filepath.Join(t.TempDir(), "runtime", "reset-code") codePath := filepath.Join(t.TempDir(), "runtime", "reset-code")
@ -217,6 +221,7 @@ func TestExpiredLeaseDestroysCodeFile(t *testing.T) {
func TestControllerDoesNotOverwriteExistingCodeFile(t *testing.T) { func TestControllerDoesNotOverwriteExistingCodeFile(t *testing.T) {
cfg := config.Default(t.TempDir()) cfg := config.Default(t.TempDir())
cfg.Paths.Data = t.TempDir()
cfg.SetupReady = true cfg.SetupReady = true
cfg.Web.Hostname = "scert.example.test" cfg.Web.Hostname = "scert.example.test"
codePath := filepath.Join(t.TempDir(), "runtime", "reset-code") codePath := filepath.Join(t.TempDir(), "runtime", "reset-code")

View File

@ -326,6 +326,7 @@ func TestServiceRestoreVersionRollsBackFilesWhenStateUpdateFails(t *testing.T) {
func certificateTestConfig(root string) config.Config { func certificateTestConfig(root string) config.Config {
cfg := config.Default(root) cfg := config.Default(root)
cfg.Paths.Config = filepath.Join(root, "conf")
cfg.Paths.Certificate = filepath.Join(root, "ssl") cfg.Paths.Certificate = filepath.Join(root, "ssl")
cfg.Paths.Backup = filepath.Join(root, "backups") cfg.Paths.Backup = filepath.Join(root, "backups")
cfg.DNS.Provider = "cloudflare" cfg.DNS.Provider = "cloudflare"

View File

@ -87,6 +87,7 @@ func TestValidateConfigKeepsExternalDatabaseUsernameOutOfPersistedDSN(t *testing
func TestVerifyWritableSQLiteCleansTemporaryTable(t *testing.T) { func TestVerifyWritableSQLiteCleansTemporaryTable(t *testing.T) {
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "verification.db")
if err := VerifyWritable(t.Context(), cfg); err != nil { if err := VerifyWritable(t.Context(), cfg); err != nil {
t.Fatalf("SQLite 可写验证失败:%v", err) t.Fatalf("SQLite 可写验证失败:%v", err)
} }

View File

@ -2,6 +2,7 @@ package database
import ( import (
"context" "context"
"path/filepath"
"strings" "strings"
"testing" "testing"
@ -10,6 +11,7 @@ import (
func TestDatabasePingAndSQLitePragmas(t *testing.T) { func TestDatabasePingAndSQLitePragmas(t *testing.T) {
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "runtime.db")
db, err := Open(context.Background(), cfg) db, err := Open(context.Background(), cfg)
if err != nil { if err != nil {
t.Fatalf("打开 SQLite 失败:%v", err) t.Fatalf("打开 SQLite 失败:%v", err)

View File

@ -22,6 +22,7 @@ func TestSQLiteCanceledQueriesReleaseFiles(t *testing.T) {
} }
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "close.db")
cfg.MaxOpenConns, cfg.MaxIdleConns = 1, 1 cfg.MaxOpenConns, cfg.MaxIdleConns = 1, 1
db, err := Open(t.Context(), cfg) db, err := Open(t.Context(), cfg)
if err != nil { if err != nil {

View File

@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"path/filepath"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -15,6 +16,7 @@ import (
func newSQLiteConcurrencyPools(t *testing.T) (*sql.DB, *sql.DB) { func newSQLiteConcurrencyPools(t *testing.T) (*sql.DB, *sql.DB) {
t.Helper() t.Helper()
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "concurrency.db")
first := openSQLiteTestPool(t, cfg) first := openSQLiteTestPool(t, cfg)
second := openSQLiteTestPool(t, cfg) second := openSQLiteTestPool(t, cfg)
if _, err := first.ExecContext(t.Context(), "CREATE TABLE lock_probe (id INTEGER PRIMARY KEY, value INTEGER NOT NULL); INSERT INTO lock_probe VALUES (1, 0)"); err != nil { if _, err := first.ExecContext(t.Context(), "CREATE TABLE lock_probe (id INTEGER PRIMARY KEY, value INTEGER NOT NULL); INSERT INTO lock_probe VALUES (1, 0)"); err != nil {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"path/filepath"
"testing" "testing"
"time" "time"
@ -98,6 +99,7 @@ func TestSQLiteDeadlineAcrossExecutionPaths(t *testing.T) {
func TestSQLitePoolWaitHonorsContext(t *testing.T) { func TestSQLitePoolWaitHonorsContext(t *testing.T) {
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "pool-wait.db")
cfg.MaxOpenConns = 1 cfg.MaxOpenConns = 1
cfg.MaxIdleConns = 1 cfg.MaxIdleConns = 1
db := openSQLiteTestPool(t, cfg) db := openSQLiteTestPool(t, cfg)
@ -146,6 +148,7 @@ func TestSQLiteDeadlinePreservesQueryMetadata(t *testing.T) {
func TestSQLiteOpenDeadlineWhileChangingJournalMode(t *testing.T) { func TestSQLiteOpenDeadlineWhileChangingJournalMode(t *testing.T) {
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "journal-mode.db")
if err := ensureSQLiteDir(cfg.DSN); err != nil { if err := ensureSQLiteDir(cfg.DSN); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -3,6 +3,7 @@ package database
import ( import (
"context" "context"
"database/sql" "database/sql"
"path/filepath"
"testing" "testing"
"git.pcux.cn/xuans/Scert/backend/internal/config" "git.pcux.cn/xuans/Scert/backend/internal/config"
@ -44,6 +45,7 @@ func TestSQLitePoolConfiguresEveryConnection(t *testing.T) {
for _, name := range []string{"parallel_connections", "no_idle_connections", "replacement_connections", "uri_options"} { for _, name := range []string{"parallel_connections", "no_idle_connections", "replacement_connections", "uri_options"} {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "pool.db")
cfg.MaxOpenConns = 4 cfg.MaxOpenConns = 4
cfg.MaxIdleConns = 4 cfg.MaxIdleConns = 4
if name == "no_idle_connections" { if name == "no_idle_connections" {

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@ -29,6 +30,7 @@ func TestSQLiteProcessConcurrency(t *testing.T) {
return return
} }
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "process.db")
db := openSQLiteTestPool(t, cfg) db := openSQLiteTestPool(t, cfg)
if _, err := db.ExecContext(t.Context(), "CREATE TABLE lock_probe (id INTEGER PRIMARY KEY, value INTEGER NOT NULL); INSERT INTO lock_probe VALUES (1, 0)"); err != nil { if _, err := db.ExecContext(t.Context(), "CREATE TABLE lock_probe (id INTEGER PRIMARY KEY, value INTEGER NOT NULL); INSERT INTO lock_probe VALUES (1, 0)"); err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"path/filepath"
"runtime" "runtime"
"sync" "sync"
"testing" "testing"
@ -15,6 +16,7 @@ import (
// 覆盖真实连接间的写入与检查点竞争,不将概率性压力测试当成上游缺陷的确定性复现。 // 覆盖真实连接间的写入与检查点竞争,不将概率性压力测试当成上游缺陷的确定性复现。
func TestSQLiteWALCheckpointIntegrity(t *testing.T) { func TestSQLiteWALCheckpointIntegrity(t *testing.T) {
cfg := config.Default(t.TempDir()).Database cfg := config.Default(t.TempDir()).Database
cfg.DSN = filepath.Join(t.TempDir(), "checkpoint.db")
cfg.MaxOpenConns, cfg.MaxIdleConns = 3, 3 cfg.MaxOpenConns, cfg.MaxIdleConns = 3, 3
db := openSQLiteTestPool(t, cfg) db := openSQLiteTestPool(t, cfg)
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)

View File

@ -1,5 +1,16 @@
# Linux 生产流程测试记录 # Linux 生产流程测试记录
## 2026年10月2日13:59 测试隔离修复与完整检出回归
- [x] 十个测试文件共二十一行调整:重置状态文件、敏感配置及 SQLite DSN 均进入临时目录;共享连接池和子进程仍复用同一个测试数据库。不改产品行为、不提权写正式目录。
- [x] `/var/tmp/scert-recovery-full-20261002-1344/full-backend.log` 保留归档复测失败:三个原路径污染包已通过,仅发布边界测试因没有 Git 元数据失败;同目录 `full-vet.log` 退出零。
- [x] `/var/tmp/scert-recovery-checkout-20261002-1348/source` 为完整 Git 检出 3b25449,叠加十个测试文件修复;nobody 执行 `go test ./... -count=1`,输出 `CHECKOUT_FULL_EXIT=0`,`full-backend.log` 记录三十一个通过包。未跳过发布边界检查。
- [x] 13:55 双机监听核查:Scert 均仅监听 127.0.0.1:8443,Nginx 占用 80/443,Scert 与 Nginx 均 active;未改公网端口或共存业务。
- [!] 第一台 `/var/tmp/scert-recovery-race-20261002-1345/race.log` 的竞争检测未完成:依赖下载连接持续 SYN-SENT,13:58 中断后退出 130,不能记为通过。
- [x] 缓存归档三端 SHA256 一致,使用独立离线代理及新 GOPATH 执行 `go test -p 2 -race ./internal/deployment -run 'TestRecovery|TestJournal' -count=20`;保留 go.sum 校验,13:58:59 返回 `OFFLINE_RACE_EXIT=0`,`race-offline.log` 记录通过。仅覆盖恢复与事务用例,不声称全后端竞争检测通过。
- [x] 完整 Git 检出目录重新执行全后端 go vet,日志为同目录 `full-vet.log`,返回 `CHECKOUT_VET_EXIT=0`。
- [~] 本批修复未正式构建部署,线上仍为此前 1150c39 对应版本;未清业务库,未显式开启外部数据库用例,不扩大为三数据库生产验收。
## 2026年10月2日13:41 上传归属与扩大回归 ## 2026年10月2日13:41 上传归属与扩大回归
- [x] 同一隔离目录的 `upload-baseline.log`:带事务日志的上传碰撞保护和未知临时文件恢复保护两个场景均失败,退出码 1,确认原实现会误删。 - [x] 同一隔离目录的 `upload-baseline.log`:带事务日志的上传碰撞保护和未知临时文件恢复保护两个场景均失败,退出码 1,确认原实现会误删。

View File

@ -1,5 +1,17 @@
# Scert 整改任务执行清单 # Scert 整改任务执行清单
## 2026年10月2日13:59 当前任务入口
需求确认:不允许额外公网端口、未知归属文件误删或绕过安全校验;测试失败必须保留证据,不能降低验收门槛。
- [x] T2-G:十个测试文件共二十一行调整,将重置状态、敏感配置与 SQLite 数据库隔离到临时目录;不修改产品行为,不写正式安装目录。
- [x] 完整 Git 检出叠加测试隔离修复后,原生 Linux 全后端测试退出零;源码归档缺 Git 元数据造成的发布边界失败已定位,未跳过检查。
- [x] T2-D:第一台竞争检测因依赖下载连接停留在 SYN-SENT,保留原日志并中断,退出 130;使用三端摘要一致的缓存归档复测,恢复及事务场景竞争检测重复二十次通过,退出零。
- [x] 完整 Git 检出上的全后端 go vet 通过,输出 CHECKOUT_VET_EXIT=0。
- [x] 双机现场监听核查:Scert 均为 127.0.0.1:8443,Nginx 提供 80/443;服务均运行中,未新增公网端口。
- [ ] T2-F:测试隔离修改提交后,从干净源码正式构建,再进行部署与业务实机恢复复审。
- [~] P0-10 及整体生产门禁仍未通过;本轮不是清库首次流程,也不等于三数据库生产验收。
## 2026年10月2日13:41 当前任务入口 ## 2026年10月2日13:41 当前任务入口
- [x] T2-E:已复现上传碰撞误删;修复为独占创建确认、摘要落库与整组临时文件预检,碰撞、变更及旧日志缺归属证据均保留现场。 - [x] T2-E:已复现上传碰撞误删;修复为独占创建确认、摘要落库与整组临时文件预检,碰撞、变更及旧日志缺归属证据均保留现场。

View File

@ -1,5 +1,15 @@
# Scert 整改、合并与优化清单 # Scert 整改、合并与优化清单
## 2026年10月2日13:59 全后端隔离回归进度
- [x] 已修复 admin、certificate、database 十个测试文件的正式路径污染,保持产品默认配置及测试语义不变。
- [x] 完整 Git 检出上的原生 Linux 全后端测试通过,退出零;归档目录缺 Git 元数据导致的发布边界失败保留,不跳过源码检查。
- [x] 双机实时确认 Scert 仅监听 127.0.0.1:8443,Nginx 负责 80/443;未开放额外公网端口。
- [x] 竞争检测:第一台原任务阻塞于外部依赖下载,保留日志并中断;改用摘要一致的缓存归档和新依赖目录复测,未关闭依赖校验,恢复及事务场景重复二十次通过,退出零。
- [x] 完整 Git 检出上的全后端 go vet 通过,退出零。
- [ ] 本批代码正式干净构建、双机部署与业务实机负向恢复复审。
- [~] P0-10 保持进行中,整体生产验收未通过;线上尚未部署本批修复。
## 2026年10月2日13:41 上传归属安全整改 ## 2026年10月2日13:41 上传归属安全整改
- [x] 已复现并修复带事务日志时误删上传碰撞文件、恢复时误删身份已变更临时文件的问题;旧实现两个新增场景均失败。 - [x] 已复现并修复带事务日志时误删上传碰撞文件、恢复时误删身份已变更临时文件的问题;旧实现两个新增场景均失败。