#1: add initial tests for queue, upgrade github.com/stretchr/testify

This commit is contained in:
ErickSkrauch 2019-04-18 02:56:20 +03:00
parent fd4e5eb9ca
commit e14619e079
4 changed files with 228 additions and 10 deletions

6
Gopkg.lock generated
View File

@ -227,12 +227,12 @@
version = "v1.0.0"
[[projects]]
digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159"
digest = "1:381bcbeb112a51493d9d998bbba207a529c73dbb49b3fd789e48c63fac1f192c"
name = "github.com/stretchr/testify"
packages = ["assert"]
pruneopts = ""
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
version = "v1.1.4"
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]]
branch = "issue-18"

View File

@ -33,7 +33,7 @@ ignored = ["github.com/elyby/chrly"]
[[constraint]]
name = "github.com/stretchr/testify"
version = "^1.1.4"
version = "^1.3.0"
[[constraint]]
name = "github.com/golang/mock"

View File

@ -10,6 +10,7 @@ import (
var usernamesToUuids = mojang.UsernamesToUuids
var uuidToTextures = mojang.UuidToTextures
var delay = time.Second
type JobsQueue struct {
Storage Storage
@ -18,24 +19,26 @@ type JobsQueue struct {
queue jobsQueue
}
func (ctx *JobsQueue) GetTexturesForUsername(username string) (resultChan chan *mojang.SignedTexturesResponse) {
func (ctx *JobsQueue) GetTexturesForUsername(username string) *mojang.SignedTexturesResponse {
ctx.onFirstCall.Do(func() {
ctx.queue.New()
ctx.startQueue()
})
resultChan := make(chan *mojang.SignedTexturesResponse)
// TODO: prevent of adding the same username more than once
ctx.queue.Enqueue(&jobItem{username, resultChan})
return
return <-resultChan
}
func (ctx *JobsQueue) startQueue() {
go func() {
for {
time.Sleep(delay)
for true {
start := time.Now()
ctx.queueRound()
time.Sleep(time.Second - time.Since(start))
time.Sleep(delay - time.Since(start))
}
}()
}
@ -66,7 +69,7 @@ func (ctx *JobsQueue) queueRound() {
var wg sync.WaitGroup
for _, job := range jobs {
wg.Add(1)
go func() {
go func(job *jobItem) {
var result *mojang.SignedTexturesResponse
shouldCache := true
var uuid string
@ -95,7 +98,7 @@ func (ctx *JobsQueue) queueRound() {
if shouldCache {
// TODO: store result to cache
}
}()
}(job)
}
wg.Wait()

View File

@ -0,0 +1,215 @@
package queue
import (
"crypto/rand"
"encoding/base64"
"errors"
"log"
"testing"
"time"
"github.com/elyby/chrly/api/mojang"
testify "github.com/stretchr/testify/assert"
)
func TestJobsQueue_GetTexturesForUsername(t *testing.T) {
delay = 50 * time.Millisecond
t.Run("receive textures for one username", func(t *testing.T) {
assert := testify.New(t)
usernamesToUuids = createUsernameToUuidsMock(
assert,
[]string{"maksimkurb"},
[]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
},
nil,
)
uuidToTextures = createUuidToTextures([]*createUuidToTexturesResult{
createTexturesResult("0d252b7218b648bfb86c2ae476954d32", "maksimkurb"),
})
queue := &JobsQueue{Storage: &NilStorage{}}
result := queue.GetTexturesForUsername("maksimkurb")
if assert.NotNil(result) {
assert.Equal("0d252b7218b648bfb86c2ae476954d32", result.Id)
assert.Equal("maksimkurb", result.Name)
}
})
t.Run("receive textures for few usernames", func(t *testing.T) {
assert := testify.New(t)
usernamesToUuids = createUsernameToUuidsMock(
assert,
[]string{"maksimkurb", "Thinkofdeath"},
[]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
{Id: "4566e69fc90748ee8d71d7ba5aa00d20", Name: "Thinkofdeath"},
},
nil,
)
uuidToTextures = createUuidToTextures([]*createUuidToTexturesResult{
createTexturesResult("0d252b7218b648bfb86c2ae476954d32", "maksimkurb"),
createTexturesResult("4566e69fc90748ee8d71d7ba5aa00d20", "Thinkofdeath"),
})
queue := &JobsQueue{Storage: &NilStorage{}}
resultChan1 := make(chan *mojang.SignedTexturesResponse)
resultChan2 := make(chan *mojang.SignedTexturesResponse)
go func() {
resultChan1 <- queue.GetTexturesForUsername("maksimkurb")
}()
go func() {
resultChan2 <- queue.GetTexturesForUsername("Thinkofdeath")
}()
assert.NotNil(<-resultChan1)
assert.NotNil(<-resultChan2)
})
t.Run("query no more than 100 usernames and all left on the next iteration", func(t *testing.T) {
assert := testify.New(t)
usernames := make([]string, 120, 120)
for i := 0; i < 120; i++ {
usernames[i] = randStr(8)
}
usernamesToUuids = createUsernameToUuidsMock(assert, usernames[0:100], []*mojang.ProfileInfo{}, nil)
queue := &JobsQueue{Storage: &NilStorage{}}
scheduleUsername := func(username string) {
queue.GetTexturesForUsername(username)
}
for _, username := range usernames {
go scheduleUsername(username)
time.Sleep(50 * time.Microsecond) // Add delay to have consistent order
}
// Let it begin first iteration
time.Sleep(delay + delay/2)
usernamesToUuids = createUsernameToUuidsMock(
assert,
usernames[100:120],
[]*mojang.ProfileInfo{},
nil,
)
time.Sleep(delay)
})
t.Run("should do nothing if queue is empty", func(t *testing.T) {
assert := testify.New(t)
usernamesToUuids = createUsernameToUuidsMock(assert, []string{"maksimkurb"}, []*mojang.ProfileInfo{}, nil)
uuidToTextures = func(uuid string, signed bool) (*mojang.SignedTexturesResponse, error) {
t.Error("this method shouldn't be called")
return nil, nil
}
// Perform first iteration and await it finish
queue := &JobsQueue{Storage: &NilStorage{}}
result := queue.GetTexturesForUsername("maksimkurb")
assert.Nil(result)
// Override external API call that indicates, that queue is still trying to obtain somethid
usernamesToUuids = func(usernames []string) ([]*mojang.ProfileInfo, error) {
t.Error("this method shouldn't be called")
return nil, nil
}
// Let it to iterate few times
time.Sleep(delay * 2)
})
t.Run("handle 429 error when exchanging usernames to uuids", func(t *testing.T) {
assert := testify.New(t)
usernamesToUuids = createUsernameToUuidsMock(assert, []string{"maksimkurb"}, nil, &mojang.TooManyRequestsError{})
queue := &JobsQueue{Storage: &NilStorage{}}
result := queue.GetTexturesForUsername("maksimkurb")
assert.Nil(result)
})
t.Run("handle 429 error when requesting user's textures", func(t *testing.T) {
assert := testify.New(t)
usernamesToUuids = createUsernameToUuidsMock(
assert,
[]string{"maksimkurb"},
[]*mojang.ProfileInfo{
{Id: "0d252b7218b648bfb86c2ae476954d32", Name: "maksimkurb"},
},
nil,
)
uuidToTextures = createUuidToTextures([]*createUuidToTexturesResult{
createTexturesResult("0d252b7218b648bfb86c2ae476954d32", &mojang.TooManyRequestsError{}),
})
queue := &JobsQueue{Storage: &NilStorage{}}
result := queue.GetTexturesForUsername("maksimkurb")
assert.Nil(result)
})
}
func createUsernameToUuidsMock(
assert *testify.Assertions,
expectedUsernames []string,
result []*mojang.ProfileInfo,
err error,
) func(usernames []string) ([]*mojang.ProfileInfo, error) {
return func(usernames []string) ([]*mojang.ProfileInfo, error) {
assert.ElementsMatch(expectedUsernames, usernames)
return result, err
}
}
type createUuidToTexturesResult struct {
uuid string
result *mojang.SignedTexturesResponse
err error
}
func createTexturesResult(uuid string, result interface{}) *createUuidToTexturesResult {
output := &createUuidToTexturesResult{uuid: uuid}
if username, ok := result.(string); ok {
output.result = &mojang.SignedTexturesResponse{Id: uuid, Name: username}
} else if err, ok := result.(error); ok {
output.err = err
} else {
log.Fatal("invalid result type passed")
}
return output
}
func createUuidToTextures(
results []*createUuidToTexturesResult,
) func(uuid string, signed bool) (*mojang.SignedTexturesResponse, error) {
return func(uuid string, signed bool) (*mojang.SignedTexturesResponse, error) {
for _, result := range results {
if result.uuid == uuid {
return result.result, result.err
}
}
return nil, errors.New("cannot find corresponding result")
}
}
// https://stackoverflow.com/a/50581165
func randStr(len int) string {
buff := make([]byte, len)
rand.Read(buff)
str := base64.StdEncoding.EncodeToString(buff)
// Base 64 can be longer than len
return str[:len]
}