shithub: hugo

Download patch

ref: e8d6ca9531d19e4e898c57d77d2fd627ea38ade0
parent: 4d32f2fa8969f368b088dc9bcedb45f2c986cb27
author: Bjørn Erik Pedersen <[email protected]>
date: Tue Apr 10 15:16:09 EDT 2018

commands: Add CLI tests

See #4598

--- a/commands/benchmark.go
+++ b/commands/benchmark.go
@@ -45,8 +45,6 @@
 	cmd.Flags().StringVar(&c.memProfileFile, "memprofile", "", "path/filename for the memory profile file")
 	cmd.Flags().IntVarP(&c.benchmarkTimes, "count", "n", 13, "number of times to build the site")
 
-	cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)")
-
 	cmd.RunE = c.benchmark
 
 	return c
@@ -56,6 +54,7 @@
 	cfgInit := func(c *commandeer) error {
 		return nil
 	}
+
 	comm, err := initializeConfig(false, &c.hugoBuilderCommon, c, cfgInit)
 	if err != nil {
 		return err
--- a/commands/commandeer.go
+++ b/commands/commandeer.go
@@ -40,7 +40,7 @@
 type commandeer struct {
 	*deps.DepsCfg
 
-	h             *hugoBuilderCommon
+	h    *hugoBuilderCommon
 	ftch flagsToConfigHandler
 
 	pathSpec    *helpers.PathSpec
@@ -109,7 +109,7 @@
 
 	c := &commandeer{
 		h:                h,
-		ftch:    f,
+		ftch:             f,
 		doWithCommandeer: doWithCommandeer,
 		visitedURLs:      types.NewEvictingStringQueue(10),
 		debounce:         rebuildDebouncer,
--- /dev/null
+++ b/commands/commands_test.go
@@ -1,0 +1,133 @@
+// Copyright 2018 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package commands
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestCommands(t *testing.T) {
+
+	assert := require.New(t)
+
+	dir, err := createSimpleTestSite(t)
+	assert.NoError(err)
+
+	dirOut, err := ioutil.TempDir("", "hugo-cli-out")
+	assert.NoError(err)
+
+	defer func() {
+		os.RemoveAll(dir)
+		os.RemoveAll(dirOut)
+	}()
+
+	sourceFlag := fmt.Sprintf("-s=%s", dir)
+
+	tests := []struct {
+		commands []string
+		flags    []string
+	}{
+		{[]string{"check", "ulimit"}, nil},
+		{[]string{"env"}, nil},
+		{[]string{"version"}, nil},
+		// no args = hugo build
+		{nil, []string{sourceFlag}},
+		// TODO(bep) cli refactor remove the HugoSites global and enable the below
+		//{nil, []string{sourceFlag, "--renderToMemory"}},
+		{[]string{"benchmark"}, []string{sourceFlag, "-n=1"}},
+		{[]string{"convert", "toTOML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "toml")}},
+		{[]string{"convert", "toYAML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "yaml")}},
+		{[]string{"convert", "toJSON"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "json")}},
+		{[]string{"gen", "autocomplete"}, []string{"--completionfile=" + filepath.Join(dirOut, "autocomplete.txt")}},
+		{[]string{"gen", "chromastyles"}, []string{"--style=manni"}},
+		{[]string{"gen", "doc"}, []string{"--dir=" + filepath.Join(dirOut, "doc")}},
+		{[]string{"gen", "man"}, []string{"--dir=" + filepath.Join(dirOut, "man")}},
+		{[]string{"list", "drafts"}, []string{sourceFlag}},
+		{[]string{"list", "expired"}, []string{sourceFlag}},
+		{[]string{"list", "future"}, []string{sourceFlag}},
+		{[]string{"new", "new-page.md"}, []string{sourceFlag}},
+		{[]string{"new", "site", filepath.Join(dirOut, "new-site")}, nil},
+		// TODO(bep) cli refactor fix https://github.com/gohugoio/hugo/issues/4450
+		//{[]string{"new", "theme", filepath.Join(dirOut, "new-theme")}, nil},
+	}
+
+	for _, test := range tests {
+
+		hugoCmd := newHugoCompleteCmd()
+		test.flags = append(test.flags, "--quiet")
+		hugoCmd.SetArgs(append(test.commands, test.flags...))
+
+		// TODO(bep) capture output and add some simple asserts
+
+		assert.NoError(hugoCmd.Execute(), fmt.Sprintf("%v", test.commands))
+	}
+
+}
+
+func createSimpleTestSite(t *testing.T) (string, error) {
+	d, e := ioutil.TempDir("", "hugo-cli")
+	if e != nil {
+		return "", e
+	}
+
+	// Just the basic. These are for CLI tests, not site testing.
+	writeFile(t, filepath.Join(d, "config.toml"), `
+
+baseURL = "https://example.org"
+title = "Hugo Commands"
+
+`)
+
+	writeFile(t, filepath.Join(d, "content", "p1.md"), `
+---
+title: "P1"
+weight: 1
+---
+
+Content
+
+`)
+
+	writeFile(t, filepath.Join(d, "layouts", "_default", "single.html"), `
+
+Single: {{ .Title }}
+
+`)
+
+	writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), `
+
+List: {{ .Title }}
+
+`)
+
+	return d, nil
+
+}
+
+func writeFile(t *testing.T, filename, content string) {
+	must(t, os.MkdirAll(filepath.Dir(filename), os.FileMode(0755)))
+	must(t, ioutil.WriteFile(filename, []byte(content), os.FileMode(0755)))
+}
+
+func must(t *testing.T, err error) {
+	if err != nil {
+		t.Fatal(err)
+	}
+}
--- a/commands/convert.go
+++ b/commands/convert.go
@@ -32,11 +32,10 @@
 	_ cmder = (*convertCmd)(nil)
 )
 
-// TODO(bep) cli refactor
-var outputDir string
-var unsafe bool
-
 type convertCmd struct {
+	outputDir string
+	unsafe    bool
+
 	*baseBuilderCmd
 }
 
@@ -43,6 +42,7 @@
 func newConvertCmd() *convertCmd {
 	cc := &convertCmd{}
 
+	// TODO(bep) cli refactor this is more than it had
 	cc.baseBuilderCmd = newBuilderCmd(&cobra.Command{
 		Use:   "convert",
 		Short: "Convert your content to different formats",
@@ -82,10 +82,9 @@
 		},
 	)
 
-	// TODO(bep) cli refactor
-	//	cmd.PersistentFlags().StringVarP(&outputDir, "output", "o", "", "filesystem path to write files to")
-	//	cmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
-	//	cmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "enable less safe operations, please backup first")
+	cc.cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
+	cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
+	cc.cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
 	cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
 
 	return cc
@@ -92,7 +91,7 @@
 }
 
 func (cc *convertCmd) convertContents(mark rune) error {
-	if outputDir == "" && !unsafe {
+	if cc.outputDir == "" && !cc.unsafe {
 		return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
 	}
 
@@ -114,7 +113,7 @@
 
 	site.Log.FEEDBACK.Println("processing", len(site.AllPages), "content files")
 	for _, p := range site.AllPages {
-		if err := convertAndSavePage(p, site, mark); err != nil {
+		if err := cc.convertAndSavePage(p, site, mark); err != nil {
 			return err
 		}
 	}
@@ -121,10 +120,10 @@
 	return nil
 }
 
-func convertAndSavePage(p *hugolib.Page, site *hugolib.Site, mark rune) error {
+func (cc *convertCmd) convertAndSavePage(p *hugolib.Page, site *hugolib.Site, mark rune) error {
 	// The resources are not in .Site.AllPages.
 	for _, r := range p.Resources.ByType("page") {
-		if err := convertAndSavePage(r.(*hugolib.Page), site, mark); err != nil {
+		if err := cc.convertAndSavePage(r.(*hugolib.Page), site, mark); err != nil {
 			return err
 		}
 	}
@@ -182,8 +181,8 @@
 	}
 
 	newFilename := p.Filename()
-	if outputDir != "" {
-		newFilename = filepath.Join(outputDir, p.Dir(), newPage.LogicalName())
+	if cc.outputDir != "" {
+		newFilename = filepath.Join(cc.outputDir, p.Dir(), newPage.LogicalName())
 	}
 
 	if err = newPage.SaveSourceAs(newFilename); err != nil {
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -141,6 +141,9 @@
 	// Set bash-completion
 	_ = cc.cmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{})
 
+	cc.cmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
+	cc.cmd.SilenceUsage = true
+
 	return cc
 }
 
@@ -191,6 +194,7 @@
 	cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
 	cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
 	cmd.Flags().BoolP("i18n-warnings", "", false, "print missing translations")
+	cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)")
 
 	cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
 
@@ -214,23 +218,11 @@
 	return nil
 }
 
-var (
-	hugoCommand = newHugoCmd()
-
-	// HugoCmd is Hugo's root command.
-	// Every other command attached to HugoCmd is a child command to it.
-	HugoCmd = hugoCommand.getCommand()
-)
-
 // Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
 func Execute() {
-	HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags)
+	hugoCmd := newHugoCompleteCmd()
 
-	HugoCmd.SilenceUsage = true
-
-	addAllCommands()
-
-	if c, err := HugoCmd.ExecuteC(); err != nil {
+	if c, err := hugoCmd.ExecuteC(); err != nil {
 		if isUserError(err) {
 			c.Println("")
 			c.Println(c.UsageString())
@@ -240,9 +232,16 @@
 	}
 }
 
+func newHugoCompleteCmd() *cobra.Command {
+	hugoCmd := newHugoCmd().getCommand()
+	addAllCommands(hugoCmd)
+	return hugoCmd
+}
+
 // addAllCommands adds child commands to the root command HugoCmd.
-func addAllCommands() {
+func addAllCommands(root *cobra.Command) {
 	addCommands(
+		root,
 		newServerCmd(),
 		newVersionCmd(),
 		newEnvCmd(),
@@ -257,9 +256,9 @@
 	)
 }
 
-func addCommands(commands ...cmder) {
+func addCommands(root *cobra.Command, commands ...cmder) {
 	for _, command := range commands {
-		HugoCmd.AddCommand(command.getCommand())
+		root.AddCommand(command.getCommand())
 	}
 }
 
--- a/commands/list.go
+++ b/commands/list.go
@@ -151,7 +151,7 @@
 	)
 
 	// TODO(bep) cli refactor
-	//	cc.cmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
+	cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
 	cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
 
 	return cc
--- a/commands/new.go
+++ b/commands/new.go
@@ -30,6 +30,7 @@
 var _ cmder = (*newCmd)(nil)
 
 type newCmd struct {
+	hugoBuilderCommon
 	contentEditor string
 	contentType   string
 
@@ -37,8 +38,8 @@
 }
 
 func newNewCmd() *newCmd {
-	ccmd := &newCmd{baseCmd: newBaseCmd(nil)}
-	cmd := &cobra.Command{
+	cc := &newCmd{}
+	cc.baseCmd = newBaseCmd(&cobra.Command{
 		Use:   "new [path]",
 		Short: "Create new content for your site",
 		Long: `Create a new content file and automatically set the date and title.
@@ -48,21 +49,19 @@
 
 If archetypes are provided in your theme or site, they will be used.`,
 
-		RunE: ccmd.newContent,
-	}
+		RunE: cc.newContent,
+	})
 
-	cmd.Flags().StringVarP(&ccmd.contentType, "kind", "k", "", "content type to create")
+	cc.cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
 	// TODO(bep) cli refactor
-	//	cmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from")
-	cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
-	cmd.Flags().StringVar(&ccmd.contentEditor, "editor", "", "edit new content with this editor, if provided")
+	cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
+	cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
+	cc.cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
 
-	cmd.AddCommand(newNewSiteCmd().getCommand())
-	cmd.AddCommand(newNewThemeCmd().getCommand())
+	cc.cmd.AddCommand(newNewSiteCmd().getCommand())
+	cc.cmd.AddCommand(newNewThemeCmd().getCommand())
 
-	ccmd.cmd = cmd
-
-	return ccmd
+	return cc
 }
 
 func (n *newCmd) newContent(cmd *cobra.Command, args []string) error {
@@ -73,7 +72,7 @@
 		return nil
 	}
 
-	c, err := initializeConfig(false, nil, n, cfgInit)
+	c, err := initializeConfig(false, &n.hugoBuilderCommon, n, cfgInit)
 
 	if err != nil {
 		return err
--- a/commands/new_theme.go
+++ b/commands/new_theme.go
@@ -32,10 +32,11 @@
 
 type newThemeCmd struct {
 	*baseCmd
+	hugoBuilderCommon
 }
 
 func newNewThemeCmd() *newThemeCmd {
-	ccmd := &newThemeCmd{newBaseCmd(nil)}
+	ccmd := &newThemeCmd{baseCmd: newBaseCmd(nil)}
 
 	cmd := &cobra.Command{
 		Use:   "theme [name]",
@@ -53,7 +54,7 @@
 }
 
 func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
-	c, err := initializeConfig(false, nil, n, nil)
+	c, err := initializeConfig(false, &n.hugoBuilderCommon, n, nil)
 
 	if err != nil {
 		return err
--- a/commands/server.go
+++ b/commands/server.go
@@ -1,4 +1,4 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
+// Copyright 2018 The Hugo Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -56,24 +56,6 @@
 	*baseCmd
 }
 
-func (cc *serverCmd) handleFlags(cmd *cobra.Command) {
-	// TODO(bep) cli refactor fields vs strings
-	cc.cmd.Flags().IntVarP(&cc.serverPort, "port", "p", 1313, "port on which the server will listen")
-	cc.cmd.Flags().IntVar(&cc.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)")
-	cc.cmd.Flags().StringVarP(&cc.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
-	cc.cmd.Flags().BoolVarP(&cc.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed")
-	cc.cmd.Flags().BoolVar(&cc.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
-	cc.cmd.Flags().BoolVarP(&cc.serverAppend, "appendPort", "", true, "append port to baseURL")
-	cc.cmd.Flags().BoolVar(&cc.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
-	cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
-	cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)")
-	cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
-
-	cc.cmd.Flags().String("memstats", "", "log memory usage to this file")
-	cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
-
-}
-
 func newServerCmd() *serverCmd {
 	cc := &serverCmd{}
 
@@ -95,6 +77,21 @@
 of a second, you will be able to save and see your changes nearly instantly.`,
 		RunE: cc.server,
 	})
+
+	// TODO(bep) cli refactor fields vs strings
+	cc.cmd.Flags().IntVarP(&cc.serverPort, "port", "p", 1313, "port on which the server will listen")
+	cc.cmd.Flags().IntVar(&cc.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)")
+	cc.cmd.Flags().StringVarP(&cc.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
+	cc.cmd.Flags().BoolVarP(&cc.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed")
+	cc.cmd.Flags().BoolVar(&cc.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
+	cc.cmd.Flags().BoolVarP(&cc.serverAppend, "appendPort", "", true, "append port to baseURL")
+	cc.cmd.Flags().BoolVar(&cc.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
+	cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
+	cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)")
+	cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
+
+	cc.cmd.Flags().String("memstats", "", "log memory usage to this file")
+	cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".")
 
 	return cc
 }
--- a/main.go
+++ b/main.go
@@ -23,6 +23,7 @@
 )
 
 func main() {
+
 	runtime.GOMAXPROCS(runtime.NumCPU())
 	commands.Execute()