test-batching.py
206 lines
| 6.0 KiB
| text/x-python
|
PythonLexer
/ tests / test-batching.py
Peter Arrenbrecht
|
r14621 | # test-batching.py - tests for transparent command batching | ||
# | ||||
# Copyright 2011 Peter Arrenbrecht <peter@arrenbrecht.ch> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
Robert Stanca
|
r28732 | from __future__ import absolute_import, print_function | ||
Yuya Nishihara
|
r28800 | |||
from mercurial import ( | ||||
Gregory Szorc
|
r33761 | error, | ||
Yuya Nishihara
|
r28800 | peer, | ||
Gregory Szorc
|
r33761 | util, | ||
Yuya Nishihara
|
r28800 | wireproto, | ||
Robert Stanca
|
r28731 | ) | ||
Peter Arrenbrecht
|
r14621 | |||
# equivalent of repo.repository | ||||
class thing(object): | ||||
def hello(self): | ||||
return "Ready." | ||||
# equivalent of localrepo.localrepository | ||||
class localthing(thing): | ||||
def foo(self, one, two=None): | ||||
if one: | ||||
return "%s and %s" % (one, two,) | ||||
return "Nope" | ||||
def bar(self, b, a): | ||||
return "%s und %s" % (b, a,) | ||||
def greet(self, name=None): | ||||
return "Hello, %s" % name | ||||
Gregory Szorc
|
r33761 | def batchiter(self): | ||
Peter Arrenbrecht
|
r14621 | '''Support for local batching.''' | ||
Gregory Szorc
|
r33761 | return peer.localiterbatcher(self) | ||
Peter Arrenbrecht
|
r14621 | |||
# usage of "thing" interface | ||||
def use(it): | ||||
# Direct call to base method shared between client and server. | ||||
Robert Stanca
|
r28732 | print(it.hello()) | ||
Peter Arrenbrecht
|
r14621 | |||
# Direct calls to proxied methods. They cause individual roundtrips. | ||||
Robert Stanca
|
r28732 | print(it.foo("Un", two="Deux")) | ||
print(it.bar("Eins", "Zwei")) | ||||
Peter Arrenbrecht
|
r14621 | |||
Gregory Szorc
|
r33761 | # Batched call to a couple of proxied methods. | ||
batch = it.batchiter() | ||||
Peter Arrenbrecht
|
r14621 | # The calls return futures to eventually hold results. | ||
foo = batch.foo(one="One", two="Two") | ||||
bar = batch.bar("Eins", "Zwei") | ||||
bar2 = batch.bar(b="Uno", a="Due") | ||||
Gregory Szorc
|
r33761 | |||
# Future shouldn't be set until we submit(). | ||||
assert isinstance(foo, peer.future) | ||||
assert not util.safehasattr(foo, 'value') | ||||
assert not util.safehasattr(bar, 'value') | ||||
Peter Arrenbrecht
|
r14621 | batch.submit() | ||
Gregory Szorc
|
r33761 | # Call results() to obtain results as a generator. | ||
results = batch.results() | ||||
# Future results shouldn't be set until we consume a value. | ||||
assert not util.safehasattr(foo, 'value') | ||||
foovalue = next(results) | ||||
assert util.safehasattr(foo, 'value') | ||||
assert foovalue == foo.value | ||||
Robert Stanca
|
r28732 | print(foo.value) | ||
Gregory Szorc
|
r33761 | next(results) | ||
Robert Stanca
|
r28732 | print(bar.value) | ||
Gregory Szorc
|
r33761 | next(results) | ||
Robert Stanca
|
r28732 | print(bar2.value) | ||
Peter Arrenbrecht
|
r14621 | |||
Gregory Szorc
|
r33761 | # We should be at the end of the results generator. | ||
try: | ||||
next(results) | ||||
except StopIteration: | ||||
print('proper end of results generator') | ||||
else: | ||||
print('extra emitted element!') | ||||
# Attempting to call a non-batchable method inside a batch fails. | ||||
batch = it.batchiter() | ||||
try: | ||||
batch.greet(name='John Smith') | ||||
except error.ProgrammingError as e: | ||||
print(e) | ||||
# Attempting to call a local method inside a batch fails. | ||||
batch = it.batchiter() | ||||
try: | ||||
batch.hello() | ||||
except error.ProgrammingError as e: | ||||
print(e) | ||||
Peter Arrenbrecht
|
r14621 | # local usage | ||
mylocal = localthing() | ||||
Robert Stanca
|
r28732 | print() | ||
print("== Local") | ||||
Peter Arrenbrecht
|
r14621 | use(mylocal) | ||
# demo remoting; mimicks what wireproto and HTTP/SSH do | ||||
# shared | ||||
def escapearg(plain): | ||||
return (plain | ||||
.replace(':', '::') | ||||
.replace(',', ':,') | ||||
.replace(';', ':;') | ||||
.replace('=', ':=')) | ||||
def unescapearg(escaped): | ||||
return (escaped | ||||
.replace(':=', '=') | ||||
.replace(':;', ';') | ||||
.replace(':,', ',') | ||||
.replace('::', ':')) | ||||
# server side | ||||
# equivalent of wireproto's global functions | ||||
Thomas Arendsen Hein
|
r14764 | class server(object): | ||
Peter Arrenbrecht
|
r14621 | def __init__(self, local): | ||
self.local = local | ||||
def _call(self, name, args): | ||||
args = dict(arg.split('=', 1) for arg in args) | ||||
return getattr(self, name)(**args) | ||||
def perform(self, req): | ||||
Robert Stanca
|
r28732 | print("REQ:", req) | ||
Peter Arrenbrecht
|
r14621 | name, args = req.split('?', 1) | ||
args = args.split('&') | ||||
vals = dict(arg.split('=', 1) for arg in args) | ||||
res = getattr(self, name)(**vals) | ||||
Robert Stanca
|
r28732 | print(" ->", res) | ||
Peter Arrenbrecht
|
r14621 | return res | ||
def batch(self, cmds): | ||||
res = [] | ||||
for pair in cmds.split(';'): | ||||
name, args = pair.split(':', 1) | ||||
vals = {} | ||||
for a in args.split(','): | ||||
if a: | ||||
n, v = a.split('=') | ||||
vals[n] = unescapearg(v) | ||||
res.append(escapearg(getattr(self, name)(**vals))) | ||||
return ';'.join(res) | ||||
def foo(self, one, two): | ||||
return mangle(self.local.foo(unmangle(one), unmangle(two))) | ||||
def bar(self, b, a): | ||||
return mangle(self.local.bar(unmangle(b), unmangle(a))) | ||||
def greet(self, name): | ||||
return mangle(self.local.greet(unmangle(name))) | ||||
myserver = server(mylocal) | ||||
# local side | ||||
# equivalent of wireproto.encode/decodelist, that is, type-specific marshalling | ||||
# here we just transform the strings a bit to check we're properly en-/decoding | ||||
def mangle(s): | ||||
return ''.join(chr(ord(c) + 1) for c in s) | ||||
def unmangle(s): | ||||
return ''.join(chr(ord(c) - 1) for c in s) | ||||
# equivalent of wireproto.wirerepository and something like http's wire format | ||||
class remotething(thing): | ||||
def __init__(self, server): | ||||
self.server = server | ||||
def _submitone(self, name, args): | ||||
req = name + '?' + '&'.join(['%s=%s' % (n, v) for n, v in args]) | ||||
return self.server.perform(req) | ||||
def _submitbatch(self, cmds): | ||||
req = [] | ||||
for name, args in cmds: | ||||
args = ','.join(n + '=' + escapearg(v) for n, v in args) | ||||
req.append(name + ':' + args) | ||||
req = ';'.join(req) | ||||
res = self._submitone('batch', [('cmds', req,)]) | ||||
Gregory Szorc
|
r33761 | for r in res.split(';'): | ||
yield r | ||||
Peter Arrenbrecht
|
r14621 | |||
Gregory Szorc
|
r33761 | def batchiter(self): | ||
return wireproto.remoteiterbatcher(self) | ||||
Peter Arrenbrecht
|
r14621 | |||
Yuya Nishihara
|
r28800 | @peer.batchable | ||
Peter Arrenbrecht
|
r14621 | def foo(self, one, two=None): | ||
encargs = [('one', mangle(one),), ('two', mangle(two),)] | ||||
Yuya Nishihara
|
r28800 | encresref = peer.future() | ||
Peter Arrenbrecht
|
r14621 | yield encargs, encresref | ||
yield unmangle(encresref.value) | ||||
Yuya Nishihara
|
r28800 | @peer.batchable | ||
Peter Arrenbrecht
|
r14621 | def bar(self, b, a): | ||
Yuya Nishihara
|
r28800 | encresref = peer.future() | ||
Peter Arrenbrecht
|
r14621 | yield [('b', mangle(b),), ('a', mangle(a),)], encresref | ||
yield unmangle(encresref.value) | ||||
# greet is coded directly. It therefore does not support batching. If it | ||||
# does appear in a batch, the batch is split around greet, and the call to | ||||
# greet is done in its own roundtrip. | ||||
def greet(self, name=None): | ||||
return unmangle(self._submitone('greet', [('name', mangle(name),)])) | ||||
# demo remote usage | ||||
myproxy = remotething(myserver) | ||||
Robert Stanca
|
r28732 | print() | ||
print("== Remote") | ||||
Peter Arrenbrecht
|
r14621 | use(myproxy) | ||