tests/test-batching.py
changeset 14621 84094c0d2724
child 14764 a7d5816087a9
equal deleted inserted replaced
14620:2b9c32929e62 14621:84094c0d2724
       
     1 # test-batching.py - tests for transparent command batching
       
     2 #
       
     3 # Copyright 2011 Peter Arrenbrecht <peter@arrenbrecht.ch>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 from mercurial.wireproto import localbatch, remotebatch, batchable, future
       
     9 
       
    10 # equivalent of repo.repository
       
    11 class thing(object):
       
    12     def hello(self):
       
    13         return "Ready."
       
    14 
       
    15 # equivalent of localrepo.localrepository
       
    16 class localthing(thing):
       
    17     def foo(self, one, two=None):
       
    18         if one:
       
    19             return "%s and %s" % (one, two,)
       
    20         return "Nope"
       
    21     def bar(self, b, a):
       
    22         return "%s und %s" % (b, a,)
       
    23     def greet(self, name=None):
       
    24         return "Hello, %s" % name
       
    25     def batch(self):
       
    26         '''Support for local batching.'''
       
    27         return localbatch(self)
       
    28 
       
    29 # usage of "thing" interface
       
    30 def use(it):
       
    31 
       
    32     # Direct call to base method shared between client and server.
       
    33     print it.hello()
       
    34 
       
    35     # Direct calls to proxied methods. They cause individual roundtrips.
       
    36     print it.foo("Un", two="Deux")
       
    37     print it.bar("Eins", "Zwei")
       
    38 
       
    39     # Batched call to a couple of (possibly proxied) methods.
       
    40     batch = it.batch()
       
    41     # The calls return futures to eventually hold results.
       
    42     foo = batch.foo(one="One", two="Two")
       
    43     foo2 = batch.foo(None)
       
    44     bar = batch.bar("Eins", "Zwei")
       
    45     # We can call non-batchable proxy methods, but the break the current batch
       
    46     # request and cause additional roundtrips.
       
    47     greet = batch.greet(name="John Smith")
       
    48     # We can also add local methods into the mix, but they break the batch too.
       
    49     hello = batch.hello()
       
    50     bar2 = batch.bar(b="Uno", a="Due")
       
    51     # Only now are all the calls executed in sequence, with as few roundtrips
       
    52     # as possible.
       
    53     batch.submit()
       
    54     # After the call to submit, the futures actually contain values.
       
    55     print foo.value
       
    56     print foo2.value
       
    57     print bar.value
       
    58     print greet.value
       
    59     print hello.value
       
    60     print bar2.value
       
    61 
       
    62 # local usage
       
    63 mylocal = localthing()
       
    64 print
       
    65 print "== Local"
       
    66 use(mylocal)
       
    67 
       
    68 # demo remoting; mimicks what wireproto and HTTP/SSH do
       
    69 
       
    70 # shared
       
    71 
       
    72 def escapearg(plain):
       
    73     return (plain
       
    74             .replace(':', '::')
       
    75             .replace(',', ':,')
       
    76             .replace(';', ':;')
       
    77             .replace('=', ':='))
       
    78 def unescapearg(escaped):
       
    79     return (escaped
       
    80             .replace(':=', '=')
       
    81             .replace(':;', ';')
       
    82             .replace(':,', ',')
       
    83             .replace('::', ':'))
       
    84 
       
    85 # server side
       
    86 
       
    87 # equivalent of wireproto's global functions
       
    88 class server:
       
    89     def __init__(self, local):
       
    90         self.local = local
       
    91     def _call(self, name, args):
       
    92         args = dict(arg.split('=', 1) for arg in args)
       
    93         return getattr(self, name)(**args)
       
    94     def perform(self, req):
       
    95         print "REQ:", req
       
    96         name, args = req.split('?', 1)
       
    97         args = args.split('&')
       
    98         vals = dict(arg.split('=', 1) for arg in args)
       
    99         res = getattr(self, name)(**vals)
       
   100         print "  ->", res
       
   101         return res
       
   102     def batch(self, cmds):
       
   103         res = []
       
   104         for pair in cmds.split(';'):
       
   105             name, args = pair.split(':', 1)
       
   106             vals = {}
       
   107             for a in args.split(','):
       
   108                 if a:
       
   109                     n, v = a.split('=')
       
   110                     vals[n] = unescapearg(v)
       
   111             res.append(escapearg(getattr(self, name)(**vals)))
       
   112         return ';'.join(res)
       
   113     def foo(self, one, two):
       
   114         return mangle(self.local.foo(unmangle(one), unmangle(two)))
       
   115     def bar(self, b, a):
       
   116         return mangle(self.local.bar(unmangle(b), unmangle(a)))
       
   117     def greet(self, name):
       
   118         return mangle(self.local.greet(unmangle(name)))
       
   119 myserver = server(mylocal)
       
   120 
       
   121 # local side
       
   122 
       
   123 # equivalent of wireproto.encode/decodelist, that is, type-specific marshalling
       
   124 # here we just transform the strings a bit to check we're properly en-/decoding
       
   125 def mangle(s):
       
   126     return ''.join(chr(ord(c) + 1) for c in s)
       
   127 def unmangle(s):
       
   128     return ''.join(chr(ord(c) - 1) for c in s)
       
   129 
       
   130 # equivalent of wireproto.wirerepository and something like http's wire format
       
   131 class remotething(thing):
       
   132     def __init__(self, server):
       
   133         self.server = server
       
   134     def _submitone(self, name, args):
       
   135         req = name + '?' + '&'.join(['%s=%s' % (n, v) for n, v in args])
       
   136         return self.server.perform(req)
       
   137     def _submitbatch(self, cmds):
       
   138         req = []
       
   139         for name, args in cmds:
       
   140             args = ','.join(n + '=' + escapearg(v) for n, v in args)
       
   141             req.append(name + ':' + args)
       
   142         req = ';'.join(req)
       
   143         res = self._submitone('batch', [('cmds', req,)])
       
   144         return res.split(';')
       
   145 
       
   146     def batch(self):
       
   147         return remotebatch(self)
       
   148 
       
   149     @batchable
       
   150     def foo(self, one, two=None):
       
   151         if not one:
       
   152             yield "Nope", None
       
   153         encargs = [('one', mangle(one),), ('two', mangle(two),)]
       
   154         encresref = future()
       
   155         yield encargs, encresref
       
   156         yield unmangle(encresref.value)
       
   157 
       
   158     @batchable
       
   159     def bar(self, b, a):
       
   160         encresref = future()
       
   161         yield [('b', mangle(b),), ('a', mangle(a),)], encresref
       
   162         yield unmangle(encresref.value)
       
   163 
       
   164     # greet is coded directly. It therefore does not support batching. If it
       
   165     # does appear in a batch, the batch is split around greet, and the call to
       
   166     # greet is done in its own roundtrip.
       
   167     def greet(self, name=None):
       
   168         return unmangle(self._submitone('greet', [('name', mangle(name),)]))
       
   169 
       
   170 # demo remote usage
       
   171 
       
   172 myproxy = remotething(myserver)
       
   173 print
       
   174 print "== Remote"
       
   175 use(myproxy)