Compare commits

...

22 Commits

Author SHA1 Message Date
diamondburned 040622f494
*: Fix test -v flag in CI 2023-11-04 03:12:39 -07:00
diamondburned 619521c41e
*: Figure out GitHub Actions ternaries 2023-11-04 03:08:25 -07:00
diamondburned 7bfaf41a15
*: Fix messed up GitHub Actions syntax 2023-11-04 03:02:56 -07:00
diamondburned ff74f27e7f
*: Fix messed up GitHub Actions checks
I'm so tired of this. Oh my fucking god.
2023-11-04 03:01:22 -07:00
diamondburned 30c2f9e5de
*: Enable verbose test on debug mode 2023-11-04 02:55:33 -07:00
diamondburned ba08c57156
*: Run only short integration tests in CI by default 2023-11-04 02:49:39 -07:00
diamondburned 47ce508089
*: Limit integration testing to one at a time in CI 2023-11-04 02:45:18 -07:00
diamondburned 6f4737aedc
README: Update badge 2023-11-04 02:39:09 -07:00
diamondburned a70012b289
shard: Fix test breaking on expected error 2023-11-04 02:35:17 -07:00
diamondburned 87fd11a68a
README: Update badges 2023-11-04 02:34:21 -07:00
diamondburned b0a773c3f8
*: Improve WSDebug logging in tests 2023-11-04 02:27:37 -07:00
diamondburned 2c2daec84b
*: Increase test timeout throughout
This should make integration tests pass in CI.
2023-11-04 02:21:49 -07:00
diamondburned 1e4c5c135a
*: Increase example test timeout 2023-11-04 01:57:57 -07:00
diamondburned ad26a72256
*: Ignore examples/voice in integration test 2023-11-04 01:57:31 -07:00
diamondburned 5ebd28bab6
examples/voice: Fix build error 2023-11-04 01:49:41 -07:00
diamondburned 093436066e
cmdroute: Fix OverwriteCommands being broken 2023-11-04 01:48:17 -07:00
diamondburned edecfde113
*: Add missing envvars to test workflow 2023-11-04 01:44:16 -07:00
diamondburned 415069be30
*: Add integration tests for examples 2023-11-04 01:35:48 -07:00
diamondburned e7c0290063
*: Update go.work.sum 2023-11-04 01:35:48 -07:00
diamondburned 0370ff1904
*: Introduce go.work for submodules 2023-11-04 01:35:47 -07:00
twoscott 4ef179343a state: Add missing error check 2023-11-03 22:33:28 -07:00
diamondburned ff709fc16c
bot: Fix failing ./extras/middlewares test 2023-11-03 19:35:40 -07:00
21 changed files with 339 additions and 226 deletions

View File

