mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
d6f7a9e28d
Also allow timeout to be set as a duration string, e.g. `30s`. Fixes #6501
348 lines
8.4 KiB
Go
348 lines
8.4 KiB
Go
// 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
|
|
|
|
}
|