github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 "bytes" 8 "flag" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "strings" 15 stdtesting "testing" 16 17 jc "github.com/juju/testing/checkers" 18 "launchpad.net/gnuflag" 19 gc "launchpad.net/gocheck" 20 21 "github.com/juju/juju/cmd" 22 "github.com/juju/juju/cmd/envcmd" 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 func TestPackage(t *stdtesting.T) { 30 testing.MgoTestPackage(t) 31 } 32 33 type MainSuite struct { 34 testing.FakeJujuHomeSuite 35 } 36 37 var _ = gc.Suite(&MainSuite{}) 38 39 var ( 40 flagRunMain = flag.Bool("run-main", false, "Run the application's main function for recursive testing") 41 ) 42 43 // Reentrancy point for testing (something as close as possible to) the juju 44 // tool itself. 45 func TestRunMain(t *stdtesting.T) { 46 if *flagRunMain { 47 Main(flag.Args()) 48 } 49 } 50 51 func badrun(c *gc.C, exit int, args ...string) string { 52 localArgs := append([]string{"-test.run", "TestRunMain", "-run-main", "--", "juju"}, args...) 53 ps := exec.Command(os.Args[0], localArgs...) 54 ps.Env = append(os.Environ(), osenv.JujuHomeEnvKey+"="+osenv.JujuHome()) 55 output, err := ps.CombinedOutput() 56 c.Logf("command output: %q", output) 57 if exit != 0 { 58 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("exit status %d", exit)) 59 } 60 return string(output) 61 } 62 63 func helpText(command cmd.Command, name string) string { 64 buff := &bytes.Buffer{} 65 info := command.Info() 66 info.Name = name 67 f := gnuflag.NewFlagSet(info.Name, gnuflag.ContinueOnError) 68 command.SetFlags(f) 69 buff.Write(info.Help(f)) 70 return buff.String() 71 } 72 73 func deployHelpText() string { 74 return helpText(envcmd.Wrap(&DeployCommand{}), "juju deploy") 75 } 76 77 func syncToolsHelpText() string { 78 return helpText(envcmd.Wrap(&SyncToolsCommand{}), "juju sync-tools") 79 } 80 81 func (s *MainSuite) TestRunMain(c *gc.C) { 82 // The test array structure needs to be inline here as some of the 83 // expected values below use deployHelpText(). This constructs the deploy 84 // command and runs gets the help for it. When the deploy command is 85 // setting the flags (which is needed for the help text) it is accessing 86 // osenv.JujuHome(), which panics if SetJujuHome has not been called. 87 // The FakeHome from testing does this. 88 for i, t := range []struct { 89 summary string 90 args []string 91 code int 92 out string 93 }{{ 94 summary: "no params shows help", 95 args: []string{}, 96 code: 0, 97 out: strings.TrimLeft(helpBasics, "\n"), 98 }, { 99 summary: "juju help is the same as juju", 100 args: []string{"help"}, 101 code: 0, 102 out: strings.TrimLeft(helpBasics, "\n"), 103 }, { 104 summary: "juju --help works too", 105 args: []string{"--help"}, 106 code: 0, 107 out: strings.TrimLeft(helpBasics, "\n"), 108 }, { 109 summary: "juju help basics is the same as juju", 110 args: []string{"help", "basics"}, 111 code: 0, 112 out: strings.TrimLeft(helpBasics, "\n"), 113 }, { 114 summary: "juju help foo doesn't exist", 115 args: []string{"help", "foo"}, 116 code: 1, 117 out: "ERROR unknown command or topic for foo\n", 118 }, { 119 summary: "juju help deploy shows the default help without global options", 120 args: []string{"help", "deploy"}, 121 code: 0, 122 out: deployHelpText(), 123 }, { 124 summary: "juju --help deploy shows the same help as 'help deploy'", 125 args: []string{"--help", "deploy"}, 126 code: 0, 127 out: deployHelpText(), 128 }, { 129 summary: "juju deploy --help shows the same help as 'help deploy'", 130 args: []string{"deploy", "--help"}, 131 code: 0, 132 out: deployHelpText(), 133 }, { 134 summary: "unknown command", 135 args: []string{"discombobulate"}, 136 code: 1, 137 out: "ERROR unrecognized command: juju discombobulate\n", 138 }, { 139 summary: "unknown option before command", 140 args: []string{"--cheese", "bootstrap"}, 141 code: 2, 142 out: "error: flag provided but not defined: --cheese\n", 143 }, { 144 summary: "unknown option after command", 145 args: []string{"bootstrap", "--cheese"}, 146 code: 2, 147 out: "error: flag provided but not defined: --cheese\n", 148 }, { 149 summary: "known option, but specified before command", 150 args: []string{"--environment", "blah", "bootstrap"}, 151 code: 2, 152 out: "error: flag provided but not defined: --environment\n", 153 }, { 154 summary: "juju sync-tools registered properly", 155 args: []string{"sync-tools", "--help"}, 156 code: 0, 157 out: syncToolsHelpText(), 158 }, { 159 summary: "check version command registered properly", 160 args: []string{"version"}, 161 code: 0, 162 out: version.Current.String() + "\n", 163 }, 164 } { 165 c.Logf("test %d: %s", i, t.summary) 166 out := badrun(c, t.code, t.args...) 167 c.Assert(out, gc.Equals, t.out) 168 } 169 } 170 171 func (s *MainSuite) TestActualRunJujuArgOrder(c *gc.C) { 172 logpath := filepath.Join(c.MkDir(), "log") 173 tests := [][]string{ 174 {"--log-file", logpath, "--debug", "env"}, // global flags before 175 {"env", "--log-file", logpath, "--debug"}, // after 176 {"--log-file", logpath, "env", "--debug"}, // mixed 177 } 178 for i, test := range tests { 179 c.Logf("test %d: %v", i, test) 180 badrun(c, 0, test...) 181 content, err := ioutil.ReadFile(logpath) 182 c.Assert(err, gc.IsNil) 183 c.Assert(string(content), gc.Matches, "(.|\n)*running juju(.|\n)*command finished(.|\n)*") 184 err = os.Remove(logpath) 185 c.Assert(err, gc.IsNil) 186 } 187 } 188 189 var commandNames = []string{ 190 "add-machine", 191 "add-relation", 192 "add-unit", 193 "api-endpoints", 194 "authorised-keys", // alias for authorized-keys 195 "authorized-keys", 196 "bootstrap", 197 "debug-hooks", 198 "debug-log", 199 "deploy", 200 "destroy-environment", 201 "destroy-machine", 202 "destroy-relation", 203 "destroy-service", 204 "destroy-unit", 205 "ensure-availability", 206 "env", // alias for switch 207 "expose", 208 "generate-config", // alias for init 209 "get", 210 "get-constraints", 211 "get-env", // alias for get-environment 212 "get-environment", 213 "help", 214 "help-tool", 215 "init", 216 "publish", 217 "remove-machine", // alias for destroy-machine 218 "remove-relation", // alias for destroy-relation 219 "remove-service", // alias for destroy-service 220 "remove-unit", // alias for destroy-unit 221 "resolved", 222 "retry-provisioning", 223 "run", 224 "scp", 225 "set", 226 "set-constraints", 227 "set-env", // alias for set-environment 228 "set-environment", 229 "ssh", 230 "stat", // alias for status 231 "status", 232 "switch", 233 "sync-tools", 234 "terminate-machine", // alias for destroy-machine 235 "unexpose", 236 "unset", 237 "unset-env", // alias for unset-environment 238 "unset-environment", 239 "upgrade-charm", 240 "upgrade-juju", 241 "user", 242 "version", 243 } 244 245 func (s *MainSuite) TestHelpCommands(c *gc.C) { 246 // Check that we have correctly registered all the commands 247 // by checking the help output. 248 defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir())) 249 out := badrun(c, 0, "help", "commands") 250 lines := strings.Split(out, "\n") 251 var names []string 252 for _, line := range lines { 253 f := strings.Fields(line) 254 if len(f) == 0 { 255 continue 256 } 257 names = append(names, f[0]) 258 } 259 // The names should be output in alphabetical order, so don't sort. 260 c.Assert(names, jc.DeepEquals, commandNames) 261 } 262 263 var topicNames = []string{ 264 "azure-provider", 265 "basics", 266 "commands", 267 "constraints", 268 "ec2-provider", 269 "global-options", 270 "glossary", 271 "hpcloud-provider", 272 "local-provider", 273 "logging", 274 "openstack-provider", 275 "plugins", 276 "topics", 277 } 278 279 func (s *MainSuite) TestHelpTopics(c *gc.C) { 280 // Check that we have correctly registered all the topics 281 // by checking the help output. 282 defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir())) 283 out := badrun(c, 0, "help", "topics") 284 lines := strings.Split(out, "\n") 285 var names []string 286 for _, line := range lines { 287 f := strings.Fields(line) 288 if len(f) == 0 { 289 continue 290 } 291 names = append(names, f[0]) 292 } 293 // The names should be output in alphabetical order, so don't sort. 294 c.Assert(names, gc.DeepEquals, topicNames) 295 } 296 297 var globalFlags = []string{ 298 "--debug .*", 299 "--description .*", 300 "-h, --help .*", 301 "--log-file .*", 302 "--logging-config .*", 303 "-q, --quiet .*", 304 "--show-log .*", 305 "-v, --verbose .*", 306 } 307 308 func (s *MainSuite) TestHelpGlobalOptions(c *gc.C) { 309 // Check that we have correctly registered all the topics 310 // by checking the help output. 311 defer osenv.SetJujuHome(osenv.SetJujuHome(c.MkDir())) 312 out := badrun(c, 0, "help", "global-options") 313 c.Assert(out, gc.Matches, `Global Options 314 315 These options may be used with any command, and may appear in front of any 316 command\.(.|\n)*`) 317 lines := strings.Split(out, "\n") 318 var flags []string 319 for _, line := range lines { 320 f := strings.Fields(line) 321 if len(f) == 0 || line[0] != '-' { 322 continue 323 } 324 flags = append(flags, line) 325 } 326 c.Assert(len(flags), gc.Equals, len(globalFlags)) 327 for i, line := range flags { 328 c.Assert(line, gc.Matches, globalFlags[i]) 329 } 330 } 331 332 type commands []cmd.Command 333 334 func (r *commands) Register(c cmd.Command) { 335 *r = append(*r, c) 336 } 337 338 func (s *MainSuite) TestEnvironCommands(c *gc.C) { 339 var commands commands 340 registerCommands(&commands, testing.Context(c)) 341 // There should not be any EnvironCommands registered. 342 // EnvironCommands must be wrapped using envcmd.Wrap. 343 for _, cmd := range commands { 344 c.Logf("%v", cmd.Info().Name) 345 c.Check(cmd, gc.Not(gc.FitsTypeOf), envcmd.EnvironCommand(&BootstrapCommand{})) 346 } 347 }