github.com/fighterlyt/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 }