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  }