github.com/kat-co/cmd@v0.0.0-20140616103059-5da365f9d57e/supercommand_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cmd_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "strings" 10 11 gitjujutesting "github.com/juju/testing" 12 "launchpad.net/gnuflag" 13 gc "launchpad.net/gocheck" 14 15 "github.com/juju/cmd" 16 "github.com/juju/cmd/cmdtesting" 17 ) 18 19 func initDefenestrate(args []string) (*cmd.SuperCommand, *TestCommand, error) { 20 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"}) 21 tc := &TestCommand{Name: "defenestrate"} 22 jc.Register(tc) 23 return jc, tc, cmdtesting.InitCommand(jc, args) 24 } 25 26 type SuperCommandSuite struct { 27 gitjujutesting.IsolationSuite 28 } 29 30 var _ = gc.Suite(&SuperCommandSuite{}) 31 32 const helpText = "\n help\\s+- show help on a command or other topic" 33 const helpCommandsText = "commands:" + helpText 34 35 func (s *SuperCommandSuite) TestDispatch(c *gc.C) { 36 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"}) 37 info := jc.Info() 38 c.Assert(info.Name, gc.Equals, "jujutest") 39 c.Assert(info.Args, gc.Equals, "<command> ...") 40 c.Assert(info.Doc, gc.Matches, helpCommandsText) 41 42 jc, _, err := initDefenestrate([]string{"discombobulate"}) 43 c.Assert(err, gc.ErrorMatches, "unrecognized command: jujutest discombobulate") 44 info = jc.Info() 45 c.Assert(info.Name, gc.Equals, "jujutest") 46 c.Assert(info.Args, gc.Equals, "<command> ...") 47 c.Assert(info.Doc, gc.Matches, "commands:\n defenestrate - defenestrate the juju"+helpText) 48 49 jc, tc, err := initDefenestrate([]string{"defenestrate"}) 50 c.Assert(err, gc.IsNil) 51 c.Assert(tc.Option, gc.Equals, "") 52 info = jc.Info() 53 c.Assert(info.Name, gc.Equals, "jujutest defenestrate") 54 c.Assert(info.Args, gc.Equals, "<something>") 55 c.Assert(info.Doc, gc.Equals, "defenestrate-doc") 56 57 _, tc, err = initDefenestrate([]string{"defenestrate", "--option", "firmly"}) 58 c.Assert(err, gc.IsNil) 59 c.Assert(tc.Option, gc.Equals, "firmly") 60 61 _, tc, err = initDefenestrate([]string{"defenestrate", "gibberish"}) 62 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["gibberish"\]`) 63 64 // --description must be used on it's own. 65 _, _, err = initDefenestrate([]string{"--description", "defenestrate"}) 66 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["defenestrate"\]`) 67 } 68 69 func (s *SuperCommandSuite) TestRegister(c *gc.C) { 70 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"}) 71 jc.Register(&TestCommand{Name: "flip"}) 72 jc.Register(&TestCommand{Name: "flap"}) 73 badCall := func() { jc.Register(&TestCommand{Name: "flap"}) } 74 c.Assert(badCall, gc.PanicMatches, "command already registered: flap") 75 } 76 77 func (s *SuperCommandSuite) TestRegisterAlias(c *gc.C) { 78 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"}) 79 jc.Register(&TestCommand{Name: "flip", Aliases: []string{"flap", "flop"}}) 80 81 info := jc.Info() 82 c.Assert(info.Doc, gc.Equals, `commands: 83 flap - alias for flip 84 flip - flip the juju 85 flop - alias for flip 86 help - show help on a command or other topic`) 87 } 88 89 func (s *SuperCommandSuite) TestInfo(c *gc.C) { 90 commandsDoc := `commands: 91 flapbabble - flapbabble the juju 92 flip - flip the juju` 93 94 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 95 Name: "jujutest", 96 Purpose: "to be purposeful", 97 Doc: "doc\nblah\ndoc", 98 }) 99 info := jc.Info() 100 c.Assert(info.Name, gc.Equals, "jujutest") 101 c.Assert(info.Purpose, gc.Equals, "to be purposeful") 102 // info doc starts with the jc.Doc and ends with the help command 103 c.Assert(info.Doc, gc.Matches, jc.Doc+"(.|\n)*") 104 c.Assert(info.Doc, gc.Matches, "(.|\n)*"+helpCommandsText) 105 106 jc.Register(&TestCommand{Name: "flip"}) 107 jc.Register(&TestCommand{Name: "flapbabble"}) 108 info = jc.Info() 109 c.Assert(info.Doc, gc.Matches, jc.Doc+"\n\n"+commandsDoc+helpText) 110 111 jc.Doc = "" 112 info = jc.Info() 113 c.Assert(info.Doc, gc.Matches, commandsDoc+helpText) 114 } 115 116 type testVersionFlagCommand struct { 117 cmd.CommandBase 118 version string 119 } 120 121 func (c *testVersionFlagCommand) Info() *cmd.Info { 122 return &cmd.Info{Name: "test"} 123 } 124 125 func (c *testVersionFlagCommand) SetFlags(f *gnuflag.FlagSet) { 126 f.StringVar(&c.version, "version", "", "") 127 } 128 129 func (c *testVersionFlagCommand) Run(_ *cmd.Context) error { 130 return nil 131 } 132 133 func (s *SuperCommandSuite) TestVersionFlag(c *gc.C) { 134 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 135 Name: "jujutest", 136 Purpose: "to be purposeful", 137 Doc: "doc\nblah\ndoc", 138 Version: "111.222.333", 139 }) 140 testVersionFlagCommand := &testVersionFlagCommand{} 141 jc.Register(testVersionFlagCommand) 142 143 var stdout, stderr bytes.Buffer 144 ctx := &cmd.Context{ 145 Stdout: &stdout, 146 Stderr: &stderr, 147 } 148 149 // baseline: juju version 150 code := cmd.Main(jc, ctx, []string{"version"}) 151 c.Check(code, gc.Equals, 0) 152 baselineStderr := stderr.String() 153 baselineStdout := stdout.String() 154 stderr.Reset() 155 stdout.Reset() 156 157 // juju --version output should match that of juju version. 158 code = cmd.Main(jc, ctx, []string{"--version"}) 159 c.Check(code, gc.Equals, 0) 160 c.Assert(stderr.String(), gc.Equals, baselineStderr) 161 c.Assert(stdout.String(), gc.Equals, baselineStdout) 162 stderr.Reset() 163 stdout.Reset() 164 165 // juju test --version should update testVersionFlagCommand.version, 166 // and there should be no output. The --version flag on the 'test' 167 // subcommand has a different type to the "juju --version" flag. 168 code = cmd.Main(jc, ctx, []string{"test", "--version=abc.123"}) 169 c.Check(code, gc.Equals, 0) 170 c.Assert(stderr.String(), gc.Equals, "") 171 c.Assert(stdout.String(), gc.Equals, "") 172 c.Assert(testVersionFlagCommand.version, gc.Equals, "abc.123") 173 } 174 175 func (s *SuperCommandSuite) TestVersionNotProvided(c *gc.C) { 176 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 177 Name: "jujutest", 178 Purpose: "to be purposeful", 179 Doc: "doc\nblah\ndoc", 180 }) 181 var stdout, stderr bytes.Buffer 182 ctx := &cmd.Context{ 183 Stdout: &stdout, 184 Stderr: &stderr, 185 } 186 187 // juju version 188 baselineCode := cmd.Main(jc, ctx, []string{"version"}) 189 c.Check(baselineCode, gc.Not(gc.Equals), 0) 190 c.Assert(stderr.String(), gc.Equals, "error: unrecognized command: jujutest version\n") 191 stderr.Reset() 192 stdout.Reset() 193 194 // juju --version 195 code := cmd.Main(jc, ctx, []string{"--version"}) 196 c.Check(code, gc.Equals, baselineCode) 197 c.Assert(stderr.String(), gc.Equals, "error: flag provided but not defined: --version\n") 198 } 199 200 func (s *SuperCommandSuite) TestLogging(c *gc.C) { 201 sc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 202 UsagePrefix: "juju", 203 Name: "command", 204 Log: &cmd.Log{}, 205 }) 206 sc.Register(&TestCommand{Name: "blah"}) 207 ctx := cmdtesting.Context(c) 208 code := cmd.Main(sc, ctx, []string{"blah", "--option", "error", "--debug"}) 209 c.Assert(code, gc.Equals, 1) 210 c.Assert(bufferString(ctx.Stderr), gc.Matches, `^.* ERROR .* BAM!\n`) 211 } 212 213 func (s *SuperCommandSuite) TestNotifyRun(c *gc.C) { 214 notifyTests := []struct { 215 usagePrefix string 216 name string 217 expectName string 218 }{ 219 {"juju", "juju", "juju"}, 220 {"something", "else", "something else"}, 221 {"", "juju", "juju"}, 222 {"", "myapp", "myapp"}, 223 } 224 for i, test := range notifyTests { 225 c.Logf("test %d. %q %q", i, test.usagePrefix, test.name) 226 notifyName := "" 227 sc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 228 UsagePrefix: test.usagePrefix, 229 Name: test.name, 230 NotifyRun: func(name string) { 231 notifyName = name 232 }, 233 }) 234 sc.Register(&TestCommand{Name: "blah"}) 235 ctx := cmdtesting.Context(c) 236 code := cmd.Main(sc, ctx, []string{"blah", "--option", "error"}) 237 c.Assert(bufferString(ctx.Stderr), gc.Matches, "") 238 c.Assert(code, gc.Equals, 1) 239 c.Assert(notifyName, gc.Equals, test.expectName) 240 } 241 } 242 243 func (s *SuperCommandSuite) TestDescription(c *gc.C) { 244 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest", Purpose: "blow up the death star"}) 245 jc.Register(&TestCommand{Name: "blah"}) 246 ctx := cmdtesting.Context(c) 247 code := cmd.Main(jc, ctx, []string{"blah", "--description"}) 248 c.Assert(code, gc.Equals, 0) 249 c.Assert(bufferString(ctx.Stdout), gc.Equals, "blow up the death star\n") 250 } 251 252 func (s *SuperCommandSuite) TestHelp(c *gc.C) { 253 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"}) 254 jc.Register(&TestCommand{Name: "blah"}) 255 ctx := cmdtesting.Context(c) 256 code := cmd.Main(jc, ctx, []string{"blah", "--help"}) 257 c.Assert(code, gc.Equals, 0) 258 stripped := strings.Replace(bufferString(ctx.Stdout), "\n", "", -1) 259 c.Assert(stripped, gc.Matches, ".*usage: jujutest blah.*blah-doc.*") 260 } 261 262 func (s *SuperCommandSuite) TestHelpWithPrefix(c *gc.C) { 263 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest", UsagePrefix: "juju"}) 264 jc.Register(&TestCommand{Name: "blah"}) 265 ctx := cmdtesting.Context(c) 266 code := cmd.Main(jc, ctx, []string{"blah", "--help"}) 267 c.Assert(code, gc.Equals, 0) 268 stripped := strings.Replace(bufferString(ctx.Stdout), "\n", "", -1) 269 c.Assert(stripped, gc.Matches, ".*usage: juju jujutest blah.*blah-doc.*") 270 } 271 272 func NewSuperWithCallback(callback func(*cmd.Context, string, []string) error) cmd.Command { 273 return cmd.NewSuperCommand(cmd.SuperCommandParams{ 274 Name: "jujutest", 275 Log: &cmd.Log{}, 276 MissingCallback: callback, 277 }) 278 } 279 280 func (s *SuperCommandSuite) TestMissingCallback(c *gc.C) { 281 var calledName string 282 var calledArgs []string 283 284 callback := func(ctx *cmd.Context, subcommand string, args []string) error { 285 calledName = subcommand 286 calledArgs = args 287 return nil 288 } 289 290 code := cmd.Main( 291 NewSuperWithCallback(callback), 292 cmdtesting.Context(c), 293 []string{"foo", "bar", "baz", "--debug"}) 294 c.Assert(code, gc.Equals, 0) 295 c.Assert(calledName, gc.Equals, "foo") 296 c.Assert(calledArgs, gc.DeepEquals, []string{"bar", "baz", "--debug"}) 297 } 298 299 func (s *SuperCommandSuite) TestMissingCallbackErrors(c *gc.C) { 300 callback := func(ctx *cmd.Context, subcommand string, args []string) error { 301 return fmt.Errorf("command not found %q", subcommand) 302 } 303 304 ctx := cmdtesting.Context(c) 305 code := cmd.Main(NewSuperWithCallback(callback), ctx, []string{"foo"}) 306 c.Assert(code, gc.Equals, 1) 307 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "") 308 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "ERROR command not found \"foo\"\n") 309 } 310 311 func (s *SuperCommandSuite) TestMissingCallbackContextWiredIn(c *gc.C) { 312 callback := func(ctx *cmd.Context, subcommand string, args []string) error { 313 fmt.Fprintf(ctx.Stdout, "this is std out") 314 fmt.Fprintf(ctx.Stderr, "this is std err") 315 return nil 316 } 317 318 ctx := cmdtesting.Context(c) 319 code := cmd.Main(NewSuperWithCallback(callback), ctx, []string{"foo", "bar", "baz", "--debug"}) 320 c.Assert(code, gc.Equals, 0) 321 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "this is std out") 322 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "this is std err") 323 } 324 325 func (s *SuperCommandSuite) TestSupercommandAliases(c *gc.C) { 326 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 327 Name: "jujutest", 328 UsagePrefix: "juju", 329 }) 330 sub := cmd.NewSuperCommand(cmd.SuperCommandParams{ 331 Name: "jubar", 332 UsagePrefix: "juju jujutest", 333 Aliases: []string{"jubaz", "jubing"}, 334 }) 335 info := sub.Info() 336 c.Check(info.Aliases, gc.DeepEquals, []string{"jubaz", "jubing"}) 337 jc.Register(sub) 338 for _, name := range []string{"jubar", "jubaz", "jubing"} { 339 c.Logf("testing command name %q", name) 340 ctx := cmdtesting.Context(c) 341 code := cmd.Main(jc, ctx, []string{name, "--help"}) 342 c.Assert(code, gc.Equals, 0) 343 stripped := strings.Replace(bufferString(ctx.Stdout), "\n", "", -1) 344 c.Assert(stripped, gc.Matches, ".*usage: juju jujutest jubar.*aliases: jubaz, jubing") 345 } 346 }