github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/os/pipe_test.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Test broken pipes on Unix systems. 6 // 7 //go:build !plan9 && !js 8 9 package os_test 10 11 import ( 12 "bufio" 13 "bytes" 14 "fmt" 15 "internal/testenv" 16 "io" 17 "io/fs" 18 "os" 19 osexec "os/exec" 20 "os/signal" 21 "runtime" 22 "strconv" 23 "strings" 24 "sync" 25 "syscall" 26 "testing" 27 "time" 28 ) 29 30 func TestEPIPE(t *testing.T) { 31 r, w, err := os.Pipe() 32 if err != nil { 33 t.Fatal(err) 34 } 35 if err := r.Close(); err != nil { 36 t.Fatal(err) 37 } 38 39 expect := syscall.EPIPE 40 if runtime.GOOS == "windows" { 41 // 232 is Windows error code ERROR_NO_DATA, "The pipe is being closed". 42 expect = syscall.Errno(232) 43 } 44 // Every time we write to the pipe we should get an EPIPE. 45 for i := 0; i < 20; i++ { 46 _, err = w.Write([]byte("hi")) 47 if err == nil { 48 t.Fatal("unexpected success of Write to broken pipe") 49 } 50 if pe, ok := err.(*fs.PathError); ok { 51 err = pe.Err 52 } 53 if se, ok := err.(*os.SyscallError); ok { 54 err = se.Err 55 } 56 if err != expect { 57 t.Errorf("iteration %d: got %v, expected %v", i, err, expect) 58 } 59 } 60 } 61 62 func TestStdPipe(t *testing.T) { 63 switch runtime.GOOS { 64 case "windows": 65 t.Skip("Windows doesn't support SIGPIPE") 66 } 67 testenv.MustHaveExec(t) 68 r, w, err := os.Pipe() 69 if err != nil { 70 t.Fatal(err) 71 } 72 if err := r.Close(); err != nil { 73 t.Fatal(err) 74 } 75 // Invoke the test program to run the test and write to a closed pipe. 76 // If sig is false: 77 // writing to stdout or stderr should cause an immediate SIGPIPE; 78 // writing to descriptor 3 should fail with EPIPE and then exit 0. 79 // If sig is true: 80 // all writes should fail with EPIPE and then exit 0. 81 for _, sig := range []bool{false, true} { 82 for dest := 1; dest < 4; dest++ { 83 cmd := osexec.Command(os.Args[0], "-test.run", "TestStdPipeHelper") 84 cmd.Stdout = w 85 cmd.Stderr = w 86 cmd.ExtraFiles = []*os.File{w} 87 cmd.Env = append(os.Environ(), fmt.Sprintf("GO_TEST_STD_PIPE_HELPER=%d", dest)) 88 if sig { 89 cmd.Env = append(cmd.Env, "GO_TEST_STD_PIPE_HELPER_SIGNAL=1") 90 } 91 if err := cmd.Run(); err == nil { 92 if !sig && dest < 3 { 93 t.Errorf("unexpected success of write to closed pipe %d sig %t in child", dest, sig) 94 } 95 } else if ee, ok := err.(*osexec.ExitError); !ok { 96 t.Errorf("unexpected exec error type %T: %v", err, err) 97 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 98 t.Errorf("unexpected wait status type %T: %v", ee.Sys(), ee.Sys()) 99 } else if ws.Signaled() && ws.Signal() == syscall.SIGPIPE { 100 if sig || dest > 2 { 101 t.Errorf("unexpected SIGPIPE signal for descriptor %d sig %t", dest, sig) 102 } 103 } else { 104 t.Errorf("unexpected exit status %v for descriptor %d sig %t", err, dest, sig) 105 } 106 } 107 } 108 109 // Test redirecting stdout but not stderr. Issue 40076. 110 cmd := osexec.Command(os.Args[0], "-test.run", "TestStdPipeHelper") 111 cmd.Stdout = w 112 var stderr bytes.Buffer 113 cmd.Stderr = &stderr 114 cmd.Env = append(os.Environ(), "GO_TEST_STD_PIPE_HELPER=1") 115 if err := cmd.Run(); err == nil { 116 t.Errorf("unexpected success of write to closed stdout") 117 } else if ee, ok := err.(*osexec.ExitError); !ok { 118 t.Errorf("unexpected exec error type %T: %v", err, err) 119 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 120 t.Errorf("unexpected wait status type %T: %v", ee.Sys(), ee.Sys()) 121 } else if !ws.Signaled() || ws.Signal() != syscall.SIGPIPE { 122 t.Errorf("unexpected exit status %v for write to closed stdout", err) 123 } 124 if output := stderr.Bytes(); len(output) > 0 { 125 t.Errorf("unexpected output on stderr: %s", output) 126 } 127 } 128 129 // This is a helper for TestStdPipe. It's not a test in itself. 130 func TestStdPipeHelper(t *testing.T) { 131 if os.Getenv("GO_TEST_STD_PIPE_HELPER_SIGNAL") != "" { 132 signal.Notify(make(chan os.Signal, 1), syscall.SIGPIPE) 133 } 134 switch os.Getenv("GO_TEST_STD_PIPE_HELPER") { 135 case "1": 136 os.Stdout.Write([]byte("stdout")) 137 case "2": 138 os.Stderr.Write([]byte("stderr")) 139 case "3": 140 if _, err := os.NewFile(3, "3").Write([]byte("3")); err == nil { 141 os.Exit(3) 142 } 143 default: 144 t.Skip("skipping test helper") 145 } 146 // For stdout/stderr, we should have crashed with a broken pipe error. 147 // The caller will be looking for that exit status, 148 // so just exit normally here to cause a failure in the caller. 149 // For descriptor 3, a normal exit is expected. 150 os.Exit(0) 151 } 152 153 func testClosedPipeRace(t *testing.T, read bool) { 154 limit := 1 155 if !read { 156 // Get the amount we have to write to overload a pipe 157 // with no reader. 158 limit = 131073 159 if b, err := os.ReadFile("/proc/sys/fs/pipe-max-size"); err == nil { 160 if i, err := strconv.Atoi(strings.TrimSpace(string(b))); err == nil { 161 limit = i + 1 162 } 163 } 164 t.Logf("using pipe write limit of %d", limit) 165 } 166 167 r, w, err := os.Pipe() 168 if err != nil { 169 t.Fatal(err) 170 } 171 defer r.Close() 172 defer w.Close() 173 174 // Close the read end of the pipe in a goroutine while we are 175 // writing to the write end, or vice-versa. 176 go func() { 177 // Give the main goroutine a chance to enter the Read or 178 // Write call. This is sloppy but the test will pass even 179 // if we close before the read/write. 180 time.Sleep(20 * time.Millisecond) 181 182 var err error 183 if read { 184 err = r.Close() 185 } else { 186 err = w.Close() 187 } 188 if err != nil { 189 t.Error(err) 190 } 191 }() 192 193 b := make([]byte, limit) 194 if read { 195 _, err = r.Read(b[:]) 196 } else { 197 _, err = w.Write(b[:]) 198 } 199 if err == nil { 200 t.Error("I/O on closed pipe unexpectedly succeeded") 201 } else if pe, ok := err.(*fs.PathError); !ok { 202 t.Errorf("I/O on closed pipe returned unexpected error type %T; expected fs.PathError", pe) 203 } else if pe.Err != fs.ErrClosed { 204 t.Errorf("got error %q but expected %q", pe.Err, fs.ErrClosed) 205 } else { 206 t.Logf("I/O returned expected error %q", err) 207 } 208 } 209 210 func TestClosedPipeRaceRead(t *testing.T) { 211 testClosedPipeRace(t, true) 212 } 213 214 func TestClosedPipeRaceWrite(t *testing.T) { 215 testClosedPipeRace(t, false) 216 } 217 218 // Issue 20915: Reading on nonblocking fd should not return "waiting 219 // for unsupported file type." Currently it returns EAGAIN; it is 220 // possible that in the future it will simply wait for data. 221 func TestReadNonblockingFd(t *testing.T) { 222 switch runtime.GOOS { 223 case "windows": 224 t.Skip("Windows doesn't support SetNonblock") 225 } 226 if os.Getenv("GO_WANT_READ_NONBLOCKING_FD") == "1" { 227 fd := syscallDescriptor(os.Stdin.Fd()) 228 syscall.SetNonblock(fd, true) 229 defer syscall.SetNonblock(fd, false) 230 _, err := os.Stdin.Read(make([]byte, 1)) 231 if err != nil { 232 if perr, ok := err.(*fs.PathError); !ok || perr.Err != syscall.EAGAIN { 233 t.Fatalf("read on nonblocking stdin got %q, should have gotten EAGAIN", err) 234 } 235 } 236 os.Exit(0) 237 } 238 239 testenv.MustHaveExec(t) 240 r, w, err := os.Pipe() 241 if err != nil { 242 t.Fatal(err) 243 } 244 defer r.Close() 245 defer w.Close() 246 cmd := osexec.Command(os.Args[0], "-test.run="+t.Name()) 247 cmd.Env = append(os.Environ(), "GO_WANT_READ_NONBLOCKING_FD=1") 248 cmd.Stdin = r 249 output, err := cmd.CombinedOutput() 250 t.Logf("%s", output) 251 if err != nil { 252 t.Errorf("child process failed: %v", err) 253 } 254 } 255 256 func TestCloseWithBlockingReadByNewFile(t *testing.T) { 257 var p [2]syscallDescriptor 258 err := syscall.Pipe(p[:]) 259 if err != nil { 260 t.Fatal(err) 261 } 262 // os.NewFile returns a blocking mode file. 263 testCloseWithBlockingRead(t, os.NewFile(uintptr(p[0]), "reader"), os.NewFile(uintptr(p[1]), "writer")) 264 } 265 266 func TestCloseWithBlockingReadByFd(t *testing.T) { 267 r, w, err := os.Pipe() 268 if err != nil { 269 t.Fatal(err) 270 } 271 // Calling Fd will put the file into blocking mode. 272 _ = r.Fd() 273 testCloseWithBlockingRead(t, r, w) 274 } 275 276 // Test that we don't let a blocking read prevent a close. 277 func testCloseWithBlockingRead(t *testing.T, r, w *os.File) { 278 defer r.Close() 279 defer w.Close() 280 281 c1, c2 := make(chan bool), make(chan bool) 282 var wg sync.WaitGroup 283 284 wg.Add(1) 285 go func(c chan bool) { 286 defer wg.Done() 287 // Give the other goroutine a chance to enter the Read 288 // or Write call. This is sloppy but the test will 289 // pass even if we close before the read/write. 290 time.Sleep(20 * time.Millisecond) 291 292 if err := r.Close(); err != nil { 293 t.Error(err) 294 } 295 close(c) 296 }(c1) 297 298 wg.Add(1) 299 go func(c chan bool) { 300 defer wg.Done() 301 var b [1]byte 302 _, err := r.Read(b[:]) 303 close(c) 304 if err == nil { 305 t.Error("I/O on closed pipe unexpectedly succeeded") 306 } 307 if pe, ok := err.(*fs.PathError); ok { 308 err = pe.Err 309 } 310 if err != io.EOF && err != fs.ErrClosed { 311 t.Errorf("got %v, expected EOF or closed", err) 312 } 313 }(c2) 314 315 for c1 != nil || c2 != nil { 316 select { 317 case <-c1: 318 c1 = nil 319 // r.Close has completed, but the blocking Read 320 // is hanging. Close the writer to unblock it. 321 w.Close() 322 case <-c2: 323 c2 = nil 324 case <-time.After(1 * time.Second): 325 switch { 326 case c1 != nil && c2 != nil: 327 t.Error("timed out waiting for Read and Close") 328 w.Close() 329 case c1 != nil: 330 t.Error("timed out waiting for Close") 331 case c2 != nil: 332 t.Error("timed out waiting for Read") 333 default: 334 t.Error("impossible case") 335 } 336 } 337 } 338 339 wg.Wait() 340 } 341 342 // Issue 24164, for pipes. 343 func TestPipeEOF(t *testing.T) { 344 r, w, err := os.Pipe() 345 if err != nil { 346 t.Fatal(err) 347 } 348 349 var wg sync.WaitGroup 350 wg.Add(1) 351 go func() { 352 defer wg.Done() 353 354 defer func() { 355 if err := w.Close(); err != nil { 356 t.Errorf("error closing writer: %v", err) 357 } 358 }() 359 360 for i := 0; i < 3; i++ { 361 time.Sleep(10 * time.Millisecond) 362 _, err := fmt.Fprintf(w, "line %d\n", i) 363 if err != nil { 364 t.Errorf("error writing to fifo: %v", err) 365 return 366 } 367 } 368 time.Sleep(10 * time.Millisecond) 369 }() 370 371 defer wg.Wait() 372 373 done := make(chan bool) 374 go func() { 375 defer close(done) 376 377 defer func() { 378 if err := r.Close(); err != nil { 379 t.Errorf("error closing reader: %v", err) 380 } 381 }() 382 383 rbuf := bufio.NewReader(r) 384 for { 385 b, err := rbuf.ReadBytes('\n') 386 if err == io.EOF { 387 break 388 } 389 if err != nil { 390 t.Error(err) 391 return 392 } 393 t.Logf("%s\n", bytes.TrimSpace(b)) 394 } 395 }() 396 397 select { 398 case <-done: 399 // Test succeeded. 400 case <-time.After(time.Second): 401 t.Error("timed out waiting for read") 402 // Close the reader to force the read to complete. 403 r.Close() 404 } 405 } 406 407 // Issue 24481. 408 func TestFdRace(t *testing.T) { 409 r, w, err := os.Pipe() 410 if err != nil { 411 t.Fatal(err) 412 } 413 defer r.Close() 414 defer w.Close() 415 416 var wg sync.WaitGroup 417 call := func() { 418 defer wg.Done() 419 w.Fd() 420 } 421 422 const tries = 100 423 for i := 0; i < tries; i++ { 424 wg.Add(1) 425 go call() 426 } 427 wg.Wait() 428 } 429 430 func TestFdReadRace(t *testing.T) { 431 t.Parallel() 432 433 r, w, err := os.Pipe() 434 if err != nil { 435 t.Fatal(err) 436 } 437 defer r.Close() 438 defer w.Close() 439 440 const count = 10 441 442 c := make(chan bool, 1) 443 var wg sync.WaitGroup 444 wg.Add(1) 445 go func() { 446 defer wg.Done() 447 var buf [count]byte 448 r.SetReadDeadline(time.Now().Add(time.Minute)) 449 c <- true 450 if _, err := r.Read(buf[:]); os.IsTimeout(err) { 451 t.Error("read timed out") 452 } 453 }() 454 455 wg.Add(1) 456 go func() { 457 defer wg.Done() 458 <-c 459 // Give the other goroutine a chance to enter the Read. 460 // It doesn't matter if this occasionally fails, the test 461 // will still pass, it just won't test anything. 462 time.Sleep(10 * time.Millisecond) 463 r.Fd() 464 465 // The bug was that Fd would hang until Read timed out. 466 // If the bug is fixed, then writing to w and closing r here 467 // will cause the Read to exit before the timeout expires. 468 w.Write(make([]byte, count)) 469 r.Close() 470 }() 471 472 wg.Wait() 473 }