File: io_test.go

package info (click to toggle)
golang-github-creack-pty 1.1.21-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 324 kB
  • sloc: sh: 51; asm: 5; makefile: 2
file content (133 lines) | stat: -rw-r--r-- 3,397 bytes parent folder | download
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) {
	t.Skip("Disabling while investigating race.")

	ptmx, success := prepare(t)

	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) {
	t.Skip("Disabling while investigating race.")

	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 *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)
	}
	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
}