Skip to content

Commit

Permalink
#1: add initial tests for queue, upgrade github.com/stretchr/testify
Browse files Browse the repository at this point in the history
  • Loading branch information
erickskrauch committed Apr 17, 2019
1 parent fd4e5eb commit e14619e
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 10 deletions.
6 changes: 3 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 9 additions & 6 deletions api/mojang/queue/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

var usernamesToUuids = mojang.UsernamesToUuids
var uuidToTextures = mojang.UuidToTextures
var delay = time.Second

type JobsQueue struct {
Storage Storage
Expand All @@ -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))
}
}()
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -95,7 +98,7 @@ func (ctx *JobsQueue) queueRound() {
if shouldCache {
// TODO: store result to cache
}
}()
}(job)
}

wg.Wait()
Expand Down
215 changes: 215 additions & 0 deletions api/mojang/queue/queue_test.go
Original file line number Diff line number Diff line change
@@ -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]
}

0 comments on commit e14619e

Please sign in to comment.