mirror of https://go.googlesource.com/go
709 lines
17 KiB
Go
709 lines
17 KiB
Go
// Copyright 2017 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 (
|
|
"fmt"
|
|
"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" {
|
|
t.Skipf("skipping on %s", runtime.GOOS)
|
|
}
|
|
t.Parallel()
|
|
|
|
f, err := os.CreateTemp("", "ostest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(f.Name())
|
|
defer f.Close()
|
|
deadline := time.Now().Add(10 * time.Second)
|
|
if err := f.SetDeadline(deadline); err != os.ErrNoDeadline {
|
|
t.Errorf("SetDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
|
|
}
|
|
if err := f.SetReadDeadline(deadline); err != os.ErrNoDeadline {
|
|
t.Errorf("SetReadDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
|
|
}
|
|
if err := f.SetWriteDeadline(deadline); err != os.ErrNoDeadline {
|
|
t.Errorf("SetWriteDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
|
|
}
|
|
}
|
|
|
|
// noDeadline is a zero time.Time value, which cancels a deadline.
|
|
var noDeadline time.Time
|
|
|
|
var readTimeoutTests = []struct {
|
|
timeout time.Duration
|
|
xerrs [2]error // expected errors in transition
|
|
}{
|
|
// Tests that read deadlines work, even if there's data ready
|
|
// to be read.
|
|
{-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
|
|
|
|
{50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
|
|
}
|
|
|
|
// There is a very similar copy of this in net/timeout_test.go.
|
|
func TestReadTimeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
if _, err := w.Write([]byte("READ TIMEOUT TEST")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, tt := range readTimeoutTests {
|
|
if err := r.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil {
|
|
t.Fatalf("#%d: %v", i, err)
|
|
}
|
|
var b [1]byte
|
|
for j, xerr := range tt.xerrs {
|
|
for {
|
|
n, err := r.Read(b[:])
|
|
if xerr != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Fatalf("#%d/%d: %v", i, j, err)
|
|
}
|
|
}
|
|
if err == nil {
|
|
time.Sleep(tt.timeout / 3)
|
|
continue
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("#%d/%d: read %d; want 0", i, j, n)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
max := time.NewTimer(100 * time.Millisecond)
|
|
defer max.Stop()
|
|
ch := make(chan error)
|
|
go func() {
|
|
if err := r.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := r.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := r.SetReadDeadline(noDeadline); err != nil {
|
|
t.Error(err)
|
|
}
|
|
var b [1]byte
|
|
_, err := r.Read(b[:])
|
|
ch <- err
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
t.Fatalf("expected Read to not return, but it returned with %v", err)
|
|
case <-max.C:
|
|
w.Close()
|
|
err := <-ch // wait for tester goroutine to stop
|
|
if os.IsTimeout(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var writeTimeoutTests = []struct {
|
|
timeout time.Duration
|
|
xerrs [2]error // expected errors in transition
|
|
}{
|
|
// Tests that write deadlines work, even if there's buffer
|
|
// space available to write.
|
|
{-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
|
|
|
|
{10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
|
|
}
|
|
|
|
// There is a very similar copy of this in net/timeout_test.go.
|
|
func TestWriteTimeout(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)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
if err := w.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
for j, xerr := range tt.xerrs {
|
|
for {
|
|
n, err := w.Write([]byte("WRITE TIMEOUT TEST"))
|
|
if xerr != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Fatalf("%d: %v", j, err)
|
|
}
|
|
}
|
|
if err == nil {
|
|
time.Sleep(tt.timeout / 3)
|
|
continue
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("%d: wrote %d; want 0", j, n)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
max := time.NewTimer(100 * time.Millisecond)
|
|
defer max.Stop()
|
|
ch := make(chan error)
|
|
go func() {
|
|
if err := w.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := w.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
if err := w.SetWriteDeadline(noDeadline); err != nil {
|
|
t.Error(err)
|
|
}
|
|
var b [1]byte
|
|
for {
|
|
if _, err := w.Write(b[:]); err != nil {
|
|
ch <- err
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
t.Fatalf("expected Write to not return, but it returned with %v", err)
|
|
case <-max.C:
|
|
r.Close()
|
|
err := <-ch // wait for tester goroutine to stop
|
|
if os.IsTimeout(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
const (
|
|
// minDynamicTimeout is the minimum timeout to attempt for
|
|
// tests that automatically increase timeouts until success.
|
|
//
|
|
// Lower values may allow tests to succeed more quickly if the value is close
|
|
// to the true minimum, but may require more iterations (and waste more time
|
|
// and CPU power on failed attempts) if the timeout is too low.
|
|
minDynamicTimeout = 1 * time.Millisecond
|
|
|
|
// maxDynamicTimeout is the maximum timeout to attempt for
|
|
// tests that automatically increase timeouts until success.
|
|
//
|
|
// This should be a strict upper bound on the latency required to hit a
|
|
// timeout accurately, even on a slow or heavily-loaded machine. If a test
|
|
// would increase the timeout beyond this value, the test fails.
|
|
maxDynamicTimeout = 4 * time.Second
|
|
)
|
|
|
|
// timeoutUpperBound returns the maximum time that we expect a timeout of
|
|
// duration d to take to return the caller.
|
|
func timeoutUpperBound(d time.Duration) time.Duration {
|
|
switch runtime.GOOS {
|
|
case "openbsd", "netbsd":
|
|
// NetBSD and OpenBSD seem to be unable to reliably hit deadlines even when
|
|
// the absolute durations are long.
|
|
// In https://build.golang.org/log/c34f8685d020b98377dd4988cd38f0c5bd72267e,
|
|
// we observed that an openbsd-amd64-68 builder took 4.090948779s for a
|
|
// 2.983020682s timeout (37.1% overhead).
|
|
// (See https://go.dev/issue/50189 for further detail.)
|
|
// Give them lots of slop to compensate.
|
|
return d * 3 / 2
|
|
}
|
|
// Other platforms seem to hit their deadlines more reliably,
|
|
// at least when they are long enough to cover scheduling jitter.
|
|
return d * 11 / 10
|
|
}
|
|
|
|
// nextTimeout returns the next timeout to try after an operation took the given
|
|
// actual duration with a timeout shorter than that duration.
|
|
func nextTimeout(actual time.Duration) (next time.Duration, ok bool) {
|
|
if actual >= maxDynamicTimeout {
|
|
return maxDynamicTimeout, false
|
|
}
|
|
// Since the previous attempt took actual, we can't expect to beat that
|
|
// duration by any significant margin. Try the next attempt with an arbitrary
|
|
// factor above that, so that our growth curve is at least exponential.
|
|
next = actual * 5 / 4
|
|
if next > maxDynamicTimeout {
|
|
return maxDynamicTimeout, true
|
|
}
|
|
return next, true
|
|
}
|
|
|
|
// There is a very similar copy of this in net/timeout_test.go.
|
|
func TestReadTimeoutFluctuation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
d := minDynamicTimeout
|
|
b := make([]byte, 256)
|
|
for {
|
|
t.Logf("SetReadDeadline(+%v)", d)
|
|
t0 := time.Now()
|
|
deadline := t0.Add(d)
|
|
if err = r.SetReadDeadline(deadline); err != nil {
|
|
t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
|
|
}
|
|
var n int
|
|
n, err = r.Read(b)
|
|
t1 := time.Now()
|
|
|
|
if n != 0 || err == nil || !isDeadlineExceeded(err) {
|
|
t.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
|
|
}
|
|
|
|
actual := t1.Sub(t0)
|
|
if t1.Before(deadline) {
|
|
t.Errorf("Read took %s; expected at least %s", actual, d)
|
|
}
|
|
if t.Failed() {
|
|
return
|
|
}
|
|
if want := timeoutUpperBound(d); actual > want {
|
|
next, ok := nextTimeout(actual)
|
|
if !ok {
|
|
t.Fatalf("Read took %s; expected at most %v", actual, want)
|
|
}
|
|
// Maybe this machine is too slow to reliably schedule goroutines within
|
|
// the requested duration. Increase the timeout and try again.
|
|
t.Logf("Read took %s (expected %s); trying with longer timeout", actual, d)
|
|
d = next
|
|
continue
|
|
}
|
|
|
|
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)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
d := minDynamicTimeout
|
|
for {
|
|
t.Logf("SetWriteDeadline(+%v)", d)
|
|
t0 := time.Now()
|
|
deadline := t0.Add(d)
|
|
if err := w.SetWriteDeadline(deadline); err != nil {
|
|
t.Fatalf("SetWriteDeadline(%v): %v", deadline, err)
|
|
}
|
|
var n int64
|
|
var err error
|
|
for {
|
|
var dn int
|
|
dn, err = w.Write([]byte("TIMEOUT TRANSMITTER"))
|
|
n += int64(dn)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
t1 := time.Now()
|
|
// Inv: err != nil
|
|
if !isDeadlineExceeded(err) {
|
|
t.Fatalf("Write did not return (any, timeout): (%d, %v)", n, err)
|
|
}
|
|
|
|
actual := t1.Sub(t0)
|
|
if t1.Before(deadline) {
|
|
t.Errorf("Write took %s; expected at least %s", actual, d)
|
|
}
|
|
if t.Failed() {
|
|
return
|
|
}
|
|
if want := timeoutUpperBound(d); actual > want {
|
|
if n > 0 {
|
|
// SetWriteDeadline specifies a time “after which I/O operations fail
|
|
// instead of blocking”. However, the kernel's send buffer is not yet
|
|
// full, we may be able to write some arbitrary (but finite) number of
|
|
// bytes to it without blocking.
|
|
t.Logf("Wrote %d bytes into send buffer; retrying until buffer is full", n)
|
|
if d <= maxDynamicTimeout/2 {
|
|
// We don't know how long the actual write loop would have taken if
|
|
// the buffer were full, so just guess and double the duration so that
|
|
// the next attempt can make twice as much progress toward filling it.
|
|
d *= 2
|
|
}
|
|
} else if next, ok := nextTimeout(actual); !ok {
|
|
t.Fatalf("Write took %s; expected at most %s", actual, want)
|
|
} else {
|
|
// Maybe this machine is too slow to reliably schedule goroutines within
|
|
// the requested duration. Increase the timeout and try again.
|
|
t.Logf("Write took %s (expected %s); trying with longer timeout", actual, d)
|
|
d = next
|
|
}
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
// There is a very similar copy of this in net/timeout_test.go.
|
|
func TestVariousDeadlines(t *testing.T) {
|
|
t.Parallel()
|
|
testVariousDeadlines(t)
|
|
}
|
|
|
|
// There is a very similar copy of this in net/timeout_test.go.
|
|
func TestVariousDeadlines1Proc(t *testing.T) {
|
|
// Cannot use t.Parallel - modifies global GOMAXPROCS.
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
|
testVariousDeadlines(t)
|
|
}
|
|
|
|
// There is a very similar copy of this in net/timeout_test.go.
|
|
func TestVariousDeadlines4Proc(t *testing.T) {
|
|
// Cannot use t.Parallel - modifies global GOMAXPROCS.
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
|
|
testVariousDeadlines(t)
|
|
}
|
|
|
|
type neverEnding byte
|
|
|
|
func (b neverEnding) Read(p []byte) (int, error) {
|
|
for i := range p {
|
|
p[i] = byte(b)
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func testVariousDeadlines(t *testing.T) {
|
|
type result struct {
|
|
n int64
|
|
err error
|
|
d time.Duration
|
|
}
|
|
|
|
handler := func(w *os.File, pasvch chan result) {
|
|
// The writer, with no timeouts of its own,
|
|
// sending bytes to clients as fast as it can.
|
|
t0 := time.Now()
|
|
n, err := io.Copy(w, neverEnding('a'))
|
|
dt := time.Since(t0)
|
|
pasvch <- result{n, err, dt}
|
|
}
|
|
|
|
for _, timeout := range []time.Duration{
|
|
1 * time.Nanosecond,
|
|
2 * time.Nanosecond,
|
|
5 * time.Nanosecond,
|
|
50 * time.Nanosecond,
|
|
100 * time.Nanosecond,
|
|
200 * time.Nanosecond,
|
|
500 * time.Nanosecond,
|
|
750 * time.Nanosecond,
|
|
1 * time.Microsecond,
|
|
5 * time.Microsecond,
|
|
25 * time.Microsecond,
|
|
250 * time.Microsecond,
|
|
500 * time.Microsecond,
|
|
1 * time.Millisecond,
|
|
5 * time.Millisecond,
|
|
100 * time.Millisecond,
|
|
250 * time.Millisecond,
|
|
500 * time.Millisecond,
|
|
1 * time.Second,
|
|
} {
|
|
numRuns := 3
|
|
if testing.Short() {
|
|
numRuns = 1
|
|
if timeout > 500*time.Microsecond {
|
|
continue
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
pasvch := make(chan result)
|
|
go handler(w, pasvch)
|
|
|
|
tooLong := 5 * time.Second
|
|
max := time.NewTimer(tooLong)
|
|
defer max.Stop()
|
|
actvch := make(chan result)
|
|
go func() {
|
|
t0 := time.Now()
|
|
if err := r.SetDeadline(t0.Add(timeout)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
n, err := io.Copy(io.Discard, r)
|
|
dt := time.Since(t0)
|
|
r.Close()
|
|
actvch <- result{n, err, dt}
|
|
}()
|
|
|
|
select {
|
|
case res := <-actvch:
|
|
if !isDeadlineExceeded(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)
|
|
}
|
|
case <-max.C:
|
|
t.Fatalf("timeout (%v) waiting for client to timeout (%v) reading", tooLong, timeout)
|
|
}
|
|
|
|
select {
|
|
case res := <-pasvch:
|
|
t.Logf("writer in %v wrote %d: %v", res.d, res.n, res.err)
|
|
case <-max.C:
|
|
t.Fatalf("timeout waiting for writer to finish writing")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// There is a very similar copy of this in net/timeout_test.go.
|
|
func TestReadWriteDeadlineRace(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
N := 1000
|
|
if testing.Short() {
|
|
N = 50
|
|
}
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(3)
|
|
go func() {
|
|
defer wg.Done()
|
|
tic := time.NewTicker(2 * time.Microsecond)
|
|
defer tic.Stop()
|
|
for i := 0; i < N; i++ {
|
|
if err := r.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
|
break
|
|
}
|
|
if err := w.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
|
|
break
|
|
}
|
|
<-tic.C
|
|
}
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
var b [1]byte
|
|
for i := 0; i < N; i++ {
|
|
_, err := r.Read(b[:])
|
|
if err != nil && !isDeadlineExceeded(err) {
|
|
t.Error("Read returned non-timeout error", err)
|
|
}
|
|
}
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
var b [1]byte
|
|
for i := 0; i < N; i++ {
|
|
_, err := w.Write(b[:])
|
|
if err != nil && !isDeadlineExceeded(err) {
|
|
t.Error("Write returned non-timeout error", err)
|
|
}
|
|
}
|
|
}()
|
|
wg.Wait() // wait for tester goroutine to stop
|
|
}
|
|
|
|
// TestRacyRead tests that it is safe to mutate the input Read buffer
|
|
// immediately after cancellation has occurred.
|
|
func TestRacyRead(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
defer wg.Wait()
|
|
|
|
go io.Copy(w, rand.New(rand.NewSource(0)))
|
|
|
|
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
b1 := make([]byte, 1024)
|
|
b2 := make([]byte, 1024)
|
|
for j := 0; j < 100; j++ {
|
|
_, err := r.Read(b1)
|
|
copy(b1, b2) // Mutate b1 to trigger potential race
|
|
if err != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Error(err)
|
|
}
|
|
r.SetReadDeadline(time.Now().Add(time.Millisecond))
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// TestRacyWrite tests that it is safe to mutate the input Write buffer
|
|
// immediately after cancellation has occurred.
|
|
func TestRacyWrite(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer r.Close()
|
|
defer w.Close()
|
|
|
|
var wg sync.WaitGroup
|
|
defer wg.Wait()
|
|
|
|
go io.Copy(io.Discard, r)
|
|
|
|
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
b1 := make([]byte, 1024)
|
|
b2 := make([]byte, 1024)
|
|
for j := 0; j < 100; j++ {
|
|
_, err := w.Write(b1)
|
|
copy(b1, b2) // Mutate b1 to trigger potential race
|
|
if err != nil {
|
|
if !isDeadlineExceeded(err) {
|
|
t.Error(err)
|
|
}
|
|
w.SetWriteDeadline(time.Now().Add(time.Millisecond))
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
}
|