1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-09-19 00:19:59 +00:00
arikawa/voice/integration_test.go
diamondburned 6c332ac145 {Voice,}Gateway: Fixed various race conditions
This commit fixes race conditions in both package voice, package
voicegateway and package gateway.

Originally, several race conditions exist when both the user's and the
pacemaker's goroutines both want to do several things to the websocket
connection. For example, the user's goroutine could be writing, and the
pacemaker's goroutine could trigger a reconnection. This is racey.

This issue is partially fixed by removing the pacer loop from package
heart and combining the ticker into the event (pacemaker) loop itself.

Technically, a race condition could still be triggered with care, but
the API itself never guaranteed any of those. As events are handled
using an internal loop into a channel, a race condition will not be
triggered just by handling events and writing to the websocket.
2020-10-22 10:47:27 -07:00

167 lines
3.4 KiB
Go

// +build integration
package voice
import (
"context"
"encoding/binary"
"io"
"log"
"os"
"runtime"
"strconv"
"testing"
"time"
"github.com/diamondburned/arikawa/discord"
"github.com/diamondburned/arikawa/gateway"
"github.com/diamondburned/arikawa/utils/wsutil"
"github.com/diamondburned/arikawa/voice/voicegateway"
)
func TestIntegration(t *testing.T) {
config := mustConfig(t)
wsutil.WSDebug = func(v ...interface{}) {
_, file, line, _ := runtime.Caller(1)
caller := file + ":" + strconv.Itoa(line)
log.Println(append([]interface{}{caller}, v...)...)
}
v, err := NewVoiceFromToken("Bot " + config.BotToken)
if err != nil {
t.Fatal("Failed to create a new voice session:", err)
}
v.Gateway.AddIntent(gateway.IntentGuildVoiceStates)
v.ErrorLog = func(err error) {
t.Error(err)
}
if err := v.Open(); err != nil {
t.Fatal("Failed to connect:", err)
}
defer v.Close()
// Validate the given voice channel.
c, err := v.Channel(config.VoiceChID)
if err != nil {
t.Fatal("Failed to get channel:", err)
}
if c.Type != discord.GuildVoice {
t.Fatal("Channel isn't a guild voice channel.")
}
log.Println("The voice channel's name is", c.Name)
// Grab a timer to benchmark things.
finish := timer()
// Join the voice channel.
vs, err := v.JoinChannel(c.GuildID, c.ID, false, false)
if err != nil {
t.Fatal("Failed to join channel:", err)
}
defer func() {
log.Println("Disconnecting from the voice channel.")
if err := vs.Disconnect(); err != nil {
t.Fatal("Failed to disconnect:", err)
}
}()
finish("joining the voice channel")
// Trigger speaking.
if err := vs.Speaking(voicegateway.Microphone); err != nil {
t.Fatal("Failed to start speaking:", err)
}
defer func() {
log.Println("Stopping speaking.") // sounds grammatically wrong
if err := vs.StopSpeaking(); err != nil {
t.Fatal("Failed to stop speaking:", err)
}
}()
finish("sending the speaking command")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := vs.UseContext(ctx); err != nil {
t.Fatal("failed to set ctx into vs:", err)
}
// Copy the audio?
nicoReadTo(t, vs)
finish("copying the audio")
}
type testConfig struct {
BotToken string
VoiceChID discord.ChannelID
}
func mustConfig(t *testing.T) testConfig {
var token = os.Getenv("BOT_TOKEN")
if token == "" {
t.Fatal("Missing $BOT_TOKEN")
}
var sid = os.Getenv("VOICE_ID")
if sid == "" {
t.Fatal("Missing $VOICE_ID")
}
id, err := discord.ParseSnowflake(sid)
if err != nil {
t.Fatal("Invalid $VOICE_ID:", err)
}
return testConfig{
BotToken: token,
VoiceChID: discord.ChannelID(id),
}
}
// file is only a few bytes lolmao
func nicoReadTo(t *testing.T, dst io.Writer) {
t.Helper()
f, err := os.Open("testdata/nico.dca")
if err != nil {
t.Fatal("Failed to open nico.dca:", err)
}
defer f.Close()
var lenbuf [4]byte
for {
if _, err := io.ReadFull(f, lenbuf[:]); err != nil {
if err == io.EOF {
break
}
t.Fatal("failed to read:", err)
}
// Read the integer
framelen := int64(binary.LittleEndian.Uint32(lenbuf[:]))
// Copy the frame.
if _, err := io.CopyN(dst, f, framelen); err != nil && err != io.EOF {
t.Fatal("failed to write:", err)
}
}
}
// simple shitty benchmark thing
func timer() func(finished string) {
var then = time.Now()
return func(finished string) {
now := time.Now()
log.Println("Finished", finished+", took", now.Sub(then))
then = now
}
}