@ -3,9 +3,27 @@ name: Test
on:
push:
pull_request:
workflow_dispatch:
inputs:
short-integration-tests:
description: 'Run only short integration tests'
required: false
default: false
type: boolean
jobs:
test:
nix-env:
name: Initialize Nix
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Nix packages
uses: diamondburned/cache-install@main
generate:
name: Generate
needs: [nix-env]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -22,15 +40,57 @@ jobs:
exit 1
fi
build:
name: Build
runs-on: ubuntu-latest
needs: [generate]
steps:
- uses: actions/checkout@v2
- name: Install Nix packages
uses: diamondburned/cache-install@main
- name: Build
run: |
go build ./...
run: go build ./...
unit-test:
name: Unit Test
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v2
- name: Install Nix packages
uses: diamondburned/cache-install@main
- name: Test
run: go test $TEST_FLAGS ./...
env:
TEST_FLAGS: >-
-v=${{ runner.debug && '1' || '0' }}
integration-test:
name: Integration Test
concurrency: integration-test
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v2
- name: Install Nix packages
uses: diamondburned/cache-install@main
- name: Test
run: |
go test -coverprofile /tmp/coverage.out -race ./...
go test -coverprofile /tmp/coverage.out -race $TEST_FLAGS ./...
go tool cover -func /tmp/coverage.out
env:
TEST_FLAGS: >-
-v=${{ runner.debug && '1' || '0' }}
-short=${{ (github.event-name == 'workflow_dispatch' || github.event.inputs.short-integration-tests) && '1' || '0' }}
CHANNEL_ID: ${{ secrets.CHANNEL_ID }}
GUILD_ID: ${{ secrets.GUILD_ID }}
VOICE_ID: ${{ secrets.VOICE_ID }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
- name: Upload coverage profile

View File

@ -53,6 +53,10 @@ func main() {
if webhookAddr != "" {
state := state.NewAPIOnlyState(token, nil)
if err := cmdroute.OverwriteCommands(state, commands); err != nil {
log.Fatalln("cannot update commands:", err)
}
h := newHandler(state)
if err := overwriteCommands(state); err != nil {
@ -74,13 +78,13 @@ func main() {
log.Println("connected to the gateway as", me.Tag())
})
h := newHandler(state)
state.AddInteractionHandler(h)
if err := overwriteCommands(state); err != nil {
if err := cmdroute.OverwriteCommands(state, commands); err != nil {
log.Fatalln("cannot update commands:", err)
}
h := newHandler(state)
state.AddInteractionHandler(h)
if err := h.s.Connect(context.Background()); err != nil {
log.Fatalln("cannot connect:", err)
}

View File

@ -54,7 +54,7 @@ func main() {
log.Println("connected to the gateway as", me.Tag())
})
if err := overwriteCommands(h.s); err != nil {
if err := cmdroute.OverwriteCommands(h.s, commands); err != nil {
log.Fatalln("cannot update commands:", err)
}
@ -66,10 +66,6 @@ func main() {
}
}
func overwriteCommands(s *state.State) error {
return cmdroute.OverwriteCommands(s, commands)
}
type handler struct {
*cmdroute.Router
s *state.State

View File

@ -0,0 +1 @@
voice

View File

@ -0,0 +1,155 @@
package examples_test
import (
"bytes"
"errors"
"io"
"os"
"os/exec"
"strings"
"testing"
"time"
_ "embed"
"github.com/diamondburned/arikawa/v3/internal/testenv"
)
//go:embed integration_exclude.txt
var integrationExclude string
func TestExamples(t *testing.T) {
// Assert that the tests only run when the environment variables are set.
testenv.Must(t)
// Assert that the Go compiler is available.
_, err := exec.LookPath("go")
if err != nil {
t.Skip("skipping test; go compiler not found")
}
excluded := make(map[string]bool)
for _, line := range strings.Split(string(integrationExclude), "\n") {
excluded[strings.TrimSpace(line)] = true
}
examplePackages, err := os.ReadDir(".")
if err != nil {
t.Fatal(err)
}
// Run all examples for 10 seconds each.
//
// TODO(diamondburned): find a way to detect that the bot is online. Maybe
// force all examples to print the current username?
const exampleRunDuration = 60 * time.Second
buildDir, err := os.MkdirTemp("", "arikawa-examples")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := os.RemoveAll(buildDir); err != nil {
t.Log("cannot remove artifacts dir:", err)
}
})
for _, pkg := range examplePackages {
if !pkg.IsDir() {
continue
}
// Assert package main.
if _, err := os.Stat(pkg.Name() + "/main.go"); err != nil {
continue
}
pkg := pkg
t.Run(pkg.Name(), func(t *testing.T) {
t.Parallel()
binPath := buildDir + "/" + pkg.Name()
gobuild := exec.Command("go", "build", "-o", binPath, "./"+pkg.Name())
gobuild.Stderr = &lineLogger{dst: func(line string) { t.Log("go build:", line) }}
if err := gobuild.Run(); err != nil {
t.Fatal("cannot go build:", err)
}
if excluded[pkg.Name()] {
t.Skip("skipping excluded example", pkg.Name())
}
timer := time.NewTimer(exampleRunDuration)
t.Cleanup(func() { timer.Stop() })
bin := exec.Command(binPath)
bin.Stderr = &lineLogger{dst: func(line string) { t.Log(pkg.Name()+":", line) }}
if err := bin.Start(); err != nil {
t.Fatal("cannot start binary:", err)
}
cmdDone := make(chan struct{})
go func() {
defer close(cmdDone)
err := bin.Wait()
if err == nil {
return // all good
}
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) || !exitErr.Exited() {
return
}
t.Error("binary exited with status", exitErr.ExitCode())
}()
select {
case <-cmdDone:
return
case <-timer.C:
}
// Works well. Just exit.
if err := bin.Process.Signal(os.Interrupt); err != nil {
t.Log("cannot interrupt binary:", err)
bin.Process.Kill()
}
exitTimer := time.NewTimer(5 * time.Second)
t.Cleanup(func() { exitTimer.Stop() })
select {
case <-cmdDone:
return
case <-exitTimer.C:
t.Error("example did not exit after 5 seconds")
bin.Process.Kill()
}
})
}
}
type lineLogger struct {
dst func(string)
buf bytes.Buffer
}
func (l *lineLogger) Write(p []byte) (n int, err error) {
n, _ = l.buf.Write(p)
for {
line, err := l.buf.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return n, err
}
line = line[:len(line)-1] // remove newline
l.dst(line)
}
return n, nil
}

