##// 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 syntax.
2777 syntax.
2778
2778
2779 A frame is composed as a type, flags, and payload. These can be parsed
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,
2780 from a string of the form:
2781 4 space-delimited strings.
2781
2782
2782 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
2783 ``payload`` is the simplest: it is evaluated as a Python byte string
2783
2784 literal.
2784 ``request-id`` and ``stream-id`` are integers defining the request and
2785
2785 stream identifiers.
2786 ``requestid`` is an integer defining the request identifier.
2787
2786
2788 ``type`` can be an integer value for the frame type or the string name
2787 ``type`` can be an integer value for the frame type or the string name
2789 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2788 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2790 ``command-name``.
2789 ``command-name``.
2791
2790
2792 ``flags`` is a ``|`` delimited list of flag components. Each component
2791 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
2793 (and there can be just one) can be an integer or a flag name for the
2792 components. Each component (and there can be just one) can be an integer
2794 specified frame type. Values are resolved to integers and then bitwise
2793 or a flag name for stream flags or frame flags, respectively. Values are
2795 OR'd together.
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 opts = pycompat.byteskwargs(opts)
2798 opts = pycompat.byteskwargs(opts)
2798
2799
@@ -489,28 +489,37 b' To operate the protocol, a bi-directiona'
489 ordered sends and receives is required. That is, each peer has one pipe
489 ordered sends and receives is required. That is, each peer has one pipe
490 for sending data and another for receiving.
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 The protocol is request-response based: the client issues requests to
501 The protocol is request-response based: the client issues requests to
493 the server, which issues replies to those requests. Server-initiated
502 the server, which issues replies to those requests. Server-initiated
494 messaging is not currently supported, but this specification carves
503 messaging is not currently supported, but this specification carves
495 out room to implement it.
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 All frames are associated with a numbered request. Frames can thus
506 All frames are associated with a numbered request. Frames can thus
502 be logically grouped by their request ID.
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 payload::
510 payload::
506
511
507 +-----------------------------------------------+
512 +------------------------------------------------+
508 | Length (24) |
513 | Length (24) |
509 +---------------------------------+-------------+
514 +--------------------------------+---------------+
510 | Request ID (16) |
515 | Request ID (16) | Stream ID (8) |
511 +----------+-----------+----------+
516 +------------------+-------------+---------------+
512 | Type (4) | Flags (4) |
517 | Stream Flags (8) |
513 +==========+===========+========================================|
518 +-----------+------+
519 | Type (4) |
520 +-----------+
521 | Flags (4) |
522 +===========+===================================================|
514 | Frame Payload (0...) ...
523 | Frame Payload (0...) ...
515 +---------------------------------------------------------------+
524 +---------------------------------------------------------------+
516
525
@@ -518,7 +527,9 b' The length of the frame payload is expre'
518 little endian integer. Values larger than 65535 MUST NOT be used unless
527 little endian integer. Values larger than 65535 MUST NOT be used unless
519 given permission by the server as part of the negotiated capabilities
528 given permission by the server as part of the negotiated capabilities
520 during the handshake. The frame header is not part of the advertised
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 The 16-bit ``Request ID`` field denotes the integer request identifier,
534 The 16-bit ``Request ID`` field denotes the integer request identifier,
524 stored as an unsigned little endian integer. Odd numbered requests are
535 stored as an unsigned little endian integer. Odd numbered requests are
@@ -529,7 +540,16 b' response to client-initiated requests. I'
529 start ordering request identifiers at ``1`` and ``0``, increment by
540 start ordering request identifiers at ``1`` and ``0``, increment by
530 ``2``, and wrap around if all available numbers have been exhausted.
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 The 4-bit ``Flags`` field defines special, per-type attributes for
554 The 4-bit ``Flags`` field defines special, per-type attributes for
535 the frame.
555 the frame.
@@ -720,6 +740,126 b' All textual data encoded in these frames'
720 The last atom in the frame SHOULD end with a newline (``\n``). If it
740 The last atom in the frame SHOULD end with a newline (``\n``). If it
721 doesn't, clients MAY add a newline to facilitate immediate printing.
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 Issuing Commands
863 Issuing Commands
724 ----------------
864 ----------------
725
865
@@ -25,15 +25,26 b' from .utils import ('
25 stringutil,
25 stringutil,
26 )
26 )
27
27
28 FRAME_HEADER_SIZE = 6
28 FRAME_HEADER_SIZE = 8
29 DEFAULT_MAX_FRAME_SIZE = 32768
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 FRAME_TYPE_COMMAND_NAME = 0x01
41 FRAME_TYPE_COMMAND_NAME = 0x01
32 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
42 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
33 FRAME_TYPE_COMMAND_DATA = 0x03
43 FRAME_TYPE_COMMAND_DATA = 0x03
34 FRAME_TYPE_BYTES_RESPONSE = 0x04
44 FRAME_TYPE_BYTES_RESPONSE = 0x04
35 FRAME_TYPE_ERROR_RESPONSE = 0x05
45 FRAME_TYPE_ERROR_RESPONSE = 0x05
36 FRAME_TYPE_TEXT_OUTPUT = 0x06
46 FRAME_TYPE_TEXT_OUTPUT = 0x06
47 FRAME_TYPE_STREAM_SETTINGS = 0x08
37
48
38 FRAME_TYPES = {
49 FRAME_TYPES = {
39 b'command-name': FRAME_TYPE_COMMAND_NAME,
50 b'command-name': FRAME_TYPE_COMMAND_NAME,
@@ -42,6 +53,7 b' FRAME_TYPES = {'
42 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
53 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
43 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
54 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
44 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
55 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
56 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
45 }
57 }
46
58
47 FLAG_COMMAND_NAME_EOS = 0x01
59 FLAG_COMMAND_NAME_EOS = 0x01
@@ -94,6 +106,7 b' FRAME_TYPE_FLAGS = {'
94 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
106 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
95 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
107 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
96 FRAME_TYPE_TEXT_OUTPUT: {},
108 FRAME_TYPE_TEXT_OUTPUT: {},
109 FRAME_TYPE_STREAM_SETTINGS: {},
97 }
110 }
98
111
99 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
112 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
@@ -104,6 +117,8 b' class frameheader(object):'
104
117
105 length = attr.ib()
118 length = attr.ib()
106 requestid = attr.ib()
119 requestid = attr.ib()
120 streamid = attr.ib()
121 streamflags = attr.ib()
107 typeid = attr.ib()
122 typeid = attr.ib()
108 flags = attr.ib()
123 flags = attr.ib()
109
124
@@ -112,25 +127,29 b' class frame(object):'
112 """Represents a parsed frame."""
127 """Represents a parsed frame."""
113
128
114 requestid = attr.ib()
129 requestid = attr.ib()
130 streamid = attr.ib()
131 streamflags = attr.ib()
115 typeid = attr.ib()
132 typeid = attr.ib()
116 flags = attr.ib()
133 flags = attr.ib()
117 payload = attr.ib()
134 payload = attr.ib()
118
135
119 def makeframe(requestid, typeid, flags, payload):
136 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
120 """Assemble a frame into a byte array."""
137 """Assemble a frame into a byte array."""
121 # TODO assert size of payload.
138 # TODO assert size of payload.
122 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
139 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
123
140
124 # 24 bits length
141 # 24 bits length
125 # 16 bits request id
142 # 16 bits request id
143 # 8 bits stream id
144 # 8 bits stream flags
126 # 4 bits type
145 # 4 bits type
127 # 4 bits flags
146 # 4 bits flags
128
147
129 l = struct.pack(r'<I', len(payload))
148 l = struct.pack(r'<I', len(payload))
130 frame[0:3] = l[0:3]
149 frame[0:3] = l[0:3]
131 struct.pack_into(r'<H', frame, 3, requestid)
150 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
132 frame[5] = (typeid << 4) | flags
151 frame[7] = (typeid << 4) | flags
133 frame[6:] = payload
152 frame[8:] = payload
134
153
135 return frame
154 return frame
136
155
@@ -139,20 +158,30 b' def makeframefromhumanstring(s):'
139
158
140 Strings have the form:
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 This can be used by user-facing applications and tests for creating
163 This can be used by user-facing applications and tests for creating
145 frames easily without having to type out a bunch of constants.
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 Flags can be delimited by `|` to bitwise OR them together.
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 requestid = int(requestid)
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 if frametype in FRAME_TYPES:
186 if frametype in FRAME_TYPES:
158 frametype = FRAME_TYPES[frametype]
187 frametype = FRAME_TYPES[frametype]
@@ -169,7 +198,8 b' def makeframefromhumanstring(s):'
169
198
170 payload = stringutil.unescapestr(payload)
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 flags=finalflags, payload=payload)
203 flags=finalflags, payload=payload)
174
204
175 def parseheader(data):
205 def parseheader(data):
@@ -179,17 +209,21 b' def parseheader(data):'
179 buffer is expected to be large enough to hold a full header.
209 buffer is expected to be large enough to hold a full header.
180 """
210 """
181 # 24 bits payload length (little endian)
211 # 24 bits payload length (little endian)
212 # 16 bits request ID
213 # 8 bits stream ID
214 # 8 bits stream flags
182 # 4 bits frame type
215 # 4 bits frame type
183 # 4 bits frame flags
216 # 4 bits frame flags
184 # ... payload
217 # ... payload
185 framelength = data[0] + 256 * data[1] + 16384 * data[2]
218 framelength = data[0] + 256 * data[1] + 16384 * data[2]
186 requestid = struct.unpack_from(r'<H', data, 3)[0]
219 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
187 typeflags = data[5]
220 typeflags = data[7]
188
221
189 frametype = (typeflags & 0xf0) >> 4
222 frametype = (typeflags & 0xf0) >> 4
190 frameflags = typeflags & 0x0f
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 def readframe(fh):
228 def readframe(fh):
195 """Read a unified framing protocol frame from a file object.
229 """Read a unified framing protocol frame from a file object.
@@ -216,7 +250,8 b' def readframe(fh):'
216 raise error.Abort(_('frame length error: expected %d; got %d') %
250 raise error.Abort(_('frame length error: expected %d; got %d') %
217 (h.length, len(payload)))
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 def createcommandframes(stream, requestid, cmd, args, datafh=None):
256 def createcommandframes(stream, requestid, cmd, args, datafh=None):
222 """Create frames necessary to transmit a request to run a command.
257 """Create frames necessary to transmit a request to run a command.
@@ -398,12 +433,28 b' def createtextoutputframe(stream, reques'
398 class stream(object):
433 class stream(object):
399 """Represents a logical unidirectional series of frames."""
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 def makeframe(self, requestid, typeid, flags, payload):
440 def makeframe(self, requestid, typeid, flags, payload):
402 """Create a frame to be sent out over this stream.
441 """Create a frame to be sent out over this stream.
403
442
404 Only returns the frame instance. Does not actually send it.
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 class serverreactor(object):
459 class serverreactor(object):
409 """Holds state of a server handling frame-based protocol requests.
460 """Holds state of a server handling frame-based protocol requests.
@@ -483,6 +534,8 b' class serverreactor(object):'
483 self._deferoutput = deferoutput
534 self._deferoutput = deferoutput
484 self._state = 'idle'
535 self._state = 'idle'
485 self._bufferedframegens = []
536 self._bufferedframegens = []
537 # stream id -> stream instance for all active streams from the client.
538 self._incomingstreams = {}
486 # request id -> dict of commands that are actively being received.
539 # request id -> dict of commands that are actively being received.
487 self._receivingcommands = {}
540 self._receivingcommands = {}
488 # Request IDs that have been received and are actively being processed.
541 # Request IDs that have been received and are actively being processed.
@@ -496,6 +549,30 b' class serverreactor(object):'
496 Returns a dict with an ``action`` key that details what action,
549 Returns a dict with an ``action`` key that details what action,
497 if any, the consumer should take next.
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 handlers = {
576 handlers = {
500 'idle': self._onframeidle,
577 'idle': self._onframeidle,
501 'command-receiving': self._onframecommandreceiving,
578 'command-receiving': self._onframecommandreceiving,
@@ -513,6 +590,8 b' class serverreactor(object):'
513
590
514 The raw bytes response is passed as an argument.
591 The raw bytes response is passed as an argument.
515 """
592 """
593 ensureserverstream(stream)
594
516 def sendframes():
595 def sendframes():
517 for frame in createbytesresponseframesfrombytes(stream, requestid,
596 for frame in createbytesresponseframesfrombytes(stream, requestid,
518 data):
597 data):
@@ -552,6 +631,8 b' class serverreactor(object):'
552 }
631 }
553
632
554 def onapplicationerror(self, stream, requestid, msg):
633 def onapplicationerror(self, stream, requestid, msg):
634 ensureserverstream(stream)
635
555 return 'sendframes', {
636 return 'sendframes', {
556 'framegen': createerrorframe(stream, requestid, msg,
637 'framegen': createerrorframe(stream, requestid, msg,
557 application=True),
638 application=True),
@@ -546,7 +546,7 b' def _httpv2runcommand(ui, repo, req, res'
546
546
547 res.status = b'200 OK'
547 res.status = b'200 OK'
548 res.headers[b'Content-Type'] = FRAMINGTYPE
548 res.headers[b'Content-Type'] = FRAMINGTYPE
549 stream = wireprotoframing.stream()
549 stream = wireprotoframing.stream(2)
550
550
551 if isinstance(rsp, wireprototypes.bytesresponse):
551 if isinstance(rsp, wireprototypes.bytesresponse):
552 action, meta = reactor.onbytesresponseready(stream,
552 action, meta = reactor.onbytesresponseready(stream,
@@ -179,7 +179,7 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 1 command-name eos customreadonly
182 > frame 1 1 stream-begin 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
@@ -190,7 +190,7 b' Request to read-only command works out o'
190 s> *\r\n (glob)
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\x01\x00\x11customreadonly
193 s> \x0e\x00\x00\x01\x00\x01\x01\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
@@ -198,8 +198,8 b' Request to read-only command works out o'
198 s> Content-Type: application/mercurial-exp-framing-0002\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> 23\r\n
201 s> 25\r\n
202 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
202 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly 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,7 +290,7 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 1 command-name eos customreadonly
293 > frame 1 1 stream-begin 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
@@ -298,10 +298,10 b' Authorized request for valid read-write '
298 s> accept: application/mercurial-exp-framing-0002\r\n
298 s> accept: application/mercurial-exp-framing-0002\r\n
299 s> content-type: application/mercurial-exp-framing-0002\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: 20\r\n
301 s> content-length: 22\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\x01\x00\x11customreadonly
304 s> \x0e\x00\x00\x01\x00\x01\x01\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
@@ -309,8 +309,8 b' Authorized request for valid read-write '
309 s> Content-Type: application/mercurial-exp-framing-0002\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> 23\r\n
312 s> 25\r\n
313 s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response
313 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly 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
@@ -382,9 +382,9 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 1 command-name have-args command1
385 > frame 1 1 stream-begin command-name have-args command1
386 > frame 1 command-argument 0 \x03\x00\x04\x00fooval1
386 > frame 1 1 0 command-argument 0 \x03\x00\x04\x00fooval1
387 > frame 1 command-argument eoa \x04\x00\x03\x00bar1val
387 > frame 1 1 0 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
@@ -392,10 +392,10 b' Command frames can be reflected via debu'
392 s> accept: application/mercurial-exp-framing-0002\r\n
392 s> accept: application/mercurial-exp-framing-0002\r\n
393 s> content-type: application/mercurial-exp-framing-0002\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: 48\r\n
395 s> content-length: 54\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\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 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
@@ -419,8 +419,8 b' Multiple requests to regular command URL'
419 > accept: $MEDIATYPE
419 > accept: $MEDIATYPE
420 > content-type: $MEDIATYPE
420 > content-type: $MEDIATYPE
421 > user-agent: test
421 > user-agent: test
422 > frame 1 command-name eos customreadonly
422 > frame 1 1 stream-begin command-name eos customreadonly
423 > frame 3 command-name eos customreadonly
423 > frame 3 1 0 command-name eos customreadonly
424 > EOF
424 > EOF
425 using raw connection to peer
425 using raw connection to peer
426 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
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 s> accept: application/mercurial-exp-framing-0002\r\n
428 s> accept: application/mercurial-exp-framing-0002\r\n
429 s> content-type: application/mercurial-exp-framing-0002\r\n
429 s> content-type: application/mercurial-exp-framing-0002\r\n
430 s> user-agent: test\r\n
430 s> user-agent: test\r\n
431 s> content-length: 40\r\n
431 s> content-length: 44\r\n
432 s> host: $LOCALIP:$HGPORT\r\n (glob)
432 s> host: $LOCALIP:$HGPORT\r\n (glob)
433 s> \r\n
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 s> makefile('rb', None)
435 s> makefile('rb', None)
436 s> HTTP/1.1 200 OK\r\n
436 s> HTTP/1.1 200 OK\r\n
437 s> Server: testing stub value\r\n
437 s> Server: testing stub value\r\n
@@ -448,8 +448,8 b' Multiple requests to "multirequest" URL '
448 > accept: $MEDIATYPE
448 > accept: $MEDIATYPE
449 > content-type: $MEDIATYPE
449 > content-type: $MEDIATYPE
450 > user-agent: test
450 > user-agent: test
451 > frame 1 command-name eos customreadonly
451 > frame 1 1 stream-begin command-name eos customreadonly
452 > frame 3 command-name eos customreadonly
452 > frame 3 1 0 command-name eos customreadonly
453 > EOF
453 > EOF
454 using raw connection to peer
454 using raw connection to peer
455 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
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 s> *\r\n (glob)
460 s> *\r\n (glob)
461 s> host: $LOCALIP:$HGPORT\r\n (glob)
461 s> host: $LOCALIP:$HGPORT\r\n (glob)
462 s> \r\n
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 s> makefile('rb', None)
464 s> makefile('rb', None)
465 s> HTTP/1.1 200 OK\r\n
465 s> HTTP/1.1 200 OK\r\n
466 s> Server: testing stub value\r\n
466 s> Server: testing stub value\r\n
@@ -469,10 +469,10 b' Multiple requests to "multirequest" URL '
469 s> Transfer-Encoding: chunked\r\n
469 s> Transfer-Encoding: chunked\r\n
470 s> \r\n
470 s> \r\n
471 s> *\r\n (glob)
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 s> \r\n
473 s> \r\n
474 s> 23\r\n
474 s> 25\r\n
475 s> \x1d\x00\x00\x03\x00Bcustomreadonly bytes response
475 s> \x1d\x00\x00\x03\x00\x02\x01Bcustomreadonly bytes response
476 s> \r\n
476 s> \r\n
477 s> 0\r\n
477 s> 0\r\n
478 s> \r\n
478 s> \r\n
@@ -484,10 +484,10 b' Interleaved requests to "multirequest" a'
484 > accept: $MEDIATYPE
484 > accept: $MEDIATYPE
485 > content-type: $MEDIATYPE
485 > content-type: $MEDIATYPE
486 > user-agent: test
486 > user-agent: test
487 > frame 1 command-name have-args listkeys
487 > frame 1 1 stream-begin command-name have-args listkeys
488 > frame 3 command-name have-args listkeys
488 > frame 3 1 0 command-name have-args listkeys
489 > frame 3 command-argument eoa \x09\x00\x09\x00namespacebookmarks
489 > frame 3 1 0 command-argument eoa \x09\x00\x09\x00namespacebookmarks
490 > frame 1 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
490 > frame 1 1 0 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
491 > EOF
491 > EOF
492 using raw connection to peer
492 using raw connection to peer
493 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
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 s> accept: application/mercurial-exp-framing-0002\r\n
495 s> accept: application/mercurial-exp-framing-0002\r\n
496 s> content-type: application/mercurial-exp-framing-0002\r\n
496 s> content-type: application/mercurial-exp-framing-0002\r\n
497 s> user-agent: test\r\n
497 s> user-agent: test\r\n
498 s> content-length: 85\r\n
498 s> content-length: 93\r\n
499 s> host: $LOCALIP:$HGPORT\r\n (glob)
499 s> host: $LOCALIP:$HGPORT\r\n (glob)
500 s> \r\n
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 s> \x00namespacenamespaces
502 s> \x00namespacenamespaces
503 s> makefile('rb', None)
503 s> makefile('rb', None)
504 s> HTTP/1.1 200 OK\r\n
504 s> HTTP/1.1 200 OK\r\n
@@ -507,11 +507,11 b' Interleaved requests to "multirequest" a'
507 s> Content-Type: application/mercurial-exp-framing-0002\r\n
507 s> Content-Type: application/mercurial-exp-framing-0002\r\n
508 s> Transfer-Encoding: chunked\r\n
508 s> Transfer-Encoding: chunked\r\n
509 s> \r\n
509 s> \r\n
510 s> 6\r\n
510 s> 8\r\n
511 s> \x00\x00\x00\x03\x00B
511 s> \x00\x00\x00\x03\x00\x02\x01B
512 s> \r\n
512 s> \r\n
513 s> 24\r\n
513 s> 26\r\n
514 s> \x1e\x00\x00\x01\x00Bbookmarks \n
514 s> \x1e\x00\x00\x01\x00\x02\x01Bbookmarks \n
515 s> namespaces \n
515 s> namespaces \n
516 s> phases
516 s> phases
517 s> \r\n
517 s> \r\n
@@ -540,7 +540,7 b' Attempting to run a read-write command v'
540 > accept: $MEDIATYPE
540 > accept: $MEDIATYPE
541 > content-type: $MEDIATYPE
541 > content-type: $MEDIATYPE
542 > user-agent: test
542 > user-agent: test
543 > frame 1 command-name eos unbundle
543 > frame 1 1 stream-begin command-name eos unbundle
544 > EOF
544 > EOF
545 using raw connection to peer
545 using raw connection to peer
546 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
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 s> accept: application/mercurial-exp-framing-0002\r\n
548 s> accept: application/mercurial-exp-framing-0002\r\n
549 s> content-type: application/mercurial-exp-framing-0002\r\n
549 s> content-type: application/mercurial-exp-framing-0002\r\n
550 s> user-agent: test\r\n
550 s> user-agent: test\r\n
551 s> content-length: 14\r\n
551 s> content-length: 16\r\n
552 s> host: $LOCALIP:$HGPORT\r\n (glob)
552 s> host: $LOCALIP:$HGPORT\r\n (glob)
553 s> \r\n
553 s> \r\n
554 s> \x08\x00\x00\x01\x00\x11unbundle
554 s> \x08\x00\x00\x01\x00\x01\x01\x11unbundle
555 s> makefile('rb', None)
555 s> makefile('rb', None)
556 s> HTTP/1.1 403 Forbidden\r\n
556 s> HTTP/1.1 403 Forbidden\r\n
557 s> Server: testing stub value\r\n
557 s> Server: testing stub value\r\n
@@ -23,6 +23,8 b' def sendframes(reactor, gen):'
23 assert len(payload) == header.length
23 assert len(payload) == header.length
24
24
25 yield reactor.onframerecv(framing.frame(header.requestid,
25 yield reactor.onframerecv(framing.frame(header.requestid,
26 header.streamid,
27 header.streamflags,
26 header.typeid,
28 header.typeid,
27 header.flags,
29 header.flags,
28 payload))
30 payload))
@@ -37,32 +39,32 b' class FrameTests(unittest.TestCase):'
37 def testdataexactframesize(self):
39 def testdataexactframesize(self):
38 data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE)
40 data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE)
39
41
40 stream = framing.stream()
42 stream = framing.stream(1)
41 frames = list(framing.createcommandframes(stream, 1, b'command',
43 frames = list(framing.createcommandframes(stream, 1, b'command',
42 {}, data))
44 {}, data))
43 self.assertEqual(frames, [
45 self.assertEqual(frames, [
44 ffs(b'1 command-name have-data command'),
46 ffs(b'1 1 stream-begin command-name have-data command'),
45 ffs(b'1 command-data continuation %s' % data.getvalue()),
47 ffs(b'1 1 0 command-data continuation %s' % data.getvalue()),
46 ffs(b'1 command-data eos ')
48 ffs(b'1 1 0 command-data eos ')
47 ])
49 ])
48
50
49 def testdatamultipleframes(self):
51 def testdatamultipleframes(self):
50 data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1))
52 data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1))
51
53
52 stream = framing.stream()
54 stream = framing.stream(1)
53 frames = list(framing.createcommandframes(stream, 1, b'command', {},
55 frames = list(framing.createcommandframes(stream, 1, b'command', {},
54 data))
56 data))
55 self.assertEqual(frames, [
57 self.assertEqual(frames, [
56 ffs(b'1 command-name have-data command'),
58 ffs(b'1 1 stream-begin command-name have-data command'),
57 ffs(b'1 command-data continuation %s' % (
59 ffs(b'1 1 0 command-data continuation %s' % (
58 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
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 def testargsanddata(self):
64 def testargsanddata(self):
63 data = util.bytesio(b'x' * 100)
65 data = util.bytesio(b'x' * 100)
64
66
65 stream = framing.stream()
67 stream = framing.stream(1)
66 frames = list(framing.createcommandframes(stream, 1, b'command', {
68 frames = list(framing.createcommandframes(stream, 1, b'command', {
67 b'key1': b'key1value',
69 b'key1': b'key1value',
68 b'key2': b'key2value',
70 b'key2': b'key2value',
@@ -70,11 +72,11 b' class FrameTests(unittest.TestCase):'
70 }, data))
72 }, data))
71
73
72 self.assertEqual(frames, [
74 self.assertEqual(frames, [
73 ffs(b'1 command-name have-args|have-data command'),
75 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
74 ffs(br'1 command-argument 0 \x04\x00\x09\x00key1key1value'),
76 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key1key1value'),
75 ffs(br'1 command-argument 0 \x04\x00\x09\x00key2key2value'),
77 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key2key2value'),
76 ffs(br'1 command-argument eoa \x04\x00\x09\x00key3key3value'),
78 ffs(br'1 1 0 command-argument eoa \x04\x00\x09\x00key3key3value'),
77 ffs(b'1 command-data eos %s' % data.getvalue()),
79 ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
78 ])
80 ])
79
81
80 def testtextoutputexcessiveargs(self):
82 def testtextoutputexcessiveargs(self):
@@ -128,64 +130,68 b' class FrameTests(unittest.TestCase):'
128 (b'bleh', [], [b'x' * 65536])]))
130 (b'bleh', [], [b'x' * 65536])]))
129
131
130 def testtextoutput1simpleatom(self):
132 def testtextoutput1simpleatom(self):
131 stream = framing.stream()
133 stream = framing.stream(1)
132 val = list(framing.createtextoutputframe(stream, 1, [
134 val = list(framing.createtextoutputframe(stream, 1, [
133 (b'foo', [], [])]))
135 (b'foo', [], [])]))
134
136
135 self.assertEqual(val, [
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 def testtextoutput2simpleatoms(self):
141 def testtextoutput2simpleatoms(self):
140 stream = framing.stream()
142 stream = framing.stream(1)
141 val = list(framing.createtextoutputframe(stream, 1, [
143 val = list(framing.createtextoutputframe(stream, 1, [
142 (b'foo', [], []),
144 (b'foo', [], []),
143 (b'bar', [], []),
145 (b'bar', [], []),
144 ]))
146 ]))
145
147
146 self.assertEqual(val, [
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 def testtextoutput1arg(self):
153 def testtextoutput1arg(self):
151 stream = framing.stream()
154 stream = framing.stream(1)
152 val = list(framing.createtextoutputframe(stream, 1, [
155 val = list(framing.createtextoutputframe(stream, 1, [
153 (b'foo %s', [b'val1'], []),
156 (b'foo %s', [b'val1'], []),
154 ]))
157 ]))
155
158
156 self.assertEqual(val, [
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 def testtextoutput2arg(self):
164 def testtextoutput2arg(self):
161 stream = framing.stream()
165 stream = framing.stream(1)
162 val = list(framing.createtextoutputframe(stream, 1, [
166 val = list(framing.createtextoutputframe(stream, 1, [
163 (b'foo %s %s', [b'val', b'value'], []),
167 (b'foo %s %s', [b'val', b'value'], []),
164 ]))
168 ]))
165
169
166 self.assertEqual(val, [
170 self.assertEqual(val, [
167 ffs(br'1 text-output 0 \x09\x00\x00\x02\x03\x00\x05\x00'
171 ffs(br'1 1 stream-begin text-output 0 '
168 br'foo %s %svalvalue'),
172 br'\x09\x00\x00\x02\x03\x00\x05\x00foo %s %svalvalue'),
169 ])
173 ])
170
174
171 def testtextoutput1label(self):
175 def testtextoutput1label(self):
172 stream = framing.stream()
176 stream = framing.stream(1)
173 val = list(framing.createtextoutputframe(stream, 1, [
177 val = list(framing.createtextoutputframe(stream, 1, [
174 (b'foo', [], [b'label']),
178 (b'foo', [], [b'label']),
175 ]))
179 ]))
176
180
177 self.assertEqual(val, [
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 def testargandlabel(self):
186 def testargandlabel(self):
182 stream = framing.stream()
187 stream = framing.stream(1)
183 val = list(framing.createtextoutputframe(stream, 1, [
188 val = list(framing.createtextoutputframe(stream, 1, [
184 (b'foo %s', [b'arg'], [b'label']),
189 (b'foo %s', [b'arg'], [b'label']),
185 ]))
190 ]))
186
191
187 self.assertEqual(val, [
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 class ServerReactorTests(unittest.TestCase):
197 class ServerReactorTests(unittest.TestCase):
@@ -208,7 +214,7 b' class ServerReactorTests(unittest.TestCa'
208 def test1framecommand(self):
214 def test1framecommand(self):
209 """Receiving a command in a single frame yields request to run it."""
215 """Receiving a command in a single frame yields request to run it."""
210 reactor = makereactor()
216 reactor = makereactor()
211 stream = framing.stream()
217 stream = framing.stream(1)
212 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
218 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
213 self.assertEqual(len(results), 1)
219 self.assertEqual(len(results), 1)
214 self.assertaction(results[0], 'runcommand')
220 self.assertaction(results[0], 'runcommand')
@@ -224,7 +230,7 b' class ServerReactorTests(unittest.TestCa'
224
230
225 def test1argument(self):
231 def test1argument(self):
226 reactor = makereactor()
232 reactor = makereactor()
227 stream = framing.stream()
233 stream = framing.stream(1)
228 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
234 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
229 {b'foo': b'bar'}))
235 {b'foo': b'bar'}))
230 self.assertEqual(len(results), 2)
236 self.assertEqual(len(results), 2)
@@ -239,7 +245,7 b' class ServerReactorTests(unittest.TestCa'
239
245
240 def testmultiarguments(self):
246 def testmultiarguments(self):
241 reactor = makereactor()
247 reactor = makereactor()
242 stream = framing.stream()
248 stream = framing.stream(1)
243 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
249 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
244 {b'foo': b'bar', b'biz': b'baz'}))
250 {b'foo': b'bar', b'biz': b'baz'}))
245 self.assertEqual(len(results), 3)
251 self.assertEqual(len(results), 3)
@@ -255,7 +261,7 b' class ServerReactorTests(unittest.TestCa'
255
261
256 def testsimplecommanddata(self):
262 def testsimplecommanddata(self):
257 reactor = makereactor()
263 reactor = makereactor()
258 stream = framing.stream()
264 stream = framing.stream(1)
259 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
265 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
260 util.bytesio(b'data!')))
266 util.bytesio(b'data!')))
261 self.assertEqual(len(results), 2)
267 self.assertEqual(len(results), 2)
@@ -270,10 +276,10 b' class ServerReactorTests(unittest.TestCa'
270
276
271 def testmultipledataframes(self):
277 def testmultipledataframes(self):
272 frames = [
278 frames = [
273 ffs(b'1 command-name have-data mycommand'),
279 ffs(b'1 1 stream-begin command-name have-data mycommand'),
274 ffs(b'1 command-data continuation data1'),
280 ffs(b'1 1 0 command-data continuation data1'),
275 ffs(b'1 command-data continuation data2'),
281 ffs(b'1 1 0 command-data continuation data2'),
276 ffs(b'1 command-data eos data3'),
282 ffs(b'1 1 0 command-data eos data3'),
277 ]
283 ]
278
284
279 reactor = makereactor()
285 reactor = makereactor()
@@ -291,11 +297,11 b' class ServerReactorTests(unittest.TestCa'
291
297
292 def testargumentanddata(self):
298 def testargumentanddata(self):
293 frames = [
299 frames = [
294 ffs(b'1 command-name have-args|have-data command'),
300 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
295 ffs(br'1 command-argument 0 \x03\x00\x03\x00keyval'),
301 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00keyval'),
296 ffs(br'1 command-argument eoa \x03\x00\x03\x00foobar'),
302 ffs(br'1 1 0 command-argument eoa \x03\x00\x03\x00foobar'),
297 ffs(b'1 command-data continuation value1'),
303 ffs(b'1 1 0 command-data continuation value1'),
298 ffs(b'1 command-data eos value2'),
304 ffs(b'1 1 0 command-data eos value2'),
299 ]
305 ]
300
306
301 reactor = makereactor()
307 reactor = makereactor()
@@ -314,8 +320,8 b' class ServerReactorTests(unittest.TestCa'
314
320
315 def testunexpectedcommandargument(self):
321 def testunexpectedcommandargument(self):
316 """Command argument frame when not running a command is an error."""
322 """Command argument frame when not running a command is an error."""
317 result = self._sendsingleframe(makereactor(),
323 result = self._sendsingleframe(
318 ffs(b'1 command-argument 0 ignored'))
324 makereactor(), ffs(b'1 1 stream-begin command-argument 0 ignored'))
319 self.assertaction(result, 'error')
325 self.assertaction(result, 'error')
320 self.assertEqual(result[1], {
326 self.assertEqual(result[1], {
321 'message': b'expected command frame; got 2',
327 'message': b'expected command frame; got 2',
@@ -324,8 +330,8 b' class ServerReactorTests(unittest.TestCa'
324 def testunexpectedcommandargumentreceiving(self):
330 def testunexpectedcommandargumentreceiving(self):
325 """Same as above but the command is receiving."""
331 """Same as above but the command is receiving."""
326 results = list(sendframes(makereactor(), [
332 results = list(sendframes(makereactor(), [
327 ffs(b'1 command-name have-data command'),
333 ffs(b'1 1 stream-begin command-name have-data command'),
328 ffs(b'1 command-argument eoa ignored'),
334 ffs(b'1 1 0 command-argument eoa ignored'),
329 ]))
335 ]))
330
336
331 self.assertaction(results[1], 'error')
337 self.assertaction(results[1], 'error')
@@ -336,8 +342,8 b' class ServerReactorTests(unittest.TestCa'
336
342
337 def testunexpectedcommanddata(self):
343 def testunexpectedcommanddata(self):
338 """Command argument frame when not running a command is an error."""
344 """Command argument frame when not running a command is an error."""
339 result = self._sendsingleframe(makereactor(),
345 result = self._sendsingleframe(
340 ffs(b'1 command-data 0 ignored'))
346 makereactor(), ffs(b'1 1 stream-begin command-data 0 ignored'))
341 self.assertaction(result, 'error')
347 self.assertaction(result, 'error')
342 self.assertEqual(result[1], {
348 self.assertEqual(result[1], {
343 'message': b'expected command frame; got 3',
349 'message': b'expected command frame; got 3',
@@ -346,8 +352,8 b' class ServerReactorTests(unittest.TestCa'
346 def testunexpectedcommanddatareceiving(self):
352 def testunexpectedcommanddatareceiving(self):
347 """Same as above except the command is receiving."""
353 """Same as above except the command is receiving."""
348 results = list(sendframes(makereactor(), [
354 results = list(sendframes(makereactor(), [
349 ffs(b'1 command-name have-args command'),
355 ffs(b'1 1 stream-begin command-name have-args command'),
350 ffs(b'1 command-data eos ignored'),
356 ffs(b'1 1 0 command-data eos ignored'),
351 ]))
357 ]))
352
358
353 self.assertaction(results[1], 'error')
359 self.assertaction(results[1], 'error')
@@ -358,8 +364,8 b' class ServerReactorTests(unittest.TestCa'
358
364
359 def testmissingcommandframeflags(self):
365 def testmissingcommandframeflags(self):
360 """Command name frame must have flags set."""
366 """Command name frame must have flags set."""
361 result = self._sendsingleframe(makereactor(),
367 result = self._sendsingleframe(
362 ffs(b'1 command-name 0 command'))
368 makereactor(), ffs(b'1 1 stream-begin command-name 0 command'))
363 self.assertaction(result, 'error')
369 self.assertaction(result, 'error')
364 self.assertEqual(result[1], {
370 self.assertEqual(result[1], {
365 'message': b'missing frame flags on command frame',
371 'message': b'missing frame flags on command frame',
@@ -369,19 +375,19 b' class ServerReactorTests(unittest.TestCa'
369 """Multiple fully serviced commands with same request ID is allowed."""
375 """Multiple fully serviced commands with same request ID is allowed."""
370 reactor = makereactor()
376 reactor = makereactor()
371 results = []
377 results = []
372 outstream = framing.stream()
378 outstream = framing.stream(2)
373 results.append(self._sendsingleframe(
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 result = reactor.onbytesresponseready(outstream, 1, b'response1')
381 result = reactor.onbytesresponseready(outstream, 1, b'response1')
376 self.assertaction(result, 'sendframes')
382 self.assertaction(result, 'sendframes')
377 list(result[1]['framegen'])
383 list(result[1]['framegen'])
378 results.append(self._sendsingleframe(
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 result = reactor.onbytesresponseready(outstream, 1, b'response2')
386 result = reactor.onbytesresponseready(outstream, 1, b'response2')
381 self.assertaction(result, 'sendframes')
387 self.assertaction(result, 'sendframes')
382 list(result[1]['framegen'])
388 list(result[1]['framegen'])
383 results.append(self._sendsingleframe(
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 result = reactor.onbytesresponseready(outstream, 1, b'response3')
391 result = reactor.onbytesresponseready(outstream, 1, b'response3')
386 self.assertaction(result, 'sendframes')
392 self.assertaction(result, 'sendframes')
387 list(result[1]['framegen'])
393 list(result[1]['framegen'])
@@ -398,8 +404,8 b' class ServerReactorTests(unittest.TestCa'
398 def testconflictingrequestid(self):
404 def testconflictingrequestid(self):
399 """Request ID for new command matching in-flight command is illegal."""
405 """Request ID for new command matching in-flight command is illegal."""
400 results = list(sendframes(makereactor(), [
406 results = list(sendframes(makereactor(), [
401 ffs(b'1 command-name have-args command'),
407 ffs(b'1 1 stream-begin command-name have-args command'),
402 ffs(b'1 command-name eos command'),
408 ffs(b'1 1 0 command-name eos command'),
403 ]))
409 ]))
404
410
405 self.assertaction(results[0], 'wantframe')
411 self.assertaction(results[0], 'wantframe')
@@ -410,12 +416,12 b' class ServerReactorTests(unittest.TestCa'
410
416
411 def testinterleavedcommands(self):
417 def testinterleavedcommands(self):
412 results = list(sendframes(makereactor(), [
418 results = list(sendframes(makereactor(), [
413 ffs(b'1 command-name have-args command1'),
419 ffs(b'1 1 stream-begin command-name have-args command1'),
414 ffs(b'3 command-name have-args command3'),
420 ffs(b'3 1 0 command-name have-args command3'),
415 ffs(br'1 command-argument 0 \x03\x00\x03\x00foobar'),
421 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00foobar'),
416 ffs(br'3 command-argument 0 \x03\x00\x03\x00bizbaz'),
422 ffs(br'3 1 0 command-argument 0 \x03\x00\x03\x00bizbaz'),
417 ffs(br'3 command-argument eoa \x03\x00\x03\x00keyval'),
423 ffs(br'3 1 0 command-argument eoa \x03\x00\x03\x00keyval'),
418 ffs(br'1 command-argument eoa \x04\x00\x03\x00key1val'),
424 ffs(br'1 1 0 command-argument eoa \x04\x00\x03\x00key1val'),
419 ]))
425 ]))
420
426
421 self.assertEqual([t[0] for t in results], [
427 self.assertEqual([t[0] for t in results], [
@@ -445,7 +451,7 b' class ServerReactorTests(unittest.TestCa'
445 # command request waiting on argument data. But it doesn't handle that
451 # command request waiting on argument data. But it doesn't handle that
446 # scenario yet. So this test does nothing of value.
452 # scenario yet. So this test does nothing of value.
447 frames = [
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 results = list(sendframes(makereactor(), frames))
457 results = list(sendframes(makereactor(), frames))
@@ -454,8 +460,8 b' class ServerReactorTests(unittest.TestCa'
454 def testincompleteargumentname(self):
460 def testincompleteargumentname(self):
455 """Argument frame with incomplete name."""
461 """Argument frame with incomplete name."""
456 frames = [
462 frames = [
457 ffs(b'1 command-name have-args command1'),
463 ffs(b'1 1 stream-begin command-name have-args command1'),
458 ffs(br'1 command-argument eoa \x04\x00\xde\xadfoo'),
464 ffs(br'1 1 0 command-argument eoa \x04\x00\xde\xadfoo'),
459 ]
465 ]
460
466
461 results = list(sendframes(makereactor(), frames))
467 results = list(sendframes(makereactor(), frames))
@@ -469,8 +475,8 b' class ServerReactorTests(unittest.TestCa'
469 def testincompleteargumentvalue(self):
475 def testincompleteargumentvalue(self):
470 """Argument frame with incomplete value."""
476 """Argument frame with incomplete value."""
471 frames = [
477 frames = [
472 ffs(b'1 command-name have-args command'),
478 ffs(b'1 1 stream-begin command-name have-args command'),
473 ffs(br'1 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
479 ffs(br'1 1 0 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
474 ]
480 ]
475
481
476 results = list(sendframes(makereactor(), frames))
482 results = list(sendframes(makereactor(), frames))
@@ -485,8 +491,8 b' class ServerReactorTests(unittest.TestCa'
485 # The reactor doesn't currently handle partially received commands.
491 # The reactor doesn't currently handle partially received commands.
486 # So this test is failing to do anything with request 1.
492 # So this test is failing to do anything with request 1.
487 frames = [
493 frames = [
488 ffs(b'1 command-name have-data command1'),
494 ffs(b'1 1 stream-begin command-name have-data command1'),
489 ffs(b'3 command-name eos command2'),
495 ffs(b'3 1 0 command-name eos command2'),
490 ]
496 ]
491 results = list(sendframes(makereactor(), frames))
497 results = list(sendframes(makereactor(), frames))
492 self.assertEqual(len(results), 2)
498 self.assertEqual(len(results), 2)
@@ -495,8 +501,8 b' class ServerReactorTests(unittest.TestCa'
495
501
496 def testmissingcommanddataframeflags(self):
502 def testmissingcommanddataframeflags(self):
497 frames = [
503 frames = [
498 ffs(b'1 command-name have-data command1'),
504 ffs(b'1 1 stream-begin command-name have-data command1'),
499 ffs(b'1 command-data 0 data'),
505 ffs(b'1 1 0 command-data 0 data'),
500 ]
506 ]
501 results = list(sendframes(makereactor(), frames))
507 results = list(sendframes(makereactor(), frames))
502 self.assertEqual(len(results), 2)
508 self.assertEqual(len(results), 2)
@@ -509,9 +515,9 b' class ServerReactorTests(unittest.TestCa'
509 def testframefornonreceivingrequest(self):
515 def testframefornonreceivingrequest(self):
510 """Receiving a frame for a command that is not receiving is illegal."""
516 """Receiving a frame for a command that is not receiving is illegal."""
511 results = list(sendframes(makereactor(), [
517 results = list(sendframes(makereactor(), [
512 ffs(b'1 command-name eos command1'),
518 ffs(b'1 1 stream-begin command-name eos command1'),
513 ffs(b'3 command-name have-data command3'),
519 ffs(b'3 1 0 command-name have-data command3'),
514 ffs(b'5 command-argument eoa ignored'),
520 ffs(b'5 1 0 command-argument eoa ignored'),
515 ]))
521 ]))
516 self.assertaction(results[2], 'error')
522 self.assertaction(results[2], 'error')
517 self.assertEqual(results[2][1], {
523 self.assertEqual(results[2][1], {
@@ -521,14 +527,14 b' class ServerReactorTests(unittest.TestCa'
521 def testsimpleresponse(self):
527 def testsimpleresponse(self):
522 """Bytes response to command sends result frames."""
528 """Bytes response to command sends result frames."""
523 reactor = makereactor()
529 reactor = makereactor()
524 instream = framing.stream()
530 instream = framing.stream(1)
525 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
531 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
526
532
527 outstream = framing.stream()
533 outstream = framing.stream(2)
528 result = reactor.onbytesresponseready(outstream, 1, b'response')
534 result = reactor.onbytesresponseready(outstream, 1, b'response')
529 self.assertaction(result, 'sendframes')
535 self.assertaction(result, 'sendframes')
530 self.assertframesequal(result[1]['framegen'], [
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 def testmultiframeresponse(self):
540 def testmultiframeresponse(self):
@@ -537,54 +543,54 b' class ServerReactorTests(unittest.TestCa'
537 second = b'y' * 100
543 second = b'y' * 100
538
544
539 reactor = makereactor()
545 reactor = makereactor()
540 instream = framing.stream()
546 instream = framing.stream(1)
541 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
547 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
542
548
543 outstream = framing.stream()
549 outstream = framing.stream(2)
544 result = reactor.onbytesresponseready(outstream, 1, first + second)
550 result = reactor.onbytesresponseready(outstream, 1, first + second)
545 self.assertaction(result, 'sendframes')
551 self.assertaction(result, 'sendframes')
546 self.assertframesequal(result[1]['framegen'], [
552 self.assertframesequal(result[1]['framegen'], [
547 b'1 bytes-response continuation %s' % first,
553 b'1 2 stream-begin bytes-response continuation %s' % first,
548 b'1 bytes-response eos %s' % second,
554 b'1 2 0 bytes-response eos %s' % second,
549 ])
555 ])
550
556
551 def testapplicationerror(self):
557 def testapplicationerror(self):
552 reactor = makereactor()
558 reactor = makereactor()
553 instream = framing.stream()
559 instream = framing.stream(1)
554 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
560 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
555
561
556 outstream = framing.stream()
562 outstream = framing.stream(2)
557 result = reactor.onapplicationerror(outstream, 1, b'some message')
563 result = reactor.onapplicationerror(outstream, 1, b'some message')
558 self.assertaction(result, 'sendframes')
564 self.assertaction(result, 'sendframes')
559 self.assertframesequal(result[1]['framegen'], [
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 def test1commanddeferresponse(self):
569 def test1commanddeferresponse(self):
564 """Responses when in deferred output mode are delayed until EOF."""
570 """Responses when in deferred output mode are delayed until EOF."""
565 reactor = makereactor(deferoutput=True)
571 reactor = makereactor(deferoutput=True)
566 instream = framing.stream()
572 instream = framing.stream(1)
567 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
573 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
568 {}))
574 {}))
569 self.assertEqual(len(results), 1)
575 self.assertEqual(len(results), 1)
570 self.assertaction(results[0], 'runcommand')
576 self.assertaction(results[0], 'runcommand')
571
577
572 outstream = framing.stream()
578 outstream = framing.stream(2)
573 result = reactor.onbytesresponseready(outstream, 1, b'response')
579 result = reactor.onbytesresponseready(outstream, 1, b'response')
574 self.assertaction(result, 'noop')
580 self.assertaction(result, 'noop')
575 result = reactor.oninputeof()
581 result = reactor.oninputeof()
576 self.assertaction(result, 'sendframes')
582 self.assertaction(result, 'sendframes')
577 self.assertframesequal(result[1]['framegen'], [
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 def testmultiplecommanddeferresponse(self):
587 def testmultiplecommanddeferresponse(self):
582 reactor = makereactor(deferoutput=True)
588 reactor = makereactor(deferoutput=True)
583 instream = framing.stream()
589 instream = framing.stream(1)
584 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
590 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
585 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
591 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
586
592
587 outstream = framing.stream()
593 outstream = framing.stream(2)
588 result = reactor.onbytesresponseready(outstream, 1, b'response1')
594 result = reactor.onbytesresponseready(outstream, 1, b'response1')
589 self.assertaction(result, 'noop')
595 self.assertaction(result, 'noop')
590 result = reactor.onbytesresponseready(outstream, 3, b'response2')
596 result = reactor.onbytesresponseready(outstream, 3, b'response2')
@@ -592,19 +598,19 b' class ServerReactorTests(unittest.TestCa'
592 result = reactor.oninputeof()
598 result = reactor.oninputeof()
593 self.assertaction(result, 'sendframes')
599 self.assertaction(result, 'sendframes')
594 self.assertframesequal(result[1]['framegen'], [
600 self.assertframesequal(result[1]['framegen'], [
595 b'1 bytes-response eos response1',
601 b'1 2 stream-begin bytes-response eos response1',
596 b'3 bytes-response eos response2'
602 b'3 2 0 bytes-response eos response2'
597 ])
603 ])
598
604
599 def testrequestidtracking(self):
605 def testrequestidtracking(self):
600 reactor = makereactor(deferoutput=True)
606 reactor = makereactor(deferoutput=True)
601 instream = framing.stream()
607 instream = framing.stream(1)
602 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
608 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
603 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
609 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
604 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
610 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
605
611
606 # Register results for commands out of order.
612 # Register results for commands out of order.
607 outstream = framing.stream()
613 outstream = framing.stream(2)
608 reactor.onbytesresponseready(outstream, 3, b'response3')
614 reactor.onbytesresponseready(outstream, 3, b'response3')
609 reactor.onbytesresponseready(outstream, 1, b'response1')
615 reactor.onbytesresponseready(outstream, 1, b'response1')
610 reactor.onbytesresponseready(outstream, 5, b'response5')
616 reactor.onbytesresponseready(outstream, 5, b'response5')
@@ -612,15 +618,15 b' class ServerReactorTests(unittest.TestCa'
612 result = reactor.oninputeof()
618 result = reactor.oninputeof()
613 self.assertaction(result, 'sendframes')
619 self.assertaction(result, 'sendframes')
614 self.assertframesequal(result[1]['framegen'], [
620 self.assertframesequal(result[1]['framegen'], [
615 b'3 bytes-response eos response3',
621 b'3 2 stream-begin bytes-response eos response3',
616 b'1 bytes-response eos response1',
622 b'1 2 0 bytes-response eos response1',
617 b'5 bytes-response eos response5',
623 b'5 2 0 bytes-response eos response5',
618 ])
624 ])
619
625
620 def testduplicaterequestonactivecommand(self):
626 def testduplicaterequestonactivecommand(self):
621 """Receiving a request ID that matches a request that isn't finished."""
627 """Receiving a request ID that matches a request that isn't finished."""
622 reactor = makereactor()
628 reactor = makereactor()
623 stream = framing.stream()
629 stream = framing.stream(1)
624 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
630 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
625 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
631 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
626
632
@@ -632,9 +638,9 b' class ServerReactorTests(unittest.TestCa'
632 def testduplicaterequestonactivecommandnosend(self):
638 def testduplicaterequestonactivecommandnosend(self):
633 """Same as above but we've registered a response but haven't sent it."""
639 """Same as above but we've registered a response but haven't sent it."""
634 reactor = makereactor()
640 reactor = makereactor()
635 instream = framing.stream()
641 instream = framing.stream(1)
636 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
642 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
637 outstream = framing.stream()
643 outstream = framing.stream(2)
638 reactor.onbytesresponseready(outstream, 1, b'response')
644 reactor.onbytesresponseready(outstream, 1, b'response')
639
645
640 # We've registered the response but haven't sent it. From the
646 # We've registered the response but haven't sent it. From the
@@ -649,11 +655,11 b' class ServerReactorTests(unittest.TestCa'
649 def testduplicaterequestargumentframe(self):
655 def testduplicaterequestargumentframe(self):
650 """Variant on above except we sent an argument frame instead of name."""
656 """Variant on above except we sent an argument frame instead of name."""
651 reactor = makereactor()
657 reactor = makereactor()
652 stream = framing.stream()
658 stream = framing.stream(1)
653 list(sendcommandframes(reactor, stream, 1, b'command', {}))
659 list(sendcommandframes(reactor, stream, 1, b'command', {}))
654 results = list(sendframes(reactor, [
660 results = list(sendframes(reactor, [
655 ffs(b'3 command-name have-args command'),
661 ffs(b'3 1 stream-begin command-name have-args command'),
656 ffs(b'1 command-argument 0 ignored'),
662 ffs(b'1 1 0 command-argument 0 ignored'),
657 ]))
663 ]))
658 self.assertaction(results[0], 'wantframe')
664 self.assertaction(results[0], 'wantframe')
659 self.assertaction(results[1], 'error')
665 self.assertaction(results[1], 'error')
@@ -664,9 +670,9 b' class ServerReactorTests(unittest.TestCa'
664 def testduplicaterequestaftersend(self):
670 def testduplicaterequestaftersend(self):
665 """We can use a duplicate request ID after we've sent the response."""
671 """We can use a duplicate request ID after we've sent the response."""
666 reactor = makereactor()
672 reactor = makereactor()
667 instream = framing.stream()
673 instream = framing.stream(1)
668 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
674 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
669 outstream = framing.stream()
675 outstream = framing.stream(2)
670 res = reactor.onbytesresponseready(outstream, 1, b'response')
676 res = reactor.onbytesresponseready(outstream, 1, b'response')
671 list(res[1]['framegen'])
677 list(res[1]['framegen'])
672
678
General Comments 0
You need to be logged in to leave comments. Login now