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 }