github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/cache/filecache/filecache_test.go (about)

     1  // Copyright 2018 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 filecache
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"io"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/gobwas/glob"
    29  
    30  	"github.com/gohugoio/hugo/langs"
    31  	"github.com/gohugoio/hugo/modules"
    32  
    33  	"github.com/gohugoio/hugo/common/hugio"
    34  	"github.com/gohugoio/hugo/config"
    35  	"github.com/gohugoio/hugo/helpers"
    36  
    37  	"github.com/gohugoio/hugo/hugofs"
    38  	"github.com/spf13/afero"
    39  
    40  	qt "github.com/frankban/quicktest"
    41  )
    42  
    43  func TestFileCache(t *testing.T) {
    44  	t.Parallel()
    45  	c := qt.New(t)
    46  
    47  	tempWorkingDir, err := ioutil.TempDir("", "hugo_filecache_test_work")
    48  	c.Assert(err, qt.IsNil)
    49  	defer os.Remove(tempWorkingDir)
    50  
    51  	tempCacheDir, err := ioutil.TempDir("", "hugo_filecache_test_cache")
    52  	c.Assert(err, qt.IsNil)
    53  	defer os.Remove(tempCacheDir)
    54  
    55  	osfs := afero.NewOsFs()
    56  
    57  	for _, test := range []struct {
    58  		cacheDir   string
    59  		workingDir string
    60  	}{
    61  		// Run with same dirs twice to make sure that works.
    62  		{tempCacheDir, tempWorkingDir},
    63  		{tempCacheDir, tempWorkingDir},
    64  	} {
    65  
    66  		configStr := `
    67  workingDir = "WORKING_DIR"
    68  resourceDir = "resources"
    69  cacheDir = "CACHEDIR"
    70  contentDir = "content"
    71  dataDir = "data"
    72  i18nDir = "i18n"
    73  layoutDir = "layouts"
    74  assetDir = "assets"
    75  archeTypedir = "archetypes"
    76  
    77  [caches]
    78  [caches.getJSON]
    79  maxAge = "10h"
    80  dir = ":cacheDir/c"
    81  
    82  `
    83  
    84  		winPathSep := "\\\\"
    85  
    86  		replacer := strings.NewReplacer("CACHEDIR", test.cacheDir, "WORKING_DIR", test.workingDir)
    87  
    88  		configStr = replacer.Replace(configStr)
    89  		configStr = strings.Replace(configStr, "\\", winPathSep, -1)
    90  
    91  		p := newPathsSpec(t, osfs, configStr)
    92  
    93  		caches, err := NewCaches(p)
    94  		c.Assert(err, qt.IsNil)
    95  
    96  		cache := caches.Get("GetJSON")
    97  		c.Assert(cache, qt.Not(qt.IsNil))
    98  		c.Assert(cache.maxAge.String(), qt.Equals, "10h0m0s")
    99  
   100  		bfs, ok := cache.Fs.(*afero.BasePathFs)
   101  		c.Assert(ok, qt.Equals, true)
   102  		filename, err := bfs.RealPath("key")
   103  		c.Assert(err, qt.IsNil)
   104  		if test.cacheDir != "" {
   105  			c.Assert(filename, qt.Equals, filepath.Join(test.cacheDir, "c/"+filecacheRootDirname+"/getjson/key"))
   106  		} else {
   107  			// Temp dir.
   108  			c.Assert(filename, qt.Matches, ".*hugo_cache.*"+filecacheRootDirname+".*key")
   109  		}
   110  
   111  		cache = caches.Get("Images")
   112  		c.Assert(cache, qt.Not(qt.IsNil))
   113  		c.Assert(cache.maxAge, qt.Equals, time.Duration(-1))
   114  		bfs, ok = cache.Fs.(*afero.BasePathFs)
   115  		c.Assert(ok, qt.Equals, true)
   116  		filename, _ = bfs.RealPath("key")
   117  		c.Assert(filename, qt.Equals, filepath.FromSlash("_gen/images/key"))
   118  
   119  		rf := func(s string) func() (io.ReadCloser, error) {
   120  			return func() (io.ReadCloser, error) {
   121  				return struct {
   122  					io.ReadSeeker
   123  					io.Closer
   124  				}{
   125  					strings.NewReader(s),
   126  					ioutil.NopCloser(nil),
   127  				}, nil
   128  			}
   129  		}
   130  
   131  		bf := func() ([]byte, error) {
   132  			return []byte("bcd"), nil
   133  		}
   134  
   135  		for _, ca := range []*Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
   136  			for i := 0; i < 2; i++ {
   137  				info, r, err := ca.GetOrCreate("a", rf("abc"))
   138  				c.Assert(err, qt.IsNil)
   139  				c.Assert(r, qt.Not(qt.IsNil))
   140  				c.Assert(info.Name, qt.Equals, "a")
   141  				b, _ := ioutil.ReadAll(r)
   142  				r.Close()
   143  				c.Assert(string(b), qt.Equals, "abc")
   144  
   145  				info, b, err = ca.GetOrCreateBytes("b", bf)
   146  				c.Assert(err, qt.IsNil)
   147  				c.Assert(r, qt.Not(qt.IsNil))
   148  				c.Assert(info.Name, qt.Equals, "b")
   149  				c.Assert(string(b), qt.Equals, "bcd")
   150  
   151  				_, b, err = ca.GetOrCreateBytes("a", bf)
   152  				c.Assert(err, qt.IsNil)
   153  				c.Assert(string(b), qt.Equals, "abc")
   154  
   155  				_, r, err = ca.GetOrCreate("a", rf("bcd"))
   156  				c.Assert(err, qt.IsNil)
   157  				b, _ = ioutil.ReadAll(r)
   158  				r.Close()
   159  				c.Assert(string(b), qt.Equals, "abc")
   160  			}
   161  		}
   162  
   163  		c.Assert(caches.Get("getJSON"), qt.Not(qt.IsNil))
   164  
   165  		info, w, err := caches.ImageCache().WriteCloser("mykey")
   166  		c.Assert(err, qt.IsNil)
   167  		c.Assert(info.Name, qt.Equals, "mykey")
   168  		io.WriteString(w, "Hugo is great!")
   169  		w.Close()
   170  		c.Assert(caches.ImageCache().getString("mykey"), qt.Equals, "Hugo is great!")
   171  
   172  		info, r, err := caches.ImageCache().Get("mykey")
   173  		c.Assert(err, qt.IsNil)
   174  		c.Assert(r, qt.Not(qt.IsNil))
   175  		c.Assert(info.Name, qt.Equals, "mykey")
   176  		b, _ := ioutil.ReadAll(r)
   177  		r.Close()
   178  		c.Assert(string(b), qt.Equals, "Hugo is great!")
   179  
   180  		info, b, err = caches.ImageCache().GetBytes("mykey")
   181  		c.Assert(err, qt.IsNil)
   182  		c.Assert(info.Name, qt.Equals, "mykey")
   183  		c.Assert(string(b), qt.Equals, "Hugo is great!")
   184  
   185  	}
   186  }
   187  
   188  func TestFileCacheConcurrent(t *testing.T) {
   189  	t.Parallel()
   190  
   191  	c := qt.New(t)
   192  
   193  	configStr := `
   194  resourceDir = "myresources"
   195  contentDir = "content"
   196  dataDir = "data"
   197  i18nDir = "i18n"
   198  layoutDir = "layouts"
   199  assetDir = "assets"
   200  archeTypedir = "archetypes"
   201  
   202  [caches]
   203  [caches.getjson]
   204  maxAge = "1s"
   205  dir = "/cache/c"
   206  
   207  `
   208  
   209  	p := newPathsSpec(t, afero.NewMemMapFs(), configStr)
   210  
   211  	caches, err := NewCaches(p)
   212  	c.Assert(err, qt.IsNil)
   213  
   214  	const cacheName = "getjson"
   215  
   216  	filenameData := func(i int) (string, string) {
   217  		data := fmt.Sprintf("data: %d", i)
   218  		filename := fmt.Sprintf("file%d", i)
   219  		return filename, data
   220  	}
   221  
   222  	var wg sync.WaitGroup
   223  
   224  	for i := 0; i < 50; i++ {
   225  		wg.Add(1)
   226  		go func(i int) {
   227  			defer wg.Done()
   228  			for j := 0; j < 20; j++ {
   229  				ca := caches.Get(cacheName)
   230  				c.Assert(ca, qt.Not(qt.IsNil))
   231  				filename, data := filenameData(i)
   232  				_, r, err := ca.GetOrCreate(filename, func() (io.ReadCloser, error) {
   233  					return hugio.ToReadCloser(strings.NewReader(data)), nil
   234  				})
   235  				c.Assert(err, qt.IsNil)
   236  				b, _ := ioutil.ReadAll(r)
   237  				r.Close()
   238  				c.Assert(string(b), qt.Equals, data)
   239  				// Trigger some expiration.
   240  				time.Sleep(50 * time.Millisecond)
   241  			}
   242  		}(i)
   243  
   244  	}
   245  	wg.Wait()
   246  }
   247  
   248  func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
   249  	t.Parallel()
   250  	c := qt.New(t)
   251  
   252  	var result string
   253  
   254  	rf := func(failLevel int) func(info ItemInfo, r io.ReadSeeker) error {
   255  		return func(info ItemInfo, r io.ReadSeeker) error {
   256  			if failLevel > 0 {
   257  				if failLevel > 1 {
   258  					return ErrFatal
   259  				}
   260  				return errors.New("fail")
   261  			}
   262  
   263  			b, _ := ioutil.ReadAll(r)
   264  			result = string(b)
   265  
   266  			return nil
   267  		}
   268  	}
   269  
   270  	bf := func(s string) func(info ItemInfo, w io.WriteCloser) error {
   271  		return func(info ItemInfo, w io.WriteCloser) error {
   272  			defer w.Close()
   273  			result = s
   274  			_, err := w.Write([]byte(s))
   275  			return err
   276  		}
   277  	}
   278  
   279  	cache := NewCache(afero.NewMemMapFs(), 100*time.Hour, "")
   280  
   281  	const id = "a32"
   282  
   283  	_, err := cache.ReadOrCreate(id, rf(0), bf("v1"))
   284  	c.Assert(err, qt.IsNil)
   285  	c.Assert(result, qt.Equals, "v1")
   286  	_, err = cache.ReadOrCreate(id, rf(0), bf("v2"))
   287  	c.Assert(err, qt.IsNil)
   288  	c.Assert(result, qt.Equals, "v1")
   289  	_, err = cache.ReadOrCreate(id, rf(1), bf("v3"))
   290  	c.Assert(err, qt.IsNil)
   291  	c.Assert(result, qt.Equals, "v3")
   292  	_, err = cache.ReadOrCreate(id, rf(2), bf("v3"))
   293  	c.Assert(err, qt.Equals, ErrFatal)
   294  }
   295  
   296  func TestCleanID(t *testing.T) {
   297  	c := qt.New(t)
   298  	c.Assert(cleanID(filepath.FromSlash("/a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
   299  	c.Assert(cleanID(filepath.FromSlash("a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))
   300  }
   301  
   302  func initConfig(fs afero.Fs, cfg config.Provider) error {
   303  	if _, err := langs.LoadLanguageSettings(cfg, nil); err != nil {
   304  		return err
   305  	}
   306  
   307  	modConfig, err := modules.DecodeConfig(cfg)
   308  	if err != nil {
   309  		return err
   310  	}
   311  
   312  	workingDir := cfg.GetString("workingDir")
   313  	themesDir := cfg.GetString("themesDir")
   314  	if !filepath.IsAbs(themesDir) {
   315  		themesDir = filepath.Join(workingDir, themesDir)
   316  	}
   317  	globAll := glob.MustCompile("**", '/')
   318  	modulesClient := modules.NewClient(modules.ClientConfig{
   319  		Fs:           fs,
   320  		WorkingDir:   workingDir,
   321  		ThemesDir:    themesDir,
   322  		ModuleConfig: modConfig,
   323  		IgnoreVendor: globAll,
   324  	})
   325  
   326  	moduleConfig, err := modulesClient.Collect()
   327  	if err != nil {
   328  		return err
   329  	}
   330  
   331  	if err := modules.ApplyProjectConfigDefaults(cfg, moduleConfig.ActiveModules[len(moduleConfig.ActiveModules)-1]); err != nil {
   332  		return err
   333  	}
   334  
   335  	cfg.Set("allModules", moduleConfig.ActiveModules)
   336  
   337  	return nil
   338  }
   339  
   340  func newPathsSpec(t *testing.T, fs afero.Fs, configStr string) *helpers.PathSpec {
   341  	c := qt.New(t)
   342  	cfg, err := config.FromConfigString(configStr, "toml")
   343  	c.Assert(err, qt.IsNil)
   344  	initConfig(fs, cfg)
   345  	p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil)
   346  	c.Assert(err, qt.IsNil)
   347  	return p
   348  }