Commit f1bb990c authored by hannahhoward's avatar hannahhoward

Refactor for DI approach + more testing

parent 2cb8bd4d
......@@ -6,7 +6,7 @@ import (
"time"
)
var sharedRNG = rand.New(rand.NewSource(time.Now().UnixNano()))
var sharedRealSleeper = NewRealSleeper()
// D (Delay) makes it easy to add (threadsafe) configurable delays to other
// objects.
......@@ -17,14 +17,11 @@ type D interface {
Get() time.Duration
}
// Fixed returns a delay with fixed latency
func Fixed(t time.Duration) D {
return &delay{t: t}
}
type delay struct {
l sync.RWMutex
t time.Duration
l sync.RWMutex
t time.Duration
sleeper Sleeper
generator Generator
}
func (d *delay) Set(t time.Duration) time.Duration {
......@@ -36,16 +33,15 @@ func (d *delay) Set(t time.Duration) time.Duration {
}
func (d *delay) Wait() {
nextWaitTime := d.NextWaitTime()
d.l.RLock()
defer d.l.RUnlock()
time.Sleep(nextWaitTime)
d.sleeper.Sleep(d.generator.NextWaitTime(d.t))
}
func (d *delay) NextWaitTime() time.Duration {
d.l.Lock()
defer d.l.Unlock()
return d.t
return d.generator.NextWaitTime(d.t)
}
func (d *delay) Get() time.Duration {
......@@ -54,58 +50,30 @@ func (d *delay) Get() time.Duration {
return d.t
}
// VariableNormal is a delay following a normal distribution
// Notice that to implement the D interface Set can only change the mean delay
// the standard deviation is set only at initialization
func VariableNormal(t, std time.Duration, rng *rand.Rand) D {
if rng == nil {
rng = sharedRNG
}
v := &variableNormal{
std: std,
rng: rng,
// Delay generates a generic delay form a t, a sleeper, and a generator
func Delay(t time.Duration, sleeper Sleeper, generator Generator) D {
return &delay{
t: t,
sleeper: sleeper,
generator: generator,
}
v.t = t
return v
}
type variableNormal struct {
delay
std time.Duration
rng *rand.Rand
}
func (d *variableNormal) NextWaitTime() time.Duration {
d.l.RLock()
defer d.l.RUnlock()
return time.Duration(d.rng.NormFloat64()*float64(d.std)) + d.t
// Fixed returns a delay with fixed latency
func Fixed(t time.Duration) D {
return Delay(t, sharedRealSleeper, FixedGenerator())
}
// VariableUniform is a delay following a uniform distribution
// Notice that to implement the D interface Set can only change the minimum delay
// the delta is set only at initialization
func VariableUniform(t, d time.Duration, rng *rand.Rand) D {
if rng == nil {
rng = sharedRNG
}
v := &variableUniform{
d: d,
rng: rng,
}
v.t = t
return v
}
type variableUniform struct {
delay
d time.Duration // max delta
rng *rand.Rand
return Delay(t, sharedRealSleeper, VariableUniformGenerator(d, rng))
}
func (d *variableUniform) NextWaitTime() time.Duration {
d.l.RLock()
defer d.l.RUnlock()
return time.Duration(d.rng.Float64()*float64(d.d)) + d.t
// VariableNormal is a delay following a normal distribution
// Notice that to implement the D interface Set can only change the mean delay
// the standard deviation is set only at initialization
func VariableNormal(t, std time.Duration, rng *rand.Rand) D {
return Delay(t, sharedRealSleeper, VariableNormalGenerator(std, rng))
}
package delay
import (
"math"
"math/rand"
"testing"
"time"
)
const testSeed = 99
func TestDelaySetAndGet(t *testing.T) {
initialValue, err := time.ParseDuration("1000ms")
if err != nil {
t.Fatal("Parse error during setup")
}
modifiedValue, err := time.ParseDuration("2000ms")
if err != nil {
t.Fatal("Parse error during setup")
}
deviation, err := time.ParseDuration("1000ms")
if err != nil {
t.Fatal("Parse error during setup")
}
initialValue := 1000 * time.Millisecond
modifiedValue := 2000 * time.Millisecond
deviation := 1000 * time.Millisecond
fixed := Fixed(initialValue)
variableNormal := VariableNormal(initialValue, deviation, rand.New(rand.NewSource(testSeed)))
variableUniform := VariableUniform(initialValue, deviation, rand.New(rand.NewSource(testSeed)))
variableNormal := VariableNormal(initialValue, deviation, nil)
variableUniform := VariableUniform(initialValue, deviation, nil)
if fixed.Get().Seconds() != 1 {
t.Fatal("Fixed delay not initialized correctly")
......@@ -64,34 +46,36 @@ func TestDelaySetAndGet(t *testing.T) {
}
func TestDelayNextWaitTime(t *testing.T) {
initialValue, err := time.ParseDuration("1000ms")
type recordSleeper struct {
lastSleep time.Duration
}
if err != nil {
t.Fatal("Parse error during setup")
}
func (rs *recordSleeper) Sleep(t time.Duration) {
rs.lastSleep = t
}
deviation, err := time.ParseDuration("1000ms")
type fixedAdd struct {
toAdd time.Duration
}
if err != nil {
t.Fatal("Parse error during setup")
}
func (fa *fixedAdd) NextWaitTime(t time.Duration) time.Duration {
return t + fa.toAdd
}
fixed := Fixed(initialValue)
firstRandomNormal := rand.New(rand.NewSource(testSeed)).NormFloat64()
firstRandom := rand.New(rand.NewSource(testSeed)).Float64()
variableNormal := VariableNormal(initialValue, deviation, rand.New(rand.NewSource(testSeed)))
variableUniform := VariableUniform(initialValue, deviation, rand.New(rand.NewSource(testSeed)))
if fixed.NextWaitTime().Seconds() != 1 {
t.Fatal("Fixed delay output incorrect wait time")
}
func TestDelaySleep(t *testing.T) {
initialValue := 1000 * time.Millisecond
toAdd := 500 * time.Millisecond
generator := &fixedAdd{toAdd: toAdd}
sleeper := &recordSleeper{lastSleep: -1}
if math.Abs(variableNormal.NextWaitTime().Seconds()-(firstRandomNormal*deviation.Seconds()+initialValue.Seconds())) > 0.00001 {
t.Fatal("Normalized variable delay output incorrect wait time")
}
delay := Delay(initialValue, sleeper, generator)
if math.Abs(variableUniform.NextWaitTime().Seconds()-(firstRandom*deviation.Seconds()+initialValue.Seconds())) > 0.00001 {
t.Fatal("Uniform variable delay output incorrect wait time")
if delay.NextWaitTime() != initialValue+toAdd {
t.Fatal("NextWaitTime should call the generator")
}
delay.Wait()
if sleeper.lastSleep != initialValue+toAdd {
t.Fatal("Wait should sleep based on the next wait time generated")
}
}
package delay
import (
"math/rand"
"time"
)
// Generator provides an interface for generating wait times
type Generator interface {
NextWaitTime(time.Duration) time.Duration
}
var sharedRNG = rand.New(rand.NewSource(time.Now().UnixNano()))
// VariableNormalGenerator makes delays that following a normal distribution
func VariableNormalGenerator(std time.Duration, rng *rand.Rand) Generator {
if rng == nil {
rng = sharedRNG
}
v := &variableNormal{
std: std,
rng: rng,
}
return v
}
type variableNormal struct {
std time.Duration
rng *rand.Rand
}
func (d *variableNormal) NextWaitTime(t time.Duration) time.Duration {
return time.Duration(d.rng.NormFloat64()*float64(d.std)) + t
}
// VariableUniformGenerator generates delays following a uniform distribution
func VariableUniformGenerator(d time.Duration, rng *rand.Rand) Generator {
if rng == nil {
rng = sharedRNG
}
v := &variableUniform{
d: d,
rng: rng,
}
return v
}
type variableUniform struct {
d time.Duration // max delta
rng *rand.Rand
}
func (d *variableUniform) NextWaitTime(t time.Duration) time.Duration {
return time.Duration(d.rng.Float64()*float64(d.d)) + t
}
type fixed struct{}
// FixedGenerator returns a delay with fixed latency
func FixedGenerator() Generator {
return &fixed{}
}
func (d *fixed) NextWaitTime(t time.Duration) time.Duration {
return t
}
package delay
import (
"math"
"math/rand"
"testing"
"time"
)
const testSeed = 99
func TestGeneratorNextWaitTime(t *testing.T) {
initialValue := 1000 * time.Millisecond
deviation := 1000 * time.Millisecond
firstRandomNormal := rand.New(rand.NewSource(testSeed)).NormFloat64()
firstRandom := rand.New(rand.NewSource(testSeed)).Float64()
fixedGenerator := FixedGenerator()
variableNormalGenerator := VariableNormalGenerator(deviation, rand.New(rand.NewSource(testSeed)))
variableUniformGenerator := VariableUniformGenerator(deviation, rand.New(rand.NewSource(testSeed)))
if fixedGenerator.NextWaitTime(initialValue).Seconds() != 1 {
t.Fatal("Fixed generator output incorrect wait time")
}
if math.Abs(variableNormalGenerator.NextWaitTime(initialValue).Seconds()-(firstRandomNormal*deviation.Seconds()+initialValue.Seconds())) > 0.00001 {
t.Fatal("Normalized variable delay generator output incorrect wait time")
}
if math.Abs(variableUniformGenerator.NextWaitTime(initialValue).Seconds()-(firstRandom*deviation.Seconds()+initialValue.Seconds())) > 0.00001 {
t.Fatal("Uniform variable delay output incorrect wait time")
}
}
package delay
import "time"
// Sleeper - a generic interface for wrapping a sleep function
// So that sleeping can be mocked in tests
type Sleeper interface {
Sleep(time.Duration)
}
type realSleeper struct{}
func (rc *realSleeper) Sleep(d time.Duration) {
time.Sleep(d)
}
// NewRealSleeper - returns a new sleeper that uses the real time.Sleep function
func NewRealSleeper() Sleeper {
return &realSleeper{}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment