aboutsummaryrefslogtreecommitdiff
path: root/internal/net/session_writer_test.go
blob: 94de4ba30e42fa521c037b101946a39fa7b95ba2 (plain)
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
package net

import (
	stdnet "net"
	"testing"
	"time"

	"thehouseoficarus/internal/config"
)

// TestEnqueueAfterCloseDoesNotPanic: a broadcast racing Close() must drop
// the message, never panic on a send to the closed outbound queue.
func TestEnqueueAfterCloseDoesNotPanic(t *testing.T) {
	conn := &testConn{}
	sess := &Session{Conn: conn}
	sess.StartWriter()
	sess.Close()

	defer func() {
		if r := recover(); r != nil {
			t.Fatalf("write after Close panicked: %v", r)
		}
		// Wait for the writer goroutine to drain and exit cleanly.
		select {
		case <-sess.writeDone:
		case <-time.After(time.Second):
			t.Fatal("writer goroutine never exited after close")
		}
	}()
	for i := 0; i < 3; i++ {
		sess.WriteLine("late broadcast")
	}
}

// TestTelnetSessionUsesQueuedWriter: production telnet sessions must start
// the queued writer like the websocket transport, so slow clients get
// per-message write deadlines and can never block game code.
func TestTelnetSessionUsesQueuedWriter(t *testing.T) {
	cfg := config.Default()
	cfg.Telnet.Enabled = true
	cfg.Telnet.Port = 0
	s, err := NewServer(cfg)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Shutdown()

	observed := make(chan bool, 1)
	s.handler = func(sess *Session, _ string) { observed <- sess.outbox != nil }
	go s.serveTelnet()

	conn, err := stdnet.Dial("tcp", s.telnetLn.Addr().String())
	if err != nil {
		t.Fatal(err)
	}
	defer conn.Close()
	conn.Write([]byte("hello\n"))

	select {
	case queued := <-observed:
		if !queued {
			t.Fatal("telnet session has no outbound queue; writes are synchronous and can stall the world")
		}
	case <-time.After(time.Second):
		t.Fatal("session handler was not invoked")
	}
}