hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

hugo_sites_build_errors_test.go (16339B)

    1 package hugolib
    2 
    3 import (
    4 	"fmt"
    5 	"os"
    6 	"path/filepath"
    7 	"strings"
    8 	"testing"
    9 
   10 	"github.com/gohugoio/hugo/htesting"
   11 
   12 	qt "github.com/frankban/quicktest"
   13 	"github.com/gohugoio/hugo/common/herrors"
   14 )
   15 
   16 type testSiteBuildErrorAsserter struct {
   17 	name string
   18 	c    *qt.C
   19 }
   20 
   21 func (t testSiteBuildErrorAsserter) getFileError(err error) herrors.FileError {
   22 	t.c.Assert(err, qt.Not(qt.IsNil), qt.Commentf(t.name))
   23 	fe := herrors.UnwrapFileError(err)
   24 	t.c.Assert(fe, qt.Not(qt.IsNil))
   25 	return fe
   26 }
   27 
   28 func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) {
   29 	t.c.Helper()
   30 	fe := t.getFileError(err)
   31 	t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber, qt.Commentf(err.Error()))
   32 }
   33 
   34 func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) {
   35 	// The error message will contain filenames with OS slashes. Normalize before compare.
   36 	e1, e2 = filepath.ToSlash(e1), filepath.ToSlash(e2)
   37 	t.c.Assert(e2, qt.Contains, e1)
   38 }
   39 
   40 func TestSiteBuildErrors(t *testing.T) {
   41 	const (
   42 		yamlcontent = "yamlcontent"
   43 		tomlcontent = "tomlcontent"
   44 		jsoncontent = "jsoncontent"
   45 		shortcode   = "shortcode"
   46 		base        = "base"
   47 		single      = "single"
   48 	)
   49 
   50 	// TODO(bep) add content tests after https://github.com/gohugoio/hugo/issues/5324
   51 	// is implemented.
   52 
   53 	tests := []struct {
   54 		name              string
   55 		fileType          string
   56 		fileFixer         func(content string) string
   57 		assertCreateError func(a testSiteBuildErrorAsserter, err error)
   58 		assertBuildError  func(a testSiteBuildErrorAsserter, err error)
   59 	}{
   60 
   61 		{
   62 			name:     "Base template parse failed",
   63 			fileType: base,
   64 			fileFixer: func(content string) string {
   65 				return strings.Replace(content, ".Title }}", ".Title }", 1)
   66 			},
   67 			// Base templates gets parsed at build time.
   68 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   69 				a.assertLineNumber(4, err)
   70 			},
   71 		},
   72 		{
   73 			name:     "Base template execute failed",
   74 			fileType: base,
   75 			fileFixer: func(content string) string {
   76 				return strings.Replace(content, ".Title", ".Titles", 1)
   77 			},
   78 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
   79 				a.assertLineNumber(4, err)
   80 			},
   81 		},
   82 		{
   83 			name:     "Single template parse failed",
   84 			fileType: single,
   85 			fileFixer: func(content string) string {
   86 				return strings.Replace(content, ".Title }}", ".Title }", 1)
   87 			},
   88 			assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
   89 				fe := a.getFileError(err)
   90 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
   91 				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1)
   92 				a.assertErrorMessage("\"layouts/foo/single.html:5:1\": parse failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error())
   93 			},
   94 		},
   95 		{
   96 			name:     "Single template execute failed",
   97 			fileType: single,
   98 			fileFixer: func(content string) string {
   99 				return strings.Replace(content, ".Title", ".Titles", 1)
  100 			},
  101 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
  102 				fe := a.getFileError(err)
  103 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
  104 				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 14)
  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.assertErrorMessage("\"layouts/_default/single.html:5:14\": execute of template failed", fe.Error())
  119 			},
  120 		},
  121 		{
  122 			name:     "Shortcode parse failed",
  123 			fileType: shortcode,
  124 			fileFixer: func(content string) string {
  125 				return strings.Replace(content, ".Title }}", ".Title }", 1)
  126 			},
  127 			assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
  128 				a.assertLineNumber(4, err)
  129 			},
  130 		},
  131 		{
  132 			name:     "Shortcode execute failed",
  133 			fileType: shortcode,
  134 			fileFixer: func(content string) string {
  135 				return strings.Replace(content, ".Title", ".Titles", 1)
  136 			},
  137 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
  138 				fe := a.getFileError(err)
  139 				// Make sure that it contains both the content file and template
  140 				a.assertErrorMessage(`"content/myyaml.md:7:10": failed to render shortcode "sc": failed to process shortcode: "layouts/shortcodes/sc.html:4:22": execute of template failed: template: shortcodes/sc.html:4:22: executing "shortcodes/sc.html" at <.Page.Titles>: can't evaluate field Titles in type page.Page`, fe.Error())
  141 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 7)
  142 
  143 			},
  144 		},
  145 		{
  146 			name:     "Shortode does not exist",
  147 			fileType: yamlcontent,
  148 			fileFixer: func(content string) string {
  149 				return strings.Replace(content, "{{< sc >}}", "{{< nono >}}", 1)
  150 			},
  151 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
  152 				fe := a.getFileError(err)
  153 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 7)
  154 				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 10)
  155 				a.assertErrorMessage(`"content/myyaml.md:7:10": failed to extract shortcode: template for shortcode "nono" not found`, fe.Error())
  156 			},
  157 		},
  158 		{
  159 			name:     "Invalid YAML front matter",
  160 			fileType: yamlcontent,
  161 			fileFixer: func(content string) string {
  162 				return `---
  163 title: "My YAML Content"
  164 foo bar
  165 ---
  166 `
  167 			},
  168 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
  169 				a.assertLineNumber(3, err)
  170 			},
  171 		},
  172 		{
  173 			name:     "Invalid TOML front matter",
  174 			fileType: tomlcontent,
  175 			fileFixer: func(content string) string {
  176 				return strings.Replace(content, "description = ", "description &", 1)
  177 			},
  178 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
  179 				fe := a.getFileError(err)
  180 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 6)
  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 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 3)
  192 			},
  193 		},
  194 		{
  195 			// See https://github.com/gohugoio/hugo/issues/5327
  196 			name:     "Panic in template Execute",
  197 			fileType: single,
  198 			fileFixer: func(content string) string {
  199 				return strings.Replace(content, ".Title", ".Parent.Parent.Parent", 1)
  200 			},
  201 
  202 			assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
  203 				a.c.Assert(err, qt.Not(qt.IsNil))
  204 				fe := a.getFileError(err)
  205 				a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
  206 				a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 21)
  207 			},
  208 		},
  209 	}
  210 
  211 	for _, test := range tests {
  212 		if test.name != "Invalid JSON front matter" {
  213 			continue
  214 		}
  215 		test := test
  216 		t.Run(test.name, func(t *testing.T) {
  217 			t.Parallel()
  218 			c := qt.New(t)
  219 			errorAsserter := testSiteBuildErrorAsserter{
  220 				c:    c,
  221 				name: test.name,
  222 			}
  223 
  224 			b := newTestSitesBuilder(t).WithSimpleConfigFile()
  225 
  226 			f := func(fileType, content string) string {
  227 				if fileType != test.fileType {
  228 					return content
  229 				}
  230 				return test.fileFixer(content)
  231 			}
  232 
  233 			b.WithTemplatesAdded("layouts/shortcodes/sc.html", f(shortcode, `SHORTCODE L1
  234 SHORTCODE L2
  235 SHORTCODE L3:
  236 SHORTCODE L4: {{ .Page.Title }}
  237 `))
  238 			b.WithTemplatesAdded("layouts/_default/baseof.html", f(base, `BASEOF L1
  239 BASEOF L2
  240 BASEOF L3
  241 BASEOF L4{{ if .Title }}{{ end }}
  242 {{block "main" .}}This is the main content.{{end}}
  243 BASEOF L6
  244 `))
  245 
  246 			b.WithTemplatesAdded("layouts/_default/single.html", f(single, `{{ define "main" }}
  247 SINGLE L2:
  248 SINGLE L3:
  249 SINGLE L4:
  250 SINGLE L5: {{ .Title }} {{ .Content }}
  251 {{ end }}
  252 `))
  253 
  254 			b.WithTemplatesAdded("layouts/foo/single.html", f(single, `
  255 SINGLE L2:
  256 SINGLE L3:
  257 SINGLE L4:
  258 SINGLE L5: {{ .Title }} {{ .Content }}
  259 `))
  260 
  261 			b.WithContent("myyaml.md", f(yamlcontent, `---
  262 title: "The YAML"
  263 ---
  264 
  265 Some content.
  266 
  267          {{< sc >}}
  268 
  269 Some more text.
  270 
  271 The end.
  272 
  273 `))
  274 
  275 			b.WithContent("mytoml.md", f(tomlcontent, `+++
  276 title = "The TOML"
  277 p1 = "v"
  278 p2 = "v"
  279 p3 = "v"
  280 description = "Descriptioon"
  281 +++
  282 
  283 Some content.
  284 
  285 
  286 `))
  287 
  288 			b.WithContent("myjson.md", f(jsoncontent, `{
  289 	"title": "This is a title",
  290 	"description": "This is a description."
  291 }
  292 
  293 Some content.
  294 
  295 
  296 `))
  297 
  298 			createErr := b.CreateSitesE()
  299 			if test.assertCreateError != nil {
  300 				test.assertCreateError(errorAsserter, createErr)
  301 			} else {
  302 				c.Assert(createErr, qt.IsNil)
  303 			}
  304 
  305 			if createErr == nil {
  306 				buildErr := b.BuildE(BuildCfg{})
  307 				if test.assertBuildError != nil {
  308 					test.assertBuildError(errorAsserter, buildErr)
  309 				} else {
  310 					c.Assert(buildErr, qt.IsNil)
  311 				}
  312 			}
  313 		})
  314 	}
  315 
  316 }
  317 
  318 // Issue 9852
  319 func TestErrorMinify(t *testing.T) {
  320 	t.Parallel()
  321 
  322 	files := `
  323 -- config.toml --
  324 minify = true
  325 
  326 -- layouts/index.html --
  327 <body>
  328 <script>=;</script>
  329 </body>
  330 
  331 `
  332 
  333 	b, err := NewIntegrationTestBuilder(
  334 		IntegrationTestConfig{
  335 			T:           t,
  336 			TxtarString: files,
  337 		},
  338 	).BuildE()
  339 
  340 	fe := herrors.UnwrapFileError(err)
  341 	b.Assert(fe, qt.IsNotNil)
  342 	b.Assert(fe.Position().LineNumber, qt.Equals, 2)
  343 	b.Assert(fe.Position().ColumnNumber, qt.Equals, 9)
  344 	b.Assert(fe.Error(), qt.Contains, "unexpected = in expression on line 2 and column 9")
  345 	b.Assert(filepath.ToSlash(fe.Position().Filename), qt.Contains, "hugo-transform-error")
  346 	b.Assert(os.Remove(fe.Position().Filename), qt.IsNil)
  347 
  348 }
  349 
  350 func TestErrorNestedRender(t *testing.T) {
  351 	t.Parallel()
  352 
  353 	files := `
  354 -- config.toml --
  355 -- content/_index.md --
  356 ---
  357 title: "Home"
  358 ---
  359 -- layouts/index.html --
  360 line 1
  361 line 2
  362 1{{ .Render "myview" }}
  363 -- layouts/_default/myview.html --
  364 line 1
  365 12{{ partial "foo.html" . }}
  366 line 4
  367 line 5
  368 -- layouts/partials/foo.html --
  369 line 1
  370 line 2
  371 123{{ .ThisDoesNotExist }}
  372 line 4
  373 `
  374 
  375 	b, err := NewIntegrationTestBuilder(
  376 		IntegrationTestConfig{
  377 			T:           t,
  378 			TxtarString: files,
  379 		},
  380 	).BuildE()
  381 
  382 	b.Assert(err, qt.IsNotNil)
  383 	errors := herrors.UnwrapFileErrorsWithErrorContext(err)
  384 	b.Assert(errors, qt.HasLen, 4)
  385 	b.Assert(errors[0].Position().LineNumber, qt.Equals, 3)
  386 	b.Assert(errors[0].Position().ColumnNumber, qt.Equals, 4)
  387 	b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/layouts/index.html:3:4": execute of template failed`))
  388 	b.Assert(errors[0].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "1{{ .Render \"myview\" }}"})
  389 	b.Assert(errors[2].Position().LineNumber, qt.Equals, 2)
  390 	b.Assert(errors[2].Position().ColumnNumber, qt.Equals, 5)
  391 	b.Assert(errors[2].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"})
  392 
  393 	b.Assert(errors[3].Position().LineNumber, qt.Equals, 3)
  394 	b.Assert(errors[3].Position().ColumnNumber, qt.Equals, 6)
  395 	b.Assert(errors[3].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"})
  396 
  397 }
  398 
  399 func TestErrorNestedShortocde(t *testing.T) {
  400 	t.Parallel()
  401 
  402 	files := `
  403 -- config.toml --
  404 -- content/_index.md --
  405 ---
  406 title: "Home"
  407 ---
  408 
  409 ## Hello
  410 {{< hello >}}
  411 
  412 -- layouts/index.html --
  413 line 1
  414 line 2
  415 {{ .Content }}
  416 line 5
  417 -- layouts/shortcodes/hello.html --
  418 line 1
  419 12{{ partial "foo.html" . }}
  420 line 4
  421 line 5
  422 -- layouts/partials/foo.html --
  423 line 1
  424 line 2
  425 123{{ .ThisDoesNotExist }}
  426 line 4
  427 `
  428 
  429 	b, err := NewIntegrationTestBuilder(
  430 		IntegrationTestConfig{
  431 			T:           t,
  432 			TxtarString: files,
  433 		},
  434 	).BuildE()
  435 
  436 	b.Assert(err, qt.IsNotNil)
  437 	errors := herrors.UnwrapFileErrorsWithErrorContext(err)
  438 
  439 	b.Assert(errors, qt.HasLen, 3)
  440 
  441 	b.Assert(errors[0].Position().LineNumber, qt.Equals, 6)
  442 	b.Assert(errors[0].Position().ColumnNumber, qt.Equals, 1)
  443 	b.Assert(errors[0].ErrorContext().ChromaLexer, qt.Equals, "md")
  444 	b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:6:1": failed to render shortcode "hello": failed to process shortcode: "/layouts/shortcodes/hello.html:2:5":`))
  445 	b.Assert(errors[0].ErrorContext().Lines, qt.DeepEquals, []string{"", "## Hello", "{{< hello >}}", ""})
  446 	b.Assert(errors[1].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"})
  447 	b.Assert(errors[2].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"})
  448 
  449 }
  450 
  451 func TestErrorRenderHookHeading(t *testing.T) {
  452 	t.Parallel()
  453 
  454 	files := `
  455 -- config.toml --
  456 -- content/_index.md --
  457 ---
  458 title: "Home"
  459 ---
  460 
  461 ## Hello
  462 
  463 -- layouts/index.html --
  464 line 1
  465 line 2
  466 {{ .Content }}
  467 line 5
  468 -- layouts/_default/_markup/render-heading.html --
  469 line 1
  470 12{{ .Levels }}
  471 line 4
  472 line 5
  473 `
  474 
  475 	b, err := NewIntegrationTestBuilder(
  476 		IntegrationTestConfig{
  477 			T:           t,
  478 			TxtarString: files,
  479 		},
  480 	).BuildE()
  481 
  482 	b.Assert(err, qt.IsNotNil)
  483 	errors := herrors.UnwrapFileErrorsWithErrorContext(err)
  484 
  485 	b.Assert(errors, qt.HasLen, 2)
  486 	b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:1:1": "/layouts/_default/_markup/render-heading.html:2:5": execute of template failed`))
  487 
  488 }
  489 
  490 func TestErrorRenderHookCodeblock(t *testing.T) {
  491 	t.Parallel()
  492 
  493 	files := `
  494 -- config.toml --
  495 -- content/_index.md --
  496 ---
  497 title: "Home"
  498 ---
  499 
  500 ## Hello
  501 
  502 §§§ foo
  503 bar
  504 §§§
  505 
  506 
  507 -- layouts/index.html --
  508 line 1
  509 line 2
  510 {{ .Content }}
  511 line 5
  512 -- layouts/_default/_markup/render-codeblock-foo.html --
  513 line 1
  514 12{{ .Foo }}
  515 line 4
  516 line 5
  517 `
  518 
  519 	b, err := NewIntegrationTestBuilder(
  520 		IntegrationTestConfig{
  521 			T:           t,
  522 			TxtarString: files,
  523 		},
  524 	).BuildE()
  525 
  526 	b.Assert(err, qt.IsNotNil)
  527 	errors := herrors.UnwrapFileErrorsWithErrorContext(err)
  528 
  529 	b.Assert(errors, qt.HasLen, 2)
  530 	first := errors[0]
  531 	b.Assert(first.Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:7:1": "/layouts/_default/_markup/render-codeblock-foo.html:2:5": execute of template failed`))
  532 
  533 }
  534 
  535 func TestErrorInBaseTemplate(t *testing.T) {
  536 	t.Parallel()
  537 
  538 	filesTemplate := `
  539 -- config.toml --
  540 -- content/_index.md --
  541 ---
  542 title: "Home"
  543 ---
  544 -- layouts/baseof.html --
  545 line 1 base
  546 line 2 base
  547 {{ block "main" . }}empty{{ end }}
  548 line 4 base
  549 {{ block "toc" . }}empty{{ end }}
  550 -- layouts/index.html --
  551 {{ define "main" }}
  552 line 2 index
  553 line 3 index
  554 line 4 index
  555 {{ end }}
  556 {{ define "toc" }}
  557 TOC: {{ partial "toc.html" . }}
  558 {{ end }}
  559 -- layouts/partials/toc.html --
  560 toc line 1
  561 toc line 2
  562 toc line 3
  563 toc line 4
  564 
  565 
  566 
  567 
  568 `
  569 
  570 	t.Run("base template", func(t *testing.T) {
  571 		files := strings.Replace(filesTemplate, "line 4 base", "123{{ .ThisDoesNotExist \"abc\" }}", 1)
  572 
  573 		b, err := NewIntegrationTestBuilder(
  574 			IntegrationTestConfig{
  575 				T:           t,
  576 				TxtarString: files,
  577 			},
  578 		).BuildE()
  579 
  580 		b.Assert(err, qt.IsNotNil)
  581 		b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`render of "home" failed: "/layouts/baseof.html:4:6"`))
  582 
  583 	})
  584 
  585 	t.Run("index template", func(t *testing.T) {
  586 		files := strings.Replace(filesTemplate, "line 3 index", "1234{{ .ThisDoesNotExist \"abc\" }}", 1)
  587 
  588 		b, err := NewIntegrationTestBuilder(
  589 			IntegrationTestConfig{
  590 				T:           t,
  591 				TxtarString: files,
  592 			},
  593 		).BuildE()
  594 
  595 		b.Assert(err, qt.IsNotNil)
  596 		b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`render of "home" failed: "/layouts/index.html:3:7"`))
  597 
  598 	})
  599 
  600 	t.Run("partial from define", func(t *testing.T) {
  601 		files := strings.Replace(filesTemplate, "toc line 2", "12345{{ .ThisDoesNotExist \"abc\" }}", 1)
  602 
  603 		b, err := NewIntegrationTestBuilder(
  604 			IntegrationTestConfig{
  605 				T:           t,
  606 				TxtarString: files,
  607 			},
  608 		).BuildE()
  609 
  610 		b.Assert(err, qt.IsNotNil)
  611 		b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`render of "home" failed: "/layouts/index.html:7:8": execute of template failed`))
  612 		b.Assert(err.Error(), qt.Contains, `execute of template failed: template: partials/toc.html:2:8: executing "partials/toc.html"`)
  613 
  614 	})
  615 
  616 }
  617 
  618 // https://github.com/gohugoio/hugo/issues/5375
  619 func TestSiteBuildTimeout(t *testing.T) {
  620 	if !htesting.IsCI() {
  621 		//defer leaktest.CheckTimeout(t, 10*time.Second)()
  622 	}
  623 
  624 	b := newTestSitesBuilder(t)
  625 	b.WithConfigFile("toml", `
  626 timeout = 5
  627 `)
  628 
  629 	b.WithTemplatesAdded("_default/single.html", `
  630 {{ .WordCount }}
  631 `, "shortcodes/c.html", `
  632 {{ range .Page.Site.RegularPages }}
  633 {{ .WordCount }}
  634 {{ end }}
  635 
  636 `)
  637 
  638 	for i := 1; i < 100; i++ {
  639 		b.WithContent(fmt.Sprintf("page%d.md", i), `---
  640 title: "A page"
  641 ---
  642 
  643 {{< c >}}`)
  644 	}
  645 
  646 	b.CreateSites().BuildFail(BuildCfg{})
  647 }