THRIFT-4780: finish the server implementation of multi in python server
- Add default processor handling to python multi
diff --git a/test/py/TestClient.py b/test/py/TestClient.py
index a85098e..e7a9a1a 100755
--- a/test/py/TestClient.py
+++ b/test/py/TestClient.py
@@ -27,8 +27,7 @@
 from optparse import OptionParser
 from util import local_libpath
 sys.path.insert(0, local_libpath())
-from thrift.protocol import TProtocolDecorator
-from thrift.protocol import TProtocol
+from thrift.protocol import TProtocol, TProtocolDecorator
 
 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
 
@@ -272,7 +271,7 @@
 
 
 # LAST_SEQID is a global because we have one transport and multiple protocols
-# running on it (when multiplexec)
+# running on it (when multiplexed)
 LAST_SEQID = None
 
 
@@ -398,6 +397,16 @@
         return make_pedantic(factory.getProtocol(transport))
 
 
+class MultiplexedHeaderTest(MultiplexedOptionalTest):
+    def get_protocol(self, transport):
+        wrapped_proto = make_pedantic(THeaderProtocol.THeaderProtocolFactory().getProtocol(transport))
+        return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "ThriftTest")
+
+    def get_protocol2(self, transport):
+        wrapped_proto = make_pedantic(THeaderProtocol.THeaderProtocolFactory().getProtocol(transport))
+        return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "SecondService")
+
+
 def suite():
     suite = unittest.TestSuite()
     loader = unittest.TestLoader()
@@ -421,6 +430,8 @@
         suite.addTest(loader.loadTestsFromTestCase(MultiplexedAcceleratedCompactTest))
     elif options.proto == 'multic':
         suite.addTest(loader.loadTestsFromTestCase(MultiplexedCompactTest))
+    elif options.proto == 'multih':
+        suite.addTest(loader.loadTestsFromTestCase(MultiplexedHeaderTest))
     elif options.proto == 'multij':
         suite.addTest(loader.loadTestsFromTestCase(MultiplexedJSONTest))
     else:
@@ -460,7 +471,7 @@
                       dest="verbose", const=0,
                       help="minimal output")
     parser.add_option('--protocol', dest="proto", type="string",
-                      help="protocol to use, one of: accel, accelc, binary, compact, header, json, multi, multia, multiac, multic, multij")
+                      help="protocol to use, one of: accel, accelc, binary, compact, header, json, multi, multia, multiac, multic, multih, multij")
     parser.add_option('--transport', dest="trans", type="string",
                       help="transport to use, one of: buffered, framed, http")
     parser.set_defaults(framed=False, http_path=None, verbose=1, host='localhost', port=9090, proto='binary')
diff --git a/test/py/TestServer.py b/test/py/TestServer.py
index aba0d42..d0a13e5 100755
--- a/test/py/TestServer.py
+++ b/test/py/TestServer.py
@@ -27,6 +27,8 @@
 from optparse import OptionParser
 
 from util import local_libpath
+sys.path.insert(0, local_libpath())
+from thrift.protocol import TProtocol, TProtocolDecorator
 
 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
 
@@ -178,21 +180,79 @@
                       byte_thing=arg0, i32_thing=arg1, i64_thing=arg2)
 
 
+class SecondHandler(object):
+    def secondtestString(self, argument):
+        return "testString(\"" + argument + "\")"
+
+
+# LAST_SEQID is a global because we have one transport and multiple protocols
+# running on it (when multiplexed)
+LAST_SEQID = None
+
+
+class TPedanticSequenceIdProtocolWrapper(TProtocolDecorator.TProtocolDecorator):
+    """
+    Wraps any protocol with sequence ID checking: looks for outbound
+    uniqueness as well as request/response alignment.
+    """
+    def __init__(self, protocol):
+        # TProtocolDecorator.__new__ does all the heavy lifting
+        pass
+
+    def readMessageBegin(self):
+        global LAST_SEQID
+        (name, type, seqid) =\
+            super(TPedanticSequenceIdProtocolWrapper, self).readMessageBegin()
+        if LAST_SEQID is not None and LAST_SEQID == seqid:
+            raise TProtocol.TProtocolException(
+                TProtocol.TProtocolException.INVALID_DATA,
+                "We received the same seqid {0} twice in a row".format(seqid))
+        LAST_SEQID = seqid
+        return (name, type, seqid)
+
+
+def make_pedantic(proto):
+    """ Wrap a protocol in the pedantic sequence ID wrapper. """
+    # NOTE: this is disabled for now as many clients send seqid
+    #       of zero and that is okay, need a way to identify
+    #       clients that MUST send seqid unique to function right
+    #       or just force all implementations to send unique seqids (preferred)
+    return proto  # TPedanticSequenceIdProtocolWrapper(proto)
+
+
+class TPedanticSequenceIdProtocolFactory(TProtocol.TProtocolFactory):
+    def __init__(self, encapsulated):
+        super(TPedanticSequenceIdProtocolFactory, self).__init__()
+        self.encapsulated = encapsulated
+
+    def getProtocol(self, trans):
+        return make_pedantic(self.encapsulated.getProtocol(trans))
+
+
 def main(options):
