github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cmd/juju/commands/main_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 13 "github.com/juju/cmd" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils/featureflag" 16 "github.com/juju/utils/set" 17 gc "gopkg.in/check.v1" 18 19 "github.com/juju/juju/cmd/envcmd" 20 "github.com/juju/juju/cmd/juju/block" 21 "github.com/juju/juju/cmd/juju/service" 22 cmdtesting "github.com/juju/juju/cmd/testing" 23 "github.com/juju/juju/juju/osenv" 24 _ "github.com/juju/juju/provider/dummy" 25 "github.com/juju/juju/testing" 26 "github.com/juju/juju/version" 27 ) 28 29 type MainSuite struct { 30 testing.FakeJujuHomeSuite 31 } 32 33 var _ = gc.Suite(&MainSuite{}) 34 35 func deployHelpText() string { 36 return cmdtesting.HelpText(envcmd.Wrap(&DeployCommand{}), "juju deploy") 37 } 38 39 func setHelpText() string { 40 return cmdtesting.HelpText(envcmd.Wrap(&service.SetCommand{}), "juju service set") 41 } 42 43 func syncToolsHelpText() string { 44 return cmdtesting.HelpText(envcmd.Wrap(&SyncToolsCommand{}), "juju sync-tools") 45 } 46 47 func blockHelpText() string { 48 return cmdtesting.HelpText(block.NewSuperBlockCommand(), "juju block") 49 } 50 51 func (s *MainSuite) TestRunMain(c *gc.C) { 52 // The test array structure needs to be inline here as some of the 53 // expected values below use deployHelpText(). This constructs the deploy 54 // command and runs gets the help for it. When the deploy command is 55 // setting the flags (which is needed for the help text) it is accessing 56 // osenv.JujuHome(), which panics if SetJujuHome has not been called. 57 // The FakeHome from testing does this. 58 for i, t := range []struct { 59 summary string 60 args []string 61 code int 62 out string 63 }{{ 64 summary: "no params shows help", 65 args: []string{}, 66 code: 0, 67 out: strings.TrimLeft(helpBasics, "\n"), 68 }, { 69 summary: "juju help is the same as juju", 70 args: []string{"help"}, 71 code: 0, 72 out: strings.TrimLeft(helpBasics, "\n"), 73 }, { 74 summary: "juju --help works too", 75 args: []string{"--help"}, 76 code: 0, 77 out: strings.TrimLeft(helpBasics, "\n"), 78 }, { 79 summary: "juju help basics is the same as juju", 80 args: []string{"help", "basics"}, 81 code: 0, 82 out: strings.TrimLeft(helpBasics, "\n"), 83 }, { 84 summary: "juju help foo doesn't exist", 85 args: []string{"help", "foo"}, 86 code: 1, 87 out: "ERROR unknown command or topic for foo\n", 88 }, { 89 summary: "juju help deploy shows the default help without global options", 90 args: []string{"help", "deploy"}, 91 code: 0, 92 out: deployHelpText(), 93 }, { 94 summary: "juju --help deploy shows the same help as 'help deploy'", 95 args: []string{"--help", "deploy"}, 96 code: 0, 97 out: deployHelpText(), 98 }, { 99 summary: "juju deploy --help shows the same help as 'help deploy'", 100 args: []string{"deploy", "--help"}, 101 code: 0, 102 out: deployHelpText(), 103 }, { 104 summary: "juju help set shows the default help without global options", 105 args: []string{"help", "set"}, 106 code: 0, 107 out: setHelpText(), 108 }, { 109 summary: "juju --help set shows the same help as 'help set'", 110 args: []string{"--help", "set"}, 111 code: 0, 112 out: setHelpText(), 113 }, { 114 summary: "juju set --help shows the same help as 'help set'", 115 args: []string{"set", "--help"}, 116 code: 0, 117 out: setHelpText(), 118 }, { 119 summary: "unknown command", 120 args: []string{"discombobulate"}, 121 code: 1, 122 out: "ERROR unrecognized command: juju discombobulate\n", 123 }, { 124 summary: "unknown option before command", 125 args: []string{"--cheese", "bootstrap"}, 126 code: 2, 127 out: "error: flag provided but not defined: --cheese\n", 128 }, { 129 summary: "unknown option after command", 130 args: []string{"bootstrap", "--cheese"}, 131 code: 2, 132 out: "error: flag provided but not defined: --cheese\n", 133 }, { 134 summary: "known option, but specified before command", 135 args: []string{"--environment", "blah", "bootstrap"}, 136 code: 2, 137 out: "error: flag provided but not defined: --environment\n", 138 }, { 139 summary: "juju sync-tools registered properly", 140 args: []string{"sync-tools", "--help"}, 141 code: 0, 142 out: syncToolsHelpText(), 143 }, { 144 summary: "check version command registered properly", 145 args: []string{"version"}, 146 code: 0, 147 out: version.Current.String() + "\n", 148 }, { 149 summary: "check block command registered properly", 150 args: []string{"block", "-h"}, 151 code: 0, 152 out: blockHelpText(), 153 }, { 154 summary: "check unblock command registered properly", 155 args: []string{"unblock"}, 156 code: 0, 157 out: "error: must specify one of [destroy-environment | remove-object | all-changes] to unblock\n", 158 }, 159 } { 160 c.Logf("test %d: %s", i, t.summary) 161 out := badrun(c, t.code, t.args...) 162 c.Assert(out, gc.Equals, t.out) 163 } 164 } 165 166 func (s *MainSuite) TestActualRunJujuArgOrder(c *gc.C) { 167 //TODO(bogdanteleaga): cannot read the env file because of some suite 168 //problems. The juju home, when calling something from the command line is 169 //not the same as in the test suite. 170 if runtime.GOOS == "windows" { 171 c.Skip("bug 1403084: cannot read env file on windows because of suite problems") 172 } 173 logpath := filepath.Join(c.MkDir(), "log") 174 tests := [][]string{ 175 {"--log-file", logpath, "--debug", "env"}, // global flags before 176 {"env", "--log-file", logpath, "--debug"}, // after 177 {"--log-file", logpath, "env", "--debug"}, // mixed 178 } 179 for i, test := range tests { 180 c.Logf("test %d: %v", i, test) 181 badrun(c, 0, test...) 182 content, err := ioutil.ReadFile(logpath) 183 c.Assert(err, jc.ErrorIsNil) 184 c.Assert(string(content), gc.Matches, "(.|\n)*running juju(.|\n)*command finished(.|\n)*") 185 err = os.Remove(logpath) 186 c.Assert(err, jc.ErrorIsNil) 187 } 188 } 189 190 var commandNames = []string{ 191 "action", 192 "add-machine", 193 "add-relation", 194 "add-unit", 195 "api-endpoints", 196 "api-info", 197 "authorised-keys", // alias for authorized-keys 198 "authorized-keys", 199 "backups", 200 "block", 201 "bootstrap", 202 "cached-images", 203 "debug-hooks", 204 "debug-log", 205 "deploy", 206 "destroy-environment", 207 "destroy-machine", 208 "destroy-relation", 209 "destroy-service", 210 "destroy-unit", 211 "ensure-availability", 212 "env", // alias for switch 213 "environment", 214 "expose", 215 "generate-config", // alias for init 216 "get", 217 "get-constraints", 218 "get-env", // alias for get-environment 219 "get-environment", 220 "help", 221 "help-tool", 222 "init", 223 "machine", 224 "publish", 225 "remove-machine", // alias for destroy-machine 226 "remove-relation", // alias for destroy-relation 227 "remove-service", // alias for destroy-service 228 "remove-unit", // alias for destroy-unit 229 "resolved", 230 "retry-provisioning", 231 "run", 232 "scp", 233 "service", 234 "set", 235 "set-constraints", 236 "set-env", // alias for set-environment 237 "set-environment", 238 "ssh", 239 "stat", // alias for status 240 "status", 241 "status-history", 242 "storage", 243 "switch", 244 "sync-tools", 245 "terminate-machine", // alias for destroy-machine 246 "unblock", 247 "unexpose", 248 "unset", 249 "unset-env", // alias for unset-environment 250 "unset-environment", 251 "upgrade-charm", 252 "upgrade-juju", 253 "user", 254 "version", 255 } 256 257 func (s *MainSuite) TestHelpCommands(c *gc.C) { 258 defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir())) 259 260 // Check that we have correctly registered all the commands 261 // by checking the help output. 262 // First check default commands, and then check commands that are 263 // activated by feature flags. 264 265 // Here we can add feature flags for any commands we want to hide by default. 266 devFeatures := []string{} 267 268 // remove features behind dev_flag for the first test 269 // since they are not enabled. 270 cmdSet := set.NewStrings(commandNames...) 271 for _, feature := range devFeatures { 272 cmdSet.Remove(feature) 273 } 274 275 // 1. Default Commands. Disable all features. 276 setFeatureFlags("") 277 c.Assert(getHelpCommandNames(c), jc.SameContents, cmdSet.Values()) 278 279 // 2. Enable development features, and test again. 280 setFeatureFlags(strings.Join(devFeatures, ",")) 281 c.Assert(getHelpCommandNames(c), jc.SameContents, commandNames) 282 } 283 284 func getHelpCommandNames(c *gc.C) []string { 285 out := badrun(c, 0, "help", "commands") 286 lines := strings.Split(out, "\n") 287 var names []string 288 for _, line := range lines { 289 f := strings.Fields(line) 290 if len(f) == 0 { 291 continue 292 } 293 names = append(names, f[0]) 294 } 295 return names 296 } 297 298 func setFeatureFlags(flags string) { 299 if err := os.Setenv(osenv.JujuFeatureFlagEnvKey, flags); err != nil { 300 panic(err) 301 } 302 featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey) 303 } 304 305 var topicNames = []string{ 306 "azure-provider", 307 "basics", 308 "commands", 309 "constraints", 310 "ec2-provider", 311 "global-options", 312 "glossary", 313 "hpcloud-provider", 314 "local-provider", 315 "logging", 316 "maas-provider", 317 "openstack-provider", 318 "placement", 319 "plugins", 320 "topics", 321 } 322 323 func (s *MainSuite) TestHelpTopics(c *gc.C) { 324 // Check that we have correctly registered all the topics 325 // by checking the help output. 326 defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir())) 327 out := badrun(c, 0, "help", "topics") 328 lines := strings.Split(out, "\n") 329 var names []string 330 for _, line := range lines { 331 f := strings.Fields(line) 332 if len(f) == 0 { 333 continue 334 } 335 names = append(names, f[0]) 336 } 337 // The names should be output in alphabetical order, so don't sort. 338 c.Assert(names, gc.DeepEquals, topicNames) 339 } 340 341 var globalFlags = []string{ 342 "--debug .*", 343 "--description .*", 344 "-h, --help .*", 345 "--log-file .*", 346 "--logging-config .*", 347 "-q, --quiet .*", 348 "--show-log .*", 349 "-v, --verbose .*", 350 } 351 352 func (s *MainSuite) TestHelpGlobalOptions(c *gc.C) { 353 // Check that we have correctly registered all the topics 354 // by checking the help output. 355 defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir())) 356 out := badrun(c, 0, "help", "global-options") 357 c.Assert(out, gc.Matches, `Global Options 358 359 These options may be used with any command, and may appear in front of any 360 command\.(.|\n)*`) 361 lines := strings.Split(out, "\n") 362 var flags []string 363 for _, line := range lines { 364 f := strings.Fields(line) 365 if len(f) == 0 || line[0] != '-' { 366 continue 367 } 368 flags = append(flags, line) 369 } 370 c.Assert(len(flags), gc.Equals, len(globalFlags)) 371 for i, line := range flags { 372 c.Assert(line, gc.Matches, globalFlags[i]) 373 } 374 } 375 376 type commands []cmd.Command 377 378 func (r *commands) Register(c cmd.Command) { 379 *r = append(*r, c) 380 } 381 382 func (r *commands) RegisterDeprecated(c cmd.Command, check cmd.DeprecationCheck) { 383 if !check.Obsolete() { 384 *r = append(*r, c) 385 } 386 } 387 388 func (r *commands) RegisterSuperAlias(name, super, forName string, check cmd.DeprecationCheck) { 389 // Do nothing. 390 } 391 392 func (s *MainSuite) TestEnvironCommands(c *gc.C) { 393 var commands commands 394 registerCommands(&commands, testing.Context(c)) 395 // There should not be any EnvironCommands registered. 396 // EnvironCommands must be wrapped using envcmd.Wrap. 397 for _, cmd := range commands { 398 c.Logf("%v", cmd.Info().Name) 399 c.Check(cmd, gc.Not(gc.FitsTypeOf), envcmd.EnvironCommand(&BootstrapCommand{})) 400 } 401 } 402 403 func (s *MainSuite) TestAllCommandsPurposeDocCapitalization(c *gc.C) { 404 // Verify each command that: 405 // - the Purpose field is not empty and begins with a lowercase 406 // letter, and, 407 // - if set, the Doc field either begins with the name of the 408 // command or and uppercase letter. 409 // 410 // The first makes Purpose a required documentation. Also, makes 411 // both "help commands"'s output and "help <cmd>"'s header more 412 // uniform. The second makes the Doc content either start like a 413 // sentence, or start godoc-like by using the command's name in 414 // lowercase. 415 var commands commands 416 registerCommands(&commands, testing.Context(c)) 417 for _, cmd := range commands { 418 info := cmd.Info() 419 c.Logf("%v", info.Name) 420 purpose := strings.TrimSpace(info.Purpose) 421 doc := strings.TrimSpace(info.Doc) 422 comment := func(message string) interface{} { 423 return gc.Commentf("command %q %s", info.Name, message) 424 } 425 426 c.Check(purpose, gc.Not(gc.Equals), "", comment("has empty Purpose")) 427 if purpose != "" { 428 prefix := string(purpose[0]) 429 c.Check(prefix, gc.Equals, strings.ToLower(prefix), 430 comment("expected lowercase first-letter Purpose"), 431 ) 432 } 433 if doc != "" && !strings.HasPrefix(doc, info.Name) { 434 prefix := string(doc[0]) 435 c.Check(prefix, gc.Equals, strings.ToUpper(prefix), 436 comment("expected uppercase first-letter Doc"), 437 ) 438 } 439 } 440 } 441 442 func (s *MainSuite) TestTwoDotOhDeprecation(c *gc.C) { 443 check := twoDotOhDeprecation("the replacement") 444 445 // first check pre-2.0 446 s.PatchValue(&version.Current.Number, version.MustParse("1.26.4")) 447 deprecated, replacement := check.Deprecated() 448 c.Check(deprecated, jc.IsFalse) 449 c.Check(replacement, gc.Equals, "") 450 c.Check(check.Obsolete(), jc.IsFalse) 451 452 s.PatchValue(&version.Current.Number, version.MustParse("2.0-alpha1")) 453 deprecated, replacement = check.Deprecated() 454 c.Check(deprecated, jc.IsTrue) 455 c.Check(replacement, gc.Equals, "the replacement") 456 c.Check(check.Obsolete(), jc.IsFalse) 457 458 s.PatchValue(&version.Current.Number, version.MustParse("3.0-alpha1")) 459 deprecated, replacement = check.Deprecated() 460 c.Check(deprecated, jc.IsTrue) 461 c.Check(replacement, gc.Equals, "the replacement") 462 c.Check(check.Obsolete(), jc.IsTrue) 463 } 464 465 // obsoleteCommandNames is the list of commands that are deprecated in 466 // 2.0, and obsolete in 3.0 467 var obsoleteCommandNames = []string{ 468 "add-machine", 469 "destroy-machine", 470 "get-constraints", 471 "get-env", 472 "get-environment", 473 "remove-machine", 474 "retry-provisioning", 475 "set-constraints", 476 "set-env", 477 "set-environment", 478 "terminate-machine", 479 "unset-env", 480 "unset-environment", 481 } 482 483 func (s *MainSuite) TestObsoleteRegistration(c *gc.C) { 484 var commands commands 485 s.PatchValue(&version.Current.Number, version.MustParse("3.0-alpha1")) 486 registerCommands(&commands, testing.Context(c)) 487 488 cmdSet := set.NewStrings(obsoleteCommandNames...) 489 registeredCmdSet := set.NewStrings() 490 for _, cmd := range commands { 491 registeredCmdSet.Add(cmd.Info().Name) 492 } 493 494 intersection := registeredCmdSet.Intersection(cmdSet) 495 c.Logf("Registered obsolete commands: %s", intersection.Values()) 496 c.Assert(intersection.IsEmpty(), gc.Equals, true) 497 }