##// END OF EJS Templates
tests: correct version check in clientreactor test...
Augie Fackler -
r41154:685cf59a default
parent child Browse files
Show More
@@ -1,610 +1,610 b''
1 1 from __future__ import absolute_import
2 2
3 3 import sys
4 4 import unittest
5 5 import zlib
6 6
7 7 from mercurial import (
8 8 error,
9 9 ui as uimod,
10 10 wireprotoframing as framing,
11 11 )
12 12 from mercurial.utils import (
13 13 cborutil,
14 14 )
15 15
16 16 try:
17 17 from mercurial import zstd
18 18 zstd.__version__
19 19 except ImportError:
20 20 zstd = None
21 21
22 22 ffs = framing.makeframefromhumanstring
23 23
24 24 globalui = uimod.ui()
25 25
26 26 def sendframe(reactor, frame):
27 27 """Send a frame bytearray to a reactor."""
28 28 header = framing.parseheader(frame)
29 29 payload = frame[framing.FRAME_HEADER_SIZE:]
30 30 assert len(payload) == header.length
31 31
32 32 return reactor.onframerecv(framing.frame(header.requestid,
33 33 header.streamid,
34 34 header.streamflags,
35 35 header.typeid,
36 36 header.flags,
37 37 payload))
38 38
39 39 class SingleSendTests(unittest.TestCase):
40 40 """A reactor that can only send once rejects subsequent sends."""
41 41
42 42 if not getattr(unittest.TestCase, 'assertRaisesRegex', False):
43 43 # Python 3.7 deprecates the regex*p* version, but 2.7 lacks
44 44 # the regex version.
45 45 assertRaisesRegex = (# camelcase-required
46 46 unittest.TestCase.assertRaisesRegexp)
47 47
48 48 def testbasic(self):
49 49 reactor = framing.clientreactor(globalui,
50 50 hasmultiplesend=False,
51 51 buffersends=True)
52 52
53 53 request, action, meta = reactor.callcommand(b'foo', {})
54 54 self.assertEqual(request.state, b'pending')
55 55 self.assertEqual(action, b'noop')
56 56
57 57 action, meta = reactor.flushcommands()
58 58 self.assertEqual(action, b'sendframes')
59 59
60 60 for frame in meta[b'framegen']:
61 61 self.assertEqual(request.state, b'sending')
62 62
63 63 self.assertEqual(request.state, b'sent')
64 64
65 65 with self.assertRaisesRegex(error.ProgrammingError,
66 66 'cannot issue new commands'):
67 67 reactor.callcommand(b'foo', {})
68 68
69 69 with self.assertRaisesRegex(error.ProgrammingError,
70 70 'cannot issue new commands'):
71 71 reactor.callcommand(b'foo', {})
72 72
73 73 class NoBufferTests(unittest.TestCase):
74 74 """A reactor without send buffering sends requests immediately."""
75 75 def testbasic(self):
76 76 reactor = framing.clientreactor(globalui,
77 77 hasmultiplesend=True,
78 78 buffersends=False)
79 79
80 80 request, action, meta = reactor.callcommand(b'command1', {})
81 81 self.assertEqual(request.requestid, 1)
82 82 self.assertEqual(action, b'sendframes')
83 83
84 84 self.assertEqual(request.state, b'pending')
85 85
86 86 for frame in meta[b'framegen']:
87 87 self.assertEqual(request.state, b'sending')
88 88
89 89 self.assertEqual(request.state, b'sent')
90 90
91 91 action, meta = reactor.flushcommands()
92 92 self.assertEqual(action, b'noop')
93 93
94 94 # And we can send another command.
95 95 request, action, meta = reactor.callcommand(b'command2', {})
96 96 self.assertEqual(request.requestid, 3)
97 97 self.assertEqual(action, b'sendframes')
98 98
99 99 for frame in meta[b'framegen']:
100 100 self.assertEqual(request.state, b'sending')
101 101
102 102 self.assertEqual(request.state, b'sent')
103 103
104 104 class BadFrameRecvTests(unittest.TestCase):
105 105 if not getattr(unittest.TestCase, 'assertRaisesRegex', False):
106 106 # Python 3.7 deprecates the regex*p* version, but 2.7 lacks
107 107 # the regex version.
108 108 assertRaisesRegex = (# camelcase-required
109 109 unittest.TestCase.assertRaisesRegexp)
110 110
111 111 def testoddstream(self):
112 112 reactor = framing.clientreactor(globalui)
113 113
114 114 action, meta = sendframe(reactor, ffs(b'1 1 0 1 0 foo'))
115 115 self.assertEqual(action, b'error')
116 116 self.assertEqual(meta[b'message'],
117 117 b'received frame with odd numbered stream ID: 1')
118 118
119 119 def testunknownstream(self):
120 120 reactor = framing.clientreactor(globalui)
121 121
122 122 action, meta = sendframe(reactor, ffs(b'1 0 0 1 0 foo'))
123 123 self.assertEqual(action, b'error')
124 124 self.assertEqual(meta[b'message'],
125 125 b'received frame on unknown stream without beginning '
126 126 b'of stream flag set')
127 127
128 128 def testunhandledframetype(self):
129 129 reactor = framing.clientreactor(globalui, buffersends=False)
130 130
131 131 request, action, meta = reactor.callcommand(b'foo', {})
132 132 for frame in meta[b'framegen']:
133 133 pass
134 134
135 135 with self.assertRaisesRegex(error.ProgrammingError,
136 136 'unhandled frame type'):
137 137 sendframe(reactor, ffs(b'1 0 stream-begin text-output 0 foo'))
138 138
139 139 class StreamTests(unittest.TestCase):
140 140 def testmultipleresponseframes(self):
141 141 reactor = framing.clientreactor(globalui, buffersends=False)
142 142
143 143 request, action, meta = reactor.callcommand(b'foo', {})
144 144
145 145 self.assertEqual(action, b'sendframes')
146 146 for f in meta[b'framegen']:
147 147 pass
148 148
149 149 action, meta = sendframe(
150 150 reactor,
151 151 ffs(b'%d 0 stream-begin command-response 0 foo' %
152 152 request.requestid))
153 153 self.assertEqual(action, b'responsedata')
154 154
155 155 action, meta = sendframe(
156 156 reactor,
157 157 ffs(b'%d 0 0 command-response eos bar' % request.requestid))
158 158 self.assertEqual(action, b'responsedata')
159 159
160 160 class RedirectTests(unittest.TestCase):
161 161 def testredirect(self):
162 162 reactor = framing.clientreactor(globalui, buffersends=False)
163 163
164 164 redirect = {
165 165 b'targets': [b'a', b'b'],
166 166 b'hashes': [b'sha256'],
167 167 }
168 168
169 169 request, action, meta = reactor.callcommand(
170 170 b'foo', {}, redirect=redirect)
171 171
172 172 self.assertEqual(action, b'sendframes')
173 173
174 174 frames = list(meta[b'framegen'])
175 175 self.assertEqual(len(frames), 1)
176 176
177 177 self.assertEqual(frames[0],
178 178 ffs(b'1 1 stream-begin command-request new '
179 179 b"cbor:{b'name': b'foo', "
180 180 b"b'redirect': {b'targets': [b'a', b'b'], "
181 181 b"b'hashes': [b'sha256']}}"))
182 182
183 183 class StreamSettingsTests(unittest.TestCase):
184 184 def testnoflags(self):
185 185 reactor = framing.clientreactor(globalui, buffersends=False)
186 186
187 187 request, action, meta = reactor.callcommand(b'foo', {})
188 188 for f in meta[b'framegen']:
189 189 pass
190 190
191 191 action, meta = sendframe(reactor,
192 192 ffs(b'1 2 stream-begin stream-settings 0 '))
193 193
194 194 self.assertEqual(action, b'error')
195 195 self.assertEqual(meta, {
196 196 b'message': b'stream encoding settings frame must have '
197 197 b'continuation or end of stream flag set',
198 198 })
199 199
200 200 def testconflictflags(self):
201 201 reactor = framing.clientreactor(globalui, buffersends=False)
202 202
203 203 request, action, meta = reactor.callcommand(b'foo', {})
204 204 for f in meta[b'framegen']:
205 205 pass
206 206
207 207 action, meta = sendframe(reactor,
208 208 ffs(b'1 2 stream-begin stream-settings continuation|eos '))
209 209
210 210 self.assertEqual(action, b'error')
211 211 self.assertEqual(meta, {
212 212 b'message': b'stream encoding settings frame cannot have both '
213 213 b'continuation and end of stream flags set',
214 214 })
215 215
216 216 def testemptypayload(self):
217 217 reactor = framing.clientreactor(globalui, buffersends=False)
218 218
219 219 request, action, meta = reactor.callcommand(b'foo', {})
220 220 for f in meta[b'framegen']:
221 221 pass
222 222
223 223 action, meta = sendframe(reactor,
224 224 ffs(b'1 2 stream-begin stream-settings eos '))
225 225
226 226 self.assertEqual(action, b'error')
227 227 self.assertEqual(meta, {
228 228 b'message': b'stream encoding settings frame did not contain '
229 229 b'CBOR data'
230 230 })
231 231
232 232 def testbadcbor(self):
233 233 reactor = framing.clientreactor(globalui, buffersends=False)
234 234
235 235 request, action, meta = reactor.callcommand(b'foo', {})
236 236 for f in meta[b'framegen']:
237 237 pass
238 238
239 239 action, meta = sendframe(reactor,
240 240 ffs(b'1 2 stream-begin stream-settings eos badvalue'))
241 241
242 242 self.assertEqual(action, b'error')
243 243
244 244 def testsingleobject(self):
245 245 reactor = framing.clientreactor(globalui, buffersends=False)
246 246
247 247 request, action, meta = reactor.callcommand(b'foo', {})
248 248 for f in meta[b'framegen']:
249 249 pass
250 250
251 251 action, meta = sendframe(reactor,
252 252 ffs(b'1 2 stream-begin stream-settings eos cbor:b"identity"'))
253 253
254 254 self.assertEqual(action, b'noop')
255 255 self.assertEqual(meta, {})
256 256
257 257 def testmultipleobjects(self):
258 258 reactor = framing.clientreactor(globalui, buffersends=False)
259 259
260 260 request, action, meta = reactor.callcommand(b'foo', {})
261 261 for f in meta[b'framegen']:
262 262 pass
263 263
264 264 data = b''.join([
265 265 b''.join(cborutil.streamencode(b'identity')),
266 266 b''.join(cborutil.streamencode({b'foo', b'bar'})),
267 267 ])
268 268
269 269 action, meta = sendframe(reactor,
270 270 ffs(b'1 2 stream-begin stream-settings eos %s' % data))
271 271
272 272 self.assertEqual(action, b'error')
273 273 self.assertEqual(meta, {
274 274 b'message': b'error setting stream decoder: identity decoder '
275 275 b'received unexpected additional values',
276 276 })
277 277
278 278 def testmultipleframes(self):
279 279 reactor = framing.clientreactor(globalui, buffersends=False)
280 280
281 281 request, action, meta = reactor.callcommand(b'foo', {})
282 282 for f in meta[b'framegen']:
283 283 pass
284 284
285 285 data = b''.join(cborutil.streamencode(b'identity'))
286 286
287 287 action, meta = sendframe(reactor,
288 288 ffs(b'1 2 stream-begin stream-settings continuation %s' %
289 289 data[0:3]))
290 290
291 291 self.assertEqual(action, b'noop')
292 292 self.assertEqual(meta, {})
293 293
294 294 action, meta = sendframe(reactor,
295 295 ffs(b'1 2 0 stream-settings eos %s' % data[3:]))
296 296
297 297 self.assertEqual(action, b'noop')
298 298 self.assertEqual(meta, {})
299 299
300 300 def testinvalidencoder(self):
301 301 reactor = framing.clientreactor(globalui, buffersends=False)
302 302
303 303 request, action, meta = reactor.callcommand(b'foo', {})
304 304 for f in meta[b'framegen']:
305 305 pass
306 306
307 307 action, meta = sendframe(reactor,
308 308 ffs(b'1 2 stream-begin stream-settings eos cbor:b"badvalue"'))
309 309
310 310 self.assertEqual(action, b'error')
311 311 self.assertEqual(meta, {
312 312 b'message': b'error setting stream decoder: unknown stream '
313 313 b'decoder: badvalue',
314 314 })
315 315
316 316 def testzlibencoding(self):
317 317 reactor = framing.clientreactor(globalui, buffersends=False)
318 318
319 319 request, action, meta = reactor.callcommand(b'foo', {})
320 320 for f in meta[b'framegen']:
321 321 pass
322 322
323 323 action, meta = sendframe(reactor,
324 324 ffs(b'%d 2 stream-begin stream-settings eos cbor:b"zlib"' %
325 325 request.requestid))
326 326
327 327 self.assertEqual(action, b'noop')
328 328 self.assertEqual(meta, {})
329 329
330 330 result = {
331 331 b'status': b'ok',
332 332 }
333 333 encoded = b''.join(cborutil.streamencode(result))
334 334
335 335 compressed = zlib.compress(encoded)
336 336 self.assertEqual(zlib.decompress(compressed), encoded)
337 337
338 338 action, meta = sendframe(reactor,
339 339 ffs(b'%d 2 encoded command-response eos %s' %
340 340 (request.requestid, compressed)))
341 341
342 342 self.assertEqual(action, b'responsedata')
343 343 self.assertEqual(meta[b'data'], encoded)
344 344
345 345 def testzlibencodingsinglebyteframes(self):
346 346 reactor = framing.clientreactor(globalui, buffersends=False)
347 347
348 348 request, action, meta = reactor.callcommand(b'foo', {})
349 349 for f in meta[b'framegen']:
350 350 pass
351 351
352 352 action, meta = sendframe(reactor,
353 353 ffs(b'%d 2 stream-begin stream-settings eos cbor:b"zlib"' %
354 354 request.requestid))
355 355
356 356 self.assertEqual(action, b'noop')
357 357 self.assertEqual(meta, {})
358 358
359 359 result = {
360 360 b'status': b'ok',
361 361 }
362 362 encoded = b''.join(cborutil.streamencode(result))
363 363
364 364 compressed = zlib.compress(encoded)
365 365 self.assertEqual(zlib.decompress(compressed), encoded)
366 366
367 367 chunks = []
368 368
369 369 for i in range(len(compressed)):
370 370 char = compressed[i:i + 1]
371 371 if char == b'\\':
372 372 char = b'\\\\'
373 373 action, meta = sendframe(reactor,
374 374 ffs(b'%d 2 encoded command-response continuation %s' %
375 375 (request.requestid, char)))
376 376
377 377 self.assertEqual(action, b'responsedata')
378 378 chunks.append(meta[b'data'])
379 379 self.assertTrue(meta[b'expectmore'])
380 380 self.assertFalse(meta[b'eos'])
381 381
382 382 # zlib will have the full data decoded at this point, even though
383 383 # we haven't flushed.
384 384 self.assertEqual(b''.join(chunks), encoded)
385 385
386 386 # End the stream for good measure.
387 387 action, meta = sendframe(reactor,
388 388 ffs(b'%d 2 stream-end command-response eos ' % request.requestid))
389 389
390 390 self.assertEqual(action, b'responsedata')
391 391 self.assertEqual(meta[b'data'], b'')
392 392 self.assertFalse(meta[b'expectmore'])
393 393 self.assertTrue(meta[b'eos'])
394 394
395 395 def testzlibmultipleresponses(self):
396 396 # We feed in zlib compressed data on the same stream but belonging to
397 397 # 2 different requests. This tests our flushing behavior.
398 398 reactor = framing.clientreactor(globalui, buffersends=False,
399 399 hasmultiplesend=True)
400 400
401 401 request1, action, meta = reactor.callcommand(b'foo', {})
402 402 for f in meta[b'framegen']:
403 403 pass
404 404
405 405 request2, action, meta = reactor.callcommand(b'foo', {})
406 406 for f in meta[b'framegen']:
407 407 pass
408 408
409 409 outstream = framing.outputstream(2)
410 410 outstream.setencoder(globalui, b'zlib')
411 411
412 412 response1 = b''.join(cborutil.streamencode({
413 413 b'status': b'ok',
414 414 b'extra': b'response1' * 10,
415 415 }))
416 416
417 417 response2 = b''.join(cborutil.streamencode({
418 418 b'status': b'error',
419 419 b'extra': b'response2' * 10,
420 420 }))
421 421
422 422 action, meta = sendframe(reactor,
423 423 ffs(b'%d 2 stream-begin stream-settings eos cbor:b"zlib"' %
424 424 request1.requestid))
425 425
426 426 self.assertEqual(action, b'noop')
427 427 self.assertEqual(meta, {})
428 428
429 429 # Feeding partial data in won't get anything useful out.
430 430 action, meta = sendframe(reactor,
431 431 ffs(b'%d 2 encoded command-response continuation %s' % (
432 432 request1.requestid, outstream.encode(response1))))
433 433 self.assertEqual(action, b'responsedata')
434 434 self.assertEqual(meta[b'data'], b'')
435 435
436 436 # But flushing data at both ends will get our original data.
437 437 action, meta = sendframe(reactor,
438 438 ffs(b'%d 2 encoded command-response eos %s' % (
439 439 request1.requestid, outstream.flush())))
440 440 self.assertEqual(action, b'responsedata')
441 441 self.assertEqual(meta[b'data'], response1)
442 442
443 443 # We should be able to reuse the compressor/decompressor for the
444 444 # 2nd response.
445 445 action, meta = sendframe(reactor,
446 446 ffs(b'%d 2 encoded command-response continuation %s' % (
447 447 request2.requestid, outstream.encode(response2))))
448 448 self.assertEqual(action, b'responsedata')
449 449 self.assertEqual(meta[b'data'], b'')
450 450
451 451 action, meta = sendframe(reactor,
452 452 ffs(b'%d 2 encoded command-response eos %s' % (
453 453 request2.requestid, outstream.flush())))
454 454 self.assertEqual(action, b'responsedata')
455 455 self.assertEqual(meta[b'data'], response2)
456 456
457 457 @unittest.skipUnless(zstd, 'zstd not available')
458 458 def testzstd8mbencoding(self):
459 459 reactor = framing.clientreactor(globalui, buffersends=False)
460 460
461 461 request, action, meta = reactor.callcommand(b'foo', {})
462 462 for f in meta[b'framegen']:
463 463 pass
464 464
465 465 action, meta = sendframe(reactor,
466 466 ffs(b'%d 2 stream-begin stream-settings eos cbor:b"zstd-8mb"' %
467 467 request.requestid))
468 468
469 469 self.assertEqual(action, b'noop')
470 470 self.assertEqual(meta, {})
471 471
472 472 result = {
473 473 b'status': b'ok',
474 474 }
475 475 encoded = b''.join(cborutil.streamencode(result))
476 476
477 477 encoder = framing.zstd8mbencoder(globalui)
478 478 compressed = encoder.encode(encoded) + encoder.finish()
479 479 self.assertEqual(zstd.ZstdDecompressor().decompress(
480 480 compressed, max_output_size=len(encoded)), encoded)
481 481
482 482 action, meta = sendframe(reactor,
483 483 ffs(b'%d 2 encoded command-response eos %s' %
484 484 (request.requestid, compressed)))
485 485
486 486 self.assertEqual(action, b'responsedata')
487 487 self.assertEqual(meta[b'data'], encoded)
488 488
489 489 @unittest.skipUnless(zstd, 'zstd not available')
490 490 def testzstd8mbencodingsinglebyteframes(self):
491 491 reactor = framing.clientreactor(globalui, buffersends=False)
492 492
493 493 request, action, meta = reactor.callcommand(b'foo', {})
494 494 for f in meta[b'framegen']:
495 495 pass
496 496
497 497 action, meta = sendframe(reactor,
498 498 ffs(b'%d 2 stream-begin stream-settings eos cbor:b"zstd-8mb"' %
499 499 request.requestid))
500 500
501 501 self.assertEqual(action, b'noop')
502 502 self.assertEqual(meta, {})
503 503
504 504 result = {
505 505 b'status': b'ok',
506 506 }
507 507 encoded = b''.join(cborutil.streamencode(result))
508 508
509 509 compressed = zstd.ZstdCompressor().compress(encoded)
510 510 self.assertEqual(zstd.ZstdDecompressor().decompress(compressed),
511 511 encoded)
512 512
513 513 chunks = []
514 514
515 515 for i in range(len(compressed)):
516 516 char = compressed[i:i + 1]
517 517 if char == b'\\':
518 518 char = b'\\\\'
519 519 action, meta = sendframe(reactor,
520 520 ffs(b'%d 2 encoded command-response continuation %s' %
521 521 (request.requestid, char)))
522 522
523 523 self.assertEqual(action, b'responsedata')
524 524 chunks.append(meta[b'data'])
525 525 self.assertTrue(meta[b'expectmore'])
526 526 self.assertFalse(meta[b'eos'])
527 527
528 528 # zstd decompressor will flush at frame boundaries.
529 529 self.assertEqual(b''.join(chunks), encoded)
530 530
531 531 # End the stream for good measure.
532 532 action, meta = sendframe(reactor,
533 533 ffs(b'%d 2 stream-end command-response eos ' % request.requestid))
534 534
535 535 self.assertEqual(action, b'responsedata')
536 536 self.assertEqual(meta[b'data'], b'')
537 537 self.assertFalse(meta[b'expectmore'])
538 538 self.assertTrue(meta[b'eos'])
539 539
540 540 @unittest.skipUnless(zstd, 'zstd not available')
541 541 def testzstd8mbmultipleresponses(self):
542 542 # We feed in zstd compressed data on the same stream but belonging to
543 543 # 2 different requests. This tests our flushing behavior.
544 544 reactor = framing.clientreactor(globalui, buffersends=False,
545 545 hasmultiplesend=True)
546 546
547 547 request1, action, meta = reactor.callcommand(b'foo', {})
548 548 for f in meta[b'framegen']:
549 549 pass
550 550
551 551 request2, action, meta = reactor.callcommand(b'foo', {})
552 552 for f in meta[b'framegen']:
553 553 pass
554 554
555 555 outstream = framing.outputstream(2)
556 556 outstream.setencoder(globalui, b'zstd-8mb')
557 557
558 558 response1 = b''.join(cborutil.streamencode({
559 559 b'status': b'ok',
560 560 b'extra': b'response1' * 10,
561 561 }))
562 562
563 563 response2 = b''.join(cborutil.streamencode({
564 564 b'status': b'error',
565 565 b'extra': b'response2' * 10,
566 566 }))
567 567
568 568 action, meta = sendframe(reactor,
569 569 ffs(b'%d 2 stream-begin stream-settings eos cbor:b"zstd-8mb"' %
570 570 request1.requestid))
571 571
572 572 self.assertEqual(action, b'noop')
573 573 self.assertEqual(meta, {})
574 574
575 575 # Feeding partial data in won't get anything useful out.
576 576 action, meta = sendframe(reactor,
577 577 ffs(b'%d 2 encoded command-response continuation %s' % (
578 578 request1.requestid, outstream.encode(response1))))
579 579 self.assertEqual(action, b'responsedata')
580 580 self.assertEqual(meta[b'data'], b'')
581 581
582 582 # But flushing data at both ends will get our original data.
583 583 action, meta = sendframe(reactor,
584 584 ffs(b'%d 2 encoded command-response eos %s' % (
585 585 request1.requestid, outstream.flush())))
586 586 self.assertEqual(action, b'responsedata')
587 587 self.assertEqual(meta[b'data'], response1)
588 588
589 589 # We should be able to reuse the compressor/decompressor for the
590 590 # 2nd response.
591 591 action, meta = sendframe(reactor,
592 592 ffs(b'%d 2 encoded command-response continuation %s' % (
593 593 request2.requestid, outstream.encode(response2))))
594 594 self.assertEqual(action, b'responsedata')
595 595 self.assertEqual(meta[b'data'], b'')
596 596
597 597 action, meta = sendframe(reactor,
598 598 ffs(b'%d 2 encoded command-response eos %s' % (
599 599 request2.requestid, outstream.flush())))
600 600 self.assertEqual(action, b'responsedata')
601 601 self.assertEqual(meta[b'data'], response2)
602 602
603 603 if __name__ == '__main__':
604 if (3, 6, 0) <= sys.version_info <= (3, 6, 3):
604 if (3, 6, 0) <= sys.version_info < (3, 6, 4):
605 605 # Python 3.6.0 through 3.6.3 inclusive shipped with
606 606 # https://bugs.python.org/issue31825 and we can't run these
607 607 # tests on those specific versions of Python. Sigh.
608 608 sys.exit(80)
609 609 import silenttestrunner
610 610 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now