github.com/dooferlad/cmd@v0.0.0-20150716022859-3edef806220b/supercommand_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the LGPLv3, 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 gc "gopkg.in/check.v1" 13 "launchpad.net/gnuflag" 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) TestAliasesRegistered(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 NewSuperWithCallback(callback func(*cmd.Context, string, []string) error) cmd.Command { 253 return cmd.NewSuperCommand(cmd.SuperCommandParams{ 254 Name: "jujutest", 255 Log: &cmd.Log{}, 256 MissingCallback: callback, 257 }) 258 } 259 260 func (s *SuperCommandSuite) TestMissingCallback(c *gc.C) { 261 var calledName string 262 var calledArgs []string 263 264 callback := func(ctx *cmd.Context, subcommand string, args []string) error { 265 calledName = subcommand 266 calledArgs = args 267 return nil 268 } 269 270 code := cmd.Main( 271 NewSuperWithCallback(callback), 272 cmdtesting.Context(c), 273 []string{"foo", "bar", "baz", "--debug"}) 274 c.Assert(code, gc.Equals, 0) 275 c.Assert(calledName, gc.Equals, "foo") 276 c.Assert(calledArgs, gc.DeepEquals, []string{"bar", "baz", "--debug"}) 277 } 278 279 func (s *SuperCommandSuite) TestMissingCallbackErrors(c *gc.C) { 280 callback := func(ctx *cmd.Context, subcommand string, args []string) error { 281 return fmt.Errorf("command not found %q", subcommand) 282 } 283 284 ctx := cmdtesting.Context(c) 285 code := cmd.Main(NewSuperWithCallback(callback), ctx, []string{"foo"}) 286 c.Assert(code, gc.Equals, 1) 287 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "") 288 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "ERROR command not found \"foo\"\n") 289 } 290 291 func (s *SuperCommandSuite) TestMissingCallbackContextWiredIn(c *gc.C) { 292 callback := func(ctx *cmd.Context, subcommand string, args []string) error { 293 fmt.Fprintf(ctx.Stdout, "this is std out") 294 fmt.Fprintf(ctx.Stderr, "this is std err") 295 return nil 296 } 297 298 ctx := cmdtesting.Context(c) 299 code := cmd.Main(NewSuperWithCallback(callback), ctx, []string{"foo", "bar", "baz", "--debug"}) 300 c.Assert(code, gc.Equals, 0) 301 c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "this is std out") 302 c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "this is std err") 303 } 304 305 func (s *SuperCommandSuite) TestSupercommandAliases(c *gc.C) { 306 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 307 Name: "jujutest", 308 UsagePrefix: "juju", 309 }) 310 sub := cmd.NewSuperCommand(cmd.SuperCommandParams{ 311 Name: "jubar", 312 UsagePrefix: "juju jujutest", 313 Aliases: []string{"jubaz", "jubing"}, 314 }) 315 info := sub.Info() 316 c.Check(info.Aliases, gc.DeepEquals, []string{"jubaz", "jubing"}) 317 jc.Register(sub) 318 for _, name := range []string{"jubar", "jubaz", "jubing"} { 319 c.Logf("testing command name %q", name) 320 ctx := cmdtesting.Context(c) 321 code := cmd.Main(jc, ctx, []string{name, "--help"}) 322 c.Assert(code, gc.Equals, 0) 323 stripped := strings.Replace(bufferString(ctx.Stdout), "\n", "", -1) 324 c.Assert(stripped, gc.Matches, ".*usage: juju jujutest jubar.*aliases: jubaz, jubing") 325 } 326 } 327 328 type simple struct { 329 cmd.CommandBase 330 name string 331 args []string 332 } 333 334 var _ cmd.Command = (*simple)(nil) 335 336 func (s *simple) Info() *cmd.Info { 337 return &cmd.Info{Name: s.name, Purpose: "to be simple"} 338 } 339 340 func (s *simple) Init(args []string) error { 341 s.args = args 342 return nil 343 } 344 345 func (s *simple) Run(ctx *cmd.Context) error { 346 fmt.Fprintf(ctx.Stdout, "%s %s\n", s.name, strings.Join(s.args, ", ")) 347 return nil 348 } 349 350 type deprecate struct { 351 replacement string 352 obsolete bool 353 } 354 355 func (d deprecate) Deprecated() (bool, string) { 356 if d.replacement == "" { 357 return false, "" 358 } 359 return true, d.replacement 360 } 361 func (d deprecate) Obsolete() bool { 362 return d.obsolete 363 } 364 365 func (s *SuperCommandSuite) TestRegisterAlias(c *gc.C) { 366 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 367 Name: "jujutest", 368 }) 369 jc.Register(&simple{name: "test"}) 370 jc.RegisterAlias("foo", "test", nil) 371 jc.RegisterAlias("bar", "test", deprecate{replacement: "test"}) 372 jc.RegisterAlias("baz", "test", deprecate{obsolete: true}) 373 374 c.Assert( 375 func() { jc.RegisterAlias("omg", "unknown", nil) }, 376 gc.PanicMatches, `"unknown" not found when registering alias`) 377 378 info := jc.Info() 379 // NOTE: deprecated `bar` not shown in commands. 380 c.Assert(info.Doc, gc.Equals, `commands: 381 foo - alias for 'test' 382 help - show help on a command or other topic 383 test - to be simple`) 384 385 for _, test := range []struct { 386 name string 387 stdout string 388 stderr string 389 code int 390 }{ 391 { 392 name: "test", 393 stdout: "test arg\n", 394 }, { 395 name: "foo", 396 stdout: "test arg\n", 397 }, { 398 name: "bar", 399 stdout: "test arg\n", 400 stderr: "WARNING: \"bar\" is deprecated, please use \"test\"\n", 401 }, { 402 name: "baz", 403 stderr: "error: unrecognized command: jujutest baz\n", 404 code: 2, 405 }, 406 } { 407 ctx := cmdtesting.Context(c) 408 code := cmd.Main(jc, ctx, []string{test.name, "arg"}) 409 c.Check(code, gc.Equals, test.code) 410 c.Check(cmdtesting.Stdout(ctx), gc.Equals, test.stdout) 411 c.Check(cmdtesting.Stderr(ctx), gc.Equals, test.stderr) 412 } 413 } 414 415 func (s *SuperCommandSuite) TestRegisterSuperAlias(c *gc.C) { 416 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 417 Name: "jujutest", 418 }) 419 jc.Register(&simple{name: "test"}) 420 sub := cmd.NewSuperCommand(cmd.SuperCommandParams{ 421 Name: "bar", 422 UsagePrefix: "jujutest", 423 Purpose: "bar functions", 424 }) 425 jc.Register(sub) 426 sub.Register(&simple{name: "foo"}) 427 428 c.Assert( 429 func() { jc.RegisterSuperAlias("bar-foo", "unknown", "foo", nil) }, 430 gc.PanicMatches, `"unknown" not found when registering alias`) 431 c.Assert( 432 func() { jc.RegisterSuperAlias("bar-foo", "test", "foo", nil) }, 433 gc.PanicMatches, `"test" is not a SuperCommand`) 434 c.Assert( 435 func() { jc.RegisterSuperAlias("bar-foo", "bar", "unknown", nil) }, 436 gc.PanicMatches, `"unknown" not found as a command in "bar"`) 437 438 jc.RegisterSuperAlias("bar-foo", "bar", "foo", nil) 439 jc.RegisterSuperAlias("bar-dep", "bar", "foo", deprecate{replacement: "bar foo"}) 440 jc.RegisterSuperAlias("bar-ob", "bar", "foo", deprecate{obsolete: true}) 441 442 info := jc.Info() 443 // NOTE: deprecated `bar` not shown in commands. 444 c.Assert(info.Doc, gc.Equals, `commands: 445 bar - bar functions 446 bar-foo - alias for 'bar foo' 447 help - show help on a command or other topic 448 test - to be simple`) 449 450 for _, test := range []struct { 451 args []string 452 stdout string 453 stderr string 454 code int 455 }{ 456 { 457 args: []string{"bar", "foo", "arg"}, 458 stdout: "foo arg\n", 459 }, { 460 args: []string{"bar-foo", "arg"}, 461 stdout: "foo arg\n", 462 }, { 463 args: []string{"bar-dep", "arg"}, 464 stdout: "foo arg\n", 465 stderr: "WARNING: \"bar-dep\" is deprecated, please use \"bar foo\"\n", 466 }, { 467 args: []string{"bar-ob", "arg"}, 468 stderr: "error: unrecognized command: jujutest bar-ob\n", 469 code: 2, 470 }, 471 } { 472 ctx := cmdtesting.Context(c) 473 code := cmd.Main(jc, ctx, test.args) 474 c.Check(code, gc.Equals, test.code) 475 c.Check(cmdtesting.Stdout(ctx), gc.Equals, test.stdout) 476 c.Check(cmdtesting.Stderr(ctx), gc.Equals, test.stderr) 477 } 478 } 479 480 type simpleAlias struct { 481 simple 482 } 483 484 func (s *simpleAlias) Info() *cmd.Info { 485 return &cmd.Info{Name: s.name, Purpose: "to be simple with an alias", 486 Aliases: []string{s.name + "-alias"}} 487 } 488 489 func (s *SuperCommandSuite) TestRegisterDeprecated(c *gc.C) { 490 jc := cmd.NewSuperCommand(cmd.SuperCommandParams{ 491 Name: "jujutest", 492 }) 493 494 // Test that calling with a nil command will not panic 495 jc.RegisterDeprecated(nil, nil) 496 497 jc.RegisterDeprecated(&simpleAlias{simple{name: "test-non-dep"}}, nil) 498 jc.RegisterDeprecated(&simpleAlias{simple{name: "test-dep"}}, deprecate{replacement: "test-dep-new"}) 499 jc.RegisterDeprecated(&simpleAlias{simple{name: "test-ob"}}, deprecate{obsolete: true}) 500 501 badCall := func() { 502 jc.RegisterDeprecated(&simpleAlias{simple{name: "test-dep"}}, deprecate{replacement: "test-dep-new"}) 503 } 504 c.Assert(badCall, gc.PanicMatches, `command already registered: "test-dep"`) 505 506 for _, test := range []struct { 507 args []string 508 stdout string 509 stderr string 510 code int 511 }{ 512 { 513 args: []string{"test-non-dep", "arg"}, 514 stdout: "test-non-dep arg\n", 515 }, { 516 args: []string{"test-non-dep-alias", "arg"}, 517 stdout: "test-non-dep arg\n", 518 }, { 519 args: []string{"test-dep", "arg"}, 520 stdout: "test-dep arg\n", 521 stderr: "WARNING: \"test-dep\" is deprecated, please use \"test-dep-new\"\n", 522 }, { 523 args: []string{"test-dep-alias", "arg"}, 524 stdout: "test-dep arg\n", 525 stderr: "WARNING: \"test-dep-alias\" is deprecated, please use \"test-dep-new\"\n", 526 }, { 527 args: []string{"test-ob", "arg"}, 528 stderr: "error: unrecognized command: jujutest test-ob\n", 529 code: 2, 530 }, { 531 args: []string{"test-ob-alias", "arg"}, 532 stderr: "error: unrecognized command: jujutest test-ob-alias\n", 533 code: 2, 534 }, 535 } { 536 537 ctx := cmdtesting.Context(c) 538 code := cmd.Main(jc, ctx, test.args) 539 c.Check(code, gc.Equals, test.code) 540 c.Check(cmdtesting.Stderr(ctx), gc.Equals, test.stderr) 541 c.Check(cmdtesting.Stdout(ctx), gc.Equals, test.stdout) 542 } 543 }