Re-vendor the canonical files from sneak/prompts at dd4027b (closes #112)
check / check (push) Failing after 4s

The shared files are fetched from sneak/prompts dd4027b, with this
repository's own entries after the shared content in .gitignore,
.editorconfig and .dockerignore.

Lint and tests are Dockerfile phases (golangci-lint v2.14.0, Debian Go
1.24.13) that the build stage depends on, and the Makefile targets call
the script/ entrypoints. make fmt also formats Markdown with prettier.

Fixes for the new lint findings: the auth cookie is always Secure, an
IRC connection's relay goroutine stops when the connection closes, and
repeated strings are constants. neoirc-cli treats a plain-HTTP server on
localhost or a loopback address as secure, so local use keeps its session.

Whether the 60-second test cap covers building the test phase is open
on sneak/prompts issue 113.

Model: opus-5-5
This commit was merged in pull request #115.
This commit is contained in:
2026-10-06 21:01:46 +02:00
parent 9a46421902
commit bb7bcb31ea
47 changed files with 2975 additions and 1427 deletions
+8 -15
View File
@@ -159,9 +159,7 @@ func (c *Conn) handleJoin(
return
}
channels := strings.Split(msg.Params[0], ",")
for _, chanName := range channels {
for chanName := range strings.SplitSeq(msg.Params[0], ",") {
chanName = strings.TrimSpace(chanName)
if !strings.HasPrefix(chanName, "#") {
@@ -305,9 +303,7 @@ func (c *Conn) handlePart(
reason = msg.Params[1]
}
channels := strings.Split(msg.Params[0], ",")
for _, ch := range channels {
for ch := range strings.SplitSeq(msg.Params[0], ",") {
ch = strings.TrimSpace(ch)
c.partChannel(ctx, ch, reason)
}
@@ -619,8 +615,8 @@ func (c *Conn) applyChannelModes(
) {
adding := true
argIdx := 0
applied := ""
appliedArgs := ""
var applied, appliedArgs strings.Builder
for _, modeChar := range modeStr {
var res modeResult
@@ -672,16 +668,13 @@ func (c *Conn) applyChannelModes(
argIdx += res.consumed
if !res.skip {
applied += res.applied
appliedArgs += res.appliedArgs
applied.WriteString(res.applied)
appliedArgs.WriteString(res.appliedArgs)
}
}
if applied != "" {
modeReply := applied
if appliedArgs != "" {
modeReply += appliedArgs
}
if applied.Len() > 0 {
modeReply := applied.String() + appliedArgs.String()
c.send(FormatMessage(
c.hostmask(), "MODE", channel, modeReply,
+4 -3
View File
@@ -62,7 +62,6 @@ type Conn struct {
lastQueueID int64
closed bool
cancel context.CancelFunc
}
func newConn(
@@ -151,8 +150,10 @@ func resolveHost(ctx context.Context, addr string) string {
}
// serve is the main loop for a single IRC client connection.
// Cancelling ctx when it returns stops the relay goroutine.
func (c *Conn) serve(ctx context.Context) {
ctx, c.cancel = context.WithCancel(ctx)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer c.cleanup(ctx)
scanner := bufio.NewScanner(c.conn)
@@ -481,7 +482,7 @@ func (c *Conn) deliverMOTD() {
"- %s Message of the Day -", c.serverSfx,
))
for _, line := range strings.Split(motd, "\n") {
for line := range strings.SplitSeq(motd, "\n") {
c.sendNumeric(irc.RplMotd, "- "+line)
}
+4
View File
@@ -282,6 +282,7 @@ func TestIntegrationTwoClients(t *testing.T) {
// Both nicks should appear in the name list.
foundBothNames := false
for _, line := range aliceNames {
if strings.Contains(line, " 353 ") &&
strings.Contains(line, "alice") &&
@@ -671,6 +672,7 @@ func TestIntegrationTwoClients(t *testing.T) {
})
foundPartErr := false
for _, line := range bobPartFail {
if strings.Contains(line, " 403 ") ||
strings.Contains(line, " 442 ") {
@@ -833,6 +835,7 @@ func TestIntegrationModeModerated(t *testing.T) {
})
foundModErr := false
for _, line := range bobLines {
if strings.Contains(line, " 404 ") ||
strings.Contains(line, " 482 ") {
@@ -859,6 +862,7 @@ func TestIntegrationModeModerated(t *testing.T) {
})
bob.send("PRIVMSG #modtest :voiced message")
aliceLines := alice.readUntil(func(l string) bool {
return strings.Contains(l, "voiced message")
})
+40 -28
View File
@@ -4,12 +4,18 @@ import (
"testing"
"sneak.berlin/go/neoirc/internal/ircserver"
"sneak.berlin/go/neoirc/pkg/irc"
)
//nolint:funlen // table-driven test
func TestParseMessage(t *testing.T) {
t.Parallel()
const (
nick = "alice"
channel = "#general"
)
tests := []struct {
name string
input string
@@ -24,10 +30,10 @@ func TestParseMessage(t *testing.T) {
},
{
name: "simple command",
input: "PING",
input: irc.CmdPing,
want: &ircserver.Message{
Prefix: "",
Command: "PING",
Command: irc.CmdPing,
Params: nil,
},
wantNil: false,
@@ -38,7 +44,7 @@ func TestParseMessage(t *testing.T) {
want: &ircserver.Message{
Prefix: "",
Command: "NICK",
Params: []string{"alice"},
Params: []string{nick},
},
wantNil: false,
},
@@ -57,8 +63,8 @@ func TestParseMessage(t *testing.T) {
input: "PRIVMSG #general :hello world",
want: &ircserver.Message{
Prefix: "",
Command: "PRIVMSG",
Params: []string{"#general", "hello world"},
Command: irc.CmdPrivmsg,
Params: []string{channel, "hello world"},
},
wantNil: false,
},
@@ -68,7 +74,7 @@ func TestParseMessage(t *testing.T) {
want: &ircserver.Message{
Prefix: "server.example.com",
Command: "001",
Params: []string{"alice", "Welcome to IRC"},
Params: []string{nick, "Welcome to IRC"},
},
wantNil: false,
},
@@ -79,7 +85,7 @@ func TestParseMessage(t *testing.T) {
Prefix: "",
Command: "USER",
Params: []string{
"alice", "0", "*", "Alice Smith",
nick, "0", "*", "Alice Smith",
},
},
wantNil: false,
@@ -90,7 +96,7 @@ func TestParseMessage(t *testing.T) {
want: &ircserver.Message{
Prefix: "",
Command: "JOIN",
Params: []string{"#general"},
Params: []string{channel},
},
wantNil: false,
},
@@ -99,17 +105,17 @@ func TestParseMessage(t *testing.T) {
input: "QUIT :leaving now",
want: &ircserver.Message{
Prefix: "",
Command: "QUIT",
Command: irc.CmdQuit,
Params: []string{"leaving now"},
},
wantNil: false,
},
{
name: "quit without reason",
input: "QUIT",
input: irc.CmdQuit,
want: &ircserver.Message{
Prefix: "",
Command: "QUIT",
Command: irc.CmdQuit,
Params: nil,
},
wantNil: false,
@@ -120,7 +126,7 @@ func TestParseMessage(t *testing.T) {
want: &ircserver.Message{
Prefix: "",
Command: "MODE",
Params: []string{"#general"},
Params: []string{channel},
},
wantNil: false,
},
@@ -131,7 +137,7 @@ func TestParseMessage(t *testing.T) {
Prefix: "",
Command: "KICK",
Params: []string{
"#general", "bob", "misbehaving",
channel, "bob", "misbehaving",
},
},
wantNil: false,
@@ -141,8 +147,8 @@ func TestParseMessage(t *testing.T) {
input: "PRIVMSG #general :",
want: &ircserver.Message{
Prefix: "",
Command: "PRIVMSG",
Params: []string{"#general", ""},
Command: irc.CmdPrivmsg,
Params: []string{channel, ""},
},
wantNil: false,
},
@@ -161,7 +167,7 @@ func TestParseMessage(t *testing.T) {
input: "PING :irc.example.com",
want: &ircserver.Message{
Prefix: "",
Command: "PING",
Command: irc.CmdPing,
Params: []string{"irc.example.com"},
},
wantNil: false,
@@ -173,7 +179,7 @@ func TestParseMessage(t *testing.T) {
Prefix: "",
Command: "TOPIC",
Params: []string{
"#general",
channel,
"Welcome to the channel!",
},
},
@@ -237,6 +243,12 @@ func TestParseMessage(t *testing.T) {
func TestFormatMessage(t *testing.T) {
t.Parallel()
const (
nick = "alice"
channel = "#general"
serverName = "server"
)
tests := []struct {
name string
prefix string
@@ -247,35 +259,35 @@ func TestFormatMessage(t *testing.T) {
{
name: "simple command",
prefix: "",
command: "PING",
command: irc.CmdPing,
params: nil,
want: "PING",
want: irc.CmdPing,
},
{
name: "with prefix",
prefix: "server",
prefix: serverName,
command: "PONG",
params: []string{"server"},
params: []string{serverName},
want: ":server PONG server",
},
{
name: "privmsg with trailing",
prefix: "alice!alice@host",
command: "PRIVMSG",
params: []string{"#general", "hello world"},
command: irc.CmdPrivmsg,
params: []string{channel, "hello world"},
want: ":alice!alice@host PRIVMSG #general :hello world",
},
{
name: "numeric reply",
prefix: "server",
prefix: serverName,
command: "001",
params: []string{"alice", "Welcome to IRC"},
params: []string{nick, "Welcome to IRC"},
want: ":server 001 alice :Welcome to IRC",
},
{
name: "empty trailing",
prefix: "server",
command: "PRIVMSG",
prefix: serverName,
command: irc.CmdPrivmsg,
params: []string{"#chan", ""},
want: ":server PRIVMSG #chan :",
},
@@ -302,7 +314,7 @@ func TestParseFormatRoundTrip(t *testing.T) {
// parameter either contains a space (gets ':' prefix
// on format) or is a non-trailing single token.
lines := []string{
"PING",
irc.CmdPing,
"NICK alice",
"PRIVMSG #general :hello world",
"JOIN #general",
+6 -4
View File
@@ -81,22 +81,24 @@ func New(
// start begins listening for TCP connections.
//
//nolint:contextcheck // long-lived server ctx, not the short Fx one
func (s *Server) start(_ context.Context, addr string) error {
ln, err := net.Listen("tcp", addr)
func (s *Server) start(ctx context.Context, addr string) error {
var listenConfig net.ListenConfig
ln, err := listenConfig.Listen(ctx, "tcp", addr)
if err != nil {
return fmt.Errorf("irc listen: %w", err)
}
s.listener = ln
ctx, cancel := context.WithCancel(context.Background())
serverCtx, cancel := context.WithCancel(context.Background())
s.cancel = cancel
s.log.Info(
"irc server listening", "addr", addr,
)
go s.acceptLoop(ctx)
go s.acceptLoop(serverCtx)
return nil
}
+59 -3
View File
@@ -7,6 +7,7 @@ import (
"log/slog"
"net"
"os"
"runtime"
"strings"
"testing"
"time"
@@ -71,7 +72,9 @@ func newTestEnv(t *testing.T) *testEnv {
MOTD: "Welcome to test IRC",
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
var listenConfig net.ListenConfig
listener, err := listenConfig.Listen(t.Context(), "tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
@@ -116,10 +119,12 @@ func newTestEnv(t *testing.T) *testEnv {
func (env *testEnv) dial(t *testing.T) *testClient {
t.Helper()
conn, err := net.DialTimeout(
dialer := net.Dialer{Timeout: testTimeout}
conn, err := dialer.DialContext(
t.Context(),
"tcp",
env.srv.Listener().Addr().String(),
testTimeout,
)
if err != nil {
t.Fatalf("dial: %v", err)
@@ -323,6 +328,7 @@ func TestPrivmsgBetweenClients(t *testing.T) {
bob.joinAndDrain("#chat")
alice.send("PRIVMSG #chat :hello bob!")
lines := bob.sendAndExpect("PING :sync", "hello bob!")
assertContains(t, lines, "hello bob!", "channel PRIVMSG")
}
@@ -603,6 +609,56 @@ func TestNamesNonExistentChannel(t *testing.T) {
)
}
// TestRelayStopsWhenConnectionCloses checks that closing a
// registered client's connection stops the goroutine that
// relays its messages.
//
//nolint:paralleltest // counts every goroutine in the process
func TestRelayStopsWhenConnectionCloses(t *testing.T) {
env := newTestEnv(t)
client := env.dial(t)
client.register("relaystop")
waitForRelayGoroutines(t, 1)
err := client.conn.Close()
if err != nil {
t.Fatalf("close: %v", err)
}
waitForRelayGoroutines(t, 0)
}
const (
stackDumpSize = 1 << 20
relayCheckStep = 10 * time.Millisecond
)
// waitForRelayGoroutines waits until exactly want goroutines
// are running the relay loop, and fails the test if that does
// not happen within testTimeout.
func waitForRelayGoroutines(t *testing.T, want int) {
t.Helper()
deadline := time.Now().Add(testTimeout)
stacks := make([]byte, stackDumpSize)
for {
n := runtime.Stack(stacks, true)
got := strings.Count(string(stacks[:n]), "(*Conn).relayMessages(")
if got == want {
return
}
if time.Now().After(deadline) {
t.Fatalf("relay goroutines: got %d, want %d", got, want)
}
time.Sleep(relayCheckStep)
}
}
func BenchmarkParseMessage(b *testing.B) {
line := ":nick!user@host PRIVMSG #channel :Hello, world!"