diff options
Diffstat (limited to 'cache/filecache/filecache_test.go')
-rw-r--r-- | cache/filecache/filecache_test.go | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go new file mode 100644 index 000000000..5a5dac983 --- /dev/null +++ b/cache/filecache/filecache_test.go @@ -0,0 +1,348 @@ +// Copyright 2018 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package filecache + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/gohugoio/hugo/langs" + "github.com/gohugoio/hugo/modules" + + "github.com/gohugoio/hugo/common/hugio" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/helpers" + + "github.com/gohugoio/hugo/hugofs" + "github.com/spf13/afero" + + qt "github.com/frankban/quicktest" +) + +func TestFileCache(t *testing.T) { + t.Parallel() + c := qt.New(t) + + tempWorkingDir, err := ioutil.TempDir("", "hugo_filecache_test_work") + c.Assert(err, qt.IsNil) + defer os.Remove(tempWorkingDir) + + tempCacheDir, err := ioutil.TempDir("", "hugo_filecache_test_cache") + c.Assert(err, qt.IsNil) + defer os.Remove(tempCacheDir) + + osfs := afero.NewOsFs() + + for _, test := range []struct { + cacheDir string + workingDir string + }{ + // Run with same dirs twice to make sure that works. + {tempCacheDir, tempWorkingDir}, + {tempCacheDir, tempWorkingDir}, + } { + + configStr := ` +workingDir = "WORKING_DIR" +resourceDir = "resources" +cacheDir = "CACHEDIR" +contentDir = "content" +dataDir = "data" +i18nDir = "i18n" +layoutDir = "layouts" +assetDir = "assets" +archeTypedir = "archetypes" + +[caches] +[caches.getJSON] +maxAge = "10h" +dir = ":cacheDir/c" + +` + + winPathSep := "\\\\" + + replacer := strings.NewReplacer("CACHEDIR", test.cacheDir, "WORKING_DIR", test.workingDir) + + configStr = replacer.Replace(configStr) + configStr = strings.Replace(configStr, "\\", winPathSep, -1) + + p := newPathsSpec(t, osfs, configStr) + + caches, err := NewCaches(p) + c.Assert(err, qt.IsNil) + + cache := caches.Get("GetJSON") + c.Assert(cache, qt.Not(qt.IsNil)) + c.Assert(cache.maxAge.String(), qt.Equals, "10h0m0s") + + bfs, ok := cache.Fs.(*afero.BasePathFs) + c.Assert(ok, qt.Equals, true) + filename, err := bfs.RealPath("key") + c.Assert(err, qt.IsNil) + if test.cacheDir != "" { + c.Assert(filename, qt.Equals, filepath.Join(test.cacheDir, "c/"+filecacheRootDirname+"/getjson/key")) + } else { + // Temp dir. + c.Assert(filename, qt.Matches, ".*hugo_cache.*"+filecacheRootDirname+".*key") + } + + cache = caches.Get("Images") + c.Assert(cache, qt.Not(qt.IsNil)) + c.Assert(cache.maxAge, qt.Equals, time.Duration(-1)) + bfs, ok = cache.Fs.(*afero.BasePathFs) + c.Assert(ok, qt.Equals, true) + filename, _ = bfs.RealPath("key") + c.Assert(filename, qt.Equals, filepath.FromSlash("_gen/images/key")) + + rf := func(s string) func() (io.ReadCloser, error) { + return func() (io.ReadCloser, error) { + return struct { + io.ReadSeeker + io.Closer + }{ + strings.NewReader(s), + ioutil.NopCloser(nil), + }, nil + } + } + + bf := func() ([]byte, error) { + return []byte("bcd"), nil + } + + for _, ca := range []*Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} { + for i := 0; i < 2; i++ { + info, r, err := ca.GetOrCreate("a", rf("abc")) + c.Assert(err, qt.IsNil) + c.Assert(r, qt.Not(qt.IsNil)) + c.Assert(info.Name, qt.Equals, "a") + b, _ := ioutil.ReadAll(r) + r.Close() + c.Assert(string(b), qt.Equals, "abc") + + info, b, err = ca.GetOrCreateBytes("b", bf) + c.Assert(err, qt.IsNil) + c.Assert(r, qt.Not(qt.IsNil)) + c.Assert(info.Name, qt.Equals, "b") + c.Assert(string(b), qt.Equals, "bcd") + + _, b, err = ca.GetOrCreateBytes("a", bf) + c.Assert(err, qt.IsNil) + c.Assert(string(b), qt.Equals, "abc") + + _, r, err = ca.GetOrCreate("a", rf("bcd")) + c.Assert(err, qt.IsNil) + b, _ = ioutil.ReadAll(r) + r.Close() + c.Assert(string(b), qt.Equals, "abc") + } + } + + c.Assert(caches.Get("getJSON"), qt.Not(qt.IsNil)) + + info, w, err := caches.ImageCache().WriteCloser("mykey") + c.Assert(err, qt.IsNil) + c.Assert(info.Name, qt.Equals, "mykey") + io.WriteString(w, "Hugo is great!") + w.Close() + c.Assert(caches.ImageCache().getString("mykey"), qt.Equals, "Hugo is great!") + + info, r, err := caches.ImageCache().Get("mykey") + c.Assert(err, qt.IsNil) + c.Assert(r, qt.Not(qt.IsNil)) + c.Assert(info.Name, qt.Equals, "mykey") + b, _ := ioutil.ReadAll(r) + r.Close() + c.Assert(string(b), qt.Equals, "Hugo is great!") + + info, b, err = caches.ImageCache().GetBytes("mykey") + c.Assert(err, qt.IsNil) + c.Assert(info.Name, qt.Equals, "mykey") + c.Assert(string(b), qt.Equals, "Hugo is great!") + + } + +} + +func TestFileCacheConcurrent(t *testing.T) { + t.Parallel() + + c := qt.New(t) + + configStr := ` +resourceDir = "myresources" +contentDir = "content" +dataDir = "data" +i18nDir = "i18n" +layoutDir = "layouts" +assetDir = "assets" +archeTypedir = "archetypes" + +[caches] +[caches.getjson] +maxAge = "1s" +dir = "/cache/c" + +` + + p := newPathsSpec(t, afero.NewMemMapFs(), configStr) + + caches, err := NewCaches(p) + c.Assert(err, qt.IsNil) + + const cacheName = "getjson" + + filenameData := func(i int) (string, string) { + data := fmt.Sprintf("data: %d", i) + filename := fmt.Sprintf("file%d", i) + return filename, data + } + + var wg sync.WaitGroup + + for i := 0; i < 50; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + for j := 0; j < 20; j++ { + ca := caches.Get(cacheName) + c.Assert(ca, qt.Not(qt.IsNil)) + filename, data := filenameData(i) + _, r, err := ca.GetOrCreate(filename, func() (io.ReadCloser, error) { + return hugio.ToReadCloser(strings.NewReader(data)), nil + }) + c.Assert(err, qt.IsNil) + b, _ := ioutil.ReadAll(r) + r.Close() + c.Assert(string(b), qt.Equals, data) + // Trigger some expiration. + time.Sleep(50 * time.Millisecond) + } + }(i) + + } + wg.Wait() +} + +func TestFileCacheReadOrCreateErrorInRead(t *testing.T) { + t.Parallel() + c := qt.New(t) + + var result string + + rf := func(failLevel int) func(info ItemInfo, r io.ReadSeeker) error { + + return func(info ItemInfo, r io.ReadSeeker) error { + if failLevel > 0 { + if failLevel > 1 { + return ErrFatal + } + return errors.New("fail") + } + + b, _ := ioutil.ReadAll(r) + result = string(b) + + return nil + } + } + + bf := func(s string) func(info ItemInfo, w io.WriteCloser) error { + return func(info ItemInfo, w io.WriteCloser) error { + defer w.Close() + result = s + _, err := w.Write([]byte(s)) + return err + } + } + + cache := NewCache(afero.NewMemMapFs(), 100*time.Hour, "") + + const id = "a32" + + _, err := cache.ReadOrCreate(id, rf(0), bf("v1")) + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, "v1") + _, err = cache.ReadOrCreate(id, rf(0), bf("v2")) + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, "v1") + _, err = cache.ReadOrCreate(id, rf(1), bf("v3")) + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, "v3") + _, err = cache.ReadOrCreate(id, rf(2), bf("v3")) + c.Assert(err, qt.Equals, ErrFatal) +} + +func TestCleanID(t *testing.T) { + c := qt.New(t) + c.Assert(cleanID(filepath.FromSlash("/a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt")) + c.Assert(cleanID(filepath.FromSlash("a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt")) +} + +func initConfig(fs afero.Fs, cfg config.Provider) error { + if _, err := langs.LoadLanguageSettings(cfg, nil); err != nil { + return err + } + + modConfig, err := modules.DecodeConfig(cfg) + if err != nil { + return err + } + + workingDir := cfg.GetString("workingDir") + themesDir := cfg.GetString("themesDir") + if !filepath.IsAbs(themesDir) { + themesDir = filepath.Join(workingDir, themesDir) + } + modulesClient := modules.NewClient(modules.ClientConfig{ + Fs: fs, + WorkingDir: workingDir, + ThemesDir: themesDir, + ModuleConfig: modConfig, + IgnoreVendor: true, + }) + + moduleConfig, err := modulesClient.Collect() + if err != nil { + return err + } + + if err := modules.ApplyProjectConfigDefaults(cfg, moduleConfig.ActiveModules[len(moduleConfig.ActiveModules)-1]); err != nil { + return err + } + + cfg.Set("allModules", moduleConfig.ActiveModules) + + return nil +} + +func newPathsSpec(t *testing.T, fs afero.Fs, configStr string) *helpers.PathSpec { + c := qt.New(t) + cfg, err := config.FromConfigString(configStr, "toml") + c.Assert(err, qt.IsNil) + initConfig(fs, cfg) + p, err := helpers.NewPathSpec(hugofs.NewFrom(fs, cfg), cfg, nil) + c.Assert(err, qt.IsNil) + return p + +} |