mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-12-02 20:02:53 +00:00
diamondburned
6c332ac145
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.
167 lines
3.4 KiB
Go
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
|
|
}
|
|
}
|