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