// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
"fmt"
"math/rand"
"strings"
"testing"
qt "github.com/frankban/quicktest"
)
// The most basic build test.
func TestHello(t *testing.T) {
t.Parallel()
b := newTestSitesBuilder(t)
b.WithConfigFile("toml", `
baseURL="https://example.org"
disableKinds = ["term", "taxonomy", "section", "page"]
`)
b.WithContent("p1", `
---
title: Page
---
`)
b.WithTemplates("index.html", `Site: {{ .Site.Language.Lang | upper }}`)
b.Build(BuildCfg{})
b.AssertFileContent("public/index.html", `Site: EN`)
}
func TestSmoke(t *testing.T) {
t.Parallel()
c := qt.New(t)
const configFile = `
baseURL = "https://example.com"
title = "Simple Site"
rssLimit = 3
defaultContentLanguage = "en"
enableRobotsTXT = true
[languages]
[languages.en]
weight = 1
title = "In English"
[languages.no]
weight = 2
title = "På norsk"
[params]
hugo = "Rules!"
[outputs]
home = ["HTML", "JSON", "CSV", "RSS"]
`
const pageContentAndSummaryDivider = `---
title: Page with outputs
hugo: "Rocks!"
outputs: ["HTML", "JSON"]
tags: [ "hugo" ]
aliases: [ "/a/b/c" ]
---
This is summary.
This is content with some shortcodes.
Shortcode 1: {{< sc >}}.
Shortcode 2: {{< sc >}}.
`
const pageContentWithMarkdownShortcodes = `---
title: Page with markdown shortcode
hugo: "Rocks!"
outputs: ["HTML", "JSON"]
---
This is summary.
This is content[^a].
# Header above
{{% markdown-shortcode %}}
# Header inside
Some **markdown**.[^b]
{{% /markdown-shortcode %}}
# Heder below
Some more content[^c].
Footnotes:
[^a]: Fn 1
[^b]: Fn 2
[^c]: Fn 3
`
pageContentAutoSummary := strings.Replace(pageContentAndSummaryDivider, "", "", 1)
b := newTestSitesBuilder(t).WithConfigFile("toml", configFile)
b.WithTemplatesAdded("shortcodes/markdown-shortcode.html", `
Some **Markdown** in shortcode.
{{ .Inner }}
`)
b.WithTemplatesAdded("shortcodes/markdown-shortcode.json", `
Some **Markdown** in JSON shortcode.
{{ .Inner }}
`)
for i := 1; i <= 11; i++ {
if i%2 == 0 {
b.WithContent(fmt.Sprintf("blog/page%d.md", i), pageContentAndSummaryDivider)
b.WithContent(fmt.Sprintf("blog/page%d.no.md", i), pageContentAndSummaryDivider)
} else {
b.WithContent(fmt.Sprintf("blog/page%d.md", i), pageContentAutoSummary)
}
}
for i := 1; i <= 5; i++ {
// Root section pages
b.WithContent(fmt.Sprintf("root%d.md", i), pageContentAutoSummary)
}
// https://github.com/gohugoio/hugo/issues/4695
b.WithContent("blog/markyshort.md", pageContentWithMarkdownShortcodes)
// Add one bundle
b.WithContent("blog/mybundle/index.md", pageContentAndSummaryDivider)
b.WithContent("blog/mybundle/mydata.csv", "Bundled CSV")
const (
commonPageTemplate = `|{{ .Kind }}|{{ .Title }}|{{ .File.Path }}|{{ .Summary }}|{{ .Content }}|RelPermalink: {{ .RelPermalink }}|WordCount: {{ .WordCount }}|Pages: {{ .Pages }}|Data Pages: Pages({{ len .Data.Pages }})|Resources: {{ len .Resources }}|Summary: {{ .Summary }}`
commonPaginatorTemplate = `|Paginator: {{ with .Paginator }}{{ .PageNumber }}{{ else }}NIL{{ end }}`
commonListTemplateNoPaginator = `|{{ $pages := .Pages }}{{ if .IsHome }}{{ $pages = .Site.RegularPages }}{{ end }}{{ range $i, $e := ($pages | first 1) }}|Render {{ $i }}: {{ .Kind }}|{{ .Render "li" }}|{{ end }}|Site params: {{ $.Site.Params.hugo }}|RelPermalink: {{ .RelPermalink }}`
commonListTemplate = commonPaginatorTemplate + `|{{ $pages := .Pages }}{{ if .IsHome }}{{ $pages = .Site.RegularPages }}{{ end }}{{ range $i, $e := ($pages | first 1) }}|Render {{ $i }}: {{ .Kind }}|{{ .Render "li" }}|{{ end }}|Site params: {{ $.Site.Params.hugo }}|RelPermalink: {{ .RelPermalink }}`
commonShortcodeTemplate = `|{{ .Name }}|{{ .Ordinal }}|{{ .Page.Summary }}|{{ .Page.Content }}|WordCount: {{ .Page.WordCount }}`
prevNextTemplate = `|Prev: {{ with .Prev }}{{ .RelPermalink }}{{ end }}|Next: {{ with .Next }}{{ .RelPermalink }}{{ end }}`
prevNextInSectionTemplate = `|PrevInSection: {{ with .PrevInSection }}{{ .RelPermalink }}{{ end }}|NextInSection: {{ with .NextInSection }}{{ .RelPermalink }}{{ end }}`
paramsTemplate = `|Params: {{ .Params.hugo }}`
treeNavTemplate = `|CurrentSection: {{ .CurrentSection }}`
)
b.WithTemplates(
"_default/list.html", "HTML: List"+commonPageTemplate+commonListTemplate+"|First Site: {{ .Sites.First.Title }}",
"_default/list.json", "JSON: List"+commonPageTemplate+commonListTemplateNoPaginator,
"_default/list.csv", "CSV: List"+commonPageTemplate+commonListTemplateNoPaginator,
"_default/single.html", "HTML: Single"+commonPageTemplate+prevNextTemplate+prevNextInSectionTemplate+treeNavTemplate,
"_default/single.json", "JSON: Single"+commonPageTemplate,
// For .Render test
"_default/li.html", `HTML: LI|{{ strings.Contains .Content "HTML: Shortcode: sc" }}`+paramsTemplate,
"_default/li.json", `JSON: LI|{{ strings.Contains .Content "JSON: Shortcode: sc" }}`+paramsTemplate,
"_default/li.csv", `CSV: LI|{{ strings.Contains .Content "CSV: Shortcode: sc" }}`+paramsTemplate,
"404.html", "{{ .Kind }}|{{ .Title }}|Page not found",
"shortcodes/sc.html", "HTML: Shortcode: "+commonShortcodeTemplate,
"shortcodes/sc.json", "JSON: Shortcode: "+commonShortcodeTemplate,
"shortcodes/sc.csv", "CSV: Shortcode: "+commonShortcodeTemplate,
)
b.CreateSites().Build(BuildCfg{})
b.AssertFileContent("public/blog/page1/index.html",
"This is content with some shortcodes.",
"Page with outputs",
"Pages: Pages(0)",
"RelPermalink: /blog/page1/|",
"Shortcode 1: HTML: Shortcode: |sc|0|||WordCount: 0.",
"Shortcode 2: HTML: Shortcode: |sc|1|||WordCount: 0.",
"Prev: /blog/page10/|Next: /blog/mybundle/",
"PrevInSection: /blog/page10/|NextInSection: /blog/mybundle/",
"Summary: This is summary.",
"CurrentSection: Page(/blog)",
)
b.AssertFileContent("public/blog/page1/index.json",
"JSON: Single|page|Page with outputs|",
"SON: Shortcode: |sc|0||")
b.AssertFileContent("public/index.html",
"home|In English",
"Site params: Rules",
"Pages: Pages(6)|Data Pages: Pages(6)",
"Paginator: 1",
"First Site: In English",
"RelPermalink: /",
)
b.AssertFileContent("public/no/index.html", "home|På norsk", "RelPermalink: /no/")
// Check RSS
rssHome := b.FileContent("public/index.xml")
c.Assert(rssHome, qt.Contains, ``)
c.Assert(strings.Count(rssHome, "- "), qt.Equals, 3) // rssLimit = 3
// .Render should use template/content from the current output format
// even if that output format isn't configured for that page.
b.AssertFileContent(
"public/index.json",
"Render 0: page|JSON: LI|false|Params: Rocks!",
)
b.AssertFileContent(
"public/index.html",
"Render 0: page|HTML: LI|false|Params: Rocks!|",
)
b.AssertFileContent(
"public/index.csv",
"Render 0: page|CSV: LI|false|Params: Rocks!|",
)
// Check bundled resources
b.AssertFileContent(
"public/blog/mybundle/index.html",
"Resources: 1",
)
// Check pages in root section
b.AssertFileContent(
"public/root3/index.html",
"Single|page|Page with outputs|root3.md|",
"Prev: /root4/|Next: /root2/|PrevInSection: /root4/|NextInSection: /root2/",
)
b.AssertFileContent(
"public/root3/index.json", "Shortcode 1: JSON:")
// Paginators
b.AssertFileContent("public/page/1/index.html", `rel="canonical" href="https://example.com/"`)
b.AssertFileContent("public/page/2/index.html", "HTML: List|home|In English|", "Paginator: 2")
// 404
b.AssertFileContent("public/404.html", "404|404 Page not found")
// Sitemaps
b.AssertFileContent("public/en/sitemap.xml", "https://example.com/blog/")
b.AssertFileContent("public/no/sitemap.xml", `hreflang="no"`)
b.AssertFileContent("public/sitemap.xml", "https://example.com/en/sitemap.xml", "https://example.com/no/sitemap.xml")
// robots.txt
b.AssertFileContent("public/robots.txt", `User-agent: *`)
// Aliases
b.AssertFileContent("public/a/b/c/index.html", `refresh`)
// Markdown vs shortcodes
// Check that all footnotes are grouped (even those from inside the shortcode)
b.AssertFileContentRe("public/blog/markyshort/index.html", `Footnotes:.*
.*Fn 1.*Fn 2.*Fn 3.*
`)
}
// https://github.com/golang/go/issues/30286
func TestDataRace(t *testing.T) {
const page = `
---
title: "The Page"
outputs: ["HTML", "JSON"]
---
The content.
`
b := newTestSitesBuilder(t).WithSimpleConfigFile()
for i := 1; i <= 50; i++ {
b.WithContent(fmt.Sprintf("blog/page%d.md", i), page)
}
b.WithContent("_index.md", `
---
title: "The Home"
outputs: ["HTML", "JSON", "CSV", "RSS"]
---
The content.
`)
commonTemplate := `{{ .Data.Pages }}`
b.WithTemplatesAdded("_default/single.html", "HTML Single: "+commonTemplate)
b.WithTemplatesAdded("_default/list.html", "HTML List: "+commonTemplate)
b.CreateSites().Build(BuildCfg{})
}
// This is just a test to verify that BenchmarkBaseline is working as intended.
func TestBenchmarkBaseline(t *testing.T) {
cfg := IntegrationTestConfig{
T: t,
TxtarString: benchmarkBaselineFiles(),
}
b := NewIntegrationTestBuilder(cfg).Build()
b.Assert(len(b.H.Sites), qt.Equals, 4)
b.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 161)
b.Assert(len(b.H.Sites[0].Pages()), qt.Equals, 197)
b.Assert(len(b.H.Sites[2].RegularPages()), qt.Equals, 158)
b.Assert(len(b.H.Sites[2].Pages()), qt.Equals, 194)
}
func BenchmarkBaseline(b *testing.B) {
cfg := IntegrationTestConfig{
T: b,
TxtarString: benchmarkBaselineFiles(),
}
builders := make([]*IntegrationTestBuilder, b.N)
for i := range builders {
builders[i] = NewIntegrationTestBuilder(cfg)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
builders[i].Build()
}
}
func benchmarkBaselineFiles() string {
rnd := rand.New(rand.NewSource(32))
files := `
-- config.toml --
baseURL = "https://example.com"
defaultContentLanguage = 'en'
[module]
[[module.mounts]]
source = 'content/en'
target = 'content/en'
lang = 'en'
[[module.mounts]]
source = 'content/nn'
target = 'content/nn'
lang = 'nn'
[[module.mounts]]
source = 'content/no'
target = 'content/no'
lang = 'no'
[[module.mounts]]
source = 'content/sv'
target = 'content/sv'
lang = 'sv'
[[module.mounts]]
source = 'layouts'
target = 'layouts'
[languages]
[languages.en]
title = "English"
weight = 1
[languages.nn]
title = "Nynorsk"
weight = 2
[languages.no]
title = "Norsk"
weight = 3
[languages.sv]
title = "Svenska"
weight = 4
-- layouts/_default/list.html --
{{ .Title }}
{{ .Content }}
-- layouts/_default/single.html --
{{ .Title }}
{{ .Content }}
-- layouts/shortcodes/myshort.html --
{{ .Inner }}
`
contentTemplate := `
---
title: "Page %d"
date: "2018-01-01"
weight: %d
---
## Heading 1
Duis nisi reprehenderit nisi cupidatat cillum aliquip ea id eu esse commodo et.
## Heading 2
Aliqua labore enim et sint anim amet excepteur ea dolore.
{{< myshort >}}
Hello, World!
{{< /myshort >}}
Aliqua labore enim et sint anim amet excepteur ea dolore.
`
for _, lang := range []string{"en", "nn", "no", "sv"} {
files += fmt.Sprintf("\n-- content/%s/_index.md --\n"+contentTemplate, lang, 1, 1, 1)
for i, root := range []string{"", "foo", "bar", "baz"} {
for j, section := range []string{"posts", "posts/funny", "posts/science", "posts/politics", "posts/world", "posts/technology", "posts/world/news", "posts/world/news/europe"} {
n := i + j + 1
files += fmt.Sprintf("\n-- content/%s/%s/%s/_index.md --\n"+contentTemplate, lang, root, section, n, n, n)
for k := 1; k < rnd.Intn(30)+1; k++ {
n := n + k
files += fmt.Sprintf("\n-- content/%s/%s/%s/p%d.md --\n"+contentTemplate, lang, root, section, n, n, n)
}
}
}
}
return files
}