package starlog import ( "os" "path/filepath" "strconv" "testing" "time" ) func writeFileWithModTime(t *testing.T, path string, modTime time.Time) { t.Helper() if err := os.WriteFile(path, []byte("x"), 0644); err != nil { t.Fatalf("write file failed: %v", err) } if err := os.Chtimes(path, modTime, modTime); err != nil { t.Fatalf("set mod time failed: %v", err) } } func TestApplyRotateManageOptionsCompress(t *testing.T) { dir := testBinDir(t) current := filepath.Join(dir, "app.log") archive := filepath.Join(dir, "app.log.1") writeFileWithModTime(t, current, time.Now()) writeFileWithModTime(t, archive, time.Now()) if err := ApplyRotateManageOptions(archive, current, RotateManageOptions{ Compress: true, Pattern: "app.log.*", }); err != nil { t.Fatalf("ApplyRotateManageOptions compress failed: %v", err) } if _, err := os.Stat(archive + ".gz"); err != nil { t.Fatalf("compressed file should exist: %v", err) } if _, err := os.Stat(archive); !os.IsNotExist(err) { t.Fatalf("original archive file should be removed after compression") } } func TestApplyRotateManageOptionsMaxBackups(t *testing.T) { dir := testBinDir(t) current := filepath.Join(dir, "app.log") writeFileWithModTime(t, current, time.Now()) baseTime := time.Now().Add(-4 * time.Hour) for i := 1; i <= 4; i++ { path := filepath.Join(dir, "app.log."+strconv.Itoa(i)) writeFileWithModTime(t, path, baseTime.Add(time.Duration(i)*time.Hour)) } if err := ApplyRotateManageOptions(filepath.Join(dir, "app.log.4"), current, RotateManageOptions{ MaxBackups: 2, Pattern: "app.log.*", }); err != nil { t.Fatalf("ApplyRotateManageOptions max backups failed: %v", err) } matches, err := filepath.Glob(filepath.Join(dir, "app.log.*")) if err != nil { t.Fatalf("glob failed: %v", err) } if len(matches) != 2 { t.Fatalf("should keep only 2 backup files, got %d (%v)", len(matches), matches) } } func TestApplyRotateManageOptionsMaxAge(t *testing.T) { dir := testBinDir(t) current := filepath.Join(dir, "app.log") oldBackup := filepath.Join(dir, "app.log.old") newBackup := filepath.Join(dir, "app.log.new") writeFileWithModTime(t, current, time.Now()) writeFileWithModTime(t, oldBackup, time.Now().Add(-4*time.Hour)) writeFileWithModTime(t, newBackup, time.Now().Add(-10*time.Minute)) if err := ApplyRotateManageOptions(newBackup, current, RotateManageOptions{ MaxAge: time.Hour, Pattern: "app.log.*", }); err != nil { t.Fatalf("ApplyRotateManageOptions max age failed: %v", err) } if _, err := os.Stat(oldBackup); !os.IsNotExist(err) { t.Fatalf("old backup should be removed by max age") } if _, err := os.Stat(newBackup); err != nil { t.Fatalf("new backup should be kept: %v", err) } } func TestWithRotateManageOptionsKeepsPreviousHook(t *testing.T) { archive := NewRotatePolicyArchive(&rotateWhenNonEmptyPolicy{}, 1) var called bool archive.SetHookAfterArchive(func(*StarLogger, string, string, os.FileInfo) error { called = true return nil }) WithRotateManageOptions(archive, RotateManageOptions{}) dir := testBinDir(t) current := filepath.Join(dir, "app.log") archived := filepath.Join(dir, "app.log.1") writeFileWithModTime(t, current, time.Now()) writeFileWithModTime(t, archived, time.Now()) hook := archive.HookAfterArchive() if hook == nil { t.Fatalf("hook after archive should not be nil") } if err := hook(nil, archived, current, nil); err != nil { t.Fatalf("hook execution failed: %v", err) } if !called { t.Fatalf("previous hook should still run after wrapping") } } func TestIsManagedBackupNameDefaultSafeMatch(t *testing.T) { base := "app.log" stem := "app" tests := []struct { name string matched bool }{ {name: "app.log.20260318", matched: true}, {name: "app.log-1", matched: true}, {name: "app.log.1.gz", matched: true}, {name: "app.log.bak", matched: true}, {name: "app_20260318.log", matched: true}, {name: "app.log.current", matched: false}, {name: "app.log.backup", matched: false}, {name: "app-config.log", matched: false}, {name: "other.log.1", matched: false}, } for _, tc := range tests { matched, err := isManagedBackupName(tc.name, base, stem, "") if err != nil { t.Fatalf("isManagedBackupName(%q) returned err: %v", tc.name, err) } if matched != tc.matched { t.Fatalf("isManagedBackupName(%q)=%v, want %v", tc.name, matched, tc.matched) } } }