122 lines
2.9 KiB
Go
122 lines
2.9 KiB
Go
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")
|
||
|
|
}
|
||
|
|
}
|