summaryrefslogtreecommitdiffstats
path: root/hugolib
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2016-07-28 09:30:58 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2016-09-06 18:32:16 +0300
commit708bc78770a0b0361908f6404f57264c53252a95 (patch)
tree9b7e3a05b1e83a768bfa0dd96b61b07dd7917cfd /hugolib
parentf023dfd7636f73b11c94e86a05c6273941d52c58 (diff)
Optimize the multilanguage build process
Work In Progress! This commit makes a rework of the build and rebuild process to better suit a multi-site setup. This also includes a complete overhaul of the site tests. Previous these were a messy mix that were testing just small parts of the build chain, some of it testing code-paths not even used in "real life". Now all tests that depends on a built site follows the same and real production code path. See #2309 Closes #2211 Closes #477 Closes #1744
Diffstat (limited to 'hugolib')
-rw-r--r--hugolib/embedded_shortcodes_test.go7
-rw-r--r--hugolib/handler_test.go36
-rw-r--r--hugolib/hugo_sites.go251
-rw-r--r--hugolib/hugo_sites_test.go522
-rw-r--r--hugolib/i18n.go3
-rw-r--r--hugolib/menu_test.go51
-rw-r--r--hugolib/multilingual.go16
-rw-r--r--hugolib/node.go16
-rw-r--r--hugolib/page.go1
-rw-r--r--hugolib/page_permalink_test.go3
-rw-r--r--hugolib/page_test.go16
-rw-r--r--hugolib/pagination_test.go24
-rw-r--r--hugolib/public/404.html0
-rw-r--r--hugolib/public/index.html0
-rw-r--r--hugolib/public/rss11
-rw-r--r--hugolib/public/sitemap.xml8
-rw-r--r--hugolib/robotstxt_test.go27
-rw-r--r--hugolib/rss_test.go20
-rw-r--r--hugolib/shortcode_test.go36
-rw-r--r--hugolib/site.go376
-rw-r--r--hugolib/site_show_plan_test.go66
-rw-r--r--hugolib/site_test.go537
-rw-r--r--hugolib/site_url_test.go39
-rw-r--r--hugolib/siteinfo_test.go64
-rw-r--r--hugolib/sitemap_test.go29
-rw-r--r--hugolib/taxonomy_test.go3
-rw-r--r--hugolib/translations.go7
27 files changed, 1211 insertions, 958 deletions
diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go
index 18f807fbf..e668ff4c8 100644
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -56,8 +56,8 @@ func doTestShortcodeCrossrefs(t *testing.T, relative bool) {
templ := tpl.New()
p, _ := pageFromString(simplePageWithURL, path)
p.Node.Site = &SiteInfo{
- AllPages: &(Pages{p}),
- BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(baseURL)),
+ rawAllPages: &(Pages{p}),
+ BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(baseURL)),
}
output, err := HandleShortcodes(in, p, templ)
@@ -72,8 +72,7 @@ func doTestShortcodeCrossrefs(t *testing.T, relative bool) {
}
func TestShortcodeHighlight(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
+ testCommonResetState()
if !helpers.HasPygments() {
t.Skip("Skip test as Pygments is not installed")
diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go
index a84d528cb..fce29df44 100644
--- a/hugolib/handler_test.go
+++ b/hugolib/handler_test.go
@@ -25,8 +25,7 @@ import (
)
func TestDefaultHandler(t *testing.T) {
- viper.Reset()
- defer viper.Reset()
+ testCommonResetState()
hugofs.InitMemFs()
sources := []source.ByteSource{
@@ -45,33 +44,30 @@ func TestDefaultHandler(t *testing.T) {
viper.Set("verbose", true)
s := &Site{
- Source: &source.InMemorySource{ByteSource: sources},
- targets: targetList{page: &target.PagePub{UglyURLs: true}},
- Lang: NewLanguage("en"),
+ Source: &source.InMemorySource{ByteSource: sources},
+ targets: targetList{page: &target.PagePub{UglyURLs: true, PublishDir: "public"}},
+ Language: NewLanguage("en"),
}
- s.initializeSiteInfo()
-
- s.prepTemplates(
+ if err := buildAndRenderSite(s,
"_default/single.html", "{{.Content}}",
"head", "<head><script src=\"script.js\"></script></head>",
- "head_abs", "<head><script src=\"/script.js\"></script></head>")
-
- // From site_test.go
- createAndRenderPages(t, s)
+ "head_abs", "<head><script src=\"/script.js\"></script></head>"); err != nil {
+ t.Fatalf("Failed to render site: %s", err)
+ }
tests := []struct {
doc string
expected string
}{
- {filepath.FromSlash("sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
- {filepath.FromSlash("sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"},
- {filepath.FromSlash("sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"},
- {filepath.FromSlash("sect/doc3/img1.png"), string([]byte("‰PNG  ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚"))},
- {filepath.FromSlash("sect/img2.gif"), string([]byte("GIF89a��€��ÿÿÿ���,�������D�;"))},
- {filepath.FromSlash("sect/img2.spf"), string([]byte("****FAKE-FILETYPE****"))},
- {filepath.FromSlash("doc7.html"), "<html><body>doc7 content</body></html>"},
- {filepath.FromSlash("sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
+ {filepath.FromSlash("public/sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
+ {filepath.FromSlash("public/sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"},
+ {filepath.FromSlash("public/sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"},
+ {filepath.FromSlash("public/sect/doc3/img1.png"), string([]byte("‰PNG  ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚"))},
+ {filepath.FromSlash("public/sect/img2.gif"), string([]byte("GIF89a��€��ÿÿÿ���,�������D�;"))},
+ {filepath.FromSlash("public/sect/img2.spf"), string([]byte("****FAKE-FILETYPE****"))},
+ {filepath.FromSlash("public/doc7.html"), "<html><body>doc7 content</body></html>"},
+ {filepath.FromSlash("public/sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"},
}
for _, test := range tests {
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index dd8d3e5d2..2dd1bb9be 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -14,42 +14,119 @@
package hugolib
import (
+ "errors"
+ "strings"
"time"
- "github.com/fsnotify/fsnotify"
+ "github.com/spf13/viper"
+ "github.com/fsnotify/fsnotify"
+ "github.com/spf13/hugo/source"
+ "github.com/spf13/hugo/tpl"
jww "github.com/spf13/jwalterweatherman"
)
// HugoSites represents the sites to build. Each site represents a language.
-type HugoSites []*Site
+type HugoSites struct {
+ Sites []*Site
+
+ Multilingual *Multilingual
+}
+
+func NewHugoSites(sites ...*Site) (*HugoSites, error) {
+ languages := make(Languages, len(sites))
+ for i, s := range sites {
+ if s.Language == nil {
+ return nil, errors.New("Missing language for site")
+ }
+ languages[i] = s.Language
+ }
+ defaultLang := viper.GetString("DefaultContentLanguage")
+ if defaultLang == "" {
+ defaultLang = "en"
+ }
+ langConfig := &Multilingual{Languages: languages, DefaultLang: NewLanguage(defaultLang)}
+
+ return &HugoSites{Multilingual: langConfig, Sites: sites}, nil
+}
// Reset resets the sites, making it ready for a full rebuild.
// TODO(bep) multilingo
func (h HugoSites) Reset() {
- for i, s := range h {
- h[i] = s.Reset()
+ for i, s := range h.Sites {
+ h.Sites[i] = s.Reset()
}
}
+type BuildCfg struct {
+ // Whether we are in watch (server) mode
+ Watching bool
+ // Print build stats at the end of a build
+ PrintStats bool
+ // Skip rendering. Useful for testing.
+ skipRender bool
+ // Use this to add templates to use for rendering.
+ // Useful for testing.
+ withTemplate func(templ tpl.Template) error
+}
+
// Build builds all sites.
-func (h HugoSites) Build(watching, printStats bool) error {
+func (h HugoSites) Build(config BuildCfg) error {
+
+ if h.Sites == nil || len(h.Sites) == 0 {
+ return errors.New("No site(s) to build")
+ }
+
t0 := time.Now()
- for _, site := range h {
- t1 := time.Now()
+ // We should probably refactor the Site and pull up most of the logic from there to here,
+ // but that seems like a daunting task.
+ // So for now, if there are more than one site (language),
+ // we pre-process the first one, then configure all the sites based on that.
+ firstSite := h.Sites[0]
+
+ for _, s := range h.Sites {
+ // TODO(bep) ml
+ s.Multilingual = h.Multilingual
+ s.RunMode.Watching = config.Watching
+ }
+
+ if err := firstSite.PreProcess(config); err != nil {
+ return err
+ }
- site.RunMode.Watching = watching
+ h.setupTranslations(firstSite)
- if err := site.Build(); err != nil {
+ if len(h.Sites) > 1 {
+ // Initialize the rest
+ for _, site := range h.Sites[1:] {
+ site.Tmpl = firstSite.Tmpl
+ site.initializeSiteInfo()
+ }
+ }
+
+ for _, s := range h.Sites {
+
+ if err := s.PostProcess(); err != nil {
return err
}
- if printStats {
- site.Stats(t1)
+
+ if !config.skipRender {
+ if err := s.Render(); err != nil {
+ return err
+ }
+
+ }
+
+ if config.PrintStats {
+ s.Stats()
}
+
+ // TODO(bep) ml lang in site.Info?
+ // TODO(bep) ml Page sorting?
}
- if printStats {
+ if config.PrintStats {
jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
}
@@ -58,25 +135,159 @@ func (h HugoSites) Build(watching, printStats bool) error {
}
// Rebuild rebuilds all sites.
-func (h HugoSites) Rebuild(events []fsnotify.Event, printStats bool) error {
+func (h HugoSites) Rebuild(config BuildCfg, events ...fsnotify.Event) error {
t0 := time.Now()
- for _, site := range h {
- t1 := time.Now()
+ firstSite := h.Sites[0]
- if err := site.ReBuild(events); err != nil {
- return err
+ for _, s := range h.Sites {
+ s.resetBuildState()
+ }
+
+ sourceChanged, err := firstSite.ReBuild(events)
+
+ if err != nil {
+ return err
+ }
+
+ // Assign pages to sites per translation.
+ h.setupTranslations(firstSite)
+
+ for _, s := range h.Sites {
+
+ if sourceChanged {
+ if err := s.PostProcess(); err != nil {
+ return err
+ }
}
- if printStats {
- site.Stats(t1)
+ if !config.skipRender {
+ if err := s.Render(); err != nil {
+ return err
+ }
+ }
+
+ if config.PrintStats {
+ s.Stats()
}
}
- if printStats {
+ if config.PrintStats {
jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
}
return nil
}
+
+func (s *HugoSites) setupTranslations(master *Site) {
+
+ for _, p := range master.rawAllPages {
+ if p.Lang() == "" {
+ panic("Page language missing: " + p.Title)
+ }
+
+ shouldBuild := p.shouldBuild()
+
+ for i, site := range s.Sites {
+ if strings.HasPrefix(site.Language.Lang, p.Lang()) {
+ site.updateBuildStats(p)
+ if shouldBuild {
+ site.Pages = append(site.Pages, p)
+ p.Site = &site.Info
+ }
+ }
+
+ if !shouldBuild {
+ continue
+ }
+
+ if i == 0 {
+ site.AllPages = append(site.AllPages, p)
+ }
+ }
+
+ for i := 1; i < len(s.Sites); i++ {
+ s.Sites[i].AllPages = s.Sites[0].AllPages
+ }
+ }
+
+ if len(s.Sites) > 1 {
+ pages := s.Sites[0].AllPages
+ allTranslations := pagesToTranslationsMap(s.Multilingual, pages)
+ assignTranslationsToPages(allTranslations, pages)
+ }
+}
+
+func (s *Site) updateBuildStats(page *Page) {
+ if page.IsDraft() {
+ s.draftCount++
+ }
+
+ if page.IsFuture() {
+ s.futureCount++
+ }
+
+ if page.IsExpired() {
+ s.expiredCount++
+ }
+}
+
+// Convenience func used in tests to build a single site/language excluding render phase.
+func buildSiteSkipRender(s *Site, additionalTemplates ...string) error {
+ return doBuildSite(s, false, additionalTemplates...)
+}
+
+// Convenience func used in tests to build a single site/language including render phase.
+func buildAndRenderSite(s *Site, additionalTemplates ...string) error {
+ return doBuildSite(s, true, additionalTemplates...)
+}
+
+// Convenience func used in tests to build a single site/language.
+func doBuildSite(s *Site, render bool, additionalTemplates ...string) error {
+ sites, err := NewHugoSites(s)
+ if err != nil {
+ return err
+ }
+
+ addTemplates := func(templ tpl.Template) error {
+ for i := 0; i < len(additionalTemplates); i += 2 {
+ err := templ.AddTemplate(additionalTemplates[i], additionalTemplates[i+1])
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
+ config := BuildCfg{skipRender: !render, withTemplate: addTemplates}
+ return sites.Build(config)
+}
+
+// Convenience func used in tests.
+func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages Languages) (*HugoSites, error) {
+ if len(languages) == 0 {
+ panic("Must provide at least one language")
+ }
+ first := &Site{
+ Source: &source.InMemorySource{ByteSource: input},
+ Language: languages[0],
+ }
+ if len(languages) == 1 {
+ return NewHugoSites(first)
+ }
+
+ sites := make([]*Site, len(languages))
+ sites[0] = first
+ for i := 1; i < len(languages); i++ {
+ sites[i] = &Site{Language: languages[i]}
+ }
+
+ return NewHugoSites(sites...)
+
+}
+
+// Convenience func used in tests.
+func newHugoSitesFromLanguages(languages Languages) (*HugoSites, error) {
+ return newHugoSitesFromSourceAndLanguages(nil, languages)
+}
diff --git a/hugolib/hugo_sites_test.go b/hugolib/hugo_sites_test.go
new file mode 100644
index 000000000..fc4801115
--- /dev/null
+++ b/hugolib/hugo_sites_test.go
@@ -0,0 +1,522 @@
+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"
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/assert"
+
+ jww "github.com/spf13/jwalterweatherman"
+)
+
+func init() {
+ testCommonResetState()
+ jww.SetStdoutThreshold(jww.LevelError)
+
+}
+
+func testCommonResetState() {
+ hugofs.InitMemFs()
+ viper.Reset()
+ viper.Set("ContentDir", "content")
+ viper.Set("DataDir", "data")
+ viper.Set("I18nDir", "i18n")
+ viper.Set("themesDir", "themes")
+ viper.Set("LayoutDir", "layouts")
+ viper.Set("PublishDir", "public")
+ viper.Set("RSSUri", "rss")
+
+ 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{skipRender: true})
+
+ 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(), 6, "should have 6 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())
+ }
+
+}
+
+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