forked from creack/pty
-
Notifications
You must be signed in to change notification settings - Fork 14
/
io_test.go
133 lines (115 loc) · 3.36 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
//go:build go1.12
// +build go1.12
package pty
import (
"context"
"errors"
"os"
"runtime"
"sync"
"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 ptmxd, ok := ptmx.(DeadlineHolder); ok {
if err := ptmxd.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.\n", err)
}
}
} else {
t.Fatalf("Unexpected ptmx type: missing deadline method (%T)", ptmx)
}
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)
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 Pty, 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)
}
t.Cleanup(func() { _ = ptmx.Close() })
t.Cleanup(func() { _ = pts.Close() })
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
}