From f8effc5e848d6d62d9af7c9d860fec6e008afaa3 Mon Sep 17 00:00:00 2001 From: CJACK Date: Tue, 17 Feb 2026 02:57:21 +0800 Subject: [PATCH] feat: add option to prune old test run directories, keeping a configurable maximum number. --- .gitignore | 4 +++ TESTING.md | 14 +++++++++- cmd/ds2api-tests/main.go | 1 + internal/testsuite/runner.go | 53 ++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bace4b7..5f776e2 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,10 @@ pnpm-lock.yaml static/admin/ internal/webui/assets/admin/ +# Go compiled binaries +ds2api +ds2api-tests + # Environment .env.local .env.*.local diff --git a/TESTING.md b/TESTING.md index 63ec6e0..52cbaf3 100644 --- a/TESTING.md +++ b/TESTING.md @@ -61,7 +61,8 @@ go run ./cmd/ds2api-tests \ --port 0 \ --timeout 120 \ --retries 2 \ - --no-preflight=false + --no-preflight=false \ + --keep 5 ``` | 参数 | 说明 | 默认值 | @@ -73,6 +74,17 @@ go run ./cmd/ds2api-tests \ | `--timeout` | 单个请求超时秒数 | `120` | | `--retries` | 网络/5xx 请求重试次数 | `2` | | `--no-preflight` | 跳过 preflight 检查 | `false` | +| `--keep` | 保留最近几次测试结果(`0` = 全部保留) | `5` | + +--- + +## 自动清理 | Auto Cleanup + +每次测试运行完成后,程序会自动扫描输出目录(`--out`),按时间排序保留最近 `--keep` 次运行的结果,超出部分自动删除。 + +- 默认保留 **5** 次 +- 设置 `--keep 0` 可关闭自动清理 +- 被删除的旧运行目录会打印日志提示 --- diff --git a/cmd/ds2api-tests/main.go b/cmd/ds2api-tests/main.go index 5966cf0..cc66c91 100644 --- a/cmd/ds2api-tests/main.go +++ b/cmd/ds2api-tests/main.go @@ -21,6 +21,7 @@ func main() { flag.IntVar(&timeoutSeconds, "timeout", int(opts.Timeout.Seconds()), "Per-request timeout in seconds") flag.IntVar(&opts.Retries, "retries", opts.Retries, "Retry count for network/5xx requests") flag.BoolVar(&opts.NoPreflight, "no-preflight", opts.NoPreflight, "Skip preflight checks") + flag.IntVar(&opts.MaxKeepRuns, "keep", opts.MaxKeepRuns, "Max test runs to keep (0 = keep all)") flag.Parse() if timeoutSeconds <= 0 { diff --git a/internal/testsuite/runner.go b/internal/testsuite/runner.go index 20fbdcc..de8ea00 100644 --- a/internal/testsuite/runner.go +++ b/internal/testsuite/runner.go @@ -31,6 +31,7 @@ type Options struct { Timeout time.Duration Retries int NoPreflight bool + MaxKeepRuns int } type runSummary struct { @@ -161,6 +162,7 @@ func DefaultOptions() Options { Timeout: 120 * time.Second, Retries: 2, NoPreflight: false, + MaxKeepRuns: 5, } } @@ -212,6 +214,11 @@ func Run(ctx context.Context, opts Options) error { return err } + // Prune old test runs, keeping only the most recent N. + if err := r.pruneOldRuns(); err != nil { + r.warnings = append(r.warnings, "prune old runs: "+err.Error()) + } + failed := 0 for _, cs := range r.results { if !cs.Passed { @@ -269,6 +276,52 @@ func (r *Runner) prepareRunDir() error { return nil } +// pruneOldRuns removes old test run directories, keeping the most recent MaxKeepRuns. +// Run IDs use the format "20060102T150405Z", so alphabetical order == chronological order. +func (r *Runner) pruneOldRuns() error { + keep := r.opts.MaxKeepRuns + if keep <= 0 { + return nil // 0 or negative means no pruning + } + + entries, err := os.ReadDir(r.opts.OutputDir) + if err != nil { + return err + } + + // Collect only directories (each run is a directory). + var runDirs []string + for _, e := range entries { + if !e.IsDir() { + continue + } + runDirs = append(runDirs, e.Name()) + } + + sort.Strings(runDirs) + + if len(runDirs) <= keep { + return nil + } + + // Remove oldest runs (those at the beginning of the sorted list). + toRemove := runDirs[:len(runDirs)-keep] + var errs []string + for _, name := range toRemove { + dirPath := filepath.Join(r.opts.OutputDir, name) + if err := os.RemoveAll(dirPath); err != nil { + errs = append(errs, fmt.Sprintf("remove %s: %v", name, err)) + } else { + fmt.Fprintf(os.Stdout, "pruned old test run: %s\n", name) + } + } + + if len(errs) > 0 { + return errors.New(strings.Join(errs, "; ")) + } + return nil +} + func (r *Runner) runPreflight(ctx context.Context) error { steps := [][]string{ {"go", "test", "./...", "-count=1"},