launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/jujuc/server_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package jujuc_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"launchpad.net/errgo/errors"
     9  	"net/rpc"
    10  	"os"
    11  	"path/filepath"
    12  	"sync"
    13  	"time"
    14  
    15  	"launchpad.net/gnuflag"
    16  	gc "launchpad.net/gocheck"
    17  
    18  	"launchpad.net/juju-core/cmd"
    19  	"launchpad.net/juju-core/testing"
    20  	jc "launchpad.net/juju-core/testing/checkers"
    21  	"launchpad.net/juju-core/testing/testbase"
    22  	"launchpad.net/juju-core/utils/exec"
    23  	"launchpad.net/juju-core/worker/uniter/jujuc"
    24  )
    25  
    26  type RpcCommand struct {
    27  	cmd.CommandBase
    28  	Value string
    29  	Slow  bool
    30  }
    31  
    32  func (c *RpcCommand) Info() *cmd.Info {
    33  	return &cmd.Info{
    34  		Name:    "remote",
    35  		Purpose: "act at a distance",
    36  		Doc:     "blah doc",
    37  	}
    38  }
    39  
    40  func (c *RpcCommand) SetFlags(f *gnuflag.FlagSet) {
    41  	f.StringVar(&c.Value, "value", "", "doc")
    42  	f.BoolVar(&c.Slow, "slow", false, "doc")
    43  }
    44  
    45  func (c *RpcCommand) Init(args []string) error {
    46  	return cmd.CheckEmpty(args)
    47  }
    48  
    49  func (c *RpcCommand) Run(ctx *cmd.Context) error {
    50  	if c.Value == "error" {
    51  		return errors.New("blam")
    52  	}
    53  	if c.Slow {
    54  		time.Sleep(testing.ShortWait)
    55  		return nil
    56  	}
    57  	ctx.Stdout.Write([]byte("eye of newt\n"))
    58  	ctx.Stderr.Write([]byte("toe of frog\n"))
    59  	return ioutil.WriteFile(ctx.AbsPath("local"), []byte(c.Value), 0644)
    60  }
    61  
    62  func factory(contextId, cmdName string) (cmd.Command, error) {
    63  	if contextId != "validCtx" {
    64  		return nil, errors.Newf("unknown context %q", contextId)
    65  	}
    66  	if cmdName != "remote" {
    67  		return nil, errors.Newf("unknown command %q", cmdName)
    68  	}
    69  	return &RpcCommand{}, nil
    70  }
    71  
    72  type ServerSuite struct {
    73  	testbase.LoggingSuite
    74  	server   *jujuc.Server
    75  	sockPath string
    76  	err      chan error
    77  }
    78  
    79  var _ = gc.Suite(&ServerSuite{})
    80  
    81  func (s *ServerSuite) SetUpTest(c *gc.C) {
    82  	s.LoggingSuite.SetUpTest(c)
    83  	s.sockPath = filepath.Join(c.MkDir(), "test.sock")
    84  	srv, err := jujuc.NewServer(factory, s.sockPath)
    85  	c.Assert(err, gc.IsNil)
    86  	c.Assert(srv, gc.NotNil)
    87  	s.server = srv
    88  	s.err = make(chan error)
    89  	go func() { s.err <- s.server.Run() }()
    90  }
    91  
    92  func (s *ServerSuite) TearDownTest(c *gc.C) {
    93  	s.server.Close()
    94  	c.Assert(<-s.err, gc.IsNil)
    95  	_, err := os.Open(s.sockPath)
    96  	c.Assert(err, jc.Satisfies, os.IsNotExist)
    97  	s.LoggingSuite.TearDownTest(c)
    98  }
    99  
   100  func (s *ServerSuite) Call(c *gc.C, req jujuc.Request) (resp exec.ExecResponse, err error) {
   101  	client, err := rpc.Dial("unix", s.sockPath)
   102  	c.Assert(err, gc.IsNil)
   103  	defer client.Close()
   104  	err = client.Call("Jujuc.Main", req, &resp)
   105  	return resp, err
   106  }
   107  
   108  func (s *ServerSuite) TestHappyPath(c *gc.C) {
   109  	dir := c.MkDir()
   110  	resp, err := s.Call(c, jujuc.Request{
   111  		"validCtx", dir, "remote", []string{"--value", "something"},
   112  	})
   113  	c.Assert(err, gc.IsNil)
   114  	c.Assert(resp.Code, gc.Equals, 0)
   115  	c.Assert(string(resp.Stdout), gc.Equals, "eye of newt\n")
   116  	c.Assert(string(resp.Stderr), gc.Equals, "toe of frog\n")
   117  	content, err := ioutil.ReadFile(filepath.Join(dir, "local"))
   118  	c.Assert(err, gc.IsNil)
   119  	c.Assert(string(content), gc.Equals, "something")
   120  }
   121  
   122  func (s *ServerSuite) TestLocks(c *gc.C) {
   123  	var wg sync.WaitGroup
   124  	t0 := time.Now()
   125  	for i := 0; i < 4; i++ {
   126  		wg.Add(1)
   127  		go func() {
   128  			dir := c.MkDir()
   129  			resp, err := s.Call(c, jujuc.Request{
   130  				"validCtx", dir, "remote", []string{"--slow"},
   131  			})
   132  			c.Assert(err, gc.IsNil)
   133  			c.Assert(resp.Code, gc.Equals, 0)
   134  			wg.Done()
   135  		}()
   136  	}
   137  	wg.Wait()
   138  	t1 := time.Now()
   139  	c.Assert(t0.Add(4*testing.ShortWait).Before(t1), gc.Equals, true)
   140  }
   141  
   142  func (s *ServerSuite) TestBadCommandName(c *gc.C) {
   143  	dir := c.MkDir()
   144  	_, err := s.Call(c, jujuc.Request{"validCtx", dir, "", nil})
   145  	c.Assert(err, gc.ErrorMatches, "bad request: command not specified")
   146  	_, err = s.Call(c, jujuc.Request{"validCtx", dir, "witchcraft", nil})
   147  	c.Assert(err, gc.ErrorMatches, `bad request: unknown command "witchcraft"`)
   148  }
   149  
   150  func (s *ServerSuite) TestBadDir(c *gc.C) {
   151  	for _, req := range []jujuc.Request{
   152  		{"validCtx", "", "anything", nil},
   153  		{"validCtx", "foo/bar", "anything", nil},
   154  	} {
   155  		_, err := s.Call(c, req)
   156  		c.Assert(err, gc.ErrorMatches, "bad request: Dir is not absolute")
   157  	}
   158  }
   159  
   160  func (s *ServerSuite) TestBadContextId(c *gc.C) {
   161  	_, err := s.Call(c, jujuc.Request{"whatever", c.MkDir(), "remote", nil})
   162  	c.Assert(err, gc.ErrorMatches, `bad request: unknown context "whatever"`)
   163  }
   164  
   165  func (s *ServerSuite) AssertBadCommand(c *gc.C, args []string, code int) exec.ExecResponse {
   166  	resp, err := s.Call(c, jujuc.Request{"validCtx", c.MkDir(), args[0], args[1:]})
   167  	c.Assert(err, gc.IsNil)
   168  	c.Assert(resp.Code, gc.Equals, code)
   169  	return resp
   170  }
   171  
   172  func (s *ServerSuite) TestParseError(c *gc.C) {
   173  	resp := s.AssertBadCommand(c, []string{"remote", "--cheese"}, 2)
   174  	c.Assert(string(resp.Stdout), gc.Equals, "")
   175  	c.Assert(string(resp.Stderr), gc.Equals, "error: flag provided but not defined: --cheese\n")
   176  }
   177  
   178  func (s *ServerSuite) TestBrokenCommand(c *gc.C) {
   179  	resp := s.AssertBadCommand(c, []string{"remote", "--value", "error"}, 1)
   180  	c.Assert(string(resp.Stdout), gc.Equals, "")
   181  	c.Assert(string(resp.Stderr), gc.Equals, "error: blam\n")
   182  }
   183  
   184  type NewCommandSuite struct {
   185  	ContextSuite
   186  }
   187  
   188  var _ = gc.Suite(&NewCommandSuite{})
   189  
   190  var newCommandTests = []struct {
   191  	name string
   192  	err  string
   193  }{
   194  	{"close-port", ""},
   195  	{"config-get", ""},
   196  	{"juju-log", ""},
   197  	{"open-port", ""},
   198  	{"relation-get", ""},
   199  	{"relation-ids", ""},
   200  	{"relation-list", ""},
   201  	{"relation-set", ""},
   202  	{"unit-get", ""},
   203  	{"random", "unknown command: random"},
   204  }
   205  
   206  func (s *NewCommandSuite) TestNewCommand(c *gc.C) {
   207  	ctx := s.GetHookContext(c, 0, "")
   208  	for _, t := range newCommandTests {
   209  		com, err := jujuc.NewCommand(ctx, t.name)
   210  		if t.err == "" {
   211  			// At this level, just check basic sanity; commands are tested in
   212  			// more detail elsewhere.
   213  			c.Assert(err, gc.IsNil)
   214  			c.Assert(com.Info().Name, gc.Equals, t.name)
   215  		} else {
   216  			c.Assert(com, gc.IsNil)
   217  			c.Assert(err, gc.ErrorMatches, t.err)
   218  		}
   219  	}
   220  }