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