hugo/hugolib/hugo_sites_test.go
Bjørn Erik Pedersen 36f2a1f676 Make the config loading testable
This is needed to verify  #2309, but it closes a big hole in Hugo's automated tests.

The loading of the config is now moved to `hugolib` and the same default settings are now used
in production and tests.

As Viper now uses Afero as its filesystem, we now can write fairly complete integration tests with ease.

See #2309
2016-09-06 18:32:17 +03:00

530 lines
15 KiB
Go

package hugolib
import (
"fmt"
"strings"
"testing"
"path/filepath"
"os"
"github.com/fsnotify/fsnotify"
"github.com/spf13/afero"
"github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func init() {
testCommonResetState()
jww.SetStdoutThreshold(jww.LevelError)
}
func testCommonResetState() {
hugofs.InitMemFs()
viper.Reset()
viper.SetFs(hugofs.Source())
loadDefaultSettings()
if err := hugofs.Source().Mkdir("content", 0755); err != nil {
panic("Content folder creation failed.")
}
}
func TestMultiSites(t *testing.T) {
sites := createMultiTestSites(t)
err := sites.Build(BuildCfg{})
if err != nil {
t.Fatalf("Failed to build sites: %s", err)
}
enSite := sites.Sites[0]
assert.Equal(t, "en", enSite.Language.Lang)
if len(enSite.Pages) != 3 {
t.Fatal("Expected 3 english pages")
}
assert.Len(t, enSite.Source.Files(), 11, "should have 11 source files")
assert.Len(t, enSite.AllPages, 6, "should have 6 total pages (including translations)")
doc1en := enSite.Pages[0]
permalink, err := doc1en.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink")
assert.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself")
doc2 := enSite.Pages[1]
permalink, err = doc2.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink")
doc3 := enSite.Pages[2]
permalink, err = doc3.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
// TODO(bep) multilingo. Check this case. This has url set in frontmatter, but we must split into lang folders
// The assertion below was missing the /en prefix.
assert.Equal(t, "/en/superbob", doc3.URL(), "invalid url, was specified on doc3 TODO(bep)")
assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
doc1fr := doc1en.Translations()[0]
permalink, err = doc1fr.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink")
assert.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation")
assert.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation")
assert.Equal(t, "fr", doc1fr.Language().Lang)
doc4 := enSite.AllPages[4]
permalink, err = doc4.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink")
assert.Len(t, doc4.Translations(), 0, "found translations for doc4")
doc5 := enSite.AllPages[5]
permalink, err = doc5.Permalink()
assert.NoError(t, err, "permalink call failed")
assert.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5", permalink, "invalid doc5 permalink")
// Taxonomies and their URLs
assert.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy")
tags := enSite.Taxonomies["tags"]
assert.Len(t, tags, 2, "should have 2 different tags")
assert.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1")
frSite := sites.Sites[1]
assert.Equal(t, "fr", frSite.Language.Lang)
assert.Len(t, frSite.Pages, 3, "should have 3 pages")
assert.Len(t, frSite.AllPages, 6, "should have 6 total pages (including translations)")
for _, frenchPage := range frSite.Pages {
assert.Equal(t, "fr", frenchPage.Lang())
}
// Check redirect to main language, French
languageRedirect := readDestination(t, "public/index.html")
require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect)
// Check sitemap(s)
sitemapIndex := readDestination(t, "public/sitemap.xml")
require.True(t, strings.Contains(sitemapIndex, "<loc>http:/example.com/blog/en/sitemap.xml</loc>"), sitemapIndex)
require.True(t, strings.Contains(sitemapIndex, "<loc>http:/example.com/blog/fr/sitemap.xml</loc>"), sitemapIndex)
sitemapEn := readDestination(t, "public/en/sitemap.xml")
sitemapFr := readDestination(t, "public/fr/sitemap.xml")
require.True(t, strings.Contains(sitemapEn, "http://example.com/blog/en/sect/doc2/"), sitemapEn)
require.True(t, strings.Contains(sitemapFr, "http://example.com/blog/fr/sect/doc1/"), sitemapFr)
}
func TestMultiSitesRebuild(t *testing.T) {
sites := createMultiTestSites(t)
cfg := BuildCfg{}
err := sites.Build(cfg)
if err != nil {
t.Fatalf("Failed to build sites: %s", err)
}
_, err = hugofs.Destination().Open("public/en/sect/doc2/index.html")
if err != nil {
t.Fatalf("Unable to locate file")
}
enSite := sites.Sites[0]
frSite := sites.Sites[1]
assert.Len(t, enSite.Pages, 3)
assert.Len(t, frSite.Pages, 3)
// Verify translations
docEn := readDestination(t, "public/en/sect/doc1-slug/index.html")
assert.True(t, strings.Contains(docEn, "Hello"), "No Hello")
docFr := readDestination(t, "public/fr/sect/doc1/index.html")
assert.True(t, strings.Contains(docFr, "Bonjour"), "No Bonjour")
for i, this := range []struct {
preFunc func(t *testing.T)
events []fsnotify.Event
assertFunc func(t *testing.T)
}{
// * Remove doc
// * Add docs existing languages
// (Add doc new language: TODO(bep) we should load config.toml as part of these so we can add languages).
// * Rename file
// * Change doc
// * Change a template
// * Change language file
{
nil,
[]fsnotify.Event{{Name: "content/sect/doc2.en.md", Op: fsnotify.Remove}},
func(t *testing.T) {
assert.Len(t, enSite.Pages, 2, "1 en removed")
// Check build stats
assert.Equal(t, 1, enSite.draftCount, "Draft")
assert.Equal(t, 1, enSite.futureCount, "Future")
assert.Equal(t, 1, enSite.expiredCount, "Expired")
assert.Equal(t, 0, frSite.draftCount, "Draft")
assert.Equal(t, 1, frSite.futureCount, "Future")
assert.Equal(t, 1, frSite.expiredCount, "Expired")
},
},
{
func(t *testing.T) {
writeNewContentFile(t, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
writeNewContentFile(t, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
writeNewContentFile(t, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
},
[]fsnotify.Event{
{Name: "content/new1.en.md", Op: fsnotify.Create},
{Name: "content/new2.en.md", Op: fsnotify.Create},
{Name: "content/new1.fr.md", Op: fsnotify.Create},
},
func(t *testing.T) {
assert.Len(t, enSite.Pages, 4)
assert.Len(t, enSite.AllPages, 8)
assert.Len(t, frSite.Pages, 4)
assert.Equal(t, "new_fr_1", frSite.Pages[3].Title)
assert.Equal(t, "new_en_2", enSite.Pages[0].Title)
assert.Equal(t, "new_en_1", enSite.Pages[1].Title)
rendered := readDestination(t, "public/en/new1/index.html")
assert.True(t, strings.Contains(rendered, "new_en_1"), rendered)
},
},
{
func(t *testing.T) {
p := "content/sect/doc1.en.md"
doc1 := readSource(t, p)
doc1 += "CHANGED"
writeSource(t, p, doc1)
},
[]fsnotify.Event{{Name: "content/sect/doc1.en.md", Op: fsnotify.Write}},
func(t *testing.T) {
assert.Len(t, enSite.Pages, 4)
doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
assert.True(t, strings.Contains(doc1, "CHANGED"), doc1)
},
},
// Rename a file
{
func(t *testing.T) {
if err := hugofs.Source().Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {
t.Fatalf("Rename failed: %s", err)
}
},
[]fsnotify.Event{
{Name: "content/new1renamed.en.md", Op: fsnotify.Rename},
{Name: "content/new1.en.md", Op: fsnotify.Rename},
},
func(t *testing.T) {
assert.Len(t, enSite.Pages, 4, "Rename")
assert.Equal(t, "new_en_1", enSite.Pages[1].Title)
rendered := readDestination(t, "public/en/new1renamed/index.html")
assert.True(t, strings.Contains(rendered, "new_en_1"), rendered)
}},
{
// Change a template
func(t *testing.T) {
template := "layouts/_default/single.html"
templateContent := readSource(t, template)
templateContent += "{{ print \"Template Changed\"}}"
writeSource(t, template, templateContent)
},
[]fsnotify.Event{{Name: "layouts/_default/single.html", Op: fsnotify.Write}},
func(t *testing.T) {
assert.Len(t, enSite.Pages, 4)
assert.Len(t, enSite.AllPages, 8)
assert.Len(t, frSite.Pages, 4)
doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
assert.True(t, strings.Contains(doc1, "Template Changed"), doc1)
},
},
{
// Change a language file
func(t *testing.T) {
languageFile := "i18n/fr.yaml"
langContent := readSource(t, languageFile)
langContent = strings.Replace(langContent, "Bonjour", "Salut", 1)
writeSource(t, languageFile, langContent)
},
[]fsnotify.Event{{Name: "i18n/fr.yaml", Op: fsnotify.Write}},
func(t *testing.T) {
assert.Len(t, enSite.Pages, 4)
assert.Len(t, enSite.AllPages, 8)
assert.Len(t, frSite.Pages, 4)
docEn := readDestination(t, "public/en/sect/doc1-slug/index.html")
assert.True(t, strings.Contains(docEn, "Hello"), "No Hello")
docFr := readDestination(t, "public/fr/sect/doc1/index.html")
assert.True(t, strings.Contains(docFr, "Salut"), "No Salut")
},
},
} {
if this.preFunc != nil {
this.preFunc(t)
}
err = sites.Rebuild(cfg, this.events...)
if err != nil {
t.Fatalf("[%d] Failed to rebuild sites: %s", i, err)
}
this.assertFunc(t)
}
}
func createMultiTestSites(t *testing.T) *HugoSites {
// General settings
hugofs.InitMemFs()
viper.Set("DefaultExtension", "html")
viper.Set("baseurl", "http://example.com/blog")
viper.Set("DisableSitemap", false)
viper.Set("DisableRSS", false)
viper.Set("RSSUri", "index.xml")
viper.Set("Taxonomies", map[string]string{"tag": "tags"})
viper.Set("Permalinks", map[string]string{"other": "/somewhere/else/:filename"})
// Add some layouts
if err := afero.WriteFile(hugofs.Source(),
filepath.Join("layouts", "_default/single.html"),
[]byte("Single: {{ .Title }}|{{ i18n \"hello\" }} {{ .Content }}"),
0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err)
}
if err := afero.WriteFile(hugofs.Source(),
filepath.Join("layouts", "_default/list.html"),
[]byte("List: {{ .Title }}"),
0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err)
}
if err := afero.WriteFile(hugofs.Source(),
filepath.Join("layouts", "index.html"),
[]byte("Home: {{ .Title }}|{{ .IsHome }}"),
0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err)
}
// Add some language files
if err := afero.WriteFile(hugofs.Source(),
filepath.Join("i18n", "en.yaml"),
[]byte(`
- id: hello
translation: "Hello"
`),
0755); err != nil {
t.Fatalf("Failed to write language file: %s", err)
}
if err := afero.WriteFile(hugofs.Source(),
filepath.Join("i18n", "fr.yaml"),
[]byte(`
- id: hello
translation: "Bonjour"
`),
0755); err != nil {
t.Fatalf("Failed to write language file: %s", err)
}
// Sources
sources := []source.ByteSource{
{filepath.FromSlash("sect/doc1.en.md"), []byte(`---
title: doc1
slug: doc1-slug
tags:
- tag1
publishdate: "2000-01-01"
---
# doc1
*some content*
NOTE: slug should be used as URL
`)},
{filepath.FromSlash("sect/doc1.fr.md"), []byte(`---
title: doc1
tags:
- tag1
- tag2
publishdate: "2000-01-04"
---
# doc1
*quelque contenu*
NOTE: should be in the 'en' Page's 'Translations' field.
NOTE: date is after "doc3"
`)},
{filepath.FromSlash("sect/doc2.en.md"), []byte(`---
title: doc2
publishdate: "2000-01-02"
---
# doc2
*some content*
NOTE: without slug, "doc2" should be used, without ".en" as URL
`)},
{filepath.FromSlash("sect/doc3.en.md"), []byte(`---
title: doc3
publishdate: "2000-01-03"
tags:
- tag2
url: /superbob
---
# doc3
*some content*
NOTE: third 'en' doc, should trigger pagination on home page.
`)},
{filepath.FromSlash("sect/doc4.md"), []byte(`---
title: doc4
tags:
- tag1
publishdate: "2000-01-05"
---
# doc4
*du contenu francophone*
NOTE: should use the DefaultContentLanguage and mark this doc as 'fr'.
NOTE: doesn't have any corresponding translation in 'en'
`)},
{filepath.FromSlash("other/doc5.fr.md"), []byte(`---
title: doc5
publishdate: "2000-01-06"
---
# doc5
*autre contenu francophone*
NOTE: should use the "permalinks" configuration with :filename
`)},
// Add some for the stats
{filepath.FromSlash("stats/expired.fr.md"), []byte(`---
title: expired
publishdate: "2000-01-06"
expiryDate: "2001-01-06"
---
# Expired
`)},
{filepath.FromSlash("stats/future.fr.md"), []byte(`---
title: future
publishdate: "2100-01-06"
---
# Future
`)},
{filepath.FromSlash("stats/expired.en.md"), []byte(`---
title: expired
publishdate: "2000-01-06"
expiryDate: "2001-01-06"
---
# Expired
`)},
{filepath.FromSlash("stats/future.en.md"), []byte(`---
title: future
publishdate: "2100-01-06"
---
# Future
`)},
{filepath.FromSlash("stats/draft.en.md"), []byte(`---
title: expired
publishdate: "2000-01-06"
draft: true
---
# Draft
`)},
}
// Multilingual settings
viper.Set("Multilingual", true)
en := NewLanguage("en")
viper.Set("DefaultContentLanguage", "fr")
viper.Set("paginate", "2")
languages := NewLanguages(en, NewLanguage("fr"))
// Hugo support using ByteSource's directly (for testing),
// but to make it more real, we write them to the mem file system.
for _, s := range sources {
if err := afero.WriteFile(hugofs.Source(), filepath.Join("content", s.Name), s.Content, 0755); err != nil {
t.Fatalf("Failed to write file: %s", err)
}
}
_, err := hugofs.Source().Open("content/other/doc5.fr.md")
if err != nil {
t.Fatalf("Unable to locate file")
}
sites, err := newHugoSitesFromLanguages(languages)
if err != nil {
t.Fatalf("Failed to create sites: %s", err)
}
if len(sites.Sites) != 2 {
t.Fatalf("Got %d sites", len(sites.Sites))
}
return sites
}
func writeSource(t *testing.T, filename, content string) {
if err := afero.WriteFile(hugofs.Source(), filepath.FromSlash(filename), []byte(content), 0755); err != nil {
t.Fatalf("Failed to write file: %s", err)
}
}
func readDestination(t *testing.T, filename string) string {
return readFileFromFs(t, hugofs.Destination(), filename)
}
func readSource(t *testing.T, filename string) string {
return readFileFromFs(t, hugofs.Source(), filename)
}
func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string {
filename = filepath.FromSlash(filename)
b, err := afero.ReadFile(fs, filename)
if err != nil {
// Print some debug info
root := strings.Split(filename, helpers.FilePathSeparator)[0]
afero.Walk(fs, root, func(path string, info os.FileInfo, err error) error {
if info != nil && !info.IsDir() {
fmt.Println(" ", path)
}
return nil
})
t.Fatalf("Failed to read file: %s", err)
}
return string(b)
}
const testPageTemplate = `---
title: "%s"
publishdate: "%s"
weight: %d
---
# Doc %s
`
func newTestPage(title, date string, weight int) string {
return fmt.Sprintf(testPageTemplate, title, date, weight, title)
}
func writeNewContentFile(t *testing.T, title, date, filename string, weight int) {
content := newTestPage(title, date, weight)
writeSource(t, filename, content)
}