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  }