github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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 "launchpad.net/gnuflag" 18 gc "launchpad.net/gocheck" 19 20 "launchpad.net/juju-core/cmd" 21 "launchpad.net/juju-core/environs" 22 "launchpad.net/juju-core/testing" 23 "launchpad.net/juju-core/testing/testbase" 24 "launchpad.net/juju-core/worker/deployer" 25 "launchpad.net/juju-core/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 := testbase.PatchValue(&deployer.InitDir, mkdtemp("juju-worker-deployer")) 56 defer restore() 57 58 // Change the path to "juju-run", so that the 59 // tests don't try to write to /usr/local/bin. 60 jujuRun = mktemp("juju-run", "") 61 defer os.Remove(jujuRun) 62 63 // Create a CA certificate available for all tests. 64 caCertFile = mktemp("juju-test-cert", testing.CACert) 65 defer os.Remove(caCertFile) 66 67 testing.MgoTestPackage(t) 68 } 69 70 type MainSuite struct{} 71 72 var _ = gc.Suite(&MainSuite{}) 73 74 var flagRunMain = flag.Bool("run-main", false, "Run the application's main function for recursive testing") 75 76 // Reentrancy point for testing (something as close as possible to) the jujud 77 // tool itself. 78 func TestRunMain(t *stdtesting.T) { 79 if *flagRunMain { 80 Main(flag.Args()) 81 } 82 } 83 84 func checkMessage(c *gc.C, msg string, cmd ...string) { 85 args := append([]string{"-test.run", "TestRunMain", "-run-main", "--", "jujud"}, cmd...) 86 c.Logf("check %#v", args) 87 ps := exec.Command(os.Args[0], args...) 88 output, err := ps.CombinedOutput() 89 c.Logf(string(output)) 90 c.Assert(err, gc.ErrorMatches, "exit status 2") 91 lines := strings.Split(string(output), "\n") 92 c.Assert(lines[len(lines)-2], gc.Equals, "error: "+msg) 93 } 94 95 func (s *MainSuite) TestParseErrors(c *gc.C) { 96 // Check all the obvious parse errors 97 checkMessage(c, "unrecognized command: jujud cavitate", "cavitate") 98 msgf := "flag provided but not defined: --cheese" 99 checkMessage(c, msgf, "--cheese", "cavitate") 100 101 cmds := []string{"bootstrap-state", "unit", "machine"} 102 for _, cmd := range cmds { 103 checkMessage(c, msgf, cmd, "--cheese") 104 } 105 106 msga := `unrecognized args: ["toastie"]` 107 checkMessage(c, msga, 108 "bootstrap-state", 109 "--env-config", b64yaml{"blah": "blah"}.encode(), 110 "toastie") 111 checkMessage(c, msga, "unit", 112 "--unit-name", "un/0", 113 "toastie") 114 checkMessage(c, msga, "machine", 115 "--machine-id", "42", 116 "toastie") 117 } 118 119 var expectedProviders = []string{ 120 "ec2", 121 "maas", 122 "openstack", 123 } 124 125 func (s *MainSuite) TestProvidersAreRegistered(c *gc.C) { 126 // check that all the expected providers are registered 127 for _, name := range expectedProviders { 128 _, err := environs.Provider(name) 129 c.Assert(err, gc.IsNil) 130 } 131 } 132 133 type RemoteCommand struct { 134 cmd.CommandBase 135 msg string 136 } 137 138 var expectUsage = `usage: remote [options] 139 purpose: test jujuc 140 141 options: 142 --error (= "") 143 if set, fail 144 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 fmt.Fprintf(ctx.Stdout, "success!\n") 169 return nil 170 } 171 172 func run(c *gc.C, sockPath string, contextId string, exit int, cmd ...string) string { 173 args := append([]string{"-test.run", "TestRunMain", "-run-main", "--"}, cmd...) 174 c.Logf("check %v %#v", os.Args[0], args) 175 ps := exec.Command(os.Args[0], args...) 176 ps.Dir = c.MkDir() 177 ps.Env = []string{ 178 fmt.Sprintf("JUJU_AGENT_SOCKET=%s", sockPath), 179 fmt.Sprintf("JUJU_CONTEXT_ID=%s", contextId), 180 // Code that imports launchpad.net/juju-core/testing needs to 181 // be able to find that module at runtime (via build.Import), 182 // so we have to preserve that env variable. 183 os.ExpandEnv("GOPATH=${GOPATH}"), 184 } 185 output, err := ps.CombinedOutput() 186 if exit == 0 { 187 c.Assert(err, gc.IsNil) 188 } else { 189 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("exit status %d", exit)) 190 } 191 return string(output) 192 } 193 194 type JujuCMainSuite struct { 195 sockPath string 196 server *jujuc.Server 197 } 198 199 var _ = gc.Suite(&JujuCMainSuite{}) 200 201 func (s *JujuCMainSuite) SetUpSuite(c *gc.C) { 202 factory := func(contextId, cmdName string) (cmd.Command, error) { 203 if contextId != "bill" { 204 return nil, fmt.Errorf("bad context: %s", contextId) 205 } 206 if cmdName != "remote" { 207 return nil, fmt.Errorf("bad command: %s", cmdName) 208 } 209 return &RemoteCommand{}, nil 210 } 211 s.sockPath = filepath.Join(c.MkDir(), "test.sock") 212 srv, err := jujuc.NewServer(factory, s.sockPath) 213 c.Assert(err, gc.IsNil) 214 s.server = srv 215 go func() { 216 if err := s.server.Run(); err != nil { 217 c.Fatalf("server died: %s", err) 218 } 219 }() 220 } 221 222 func (s *JujuCMainSuite) TearDownSuite(c *gc.C) { 223 s.server.Close() 224 } 225 226 var argsTests = []struct { 227 args []string 228 code int 229 output string 230 }{ 231 {[]string{"jujuc", "whatever"}, 2, jujudDoc + "error: jujuc should not be called directly\n"}, 232 {[]string{"remote"}, 0, "success!\n"}, 233 {[]string{"/path/to/remote"}, 0, "success!\n"}, 234 {[]string{"remote", "--help"}, 0, expectUsage}, 235 {[]string{"unknown"}, 1, "error: bad request: bad command: unknown\n"}, 236 {[]string{"remote", "--error", "borken"}, 1, "error: borken\n"}, 237 {[]string{"remote", "--unknown"}, 2, "error: flag provided but not defined: --unknown\n"}, 238 {[]string{"remote", "unwanted"}, 2, `error: unrecognized args: ["unwanted"]` + "\n"}, 239 } 240 241 func (s *JujuCMainSuite) TestArgs(c *gc.C) { 242 for _, t := range argsTests { 243 fmt.Println(t.args) 244 output := run(c, s.sockPath, "bill", t.code, t.args...) 245 c.Assert(output, gc.Equals, t.output) 246 } 247 } 248 249 func (s *JujuCMainSuite) TestNoClientId(c *gc.C) { 250 output := run(c, s.sockPath, "", 1, "remote") 251 c.Assert(output, gc.Equals, "error: JUJU_CONTEXT_ID not set\n") 252 } 253 254 func (s *JujuCMainSuite) TestBadClientId(c *gc.C) { 255 output := run(c, s.sockPath, "ben", 1, "remote") 256 c.Assert(output, gc.Equals, "error: bad request: bad context: ben\n") 257 } 258 259 func (s *JujuCMainSuite) TestNoSockPath(c *gc.C) { 260 output := run(c, "", "bill", 1, "remote") 261 c.Assert(output, gc.Equals, "error: JUJU_AGENT_SOCKET not set\n") 262 } 263 264 func (s *JujuCMainSuite) TestBadSockPath(c *gc.C) { 265 badSock := filepath.Join(c.MkDir(), "bad.sock") 266 output := run(c, badSock, "bill", 1, "remote") 267 err := fmt.Sprintf("error: dial unix %s: .*\n", badSock) 268 c.Assert(output, gc.Matches, err) 269 }