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