Skip to content

Commit

Permalink
Harden generator for concurrency (#25)
Browse files Browse the repository at this point in the history
* Harden generator for concurrency

* Harden generator for concurrency
  • Loading branch information
themue authored Mar 1, 2022
1 parent cffb437 commit 8417352
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v0.6.5

* (C) Fix checking for nil error in Asserts.ErrorContains()
* (C) Fix generator to resist concurrent calls

## v0.6.4

Expand Down
36 changes: 25 additions & 11 deletions generators/generators.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"fmt"
"math/rand"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
Expand Down Expand Up @@ -118,13 +119,16 @@ func UUIDString(uuid [16]byte) string {
// Generator is responsible for generating different random data
// based on a random number generator.
type Generator struct {
mu sync.Mutex
rand *rand.Rand
}

// New returns a new generator using the passed random number
// generator.
func New(rand *rand.Rand) *Generator {
return &Generator{rand}
return &Generator{
rand: rand,
}
}

// Byte generates a byte between lo and hi including
Expand All @@ -137,10 +141,21 @@ func (g *Generator) Byte(lo, hi byte) byte {
lo, hi = hi, lo
}
i := int(hi - lo)
g.mu.Lock()
n := byte(g.rand.Intn(i))
g.mu.Unlock()
return lo + n
}

// Bytes generates a slice of random bytes.
func (g *Generator) Bytes(lo, hi byte, count int) []byte {
bytes := make([]byte, count)
for i := 0; i < count; i++ {
bytes[i] = g.Byte(lo, hi)
}
return bytes
}

// Int generates an int between lo and hi including
// those values.
func (g *Generator) Int(lo, hi int) int {
Expand All @@ -150,7 +165,13 @@ func (g *Generator) Int(lo, hi int) int {
if lo > hi {
lo, hi = hi, lo
}
n := g.rand.Intn(hi - lo + 1)
i := hi - lo + 1
if i < 1 {
return 1
}
g.mu.Lock()
n := g.rand.Intn(i)
g.mu.Unlock()
return lo + n
}

Expand All @@ -163,15 +184,6 @@ func (g *Generator) Ints(lo, hi, count int) []int {
return ints
}

// Bytes generates a slice of random bytes.
func (g *Generator) Bytes(lo, hi byte, count int) []byte {
bytes := make([]byte, count)
for i := 0; i < count; i++ {
bytes[i] = g.Byte(lo, hi)
}
return bytes
}

// UUID generates a 16 byte long random byte array. So it's
// no real UUID, even no v4, but it looks like.
func (g *Generator) UUID() [16]byte {
Expand Down Expand Up @@ -483,7 +495,9 @@ func (g *Generator) Duration(lo, hi time.Duration) time.Duration {
if lo > hi {
lo, hi = hi, lo
}
g.mu.Lock()
n := g.rand.Int63n(int64(hi) - int64(lo) + 1)
g.mu.Unlock()
return lo + time.Duration(n)
}

Expand Down
17 changes: 17 additions & 0 deletions generators/generators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,23 @@ func TestTimes(t *testing.T) {
}
}

// TestConcurrency simply produces a number of concurrent calls simply to let
// the race detection do its work.
func TestConcurrency(t *testing.T) {
gen := generators.New(generators.FixedRand())

run := func() {
go gen.Byte(0, 255)
go gen.Int(0, 9999)
go gen.Duration(25*time.Millisecond, 75*time.Millisecond)
}
for i := 0; i < 100; i++ {
go run()
}

time.Sleep(3 * time.Second)
}

//--------------------
// HELPER
//--------------------
Expand Down

0 comments on commit 8417352

Please sign in to comment.