package loggerx import ( "fmt" "os" "path/filepath" "strings" "sync" "testing" "time" ) // removeWithRetry 遇到「文件被占用」应重试;占用解除后必须删掉 func TestRemoveWithRetryHandlesTransientLock(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "locked.log") if err := os.WriteFile(path, []byte("data"), 0644); err != nil { t.Fatal(err) } // 先独占打开,制造占用 holder, err := os.Open(path) if err != nil { t.Fatal(err) } locked := make(chan struct{}) release := make(chan struct{}) closed := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() close(locked) <-release _ = holder.Close() close(closed) }() <-locked // 120ms 后释放占用;退避总时长(20+40+80+160+320+640 ≈ 1.2s)要能覆盖 go func() { time.Sleep(120 * time.Millisecond) close(release) }() start := time.Now() err = removeWithRetry(path, 7) elapsed := time.Since(start) <-closed wg.Wait() if err != nil { t.Fatalf("占用解除后仍未删除成功: %v", err) } if _, statErr := os.Stat(path); !os.IsNotExist(statErr) { t.Errorf("文件仍然存在") } t.Logf("重试 %v 后删除成功", elapsed.Round(time.Millisecond)) } // 不可重试的错误(文件不存在)应立即返回,不做无谓等待 func TestRemoveWithRetryFastFail(t *testing.T) { dir := t.TempDir() missing := filepath.Join(dir, "nope.log") start := time.Now() err := removeWithRetry(missing, 5) elapsed := time.Since(start) if err == nil { t.Fatal("删除不存在的文件应该返回错误") } if elapsed > 100*time.Millisecond { t.Errorf("不该对不可重试的错误做退避等待,耗时 %v", elapsed) } } // isTransientRemoveErr 判定 func TestIsTransientRemoveErr(t *testing.T) { cases := []struct { msg string want bool }{ {"remove x.log: The process cannot access the file because it is being used by another process.", true}, {"sharing violation", true}, {"resource busy", true}, {"permission denied", false}, {"no such file or directory", false}, } for _, c := range cases { got := isTransientRemoveErr(fmt.Errorf("%s", c.msg)) if got != c.want { t.Errorf("isTransientRemoveErr(%q) = %v, 期望 %v", c.msg, got, c.want) } } if isTransientRemoveErr(nil) { t.Error("nil 不应判为可重试") } } // 压缩完成后,源文件与 .gz 不应长期并存 func TestCompressCleansSourceFile(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "a.log") if err := os.WriteFile(src, []byte(strings.Repeat("log line\n", 500)), 0644); err != nil { t.Fatal(err) } if err := gzipFile(src, 1); err != nil { t.Fatalf("gzipFile: %v", err) } if _, err := os.Stat(src + ".gz"); err != nil { t.Fatalf("没有生成 .gz: %v", err) } if _, err := os.Stat(src); !os.IsNotExist(err) { t.Errorf("源文件没有被删除: %v", err) } if _, err := os.Stat(src + ".gz.tmp"); !os.IsNotExist(err) { t.Errorf("残留了 .gz.tmp") } }