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 }