commands_test.go (11921B)
1 // Copyright 2019 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 commands
15
16 import (
17 "fmt"
18 "io/ioutil"
19 "os"
20 "path/filepath"
21 "testing"
22
23 "github.com/gohugoio/hugo/config"
24
25 "github.com/spf13/afero"
26
27 "github.com/gohugoio/hugo/hugofs"
28
29 "github.com/gohugoio/hugo/common/types"
30
31 "github.com/spf13/cobra"
32
33 qt "github.com/frankban/quicktest"
34 )
35
36 func TestExecute(t *testing.T) {
37 c := qt.New(t)
38
39 createSite := func(c *qt.C) string {
40 dir := createSimpleTestSite(t, testSiteConfig{})
41 return dir
42 }
43
44 c.Run("hugo", func(c *qt.C) {
45 dir := createSite(c)
46 resp := Execute([]string{"-s=" + dir})
47 c.Assert(resp.Err, qt.IsNil)
48 result := resp.Result
49 c.Assert(len(result.Sites) == 1, qt.Equals, true)
50 c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true)
51 c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramproduction")
52 })
53
54 c.Run("hugo, set environment", func(c *qt.C) {
55 dir := createSite(c)
56 resp := Execute([]string{"-s=" + dir, "-e=staging"})
57 c.Assert(resp.Err, qt.IsNil)
58 result := resp.Result
59 c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramstaging")
60 })
61
62 c.Run("convert toJSON", func(c *qt.C) {
63 dir := createSite(c)
64 output := filepath.Join(dir, "myjson")
65 resp := Execute([]string{"convert", "toJSON", "-s=" + dir, "-e=staging", "-o=" + output})
66 c.Assert(resp.Err, qt.IsNil)
67 converted := readFileFrom(c, filepath.Join(output, "content", "p1.md"))
68 c.Assert(converted, qt.Equals, "{\n \"title\": \"P1\",\n \"weight\": 1\n}\n\nContent\n\n", qt.Commentf(converted))
69 })
70
71 c.Run("config, set environment", func(c *qt.C) {
72 dir := createSite(c)
73 out, err := captureStdout(func() error {
74 resp := Execute([]string{"config", "-s=" + dir, "-e=staging"})
75 return resp.Err
76 })
77 c.Assert(err, qt.IsNil)
78 c.Assert(out, qt.Contains, "params = map[myparam:paramstaging]", qt.Commentf(out))
79 })
80
81 c.Run("deploy, environment set", func(c *qt.C) {
82 dir := createSite(c)
83 resp := Execute([]string{"deploy", "-s=" + dir, "-e=staging", "--target=mydeployment", "--dryRun"})
84 c.Assert(resp.Err, qt.Not(qt.IsNil))
85 c.Assert(resp.Err.Error(), qt.Contains, `no driver registered for "hugocloud"`)
86 })
87
88 c.Run("list", func(c *qt.C) {
89 dir := createSite(c)
90 out, err := captureStdout(func() error {
91 resp := Execute([]string{"list", "all", "-s=" + dir, "-e=staging"})
92 return resp.Err
93 })
94 c.Assert(err, qt.IsNil)
95 c.Assert(out, qt.Contains, "p1.md")
96 })
97
98 c.Run("new theme", func(c *qt.C) {
99 dir := createSite(c)
100 themesDir := filepath.Join(dir, "mythemes")
101 resp := Execute([]string{"new", "theme", "mytheme", "-s=" + dir, "-e=staging", "--themesDir=" + themesDir})
102 c.Assert(resp.Err, qt.IsNil)
103 themeTOML := readFileFrom(c, filepath.Join(themesDir, "mytheme", "theme.toml"))
104 c.Assert(themeTOML, qt.Contains, "name = \"Mytheme\"")
105 })
106
107 c.Run("new site", func(c *qt.C) {
108 dir := createSite(c)
109 siteDir := filepath.Join(dir, "mysite")
110 resp := Execute([]string{"new", "site", siteDir, "-e=staging"})
111 c.Assert(resp.Err, qt.IsNil)
112 config := readFileFrom(c, filepath.Join(siteDir, "config.toml"))
113 c.Assert(config, qt.Contains, "baseURL = 'http://example.org/'")
114 checkNewSiteInited(c, siteDir)
115 })
116 }
117
118 func checkNewSiteInited(c *qt.C, basepath string) {
119 paths := []string{
120 filepath.Join(basepath, "layouts"),
121 filepath.Join(basepath, "content"),
122 filepath.Join(basepath, "archetypes"),
123 filepath.Join(basepath, "static"),
124 filepath.Join(basepath, "data"),
125 filepath.Join(basepath, "config.toml"),
126 }
127
128 for _, path := range paths {
129 _, err := os.Stat(path)
130 c.Assert(err, qt.IsNil)
131 }
132 }
133
134 func readFileFrom(c *qt.C, filename string) string {
135 c.Helper()
136 filename = filepath.Clean(filename)
137 b, err := afero.ReadFile(hugofs.Os, filename)
138 c.Assert(err, qt.IsNil)
139 return string(b)
140 }
141
142 func TestFlags(t *testing.T) {
143 c := qt.New(t)
144
145 noOpRunE := func(cmd *cobra.Command, args []string) error {
146 return nil
147 }
148
149 tests := []struct {
150 name string
151 args []string
152 check func(c *qt.C, cmd *serverCmd)
153 }{
154 {
155 // https://github.com/gohugoio/hugo/issues/7642
156 name: "ignoreVendorPaths",
157 args: []string{"server", "--ignoreVendorPaths=github.com/**"},
158 check: func(c *qt.C, cmd *serverCmd) {
159 cfg := config.NewWithTestDefaults()
160 cmd.flagsToConfig(cfg)
161 c.Assert(cfg.Get("ignoreVendorPaths"), qt.Equals, "github.com/**")
162 },
163 },
164 {
165 name: "Persistent flags",
166 args: []string{
167 "server",
168 "--config=myconfig.toml",
169 "--configDir=myconfigdir",
170 "--contentDir=mycontent",
171 "--disableKinds=page,home",
172 "--environment=testing",
173 "--configDir=myconfigdir",
174 "--layoutDir=mylayouts",
175 "--theme=mytheme",
176 "--gc",
177 "--themesDir=mythemes",
178 "--cleanDestinationDir",
179 "--navigateToChanged",
180 "--disableLiveReload",
181 "--noHTTPCache",
182 "--printI18nWarnings",
183 "--destination=/tmp/mydestination",
184 "-b=https://example.com/b/",
185 "--port=1366",
186 "--renderToDisk",
187 "--source=mysource",
188 "--printPathWarnings",
189 "--printUnusedTemplates",
190 },
191 check: func(c *qt.C, sc *serverCmd) {
192 c.Assert(sc, qt.Not(qt.IsNil))
193 c.Assert(sc.navigateToChanged, qt.Equals, true)
194 c.Assert(sc.disableLiveReload, qt.Equals, true)
195 c.Assert(sc.noHTTPCache, qt.Equals, true)
196 c.Assert(sc.renderToDisk, qt.Equals, true)
197 c.Assert(sc.serverPort, qt.Equals, 1366)
198 c.Assert(sc.environment, qt.Equals, "testing")
199
200 cfg := config.NewWithTestDefaults()
201 sc.flagsToConfig(cfg)
202 c.Assert(cfg.GetString("publishDir"), qt.Equals, "/tmp/mydestination")
203 c.Assert(cfg.GetString("contentDir"), qt.Equals, "mycontent")
204 c.Assert(cfg.GetString("layoutDir"), qt.Equals, "mylayouts")
205 c.Assert(cfg.GetStringSlice("theme"), qt.DeepEquals, []string{"mytheme"})
206 c.Assert(cfg.GetString("themesDir"), qt.Equals, "mythemes")
207 c.Assert(cfg.GetString("baseURL"), qt.Equals, "https://example.com/b/")
208
209 c.Assert(cfg.Get("disableKinds"), qt.DeepEquals, []string{"page", "home"})
210
211 c.Assert(cfg.GetBool("gc"), qt.Equals, true)
212
213 // The flag is named printPathWarnings
214 c.Assert(cfg.GetBool("logPathWarnings"), qt.Equals, true)
215
216 // The flag is named printI18nWarnings
217 c.Assert(cfg.GetBool("logI18nWarnings"), qt.Equals, true)
218 },
219 },
220 }
221
222 for _, test := range tests {
223 c.Run(test.name, func(c *qt.C) {
224 b := newCommandsBuilder()
225 root := b.addAll().build()
226
227 for _, cmd := range b.commands {
228 if cmd.getCommand() == nil {
229 continue
230 }
231 // We are only intereseted in the flag handling here.
232 cmd.getCommand().RunE = noOpRunE
233 }
234 rootCmd := root.getCommand()
235 rootCmd.SetArgs(test.args)
236 c.Assert(rootCmd.Execute(), qt.IsNil)
237 test.check(c, b.commands[0].(*serverCmd))
238 })
239 }
240 }
241
242 func TestCommandsExecute(t *testing.T) {
243 c := qt.New(t)
244
245 dir := createSimpleTestSite(t, testSiteConfig{})
246 dirOut := t.TempDir()
247
248 sourceFlag := fmt.Sprintf("-s=%s", dir)
249
250 tests := []struct {
251 commands []string
252 flags []string
253 expectErrToContain string
254 }{
255 // TODO(bep) permission issue on my OSX? "operation not permitted" {[]string{"check", "ulimit"}, nil, false},
256 {[]string{"env"}, nil, ""},
257 {[]string{"version"}, nil, ""},
258 // no args = hugo build
259 {nil, []string{sourceFlag}, ""},
260 {nil, []string{sourceFlag, "--renderToMemory"}, ""},
261 {[]string{"completion", "bash"}, nil, ""},
262 {[]string{"completion", "fish"}, nil, ""},
263 {[]string{"completion", "powershell"}, nil, ""},
264 {[]string{"completion", "zsh"}, nil, ""},
265 {[]string{"config"}, []string{sourceFlag}, ""},
266 {[]string{"convert", "toTOML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "toml")}, ""},
267 {[]string{"convert", "toYAML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "yaml")}, ""},
268 {[]string{"convert", "toJSON"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "json")}, ""},
269 {[]string{"gen", "chromastyles"}, []string{"--style=manni"}, ""},
270 {[]string{"gen", "doc"}, []string{"--dir=" + filepath.Join(dirOut, "doc")}, ""},
271 {[]string{"gen", "man"}, []string{"--dir=" + filepath.Join(dirOut, "man")}, ""},
272 {[]string{"list", "drafts"}, []string{sourceFlag}, ""},
273 {[]string{"list", "expired"}, []string{sourceFlag}, ""},
274 {[]string{"list", "future"}, []string{sourceFlag}, ""},
275 {[]string{"new", "new-page.md"}, []string{sourceFlag}, ""},
276 {[]string{"new", "site", filepath.Join(dirOut, "new-site")}, nil, ""},
277 {[]string{"unknowncommand"}, nil, "unknown command"},
278 // TODO(bep) cli refactor fix https://github.com/gohugoio/hugo/issues/4450
279 //{[]string{"new", "theme", filepath.Join(dirOut, "new-theme")}, nil,false},
280 }
281
282 for _, test := range tests {
283 name := "hugo"
284 if len(test.commands) > 0 {
285 name = test.commands[0]
286 }
287 c.Run(name, func(c *qt.C) {
288 b := newCommandsBuilder().addAll().build()
289 hugoCmd := b.getCommand()
290 test.flags = append(test.flags, "--quiet")
291 hugoCmd.SetArgs(append(test.commands, test.flags...))
292
293 // TODO(bep) capture output and add some simple asserts
294 // TODO(bep) misspelled subcommands does not return an error. We should investigate this
295 // but before that, check for "Error: unknown command".
296
297 _, err := hugoCmd.ExecuteC()
298 if test.expectErrToContain != "" {
299 c.Assert(err, qt.Not(qt.IsNil))
300 c.Assert(err.Error(), qt.Contains, test.expectErrToContain)
301 } else {
302 c.Assert(err, qt.IsNil)
303 }
304
305 // Assert that we have not left any development debug artifacts in
306 // the code.
307 if b.c != nil {
308 _, ok := b.c.publishDirFs.(types.DevMarker)
309 c.Assert(ok, qt.Equals, false)
310 }
311 })
312
313 }
314 }
315
316 type testSiteConfig struct {
317 configTOML string
318 contentDir string
319 }
320
321 func createSimpleTestSite(t testing.TB, cfg testSiteConfig) string {
322 dir := t.TempDir()
323
324 cfgStr := `
325
326 baseURL = "https://example.org"
327 title = "Hugo Commands"
328
329
330 `
331
332 contentDir := "content"
333
334 if cfg.configTOML != "" {
335 cfgStr = cfg.configTOML
336 }
337 if cfg.contentDir != "" {
338 contentDir = cfg.contentDir
339 }
340
341 os.MkdirAll(filepath.Join(dir, "public"), 0777)
342
343 // Just the basic. These are for CLI tests, not site testing.
344 writeFile(t, filepath.Join(dir, "config.toml"), cfgStr)
345 writeFile(t, filepath.Join(dir, "config", "staging", "params.toml"), `myparam="paramstaging"`)
346 writeFile(t, filepath.Join(dir, "config", "staging", "deployment.toml"), `
347 [[targets]]
348 name = "mydeployment"
349 URL = "hugocloud://hugotestbucket"
350 `)
351
352 writeFile(t, filepath.Join(dir, "config", "testing", "params.toml"), `myparam="paramtesting"`)
353 writeFile(t, filepath.Join(dir, "config", "production", "params.toml"), `myparam="paramproduction"`)
354
355 writeFile(t, filepath.Join(dir, "static", "myfile.txt"), `Hello World!`)
356
357 writeFile(t, filepath.Join(dir, contentDir, "p1.md"), `
358 ---
359 title: "P1"
360 weight: 1
361 ---
362
363 Content
364
365 `)
366
367 writeFile(t, filepath.Join(dir, "layouts", "_default", "single.html"), `
368
369 Single: {{ .Title }}
370
371 `)
372
373 writeFile(t, filepath.Join(dir, "layouts", "_default", "list.html"), `
374
375 List: {{ .Title }}
376 Environment: {{ hugo.Environment }}
377
378 For issue 9788:
379 {{ $foo :="abc" | resources.FromString "foo.css" | minify | resources.PostProcess }}
380 PostProcess: {{ $foo.RelPermalink }}
381
382 `)
383
384 return dir
385 }
386
387 func writeFile(t testing.TB, filename, content string) {
388 must(t, os.MkdirAll(filepath.Dir(filename), os.FileMode(0755)))
389 must(t, ioutil.WriteFile(filename, []byte(content), os.FileMode(0755)))
390 }
391
392 func must(t testing.TB, err error) {
393 if err != nil {
394 t.Fatal(err)
395 }
396 }