github.com/jmigpin/editor@v1.6.0/util/osutil/cmd_test.go (about) 1 package osutil 2 3 import ( 4 "bytes" 5 "context" 6 "io" 7 "io/ioutil" 8 "testing" 9 "time" 10 ) 11 12 //godebug:annotatepackage 13 14 //---------- 15 16 func TestCmdRead1(t *testing.T) { 17 // wait for stdin indefinitely (correct, cmd.cmd.wait waits) 18 19 ctx := context.Background() 20 cmd := NewCmd(ctx, "sh", "-c", "sleep 1") 21 midT := 2 * time.Second 22 h := NewHanger(3 * time.Second) 23 cmd.Stdin = h // hangs 24 if err := cmd.Start(); err != nil { 25 t.Fatal(err) 26 } 27 now := time.Now() 28 go func() { 29 <-ctx.Done() 30 t.Logf("context: %v\n", time.Since(now)) 31 }() 32 if err := cmd.Wait(); err != nil { 33 t.Fatal(err) 34 } 35 dur := time.Since(now) 36 t.Logf("wait done: %v\n", dur) 37 if dur < midT { 38 t.Fatalf("cmd did end, did not wait for stdin") 39 } 40 } 41 42 func TestCmdRead2(t *testing.T) { 43 // don't wait for stdin 44 45 ctx := context.Background() 46 cmd := NewCmd(ctx, "sh", "-c", "sleep 1") 47 midT := 2 * time.Second 48 h := NewHanger(3 * time.Second) 49 //cmd.Stdin = h // hangs 50 //ipwc, _ := cmd.StdinPipe() // doesn't hang 51 //go func() { 52 // io.Copy(ipwc, h) 53 //}() 54 cmd.SetupStdio(h, nil, nil) // doesn't hang 55 if err := cmd.Start(); err != nil { 56 t.Fatal(err) 57 } 58 now := time.Now() 59 go func() { 60 <-ctx.Done() 61 t.Logf("context: %v\n", time.Since(now)) 62 }() 63 if err := cmd.Wait(); err != nil { 64 t.Fatal(err) 65 } 66 dur := time.Since(now) 67 t.Logf("wait done: %v\n", dur) 68 if dur > midT { 69 t.Fatalf("cmd waited for stdin") 70 } 71 } 72 73 func TestCmdRead2Ctx(t *testing.T) { 74 // ctx cancel should be able to stop the hang on stdin 75 76 ctx, _ := context.WithTimeout(context.Background(), 1*time.Second) 77 cmd := NewCmd(ctx, "sh", "-c", "sleep 1") 78 midT := 2 * time.Second 79 h := NewHanger(3 * time.Second) 80 //cmd.Stdin = h // hangs 81 //ipwc, _ := cmd.StdinPipe() // doesn't hang 82 //go func() { 83 // io.Copy(ipwc, h) 84 //}() 85 cmd.SetupStdio(h, nil, nil) // doesn't hang 86 if err := cmd.Start(); err != nil { 87 t.Fatal(err) 88 } 89 now := time.Now() 90 go func() { 91 <-ctx.Done() 92 t.Logf("context: %v\n", time.Since(now)) 93 }() 94 if err := cmd.Wait(); err == nil { 95 t.Fatal("expecting error") 96 } 97 dur := time.Since(now) 98 t.Logf("wait done: %v\n", dur) 99 if dur > midT { 100 t.Fatalf("cmd did not end at ctx cancel") 101 } 102 } 103 104 //---------- 105 106 func TestCmdWrite1(t *testing.T) { 107 // wait for stdout indefinitely (correct, cmd.cmd.wait waits) 108 109 ctx := context.Background() 110 cmd := NewCmd(ctx, "sh", "-c", "sleep 1; echo aaa") 111 midT := 2 * time.Second 112 h := NewHanger(3 * time.Second) 113 cmd.Stdout = h // hangs 114 if err := cmd.Start(); err != nil { 115 t.Fatal(err) 116 } 117 now := time.Now() 118 go func() { 119 <-ctx.Done() 120 t.Logf("context: %v\n", time.Since(now)) 121 }() 122 if err := cmd.Wait(); err != nil { 123 t.Fatal(err) 124 } 125 dur := time.Since(now) 126 t.Logf("wait done: %v\n", dur) 127 if dur < midT { 128 t.Fatalf("cmd did end, did not wait for stdout") 129 } 130 s := string(h.buf.Bytes()) 131 if s != "aaa\n" { 132 t.Fatalf("bad output: %v", s) 133 } 134 } 135 136 func TestCmdWrite2(t *testing.T) { 137 // wait for stdout indefinitely (correct, cmd.cmd.wait waits) 138 139 ctx := context.Background() 140 cmd := NewCmd(ctx, "sh", "-c", "sleep 1; echo aaa") 141 midT := 2 * time.Second 142 h := NewHanger(3 * time.Second) 143 //cmd.Stdout = h // hangs 144 cmd.SetupStdio(nil, h, nil) // hangs 145 if err := cmd.Start(); err != nil { 146 t.Fatal(err) 147 } 148 now := time.Now() 149 go func() { 150 <-ctx.Done() 151 t.Logf("context: %v\n", time.Since(now)) 152 }() 153 if err := cmd.Wait(); err != nil { 154 t.Fatal(err) 155 } 156 dur := time.Since(now) 157 t.Logf("wait done: %v\n", dur) 158 if dur < midT { 159 t.Fatalf("cmd did end, did not wait for stdout") 160 } 161 s := string(h.buf.Bytes()) 162 if s != "aaa\n" { 163 t.Fatalf("bad output: %v", s) 164 } 165 } 166 167 func TestCmdWrite2Ctx(t *testing.T) { 168 // ctx cancel should be able to stop the hang on stdout (correct, cmd.cmd cancels and sends kill sig) 169 170 ctx, _ := context.WithTimeout(context.Background(), 1*time.Second) 171 midT := 2 * time.Second 172 cmd := NewCmd(ctx, "sh", "-c", "sleep 3; echo aaa") 173 h := NewHanger(4 * time.Second) 174 //cmd.Stdout = h // doesn't hang 175 cmd.SetupStdio(nil, h, nil) // doesn't hang 176 if err := cmd.Start(); err != nil { 177 t.Fatal(err) 178 } 179 now := time.Now() 180 go func() { 181 <-ctx.Done() 182 t.Logf("context: %v\n", time.Since(now)) 183 }() 184 if err := cmd.Wait(); err == nil { 185 t.Fatal("expecting error") 186 } else { 187 t.Logf("err: %v", err) 188 } 189 dur := time.Since(now) 190 t.Logf("wait done: %v\n", dur) 191 if dur > midT { 192 t.Fatalf("cmd did not end at ctx cancel") 193 } 194 s := string(h.buf.Bytes()) 195 if s != "" { 196 t.Fatalf("bad output: %v", s) 197 } 198 } 199 200 //---------- 201 202 func TestCmdKill(t *testing.T) { 203 // killing a process will still wait for the input to complete (correct, cmd.cmd.wait waits) 204 // the hanger used in this test is sleeping, but in normal conditions, its write attempt would fail when the kill happens 205 206 ctx := context.Background() 207 killT := 1 * time.Second 208 cmd := NewCmd(ctx, "sh", "-c", "sleep 2") 209 midT := 2 * time.Second 210 h := NewHanger(4 * time.Second) 211 cmd.Stdin = h // hangs 212 //cmd.SetupStdInOutErr(h, nil, nil) // hangs 213 cmd.Start() 214 now := time.Now() 215 go func() { 216 time.Sleep(killT) 217 cmd.Process.Kill() 218 t.Logf("kill: %v\n", time.Since(now)) 219 }() 220 if err := cmd.Wait(); err == nil { 221 t.Fatal(err) 222 } 223 dur := time.Since(now) 224 t.Logf("wait done: %v\n", dur) 225 //if dur > 2*time.Second { 226 //t.Fatalf("cmd did not end at kill") 227 //} 228 if dur < midT { 229 t.Fatalf("cmd did end at kill") 230 } 231 } 232 233 //---------- 234 235 func TestCmdPipe(t *testing.T) { 236 type strErr struct { 237 s string 238 err error 239 } 240 for i := 0; i < 100; i++ { 241 ctx := context.Background() 242 cmd := NewCmd(ctx, "echo", "aaa") 243 pr, pw := io.Pipe() 244 c := make(chan strErr) 245 cmd.SetupStdio(nil, pw, nil) 246 go func() { 247 b, err := ioutil.ReadAll(pr) 248 c <- strErr{string(b), err} 249 }() 250 if err := cmd.Start(); err != nil { 251 t.Errorf("%d. Start: %v", i, err) 252 continue 253 } 254 if err := cmd.Wait(); err != nil { 255 t.Errorf("%d. Wait: %v", i, err) 256 continue 257 } 258 pw.Close() // must close after wait (handle pipe externally to cmd), or it will hang 259 se := <-c 260 if se.err != nil { 261 t.Errorf("%d. echo: %v", i, se.err) 262 } 263 if se.s != "aaa\n" { 264 t.Errorf("%d. echo: want %q, got %q", i, "aaa\n", se.s) 265 } 266 } 267 } 268 269 //---------- 270 271 func TestStress1(t *testing.T) { 272 // direct cmd.stoutpipe test for understanding 273 274 type strErr struct { 275 s string 276 err error 277 } 278 for i := 0; i < 1000; i++ { 279 ctx := context.Background() 280 cmd := NewCmd(ctx, "echo", "aaa") 281 p, err := cmd.StdoutPipe() 282 if err != nil { 283 t.Errorf("%d. StdoutPipe: %v", i, err) 284 continue 285 } 286 c := make(chan strErr) 287 go func() { 288 b, err := ioutil.ReadAll(p) 289 c <- strErr{string(b), err} 290 }() 291 if err := cmd.Start(); err != nil { 292 t.Errorf("%d. Start: %v", i, err) 293 continue 294 } 295 se := <-c // must read before wait (using stdoutpipe directly) 296 if err := cmd.Wait(); err != nil { 297 t.Errorf("%d. Wait: %v", i, err) 298 continue 299 } 300 //se := <-c // fails to get all output since wait will not have a copy loop 301 if se.err != nil { 302 t.Errorf("%d. echo: %v", i, se.err) 303 } 304 if se.s != "aaa\n" { 305 t.Errorf("%d. echo: want %q, got %q", i, "aaa\n", se.s) 306 } 307 } 308 } 309 310 //---------- 311 312 type Hanger struct { 313 t time.Duration 314 buf bytes.Buffer 315 } 316 317 func NewHanger(t time.Duration) *Hanger { 318 return &Hanger{t: t} 319 } 320 func (h *Hanger) Read(b []byte) (int, error) { 321 time.Sleep(time.Duration(h.t)) 322 //return 0, io.EOF 323 return h.buf.Read(b) 324 } 325 func (h *Hanger) Write(b []byte) (int, error) { 326 time.Sleep(time.Duration(h.t)) 327 //return 0, io.EOF 328 return h.buf.Write(b) 329 }