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  }