mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
os: test overlapped pipes deadlines on Windows
NewFile recently added support for overlapped I/O on Windows, which allows us to set deadlines on them, but the test coverage for this new feature is not exhaustive. Modify the existing pipe deadline tests to also exercise named overlapped pipes. Updates #19098. Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-race,gotip-windows-amd64-longtest,gotip-windows-arm64 Change-Id: I86d284d9fb054c24959045a922cf84feeda5b5f0 Reviewed-on: https://go-review.googlesource.com/c/go/+/668095 Reviewed-by: Alex Brainman <alex.brainman@gmail.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Carlos Amedee <carlos@golang.org> Reviewed-by: Junyang Shao <shaojunyang@google.com>
This commit is contained in:
parent
06751c455d
commit
3cebfb678b
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !js && !plan9 && !wasip1 && !windows
|
||||
//go:build !js && !plan9 && !wasip1
|
||||
|
||||
package os_test
|
||||
|
||||
@ -11,18 +11,16 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNonpollableDeadline(t *testing.T) {
|
||||
// On BSD systems regular files seem to be pollable,
|
||||
// so just run this test on Linux.
|
||||
if runtime.GOOS != "linux" {
|
||||
// so just run this test on Linux and Windows.
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
|
||||
t.Skipf("skipping on %s", runtime.GOOS)
|
||||
}
|
||||
t.Parallel()
|
||||
@ -45,6 +43,13 @@ func TestNonpollableDeadline(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type pipeDeadlineTest struct {
|
||||
name string
|
||||
create func(t *testing.T) (r, w *os.File)
|
||||
}
|
||||
|
||||
var pipeDeadlinesTestCases []pipeDeadlineTest
|
||||
|
||||
// noDeadline is a zero time.Time value, which cancels a deadline.
|
||||
var noDeadline time.Time
|
||||
|
||||
@ -63,10 +68,11 @@ var readTimeoutTests = []struct {
|
||||
func TestReadTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -98,16 +104,19 @@ func TestReadTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
func TestReadTimeoutMustNotReturn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -139,6 +148,8 @@ func TestReadTimeoutMustNotReturn(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var writeTimeoutTests = []struct {
|
||||
@ -156,12 +167,13 @@ var writeTimeoutTests = []struct {
|
||||
func TestWriteTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i, tt := range writeTimeoutTests {
|
||||
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -188,16 +200,19 @@ func TestWriteTimeout(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
func TestWriteTimeoutMustNotReturn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -233,6 +248,8 @@ func TestWriteTimeoutMustNotReturn(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
@ -289,10 +306,11 @@ func nextTimeout(actual time.Duration) (next time.Duration, ok bool) {
|
||||
func TestReadTimeoutFluctuation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -302,11 +320,11 @@ func TestReadTimeoutFluctuation(t *testing.T) {
|
||||
t.Logf("SetReadDeadline(+%v)", d)
|
||||
t0 := time.Now()
|
||||
deadline := t0.Add(d)
|
||||
if err = r.SetReadDeadline(deadline); err != nil {
|
||||
if err := r.SetReadDeadline(deadline); err != nil {
|
||||
t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
|
||||
}
|
||||
var n int
|
||||
n, err = r.Read(b)
|
||||
n, err := r.Read(b)
|
||||
t1 := time.Now()
|
||||
|
||||
if n != 0 || err == nil || !isDeadlineExceeded(err) {
|
||||
@ -334,16 +352,19 @@ func TestReadTimeoutFluctuation(t *testing.T) {
|
||||
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
func TestWriteTimeoutFluctuation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -404,12 +425,19 @@ func TestWriteTimeoutFluctuation(t *testing.T) {
|
||||
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
func TestVariousDeadlines(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t)
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t, tc.create)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
@ -419,7 +447,12 @@ func TestVariousDeadlines1Proc(t *testing.T) {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
||||
testVariousDeadlines(t)
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t, tc.create)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// There is a very similar copy of this in net/timeout_test.go.
|
||||
@ -429,7 +462,12 @@ func TestVariousDeadlines4Proc(t *testing.T) {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
|
||||
testVariousDeadlines(t)
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testVariousDeadlines(t, tc.create)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type neverEnding byte
|
||||
@ -441,7 +479,7 @@ func (b neverEnding) Read(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func testVariousDeadlines(t *testing.T) {
|
||||
func testVariousDeadlines(t *testing.T, create func(t *testing.T) (r, w *os.File)) {
|
||||
type result struct {
|
||||
n int64
|
||||
err error
|
||||
@ -487,10 +525,7 @@ func testVariousDeadlines(t *testing.T) {
|
||||
}
|
||||
for run := 0; run < numRuns; run++ {
|
||||
t.Run(fmt.Sprintf("%v-%d", timeout, run+1), func(t *testing.T) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, w := create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -514,7 +549,7 @@ func testVariousDeadlines(t *testing.T) {
|
||||
|
||||
select {
|
||||
case res := <-actvch:
|
||||
if !isDeadlineExceeded(err) {
|
||||
if isDeadlineExceeded(res.err) {
|
||||
t.Logf("good client timeout after %v, reading %d bytes", res.d, res.n)
|
||||
} else {
|
||||
t.Fatalf("client Copy = %d, %v; want timeout", res.n, res.err)
|
||||
@ -543,10 +578,11 @@ func TestReadWriteDeadlineRace(t *testing.T) {
|
||||
N = 50
|
||||
}
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -587,6 +623,8 @@ func TestReadWriteDeadlineRace(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
wg.Wait() // wait for tester goroutine to stop
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRacyRead tests that it is safe to mutate the input Read buffer
|
||||
@ -594,10 +632,11 @@ func TestReadWriteDeadlineRace(t *testing.T) {
|
||||
func TestRacyRead(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -626,6 +665,8 @@ func TestRacyRead(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRacyWrite tests that it is safe to mutate the input Write buffer
|
||||
@ -633,10 +674,11 @@ func TestRacyRead(t *testing.T) {
|
||||
func TestRacyWrite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range pipeDeadlinesTestCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r, w := tc.create(t)
|
||||
defer r.Close()
|
||||
defer w.Close()
|
||||
|
||||
@ -665,41 +707,6 @@ func TestRacyWrite(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Closing a TTY while reading from it should not hang. Issue 23943.
|
||||
func TestTTYClose(t *testing.T) {
|
||||
// Ignore SIGTTIN in case we are running in the background.
|
||||
signal.Ignore(syscall.SIGTTIN)
|
||||
defer signal.Reset(syscall.SIGTTIN)
|
||||
|
||||
f, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
t.Skipf("skipping because opening /dev/tty failed: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
var buf [1]byte
|
||||
f.Read(buf[:])
|
||||
}()
|
||||
|
||||
// Give the goroutine a chance to enter the read.
|
||||
// It doesn't matter much if it occasionally fails to do so,
|
||||
// we won't be testing what we want to test but the test will pass.
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
defer close(c)
|
||||
f.Close()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
case <-time.After(time.Second):
|
||||
t.Error("timed out waiting for close")
|
||||
}
|
||||
|
||||
// On some systems the goroutines may now be hanging.
|
||||
// There's not much we can do about that.
|
||||
}
|
||||
|
65
src/os/timeout_unix_test.go
Normal file
65
src/os/timeout_unix_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !js && !plan9 && !wasip1 && !windows
|
||||
|
||||
package os_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pipeDeadlinesTestCases = []pipeDeadlineTest{{
|
||||
"anonymous pipe",
|
||||
func(t *testing.T) (r, w *os.File) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return r, w
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// Closing a TTY while reading from it should not hang. Issue 23943.
|
||||
func TestTTYClose(t *testing.T) {
|
||||
// Ignore SIGTTIN in case we are running in the background.
|
||||
signal.Ignore(syscall.SIGTTIN)
|
||||
defer signal.Reset(syscall.SIGTTIN)
|
||||
|
||||
f, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
t.Skipf("skipping because opening /dev/tty failed: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
var buf [1]byte
|
||||
f.Read(buf[:])
|
||||
}()
|
||||
|
||||
// Give the goroutine a chance to enter the read.
|
||||
// It doesn't matter much if it occasionally fails to do so,
|
||||
// we won't be testing what we want to test but the test will pass.
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
defer close(c)
|
||||
f.Close()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
case <-time.After(time.Second):
|
||||
t.Error("timed out waiting for close")
|
||||
}
|
||||
|
||||
// On some systems the goroutines may now be hanging.
|
||||
// There's not much we can do about that.
|
||||
}
|
24
src/os/timeout_windows_test.go
Normal file
24
src/os/timeout_windows_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package os_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
pipeDeadlinesTestCases = []pipeDeadlineTest{
|
||||
{
|
||||
"named overlapped pipe",
|
||||
func(t *testing.T) (r, w *os.File) {
|
||||
name := pipeName()
|
||||
w = newBytePipe(t, name, true)
|
||||
r = newFileOverlapped(t, name, true)
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user