github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/runner/runner_test.go (about)

     1  // Copyright 2012-2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package runner_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/errors"
    15  	envtesting "github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/worker/uniter/runner"
    21  )
    22  
    23  type RunCommandSuite struct {
    24  	HookContextSuite
    25  }
    26  
    27  var _ = gc.Suite(&RunCommandSuite{})
    28  
    29  func (s *RunCommandSuite) getHookContext(c *gc.C) *runner.HookContext {
    30  	uuid, err := utils.NewUUID()
    31  	c.Assert(err, jc.ErrorIsNil)
    32  	return s.HookContextSuite.getHookContext(c, uuid.String(), -1, "", noProxies)
    33  }
    34  
    35  func (s *RunCommandSuite) TestRunCommandsEnvStdOutAndErrAndRC(c *gc.C) {
    36  	ctx := s.getHookContext(c)
    37  	paths := NewRealPaths(c)
    38  	runner := runner.NewRunner(ctx, paths)
    39  
    40  	commands := `
    41  echo $JUJU_CHARM_DIR
    42  echo this is standard err >&2
    43  exit 42
    44  `
    45  	result, err := runner.RunCommands(commands)
    46  	c.Assert(err, jc.ErrorIsNil)
    47  
    48  	c.Assert(result.Code, gc.Equals, 42)
    49  	c.Assert(string(result.Stdout), gc.Equals, paths.charm+"\n")
    50  	c.Assert(string(result.Stderr), gc.Equals, "this is standard err\n")
    51  	c.Assert(ctx.GetProcess(), gc.NotNil)
    52  }
    53  
    54  type RunHookSuite struct {
    55  	HookContextSuite
    56  }
    57  
    58  var _ = gc.Suite(&RunHookSuite{})
    59  
    60  // LineBufferSize matches the constant used when creating
    61  // the bufio line reader.
    62  const lineBufferSize = 4096
    63  
    64  var runHookTests = []struct {
    65  	summary string
    66  	relid   int
    67  	remote  string
    68  	spec    hookSpec
    69  	err     string
    70  }{
    71  	{
    72  		summary: "missing hook is not an error",
    73  		relid:   -1,
    74  	}, {
    75  		summary: "report failure to execute hook",
    76  		relid:   -1,
    77  		spec:    hookSpec{perm: 0600},
    78  		err:     `exec: .*something-happened": permission denied`,
    79  	}, {
    80  		summary: "report error indicated by hook's exit status",
    81  		relid:   -1,
    82  		spec: hookSpec{
    83  			perm: 0700,
    84  			code: 99,
    85  		},
    86  		err: "exit status 99",
    87  	}, {
    88  		summary: "output logging",
    89  		relid:   -1,
    90  		spec: hookSpec{
    91  			perm:   0700,
    92  			stdout: "stdout",
    93  			stderr: "stderr",
    94  		},
    95  	}, {
    96  		summary: "output logging with background process",
    97  		relid:   -1,
    98  		spec: hookSpec{
    99  			perm:       0700,
   100  			stdout:     "stdout",
   101  			background: "not printed",
   102  		},
   103  	}, {
   104  		summary: "long line split",
   105  		relid:   -1,
   106  		spec: hookSpec{
   107  			perm:   0700,
   108  			stdout: strings.Repeat("a", lineBufferSize+10),
   109  		},
   110  	},
   111  }
   112  
   113  func (s *RunHookSuite) TestRunHook(c *gc.C) {
   114  	uuid, err := utils.NewUUID()
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	for i, t := range runHookTests {
   117  		c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm)
   118  		ctx := s.getHookContext(c, uuid.String(), t.relid, t.remote, noProxies)
   119  		paths := NewRealPaths(c)
   120  		rnr := runner.NewRunner(ctx, paths)
   121  		var hookExists bool
   122  		if t.spec.perm != 0 {
   123  			spec := t.spec
   124  			spec.dir = "hooks"
   125  			spec.name = "something-happened"
   126  			c.Logf("makeCharm %#v", spec)
   127  			makeCharm(c, spec, paths.charm)
   128  			hookExists = true
   129  		}
   130  		t0 := time.Now()
   131  		err := rnr.RunHook("something-happened")
   132  		if t.err == "" && hookExists {
   133  			c.Assert(err, jc.ErrorIsNil)
   134  		} else if !hookExists {
   135  			c.Assert(runner.IsMissingHookError(err), jc.IsTrue)
   136  		} else {
   137  			c.Assert(err, gc.ErrorMatches, t.err)
   138  		}
   139  		if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second {
   140  			c.Errorf("background process holding up hook execution")
   141  		}
   142  	}
   143  }
   144  
   145  type MockContext struct {
   146  	runner.Context
   147  	actionData   *runner.ActionData
   148  	expectPid    int
   149  	flushBadge   string
   150  	flushFailure error
   151  	flushResult  error
   152  }
   153  
   154  func (ctx *MockContext) UnitName() string {
   155  	return "some-unit/999"
   156  }
   157  
   158  func (ctx *MockContext) HookVars(paths runner.Paths) []string {
   159  	return []string{"VAR=value"}
   160  }
   161  
   162  func (ctx *MockContext) ActionData() (*runner.ActionData, error) {
   163  	if ctx.actionData == nil {
   164  		return nil, errors.New("blam")
   165  	}
   166  	return ctx.actionData, nil
   167  }
   168  
   169  func (ctx *MockContext) SetProcess(process *os.Process) {
   170  	ctx.expectPid = process.Pid
   171  }
   172  
   173  func (ctx *MockContext) FlushContext(badge string, failure error) error {
   174  	ctx.flushBadge = badge
   175  	ctx.flushFailure = failure
   176  	return ctx.flushResult
   177  }
   178  
   179  type RunMockContextSuite struct {
   180  	envtesting.IsolationSuite
   181  	paths RealPaths
   182  }
   183  
   184  var _ = gc.Suite(&RunMockContextSuite{})
   185  
   186  func (s *RunMockContextSuite) SetUpTest(c *gc.C) {
   187  	s.IsolationSuite.SetUpTest(c)
   188  	s.paths = NewRealPaths(c)
   189  }
   190  
   191  func (s *RunMockContextSuite) assertRecordedPid(c *gc.C, expectPid int) {
   192  	path := filepath.Join(s.paths.charm, "pid")
   193  	content, err := ioutil.ReadFile(path)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	expectContent := fmt.Sprintf("%d\n", expectPid)
   196  	c.Assert(string(content), gc.Equals, expectContent)
   197  }
   198  
   199  func (s *RunMockContextSuite) TestRunHookFlushSuccess(c *gc.C) {
   200  	expectErr := errors.New("pew pew pew")
   201  	ctx := &MockContext{
   202  		flushResult: expectErr,
   203  	}
   204  	makeCharm(c, hookSpec{
   205  		dir:  "hooks",
   206  		name: "something-happened",
   207  		perm: 0700,
   208  	}, s.paths.charm)
   209  	actualErr := runner.NewRunner(ctx, s.paths).RunHook("something-happened")
   210  	c.Assert(actualErr, gc.Equals, expectErr)
   211  	c.Assert(ctx.flushBadge, gc.Equals, "something-happened")
   212  	c.Assert(ctx.flushFailure, gc.IsNil)
   213  	s.assertRecordedPid(c, ctx.expectPid)
   214  }
   215  
   216  func (s *RunMockContextSuite) TestRunHookFlushFailure(c *gc.C) {
   217  	expectErr := errors.New("pew pew pew")
   218  	ctx := &MockContext{
   219  		flushResult: expectErr,
   220  	}
   221  	makeCharm(c, hookSpec{
   222  		dir:  "hooks",
   223  		name: "something-happened",
   224  		perm: 0700,
   225  		code: 123,
   226  	}, s.paths.charm)
   227  	actualErr := runner.NewRunner(ctx, s.paths).RunHook("something-happened")
   228  	c.Assert(actualErr, gc.Equals, expectErr)
   229  	c.Assert(ctx.flushBadge, gc.Equals, "something-happened")
   230  	c.Assert(ctx.flushFailure, gc.ErrorMatches, "exit status 123")
   231  	s.assertRecordedPid(c, ctx.expectPid)
   232  }
   233  
   234  func (s *RunMockContextSuite) TestRunActionFlushSuccess(c *gc.C) {
   235  	expectErr := errors.New("pew pew pew")
   236  	ctx := &MockContext{
   237  		flushResult: expectErr,
   238  		actionData:  &runner.ActionData{},
   239  	}
   240  	makeCharm(c, hookSpec{
   241  		dir:  "actions",
   242  		name: "do-something",
   243  		perm: 0700,
   244  	}, s.paths.charm)
   245  	actualErr := runner.NewRunner(ctx, s.paths).RunAction("do-something")
   246  	c.Assert(actualErr, gc.Equals, expectErr)
   247  	c.Assert(ctx.flushBadge, gc.Equals, "do-something")
   248  	c.Assert(ctx.flushFailure, gc.IsNil)
   249  	s.assertRecordedPid(c, ctx.expectPid)
   250  }
   251  
   252  func (s *RunMockContextSuite) TestRunActionFlushFailure(c *gc.C) {
   253  	expectErr := errors.New("pew pew pew")
   254  	ctx := &MockContext{
   255  		flushResult: expectErr,
   256  		actionData:  &runner.ActionData{},
   257  	}
   258  	makeCharm(c, hookSpec{
   259  		dir:  "actions",
   260  		name: "do-something",
   261  		perm: 0700,
   262  		code: 123,
   263  	}, s.paths.charm)
   264  	actualErr := runner.NewRunner(ctx, s.paths).RunAction("do-something")
   265  	c.Assert(actualErr, gc.Equals, expectErr)
   266  	c.Assert(ctx.flushBadge, gc.Equals, "do-something")
   267  	c.Assert(ctx.flushFailure, gc.ErrorMatches, "exit status 123")
   268  	s.assertRecordedPid(c, ctx.expectPid)
   269  }
   270  
   271  func (s *RunMockContextSuite) TestRunCommandsFlushSuccess(c *gc.C) {
   272  	expectErr := errors.New("pew pew pew")
   273  	ctx := &MockContext{
   274  		flushResult: expectErr,
   275  	}
   276  	_, actualErr := runner.NewRunner(ctx, s.paths).RunCommands("echo $$ > pid")
   277  	c.Assert(actualErr, gc.Equals, expectErr)
   278  	c.Assert(ctx.flushBadge, gc.Equals, "run commands")
   279  	c.Assert(ctx.flushFailure, gc.IsNil)
   280  	s.assertRecordedPid(c, ctx.expectPid)
   281  }
   282  
   283  func (s *RunMockContextSuite) TestRunCommandsFlushFailure(c *gc.C) {
   284  	expectErr := errors.New("pew pew pew")
   285  	ctx := &MockContext{
   286  		flushResult: expectErr,
   287  	}
   288  	_, actualErr := runner.NewRunner(ctx, s.paths).RunCommands("echo $$ > pid; exit 123")
   289  	c.Assert(actualErr, gc.Equals, expectErr)
   290  	c.Assert(ctx.flushBadge, gc.Equals, "run commands")
   291  	c.Assert(ctx.flushFailure, gc.IsNil) // exit code in _ result, as tested elsewhere
   292  	s.assertRecordedPid(c, ctx.expectPid)
   293  }