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