github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugolib/hugo_sites_build_errors_test.go (about)

     1  package hugolib
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/gohugoio/hugo/htesting"
    10  
    11  	qt "github.com/frankban/quicktest"
    12  	"github.com/gohugoio/hugo/common/herrors"
    13  )
    14  
    15  type testSiteBuildErrorAsserter struct {
    16  	name string
    17  	c    *qt.C
    18  }
    19  
    20  func (t testSiteBuildErrorAsserter) getFileError(err error) *herrors.ErrorWithFileContext {
    21  	t.c.Assert(err, qt.Not(qt.IsNil), qt.Commentf(t.name))
    22  	ferr := herrors.UnwrapErrorWithFileContext(err)
    23  	t.c.Assert(ferr, qt.Not(qt.IsNil))
    24  	return ferr
    25  }
    26  
    27  func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) {
    28  	fe := t.getFileError(err)
    29  	t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber, qt.Commentf(err.Error()))
    30  }
    31  
    32  func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) {
    33  	// The error message will contain filenames with OS slashes. Normalize before compare.
    34  	e1, e2 = filepath.ToSlash(e1), filepath.ToSlash(e2)
    35  	t.c.Assert(e2, qt.Contains, e1)
    36  }
    37  
    38  func TestSiteBuildErrors(t *testing.T) {
    39  	const (
    40  		yamlcontent = "yamlcontent"
    41  		tomlcontent = "tomlcontent"
    42  		jsoncontent = "jsoncontent"
    43  		shortcode   = "shortcode"
    44  		base        = "base"
    45  		single      = "single"
    46  	)
    47  
    48  	// TODO(bep) add content tests after https://github.com/gohugoio/hugo/issues/5324
    49  	// is implemented.
    50  
    51  	tests := []struct {
    52  		name              string
    53  		fileType          string
    54  		fileFixer         func(content string) string
    55  		assertCreateError func(a testSiteBuildErrorAsserter, err error)
    56  		assertBuildError  func(a testSiteBuildErrorAsserter, err error)
    57  	}{
    58  
    59  		{
    60  			name:     "Base template parse failed",
    61  			fileType: base,
    62  			fileFixer: func(content string) string {
    63  				return strings.Replace(content, ".Title }}", ".Title }", 1)
    64  			},
    65  			// Base templates gets parsed at build time.
    66  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
    67  				a.assertLineNumber(4, err)
    68  			},
    69  		},
    70  		{
    71  			name:     "Base template execute failed",
    72  			fileType: base,
    73  			fileFixer: func(content string) string {
    74  				return strings.Replace(content, ".Title", ".Titles", 1)
    75  			},
    76  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
    77  				a.assertLineNumber(4, err)
    78  			},
    79  		},
    80  		{
    81  			name:     "Single template parse failed",
    82  			fileType: single,
    83  			fileFixer: func(content string) string {
    84  				return strings.Replace(content, ".Title }}", ".Title }", 1)
    85  			},
    86  			assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
    87  				fe := a.getFileError(err)
    88  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
    89  				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1)
    90  				a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template")
    91  				a.assertErrorMessage("\"layouts/foo/single.html:5:1\": parse failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error())
    92  			},
    93  		},
    94  		{
    95  			name:     "Single template execute failed",
    96  			fileType: single,
    97  			fileFixer: func(content string) string {
    98  				return strings.Replace(content, ".Title", ".Titles", 1)
    99  			},
   100  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   101  				fe := a.getFileError(err)
   102  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
   103  				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 14)
   104  				a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template")
   105  				a.assertErrorMessage("\"layouts/_default/single.html:5:14\": execute of template failed", fe.Error())
   106  			},
   107  		},
   108  		{
   109  			name:     "Single template execute failed, long keyword",
   110  			fileType: single,
   111  			fileFixer: func(content string) string {
   112  				return strings.Replace(content, ".Title", ".ThisIsAVeryLongTitle", 1)
   113  			},
   114  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   115  				fe := a.getFileError(err)
   116  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
   117  				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 14)
   118  				a.c.Assert(fe.ChromaLexer, qt.Equals, "go-html-template")
   119  				a.assertErrorMessage("\"layouts/_default/single.html:5:14\": execute of template failed", fe.Error())
   120  			},
   121  		},
   122  		{
   123  			name:     "Shortcode parse failed",
   124  			fileType: shortcode,
   125  			fileFixer: func(content string) string {
   126  				return strings.Replace(content, ".Title }}", ".Title }", 1)
   127  			},
   128  			assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
   129  				a.assertLineNumber(4, err)
   130  			},
   131  		},
   132  		{
   133  			name:     "Shortode execute failed",
   134  			fileType: shortcode,
   135  			fileFixer: func(content string) string {
   136  				return strings.Replace(content, ".Title", ".Titles", 1)
   137  			},
   138  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   139  				fe := a.getFileError(err)
   140  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 7)
   141  				a.c.Assert(fe.ChromaLexer, qt.Equals, "md")
   142  				// Make sure that it contains both the content file and template
   143  				a.assertErrorMessage(`content/myyaml.md:7:10": failed to render shortcode "sc"`, fe.Error())
   144  				a.assertErrorMessage(`shortcodes/sc.html:4:22: executing "shortcodes/sc.html" at <.Page.Titles>: can't evaluate`, fe.Error())
   145  			},
   146  		},
   147  		{
   148  			name:     "Shortode does not exist",
   149  			fileType: yamlcontent,
   150  			fileFixer: func(content string) string {
   151  				return strings.Replace(content, "{{< sc >}}", "{{< nono >}}", 1)
   152  			},
   153  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   154  				fe := a.getFileError(err)
   155  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 7)
   156  				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 10)
   157  				a.c.Assert(fe.ChromaLexer, qt.Equals, "md")
   158  				a.assertErrorMessage(`"content/myyaml.md:7:10": failed to extract shortcode: template for shortcode "nono" not found`, fe.Error())
   159  			},
   160  		},
   161  		{
   162  			name:     "Invalid YAML front matter",
   163  			fileType: yamlcontent,
   164  			fileFixer: func(content string) string {
   165  				return strings.Replace(content, "title:", "title: %foo", 1)
   166  			},
   167  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   168  				a.assertLineNumber(2, err)
   169  			},
   170  		},
   171  		{
   172  			name:     "Invalid TOML front matter",
   173  			fileType: tomlcontent,
   174  			fileFixer: func(content string) string {
   175  				return strings.Replace(content, "description = ", "description &", 1)
   176  			},
   177  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   178  				fe := a.getFileError(err)
   179  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 6)
   180  				a.c.Assert(fe.ErrorContext.ChromaLexer, qt.Equals, "toml")
   181  			},
   182  		},
   183  		{
   184  			name:     "Invalid JSON front matter",
   185  			fileType: jsoncontent,
   186  			fileFixer: func(content string) string {
   187  				return strings.Replace(content, "\"description\":", "\"description\"", 1)
   188  			},
   189  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   190  				fe := a.getFileError(err)
   191  
   192  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 3)
   193  				a.c.Assert(fe.ErrorContext.ChromaLexer, qt.Equals, "json")
   194  			},
   195  		},
   196  		{
   197  			// See https://github.com/gohugoio/hugo/issues/5327
   198  			name:     "Panic in template Execute",
   199  			fileType: single,
   200  			fileFixer: func(content string) string {
   201  				return strings.Replace(content, ".Title", ".Parent.Parent.Parent", 1)
   202  			},
   203  
   204  			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   205  				a.c.Assert(err, qt.Not(qt.IsNil))
   206  				fe := a.getFileError(err)
   207  				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
   208  				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 21)
   209  			},
   210  		},
   211  	}
   212  
   213  	for _, test := range tests {
   214  		test := test
   215  		t.Run(test.name, func(t *testing.T) {
   216  			t.Parallel()
   217  			c := qt.New(t)
   218  			errorAsserter := testSiteBuildErrorAsserter{
   219  				c:    c,
   220  				name: test.name,
   221  			}
   222  
   223  			b := newTestSitesBuilder(t).WithSimpleConfigFile()
   224  
   225  			f := func(fileType, content string) string {
   226  				if fileType != test.fileType {
   227  					return content
   228  				}
   229  				return test.fileFixer(content)
   230  			}
   231  
   232  			b.WithTemplatesAdded("layouts/shortcodes/sc.html", f(shortcode, `SHORTCODE L1
   233  SHORTCODE L2
   234  SHORTCODE L3:
   235  SHORTCODE L4: {{ .Page.Title }}
   236  `))
   237  			b.WithTemplatesAdded("layouts/_default/baseof.html", f(base, `BASEOF L1
   238  BASEOF L2
   239  BASEOF L3
   240  BASEOF L4{{ if .Title }}{{ end }}
   241  {{block "main" .}}This is the main content.{{end}}
   242  BASEOF L6
   243  `))
   244  
   245  			b.WithTemplatesAdded("layouts/_default/single.html", f(single, `{{ define "main" }}
   246  SINGLE L2:
   247  SINGLE L3:
   248  SINGLE L4:
   249  SINGLE L5: {{ .Title }} {{ .Content }}
   250  {{ end }}
   251  `))
   252  
   253  			b.WithTemplatesAdded("layouts/foo/single.html", f(single, `
   254  SINGLE L2:
   255  SINGLE L3:
   256  SINGLE L4:
   257  SINGLE L5: {{ .Title }} {{ .Content }}
   258  `))
   259  
   260  			b.WithContent("myyaml.md", f(yamlcontent, `---
   261  title: "The YAML"
   262  ---
   263  
   264  Some content.
   265  
   266           {{< sc >}}
   267  
   268  Some more text.
   269  
   270  The end.
   271  
   272  `))
   273  
   274  			b.WithContent("mytoml.md", f(tomlcontent, `+++
   275  title = "The TOML"
   276  p1 = "v"
   277  p2 = "v"
   278  p3 = "v"
   279  description = "Descriptioon"
   280  +++
   281  
   282  Some content.
   283  
   284  
   285  `))
   286  
   287  			b.WithContent("myjson.md", f(jsoncontent, `{
   288  	"title": "This is a title",
   289  	"description": "This is a description."
   290  }
   291  
   292  Some content.
   293  
   294  
   295  `))
   296  
   297  			createErr := b.CreateSitesE()
   298  			if test.assertCreateError != nil {
   299  				test.assertCreateError(errorAsserter, createErr)
   300  			} else {
   301  				c.Assert(createErr, qt.IsNil)
   302  			}
   303  
   304  			if createErr == nil {
   305  				buildErr := b.BuildE(BuildCfg{})
   306  				if test.assertBuildError != nil {
   307  					test.assertBuildError(errorAsserter, buildErr)
   308  				} else {
   309  					c.Assert(buildErr, qt.IsNil)
   310  				}
   311  			}
   312  		})
   313  	}
   314  }
   315  
   316  // https://github.com/gohugoio/hugo/issues/5375
   317  func TestSiteBuildTimeout(t *testing.T) {
   318  	if !htesting.IsCI() {
   319  		//defer leaktest.CheckTimeout(t, 10*time.Second)()
   320  	}
   321  
   322  	b := newTestSitesBuilder(t)
   323  	b.WithConfigFile("toml", `
   324  timeout = 5
   325  `)
   326  
   327  	b.WithTemplatesAdded("_default/single.html", `
   328  {{ .WordCount }}
   329  `, "shortcodes/c.html", `
   330  {{ range .Page.Site.RegularPages }}
   331  {{ .WordCount }}
   332  {{ end }}
   333  
   334  `)
   335  
   336  	for i := 1; i < 100; i++ {
   337  		b.WithContent(fmt.Sprintf("page%d.md", i), `---
   338  title: "A page"
   339  ---
   340  
   341  {{< c >}}`)
   342  	}
   343  
   344  	b.CreateSites().BuildFail(BuildCfg{})
   345  }