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  }