github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/pkg/execctl/cmd_test.go (about)

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