##// END OF EJS Templates
wireproto: add streams to frame-based protocol...
Gregory Szorc -
r37304:9bfcbe4f default
parent child Browse files
Show More
@@ -2777,22 +2777,23 b' def debugwireproto(ui, repo, path=None, '
2777 2777 syntax.
2778 2778
2779 2779 A frame is composed as a type, flags, and payload. These can be parsed
2780 from a string of the form ``<requestid> <type> <flags> <payload>``. That is,
2781 4 space-delimited strings.
2782
2783 ``payload`` is the simplest: it is evaluated as a Python byte string
2784 literal.
2785
2786 ``requestid`` is an integer defining the request identifier.
2780 from a string of the form:
2781
2782 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
2783
2784 ``request-id`` and ``stream-id`` are integers defining the request and
2785 stream identifiers.
2787 2786
2788 2787 ``type`` can be an integer value for the frame type or the string name
2789 2788 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2790 2789 ``command-name``.
2791 2790
2792 ``flags`` is a ``|`` delimited list of flag components. Each component
2793 (and there can be just one) can be an integer or a flag name for the
2794 specified frame type. Values are resolved to integers and then bitwise
2795 OR'd together.
2791 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
2792 components. Each component (and there can be just one) can be an integer
2793 or a flag name for stream flags or frame flags, respectively. Values are
2794 resolved to integers and then bitwise OR'd together.
2795
2796 ``payload`` is is evaluated as a Python byte string literal.
2796 2797 """
2797 2798 opts = pycompat.byteskwargs(opts)
2798 2799
@@ -489,28 +489,37 b' To operate the protocol, a bi-directiona'
489 489 ordered sends and receives is required. That is, each peer has one pipe
490 490 for sending data and another for receiving.
491 491
492 All data is read and written in atomic units called *frames*. These
493 are conceptually similar to TCP packets. Higher-level functionality
494 is built on the exchange and processing of frames.
495
496 All frames are associated with a *stream*. A *stream* provides a
497 unidirectional grouping of frames. Streams facilitate two goals:
498 content encoding and parallelism. There is a dedicated section on
499 streams below.
500
492 501 The protocol is request-response based: the client issues requests to
493 502 the server, which issues replies to those requests. Server-initiated
494 503 messaging is not currently supported, but this specification carves
495 504 out room to implement it.
496 505
497 All data is read and written in atomic units called *frames*. These
498 are conceptually similar to TCP packets. Higher-level functionality
499 is built on the exchange and processing of frames.
500
501 506 All frames are associated with a numbered request. Frames can thus
502 507 be logically grouped by their request ID.
503 508
504 Frames begin with a 6 octet header followed by a variable length
509 Frames begin with an 8 octet header followed by a variable length
505 510 payload::
506 511
507 +-----------------------------------------------+
508 | Length (24) |
509 +---------------------------------+-------------+
510 | Request ID (16) |
511 +----------+-----------+----------+
512 | Type (4) | Flags (4) |
513 +==========+===========+========================================|
512 +------------------------------------------------+
513 | Length (24) |
514 +--------------------------------+---------------+
515 | Request ID (16) | Stream ID (8) |
516 +------------------+-------------+---------------+
517 | Stream Flags (8) |
518 +-----------+------+
519 | Type (4) |
520 +-----------+
521 | Flags (4) |
522 +===========+===================================================|
514 523 | Frame Payload (0...) ...
515 524 +---------------------------------------------------------------+
516 525
@@ -518,7 +527,9 b' The length of the frame payload is expre'
518 527 little endian integer. Values larger than 65535 MUST NOT be used unless
519 528 given permission by the server as part of the negotiated capabilities
520 529 during the handshake. The frame header is not part of the advertised
521 frame length.
530 frame length. The payload length is the over-the-wire length. If there
531 is content encoding applied to the payload as part of the frame's stream,
532 the length is the output of that content encoding, not the input.
522 533
523 534 The 16-bit ``Request ID`` field denotes the integer request identifier,
524 535 stored as an unsigned little endian integer. Odd numbered requests are
@@ -529,7 +540,16 b' response to client-initiated requests. I'
529 540 start ordering request identifiers at ``1`` and ``0``, increment by
530 541 ``2``, and wrap around if all available numbers have been exhausted.
531 542
532 The 4-bit ``Type`` field denotes the type of message being sent.
543 The 8-bit ``Stream ID`` field denotes the stream that the frame is
544 associated with. Frames belonging to a stream may have content
545 encoding applied and the receiver may need to decode the raw frame
546 payload to obtain the original data. Odd numbered IDs are
547 client-initiated. Even numbered IDs are server-initiated.
548
549 The 8-bit ``Stream Flags`` field defines stream processing semantics.
550 See the section on streams below.
551
552 The 4-bit ``Type`` field denotes the type of frame being sent.
533 553
534 554 The 4-bit ``Flags`` field defines special, per-type attributes for
535 555 the frame.
@@ -720,6 +740,126 b' All textual data encoded in these frames'
720 740 The last atom in the frame SHOULD end with a newline (``\n``). If it
721 741 doesn't, clients MAY add a newline to facilitate immediate printing.
722 742
743 Stream Encoding Settings (``0x08``)
744 -----------------------------------
745
746 This frame type holds information defining the content encoding
747 settings for a *stream*.
748
749 This frame type is likely consumed by the protocol layer and is not
750 passed on to applications.
751
752 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
753 ``Stream Flag`` set.
754
755 The payload of this frame defines what content encoding has (possibly)
756 been applied to the payloads of subsequent frames in this stream.
757
758 The payload begins with an 8-bit integer defining the length of the
759 encoding *profile*, followed by the string name of that profile, which
760 must be an ASCII string. All bytes that follow can be used by that
761 profile for supplemental settings definitions. See the section below
762 on defined encoding profiles.
763
764 Stream States and Flags
765 -----------------------
766
767 Streams can be in two states: *open* and *closed*. An *open* stream
768 is active and frames attached to that stream could arrive at any time.
769 A *closed* stream is not active. If a frame attached to a *closed*
770 stream arrives, that frame MUST have an appropriate stream flag
771 set indicating beginning of stream. All streams are in the *closed*
772 state by default.
773
774 The ``Stream Flags`` field denotes a set of bit flags for defining
775 the relationship of this frame within a stream. The following flags
776 are defined:
777
778 0x01
779 Beginning of stream. The first frame in the stream MUST set this
780 flag. When received, the ``Stream ID`` this frame is attached to
781 becomes ``open``.
782
783 0x02
784 End of stream. The last frame in a stream MUST set this flag. When
785 received, the ``Stream ID`` this frame is attached to becomes
786 ``closed``. Any content encoding context associated with this stream
787 can be destroyed after processing the payload of this frame.
788
789 0x04
790 Apply content encoding. When set, any content encoding settings
791 defined by the stream should be applied when attempting to read
792 the frame. When not set, the frame payload isn't encoded.
793
794 Streams
795 -------
796
797 Streams - along with ``Request IDs`` - facilitate grouping of frames.
798 But the purpose of each is quite different and the groupings they
799 constitute are independent.
800
801 A ``Request ID`` is essentially a tag. It tells you which logical
802 request a frame is associated with.
803
804 A *stream* is a sequence of frames grouped for the express purpose
805 of applying a stateful encoding or for denoting sub-groups of frames.
806
807 Unlike ``Request ID``s which span the request and response, a stream
808 is unidirectional and stream IDs are independent from client to
809 server.
810
811 There is no strict hierarchical relationship between ``Request IDs``
812 and *streams*. A stream can contain frames having multiple
813 ``Request IDs``. Frames belonging to the same ``Request ID`` can
814 span multiple streams.
815
816 One goal of streams is to facilitate content encoding. A stream can
817 define an encoding to be applied to frame payloads. For example, the
818 payload transmitted over the wire may contain output from a
819 zstandard compression operation and the receiving end may decompress
820 that payload to obtain the original data.
821
822 The other goal of streams is to facilitate concurrent execution. For
823 example, a server could spawn 4 threads to service a request that can
824 be easily parallelized. Each of those 4 threads could write into its
825 own stream. Those streams could then in turn be delivered to 4 threads
826 on the receiving end, with each thread consuming its stream in near
827 isolation. The *main* thread on both ends merely does I/O and
828 encodes/decodes frame headers: the bulk of the work is done by worker
829 threads.
830
831 In addition, since content encoding is defined per stream, each
832 *worker thread* could perform potentially CPU bound work concurrently
833 with other threads. This approach of applying encoding at the
834 sub-protocol / stream level eliminates a potential resource constraint
835 on the protocol stream as a whole (it is common for the throughput of
836 a compression engine to be smaller than the throughput of a network).
837
838 Having multiple streams - each with their own encoding settings - also
839 facilitates the use of advanced data compression techniques. For
840 example, a transmitter could see that it is generating data faster
841 and slower than the receiving end is consuming it and adjust its
842 compression settings to trade CPU for compression ratio accordingly.
843
844 While streams can define a content encoding, not all frames within
845 that stream must use that content encoding. This can be useful when
846 data is being served from caches and being derived dynamically. A
847 cache could pre-compressed data so the server doesn't have to
848 recompress it. The ability to pick and choose which frames are
849 compressed allows servers to easily send data to the wire without
850 involving potentially expensive encoding overhead.
851
852 Content Encoding Profiles
853 -------------------------
854
855 Streams can have named content encoding *profiles* associated with
856 them. A profile defines a shared understanding of content encoding
857 settings and behavior.
858
859 The following profiles are defined:
860
861 TBD
862
723 863 Issuing Commands
724 864 ----------------
725 865
@@ -25,15 +25,26 b' from .utils import ('
25 25 stringutil,
26 26 )
27 27
28 FRAME_HEADER_SIZE = 6
28 FRAME_HEADER_SIZE = 8
29 29 DEFAULT_MAX_FRAME_SIZE = 32768
30 30
31 STREAM_FLAG_BEGIN_STREAM = 0x01
32 STREAM_FLAG_END_STREAM = 0x02
33 STREAM_FLAG_ENCODING_APPLIED = 0x04
34
35 STREAM_FLAGS = {
36 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
37 b'stream-end': STREAM_FLAG_END_STREAM,
38 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
39 }
40
31 41 FRAME_TYPE_COMMAND_NAME = 0x01
32 42 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
33 43 FRAME_TYPE_COMMAND_DATA = 0x03
34 44 FRAME_TYPE_BYTES_RESPONSE = 0x04
35 45 FRAME_TYPE_ERROR_RESPONSE = 0x05
36 46 FRAME_TYPE_TEXT_OUTPUT = 0x06
47 FRAME_TYPE_STREAM_SETTINGS = 0x08
37 48
38 49 FRAME_TYPES = {
39 50 b'command-name': FRAME_TYPE_COMMAND_NAME,
@@ -42,6 +53,7 b' FRAME_TYPES = {'
42 53 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
43 54 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
44 55 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
56 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
45 57 }
46 58
47 59 FLAG_COMMAND_NAME_EOS = 0x01
@@ -94,6 +106,7 b' FRAME_TYPE_FLAGS = {'
94 106 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
95 107 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
96 108 FRAME_TYPE_TEXT_OUTPUT: {},
109 FRAME_TYPE_STREAM_SETTINGS: {},
97 110 }
98 111
99 112 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
@@ -104,6 +117,8 b' class frameheader(object):'
104 117
105 118 length = attr.ib()
106 119 requestid = attr.ib()
120 streamid = attr.ib()
121 streamflags = attr.ib()
107 122 typeid = attr.ib()
108 123 flags = attr.ib()
109 124
@@ -112,25 +127,29 b' class frame(object):'
112 127 """Represents a parsed frame."""
113 128
114 129 requestid = attr.ib()
130 streamid = attr.ib()
131 streamflags = attr.ib()
115 132 typeid = attr.ib()
116 133 flags = attr.ib()
117 134 payload = attr.ib()
118 135
119 def makeframe(requestid, typeid, flags, payload):
136 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
120 137 """Assemble a frame into a byte array."""
121 138 # TODO assert size of payload.
122 139 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
123 140
124 141 # 24 bits length
125 142 # 16 bits request id
143 # 8 bits stream id
144 # 8 bits stream flags
126 145 # 4 bits type
127 146 # 4 bits flags
128 147
129 148 l = struct.pack(r'<I', len(payload))
130 149 frame[0:3] = l[0:3]
131 struct.pack_into(r'<H', frame, 3, requestid)
132 frame[5] = (typeid << 4) | flags
133 frame[6:] = payload
150 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
151 frame[7] = (typeid << 4) | flags
152 frame[8:] = payload
134 153
135 154 return frame
136 155
@@ -139,20 +158,30 b' def makeframefromhumanstring(s):'
139 158
140 159 Strings have the form:
141 160
142 <request-id> <type> <flags> <payload>
161 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
143 162
144 163 This can be used by user-facing applications and tests for creating
145 164 frames easily without having to type out a bunch of constants.
146 165
147 Request ID is an integer.
166 Request ID and stream IDs are integers.
148 167
149 Frame type and flags can be specified by integer or named constant.
168 Stream flags, frame type, and flags can be specified by integer or
169 named constant.
150 170
151 171 Flags can be delimited by `|` to bitwise OR them together.
152 172 """
153 requestid, frametype, frameflags, payload = s.split(b' ', 3)
173 fields = s.split(b' ', 5)
174 requestid, streamid, streamflags, frametype, frameflags, payload = fields
154 175
155 176 requestid = int(requestid)
177 streamid = int(streamid)
178
179 finalstreamflags = 0
180 for flag in streamflags.split(b'|'):
181 if flag in STREAM_FLAGS:
182 finalstreamflags |= STREAM_FLAGS[flag]
183 else:
184 finalstreamflags |= int(flag)
156 185
157 186 if frametype in FRAME_TYPES:
158 187 frametype = FRAME_TYPES[frametype]
@@ -169,7 +198,8 b' def makeframefromhumanstring(s):'
169 198
170 199 payload = stringutil.unescapestr(payload)
171 200
172 return makeframe(requestid=requestid, typeid=frametype,
201 return makeframe(requestid=requestid, streamid=streamid,
202 streamflags=finalstreamflags, typeid=frametype,
173 203 flags=finalflags, payload=payload)
174 204
175 205 def parseheader(data):
@@ -179,17 +209,21 b' def parseheader(data):'
179 209 buffer is expected to be large enough to hold a full header.
180 210 """
181 211 # 24 bits payload length (little endian)
212 # 16 bits request ID
213 # 8 bits stream ID
214 # 8 bits stream flags
182 215 # 4 bits frame type
183 216 # 4 bits frame flags
184 217 # ... payload
185 218 framelength = data[0] + 256 * data[1] + 16384 * data[2]
186 requestid = struct.unpack_from(r'<H', data, 3)[0]
187 typeflags = data[5]
219 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
220 typeflags = data[7]
188 221
189 222 frametype = (typeflags & 0xf0) >> 4
190 223 frameflags = typeflags & 0x0f
191 224
192 return frameheader(framelength, requestid, frametype, frameflags)
225 return frameheader(framelength, requestid, streamid, streamflags,
226 frametype, frameflags)
193 227
194 228 def readframe(fh):
195 229 """Read a unified framing protocol frame from a file object.
@@ -216,7 +250,8 b' def readframe(fh):'
216 250 raise error.Abort(_('frame length error: expected %d; got %d') %
217 251 (h.length, len(payload)))
218 252
219 return frame(h.requestid, h.typeid, h.flags, payload)
253 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
254 payload)
220 255
221 256 def createcommandframes(stream, requestid, cmd, args, datafh=None):
222 257 """Create frames necessary to transmit a request to run a command.
@@ -398,12 +433,28 b' def createtextoutputframe(stream, reques'
398 433 class stream(object):
399 434 """Represents a logical unidirectional series of frames."""
400 435
436 def __init__(self, streamid, active=False):
437 self.streamid = streamid
438 self._active = False
439
401 440 def makeframe(self, requestid, typeid, flags, payload):
402 441 """Create a frame to be sent out over this stream.
403 442
404 443 Only returns the frame instance. Does not actually send it.
405 444 """
406 return makeframe(requestid, typeid, flags, payload)
445 streamflags = 0
446 if not self._active:
447 streamflags |= STREAM_FLAG_BEGIN_STREAM
448 self._active = True
449
450 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
451 payload)
452
453 def ensureserverstream(stream):
454 if stream.streamid % 2:
455 raise error.ProgrammingError('server should only write to even '
456 'numbered streams; %d is not even' %
457 stream.streamid)
407 458
408 459 class serverreactor(object):
409 460 """Holds state of a server handling frame-based protocol requests.
@@ -483,6 +534,8 b' class serverreactor(object):'
483 534 self._deferoutput = deferoutput
484 535 self._state = 'idle'
485 536 self._bufferedframegens = []
537 # stream id -> stream instance for all active streams from the client.
538 self._incomingstreams = {}
486 539 # request id -> dict of commands that are actively being received.
487 540 self._receivingcommands = {}
488 541 # Request IDs that have been received and are actively being processed.
@@ -496,6 +549,30 b' class serverreactor(object):'
496 549 Returns a dict with an ``action`` key that details what action,
497 550 if any, the consumer should take next.
498 551 """
552 if not frame.streamid % 2:
553 self._state = 'errored'
554 return self._makeerrorresult(
555 _('received frame with even numbered stream ID: %d') %
556 frame.streamid)
557
558 if frame.streamid not in self._incomingstreams:
559 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
560 self._state = 'errored'
561 return self._makeerrorresult(
562 _('received frame on unknown inactive stream without '
563 'beginning of stream flag set'))
564
565 self._incomingstreams[frame.streamid] = stream(frame.streamid)
566
567 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
568 # TODO handle decoding frames
569 self._state = 'errored'
570 raise error.ProgrammingError('support for decoding stream payloads '
571 'not yet implemented')
572
573 if frame.streamflags & STREAM_FLAG_END_STREAM:
574 del self._incomingstreams[frame.streamid]
575
499 576 handlers = {
500 577 'idle': self._onframeidle,
501 578 'command-receiving': self._onframecommandreceiving,
@@ -513,6 +590,8 b' class serverreactor(object):'
513 590
514 591 The raw bytes response is passed as an argument.
515 592 """
593 ensureserverstream(stream)
594
516 595 def sendframes():
517 596 for frame in createbytesresponseframesfrombytes(stream, requestid,
518 597 data):
@@ -552,6 +631,8 b' class serverreactor(object):'
552 631 }
553 632
554 633 def onapplicationerror(self, stream, requestid, msg):
634 ensureserverstream(stream)
635
555 636 return 'sendframes', {
556 637 'framegen': createerrorframe(stream, requestid, msg,
557 638 application=True),
@@ -546,7 +546,7 b' def _httpv2runcommand(ui, repo, req, res'
546 546
547 547 res.status = b'200 OK'
548 548 res.headers[b'Content-Type'] = FRAMINGTYPE
549 stream = wireprotoframing.stream()
549 stream = wireprotoframing.stream(2)
550 550
551 551 if isinstance(rsp, wireprototypes.bytesresponse):
552 552 action, meta = reactor.onbytesresponseready(stream,
@@ -179,7 +179,7 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 1 command-name eos customreadonly
182 > frame 1 1 stream-begin 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
@@ -190,7 +190,7 b' Request to read-only command works out o'
190 190 s> *\r\n (glob)
191 191 s> host: $LOCALIP:$HGPORT\r\n (glob)
192 192 s> \r\n
193 s> \x0e\x00\x00\x01\x00\x11customreadonly
193 s> \x0e\x00\x00\x01\x00\x01\x01\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
@@ -198,8 +198,8 b' Request to read-only command works out o'
198 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> 23\r\n
202 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
201 s> 25\r\n
202 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
203 203 s> \r\n
204 204 s> 0\r\n
205 205 s> \r\n
@@ -290,7 +290,7 b' Authorized request for valid read-write '
290 290 > user-agent: test
291 291 > accept: $MEDIATYPE
292 292 > content-type: $MEDIATYPE
293 > frame 1 command-name eos customreadonly
293 > frame 1 1 stream-begin 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
@@ -298,10 +298,10 b' Authorized request for valid read-write '
298 298 s> accept: application/mercurial-exp-framing-0002\r\n
299 299 s> content-type: application/mercurial-exp-framing-0002\r\n
300 300 s> user-agent: test\r\n
301 s> content-length: 20\r\n
301 s> content-length: 22\r\n
302 302 s> host: $LOCALIP:$HGPORT\r\n (glob)
303 303 s> \r\n
304 s> \x0e\x00\x00\x01\x00\x11customreadonly
304 s> \x0e\x00\x00\x01\x00\x01\x01\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
@@ -309,8 +309,8 b' Authorized request for valid read-write '
309 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> 23\r\n
313 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
312 s> 25\r\n
313 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
314 314 s> \r\n
315 315 s> 0\r\n
316 316 s> \r\n
@@ -382,9 +382,9 b' Command frames can be reflected via debu'
382 382 > accept: $MEDIATYPE
383 383 > content-type: $MEDIATYPE
384 384 > user-agent: test
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
385 > frame 1 1 stream-begin command-name have-args command1
386 > frame 1 1 0 command-argument 0 \x03\x00\x04\x00fooval1
387 > frame 1 1 0 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
@@ -392,10 +392,10 b' Command frames can be reflected via debu'
392 392 s> accept: application/mercurial-exp-framing-0002\r\n
393 393 s> content-type: application/mercurial-exp-framing-0002\r\n
394 394 s> user-agent: test\r\n
395 s> content-length: 48\r\n
395 s> content-length: 54\r\n
396 396 s> host: $LOCALIP:$HGPORT\r\n (glob)
397 397 s> \r\n
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
398 s> \x08\x00\x00\x01\x00\x01\x01\x12command1\x0b\x00\x00\x01\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\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
@@ -419,8 +419,8 b' Multiple requests to regular command URL'
419 419 > accept: $MEDIATYPE
420 420 > content-type: $MEDIATYPE
421 421 > user-agent: test
422 > frame 1 command-name eos customreadonly
423 > frame 3 command-name eos customreadonly
422 > frame 1 1 stream-begin command-name eos customreadonly
423 > frame 3 1 0 command-name eos customreadonly
424 424 > EOF
425 425 using raw connection to peer
426 426 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
@@ -428,10 +428,10 b' Multiple requests to regular command URL'
428 428 s> accept: application/mercurial-exp-framing-0002\r\n
429 429 s> content-type: application/mercurial-exp-framing-0002\r\n
430 430 s> user-agent: test\r\n
431 s> content-length: 40\r\n
431 s> content-length: 44\r\n
432 432 s> host: $LOCALIP:$HGPORT\r\n (glob)
433 433 s> \r\n
434 s> \x0e\x00\x00\x01\x00\x11customreadonly\x0e\x00\x00\x03\x00\x11customreadonly
434 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
435 435 s> makefile('rb', None)
436 436 s> HTTP/1.1 200 OK\r\n
437 437 s> Server: testing stub value\r\n
@@ -448,8 +448,8 b' Multiple requests to "multirequest" URL '
448 448 > accept: $MEDIATYPE
449 449 > content-type: $MEDIATYPE
450 450 > user-agent: test
451 > frame 1 command-name eos customreadonly
452 > frame 3 command-name eos customreadonly
451 > frame 1 1 stream-begin command-name eos customreadonly
452 > frame 3 1 0 command-name eos customreadonly
453 453 > EOF
454 454 using raw connection to peer
455 455 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
@@ -460,7 +460,7 b' Multiple requests to "multirequest" URL '
460 460 s> *\r\n (glob)
461 461 s> host: $LOCALIP:$HGPORT\r\n (glob)
462 462 s> \r\n
463 s> \x0e\x00\x00\x01\x00\x11customreadonly\x0e\x00\x00\x03\x00\x11customreadonly
463 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
464 464 s> makefile('rb', None)
465 465 s> HTTP/1.1 200 OK\r\n
466 466 s> Server: testing stub value\r\n
@@ -469,10 +469,10 b' Multiple requests to "multirequest" URL '
469 469 s> Transfer-Encoding: chunked\r\n
470 470 s> \r\n
471 471 s> *\r\n (glob)
472 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
472 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
473 473 s> \r\n
474 s> 23\r\n
475 s> \x1d\x00\x00\x03\x00Bcustomreadonly bytes response
474 s> 25\r\n
475 s> \x1d\x00\x00\x03\x00\x02\x01Bcustomreadonly bytes response
476 476 s> \r\n
477 477 s> 0\r\n
478 478 s> \r\n
@@ -484,10 +484,10 b' Interleaved requests to "multirequest" a'
484 484 > accept: $MEDIATYPE
485 485 > content-type: $MEDIATYPE
486 486 > user-agent: test
487 > frame 1 command-name have-args listkeys
488 > frame 3 command-name have-args listkeys
489 > frame 3 command-argument eoa \x09\x00\x09\x00namespacebookmarks
490 > frame 1 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
487 > frame 1 1 stream-begin command-name have-args listkeys
488 > frame 3 1 0 command-name have-args listkeys
489 > frame 3 1 0 command-argument eoa \x09\x00\x09\x00namespacebookmarks
490 > frame 1 1 0 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
491 491 > EOF
492 492 using raw connection to peer
493 493 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
@@ -495,10 +495,10 b' Interleaved requests to "multirequest" a'
495 495 s> accept: application/mercurial-exp-framing-0002\r\n
496 496 s> content-type: application/mercurial-exp-framing-0002\r\n
497 497 s> user-agent: test\r\n
498 s> content-length: 85\r\n
498 s> content-length: 93\r\n
499 499 s> host: $LOCALIP:$HGPORT\r\n (glob)
500 500 s> \r\n
501 s> \x08\x00\x00\x01\x00\x12listkeys\x08\x00\x00\x03\x00\x12listkeys\x16\x00\x00\x03\x00" \x00 \x00namespacebookmarks\x17\x00\x00\x01\x00" \x00\n
501 s> \x08\x00\x00\x01\x00\x01\x01\x12listkeys\x08\x00\x00\x03\x00\x01\x00\x12listkeys\x16\x00\x00\x03\x00\x01\x00" \x00 \x00namespacebookmarks\x17\x00\x00\x01\x00\x01\x00" \x00\n
502 502 s> \x00namespacenamespaces
503 503 s> makefile('rb', None)
504 504 s> HTTP/1.1 200 OK\r\n
@@ -507,11 +507,11 b' Interleaved requests to "multirequest" a'
507 507 s> Content-Type: application/mercurial-exp-framing-0002\r\n
508 508 s> Transfer-Encoding: chunked\r\n
509 509 s> \r\n
510 s> 6\r\n
511 s> \x00\x00\x00\x03\x00B
510 s> 8\r\n
511 s> \x00\x00\x00\x03\x00\x02\x01B
512 512 s> \r\n
513 s> 24\r\n
514 s> \x1e\x00\x00\x01\x00Bbookmarks \n
513 s> 26\r\n
514 s> \x1e\x00\x00\x01\x00\x02\x01Bbookmarks \n
515 515 s> namespaces \n
516 516 s> phases
517 517 s> \r\n
@@ -540,7 +540,7 b' Attempting to run a read-write command v'
540 540 > accept: $MEDIATYPE
541 541 > content-type: $MEDIATYPE
542 542 > user-agent: test
543 > frame 1 command-name eos unbundle
543 > frame 1 1 stream-begin command-name eos unbundle
544 544 > EOF
545 545 using raw connection to peer
546 546 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
@@ -548,10 +548,10 b' Attempting to run a read-write command v'
548 548 s> accept: application/mercurial-exp-framing-0002\r\n
549 549 s> content-type: application/mercurial-exp-framing-0002\r\n
550 550 s> user-agent: test\r\n
551 s> content-length: 14\r\n
551 s> content-length: 16\r\n
552 552 s> host: $LOCALIP:$HGPORT\r\n (glob)
553 553 s> \r\n
554 s> \x08\x00\x00\x01\x00\x11unbundle
554 s> \x08\x00\x00\x01\x00\x01\x01\x11unbundle
555 555 s> makefile('rb', None)
556 556 s> HTTP/1.1 403 Forbidden\r\n
557 557 s> Server: testing stub value\r\n
@@ -23,6 +23,8 b' def sendframes(reactor, gen):'
23 23 assert len(payload) == header.length
24 24
25 25 yield reactor.onframerecv(framing.frame(header.requestid,
26 header.streamid,
27 header.streamflags,
26 28 header.typeid,
27 29 header.flags,
28 30 payload))
@@ -37,32 +39,32 b' class FrameTests(unittest.TestCase):'
37 39 def testdataexactframesize(self):
38 40 data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE)
39 41
40 stream = framing.stream()
42 stream = framing.stream(1)
41 43 frames = list(framing.createcommandframes(stream, 1, b'command',
42 44 {}, data))
43 45 self.assertEqual(frames, [
44 ffs(b'1 command-name have-data command'),
45 ffs(b'1 command-data continuation %s' % data.getvalue()),
46 ffs(b'1 command-data eos ')
46 ffs(b'1 1 stream-begin command-name have-data command'),
47 ffs(b'1 1 0 command-data continuation %s' % data.getvalue()),
48 ffs(b'1 1 0 command-data eos ')
47 49 ])
48 50
49 51 def testdatamultipleframes(self):
50 52 data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1))
51 53
52 stream = framing.stream()
54 stream = framing.stream(1)
53 55 frames = list(framing.createcommandframes(stream, 1, b'command', {},
54 56 data))
55 57 self.assertEqual(frames, [
56 ffs(b'1 command-name have-data command'),
57 ffs(b'1 command-data continuation %s' % (
58 ffs(b'1 1 stream-begin command-name have-data command'),
59 ffs(b'1 1 0 command-data continuation %s' % (
58 60 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
59 ffs(b'1 command-data eos x'),
61 ffs(b'1 1 0 command-data eos x'),
60 62 ])
61 63
62 64 def testargsanddata(self):
63 65 data = util.bytesio(b'x' * 100)
64 66
65 stream = framing.stream()
67 stream = framing.stream(1)
66 68 frames = list(framing.createcommandframes(stream, 1, b'command', {
67 69 b'key1': b'key1value',
68 70 b'key2': b'key2value',
@@ -70,11 +72,11 b' class FrameTests(unittest.TestCase):'
70 72 }, data))
71 73
72 74 self.assertEqual(frames, [
73 ffs(b'1 command-name have-args|have-data command'),
74 ffs(br'1 command-argument 0 \x04\x00\x09\x00key1key1value'),
75 ffs(br'1 command-argument 0 \x04\x00\x09\x00key2key2value'),
76 ffs(br'1 command-argument eoa \x04\x00\x09\x00key3key3value'),
77 ffs(b'1 command-data eos %s' % data.getvalue()),
75 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
76 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key1key1value'),
77 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key2key2value'),
78 ffs(br'1 1 0 command-argument eoa \x04\x00\x09\x00key3key3value'),
79 ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
78 80 ])
79 81
80 82 def testtextoutputexcessiveargs(self):
@@ -128,64 +130,68 b' class FrameTests(unittest.TestCase):'
128 130 (b'bleh', [], [b'x' * 65536])]))
129 131
130 132 def testtextoutput1simpleatom(self):
131 stream = framing.stream()
133 stream = framing.stream(1)
132 134 val = list(framing.createtextoutputframe(stream, 1, [
133 135 (b'foo', [], [])]))
134 136
135 137 self.assertEqual(val, [
136 ffs(br'1 text-output 0 \x03\x00\x00\x00foo'),
138 ffs(br'1 1 stream-begin text-output 0 \x03\x00\x00\x00foo'),
137 139 ])
138 140
139 141 def testtextoutput2simpleatoms(self):
140 stream = framing.stream()
142 stream = framing.stream(1)
141 143 val = list(framing.createtextoutputframe(stream, 1, [
142 144 (b'foo', [], []),
143 145 (b'bar', [], []),
144 146 ]))
145 147
146 148 self.assertEqual(val, [
147 ffs(br'1 text-output 0 \x03\x00\x00\x00foo\x03\x00\x00\x00bar'),
149 ffs(br'1 1 stream-begin text-output 0 '
150 br'\x03\x00\x00\x00foo\x03\x00\x00\x00bar'),
148 151 ])
149 152
150 153 def testtextoutput1arg(self):
151 stream = framing.stream()
154 stream = framing.stream(1)
152 155 val = list(framing.createtextoutputframe(stream, 1, [
153 156 (b'foo %s', [b'val1'], []),
154 157 ]))
155 158
156 159 self.assertEqual(val, [
157 ffs(br'1 text-output 0 \x06\x00\x00\x01\x04\x00foo %sval1'),
160 ffs(br'1 1 stream-begin text-output 0 '
161 br'\x06\x00\x00\x01\x04\x00foo %sval1'),
158 162 ])
159 163
160 164 def testtextoutput2arg(self):
161 stream = framing.stream()
165 stream = framing.stream(1)
162 166 val = list(framing.createtextoutputframe(stream, 1, [
163 167 (b'foo %s %s', [b'val', b'value'], []),
164 168 ]))
165 169
166 170 self.assertEqual(val, [
167 ffs(br'1 text-output 0 \x09\x00\x00\x02\x03\x00\x05\x00'
168 br'foo %s %svalvalue'),
171 ffs(br'1 1 stream-begin text-output 0 '
172 br'\x09\x00\x00\x02\x03\x00\x05\x00foo %s %svalvalue'),
169 173 ])
170 174
171 175 def testtextoutput1label(self):
172 stream = framing.stream()
176 stream = framing.stream(1)
173 177 val = list(framing.createtextoutputframe(stream, 1, [
174 178 (b'foo', [], [b'label']),
175 179 ]))
176 180
177 181 self.assertEqual(val, [
178 ffs(br'1 text-output 0 \x03\x00\x01\x00\x05foolabel'),
182 ffs(br'1 1 stream-begin text-output 0 '
183 br'\x03\x00\x01\x00\x05foolabel'),
179 184 ])
180 185
181 186 def testargandlabel(self):
182 stream = framing.stream()
187 stream = framing.stream(1)
183 188 val = list(framing.createtextoutputframe(stream, 1, [
184 189 (b'foo %s', [b'arg'], [b'label']),
185 190 ]))
186 191
187 192 self.assertEqual(val, [
188 ffs(br'1 text-output 0 \x06\x00\x01\x01\x05\x03\x00foo %slabelarg'),
193 ffs(br'1 1 stream-begin text-output 0 '
194 br'\x06\x00\x01\x01\x05\x03\x00foo %slabelarg'),
189 195 ])
190 196
191 197 class ServerReactorTests(unittest.TestCase):
@@ -208,7 +214,7 b' class ServerReactorTests(unittest.TestCa'
208 214 def test1framecommand(self):
209 215 """Receiving a command in a single frame yields request to run it."""
210 216 reactor = makereactor()
211 stream = framing.stream()
217 stream = framing.stream(1)
212 218 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
213 219 self.assertEqual(len(results), 1)
214 220 self.assertaction(results[0], 'runcommand')
@@ -224,7 +230,7 b' class ServerReactorTests(unittest.TestCa'
224 230
225 231 def test1argument(self):
226 232 reactor = makereactor()
227 stream = framing.stream()
233 stream = framing.stream(1)
228 234 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
229 235 {b'foo': b'bar'}))
230 236 self.assertEqual(len(results), 2)
@@ -239,7 +245,7 b' class ServerReactorTests(unittest.TestCa'
239 245
240 246 def testmultiarguments(self):
241 247 reactor = makereactor()
242 stream = framing.stream()
248 stream = framing.stream(1)
243 249 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
244 250 {b'foo': b'bar', b'biz': b'baz'}))
245 251 self.assertEqual(len(results), 3)
@@ -255,7 +261,7 b' class ServerReactorTests(unittest.TestCa'
255 261
256 262 def testsimplecommanddata(self):
257 263 reactor = makereactor()
258 stream = framing.stream()
264 stream = framing.stream(1)
259 265 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
260 266 util.bytesio(b'data!')))
261 267 self.assertEqual(len(results), 2)
@@ -270,10 +276,10 b' class ServerReactorTests(unittest.TestCa'
270 276
271 277 def testmultipledataframes(self):
272 278 frames = [
273 ffs(b'1 command-name have-data mycommand'),
274 ffs(b'1 command-data continuation data1'),
275 ffs(b'1 command-data continuation data2'),
276 ffs(b'1 command-data eos data3'),
279 ffs(b'1 1 stream-begin command-name have-data mycommand'),
280 ffs(b'1 1 0 command-data continuation data1'),
281 ffs(b'1 1 0 command-data continuation data2'),
282 ffs(b'1 1 0 command-data eos data3'),
277 283 ]
278 284
279 285 reactor = makereactor()
@@ -291,11 +297,11 b' class ServerReactorTests(unittest.TestCa'
291 297
292 298 def testargumentanddata(self):
293 299 frames = [
294 ffs(b'1 command-name have-args|have-data command'),
295 ffs(br'1 command-argument 0 \x03\x00\x03\x00keyval'),
296 ffs(br'1 command-argument eoa \x03\x00\x03\x00foobar'),
297 ffs(b'1 command-data continuation value1'),
298 ffs(b'1 command-data eos value2'),
300 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
301 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00keyval'),
302 ffs(br'1 1 0 command-argument eoa \x03\x00\x03\x00foobar'),
303 ffs(b'1 1 0 command-data continuation value1'),
304 ffs(b'1 1 0 command-data eos value2'),
299 305 ]
300 306
301 307 reactor = makereactor()
@@ -314,8 +320,8 b' class ServerReactorTests(unittest.TestCa'
314 320
315 321 def testunexpectedcommandargument(self):
316 322 """Command argument frame when not running a command is an error."""
317 result = self._sendsingleframe(makereactor(),
318 ffs(b'1 command-argument 0 ignored'))
323 result = self._sendsingleframe(
324 makereactor(), ffs(b'1 1 stream-begin command-argument 0 ignored'))
319 325 self.assertaction(result, 'error')
320 326 self.assertEqual(result[1], {
321 327 'message': b'expected command frame; got 2',
@@ -324,8 +330,8 b' class ServerReactorTests(unittest.TestCa'
324 330 def testunexpectedcommandargumentreceiving(self):
325 331 """Same as above but the command is receiving."""
326 332 results = list(sendframes(makereactor(), [
327 ffs(b'1 command-name have-data command'),
328 ffs(b'1 command-argument eoa ignored'),
333 ffs(b'1 1 stream-begin command-name have-data command'),
334 ffs(b'1 1 0 command-argument eoa ignored'),
329 335 ]))
330 336
331 337 self.assertaction(results[1], 'error')
@@ -336,8 +342,8 b' class ServerReactorTests(unittest.TestCa'
336 342
337 343 def testunexpectedcommanddata(self):
338 344 """Command argument frame when not running a command is an error."""
339 result = self._sendsingleframe(makereactor(),
340 ffs(b'1 command-data 0 ignored'))
345 result = self._sendsingleframe(
346 makereactor(), ffs(b'1 1 stream-begin command-data 0 ignored'))
341 347 self.assertaction(result, 'error')
342 348 self.assertEqual(result[1], {
343 349 'message': b'expected command frame; got 3',
@@ -346,8 +352,8 b' class ServerReactorTests(unittest.TestCa'
346 352 def testunexpectedcommanddatareceiving(self):
347 353 """Same as above except the command is receiving."""
348 354 results = list(sendframes(makereactor(), [
349 ffs(b'1 command-name have-args command'),
350 ffs(b'1 command-data eos ignored'),
355 ffs(b'1 1 stream-begin command-name have-args command'),
356 ffs(b'1 1 0 command-data eos ignored'),
351 357 ]))
352 358
353 359 self.assertaction(results[1], 'error')
@@ -358,8 +364,8 b' class ServerReactorTests(unittest.TestCa'
358 364
359 365 def testmissingcommandframeflags(self):
360 366 """Command name frame must have flags set."""
361 result = self._sendsingleframe(makereactor(),
362 ffs(b'1 command-name 0 command'))
367 result = self._sendsingleframe(
368 makereactor(), ffs(b'1 1 stream-begin command-name 0 command'))
363 369 self.assertaction(result, 'error')
364 370 self.assertEqual(result[1], {
365 371 'message': b'missing frame flags on command frame',
@@ -369,19 +375,19 b' class ServerReactorTests(unittest.TestCa'
369 375 """Multiple fully serviced commands with same request ID is allowed."""
370 376 reactor = makereactor()
371 377 results = []
372 outstream = framing.stream()
378 outstream = framing.stream(2)
373 379 results.append(self._sendsingleframe(
374 reactor, ffs(b'1 command-name eos command')))
380 reactor, ffs(b'1 1 stream-begin command-name eos command')))
375 381 result = reactor.onbytesresponseready(outstream, 1, b'response1')
376 382 self.assertaction(result, 'sendframes')
377 383 list(result[1]['framegen'])
378 384 results.append(self._sendsingleframe(
379 reactor, ffs(b'1 command-name eos command')))
385 reactor, ffs(b'1 1 0 command-name eos command')))
380 386 result = reactor.onbytesresponseready(outstream, 1, b'response2')
381 387 self.assertaction(result, 'sendframes')
382 388 list(result[1]['framegen'])
383 389 results.append(self._sendsingleframe(
384 reactor, ffs(b'1 command-name eos command')))
390 reactor, ffs(b'1 1 0 command-name eos command')))
385 391 result = reactor.onbytesresponseready(outstream, 1, b'response3')
386 392 self.assertaction(result, 'sendframes')
387 393 list(result[1]['framegen'])
@@ -398,8 +404,8 b' class ServerReactorTests(unittest.TestCa'
398 404 def testconflictingrequestid(self):
399 405 """Request ID for new command matching in-flight command is illegal."""
400 406 results = list(sendframes(makereactor(), [
401 ffs(b'1 command-name have-args command'),
402 ffs(b'1 command-name eos command'),
407 ffs(b'1 1 stream-begin command-name have-args command'),
408 ffs(b'1 1 0 command-name eos command'),
403 409 ]))
404 410
405 411 self.assertaction(results[0], 'wantframe')
@@ -410,12 +416,12 b' class ServerReactorTests(unittest.TestCa'
410 416
411 417 def testinterleavedcommands(self):
412 418 results = list(sendframes(makereactor(), [
413 ffs(b'1 command-name have-args command1'),
414 ffs(b'3 command-name have-args command3'),
415 ffs(br'1 command-argument 0 \x03\x00\x03\x00foobar'),
416 ffs(br'3 command-argument 0 \x03\x00\x03\x00bizbaz'),
417 ffs(br'3 command-argument eoa \x03\x00\x03\x00keyval'),
418 ffs(br'1 command-argument eoa \x04\x00\x03\x00key1val'),
419 ffs(b'1 1 stream-begin command-name have-args command1'),
420 ffs(b'3 1 0 command-name have-args command3'),
421 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00foobar'),
422 ffs(br'3 1 0 command-argument 0 \x03\x00\x03\x00bizbaz'),
423 ffs(br'3 1 0 command-argument eoa \x03\x00\x03\x00keyval'),
424 ffs(br'1 1 0 command-argument eoa \x04\x00\x03\x00key1val'),
419 425 ]))
420 426
421 427 self.assertEqual([t[0] for t in results], [
@@ -445,7 +451,7 b' class ServerReactorTests(unittest.TestCa'
445 451 # command request waiting on argument data. But it doesn't handle that
446 452 # scenario yet. So this test does nothing of value.
447 453 frames = [
448 ffs(b'1 command-name have-args command'),
454 ffs(b'1 1 stream-begin command-name have-args command'),
449 455 ]
450 456
451 457 results = list(sendframes(makereactor(), frames))
@@ -454,8 +460,8 b' class ServerReactorTests(unittest.TestCa'
454 460 def testincompleteargumentname(self):
455 461 """Argument frame with incomplete name."""
456 462 frames = [
457 ffs(b'1 command-name have-args command1'),
458 ffs(br'1 command-argument eoa \x04\x00\xde\xadfoo'),
463 ffs(b'1 1 stream-begin command-name have-args command1'),
464 ffs(br'1 1 0 command-argument eoa \x04\x00\xde\xadfoo'),
459 465 ]
460 466
461 467 results = list(sendframes(makereactor(), frames))
@@ -469,8 +475,8 b' class ServerReactorTests(unittest.TestCa'
469 475 def testincompleteargumentvalue(self):
470 476 """Argument frame with incomplete value."""
471 477 frames = [
472 ffs(b'1 command-name have-args command'),
473 ffs(br'1 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
478 ffs(b'1 1 stream-begin command-name have-args command'),
479 ffs(br'1 1 0 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
474 480 ]
475 481
476 482 results = list(sendframes(makereactor(), frames))
@@ -485,8 +491,8 b' class ServerReactorTests(unittest.TestCa'
485 491 # The reactor doesn't currently handle partially received commands.
486 492 # So this test is failing to do anything with request 1.
487 493 frames = [
488 ffs(b'1 command-name have-data command1'),
489 ffs(b'3 command-name eos command2'),
494 ffs(b'1 1 stream-begin command-name have-data command1'),
495 ffs(b'3 1 0 command-name eos command2'),
490 496 ]
491 497 results = list(sendframes(makereactor(), frames))
492 498 self.assertEqual(len(results), 2)
@@ -495,8 +501,8 b' class ServerReactorTests(unittest.TestCa'
495 501
496 502 def testmissingcommanddataframeflags(self):
497 503 frames = [
498 ffs(b'1 command-name have-data command1'),
499 ffs(b'1 command-data 0 data'),
504 ffs(b'1 1 stream-begin command-name have-data command1'),
505 ffs(b'1 1 0 command-data 0 data'),
500 506 ]
501 507 results = list(sendframes(makereactor(), frames))
502 508 self.assertEqual(len(results), 2)
@@ -509,9 +515,9 b' class ServerReactorTests(unittest.TestCa'
509 515 def testframefornonreceivingrequest(self):
510 516 """Receiving a frame for a command that is not receiving is illegal."""
511 517 results = list(sendframes(makereactor(), [
512 ffs(b'1 command-name eos command1'),
513 ffs(b'3 command-name have-data command3'),
514 ffs(b'5 command-argument eoa ignored'),
518 ffs(b'1 1 stream-begin command-name eos command1'),
519 ffs(b'3 1 0 command-name have-data command3'),
520 ffs(b'5 1 0 command-argument eoa ignored'),
515 521 ]))
516 522 self.assertaction(results[2], 'error')
517 523 self.assertEqual(results[2][1], {
@@ -521,14 +527,14 b' class ServerReactorTests(unittest.TestCa'
521 527 def testsimpleresponse(self):
522 528 """Bytes response to command sends result frames."""
523 529 reactor = makereactor()
524 instream = framing.stream()
530 instream = framing.stream(1)
525 531 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
526 532
527 outstream = framing.stream()
533 outstream = framing.stream(2)
528 534 result = reactor.onbytesresponseready(outstream, 1, b'response')
529 535 self.assertaction(result, 'sendframes')
530 536 self.assertframesequal(result[1]['framegen'], [
531 b'1 bytes-response eos response',
537 b'1 2 stream-begin bytes-response eos response',
532 538 ])
533 539
534 540 def testmultiframeresponse(self):
@@ -537,54 +543,54 b' class ServerReactorTests(unittest.TestCa'
537 543 second = b'y' * 100
538 544
539 545 reactor = makereactor()
540 instream = framing.stream()
546 instream = framing.stream(1)
541 547 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
542 548
543 outstream = framing.stream()
549 outstream = framing.stream(2)
544 550 result = reactor.onbytesresponseready(outstream, 1, first + second)
545 551 self.assertaction(result, 'sendframes')
546 552 self.assertframesequal(result[1]['framegen'], [
547 b'1 bytes-response continuation %s' % first,
548 b'1 bytes-response eos %s' % second,
553 b'1 2 stream-begin bytes-response continuation %s' % first,
554 b'1 2 0 bytes-response eos %s' % second,
549 555 ])
550 556
551 557 def testapplicationerror(self):
552 558 reactor = makereactor()
553 instream = framing.stream()
559 instream = framing.stream(1)
554 560 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
555 561
556 outstream = framing.stream()
562 outstream = framing.stream(2)
557 563 result = reactor.onapplicationerror(outstream, 1, b'some message')
558 564 self.assertaction(result, 'sendframes')
559 565 self.assertframesequal(result[1]['framegen'], [
560 b'1 error-response application some message',
566 b'1 2 stream-begin error-response application some message',
561 567 ])
562 568
563 569 def test1commanddeferresponse(self):
564 570 """Responses when in deferred output mode are delayed until EOF."""
565 571 reactor = makereactor(deferoutput=True)
566 instream = framing.stream()
572 instream = framing.stream(1)
567 573 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
568 574 {}))
569 575 self.assertEqual(len(results), 1)
570 576 self.assertaction(results[0], 'runcommand')
571 577
572 outstream = framing.stream()
578 outstream = framing.stream(2)
573 579 result = reactor.onbytesresponseready(outstream, 1, b'response')
574 580 self.assertaction(result, 'noop')
575 581 result = reactor.oninputeof()
576 582 self.assertaction(result, 'sendframes')
577 583 self.assertframesequal(result[1]['framegen'], [
578 b'1 bytes-response eos response',
584 b'1 2 stream-begin bytes-response eos response',
579 585 ])
580 586
581 587 def testmultiplecommanddeferresponse(self):
582 588 reactor = makereactor(deferoutput=True)
583 instream = framing.stream()
589 instream = framing.stream(1)
584 590 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
585 591 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
586 592
587 outstream = framing.stream()
593 outstream = framing.stream(2)
588 594 result = reactor.onbytesresponseready(outstream, 1, b'response1')
589 595 self.assertaction(result, 'noop')
590 596 result = reactor.onbytesresponseready(outstream, 3, b'response2')
@@ -592,19 +598,19 b' class ServerReactorTests(unittest.TestCa'
592 598 result = reactor.oninputeof()
593 599 self.assertaction(result, 'sendframes')
594 600 self.assertframesequal(result[1]['framegen'], [
595 b'1 bytes-response eos response1',
596 b'3 bytes-response eos response2'
601 b'1 2 stream-begin bytes-response eos response1',
602 b'3 2 0 bytes-response eos response2'
597 603 ])
598 604
599 605 def testrequestidtracking(self):
600 606 reactor = makereactor(deferoutput=True)
601 instream = framing.stream()
607 instream = framing.stream(1)
602 608 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
603 609 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
604 610 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
605 611
606 612 # Register results for commands out of order.
607 outstream = framing.stream()
613 outstream = framing.stream(2)
608 614 reactor.onbytesresponseready(outstream, 3, b'response3')
609 615 reactor.onbytesresponseready(outstream, 1, b'response1')
610 616 reactor.onbytesresponseready(outstream, 5, b'response5')
@@ -612,15 +618,15 b' class ServerReactorTests(unittest.TestCa'
612 618 result = reactor.oninputeof()
613 619 self.assertaction(result, 'sendframes')
614 620 self.assertframesequal(result[1]['framegen'], [
615 b'3 bytes-response eos response3',
616 b'1 bytes-response eos response1',
617 b'5 bytes-response eos response5',
621 b'3 2 stream-begin bytes-response eos response3',
622 b'1 2 0 bytes-response eos response1',
623 b'5 2 0 bytes-response eos response5',
618 624 ])
619 625
620 626 def testduplicaterequestonactivecommand(self):
621 627 """Receiving a request ID that matches a request that isn't finished."""
622 628 reactor = makereactor()
623 stream = framing.stream()
629 stream = framing.stream(1)
624 630 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
625 631 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
626 632
@@ -632,9 +638,9 b' class ServerReactorTests(unittest.TestCa'
632 638 def testduplicaterequestonactivecommandnosend(self):
633 639 """Same as above but we've registered a response but haven't sent it."""
634 640 reactor = makereactor()
635 instream = framing.stream()
641 instream = framing.stream(1)
636 642 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
637 outstream = framing.stream()
643 outstream = framing.stream(2)
638 644 reactor.onbytesresponseready(outstream, 1, b'response')
639 645
640 646 # We've registered the response but haven't sent it. From the
@@ -649,11 +655,11 b' class ServerReactorTests(unittest.TestCa'
649 655 def testduplicaterequestargumentframe(self):
650 656 """Variant on above except we sent an argument frame instead of name."""
651 657 reactor = makereactor()
652 stream = framing.stream()
658 stream = framing.stream(1)
653 659 list(sendcommandframes(reactor, stream, 1, b'command', {}))
654 660 results = list(sendframes(reactor, [
655 ffs(b'3 command-name have-args command'),
656 ffs(b'1 command-argument 0 ignored'),
661 ffs(b'3 1 stream-begin command-name have-args command'),
662 ffs(b'1 1 0 command-argument 0 ignored'),
657 663 ]))
658 664 self.assertaction(results[0], 'wantframe')
659 665 self.assertaction(results[1], 'error')
@@ -664,9 +670,9 b' class ServerReactorTests(unittest.TestCa'
664 670 def testduplicaterequestaftersend(self):
665 671 """We can use a duplicate request ID after we've sent the response."""
666 672 reactor = makereactor()
667 instream = framing.stream()
673 instream = framing.stream(1)
668 674 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
669 outstream = framing.stream()
675 outstream = framing.stream(2)
670 676 res = reactor.onbytesresponseready(outstream, 1, b'response')
671 677 list(res[1]['framegen'])
672 678
General Comments 0
You need to be logged in to leave comments. Login now