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

     1  // Copyright 2024 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  	"testing"
    20  
    21  	"github.com/bep/logg"
    22  	qt "github.com/frankban/quicktest"
    23  )
    24  
    25  // The most basic build test.
    26  func TestHello(t *testing.T) {
    27  	files := `
    28  -- hugo.toml --
    29  title = "Hello"
    30  baseURL="https://example.org"
    31  disableKinds = ["term", "taxonomy", "section", "page"]
    32  -- content/p1.md --
    33  ---
    34  title: Page
    35  ---
    36  -- layouts/index.html --
    37  Home: {{ .Title }}
    38  `
    39  
    40  	b := NewIntegrationTestBuilder(
    41  		IntegrationTestConfig{
    42  			T:           t,
    43  			TxtarString: files,
    44  			LogLevel:    logg.LevelTrace,
    45  		},
    46  	).Build()
    47  
    48  	b.Assert(b.H.Log.LoggCount(logg.LevelWarn), qt.Equals, 0)
    49  	b.AssertFileContent("public/index.html", `Hello`)
    50  }
    51  
    52  func TestSmokeOutputFormats(t *testing.T) {
    53  	t.Parallel()
    54  
    55  	files := `
    56  -- hugo.toml --
    57  baseURL = "https://example.com/"
    58  defaultContentLanguage = "en"
    59  disableKinds = ["term", "taxonomy", "robotsTXT", "sitemap"]
    60  [outputs]
    61  home = ["html",  "rss"]
    62  section = ["html", "rss"]
    63  page = ["html"]
    64  -- content/p1.md --
    65  ---
    66  title: Page
    67  ---
    68  Page.
    69  
    70  -- layouts/_default/list.html --
    71  List: {{ .Title }}|{{ .RelPermalink}}|{{ range .OutputFormats }}{{ .Name }}: {{ .RelPermalink }}|{{ end }}$
    72  -- layouts/_default/list.xml --
    73  List xml: {{ .Title }}|{{ .RelPermalink}}|{{ range .OutputFormats }}{{ .Name }}: {{ .RelPermalink }}|{{ end }}$
    74  -- layouts/_default/single.html --
    75  Single: {{ .Title }}|{{ .RelPermalink}}|{{ range .OutputFormats }}{{ .Name }}: {{ .RelPermalink }}|{{ end }}$
    76  
    77  `
    78  
    79  	b := Test(t, files)
    80  
    81  	b.AssertFileContent("public/index.html", `List: |/|html: /|rss: /index.xml|$`)
    82  	b.AssertFileContent("public/index.xml", `List xml: |/|html: /|rss: /index.xml|$`)
    83  	b.AssertFileContent("public/p1/index.html", `Single: Page|/p1/|html: /p1/|$`)
    84  	b.AssertFileExists("public/p1/index.xml", false)
    85  }
    86  
    87  func TestSmoke(t *testing.T) {
    88  	t.Parallel()
    89  
    90  	// Basic test cases.
    91  	// OK translations
    92  	// OK page collections
    93  	// OK next, prev in section
    94  	// OK GetPage
    95  	// OK Pagination
    96  	// OK RenderString with shortcode
    97  	// OK cascade
    98  	// OK site last mod, section last mod.
    99  	// OK main sections
   100  	// OK taxonomies
   101  	// OK GetTerms
   102  	// OK Resource page
   103  	// OK Resource txt
   104  
   105  	const files = `
   106  -- hugo.toml --
   107  baseURL = "https://example.com"
   108  title = "Smoke Site"
   109  rssLimit = 3
   110  paginate = 1
   111  defaultContentLanguage = "en"
   112  defaultContentLanguageInSubdir = true
   113  enableRobotsTXT = true
   114  [taxonomies]
   115  category = 'categories'
   116  tag = 'tags'
   117  
   118  [languages]
   119  [languages.en]
   120  weight = 1
   121  title = "In English"
   122  [languages.no]
   123  weight = 2
   124  title = "På norsk"
   125  
   126  [params]
   127  hugo = "Rules!"
   128  
   129  [outputs]
   130    home = ["html", "json", "rss"]
   131  -- layouts/index.html --
   132  Home: {{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}|HTML
   133  Resources: {{ range .Resources }}{{ .ResourceType }}|{{ .RelPermalink }}|{{ .MediaType }} - {{ end }}|
   134  Site last mod: {{ site.Lastmod.Format "2006-02-01" }}|
   135  Home last mod: {{ .Lastmod.Format "2006-02-01" }}|
   136  Len Translations: {{ len .Translations }}|
   137  Len home.RegularPagesRecursive: {{ len .RegularPagesRecursive }}|
   138  RegularPagesRecursive: {{ range .RegularPagesRecursive }}{{ .RelPermalink }}|{{ end }}@
   139  Len site.RegularPages: {{ len site.RegularPages }}|
   140  Len site.Pages: {{ len site.Pages }}|
   141  Len site.AllPages: {{ len site.AllPages }}|
   142  GetPage: {{ with .Site.GetPage "posts/p1" }}{{ .RelPermalink }}|{{ .Title }}{{ end }}|
   143  RenderString with shortcode: {{ .RenderString "{{% hello %}}" }}|
   144  Paginate: {{ .Paginator.PageNumber }}/{{ .Paginator.TotalPages }}|
   145  -- layouts/index.json --
   146  Home:{{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}|JSON
   147  -- layouts/_default/list.html --
   148  List: {{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}|
   149  Resources: {{ range .Resources }}{{ .ResourceType }}|{{ .RelPermalink }}|{{ .MediaType }} - {{ end }}
   150  Pages Length: {{ len .Pages }}
   151  RegularPages Length: {{ len .RegularPages }}
   152  RegularPagesRecursive Length: {{ len .RegularPagesRecursive }}
   153  List last mod: {{ .Lastmod.Format "2006-02-01" }}
   154  Background: {{ .Params.background }}|
   155  Kind: {{ .Kind }}
   156  Type: {{ .Type }}
   157  Paginate: {{ .Paginator.PageNumber }}/{{ .Paginator.TotalPages }}|
   158  -- layouts/_default/single.html --
   159  Single: {{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}|Background: {{ .Params.background }}|
   160  Resources: {{ range .Resources }}{{ .ResourceType }}|{{ .RelPermalink }}|{{ .MediaType }}|{{ .Params }} - {{ end }}
   161  {{ $textResource := .Resources.GetMatch "**.txt" }}
   162  {{ with $textResource }}
   163  Icon: {{ .Params.icon }}|
   164  {{ $textResourceFingerprinted :=  . | fingerprint }}
   165  Icon fingerprinted: {{ with $textResourceFingerprinted }}{{ .Params.icon }}|{{ .RelPermalink }}{{ end }}|
   166  {{ end }}
   167  NextInSection: {{ with .NextInSection }}{{ .RelPermalink }}|{{ .Title }}{{ end }}|
   168  PrevInSection: {{ with .PrevInSection }}{{ .RelPermalink }}|{{ .Title }}{{ end }}|
   169  GetTerms: {{ range .GetTerms "tags" }}name: {{ .Name }}, title: {{ .Title }}|{{ end }}
   170  -- layouts/shortcodes/hello.html --
   171  Hello.
   172  -- content/_index.md --
   173  ---
   174  title: Home in English
   175  ---
   176  Home Content.
   177  -- content/_index.no.md --
   178  ---
   179  title: Hjem
   180  cascade:
   181    - _target:
   182        kind: page
   183        path: /posts/**
   184      background: post.jpg
   185    - _target:
   186        kind: term
   187      background: term.jpg
   188  ---
   189  Hjem Innhold.
   190  -- content/posts/f1.txt --
   191  posts f1 text.
   192  -- content/posts/sub/f1.txt --
   193  posts sub f1 text.
   194  -- content/posts/p1/index.md --
   195  +++
   196  title = "Post 1"
   197  lastMod = "2001-01-01"
   198  tags = ["tag1"]
   199  [[resources]]
   200  src = '**'
   201  [resources.params]
   202  icon = 'enicon'
   203  +++
   204  Content 1.
   205  -- content/posts/p1/index.no.md --
   206  +++
   207  title = "Post 1 no"
   208  lastMod = "2002-02-02"
   209  tags = ["tag1", "tag2"]
   210  [[resources]]
   211  src = '**'
   212  [resources.params]
   213  icon = 'noicon'
   214  +++
   215  Content 1 no.
   216  -- content/posts/_index.md --
   217  ---
   218  title: Posts
   219  ---
   220  -- content/posts/p1/f1.txt --
   221  posts p1 f1 text.
   222  -- content/posts/p1/sub/ps1.md --
   223  ---
   224  title: Post Sub 1
   225  ---
   226  Content Sub 1.
   227  -- content/posts/p2.md --
   228  ---
   229  title: Post 2
   230  tags: ["tag1", "tag3"]
   231  ---
   232  Content 2.
   233  -- content/posts/p2.no.md --
   234  ---
   235  title: Post 2 No
   236  ---
   237  Content 2 No.
   238  -- content/tags/_index.md --
   239  ---
   240  title: Tags
   241  ---
   242  Content Tags.
   243  -- content/tags/tag1/_index.md --
   244  ---
   245  title: Tag 1
   246  ---
   247  Content Tag 1.
   248  
   249  
   250  `
   251  
   252  	b := NewIntegrationTestBuilder(IntegrationTestConfig{
   253  		T:           t,
   254  		TxtarString: files,
   255  		NeedsOsFS:   true,
   256  		// Verbose:     true,
   257  		// LogLevel:    logg.LevelTrace,
   258  	}).Build()
   259  
   260  	b.AssertFileContent("public/en/index.html",
   261  		"Home: en|home|/en/|Home in English|<p>Home Content.</p>\n|HTML",
   262  		"Site last mod: 2001-01-01",
   263  		"Home last mod: 2001-01-01",
   264  		"Translations: 1|",
   265  		"Len home.RegularPagesRecursive: 2|",
   266  		"Len site.RegularPages: 2|",
   267  		"Len site.Pages: 8|",
   268  		"Len site.AllPages: 16|",
   269  		"GetPage: /en/posts/p1/|Post 1|",
   270  		"RenderString with shortcode: Hello.|",
   271  		"Paginate: 1/2|",
   272  	)
   273  	b.AssertFileContent("public/en/page/2/index.html", "Paginate: 2/2|")
   274  
   275  	b.AssertFileContent("public/no/index.html",
   276  		"Home: no|home|/no/|Hjem|<p>Hjem Innhold.</p>\n|HTML",
   277  		"Site last mod: 2002-02-02",
   278  		"Home last mod: 2002-02-02",
   279  		"Translations: 1",
   280  		"GetPage: /no/posts/p1/|Post 1 no|",
   281  	)
   282  
   283  	b.AssertFileContent("public/en/index.json", "Home:en|home|/en/|Home in English|<p>Home Content.</p>\n|JSON")
   284  	b.AssertFileContent("public/no/index.json", "Home:no|home|/no/|Hjem|<p>Hjem Innhold.</p>\n|JSON")
   285  
   286  	b.AssertFileContent("public/en/posts/p1/index.html",
   287  		"Single: en|page|/en/posts/p1/|Post 1|<p>Content 1.</p>\n|Len Resources: 2|",
   288  		"Resources: text|/en/posts/p1/f1.txt|text/plain|map[icon:enicon] - page||application/octet-stream|map[draft:false iscjklanguage:false title:Post Sub 1] -",
   289  		"Icon: enicon",
   290  		"Icon fingerprinted: enicon|/en/posts/p1/f1.e5746577af5cbfc4f34c558051b7955a9a5a795a84f1c6ab0609cb3473a924cb.txt|",
   291  		"NextInSection: |\nPrevInSection: /en/posts/p2/|Post 2|",
   292  		"GetTerms: name: tag1, title: Tag 1|",
   293  	)
   294  
   295  	b.AssertFileContent("public/no/posts/p1/index.html",
   296  		"Resources: 1",
   297  		"Resources: text|/en/posts/p1/f1.txt|text/plain|map[icon:noicon] -",
   298  		"Icon: noicon",
   299  		"Icon fingerprinted: noicon|/en/posts/p1/f1.e5746577af5cbfc4f34c558051b7955a9a5a795a84f1c6ab0609cb3473a924cb.txt|",
   300  		"Background: post.jpg",
   301  		"NextInSection: |\nPrevInSection: /no/posts/p2/|Post 2 No|",
   302  	)
   303  
   304  	b.AssertFileContent("public/en/posts/index.html",
   305  		"List: en|section|/en/posts/|Posts||Len Resources: 2|",
   306  		"Resources: text|/en/posts/f1.txt|text/plain - text|/en/posts/sub/f1.txt|text/plain -",
   307  		"List last mod: 2001-01-01",
   308  	)
   309  
   310  	b.AssertFileContent("public/no/posts/index.html",
   311  		"List last mod: 2002-02-02",
   312  	)
   313  
   314  	b.AssertFileContent("public/en/posts/p2/index.html", "Single: en|page|/en/posts/p2/|Post 2|<p>Content 2.</p>\n|",
   315  		"|Len Resources: 0",
   316  		"GetTerms: name: tag1, title: Tag 1|name: tag3, title: Tag3|",
   317  	)
   318  	b.AssertFileContent("public/no/posts/p2/index.html", "Single: no|page|/no/posts/p2/|Post 2 No|<p>Content 2 No.</p>\n|")
   319  
   320  	b.AssertFileContent("public/no/categories/index.html",
   321  		"Kind: taxonomy",
   322  		"Type: categories",
   323  	)
   324  	b.AssertFileContent("public/no/tags/index.html",
   325  		"Kind: taxonomy",
   326  		"Type: tags",
   327  	)
   328  
   329  	b.AssertFileContent("public/no/tags/tag1/index.html",
   330  		"Background: term.jpg",
   331  		"Kind: term",
   332  		"Type: tags",
   333  		"Paginate: 1/1|",
   334  	)
   335  
   336  	b.AssertFileContent("public/en/tags/tag1/index.html",
   337  		"Kind: term",
   338  		"Type: tags",
   339  		"Paginate: 1/2|",
   340  	)
   341  }
   342  
   343  // Basic tests that verifies that the different file systems work as expected.
   344  func TestSmokeFilesystems(t *testing.T) {
   345  	t.Parallel()
   346  
   347  	files := `
   348  -- hugo.toml --
   349  baseURL = "https://example.com"
   350  defaultContentLanguage = "en"
   351  defaultContentLanguageInSubdir = true
   352  [languages]
   353  [languages.en]
   354  title = "In English"
   355  [languages.nn]
   356  title = "På nynorsk"
   357  [module]
   358  [[module.mounts]]
   359  source = "i18n"
   360  target = "i18n"
   361  [[module.mounts]]
   362  source = "data"
   363  target = "data"
   364  [[module.mounts]]
   365  source = "content/en"
   366  target = "content"
   367  lang = "en"
   368  [[module.mounts]]
   369  source = "content/nn"
   370  target = "content"
   371  lang = "nn"
   372  [[module.imports]]
   373  path = "mytheme"
   374  -- layouts/index.html --
   375  i18n s1: {{ i18n "s1" }}|
   376  i18n s2: {{ i18n "s2" }}|
   377  data s1: {{ site.Data.d1.s1 }}|
   378  data s2: {{ site.Data.d1.s2 }}|
   379  title: {{ .Title }}|
   380  -- themes/mytheme/hugo.toml --
   381  [[module.mounts]]
   382  source = "i18n"
   383  target = "i18n"
   384  [[module.mounts]]
   385  source = "data"
   386  target = "data"
   387  # i18n files both project and theme.
   388  -- i18n/en.toml --
   389  [s1]
   390  other = 's1project'
   391  -- i18n/nn.toml --
   392  [s1]
   393  other = 's1prosjekt'
   394  -- themes/mytheme/i18n/en.toml --
   395  [s1]
   396  other = 's1theme'
   397  [s2]
   398  other = 's2theme'
   399  # data files both project and theme.
   400  -- data/d1.yaml --
   401  s1: s1project
   402  -- themes/mytheme/data/d1.yaml --
   403  s1: s1theme
   404  s2: s2theme
   405  # Content
   406  -- content/en/_index.md --
   407  ---
   408  title: "Home"
   409  ---
   410  -- content/nn/_index.md --
   411  ---
   412  title: "Heim"
   413  ---
   414  
   415  `
   416  	b := Test(t, files)
   417  
   418  	b.AssertFileContent("public/en/index.html",
   419  		"i18n s1: s1project", "i18n s2: s2theme",
   420  		"data s1: s1project", "data s2: s2theme",
   421  		"title: Home",
   422  	)
   423  
   424  	b.AssertFileContent("public/nn/index.html",
   425  		"i18n s1: s1prosjekt", "i18n s2: s2theme",
   426  		"data s1: s1project", "data s2: s2theme",
   427  		"title: Heim",
   428  	)
   429  }
   430  
   431  // https://github.com/golang/go/issues/30286
   432  func TestDataRace(t *testing.T) {
   433  	const page = `
   434  ---
   435  title: "The Page"
   436  outputs: ["HTML", "JSON"]
   437  ---	
   438  
   439  The content.
   440  	
   441  
   442  	`
   443  
   444  	b := newTestSitesBuilder(t).WithSimpleConfigFile()
   445  	for i := 1; i <= 50; i++ {
   446  		b.WithContent(fmt.Sprintf("blog/page%d.md", i), page)
   447  	}
   448  
   449  	b.WithContent("_index.md", `
   450  ---
   451  title: "The Home"
   452  outputs: ["HTML", "JSON", "CSV", "RSS"]
   453  ---	
   454  
   455  The content.
   456  	
   457  
   458  `)
   459  
   460  	commonTemplate := `{{ .Data.Pages }}`
   461  
   462  	b.WithTemplatesAdded("_default/single.html", "HTML Single: "+commonTemplate)
   463  	b.WithTemplatesAdded("_default/list.html", "HTML List: "+commonTemplate)
   464  
   465  	b.CreateSites().Build(BuildCfg{})
   466  }
   467  
   468  // This is just a test to verify that BenchmarkBaseline is working as intended.
   469  func TestBenchmarkBaseline(t *testing.T) {
   470  	cfg := IntegrationTestConfig{
   471  		T:           t,
   472  		TxtarString: benchmarkBaselineFiles(true),
   473  	}
   474  	b := NewIntegrationTestBuilder(cfg).Build()
   475  
   476  	b.Assert(len(b.H.Sites), qt.Equals, 4)
   477  	b.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 161)
   478  	b.Assert(len(b.H.Sites[0].Pages()), qt.Equals, 197)
   479  	b.Assert(len(b.H.Sites[2].RegularPages()), qt.Equals, 158)
   480  	b.Assert(len(b.H.Sites[2].Pages()), qt.Equals, 194)
   481  }
   482  
   483  func BenchmarkBaseline(b *testing.B) {
   484  	cfg := IntegrationTestConfig{
   485  		T:           b,
   486  		TxtarString: benchmarkBaselineFiles(false),
   487  	}
   488  	builders := make([]*IntegrationTestBuilder, b.N)
   489  
   490  	for i := range builders {
   491  		builders[i] = NewIntegrationTestBuilder(cfg)
   492  	}
   493  
   494  	b.ResetTimer()
   495  	for i := 0; i < b.N; i++ {
   496  		builders[i].Build()
   497  	}
   498  }
   499  
   500  func benchmarkBaselineFiles(leafBundles bool) string {
   501  	rnd := rand.New(rand.NewSource(32))
   502  
   503  	files := `
   504  -- config.toml --
   505  baseURL = "https://example.com"
   506  defaultContentLanguage = 'en'
   507  
   508  [module]
   509  [[module.mounts]]
   510  source = 'content/en'
   511  target = 'content/en'
   512  lang = 'en'
   513  [[module.mounts]]
   514  source = 'content/nn'
   515  target = 'content/nn'
   516  lang = 'nn'
   517  [[module.mounts]]
   518  source = 'content/no'
   519  target = 'content/no'
   520  lang = 'no'
   521  [[module.mounts]]
   522  source = 'content/sv'
   523  target = 'content/sv'
   524  lang = 'sv'
   525  [[module.mounts]]
   526  source = 'layouts'
   527  target = 'layouts'
   528  
   529  [languages]
   530  [languages.en]
   531  title = "English"
   532  weight = 1
   533  [languages.nn]
   534  title = "Nynorsk"
   535  weight = 2
   536  [languages.no]
   537  title = "Norsk"
   538  weight = 3
   539  [languages.sv]
   540  title = "Svenska"
   541  weight = 4
   542  -- layouts/_default/list.html --
   543  {{ .Title }}
   544  {{ .Content }}
   545  -- layouts/_default/single.html --
   546  {{ .Title }}
   547  {{ .Content }}
   548  -- layouts/shortcodes/myshort.html --
   549  {{ .Inner }}
   550  `
   551  
   552  	contentTemplate := `
   553  ---
   554  title: "Page %d"
   555  date: "2018-01-01"
   556  weight: %d
   557  ---
   558  
   559  ## Heading 1
   560  
   561  Duis nisi reprehenderit nisi cupidatat cillum aliquip ea id eu esse commodo et.
   562  
   563  ## Heading 2
   564  
   565  Aliqua labore enim et sint anim amet excepteur ea dolore.
   566  
   567  {{< myshort >}}
   568  Hello, World!
   569  {{< /myshort >}}
   570  
   571  Aliqua labore enim et sint anim amet excepteur ea dolore.
   572  
   573  
   574  `
   575  
   576  	for _, lang := range []string{"en", "nn", "no", "sv"} {
   577  		files += fmt.Sprintf("\n-- content/%s/_index.md --\n"+contentTemplate, lang, 1, 1, 1)
   578  		for i, root := range []string{"", "foo", "bar", "baz"} {
   579  			for j, section := range []string{"posts", "posts/funny", "posts/science", "posts/politics", "posts/world", "posts/technology", "posts/world/news", "posts/world/news/europe"} {
   580  				n := i + j + 1
   581  				files += fmt.Sprintf("\n-- content/%s/%s/%s/_index.md --\n"+contentTemplate, lang, root, section, n, n, n)
   582  				for k := 1; k < rnd.Intn(30)+1; k++ {
   583  					n := n + k
   584  					ns := fmt.Sprintf("%d", n)
   585  					if leafBundles {
   586  						ns = fmt.Sprintf("%d/index", n)
   587  					}
   588  					file := fmt.Sprintf("\n-- content/%s/%s/%s/p%s.md --\n"+contentTemplate, lang, root, section, ns, n, n)
   589  					files += file
   590  				}
   591  			}
   592  		}
   593  	}
   594  
   595  	return files
   596  }