github.com/neohugo/neohugo@v0.123.8/hugolib/hugo_smoke_test.go (about) 1 // Copyright 2024 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package hugolib 15 16 import ( 17 "fmt" 18 "math/rand" 19 "testing" 20 21 "github.com/bep/logg" 22 qt "github.com/frankban/quicktest" 23 ) 24 25 // The most basic build test. 26 func TestHello(t *testing.T) { 27 files := ` 28 -- hugo.toml -- 29 title = "Hello" 30 baseURL="https://example.org" 31 disableKinds = ["term", "taxonomy", "section", "page"] 32 -- content/p1.md -- 33 --- 34 title: Page 35 --- 36 -- layouts/index.html -- 37 Home: {{ .Title }} 38 ` 39 40 b := NewIntegrationTestBuilder( 41 IntegrationTestConfig{ 42 T: t, 43 TxtarString: files, 44 LogLevel: logg.LevelTrace, 45 }, 46 ).Build() 47 48 b.Assert(b.H.Log.LoggCount(logg.LevelWarn), qt.Equals, 0) 49 b.AssertFileContent("public/index.html", `Hello`) 50 } 51 52 func TestSmokeOutputFormats(t *testing.T) { 53 t.Parallel() 54 55 files := ` 56 -- hugo.toml -- 57 baseURL = "https://example.com/" 58 defaultContentLanguage = "en" 59 disableKinds = ["term", "taxonomy", "robotsTXT", "sitemap"] 60 [outputs] 61 home = ["html", "rss"] 62 section = ["html", "rss"] 63 page = ["html"] 64 -- content/p1.md -- 65 --- 66 title: Page 67 --- 68 Page. 69 70 -- layouts/_default/list.html -- 71 List: {{ .Title }}|{{ .RelPermalink}}|{{ range .OutputFormats }}{{ .Name }}: {{ .RelPermalink }}|{{ end }}$ 72 -- layouts/_default/list.xml -- 73 List xml: {{ .Title }}|{{ .RelPermalink}}|{{ range .OutputFormats }}{{ .Name }}: {{ .RelPermalink }}|{{ end }}$ 74 -- layouts/_default/single.html -- 75 Single: {{ .Title }}|{{ .RelPermalink}}|{{ range .OutputFormats }}{{ .Name }}: {{ .RelPermalink }}|{{ end }}$ 76 77 ` 78 79 b := Test(t, files) 80 81 b.AssertFileContent("public/index.html", `List: |/|html: /|rss: /index.xml|$`) 82 b.AssertFileContent("public/index.xml", `List xml: |/|html: /|rss: /index.xml|$`) 83 b.AssertFileContent("public/p1/index.html", `Single: Page|/p1/|html: /p1/|$`) 84 b.AssertFileExists("public/p1/index.xml", false) 85 } 86 87 func TestSmoke(t *testing.T) { 88 t.Parallel() 89 90 // Basic test cases. 91 // OK translations 92 // OK page collections 93 // OK next, prev in section 94 // OK GetPage 95 // OK Pagination 96 // OK RenderString with shortcode 97 // OK cascade 98 // OK site last mod, section last mod. 99 // OK main sections 100 // OK taxonomies 101 // OK GetTerms 102 // OK Resource page 103 // OK Resource txt 104 105 const files = ` 106 -- hugo.toml -- 107 baseURL = "https://example.com" 108 title = "Smoke Site" 109 rssLimit = 3 110 paginate = 1 111 defaultContentLanguage = "en" 112 defaultContentLanguageInSubdir = true 113 enableRobotsTXT = true 114 [taxonomies] 115 category = 'categories' 116 tag = 'tags' 117 118 [languages] 119 [languages.en] 120 weight = 1 121 title = "In English" 122 [languages.no] 123 weight = 2 124 title = "På norsk" 125 126 [params] 127 hugo = "Rules!" 128 129 [outputs] 130 home = ["html", "json", "rss"] 131 -- layouts/index.html -- 132 Home: {{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}|HTML 133 Resources: {{ range .Resources }}{{ .ResourceType }}|{{ .RelPermalink }}|{{ .MediaType }} - {{ end }}| 134 Site last mod: {{ site.Lastmod.Format "2006-02-01" }}| 135 Home last mod: {{ .Lastmod.Format "2006-02-01" }}| 136 Len Translations: {{ len .Translations }}| 137 Len home.RegularPagesRecursive: {{ len .RegularPagesRecursive }}| 138 RegularPagesRecursive: {{ range .RegularPagesRecursive }}{{ .RelPermalink }}|{{ end }}@ 139 Len site.RegularPages: {{ len site.RegularPages }}| 140 Len site.Pages: {{ len site.Pages }}| 141 Len site.AllPages: {{ len site.AllPages }}| 142 GetPage: {{ with .Site.GetPage "posts/p1" }}{{ .RelPermalink }}|{{ .Title }}{{ end }}| 143 RenderString with shortcode: {{ .RenderString "{{% hello %}}" }}| 144 Paginate: {{ .Paginator.PageNumber }}/{{ .Paginator.TotalPages }}| 145 -- layouts/index.json -- 146 Home:{{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}|JSON 147 -- layouts/_default/list.html -- 148 List: {{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}| 149 Resources: {{ range .Resources }}{{ .ResourceType }}|{{ .RelPermalink }}|{{ .MediaType }} - {{ end }} 150 Pages Length: {{ len .Pages }} 151 RegularPages Length: {{ len .RegularPages }} 152 RegularPagesRecursive Length: {{ len .RegularPagesRecursive }} 153 List last mod: {{ .Lastmod.Format "2006-02-01" }} 154 Background: {{ .Params.background }}| 155 Kind: {{ .Kind }} 156 Type: {{ .Type }} 157 Paginate: {{ .Paginator.PageNumber }}/{{ .Paginator.TotalPages }}| 158 -- layouts/_default/single.html -- 159 Single: {{ .Lang}}|{{ .Kind }}|{{ .RelPermalink }}|{{ .Title }}|{{ .Content }}|Len Resources: {{ len .Resources }}|Background: {{ .Params.background }}| 160 Resources: {{ range .Resources }}{{ .ResourceType }}|{{ .RelPermalink }}|{{ .MediaType }}|{{ .Params }} - {{ end }} 161 {{ $textResource := .Resources.GetMatch "**.txt" }} 162 {{ with $textResource }} 163 Icon: {{ .Params.icon }}| 164 {{ $textResourceFingerprinted := . | fingerprint }} 165 Icon fingerprinted: {{ with $textResourceFingerprinted }}{{ .Params.icon }}|{{ .RelPermalink }}{{ end }}| 166 {{ end }} 167 NextInSection: {{ with .NextInSection }}{{ .RelPermalink }}|{{ .Title }}{{ end }}| 168 PrevInSection: {{ with .PrevInSection }}{{ .RelPermalink }}|{{ .Title }}{{ end }}| 169 GetTerms: {{ range .GetTerms "tags" }}name: {{ .Name }}, title: {{ .Title }}|{{ end }} 170 -- layouts/shortcodes/hello.html -- 171 Hello. 172 -- content/_index.md -- 173 --- 174 title: Home in English 175 --- 176 Home Content. 177 -- content/_index.no.md -- 178 --- 179 title: Hjem 180 cascade: 181 - _target: 182 kind: page 183 path: /posts/** 184 background: post.jpg 185 - _target: 186 kind: term 187 background: term.jpg 188 --- 189 Hjem Innhold. 190 -- content/posts/f1.txt -- 191 posts f1 text. 192 -- content/posts/sub/f1.txt -- 193 posts sub f1 text. 194 -- content/posts/p1/index.md -- 195 +++ 196 title = "Post 1" 197 lastMod = "2001-01-01" 198 tags = ["tag1"] 199 [[resources]] 200 src = '**' 201 [resources.params] 202 icon = 'enicon' 203 +++ 204 Content 1. 205 -- content/posts/p1/index.no.md -- 206 +++ 207 title = "Post 1 no" 208 lastMod = "2002-02-02" 209 tags = ["tag1", "tag2"] 210 [[resources]] 211 src = '**' 212 [resources.params] 213 icon = 'noicon' 214 +++ 215 Content 1 no. 216 -- content/posts/_index.md -- 217 --- 218 title: Posts 219 --- 220 -- content/posts/p1/f1.txt -- 221 posts p1 f1 text. 222 -- content/posts/p1/sub/ps1.md -- 223 --- 224 title: Post Sub 1 225 --- 226 Content Sub 1. 227 -- content/posts/p2.md -- 228 --- 229 title: Post 2 230 tags: ["tag1", "tag3"] 231 --- 232 Content 2. 233 -- content/posts/p2.no.md -- 234 --- 235 title: Post 2 No 236 --- 237 Content 2 No. 238 -- content/tags/_index.md -- 239 --- 240 title: Tags 241 --- 242 Content Tags. 243 -- content/tags/tag1/_index.md -- 244 --- 245 title: Tag 1 246 --- 247 Content Tag 1. 248 249 250 ` 251 252 b := NewIntegrationTestBuilder(IntegrationTestConfig{ 253 T: t, 254 TxtarString: files, 255 NeedsOsFS: true, 256 // Verbose: true, 257 // LogLevel: logg.LevelTrace, 258 }).Build() 259 260 b.AssertFileContent("public/en/index.html", 261 "Home: en|home|/en/|Home in English|<p>Home Content.</p>\n|HTML", 262 "Site last mod: 2001-01-01", 263 "Home last mod: 2001-01-01", 264 "Translations: 1|", 265 "Len home.RegularPagesRecursive: 2|", 266 "Len site.RegularPages: 2|", 267 "Len site.Pages: 8|", 268 "Len site.AllPages: 16|", 269 "GetPage: /en/posts/p1/|Post 1|", 270 "RenderString with shortcode: Hello.|", 271 "Paginate: 1/2|", 272 ) 273 b.AssertFileContent("public/en/page/2/index.html", "Paginate: 2/2|") 274 275 b.AssertFileContent("public/no/index.html", 276 "Home: no|home|/no/|Hjem|<p>Hjem Innhold.</p>\n|HTML", 277 "Site last mod: 2002-02-02", 278 "Home last mod: 2002-02-02", 279 "Translations: 1", 280 "GetPage: /no/posts/p1/|Post 1 no|", 281 ) 282 283 b.AssertFileContent("public/en/index.json", "Home:en|home|/en/|Home in English|<p>Home Content.</p>\n|JSON") 284 b.AssertFileContent("public/no/index.json", "Home:no|home|/no/|Hjem|<p>Hjem Innhold.</p>\n|JSON") 285 286 b.AssertFileContent("public/en/posts/p1/index.html", 287 "Single: en|page|/en/posts/p1/|Post 1|<p>Content 1.</p>\n|Len Resources: 2|", 288 "Resources: text|/en/posts/p1/f1.txt|text/plain|map[icon:enicon] - page||application/octet-stream|map[draft:false iscjklanguage:false title:Post Sub 1] -", 289 "Icon: enicon", 290 "Icon fingerprinted: enicon|/en/posts/p1/f1.e5746577af5cbfc4f34c558051b7955a9a5a795a84f1c6ab0609cb3473a924cb.txt|", 291 "NextInSection: |\nPrevInSection: /en/posts/p2/|Post 2|", 292 "GetTerms: name: tag1, title: Tag 1|", 293 ) 294 295 b.AssertFileContent("public/no/posts/p1/index.html", 296 "Resources: 1", 297 "Resources: text|/en/posts/p1/f1.txt|text/plain|map[icon:noicon] -", 298 "Icon: noicon", 299 "Icon fingerprinted: noicon|/en/posts/p1/f1.e5746577af5cbfc4f34c558051b7955a9a5a795a84f1c6ab0609cb3473a924cb.txt|", 300 "Background: post.jpg", 301 "NextInSection: |\nPrevInSection: /no/posts/p2/|Post 2 No|", 302 ) 303 304 b.AssertFileContent("public/en/posts/index.html", 305 "List: en|section|/en/posts/|Posts||Len Resources: 2|", 306 "Resources: text|/en/posts/f1.txt|text/plain - text|/en/posts/sub/f1.txt|text/plain -", 307 "List last mod: 2001-01-01", 308 ) 309 310 b.AssertFileContent("public/no/posts/index.html", 311 "List last mod: 2002-02-02", 312 ) 313 314 b.AssertFileContent("public/en/posts/p2/index.html", "Single: en|page|/en/posts/p2/|Post 2|<p>Content 2.</p>\n|", 315 "|Len Resources: 0", 316 "GetTerms: name: tag1, title: Tag 1|name: tag3, title: Tag3|", 317 ) 318 b.AssertFileContent("public/no/posts/p2/index.html", "Single: no|page|/no/posts/p2/|Post 2 No|<p>Content 2 No.</p>\n|") 319 320 b.AssertFileContent("public/no/categories/index.html", 321 "Kind: taxonomy", 322 "Type: categories", 323 ) 324 b.AssertFileContent("public/no/tags/index.html", 325 "Kind: taxonomy", 326 "Type: tags", 327 ) 328 329 b.AssertFileContent("public/no/tags/tag1/index.html", 330 "Background: term.jpg", 331 "Kind: term", 332 "Type: tags", 333 "Paginate: 1/1|", 334 ) 335 336 b.AssertFileContent("public/en/tags/tag1/index.html", 337 "Kind: term", 338 "Type: tags", 339 "Paginate: 1/2|", 340 ) 341 } 342 343 // Basic tests that verifies that the different file systems work as expected. 344 func TestSmokeFilesystems(t *testing.T) { 345 t.Parallel() 346 347 files := ` 348 -- hugo.toml -- 349 baseURL = "https://example.com" 350 defaultContentLanguage = "en" 351 defaultContentLanguageInSubdir = true 352 [languages] 353 [languages.en] 354 title = "In English" 355 [languages.nn] 356 title = "På nynorsk" 357 [module] 358 [[module.mounts]] 359 source = "i18n" 360 target = "i18n" 361 [[module.mounts]] 362 source = "data" 363 target = "data" 364 [[module.mounts]] 365 source = "content/en" 366 target = "content" 367 lang = "en" 368 [[module.mounts]] 369 source = "content/nn" 370 target = "content" 371 lang = "nn" 372 [[module.imports]] 373 path = "mytheme" 374 -- layouts/index.html -- 375 i18n s1: {{ i18n "s1" }}| 376 i18n s2: {{ i18n "s2" }}| 377 data s1: {{ site.Data.d1.s1 }}| 378 data s2: {{ site.Data.d1.s2 }}| 379 title: {{ .Title }}| 380 -- themes/mytheme/hugo.toml -- 381 [[module.mounts]] 382 source = "i18n" 383 target = "i18n" 384 [[module.mounts]] 385 source = "data" 386 target = "data" 387 # i18n files both project and theme. 388 -- i18n/en.toml -- 389 [s1] 390 other = 's1project' 391 -- i18n/nn.toml -- 392 [s1] 393 other = 's1prosjekt' 394 -- themes/mytheme/i18n/en.toml -- 395 [s1] 396 other = 's1theme' 397 [s2] 398 other = 's2theme' 399 # data files both project and theme. 400 -- data/d1.yaml -- 401 s1: s1project 402 -- themes/mytheme/data/d1.yaml -- 403 s1: s1theme 404 s2: s2theme 405 # Content 406 -- content/en/_index.md -- 407 --- 408 title: "Home" 409 --- 410 -- content/nn/_index.md -- 411 --- 412 title: "Heim" 413 --- 414 415 ` 416 b := Test(t, files) 417 418 b.AssertFileContent("public/en/index.html", 419 "i18n s1: s1project", "i18n s2: s2theme", 420 "data s1: s1project", "data s2: s2theme", 421 "title: Home", 422 ) 423 424 b.AssertFileContent("public/nn/index.html", 425 "i18n s1: s1prosjekt", "i18n s2: s2theme", 426 "data s1: s1project", "data s2: s2theme", 427 "title: Heim", 428 ) 429 } 430 431 // https://github.com/golang/go/issues/30286 432 func TestDataRace(t *testing.T) { 433 const page = ` 434 --- 435 title: "The Page" 436 outputs: ["HTML", "JSON"] 437 --- 438 439 The content. 440 441 442 ` 443 444 b := newTestSitesBuilder(t).WithSimpleConfigFile() 445 for i := 1; i <= 50; i++ { 446 b.WithContent(fmt.Sprintf("blog/page%d.md", i), page) 447 } 448 449 b.WithContent("_index.md", ` 450 --- 451 title: "The Home" 452 outputs: ["HTML", "JSON", "CSV", "RSS"] 453 --- 454 455 The content. 456 457 458 `) 459 460 commonTemplate := `{{ .Data.Pages }}` 461 462 b.WithTemplatesAdded("_default/single.html", "HTML Single: "+commonTemplate) 463 b.WithTemplatesAdded("_default/list.html", "HTML List: "+commonTemplate) 464 465 b.CreateSites().Build(BuildCfg{}) 466 } 467 468 // This is just a test to verify that BenchmarkBaseline is working as intended. 469 func TestBenchmarkBaseline(t *testing.T) { 470 cfg := IntegrationTestConfig{ 471 T: t, 472 TxtarString: benchmarkBaselineFiles(true), 473 } 474 b := NewIntegrationTestBuilder(cfg).Build() 475 476 b.Assert(len(b.H.Sites), qt.Equals, 4) 477 b.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 161) 478 b.Assert(len(b.H.Sites[0].Pages()), qt.Equals, 197) 479 b.Assert(len(b.H.Sites[2].RegularPages()), qt.Equals, 158) 480 b.Assert(len(b.H.Sites[2].Pages()), qt.Equals, 194) 481 } 482 483 func BenchmarkBaseline(b *testing.B) { 484 cfg := IntegrationTestConfig{ 485 T: b, 486 TxtarString: benchmarkBaselineFiles(false), 487 } 488 builders := make([]*IntegrationTestBuilder, b.N) 489 490 for i := range builders { 491 builders[i] = NewIntegrationTestBuilder(cfg) 492 } 493 494 b.ResetTimer() 495 for i := 0; i < b.N; i++ { 496 builders[i].Build() 497 } 498 } 499 500 func benchmarkBaselineFiles(leafBundles bool) string { 501 rnd := rand.New(rand.NewSource(32)) 502 503 files := ` 504 -- config.toml -- 505 baseURL = "https://example.com" 506 defaultContentLanguage = 'en' 507 508 [module] 509 [[module.mounts]] 510 source = 'content/en' 511 target = 'content/en' 512 lang = 'en' 513 [[module.mounts]] 514 source = 'content/nn' 515 target = 'content/nn' 516 lang = 'nn' 517 [[module.mounts]] 518 source = 'content/no' 519 target = 'content/no' 520 lang = 'no' 521 [[module.mounts]] 522 source = 'content/sv' 523 target = 'content/sv' 524 lang = 'sv' 525 [[module.mounts]] 526 source = 'layouts' 527 target = 'layouts' 528 529 [languages] 530 [languages.en] 531 title = "English" 532 weight = 1 533 [languages.nn] 534 title = "Nynorsk" 535 weight = 2 536 [languages.no] 537 title = "Norsk" 538 weight = 3 539 [languages.sv] 540 title = "Svenska" 541 weight = 4 542 -- layouts/_default/list.html -- 543 {{ .Title }} 544 {{ .Content }} 545 -- layouts/_default/single.html -- 546 {{ .Title }} 547 {{ .Content }} 548 -- layouts/shortcodes/myshort.html -- 549 {{ .Inner }} 550 ` 551 552 contentTemplate := ` 553 --- 554 title: "Page %d" 555 date: "2018-01-01" 556 weight: %d 557 --- 558 559 ## Heading 1 560 561 Duis nisi reprehenderit nisi cupidatat cillum aliquip ea id eu esse commodo et. 562 563 ## Heading 2 564 565 Aliqua labore enim et sint anim amet excepteur ea dolore. 566 567 {{< myshort >}} 568 Hello, World! 569 {{< /myshort >}} 570 571 Aliqua labore enim et sint anim amet excepteur ea dolore. 572 573 574 ` 575 576 for _, lang := range []string{"en", "nn", "no", "sv"} { 577 files += fmt.Sprintf("\n-- content/%s/_index.md --\n"+contentTemplate, lang, 1, 1, 1) 578 for i, root := range []string{"", "foo", "bar", "baz"} { 579 for j, section := range []string{"posts", "posts/funny", "posts/science", "posts/politics", "posts/world", "posts/technology", "posts/world/news", "posts/world/news/europe"} { 580 n := i + j + 1 581 files += fmt.Sprintf("\n-- content/%s/%s/%s/_index.md --\n"+contentTemplate, lang, root, section, n, n, n) 582 for k := 1; k < rnd.Intn(30)+1; k++ { 583 n := n + k 584 ns := fmt.Sprintf("%d", n) 585 if leafBundles { 586 ns = fmt.Sprintf("%d/index", n) 587 } 588 file := fmt.Sprintf("\n-- content/%s/%s/%s/p%s.md --\n"+contentTemplate, lang, root, section, ns, n, n) 589 files += file 590 } 591 } 592 } 593 } 594 595 return files 596 }