Skip to content

Commit 1bd8996

Browse files
committed
Add hardlink unit tests
1. Adds comprehensive tests for the hardlink manager functionality: - CreateLink tests - GetLink tests - Cleanup tests - Persistence/Restoration tests - Concurrent operation tests - Disabled functionality tests 2. Adds tests for disabled hardlink functionality 3. Verifies proper error handling and edge cases 4. Tests the integration with the directory cache The tests ensure that: - Hardlinks are created correctly and point to the same inode - Invalid links are handled gracefully - Cleanup works as expected - Link information is properly persisted and restored - The system works correctly under concurrent access - The cache works properly when hardlinks are disabled Signed-off-by: ChengyuZhu6 <hudson@cyzhu.com>
1 parent c10c085 commit 1bd8996

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed

cache/hardlink_test.go

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cache
18+
19+
import (
20+
"os"
21+
"path/filepath"
22+
"testing"
23+
"time"
24+
)
25+
26+
// Common test setup helper
27+
func setupTestEnvironment(t *testing.T) (string, string, string, *HardlinkManager) {
28+
tmpDir := t.TempDir()
29+
sourceDir := filepath.Join(tmpDir, "source")
30+
if err := os.MkdirAll(sourceDir, 0700); err != nil {
31+
t.Fatalf("failed to create source dir: %v", err)
32+
}
33+
34+
sourceFile := filepath.Join(sourceDir, "test.txt")
35+
content := []byte("test content")
36+
if err := os.WriteFile(sourceFile, content, 0600); err != nil {
37+
t.Fatalf("failed to create source file: %v", err)
38+
}
39+
40+
hlm, err := NewHardlinkManager(tmpDir)
41+
if err != nil {
42+
t.Fatalf("failed to create hardlink manager: %v", err)
43+
}
44+
45+
t.Cleanup(func() {
46+
if err := hlm.Close(); err != nil {
47+
t.Errorf("failed to close hardlink manager: %v", err)
48+
}
49+
})
50+
51+
return tmpDir, sourceDir, sourceFile, hlm
52+
}
53+
54+
// Test CreateLink functionality
55+
func TestHardlinkManager_CreateLink(t *testing.T) {
56+
_, _, sourceFile, hlm := setupTestEnvironment(t)
57+
58+
t.Run("Success", func(t *testing.T) {
59+
key := "test-key"
60+
if err := hlm.CreateLink(key, sourceFile); err != nil {
61+
t.Fatalf("CreateLink failed: %v", err)
62+
}
63+
64+
// Verify link exists
65+
linkPath, exists := hlm.GetLink(key)
66+
if !exists {
67+
t.Fatal("link should exist")
68+
}
69+
70+
// Verify hardlink
71+
sourceStat, err := os.Stat(sourceFile)
72+
if err != nil {
73+
t.Fatalf("failed to stat source: %v", err)
74+
}
75+
linkStat, err := os.Stat(linkPath)
76+
if err != nil {
77+
t.Fatalf("failed to stat link: %v", err)
78+
}
79+
if !os.SameFile(sourceStat, linkStat) {
80+
t.Fatal("should hardlink files")
81+
}
82+
})
83+
84+
t.Run("NonExistentSource", func(t *testing.T) {
85+
err := hlm.CreateLink("bad-key", "nonexistent")
86+
if err == nil {
87+
t.Fatal("should fail with nonexistent source")
88+
}
89+
})
90+
}
91+
92+
// Test GetLink functionality
93+
func TestHardlinkManager_GetLink(t *testing.T) {
94+
_, _, sourceFile, hlm := setupTestEnvironment(t)
95+
96+
t.Run("ExistingLink", func(t *testing.T) {
97+
key := "get-test"
98+
if err := hlm.CreateLink(key, sourceFile); err != nil {
99+
t.Fatalf("CreateLink failed: %v", err)
100+
}
101+
102+
linkPath, exists := hlm.GetLink(key)
103+
if !exists {
104+
t.Fatal("link should exist")
105+
}
106+
if linkPath == "" {
107+
t.Fatal("link path should not be empty")
108+
}
109+
})
110+
111+
t.Run("NonExistentLink", func(t *testing.T) {
112+
_, exists := hlm.GetLink("nonexistent")
113+
if exists {
114+
t.Fatal("should not find nonexistent link")
115+
}
116+
})
117+
}
118+
119+
// Test cleanup functionality
120+
func TestHardlinkManager_Cleanup(t *testing.T) {
121+
tmpDir, _, sourceFile, _ := setupTestEnvironment(t)
122+
123+
cleanupDir := filepath.Join(tmpDir, "cleanup")
124+
cleanupHLM, err := NewHardlinkManager(cleanupDir)
125+
if err != nil {
126+
t.Fatalf("failed to create cleanup manager: %v", err)
127+
}
128+
t.Cleanup(func() {
129+
if err := cleanupHLM.Close(); err != nil {
130+
t.Errorf("failed to close cleanup manager: %v", err)
131+
}
132+
os.RemoveAll(cleanupDir)
133+
})
134+
135+
t.Run("ExpiredLink", func(t *testing.T) {
136+
key := "cleanup-test"
137+
if err := cleanupHLM.CreateLink(key, sourceFile); err != nil {
138+
t.Fatalf("CreateLink failed: %v", err)
139+
}
140+
141+
// Force persist
142+
if err := cleanupHLM.persist(); err != nil {
143+
t.Fatalf("failed to persist: %v", err)
144+
}
145+
146+
// Set link as expired
147+
cleanupHLM.mu.Lock()
148+
cleanupHLM.links[key].LastUsed = time.Now().Add(-31 * 24 * time.Hour)
149+
cleanupHLM.mu.Unlock()
150+
151+
// Run cleanup with timeout
152+
done := make(chan error)
153+
go func() {
154+
done <- cleanupHLM.cleanup()
155+
}()
156+
select {
157+
case err := <-done:
158+
if err != nil {
159+
t.Fatalf("cleanup failed: %v", err)
160+
}
161+
case <-time.After(5 * time.Second):
162+
t.Fatal("cleanup timed out")
163+
}
164+
165+
// Verify cleanup
166+
if _, exists := cleanupHLM.GetLink(key); exists {
167+
t.Fatal("expired link should be cleaned up")
168+
}
169+
})
170+
}
171+
172+
// Test persistence and restoration
173+
func TestHardlinkManager_PersistRestore(t *testing.T) {
174+
tmpDir, _, sourceFile, hlm := setupTestEnvironment(t)
175+
176+
t.Run("PersistAndRestore", func(t *testing.T) {
177+
key := "persist-test"
178+
if err := hlm.CreateLink(key, sourceFile); err != nil {
179+
t.Fatalf("CreateLink failed: %v", err)
180+
}
181+
182+
// Force persist
183+
if err := hlm.persist(); err != nil {
184+
t.Fatalf("failed to persist: %v", err)
185+
}
186+
time.Sleep(100 * time.Millisecond)
187+
188+
// Create new manager
189+
hlm2, err := NewHardlinkManager(tmpDir)
190+
if err != nil {
191+
t.Fatalf("failed to create second manager: %v", err)
192+
}
193+
t.Cleanup(func() {
194+
if err := hlm2.Close(); err != nil {
195+
t.Errorf("failed to close second manager: %v", err)
196+
}
197+
})
198+
199+
// Verify restoration
200+
linkPath, exists := hlm2.GetLink(key)
201+
if !exists {
202+
t.Fatal("link should be restored")
203+
}
204+
205+
// Verify hardlink validity
206+
sourceStat, err := os.Stat(sourceFile)
207+
if err != nil {
208+
t.Fatalf("failed to stat source: %v", err)
209+
}
210+
linkStat, err := os.Stat(linkPath)
211+
if err != nil {
212+
t.Fatalf("failed to stat link: %v", err)
213+
}
214+
if !os.SameFile(sourceStat, linkStat) {
215+
t.Fatal("should hardlink restored files")
216+
}
217+
})
218+
}
219+
220+
// Test concurrent operations
221+
func TestHardlinkManager_Concurrent(t *testing.T) {
222+
_, _, sourceFile, hlm := setupTestEnvironment(t)
223+
224+
done := make(chan bool)
225+
timeout := time.After(10 * time.Second)
226+
227+
go func() {
228+
for i := 0; i < 100; i++ {
229+
hlm.GetLink("test-key")
230+
}
231+
done <- true
232+
}()
233+
234+
go func() {
235+
for i := 0; i < 100; i++ {
236+
hlm.CreateLink("test-key"+string(rune(i)), sourceFile)
237+
}
238+
done <- true
239+
}()
240+
241+
for i := 0; i < 2; i++ {
242+
select {
243+
case <-done:
244+
continue
245+
case <-timeout:
246+
t.Fatal("concurrent test timed out")
247+
}
248+
}
249+
}
250+
251+
// Test disabled hardlink functionality
252+
func TestHardlinkManagerDisabled(t *testing.T) {
253+
tmpDir := t.TempDir()
254+
dc, err := NewDirectoryCache(tmpDir, DirectoryCacheConfig{
255+
EnableHardlink: false,
256+
})
257+
if err != nil {
258+
t.Fatalf("failed to create cache: %v", err)
259+
}
260+
defer dc.Close()
261+
262+
dirCache := dc.(*directoryCache)
263+
if dirCache.hlManager != nil {
264+
t.Fatal("hardlink manager should be nil when disabled")
265+
}
266+
267+
t.Cleanup(func() {
268+
if err := dc.Close(); err != nil {
269+
t.Errorf("failed to close directory cache: %v", err)
270+
}
271+
})
272+
273+
// Basic operations should work without hardlinks
274+
t.Run("BasicOperations", func(t *testing.T) {
275+
// Write test
276+
w, err := dc.Add("test-key")
277+
if err != nil {
278+
t.Fatalf("Add failed: %v", err)
279+
}
280+
if _, err := w.Write([]byte("test")); err != nil {
281+
t.Fatalf("Write failed: %v", err)
282+
}
283+
if err := w.Commit(); err != nil {
284+
t.Fatalf("Commit failed: %v", err)
285+
}
286+
w.Close()
287+
288+
// Read test
289+
r, err := dc.Get("test-key")
290+
if err != nil {
291+
t.Fatalf("Get failed: %v", err)
292+
}
293+
defer r.Close()
294+
})
295+
}

0 commit comments

Comments
 (0)