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