Add testing and fix tons of bugs
Some checks reported errors
continuous-integration/drone/push Build was killed
Some checks reported errors
continuous-integration/drone/push Build was killed
This commit is contained in:
parent
5158ab539a
commit
4a521f595d
8 changed files with 272 additions and 35 deletions
|
@ -7,14 +7,14 @@ steps:
|
|||
commands:
|
||||
- cd ./draftbot
|
||||
- GOPROXY=https://modules.fromouter.space go mod download
|
||||
- CGO_ENABLED=0 go test .
|
||||
- CGO_ENABLED=0 go test ./...
|
||||
|
||||
- name: build_draftbot
|
||||
image: golang
|
||||
commands:
|
||||
- cd ./draftbot
|
||||
- GOPROXY=https://modules.fromouter.space go mod download
|
||||
- CGO_ENABLED=0 go install .
|
||||
- CGO_ENABLED=0 go install ./...
|
||||
volumes:
|
||||
- name: gopath
|
||||
path: /go
|
||||
|
|
66
draftbot/bot/bot_test.go
Normal file
66
draftbot/bot/bot_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package bot_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.fromouter.space/mcg/mlp-server-tools/draftbot/bot"
|
||||
|
||||
"git.fromouter.space/mcg/draft"
|
||||
)
|
||||
|
||||
const PACKSIZE = 5
|
||||
|
||||
// Test set that can be used by tests that don't need special features (like alternates)
|
||||
var testSet = &draft.GenericSet{
|
||||
Cards: []draft.Card{{ID: "a"}, {ID: "b"}, {ID: "c"}},
|
||||
PackSize: PACKSIZE,
|
||||
}
|
||||
|
||||
func TestPick(t *testing.T) {
|
||||
const PacksPerPlayer = 3
|
||||
const PlayersPerPod = 5
|
||||
|
||||
// Get provider for test set
|
||||
testProvider := draft.PacksFromSet(PacksPerPlayer, testSet)
|
||||
|
||||
// Create pod
|
||||
pod := draft.MakePod(PlayersPerPod, testProvider)
|
||||
|
||||
// Create a bot for each player in the pod
|
||||
var bots []*bot.Bot
|
||||
for _, player := range pod.Players {
|
||||
bots = append(bots, bot.MakeBot(player))
|
||||
}
|
||||
|
||||
// Simulate a round of drafting
|
||||
// Repeat until all packs are gone
|
||||
// Channels are tested when they should trigger
|
||||
for packnum := 0; packnum < PacksPerPlayer; packnum++ {
|
||||
|
||||
// Open new packs!
|
||||
err := pod.OpenPacks()
|
||||
if err != nil {
|
||||
t.Fatalf("Got an error while opening packs #%d: %s", packnum, err.Error())
|
||||
}
|
||||
|
||||
for picknum := 0; picknum < PACKSIZE; picknum++ {
|
||||
for _, bot := range bots {
|
||||
bot.PickNext()
|
||||
}
|
||||
|
||||
// Make sure either ReadyNextPick or ReadyNextPack triggers
|
||||
select {
|
||||
case <-pod.ReadyNextPick:
|
||||
// Pass packs around
|
||||
err := pod.NextPacks()
|
||||
if err != nil {
|
||||
t.Fatalf("Got an error while passing packs: %s", err.Error())
|
||||
}
|
||||
case <-pod.ReadyNextPack:
|
||||
break
|
||||
default:
|
||||
t.Fatal("Either ReadyNextPick/ReadyNextPack should trigger but neither has")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ func NewDraftBot(botAPI BotInterface, name string) *DraftBot {
|
|||
return &DraftBot{
|
||||
API: botAPI,
|
||||
Name: name,
|
||||
Rooms: make(map[string]roomInfo),
|
||||
Sessions: make(map[string]*session),
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +38,7 @@ func NewDraftBot(botAPI BotInterface, name string) *DraftBot {
|
|||
func (d *DraftBot) OnMessage(msg room.ServerMessage) {
|
||||
switch msg.Type {
|
||||
case room.MsgMessage:
|
||||
if *logAll {
|
||||
if logAll {
|
||||
logger.Log("event", "message",
|
||||
"roomid", msg.RoomID,
|
||||
"from", msg.Message.From,
|
||||
|
@ -49,7 +50,7 @@ func (d *DraftBot) OnMessage(msg room.ServerMessage) {
|
|||
d.handleMessage(msg.RoomID, *msg.Message)
|
||||
}
|
||||
case room.MsgEvent:
|
||||
if *logAll {
|
||||
if logAll {
|
||||
logger.Log("event", "event",
|
||||
"roomid", msg.RoomID,
|
||||
"content", msg.Event.Message)
|
||||
|
|
|
@ -103,11 +103,7 @@ func (d *DraftBot) cmdPickCard(roomid string, msg room.Message) {
|
|||
|
||||
func (d *DraftBot) cmdCreateSession(roomid string, msg room.Message) {
|
||||
// Get session options from data
|
||||
type sessionOptions struct {
|
||||
players int
|
||||
options draftOptions
|
||||
}
|
||||
var opt sessionOptions
|
||||
var opt SessionOptions
|
||||
err := mapstructure.Decode(msg.Data, &opt)
|
||||
if err != nil {
|
||||
d.sendMessage(roomid, room.Message{
|
||||
|
@ -118,7 +114,7 @@ func (d *DraftBot) cmdCreateSession(roomid string, msg room.Message) {
|
|||
return
|
||||
}
|
||||
|
||||
sess, err := newSession(opt.players, opt.options)
|
||||
sess, err := newSession(opt.Players, opt.Options)
|
||||
if err != nil {
|
||||
d.sendMessage(roomid, room.Message{
|
||||
To: msg.From,
|
||||
|
@ -140,7 +136,7 @@ func (d *DraftBot) cmdCreateSession(roomid string, msg room.Message) {
|
|||
Channel: "draft",
|
||||
Type: "session-open",
|
||||
Data: opt,
|
||||
Message: fmt.Sprintf("Created a new draft session for %d players, type: %s", opt.players, opt.options.Type),
|
||||
Message: fmt.Sprintf("Created a new draft session for %d players, type: %s", opt.Players, opt.Options.Type),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,19 @@ package main_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.fromouter.space/mcg/draft/mlp"
|
||||
|
||||
"git.fromouter.space/mcg/cardgage/client/bot"
|
||||
lobby "git.fromouter.space/mcg/cardgage/lobby/proto"
|
||||
room "git.fromouter.space/mcg/cardgage/room/api"
|
||||
draft "git.fromouter.space/mcg/mlp-server-tools/draftbot"
|
||||
)
|
||||
|
||||
const TestBotName = "test-bot"
|
||||
const TestRoomName = "test-room"
|
||||
|
||||
type MockServer struct {
|
||||
in chan room.ServerMessage
|
||||
out chan room.BotMessage
|
||||
|
@ -35,8 +42,163 @@ func (m *MockServer) Bind(fn bot.MessageHandler) {
|
|||
|
||||
func TestDraftSession(t *testing.T) {
|
||||
mock := makeMockServer()
|
||||
draftbot := draft.NewDraftBot(mock, "bot")
|
||||
draftbot := draft.NewDraftBot(mock, TestBotName)
|
||||
go mock.Bind(draftbot.OnMessage)
|
||||
|
||||
//TODO sample session
|
||||
// Create a new room
|
||||
mock.in <- room.ServerMessage{
|
||||
RoomID: TestRoomName,
|
||||
Type: room.MsgEvent,
|
||||
Event: &room.Event{
|
||||
Type: room.EvtNewRoom,
|
||||
Room: &lobby.Room{
|
||||
Id: TestRoomName,
|
||||
Name: "Test draft room",
|
||||
Creator: "test-owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create new session with a fake message from owner
|
||||
mock.message("test-owner", "create", draft.SessionOptions{
|
||||
Players: 8, // Two players, six bots
|
||||
Options: draft.DraftOptions{
|
||||
Type: draft.DraftSet,
|
||||
Positioning: draft.PosEven,
|
||||
Set: mlp.SetAbsoluteDiscord,
|
||||
PackCount: 4,
|
||||
},
|
||||
})
|
||||
|
||||
mock.expect(t, "session-open", 5)
|
||||
|
||||
// Join session as owner
|
||||
mock.message("test-owner", "join", nil)
|
||||
mock.expect(t, "player-joined-session", 5)
|
||||
|
||||
// .. and as second player
|
||||
mock.message("test-guest", "join", nil)
|
||||
mock.expect(t, "player-joined-session", 5)
|
||||
|
||||
// Try to start the session
|
||||
mock.message("test-owner", "start", nil)
|
||||
|
||||
// These two can happen in any order, so they get their own special check
|
||||
mock.multiexpect(t, 5, "session-start", "draft-order")
|
||||
|
||||
//TODO make players pick cards etc
|
||||
|
||||
// Close the room
|
||||
mock.in <- room.ServerMessage{
|
||||
RoomID: TestRoomName,
|
||||
Type: room.MsgEvent,
|
||||
Event: &room.Event{
|
||||
Type: room.EvtRoomClosed,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestDraftSessionButEverythingGoesWrong(t *testing.T) {
|
||||
mock := makeMockServer()
|
||||
draftbot := draft.NewDraftBot(mock, TestBotName)
|
||||
go mock.Bind(draftbot.OnMessage)
|
||||
|
||||
// Create a new room
|
||||
mock.in <- room.ServerMessage{
|
||||
Type: room.MsgEvent,
|
||||
Event: &room.Event{
|
||||
Type: room.EvtNewRoom,
|
||||
Room: &lobby.Room{
|
||||
Id: TestRoomName,
|
||||
Name: "Test draft room",
|
||||
Creator: "test-owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Try creating a new session as NOT the owner
|
||||
mock.message("test-guest", "create", draft.SessionOptions{
|
||||
Players: 8, // Two players, six bots
|
||||
Options: draft.DraftOptions{
|
||||
Type: draft.DraftSet,
|
||||
Positioning: draft.PosEven,
|
||||
Set: mlp.SetAbsoluteDiscord,
|
||||
PackCount: 4,
|
||||
},
|
||||
})
|
||||
mock.expect(t, "must-be-owner", 5)
|
||||
|
||||
//TODO:
|
||||
// Try to start session when session doesn't exist
|
||||
// Try to create session twice
|
||||
// Try to start session with no players
|
||||
// Try to start session as not the owner
|
||||
// Try to make too many players join a session
|
||||
}
|
||||
|
||||
func (m *MockServer) expect(t *testing.T, typ string, timeout int) {
|
||||
for {
|
||||
select {
|
||||
case msg := <-m.out:
|
||||
// Skip all actions
|
||||
if msg.Type != room.MsgMessage {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check expected type
|
||||
if msg.Message.Type != typ {
|
||||
// Oh noes
|
||||
t.Fatalf("Expected message \"%s\" but got \"%s\"", typ, msg.Message.Type)
|
||||
}
|
||||
return
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
t.Fatalf("Expected message \"%s\" but found nothing (timeout after %d seconds)!", typ, timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockServer) multiexpect(t *testing.T, timeout int, types ...string) {
|
||||
for {
|
||||
if len(types) < 1 {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case msg := <-m.out:
|
||||
// Skip all actions
|
||||
if msg.Type != room.MsgMessage {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check expected type
|
||||
found := false
|
||||
for i, typ := range types {
|
||||
if typ == msg.Message.Type {
|
||||
found = true
|
||||
types[i] = types[len(types)-1]
|
||||
types = types[:len(types)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// Oh noes
|
||||
t.Fatalf("Expected one of %s but got \"%s\"", types, msg.Message.Type)
|
||||
}
|
||||
return
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
t.Fatalf("Expected one of %s but found nothing (timeout after %d seconds)!", types, timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockServer) message(from string, typ string, data interface{}) {
|
||||
m.in <- room.ServerMessage{
|
||||
RoomID: TestRoomName,
|
||||
Type: room.MsgMessage,
|
||||
Message: &room.Message{
|
||||
From: from,
|
||||
To: TestBotName,
|
||||
Type: typ,
|
||||
Data: data,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
|
|||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
|
@ -105,6 +106,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
|
|||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.13 h1:x7DQtkU0cedzeS8TD36tT/w1Hm4rDtfCaYYAHE7TTBI=
|
||||
github.com/miekg/dns v1.1.13/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA=
|
||||
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
|
@ -144,6 +146,7 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
|
|||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
|
|
|
@ -17,14 +17,15 @@ import (
|
|||
)
|
||||
|
||||
var logger log.Logger
|
||||
var logAll *bool
|
||||
var logAll bool
|
||||
|
||||
func main() {
|
||||
consulAddr := flag.String("consul.addr", "consul:8500", "Consul address")
|
||||
botName := flag.String("bot.name", "draftbot", "Bot name")
|
||||
gameFilter := flag.String("filter.game", "mlpccg-mcg", "What game to filter for (separated by comma)")
|
||||
tagFilter := flag.String("filter.tag", "draft", "What tags to filter for (separated by comma)")
|
||||
logAll = flag.Bool("debug.log", false, "Log a lot of stuff")
|
||||
flag.BoolVar(&logAll, "debug.log", false, "Log a lot of stuff")
|
||||
flag.Parse()
|
||||
|
||||
logger = log.NewLogfmtLogger(os.Stderr)
|
||||
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
|
||||
|
|
|
@ -20,7 +20,7 @@ var (
|
|||
)
|
||||
|
||||
type session struct {
|
||||
Options draftOptions
|
||||
Options DraftOptions
|
||||
Players map[string]*draft.Player
|
||||
Bots []*bot.Bot
|
||||
Pod *draft.Pod
|
||||
|
@ -35,27 +35,34 @@ type session struct {
|
|||
|
||||
// Types of drafts
|
||||
const (
|
||||
draftBlock = "block"
|
||||
draftSet = "set"
|
||||
draftCube = "cube"
|
||||
draftI8PCube = "i8pcube"
|
||||
DraftBlock = "block"
|
||||
DraftSet = "set"
|
||||
DraftCube = "cube"
|
||||
DraftI8PCube = "i8pcube"
|
||||
)
|
||||
|
||||
// Ways in which players can be positioned along the draft pod
|
||||
const (
|
||||
posRandom = "random" // Place players randomly
|
||||
posEven = "even" // Place players spaced as evenly as possible
|
||||
PosRandom = "random" // Place players randomly
|
||||
PosEven = "even" // Place players spaced as evenly as possible
|
||||
)
|
||||
|
||||
type draftOptions struct {
|
||||
// SessionOptions is the data contained in a create session request
|
||||
type SessionOptions struct {
|
||||
Players int `json:"players"`
|
||||
Options DraftOptions `json:"options"`
|
||||
}
|
||||
|
||||
// DraftOptions are the options needed for a draft session
|
||||
type DraftOptions struct {
|
||||
Type string `json:"type"`
|
||||
Positioning string `json:"positioning"`
|
||||
|
||||
// Block draft properties
|
||||
Block string `json:"block,omitempty"`
|
||||
Block mlp.BlockID `json:"block,omitempty"`
|
||||
|
||||
// Set draft properties
|
||||
Set string `json:"set,omitempty"`
|
||||
Set mlp.SetID `json:"set,omitempty"`
|
||||
|
||||
// Cube draft properties
|
||||
CubeURL string `json:"cube_url,omitempty"`
|
||||
|
@ -69,17 +76,17 @@ type draftOptions struct {
|
|||
PackCount int `json:"pack_count,omitempty"` // Set and Cube
|
||||
}
|
||||
|
||||
func (do draftOptions) getProvider() (draft.PackProvider, error) {
|
||||
func (do DraftOptions) getProvider() (draft.PackProvider, error) {
|
||||
switch do.Type {
|
||||
case draftBlock:
|
||||
return mlp.BlockPacks(mlp.BlockID(do.Block))
|
||||
case draftSet:
|
||||
set, err := mlp.LoadSetHTTP(mlp.SetID(do.Set))
|
||||
case DraftBlock:
|
||||
return mlp.BlockPacks(do.Block)
|
||||
case DraftSet:
|
||||
set, err := mlp.LoadSetHTTP(do.Set)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return draft.PacksFromSet(do.PackCount, set), nil
|
||||
case draftCube:
|
||||
case DraftCube:
|
||||
cards, err := loadCube(do.CubeURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -89,7 +96,7 @@ func (do draftOptions) getProvider() (draft.PackProvider, error) {
|
|||
PackSize: do.PackSize,
|
||||
}
|
||||
return draft.PacksFromSet(do.PackCount, cube), nil
|
||||
case draftI8PCube:
|
||||
case DraftI8PCube:
|
||||
cube, err := loadI8PCube(do.CubeURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -99,7 +106,7 @@ func (do draftOptions) getProvider() (draft.PackProvider, error) {
|
|||
return nil, errors.New("unknown draft type")
|
||||
}
|
||||
|
||||
func newSession(playerCount int, opt draftOptions) (*session, error) {
|
||||
func newSession(playerCount int, opt DraftOptions) (*session, error) {
|
||||
// Get pack provider for given options
|
||||
provider, err := opt.getProvider()
|
||||
if err != nil {
|
||||
|
@ -109,6 +116,7 @@ func newSession(playerCount int, opt draftOptions) (*session, error) {
|
|||
return &session{
|
||||
Options: opt,
|
||||
Pod: draft.MakePod(playerCount, provider),
|
||||
Players: make(map[string]*draft.Player),
|
||||
messages: make(chan room.Message),
|
||||
exit: make(chan bool),
|
||||
}, nil
|
||||
|
@ -130,7 +138,7 @@ func (s *session) Start() error {
|
|||
playerSpot := make(map[int]string)
|
||||
|
||||
switch s.Options.Positioning {
|
||||
case posRandom:
|
||||
case PosRandom:
|
||||
// Assign a random number to each player
|
||||
for pname := range s.Players {
|
||||
var pos int
|
||||
|
@ -143,7 +151,7 @@ func (s *session) Start() error {
|
|||
}
|
||||
playerSpot[pos] = pname
|
||||
}
|
||||
case posEven:
|
||||
case PosEven:
|
||||
// Space players evenly
|
||||
playerRatio := float64(spots) / float64(players)
|
||||
i := 0
|
||||
|
|
Loading…
Reference in a new issue