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  }