|
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) |