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  }