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