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