1
2
3
4 package pwalkdir
5
6 import (
7 "errors"
8 "io/fs"
9 "math/rand"
10 "os"
11 "path/filepath"
12 "runtime"
13 "sync/atomic"
14 "testing"
15 "time"
16 )
17
18 func TestWalkDir(t *testing.T) {
19 var count uint32
20 concurrency := runtime.NumCPU() * 2
21
22 dir, total, err := prepareTestSet(3, 2, 1)
23 if err != nil {
24 t.Fatalf("dataset creation failed: %v", err)
25 }
26 defer os.RemoveAll(dir)
27
28 err = WalkN(dir,
29 func(_ string, _ fs.DirEntry, _ error) error {
30 atomic.AddUint32(&count, 1)
31 return nil
32 },
33 concurrency)
34
35 if err != nil {
36 t.Errorf("Walk failed: %v", err)
37 }
38 if count != uint32(total) {
39 t.Errorf("File count mismatch: found %d, expected %d", count, total)
40 }
41
42 t.Logf("concurrency: %d, files found: %d\n", concurrency, count)
43 }
44
45 func TestWalkDirManyErrors(t *testing.T) {
46 var count uint32
47
48 dir, total, err := prepareTestSet(3, 3, 2)
49 if err != nil {
50 t.Fatalf("dataset creation failed: %v", err)
51 }
52 defer os.RemoveAll(dir)
53
54 max := uint32(total / 2)
55 e42 := errors.New("42")
56 err = Walk(dir,
57 func(p string, e fs.DirEntry, _ error) error {
58 if atomic.AddUint32(&count, 1) > max {
59 return e42
60 }
61 return nil
62 })
63 t.Logf("found %d of %d files", count, total)
64
65 if err == nil {
66 t.Error("Walk succeeded, but error is expected")
67 if count != uint32(total) {
68 t.Errorf("File count mismatch: found %d, expected %d", count, total)
69 }
70 }
71 }
72
73 func makeManyDirs(prefix string, levels, dirs, files int) (count int, err error) {
74 for d := 0; d < dirs; d++ {
75 var dir string
76 dir, err = os.MkdirTemp(prefix, "d-")
77 if err != nil {
78 return
79 }
80 count++
81 for f := 0; f < files; f++ {
82 var fi *os.File
83 fi, err = os.CreateTemp(dir, "f-")
84 if err != nil {
85 return count, err
86 }
87 fi.Close()
88 count++
89 }
90 if levels == 0 {
91 continue
92 }
93 var c int
94 if c, err = makeManyDirs(dir, levels-1, dirs, files); err != nil {
95 return
96 }
97 count += c
98 }
99
100 return
101 }
102
103
104
105
106
107
108 func prepareTestSet(levels, dirs, files int) (dir string, total int, err error) {
109 dir, err = os.MkdirTemp(".", "pwalk-test-")
110 if err != nil {
111 return
112 }
113 total, err = makeManyDirs(dir, levels, dirs, files)
114 if err != nil && total > 0 {
115 _ = os.RemoveAll(dir)
116 dir = ""
117 total = 0
118 return
119 }
120 total++
121
122 return
123 }
124
125 type walkerFunc func(root string, walkFn fs.WalkDirFunc) error
126
127 func genWalkN(n int) walkerFunc {
128 return func(root string, walkFn fs.WalkDirFunc) error {
129 return WalkN(root, walkFn, n)
130 }
131 }
132
133 func BenchmarkWalk(b *testing.B) {
134 const (
135 levels = 5
136 dirs = 3
137 files = 8
138 )
139
140 benchmarks := []struct {
141 walk fs.WalkDirFunc
142 name string
143 }{
144 {name: "Empty", walk: cbEmpty},
145 {name: "ReadFile", walk: cbReadFile},
146 {name: "ChownChmod", walk: cbChownChmod},
147 {name: "RandomSleep", walk: cbRandomSleep},
148 }
149
150 walkers := []struct {
151 walker walkerFunc
152 name string
153 }{
154 {name: "filepath.WalkDir", walker: filepath.WalkDir},
155 {name: "pwalkdir.Walk", walker: Walk},
156
157 {name: "pwalkdir.Walk1", walker: genWalkN(1)},
158 {name: "pwalkdir.Walk2", walker: genWalkN(2)},
159 {name: "pwalkdir.Walk4", walker: genWalkN(4)},
160 {name: "pwalkdir.Walk8", walker: genWalkN(8)},
161 {name: "pwalkdir.Walk16", walker: genWalkN(16)},
162 {name: "pwalkdir.Walk32", walker: genWalkN(32)},
163 {name: "pwalkdir.Walk64", walker: genWalkN(64)},
164 {name: "pwalkdir.Walk128", walker: genWalkN(128)},
165 {name: "pwalkdir.Walk256", walker: genWalkN(256)},
166 }
167
168 dir, total, err := prepareTestSet(levels, dirs, files)
169 if err != nil {
170 b.Fatalf("dataset creation failed: %v", err)
171 }
172 defer os.RemoveAll(dir)
173 b.Logf("dataset: %d levels x %d dirs x %d files, total entries: %d", levels, dirs, files, total)
174
175 for _, bm := range benchmarks {
176 for _, w := range walkers {
177 walker := w.walker
178 walkFn := bm.walk
179
180 if err := w.walker(dir, bm.walk); err != nil {
181 b.Errorf("walk failed: %v", err)
182 }
183
184 b.Run(bm.name+"/"+w.name, func(b *testing.B) {
185 for i := 0; i < b.N; i++ {
186 if err := walker(dir, walkFn); err != nil {
187 b.Errorf("walk failed: %v", err)
188 }
189 }
190 })
191 }
192 }
193 }
194
195 func cbEmpty(_ string, _ fs.DirEntry, _ error) error {
196 return nil
197 }
198
199 func cbChownChmod(path string, e fs.DirEntry, _ error) error {
200 _ = os.Chown(path, 0, 0)
201 mode := os.FileMode(0o644)
202 if e.IsDir() {
203 mode = os.FileMode(0o755)
204 }
205 _ = os.Chmod(path, mode)
206
207 return nil
208 }
209
210 func cbReadFile(path string, e fs.DirEntry, _ error) error {
211 var err error
212 if e.Type().IsRegular() {
213 _, err = os.ReadFile(path)
214 }
215 return err
216 }
217
218 func cbRandomSleep(_ string, _ fs.DirEntry, _ error) error {
219 time.Sleep(time.Duration(rand.Intn(500)) * time.Microsecond)
220 return nil
221 }
222
View as plain text