github.com/gohugoio/hugo@v0.88.1/hugofs/rootmapping_fs_test.go (about) 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 "fmt" 18 "io/ioutil" 19 "path/filepath" 20 "sort" 21 "testing" 22 23 "github.com/gohugoio/hugo/config" 24 25 qt "github.com/frankban/quicktest" 26 "github.com/gohugoio/hugo/htesting" 27 "github.com/spf13/afero" 28 ) 29 30 func TestLanguageRootMapping(t *testing.T) { 31 c := qt.New(t) 32 v := config.New() 33 v.Set("contentDir", "content") 34 35 fs := NewBaseFileDecorator(afero.NewMemMapFs()) 36 37 c.Assert(afero.WriteFile(fs, filepath.Join("content/sv/svdir", "main.txt"), []byte("main sv"), 0755), qt.IsNil) 38 39 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", "sv-f.txt"), []byte("some sv blog content"), 0755), qt.IsNil) 40 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myenblogcontent", "en-f.txt"), []byte("some en blog content in a"), 0755), qt.IsNil) 41 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent/d1", "sv-d1-f.txt"), []byte("some sv blog content"), 0755), qt.IsNil) 42 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myenblogcontent/d1", "en-d1-f.txt"), []byte("some en blog content in a"), 0755), qt.IsNil) 43 44 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myotherenblogcontent", "en-f2.txt"), []byte("some en content"), 0755), qt.IsNil) 45 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvdocs", "sv-docs.txt"), []byte("some sv docs content"), 0755), qt.IsNil) 46 c.Assert(afero.WriteFile(fs, filepath.Join("themes/b/myenblogcontent", "en-b-f.txt"), []byte("some en content"), 0755), qt.IsNil) 47 48 rfs, err := NewRootMappingFs(fs, 49 RootMapping{ 50 From: "content/blog", // Virtual path, first element is one of content, static, layouts etc. 51 To: "themes/a/mysvblogcontent", // Real path 52 Meta: &FileMeta{Lang: "sv"}, 53 }, 54 RootMapping{ 55 From: "content/blog", 56 To: "themes/a/myenblogcontent", 57 Meta: &FileMeta{Lang: "en"}, 58 }, 59 RootMapping{ 60 From: "content/blog", 61 To: "content/sv", 62 Meta: &FileMeta{Lang: "sv"}, 63 }, 64 RootMapping{ 65 From: "content/blog", 66 To: "themes/a/myotherenblogcontent", 67 Meta: &FileMeta{Lang: "en"}, 68 }, 69 RootMapping{ 70 From: "content/docs", 71 To: "themes/a/mysvdocs", 72 Meta: &FileMeta{Lang: "sv"}, 73 }, 74 ) 75 76 c.Assert(err, qt.IsNil) 77 78 collected, err := collectFilenames(rfs, "content", "content") 79 c.Assert(err, qt.IsNil) 80 c.Assert(collected, qt.DeepEquals, 81 []string{"blog/d1/en-d1-f.txt", "blog/d1/sv-d1-f.txt", "blog/en-f.txt", "blog/en-f2.txt", "blog/sv-f.txt", "blog/svdir/main.txt", "docs/sv-docs.txt"}, qt.Commentf("%#v", collected)) 82 83 dirs, err := rfs.Dirs(filepath.FromSlash("content/blog")) 84 c.Assert(err, qt.IsNil) 85 c.Assert(len(dirs), qt.Equals, 4) 86 for _, dir := range dirs { 87 f, err := dir.Meta().Open() 88 c.Assert(err, qt.IsNil) 89 f.Close() 90 } 91 92 blog, err := rfs.Open(filepath.FromSlash("content/blog")) 93 c.Assert(err, qt.IsNil) 94 fis, err := blog.Readdir(-1) 95 for _, fi := range fis { 96 f, err := fi.(FileMetaInfo).Meta().Open() 97 c.Assert(err, qt.IsNil) 98 f.Close() 99 } 100 blog.Close() 101 102 getDirnames := func(name string, rfs *RootMappingFs) []string { 103 c.Helper() 104 filename := filepath.FromSlash(name) 105 f, err := rfs.Open(filename) 106 c.Assert(err, qt.IsNil) 107 names, err := f.Readdirnames(-1) 108 109 f.Close() 110 c.Assert(err, qt.IsNil) 111 112 info, err := rfs.Stat(filename) 113 c.Assert(err, qt.IsNil) 114 f2, err := info.(FileMetaInfo).Meta().Open() 115 c.Assert(err, qt.IsNil) 116 names2, err := f2.Readdirnames(-1) 117 c.Assert(err, qt.IsNil) 118 c.Assert(names2, qt.DeepEquals, names) 119 f2.Close() 120 121 return names 122 } 123 124 rfsEn := rfs.Filter(func(rm RootMapping) bool { 125 return rm.Meta.Lang == "en" 126 }) 127 128 c.Assert(getDirnames("content/blog", rfsEn), qt.DeepEquals, []string{"d1", "en-f.txt", "en-f2.txt"}) 129 130 rfsSv := rfs.Filter(func(rm RootMapping) bool { 131 return rm.Meta.Lang == "sv" 132 }) 133 134 c.Assert(getDirnames("content/blog", rfsSv), qt.DeepEquals, []string{"d1", "sv-f.txt", "svdir"}) 135 136 // Make sure we have not messed with the original 137 c.Assert(getDirnames("content/blog", rfs), qt.DeepEquals, []string{"d1", "sv-f.txt", "en-f.txt", "svdir", "en-f2.txt"}) 138 139 c.Assert(getDirnames("content", rfsSv), qt.DeepEquals, []string{"blog", "docs"}) 140 c.Assert(getDirnames("content", rfs), qt.DeepEquals, []string{"blog", "docs"}) 141 } 142 143 func TestRootMappingFsDirnames(t *testing.T) { 144 c := qt.New(t) 145 fs := NewBaseFileDecorator(afero.NewMemMapFs()) 146 147 testfile := "myfile.txt" 148 c.Assert(fs.Mkdir("f1t", 0755), qt.IsNil) 149 c.Assert(fs.Mkdir("f2t", 0755), qt.IsNil) 150 c.Assert(fs.Mkdir("f3t", 0755), qt.IsNil) 151 c.Assert(afero.WriteFile(fs, filepath.Join("f2t", testfile), []byte("some content"), 0755), qt.IsNil) 152 153 rfs, err := newRootMappingFsFromFromTo("", fs, "static/bf1", "f1t", "static/cf2", "f2t", "static/af3", "f3t") 154 c.Assert(err, qt.IsNil) 155 156 fif, err := rfs.Stat(filepath.Join("static/cf2", testfile)) 157 c.Assert(err, qt.IsNil) 158 c.Assert(fif.Name(), qt.Equals, "myfile.txt") 159 fifm := fif.(FileMetaInfo).Meta() 160 c.Assert(fifm.Filename, qt.Equals, filepath.FromSlash("f2t/myfile.txt")) 161 162 root, err := rfs.Open("static") 163 c.Assert(err, qt.IsNil) 164 165 dirnames, err := root.Readdirnames(-1) 166 c.Assert(err, qt.IsNil) 167 c.Assert(dirnames, qt.DeepEquals, []string{"af3", "bf1", "cf2"}) 168 } 169 170 func TestRootMappingFsFilename(t *testing.T) { 171 c := qt.New(t) 172 workDir, clean, err := htesting.CreateTempDir(Os, "hugo-root-filename") 173 c.Assert(err, qt.IsNil) 174 defer clean() 175 fs := NewBaseFileDecorator(Os) 176 177 testfilename := filepath.Join(workDir, "f1t/foo/file.txt") 178 179 c.Assert(fs.MkdirAll(filepath.Join(workDir, "f1t/foo"), 0777), qt.IsNil) 180 c.Assert(afero.WriteFile(fs, testfilename, []byte("content"), 0666), qt.IsNil) 181 182 rfs, err := newRootMappingFsFromFromTo(workDir, fs, "static/f1", filepath.Join(workDir, "f1t"), "static/f2", filepath.Join(workDir, "f2t")) 183 c.Assert(err, qt.IsNil) 184 185 fi, err := rfs.Stat(filepath.FromSlash("static/f1/foo/file.txt")) 186 c.Assert(err, qt.IsNil) 187 fim := fi.(FileMetaInfo) 188 c.Assert(fim.Meta().Filename, qt.Equals, testfilename) 189 _, err = rfs.Stat(filepath.FromSlash("static/f1")) 190 c.Assert(err, qt.IsNil) 191 } 192 193 func TestRootMappingFsMount(t *testing.T) { 194 c := qt.New(t) 195 fs := NewBaseFileDecorator(afero.NewMemMapFs()) 196 197 testfile := "test.txt" 198 199 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mynoblogcontent", testfile), []byte("some no content"), 0755), qt.IsNil) 200 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myenblogcontent", testfile), []byte("some en content"), 0755), qt.IsNil) 201 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", testfile), []byte("some sv content"), 0755), qt.IsNil) 202 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", "other.txt"), []byte("some sv content"), 0755), qt.IsNil) 203 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/singlefiles", "no.txt"), []byte("no text"), 0755), qt.IsNil) 204 c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/singlefiles", "sv.txt"), []byte("sv text"), 0755), qt.IsNil) 205 206 bfs := afero.NewBasePathFs(fs, "themes/a").(*afero.BasePathFs) 207 rm := []RootMapping{ 208 // Directories 209 { 210 From: "content/blog", 211 To: "mynoblogcontent", 212 Meta: &FileMeta{Lang: "no"}, 213 }, 214 { 215 From: "content/blog", 216 To: "myenblogcontent", 217 Meta: &FileMeta{Lang: "en"}, 218 }, 219 { 220 From: "content/blog", 221 To: "mysvblogcontent", 222 Meta: &FileMeta{Lang: "sv"}, 223 }, 224 // Files 225 { 226 From: "content/singles/p1.md", 227 To: "singlefiles/no.txt", 228 ToBasedir: "singlefiles", 229 Meta: &FileMeta{Lang: "no"}, 230 }, 231 { 232 From: "content/singles/p1.md", 233 To: "singlefiles/sv.txt", 234 ToBasedir: "singlefiles", 235 Meta: &FileMeta{Lang: "sv"}, 236 }, 237 } 238 239 rfs, err := NewRootMappingFs(bfs, rm...) 240 c.Assert(err, qt.IsNil) 241 242 blog, err := rfs.Stat(filepath.FromSlash("content/blog")) 243 c.Assert(err, qt.IsNil) 244 c.Assert(blog.IsDir(), qt.Equals, true) 245 blogm := blog.(FileMetaInfo).Meta() 246 c.Assert(blogm.Lang, qt.Equals, "no") // First match 247 248 f, err := blogm.Open() 249 c.Assert(err, qt.IsNil) 250 defer f.Close() 251 dirs1, err := f.Readdirnames(-1) 252 c.Assert(err, qt.IsNil) 253 // Union with duplicate dir names filtered. 254 c.Assert(dirs1, qt.DeepEquals, []string{"test.txt", "test.txt", "other.txt", "test.txt"}) 255 256 files, err := afero.ReadDir(rfs, filepath.FromSlash("content/blog")) 257 c.Assert(err, qt.IsNil) 258 c.Assert(len(files), qt.Equals, 4) 259 260 testfilefi := files[1] 261 c.Assert(testfilefi.Name(), qt.Equals, testfile) 262 263 testfilem := testfilefi.(FileMetaInfo).Meta() 264 c.Assert(testfilem.Filename, qt.Equals, filepath.FromSlash("themes/a/mynoblogcontent/test.txt")) 265 266 tf, err := testfilem.Open() 267 c.Assert(err, qt.IsNil) 268 defer tf.Close() 269 b, err := ioutil.ReadAll(tf) 270 c.Assert(err, qt.IsNil) 271 c.Assert(string(b), qt.Equals, "some no content") 272 273 // Ambiguous 274 _, err = rfs.Stat(filepath.FromSlash("content/singles/p1.md")) 275 c.Assert(err, qt.Not(qt.IsNil)) 276 277 singlesDir, err := rfs.Open(filepath.FromSlash("content/singles")) 278 c.Assert(err, qt.IsNil) 279 defer singlesDir.Close() 280 singles, err := singlesDir.Readdir(-1) 281 c.Assert(err, qt.IsNil) 282 c.Assert(singles, qt.HasLen, 2) 283 for i, lang := range []string{"no", "sv"} { 284 fi := singles[i].(FileMetaInfo) 285 c.Assert(fi.Meta().PathFile(), qt.Equals, filepath.FromSlash("themes/a/singlefiles/"+lang+".txt")) 286 c.Assert(fi.Meta().Lang, qt.Equals, lang) 287 c.Assert(fi.Name(), qt.Equals, "p1.md") 288 } 289 } 290 291 func TestRootMappingFsMountOverlap(t *testing.T) { 292 c := qt.New(t) 293 fs := NewBaseFileDecorator(afero.NewMemMapFs()) 294 295 c.Assert(afero.WriteFile(fs, filepath.FromSlash("da/a.txt"), []byte("some no content"), 0755), qt.IsNil) 296 c.Assert(afero.WriteFile(fs, filepath.FromSlash("db/b.txt"), []byte("some no content"), 0755), qt.IsNil) 297 c.Assert(afero.WriteFile(fs, filepath.FromSlash("dc/c.txt"), []byte("some no content"), 0755), qt.IsNil) 298 c.Assert(afero.WriteFile(fs, filepath.FromSlash("de/e.txt"), []byte("some no content"), 0755), qt.IsNil) 299 300 rm := []RootMapping{ 301 { 302 From: "static", 303 To: "da", 304 }, 305 { 306 From: "static/b", 307 To: "db", 308 }, 309 { 310 From: "static/b/c", 311 To: "dc", 312 }, 313 { 314 From: "/static/e/", 315 To: "de", 316 }, 317 } 318 319 rfs, err := NewRootMappingFs(fs, rm...) 320 c.Assert(err, qt.IsNil) 321 322 checkDirnames := func(name string, expect []string) { 323 c.Helper() 324 name = filepath.FromSlash(name) 325 f, err := rfs.Open(name) 326 c.Assert(err, qt.IsNil) 327 defer f.Close() 328 names, err := f.Readdirnames(-1) 329 c.Assert(err, qt.IsNil) 330 c.Assert(names, qt.DeepEquals, expect, qt.Commentf(fmt.Sprintf("%#v", names))) 331 } 332 333 checkDirnames("static", []string{"a.txt", "b", "e"}) 334 checkDirnames("static/b", []string{"b.txt", "c"}) 335 checkDirnames("static/b/c", []string{"c.txt"}) 336 337 fi, err := rfs.Stat(filepath.FromSlash("static/b/b.txt")) 338 c.Assert(err, qt.IsNil) 339 c.Assert(fi.Name(), qt.Equals, "b.txt") 340 } 341 342 func TestRootMappingFsOs(t *testing.T) { 343 c := qt.New(t) 344 fs := NewBaseFileDecorator(afero.NewOsFs()) 345 346 d, clean, err := htesting.CreateTempDir(fs, "hugo-root-mapping-os") 347 c.Assert(err, qt.IsNil) 348 defer clean() 349 350 testfile := "myfile.txt" 351 c.Assert(fs.Mkdir(filepath.Join(d, "f1t"), 0755), qt.IsNil) 352 c.Assert(fs.Mkdir(filepath.Join(d, "f2t"), 0755), qt.IsNil) 353 c.Assert(fs.Mkdir(filepath.Join(d, "f3t"), 0755), qt.IsNil) 354 355 // Deep structure 356 deepDir := filepath.Join(d, "d1", "d2", "d3", "d4", "d5") 357 c.Assert(fs.MkdirAll(deepDir, 0755), qt.IsNil) 358 for i := 1; i <= 3; i++ { 359 c.Assert(fs.MkdirAll(filepath.Join(d, "d1", "d2", "d3", "d4", fmt.Sprintf("d4-%d", i)), 0755), qt.IsNil) 360 c.Assert(afero.WriteFile(fs, filepath.Join(d, "d1", "d2", "d3", fmt.Sprintf("f-%d.txt", i)), []byte("some content"), 0755), qt.IsNil) 361 } 362 363 c.Assert(afero.WriteFile(fs, filepath.Join(d, "f2t", testfile), []byte("some content"), 0755), qt.IsNil) 364 365 // https://github.com/gohugoio/hugo/issues/6854 366 mystaticDir := filepath.Join(d, "mystatic", "a", "b", "c") 367 c.Assert(fs.MkdirAll(mystaticDir, 0755), qt.IsNil) 368 c.Assert(afero.WriteFile(fs, filepath.Join(mystaticDir, "ms-1.txt"), []byte("some content"), 0755), qt.IsNil) 369 370 rfs, err := newRootMappingFsFromFromTo( 371 d, 372 fs, 373 "static/bf1", filepath.Join(d, "f1t"), 374 "static/cf2", filepath.Join(d, "f2t"), 375 "static/af3", filepath.Join(d, "f3t"), 376 "static", filepath.Join(d, "mystatic"), 377 "static/a/b/c", filepath.Join(d, "d1", "d2", "d3"), 378 "layouts", filepath.Join(d, "d1"), 379 ) 380 381 c.Assert(err, qt.IsNil) 382 383 fif, err := rfs.Stat(filepath.Join("static/cf2", testfile)) 384 c.Assert(err, qt.IsNil) 385 c.Assert(fif.Name(), qt.Equals, "myfile.txt") 386 387 root, err := rfs.Open("static") 388 c.Assert(err, qt.IsNil) 389 390 dirnames, err := root.Readdirnames(-1) 391 c.Assert(err, qt.IsNil) 392 c.Assert(dirnames, qt.DeepEquals, []string{"a", "af3", "bf1", "cf2"}, qt.Commentf(fmt.Sprintf("%#v", dirnames))) 393 394 getDirnames := func(dirname string) []string { 395 dirname = filepath.FromSlash(dirname) 396 f, err := rfs.Open(dirname) 397 c.Assert(err, qt.IsNil) 398 defer f.Close() 399 dirnames, err := f.Readdirnames(-1) 400 c.Assert(err, qt.IsNil) 401 sort.Strings(dirnames) 402 return dirnames 403 } 404 405 c.Assert(getDirnames("static/a/b"), qt.DeepEquals, []string{"c"}) 406 c.Assert(getDirnames("static/a/b/c"), qt.DeepEquals, []string{"d4", "f-1.txt", "f-2.txt", "f-3.txt", "ms-1.txt"}) 407 c.Assert(getDirnames("static/a/b/c/d4"), qt.DeepEquals, []string{"d4-1", "d4-2", "d4-3", "d5"}) 408 409 all, err := collectFilenames(rfs, "static", "static") 410 c.Assert(err, qt.IsNil) 411 412 c.Assert(all, qt.DeepEquals, []string{"a/b/c/f-1.txt", "a/b/c/f-2.txt", "a/b/c/f-3.txt", "a/b/c/ms-1.txt", "cf2/myfile.txt"}) 413 414 fis, err := collectFileinfos(rfs, "static", "static") 415 c.Assert(err, qt.IsNil) 416 417 c.Assert(fis[9].Meta().PathFile(), qt.Equals, filepath.FromSlash("d1/d2/d3/f-1.txt")) 418 419 dirc := fis[3].Meta() 420 421 f, err := dirc.Open() 422 c.Assert(err, qt.IsNil) 423 defer f.Close() 424 fileInfos, err := f.Readdir(-1) 425 c.Assert(err, qt.IsNil) 426 sortFileInfos(fileInfos) 427 i := 0 428 for _, fi := range fileInfos { 429 if fi.IsDir() || fi.Name() == "ms-1.txt" { 430 continue 431 } 432 i++ 433 meta := fi.(FileMetaInfo).Meta() 434 c.Assert(meta.Filename, qt.Equals, filepath.Join(d, fmt.Sprintf("/d1/d2/d3/f-%d.txt", i))) 435 c.Assert(meta.PathFile(), qt.Equals, filepath.FromSlash(fmt.Sprintf("d1/d2/d3/f-%d.txt", i))) 436 } 437 438 _, err = rfs.Stat(filepath.FromSlash("layouts/d2/d3/f-1.txt")) 439 c.Assert(err, qt.IsNil) 440 _, err = rfs.Stat(filepath.FromSlash("layouts/d2/d3")) 441 c.Assert(err, qt.IsNil) 442 } 443 444 func TestRootMappingFsOsBase(t *testing.T) { 445 c := qt.New(t) 446 fs := NewBaseFileDecorator(afero.NewOsFs()) 447 448 d, clean, err := htesting.CreateTempDir(fs, "hugo-root-mapping-os-base") 449 c.Assert(err, qt.IsNil) 450 defer clean() 451 452 // Deep structure 453 deepDir := filepath.Join(d, "d1", "d2", "d3", "d4", "d5") 454 c.Assert(fs.MkdirAll(deepDir, 0755), qt.IsNil) 455 for i := 1; i <= 3; i++ { 456 c.Assert(fs.MkdirAll(filepath.Join(d, "d1", "d2", "d3", "d4", fmt.Sprintf("d4-%d", i)), 0755), qt.IsNil) 457 c.Assert(afero.WriteFile(fs, filepath.Join(d, "d1", "d2", "d3", fmt.Sprintf("f-%d.txt", i)), []byte("some content"), 0755), qt.IsNil) 458 } 459 460 mystaticDir := filepath.Join(d, "mystatic", "a", "b", "c") 461 c.Assert(fs.MkdirAll(mystaticDir, 0755), qt.IsNil) 462 c.Assert(afero.WriteFile(fs, filepath.Join(mystaticDir, "ms-1.txt"), []byte("some content"), 0755), qt.IsNil) 463 464 bfs := afero.NewBasePathFs(fs, d) 465 466 rfs, err := newRootMappingFsFromFromTo( 467 "", 468 bfs, 469 "static", "mystatic", 470 "static/a/b/c", filepath.Join("d1", "d2", "d3"), 471 ) 472 473 getDirnames := func(dirname string) []string { 474 dirname = filepath.FromSlash(dirname) 475 f, err := rfs.Open(dirname) 476 c.Assert(err, qt.IsNil) 477 defer f.Close() 478 dirnames, err := f.Readdirnames(-1) 479 c.Assert(err, qt.IsNil) 480 sort.Strings(dirnames) 481 return dirnames 482 } 483 484 c.Assert(getDirnames("static/a/b/c"), qt.DeepEquals, []string{"d4", "f-1.txt", "f-2.txt", "f-3.txt", "ms-1.txt"}) 485 }