walk_test.go (6589B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package hugofs
15
16 import (
17 "context"
18 "fmt"
19 "os"
20 "path/filepath"
21 "runtime"
22 "strings"
23 "testing"
24
25 "errors"
26
27 "github.com/gohugoio/hugo/common/para"
28 "github.com/gohugoio/hugo/htesting"
29
30 "github.com/spf13/afero"
31
32 qt "github.com/frankban/quicktest"
33 )
34
35 func TestWalk(t *testing.T) {
36 c := qt.New(t)
37
38 fs := NewBaseFileDecorator(afero.NewMemMapFs())
39
40 afero.WriteFile(fs, "b.txt", []byte("content"), 0777)
41 afero.WriteFile(fs, "c.txt", []byte("content"), 0777)
42 afero.WriteFile(fs, "a.txt", []byte("content"), 0777)
43
44 names, err := collectFilenames(fs, "", "")
45
46 c.Assert(err, qt.IsNil)
47 c.Assert(names, qt.DeepEquals, []string{"a.txt", "b.txt", "c.txt"})
48 }
49
50 func TestWalkRootMappingFs(t *testing.T) {
51 c := qt.New(t)
52
53 prepare := func(c *qt.C) afero.Fs {
54 fs := NewBaseFileDecorator(afero.NewMemMapFs())
55
56 testfile := "test.txt"
57
58 c.Assert(afero.WriteFile(fs, filepath.Join("a/b", testfile), []byte("some content"), 0755), qt.IsNil)
59 c.Assert(afero.WriteFile(fs, filepath.Join("c/d", testfile), []byte("some content"), 0755), qt.IsNil)
60 c.Assert(afero.WriteFile(fs, filepath.Join("e/f", testfile), []byte("some content"), 0755), qt.IsNil)
61
62 rm := []RootMapping{
63 {
64 From: "static/b",
65 To: "e/f",
66 },
67 {
68 From: "static/a",
69 To: "c/d",
70 },
71
72 {
73 From: "static/c",
74 To: "a/b",
75 },
76 }
77
78 rfs, err := NewRootMappingFs(fs, rm...)
79 c.Assert(err, qt.IsNil)
80 return afero.NewBasePathFs(rfs, "static")
81 }
82
83 c.Run("Basic", func(c *qt.C) {
84 bfs := prepare(c)
85
86 names, err := collectFilenames(bfs, "", "")
87
88 c.Assert(err, qt.IsNil)
89 c.Assert(names, qt.DeepEquals, []string{"a/test.txt", "b/test.txt", "c/test.txt"})
90 })
91
92 c.Run("Para", func(c *qt.C) {
93 bfs := prepare(c)
94
95 p := para.New(4)
96 r, _ := p.Start(context.Background())
97
98 for i := 0; i < 8; i++ {
99 r.Run(func() error {
100 _, err := collectFilenames(bfs, "", "")
101 if err != nil {
102 return err
103 }
104 fi, err := bfs.Stat("b/test.txt")
105 if err != nil {
106 return err
107 }
108 meta := fi.(FileMetaInfo).Meta()
109 if meta.Filename == "" {
110 return errors.New("fail")
111 }
112 return nil
113 })
114 }
115
116 c.Assert(r.Wait(), qt.IsNil)
117 })
118 }
119
120 func skipSymlink() bool {
121 if runtime.GOOS != "windows" {
122 return false
123 }
124 if os.Getenv("GITHUB_ACTION") != "" {
125 // TODO(bep) figure out why this fails on GitHub Actions.
126 return true
127 }
128 return os.Getenv("CI") == ""
129 }
130
131 func TestWalkSymbolicLink(t *testing.T) {
132 if skipSymlink() {
133 t.Skip("Skip; os.Symlink needs administrator rights on Windows")
134 }
135 c := qt.New(t)
136 workDir, clean, err := htesting.CreateTempDir(Os, "hugo-walk-sym")
137 c.Assert(err, qt.IsNil)
138 defer clean()
139 wd, _ := os.Getwd()
140 defer func() {
141 os.Chdir(wd)
142 }()
143
144 fs := NewBaseFileDecorator(Os)
145
146 blogDir := filepath.Join(workDir, "blog")
147 docsDir := filepath.Join(workDir, "docs")
148 blogReal := filepath.Join(blogDir, "real")
149 blogRealSub := filepath.Join(blogReal, "sub")
150 c.Assert(os.MkdirAll(blogRealSub, 0777), qt.IsNil)
151 c.Assert(os.MkdirAll(docsDir, 0777), qt.IsNil)
152 afero.WriteFile(fs, filepath.Join(blogRealSub, "a.txt"), []byte("content"), 0777)
153 afero.WriteFile(fs, filepath.Join(docsDir, "b.txt"), []byte("content"), 0777)
154
155 os.Chdir(blogDir)
156 c.Assert(os.Symlink("real", "symlinked"), qt.IsNil)
157 os.Chdir(blogReal)
158 c.Assert(os.Symlink("../real", "cyclic"), qt.IsNil)
159 os.Chdir(docsDir)
160 c.Assert(os.Symlink("../blog/real/cyclic", "docsreal"), qt.IsNil)
161
162 t.Run("OS Fs", func(t *testing.T) {
163 c := qt.New(t)
164
165 names, err := collectFilenames(fs, workDir, workDir)
166 c.Assert(err, qt.IsNil)
167
168 c.Assert(names, qt.DeepEquals, []string{"blog/real/sub/a.txt", "blog/symlinked/sub/a.txt", "docs/b.txt"})
169 })
170
171 t.Run("BasePath Fs", func(t *testing.T) {
172 c := qt.New(t)
173
174 docsFs := afero.NewBasePathFs(fs, docsDir)
175
176 names, err := collectFilenames(docsFs, "", "")
177 c.Assert(err, qt.IsNil)
178
179 // Note: the docsreal folder is considered cyclic when walking from the root, but this works.
180 c.Assert(names, qt.DeepEquals, []string{"b.txt", "docsreal/sub/a.txt"})
181 })
182 }
183
184 func collectFilenames(fs afero.Fs, base, root string) ([]string, error) {
185 var names []string
186
187 walkFn := func(path string, info FileMetaInfo, err error) error {
188 if err != nil {
189 return err
190 }
191
192 if info.IsDir() {
193 return nil
194 }
195
196 filename := info.Meta().Path
197 filename = filepath.ToSlash(filename)
198
199 names = append(names, filename)
200
201 return nil
202 }
203
204 w := NewWalkway(WalkwayConfig{Fs: fs, BasePath: base, Root: root, WalkFn: walkFn})
205
206 err := w.Walk()
207
208 return names, err
209 }
210
211 func collectFileinfos(fs afero.Fs, base, root string) ([]FileMetaInfo, error) {
212 var fis []FileMetaInfo
213
214 walkFn := func(path string, info FileMetaInfo, err error) error {
215 if err != nil {
216 return err
217 }
218
219 fis = append(fis, info)
220
221 return nil
222 }
223
224 w := NewWalkway(WalkwayConfig{Fs: fs, BasePath: base, Root: root, WalkFn: walkFn})
225
226 err := w.Walk()
227
228 return fis, err
229 }
230
231 func BenchmarkWalk(b *testing.B) {
232 c := qt.New(b)
233 fs := NewBaseFileDecorator(afero.NewMemMapFs())
234
235 writeFiles := func(dir string, numfiles int) {
236 for i := 0; i < numfiles; i++ {
237 filename := filepath.Join(dir, fmt.Sprintf("file%d.txt", i))
238 c.Assert(afero.WriteFile(fs, filename, []byte("content"), 0777), qt.IsNil)
239 }
240 }
241
242 const numFilesPerDir = 20
243
244 writeFiles("root", numFilesPerDir)
245 writeFiles("root/l1_1", numFilesPerDir)
246 writeFiles("root/l1_1/l2_1", numFilesPerDir)
247 writeFiles("root/l1_1/l2_2", numFilesPerDir)
248 writeFiles("root/l1_2", numFilesPerDir)
249 writeFiles("root/l1_2/l2_1", numFilesPerDir)
250 writeFiles("root/l1_3", numFilesPerDir)
251
252 walkFn := func(path string, info FileMetaInfo, err error) error {
253 if err != nil {
254 return err
255 }
256 if info.IsDir() {
257 return nil
258 }
259
260 filename := info.Meta().Filename
261 if !strings.HasPrefix(filename, "root") {
262 return errors.New(filename)
263 }
264
265 return nil
266 }
267
268 b.ResetTimer()
269 for i := 0; i < b.N; i++ {
270 w := NewWalkway(WalkwayConfig{Fs: fs, Root: "root", WalkFn: walkFn})
271
272 if err := w.Walk(); err != nil {
273 b.Fatal(err)
274 }
275 }
276 }