github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/connhelper/commandconn/commandconn_unix_test.go (about)

     1  //go:build !windows
     2  
     3  package commandconn
     4  
     5  import (
     6  	"context"
     7  	"io"
     8  	"io/fs"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/docker/docker/pkg/process"
    13  	"gotest.tools/v3/assert"
    14  	is "gotest.tools/v3/assert/cmp"
    15  )
    16  
    17  // For https://github.com/khulnasoft/cli/pull/1014#issuecomment-409308139
    18  func TestEOFWithError(t *testing.T) {
    19  	ctx := context.TODO()
    20  	c, err := New(ctx, "sh", "-c", "echo hello; echo some error >&2; exit 42")
    21  	assert.NilError(t, err)
    22  	b := make([]byte, 32)
    23  	n, err := c.Read(b)
    24  	assert.Check(t, is.Equal(len("hello\n"), n))
    25  	assert.NilError(t, err)
    26  	n, err = c.Read(b)
    27  	assert.Check(t, is.Equal(0, n))
    28  	assert.ErrorContains(t, err, "some error")
    29  	assert.ErrorContains(t, err, "42")
    30  }
    31  
    32  func TestEOFWithoutError(t *testing.T) {
    33  	ctx := context.TODO()
    34  	c, err := New(ctx, "sh", "-c", "echo hello; echo some debug log >&2; exit 0")
    35  	assert.NilError(t, err)
    36  	b := make([]byte, 32)
    37  	n, err := c.Read(b)
    38  	assert.Check(t, is.Equal(len("hello\n"), n))
    39  	assert.NilError(t, err)
    40  	n, err = c.Read(b)
    41  	assert.Check(t, is.Equal(0, n))
    42  	assert.Check(t, is.Equal(io.EOF, err))
    43  }
    44  
    45  func TestCloseRunningCommand(t *testing.T) {
    46  	ctx := context.TODO()
    47  	done := make(chan struct{})
    48  	defer close(done)
    49  
    50  	go func() {
    51  		c, err := New(ctx, "sh", "-c", "while true; sleep 1; done")
    52  		assert.NilError(t, err)
    53  		cmdConn := c.(*commandConn)
    54  		assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
    55  
    56  		n, err := c.Write([]byte("hello"))
    57  		assert.Check(t, is.Equal(len("hello"), n))
    58  		assert.NilError(t, err)
    59  		assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
    60  
    61  		err = cmdConn.Close()
    62  		assert.NilError(t, err)
    63  		assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
    64  		done <- struct{}{}
    65  	}()
    66  
    67  	select {
    68  	case <-time.After(5 * time.Second):
    69  		t.Error("test did not finish in time")
    70  	case <-done:
    71  		break
    72  	}
    73  }
    74  
    75  func TestCloseTwice(t *testing.T) {
    76  	ctx := context.TODO()
    77  	done := make(chan struct{})
    78  	go func() {
    79  		c, err := New(ctx, "sh", "-c", "echo hello; sleep 1; exit 0")
    80  		assert.NilError(t, err)
    81  		cmdConn := c.(*commandConn)
    82  		assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
    83  
    84  		b := make([]byte, 32)
    85  		n, err := c.Read(b)
    86  		assert.Check(t, is.Equal(len("hello\n"), n))
    87  		assert.NilError(t, err)
    88  
    89  		err = cmdConn.Close()
    90  		assert.NilError(t, err)
    91  		assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
    92  
    93  		err = cmdConn.Close()
    94  		assert.NilError(t, err)
    95  		assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
    96  		done <- struct{}{}
    97  	}()
    98  
    99  	select {
   100  	case <-time.After(10 * time.Second):
   101  		t.Error("test did not finish in time")
   102  	case <-done:
   103  		break
   104  	}
   105  }
   106  
   107  func TestEOFTimeout(t *testing.T) {
   108  	ctx := context.TODO()
   109  	done := make(chan struct{})
   110  	go func() {
   111  		c, err := New(ctx, "sh", "-c", "sleep 20")
   112  		assert.NilError(t, err)
   113  		cmdConn := c.(*commandConn)
   114  		assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
   115  
   116  		cmdConn.stdout = mockStdoutEOF{}
   117  
   118  		b := make([]byte, 32)
   119  		n, err := c.Read(b)
   120  		assert.Check(t, is.Equal(0, n))
   121  		assert.ErrorContains(t, err, "did not exit after EOF")
   122  
   123  		done <- struct{}{}
   124  	}()
   125  
   126  	// after receiving an EOF, we try to kill the command
   127  	// if it doesn't exit after 10s, we throw an error
   128  	select {
   129  	case <-time.After(12 * time.Second):
   130  		t.Error("test did not finish in time")
   131  	case <-done:
   132  		break
   133  	}
   134  }
   135  
   136  type mockStdoutEOF struct{}
   137  
   138  func (mockStdoutEOF) Read(_ []byte) (int, error) {
   139  	return 0, io.EOF
   140  }
   141  
   142  func (mockStdoutEOF) Close() error {
   143  	return nil
   144  }
   145  
   146  func TestCloseWhileWriting(t *testing.T) {
   147  	ctx := context.TODO()
   148  	c, err := New(ctx, "sh", "-c", "while true; sleep 1; done")
   149  	assert.NilError(t, err)
   150  	cmdConn := c.(*commandConn)
   151  	assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
   152  
   153  	writeErrC := make(chan error)
   154  	go func() {
   155  		for {
   156  			n, err := c.Write([]byte("hello"))
   157  			if err != nil {
   158  				writeErrC <- err
   159  				return
   160  			}
   161  			assert.Equal(t, n, len("hello"))
   162  		}
   163  	}()
   164  
   165  	err = c.Close()
   166  	assert.NilError(t, err)
   167  	assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
   168  
   169  	writeErr := <-writeErrC
   170  	assert.ErrorContains(t, writeErr, "file already closed")
   171  	assert.Check(t, is.ErrorIs(writeErr, fs.ErrClosed))
   172  }
   173  
   174  func TestCloseWhileReading(t *testing.T) {
   175  	ctx := context.TODO()
   176  	c, err := New(ctx, "sh", "-c", "while true; sleep 1; done")
   177  	assert.NilError(t, err)
   178  	cmdConn := c.(*commandConn)
   179  	assert.Check(t, process.Alive(cmdConn.cmd.Process.Pid))
   180  
   181  	readErrC := make(chan error)
   182  	go func() {
   183  		for {
   184  			b := make([]byte, 32)
   185  			n, err := c.Read(b)
   186  			if err != nil {
   187  				readErrC <- err
   188  				return
   189  			}
   190  			assert.Check(t, is.Equal(0, n))
   191  		}
   192  	}()
   193  
   194  	err = cmdConn.Close()
   195  	assert.NilError(t, err)
   196  	assert.Check(t, !process.Alive(cmdConn.cmd.Process.Pid))
   197  
   198  	readErr := <-readErrC
   199  	assert.Check(t, is.ErrorIs(readErr, fs.ErrClosed))
   200  }