-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* counter added * keep it simple + test * fix doc
- Loading branch information
Showing
3 changed files
with
210 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package counter | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
) | ||
|
||
const ( | ||
// MaxInt - holds maximum int value for your platform | ||
MaxInt = int(^uint(0) >> 1) | ||
) | ||
|
||
// CyclicIncrementor - step-by-step counter with limitation of its maximum value. | ||
// After maximum is reached counter will reset into zero. | ||
// You should use NewCyclicIncrementor() to create counter, but also can create counter like this: | ||
// c := &counter.CyclicIncrementor{} | ||
// But in that case, counter is not operational until its maximum value will be set: | ||
// err := c.SetMaxValue(max) | ||
// Also note, if counter is only declared as pointer: | ||
// var c *CyclicIncrementor | ||
// it is not really initialized and it cannot be used at this point. | ||
type CyclicIncrementor struct { | ||
mx sync.RWMutex // for value and max | ||
value int | ||
max int | ||
} | ||
|
||
// GetValue - return counter value | ||
func (c *CyclicIncrementor) GetValue() int { | ||
c.mx.RLock() | ||
defer c.mx.RUnlock() | ||
return c.value | ||
} | ||
|
||
// Inc - increment by 1 current value of counter. When value is reached max, counter will reset into zero. | ||
func (c *CyclicIncrementor) Inc() { | ||
c.mx.Lock() | ||
if c.value < c.max { | ||
c.value++ | ||
} else { | ||
c.value = 0 | ||
} | ||
c.mx.Unlock() | ||
} | ||
|
||
// SetMaxValue - change max allowed value for counter. | ||
// Only positive integers allowed to set max value. | ||
func (c *CyclicIncrementor) SetMaxValue(max int) error { | ||
if max < 0 { | ||
return fmt.Errorf("counter.CyclicIncrementor: invalid max value (%d)", max) | ||
} | ||
c.mx.Lock() | ||
if c.value > max { | ||
c.value = 0 | ||
} | ||
c.max = max | ||
c.mx.Unlock() | ||
return nil | ||
} | ||
|
||
// NewCyclicIncrementor - return new cyclic counter with preassigned maximum value equals to MaxInt. | ||
func NewCyclicIncrementor() (*CyclicIncrementor, error) { | ||
c := &CyclicIncrementor{} | ||
if err := c.SetMaxValue(MaxInt); err != nil { | ||
return nil, err | ||
} | ||
return c, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package counter | ||
|
||
import ( | ||
"math/rand" | ||
"sync" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestUninitialized(t *testing.T) { | ||
mustPanic := func(method string) { | ||
if r := recover(); r == nil { | ||
t.Errorf("Uninitialized counter (nil) did not panic for %s", method) | ||
} | ||
} | ||
withoutPanic := func(method string) { | ||
if r := recover(); r != nil { | ||
t.Errorf("Uninitialized counter (nil) did panic for %s", method) | ||
} | ||
} | ||
cases := []struct { | ||
testMethod func(c *CyclicIncrementor) | ||
}{ | ||
{func(c *CyclicIncrementor) { defer mustPanic("GetValue()"); c.GetValue() }}, | ||
{func(c *CyclicIncrementor) { defer mustPanic("Inc()"); c.Inc() }}, | ||
{func(c *CyclicIncrementor) { | ||
defer withoutPanic("SetMaxValue(-1)") | ||
if err := c.SetMaxValue(-1); err == nil { | ||
t.Errorf("SetMaxValue(-1) must return error, but it is not") | ||
} | ||
}}, | ||
{func(c *CyclicIncrementor) { defer mustPanic("SetMaxValue(0)"); c.SetMaxValue(0) }}, | ||
{func(c *CyclicIncrementor) { defer mustPanic("SetMaxValue(1)"); c.SetMaxValue(1) }}, | ||
} | ||
for _, c := range cases { | ||
c.testMethod(nil) | ||
} | ||
} | ||
|
||
func TestCyclicIncrementor(t *testing.T) { | ||
// testing in normal flow, without concurrency | ||
c, err := NewCyclicIncrementor() | ||
if err != nil { | ||
t.Errorf("Unexpected initial error: %s", err.Error()) | ||
} | ||
|
||
if c.max != MaxInt { | ||
t.Errorf("Unexpected initial maximum value (%d)", c.max) | ||
} | ||
|
||
if c.value != 0 { | ||
t.Errorf("Unexpected initial current value (%d)", c.value) | ||
} | ||
|
||
up := 5 | ||
for i := 0; i < up; i++ { | ||
c.Inc() | ||
} | ||
if c.GetValue() != up { | ||
t.Errorf("Unexpected counter value (%d) after sequential incrementing (%d)", c.GetValue(), up) | ||
} | ||
|
||
err = c.SetMaxValue(10) | ||
if err != nil { | ||
t.Errorf("Unexpected SetMaxValue(10) error: %s", err.Error()) | ||
} | ||
if c.GetValue() != 5 { | ||
t.Errorf("Unexpected counter value after maximum changed (%d)", c.GetValue()) | ||
} | ||
|
||
max := 4 | ||
err = c.SetMaxValue(max) | ||
if err != nil { | ||
t.Errorf("Unexpected SetMaxValue(%d) error: %s", max, err.Error()) | ||
} | ||
if c.GetValue() != 0 { | ||
t.Errorf("Counter value was not reset into zero (%d)", c.GetValue()) | ||
} | ||
for i := 0; i < max; i++ { | ||
c.Inc() | ||
} | ||
if c.GetValue() != max { | ||
t.Errorf("Counter value (%d) was not reach allowed maximum (%d)", c.GetValue(), max) | ||
} | ||
|
||
c.Inc() | ||
if c.GetValue() != 0 { | ||
t.Errorf("Counter value (%d) was not reset into zero next after reaching maximum (%d)", c.GetValue(), max) | ||
} | ||
} | ||
|
||
func TestCyclicIncrementorRWConcurrency(t *testing.T) { | ||
|
||
read := func(c *CyclicIncrementor, times int) { | ||
for i := 0; i < times; i++ { | ||
c.GetValue() | ||
randomSleep(0, 100*time.Millisecond) | ||
} | ||
} | ||
|
||
write := func(c *CyclicIncrementor, times int) { | ||
for i := 0; i < times; i++ { | ||
c.Inc() | ||
randomSleep(0, 100*time.Millisecond) | ||
} | ||
} | ||
|
||
c, _ := NewCyclicIncrementor() | ||
|
||
numWriters := 5 | ||
numIncrementsPerWriter := 10 | ||
expectedValue := numWriters * numIncrementsPerWriter | ||
wg := sync.WaitGroup{} | ||
wg.Add(numWriters * 2) | ||
// if race will be detected test will fail | ||
for i := 0; i < numWriters; i++ { | ||
go func() { | ||
write(c, numIncrementsPerWriter) | ||
wg.Done() | ||
}() | ||
// also run concurrent reads | ||
go func() { | ||
read(c, 20) | ||
wg.Done() | ||
}() | ||
} | ||
wg.Wait() | ||
|
||
if c.GetValue() != expectedValue { | ||
t.Errorf("Unexpected counter value (%d)", c.GetValue()) | ||
} | ||
} | ||
|
||
func randomSleep(min, max time.Duration) { | ||
if min > max { | ||
min, max = max, min | ||
} | ||
r := rand.Int63n(int64(max - min)) | ||
time.Sleep(time.Duration(r) + min) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Package counter provides different strategies of incrementing/decrementing values. | ||
package counter |