Skip to content

Commit ffd4776

Browse files
committed
Add tty test and patch tty stderr/stdout
1 parent f731fe6 commit ffd4776

File tree

6 files changed

+92
-46
lines changed

6 files changed

+92
-46
lines changed

client.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,5 +200,21 @@ func (r remoteProcess) Wait() error {
200200
}
201201

202202
func (r remoteProcess) Close() error {
203-
return r.conn.Close(websocket.StatusAbnormalClosure, "kill process")
203+
err := r.conn.Close(websocket.StatusNormalClosure, "")
204+
err1 := r.stderr.w.Close()
205+
err2 := r.stdout.w.Close()
206+
return joinErrs(err, err1, err2)
207+
}
208+
209+
func joinErrs(errs ...error) error {
210+
var str string
211+
for _, e := range errs {
212+
if e != nil {
213+
if str != "" {
214+
str += ", "
215+
}
216+
str += e.Error()
217+
}
218+
}
219+
return xerrors.New(str)
204220
}

client_test.go

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@ import (
88
"net"
99
"net/http"
1010
"net/http/httptest"
11-
"os"
1211
"strings"
13-
"sync"
1412
"testing"
1513
"time"
1614

1715
"cdr.dev/slog/sloggers/slogtest/assert"
1816
"cdr.dev/wsep/internal/proto"
1917
"github.com/google/go-cmp/cmp"
20-
"go.coder.com/flog"
2118
"nhooyr.io/websocket"
2219
)
2320

@@ -51,7 +48,7 @@ func TestRemoteStdin(t *testing.T) {
5148
}
5249
}
5350

