7acb293dea
Adds benchmarking tests to ensure the application remains low on resources. Covers payload parsing, auth verification, IRC chat line formatting, and more.
240 lines
5.0 KiB
Go
240 lines
5.0 KiB
Go
package irc
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRunOnceSendsAuthJoinAndRespondsToPing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
lines := make(chan string, 10)
|
|
serverErr := make(chan error, 1)
|
|
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
serverErr <- err
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
reader := bufio.NewReader(conn)
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
serverErr <- nil
|
|
return
|
|
}
|
|
serverErr <- err
|
|
return
|
|
}
|
|
line = strings.TrimRight(line, "\r\n")
|
|
lines <- line
|
|
switch line {
|
|
case "NICK viewer":
|
|
_, _ = conn.Write([]byte(":tmi.twitch.tv 001 viewer :Welcome\r\n"))
|
|
case "JOIN #streamer":
|
|
_, _ = conn.Write([]byte("PING :tmi.twitch.tv\r\n"))
|
|
case "PONG :tmi.twitch.tv":
|
|
_, _ = conn.Write([]byte(":viewer!viewer@viewer.tmi.twitch.tv JOIN #streamer\r\n"))
|
|
case "QUIT :parasocial --once complete":
|
|
serverErr <- nil
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
client := &Client{
|
|
Addr: listener.Addr().String(),
|
|
Login: "viewer",
|
|
Token: "token",
|
|
Streamer: "streamer",
|
|
Once: true,
|
|
Out: io.Discard,
|
|
}
|
|
if err := client.Run(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
want := []string{
|
|
"PASS oauth:token",
|
|
"NICK viewer",
|
|
"JOIN #streamer",
|
|
"PONG :tmi.twitch.tv",
|
|
"QUIT :parasocial --once complete",
|
|
}
|
|
for _, expected := range want {
|
|
select {
|
|
case got := <-lines:
|
|
if got != expected {
|
|
t.Fatalf("line = %q, want %q", got, expected)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timed out waiting for %q", expected)
|
|
}
|
|
}
|
|
|
|
select {
|
|
case err := <-serverErr:
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("server did not finish")
|
|
}
|
|
}
|
|
|
|
func TestRunEmitsJoinAndPostJoinEvents(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
events := make(chan Event, 4)
|
|
serverErr := make(chan error, 1)
|
|
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
serverErr <- err
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
reader := bufio.NewReader(conn)
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
serverErr <- err
|
|
return
|
|
}
|
|
line = strings.TrimRight(line, "\r\n")
|
|
switch line {
|
|
case "NICK viewer":
|
|
_, _ = conn.Write([]byte(":tmi.twitch.tv 001 viewer :Welcome\r\n"))
|
|
case "JOIN #streamer":
|
|
_, _ = conn.Write([]byte(":viewer!viewer@viewer.tmi.twitch.tv JOIN #streamer\r\n"))
|
|
_, _ = conn.Write([]byte(":someone!someone@someone.tmi.twitch.tv PRIVMSG #streamer :hello\r\n"))
|
|
_, _ = conn.Write([]byte("PING :tmi.twitch.tv\r\n"))
|
|
case "PONG :tmi.twitch.tv":
|
|
serverErr <- nil
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
client := &Client{
|
|
Addr: listener.Addr().String(),
|
|
Login: "viewer",
|
|
Token: "token",
|
|
Streamer: "streamer",
|
|
Events: func(event Event) {
|
|
events <- event
|
|
},
|
|
Out: io.Discard,
|
|
}
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- client.Run(ctx)
|
|
}()
|
|
|
|
var got []Event
|
|
for len(got) < 2 {
|
|
select {
|
|
case event := <-events:
|
|
got = append(got, event)
|
|
if event.Line == ":someone!someone@someone.tmi.twitch.tv PRIVMSG #streamer :hello" {
|
|
cancel()
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timed out waiting for IRC events")
|
|
}
|
|
}
|
|
|
|
if err := <-done; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := <-serverErr; err != nil && err != io.EOF {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
want := []Event{
|
|
{Streamer: "streamer", State: StateJoined, Line: "Joined #streamer as viewer"},
|
|
{Streamer: "streamer", Line: ":someone!someone@someone.tmi.twitch.tv PRIVMSG #streamer :hello"},
|
|
}
|
|
if !reflect.DeepEqual(got[:2], want) {
|
|
t.Fatalf("events = %#v, want %#v", got[:2], want)
|
|
}
|
|
}
|
|
|
|
func TestAuthFailureReturnsError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
_, _ = conn.Write([]byte(":tmi.twitch.tv NOTICE * :Login authentication failed\r\n"))
|
|
}()
|
|
|
|
client := &Client{
|
|
Addr: listener.Addr().String(),
|
|
Login: "viewer",
|
|
Token: "bad-token",
|
|
Streamer: "streamer",
|
|
Out: io.Discard,
|
|
}
|
|
err = client.Run(context.Background())
|
|
if err == nil {
|
|
t.Fatal("expected auth failure")
|
|
}
|
|
if !strings.Contains(err.Error(), "authentication failure") {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
}
|
|
|
|
func BenchmarkPingPayload(b *testing.B) {
|
|
line := "PING :tmi.twitch.tv"
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = pingPayload(line)
|
|
}
|
|
}
|
|
|
|
func BenchmarkIsJoinConfirmation(b *testing.B) {
|
|
line := ":jules!jules@jules.tmi.twitch.tv JOIN #parasocial"
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = isJoinConfirmation(line, "jules", "parasocial")
|
|
}
|
|
}
|