View File

@ -2,12 +2,9 @@ module github.com/diamondburned/arikawa/v3/0-examples/voice
go 1.17
replace github.com/diamondburned/arikawa/v3 => ../../
require (
github.com/diamondburned/arikawa/v3 v3.0.0-rc.6
github.com/diamondburned/oggreader v0.0.0-20201118014549-87df9534b647
github.com/pkg/errors v0.9.1
)
require (

View File

@ -4,8 +4,6 @@ github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"flag"
"fmt"
"log"
"os"
"os/exec"
@ -61,7 +62,7 @@ const (
func start(ctx context.Context, s *state.State, id discord.ChannelID, file string) error {
v, err := voice.NewSession(s)
if err != nil {
return errors.Wrap(err, "cannot make new voice session")
return fmt.Errorf("cannot make new voice session: %w", err)
}
// Optimize Opus frame duration. This step is optional, but it is
@ -97,30 +98,30 @@ func start(ctx context.Context, s *state.State, id discord.ChannelID, file strin
stdout, err := ffmpeg.StdoutPipe()
if err != nil {
return errors.Wrap(err, "failed to get stdout pipe")
return fmt.Errorf("failed to get stdout pipe: %w", err)
}
// Kickstart FFmpeg before we join. FFmpeg will wait until we start
// consuming the stream to process further.
if err := ffmpeg.Start(); err != nil {
return errors.Wrap(err, "failed to start ffmpeg")
return fmt.Errorf("failed to start ffmpeg: %w", err)
}
// Join the voice channel.
if err := v.JoinChannelAndSpeak(ctx, id, false, true); err != nil {
return errors.Wrap(err, "failed to join channel")
return fmt.Errorf("failed to join channel: %w", err)
}
defer v.Leave(ctx)
// Start decoding FFmpeg's OGG-container output and extract the raw Opus
// frames into the stream.
if err := oggreader.DecodeBuffered(v, stdout); err != nil {
return errors.Wrap(err, "failed to decode ogg")
return fmt.Errorf("failed to decode ogg: %w", err)
}
// Wait until FFmpeg finishes writing entirely and leave.
if err := ffmpeg.Wait(); err != nil {
return errors.Wrap(err, "failed to finish ffmpeg")
return fmt.Errorf("failed to finish ffmpeg: %w", err)
}
return nil

View File

@ -13,19 +13,19 @@ A Golang library for the Discord API.
[dgophers_img]: https://img.shields.io/badge/Discord%20Gophers-%23arikawa-%237289da?style=flat-square
[examples]: https://github.com/diamondburned/arikawa/tree/v3/0-examples
[examples_img]: https://img.shields.io/badge/Example-__example%2F-blueviolet?style=flat-square
[examples_img]: https://img.shields.io/badge/Example-.%2F0--examples%2F-blueviolet?style=flat-square
[pipeline]: https://builds.sr.ht/~diamondburned/arikawa
[pipeline_img]: https://builds.sr.ht/~diamondburned/arikawa.svg?style=flat-square
[pipeline]: https://github.com/diamondburned/arikawa/actions/workflows/test.yml
[pipeline_img]: https://img.shields.io/github/actions/workflow/status/diamondburned/arikawa/test.yml?style=flat-square&label=Tests
[pkg.go.dev]: https://pkg.go.dev/github.com/diamondburned/arikawa/v3
[pkg.go.dev_img]: https://pkg.go.dev/badge/github.com/diamondburned/arikawa/v3
[pkg.go.dev_img]: https://img.shields.io/badge/%E2%80%8B-reference-007d9c?logo=go&logoColor=white&style=flat-square
[himeArikawa]: https://hime-goto.fandom.com/wiki/Hime_Arikawa
[himeArikawa_img]: https://img.shields.io/badge/Hime-Arikawa-ea75a2?style=flat-square
[goreportcard]: https://goreportcard.com/report/github.com/diamondburned/arikawa
[goreportcard_img]: https://goreportcard.com/badge/github.com/diamondburned/arikawa?style=flat-square
[goreportcard_img]: https://goreportcard.com/badge/github.com/diamondburned/arikawa?style=flat-square&label=Go%20Report
## Library Highlights

View File

@ -24,6 +24,9 @@ func OverwriteCommands(client BulkCommandsOverwriter, cmds []api.CreateCommandDa
return fmt.Errorf("cannot get current app ID: %w", err)
}
_, err = client.BulkOverwriteCommands(app.ID, cmds)
return fmt.Errorf("cannot overwrite commands: %w", err)
if _, err = client.BulkOverwriteCommands(app.ID, cmds); err != nil {
return fmt.Errorf("cannot overwrite commands: %w", err)
}
return nil
}

View File

@ -5,7 +5,6 @@ import (
"log"
"strconv"
"strings"
"sync"
"testing"
"time"
@ -14,16 +13,18 @@ import (
"github.com/diamondburned/arikawa/v3/utils/ws"
)
var doLogOnce sync.Once
func doLog(t *testing.T) {
if !testing.Verbose() {
return
}
func doLog() {
doLogOnce.Do(func() {
if testing.Verbose() {
ws.WSDebug = func(v ...interface{}) {
log.Println(append([]interface{}{"Debug:"}, v...)...)
}
}
})
prev := ws.WSDebug
t.Cleanup(func() { ws.WSDebug = prev })
ws.WSDebug = func(v ...interface{}) {
t.Helper()
t.Log(v...)
}
}
func TestURL(t *testing.T) {
@ -45,7 +46,7 @@ func TestURL(t *testing.T) {
}
func TestInvalidToken(t *testing.T) {
doLog()
doLog(t)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
t.Cleanup(cancel)
@ -90,7 +91,7 @@ func TestInvalidToken(t *testing.T) {
}
func TestIntegration(t *testing.T) {
doLog()
doLog(t)
config := testenv.Must(t)
@ -108,7 +109,7 @@ func TestIntegration(t *testing.T) {
}
func TestReuseGateway(t *testing.T) {
doLog()
doLog(t)
config := testenv.Must(t)

9
go.work Normal file
View File

@ -0,0 +1,9 @@
go 1.21.3
// To update this file, run:
// go work use $(find . -name go.mod -exec dirname {} \;)
use (
.
./0-examples/voice
)

3
go.work.sum Normal file
View File

@ -0,0 +1,3 @@
github.com/diamondburned/arikawa/v3 v3.0.0-rc.6/go.mod h1:5jBSNnp82Z/EhsKa6Wk9FsOqSxfVkNZDTDBPOj47LpY=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/sys v0.0.0-20211001092434-39dca1131b70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -16,12 +16,13 @@ import (
)
const PerseveranceTime = 50 * time.Minute
const DefaultShardCount = 2
type Env struct {
BotToken string
ChannelID discord.ChannelID
VoiceChID discord.ChannelID
ShardCount int // default 3
ShardCount int
}
var (
@ -67,7 +68,7 @@ func getEnv() {
return
}
shardCount := 2
shardCount := DefaultShardCount
if c, err := strconv.Atoi(os.Getenv("SHARD_COUNT")); err == nil {
shardCount = c
}

View File

@ -11,11 +11,11 @@ import (
func TestSession(t *testing.T) {
attempts := 1
timeout := 15 * time.Second
timeout := 45 * time.Second
if !testing.Short() {
attempts = 5
timeout = time.Minute // 5s-10s each reconnection
timeout = 8 * time.Minute
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
@ -51,11 +51,11 @@ func TestSession(t *testing.T) {
func TestSessionConnect(t *testing.T) {
attempts := 1
timeout := 15 * time.Second
timeout := 45 * time.Second
if !testing.Short() {
attempts = 5
timeout = time.Minute // 5s-10s each reconnection
timeout = 8 * time.Minute
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)

View File

@ -1,4 +1,4 @@
package shard
package shard_test
import (
"context"
@ -8,6 +8,7 @@ import (
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/internal/testenv"
"github.com/diamondburned/arikawa/v3/session"
"github.com/diamondburned/arikawa/v3/session/shard"
)
func TestSharding(t *testing.T) {
@ -18,15 +19,15 @@ func TestSharding(t *testing.T) {
readyCh := make(chan *gateway.ReadyEvent)
m, err := NewIdentifiedManager(data, NewSessionShard(
func(m *Manager, s *session.Session) {
m, err := shard.NewIdentifiedManager(data, shard.NewSessionShard(
func(m *shard.Manager, s *session.Session) {
now := time.Now().Format(time.StampMilli)
t.Log(now, "initializing shard")
s.AddIntents(gateway.IntentGuilds)
s.AddHandler(readyCh)
s.AddHandler(func(err error) {
t.Error("unexpected error:", err)
t.Log(err)
})
},
))
@ -34,32 +35,40 @@ func TestSharding(t *testing.T) {
t.Fatal("failed to make shard manager:", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
ctx, cancel := context.WithTimeout(
context.Background(),
2*time.Minute*time.Duration(env.ShardCount))
defer cancel()
openDone := make(chan struct{})
go func() {
// Timeout
defer close(openDone)
if err := m.Open(ctx); err != nil {
t.Error("failed to open:", err)
cancel()
}
t.Cleanup(func() {
if err := m.Close(); err != nil {
t.Error("failed to close:", err)
cancel()
}
})
}()
t.Cleanup(func() { m.Close() })
// Expect 4 Ready events.
shardLoop:
for i := 0; i < env.ShardCount; i++ {
select {
case ready := <-readyCh:
now := time.Now().Format(time.StampMilli)
t.Log(now, "shard", ready.Shard.ShardID(), "is ready out of", env.ShardCount)
case <-ctx.Done():
t.Fatal("test expired, got", i, "shards")
t.Error("test expired, got", i, "shards")
break shardLoop
}
}
select {
case <-openDone:
t.Log("all shards opened")
case <-ctx.Done():
t.Error("test expired")
}
if err := m.Close(); err != nil {
t.Error("failed to close:", err)
}
}

View File

@ -362,6 +362,9 @@ func (s *State) Permissions(
if merr != nil {
return 0, fmt.Errorf("failed to get member: %w", merr)
}
if rerr != nil {
return 0, fmt.Errorf("failed to get roles: %w", rerr)
}
return discord.CalcOverrides(*g, *ch, *m, rs), nil
}

View File

@ -1,72 +0,0 @@
package state
import (
"context"
"testing"
"time"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/internal/testenv"
"github.com/diamondburned/arikawa/v3/session/shard"
)
func TestSharding(t *testing.T) {
env := testenv.Must(t)
data := gateway.DefaultIdentifyCommand("Bot " + env.BotToken)
data.Shard = &gateway.Shard{0, env.ShardCount}
data.Presence = &gateway.UpdatePresenceCommand{
Status: discord.DoNotDisturbStatus,
Activities: []discord.Activity{{
Name: "Testing shards...",
Type: discord.CustomActivity,
}},
}
readyCh := make(chan *gateway.ReadyEvent)
m, err := shard.NewIdentifiedManager(data, NewShardFunc(
func(m *shard.Manager, s *State) {
now := time.Now().Format(time.StampMilli)
t.Log(now, "initializing shard")
s.AddIntents(gateway.IntentGuilds)
s.AddSyncHandler(readyCh)
s.AddSyncHandler(func(err error) {
t.Log("background error:", err)
})
},
))
if err != nil {
t.Fatal("failed to make shard manager:", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
go func() {
// Timeout
if err := m.Open(ctx); err != nil {
t.Error("failed to open:", err)
cancel()
}
t.Cleanup(func() {
if err := m.Close(); err != nil {
t.Error("failed to close:", err)
cancel()
}
})
}()
for i := 0; i < env.ShardCount; i++ {
select {
case ready := <-readyCh:
now := time.Now().Format(time.StampMilli)
t.Log(now, "shard", ready.Shard.ShardID(), "is ready out of", env.ShardCount)
case <-ctx.Done():
t.Fatal("test expired, got", i, "shards")
}
}
}

View File

@ -1,75 +0,0 @@
package bot
import (
"context"
"testing"
"time"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/internal/testenv"
"github.com/diamondburned/arikawa/v3/session/shard"
"github.com/diamondburned/arikawa/v3/state"
)
type shardedBot struct {
Ctx *Context
readyCh chan *gateway.ReadyEvent
}
func (bot *shardedBot) OnReady(r *gateway.ReadyEvent) {
bot.readyCh <- r
}
func TestSharding(t *testing.T) {
env := testenv.Must(t)
data := gateway.DefaultIdentifyCommand("Bot " + env.BotToken)
data.Shard = &gateway.Shard{0, env.ShardCount}
readyCh := make(chan *gateway.ReadyEvent)
newShard := NewShardFunc(func(s *state.State) (*Context, error) {
b, err := New(s, &shardedBot{nil, readyCh})
if err != nil {
return nil, err
}
b.AddIntents(gateway.IntentGuilds)
return b, nil
})
m, err := shard.NewIdentifiedManager(data, newShard)
if err != nil {
t.Fatal("failed to make shard manager:", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
go func() {
// Timeout
if err := m.Open(ctx); err != nil {
t.Error("failed to open:", err)
cancel()
}
t.Cleanup(func() {
if err := m.Close(); err != nil {
t.Error("failed to close:", err)
cancel()
}
})
}()
// Expect 4 Ready events.
for i := 0; i < env.ShardCount; i++ {
select {
case ready := <-readyCh:
now := time.Now().Format(time.StampMilli)
t.Log(now, "shard", ready.Shard.ShardID(), "is ready out of", env.ShardCount)
case <-ctx.Done():
t.Fatal("test expired, got", i, "shards")
}
}
}

View File

@ -168,6 +168,7 @@ type mockStore struct {
func mockCabinet() *store.Cabinet {
c := *store.NoopCabinet
c.RoleStore = &mockStore{}
c.GuildStore = &mockStore{}
c.MemberStore = &mockStore{}
c.ChannelStore = &mockStore{}
@ -178,13 +179,28 @@ func mockCabinet() *store.Cabinet {
func (s *mockStore) Guild(id discord.GuildID) (*discord.Guild, error) {
return &discord.Guild{
ID: id,
Roles: []discord.Role{{
}, nil
}
func (s *mockStore) Roles(id discord.GuildID) ([]discord.Role, error) {
return []discord.Role{
{
ID: 69420,
Permissions: discord.PermissionAdministrator,
}},
},
}, nil
}
func (s *mockStore) Role(_ discord.GuildID, roleID discord.RoleID) (*discord.Role, error) {
if roleID == 69420 {
return &discord.Role{
ID: roleID,
Permissions: discord.PermissionAdministrator,
}, nil
}
return nil, store.ErrNotFound
}
func (s *mockStore) Member(_ discord.GuildID, userID discord.UserID) (*discord.Member, error) {
return &discord.Member{
User: discord.User{ID: userID},

View File

@ -6,9 +6,6 @@ import (
"fmt"
"log"
"math/rand"
"os"
"runtime"
"strconv"
"sync"
"testing"
"time"
@ -24,15 +21,18 @@ import (
"github.com/diamondburned/arikawa/v3/voice/voicegateway"
)
func TestMain(m *testing.M) {
ws.WSDebug = func(v ...interface{}) {
_, file, line, _ := runtime.Caller(1)
caller := file + ":" + strconv.Itoa(line)
log.Println(append([]interface{}{caller}, v...)...)
func doLog(t *testing.T) {
if !testing.Verbose() {
return
}
code := m.Run()
os.Exit(code)
prev := ws.WSDebug
t.Cleanup(func() { ws.WSDebug = prev })
ws.WSDebug = func(v ...interface{}) {
t.Helper()
t.Log(v...)
}
}
type testState struct {
@ -41,13 +41,16 @@ type testState struct {
}
func testOpen(t *testing.T) *testState {
t.Helper()
doLog(t)
config := testenv.Must(t)
s := state.New("Bot " + config.BotToken)
AddIntents(s)
func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
if err := s.Open(ctx); err != nil {