github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/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  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.New()
    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.New()
   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  	fs := hugofs.NewDefault(v)
   223  	sfs := fs.Source
   224  
   225  	root, err := afero.TempDir(sfs, "", "realdir")
   226  	c.Assert(err, qt.IsNil)
   227  	themesDir, err := afero.TempDir(sfs, "", "themesDir")
   228  	c.Assert(err, qt.IsNil)
   229  	defer func() {
   230  		os.RemoveAll(root)
   231  		os.RemoveAll(themesDir)
   232  	}()
   233  
   234  	v.Set("workingDir", root)
   235  	v.Set("themesDir", themesDir)
   236  	v.Set("theme", "mytheme")
   237  
   238  	c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf1"), 0755), qt.IsNil)
   239  	c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "scss", "sf2"), 0755), qt.IsNil)
   240  	c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2"), 0755), qt.IsNil)
   241  	c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3"), 0755), qt.IsNil)
   242  	c.Assert(sfs.MkdirAll(filepath.Join(root, "resources"), 0755), qt.IsNil)
   243  	c.Assert(sfs.MkdirAll(filepath.Join(themesDir, "mytheme", "resources"), 0755), qt.IsNil)
   244  
   245  	c.Assert(sfs.MkdirAll(filepath.Join(root, "myassets", "js", "f2"), 0755), qt.IsNil)
   246  
   247  	afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf1", "a1.scss")), []byte("content"), 0755)
   248  	afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
   249  	afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "scss", "a2.scss")), []byte("content"), 0755)
   250  	afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf2", "a3.scss")), []byte("content"), 0755)
   251  	afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "assets", "scss", "sf3", "a4.scss")), []byte("content"), 0755)
   252  
   253  	afero.WriteFile(sfs, filepath.Join(filepath.Join(themesDir, "mytheme", "resources", "t1.txt")), []byte("content"), 0755)
   254  	afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p1.txt")), []byte("content"), 0755)
   255  	afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "resources", "p2.txt")), []byte("content"), 0755)
   256  
   257  	afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "f2", "a1.js")), []byte("content"), 0755)
   258  	afero.WriteFile(sfs, filepath.Join(filepath.Join(root, "myassets", "js", "a2.js")), []byte("content"), 0755)
   259  
   260  	c.Assert(initConfig(fs.Source, v), qt.IsNil)
   261  
   262  	p, err := paths.New(fs, v)
   263  	c.Assert(err, qt.IsNil)
   264  	bfs, err := NewBase(p, nil)
   265  	c.Assert(err, qt.IsNil)
   266  	c.Assert(bfs, qt.Not(qt.IsNil))
   267  
   268  	checkFileCount(bfs.Assets.Fs, "", c, 6)
   269  
   270  	realDirs := bfs.Assets.RealDirs("scss")
   271  	c.Assert(len(realDirs), qt.Equals, 2)
   272  	c.Assert(realDirs[0], qt.Equals, filepath.Join(root, "myassets/scss"))
   273  	c.Assert(realDirs[len(realDirs)-1], qt.Equals, filepath.Join(themesDir, "mytheme/assets/scss"))
   274  
   275  	c.Assert(bfs.theBigFs, qt.Not(qt.IsNil))
   276  }
   277  
   278  func TestStaticFs(t *testing.T) {
   279  	c := qt.New(t)
   280  	v := createConfig()
   281  	workDir := "mywork"
   282  	v.Set("workingDir", workDir)
   283  	v.Set("themesDir", "themes")
   284  	v.Set("theme", []string{"t1", "t2"})
   285  
   286  	fs := hugofs.NewMem(v)
   287  
   288  	themeStaticDir := filepath.Join(workDir, "themes", "t1", "static")
   289  	themeStaticDir2 := filepath.Join(workDir, "themes", "t2", "static")
   290  
   291  	afero.WriteFile(fs.Source, filepath.Join(workDir, "mystatic", "f1.txt"), []byte("Hugo Rocks!"), 0755)
   292  	afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f1.txt"), []byte("Hugo Themes Rocks!"), 0755)
   293  	afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f2.txt"), []byte("Hugo Themes Still Rocks!"), 0755)
   294  	afero.WriteFile(fs.Source, filepath.Join(themeStaticDir2, "f2.txt"), []byte("Hugo Themes Rocks in t2!"), 0755)
   295  
   296  	c.Assert(initConfig(fs.Source, v), qt.IsNil)
   297  
   298  	p, err := paths.New(fs, v)
   299  	c.Assert(err, qt.IsNil)
   300  	bfs, err := NewBase(p, nil)
   301  	c.Assert(err, qt.IsNil)
   302  
   303  	sfs := bfs.StaticFs("en")
   304  	checkFileContent(sfs, "f1.txt", c, "Hugo Rocks!")
   305  	checkFileContent(sfs, "f2.txt", c, "Hugo Themes Still Rocks!")
   306  }
   307  
   308  func TestStaticFsMultiHost(t *testing.T) {
   309  	c := qt.New(t)
   310  	v := createConfig()
   311  	workDir := "mywork"
   312  	v.Set("workingDir", workDir)
   313  	v.Set("themesDir", "themes")
   314  	v.Set("theme", "t1")
   315  	v.Set("defaultContentLanguage", "en")
   316  
   317  	langConfig := map[string]interface{}{
   318  		"no": map[string]interface{}{
   319  			"staticDir": "static_no",
   320  			"baseURL":   "https://example.org/no/",
   321  		},
   322  		"en": map[string]interface{}{
   323  			"baseURL": "https://example.org/en/",
   324  		},
   325  	}
   326  
   327  	v.Set("languages", langConfig)
   328  
   329  	fs := hugofs.NewMem(v)
   330  
   331  	themeStaticDir := filepath.Join(workDir, "themes", "t1", "static")
   332  
   333  	afero.WriteFile(fs.Source, filepath.Join(workDir, "mystatic", "f1.txt"), []byte("Hugo Rocks!"), 0755)
   334  	afero.WriteFile(fs.Source, filepath.Join(workDir, "static_no", "f1.txt"), []byte("Hugo Rocks in Norway!"), 0755)
   335  
   336  	afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f1.txt"), []byte("Hugo Themes Rocks!"), 0755)
   337  	afero.WriteFile(fs.Source, filepath.Join(themeStaticDir, "f2.txt"), []byte("Hugo Themes Still Rocks!"), 0755)
   338  
   339  	c.Assert(initConfig(fs.Source, v), qt.IsNil)
   340  
   341  	p, err := paths.New(fs, v)
   342  	c.Assert(err, qt.IsNil)
   343  	bfs, err := NewBase(p, nil)
   344  	c.Assert(err, qt.IsNil)
   345  	enFs := bfs.StaticFs("en")
   346  	checkFileContent(enFs, "f1.txt", c, "Hugo Rocks!")
   347  	checkFileContent(enFs, "f2.txt", c, "Hugo Themes Still Rocks!")
   348  
   349  	noFs := bfs.StaticFs("no")
   350  	checkFileContent(noFs, "f1.txt", c, "Hugo Rocks in Norway!")
   351  	checkFileContent(noFs, "f2.txt", c, "Hugo Themes Still Rocks!")
   352  }
   353  
   354  func TestMakePathRelative(t *testing.T) {
   355  	c := qt.New(t)
   356  	v := createConfig()
   357  	fs := hugofs.NewMem(v)
   358  	workDir := "mywork"
   359  	v.Set("workingDir", workDir)
   360  
   361  	c.Assert(fs.Source.MkdirAll(filepath.Join(workDir, "dist", "d1"), 0777), qt.IsNil)
   362  	c.Assert(fs.Source.MkdirAll(filepath.Join(workDir, "static", "d2"), 0777), qt.IsNil)
   363  	c.Assert(fs.Source.MkdirAll(filepath.Join(workDir, "dust", "d2"), 0777), qt.IsNil)
   364  
   365  	moduleCfg := map[string]interface{}{
   366  		"mounts": []interface{}{
   367  			map[string]interface{}{
   368  				"source": "dist",
   369  				"target": "static/mydist",
   370  			},
   371  			map[string]interface{}{
   372  				"source": "dust",
   373  				"target": "static/foo/bar",
   374  			},
   375  			map[string]interface{}{
   376  				"source": "static",
   377  				"target": "static",
   378  			},
   379  		},
   380  	}
   381  
   382  	v.Set("module", moduleCfg)
   383  
   384  	c.Assert(initConfig(fs.Source, v), qt.IsNil)
   385  
   386  	p, err := paths.New(fs, v)
   387  	c.Assert(err, qt.IsNil)
   388  	bfs, err := NewBase(p, nil)
   389  	c.Assert(err, qt.IsNil)
   390  
   391  	sfs := bfs.Static[""]
   392  	c.Assert(sfs, qt.Not(qt.IsNil))
   393  
   394  	makeRel := func(s string) string {
   395  		r, _ := sfs.MakePathRelative(s)
   396  		return r
   397  	}
   398  
   399  	c.Assert(makeRel(filepath.Join(workDir, "dist", "d1", "foo.txt")), qt.Equals, filepath.FromSlash("mydist/d1/foo.txt"))
   400  	c.Assert(makeRel(filepath.Join(workDir, "static", "d2", "foo.txt")), qt.Equals, filepath.FromSlash("d2/foo.txt"))
   401  	c.Assert(makeRel(filepath.Join(workDir, "dust", "d3", "foo.txt")), qt.Equals, filepath.FromSlash("foo/bar/d3/foo.txt"))
   402  }
   403  
   404  func checkFileCount(fs afero.Fs, dirname string, c *qt.C, expected int) {
   405  	count, _, err := countFilesAndGetFilenames(fs, dirname)
   406  	c.Assert(err, qt.IsNil)
   407  	c.Assert(count, qt.Equals, expected)
   408  }
   409  
   410  func checkFileContent(fs afero.Fs, filename string, c *qt.C, expected ...string) {
   411  	b, err := afero.ReadFile(fs, filename)
   412  	c.Assert(err, qt.IsNil)
   413  
   414  	content := string(b)
   415  
   416  	for _, e := range expected {
   417  		c.Assert(content, qt.Contains, e)
   418  	}
   419  }
   420  
   421  func countFilesAndGetFilenames(fs afero.Fs, dirname string) (int, []string, error) {
   422  	if fs == nil {
   423  		return 0, nil, errors.New("no fs")
   424  	}
   425  
   426  	counter := 0
   427  	var filenames []string
   428  
   429  	wf := func(path string, info hugofs.FileMetaInfo, err error) error {
   430  		if err != nil {
   431  			return err
   432  		}
   433  		if !info.IsDir() {
   434  			counter++
   435  		}
   436  
   437  		if info.Name() != "." {
   438  			name := info.Name()
   439  			name = strings.Replace(name, filepath.FromSlash("/my/work"), "WORK_DIR", 1)
   440  			filenames = append(filenames, name)
   441  		}
   442  
   443  		return nil
   444  	}
   445  
   446  	w := hugofs.NewWalkway(hugofs.WalkwayConfig{Fs: fs, Root: dirname, WalkFn: wf})
   447  
   448  	if err := w.Walk(); err != nil {
   449  		return -1, nil, err
   450  	}
   451  
   452  	return counter, filenames, nil
   453  }
   454  
   455  func setConfigAndWriteSomeFilesTo(fs afero.Fs, v config.Provider, key, val string, num int) {
   456  	workingDir := v.GetString("workingDir")
   457  	v.Set(key, val)
   458  	fs.Mkdir(val, 0755)
   459  	for i := 0; i < num; i++ {
   460  		filename := filepath.Join(workingDir, val, fmt.Sprintf("f%d.txt", i+1))
   461  		afero.WriteFile(fs, filename, []byte(fmt.Sprintf("content:%s:%d", key, i+1)), 0755)
   462  	}
   463  }