github.com/diamondburned/arikawa/v2@v2.1.0/voice/session_test.go (about) 1 package voice 2 3 import ( 4 "context" 5 "encoding/binary" 6 "io" 7 "log" 8 "os" 9 "runtime" 10 "strconv" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/diamondburned/arikawa/v2/discord" 16 "github.com/diamondburned/arikawa/v2/internal/testenv" 17 "github.com/diamondburned/arikawa/v2/state" 18 "github.com/diamondburned/arikawa/v2/utils/wsutil" 19 "github.com/diamondburned/arikawa/v2/voice/voicegateway" 20 "github.com/pkg/errors" 21 ) 22 23 func TestIntegration(t *testing.T) { 24 config := testenv.Must(t) 25 26 wsutil.WSDebug = func(v ...interface{}) { 27 _, file, line, _ := runtime.Caller(1) 28 caller := file + ":" + strconv.Itoa(line) 29 log.Println(append([]interface{}{caller}, v...)...) 30 } 31 32 s, err := state.New("Bot " + config.BotToken) 33 if err != nil { 34 t.Fatal("Failed to create a new state:", err) 35 } 36 AddIntents(s.Gateway) 37 38 if err := s.Open(); err != nil { 39 t.Fatal("Failed to connect:", err) 40 } 41 t.Cleanup(func() { s.Close() }) 42 43 // Validate the given voice channel. 44 c, err := s.Channel(config.VoiceChID) 45 if err != nil { 46 t.Fatal("Failed to get channel:", err) 47 } 48 if c.Type != discord.GuildVoice { 49 t.Fatal("Channel isn't a guild voice channel.") 50 } 51 52 log.Println("The voice channel's name is", c.Name) 53 54 v, err := NewSession(s) 55 if err != nil { 56 t.Fatal("Failed to create a new voice session:", err) 57 } 58 v.ErrorLog = func(err error) { t.Error(err) } 59 60 // Grab a timer to benchmark things. 61 finish := timer() 62 63 // Add handler to receive speaking update beforehand. 64 v.AddHandler(func(e *voicegateway.SpeakingEvent) { 65 finish("receiving voice speaking event") 66 }) 67 68 // Join the voice channel concurrently. 69 raceMe(t, "failed to join voice channel", func() (interface{}, error) { 70 return nil, v.JoinChannel(c.GuildID, c.ID, false, false) 71 }) 72 73 t.Cleanup(func() { 74 log.Println("Leaving the voice channel concurrently.") 75 76 raceMe(t, "failed to leave voice channel", func() (interface{}, error) { 77 return nil, v.Leave() 78 }) 79 }) 80 81 finish("joining the voice channel") 82 83 // Create a context and only cancel it AFTER we're done sending silence 84 // frames. 85 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 86 t.Cleanup(cancel) 87 88 // Trigger speaking. 89 if err := v.Speaking(voicegateway.Microphone); err != nil { 90 t.Fatal("failed to start speaking:", err) 91 } 92 93 finish("sending the speaking command") 94 95 if err := v.UseContext(ctx); err != nil { 96 t.Fatal("failed to set ctx into vs:", err) 97 } 98 99 f, err := os.Open("testdata/nico.dca") 100 if err != nil { 101 t.Fatal("Failed to open nico.dca:", err) 102 } 103 defer f.Close() 104 105 var lenbuf [4]byte 106 107 // Copy the audio? 108 for { 109 if _, err := io.ReadFull(f, lenbuf[:]); err != nil { 110 if err == io.EOF { 111 break 112 } 113 t.Fatal("failed to read:", err) 114 } 115 116 // Read the integer 117 framelen := int64(binary.LittleEndian.Uint32(lenbuf[:])) 118 119 // Copy the frame. 120 if _, err := io.CopyN(v, f, framelen); err != nil && err != io.EOF { 121 t.Fatal("failed to write:", err) 122 } 123 } 124 125 finish("copying the audio") 126 } 127 128 // raceMe intentionally calls fn multiple times in goroutines to ensure it's not 129 // racy. 130 func raceMe(t *testing.T, wrapErr string, fn func() (interface{}, error)) interface{} { 131 const n = 3 // run 3 times 132 t.Helper() 133 134 // It is very ironic how this method itself is racy. 135 136 var wgr sync.WaitGroup 137 var mut sync.Mutex 138 var val interface{} 139 var err error 140 141 for i := 0; i < n; i++ { 142 wgr.Add(1) 143 go func() { 144 v, e := fn() 145 146 mut.Lock() 147 val = v 148 err = e 149 mut.Unlock() 150 151 if e != nil { 152 log.Println("Potential race test error:", e) 153 } 154 155 wgr.Done() 156 }() 157 } 158 159 wgr.Wait() 160 161 if err != nil { 162 t.Fatal("Race test failed:", errors.Wrap(err, wrapErr)) 163 } 164 165 return val 166 } 167 168 // simple shitty benchmark thing 169 func timer() func(finished string) { 170 var then = time.Now() 171 172 return func(finished string) { 173 now := time.Now() 174 log.Println("Finished", finished+", took", now.Sub(then)) 175 then = now 176 } 177 }