-
Notifications
You must be signed in to change notification settings - Fork 0
/
spara_test.go
executable file
·160 lines (143 loc) · 3.73 KB
/
spara_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package spara
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestRunBasic(t *testing.T) {
tests := []struct {
name string
workers int
iterations int
}{
{"ZeroIterations", 1, 0},
{"SingleIteration", 1, 1},
{"SingleWorker", 1, 10},
{"EqualWorkersAndIterations", 10, 10},
{"AbundanceOfWorkers", 100, 10},
{"CommonCase#1", 3, 10},
{"CommonCase#2", 5, 10},
{"CommonCase#3", 7, 10},
{"CommonCase#4", 9, 10},
}
for i, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
defer func() {
if t.Failed() {
t.Logf("case[%d] workers=%d iterations=%d", i, tc.workers, tc.iterations)
}
}()
subtestRunBasic(t, tc.workers, tc.iterations)
})
}
}
func subtestRunBasic(t *testing.T, workers, iterations int) {
calls := make(map[int]int)
total := 0
var mu sync.Mutex
err := Run(workers, iterations, func(i int) error {
mu.Lock()
defer mu.Unlock()
calls[i] = calls[i] + 1
total += 1
return nil
})
if err != nil {
t.Fatalf("err: %v", err)
}
if iterations != total {
t.Errorf(
"number of inputs: %d != number of times called: %d",
iterations, total,
)
}
for i := 0; i < iterations; i++ {
count := calls[i]
if count == 0 {
t.Errorf("function was not called with index %d", i)
} else if count > 1 {
t.Errorf("function called %d times with index %d", count, i)
}
}
}
func noopMappingFunc(i int) error {
return nil
}
func TestRunInputErrors(t *testing.T) {
if err := Run(0, 10, noopMappingFunc); err != ErrInvalidWorkers {
t.Errorf("expected calling Run with zero workers to fail: %s", err)
}
if err := Run(1, -1, noopMappingFunc); err != ErrInvalidIterations {
t.Errorf("expected calling Run with negative iterations to fail: %s", err)
}
}
func TestRunErrorBasic(t *testing.T) {
const (
workers = 5
iterations = 20
)
var count int32
expectedError := errors.New("")
err := RunWithContext(context.Background(), workers, iterations, func(ctx context.Context, i int) error {
atomic.AddInt32(&count, 1)
if i == workers-1 { // last initial worker
time.Sleep(time.Millisecond * 10)
return expectedError
}
<-ctx.Done() // wait for context to complete
return fmt.Errorf("unexpected error, returned from index %d", i)
})
if err != expectedError {
t.Errorf("did not return the expected error: %s", err)
} else if count != workers {
t.Errorf("call count: %d != initial worker count: %d", count, workers)
}
}
// Make sure that the iteration loop exits early, whether or not the mapping
// function cancels on context completion.
func TestRunWithContextStopsIteration(t *testing.T) {
const (
workers = 5
iterations = 500000
)
var completed int32
parent, cancel := context.WithTimeout(context.Background(), time.Millisecond * 100)
defer cancel()
err := RunWithContext(parent, workers, iterations, func(ctx context.Context, i int) error {
time.Sleep(time.Millisecond)
atomic.AddInt32(&completed, 1)
return nil
})
if err != context.DeadlineExceeded {
t.Errorf("unexpected err returned: %#v", err)
}
if completed == 0 || completed == iterations {
t.Errorf("completed was not in expected range: %d", completed)
}
t.Logf("actual completed: %d", completed)
}
func ExampleRun() {
const workers = 5
inputs := []int{1, 2, 3, 4, 5}
outputs := make([]int, len(inputs))
Run(workers, len(inputs), func(idx int) error {
outputs[idx] = inputs[idx] * 2
return nil
})
fmt.Println(outputs)
// Output: [2 4 6 8 10]
}
func ExampleRunWithContext() {
parent, cancel := context.WithTimeout(context.Background(), time.Millisecond * 10)
defer cancel()
err := RunWithContext(parent, 5, 50, func(ctx context.Context, idx int) error {
<-ctx.Done()
return ctx.Err()
})
fmt.Println(err)
// Output: context deadline exceeded
}