github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/pkg/execctl/cmd_test.go (about) 1 package execctl 2 3 import ( 4 "bufio" 5 "os" 6 "testing" 7 8 . "github.com/onsi/ginkgo" 9 . "github.com/onsi/gomega" 10 ) 11 12 var ( 13 // script that can be interrupted and does some cleanup if it is 14 script = []byte(` 15 cleanup() { 16 echo "interrupted" 17 sleep 1 18 echo "exited" 19 exit 0 20 } 21 22 trap cleanup INT 23 24 sleep .5 25 26 echo "running" 27 sleep 1 28 echo "exited" 29 exit 0 30 `) 31 // script that just errors 32 scriptErr = []byte(` 33 exit -1 34 `) 35 // script that prints the user-provided input to stderr 36 scriptEchoErr = []byte(` 37 read var 38 echo $var >&2 39 exit 0 40 `) 41 ) 42 43 var ( 44 tmpDir string 45 scriptPath string 46 scriptErrPath string 47 scriptEchoErrPath string 48 ) 49 50 var _ = BeforeSuite(func() { 51 var err error 52 tmpDir, err = os.MkdirTemp("", "execctl-*") 53 Expect(err).NotTo(HaveOccurred()) 54 55 scriptPath, err = createTempScript(tmpDir, script) 56 Expect(err).NotTo(HaveOccurred()) 57 Expect(scriptPath).NotTo(BeEmpty()) 58 59 scriptErrPath, err = createTempScript(tmpDir, scriptErr) 60 Expect(err).NotTo(HaveOccurred()) 61 Expect(scriptErrPath).NotTo(BeEmpty()) 62 63 scriptEchoErrPath, err = createTempScript(tmpDir, scriptEchoErr) 64 Expect(err).NotTo(HaveOccurred()) 65 Expect(scriptErrPath).NotTo(BeEmpty()) 66 }) 67 68 var _ = AfterSuite(func() { 69 err := os.RemoveAll(tmpDir) 70 Expect(err).NotTo(HaveOccurred()) 71 }) 72 73 var _ = Describe("Test command start and wait", func() { 74 var cmd *Cmd 75 var r *bufio.Reader 76 77 It("command started", func() { 78 var err error 79 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptPath)) 80 Expect(err).NotTo(HaveOccurred()) 81 82 err = cmd.Start() 83 Expect(err).NotTo(HaveOccurred()) 84 85 r = bufio.NewReader(cmd.Stdout()) 86 }) 87 88 It("command exited gracefully", func() { 89 err := cmd.Wait() 90 Expect(err).NotTo(HaveOccurred()) 91 92 l, err := readLine(r) 93 Expect(err).NotTo(HaveOccurred()) 94 Expect(l).To(Equal("running")) 95 96 l, err = readLine(r) 97 Expect(err).NotTo(HaveOccurred()) 98 Expect(l).To(Equal("exited")) 99 }) 100 }) 101 102 var _ = Describe("Test command stop", func() { 103 var cmd *Cmd 104 var r *bufio.Reader 105 106 It("command started", func() { 107 var err error 108 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptPath)) 109 Expect(err).NotTo(HaveOccurred()) 110 111 err = cmd.Start() 112 Expect(err).NotTo(HaveOccurred()) 113 114 r = bufio.NewReader(cmd.Stdout()) 115 }) 116 117 It("command interrupted", func() { 118 // allow the command to start running but don't give it enough time to exit gracefully 119 l, err := readLine(r) 120 Expect(err).NotTo(HaveOccurred()) 121 Expect(l).To(Equal("running")) 122 123 err = cmd.Stop() 124 Expect(err).NotTo(HaveOccurred()) 125 126 l, err = readLine(r) 127 Expect(err).NotTo(HaveOccurred()) 128 Expect(l).To(Equal("interrupted")) 129 130 l, err = readLine(r) 131 Expect(err).NotTo(HaveOccurred()) 132 Expect(l).To(Equal("exited")) 133 }) 134 }) 135 136 var _ = Describe("Test command stop when already exited gracefully", func() { 137 var cmd *Cmd 138 var r *bufio.Reader 139 140 It("command started", func() { 141 var err error 142 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptPath)) 143 Expect(err).NotTo(HaveOccurred()) 144 145 err = cmd.Start() 146 Expect(err).NotTo(HaveOccurred()) 147 148 r = bufio.NewReader(cmd.Stdout()) 149 }) 150 151 It("command was not interrupted (it was allowed to gracefully exit)", func() { 152 // let the command exit gracefully 153 l, err := readLine(r) 154 Expect(err).NotTo(HaveOccurred()) 155 Expect(l).To(Equal("running")) 156 157 l, err = readLine(r) 158 Expect(err).NotTo(HaveOccurred()) 159 Expect(l).To(Equal("exited")) 160 161 err = cmd.Stop() 162 Expect(err).NotTo(HaveOccurred()) 163 }) 164 }) 165 166 var _ = Describe("Test command manual restart", func() { 167 var cmd *Cmd 168 var r *bufio.Reader 169 170 It("command started", func() { 171 var err error 172 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptPath)) 173 Expect(err).NotTo(HaveOccurred()) 174 175 err = cmd.Start() 176 Expect(err).NotTo(HaveOccurred()) 177 178 r = bufio.NewReader(cmd.Stdout()) 179 }) 180 181 It("command interrupted", func() { 182 // don't give the command enough time to exit gracefully 183 l, err := readLine(r) 184 Expect(err).NotTo(HaveOccurred()) 185 Expect(l).To(Equal("running")) 186 187 err = cmd.Stop() 188 Expect(err).NotTo(HaveOccurred()) 189 190 l, err = readLine(r) 191 Expect(err).NotTo(HaveOccurred()) 192 Expect(l).To(Equal("interrupted")) 193 194 l, err = readLine(r) 195 Expect(err).NotTo(HaveOccurred()) 196 Expect(l).To(Equal("exited")) 197 }) 198 199 It("command started again and exited gracefully", func() { 200 err := cmd.Start() 201 Expect(err).NotTo(HaveOccurred()) 202 203 err = cmd.Wait() 204 Expect(err).NotTo(HaveOccurred()) 205 206 l, err := readLine(r) 207 Expect(err).NotTo(HaveOccurred()) 208 Expect(l).To(Equal("running")) 209 210 l, err = readLine(r) 211 Expect(err).NotTo(HaveOccurred()) 212 Expect(l).To(Equal("exited")) 213 }) 214 }) 215 216 var _ = Describe("Test command restart", func() { 217 var cmd *Cmd 218 var r *bufio.Reader 219 220 It("command started", func() { 221 var err error 222 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptPath)) 223 Expect(err).NotTo(HaveOccurred()) 224 225 err = cmd.Start() 226 Expect(err).NotTo(HaveOccurred()) 227 228 r = bufio.NewReader(cmd.Stdout()) 229 }) 230 231 It("command is restarted (interrupted and then started and exits gracefully)", func() { 232 // interrupt the command with a restart 233 l, err := readLine(r) 234 Expect(err).NotTo(HaveOccurred()) 235 Expect(l).To(Equal("running")) 236 237 err = cmd.Restart() 238 Expect(err).NotTo(HaveOccurred()) 239 240 l, err = readLine(r) 241 Expect(err).NotTo(HaveOccurred()) 242 Expect(l).To(Equal("interrupted")) 243 244 l, err = readLine(r) 245 Expect(err).NotTo(HaveOccurred()) 246 Expect(l).To(Equal("exited")) 247 248 err = cmd.Wait() 249 Expect(err).NotTo(HaveOccurred()) 250 251 l, err = readLine(r) 252 Expect(err).NotTo(HaveOccurred()) 253 Expect(l).To(Equal("running")) 254 255 l, err = readLine(r) 256 Expect(err).NotTo(HaveOccurred()) 257 Expect(l).To(Equal("exited")) 258 }) 259 }) 260 261 var _ = Describe("Test Wait() called multiple times on command that succeeded", func() { 262 var cmd *Cmd 263 var r *bufio.Reader 264 265 It("command started", func() { 266 var err error 267 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptPath)) 268 Expect(err).NotTo(HaveOccurred()) 269 270 err = cmd.Start() 271 Expect(err).NotTo(HaveOccurred()) 272 273 r = bufio.NewReader(cmd.Stdout()) 274 }) 275 276 It("command is awaited multiple times without errors", func() { 277 err := cmd.Wait() 278 Expect(err).NotTo(HaveOccurred()) 279 280 err = cmd.Wait() 281 Expect(err).NotTo(HaveOccurred()) 282 283 l, err := readLine(r) 284 Expect(err).NotTo(HaveOccurred()) 285 Expect(l).To(Equal("running")) 286 287 l, err = readLine(r) 288 Expect(err).NotTo(HaveOccurred()) 289 Expect(l).To(Equal("exited")) 290 }) 291 }) 292 293 var _ = Describe("Test Wait() called multiple times on command that returned error", func() { 294 var cmd *Cmd 295 296 It("command started", func() { 297 var err error 298 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptErrPath)) 299 Expect(err).NotTo(HaveOccurred()) 300 301 err = cmd.Start() 302 Expect(err).NotTo(HaveOccurred()) 303 }) 304 305 It("command awaited multiple times and returned an error on all", func() { 306 err := cmd.Wait() 307 Expect(err).To(HaveOccurred()) // original interrupt error 308 309 err = cmd.Wait() 310 Expect(err).To(HaveOccurred()) // from cmd.lastErr 311 }) 312 }) 313 314 var _ = Describe("Test Stop() called multiple times on command that returned error", func() { 315 var cmd *Cmd 316 317 It("command started", func() { 318 var err error 319 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptErrPath)) 320 Expect(err).NotTo(HaveOccurred()) 321 322 err = cmd.Start() 323 Expect(err).NotTo(HaveOccurred()) 324 }) 325 326 It("command stopped multiple times and returned an error on all", func() { 327 err := cmd.Wait() 328 Expect(err).To(HaveOccurred()) // original exit error 329 330 err = cmd.Stop() 331 Expect(err).To(HaveOccurred()) // from cmd.lastErr 332 333 err = cmd.Stop() 334 Expect(err).To(HaveOccurred()) // from cmd.lastErr 335 }) 336 }) 337 338 var _ = Describe("Test Start() called multiple times", func() { 339 var cmd *Cmd 340 341 It("command started multiple times", func() { 342 var err error 343 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptErrPath)) 344 Expect(err).NotTo(HaveOccurred()) 345 346 err = cmd.Start() 347 Expect(err).NotTo(HaveOccurred()) 348 349 err = cmd.Start() 350 Expect(err).NotTo(HaveOccurred()) 351 }) 352 }) 353 354 var _ = Describe("Test Stdin() and Stderr()", func() { 355 var cmd *Cmd 356 var r *bufio.Reader 357 358 It("command started multiple times", func() { 359 var err error 360 cmd, err = NewCmd("test", "/bin/bash", WithArgs("-c", scriptEchoErrPath)) 361 Expect(err).NotTo(HaveOccurred()) 362 363 err = cmd.Start() 364 Expect(err).NotTo(HaveOccurred()) 365 366 r = bufio.NewReader(cmd.Stderr()) 367 }) 368 369 It("command echoed user input to stderr", func() { 370 in := "ping!" 371 lf := "\n" 372 373 _, err := cmd.Stdin().Write([]byte(in + lf)) 374 Expect(err).NotTo(HaveOccurred()) 375 376 err = cmd.Wait() 377 Expect(err).NotTo(HaveOccurred()) 378 379 l, err := readLine(r) 380 Expect(err).NotTo(HaveOccurred()) 381 Expect(l).To(Equal(in)) 382 }) 383 }) 384 385 func TestExecctl(t *testing.T) { 386 RegisterFailHandler(Fail) 387 RunSpecs(t, "execctl suite") 388 } 389 390 func createTempScript(dir string, b []byte) (string, error) { 391 f, err := os.CreateTemp(dir, "shell-*.sh") 392 if err != nil { 393 return "", err 394 } 395 396 path := f.Name() 397 398 _, err = f.Write(b) 399 if err != nil { 400 return "", err 401 } 402 403 err = f.Chmod(0775) 404 if err != nil { 405 return "", err 406 } 407 408 err = f.Close() 409 if err != nil { 410 return "", err 411 } 412 413 return path, nil 414 } 415 416 func readLine(r *bufio.Reader) (string, error) { 417 l, _, err := r.ReadLine() 418 if err != nil { 419 return "", err 420 } 421 return string(l), nil 422 }