3
3
package middleware
4
4
5
5
import (
6
- "context"
7
6
"errors"
8
7
"github.com/labstack/echo/v4"
9
8
"github.com/stretchr/testify/assert"
@@ -22,6 +21,7 @@ func TestTimeoutSkipper(t *testing.T) {
22
21
Skipper : func (context echo.Context ) bool {
23
22
return true
24
23
},
24
+ Timeout : 1 * time .Nanosecond ,
25
25
})
26
26
27
27
req := httptest .NewRequest (http .MethodGet , "/" , nil )
@@ -31,18 +31,17 @@ func TestTimeoutSkipper(t *testing.T) {
31
31
c := e .NewContext (req , rec )
32
32
33
33
err := m (func (c echo.Context ) error {
34
- assert . NotEqual ( t , "*context.timerCtx" , reflect . TypeOf ( c . Request (). Context ()). String () )
35
- return nil
34
+ time . Sleep ( 25 * time . Microsecond )
35
+ return errors . New ( "response from handler" )
36
36
})(c )
37
37
38
- assert .NoError (t , err )
38
+ // if not skipped we would have not returned error due context timeout logic
39
+ assert .EqualError (t , err , "response from handler" )
39
40
}
40
41
41
42
func TestTimeoutWithTimeout0 (t * testing.T ) {
42
43
t .Parallel ()
43
- m := TimeoutWithConfig (TimeoutConfig {
44
- Timeout : 0 ,
45
- })
44
+ m := Timeout ()
46
45
47
46
req := httptest .NewRequest (http .MethodGet , "/" , nil )
48
47
rec := httptest .NewRecorder ()
@@ -58,10 +57,11 @@ func TestTimeoutWithTimeout0(t *testing.T) {
58
57
assert .NoError (t , err )
59
58
}
60
59
61
- func TestTimeoutIsCancelable (t * testing.T ) {
60
+ func TestTimeoutErrorOutInHandler (t * testing.T ) {
62
61
t .Parallel ()
63
62
m := TimeoutWithConfig (TimeoutConfig {
64
- Timeout : time .Minute ,
63
+ // Timeout has to be defined or the whole flow for timeout middleware will be skipped
64
+ Timeout : 50 * time .Millisecond ,
65
65
})
66
66
67
67
req := httptest .NewRequest (http .MethodGet , "/" , nil )
@@ -70,59 +70,22 @@ func TestTimeoutIsCancelable(t *testing.T) {
70
70
e := echo .New ()
71
71
c := e .NewContext (req , rec )
72
72
73
- err := m (func (c echo.Context ) error {
74
- assert .EqualValues (t , "*context.timerCtx" , reflect .TypeOf (c .Request ().Context ()).String ())
75
- return nil
76
- })(c )
77
-
78
- assert .NoError (t , err )
79
- }
80
-
81
- func TestTimeoutErrorOutInHandler (t * testing.T ) {
82
- t .Parallel ()
83
- m := Timeout ()
84
-
85
- req := httptest .NewRequest (http .MethodGet , "/" , nil )
86
- rec := httptest .NewRecorder ()
87
-
88
- e := echo .New ()
89
- c := e .NewContext (req , rec )
90
-
91
73
err := m (func (c echo.Context ) error {
92
74
return errors .New ("err" )
93
75
})(c )
94
76
95
77
assert .Error (t , err )
96
78
}
97
79
98
- func TestTimeoutTimesOutAfterPredefinedTimeoutWithErrorHandler (t * testing.T ) {
80
+ func TestTimeoutOnTimeoutRouteErrorHandler (t * testing.T ) {
99
81
t .Parallel ()
100
- m := TimeoutWithConfig (TimeoutConfig {
101
- Timeout : time .Second ,
102
- ErrorHandler : func (err error , e echo.Context ) error {
103
- assert .EqualError (t , err , context .DeadlineExceeded .Error ())
104
- return errors .New ("err" )
105
- },
106
- })
107
-
108
- req := httptest .NewRequest (http .MethodGet , "/" , nil )
109
- rec := httptest .NewRecorder ()
110
-
111
- e := echo .New ()
112
- c := e .NewContext (req , rec )
113
82
114
- err := m (func (c echo.Context ) error {
115
- time .Sleep (time .Minute )
116
- return nil
117
- })(c )
118
-
119
- assert .EqualError (t , err , errors .New ("err" ).Error ())
120
- }
121
-
122
- func TestTimeoutTimesOutAfterPredefinedTimeout (t * testing.T ) {
123
- t .Parallel ()
83
+ actualErrChan := make (chan error , 1 )
124
84
m := TimeoutWithConfig (TimeoutConfig {
125
- Timeout : time .Second ,
85
+ Timeout : 1 * time .Millisecond ,
86
+ OnTimeoutRouteErrorHandler : func (err error , c echo.Context ) {
87
+ actualErrChan <- err
88
+ },
126
89
})
127
90
128
91
req := httptest .NewRequest (http .MethodGet , "/" , nil )
@@ -131,12 +94,16 @@ func TestTimeoutTimesOutAfterPredefinedTimeout(t *testing.T) {
131
94
e := echo .New ()
132
95
c := e .NewContext (req , rec )
133
96
97
+ stopChan := make (chan struct {}, 0 )
134
98
err := m (func (c echo.Context ) error {
135
- time . Sleep ( time . Minute )
136
- return nil
99
+ <- stopChan
100
+ return errors . New ( "error in route after timeout" )
137
101
})(c )
102
+ stopChan <- struct {}{}
103
+ assert .NoError (t , err )
138
104
139
- assert .EqualError (t , err , context .DeadlineExceeded .Error ())
105
+ actualErr := <- actualErrChan
106
+ assert .EqualError (t , actualErr , "error in route after timeout" )
140
107
}
141
108
142
109
func TestTimeoutTestRequestClone (t * testing.T ) {
@@ -148,7 +115,7 @@ func TestTimeoutTestRequestClone(t *testing.T) {
148
115
149
116
m := TimeoutWithConfig (TimeoutConfig {
150
117
// Timeout has to be defined or the whole flow for timeout middleware will be skipped
151
- Timeout : time .Second ,
118
+ Timeout : 1 * time .Second ,
152
119
})
153
120
154
121
e := echo .New ()
@@ -178,8 +145,63 @@ func TestTimeoutTestRequestClone(t *testing.T) {
178
145
179
146
func TestTimeoutRecoversPanic (t * testing.T ) {
180
147
t .Parallel ()
148
+ e := echo .New ()
149
+ e .Use (Recover ()) // recover middleware will handler our panic
150
+ e .Use (TimeoutWithConfig (TimeoutConfig {
151
+ Timeout : 50 * time .Millisecond ,
152
+ }))
153
+
154
+ e .GET ("/" , func (c echo.Context ) error {
155
+ panic ("panic!!!" )
156
+ })
157
+
158
+ req := httptest .NewRequest (http .MethodGet , "/" , nil )
159
+ rec := httptest .NewRecorder ()
160
+
161
+ assert .NotPanics (t , func () {
162
+ e .ServeHTTP (rec , req )
163
+ })
164
+ }
165
+
166
+ func TestTimeoutDataRace (t * testing.T ) {
167
+ t .Parallel ()
168
+
169
+ timeout := 1 * time .Millisecond
170
+ m := TimeoutWithConfig (TimeoutConfig {
171
+ Timeout : timeout ,
172
+ ErrorMessage : "Timeout! change me" ,
173
+ })
174
+
175
+ req := httptest .NewRequest (http .MethodGet , "/" , nil )
176
+ rec := httptest .NewRecorder ()
177
+
178
+ e := echo .New ()
179
+ c := e .NewContext (req , rec )
180
+
181
+ err := m (func (c echo.Context ) error {
182
+ // NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
183
+ // the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
184
+ // difference over 500microseconds (0.5millisecond) response seems to be reliable
185
+ time .Sleep (timeout ) // timeout and handler execution time difference is close to zero
186
+ return c .String (http .StatusOK , "Hello, World!" )
187
+ })(c )
188
+
189
+ assert .NoError (t , err )
190
+
191
+ if rec .Code == http .StatusServiceUnavailable {
192
+ assert .Equal (t , "Timeout! change me" , rec .Body .String ())
193
+ } else {
194
+ assert .Equal (t , "Hello, World!" , rec .Body .String ())
195
+ }
196
+ }
197
+
198
+ func TestTimeoutWithErrorMessage (t * testing.T ) {
199
+ t .Parallel ()
200
+
201
+ timeout := 1 * time .Millisecond
181
202
m := TimeoutWithConfig (TimeoutConfig {
182
- Timeout : 25 * time .Millisecond ,
203
+ Timeout : timeout ,
204
+ ErrorMessage : "Timeout! change me" ,
183
205
})
184
206
185
207
req := httptest .NewRequest (http .MethodGet , "/" , nil )
@@ -188,9 +210,44 @@ func TestTimeoutRecoversPanic(t *testing.T) {
188
210
e := echo .New ()
189
211
c := e .NewContext (req , rec )
190
212
213
+ stopChan := make (chan struct {}, 0 )
191
214
err := m (func (c echo.Context ) error {
192
- panic ("panic in handler" )
215
+ // NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
216
+ // the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
217
+ // difference over 500microseconds (0.5millisecond) response seems to be reliable
218
+ <- stopChan
219
+ return c .String (http .StatusOK , "Hello, World!" )
193
220
})(c )
221
+ stopChan <- struct {}{}
222
+
223
+ assert .NoError (t , err )
224
+ assert .Equal (t , http .StatusServiceUnavailable , rec .Code )
225
+ assert .Equal (t , "Timeout! change me" , rec .Body .String ())
226
+ }
227
+
228
+ func TestTimeoutWithDefaultErrorMessage (t * testing.T ) {
229
+ t .Parallel ()
194
230
195
- assert .Error (t , err , "panic recovered in timeout middleware: panic in handler" )
231
+ timeout := 1 * time .Millisecond
232
+ m := TimeoutWithConfig (TimeoutConfig {
233
+ Timeout : timeout ,
234
+ ErrorMessage : "" ,
235
+ })
236
+
237
+ req := httptest .NewRequest (http .MethodGet , "/" , nil )
238
+ rec := httptest .NewRecorder ()
239
+
240
+ e := echo .New ()
241
+ c := e .NewContext (req , rec )
242
+
243
+ stopChan := make (chan struct {}, 0 )
244
+ err := m (func (c echo.Context ) error {
245
+ <- stopChan
246
+ return c .String (http .StatusOK , "Hello, World!" )
247
+ })(c )
248
+ stopChan <- struct {}{}
249
+
250
+ assert .NoError (t , err )
251
+ assert .Equal (t , http .StatusServiceUnavailable , rec .Code )
252
+ assert .Equal (t , `<html><head><title>Timeout</title></head><body><h1>Timeout</h1></body></html>` , rec .Body .String ())
196
253
}
0 commit comments