-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathlog_test.go
More file actions
210 lines (169 loc) · 6.51 KB
/
Copy pathlog_test.go
File metadata and controls
210 lines (169 loc) · 6.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package pairec
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/alibaba/pairec/v2/recconf"
)
func setupTestLogDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
// override package-level logDir
logDir = dir
return dir
}
func createFileWithAge(t *testing.T, dir, name string, content []byte, age time.Duration) string {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, content, 0644); err != nil {
t.Fatal(err)
}
oldTime := time.Now().Add(-age)
if err := os.Chtimes(path, oldTime, oldTime); err != nil {
t.Fatal(err)
}
return path
}
// TestClearOnce_SymlinkNotDeletedByRetention verifies that a symlink is NOT
// removed even when its mtime is older than the retention threshold.
func TestClearOnce_SymlinkNotDeletedByRetention(t *testing.T) {
dir := setupTestLogDir(t)
// create a real log file (target of symlink)
targetPath := createFileWithAge(t, dir, "appd.host.log.INFO.20260101", []byte("log data"), 1*time.Hour)
// create symlink pointing to the target
linkPath := filepath.Join(dir, "appd.INFO")
if err := os.Symlink(targetPath, linkPath); err != nil {
t.Fatal(err)
}
// RetensionDays=0 means pointTime = now, so any file with mtime < now is "expired".
// The symlink mtime is in the past, so without the fix it would be deleted.
config := recconf.LogConfig{
RetensionDays: 0,
DiskSize: 20,
}
clearOnce(config)
// symlink must still exist
if _, err := os.Lstat(linkPath); os.IsNotExist(err) {
t.Fatal("symlink appd.INFO was incorrectly deleted by retention logic")
}
// target file should also still exist (it's recent enough with 1h age vs 0 days retention
// actually with RetensionDays=0, pointTime=now, target with mtime 1h ago would be deleted)
// Let's just verify the symlink survives - that's the core assertion.
}
// TestClearOnce_SymlinkNotCountedInTotalSize verifies that symlinks are excluded
// from the totalSize calculation and the capacity-based deletion list.
func TestClearOnce_SymlinkNotCountedInTotalSize(t *testing.T) {
dir := setupTestLogDir(t)
// create a large regular file that alone does NOT exceed threshold
// DiskSize=1 GB, threshold = 0.8 GB. We create a small file.
smallContent := make([]byte, 1024) // 1KB
createFileWithAge(t, dir, "small.log", smallContent, 1*time.Minute)
// create a symlink - if incorrectly counted, it shouldn't affect anything meaningful,
// but the key test is it's not in the deletion list
targetPath := filepath.Join(dir, "small.log")
linkPath := filepath.Join(dir, "appd.INFO")
if err := os.Symlink(targetPath, linkPath); err != nil {
t.Fatal(err)
}
config := recconf.LogConfig{
RetensionDays: 3,
DiskSize: 1,
}
clearOnce(config)
// both files should still exist
if _, err := os.Lstat(linkPath); os.IsNotExist(err) {
t.Fatal("symlink was incorrectly deleted during capacity cleanup")
}
if _, err := os.Stat(filepath.Join(dir, "small.log")); os.IsNotExist(err) {
t.Fatal("regular file was incorrectly deleted")
}
}
// TestClearOnce_ExpiredRegularFileDeleted verifies that normal (non-symlink) files
// older than RetensionDays are still properly removed.
func TestClearOnce_ExpiredRegularFileDeleted(t *testing.T) {
dir := setupTestLogDir(t)
// create an old regular file (5 days old, retention = 3 days)
oldFile := createFileWithAge(t, dir, "old.log", []byte("old data"), 5*24*time.Hour)
// create a recent regular file (1 hour old)
recentFile := createFileWithAge(t, dir, "recent.log", []byte("recent data"), 1*time.Hour)
config := recconf.LogConfig{
RetensionDays: 3,
DiskSize: 20,
}
clearOnce(config)
// old file should be deleted
if _, err := os.Stat(oldFile); !os.IsNotExist(err) {
t.Fatal("expired regular file was NOT deleted")
}
// recent file should still exist
if _, err := os.Stat(recentFile); os.IsNotExist(err) {
t.Fatal("recent regular file was incorrectly deleted")
}
}
// TestClearOnce_CapacityDeletion verifies that when total file size exceeds the
// threshold, the oldest files are deleted first, and symlinks are never touched.
// Using DiskSize=0 makes sizeThreshold=0, so any non-empty totalSize triggers deletion.
func TestClearOnce_CapacityDeletion(t *testing.T) {
dir := setupTestLogDir(t)
// create two regular files with different ages
createFileWithAge(t, dir, "a.log", []byte("data-a"), 1*time.Minute)
createFileWithAge(t, dir, "b.log", []byte("data-b-longer-content"), 2*time.Minute)
// symlink should survive capacity cleanup
linkPath := filepath.Join(dir, "appd.INFO")
if err := os.Symlink(filepath.Join(dir, "a.log"), linkPath); err != nil {
t.Fatal(err)
}
// DiskSize=0 -> threshold=0, any totalSize > 0 triggers capacity deletion
config := recconf.LogConfig{
RetensionDays: 30,
DiskSize: 0,
}
clearOnce(config)
// symlink must survive
if _, err := os.Lstat(linkPath); os.IsNotExist(err) {
t.Fatal("symlink was deleted during capacity cleanup")
}
// at least one regular file should have been deleted (oldest first)
aExists := fileExists(filepath.Join(dir, "a.log"))
bExists := fileExists(filepath.Join(dir, "b.log"))
if aExists && bExists {
t.Fatal("expected at least one regular file to be deleted by capacity cleanup")
}
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
// TestClearOnce_SymlinkSurvivesBothPaths is a combined scenario: symlink with old
// mtime coexists with expired regular files. After clearOnce, symlink must remain
// while expired regular files are removed.
func TestClearOnce_SymlinkSurvivesBothPaths(t *testing.T) {
dir := setupTestLogDir(t)
// target log file (recent, should survive)
targetPath := createFileWithAge(t, dir, "appd.host.log.INFO.20260722", []byte("active log"), 1*time.Hour)
// symlink with old creation time (simulated by RetensionDays=0 making everything "expired")
linkPath := filepath.Join(dir, "appd.INFO")
if err := os.Symlink(targetPath, linkPath); err != nil {
t.Fatal(err)
}
// an expired regular file that SHOULD be deleted
expiredFile := createFileWithAge(t, dir, "old_rotated.log", []byte("stale"), 10*24*time.Hour)
config := recconf.LogConfig{
RetensionDays: 3,
DiskSize: 20,
}
clearOnce(config)
// symlink must survive
if _, err := os.Lstat(linkPath); os.IsNotExist(err) {
t.Fatal("symlink appd.INFO was deleted")
}
// target (recent) must survive
if _, err := os.Stat(targetPath); os.IsNotExist(err) {
t.Fatal("recent target log file was deleted")
}
// expired regular file must be gone
if _, err := os.Stat(expiredFile); !os.IsNotExist(err) {
t.Fatal("expired regular file was NOT deleted")
}
}