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