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 )
@@ -71,58 +71,76 @@ func TestTimeoutIsCancelable(t *testing.T) {
71
71
c := e .NewContext (req , rec )
72
72
73
73
err := m (func (c echo.Context ) error {
74
- assert .EqualValues (t , "*context.timerCtx" , reflect .TypeOf (c .Request ().Context ()).String ())
75
- return nil
74
+ return errors .New ("err" )
76
75
})(c )
77
76
78
- assert .NoError (t , err )
77
+ assert .Error (t , err )
79
78
}
80
79
81
- func TestTimeoutErrorOutInHandler (t * testing.T ) {
80
+ func TestTimeoutTestRequestClone (t * testing.T ) {
82
81
t .Parallel ()
83
- m := Timeout ( )
84
-
85
- req := httptest . NewRequest ( http . MethodGet , "/" , nil )
82
+ req := httptest . NewRequest ( http . MethodPost , "/uri?query=value" , strings . NewReader (url. Values { "form" : { "value" }}. Encode ()) )
83
+ req . AddCookie ( & http. Cookie { Name : "cookie" , Value : "value" })
84
+ req . Header . Set ( "Content-Type" , "application/x-www-form-urlencoded" )
86
85
rec := httptest .NewRecorder ()
87
86
87
+ m := TimeoutWithConfig (TimeoutConfig {
88
+ // Timeout has to be defined or the whole flow for timeout middleware will be skipped
89
+ Timeout : 1 * time .Second ,
90
+ })
91
+
88
92
e := echo .New ()
89
93
c := e .NewContext (req , rec )
90
94
91
95
err := m (func (c echo.Context ) error {
92
- return errors .New ("err" )
96
+ // Cookie test
97
+ cookie , err := c .Request ().Cookie ("cookie" )
98
+ if assert .NoError (t , err ) {
99
+ assert .EqualValues (t , "cookie" , cookie .Name )
100
+ assert .EqualValues (t , "value" , cookie .Value )
101
+ }
102
+
103
+ // Form values
104
+ if assert .NoError (t , c .Request ().ParseForm ()) {
105
+ assert .EqualValues (t , "value" , c .Request ().FormValue ("form" ))
106
+ }
107
+
108
+ // Query string
109
+ assert .EqualValues (t , "value" , c .Request ().URL .Query ()["query" ][0 ])
110
+ return nil
93
111
})(c )
94
112
95
- assert .Error (t , err )
113
+ assert .NoError (t , err )
114
+
96
115
}
97
116
98
- func TestTimeoutTimesOutAfterPredefinedTimeoutWithErrorHandler (t * testing.T ) {
117
+ func TestTimeoutRecoversPanic (t * testing.T ) {
99
118
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
- },
119
+ e := echo .New ()
120
+ e .Use (Recover ()) // recover middleware will handler our panic
121
+ e .Use (TimeoutWithConfig (TimeoutConfig {
122
+ Timeout : 50 * time .Millisecond ,
123
+ }))
124
+
125
+ e .GET ("/" , func (c echo.Context ) error {
126
+ panic ("panic!!!" )
106
127
})
107
128
108
129
req := httptest .NewRequest (http .MethodGet , "/" , nil )
109
130
rec := httptest .NewRecorder ()
110
131
111
- e := echo .New ()
112
- c := e .NewContext (req , rec )
113
-
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 ())
132
+ assert .NotPanics (t , func () {
133
+ e .ServeHTTP (rec , req )
134
+ })
120
135
}
121
136
122
- func TestTimeoutTimesOutAfterPredefinedTimeout (t * testing.T ) {
137
+ func TestTimeoutDataRace (t * testing.T ) {
123
138
t .Parallel ()
139
+
140
+ timeout := 1 * time .Millisecond
124
141
m := TimeoutWithConfig (TimeoutConfig {
125
- Timeout : time .Second ,
142
+ Timeout : timeout ,
143
+ ErrorMessage : "Timeout! change me" ,
126
144
})
127
145
128
146
req := httptest .NewRequest (http .MethodGet , "/" , nil )
@@ -132,54 +150,57 @@ func TestTimeoutTimesOutAfterPredefinedTimeout(t *testing.T) {
132
150
c := e .NewContext (req , rec )
133
151
134
152
err := m (func (c echo.Context ) error {
135
- time .Sleep (time .Minute )
136
- return nil
153
+ // NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
154
+ // the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
155
+ // difference over 500microseconds (0.5millisecond) response seems to be reliable
156
+ time .Sleep (timeout ) // timeout and handler execution time difference is close to zero
157
+ return c .String (http .StatusOK , "Hello, World!" )
137
158
})(c )
138
159
139
- assert .EqualError (t , err , context .DeadlineExceeded .Error ())
160
+ assert .NoError (t , err )
161
+
162
+ if rec .Code == http .StatusServiceUnavailable {
163
+ assert .Equal (t , "Timeout! change me" , rec .Body .String ())
164
+ } else {
165
+ assert .Equal (t , "Hello, World!" , rec .Body .String ())
166
+ }
140
167
}
141
168
142
- func TestTimeoutTestRequestClone (t * testing.T ) {
169
+ func TestTimeoutWithErrorMessage (t * testing.T ) {
143
170
t .Parallel ()
144
- req := httptest .NewRequest (http .MethodPost , "/uri?query=value" , strings .NewReader (url.Values {"form" : {"value" }}.Encode ()))
145
- req .AddCookie (& http.Cookie {Name : "cookie" , Value : "value" })
146
- req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
147
- rec := httptest .NewRecorder ()
148
171
172
+ timeout := 1 * time .Millisecond
149
173
m := TimeoutWithConfig (TimeoutConfig {
150
- // Timeout has to be defined or the whole flow for timeout middleware will be skipped
151
- Timeout : time . Second ,
174
+ Timeout : timeout ,
175
+ ErrorMessage : "Timeout! change me" ,
152
176
})
153
177
178
+ req := httptest .NewRequest (http .MethodGet , "/" , nil )
179
+ rec := httptest .NewRecorder ()
180
+
154
181
e := echo .New ()
155
182
c := e .NewContext (req , rec )
156
183
157
184
err := m (func (c echo.Context ) error {
158
- // Cookie test
159
- cookie , err := c .Request ().Cookie ("cookie" )
160
- if assert .NoError (t , err ) {
161
- assert .EqualValues (t , "cookie" , cookie .Name )
162
- assert .EqualValues (t , "value" , cookie .Value )
163
- }
164
-
165
- // Form values
166
- if assert .NoError (t , c .Request ().ParseForm ()) {
167
- assert .EqualValues (t , "value" , c .Request ().FormValue ("form" ))
168
- }
169
-
170
- // Query string
171
- assert .EqualValues (t , "value" , c .Request ().URL .Query ()["query" ][0 ])
172
- return nil
185
+ // NOTE: when difference between timeout duration and handler execution time is almost the same (in range of 100microseconds)
186
+ // the result of timeout does not seem to be reliable - could respond timeout, could respond handler output
187
+ // difference over 500microseconds (0.5millisecond) response seems to be reliable
188
+ time .Sleep (timeout + 1 * time .Millisecond ) // minimal difference
189
+ return c .String (http .StatusOK , "Hello, World!" )
173
190
})(c )
174
191
175
192
assert .NoError (t , err )
176
-
193
+ assert .Equal (t , http .StatusServiceUnavailable , rec .Code )
194
+ assert .Equal (t , "Timeout! change me" , rec .Body .String ())
177
195
}
178
196
179
- func TestTimeoutRecoversPanic (t * testing.T ) {
197
+ func TestTimeoutWithDefaultErrorMessage (t * testing.T ) {
180
198
t .Parallel ()
199
+
200
+ timeout := 1 * time .Millisecond
181
201
m := TimeoutWithConfig (TimeoutConfig {
182
- Timeout : 25 * time .Millisecond ,
202
+ Timeout : timeout ,
203
+ ErrorMessage : "" ,
183
204
})
184
205
185
206
req := httptest .NewRequest (http .MethodGet , "/" , nil )
@@ -189,8 +210,11 @@ func TestTimeoutRecoversPanic(t *testing.T) {
189
210
c := e .NewContext (req , rec )
190
211
191
212
err := m (func (c echo.Context ) error {
192
- panic ("panic in handler" )
213
+ time .Sleep (timeout + 25 * time .Millisecond )
214
+ return c .String (http .StatusOK , "Hello, World!" )
193
215
})(c )
194
216
195
- assert .Error (t , err , "panic recovered in timeout middleware: panic in handler" )
217
+ assert .NoError (t , err )
218
+ assert .Equal (t , http .StatusServiceUnavailable , rec .Code )
219
+ assert .Equal (t , `<html><head><title>Timeout</title></head><body><h1>Timeout</h1></body></html>` , rec .Body .String ())
196
220
}
0 commit comments