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 }