github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/jujud/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 "errors" 9 "flag" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "runtime" 17 "strings" 18 stdtesting "testing" 19 20 "github.com/juju/cmd" 21 jc "github.com/juju/testing/checkers" 22 gc "gopkg.in/check.v1" 23 "launchpad.net/gnuflag" 24 25 agentcmd "github.com/juju/juju/cmd/jujud/agent" 26 "github.com/juju/juju/environs" 27 "github.com/juju/juju/juju/names" 28 coretesting "github.com/juju/juju/testing" 29 "github.com/juju/juju/worker/uniter/runner/jujuc" 30 ) 31 32 var caCertFile string 33 34 func mkdtemp(prefix string) string { 35 d, err := ioutil.TempDir("", prefix) 36 if err != nil { 37 panic(err) 38 } 39 return d 40 } 41 42 func mktemp(prefix string, content string) string { 43 f, err := ioutil.TempFile("", prefix) 44 if err != nil { 45 panic(err) 46 } 47 _, err = f.WriteString(content) 48 if err != nil { 49 panic(err) 50 } 51 f.Close() 52 return f.Name() 53 } 54 55 func TestPackage(t *stdtesting.T) { 56 // TODO(waigani) 2014-03-19 bug 1294458 57 // Refactor to use base suites 58 59 // Change the path to "juju-run", so that the 60 // tests don't try to write to /usr/local/bin. 61 agentcmd.JujuRun = mktemp("juju-run", "") 62 defer os.Remove(agentcmd.JujuRun) 63 64 // Create a CA certificate available for all tests. 65 caCertFile = mktemp("juju-test-cert", coretesting.CACert) 66 defer os.Remove(caCertFile) 67 68 coretesting.MgoTestPackage(t) 69 } 70 71 type MainSuite struct{} 72 73 var _ = gc.Suite(&MainSuite{}) 74 75 var flagRunMain = flag.Bool("run-main", false, "Run the application's main function for recursive testing") 76 77 // Reentrancy point for testing (something as close as possible to) the jujud 78 // tool itself. 79 func TestRunMain(t *stdtesting.T) { 80 if *flagRunMain { 81 MainWrapper(flag.Args()) 82 } 83 } 84 85 func checkMessage(c *gc.C, msg string, cmd ...string) { 86 args := append([]string{"-test.run", "TestRunMain", "-run-main", "--", names.Jujud}, cmd...) 87 c.Logf("check %#v", args) 88 ps := exec.Command(os.Args[0], args...) 89 output, err := ps.CombinedOutput() 90 c.Logf(string(output)) 91 c.Assert(err, gc.ErrorMatches, "exit status 2") 92 lines := strings.Split(string(output), "\n") 93 c.Assert(lines[len(lines)-2], gc.Equals, "error: "+msg) 94 } 95 96 func (s *MainSuite) TestParseErrors(c *gc.C) { 97 // Check all the obvious parse errors 98 checkMessage(c, "unrecognized command: jujud cavitate", "cavitate") 99 msgf := "flag provided but not defined: --cheese" 100 checkMessage(c, msgf, "--cheese", "cavitate") 101 102 cmds := []string{"bootstrap-state", "unit", "machine"} 103 for _, cmd := range cmds { 104 checkMessage(c, msgf, cmd, "--cheese") 105 } 106 107 msga := `unrecognized args: ["toastie"]` 108 checkMessage(c, msga, 109 "bootstrap-state", 110 "--env-config", b64yaml{"blah": "blah"}.encode(), 111 "--instance-id", "inst", 112 "toastie") 113 checkMessage(c, msga, "unit", 114 "--unit-name", "un/0", 115 "toastie") 116 checkMessage(c, msga, "machine", 117 "--machine-id", "42", 118 "toastie") 119 } 120 121 var expectedProviders = []string{ 122 "ec2", 123 "maas", 124 "openstack", 125 } 126 127 func (s *MainSuite) TestProvidersAreRegistered(c *gc.C) { 128 // check that all the expected providers are registered 129 for _, name := range expectedProviders { 130 _, err := environs.Provider(name) 131 c.Assert(err, jc.ErrorIsNil) 132 } 133 } 134 135 type RemoteCommand struct { 136 cmd.CommandBase 137 msg string 138 } 139 140 var expectUsage = `usage: remote [options] 141 purpose: test jujuc 142 143 options: 144 --error (= "") 145 if set, fail 146 147 here is some documentation 148 ` 149 150 func (c *RemoteCommand) Info() *cmd.Info { 151 return &cmd.Info{ 152 Name: "remote", 153 Purpose: "test jujuc", 154 Doc: "here is some documentation", 155 } 156 } 157 158 func (c *RemoteCommand) SetFlags(f *gnuflag.FlagSet) { 159 f.StringVar(&c.msg, "error", "", "if set, fail") 160 } 161 162 func (c *RemoteCommand) Init(args []string) error { 163 return cmd.CheckEmpty(args) 164 } 165 166 func (c *RemoteCommand) Run(ctx *cmd.Context) error { 167 if c.msg != "" { 168 return errors.New(c.msg) 169 } 170 n, err := io.Copy(ctx.Stdout, ctx.Stdin) 171 if err != nil { 172 return err 173 } 174 if n == 0 { 175 fmt.Fprintf(ctx.Stdout, "success!\n") 176 } 177 return nil 178 } 179 180 func run(c *gc.C, sockPath string, contextId string, exit int, stdin []byte, cmd ...string) string { 181 args := append([]string{"-test.run", "TestRunMain", "-run-main", "--"}, cmd...) 182 c.Logf("check %v %#v", os.Args[0], args) 183 ps := exec.Command(os.Args[0], args...) 184 ps.Stdin = bytes.NewBuffer(stdin) 185 ps.Dir = c.MkDir() 186 ps.Env = []string{ 187 fmt.Sprintf("JUJU_AGENT_SOCKET=%s", sockPath), 188 fmt.Sprintf("JUJU_CONTEXT_ID=%s", contextId), 189 // Code that imports github.com/juju/juju/testing needs to 190 // be able to find that module at runtime (via build.Import), 191 // so we have to preserve that env variable. 192 os.ExpandEnv("GOPATH=${GOPATH}"), 193 } 194 output, err := ps.CombinedOutput() 195 if exit == 0 { 196 c.Assert(err, jc.ErrorIsNil) 197 } else { 198 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("exit status %d", exit)) 199 } 200 return string(output) 201 } 202 203 type JujuCMainSuite struct { 204 sockPath string 205 server *jujuc.Server 206 } 207 208 var _ = gc.Suite(&JujuCMainSuite{}) 209 210 func osDependentSockPath(c *gc.C) string { 211 sockPath := filepath.Join(c.MkDir(), "test.sock") 212 if runtime.GOOS == "windows" { 213 return `\\.\pipe` + sockPath[2:] 214 } 215 return sockPath 216 } 217 218 func (s *JujuCMainSuite) SetUpSuite(c *gc.C) { 219 factory := func(contextId, cmdName string) (cmd.Command, error) { 220 if contextId != "bill" { 221 return nil, fmt.Errorf("bad context: %s", contextId) 222 } 223 if cmdName != "remote" { 224 return nil, fmt.Errorf("bad command: %s", cmdName) 225 } 226 return &RemoteCommand{}, nil 227 } 228 s.sockPath = osDependentSockPath(c) 229 srv, err := jujuc.NewServer(factory, s.sockPath) 230 c.Assert(err, jc.ErrorIsNil) 231 s.server = srv 232 go func() { 233 if err := s.server.Run(); err != nil { 234 c.Fatalf("server died: %s", err) 235 } 236 }() 237 } 238 239 func (s *JujuCMainSuite) TearDownSuite(c *gc.C) { 240 s.server.Close() 241 } 242 243 var argsTests = []struct { 244 args []string 245 code int 246 output string 247 }{ 248 {[]string{"jujuc", "whatever"}, 2, jujudDoc + "error: jujuc should not be called directly\n"}, 249 {[]string{"remote"}, 0, "success!\n"}, 250 {[]string{"/path/to/remote"}, 0, "success!\n"}, 251 {[]string{"remote", "--help"}, 0, expectUsage}, 252 {[]string{"unknown"}, 1, "error: bad request: bad command: unknown\n"}, 253 {[]string{"remote", "--error", "borken"}, 1, "error: borken\n"}, 254 {[]string{"remote", "--unknown"}, 2, "error: flag provided but not defined: --unknown\n"}, 255 {[]string{"remote", "unwanted"}, 2, `error: unrecognized args: ["unwanted"]` + "\n"}, 256 } 257 258 func (s *JujuCMainSuite) TestArgs(c *gc.C) { 259 if runtime.GOOS == "windows" { 260 c.Skip("issue 1403084: test panics on CryptAcquireContext on windows") 261 } 262 for _, t := range argsTests { 263 c.Log(t.args) 264 output := run(c, s.sockPath, "bill", t.code, nil, t.args...) 265 c.Assert(output, gc.Equals, t.output) 266 } 267 } 268 269 func (s *JujuCMainSuite) TestNoClientId(c *gc.C) { 270 if runtime.GOOS == "windows" { 271 c.Skip("issue 1403084: test panics on CryptAcquireContext on windows") 272 } 273 output := run(c, s.sockPath, "", 1, nil, "remote") 274 c.Assert(output, gc.Equals, "error: JUJU_CONTEXT_ID not set\n") 275 } 276 277 func (s *JujuCMainSuite) TestBadClientId(c *gc.C) { 278 if runtime.GOOS == "windows" { 279 c.Skip("issue 1403084: test panics on CryptAcquireContext on windows") 280 } 281 output := run(c, s.sockPath, "ben", 1, nil, "remote") 282 c.Assert(output, gc.Equals, "error: bad request: bad context: ben\n") 283 } 284 285 func (s *JujuCMainSuite) TestNoSockPath(c *gc.C) { 286 if runtime.GOOS == "windows" { 287 c.Skip("issue 1403084: test panics on CryptAcquireContext on windows") 288 } 289 output := run(c, "", "bill", 1, nil, "remote") 290 c.Assert(output, gc.Equals, "error: JUJU_AGENT_SOCKET not set\n") 291 } 292 293 func (s *JujuCMainSuite) TestBadSockPath(c *gc.C) { 294 if runtime.GOOS == "windows" { 295 c.Skip("issue 1403084: test panics on CryptAcquireContext on windows") 296 } 297 badSock := filepath.Join(c.MkDir(), "bad.sock") 298 output := run(c, badSock, "bill", 1, nil, "remote") 299 err := fmt.Sprintf("error: dial unix %s: .*\n", badSock) 300 c.Assert(output, gc.Matches, err) 301 } 302 303 func (s *JujuCMainSuite) TestStdin(c *gc.C) { 304 if runtime.GOOS == "windows" { 305 c.Skip("issue 1403084: test panics on CryptAcquireContext on windows") 306 } 307 output := run(c, s.sockPath, "bill", 0, []byte("some standard input"), "remote") 308 c.Assert(output, gc.Equals, "some standard input") 309 }