Show More
@@ -2765,12 +2765,14 b' def debugwireproto(ui, repo, path=None, ' | |||
|
2765 | 2765 | syntax. |
|
2766 | 2766 | |
|
2767 | 2767 | A frame is composed as a type, flags, and payload. These can be parsed |
|
2768 |
from a string of the form ``<type> <flags> <payload>``. That is, |
|
|
2769 | space-delimited strings. | |
|
2768 | from a string of the form ``<requestid> <type> <flags> <payload>``. That is, | |
|
2769 | 4 space-delimited strings. | |
|
2770 | 2770 | |
|
2771 | 2771 | ``payload`` is the simplest: it is evaluated as a Python byte string |
|
2772 | 2772 | literal. |
|
2773 | 2773 | |
|
2774 | ``requestid`` is an integer defining the request identifier. | |
|
2775 | ||
|
2774 | 2776 | ``type`` can be an integer value for the frame type or the string name |
|
2775 | 2777 | of the type. The strings are defined in ``wireprotoframing.py``. e.g. |
|
2776 | 2778 | ``command-name``. |
@@ -469,22 +469,26 b' for sending data and another for receivi' | |||
|
469 | 469 | |
|
470 | 470 | The protocol is request-response based: the client issues requests to |
|
471 | 471 | the server, which issues replies to those requests. Server-initiated |
|
472 | messaging is not supported. | |
|
472 | messaging is not currently supported, but this specification carves | |
|
473 | out room to implement it. | |
|
473 | 474 | |
|
474 | 475 | All data is read and written in atomic units called *frames*. These |
|
475 | 476 | are conceptually similar to TCP packets. Higher-level functionality |
|
476 | 477 | is built on the exchange and processing of frames. |
|
477 | 478 | |
|
478 | Frames begin with a 4 octet header followed by a variable length | |
|
479 | All frames are associated with a numbered request. Frames can thus | |
|
480 | be logically grouped by their request ID. | |
|
481 | ||
|
482 | Frames begin with a 6 octet header followed by a variable length | |
|
479 | 483 | payload:: |
|
480 | 484 | |
|
481 | 485 | +-----------------------------------------------+ |
|
482 | 486 | | Length (24) | |
|
483 |
+----------- |
|
|
484 | | Type (4) | | |
|
485 | +-----------+ | |
|
486 | | Flags (4) | | |
|
487 |
+========== |
|
|
487 | +---------------------------------+-------------+ | |
|
488 | | Request ID (16) | | |
|
489 | +----------+-----------+----------+ | |
|
490 | | Type (4) | Flags (4) | | |
|
491 | +==========+===========+========================================| | |
|
488 | 492 | | Frame Payload (0...) ... |
|
489 | 493 | +---------------------------------------------------------------+ |
|
490 | 494 | |
@@ -494,6 +498,15 b' given permission by the server as part o' | |||
|
494 | 498 | during the handshake. The frame header is not part of the advertised |
|
495 | 499 | frame length. |
|
496 | 500 | |
|
501 | The 16-bit ``Request ID`` field denotes the integer request identifier, | |
|
502 | stored as an unsigned little endian integer. Odd numbered requests are | |
|
503 | client-initiated. Even numbered requests are server-initiated. This | |
|
504 | refers to where the *request* was initiated - not where the *frame* was | |
|
505 | initiated, so servers will send frames with odd ``Request ID`` in | |
|
506 | response to client-initiated requests. Implementations are advised to | |
|
507 | start ordering request identifiers at ``1`` and ``0``, increment by | |
|
508 | ``2``, and wrap around if all available numbers have been exhausted. | |
|
509 | ||
|
497 | 510 | The 4-bit ``Type`` field denotes the type of message being sent. |
|
498 | 511 | |
|
499 | 512 | The 4-bit ``Flags`` field defines special, per-type attributes for |
@@ -633,6 +646,28 b' frames defining that command. This logic' | |||
|
633 | 646 | 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames, |
|
634 | 647 | and 0 or more ``Command Data`` frames. |
|
635 | 648 | |
|
649 | All frames composing a single command request MUST be associated with | |
|
650 | the same ``Request ID``. | |
|
651 | ||
|
652 | Clients MAY send additional command requests without waiting on the | |
|
653 | response to a previous command request. If they do so, they MUST ensure | |
|
654 | that the ``Request ID`` field of outbound frames does not conflict | |
|
655 | with that of an active ``Request ID`` whose response has not yet been | |
|
656 | fully received. | |
|
657 | ||
|
658 | Servers MAY respond to commands in a different order than they were | |
|
659 | sent over the wire. Clients MUST be prepared to deal with this. Servers | |
|
660 | also MAY start executing commands in a different order than they were | |
|
661 | received, or MAY execute multiple commands concurrently. | |
|
662 | ||
|
663 | If there is a dependency between commands or a race condition between | |
|
664 | commands executing (e.g. a read-only command that depends on the results | |
|
665 | of a command that mutates the repository), then clients MUST NOT send | |
|
666 | frames issuing a command until a response to all dependent commands has | |
|
667 | been received. | |
|
668 | TODO think about whether we should express dependencies between commands | |
|
669 | to avoid roundtrip latency. | |
|
670 | ||
|
636 | 671 | Argument frames are the recommended mechanism for transferring fixed |
|
637 | 672 | sets of parameters to a command. Data frames are appropriate for |
|
638 | 673 | transferring variable data. A similar comparison would be to HTTP: |
@@ -19,7 +19,7 b' from . import (' | |||
|
19 | 19 | util, |
|
20 | 20 | ) |
|
21 | 21 | |
|
22 |
FRAME_HEADER_SIZE = |
|
|
22 | FRAME_HEADER_SIZE = 6 | |
|
23 | 23 | DEFAULT_MAX_FRAME_SIZE = 32768 |
|
24 | 24 | |
|
25 | 25 | FRAME_TYPE_COMMAND_NAME = 0x01 |
@@ -89,28 +89,43 b' FRAME_TYPE_FLAGS = {' | |||
|
89 | 89 | |
|
90 | 90 | ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH') |
|
91 | 91 | |
|
92 | def makeframe(frametype, frameflags, payload): | |
|
92 | def makeframe(requestid, frametype, frameflags, payload): | |
|
93 | 93 | """Assemble a frame into a byte array.""" |
|
94 | 94 | # TODO assert size of payload. |
|
95 | 95 | frame = bytearray(FRAME_HEADER_SIZE + len(payload)) |
|
96 | 96 | |
|
97 | # 24 bits length | |
|
98 | # 16 bits request id | |
|
99 | # 4 bits type | |
|
100 | # 4 bits flags | |
|
101 | ||
|
97 | 102 | l = struct.pack(r'<I', len(payload)) |
|
98 | 103 | frame[0:3] = l[0:3] |
|
99 | frame[3] = (frametype << 4) | frameflags | |
|
100 | frame[4:] = payload | |
|
104 | struct.pack_into(r'<H', frame, 3, requestid) | |
|
105 | frame[5] = (frametype << 4) | frameflags | |
|
106 | frame[6:] = payload | |
|
101 | 107 | |
|
102 | 108 | return frame |
|
103 | 109 | |
|
104 | 110 | def makeframefromhumanstring(s): |
|
105 | """Given a string of the form: <type> <flags> <payload>, creates a frame. | |
|
111 | """Create a frame from a human readable string | |
|
112 | ||
|
113 | Strings have the form: | |
|
114 | ||
|
115 | <request-id> <type> <flags> <payload> | |
|
106 | 116 | |
|
107 | 117 | This can be used by user-facing applications and tests for creating |
|
108 | 118 | frames easily without having to type out a bunch of constants. |
|
109 | 119 | |
|
120 | Request ID is an integer. | |
|
121 | ||
|
110 | 122 | Frame type and flags can be specified by integer or named constant. |
|
123 | ||
|
111 | 124 | Flags can be delimited by `|` to bitwise OR them together. |
|
112 | 125 | """ |
|
113 |
frametype, frameflags, payload = s.split(b' ', |
|
|
126 | requestid, frametype, frameflags, payload = s.split(b' ', 3) | |
|
127 | ||
|
128 | requestid = int(requestid) | |
|
114 | 129 | |
|
115 | 130 | if frametype in FRAME_TYPES: |
|
116 | 131 | frametype = FRAME_TYPES[frametype] |
@@ -127,7 +142,7 b' def makeframefromhumanstring(s):' | |||
|
127 | 142 | |
|
128 | 143 | payload = util.unescapestr(payload) |
|
129 | 144 | |
|
130 | return makeframe(frametype, finalflags, payload) | |
|
145 | return makeframe(requestid, frametype, finalflags, payload) | |
|
131 | 146 | |
|
132 | 147 | def parseheader(data): |
|
133 | 148 | """Parse a unified framing protocol frame header from a buffer. |
@@ -140,12 +155,13 b' def parseheader(data):' | |||
|
140 | 155 | # 4 bits frame flags |
|
141 | 156 | # ... payload |
|
142 | 157 | framelength = data[0] + 256 * data[1] + 16384 * data[2] |
|
143 | typeflags = data[3] | |
|
158 | requestid = struct.unpack_from(r'<H', data, 3)[0] | |
|
159 | typeflags = data[5] | |
|
144 | 160 | |
|
145 | 161 | frametype = (typeflags & 0xf0) >> 4 |
|
146 | 162 | frameflags = typeflags & 0x0f |
|
147 | 163 | |
|
148 | return frametype, frameflags, framelength | |
|
164 | return requestid, frametype, frameflags, framelength | |
|
149 | 165 | |
|
150 | 166 | def readframe(fh): |
|
151 | 167 | """Read a unified framing protocol frame from a file object. |
@@ -165,16 +181,16 b' def readframe(fh):' | |||
|
165 | 181 | raise error.Abort(_('received incomplete frame: got %d bytes: %s') % |
|
166 | 182 | (readcount, header)) |
|
167 | 183 | |
|
168 | frametype, frameflags, framelength = parseheader(header) | |
|
184 | requestid, frametype, frameflags, framelength = parseheader(header) | |
|
169 | 185 | |
|
170 | 186 | payload = fh.read(framelength) |
|
171 | 187 | if len(payload) != framelength: |
|
172 | 188 | raise error.Abort(_('frame length error: expected %d; got %d') % |
|
173 | 189 | (framelength, len(payload))) |
|
174 | 190 | |
|
175 | return frametype, frameflags, payload | |
|
191 | return requestid, frametype, frameflags, payload | |
|
176 | 192 | |
|
177 | def createcommandframes(cmd, args, datafh=None): | |
|
193 | def createcommandframes(requestid, cmd, args, datafh=None): | |
|
178 | 194 | """Create frames necessary to transmit a request to run a command. |
|
179 | 195 | |
|
180 | 196 | This is a generator of bytearrays. Each item represents a frame |
@@ -189,7 +205,7 b' def createcommandframes(cmd, args, dataf' | |||
|
189 | 205 | if not flags: |
|
190 | 206 | flags |= FLAG_COMMAND_NAME_EOS |
|
191 | 207 | |
|
192 | yield makeframe(FRAME_TYPE_COMMAND_NAME, flags, cmd) | |
|
208 | yield makeframe(requestid, FRAME_TYPE_COMMAND_NAME, flags, cmd) | |
|
193 | 209 | |
|
194 | 210 | for i, k in enumerate(sorted(args)): |
|
195 | 211 | v = args[k] |
@@ -205,7 +221,7 b' def createcommandframes(cmd, args, dataf' | |||
|
205 | 221 | payload[offset:offset + len(v)] = v |
|
206 | 222 | |
|
207 | 223 | flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0 |
|
208 | yield makeframe(FRAME_TYPE_COMMAND_ARGUMENT, flags, payload) | |
|
224 | yield makeframe(requestid, FRAME_TYPE_COMMAND_ARGUMENT, flags, payload) | |
|
209 | 225 | |
|
210 | 226 | if datafh: |
|
211 | 227 | while True: |
@@ -219,12 +235,12 b' def createcommandframes(cmd, args, dataf' | |||
|
219 | 235 | assert datafh.read(1) == b'' |
|
220 | 236 | done = True |
|
221 | 237 | |
|
222 | yield makeframe(FRAME_TYPE_COMMAND_DATA, flags, data) | |
|
238 | yield makeframe(requestid, FRAME_TYPE_COMMAND_DATA, flags, data) | |
|
223 | 239 | |
|
224 | 240 | if done: |
|
225 | 241 | break |
|
226 | 242 | |
|
227 | def createbytesresponseframesfrombytes(data, | |
|
243 | def createbytesresponseframesfrombytes(requestid, data, | |
|
228 | 244 | maxframesize=DEFAULT_MAX_FRAME_SIZE): |
|
229 | 245 | """Create a raw frame to send a bytes response from static bytes input. |
|
230 | 246 | |
@@ -233,7 +249,7 b' def createbytesresponseframesfrombytes(d' | |||
|
233 | 249 | |
|
234 | 250 | # Simple case of a single frame. |
|
235 | 251 | if len(data) <= maxframesize: |
|
236 | yield makeframe(FRAME_TYPE_BYTES_RESPONSE, | |
|
252 | yield makeframe(requestid, FRAME_TYPE_BYTES_RESPONSE, | |
|
237 | 253 | FLAG_BYTES_RESPONSE_EOS, data) |
|
238 | 254 | return |
|
239 | 255 | |
@@ -248,12 +264,12 b' def createbytesresponseframesfrombytes(d' | |||
|
248 | 264 | else: |
|
249 | 265 | flags = FLAG_BYTES_RESPONSE_CONTINUATION |
|
250 | 266 | |
|
251 | yield makeframe(FRAME_TYPE_BYTES_RESPONSE, flags, chunk) | |
|
267 | yield makeframe(requestid, FRAME_TYPE_BYTES_RESPONSE, flags, chunk) | |
|
252 | 268 | |
|
253 | 269 | if done: |
|
254 | 270 | break |
|
255 | 271 | |
|
256 | def createerrorframe(msg, protocol=False, application=False): | |
|
272 | def createerrorframe(requestid, msg, protocol=False, application=False): | |
|
257 | 273 | # TODO properly handle frame size limits. |
|
258 | 274 | assert len(msg) <= DEFAULT_MAX_FRAME_SIZE |
|
259 | 275 | |
@@ -263,7 +279,7 b' def createerrorframe(msg, protocol=False' | |||
|
263 | 279 | if application: |
|
264 | 280 | flags |= FLAG_ERROR_RESPONSE_APPLICATION |
|
265 | 281 | |
|
266 | yield makeframe(FRAME_TYPE_ERROR_RESPONSE, flags, msg) | |
|
282 | yield makeframe(requestid, FRAME_TYPE_ERROR_RESPONSE, flags, msg) | |
|
267 | 283 | |
|
268 | 284 | class serverreactor(object): |
|
269 | 285 | """Holds state of a server handling frame-based protocol requests. |
@@ -326,6 +342,7 b' class serverreactor(object):' | |||
|
326 | 342 | self._deferoutput = deferoutput |
|
327 | 343 | self._state = 'idle' |
|
328 | 344 | self._bufferedframegens = [] |
|
345 | self._activerequestid = None | |
|
329 | 346 | self._activecommand = None |
|
330 | 347 | self._activeargs = None |
|
331 | 348 | self._activedata = None |
@@ -334,7 +351,7 b' class serverreactor(object):' | |||
|
334 | 351 | self._activeargname = None |
|
335 | 352 | self._activeargchunks = None |
|
336 | 353 | |
|
337 | def onframerecv(self, frametype, frameflags, payload): | |
|
354 | def onframerecv(self, requestid, frametype, frameflags, payload): | |
|
338 | 355 | """Process a frame that has been received off the wire. |
|
339 | 356 | |
|
340 | 357 | Returns a dict with an ``action`` key that details what action, |
@@ -351,14 +368,14 b' class serverreactor(object):' | |||
|
351 | 368 | if not meth: |
|
352 | 369 | raise error.ProgrammingError('unhandled state: %s' % self._state) |
|
353 | 370 | |
|
354 | return meth(frametype, frameflags, payload) | |
|
371 | return meth(requestid, frametype, frameflags, payload) | |
|
355 | 372 | |
|
356 | def onbytesresponseready(self, data): | |
|
373 | def onbytesresponseready(self, requestid, data): | |
|
357 | 374 | """Signal that a bytes response is ready to be sent to the client. |
|
358 | 375 | |
|
359 | 376 | The raw bytes response is passed as an argument. |
|
360 | 377 | """ |
|
361 | framegen = createbytesresponseframesfrombytes(data) | |
|
378 | framegen = createbytesresponseframesfrombytes(requestid, data) | |
|
362 | 379 | |
|
363 | 380 | if self._deferoutput: |
|
364 | 381 | self._bufferedframegens.append(framegen) |
@@ -387,9 +404,9 b' class serverreactor(object):' | |||
|
387 | 404 | 'framegen': makegen(), |
|
388 | 405 | } |
|
389 | 406 | |
|
390 | def onapplicationerror(self, msg): | |
|
407 | def onapplicationerror(self, requestid, msg): | |
|
391 | 408 | return 'sendframes', { |
|
392 | 'framegen': createerrorframe(msg, application=True), | |
|
409 | 'framegen': createerrorframe(requestid, msg, application=True), | |
|
393 | 410 | } |
|
394 | 411 | |
|
395 | 412 | def _makeerrorresult(self, msg): |
@@ -399,6 +416,7 b' class serverreactor(object):' | |||
|
399 | 416 | |
|
400 | 417 | def _makeruncommandresult(self): |
|
401 | 418 | return 'runcommand', { |
|
419 | 'requestid': self._activerequestid, | |
|
402 | 420 | 'command': self._activecommand, |
|
403 | 421 | 'args': self._activeargs, |
|
404 | 422 | 'data': self._activedata.getvalue() if self._activedata else None, |
@@ -409,7 +427,7 b' class serverreactor(object):' | |||
|
409 | 427 | 'state': self._state, |
|
410 | 428 | } |
|
411 | 429 | |
|
412 | def _onframeidle(self, frametype, frameflags, payload): | |
|
430 | def _onframeidle(self, requestid, frametype, frameflags, payload): | |
|
413 | 431 | # The only frame type that should be received in this state is a |
|
414 | 432 | # command request. |
|
415 | 433 | if frametype != FRAME_TYPE_COMMAND_NAME: |
@@ -417,6 +435,7 b' class serverreactor(object):' | |||
|
417 | 435 | return self._makeerrorresult( |
|
418 | 436 | _('expected command frame; got %d') % frametype) |
|
419 | 437 | |
|
438 | self._activerequestid = requestid | |
|
420 | 439 | self._activecommand = payload |
|
421 | 440 | self._activeargs = {} |
|
422 | 441 | self._activedata = None |
@@ -439,7 +458,7 b' class serverreactor(object):' | |||
|
439 | 458 | return self._makeerrorresult(_('missing frame flags on ' |
|
440 | 459 | 'command frame')) |
|
441 | 460 | |
|
442 | def _onframereceivingargs(self, frametype, frameflags, payload): | |
|
461 | def _onframereceivingargs(self, requestid, frametype, frameflags, payload): | |
|
443 | 462 | if frametype != FRAME_TYPE_COMMAND_ARGUMENT: |
|
444 | 463 | self._state = 'errored' |
|
445 | 464 | return self._makeerrorresult(_('expected command argument ' |
@@ -492,7 +511,7 b' class serverreactor(object):' | |||
|
492 | 511 | else: |
|
493 | 512 | return self._makewantframeresult() |
|
494 | 513 | |
|
495 | def _onframereceivingdata(self, frametype, frameflags, payload): | |
|
514 | def _onframereceivingdata(self, requestid, frametype, frameflags, payload): | |
|
496 | 515 | if frametype != FRAME_TYPE_COMMAND_DATA: |
|
497 | 516 | self._state = 'errored' |
|
498 | 517 | return self._makeerrorresult(_('expected command data frame; ' |
@@ -512,5 +531,5 b' class serverreactor(object):' | |||
|
512 | 531 | return self._makeerrorresult(_('command data frame without ' |
|
513 | 532 | 'flags')) |
|
514 | 533 | |
|
515 | def _onframeerrored(self, frametype, frameflags, payload): | |
|
534 | def _onframeerrored(self, requestid, frametype, frameflags, payload): | |
|
516 | 535 | return self._makeerrorresult(_('server already errored')) |
@@ -33,7 +33,7 b' HTTP_OK = 200' | |||
|
33 | 33 | HGTYPE = 'application/mercurial-0.1' |
|
34 | 34 | HGTYPE2 = 'application/mercurial-0.2' |
|
35 | 35 | HGERRTYPE = 'application/hg-error' |
|
36 |
FRAMINGTYPE = b'application/mercurial-exp-framing-000 |
|
|
36 | FRAMINGTYPE = b'application/mercurial-exp-framing-0002' | |
|
37 | 37 | |
|
38 | 38 | HTTPV2 = wireprototypes.HTTPV2 |
|
39 | 39 | SSHV1 = wireprototypes.SSHV1 |
@@ -394,10 +394,12 b' def _processhttpv2reflectrequest(ui, rep' | |||
|
394 | 394 | states.append(b'received: <no frame>') |
|
395 | 395 | break |
|
396 | 396 | |
|
397 | frametype, frameflags, payload = frame | |
|
398 |
states.append(b'received: %d %d %s' % (frametype, frameflags, |
|
|
397 | requestid, frametype, frameflags, payload = frame | |
|
398 | states.append(b'received: %d %d %d %s' % (frametype, frameflags, | |
|
399 | requestid, payload)) | |
|
399 | 400 | |
|
400 |
action, meta = reactor.onframerecv(frametype, frameflags, |
|
|
401 | action, meta = reactor.onframerecv(requestid, frametype, frameflags, | |
|
402 | payload) | |
|
401 | 403 | states.append(json.dumps((action, meta), sort_keys=True, |
|
402 | 404 | separators=(', ', ': '))) |
|
403 | 405 | |
@@ -517,7 +519,8 b' def _httpv2runcommand(ui, repo, req, res' | |||
|
517 | 519 | res.headers[b'Content-Type'] = FRAMINGTYPE |
|
518 | 520 | |
|
519 | 521 | if isinstance(rsp, wireprototypes.bytesresponse): |
|
520 |
action, meta = reactor.onbytesresponseready( |
|
|
522 | action, meta = reactor.onbytesresponseready(command['requestid'], | |
|
523 | rsp.data) | |
|
521 | 524 | else: |
|
522 | 525 | action, meta = reactor.onapplicationerror( |
|
523 | 526 | _('unhandled response type from wire proto command')) |
@@ -1,5 +1,5 b'' | |||
|
1 | 1 | $ HTTPV2=exp-http-v2-0001 |
|
2 |
$ MEDIATYPE=application/mercurial-exp-framing-000 |
|
|
2 | $ MEDIATYPE=application/mercurial-exp-framing-0002 | |
|
3 | 3 | |
|
4 | 4 | $ send() { |
|
5 | 5 | > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ |
@@ -122,7 +122,7 b' Missing Accept header results in 406' | |||
|
122 | 122 | s> Content-Type: text/plain\r\n |
|
123 | 123 | s> Content-Length: 85\r\n |
|
124 | 124 | s> \r\n |
|
125 |
s> client MUST specify Accept header with value: application/mercurial-exp-framing-000 |
|
|
125 | s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n | |
|
126 | 126 | |
|
127 | 127 | Bad Accept header results in 406 |
|
128 | 128 | |
@@ -145,7 +145,7 b' Bad Accept header results in 406' | |||
|
145 | 145 | s> Content-Type: text/plain\r\n |
|
146 | 146 | s> Content-Length: 85\r\n |
|
147 | 147 | s> \r\n |
|
148 |
s> client MUST specify Accept header with value: application/mercurial-exp-framing-000 |
|
|
148 | s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n | |
|
149 | 149 | |
|
150 | 150 | Bad Content-Type header results in 415 |
|
151 | 151 | |
@@ -158,7 +158,7 b' Bad Content-Type header results in 415' | |||
|
158 | 158 | using raw connection to peer |
|
159 | 159 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n |
|
160 | 160 | s> Accept-Encoding: identity\r\n |
|
161 |
s> accept: application/mercurial-exp-framing-000 |
|
|
161 | s> accept: application/mercurial-exp-framing-0002\r\n | |
|
162 | 162 | s> content-type: badmedia\r\n |
|
163 | 163 | s> user-agent: test\r\n |
|
164 | 164 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
@@ -170,7 +170,7 b' Bad Content-Type header results in 415' | |||
|
170 | 170 | s> Content-Type: text/plain\r\n |
|
171 | 171 | s> Content-Length: 88\r\n |
|
172 | 172 | s> \r\n |
|
173 |
s> client MUST send Content-Type header with value: application/mercurial-exp-framing-000 |
|
|
173 | s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0002\n | |
|
174 | 174 | |
|
175 | 175 | Request to read-only command works out of the box |
|
176 | 176 | |
@@ -179,27 +179,27 b' Request to read-only command works out o' | |||
|
179 | 179 | > accept: $MEDIATYPE |
|
180 | 180 | > content-type: $MEDIATYPE |
|
181 | 181 | > user-agent: test |
|
182 | > frame command-name eos customreadonly | |
|
182 | > frame 1 command-name eos customreadonly | |
|
183 | 183 | > EOF |
|
184 | 184 | using raw connection to peer |
|
185 | 185 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n |
|
186 | 186 | s> Accept-Encoding: identity\r\n |
|
187 |
s> accept: application/mercurial-exp-framing-000 |
|
|
188 |
s> content-type: application/mercurial-exp-framing-000 |
|
|
187 | s> accept: application/mercurial-exp-framing-0002\r\n | |
|
188 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
|
189 | 189 | s> user-agent: test\r\n |
|
190 | s> content-length: 18\r\n | |
|
190 | s> *\r\n (glob) | |
|
191 | 191 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
192 | 192 | s> \r\n |
|
193 | s> \x0e\x00\x00\x11customreadonly | |
|
193 | s> \x0e\x00\x00\x01\x00\x11customreadonly | |
|
194 | 194 | s> makefile('rb', None) |
|
195 | 195 | s> HTTP/1.1 200 OK\r\n |
|
196 | 196 | s> Server: testing stub value\r\n |
|
197 | 197 | s> Date: $HTTP_DATE$\r\n |
|
198 |
s> Content-Type: application/mercurial-exp-framing-000 |
|
|
198 | s> Content-Type: application/mercurial-exp-framing-0002\r\n | |
|
199 | 199 | s> Transfer-Encoding: chunked\r\n |
|
200 | 200 | s> \r\n |
|
201 |
s> 2 |
|
|
202 | s> \x1d\x00\x00Bcustomreadonly bytes response | |
|
201 | s> 23\r\n | |
|
202 | s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response | |
|
203 | 203 | s> \r\n |
|
204 | 204 | s> 0\r\n |
|
205 | 205 | s> \r\n |
@@ -290,27 +290,27 b' Authorized request for valid read-write ' | |||
|
290 | 290 | > user-agent: test |
|
291 | 291 | > accept: $MEDIATYPE |
|
292 | 292 | > content-type: $MEDIATYPE |
|
293 | > frame command-name eos customreadonly | |
|
293 | > frame 1 command-name eos customreadonly | |
|
294 | 294 | > EOF |
|
295 | 295 | using raw connection to peer |
|
296 | 296 | s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n |
|
297 | 297 | s> Accept-Encoding: identity\r\n |
|
298 |
s> accept: application/mercurial-exp-framing-000 |
|
|
299 |
s> content-type: application/mercurial-exp-framing-000 |
|
|
298 | s> accept: application/mercurial-exp-framing-0002\r\n | |
|
299 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
|
300 | 300 | s> user-agent: test\r\n |
|
301 |
s> content-length: |
|
|
301 | s> content-length: 20\r\n | |
|
302 | 302 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
303 | 303 | s> \r\n |
|
304 | s> \x0e\x00\x00\x11customreadonly | |
|
304 | s> \x0e\x00\x00\x01\x00\x11customreadonly | |
|
305 | 305 | s> makefile('rb', None) |
|
306 | 306 | s> HTTP/1.1 200 OK\r\n |
|
307 | 307 | s> Server: testing stub value\r\n |
|
308 | 308 | s> Date: $HTTP_DATE$\r\n |
|
309 |
s> Content-Type: application/mercurial-exp-framing-000 |
|
|
309 | s> Content-Type: application/mercurial-exp-framing-0002\r\n | |
|
310 | 310 | s> Transfer-Encoding: chunked\r\n |
|
311 | 311 | s> \r\n |
|
312 |
s> 2 |
|
|
313 | s> \x1d\x00\x00Bcustomreadonly bytes response | |
|
312 | s> 23\r\n | |
|
313 | s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response | |
|
314 | 314 | s> \r\n |
|
315 | 315 | s> 0\r\n |
|
316 | 316 | s> \r\n |
@@ -325,7 +325,7 b' Authorized request for unknown command i' | |||
|
325 | 325 | using raw connection to peer |
|
326 | 326 | s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n |
|
327 | 327 | s> Accept-Encoding: identity\r\n |
|
328 |
s> accept: application/mercurial-exp-framing-000 |
|
|
328 | s> accept: application/mercurial-exp-framing-0002\r\n | |
|
329 | 329 | s> user-agent: test\r\n |
|
330 | 330 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
331 | 331 | s> \r\n |
@@ -382,33 +382,33 b' Command frames can be reflected via debu' | |||
|
382 | 382 | > accept: $MEDIATYPE |
|
383 | 383 | > content-type: $MEDIATYPE |
|
384 | 384 | > user-agent: test |
|
385 | > frame command-name have-args command1 | |
|
386 | > frame command-argument 0 \x03\x00\x04\x00fooval1 | |
|
387 | > frame command-argument eoa \x04\x00\x03\x00bar1val | |
|
385 | > frame 1 command-name have-args command1 | |
|
386 | > frame 1 command-argument 0 \x03\x00\x04\x00fooval1 | |
|
387 | > frame 1 command-argument eoa \x04\x00\x03\x00bar1val | |
|
388 | 388 | > EOF |
|
389 | 389 | using raw connection to peer |
|
390 | 390 | s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n |
|
391 | 391 | s> Accept-Encoding: identity\r\n |
|
392 |
s> accept: application/mercurial-exp-framing-000 |
|
|
393 |
s> content-type: application/mercurial-exp-framing-000 |
|
|
392 | s> accept: application/mercurial-exp-framing-0002\r\n | |
|
393 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
|
394 | 394 | s> user-agent: test\r\n |
|
395 |
s> content-length: 4 |
|
|
395 | s> content-length: 48\r\n | |
|
396 | 396 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
397 | 397 | s> \r\n |
|
398 | s> \x08\x00\x00\x12command1\x0b\x00\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00"\x04\x00\x03\x00bar1val | |
|
398 | s> \x08\x00\x00\x01\x00\x12command1\x0b\x00\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\x00"\x04\x00\x03\x00bar1val | |
|
399 | 399 | s> makefile('rb', None) |
|
400 | 400 | s> HTTP/1.1 200 OK\r\n |
|
401 | 401 | s> Server: testing stub value\r\n |
|
402 | 402 | s> Date: $HTTP_DATE$\r\n |
|
403 | 403 | s> Content-Type: text/plain\r\n |
|
404 |
s> Content-Length: 3 |
|
|
404 | s> Content-Length: 332\r\n | |
|
405 | 405 | s> \r\n |
|
406 | s> received: 1 2 command1\n | |
|
406 | s> received: 1 2 1 command1\n | |
|
407 | 407 | s> ["wantframe", {"state": "command-receiving-args"}]\n |
|
408 | s> received: 2 0 \x03\x00\x04\x00fooval1\n | |
|
408 | s> received: 2 0 1 \x03\x00\x04\x00fooval1\n | |
|
409 | 409 | s> ["wantframe", {"state": "command-receiving-args"}]\n |
|
410 | s> received: 2 2 \x04\x00\x03\x00bar1val\n | |
|
411 | s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null}]\n | |
|
410 | s> received: 2 2 1 \x04\x00\x03\x00bar1val\n | |
|
411 | s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n | |
|
412 | 412 | s> received: <no frame>\n |
|
413 | 413 | s> {"action": "noop"} |
|
414 | 414 |
@@ -18,52 +18,53 b' def sendframes(reactor, gen):' | |||
|
18 | 18 | Emits a generator of results from ``onframerecv()`` calls. |
|
19 | 19 | """ |
|
20 | 20 | for frame in gen: |
|
21 | frametype, frameflags, framelength = framing.parseheader(frame) | |
|
21 | rid, frametype, frameflags, framelength = framing.parseheader(frame) | |
|
22 | 22 | payload = frame[framing.FRAME_HEADER_SIZE:] |
|
23 | 23 | assert len(payload) == framelength |
|
24 | 24 | |
|
25 | yield reactor.onframerecv(frametype, frameflags, payload) | |
|
25 | yield reactor.onframerecv(rid, frametype, frameflags, payload) | |
|
26 | 26 | |
|
27 | def sendcommandframes(reactor, cmd, args, datafh=None): | |
|
27 | def sendcommandframes(reactor, rid, cmd, args, datafh=None): | |
|
28 | 28 | """Generate frames to run a command and send them to a reactor.""" |
|
29 | return sendframes(reactor, framing.createcommandframes(cmd, args, datafh)) | |
|
29 | return sendframes(reactor, | |
|
30 | framing.createcommandframes(rid, cmd, args, datafh)) | |
|
30 | 31 | |
|
31 | 32 | class FrameTests(unittest.TestCase): |
|
32 | 33 | def testdataexactframesize(self): |
|
33 | 34 | data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE) |
|
34 | 35 | |
|
35 | frames = list(framing.createcommandframes(b'command', {}, data)) | |
|
36 | frames = list(framing.createcommandframes(1, b'command', {}, data)) | |
|
36 | 37 | self.assertEqual(frames, [ |
|
37 | ffs(b'command-name have-data command'), | |
|
38 | ffs(b'command-data continuation %s' % data.getvalue()), | |
|
39 | ffs(b'command-data eos ') | |
|
38 | ffs(b'1 command-name have-data command'), | |
|
39 | ffs(b'1 command-data continuation %s' % data.getvalue()), | |
|
40 | ffs(b'1 command-data eos ') | |
|
40 | 41 | ]) |
|
41 | 42 | |
|
42 | 43 | def testdatamultipleframes(self): |
|
43 | 44 | data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1)) |
|
44 | frames = list(framing.createcommandframes(b'command', {}, data)) | |
|
45 | frames = list(framing.createcommandframes(1, b'command', {}, data)) | |
|
45 | 46 | self.assertEqual(frames, [ |
|
46 | ffs(b'command-name have-data command'), | |
|
47 | ffs(b'command-data continuation %s' % ( | |
|
47 | ffs(b'1 command-name have-data command'), | |
|
48 | ffs(b'1 command-data continuation %s' % ( | |
|
48 | 49 | b'x' * framing.DEFAULT_MAX_FRAME_SIZE)), |
|
49 | ffs(b'command-data eos x'), | |
|
50 | ffs(b'1 command-data eos x'), | |
|
50 | 51 | ]) |
|
51 | 52 | |
|
52 | 53 | def testargsanddata(self): |
|
53 | 54 | data = util.bytesio(b'x' * 100) |
|
54 | 55 | |
|
55 | frames = list(framing.createcommandframes(b'command', { | |
|
56 | frames = list(framing.createcommandframes(1, b'command', { | |
|
56 | 57 | b'key1': b'key1value', |
|
57 | 58 | b'key2': b'key2value', |
|
58 | 59 | b'key3': b'key3value', |
|
59 | 60 | }, data)) |
|
60 | 61 | |
|
61 | 62 | self.assertEqual(frames, [ |
|
62 | ffs(b'command-name have-args|have-data command'), | |
|
63 | ffs(br'command-argument 0 \x04\x00\x09\x00key1key1value'), | |
|
64 | ffs(br'command-argument 0 \x04\x00\x09\x00key2key2value'), | |
|
65 | ffs(br'command-argument eoa \x04\x00\x09\x00key3key3value'), | |
|
66 | ffs(b'command-data eos %s' % data.getvalue()), | |
|
63 | ffs(b'1 command-name have-args|have-data command'), | |
|
64 | ffs(br'1 command-argument 0 \x04\x00\x09\x00key1key1value'), | |
|
65 | ffs(br'1 command-argument 0 \x04\x00\x09\x00key2key2value'), | |
|
66 | ffs(br'1 command-argument eoa \x04\x00\x09\x00key3key3value'), | |
|
67 | ffs(b'1 command-data eos %s' % data.getvalue()), | |
|
67 | 68 | ]) |
|
68 | 69 | |
|
69 | 70 | class ServerReactorTests(unittest.TestCase): |
@@ -86,10 +87,11 b' class ServerReactorTests(unittest.TestCa' | |||
|
86 | 87 | def test1framecommand(self): |
|
87 | 88 | """Receiving a command in a single frame yields request to run it.""" |
|
88 | 89 | reactor = makereactor() |
|
89 | results = list(sendcommandframes(reactor, b'mycommand', {})) | |
|
90 | results = list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
|
90 | 91 | self.assertEqual(len(results), 1) |
|
91 | 92 | self.assertaction(results[0], 'runcommand') |
|
92 | 93 | self.assertEqual(results[0][1], { |
|
94 | 'requestid': 1, | |
|
93 | 95 | 'command': b'mycommand', |
|
94 | 96 | 'args': {}, |
|
95 | 97 | 'data': None, |
@@ -100,12 +102,13 b' class ServerReactorTests(unittest.TestCa' | |||
|
100 | 102 | |
|
101 | 103 | def test1argument(self): |
|
102 | 104 | reactor = makereactor() |
|
103 | results = list(sendcommandframes(reactor, b'mycommand', | |
|
105 | results = list(sendcommandframes(reactor, 41, b'mycommand', | |
|
104 | 106 | {b'foo': b'bar'})) |
|
105 | 107 | self.assertEqual(len(results), 2) |
|
106 | 108 | self.assertaction(results[0], 'wantframe') |
|
107 | 109 | self.assertaction(results[1], 'runcommand') |
|
108 | 110 | self.assertEqual(results[1][1], { |
|
111 | 'requestid': 41, | |
|
109 | 112 | 'command': b'mycommand', |
|
110 | 113 | 'args': {b'foo': b'bar'}, |
|
111 | 114 | 'data': None, |
@@ -113,13 +116,14 b' class ServerReactorTests(unittest.TestCa' | |||
|
113 | 116 | |
|
114 | 117 | def testmultiarguments(self): |
|
115 | 118 | reactor = makereactor() |
|
116 | results = list(sendcommandframes(reactor, b'mycommand', | |
|
119 | results = list(sendcommandframes(reactor, 1, b'mycommand', | |
|
117 | 120 | {b'foo': b'bar', b'biz': b'baz'})) |
|
118 | 121 | self.assertEqual(len(results), 3) |
|
119 | 122 | self.assertaction(results[0], 'wantframe') |
|
120 | 123 | self.assertaction(results[1], 'wantframe') |
|
121 | 124 | self.assertaction(results[2], 'runcommand') |
|
122 | 125 | self.assertEqual(results[2][1], { |
|
126 | 'requestid': 1, | |
|
123 | 127 | 'command': b'mycommand', |
|
124 | 128 | 'args': {b'foo': b'bar', b'biz': b'baz'}, |
|
125 | 129 | 'data': None, |
@@ -127,12 +131,13 b' class ServerReactorTests(unittest.TestCa' | |||
|
127 | 131 | |
|
128 | 132 | def testsimplecommanddata(self): |
|
129 | 133 | reactor = makereactor() |
|
130 | results = list(sendcommandframes(reactor, b'mycommand', {}, | |
|
134 | results = list(sendcommandframes(reactor, 1, b'mycommand', {}, | |
|
131 | 135 | util.bytesio(b'data!'))) |
|
132 | 136 | self.assertEqual(len(results), 2) |
|
133 | 137 | self.assertaction(results[0], 'wantframe') |
|
134 | 138 | self.assertaction(results[1], 'runcommand') |
|
135 | 139 | self.assertEqual(results[1][1], { |
|
140 | 'requestid': 1, | |
|
136 | 141 | 'command': b'mycommand', |
|
137 | 142 | 'args': {}, |
|
138 | 143 | 'data': b'data!', |
@@ -140,10 +145,10 b' class ServerReactorTests(unittest.TestCa' | |||
|
140 | 145 | |
|
141 | 146 | def testmultipledataframes(self): |
|
142 | 147 | frames = [ |
|
143 | ffs(b'command-name have-data mycommand'), | |
|
144 | ffs(b'command-data continuation data1'), | |
|
145 | ffs(b'command-data continuation data2'), | |
|
146 | ffs(b'command-data eos data3'), | |
|
148 | ffs(b'1 command-name have-data mycommand'), | |
|
149 | ffs(b'1 command-data continuation data1'), | |
|
150 | ffs(b'1 command-data continuation data2'), | |
|
151 | ffs(b'1 command-data eos data3'), | |
|
147 | 152 | ] |
|
148 | 153 | |
|
149 | 154 | reactor = makereactor() |
@@ -153,6 +158,7 b' class ServerReactorTests(unittest.TestCa' | |||
|
153 | 158 | self.assertaction(results[i], 'wantframe') |
|
154 | 159 | self.assertaction(results[3], 'runcommand') |
|
155 | 160 | self.assertEqual(results[3][1], { |
|
161 | 'requestid': 1, | |
|
156 | 162 | 'command': b'mycommand', |
|
157 | 163 | 'args': {}, |
|
158 | 164 | 'data': b'data1data2data3', |
@@ -160,11 +166,11 b' class ServerReactorTests(unittest.TestCa' | |||
|
160 | 166 | |
|
161 | 167 | def testargumentanddata(self): |
|
162 | 168 | frames = [ |
|
163 | ffs(b'command-name have-args|have-data command'), | |
|
164 | ffs(br'command-argument 0 \x03\x00\x03\x00keyval'), | |
|
165 | ffs(br'command-argument eoa \x03\x00\x03\x00foobar'), | |
|
166 | ffs(b'command-data continuation value1'), | |
|
167 | ffs(b'command-data eos value2'), | |
|
169 | ffs(b'1 command-name have-args|have-data command'), | |
|
170 | ffs(br'1 command-argument 0 \x03\x00\x03\x00keyval'), | |
|
171 | ffs(br'1 command-argument eoa \x03\x00\x03\x00foobar'), | |
|
172 | ffs(b'1 command-data continuation value1'), | |
|
173 | ffs(b'1 command-data eos value2'), | |
|
168 | 174 | ] |
|
169 | 175 | |
|
170 | 176 | reactor = makereactor() |
@@ -172,6 +178,7 b' class ServerReactorTests(unittest.TestCa' | |||
|
172 | 178 | |
|
173 | 179 | self.assertaction(results[-1], 'runcommand') |
|
174 | 180 | self.assertEqual(results[-1][1], { |
|
181 | 'requestid': 1, | |
|
175 | 182 | 'command': b'command', |
|
176 | 183 | 'args': { |
|
177 | 184 | b'key': b'val', |
@@ -183,7 +190,7 b' class ServerReactorTests(unittest.TestCa' | |||
|
183 | 190 | def testunexpectedcommandargument(self): |
|
184 | 191 | """Command argument frame when not running a command is an error.""" |
|
185 | 192 | result = self._sendsingleframe(makereactor(), |
|
186 | b'command-argument 0 ignored') | |
|
193 | b'1 command-argument 0 ignored') | |
|
187 | 194 | self.assertaction(result, 'error') |
|
188 | 195 | self.assertEqual(result[1], { |
|
189 | 196 | 'message': b'expected command frame; got 2', |
@@ -192,7 +199,7 b' class ServerReactorTests(unittest.TestCa' | |||
|
192 | 199 | def testunexpectedcommanddata(self): |
|
193 | 200 | """Command argument frame when not running a command is an error.""" |
|
194 | 201 | result = self._sendsingleframe(makereactor(), |
|
195 | b'command-data 0 ignored') | |
|
202 | b'1 command-data 0 ignored') | |
|
196 | 203 | self.assertaction(result, 'error') |
|
197 | 204 | self.assertEqual(result[1], { |
|
198 | 205 | 'message': b'expected command frame; got 3', |
@@ -201,7 +208,7 b' class ServerReactorTests(unittest.TestCa' | |||
|
201 | 208 | def testmissingcommandframeflags(self): |
|
202 | 209 | """Command name frame must have flags set.""" |
|
203 | 210 | result = self._sendsingleframe(makereactor(), |
|
204 | b'command-name 0 command') | |
|
211 | b'1 command-name 0 command') | |
|
205 | 212 | self.assertaction(result, 'error') |
|
206 | 213 | self.assertEqual(result[1], { |
|
207 | 214 | 'message': b'missing frame flags on command frame', |
@@ -209,8 +216,8 b' class ServerReactorTests(unittest.TestCa' | |||
|
209 | 216 | |
|
210 | 217 | def testmissingargumentframe(self): |
|
211 | 218 | frames = [ |
|
212 | ffs(b'command-name have-args command'), | |
|
213 | ffs(b'command-name 0 ignored'), | |
|
219 | ffs(b'1 command-name have-args command'), | |
|
220 | ffs(b'1 command-name 0 ignored'), | |
|
214 | 221 | ] |
|
215 | 222 | |
|
216 | 223 | results = list(sendframes(makereactor(), frames)) |
@@ -224,8 +231,8 b' class ServerReactorTests(unittest.TestCa' | |||
|
224 | 231 | def testincompleteargumentname(self): |
|
225 | 232 | """Argument frame with incomplete name.""" |
|
226 | 233 | frames = [ |
|
227 | ffs(b'command-name have-args command1'), | |
|
228 | ffs(br'command-argument eoa \x04\x00\xde\xadfoo'), | |
|
234 | ffs(b'1 command-name have-args command1'), | |
|
235 | ffs(br'1 command-argument eoa \x04\x00\xde\xadfoo'), | |
|
229 | 236 | ] |
|
230 | 237 | |
|
231 | 238 | results = list(sendframes(makereactor(), frames)) |
@@ -239,8 +246,8 b' class ServerReactorTests(unittest.TestCa' | |||
|
239 | 246 | def testincompleteargumentvalue(self): |
|
240 | 247 | """Argument frame with incomplete value.""" |
|
241 | 248 | frames = [ |
|
242 | ffs(b'command-name have-args command'), | |
|
243 | ffs(br'command-argument eoa \x03\x00\xaa\xaafoopartialvalue'), | |
|
249 | ffs(b'1 command-name have-args command'), | |
|
250 | ffs(br'1 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'), | |
|
244 | 251 | ] |
|
245 | 252 | |
|
246 | 253 | results = list(sendframes(makereactor(), frames)) |
@@ -253,8 +260,8 b' class ServerReactorTests(unittest.TestCa' | |||
|
253 | 260 | |
|
254 | 261 | def testmissingcommanddataframe(self): |
|
255 | 262 | frames = [ |
|
256 | ffs(b'command-name have-data command1'), | |
|
257 | ffs(b'command-name eos command2'), | |
|
263 | ffs(b'1 command-name have-data command1'), | |
|
264 | ffs(b'1 command-name eos command2'), | |
|
258 | 265 | ] |
|
259 | 266 | results = list(sendframes(makereactor(), frames)) |
|
260 | 267 | self.assertEqual(len(results), 2) |
@@ -266,8 +273,8 b' class ServerReactorTests(unittest.TestCa' | |||
|
266 | 273 | |
|
267 | 274 | def testmissingcommanddataframeflags(self): |
|
268 | 275 | frames = [ |
|
269 | ffs(b'command-name have-data command1'), | |
|
270 | ffs(b'command-data 0 data'), | |
|
276 | ffs(b'1 command-name have-data command1'), | |
|
277 | ffs(b'1 command-data 0 data'), | |
|
271 | 278 | ] |
|
272 | 279 | results = list(sendframes(makereactor(), frames)) |
|
273 | 280 | self.assertEqual(len(results), 2) |
@@ -280,12 +287,12 b' class ServerReactorTests(unittest.TestCa' | |||
|
280 | 287 | def testsimpleresponse(self): |
|
281 | 288 | """Bytes response to command sends result frames.""" |
|
282 | 289 | reactor = makereactor() |
|
283 | list(sendcommandframes(reactor, b'mycommand', {})) | |
|
290 | list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
|
284 | 291 | |
|
285 | result = reactor.onbytesresponseready(b'response') | |
|
292 | result = reactor.onbytesresponseready(1, b'response') | |
|
286 | 293 | self.assertaction(result, 'sendframes') |
|
287 | 294 | self.assertframesequal(result[1]['framegen'], [ |
|
288 | b'bytes-response eos response', | |
|
295 | b'1 bytes-response eos response', | |
|
289 | 296 | ]) |
|
290 | 297 | |
|
291 | 298 | def testmultiframeresponse(self): |
@@ -294,54 +301,73 b' class ServerReactorTests(unittest.TestCa' | |||
|
294 | 301 | second = b'y' * 100 |
|
295 | 302 | |
|
296 | 303 | reactor = makereactor() |
|
297 | list(sendcommandframes(reactor, b'mycommand', {})) | |
|
304 | list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
|
298 | 305 | |
|
299 | result = reactor.onbytesresponseready(first + second) | |
|
306 | result = reactor.onbytesresponseready(1, first + second) | |
|
300 | 307 | self.assertaction(result, 'sendframes') |
|
301 | 308 | self.assertframesequal(result[1]['framegen'], [ |
|
302 | b'bytes-response continuation %s' % first, | |
|
303 | b'bytes-response eos %s' % second, | |
|
309 | b'1 bytes-response continuation %s' % first, | |
|
310 | b'1 bytes-response eos %s' % second, | |
|
304 | 311 | ]) |
|
305 | 312 | |
|
306 | 313 | def testapplicationerror(self): |
|
307 | 314 | reactor = makereactor() |
|
308 | list(sendcommandframes(reactor, b'mycommand', {})) | |
|
315 | list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
|
309 | 316 | |
|
310 | result = reactor.onapplicationerror(b'some message') | |
|
317 | result = reactor.onapplicationerror(1, b'some message') | |
|
311 | 318 | self.assertaction(result, 'sendframes') |
|
312 | 319 | self.assertframesequal(result[1]['framegen'], [ |
|
313 | b'error-response application some message', | |
|
320 | b'1 error-response application some message', | |
|
314 | 321 | ]) |
|
315 | 322 | |
|
316 | 323 | def test1commanddeferresponse(self): |
|
317 | 324 | """Responses when in deferred output mode are delayed until EOF.""" |
|
318 | 325 | reactor = makereactor(deferoutput=True) |
|
319 | results = list(sendcommandframes(reactor, b'mycommand', {})) | |
|
326 | results = list(sendcommandframes(reactor, 1, b'mycommand', {})) | |
|
320 | 327 | self.assertEqual(len(results), 1) |
|
321 | 328 | self.assertaction(results[0], 'runcommand') |
|
322 | 329 | |
|
323 | result = reactor.onbytesresponseready(b'response') | |
|
330 | result = reactor.onbytesresponseready(1, b'response') | |
|
324 | 331 | self.assertaction(result, 'noop') |
|
325 | 332 | result = reactor.oninputeof() |
|
326 | 333 | self.assertaction(result, 'sendframes') |
|
327 | 334 | self.assertframesequal(result[1]['framegen'], [ |
|
328 | b'bytes-response eos response', | |
|
335 | b'1 bytes-response eos response', | |
|
329 | 336 | ]) |
|
330 | 337 | |
|
331 | 338 | def testmultiplecommanddeferresponse(self): |
|
332 | 339 | reactor = makereactor(deferoutput=True) |
|
333 | list(sendcommandframes(reactor, b'command1', {})) | |
|
334 | list(sendcommandframes(reactor, b'command2', {})) | |
|
340 | list(sendcommandframes(reactor, 1, b'command1', {})) | |
|
341 | list(sendcommandframes(reactor, 3, b'command2', {})) | |
|
335 | 342 | |
|
336 | result = reactor.onbytesresponseready(b'response1') | |
|
343 | result = reactor.onbytesresponseready(1, b'response1') | |
|
337 | 344 | self.assertaction(result, 'noop') |
|
338 | result = reactor.onbytesresponseready(b'response2') | |
|
345 | result = reactor.onbytesresponseready(3, b'response2') | |
|
339 | 346 | self.assertaction(result, 'noop') |
|
340 | 347 | result = reactor.oninputeof() |
|
341 | 348 | self.assertaction(result, 'sendframes') |
|
342 | 349 | self.assertframesequal(result[1]['framegen'], [ |
|
343 | b'bytes-response eos response1', | |
|
344 | b'bytes-response eos response2' | |
|
350 | b'1 bytes-response eos response1', | |
|
351 | b'3 bytes-response eos response2' | |
|
352 | ]) | |
|
353 | ||
|
354 | def testrequestidtracking(self): | |
|
355 | reactor = makereactor(deferoutput=True) | |
|
356 | list(sendcommandframes(reactor, 1, b'command1', {})) | |
|
357 | list(sendcommandframes(reactor, 3, b'command2', {})) | |
|
358 | list(sendcommandframes(reactor, 5, b'command3', {})) | |
|
359 | ||
|
360 | # Register results for commands out of order. | |
|
361 | reactor.onbytesresponseready(3, b'response3') | |
|
362 | reactor.onbytesresponseready(1, b'response1') | |
|
363 | reactor.onbytesresponseready(5, b'response5') | |
|
364 | ||
|
365 | result = reactor.oninputeof() | |
|
366 | self.assertaction(result, 'sendframes') | |
|
367 | self.assertframesequal(result[1]['framegen'], [ | |
|
368 | b'3 bytes-response eos response3', | |
|
369 | b'1 bytes-response eos response1', | |
|
370 | b'5 bytes-response eos response5', | |
|
345 | 371 | ]) |
|
346 | 372 | |
|
347 | 373 | if __name__ == '__main__': |
General Comments 0
You need to be logged in to leave comments.
Login now