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 }