github.com/jbramsden/hugo@v0.47.1/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, clean, workDir := newTestBundleSymbolicSources(t)
    94  	sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.Content.Fs)
    95  	defer clean()
    96  
    97  	fileStore := &storeFilenames{}
    98  	logger := loggers.NewErrorLogger()
    99  	c := newCapturer(logger, sourceSpec, fileStore, nil)
   100  
   101  	assert.NoError(c.capture())
   102  
   103  	// Symlink back to content skipped to prevent infinite recursion.
   104  	assert.Equal(uint64(3), logger.LogCountForLevelsGreaterThanorEqualTo(jww.LevelWarn))
   105  
   106  	expected := `
   107  F:
   108  /base/a/page_s.md
   109  /base/a/regular.md
   110  /base/symbolic1/s1.md
   111  /base/symbolic1/s2.md
   112  /base/symbolic3/circus/a/page_s.md
   113  /base/symbolic3/circus/a/regular.md
   114  D:
   115  __bundle/en/base/symbolic2/a1/index.md/resources/en/base/symbolic2/a1/logo.png|en/base/symbolic2/a1/page.md
   116  C:
   117  /base/symbolic3/s1.png
   118  /base/symbolic3/s2.png
   119  `
   120  
   121  	got := strings.Replace(fileStore.sortedStr(), filepath.ToSlash(workDir), "", -1)
   122  	got = strings.Replace(got, "//", "/", -1)
   123  
   124  	if expected != got {
   125  		diff := helpers.DiffStringSlices(strings.Fields(expected), strings.Fields(got))
   126  		t.Log(got)
   127  		t.Fatalf("Failed:\n%s", diff)
   128  	}
   129  }
   130  
   131  func TestPageBundlerCaptureBasic(t *testing.T) {
   132  	t.Parallel()
   133  
   134  	assert := require.New(t)
   135  	fs, cfg := newTestBundleSources(t)
   136  	assert.NoError(loadDefaultSettingsFor(cfg))
   137  	assert.NoError(loadLanguageSettings(cfg, nil))
   138  	ps, err := helpers.NewPathSpec(fs, cfg)
   139  	assert.NoError(err)
   140  
   141  	sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.Content.Fs)
   142  
   143  	fileStore := &storeFilenames{}
   144  
   145  	c := newCapturer(loggers.NewErrorLogger(), sourceSpec, fileStore, nil)
   146  
   147  	assert.NoError(c.capture())
   148  
   149  	expected := `
   150  F:
   151  /work/base/_1.md
   152  /work/base/a/1.md
   153  /work/base/a/2.md
   154  /work/base/assets/pages/mypage.md
   155  D:
   156  __bundle/en/work/base/_index.md/resources/en/work/base/_1.png
   157  __bundle/en/work/base/a/b/index.md/resources/en/work/base/a/b/ab1.md
   158  __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
   159  __bundle/en/work/base/c/bundle/index.md/resources/en/work/base/c/bundle/logo-은행.png
   160  __bundle/en/work/base/root/index.md/resources/en/work/base/root/1.md|en/work/base/root/c/logo.png
   161  C:
   162  /work/base/assets/pic1.png
   163  /work/base/assets/pic2.png
   164  /work/base/images/hugo-logo.png
   165  `
   166  
   167  	got := fileStore.sortedStr()
   168  
   169  	if expected != got {
   170  		diff := helpers.DiffStringSlices(strings.Fields(expected), strings.Fields(got))
   171  		t.Log(got)
   172  		t.Fatalf("Failed:\n%s", diff)
   173  	}
   174  }
   175  
   176  func TestPageBundlerCaptureMultilingual(t *testing.T) {
   177  	t.Parallel()
   178  
   179  	assert := require.New(t)
   180  	fs, cfg := newTestBundleSourcesMultilingual(t)
   181  	assert.NoError(loadDefaultSettingsFor(cfg))
   182  	assert.NoError(loadLanguageSettings(cfg, nil))
   183  
   184  	ps, err := helpers.NewPathSpec(fs, cfg)
   185  	assert.NoError(err)
   186  
   187  	sourceSpec := source.NewSourceSpec(ps, ps.BaseFs.Content.Fs)
   188  	fileStore := &storeFilenames{}
   189  	c := newCapturer(loggers.NewErrorLogger(), sourceSpec, fileStore, nil)
   190  
   191  	assert.NoError(c.capture())
   192  
   193  	expected := `
   194  F:
   195  /work/base/1s/mypage.md
   196  /work/base/1s/mypage.nn.md
   197  /work/base/bb/_1.md
   198  /work/base/bb/_1.nn.md
   199  /work/base/bb/en.md
   200  /work/base/bc/page.md
   201  /work/base/bc/page.nn.md
   202  /work/base/be/_index.md
   203  /work/base/be/page.md
   204  /work/base/be/page.nn.md
   205  D:
   206  __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
   207  __bundle/en/work/base/bc/_index.md/resources/en/work/base/bc/logo-bc.png
   208  __bundle/en/work/base/bd/index.md/resources/en/work/base/bd/page.md
   209  __bundle/en/work/base/bf/my-bf-bundle/index.md/resources/en/work/base/bf/my-bf-bundle/page.md
   210  __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
   211  __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
   212  __bundle/nn/work/base/bd/index.md/resources/nn/work/base/bd/page.nn.md
   213  __bundle/nn/work/base/bf/my-bf-bundle/index.nn.md/resources
   214  __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
   215  C:
   216  /work/base/1s/mylogo.png
   217  /work/base/bb/b/d.nn.png
   218  `
   219  
   220  	got := fileStore.sortedStr()
   221  
   222  	if expected != got {
   223  		diff := helpers.DiffStringSlices(strings.Fields(expected), strings.Fields(got))
   224  		t.Log(got)
   225  		t.Fatalf("Failed:\n%s", strings.Join(diff, "\n"))
   226  	}
   227  
   228  }
   229  
   230  type noOpFileStore int
   231  
   232  func (noOpFileStore) handleSingles(fis ...*fileInfo)        {}
   233  func (noOpFileStore) handleBundles(b *bundleDirs)           {}
   234  func (noOpFileStore) handleCopyFiles(files ...pathLangFile) {}
   235  
   236  func BenchmarkPageBundlerCapture(b *testing.B) {
   237  	capturers := make([]*capturer, b.N)
   238  
   239  	for i := 0; i < b.N; i++ {
   240  		cfg, fs := newTestCfg()
   241  		ps, _ := helpers.NewPathSpec(fs, cfg)
   242  		sourceSpec := source.NewSourceSpec(ps, fs.Source)
   243  
   244  		base := fmt.Sprintf("base%d", i)
   245  		for j := 1; j <= 5; j++ {
   246  			js := fmt.Sprintf("j%d", j)
   247  			writeSource(b, fs, filepath.Join(base, js, "index.md"), "content")
   248  			writeSource(b, fs, filepath.Join(base, js, "logo1.png"), "content")
   249  			writeSource(b, fs, filepath.Join(base, js, "sub", "logo2.png"), "content")
   250  			writeSource(b, fs, filepath.Join(base, js, "section", "_index.md"), "content")
   251  			writeSource(b, fs, filepath.Join(base, js, "section", "logo.png"), "content")
   252  			writeSource(b, fs, filepath.Join(base, js, "section", "sub", "logo.png"), "content")
   253  
   254  			for k := 1; k <= 5; k++ {
   255  				ks := fmt.Sprintf("k%d", k)
   256  				writeSource(b, fs, filepath.Join(base, js, ks, "logo1.png"), "content")
   257  				writeSource(b, fs, filepath.Join(base, js, "section", ks, "logo.png"), "content")
   258  			}
   259  		}
   260  
   261  		for i := 1; i <= 5; i++ {
   262  			writeSource(b, fs, filepath.Join(base, "assetsonly", fmt.Sprintf("image%d.png", i)), "image")
   263  		}
   264  
   265  		for i := 1; i <= 5; i++ {
   266  			writeSource(b, fs, filepath.Join(base, "contentonly", fmt.Sprintf("c%d.md", i)), "content")
   267  		}
   268  
   269  		capturers[i] = newCapturer(loggers.NewErrorLogger(), sourceSpec, new(noOpFileStore), nil, base)
   270  	}
   271  
   272  	b.ResetTimer()
   273  	for i := 0; i < b.N; i++ {
   274  		err := capturers[i].capture()
   275  		if err != nil {
   276  			b.Fatal(err)
   277  		}
   278  	}
   279  }