- diffConfig reports the env var names (from new struct tags) instead of Go field names, so the reload-env reply names what the user can change - the monitor's GPU line now shows the game detector's last finding (external holders or none) and how long ago the check ran - control channel: 10s per-connection watchdog (abortive force-close), cap of 32 concurrent connections, reload-env rate-limited; command read was already capped at 4 KiB
104 lines
2.3 KiB
Go
104 lines
2.3 KiB
Go
package control
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
server, client := net.Pipe()
|
|
go serveConn(server, func(cmd string) string {
|
|
if cmd != CmdUpdateNow {
|
|
return "ERR unknown command: " + cmd
|
|
}
|
|
return "OK v0.2.2 is up to date"
|
|
})
|
|
reply, err := readReply(client, CmdUpdateNow)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reply != "OK v0.2.2 is up to date" {
|
|
t.Fatalf("reply = %q", reply)
|
|
}
|
|
|
|
server2, client2 := net.Pipe()
|
|
go serveConn(server2, func(cmd string) string { return "ERR unknown command: " + cmd })
|
|
reply, err = readReply(client2, "bogus")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.HasPrefix(reply, "ERR ") {
|
|
t.Fatalf("reply = %q, want ERR prefix", reply)
|
|
}
|
|
}
|
|
|
|
func TestEmptyReplyIsUnavailable(t *testing.T) {
|
|
server, client := net.Pipe()
|
|
go serveConn(server, func(cmd string) string {
|
|
server.Close() // hang up without answering
|
|
return ""
|
|
})
|
|
if _, err := readReply(client, CmdUpdateNow); !errors.Is(err, ErrUnavailable) {
|
|
t.Fatalf("err = %v, want ErrUnavailable", err)
|
|
}
|
|
}
|
|
|
|
func TestServeCap(t *testing.T) {
|
|
for i := 0; i < maxConns; i++ {
|
|
connSem <- struct{}{}
|
|
}
|
|
defer func() {
|
|
for i := 0; i < maxConns; i++ {
|
|
<-connSem
|
|
}
|
|
}()
|
|
server, client := net.Pipe()
|
|
defer server.Close()
|
|
defer client.Close()
|
|
if serve(server, func(string) string { return "OK" }) {
|
|
t.Fatal("serve accepted a connection beyond the cap")
|
|
}
|
|
}
|
|
|
|
// forcePipe records ForceClose calls for the watchdog test.
|
|
type forcePipe struct {
|
|
net.Conn
|
|
forced chan struct{}
|
|
}
|
|
|
|
func (c forcePipe) ForceClose() error {
|
|
err := c.Conn.Close()
|
|
close(c.forced)
|
|
return err
|
|
}
|
|
|
|
func TestConnWatchdog(t *testing.T) {
|
|
old := connTimeout
|
|
connTimeout = 50 * time.Millisecond
|
|
defer func() { connTimeout = old }()
|
|
|
|
server, client := net.Pipe()
|
|
defer client.Close()
|
|
fc := forcePipe{Conn: server, forced: make(chan struct{})}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
serveConn(fc, func(string) string { return "OK" })
|
|
close(done)
|
|
}()
|
|
// The client never sends anything; the watchdog must tear the
|
|
// connection down instead of blocking forever.
|
|
select {
|
|
case <-fc.forced:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("watchdog did not force-close the stalled connection")
|
|
}
|
|
select {
|
|
case <-done:
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("serveConn still blocked after the force close")
|
|
}
|
|
}
|