-
-
Notifications
You must be signed in to change notification settings - Fork 240
/
io_test.go
139 lines (120 loc) · 3.52 KB
/
io_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//go:build go1.12
// +build go1.12
package pty
import (
"context"
"errors"
"os"
"runtime"
"sync"
"syscall"
"testing"
"time"
)
const (
errMarker byte = 0xEE
timeout = time.Second
)
//nolint:gochecknoglobals // Expected global lock to avoid potential race on (*os.File).Fd().
var glTestFdLock sync.Mutex
// Check that SetDeadline() works for ptmx.
// Outstanding Read() calls must be interrupted by deadline.
//
// https://github.com/creack/pty/issues/162
//
//nolint:paralleltest // Potential in (*os.File).Fd().
func TestReadDeadline(t *testing.T) {
ptmx, success := prepare(t)
if err := syscall.SetNonblock(int(ptmx.Fd()), true); err != nil {
t.Fatalf("Error: set non block: %s", err)
}
if err := ptmx.SetDeadline(time.Now().Add(timeout / 10)); err != nil {
if errors.Is(err, os.ErrNoDeadline) {
t.Skipf("Deadline is not supported on %s/%s.", runtime.GOOS, runtime.GOARCH)
} else {
t.Fatalf("Error: set deadline: %s.", err)
}
}
buf := make([]byte, 1)
n, err := ptmx.Read(buf)
success()
if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatalf("Unexpected read error: %s.", err)
}
if n != 0 && buf[0] != errMarker {
t.Errorf("Received unexpected data from pmtx (%d bytes): 0x%X; err=%v.", n, buf, err)
}
}
// Check that ptmx.Close() interrupts outstanding ptmx.Read() calls.
//
// https://github.com/creack/pty/issues/114
// https://github.com/creack/pty/issues/88
//
//nolint:paralleltest // Potential in (*os.File).Fd().
func TestReadClose(t *testing.T) {
ptmx, success := prepare(t)
if err := syscall.SetNonblock(int(ptmx.Fd()), true); err != nil {
t.Fatalf("Error: set non block: %s", err)
}
go func() {
time.Sleep(timeout / 10)
if err := ptmx.Close(); err != nil {
t.Errorf("Failed to close ptmx: %s.", err)
}
}()
buf := make([]byte, 1)
n, err := ptmx.Read(buf)
success()
if err != nil && !errors.Is(err, os.ErrClosed) {
t.Fatalf("Unexpected read error: %s.", err)
}
if n != 0 && buf[0] != errMarker {
t.Errorf("Received unexpected data from pmtx (%d bytes): 0x%X; err=%v.", n, buf, err)
}
}
// Open pty and setup watchdogs for graceful and not so graceful failure modes.
func prepare(t *testing.T) (ptmx *os.File, done func()) {
t.Helper()
if runtime.GOOS == "darwin" {
t.Log("creack/pty uses blocking i/o on darwin intentionally:")
t.Log("> https://github.com/creack/pty/issues/52")
t.Log("> https://github.com/creack/pty/pull/53")
t.Log("> https://github.com/golang/go/issues/22099")
t.SkipNow()
}
// Due to data race potential in (*os.File).Fd()
// we should never run these two tests in parallel.
glTestFdLock.Lock()
t.Cleanup(glTestFdLock.Unlock)
ptmx, pts, err := Open()
if err != nil {
t.Fatalf("Error: open: %s.\n", err)
}
_ptmx := ptmx
t.Cleanup(func() { _ = _ptmx.Close() })
t.Cleanup(func() { _ = pts.Close() })
ptmx = getNonBlockingFile(t, ptmx, "/dev/ptmx")
ctx, done := context.WithCancel(context.Background())
t.Cleanup(done)
go func() {
select {
case <-ctx.Done():
// ptmx.Read() did not block forever, yay!
case <-time.After(timeout):
if _, err := pts.Write([]byte{errMarker}); err != nil { // Unblock ptmx.Read().
t.Errorf("Failed to write to pts: %s.", err)
}
t.Error("ptmx.Read() was not unblocked.")
done() // Cancel panic().
}
}()
go func() {
select {
case <-ctx.Done():
// Test has either failed or succeeded; it definitely did not hang.
case <-time.After(timeout * 10 / 9): // Timeout + 11%.
panic("ptmx.Read() was not unblocked; avoid hanging forever.") // Just in case.
}
}()
return ptmx, done
}