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