Merge lp:~pedronis/ubuntu-push/acceptance-flex-2 into lp:ubuntu-push
- acceptance-flex-2
- Merge into trunk
Proposed by
Samuele Pedroni
Status: | Merged |
---|---|
Approved by: | Samuele Pedroni |
Approved revision: | 69 |
Merged at revision: | 66 |
Proposed branch: | lp:~pedronis/ubuntu-push/acceptance-flex-2 |
Merge into: | lp:ubuntu-push |
Prerequisite: | lp:~pedronis/ubuntu-push/acceptance-flex-1 |
Diff against target: |
850 lines (+363/-319) 5 files modified
server/acceptance/acceptance_test.go (+62/-0) server/acceptance/suites/broadcast.go (+35/-316) server/acceptance/suites/helpers.go (+3/-3) server/acceptance/suites/pingpong.go (+93/-0) server/acceptance/suites/suite.go (+170/-0) |
To merge this branch: | bzr merge lp:~pedronis/ubuntu-push/acceptance-flex-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Review via email: mp+205847@code.launchpad.net |
Commit message
restructure acceptance tests into reusable suites
Description of the change
restructure acceptance tests into reusable suites
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'server/acceptance/acceptance_test.go' | |||
2 | --- server/acceptance/acceptance_test.go 1970-01-01 00:00:00 +0000 | |||
3 | +++ server/acceptance/acceptance_test.go 2014-02-11 20:40:06 +0000 | |||
4 | @@ -0,0 +1,62 @@ | |||
5 | 1 | /* | ||
6 | 2 | Copyright 2013-2014 Canonical Ltd. | ||
7 | 3 | |||
8 | 4 | This program is free software: you can redistribute it and/or modify it | ||
9 | 5 | under the terms of the GNU General Public License version 3, as published | ||
10 | 6 | by the Free Software Foundation. | ||
11 | 7 | |||
12 | 8 | This program is distributed in the hope that it will be useful, but | ||
13 | 9 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
14 | 10 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
15 | 11 | PURPOSE. See the GNU General Public License for more details. | ||
16 | 12 | |||
17 | 13 | You should have received a copy of the GNU General Public License along | ||
18 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | 15 | */ | ||
20 | 16 | |||
21 | 17 | package acceptance_test | ||
22 | 18 | |||
23 | 19 | import ( | ||
24 | 20 | "flag" | ||
25 | 21 | "fmt" | ||
26 | 22 | "strings" | ||
27 | 23 | "testing" | ||
28 | 24 | |||
29 | 25 | . "launchpad.net/gocheck" | ||
30 | 26 | |||
31 | 27 | "launchpad.net/ubuntu-push/server/acceptance/suites" | ||
32 | 28 | ) | ||
33 | 29 | |||
34 | 30 | func TestAcceptance(t *testing.T) { TestingT(t) } | ||
35 | 31 | |||
36 | 32 | var serverCmd = flag.String("server", "", "server to test") | ||
37 | 33 | var serverAuxCfg = flag.String("auxcfg", "", "auxiliary config for the server") | ||
38 | 34 | |||
39 | 35 | func testServerConfig(addr, httpAddr string) map[string]interface{} { | ||
40 | 36 | cfg := make(map[string]interface{}) | ||
41 | 37 | suites.FillServerConfig(cfg, addr) | ||
42 | 38 | suites.FillHTTPServerConfig(cfg, httpAddr) | ||
43 | 39 | return cfg | ||
44 | 40 | } | ||
45 | 41 | |||
46 | 42 | // Start a server. | ||
47 | 43 | func StartServer(c *C) (<-chan string, func(), string, string) { | ||
48 | 44 | if *serverCmd == "" { | ||
49 | 45 | c.Skip("executable server not specified") | ||
50 | 46 | } | ||
51 | 47 | tmpDir := c.MkDir() | ||
52 | 48 | cfg := testServerConfig("127.0.0.1:0", "127.0.0.1:0") | ||
53 | 49 | cfgFilename := suites.WriteConfig(c, tmpDir, "config.json", cfg) | ||
54 | 50 | cfgs := append(strings.Fields(*serverAuxCfg), cfgFilename) | ||
55 | 51 | logs, killServer := suites.RunAndObserve(c, *serverCmd, cfgs...) | ||
56 | 52 | serverHTTPAddr := suites.ExtractListeningAddr(c, logs, suites.HTTPListeningOnPat) | ||
57 | 53 | serverURL := fmt.Sprintf("http://%s", serverHTTPAddr) | ||
58 | 54 | serverAddr := suites.ExtractListeningAddr(c, logs, suites.DevListeningOnPat) | ||
59 | 55 | return logs, killServer, serverAddr, serverURL | ||
60 | 56 | } | ||
61 | 57 | |||
62 | 58 | // ping pong/connectivity | ||
63 | 59 | var _ = Suite(&suites.PingPongAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}}) | ||
64 | 60 | |||
65 | 61 | // broadcast | ||
66 | 62 | var _ = Suite(&suites.BroadcastAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}}) | ||
67 | 0 | 63 | ||
68 | === added directory 'server/acceptance/suites' | |||
69 | === renamed file 'server/acceptance/acceptance_test.go' => 'server/acceptance/suites/broadcast.go' | |||
70 | --- server/acceptance/acceptance_test.go 2014-02-11 20:40:06 +0000 | |||
71 | +++ server/acceptance/suites/broadcast.go 2014-02-11 20:40:06 +0000 | |||
72 | @@ -14,248 +14,30 @@ | |||
73 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. |
74 | 15 | */ | 15 | */ |
75 | 16 | 16 | ||
77 | 17 | package acceptance | 17 | package suites |
78 | 18 | 18 | ||
79 | 19 | import ( | 19 | import ( |
80 | 20 | "bytes" | ||
81 | 21 | "encoding/json" | 20 | "encoding/json" |
82 | 22 | "flag" | ||
83 | 23 | "fmt" | 21 | "fmt" |
84 | 24 | "io/ioutil" | ||
85 | 25 | "net" | ||
86 | 26 | "net/http" | ||
87 | 27 | "runtime" | ||
88 | 28 | "strings" | 22 | "strings" |
89 | 29 | "testing" | ||
90 | 30 | "time" | 23 | "time" |
91 | 31 | 24 | ||
92 | 32 | . "launchpad.net/gocheck" | 25 | . "launchpad.net/gocheck" |
93 | 33 | 26 | ||
94 | 34 | "launchpad.net/ubuntu-push/protocol" | 27 | "launchpad.net/ubuntu-push/protocol" |
95 | 35 | "launchpad.net/ubuntu-push/server/api" | 28 | "launchpad.net/ubuntu-push/server/api" |
96 | 36 | helpers "launchpad.net/ubuntu-push/testing" | ||
97 | 37 | ) | 29 | ) |
98 | 38 | 30 | ||
268 | 39 | func TestAcceptance(t *testing.T) { TestingT(t) } | 31 | // BroadCastAcceptanceSuite has tests about broadcast. |
269 | 40 | 32 | type BroadcastAcceptanceSuite struct { | |
270 | 41 | type acceptanceSuite struct { | 33 | AcceptanceSuite |
271 | 42 | serverKill func() | 34 | } |
272 | 43 | serverAddr string | 35 | |
273 | 44 | serverURL string | 36 | // Long after the end of the tests. |
105 | 45 | serverEvents <-chan string | ||
106 | 46 | httpClient *http.Client | ||
107 | 47 | } | ||
108 | 48 | |||
109 | 49 | var _ = Suite(&acceptanceSuite{}) | ||
110 | 50 | |||
111 | 51 | var serverCmd = flag.String("server", "", "server to test") | ||
112 | 52 | var serverAuxCfg = flag.String("auxcfg", "", "auxiliary config for the server") | ||
113 | 53 | |||
114 | 54 | func testServerConfig(addr, httpAddr string) map[string]interface{} { | ||
115 | 55 | cfg := make(map[string]interface{}) | ||
116 | 56 | FillServerConfig(cfg, addr) | ||
117 | 57 | FillHTTPServerConfig(cfg, httpAddr) | ||
118 | 58 | return cfg | ||
119 | 59 | } | ||
120 | 60 | |||
121 | 61 | func testClientSession(addr string, deviceId string, reportPings bool) *ClientSession { | ||
122 | 62 | certPEMBlock, err := ioutil.ReadFile(helpers.SourceRelative("config/testing.cert")) | ||
123 | 63 | if err != nil { | ||
124 | 64 | panic(fmt.Sprintf("could not read config/testing.cert: %v", err)) | ||
125 | 65 | } | ||
126 | 66 | return &ClientSession{ | ||
127 | 67 | ExchangeTimeout: 100 * time.Millisecond, | ||
128 | 68 | ServerAddr: addr, | ||
129 | 69 | CertPEMBlock: certPEMBlock, | ||
130 | 70 | DeviceId: deviceId, | ||
131 | 71 | ReportPings: reportPings, | ||
132 | 72 | } | ||
133 | 73 | } | ||
134 | 74 | |||
135 | 75 | // start a new server for each test | ||
136 | 76 | func (s *acceptanceSuite) SetUpTest(c *C) { | ||
137 | 77 | if *serverCmd == "" { | ||
138 | 78 | c.Skip("executable server not specified") | ||
139 | 79 | } | ||
140 | 80 | tmpDir := c.MkDir() | ||
141 | 81 | cfg := testServerConfig("127.0.0.1:0", "127.0.0.1:0") | ||
142 | 82 | cfgFilename := WriteConfig(c, tmpDir, "config.json", cfg) | ||
143 | 83 | cfgs := append(strings.Fields(*serverAuxCfg), cfgFilename) | ||
144 | 84 | serverEvents, killServer := RunAndObserve(c, *serverCmd, cfgs...) | ||
145 | 85 | s.serverKill = killServer | ||
146 | 86 | serverHTTPAddr := ExtractListeningAddr(c, serverEvents, HTTPListeningOnPat) | ||
147 | 87 | s.serverURL = fmt.Sprintf("http://%s", serverHTTPAddr) | ||
148 | 88 | s.serverAddr = ExtractListeningAddr(c, serverEvents, DevListeningOnPat) | ||
149 | 89 | s.serverEvents = serverEvents | ||
150 | 90 | s.httpClient = &http.Client{} | ||
151 | 91 | } | ||
152 | 92 | |||
153 | 93 | func (s *acceptanceSuite) TearDownTest(c *C) { | ||
154 | 94 | if s.serverKill != nil { | ||
155 | 95 | s.serverKill() | ||
156 | 96 | } | ||
157 | 97 | } | ||
158 | 98 | |||
159 | 99 | // Tests about connection, ping-pong, disconnection scenarios | ||
160 | 100 | |||
161 | 101 | // typically combined with -gocheck.vv or test selection | ||
162 | 102 | var logTraffic = flag.Bool("logTraffic", false, "log traffic") | ||
163 | 103 | |||
164 | 104 | type connInterceptor func(ic *interceptingConn, op string, b []byte) (bool, int, error) | ||
165 | 105 | |||
166 | 106 | type interceptingConn struct { | ||
167 | 107 | net.Conn | ||
168 | 108 | totalRead int | ||
169 | 109 | totalWritten int | ||
170 | 110 | intercept connInterceptor | ||
171 | 111 | } | ||
172 | 112 | |||
173 | 113 | func (ic *interceptingConn) Write(b []byte) (n int, err error) { | ||
174 | 114 | done := false | ||
175 | 115 | before := ic.totalWritten | ||
176 | 116 | if ic.intercept != nil { | ||
177 | 117 | done, n, err = ic.intercept(ic, "write", b) | ||
178 | 118 | } | ||
179 | 119 | if !done { | ||
180 | 120 | n, err = ic.Conn.Write(b) | ||
181 | 121 | } | ||
182 | 122 | ic.totalWritten += n | ||
183 | 123 | if *logTraffic { | ||
184 | 124 | fmt.Printf("W[%v]: %d %#v %v %d\n", ic.Conn.LocalAddr(), before, string(b[:n]), err, ic.totalWritten) | ||
185 | 125 | } | ||
186 | 126 | return | ||
187 | 127 | } | ||
188 | 128 | |||
189 | 129 | func (ic *interceptingConn) Read(b []byte) (n int, err error) { | ||
190 | 130 | done := false | ||
191 | 131 | before := ic.totalRead | ||
192 | 132 | if ic.intercept != nil { | ||
193 | 133 | done, n, err = ic.intercept(ic, "read", b) | ||
194 | 134 | } | ||
195 | 135 | if !done { | ||
196 | 136 | n, err = ic.Conn.Read(b) | ||
197 | 137 | } | ||
198 | 138 | ic.totalRead += n | ||
199 | 139 | if *logTraffic { | ||
200 | 140 | fmt.Printf("R[%v]: %d %#v %v %d\n", ic.Conn.LocalAddr(), before, string(b[:n]), err, ic.totalRead) | ||
201 | 141 | } | ||
202 | 142 | return | ||
203 | 143 | } | ||
204 | 144 | |||
205 | 145 | func (s *acceptanceSuite) TestConnectPingPing(c *C) { | ||
206 | 146 | errCh := make(chan error, 1) | ||
207 | 147 | events := make(chan string, 10) | ||
208 | 148 | sess := testClientSession(s.serverAddr, "DEVA", true) | ||
209 | 149 | err := sess.Dial() | ||
210 | 150 | c.Assert(err, IsNil) | ||
211 | 151 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
212 | 152 | // would be 3rd ping read, based on logged traffic | ||
213 | 153 | if op == "read" && ic.totalRead >= 79 { | ||
214 | 154 | // exit the sess.Run() goroutine, client will close | ||
215 | 155 | runtime.Goexit() | ||
216 | 156 | } | ||
217 | 157 | return false, 0, nil | ||
218 | 158 | } | ||
219 | 159 | sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} | ||
220 | 160 | go func() { | ||
221 | 161 | errCh <- sess.Run(events) | ||
222 | 162 | }() | ||
223 | 163 | connectCli := NextEvent(events, errCh) | ||
224 | 164 | connectSrv := NextEvent(s.serverEvents, nil) | ||
225 | 165 | registeredSrv := NextEvent(s.serverEvents, nil) | ||
226 | 166 | tconnect := time.Now() | ||
227 | 167 | c.Assert(connectSrv, Matches, ".*session.* connected .*") | ||
228 | 168 | c.Assert(registeredSrv, Matches, ".*session.* registered DEVA") | ||
229 | 169 | c.Assert(strings.HasSuffix(connectSrv, connectCli), Equals, true) | ||
230 | 170 | c.Assert(NextEvent(events, errCh), Equals, "Ping") | ||
231 | 171 | elapsedOfPing := float64(time.Since(tconnect)) / float64(500*time.Millisecond) | ||
232 | 172 | c.Check(elapsedOfPing >= 1.0, Equals, true) | ||
233 | 173 | c.Check(elapsedOfPing < 1.05, Equals, true) | ||
234 | 174 | c.Assert(NextEvent(events, errCh), Equals, "Ping") | ||
235 | 175 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* ended with: EOF") | ||
236 | 176 | c.Check(len(errCh), Equals, 0) | ||
237 | 177 | } | ||
238 | 178 | |||
239 | 179 | func (s *acceptanceSuite) TestConnectPingNeverPong(c *C) { | ||
240 | 180 | errCh := make(chan error, 1) | ||
241 | 181 | events := make(chan string, 10) | ||
242 | 182 | sess := testClientSession(s.serverAddr, "DEVB", true) | ||
243 | 183 | err := sess.Dial() | ||
244 | 184 | c.Assert(err, IsNil) | ||
245 | 185 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
246 | 186 | // would be pong to 2nd ping, based on logged traffic | ||
247 | 187 | if op == "write" && ic.totalRead >= 67 { | ||
248 | 188 | time.Sleep(200 * time.Millisecond) | ||
249 | 189 | // exit the sess.Run() goroutine, client will close | ||
250 | 190 | runtime.Goexit() | ||
251 | 191 | } | ||
252 | 192 | return false, 0, nil | ||
253 | 193 | } | ||
254 | 194 | sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} | ||
255 | 195 | go func() { | ||
256 | 196 | errCh <- sess.Run(events) | ||
257 | 197 | }() | ||
258 | 198 | c.Assert(NextEvent(events, errCh), Matches, "connected .*") | ||
259 | 199 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* connected .*") | ||
260 | 200 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* registered .*") | ||
261 | 201 | c.Assert(NextEvent(events, errCh), Equals, "Ping") | ||
262 | 202 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*timeout`) | ||
263 | 203 | c.Check(len(errCh), Equals, 0) | ||
264 | 204 | } | ||
265 | 205 | |||
266 | 206 | // Tests about broadcast | ||
267 | 207 | |||
274 | 208 | var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339) | 37 | var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339) |
275 | 209 | 38 | ||
325 | 210 | func (s *acceptanceSuite) postRequest(path string, message interface{}) (string, error) { | 39 | func (s *BroadcastAcceptanceSuite) TestBroadcastToConnected(c *C) { |
326 | 211 | packedMessage, err := json.Marshal(message) | 40 | events, errCh, stop := s.startClient(c, "DEVB", nil) |
278 | 212 | if err != nil { | ||
279 | 213 | panic(err) | ||
280 | 214 | } | ||
281 | 215 | reader := bytes.NewReader(packedMessage) | ||
282 | 216 | |||
283 | 217 | url := s.serverURL + path | ||
284 | 218 | request, _ := http.NewRequest("POST", url, reader) | ||
285 | 219 | request.ContentLength = int64(reader.Len()) | ||
286 | 220 | request.Header.Set("Content-Type", "application/json") | ||
287 | 221 | |||
288 | 222 | resp, err := s.httpClient.Do(request) | ||
289 | 223 | if err != nil { | ||
290 | 224 | panic(err) | ||
291 | 225 | } | ||
292 | 226 | defer resp.Body.Close() | ||
293 | 227 | body, err := ioutil.ReadAll(resp.Body) | ||
294 | 228 | return string(body), err | ||
295 | 229 | } | ||
296 | 230 | |||
297 | 231 | func (s *acceptanceSuite) startClient(c *C, devId string, intercept connInterceptor, levels map[string]int64) (<-chan string, <-chan error) { | ||
298 | 232 | errCh := make(chan error, 1) | ||
299 | 233 | events := make(chan string, 10) | ||
300 | 234 | sess := testClientSession(s.serverAddr, devId, false) | ||
301 | 235 | sess.Levels = levels | ||
302 | 236 | err := sess.Dial() | ||
303 | 237 | c.Assert(err, IsNil) | ||
304 | 238 | sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} | ||
305 | 239 | go func() { | ||
306 | 240 | errCh <- sess.Run(events) | ||
307 | 241 | }() | ||
308 | 242 | c.Assert(NextEvent(events, errCh), Matches, "connected .*") | ||
309 | 243 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* connected .*") | ||
310 | 244 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* registered "+devId) | ||
311 | 245 | return events, errCh | ||
312 | 246 | } | ||
313 | 247 | |||
314 | 248 | func (s *acceptanceSuite) TestBroadcastToConnected(c *C) { | ||
315 | 249 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | ||
316 | 250 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
317 | 251 | // read after ack | ||
318 | 252 | if op == "read" && len(clientShutdown) > 0 { | ||
319 | 253 | // exit the sess.Run() goroutine, client will close | ||
320 | 254 | runtime.Goexit() | ||
321 | 255 | } | ||
322 | 256 | return false, 0, nil | ||
323 | 257 | } | ||
324 | 258 | events, errCh := s.startClient(c, "DEVB", intercept, nil) | ||
327 | 259 | got, err := s.postRequest("/broadcast", &api.Broadcast{ | 41 | got, err := s.postRequest("/broadcast", &api.Broadcast{ |
328 | 260 | Channel: "system", | 42 | Channel: "system", |
329 | 261 | ExpireOn: future, | 43 | ExpireOn: future, |
330 | @@ -264,12 +46,12 @@ | |||
331 | 264 | c.Assert(err, IsNil) | 46 | c.Assert(err, IsNil) |
332 | 265 | c.Assert(got, Matches, ".*ok.*") | 47 | c.Assert(got, Matches, ".*ok.*") |
333 | 266 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"n":42}]`) | 48 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"n":42}]`) |
335 | 267 | clientShutdown <- true | 49 | stop() |
336 | 268 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 50 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
337 | 269 | c.Check(len(errCh), Equals, 0) | 51 | c.Check(len(errCh), Equals, 0) |
338 | 270 | } | 52 | } |
339 | 271 | 53 | ||
341 | 272 | func (s *acceptanceSuite) TestBroadcastPending(c *C) { | 54 | func (s *BroadcastAcceptanceSuite) TestBroadcastPending(c *C) { |
342 | 273 | // send broadcast that will be pending | 55 | // send broadcast that will be pending |
343 | 274 | got, err := s.postRequest("/broadcast", &api.Broadcast{ | 56 | got, err := s.postRequest("/broadcast", &api.Broadcast{ |
344 | 275 | Channel: "system", | 57 | Channel: "system", |
345 | @@ -279,24 +61,15 @@ | |||
346 | 279 | c.Assert(err, IsNil) | 61 | c.Assert(err, IsNil) |
347 | 280 | c.Assert(got, Matches, ".*ok.*") | 62 | c.Assert(got, Matches, ".*ok.*") |
348 | 281 | 63 | ||
359 | 282 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | 64 | events, errCh, stop := s.startClient(c, "DEVB", nil) |
350 | 283 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
351 | 284 | // read after ack | ||
352 | 285 | if op == "read" && len(clientShutdown) > 0 { | ||
353 | 286 | // exit the sess.Run() goroutine, client will close | ||
354 | 287 | runtime.Goexit() | ||
355 | 288 | } | ||
356 | 289 | return false, 0, nil | ||
357 | 290 | } | ||
358 | 291 | events, errCh := s.startClient(c, "DEVB", intercept, nil) | ||
360 | 292 | // gettting pending on connect | 65 | // gettting pending on connect |
361 | 293 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"b":1}]`) | 66 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"b":1}]`) |
363 | 294 | clientShutdown <- true | 67 | stop() |
364 | 295 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 68 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
365 | 296 | c.Check(len(errCh), Equals, 0) | 69 | c.Check(len(errCh), Equals, 0) |
366 | 297 | } | 70 | } |
367 | 298 | 71 | ||
369 | 299 | func (s *acceptanceSuite) TestBroadcasLargeNeedsSplitting(c *C) { | 72 | func (s *BroadcastAcceptanceSuite) TestBroadcasLargeNeedsSplitting(c *C) { |
370 | 300 | // send bunch of broadcasts that will be pending | 73 | // send bunch of broadcasts that will be pending |
371 | 301 | payloadFmt := fmt.Sprintf(`{"b":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2)) | 74 | payloadFmt := fmt.Sprintf(`{"b":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2)) |
372 | 302 | for i := 0; i < 32; i++ { | 75 | for i := 0; i < 32; i++ { |
373 | @@ -309,38 +82,20 @@ | |||
374 | 309 | c.Assert(got, Matches, ".*ok.*") | 82 | c.Assert(got, Matches, ".*ok.*") |
375 | 310 | } | 83 | } |
376 | 311 | 84 | ||
387 | 312 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | 85 | events, errCh, stop := s.startClient(c, "DEVC", nil) |
378 | 313 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
379 | 314 | // read after ack | ||
380 | 315 | if op == "read" && len(clientShutdown) > 0 { | ||
381 | 316 | // exit the sess.Run() goroutine, client will close | ||
382 | 317 | runtime.Goexit() | ||
383 | 318 | } | ||
384 | 319 | return false, 0, nil | ||
385 | 320 | } | ||
386 | 321 | events, errCh := s.startClient(c, "DEVC", intercept, nil) | ||
388 | 322 | // gettting pending on connect | 86 | // gettting pending on connect |
389 | 323 | c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:30 payloads:\[{"b":0,.*`) | 87 | c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:30 payloads:\[{"b":0,.*`) |
390 | 324 | c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:32 payloads:\[.*`) | 88 | c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:32 payloads:\[.*`) |
392 | 325 | clientShutdown <- true | 89 | stop() |
393 | 326 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 90 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
394 | 327 | c.Check(len(errCh), Equals, 0) | 91 | c.Check(len(errCh), Equals, 0) |
395 | 328 | } | 92 | } |
396 | 329 | 93 | ||
407 | 330 | func (s *acceptanceSuite) TestBroadcastDistribution2(c *C) { | 94 | func (s *BroadcastAcceptanceSuite) TestBroadcastDistribution2(c *C) { |
398 | 331 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | ||
399 | 332 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
400 | 333 | // read after ack | ||
401 | 334 | if op == "read" && len(clientShutdown) > 0 { | ||
402 | 335 | // exit the sess.Run() goroutine, client will close | ||
403 | 336 | runtime.Goexit() | ||
404 | 337 | } | ||
405 | 338 | return false, 0, nil | ||
406 | 339 | } | ||
408 | 340 | // start 1st clinet | 95 | // start 1st clinet |
410 | 341 | events1, errCh1 := s.startClient(c, "DEV1", intercept, nil) | 96 | events1, errCh1, stop1 := s.startClient(c, "DEV1", nil) |
411 | 342 | // start 2nd client | 97 | // start 2nd client |
413 | 343 | events2, errCh2 := s.startClient(c, "DEV2", intercept, nil) | 98 | events2, errCh2, stop2 := s.startClient(c, "DEV2", nil) |
414 | 344 | // broadcast | 99 | // broadcast |
415 | 345 | got, err := s.postRequest("/broadcast", &api.Broadcast{ | 100 | got, err := s.postRequest("/broadcast", &api.Broadcast{ |
416 | 346 | Channel: "system", | 101 | Channel: "system", |
417 | @@ -351,24 +106,16 @@ | |||
418 | 351 | c.Assert(got, Matches, ".*ok.*") | 106 | c.Assert(got, Matches, ".*ok.*") |
419 | 352 | c.Check(NextEvent(events1, errCh1), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"n":42}]`) | 107 | c.Check(NextEvent(events1, errCh1), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"n":42}]`) |
420 | 353 | c.Check(NextEvent(events2, errCh2), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"n":42}]`) | 108 | c.Check(NextEvent(events2, errCh2), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"n":42}]`) |
422 | 354 | clientShutdown <- true | 109 | stop1() |
423 | 110 | stop2() | ||
424 | 355 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 111 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
425 | 356 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 112 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
426 | 357 | c.Check(len(errCh1), Equals, 0) | 113 | c.Check(len(errCh1), Equals, 0) |
427 | 358 | c.Check(len(errCh2), Equals, 0) | 114 | c.Check(len(errCh2), Equals, 0) |
428 | 359 | } | 115 | } |
429 | 360 | 116 | ||
441 | 361 | func (s *acceptanceSuite) TestBroadcastFilterByLevel(c *C) { | 117 | func (s *BroadcastAcceptanceSuite) TestBroadcastFilterByLevel(c *C) { |
442 | 362 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | 118 | events, errCh, stop := s.startClient(c, "DEVD", nil) |
432 | 363 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
433 | 364 | // read after ack | ||
434 | 365 | if op == "read" && len(clientShutdown) > 0 { | ||
435 | 366 | // exit the sess.Run() goroutine, client will close | ||
436 | 367 | runtime.Goexit() | ||
437 | 368 | } | ||
438 | 369 | return false, 0, nil | ||
439 | 370 | } | ||
440 | 371 | events, errCh := s.startClient(c, "DEVD", intercept, nil) | ||
443 | 372 | got, err := s.postRequest("/broadcast", &api.Broadcast{ | 119 | got, err := s.postRequest("/broadcast", &api.Broadcast{ |
444 | 373 | Channel: "system", | 120 | Channel: "system", |
445 | 374 | ExpireOn: future, | 121 | ExpireOn: future, |
446 | @@ -377,7 +124,7 @@ | |||
447 | 377 | c.Assert(err, IsNil) | 124 | c.Assert(err, IsNil) |
448 | 378 | c.Assert(got, Matches, ".*ok.*") | 125 | c.Assert(got, Matches, ".*ok.*") |
449 | 379 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"b":1}]`) | 126 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"b":1}]`) |
451 | 380 | clientShutdown <- true | 127 | stop() |
452 | 381 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 128 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
453 | 382 | c.Check(len(errCh), Equals, 0) | 129 | c.Check(len(errCh), Equals, 0) |
454 | 383 | // another broadcast | 130 | // another broadcast |
455 | @@ -389,17 +136,16 @@ | |||
456 | 389 | c.Assert(err, IsNil) | 136 | c.Assert(err, IsNil) |
457 | 390 | c.Assert(got, Matches, ".*ok.*") | 137 | c.Assert(got, Matches, ".*ok.*") |
458 | 391 | // reconnect, provide levels, get only later notification | 138 | // reconnect, provide levels, get only later notification |
461 | 392 | <-clientShutdown // reset | 139 | events, errCh, stop = s.startClient(c, "DEVD", map[string]int64{ |
460 | 393 | events, errCh = s.startClient(c, "DEVD", intercept, map[string]int64{ | ||
462 | 394 | protocol.SystemChannelId: 1, | 140 | protocol.SystemChannelId: 1, |
463 | 395 | }) | 141 | }) |
464 | 396 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"b":2}]`) | 142 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"b":2}]`) |
466 | 397 | clientShutdown <- true | 143 | stop() |
467 | 398 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 144 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
468 | 399 | c.Check(len(errCh), Equals, 0) | 145 | c.Check(len(errCh), Equals, 0) |
469 | 400 | } | 146 | } |
470 | 401 | 147 | ||
472 | 402 | func (s *acceptanceSuite) TestBroadcastTooAhead(c *C) { | 148 | func (s *BroadcastAcceptanceSuite) TestBroadcastTooAhead(c *C) { |
473 | 403 | // send broadcasts that will be pending | 149 | // send broadcasts that will be pending |
474 | 404 | got, err := s.postRequest("/broadcast", &api.Broadcast{ | 150 | got, err := s.postRequest("/broadcast", &api.Broadcast{ |
475 | 405 | Channel: "system", | 151 | Channel: "system", |
476 | @@ -416,47 +162,29 @@ | |||
477 | 416 | c.Assert(err, IsNil) | 162 | c.Assert(err, IsNil) |
478 | 417 | c.Assert(got, Matches, ".*ok.*") | 163 | c.Assert(got, Matches, ".*ok.*") |
479 | 418 | 164 | ||
490 | 419 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | 165 | events, errCh, stop := s.startClient(c, "DEVB", map[string]int64{ |
481 | 420 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
482 | 421 | // read after ack | ||
483 | 422 | if op == "read" && len(clientShutdown) > 0 { | ||
484 | 423 | // exit the sess.Run() goroutine, client will close | ||
485 | 424 | runtime.Goexit() | ||
486 | 425 | } | ||
487 | 426 | return false, 0, nil | ||
488 | 427 | } | ||
489 | 428 | events, errCh := s.startClient(c, "DEVB", intercept, map[string]int64{ | ||
491 | 429 | protocol.SystemChannelId: 10, | 166 | protocol.SystemChannelId: 10, |
492 | 430 | }) | 167 | }) |
493 | 431 | // gettting last one pending on connect | 168 | // gettting last one pending on connect |
494 | 432 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"b":2}]`) | 169 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"b":2}]`) |
496 | 433 | clientShutdown <- true | 170 | stop() |
497 | 434 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 171 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
498 | 435 | c.Check(len(errCh), Equals, 0) | 172 | c.Check(len(errCh), Equals, 0) |
499 | 436 | } | 173 | } |
500 | 437 | 174 | ||
502 | 438 | func (s *acceptanceSuite) TestBroadcastTooAheadOnEmpty(c *C) { | 175 | func (s *BroadcastAcceptanceSuite) TestBroadcastTooAheadOnEmpty(c *C) { |
503 | 439 | // nothing there | 176 | // nothing there |
514 | 440 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | 177 | events, errCh, stop := s.startClient(c, "DEVB", map[string]int64{ |
505 | 441 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
506 | 442 | // read after ack | ||
507 | 443 | if op == "read" && len(clientShutdown) > 0 { | ||
508 | 444 | // exit the sess.Run() goroutine, client will close | ||
509 | 445 | runtime.Goexit() | ||
510 | 446 | } | ||
511 | 447 | return false, 0, nil | ||
512 | 448 | } | ||
513 | 449 | events, errCh := s.startClient(c, "DEVB", intercept, map[string]int64{ | ||
515 | 450 | protocol.SystemChannelId: 10, | 178 | protocol.SystemChannelId: 10, |
516 | 451 | }) | 179 | }) |
517 | 452 | // gettting empty pending on connect | 180 | // gettting empty pending on connect |
518 | 453 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:0 payloads:null`) | 181 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:0 payloads:null`) |
520 | 454 | clientShutdown <- true | 182 | stop() |
521 | 455 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 183 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
522 | 456 | c.Check(len(errCh), Equals, 0) | 184 | c.Check(len(errCh), Equals, 0) |
523 | 457 | } | 185 | } |
524 | 458 | 186 | ||
526 | 459 | func (s *acceptanceSuite) TestBroadcastWayBehind(c *C) { | 187 | func (s *BroadcastAcceptanceSuite) TestBroadcastWayBehind(c *C) { |
527 | 460 | // send broadcasts that will be pending | 188 | // send broadcasts that will be pending |
528 | 461 | got, err := s.postRequest("/broadcast", &api.Broadcast{ | 189 | got, err := s.postRequest("/broadcast", &api.Broadcast{ |
529 | 462 | Channel: "system", | 190 | Channel: "system", |
530 | @@ -473,21 +201,12 @@ | |||
531 | 473 | c.Assert(err, IsNil) | 201 | c.Assert(err, IsNil) |
532 | 474 | c.Assert(got, Matches, ".*ok.*") | 202 | c.Assert(got, Matches, ".*ok.*") |
533 | 475 | 203 | ||
544 | 476 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | 204 | events, errCh, stop := s.startClient(c, "DEVB", map[string]int64{ |
535 | 477 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
536 | 478 | // read after ack | ||
537 | 479 | if op == "read" && len(clientShutdown) > 0 { | ||
538 | 480 | // exit the sess.Run() goroutine, client will close | ||
539 | 481 | runtime.Goexit() | ||
540 | 482 | } | ||
541 | 483 | return false, 0, nil | ||
542 | 484 | } | ||
543 | 485 | events, errCh := s.startClient(c, "DEVB", intercept, map[string]int64{ | ||
545 | 486 | protocol.SystemChannelId: -10, | 205 | protocol.SystemChannelId: -10, |
546 | 487 | }) | 206 | }) |
547 | 488 | // gettting pending on connect | 207 | // gettting pending on connect |
548 | 489 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"b":1},{"b":2}]`) | 208 | c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"b":1},{"b":2}]`) |
550 | 490 | clientShutdown <- true | 209 | stop() |
551 | 491 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) | 210 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*EOF`) |
552 | 492 | c.Check(len(errCh), Equals, 0) | 211 | c.Check(len(errCh), Equals, 0) |
553 | 493 | } | 212 | } |
554 | 494 | 213 | ||
555 | === renamed file 'server/acceptance/acceptance_helpers.go' => 'server/acceptance/suites/helpers.go' | |||
556 | --- server/acceptance/acceptance_helpers.go 2014-02-11 20:40:06 +0000 | |||
557 | +++ server/acceptance/suites/helpers.go 2014-02-11 20:40:06 +0000 | |||
558 | @@ -14,7 +14,7 @@ | |||
559 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. |
560 | 15 | */ | 15 | */ |
561 | 16 | 16 | ||
563 | 17 | package acceptance | 17 | package suites |
564 | 18 | 18 | ||
565 | 19 | import ( | 19 | import ( |
566 | 20 | "bufio" | 20 | "bufio" |
567 | @@ -48,8 +48,8 @@ | |||
568 | 48 | "session_queue_size": 10, | 48 | "session_queue_size": 10, |
569 | 49 | "broker_queue_size": 100, | 49 | "broker_queue_size": 100, |
570 | 50 | "addr": addr, | 50 | "addr": addr, |
573 | 51 | "key_pem_file": helpers.SourceRelative("config/testing.key"), | 51 | "key_pem_file": helpers.SourceRelative("../config/testing.key"), |
574 | 52 | "cert_pem_file": helpers.SourceRelative("config/testing.cert"), | 52 | "cert_pem_file": helpers.SourceRelative("../config/testing.cert"), |
575 | 53 | }) | 53 | }) |
576 | 54 | } | 54 | } |
577 | 55 | 55 | ||
578 | 56 | 56 | ||
579 | === added file 'server/acceptance/suites/pingpong.go' | |||
580 | --- server/acceptance/suites/pingpong.go 1970-01-01 00:00:00 +0000 | |||
581 | +++ server/acceptance/suites/pingpong.go 2014-02-11 20:40:06 +0000 | |||
582 | @@ -0,0 +1,93 @@ | |||
583 | 1 | /* | ||
584 | 2 | Copyright 2013-2014 Canonical Ltd. | ||
585 | 3 | |||
586 | 4 | This program is free software: you can redistribute it and/or modify it | ||
587 | 5 | under the terms of the GNU General Public License version 3, as published | ||
588 | 6 | by the Free Software Foundation. | ||
589 | 7 | |||
590 | 8 | This program is distributed in the hope that it will be useful, but | ||
591 | 9 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
592 | 10 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
593 | 11 | PURPOSE. See the GNU General Public License for more details. | ||
594 | 12 | |||
595 | 13 | You should have received a copy of the GNU General Public License along | ||
596 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
597 | 15 | */ | ||
598 | 16 | |||
599 | 17 | package suites | ||
600 | 18 | |||
601 | 19 | import ( | ||
602 | 20 | "runtime" | ||
603 | 21 | "strings" | ||
604 | 22 | "time" | ||
605 | 23 | |||
606 | 24 | . "launchpad.net/gocheck" | ||
607 | 25 | ) | ||
608 | 26 | |||
609 | 27 | // PingPongAcceptanceSuite has tests about connectivity and ping-pong requests. | ||
610 | 28 | type PingPongAcceptanceSuite struct { | ||
611 | 29 | AcceptanceSuite | ||
612 | 30 | } | ||
613 | 31 | |||
614 | 32 | // Tests about connection, ping-pong, disconnection scenarios | ||
615 | 33 | |||
616 | 34 | func (s *PingPongAcceptanceSuite) TestConnectPingPing(c *C) { | ||
617 | 35 | errCh := make(chan error, 1) | ||
618 | 36 | events := make(chan string, 10) | ||
619 | 37 | sess := testClientSession(s.serverAddr, "DEVA", true) | ||
620 | 38 | err := sess.Dial() | ||
621 | 39 | c.Assert(err, IsNil) | ||
622 | 40 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
623 | 41 | // would be 3rd ping read, based on logged traffic | ||
624 | 42 | if op == "read" && ic.totalRead >= 79 { | ||
625 | 43 | // exit the sess.Run() goroutine, client will close | ||
626 | 44 | runtime.Goexit() | ||
627 | 45 | } | ||
628 | 46 | return false, 0, nil | ||
629 | 47 | } | ||
630 | 48 | sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} | ||
631 | 49 | go func() { | ||
632 | 50 | errCh <- sess.Run(events) | ||
633 | 51 | }() | ||
634 | 52 | connectCli := NextEvent(events, errCh) | ||
635 | 53 | connectSrv := NextEvent(s.serverEvents, nil) | ||
636 | 54 | registeredSrv := NextEvent(s.serverEvents, nil) | ||
637 | 55 | tconnect := time.Now() | ||
638 | 56 | c.Assert(connectSrv, Matches, ".*session.* connected .*") | ||
639 | 57 | c.Assert(registeredSrv, Matches, ".*session.* registered DEVA") | ||
640 | 58 | c.Assert(strings.HasSuffix(connectSrv, connectCli), Equals, true) | ||
641 | 59 | c.Assert(NextEvent(events, errCh), Equals, "Ping") | ||
642 | 60 | elapsedOfPing := float64(time.Since(tconnect)) / float64(500*time.Millisecond) | ||
643 | 61 | c.Check(elapsedOfPing >= 1.0, Equals, true) | ||
644 | 62 | c.Check(elapsedOfPing < 1.05, Equals, true) | ||
645 | 63 | c.Assert(NextEvent(events, errCh), Equals, "Ping") | ||
646 | 64 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* ended with: EOF") | ||
647 | 65 | c.Check(len(errCh), Equals, 0) | ||
648 | 66 | } | ||
649 | 67 | |||
650 | 68 | func (s *PingPongAcceptanceSuite) TestConnectPingNeverPong(c *C) { | ||
651 | 69 | errCh := make(chan error, 1) | ||
652 | 70 | events := make(chan string, 10) | ||
653 | 71 | sess := testClientSession(s.serverAddr, "DEVB", true) | ||
654 | 72 | err := sess.Dial() | ||
655 | 73 | c.Assert(err, IsNil) | ||
656 | 74 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
657 | 75 | // would be pong to 2nd ping, based on logged traffic | ||
658 | 76 | if op == "write" && ic.totalRead >= 67 { | ||
659 | 77 | time.Sleep(200 * time.Millisecond) | ||
660 | 78 | // exit the sess.Run() goroutine, client will close | ||
661 | 79 | runtime.Goexit() | ||
662 | 80 | } | ||
663 | 81 | return false, 0, nil | ||
664 | 82 | } | ||
665 | 83 | sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} | ||
666 | 84 | go func() { | ||
667 | 85 | errCh <- sess.Run(events) | ||
668 | 86 | }() | ||
669 | 87 | c.Assert(NextEvent(events, errCh), Matches, "connected .*") | ||
670 | 88 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* connected .*") | ||
671 | 89 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* registered .*") | ||
672 | 90 | c.Assert(NextEvent(events, errCh), Equals, "Ping") | ||
673 | 91 | c.Assert(NextEvent(s.serverEvents, nil), Matches, `.* ended with:.*timeout`) | ||
674 | 92 | c.Check(len(errCh), Equals, 0) | ||
675 | 93 | } | ||
676 | 0 | 94 | ||
677 | === added file 'server/acceptance/suites/suite.go' | |||
678 | --- server/acceptance/suites/suite.go 1970-01-01 00:00:00 +0000 | |||
679 | +++ server/acceptance/suites/suite.go 2014-02-11 20:40:06 +0000 | |||
680 | @@ -0,0 +1,170 @@ | |||
681 | 1 | /* | ||
682 | 2 | Copyright 2013-2014 Canonical Ltd. | ||
683 | 3 | |||
684 | 4 | This program is free software: you can redistribute it and/or modify it | ||
685 | 5 | under the terms of the GNU General Public License version 3, as published | ||
686 | 6 | by the Free Software Foundation. | ||
687 | 7 | |||
688 | 8 | This program is distributed in the hope that it will be useful, but | ||
689 | 9 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
690 | 10 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
691 | 11 | PURPOSE. See the GNU General Public License for more details. | ||
692 | 12 | |||
693 | 13 | You should have received a copy of the GNU General Public License along | ||
694 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
695 | 15 | */ | ||
696 | 16 | |||
697 | 17 | // Package suites contains reusable acceptance test suites. | ||
698 | 18 | package suites | ||
699 | 19 | |||
700 | 20 | import ( | ||
701 | 21 | "bytes" | ||
702 | 22 | "encoding/json" | ||
703 | 23 | "flag" | ||
704 | 24 | "fmt" | ||
705 | 25 | "io/ioutil" | ||
706 | 26 | "net" | ||
707 | 27 | "net/http" | ||
708 | 28 | "runtime" | ||
709 | 29 | "time" | ||
710 | 30 | |||
711 | 31 | . "launchpad.net/gocheck" | ||
712 | 32 | |||
713 | 33 | "launchpad.net/ubuntu-push/server/acceptance" | ||
714 | 34 | helpers "launchpad.net/ubuntu-push/testing" | ||
715 | 35 | ) | ||
716 | 36 | |||
717 | 37 | // AcceptanceSuite has the basic functionality of the acceptance suites. | ||
718 | 38 | type AcceptanceSuite struct { | ||
719 | 39 | // hook to start the server(s) | ||
720 | 40 | StartServer func(c *C) (logs <-chan string, kill func(), serverAddr, apiURL string) | ||
721 | 41 | // running bits | ||
722 | 42 | serverKill func() | ||
723 | 43 | serverAddr string | ||
724 | 44 | serverAPIURL string | ||
725 | 45 | serverEvents <-chan string | ||
726 | 46 | httpClient *http.Client | ||
727 | 47 | } | ||
728 | 48 | |||
729 | 49 | // Start a new server for each test. | ||
730 | 50 | func (s *AcceptanceSuite) SetUpTest(c *C) { | ||
731 | 51 | logs, kill, addr, url := s.StartServer(c) | ||
732 | 52 | s.serverEvents = logs | ||
733 | 53 | s.serverKill = kill | ||
734 | 54 | s.serverAddr = addr | ||
735 | 55 | s.serverAPIURL = url | ||
736 | 56 | s.httpClient = &http.Client{} | ||
737 | 57 | } | ||
738 | 58 | |||
739 | 59 | func (s *AcceptanceSuite) TearDownTest(c *C) { | ||
740 | 60 | if s.serverKill != nil { | ||
741 | 61 | s.serverKill() | ||
742 | 62 | } | ||
743 | 63 | } | ||
744 | 64 | |||
745 | 65 | // Post a request. | ||
746 | 66 | func (s *AcceptanceSuite) postRequest(path string, message interface{}) (string, error) { | ||
747 | 67 | packedMessage, err := json.Marshal(message) | ||
748 | 68 | if err != nil { | ||
749 | 69 | panic(err) | ||
750 | 70 | } | ||
751 | 71 | reader := bytes.NewReader(packedMessage) | ||
752 | 72 | |||
753 | 73 | url := s.serverAPIURL + path | ||
754 | 74 | request, _ := http.NewRequest("POST", url, reader) | ||
755 | 75 | request.ContentLength = int64(reader.Len()) | ||
756 | 76 | request.Header.Set("Content-Type", "application/json") | ||
757 | 77 | |||
758 | 78 | resp, err := s.httpClient.Do(request) | ||
759 | 79 | if err != nil { | ||
760 | 80 | panic(err) | ||
761 | 81 | } | ||
762 | 82 | defer resp.Body.Close() | ||
763 | 83 | body, err := ioutil.ReadAll(resp.Body) | ||
764 | 84 | return string(body), err | ||
765 | 85 | } | ||
766 | 86 | |||
767 | 87 | func testClientSession(addr string, deviceId string, reportPings bool) *acceptance.ClientSession { | ||
768 | 88 | certPEMBlock, err := ioutil.ReadFile(helpers.SourceRelative("../config/testing.cert")) | ||
769 | 89 | if err != nil { | ||
770 | 90 | panic(fmt.Sprintf("could not read config/testing.cert: %v", err)) | ||
771 | 91 | } | ||
772 | 92 | return &acceptance.ClientSession{ | ||
773 | 93 | ExchangeTimeout: 100 * time.Millisecond, | ||
774 | 94 | ServerAddr: addr, | ||
775 | 95 | CertPEMBlock: certPEMBlock, | ||
776 | 96 | DeviceId: deviceId, | ||
777 | 97 | ReportPings: reportPings, | ||
778 | 98 | } | ||
779 | 99 | } | ||
780 | 100 | |||
781 | 101 | // typically combined with -gocheck.vv or test selection | ||
782 | 102 | var logTraffic = flag.Bool("logTraffic", false, "log traffic") | ||
783 | 103 | |||
784 | 104 | type connInterceptor func(ic *interceptingConn, op string, b []byte) (bool, int, error) | ||
785 | 105 | |||
786 | 106 | type interceptingConn struct { | ||
787 | 107 | net.Conn | ||
788 | 108 | totalRead int | ||
789 | 109 | totalWritten int | ||
790 | 110 | intercept connInterceptor | ||
791 | 111 | } | ||
792 | 112 | |||
793 | 113 | func (ic *interceptingConn) Write(b []byte) (n int, err error) { | ||
794 | 114 | done := false | ||
795 | 115 | before := ic.totalWritten | ||
796 | 116 | if ic.intercept != nil { | ||
797 | 117 | done, n, err = ic.intercept(ic, "write", b) | ||
798 | 118 | } | ||
799 | 119 | if !done { | ||
800 | 120 | n, err = ic.Conn.Write(b) | ||
801 | 121 | } | ||
802 | 122 | ic.totalWritten += n | ||
803 | 123 | if *logTraffic { | ||
804 | 124 | fmt.Printf("W[%v]: %d %#v %v %d\n", ic.Conn.LocalAddr(), before, string(b[:n]), err, ic.totalWritten) | ||
805 | 125 | } | ||
806 | 126 | return | ||
807 | 127 | } | ||
808 | 128 | |||
809 | 129 | func (ic *interceptingConn) Read(b []byte) (n int, err error) { | ||
810 | 130 | done := false | ||
811 | 131 | before := ic.totalRead | ||
812 | 132 | if ic.intercept != nil { | ||
813 | 133 | done, n, err = ic.intercept(ic, "read", b) | ||
814 | 134 | } | ||
815 | 135 | if !done { | ||
816 | 136 | n, err = ic.Conn.Read(b) | ||
817 | 137 | } | ||
818 | 138 | ic.totalRead += n | ||
819 | 139 | if *logTraffic { | ||
820 | 140 | fmt.Printf("R[%v]: %d %#v %v %d\n", ic.Conn.LocalAddr(), before, string(b[:n]), err, ic.totalRead) | ||
821 | 141 | } | ||
822 | 142 | return | ||
823 | 143 | } | ||
824 | 144 | |||
825 | 145 | // Start a client. | ||
826 | 146 | func (s *AcceptanceSuite) startClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) { | ||
827 | 147 | errCh := make(chan error, 1) | ||
828 | 148 | cliEvents := make(chan string, 10) | ||
829 | 149 | sess := testClientSession(s.serverAddr, devId, false) | ||
830 | 150 | sess.Levels = levels | ||
831 | 151 | err := sess.Dial() | ||
832 | 152 | c.Assert(err, IsNil) | ||
833 | 153 | clientShutdown := make(chan bool, 1) // abused as an atomic flag | ||
834 | 154 | intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { | ||
835 | 155 | // read after ack | ||
836 | 156 | if op == "read" && len(clientShutdown) > 0 { | ||
837 | 157 | // exit the sess.Run() goroutine, client will close | ||
838 | 158 | runtime.Goexit() | ||
839 | 159 | } | ||
840 | 160 | return false, 0, nil | ||
841 | 161 | } | ||
842 | 162 | sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} | ||
843 | 163 | go func() { | ||
844 | 164 | errCh <- sess.Run(cliEvents) | ||
845 | 165 | }() | ||
846 | 166 | c.Assert(NextEvent(cliEvents, errCh), Matches, "connected .*") | ||
847 | 167 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* connected .*") | ||
848 | 168 | c.Assert(NextEvent(s.serverEvents, nil), Matches, ".*session.* registered "+devId) | ||
849 | 169 | return cliEvents, errCh, func() { clientShutdown <- true } | ||
850 | 170 | } |
Thank you!