+    # common header allowed client types
+    allowed_client_types = [
+        THeaderTransport.THeaderClientType.HEADERS,
+        THeaderTransport.THeaderClientType.FRAMED_BINARY,
+        THeaderTransport.THeaderClientType.UNFRAMED_BINARY,
+        THeaderTransport.THeaderClientType.FRAMED_COMPACT,
+        THeaderTransport.THeaderClientType.UNFRAMED_COMPACT,
+    ]
+
     # set up the protocol factory form the --protocol option
     prot_factories = {
         'accel': TBinaryProtocol.TBinaryProtocolAcceleratedFactory(),
+        'multia': TBinaryProtocol.TBinaryProtocolAcceleratedFactory(),
         'accelc': TCompactProtocol.TCompactProtocolAcceleratedFactory(),
-        'binary': TBinaryProtocol.TBinaryProtocolFactory(),
+        'multiac': TCompactProtocol.TCompactProtocolAcceleratedFactory(),
+        'binary': TPedanticSequenceIdProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory()),
+        'multi': TPedanticSequenceIdProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory()),
         'compact': TCompactProtocol.TCompactProtocolFactory(),
-        'header': THeaderProtocol.THeaderProtocolFactory(allowed_client_types=[
-            THeaderTransport.THeaderClientType.HEADERS,
-            THeaderTransport.THeaderClientType.FRAMED_BINARY,
-            THeaderTransport.THeaderClientType.UNFRAMED_BINARY,
-            THeaderTransport.THeaderClientType.FRAMED_COMPACT,
-            THeaderTransport.THeaderClientType.UNFRAMED_COMPACT,
-        ]),
+        'multic': TCompactProtocol.TCompactProtocolFactory(),
+        'header': THeaderProtocol.THeaderProtocolFactory(allowed_client_types),
+        'multih': THeaderProtocol.THeaderProtocolFactory(allowed_client_types),
         'json': TJSONProtocol.TJSONProtocolFactory(),
+        'multij': TJSONProtocol.TJSONProtocolFactory(),
     }
     pfactory = prot_factories.get(options.proto, None)
     if pfactory is None:
@@ -215,6 +275,16 @@
     handler = TestHandler()
     processor = ThriftTest.Processor(handler)
 
+    if options.proto.startswith('multi'):
+        secondHandler = SecondHandler()
+        secondProcessor = SecondService.Processor(secondHandler)
+
+        multiplexedProcessor = TMultiplexedProcessor()
+        multiplexedProcessor.registerDefault(processor)
+        multiplexedProcessor.registerProcessor('ThriftTest', processor)
+        multiplexedProcessor.registerProcessor('SecondService', secondProcessor)
+        processor = multiplexedProcessor
+
     global server
 
     # Handle THttpServer as a special case
@@ -312,7 +382,7 @@
                       dest="verbose", const=0,
                       help="minimal output")
     parser.add_option('--protocol', dest="proto", type="string",
-                      help="protocol to use, one of: accel, accelc, binary, compact, json")
+                      help="protocol to use, one of: accel, accelc, binary, compact, json, multi, multia, multiac, multic, multih, multij")
     parser.add_option('--transport', dest="trans", type="string",
                       help="transport to use, one of: buffered, framed, http")
     parser.add_option('--container-limit', dest='container_limit', type='int', default=None)
@@ -324,11 +394,11 @@
     logging.basicConfig(level=options.verbose)
 
     sys.path.insert(0, os.path.join(SCRIPT_DIR, options.genpydir))
-    sys.path.insert(0, local_libpath())
 
-    from ThriftTest import ThriftTest
+    from ThriftTest import ThriftTest, SecondService
     from ThriftTest.ttypes import Xtruct, Xception, Xception2, Insanity
     from thrift.Thrift import TException
+    from thrift.TMultiplexedProcessor import TMultiplexedProcessor
     from thrift.transport import THeaderTransport
     from thrift.transport import TTransport
     from thrift.transport import TSocket