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 }