Compare commits
22 Commits
d9681ea1d1
...
040622f494
Author | SHA1 | Date |
---|---|---|
diamondburned | 040622f494 | |
diamondburned | 619521c41e | |
diamondburned | 7bfaf41a15 | |
diamondburned | ff74f27e7f | |
diamondburned | 30c2f9e5de | |
diamondburned | ba08c57156 | |
diamondburned | 47ce508089 | |
diamondburned | 6f4737aedc | |
diamondburned | a70012b289 | |
diamondburned | 87fd11a68a | |
diamondburned | b0a773c3f8 | |
diamondburned | 2c2daec84b | |
diamondburned | 1e4c5c135a | |
diamondburned | ad26a72256 | |
diamondburned | 5ebd28bab6 | |
diamondburned | 093436066e | |
diamondburned | edecfde113 | |
diamondburned | 415069be30 | |
diamondburned | e7c0290063 | |
diamondburned | 0370ff1904 | |
twoscott | 4ef179343a | |
diamondburned | ff709fc16c |
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
voice
|
|
@ -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
|
||||
}
|
|
@ -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 (
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
10
README.md
10
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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},
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue