github.com/olliephillips/hugo@v0.42.2/hugolib/page_bundler_capture_test.go (about)

     1  // Copyright 2017-present 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 hugolib
    15  
    16  import (
    17  	"fmt"
    18  	"os"
    19  	"path"
    20  	"path/filepath"
    21  	"sort"
    22  
    23  	"github.com/gohugoio/hugo/common/loggers"
    24  
    25  	jww "github.com/spf13/jwalterweatherman"
    26  
    27  	"runtime"
    28  	"strings"
    29  	"sync"
    30  	"testing"
    31  
    32  	"github.com/gohugoio/hugo/helpers"
    33  	"github.com/gohugoio/hugo/source"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  type storeFilenames struct {
    38  	sync.Mutex
    39  	filenames []string
    40  	copyNames []string
    41  	dirKeys   []string
    42  }
    43  
    44  func (s *storeFilenames) handleSingles(fis ...*fileInfo) {
    45  	s.Lock()
    46  	defer s.Unlock()
    47  	for _, fi := range fis {
    48  		s.filenames = append(s.filenames, filepath.ToSlash(fi.Filename()))
    49  	}
    50  }
    51  
    52  func (s *storeFilenames) handleBundles(d *bundleDirs) {
    53  	s.Lock()
    54  	defer s.Unlock()
    55  	var keys []string
    56  	for _, b := range d.bundles {
    57  		res := make([]string, len(b.resources))
    58  		i := 0
    59  		for _, r := range b.resources {
    60  			res[i] = path.Join(r.Lang(), filepath.ToSlash(r.Filename()))
    61  			i++
    62  		}
    63  		sort.Strings(res)
    64  		keys = append(keys, path.Join("__bundle", b.fi.Lang(), filepath.ToSlash(b.fi.Filename()), "resources", strings.Join(res, "|")))
    65  	}
    66  	s.dirKeys = append(s.dirKeys, keys...)
    67  }
    68  
    69  func (s *storeFilenames) handleCopyFiles(files ...pathLangFile) {
    70  	s.Lock()
    71  	defer s.Unlock()
    72  	for _, file := range files {
    73  		s.copyNames = append(s.copyNames, filepath.ToSlash(file.Filename()))
    74  	}
    75  }
    76  
    77  func (s *storeFilenames) sortedStr() string {
    78  	s.Lock()
    79  	defer s.Unlock()
    80  	sort.Strings(s.filenames)
    81  	sort.Strings(s.dirKeys)
    82  	sort.Strings(s.copyNames)
    83  	return "\nF:\n" + strings.Join(s.filenames, "\n") + "\nD:\n" + strings.Join(s.dirKeys, "\n") +
    84  		"\nC:\n" + strings.Join(s.copyNames, "\n") + "\n"
    85  }
    86  
    87  func TestPageBundlerCaptureSymlinks(t *testing.T) {
    88  	if runtime.GOOS == "windows" && os.Getenv("CI") == "" {
    89  		t.Skip("Skip TestPageBundlerCaptureSymlinks as os.Symlink needs administrator rights on Windows")
    90  	}
    91  
    92  	assert := require.New(t)
    93  	ps, workDir := newTestBundleSymbolicSources(t)
    94  	sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.ContentFs)
    95  
    96  	fileStore := &storeFilenames{}
    97  	logger := loggers.NewErrorLogger()
    98  	c := newCapturer(logger, sourceSpec, fileStore, nil)
    99  
   100  	assert.NoError(c.capture())
   101  
   102  	// Symlink back to content skipped to prevent infinite recursion.
   103  	assert.Equal(uint64(3), logger.LogCountForLevelsGreaterThanorEqualTo(jww.LevelWarn))
   104  
   105  	expected := `
   106  F:
   107  /base/a/page_s.md
   108  /base/a/regular.md
   109  /base/symbolic1/s1.md
   110  /base/symbolic1/s2.md
   111  /base/symbolic3/circus/a/page_s.md
   112  /base/symbolic3/circus/a/regular.md
   113  D:
   114  __bundle/en/base/symbolic2/a1/index.md/resources/en/base/symbolic2/a1/logo.png|en/base/symbolic2/a1/page.md
   115  C:
   116  /base/symbolic3/s1.png
   117  /base/symbolic3/s2.png
   118  `
   119  
   120  	got := strings.Replace(fileStore.sortedStr(), filepath.ToSlash(workDir), "", -1)
   121  	got = strings.Replace(got, "//", "/", -1)
   122  
   123  	if expected != got {
   124  		diff := helpers.DiffStringSlices(strings.Fields(expected), strings.Fields(got))
   125  		t.Log(got)
   126  		t.Fatalf("Failed:\n%s", diff)
   127  	}
   128  }
   129  
   130  func TestPageBundlerCaptureBasic(t *testing.T) {
   131  	t.Parallel()
   132  
   133  	assert := require.New(t)
   134  	fs, cfg := newTestBundleSources(t)
   135  	assert.NoError(loadDefaultSettingsFor(cfg))
   136  	assert.NoError(loadLanguageSettings(cfg, nil))
   137  	ps, err := helpers.NewPathSpec(fs, cfg)
   138  	assert.NoError(err)
   139  
   140  	sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.ContentFs)
   141  
   142  	fileStore := &storeFilenames{}
   143  
   144  	c := newCapturer(loggers.NewErrorLogger(), sourceSpec, fileStore, nil)
   145  
   146  	assert.NoError(c.capture())
   147  
   148  	expected := `
   149  F:
   150  /work/base/_1.md
   151  /work/base/a/1.md
   152  /work/base/a/2.md
   153  /work/base/assets/pages/mypage.md
   154  D:
   155  __bundle/en/work/base/_index.md/resources/en/work/base/_1.png
   156  __bundle/en/work/base/a/b/index.md/resources/en/work/base/a/b/ab1.md
   157  __bundle/en/work/base/b/my-bundle/index.md/resources/en/work/base/b/my-bundle/1.md|en/work/base/b/my-bundle/2.md|en/work/base/b/my-bundle/c/logo.png|en/work/base/b/my-bundle/custom-mime.bep|en/work/base/b/my-bundle/sunset1.jpg|en/work/base/b/my-bundle/sunset2.jpg
   158  __bundle/en/work/base/c/bundle/index.md/resources/en/work/base/c/bundle/logo-은행.png
   159  __bundle/en/work/base/root/index.md/resources/en/work/base/root/1.md|en/work/base/root/c/logo.png
   160  C:
   161  /work/base/assets/pic1.png
   162  /work/base/assets/pic2.png
   163  /work/base/images/hugo-logo.png
   164  `
   165  
   166  	got := fileStore.sortedStr()
   167  
   168  	if expected != got {
   169  		diff := helpers.DiffStringSlices(strings.Fields(expected), strings.Fields(got))
   170  		t.Log(got)
   171  		t.Fatalf("Failed:\n%s", diff)
   172  	}
   173  }
   174  
   175  func TestPageBundlerCaptureMultilingual(t *testing.T) {
   176  	t.Parallel()
   177  
   178  	assert := require.New(t)
   179  	fs, cfg := newTestBundleSourcesMultilingual(t)
   180  	assert.NoError(loadDefaultSettingsFor(cfg))
   181  	assert.NoError(loadLanguageSettings(cfg, nil))
   182  
   183  	ps, err := helpers.NewPathSpec(fs, cfg)
   184  	assert.NoError(err)
   185  
   186  	sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.ContentFs)
   187  	fileStore := &storeFilenames{}
   188  	c := newCapturer(loggers.NewErrorLogger(), sourceSpec, fileStore, nil)
   189  
   190  	assert.NoError(c.capture())
   191  
   192  	expected := `
   193  F:
   194  /work/base/1s/mypage.md
   195  /work/base/1s/mypage.nn.md
   196  /work/base/bb/_1.md
   197  /work/base/bb/_1.nn.md
   198  /work/base/bb/en.md
   199  /work/base/bc/page.md
   200  /work/base/bc/page.nn.md
   201  /work/base/be/_index.md
   202  /work/base/be/page.md
   203  /work/base/be/page.nn.md
   204  D:
   205  __bundle/en/work/base/bb/_index.md/resources/en/work/base/bb/a.png|en/work/base/bb/b.png|nn/work/base/bb/c.nn.png
   206  __bundle/en/work/base/bc/_index.md/resources/en/work/base/bc/logo-bc.png
   207  __bundle/en/work/base/bd/index.md/resources/en/work/base/bd/page.md
   208  __bundle/en/work/base/bf/my-bf-bundle/index.md/resources/en/work/base/bf/my-bf-bundle/page.md
   209  __bundle/en/work/base/lb/index.md/resources/en/work/base/lb/1.md|en/work/base/lb/2.md|en/work/base/lb/c/d/deep.png|en/work/base/lb/c/logo.png|en/work/base/lb/c/one.png|en/work/base/lb/c/page.md
   210  __bundle/nn/work/base/bb/_index.nn.md/resources/en/work/base/bb/a.png|nn/work/base/bb/b.nn.png|nn/work/base/bb/c.nn.png
   211  __bundle/nn/work/base/bd/index.md/resources/nn/work/base/bd/page.nn.md
   212  __bundle/nn/work/base/bf/my-bf-bundle/index.nn.md/resources
   213  __bundle/nn/work/base/lb/index.nn.md/resources/en/work/base/lb/c/d/deep.png|en/work/base/lb/c/one.png|nn/work/base/lb/2.nn.md|nn/work/base/lb/c/logo.nn.png
   214  C:
   215  /work/base/1s/mylogo.png
   216  /work/base/bb/b/d.nn.png
   217  `
   218  
   219  	got := fileStore.sortedStr()
   220  
   221  	if expected != got {
   222  		diff := helpers.DiffStringSlices(strings.Fields(expected), strings.Fields(got))
   223  		t.Log(got)
   224  		t.Fatalf("Failed:\n%s", strings.Join(diff, "\n"))
   225  	}
   226  
   227  }
   228  
   229  type noOpFileStore int
   230  
   231  func (noOpFileStore) handleSingles(fis ...*fileInfo)        {}
   232  func (noOpFileStore) handleBundles(b *bundleDirs)           {}
   233  func (noOpFileStore) handleCopyFiles(files ...pathLangFile) {}
   234  
   235  func BenchmarkPageBundlerCapture(b *testing.B) {
   236  	capturers := make([]*capturer, b.N)
   237  
   238  	for i := 0; i < b.N; i++ {
   239  		cfg, fs := newTestCfg()
   240  		ps, _ := helpers.NewPathSpec(fs, cfg)
   241  		sourceSpec := source.NewSourceSpec(ps, fs.Source)
   242  
   243  		base := fmt.Sprintf("base%d", i)
   244  		for j := 1; j <= 5; j++ {
   245  			js := fmt.Sprintf("j%d", j)
   246  			writeSource(b, fs, filepath.Join(base, js, "index.md"), "content")
   247  			writeSource(b, fs, filepath.Join(base, js, "logo1.png"), "content")
   248  			writeSource(b, fs, filepath.Join(base, js, "sub", "logo2.png"), "content")
   249  			writeSource(b, fs, filepath.Join(base, js, "section", "_index.md"), "content")
   250  			writeSource(b, fs, filepath.Join(base, js, "section", "logo.png"), "content")
   251  			writeSource(b, fs, filepath.Join(base, js, "section", "sub", "logo.png"), "content")
   252  
   253  			for k := 1; k <= 5; k++ {
   254  				ks := fmt.Sprintf("k%d", k)
   255  				writeSource(b, fs, filepath.Join(base, js, ks, "logo1.png"), "content")
   256  				writeSource(b, fs, filepath.Join(base, js, "section", ks, "logo.png"), "content")
   257  			}
   258  		}
   259  
   260  		for i := 1; i <= 5; i++ {
   261  			writeSource(b, fs, filepath.Join(base, "assetsonly", fmt.Sprintf("image%d.png", i)), "image")
   262  		}
   263  
   264  		for i := 1; i <= 5; i++ {
   265  			writeSource(b, fs, filepath.Join(base, "contentonly", fmt.Sprintf("c%d.md", i)), "content")
   266  		}
   267  
   268  		capturers[i] = newCapturer(loggers.NewErrorLogger(), sourceSpec, new(noOpFileStore), nil, base)
   269  	}
   270  
   271  	b.ResetTimer()
   272  	for i := 0; i < b.N; i++ {
   273  		err := capturers[i].capture()
   274  		if err != nil {
   275  			b.Fatal(err)
   276  		}
   277  	}
   278  }