test-batching.py
184 lines
| 5.5 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 | |||
Gregory Szorc
|
r37651 | import contextlib | ||
Yuya Nishihara
|
r28800 | from mercurial import ( | ||
Gregory Szorc
|
r37633 | localrepo, | ||
Augie Fackler
|
r41366 | pycompat, | ||
Gregory Szorc
|
r37632 | wireprotov1peer, | ||
Augie Fackler
|
r41366 | ) | ||
Gregory Szorc
|
r37633 | |||
Augie Fackler
|
r41366 | def bprint(*bs): | ||
print(*[pycompat.sysstr(b) for b in bs]) | ||||
Peter Arrenbrecht
|
r14621 | |||
# equivalent of repo.repository | ||||
class thing(object): | ||||
def hello(self): | ||||
Augie Fackler
|
r41366 | return b"Ready." | ||
Peter Arrenbrecht
|
r14621 | |||
# equivalent of localrepo.localrepository | ||||
class localthing(thing): | ||||
def foo(self, one, two=None): | ||||
if one: | ||||
Augie Fackler
|
r41366 | return b"%s and %s" % (one, two,) | ||
return b"Nope" | ||||
Peter Arrenbrecht
|
r14621 | def bar(self, b, a): | ||
Augie Fackler
|
r41366 | return b"%s und %s" % (b, a,) | ||
Peter Arrenbrecht
|
r14621 | def greet(self, name=None): | ||
Augie Fackler
|
r41366 | return b"Hello, %s" % name | ||
Gregory Szorc
|
r37651 | |||
@contextlib.contextmanager | ||||
def commandexecutor(self): | ||||
e = localrepo.localcommandexecutor(self) | ||||
try: | ||||
yield e | ||||
finally: | ||||
e.close() | ||||
Peter Arrenbrecht
|
r14621 | |||
# usage of "thing" interface | ||||
def use(it): | ||||
# Direct call to base method shared between client and server. | ||||
Augie Fackler
|
r41366 | bprint(it.hello()) | ||
Peter Arrenbrecht
|
r14621 | |||
# Direct calls to proxied methods. They cause individual roundtrips. | ||||
Augie Fackler
|
r41366 | bprint(it.foo(b"Un", two=b"Deux")) | ||
bprint(it.bar(b"Eins", b"Zwei")) | ||||
Peter Arrenbrecht
|
r14621 | |||
Gregory Szorc
|
r33761 | # Batched call to a couple of proxied methods. | ||
Gregory Szorc
|
r37651 | with it.commandexecutor() as e: | ||
Augie Fackler
|
r41366 | ffoo = e.callcommand(b'foo', {b'one': b'One', b'two': b'Two'}) | ||
fbar = e.callcommand(b'bar', {b'b': b'Eins', b'a': b'Zwei'}) | ||||
fbar2 = e.callcommand(b'bar', {b'b': b'Uno', b'a': b'Due'}) | ||||
Peter Arrenbrecht
|
r14621 | |||
Augie Fackler
|
r41366 | bprint(ffoo.result()) | ||
bprint(fbar.result()) | ||||
bprint(fbar2.result()) | ||||
Gregory Szorc
|
r33761 | |||
Peter Arrenbrecht
|
r14621 | # local usage | ||
mylocal = localthing() | ||||
Robert Stanca
|
r28732 | print() | ||
Augie Fackler
|
r41366 | bprint(b"== Local") | ||
Peter Arrenbrecht
|
r14621 | use(mylocal) | ||
# demo remoting; mimicks what wireproto and HTTP/SSH do | ||||
# shared | ||||
def escapearg(plain): | ||||
return (plain | ||||
Augie Fackler
|
r41366 | .replace(b':', b'::') | ||
.replace(b',', b':,') | ||||
.replace(b';', b':;') | ||||
.replace(b'=', b':=')) | ||||
Peter Arrenbrecht
|
r14621 | def unescapearg(escaped): | ||
return (escaped | ||||
Augie Fackler
|
r41366 | .replace(b':=', b'=') | ||
.replace(b':;', b';') | ||||
.replace(b':,', b',') | ||||
.replace(b'::', b':')) | ||||
Peter Arrenbrecht
|
r14621 | |||
# 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): | ||||
Augie Fackler
|
r41366 | args = dict(arg.split(b'=', 1) for arg in args) | ||
Peter Arrenbrecht
|
r14621 | return getattr(self, name)(**args) | ||
def perform(self, req): | ||||
Augie Fackler
|
r41366 | bprint(b"REQ:", req) | ||
name, args = req.split(b'?', 1) | ||||
args = args.split(b'&') | ||||
vals = dict(arg.split(b'=', 1) for arg in args) | ||||
res = getattr(self, pycompat.sysstr(name))(**pycompat.strkwargs(vals)) | ||||
bprint(b" ->", res) | ||||
Peter Arrenbrecht
|
r14621 | return res | ||
def batch(self, cmds): | ||||
res = [] | ||||
Augie Fackler
|
r41366 | for pair in cmds.split(b';'): | ||
name, args = pair.split(b':', 1) | ||||
Peter Arrenbrecht
|
r14621 | vals = {} | ||
Augie Fackler
|
r41366 | for a in args.split(b','): | ||
Peter Arrenbrecht
|
r14621 | if a: | ||
Augie Fackler
|
r41366 | n, v = a.split(b'=') | ||
Peter Arrenbrecht
|
r14621 | vals[n] = unescapearg(v) | ||
Augie Fackler
|
r41366 | res.append(escapearg(getattr(self, pycompat.sysstr(name))( | ||
**pycompat.strkwargs(vals)))) | ||||
return b';'.join(res) | ||||
Peter Arrenbrecht
|
r14621 | 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): | ||||
Augie Fackler
|
r41366 | return b''.join(pycompat.bytechr(ord(c) + 1) for c in pycompat.bytestr(s)) | ||
Peter Arrenbrecht
|
r14621 | def unmangle(s): | ||
Augie Fackler
|
r41366 | return b''.join(pycompat.bytechr(ord(c) - 1) for c in pycompat.bytestr(s)) | ||
Peter Arrenbrecht
|
r14621 | |||
# 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): | ||||
Augie Fackler
|
r41366 | req = name + b'?' + b'&'.join([b'%s=%s' % (n, v) for n, v in args]) | ||
Peter Arrenbrecht
|
r14621 | return self.server.perform(req) | ||
def _submitbatch(self, cmds): | ||||
req = [] | ||||
for name, args in cmds: | ||||
Augie Fackler
|
r41366 | args = b','.join(n + b'=' + escapearg(v) for n, v in args) | ||
req.append(name + b':' + args) | ||||
req = b';'.join(req) | ||||
res = self._submitone(b'batch', [(b'cmds', req,)]) | ||||
for r in res.split(b';'): | ||||
Gregory Szorc
|
r33761 | yield r | ||
Peter Arrenbrecht
|
r14621 | |||
Gregory Szorc
|
r37651 | @contextlib.contextmanager | ||
def commandexecutor(self): | ||||
e = wireprotov1peer.peerexecutor(self) | ||||
try: | ||||
yield e | ||||
finally: | ||||
e.close() | ||||
Peter Arrenbrecht
|
r14621 | |||
Gregory Szorc
|
r37633 | @wireprotov1peer.batchable | ||
Peter Arrenbrecht
|
r14621 | def foo(self, one, two=None): | ||
Augie Fackler
|
r41366 | encargs = [(b'one', mangle(one),), (b'two', mangle(two),)] | ||
Gregory Szorc
|
r37633 | encresref = wireprotov1peer.future() | ||
Peter Arrenbrecht
|
r14621 | yield encargs, encresref | ||
yield unmangle(encresref.value) | ||||
Gregory Szorc
|
r37633 | @wireprotov1peer.batchable | ||
Peter Arrenbrecht
|
r14621 | def bar(self, b, a): | ||
Gregory Szorc
|
r37633 | encresref = wireprotov1peer.future() | ||
Augie Fackler
|
r41366 | yield [(b'b', mangle(b),), (b'a', mangle(a),)], encresref | ||
Peter Arrenbrecht
|
r14621 | 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): | ||||
Augie Fackler
|
r41366 | return unmangle(self._submitone(b'greet', [(b'name', mangle(name),)])) | ||
Peter Arrenbrecht
|
r14621 | |||
# demo remote usage | ||||
myproxy = remotething(myserver) | ||||
Robert Stanca
|
r28732 | print() | ||
Augie Fackler
|
r41366 | bprint(b"== Remote") | ||
Peter Arrenbrecht
|
r14621 | use(myproxy) | ||