-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequest_concurrency_test.go
More file actions
127 lines (117 loc) · 3.42 KB
/
Copy pathrequest_concurrency_test.go
File metadata and controls
127 lines (117 loc) · 3.42 KB
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
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package httpserver
import (
"net/http"
"strconv"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
// concurrencyResult captures the outcome of a single concurrent worker so the
// main test goroutine can run all assertions.
type concurrencyResult struct {
index int
status int
body string
}
// TestConcurrency_SharedRouter_PerRequestIsolation proves that a single shared
// Router dispatches concurrent requests without cross-talk. Each worker sends a
// unique query value and the response body must echo back exactly that worker's
// own value (status-only assertions would miss data races in the parser cache).
func TestConcurrency_SharedRouter_PerRequestIsolation(t *testing.T) {
type Req struct {
Name string `query:"name" validate:"required"`
}
router := newTestRouter()
router.Handle("GET /", RequestParser(func(ctx *Context, req Req) {
ctx.NewResponse(http.StatusOK).StringBody(req.Name)
}))
const n = 200
start := make(chan struct{})
var wg sync.WaitGroup
wg.Add(n)
results := make(chan concurrencyResult, n)
for i := range n {
go func(idx int) {
defer wg.Done()
<-start
target := "/?name=user" + strconv.Itoa(idx)
rec := doRouterRequest(router, http.MethodGet, target)
results <- concurrencyResult{
index: idx,
status: rec.Code,
body: rec.Body.String(),
}
}(i)
}
close(start)
wg.Wait()
close(results)
bodies := make(map[int]string, n)
statuses := make(map[int]int, n)
for r := range results {
bodies[r.index] = r.body
statuses[r.index] = r.status
}
for i := range n {
assert.Equal(t, http.StatusOK, statuses[i], "worker %d status", i)
expected := "user" + strconv.Itoa(i)
assert.Equal(t, expected, bodies[i], "worker %d body", i)
}
}
// TestConcurrency_SharedRouter_WithMiddleware race-tests the dispatcher with
// middleware attached. The middleware increments a shared atomic counter to
// prove every request traverses the chain; the body still echoes the unique
// per-worker value.
func TestConcurrency_SharedRouter_WithMiddleware(t *testing.T) {
type Req struct {
ID string `query:"id" validate:"required"`
}
router := newTestRouter()
var mwHits atomic.Int64
mw := func(ctx *Context, next func()) {
mwHits.Add(1)
next()
}
router.Group(mw).Handle("GET /", RequestParser(func(ctx *Context, req Req) {
ctx.NewResponse(http.StatusOK).StringBody(req.ID)
}))
const n = 200
start := make(chan struct{})
var wg sync.WaitGroup
wg.Add(n)
results := make(chan concurrencyResult, n)
for i := range n {
go func(idx int) {
defer wg.Done()
<-start
target := "/?id=req" + strconv.Itoa(idx)
rec := doRouterRequest(router, http.MethodGet, target)
results <- concurrencyResult{
index: idx,
status: rec.Code,
body: rec.Body.String(),
}
}(i)
}
close(start)
wg.Wait()
close(results)
assert.Equal(t, int64(n), mwHits.Load(), "middleware should run once per request")
bodies := make(map[int]string, n)
statuses := make(map[int]int, n)
for r := range results {
bodies[r.index] = r.body
statuses[r.index] = r.status
}
for i := range n {
assert.Equal(t, http.StatusOK, statuses[i], "worker %d status", i)
expected := "req" + strconv.Itoa(i)
assert.Equal(t, expected, bodies[i], "worker %d body", i)
}
}