mirror of
https://github.com/gohugoio/hugo.git
synced 2025-12-13 20:36:04 +01:00
637 lines
16 KiB
Go
637 lines
16 KiB
Go
package hugolib
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
qt "github.com/frankban/quicktest"
|
|
"github.com/gohugoio/hugo/common/herrors"
|
|
)
|
|
|
|
type testSiteBuildErrorAsserter struct {
|
|
name string
|
|
c *qt.C
|
|
}
|
|
|
|
func (t testSiteBuildErrorAsserter) getFileError(err error) herrors.FileError {
|
|
t.c.Assert(err, qt.Not(qt.IsNil), qt.Commentf(t.name))
|
|
fe := herrors.UnwrapFileError(err)
|
|
t.c.Assert(fe, qt.Not(qt.IsNil))
|
|
return fe
|
|
}
|
|
|
|
func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) {
|
|
t.c.Helper()
|
|
fe := t.getFileError(err)
|
|
t.c.Assert(fe.Position().LineNumber, qt.Equals, lineNumber, qt.Commentf(err.Error()))
|
|
}
|
|
|
|
func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) {
|
|
// The error message will contain filenames with OS slashes. Normalize before compare.
|
|
e1, e2 = filepath.ToSlash(e1), filepath.ToSlash(e2)
|
|
t.c.Assert(e2, qt.Contains, e1)
|
|
}
|
|
|
|
func TestSiteBuildErrors(t *testing.T) {
|
|
const (
|
|
yamlcontent = "yamlcontent"
|
|
tomlcontent = "tomlcontent"
|
|
jsoncontent = "jsoncontent"
|
|
shortcode = "shortcode"
|
|
base = "base"
|
|
single = "single"
|
|
)
|
|
|
|
type testCase struct {
|
|
name string
|
|
fileType string
|
|
fileFixer func(content string) string
|
|
assertErr func(a testSiteBuildErrorAsserter, err error)
|
|
}
|
|
|
|
createTestFiles := func(tc testCase) string {
|
|
f := func(ftype, content string) string {
|
|
if ftype != tc.fileType {
|
|
return content
|
|
}
|
|
return tc.fileFixer(content)
|
|
}
|
|
|
|
return `
|
|
-- hugo.toml --
|
|
baseURL = "https://example.com"
|
|
-- layouts/_shortcodes/sc.html --
|
|
` + f(shortcode, `SHORTCODE L1
|
|
SHORTCODE L2
|
|
SHORTCODE L3:
|
|
SHORTCODE L4: {{ .Page.Title }}
|
|
`) + `
|
|
-- layouts/baseof.html --
|
|
` + f(base, `BASEOF L1
|
|
BASEOF L2
|
|
BASEOF L3
|
|
BASEOF L4{{ if .Title }}{{ end }}
|
|
{{block "main" .}}This is the main content.{{end}}
|
|
BASEOF L6
|
|
`) + `
|
|
-- layouts/single.html --
|
|
` + f(single, `{{ define "main" }}
|
|
SINGLE L2:
|
|
SINGLE L3:
|
|
SINGLE L4:
|
|
SINGLE L5: {{ .Title }} {{ .Content }}
|
|
{{ end }}
|
|
`) + `
|
|
-- layouts/foo/single.html --
|
|
` + f(single, `
|
|
SINGLE L2:
|
|
SINGLE L3:
|
|
SINGLE L4:
|
|
SINGLE L5: {{ .Title }} {{ .Content }}
|
|
`) + `
|
|
-- content/myyaml.md --
|
|
` + f(yamlcontent, `---
|
|
title: "The YAML"
|
|
---
|
|
|
|
Some content.
|
|
|
|
{{< sc >}}
|
|
|
|
Some more text.
|
|
|
|
The end.
|
|
`) + `
|
|
-- content/mytoml.md --
|
|
` + f(tomlcontent, `+++
|
|
title = "The TOML"
|
|
p1 = "v"
|
|
p2 = "v"
|
|
p3 = "v"
|
|
description = "Descriptioon"
|
|
+++
|
|
|
|
Some content.
|
|
`) + `
|
|
-- content/myjson.md --
|
|
` + f(jsoncontent, `{
|
|
"title": "This is a title",
|
|
"description": "This is a description."
|
|
}
|
|
|
|
Some content.
|
|
`)
|
|
}
|
|
|
|
tests := []testCase{
|
|
{
|
|
name: "Base template parse failed",
|
|
fileType: base,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title }}", ".Title }", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
a.assertLineNumber(4, err)
|
|
},
|
|
},
|
|
{
|
|
name: "Base template execute failed",
|
|
fileType: base,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title", ".Titles", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
a.assertLineNumber(4, err)
|
|
},
|
|
},
|
|
{
|
|
name: "Single template parse failed",
|
|
fileType: single,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title }}", ".Title }", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
fe := a.getFileError(err)
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
|
|
a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 1)
|
|
a.assertErrorMessage("\"/layouts/foo/single.html:5:1\": parse of template failed: template: foo/single.html:5: unexpected \"}\" in operand", fe.Error())
|
|
},
|
|
},
|
|
{
|
|
name: "Single template execute failed",
|
|
fileType: single,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title", ".Titles", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
fe := a.getFileError(err)
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
|
|
a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 14)
|
|
a.assertErrorMessage("\"layouts/single.html:5:14\": execute of template failed", fe.Error())
|
|
},
|
|
},
|
|
{
|
|
name: "Single template execute failed, long keyword",
|
|
fileType: single,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title", ".ThisIsAVeryLongTitle", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
fe := a.getFileError(err)
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
|
|
a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 14)
|
|
a.assertErrorMessage("\"layouts/single.html:5:14\": execute of template failed", fe.Error())
|
|
},
|
|
},
|
|
{
|
|
name: "Shortcode parse failed",
|
|
fileType: shortcode,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title }}", ".Title }", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
a.assertLineNumber(4, err)
|
|
},
|
|
},
|
|
{
|
|
name: "Shortcode execute failed",
|
|
fileType: shortcode,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title", ".Titles", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
fe := a.getFileError(err)
|
|
// Make sure that it contains both the content file and template
|
|
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())
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 7)
|
|
},
|
|
},
|
|
{
|
|
name: "Shortode does not exist",
|
|
fileType: yamlcontent,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, "{{< sc >}}", "{{< nono >}}", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
fe := a.getFileError(err)
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 7)
|
|
a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 10)
|
|
a.assertErrorMessage(`"content/myyaml.md:7:10": failed to extract shortcode: template for shortcode "nono" not found`, fe.Error())
|
|
},
|
|
},
|
|
{
|
|
name: "Invalid YAML front matter",
|
|
fileType: yamlcontent,
|
|
fileFixer: func(content string) string {
|
|
return `---
|
|
title: "My YAML Content"
|
|
foo bar
|
|
---
|
|
`
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
a.assertLineNumber(3, err)
|
|
},
|
|
},
|
|
{
|
|
name: "Invalid TOML front matter",
|
|
fileType: tomlcontent,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, "description = ", "description &", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
fe := a.getFileError(err)
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 6)
|
|
},
|
|
},
|
|
{
|
|
name: "Invalid JSON front matter",
|
|
fileType: jsoncontent,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, "\"description\":", "\"description\"", 1)
|
|
},
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
fe := a.getFileError(err)
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 3)
|
|
},
|
|
},
|
|
{
|
|
// See https://github.com/gohugoio/hugo/issues/5327
|
|
name: "Panic in template Execute",
|
|
fileType: single,
|
|
fileFixer: func(content string) string {
|
|
return strings.Replace(content, ".Title", ".Parent.Parent.Parent", 1)
|
|
},
|
|
|
|
assertErr: func(a testSiteBuildErrorAsserter, err error) {
|
|
a.c.Assert(err, qt.Not(qt.IsNil))
|
|
fe := a.getFileError(err)
|
|
a.c.Assert(fe.Position().LineNumber, qt.Equals, 5)
|
|
a.c.Assert(fe.Position().ColumnNumber, qt.Equals, 21)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
if test.name != "Base template parse failed" {
|
|
continue
|
|
}
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
c := qt.New(t)
|
|
errorAsserter := testSiteBuildErrorAsserter{
|
|
c: c,
|
|
name: test.name,
|
|
}
|
|
|
|
files := createTestFiles(test)
|
|
|
|
_, err := TestE(t, files)
|
|
|
|
if test.assertErr != nil {
|
|
test.assertErr(errorAsserter, err)
|
|
} else {
|
|
c.Assert(err, qt.IsNil)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Issue 9852
|
|
func TestErrorMinify(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
files := `
|
|
-- hugo.toml --
|
|
[minify]
|
|
minifyOutput = true
|
|
|
|
-- layouts/home.html --
|
|
<body>
|
|
<script>=;</script>
|
|
</body>
|
|
|
|
`
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
fe := herrors.UnwrapFileError(err)
|
|
b.Assert(fe, qt.IsNotNil)
|
|
b.Assert(fe.Position().LineNumber, qt.Equals, 2)
|
|
b.Assert(fe.Position().ColumnNumber, qt.Equals, 9)
|
|
b.Assert(fe.Error(), qt.Contains, "unexpected = in expression on line 2 and column 9")
|
|
b.Assert(filepath.ToSlash(fe.Position().Filename), qt.Contains, "hugo-transform-error")
|
|
// os.Remove is not needed in txtar tests as the filesystem is ephemeral.
|
|
// b.Assert(os.Remove(fe.Position().Filename), qt.IsNil)
|
|
}
|
|
|
|
func TestErrorNestedRender(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
files := `
|
|
-- hugo.toml --
|
|
-- content/_index.md --
|
|
---
|
|
title: "Home"
|
|
---
|
|
-- layouts/home.html --
|
|
line 1
|
|
line 2
|
|
1{{ .Render "myview" }}
|
|
-- layouts/myview.html --
|
|
line 1
|
|
12{{ partial "foo.html" . }}
|
|
line 4
|
|
line 5
|
|
-- layouts/_partials/foo.html --
|
|
line 1
|
|
line 2
|
|
123{{ .ThisDoesNotExist }}
|
|
line 4
|
|
`
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
|
b.Assert(errors, qt.HasLen, 4)
|
|
b.Assert(errors[0].Position().LineNumber, qt.Equals, 3)
|
|
b.Assert(errors[0].Position().ColumnNumber, qt.Equals, 4)
|
|
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/layouts/home.html:3:4": execute of template failed`))
|
|
b.Assert(errors[0].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "1{{ .Render \"myview\" }}"})
|
|
b.Assert(errors[2].Position().LineNumber, qt.Equals, 2)
|
|
b.Assert(errors[2].Position().ColumnNumber, qt.Equals, 5)
|
|
b.Assert(errors[2].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"})
|
|
|
|
b.Assert(errors[3].Position().LineNumber, qt.Equals, 3)
|
|
b.Assert(errors[3].Position().ColumnNumber, qt.Equals, 6)
|
|
b.Assert(errors[3].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"})
|
|
}
|
|
|
|
func TestErrorNestedShortcode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
files := `
|
|
-- hugo.toml --
|
|
-- content/_index.md --
|
|
---
|
|
title: "Home"
|
|
---
|
|
|
|
## Hello
|
|
{{< hello >}}
|
|
|
|
-- layouts/home.html --
|
|
line 1
|
|
line 2
|
|
{{ .Content }}
|
|
line 5
|
|
-- layouts/_shortcodes/hello.html --
|
|
line 1
|
|
12{{ partial "foo.html" . }}
|
|
line 4
|
|
line 5
|
|
-- layouts/_partials/foo.html --
|
|
line 1
|
|
line 2
|
|
123{{ .ThisDoesNotExist }}
|
|
line 4
|
|
`
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
|
|
|
b.Assert(errors, qt.HasLen, 4)
|
|
|
|
b.Assert(errors[1].Position().LineNumber, qt.Equals, 6)
|
|
b.Assert(errors[1].Position().ColumnNumber, qt.Equals, 1)
|
|
b.Assert(errors[1].ErrorContext().ChromaLexer, qt.Equals, "md")
|
|
b.Assert(errors[1].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":`))
|
|
b.Assert(errors[1].ErrorContext().Lines, qt.DeepEquals, []string{"", "## Hello", "{{< hello >}}", ""})
|
|
b.Assert(errors[2].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "12{{ partial \"foo.html\" . }}", "line 4", "line 5"})
|
|
b.Assert(errors[3].Position().LineNumber, qt.Equals, 3)
|
|
b.Assert(errors[3].Position().ColumnNumber, qt.Equals, 6)
|
|
b.Assert(errors[3].ErrorContext().Lines, qt.DeepEquals, []string{"line 1", "line 2", "123{{ .ThisDoesNotExist }}", "line 4"})
|
|
}
|
|
|
|
func TestErrorRenderHookHeading(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
files := `
|
|
-- hugo.toml --
|
|
-- content/_index.md --
|
|
---
|
|
title: "Home"
|
|
---
|
|
|
|
## Hello
|
|
|
|
-- layouts/home.html --
|
|
line 1
|
|
line 2
|
|
{{ .Content }}
|
|
line 5
|
|
-- layouts/_markup/render-heading.html --
|
|
line 1
|
|
12{{ .Levels }}
|
|
line 4
|
|
line 5
|
|
`
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
|
|
|
b.Assert(errors, qt.HasLen, 3)
|
|
b.Assert(errors[0].Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:2:5": "/layouts/_markup/render-heading.html:2:5": execute of template failed`))
|
|
}
|
|
|
|
func TestErrorRenderHookCodeblock(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
files := `
|
|
-- hugo.toml --
|
|
-- content/_index.md --
|
|
---
|
|
title: "Home"
|
|
---
|
|
|
|
## Hello
|
|
|
|
§§§ foo
|
|
bar
|
|
§§§
|
|
|
|
|
|
-- layouts/home.html --
|
|
line 1
|
|
line 2
|
|
{{ .Content }}
|
|
line 5
|
|
-- layouts/_markup/render-codeblock-foo.html --
|
|
line 1
|
|
12{{ .Foo }}
|
|
line 4
|
|
line 5
|
|
`
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
errors := herrors.UnwrapFileErrorsWithErrorContext(err)
|
|
|
|
b.Assert(errors, qt.HasLen, 3)
|
|
first := errors[0]
|
|
b.Assert(first.Error(), qt.Contains, filepath.FromSlash(`"/content/_index.md:7:1": "/layouts/_markup/render-codeblock-foo.html:2:5": execute of template failed`))
|
|
}
|
|
|
|
func TestErrorInBaseTemplate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
filesTemplate := `
|
|
-- hugo.toml --
|
|
-- content/_index.md --
|
|
---
|
|
title: "Home"
|
|
---
|
|
-- layouts/baseof.html --
|
|
line 1 base
|
|
line 2 base
|
|
{{ block "main" . }}empty{{ end }}
|
|
line 4 base
|
|
{{ block "toc" . }}empty{{ end }}
|
|
-- layouts/home.html --
|
|
{{ define "main" }}
|
|
line 2 index
|
|
line 3 index
|
|
line 4 index
|
|
{{ end }}
|
|
{{ define "toc" }}
|
|
TOC: {{ partial "toc.html" . }}
|
|
{{ end }}
|
|
-- layouts/_partials/toc.html --
|
|
toc line 1
|
|
toc line 2
|
|
toc line 3
|
|
toc line 4
|
|
|
|
|
|
`
|
|
|
|
t.Run("base template", func(t *testing.T) {
|
|
files := strings.Replace(filesTemplate, "line 4 base", "123{{ .ThisDoesNotExist \"abc\" }}", 1)
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
b.Assert(err.Error(), qt.Contains, `baseof.html:4:6`)
|
|
})
|
|
|
|
t.Run("home template", func(t *testing.T) {
|
|
files := strings.Replace(filesTemplate, "line 3 index", "1234{{ .ThisDoesNotExist \"abc\" }}", 1)
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
b.Assert(err.Error(), qt.Contains, `home.html:3:7"`)
|
|
})
|
|
|
|
t.Run("partial from define", func(t *testing.T) {
|
|
files := strings.Replace(filesTemplate, "toc line 2", "12345{{ .ThisDoesNotExist \"abc\" }}", 1)
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.IsNotNil)
|
|
b.Assert(err.Error(), qt.Contains, `toc.html:2:8"`)
|
|
})
|
|
}
|
|
|
|
// https://github.com/gohugoio/hugo/issues/5375
|
|
func TestSiteBuildTimeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var filesBuilder strings.Builder
|
|
filesBuilder.WriteString(`
|
|
-- hugo.toml --
|
|
timeout = 5
|
|
-- layouts/single.html --
|
|
{{ .WordCount }}
|
|
-- layouts/_shortcodes/c.html --
|
|
{{ range .Page.Site.RegularPages }}
|
|
{{ .WordCount }}
|
|
{{ end }}
|
|
`)
|
|
|
|
for i := 1; i < 100; i++ {
|
|
filesBuilder.WriteString(fmt.Sprintf(`
|
|
-- content/page%d.md --
|
|
---
|
|
title: "A page"
|
|
---
|
|
|
|
{{< c >}}
|
|
`, i))
|
|
}
|
|
|
|
_, err := TestE(t, filesBuilder.String())
|
|
|
|
qt.Assert(t, err, qt.Not(qt.IsNil))
|
|
qt.Assert(t, err.Error(), qt.Contains, "timed out rendering the page content")
|
|
}
|
|
|
|
func TestErrorTemplateRuntime(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
files := `
|
|
-- hugo.toml --
|
|
-- layouts/home.html --
|
|
Home.
|
|
{{ .ThisDoesNotExist }}
|
|
`
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.Not(qt.IsNil))
|
|
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`/layouts/home.html:2:3`))
|
|
b.Assert(err.Error(), qt.Contains, `can't evaluate field ThisDoesNotExist`)
|
|
}
|
|
|
|
func TestErrorFrontmatterYAMLSyntax(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
files := `
|
|
-- hugo.toml --
|
|
-- content/_index.md --
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
line1: 'value1'
|
|
x
|
|
line2: 'value2'
|
|
line3: 'value3'
|
|
---
|
|
`
|
|
|
|
b, err := TestE(t, files)
|
|
|
|
b.Assert(err, qt.Not(qt.IsNil))
|
|
b.Assert(err.Error(), qt.Contains, "[2:1] non-map value is specified")
|
|
fe := herrors.UnwrapFileError(err)
|
|
b.Assert(fe, qt.Not(qt.IsNil))
|
|
pos := fe.Position()
|
|
b.Assert(pos.Filename, qt.Contains, filepath.FromSlash("content/_index.md"))
|
|
b.Assert(fe.ErrorContext(), qt.Not(qt.IsNil))
|
|
b.Assert(pos.LineNumber, qt.Equals, 8)
|
|
b.Assert(pos.ColumnNumber, qt.Equals, 1)
|
|
}
|