// 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 ( "fmt" "io" "io/ioutil" "path/filepath" "regexp" "strings" "sync" "testing" "time" "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" "github.com/stretchr/testify/require" ) func TestFileCache(t *testing.T) { t.Parallel() assert := require.New(t) for _, cacheDir := range []string{"mycache", ""} { configStr := ` workingDir = "/my/work" resourceDir = "resources" cacheDir = "CACHEDIR" contentDir = "content" dataDir = "data" i18nDir = "i18n" layoutDir = "layouts" assetDir = "assets" archeTypedir = "archetypes" [caches] [caches.getJSON] maxAge = "10h" dir = ":cacheDir/c" ` configStr = strings.Replace(configStr, "CACHEDIR", cacheDir, 1) cfg, err := config.FromConfigString(configStr, "toml") assert.NoError(err) fs := hugofs.NewMem(cfg) p, err := helpers.NewPathSpec(fs, cfg) assert.NoError(err) caches, err := NewCaches(p) assert.NoError(err) c := caches.Get("GetJSON") assert.NotNil(c) assert.Equal("10h0m0s", c.maxAge.String()) bfs, ok := c.Fs.(*afero.BasePathFs) assert.True(ok) filename, err := bfs.RealPath("key") assert.NoError(err) if cacheDir != "" { assert.Equal(filepath.FromSlash(cacheDir+"/c/"+filecacheRootDirname+"/getjson/key"), filename) } else { // Temp dir. assert.Regexp(regexp.MustCompile(".*hugo_cache.*"+filecacheRootDirname+".*key"), filename) } c = caches.Get("images") assert.NotNil(c) assert.Equal(time.Duration(-1), c.maxAge) bfs, ok = c.Fs.(*afero.BasePathFs) assert.True(ok) filename, _ = bfs.RealPath("key") assert.Equal(filepath.FromSlash("_gen/images/key"), filename) 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 i := 0; i < 2; i++ { info, r, err := c.GetOrCreate("a", rf("abc")) assert.NoError(err) assert.NotNil(r) assert.Equal("a", info.Name) b, _ := ioutil.ReadAll(r) r.Close() assert.Equal("abc", string(b)) info, b, err = c.GetOrCreateBytes("b", bf) assert.NoError(err) assert.NotNil(r) assert.Equal("b", info.Name) assert.Equal("bcd", string(b)) _, b, err = c.GetOrCreateBytes("a", bf) assert.NoError(err) assert.Equal("abc", string(b)) _, r, err = c.GetOrCreate("a", rf("bcd")) assert.NoError(err) b, _ = ioutil.ReadAll(r) r.Close() assert.Equal("abc", string(b)) } assert.NotNil(caches.Get("getJSON")) info, w, err := caches.ImageCache().WriteCloser("mykey") assert.NoError(err) assert.Equal("mykey", info.Name) io.WriteString(w, "Hugo is great!") w.Close() assert.Equal("Hugo is great!", caches.ImageCache().getString("mykey")) info, r, err := caches.ImageCache().Get("mykey") assert.NoError(err) assert.NotNil(r) assert.Equal("mykey", info.Name) b, _ := ioutil.ReadAll(r) r.Close() assert.Equal("Hugo is great!", string(b)) info, b, err = caches.ImageCache().GetBytes("mykey") assert.NoError(err) assert.Equal("mykey", info.Name) assert.Equal("Hugo is great!", string(b)) } } func TestFileCacheConcurrent(t *testing.T) { t.Parallel() assert := require.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" ` cfg, err := config.FromConfigString(configStr, "toml") assert.NoError(err) fs := hugofs.NewMem(cfg) p, err := helpers.NewPathSpec(fs, cfg) assert.NoError(err) caches, err := NewCaches(p) assert.NoError(err) 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++ { c := caches.Get(cacheName) assert.NotNil(c) filename, data := filenameData(i) _, r, err := c.GetOrCreate(filename, func() (io.ReadCloser, error) { return hugio.ToReadCloser(strings.NewReader(data)), nil }) assert.NoError(err) b, _ := ioutil.ReadAll(r) r.Close() assert.Equal(data, string(b)) // Trigger some expiration. time.Sleep(50 * time.Millisecond) } }(i) } wg.Wait() } func TestCleanID(t *testing.T) { assert := require.New(t) assert.Equal(filepath.FromSlash("a/b/c.txt"), cleanID(filepath.FromSlash("/a/b//c.txt"))) assert.Equal(filepath.FromSlash("a/b/c.txt"), cleanID(filepath.FromSlash("a/b//c.txt"))) }