diff --git a/hugolib/hugo_sites_build_failures_test.go b/hugolib/hugo_sites_build_failures_test.go new file mode 100644 index 00000000..b347490c --- /dev/null +++ b/hugolib/hugo_sites_build_failures_test.go @@ -0,0 +1,42 @@ +package hugolib + +import ( + "fmt" + "testing" +) + +// https://github.com/gohugoio/hugo/issues/4526 +func TestSiteBuildFailureInvalidPageMetadata(t *testing.T) { + t.Parallel() + + validContentFile := ` +--- +title = "This is good" +--- + +Some content. +` + + invalidContentFile := ` +--- +title = "PDF EPUB: Anne Bradstreet: Poems "The Prologue Summary And Analysis EBook Full Text " +--- + +Some content. +` + + var contentFiles []string + for i := 0; i <= 30; i++ { + name := fmt.Sprintf("valid%d.md", i) + contentFiles = append(contentFiles, name, validContentFile) + if i%5 == 0 { + name = fmt.Sprintf("invalid%d.md", i) + contentFiles = append(contentFiles, name, invalidContentFile) + } + } + + b := newTestSitesBuilder(t) + b.WithSimpleConfigFile().WithContent(contentFiles...) + b.CreateSites().BuildFail(BuildCfg{}) + +} diff --git a/hugolib/page_bundler.go b/hugolib/page_bundler.go index 2f6b4d09..bedfd58f 100644 --- a/hugolib/page_bundler.go +++ b/hugolib/page_bundler.go @@ -32,6 +32,8 @@ type siteContentProcessor struct { handleContent contentHandler + ctx context.Context + // The input file bundles. fileBundlesChan chan *bundleDir @@ -51,7 +53,28 @@ type siteContentProcessor struct { partialBuild bool } -func newSiteContentProcessor(baseDir string, partialBuild bool, s *Site) *siteContentProcessor { +func (s *siteContentProcessor) processBundle(b *bundleDir) { + select { + case s.fileBundlesChan <- b: + case <-s.ctx.Done(): + } +} + +func (s *siteContentProcessor) processSingle(fi *fileInfo) { + select { + case s.fileSinglesChan <- fi: + case <-s.ctx.Done(): + } +} + +func (s *siteContentProcessor) processAssets(assets []string) { + select { + case s.fileAssetsChan <- assets: + case <-s.ctx.Done(): + } +} + +func newSiteContentProcessor(ctx context.Context, baseDir string, partialBuild bool, s *Site) *siteContentProcessor { numWorkers := 12 if n := runtime.NumCPU() * 3; n > numWorkers { numWorkers = n @@ -60,6 +83,7 @@ func newSiteContentProcessor(baseDir string, partialBuild bool, s *Site) *siteCo numWorkers = int(math.Ceil(float64(numWorkers) / float64(len(s.owner.Sites)))) return &siteContentProcessor{ + ctx: ctx, partialBuild: partialBuild, baseDir: baseDir, site: s, @@ -80,7 +104,7 @@ func (s *siteContentProcessor) closeInput() { func (s *siteContentProcessor) process(ctx context.Context) error { g1, ctx := errgroup.WithContext(ctx) - g2, _ := errgroup.WithContext(ctx) + g2, ctx := errgroup.WithContext(ctx) // There can be only one of these per site. g1.Go(func() error { @@ -161,12 +185,14 @@ func (s *siteContentProcessor) process(ctx context.Context) error { }) } - if err := g2.Wait(); err != nil { - return err - } + err := g2.Wait() close(s.pagesChan) + if err != nil { + return err + } + if err := g1.Wait(); err != nil { return err } diff --git a/hugolib/site.go b/hugolib/site.go index 0ffe153e..c3e2d9cb 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -1281,19 +1281,19 @@ func (c *contentCaptureResultHandler) getContentProcessor(lang string) *siteCont func (c *contentCaptureResultHandler) handleSingles(fis ...*fileInfo) { for _, fi := range fis { proc := c.getContentProcessor(fi.Lang()) - proc.fileSinglesChan <- fi + proc.processSingle(fi) } } func (c *contentCaptureResultHandler) handleBundles(d *bundleDirs) { for _, b := range d.bundles { proc := c.getContentProcessor(b.fi.Lang()) - proc.fileBundlesChan <- b + proc.processBundle(b) } } func (c *contentCaptureResultHandler) handleCopyFiles(filenames ...string) { for _, proc := range c.contentProcessors { - proc.fileAssetsChan <- filenames + proc.processAssets(filenames) } } @@ -1309,7 +1309,7 @@ func (s *Site) readAndProcessContent(filenames ...string) error { var defaultContentProcessor *siteContentProcessor sites := s.owner.langSite() for k, v := range sites { - proc := newSiteContentProcessor(baseDir, len(filenames) > 0, v) + proc := newSiteContentProcessor(ctx, baseDir, len(filenames) > 0, v) contentProcessors[k] = proc if k == defaultContentLanguage { defaultContentProcessor = proc @@ -1337,15 +1337,18 @@ func (s *Site) readAndProcessContent(filenames ...string) error { c := newCapturer(s.Log, sourceSpec, handler, bundleMap, baseDir, filenames...) - if err := c.capture(); err != nil { - return err - } + err1 := c.capture() for _, proc := range contentProcessors { proc.closeInput() } - return g.Wait() + err2 := g.Wait() + + if err1 != nil { + return err1 + } + return err2 } func (s *Site) buildSiteMeta() (err error) { diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index 1f22e428..6f513b3b 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -272,12 +272,22 @@ func (s *sitesBuilder) CreateSites() *sitesBuilder { } func (s *sitesBuilder) Build(cfg BuildCfg) *sitesBuilder { + return s.build(cfg, false) +} + +func (s *sitesBuilder) BuildFail(cfg BuildCfg) *sitesBuilder { + return s.build(cfg, true) +} + +func (s *sitesBuilder) build(cfg BuildCfg, shouldFail bool) *sitesBuilder { if s.H == nil { s.CreateSites() } err := s.H.Build(cfg) - if err != nil { + if err != nil && !shouldFail { s.Fatalf("Build failed: %s", err) + } else if err == nil && shouldFail { + s.Fatalf("Expected error") } return s