hugo/hugolib/testhelpers_test.go
Bjørn Erik Pedersen a03c631c42
Rework template handling for function and map lookups
This is a big commit, but it deletes lots of code and simplifies a lot.

* Resolving the template funcs at execution time means we don't have to create template clones per site
* Having a custom map resolver means that we can remove the AST lower case transformation for the special lower case Params map

Not only is the above easier to reason about, it's also faster, especially if you have more than one language, as in the benchmark below:

```
name                          old time/op    new time/op    delta
SiteNew/Deep_content_tree-16    53.7ms ± 0%    48.1ms ± 2%  -10.38%  (p=0.029 n=4+4)

name                          old alloc/op   new alloc/op   delta
SiteNew/Deep_content_tree-16    41.0MB ± 0%    36.8MB ± 0%  -10.26%  (p=0.029 n=4+4)

name                          old allocs/op  new allocs/op  delta
SiteNew/Deep_content_tree-16      481k ± 0%      410k ± 0%  -14.66%  (p=0.029 n=4+4)
```

This should be even better if you also have lots of templates.

Closes #6594
2019-12-12 10:04:35 +01:00

985 lines
24 KiB
Go

package hugolib
import (
"image/jpeg"
"io"
"path/filepath"
"runtime"
"strconv"
"testing"
"unicode/utf8"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/google/go-cmp/cmp"
"github.com/gohugoio/hugo/parser"
"github.com/pkg/errors"
"bytes"
"fmt"
"regexp"
"strings"
"text/template"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources/page"
"github.com/sanity-io/litter"
"github.com/spf13/afero"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/tpl"
"github.com/spf13/viper"
"os"
"github.com/gohugoio/hugo/resources/resource"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugofs"
)
var (
deepEqualsPages = qt.CmpEquals(cmp.Comparer(func(p1, p2 *pageState) bool { return p1 == p2 }))
deepEqualsOutputFormats = qt.CmpEquals(cmp.Comparer(func(o1, o2 output.Format) bool {
return o1.Name == o2.Name && o1.MediaType.Type() == o2.MediaType.Type()
}))
)
type sitesBuilder struct {
Cfg config.Provider
environ []string
Fs *hugofs.Fs
T testing.TB
depsCfg deps.DepsCfg
*qt.C
logger *loggers.Logger
dumper litter.Options
// Used to test partial rebuilds.
changedFiles []string
// Aka the Hugo server mode.
running bool
H *HugoSites
theme string
// Default toml
configFormat string
configFileSet bool
viperSet bool
// Default is empty.
// TODO(bep) revisit this and consider always setting it to something.
// Consider this in relation to using the BaseFs.PublishFs to all publishing.
workingDir string
addNothing bool
// Base data/content
contentFilePairs []string
templateFilePairs []string
i18nFilePairs []string
dataFilePairs []string
// Additional data/content.
// As in "use the base, but add these on top".
contentFilePairsAdded []string
templateFilePairsAdded []string
i18nFilePairsAdded []string
dataFilePairsAdded []string
}
func newTestSitesBuilder(t testing.TB) *sitesBuilder {
v := viper.New()
fs := hugofs.NewMem(v)
litterOptions := litter.Options{
HidePrivateFields: true,
StripPackageNames: true,
Separator: " ",
}
return &sitesBuilder{T: t, C: qt.New(t), Fs: fs, configFormat: "toml", dumper: litterOptions}
}
func newTestSitesBuilderFromDepsCfg(t testing.TB, d deps.DepsCfg) *sitesBuilder {
c := qt.New(t)
litterOptions := litter.Options{
HidePrivateFields: true,
StripPackageNames: true,
Separator: " ",
}
b := &sitesBuilder{T: t, C: c, depsCfg: d, Fs: d.Fs, dumper: litterOptions}
workingDir := d.Cfg.GetString("workingDir")
b.WithWorkingDir(workingDir)
return b.WithViper(d.Cfg.(*viper.Viper))
}
func (s *sitesBuilder) Running() *sitesBuilder {
s.running = true
return s
}
func (s *sitesBuilder) WithNothingAdded() *sitesBuilder {
s.addNothing = true
return s
}
func (s *sitesBuilder) WithLogger(logger *loggers.Logger) *sitesBuilder {
s.logger = logger
return s
}
func (s *sitesBuilder) WithWorkingDir(dir string) *sitesBuilder {
s.workingDir = filepath.FromSlash(dir)
return s
}
func (s *sitesBuilder) WithEnviron(env ...string) *sitesBuilder {
for i := 0; i < len(env); i += 2 {
s.environ = append(s.environ, fmt.Sprintf("%s=%s", env[i], env[i+1]))
}
return s
}
func (s *sitesBuilder) WithConfigTemplate(data interface{}, format, configTemplate string) *sitesBuilder {
s.T.Helper()
if format == "" {
format = "toml"
}
templ, err := template.New("test").Parse(configTemplate)
if err != nil {
s.Fatalf("Template parse failed: %s", err)
}
var b bytes.Buffer
templ.Execute(&b, data)
return s.WithConfigFile(format, b.String())
}
func (s *sitesBuilder) WithViper(v *viper.Viper) *sitesBuilder {
s.T.Helper()
if s.configFileSet {
s.T.Fatal("WithViper: use Viper or config.toml, not both")
}
defer func() {
s.viperSet = true
}()
// Write to a config file to make sure the tests follow the same code path.
var buff bytes.Buffer
m := v.AllSettings()
s.Assert(parser.InterfaceToConfig(m, metadecoders.TOML, &buff), qt.IsNil)
return s.WithConfigFile("toml", buff.String())
}
func (s *sitesBuilder) WithConfigFile(format, conf string) *sitesBuilder {
s.T.Helper()
if s.viperSet {
s.T.Fatal("WithConfigFile: use Viper or config.toml, not both")
}
s.configFileSet = true
filename := s.absFilename("config." + format)
writeSource(s.T, s.Fs, filename, conf)
s.configFormat = format
return s
}
func (s *sitesBuilder) WithThemeConfigFile(format, conf string) *sitesBuilder {
s.T.Helper()
if s.theme == "" {
s.theme = "test-theme"
}
filename := filepath.Join("themes", s.theme, "config."+format)
writeSource(s.T, s.Fs, s.absFilename(filename), conf)
return s
}
func (s *sitesBuilder) WithSourceFile(filenameContent ...string) *sitesBuilder {
s.T.Helper()
for i := 0; i < len(filenameContent); i += 2 {
writeSource(s.T, s.Fs, s.absFilename(filenameContent[i]), filenameContent[i+1])
}
return s
}
func (s *sitesBuilder) absFilename(filename string) string {
filename = filepath.FromSlash(filename)
if s.workingDir != "" && !strings.HasPrefix(filename, s.workingDir) {
filename = filepath.Join(s.workingDir, filename)
}
return filename
}
const commonConfigSections = `
[services]
[services.disqus]
shortname = "disqus_shortname"
[services.googleAnalytics]
id = "ga_id"
[privacy]
[privacy.disqus]
disable = false
[privacy.googleAnalytics]
respectDoNotTrack = true
anonymizeIP = true
[privacy.instagram]
simple = true
[privacy.twitter]
enableDNT = true
[privacy.vimeo]
disable = false
[privacy.youtube]
disable = false
privacyEnhanced = true
`
func (s *sitesBuilder) WithSimpleConfigFile() *sitesBuilder {
s.T.Helper()
return s.WithSimpleConfigFileAndBaseURL("http://example.com/")
}
func (s *sitesBuilder) WithSimpleConfigFileAndBaseURL(baseURL string) *sitesBuilder {
s.T.Helper()
config := fmt.Sprintf("baseURL = %q", baseURL)
config = config + commonConfigSections
return s.WithConfigFile("toml", config)
}
func (s *sitesBuilder) WithDefaultMultiSiteConfig() *sitesBuilder {
var defaultMultiSiteConfig = `
baseURL = "http://example.com/blog"
paginate = 1
disablePathToLower = true
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[permalinks]
other = "/somewhere/else/:filename"
[blackfriday]
angledQuotes = true
[Taxonomies]
tag = "tags"
[Languages]
[Languages.en]
weight = 10
title = "In English"
languageName = "English"
[Languages.en.blackfriday]
angledQuotes = false
[[Languages.en.menu.main]]
url = "/"
name = "Home"
weight = 0
[Languages.fr]
weight = 20
title = "Le Français"
languageName = "Français"
[Languages.fr.Taxonomies]
plaque = "plaques"
[Languages.nn]
weight = 30
title = "På nynorsk"
languageName = "Nynorsk"
paginatePath = "side"
[Languages.nn.Taxonomies]
lag = "lag"
[[Languages.nn.menu.main]]
url = "/"
name = "Heim"
weight = 1
[Languages.nb]
weight = 40
title = "På bokmål"
languageName = "Bokmål"
paginatePath = "side"
[Languages.nb.Taxonomies]
lag = "lag"
` + commonConfigSections
return s.WithConfigFile("toml", defaultMultiSiteConfig)
}
func (s *sitesBuilder) WithSunset(in string) {
// Write a real image into one of the bundle above.
src, err := os.Open(filepath.FromSlash("testdata/sunset.jpg"))
s.Assert(err, qt.IsNil)
out, err := s.Fs.Source.Create(filepath.FromSlash(filepath.Join(s.workingDir, in)))
s.Assert(err, qt.IsNil)
_, err = io.Copy(out, src)
s.Assert(err, qt.IsNil)
out.Close()
src.Close()
}
func (s *sitesBuilder) WithContent(filenameContent ...string) *sitesBuilder {
s.contentFilePairs = append(s.contentFilePairs, filenameContent...)
return s
}
func (s *sitesBuilder) WithContentAdded(filenameContent ...string) *sitesBuilder {
s.contentFilePairsAdded = append(s.contentFilePairsAdded, filenameContent...)
return s
}
func (s *sitesBuilder) WithTemplates(filenameContent ...string) *sitesBuilder {
s.templateFilePairs = append(s.templateFilePairs, filenameContent...)
return s
}
func (s *sitesBuilder) WithTemplatesAdded(filenameContent ...string) *sitesBuilder {
s.templateFilePairsAdded = append(s.templateFilePairsAdded, filenameContent...)
return s
}
func (s *sitesBuilder) WithData(filenameContent ...string) *sitesBuilder {
s.dataFilePairs = append(s.dataFilePairs, filenameContent...)
return s
}
func (s *sitesBuilder) WithDataAdded(filenameContent ...string) *sitesBuilder {
s.dataFilePairsAdded = append(s.dataFilePairsAdded, filenameContent...)
return s
}
func (s *sitesBuilder) WithI18n(filenameContent ...string) *sitesBuilder {
s.i18nFilePairs = append(s.i18nFilePairs, filenameContent...)
return s
}
func (s *sitesBuilder) WithI18nAdded(filenameContent ...string) *sitesBuilder {
s.i18nFilePairsAdded = append(s.i18nFilePairsAdded, filenameContent...)
return s
}
func (s *sitesBuilder) EditFiles(filenameContent ...string) *sitesBuilder {
var changedFiles []string
for i := 0; i < len(filenameContent); i += 2 {
filename, content := filepath.FromSlash(filenameContent[i]), filenameContent[i+1]
changedFiles = append(changedFiles, filename)
writeSource(s.T, s.Fs, s.absFilename(filename), content)
}
s.changedFiles = changedFiles
return s
}
func (s *sitesBuilder) writeFilePairs(folder string, filenameContent []string) *sitesBuilder {
if len(filenameContent)%2 != 0 {
s.Fatalf("expect filenameContent for %q in pairs (%d)", folder, len(filenameContent))
}
for i := 0; i < len(filenameContent); i += 2 {
filename, content := filenameContent[i], filenameContent[i+1]
target := folder
// TODO(bep) clean up this magic.
if strings.HasPrefix(filename, folder) {
target = ""
}
if s.workingDir != "" {
target = filepath.Join(s.workingDir, target)
}
writeSource(s.T, s.Fs, filepath.Join(target, filename), content)
}
return s
}
func (s *sitesBuilder) CreateSites() *sitesBuilder {
if err := s.CreateSitesE(); err != nil {
herrors.PrintStackTrace(err)
s.Fatalf("Failed to create sites: %s", err)
}
return s
}
func (s *sitesBuilder) LoadConfig() error {
if !s.configFileSet {
s.WithSimpleConfigFile()
}
cfg, _, err := LoadConfig(ConfigSourceDescriptor{
WorkingDir: s.workingDir,
Fs: s.Fs.Source,
Logger: s.logger,
Environ: s.environ,
Filename: "config." + s.configFormat}, func(cfg config.Provider) error {
return nil
})
if err != nil {
return err
}
s.Cfg = cfg
return nil
}
func (s *sitesBuilder) CreateSitesE() error {
if !s.addNothing {
if _, ok := s.Fs.Source.(*afero.OsFs); ok {
for _, dir := range []string{
"content/sect",
"layouts/_default",
"layouts/partials",
"layouts/shortcodes",
"data",
"i18n",
} {
if err := os.MkdirAll(filepath.Join(s.workingDir, dir), 0777); err != nil {
return errors.Wrapf(err, "failed to create %q", dir)
}
}
}
s.addDefaults()
s.writeFilePairs("content", s.contentFilePairsAdded)
s.writeFilePairs("layouts", s.templateFilePairsAdded)
s.writeFilePairs("data", s.dataFilePairsAdded)
s.writeFilePairs("i18n", s.i18nFilePairsAdded)
s.writeFilePairs("i18n", s.i18nFilePairs)
s.writeFilePairs("data", s.dataFilePairs)
s.writeFilePairs("content", s.contentFilePairs)
s.writeFilePairs("layouts", s.templateFilePairs)
}
if err := s.LoadConfig(); err != nil {
return errors.Wrap(err, "failed to load config")
}
s.Fs.Destination = hugofs.NewCreateCountingFs(s.Fs.Destination)
depsCfg := s.depsCfg
depsCfg.Fs = s.Fs
depsCfg.Cfg = s.Cfg
depsCfg.Logger = s.logger
depsCfg.Running = s.running
sites, err := NewHugoSites(depsCfg)
if err != nil {
return errors.Wrap(err, "failed to create sites")
}
s.H = sites
return nil
}
func (s *sitesBuilder) BuildE(cfg BuildCfg) error {
if s.H == nil {
s.CreateSites()
}
return s.H.Build(cfg)
}
func (s *sitesBuilder) Build(cfg BuildCfg) *sitesBuilder {
s.T.Helper()
return s.build(cfg, false)
}
func (s *sitesBuilder) BuildFail(cfg BuildCfg) *sitesBuilder {
s.T.Helper()
return s.build(cfg, true)
}
func (s *sitesBuilder) changeEvents() []fsnotify.Event {
if len(s.changedFiles) == 0 {
return nil
}
events := make([]fsnotify.Event, len(s.changedFiles))
// TODO(bep) remove?
for i, v := range s.changedFiles {
events[i] = fsnotify.Event{
Name: v,
Op: fsnotify.Write,
}
}
return events
}
func (s *sitesBuilder) build(cfg BuildCfg, shouldFail bool) *sitesBuilder {
s.Helper()
defer func() {
s.changedFiles = nil
}()
if s.H == nil {
s.CreateSites()
}
err := s.H.Build(cfg, s.changeEvents()...)
if err == nil {
logErrorCount := s.H.NumLogErrors()
if logErrorCount > 0 {
err = fmt.Errorf("logged %d errors", logErrorCount)
}
}
if err != nil && !shouldFail {
herrors.PrintStackTrace(err)
s.Fatalf("Build failed: %s", err)
} else if err == nil && shouldFail {
s.Fatalf("Expected error")
}
return s
}
func (s *sitesBuilder) addDefaults() {
var (
contentTemplate = `---
title: doc1
weight: 1
tags:
- tag1
date: "2018-02-28"
---
# doc1
*some "content"*
{{< shortcode >}}
{{< lingo >}}
`
defaultContent = []string{
"content/sect/doc1.en.md", contentTemplate,
"content/sect/doc1.fr.md", contentTemplate,
"content/sect/doc1.nb.md", contentTemplate,
"content/sect/doc1.nn.md", contentTemplate,
}
listTemplateCommon = "{{ $p := .Paginator }}{{ $p.PageNumber }}|{{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}|Pager: {{ template \"_internal/pagination.html\" . }}|Kind: {{ .Kind }}|Content: {{ .Content }}|Len Pages: {{ len .Pages }}|Len RegularPages: {{ len .RegularPages }}| HasParent: {{ if .Parent }}YES{{ else }}NO{{ end }}"
defaultTemplates = []string{
"_default/single.html", "Single: {{ .Title }}|{{ i18n \"hello\" }}|{{.Language.Lang}}|RelPermalink: {{ .RelPermalink }}|Permalink: {{ .Permalink }}|{{ .Content }}|Resources: {{ range .Resources }}{{ .MediaType }}: {{ .RelPermalink}} -- {{ end }}|Summary: {{ .Summary }}|Truncated: {{ .Truncated }}|Parent: {{ .Parent.Title }}",
"_default/list.html", "List Page " + listTemplateCommon,
"index.html", "{{ $p := .Paginator }}Default Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}|String Resource: {{ ( \"Hugo Pipes\" | resources.FromString \"text/pipes.txt\").RelPermalink }}",
"index.fr.html", "{{ $p := .Paginator }}French Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}|String Resource: {{ ( \"Hugo Pipes\" | resources.FromString \"text/pipes.txt\").RelPermalink }}",
"_default/terms.html", "Taxonomy Term Page " + listTemplateCommon,
"_default/taxonomy.html", "Taxonomy List Page " + listTemplateCommon,
// Shortcodes
"shortcodes/shortcode.html", "Shortcode: {{ i18n \"hello\" }}",
// A shortcode in multiple languages
"shortcodes/lingo.html", "LingoDefault",
"shortcodes/lingo.fr.html", "LingoFrench",
// Special templates
"404.html", "404|{{ .Lang }}|{{ .Title }}",
"robots.txt", "robots|{{ .Lang }}|{{ .Title }}",
}
defaultI18n = []string{
"en.yaml", `
hello:
other: "Hello"
`,
"fr.yaml", `
hello:
other: "Bonjour"
`,
}
defaultData = []string{
"hugo.toml", "slogan = \"Hugo Rocks!\"",
}
)
if len(s.contentFilePairs) == 0 {
s.writeFilePairs("content", defaultContent)
}
if len(s.templateFilePairs) == 0 {
s.writeFilePairs("layouts", defaultTemplates)
}
if len(s.dataFilePairs) == 0 {
s.writeFilePairs("data", defaultData)
}
if len(s.i18nFilePairs) == 0 {
s.writeFilePairs("i18n", defaultI18n)
}
}
func (s *sitesBuilder) Fatalf(format string, args ...interface{}) {
s.T.Helper()
s.T.Fatalf(format, args...)
}
func (s *sitesBuilder) AssertFileContentFn(filename string, f func(s string) bool) {
s.T.Helper()
content := s.FileContent(filename)
if !f(content) {
s.Fatalf("Assert failed for %q in content\n%s", filename, content)
}
}
func (s *sitesBuilder) AssertHome(matches ...string) {
s.AssertFileContent("public/index.html", matches...)
}
func (s *sitesBuilder) AssertFileContent(filename string, matches ...string) {
s.T.Helper()
content := s.FileContent(filename)
for _, m := range matches {
lines := strings.Split(m, "\n")
for _, match := range lines {
match = strings.TrimSpace(match)
if match == "" {
continue
}
if !strings.Contains(content, match) {
s.Fatalf("No match for %q in content for %s\n%s\n%q", match, filename, content, content)
}
}
}
}
func (s *sitesBuilder) AssertImage(width, height int, filename string) {
filename = filepath.Join(s.workingDir, filename)
f, err := s.Fs.Destination.Open(filename)
s.Assert(err, qt.IsNil)
defer f.Close()
cfg, err := jpeg.DecodeConfig(f)
s.Assert(cfg.Width, qt.Equals, width)
s.Assert(cfg.Height, qt.Equals, height)
}
func (s *sitesBuilder) AssertNoDuplicateWrites() {
s.Helper()
d := s.Fs.Destination.(hugofs.DuplicatesReporter)
s.Assert(d.ReportDuplicates(), qt.Equals, "")
}
func (s *sitesBuilder) FileContent(filename string) string {
s.T.Helper()
filename = filepath.FromSlash(filename)
if !strings.HasPrefix(filename, s.workingDir) {
filename = filepath.Join(s.workingDir, filename)
}
return readDestination(s.T, s.Fs, filename)
}
func (s *sitesBuilder) AssertObject(expected string, object interface{}) {
s.T.Helper()
got := s.dumper.Sdump(object)
expected = strings.TrimSpace(expected)
if expected != got {
fmt.Println(got)
diff := helpers.DiffStrings(expected, got)
s.Fatalf("diff:\n%s\nexpected\n%s\ngot\n%s", diff, expected, got)
}
}
func (s *sitesBuilder) AssertFileContentRe(filename string, matches ...string) {
content := readDestination(s.T, s.Fs, filename)
for _, match := range matches {
r := regexp.MustCompile("(?s)" + match)
if !r.MatchString(content) {
s.Fatalf("No match for %q in content for %s\n%q", match, filename, content)
}
}
}
func (s *sitesBuilder) CheckExists(filename string) bool {
return destinationExists(s.Fs, filepath.Clean(filename))
}
func newTestHelper(cfg config.Provider, fs *hugofs.Fs, t testing.TB) testHelper {
return testHelper{
Cfg: cfg,
Fs: fs,
C: qt.New(t),
}
}
type testHelper struct {
Cfg config.Provider
Fs *hugofs.Fs
*qt.C
}
func (th testHelper) assertFileContent(filename string, matches ...string) {
th.Helper()
filename = th.replaceDefaultContentLanguageValue(filename)
content := readDestination(th, th.Fs, filename)
for _, match := range matches {
match = th.replaceDefaultContentLanguageValue(match)
th.Assert(strings.Contains(content, match), qt.Equals, true, qt.Commentf(match+" not in: \n"+content))
}
}
func (th testHelper) assertFileContentRegexp(filename string, matches ...string) {
filename = th.replaceDefaultContentLanguageValue(filename)
content := readDestination(th, th.Fs, filename)
for _, match := range matches {
match = th.replaceDefaultContentLanguageValue(match)
r := regexp.MustCompile(match)
matches := r.MatchString(content)
if !matches {
fmt.Println(match+":\n", content)
}
th.Assert(matches, qt.Equals, true)
}
}
func (th testHelper) assertFileNotExist(filename string) {
exists, err := helpers.Exists(filename, th.Fs.Destination)
th.Assert(err, qt.IsNil)
th.Assert(exists, qt.Equals, false)
}
func (th testHelper) replaceDefaultContentLanguageValue(value string) string {
defaultInSubDir := th.Cfg.GetBool("defaultContentLanguageInSubDir")
replace := th.Cfg.GetString("defaultContentLanguage") + "/"
if !defaultInSubDir {
value = strings.Replace(value, replace, "", 1)
}
return value
}
func loadTestConfig(fs afero.Fs, withConfig ...func(cfg config.Provider) error) (*viper.Viper, error) {
v, _, err := LoadConfig(ConfigSourceDescriptor{Fs: fs}, withConfig...)
return v, err
}
func newTestCfgBasic() (*viper.Viper, *hugofs.Fs) {
mm := afero.NewMemMapFs()
v := viper.New()
v.Set("defaultContentLanguageInSubdir", true)
fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(mm), v)
return v, fs
}
func newTestCfg(withConfig ...func(cfg config.Provider) error) (*viper.Viper, *hugofs.Fs) {
mm := afero.NewMemMapFs()
v, err := loadTestConfig(mm, func(cfg config.Provider) error {
// Default is false, but true is easier to use as default in tests
cfg.Set("defaultContentLanguageInSubdir", true)
for _, w := range withConfig {
w(cfg)
}
return nil
})
if err != nil && err != ErrNoConfigFile {
panic(err)
}
fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(mm), v)
return v, fs
}
func newTestSitesFromConfig(t testing.TB, afs afero.Fs, tomlConfig string, layoutPathContentPairs ...string) (testHelper, *HugoSites) {
if len(layoutPathContentPairs)%2 != 0 {
t.Fatalf("Layouts must be provided in pairs")
}
c := qt.New(t)
writeToFs(t, afs, filepath.Join("content", ".gitkeep"), "")
writeToFs(t, afs, "config.toml", tomlConfig)
cfg, err := LoadConfigDefault(afs)
c.Assert(err, qt.IsNil)
fs := hugofs.NewFrom(afs, cfg)
th := newTestHelper(cfg, fs, t)
for i := 0; i < len(layoutPathContentPairs); i += 2 {
writeSource(t, fs, layoutPathContentPairs[i], layoutPathContentPairs[i+1])
}
h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
c.Assert(err, qt.IsNil)
return th, h
}
func createWithTemplateFromNameValues(additionalTemplates ...string) func(templ tpl.TemplateManager) error {
return func(templ tpl.TemplateManager) error {
for i := 0; i < len(additionalTemplates); i += 2 {
err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
if err != nil {
return err
}
}
return nil
}
}
// TODO(bep) replace these with the builder
func buildSingleSite(t testing.TB, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
t.Helper()
return buildSingleSiteExpected(t, false, false, depsCfg, buildCfg)
}
func buildSingleSiteExpected(t testing.TB, expectSiteInitEror, expectBuildError bool, depsCfg deps.DepsCfg, buildCfg BuildCfg) *Site {
t.Helper()
b := newTestSitesBuilderFromDepsCfg(t, depsCfg).WithNothingAdded()
err := b.CreateSitesE()
if expectSiteInitEror {
b.Assert(err, qt.Not(qt.IsNil))
return nil
} else {
b.Assert(err, qt.IsNil)
}
h := b.H
b.Assert(len(h.Sites), qt.Equals, 1)
if expectBuildError {
b.Assert(h.Build(buildCfg), qt.Not(qt.IsNil))
return nil
}
b.Assert(h.Build(buildCfg), qt.IsNil)
return h.Sites[0]
}
func writeSourcesToSource(t *testing.T, base string, fs *hugofs.Fs, sources ...[2]string) {
for _, src := range sources {
writeSource(t, fs, filepath.Join(base, src[0]), src[1])
}
}
func getPage(in page.Page, ref string) page.Page {
p, err := in.GetPage(ref)
if err != nil {
panic(err)
}
return p
}
func content(c resource.ContentProvider) string {
cc, err := c.Content()
if err != nil {
panic(err)
}
ccs, err := cast.ToStringE(cc)
if err != nil {
panic(err)
}
return ccs
}
func dumpPages(pages ...page.Page) {
fmt.Println("---------")
for _, p := range pages {
var meta interface{}
if p.File() != nil && p.File().FileInfo() != nil {
meta = p.File().FileInfo().Meta()
}
fmt.Printf("Kind: %s Title: %-10s RelPermalink: %-10s Path: %-10s sections: %s Lang: %s Meta: %v\n",
p.Kind(), p.Title(), p.RelPermalink(), p.Path(), p.SectionsPath(), p.Lang(), meta)
}
}
func dumpSPages(pages ...*pageState) {
for i, p := range pages {
fmt.Printf("%d: Kind: %s Title: %-10s RelPermalink: %-10s Path: %-10s sections: %s\n",
i+1,
p.Kind(), p.Title(), p.RelPermalink(), p.Path(), p.SectionsPath())
}
}
func printStringIndexes(s string) {
lines := strings.Split(s, "\n")
i := 0
for _, line := range lines {
for _, r := range line {
fmt.Printf("%-3s", strconv.Itoa(i))
i += utf8.RuneLen(r)
}
i++
fmt.Println()
for _, r := range line {
fmt.Printf("%-3s", string(r))
}
fmt.Println()
}
}
func isCI() bool {
return os.Getenv("CI") != ""
}
func isGo111() bool {
return strings.Contains(runtime.Version(), "1.11")
}
// See https://github.com/golang/go/issues/19280
// Not in use.
var parallelEnabled = true
func parallel(t *testing.T) {
if parallelEnabled {
t.Parallel()
}
}
func skipSymlink(t *testing.T) {
if runtime.GOOS == "windows" && os.Getenv("CI") == "" {
t.Skip("skip symlink test on local Windows (needs admin)")
}
}