54-
func TestRemoteExec(t *testing.T) {
51+
func mockConn(ctx context.Context, t *testing.T) (*websocket.Conn, *httptest.Server) {
5552
mockServerHandler := func(w http.ResponseWriter, r *http.Request) {
5653
ws, err := websocket.Accept(w, r, nil)
5754
if err != nil {
@@ -60,54 +57,28 @@ func TestRemoteExec(t *testing.T) {
6057
}
6158
err = Serve(r.Context(), ws, LocalExecer{})
6259
if err != nil {
63-
flog.Error("failed to serve execer: %v", err)
60+
t.Errorf("failed to serve execer: %v", err)
6461
ws.Close(websocket.StatusAbnormalClosure, "failed to serve execer")
6562
return
6663
}
6764
ws.Close(websocket.StatusNormalClosure, "normal closure")
6865
}
6966

70-
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
71-
defer cancel()
72-
7367
server := httptest.NewServer(http.HandlerFunc(mockServerHandler))
74-
defer server.Close()
7568

7669
ws, _, err := websocket.Dial(ctx, "ws"+strings.TrimPrefix(server.URL, "http"), nil)
7770
assert.Success(t, "dial websocket server", err)
78-
defer ws.Close(websocket.StatusAbnormalClosure, "abnormal closure")
79-
80-
execer := RemoteExecer(ws)
81-
process, err := execer.Start(ctx, Command{
82-
Command: "pwd",
83-
})
84-
assert.Success(t, "start process", err)
85-
86-
assert.Equal(t, "pid", false, process.Pid() == 0)
87-
88-
var wg sync.WaitGroup
89-
go func() {
90-
wg.Add(1)
91-
defer wg.Done()
92-
93-
stdout, err := ioutil.ReadAll(process.Stdout())
94-
assert.Success(t, "read stdout", err)
95-
wd, err := os.Getwd()
96-
assert.Success(t, "get real working dir", err)
97-
98-
assert.Equal(t, "stdout", wd, strings.TrimSuffix(string(stdout), "\n"))
99-
}()
100-
go func() {
101-
wg.Add(1)
102-
defer wg.Done()
71+
return ws, server
72+
}
10373

104-
stderr, err := ioutil.ReadAll(process.Stderr())
105-
assert.Success(t, "read stderr", err)
106-
assert.Equal(t, "len stderr", 0, len(stderr))
107-
}()
74+
func TestRemoteExec(t *testing.T) {
75+
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
76+
defer cancel()
10877

109-
err = process.Wait()
110-
assert.Success(t, "wait for process to complete", err)
78+
ws, server := mockConn(ctx, t)
79+
defer ws.Close(websocket.StatusAbnormalClosure, "abnormal closure")
80+
defer server.Close()
11181

112-
wg.Wait()
82+
execer := RemoteExecer(ws)
83+
testExecer(ctx, t, execer)
11384
}

dev/client/main.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ func do(fl *pflag.FlagSet, tty bool) {
7878
err = process.Wait()
7979
if err != nil {
8080
flog.Error("process failed: %v", err)
81-
} else {
82-
flog.Info("process finished successfully")
8381
}
8482
conn.Close(websocket.StatusNormalClosure, "normal closure")
8583
}

localexec.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package wsep
22

33
import (
4+
"bytes"
45
"context"
56
"io"
7+
"io/ioutil"
68
"os"
79
"os/exec"
810
"syscall"
@@ -93,7 +95,7 @@ func (l LocalExecer) Start(ctx context.Context, c Command) (Process, error) {
9395
return nil, xerrors.Errorf("start command with pty: %w", err)
9496
}
9597
process.stdout = process.tty
96-
process.stderr = process.tty
98+
process.stderr = ioutil.NopCloser(bytes.NewReader(nil))
9799
process.stdin = process.tty
98100
} else {
99101
process.stdin, err = process.cmd.StdinPipe()

localexec_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ func TestLocalExec(t *testing.T) {
1616
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
1717
defer cancel()
1818

19-
execer := LocalExecer{}
19+
testExecer(ctx, t, LocalExecer{})
20+
}
21+
22+
func testExecer(ctx context.Context, t *testing.T, execer Execer) {
2023
process, err := execer.Start(ctx, Command{
2124
Command: "pwd",
2225
})

tty_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package wsep
2+
3+
import (
4+
"context"
5+
"io/ioutil"
6+
"strings"
7+
"sync"
8+
"testing"
9+
"time"
10+
11+
"cdr.dev/slog/sloggers/slogtest/assert"
12+
"nhooyr.io/websocket"
13+
)
14+
15+
func TestTTY(t *testing.T) {
16+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
17+
defer cancel()
18+
19+
ws, server := mockConn(ctx, t)
20+
defer ws.Close(websocket.StatusInternalError, "")
21+
defer server.Close()
22+
23+
execer := RemoteExecer(ws)
24+
testTTY(ctx, t, execer)
25+
}
26+
27+
func testTTY(ctx context.Context, t *testing.T, e Execer) {
28+
process, err := e.Start(ctx, Command{
29+
Command: "bash",
30+
TTY: true,
31+
})
32+
assert.Success(t, "start bash", err)
33+
var wg sync.WaitGroup
34+
go func() {
35+
wg.Add(1)
36+
defer wg.Done()
37+
38+
stdout, err := ioutil.ReadAll(process.Stdout())
39+
assert.Success(t, "read stdout", err)
40+
t.Logf("bash tty stdout = %s", stdout)
41+
assert.True(t, `bash "$" prompt found`, strings.HasSuffix(string(stdout), "$ "))
42+
}()
43+
go func() {
44+
wg.Add(1)
45+
defer wg.Done()
46+
47+
stderr, err := ioutil.ReadAll(process.Stderr())
48+
assert.Success(t, "read stderr", err)
49+
t.Logf("bash tty stderr = %s", stderr)
50+
assert.True(t, "stderr is empty", len(stderr) == 0)
51+
}()
52+
time.Sleep(5 * time.Second)
53+
54+
process.Close()
55+
wg.Wait()
56+
}

0 commit comments

Comments
 (0)