github.com/neohugo/neohugo@v0.123.8/hugolib/site_benchmark_new_test.go (about)

     1  // Copyright 2019 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  	"math/rand"
    19  	"path"
    20  	"path/filepath"
    21  	"strconv"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/neohugo/neohugo/resources/page"
    26  
    27  	qt "github.com/frankban/quicktest"
    28  )
    29  
    30  type siteBenchmarkTestcase struct {
    31  	name   string
    32  	create func(t testing.TB) *sitesBuilder
    33  	check  func(s *sitesBuilder)
    34  }
    35  
    36  func getBenchmarkSiteDeepContent(b testing.TB) *sitesBuilder {
    37  	pageContent := func(size int) string {
    38  		return getBenchmarkTestDataPageContentForMarkdown(size, false, "", benchmarkMarkdownSnippets)
    39  	}
    40  
    41  	sb := newTestSitesBuilder(b).WithConfigFile("toml", `
    42  baseURL = "https://example.com"
    43  
    44  [languages]
    45  [languages.en]
    46  weight=1
    47  contentDir="content/en"
    48  [languages.fr]
    49  weight=2
    50  contentDir="content/fr"
    51  [languages.no]
    52  weight=3
    53  contentDir="content/no"
    54  [languages.sv]
    55  weight=4
    56  contentDir="content/sv"
    57  			
    58  `)
    59  
    60  	createContent := func(dir, name string) {
    61  		sb.WithContent(filepath.Join("content", dir, name), pageContent(1))
    62  	}
    63  
    64  	createBundledFiles := func(dir string) {
    65  		sb.WithContent(filepath.Join("content", dir, "data.json"), `{ "hello": "world" }`)
    66  		for i := 1; i <= 3; i++ {
    67  			sb.WithContent(filepath.Join("content", dir, fmt.Sprintf("page%d.md", i)), pageContent(1))
    68  		}
    69  	}
    70  
    71  	for _, lang := range []string{"en", "fr", "no", "sv"} {
    72  		for level := 1; level <= 5; level++ {
    73  			sectionDir := path.Join(lang, strings.Repeat("section/", level))
    74  			createContent(sectionDir, "_index.md")
    75  			createBundledFiles(sectionDir)
    76  			for i := 1; i <= 3; i++ {
    77  				leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
    78  				createContent(leafBundleDir, "index.md")
    79  				createBundledFiles(path.Join(leafBundleDir, "assets1"))
    80  				createBundledFiles(path.Join(leafBundleDir, "assets1", "assets2"))
    81  			}
    82  		}
    83  	}
    84  
    85  	return sb
    86  }
    87  
    88  func getBenchmarkTestDataPageContentForMarkdown(size int, toml bool, category, markdown string) string {
    89  	base := `---
    90  title: "My Page"
    91  %s
    92  ---
    93  
    94  My page content.
    95  `
    96  	if toml {
    97  		base = `+++
    98  title="My Page"
    99  %s
   100  +++
   101  
   102  My page content.
   103  `
   104  	}
   105  
   106  	var categoryKey string
   107  	if category != "" {
   108  		categoryKey = fmt.Sprintf("categories: [%s]", category)
   109  		if toml {
   110  			categoryKey = fmt.Sprintf("categories=[%s]", category)
   111  		}
   112  	}
   113  	base = fmt.Sprintf(base, categoryKey)
   114  
   115  	return base + strings.Repeat(markdown, size)
   116  }
   117  
   118  const benchmarkMarkdownSnippets = `
   119  
   120  ## Links
   121  
   122  
   123  This is [an example](http://example.com/ "Title") inline link.
   124  
   125  [This link](http://example.net/) has no title attribute.
   126  
   127  This is [Relative](/all-is-relative).
   128  
   129  See my [About](/about/) page for details. 
   130  `
   131  
   132  func getBenchmarkSiteTestCases() []siteBenchmarkTestcase {
   133  	pageContentWithCategory := func(size int, category string) string {
   134  		return getBenchmarkTestDataPageContentForMarkdown(size, false, category, benchmarkMarkdownSnippets)
   135  	}
   136  
   137  	pageContent := func(size int) string {
   138  		return getBenchmarkTestDataPageContentForMarkdown(size, false, "", benchmarkMarkdownSnippets)
   139  	}
   140  
   141  	config := `
   142  baseURL = "https://example.com"
   143  `
   144  
   145  	benchmarks := []siteBenchmarkTestcase{
   146  		{
   147  			"Bundle with image", func(b testing.TB) *sitesBuilder {
   148  				sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
   149  				sb.WithContent("content/blog/mybundle/index.md", pageContent(1))
   150  				sb.WithSunset("content/blog/mybundle/sunset1.jpg")
   151  
   152  				return sb
   153  			},
   154  			func(s *sitesBuilder) {
   155  				s.AssertFileContent("public/blog/mybundle/index.html", "/blog/mybundle/sunset1.jpg")
   156  				s.CheckExists("public/blog/mybundle/sunset1.jpg")
   157  			},
   158  		},
   159  		{
   160  			"Bundle with JSON file", func(b testing.TB) *sitesBuilder {
   161  				sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
   162  				sb.WithContent("content/blog/mybundle/index.md", pageContent(1))
   163  				sb.WithContent("content/blog/mybundle/mydata.json", `{ "hello": "world" }`)
   164  
   165  				return sb
   166  			},
   167  			func(s *sitesBuilder) {
   168  				s.AssertFileContent("public/blog/mybundle/index.html", "Resources: application/json: /blog/mybundle/mydata.json")
   169  				s.CheckExists("public/blog/mybundle/mydata.json")
   170  			},
   171  		},
   172  		{
   173  			"Tags and categories", func(b testing.TB) *sitesBuilder {
   174  				sb := newTestSitesBuilder(b).WithConfigFile("toml", `
   175  title = "Tags and Cats"
   176  baseURL = "https://example.com"
   177  
   178  `)
   179  
   180  				const pageTemplate = `
   181  ---
   182  title: "Some tags and cats"
   183  categories: ["caGR", "cbGR"]
   184  tags: ["taGR", "tbGR"]
   185  ---
   186  
   187  Some content.
   188  			
   189  `
   190  				for i := 1; i <= 100; i++ {
   191  					content := strings.Replace(pageTemplate, "GR", strconv.Itoa(i/3), -1)
   192  					sb.WithContent(fmt.Sprintf("content/page%d.md", i), content)
   193  				}
   194  
   195  				return sb
   196  			},
   197  			func(s *sitesBuilder) {
   198  				s.AssertFileContent("public/page3/index.html", "/page3/|Permalink: https://example.com/page3/")
   199  				s.AssertFileContent("public/tags/ta3/index.html", "a3")
   200  			},
   201  		},
   202  		{
   203  			"Canonify URLs", func(b testing.TB) *sitesBuilder {
   204  				sb := newTestSitesBuilder(b).WithConfigFile("toml", `
   205  title = "Canon"
   206  baseURL = "https://example.com"
   207  canonifyURLs = true
   208  
   209  `)
   210  				for i := 1; i <= 100; i++ {
   211  					sb.WithContent(fmt.Sprintf("content/page%d.md", i), pageContent(i))
   212  				}
   213  
   214  				return sb
   215  			},
   216  			func(s *sitesBuilder) {
   217  				s.AssertFileContent("public/page8/index.html", "https://example.com/about/")
   218  			},
   219  		},
   220  
   221  		{
   222  			"Deep content tree", func(b testing.TB) *sitesBuilder {
   223  				return getBenchmarkSiteDeepContent(b)
   224  			},
   225  			func(s *sitesBuilder) {
   226  				s.CheckExists("public/blog/mybundle/index.html")
   227  				s.Assert(len(s.H.Sites), qt.Equals, 4)
   228  				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, len(s.H.Sites[1].RegularPages()))
   229  				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 30)
   230  			},
   231  		},
   232  		{
   233  			"TOML front matter", func(b testing.TB) *sitesBuilder {
   234  				sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
   235  				for i := 1; i <= 200; i++ {
   236  					content := getBenchmarkTestDataPageContentForMarkdown(1, true, "\"a\", \"b\", \"c\"", benchmarkMarkdownSnippets)
   237  					sb.WithContent(fmt.Sprintf("content/p%d.md", i), content)
   238  				}
   239  
   240  				return sb
   241  			},
   242  			func(s *sitesBuilder) {
   243  			},
   244  		},
   245  		{
   246  			"Many HTML templates", func(b testing.TB) *sitesBuilder {
   247  				pageTemplateTemplate := `
   248  <!DOCTYPE html>
   249  <html>
   250    <head>
   251      <meta charset="utf-8">
   252      <title>{{ if not .IsPage }}{{ .Title }}{{ else }}{{ printf "Site: %s" site.Title }}{{ end }}</title>
   253      <style>
   254       body {
   255         margin: 3rem;
   256       }
   257      </style>
   258    </head>
   259    <body>
   260      <div class="page">{{ .Content }}</div>
   261      <ul>
   262      {{ with .Pages }}
   263      {{ range . }}
   264      <li><a href="{{ .RelPermalink }}">{{ .LinkTitle }} {{ if not .IsNode }} (Page){{ end }}</a></li>
   265      {{ end }}
   266      {{ end }}
   267      </ul>
   268    </body>
   269  </html>
   270  `
   271  
   272  				sb := newTestSitesBuilder(b).WithConfigFile("toml", `
   273  baseURL = "https://example.com"
   274  
   275  [languages]
   276  [languages.en]
   277  weight=1
   278  contentDir="content/en"
   279  [languages.fr]
   280  weight=2
   281  contentDir="content/fr"
   282  [languages.no]
   283  weight=3
   284  contentDir="content/no"
   285  [languages.sv]
   286  weight=4
   287  contentDir="content/sv"
   288  			
   289  `)
   290  
   291  				createContent := func(dir, name string) {
   292  					sb.WithContent(filepath.Join("content", dir, name), pageContent(1))
   293  				}
   294  
   295  				for _, lang := range []string{"en", "fr", "no", "sv"} {
   296  					sb.WithTemplatesAdded(fmt.Sprintf("_default/single.%s.html", lang), pageTemplateTemplate)
   297  					sb.WithTemplatesAdded(fmt.Sprintf("_default/list.%s.html", lang), pageTemplateTemplate)
   298  
   299  					for level := 1; level <= 5; level++ {
   300  						sectionDir := path.Join(lang, strings.Repeat("section/", level))
   301  						createContent(sectionDir, "_index.md")
   302  						for i := 1; i <= 3; i++ {
   303  							leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
   304  							createContent(leafBundleDir, "index.md")
   305  						}
   306  					}
   307  				}
   308  
   309  				return sb
   310  			},
   311  			func(s *sitesBuilder) {
   312  				s.CheckExists("public/blog/mybundle/index.html")
   313  				s.Assert(len(s.H.Sites), qt.Equals, 4)
   314  				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, len(s.H.Sites[1].RegularPages()))
   315  				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 15)
   316  			},
   317  		},
   318  		{
   319  			"Page collections", func(b testing.TB) *sitesBuilder {
   320  				pageTemplateTemplate := `
   321  {{ if .IsNode }}
   322  {{ len .Paginator.Pages }}
   323  {{ end }}
   324  {{ len .Sections }}
   325  {{ len .Pages }}
   326  {{ len .RegularPages }}
   327  {{ len .Resources }}
   328  {{ len site.RegularPages }}
   329  {{ len site.Pages }}
   330  {{ with .NextInSection }}Next in section: {{ .RelPermalink }}{{ end }}
   331  {{ with .PrevInSection }}Prev in section: {{ .RelPermalink }}{{ end }}
   332  {{ with .Next }}Next: {{ .RelPermalink }}{{ end }}
   333  {{ with .Prev }}Prev: {{ .RelPermalink }}{{ end }}
   334  `
   335  
   336  				sb := newTestSitesBuilder(b).WithConfigFile("toml", `
   337  baseURL = "https://example.com"
   338  
   339  [languages]
   340  [languages.en]
   341  weight=1
   342  contentDir="content/en"
   343  [languages.fr]
   344  weight=2
   345  contentDir="content/fr"
   346  [languages.no]
   347  weight=3
   348  contentDir="content/no"
   349  [languages.sv]
   350  weight=4
   351  contentDir="content/sv"
   352  			
   353  `)
   354  
   355  				sb.WithTemplates("index.html", pageTemplateTemplate)
   356  				sb.WithTemplates("_default/single.html", pageTemplateTemplate)
   357  				sb.WithTemplates("_default/list.html", pageTemplateTemplate)
   358  
   359  				r := rand.New(rand.NewSource(99))
   360  
   361  				createContent := func(dir, name string) {
   362  					var content string
   363  					if strings.Contains(name, "_index") {
   364  						content = pageContent(1)
   365  					} else {
   366  						content = pageContentWithCategory(1, fmt.Sprintf("category%d", r.Intn(5)+1))
   367  					}
   368  
   369  					sb.WithContent(filepath.Join("content", dir, name), content)
   370  				}
   371  
   372  				createBundledFiles := func(dir string) {
   373  					sb.WithContent(filepath.Join("content", dir, "data.json"), `{ "hello": "world" }`)
   374  					for i := 1; i <= 3; i++ {
   375  						sb.WithContent(filepath.Join("content", dir, fmt.Sprintf("page%d.md", i)), pageContent(1))
   376  					}
   377  				}
   378  
   379  				for _, lang := range []string{"en", "fr", "no", "sv"} {
   380  					for level := 1; level <= r.Intn(5)+1; level++ {
   381  						sectionDir := path.Join(lang, strings.Repeat("section/", level))
   382  						createContent(sectionDir, "_index.md")
   383  						createBundledFiles(sectionDir)
   384  						for i := 1; i <= r.Intn(20)+1; i++ {
   385  							leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
   386  							createContent(leafBundleDir, "index.md")
   387  							createBundledFiles(path.Join(leafBundleDir, "assets1"))
   388  							createBundledFiles(path.Join(leafBundleDir, "assets1", "assets2"))
   389  						}
   390  					}
   391  				}
   392  
   393  				return sb
   394  			},
   395  			func(s *sitesBuilder) {
   396  				s.CheckExists("public/blog/mybundle/index.html")
   397  				s.Assert(len(s.H.Sites), qt.Equals, 4)
   398  				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 26)
   399  			},
   400  		},
   401  		{
   402  			"List terms", func(b testing.TB) *sitesBuilder {
   403  				pageTemplateTemplate := `
   404  <ul>
   405      {{ range (.GetTerms "categories") }}
   406          <li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
   407     {{ end }}
   408  </ul>
   409  `
   410  
   411  				sb := newTestSitesBuilder(b).WithConfigFile("toml", `
   412  baseURL = "https://example.com"
   413  `)
   414  
   415  				sb.WithTemplates("_default/single.html", pageTemplateTemplate)
   416  				sb.WithTemplates("_default/list.html", "List")
   417  
   418  				r := rand.New(rand.NewSource(99))
   419  
   420  				createContent := func(dir, name string) {
   421  					var content string
   422  					if strings.Contains(name, "_index") {
   423  						// Empty
   424  					} else {
   425  						content = pageContentWithCategory(1, fmt.Sprintf("category%d", r.Intn(5)+1))
   426  					}
   427  					sb.WithContent(filepath.Join("content", dir, name), content)
   428  				}
   429  
   430  				for level := 1; level <= r.Intn(5)+1; level++ {
   431  					sectionDir := path.Join(strings.Repeat("section/", level))
   432  					createContent(sectionDir, "_index.md")
   433  					for i := 1; i <= r.Intn(33); i++ {
   434  						leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
   435  						createContent(leafBundleDir, "index.md")
   436  					}
   437  				}
   438  
   439  				return sb
   440  			},
   441  			func(s *sitesBuilder) {
   442  				s.AssertFileContent("public/section/bundle8/index.html", `<a href="https://example.com/categories/category1/">`)
   443  				s.Assert(len(s.H.Sites), qt.Equals, 1)
   444  				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 35)
   445  			},
   446  		},
   447  	}
   448  
   449  	return benchmarks
   450  }
   451  
   452  // Run the benchmarks below as tests. Mostly useful when adding new benchmark
   453  // variants.
   454  func TestBenchmarkSite(b *testing.T) {
   455  	benchmarks := getBenchmarkSiteTestCases()
   456  	for _, bm := range benchmarks {
   457  		if bm.name != "Deep content tree" {
   458  			continue
   459  		}
   460  		b.Run(bm.name, func(b *testing.T) {
   461  			s := bm.create(b)
   462  
   463  			err := s.BuildE(BuildCfg{})
   464  			if err != nil {
   465  				b.Fatal(err)
   466  			}
   467  			bm.check(s)
   468  		})
   469  	}
   470  }
   471  
   472  func TestBenchmarkSiteDeepContentEdit(t *testing.T) {
   473  	b := getBenchmarkSiteDeepContent(t).Running()
   474  	b.Build(BuildCfg{})
   475  
   476  	p := b.H.Sites[0].RegularPages()[12]
   477  
   478  	b.EditFiles(p.File().Filename(), fmt.Sprintf(`---
   479  title: %s
   480  ---
   481  
   482  Edited!!`, p.Title()))
   483  
   484  	counters := &buildCounters{}
   485  
   486  	b.Build(BuildCfg{testCounters: counters})
   487  
   488  	// We currently rebuild all the language versions of the same content file.
   489  	// We could probably optimize that case, but it's not trivial.
   490  	b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 4)
   491  	b.AssertFileContent("public"+p.RelPermalink()+"index.html", "Edited!!")
   492  }
   493  
   494  func BenchmarkSiteNew(b *testing.B) {
   495  	rnd := rand.New(rand.NewSource(32))
   496  	benchmarks := getBenchmarkSiteTestCases()
   497  	for _, edit := range []bool{true, false} {
   498  		for _, bm := range benchmarks {
   499  			name := bm.name
   500  			if edit {
   501  				name = "Edit_" + name
   502  			} else {
   503  				name = "Regular_" + name
   504  			}
   505  			b.Run(name, func(b *testing.B) {
   506  				sites := make([]*sitesBuilder, b.N)
   507  				for i := 0; i < b.N; i++ {
   508  					sites[i] = bm.create(b)
   509  					if edit {
   510  						sites[i].Running()
   511  					}
   512  				}
   513  
   514  				b.ResetTimer()
   515  				for i := 0; i < b.N; i++ {
   516  					if edit {
   517  						b.StopTimer()
   518  					}
   519  					s := sites[i]
   520  					err := s.BuildE(BuildCfg{})
   521  					if err != nil {
   522  						b.Fatal(err)
   523  					}
   524  					bm.check(s)
   525  
   526  					if edit {
   527  						if edit {
   528  							b.StartTimer()
   529  						}
   530  						// Edit a random page in a random language.
   531  						pages := s.H.Sites[rnd.Intn(len(s.H.Sites))].Pages()
   532  						var p page.Page
   533  						count := 0
   534  						for {
   535  							count++
   536  							if count > 100 {
   537  								panic("infinite loop")
   538  							}
   539  							p = pages[rnd.Intn(len(pages))]
   540  							if p.File() != nil {
   541  								break
   542  							}
   543  						}
   544  
   545  						s.EditFiles(p.File().Filename(), fmt.Sprintf(`---
   546  title: %s
   547  ---
   548  
   549  Edited!!`, p.Title()))
   550  
   551  						err := s.BuildE(BuildCfg{})
   552  						if err != nil {
   553  							b.Fatal(err)
   554  						}
   555  					}
   556  				}
   557  			})
   558  		}
   559  	}
   560  }