basefs_test.go (15429B)
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 filesystems
15
16 import (
17 "errors"
18 "fmt"
19 "os"
20 "path/filepath"
21 "strings"
22 "testing"
23
24 "github.com/gobwas/glob"
25
26 "github.com/gohugoio/hugo/config"
27
28 "github.com/gohugoio/hugo/langs"
29
30 "github.com/spf13/afero"
31
32 qt "github.com/frankban/quicktest"
33 "github.com/gohugoio/hugo/hugofs"
34 "github.com/gohugoio/hugo/hugolib/paths"
35 "github.com/gohugoio/hugo/modules"
36 )
37
38 func initConfig(fs afero.Fs, cfg config.Provider) error {
39 if _, err := langs.LoadLanguageSettings(cfg, nil); err != nil {
40 return err
41 }
42
43 modConfig, err := modules.DecodeConfig(cfg)
44 if err != nil {
45 return err
46 }
47
48 workingDir := cfg.GetString("workingDir")
49 themesDir := cfg.GetString("themesDir")
50 if !filepath.IsAbs(themesDir) {
51 themesDir = filepath.Join(workingDir, themesDir)
52 }
53 globAll := glob.MustCompile("**", '/')
54 modulesClient := modules.NewClient(modules.ClientConfig{
55 Fs: fs,
56 WorkingDir: workingDir,
57 ThemesDir: themesDir,
58 ModuleConfig: modConfig,
59 IgnoreVendor: globAll,
60 })
61
62 moduleConfig, err := modulesClient.Collect()
63 if err != nil {
64 return err
65 }
66
67 if err := modules.ApplyProjectConfigDefaults(cfg, moduleConfig.ActiveModules[0]); err != nil {
68 return err
69 }
70
71 cfg.Set("allModules", moduleConfig.ActiveModules)
72
73 return nil
74 }
75
76 func TestNewBaseFs(t *testing.T) {
77 c := qt.New(t)
78 v := config.NewWithTestDefaults()
79
80 fs := hugofs.NewMem(v)
81
82 themes := []string{"btheme", "atheme"}
83
84 workingDir := filepath.FromSlash("/my/work")
85 v.Set("workingDir", workingDir)
86 v.Set("contentDir", "content")
87 v.Set("themesDir", "themes")
88 v.Set("defaultContentLanguage", "en")
89 v.Set("theme", themes[:1])
90
91 // Write some data to the themes
92 for _, theme := range themes {
93 for _, dir := range []string{"i18n", "data", "archetypes", "layouts"} {
94 base := filepath.Join(workingDir, "themes", theme, dir)
95 filenameTheme := filepath.Join(base, fmt.Sprintf("theme-file-%s.txt", theme))
96 filenameOverlap := filepath.Join(base, "f3.txt")
97 fs.Source.Mkdir(base, 0755)
98 content := []byte(fmt.Sprintf("content:%s:%s", theme, dir))
99 afero.WriteFile(fs.Source, filenameTheme, content, 0755)
100 afero.WriteFile(fs.Source, filenameOverlap, content, 0755)
101 }
102 // Write some files to the root of the theme
103 base := filepath.Join(workingDir, "themes", theme)
104 afero.WriteFile(fs.Source, filepath.Join(base, fmt.Sprintf("theme-root-%s.txt", theme)), []byte(fmt.Sprintf("content:%s", theme)), 0755)
105 afero.WriteFile(fs.Source, filepath.Join(base, "file-theme-root.txt"), []byte(fmt.Sprintf("content:%s", theme)), 0755)
106 }
107
108 afero.WriteFile(fs.Source, filepath.Join(workingDir, "file-root.txt"), []byte("content-project"), 0755)
109
110 afero.WriteFile(fs.Source, filepath.Join(workingDir, "themes", "btheme", "config.toml"), []byte(`
111 theme = ["atheme"]
112 `), 0755)
113
114 setConfigAndWriteSomeFilesTo(fs.Source, v, "contentDir", "mycontent", 3)
115 setConfigAndWriteSomeFilesTo(fs.Source, v, "i18nDir", "myi18n", 4)
116 setConfigAndWriteSomeFilesTo(fs.Source, v, "layoutDir", "mylayouts", 5)
117 setConfigAndWriteSomeFilesTo(fs.Source, v, "staticDir", "mystatic", 6)
118 setConfigAndWriteSomeFilesTo(fs.Source, v, "dataDir", "mydata", 7)
119 setConfigAndWriteSomeFilesTo(fs.Source, v, "archetypeDir", "myarchetypes", 8)
120 setConfigAndWriteSomeFilesTo(fs.Source, v, "assetDir", "myassets", 9)
121 setConfigAndWriteSomeFilesTo(fs.Source, v, "resourceDir", "myrsesource", 10)
122
123 v.Set("publishDir", "public")
124 c.Assert(initConfig(fs.Source, v), qt.IsNil)
125
126 p, err := paths.New(fs, v)
127 c.Assert(err, qt.IsNil)
128
129 bfs, err := NewBase(p, nil)
130 c.Assert(err, qt.IsNil)
131 c.Assert(bfs, qt.Not(qt.IsNil))
132
133 root, err := bfs.I18n.Fs.Open("")
134 c.Assert(err, qt.IsNil)
135 dirnames, err := root.Readdirnames(-1)
136 c.Assert(err, qt.IsNil)
137 c.Assert(dirnames, qt.DeepEquals, []string{"f1.txt", "f2.txt", "f3.txt", "f4.txt", "f3.txt", "theme-file-btheme.txt", "f3.txt", "theme-file-atheme.txt"})
138
139 root, err = bfs.Data.Fs.Open("")
140 c.Assert(err, qt.IsNil)
141 dirnames, err = root.Readdirnames(-1)
142 c.Assert(err, qt.IsNil)
143 c.Assert(dirnames, qt.DeepEquals, []string{"f1.txt", "f2.txt", "f3.txt", "f4.txt", "f5.txt", "f6.txt", "f7.txt", "f3.txt", "theme-file-btheme.txt", "f3.txt", "theme-file-atheme.txt"})
144
145 checkFileCount(bfs.Layouts.Fs, "", c, 7)
146
147 checkFileCount(bfs.Content.Fs, "", c, 3)
148 checkFileCount(bfs.I18n.Fs, "", c, 8) // 4 + 4 themes
149
150 checkFileCount(bfs.Static[""].Fs, "", c, 6)
151 checkFileCount(bfs.Data.Fs, "", c, 11) // 7 + 4 themes
152 checkFileCount(bfs.Archetypes.Fs, "", c, 10) // 8 + 2 themes
153 checkFileCount(bfs.Assets.Fs, "", c, 9)
154 checkFileCount(bfs.Work, "", c, 90)
155
156 c.Assert(bfs.IsData(filepath.Join(workingDir, "mydata", "file1.txt")), qt.Equals, true)
157 c.Assert(bfs.IsI18n(filepath.Join(workingDir, "myi18n", "file1.txt")), qt.Equals, true)
158 c.Assert(bfs.IsLayout(filepath.Join(workingDir, "mylayouts", "file1.txt")), qt.Equals, true)
159 c.Assert(bfs.IsStatic(filepath.Join(workingDir, "mystatic", "file1.txt")), qt.Equals, true)
160 c.Assert(bfs.IsAsset(filepath.Join(workingDir, "myassets", "file1.txt")), qt.Equals, true)
161
162 contentFilename := filepath.Join(workingDir, "mycontent", "file1.txt")
163 c.Assert(bfs.IsContent(contentFilename), qt.Equals, true)
164 rel := bfs.RelContentDir(contentFilename)
165 c.Assert(rel, qt.Equals, "file1.txt")
166
167 // Check Work fs vs theme
168 checkFileContent(bfs.Work, "file-root.txt", c, "content-project")
169 checkFileContent(bfs.Work, "theme-root-atheme.txt", c, "content:atheme")
170
171 // https://github.com/gohugoio/hugo/issues/5318
172 // Check both project and theme.
173 for _, fs := range []afero.Fs{bfs.Archetypes.Fs, bfs.Layouts.Fs} {
174 for _, filename := range []string{"/f1.txt", "/theme-file-atheme.txt"} {
175 filename = filepath.FromSlash(filename)
176 f, err := fs.Open(filename)
177 c.Assert(err, qt.IsNil)
178 f.Close()
179 }
180 }
181 }
182
183 func createConfig() config.Provider {
184 v := config.NewWithTestDefaults()
185 v.Set("contentDir", "mycontent")
186 v.Set("i18nDir", "myi18n")
187 v.Set("staticDir", "mystatic")
188 v.Set("dataDir", "mydata")
189 v.Set("layoutDir", "mylayouts")
190 v.Set("archetypeDir", "myarchetypes")
191 v.Set("assetDir", "myassets")
192 v.Set("resourceDir", "resources")
193 v.Set("publishDir", "public")
194 v.Set("defaultContentLanguage", "en")
195
196 return v
197 }
198
199 func TestNewBaseFsEmpty(t *testing.T) {
200 c := qt.New(t)
201 v := createConfig()
202 fs := hugofs.NewMem(v)
203 c.Assert(initConfig(fs.Source, v), qt.IsNil)
204
205 p, err := paths.New(fs, v)
206 c.Assert(err, qt.IsNil)
207 bfs, err := NewBase(p, nil)
208 c.Assert(err, qt.IsNil)
209 c.Assert(bfs, qt.Not(qt.IsNil))
210 c.Assert(bfs.Archetypes.Fs, qt.Not(qt.IsNil))
211 c.Assert(bfs.Layouts.Fs, qt.Not(qt.IsNil))
212 c.Assert(bfs.Data.Fs, qt.Not(qt.IsNil))
213 c.Assert(bfs.I18n.Fs, qt.Not(qt.IsNil))
214 c.Assert(bfs.Work, qt.Not(qt.IsNil))
215 c.Assert(bfs.Content.Fs, qt.Not(qt.IsNil))
216 c.Assert(bfs.Static, qt.Not(qt.IsNil))
217 }
218
219 func TestRealDirs(t *testing.T) {
220 c := qt.New(t)
221 v := createConfig()
222 root, themesDir := t.TempDir(), t.TempDir()
223 v.Set("workingDir", root)
224 v.Set("themesDir", themesDir)
225 v.Set("theme", "mytheme")
226
227 fs := hugofs.NewDefault(v)
228 sfs := fs.Source
229
230 defer func() {
231 os.RemoveAll(root)
232 os.RemoveAll(themesDir)
233 }()
234
235 c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf1"), 0755), qt.IsNil)
236 c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf2"), 0755), qt.IsNil)
237 c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2"), 0755), qt.IsNil)
238 c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3"), 0755), qt.IsNil)
239 c.Assert(sfs.MkdirAll(filepath.Join(root, "resources"), 0755), qt.IsNil)
240 c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "resources"), 0755), qt.IsNil)
241
242 c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "js", "f2"), 0755), qt.IsNil)
243
244 afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf1", "a1.scss")), []byte("content"), 0755)
245 afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
246 afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "a2.scss")), []byte("content"), 0755)
247 afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
248 afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3", "a4.scss")), []byte("content"), 0755)
249
250 afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "resources", "t1.txt")), []byte("content"), 0755)
251 afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p1.txt")), []byte("content"), 0755)
252 afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p2.txt")), []byte("content"), 0755)
253
254 afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "f2", "a1.js")), []byte("content"), 0755)
255 afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "a2.js")), []byte("content"), 0755)
256
257 c.Assert(initConfig(fs.Source, v), qt.IsNil)
258
259 p, err := paths.New(fs, v)
260 c.Assert(err, qt.IsNil)
261 bfs, err := NewBase(p, nil)
262 c.Assert(err, qt.IsNil)
263 c.Assert(bfs, qt.Not(qt.IsNil))
264
265 checkFileCount(bfs.Assets.Fs, "", c, 6)
266
267 realDirs := bfs.Assets.RealDirs("scss")
268 c.Assert(len(realDirs), qt.Equals, 2)
269 c.Assert(realDirs[0], qt.Equals, filepath.Join(root, "myassets/scss"))
270 c.Assert(realDirs[len(realDirs)-1], qt.Equals, filepath.Join(themesDir, "mytheme/assets/scss"))
271
272 c.Assert(bfs.theBigFs, qt.Not(qt.IsNil))
273 }
274
275 func TestStaticFs(t *testing.T) {
276 c := qt.New(t)
277 v := createConfig()
278 workDir := "mywork"
279 v.Set("workingDir", workDir)
280 v.Set("themesDir", "themes")
281 v.Set("theme", []string{"t1", "t2"})
282
283 fs := hugofs.NewMem(v)
284
285 themeStaticDir := filepath.Join(workDir, "themes", "t1", "static")
286 themeStaticDir2 := filepath.Join(workDir, "themes", "t2", "static")
287
288 afero.WriteFile(fs.Source, filepath.Join(workDir, "mystatic", "f1.txt"), []byte("Hugo Rocks!"), 0755)
289 afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f1.txt"), []byte("Hugo Themes Rocks!"), 0755)
290 afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f2.txt"), []byte("Hugo Themes Still Rocks!"), 0755)
291 afero.WriteFile(fs.Source, filepath.Join(themeStaticDir2, "f2.txt"), []byte("Hugo Themes Rocks in t2!"), 0755)
292
293 c.Assert(initConfig(fs.Source, v), qt.IsNil)
294
295 p, err := paths.New(fs, v)
296 c.Assert(err, qt.IsNil)
297 bfs, err := NewBase(p, nil)
298 c.Assert(err, qt.IsNil)
299
300 sfs := bfs.StaticFs("en")
301 checkFileContent(sfs, "f1.txt", c, "Hugo Rocks!")
302 checkFileContent(sfs, "f2.txt", c, "Hugo Themes Still Rocks!")
303 }
304
305 func TestStaticFsMultiHost(t *testing.T) {
306 c := qt.New(t)
307 v := createConfig()
308 workDir := "mywork"
309 v.Set("workingDir", workDir)
310 v.Set("themesDir", "themes")
311 v.Set("theme", "t1")
312 v.Set("defaultContentLanguage", "en")
313
314 langConfig := map[string]any{
315 "no": map[string]any{
316 "staticDir": "static_no",
317 "baseURL": "https://example.org/no/",
318 },
319 "en": map[string]any{
320 "baseURL": "https://example.org/en/",
321 },
322 }
323
324 v.Set("languages", langConfig)
325
326 fs := hugofs.NewMem(v)
327
328 themeStaticDir := filepath.Join(workDir, "themes", "t1", "static")
329
330 afero.WriteFile(fs.Source, filepath.Join(workDir, "mystatic", "f1.txt"), []byte("Hugo Rocks!"), 0755)
331 afero.WriteFile(fs.Source, filepath.Join(workDir, "static_no", "f1.txt"), []byte("Hugo Rocks in Norway!"), 0755)
332
333 afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f1.txt"), []byte("Hugo Themes Rocks!"), 0755)
334 afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f2.txt"), []byte("Hugo Themes Still Rocks!"), 0755)
335
336 c.Assert(initConfig(fs.Source, v), qt.IsNil)
337
338 p, err := paths.New(fs, v)
339 c.Assert(err, qt.IsNil)
340 bfs, err := NewBase(p, nil)
341 c.Assert(err, qt.IsNil)
342 enFs := bfs.StaticFs("en")
343 checkFileContent(enFs, "f1.txt", c, "Hugo Rocks!")
344 checkFileContent(enFs, "f2.txt", c, "Hugo Themes Still Rocks!")
345
346 noFs := bfs.StaticFs("no")
347 checkFileContent(noFs, "f1.txt", c, "Hugo Rocks in Norway!")
348 checkFileContent(noFs, "f2.txt", c, "Hugo Themes Still Rocks!")
349 }
350
351 func TestMakePathRelative(t *testing.T) {
352 c := qt.New(t)
353 v := createConfig()
354 fs := hugofs.NewMem(v)
355 workDir := "mywork"
356 v.Set("workingDir", workDir)
357
358 c.Assert(fs.Source.MkdirAll(filepath.Join(workDir, "dist", "d1"), 0777), qt.IsNil)
359 c.Assert(fs.Source.MkdirAll(filepath.Join(workDir, "static", "d2"), 0777), qt.IsNil)
360 c.Assert(fs.Source.MkdirAll(filepath.Join(workDir, "dust", "d2"), 0777), qt.IsNil)
361
362 moduleCfg := map[string]any{
363 "mounts": []any{
364 map[string]any{
365 "source": "dist",
366 "target": "static/mydist",
367 },
368 map[string]any{
369 "source": "dust",
370 "target": "static/foo/bar",
371 },
372 map[string]any{
373 "source": "static",
374 "target": "static",
375 },
376 },
377 }
378
379 v.Set("module", moduleCfg)
380
381 c.Assert(initConfig(fs.Source, v), qt.IsNil)
382
383 p, err := paths.New(fs, v)
384 c.Assert(err, qt.IsNil)
385 bfs, err := NewBase(p, nil)
386 c.Assert(err, qt.IsNil)
387
388 sfs := bfs.Static[""]
389 c.Assert(sfs, qt.Not(qt.IsNil))
390
391 makeRel := func(s string) string {
392 r, _ := sfs.MakePathRelative(s)
393 return r
394 }
395
396 c.Assert(makeRel(filepath.Join(workDir, "dist", "d1", "foo.txt")), qt.Equals, filepath.FromSlash("mydist/d1/foo.txt"))
397 c.Assert(makeRel(filepath.Join(workDir, "static", "d2", "foo.txt")), qt.Equals, filepath.FromSlash("d2/foo.txt"))
398 c.Assert(makeRel(filepath.Join(workDir, "dust", "d3", "foo.txt")), qt.Equals, filepath.FromSlash("foo/bar/d3/foo.txt"))
399 }
400
401 func checkFileCount(fs afero.Fs, dirname string, c *qt.C, expected int) {
402 count, _, err := countFilesAndGetFilenames(fs, dirname)
403 c.Assert(err, qt.IsNil)
404 c.Assert(count, qt.Equals, expected)
405 }
406
407 func checkFileContent(fs afero.Fs, filename string, c *qt.C, expected ...string) {
408 b, err := afero.ReadFile(fs, filename)
409 c.Assert(err, qt.IsNil)
410
411 content := string(b)
412
413 for _, e := range expected {
414 c.Assert(content, qt.Contains, e)
415 }
416 }
417
418 func countFilesAndGetFilenames(fs afero.Fs, dirname string) (int, []string, error) {
419 if fs == nil {
420 return 0, nil, errors.New("no fs")
421 }
422
423 counter := 0
424 var filenames []string
425
426 wf := func(path string, info hugofs.FileMetaInfo, err error) error {
427 if err != nil {
428 return err
429 }
430 if !info.IsDir() {
431 counter++
432 }
433
434 if info.Name() != "." {
435 name := info.Name()
436 name = strings.Replace(name, filepath.FromSlash("/my/work"), "WORK_DIR", 1)
437 filenames = append(filenames, name)
438 }
439
440 return nil
441 }
442
443 w := hugofs.NewWalkway(hugofs.WalkwayConfig{Fs: fs, Root: dirname, WalkFn: wf})
444
445 if err := w.Walk(); err != nil {
446 return -1, nil, err
447 }
448
449 return counter, filenames, nil
450 }
451
452 func setConfigAndWriteSomeFilesTo(fs afero.Fs, v config.Provider, key, val string, num int) {
453 workingDir := v.GetString("workingDir")
454 v.Set(key, val)
455 fs.Mkdir(val, 0755)
456 for i := 0; i < num; i++ {
457 filename := filepath.Join(workingDir, val, fmt.Sprintf("f%d.txt", i+1))
458 afero.WriteFile(fs, filename, []byte(fmt.Sprintf("content:%s:%d", key, i+1)), 0755)
459 }
460 }