Merge lp:~kalikiana/u1db-qt/upstream-tests into lp:u1db-qt
- upstream-tests
- Merge into trunk
Proposed by
Cris Dywan
Status: | Work in progress |
---|---|
Proposed branch: | lp:~kalikiana/u1db-qt/upstream-tests |
Merge into: | lp:u1db-qt |
Diff against target: |
693 lines (+678/-0) 3 files modified
tests/bridged.py (+486/-0) tests/setup.py (+6/-0) tests/test-bridged.py (+186/-0) |
To merge this branch: | bzr merge lp:~kalikiana/u1db-qt/upstream-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
U1DB Qt developers | Pending | ||
Review via email: mp+182339@code.launchpad.net |
Commit message
First attempt at implementing Python U1Db class for unit tests
Description of the change
To post a comment you must log in.
Unmerged revisions
- 68. By Cris Dywan
-
First attempt at implementing Python U1Db class for unit tests
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'tests/__init__.py' | |||
2 | === added file 'tests/bridged.py' | |||
3 | --- tests/bridged.py 1970-01-01 00:00:00 +0000 | |||
4 | +++ tests/bridged.py 2013-08-27 10:28:13 +0000 | |||
5 | @@ -0,0 +1,486 @@ | |||
6 | 1 | import atexit | ||
7 | 2 | import functools | ||
8 | 3 | import inspect | ||
9 | 4 | import json | ||
10 | 5 | import os | ||
11 | 6 | |||
12 | 7 | from u1db import ( | ||
13 | 8 | Database as DbInterface, | ||
14 | 9 | SyncTarget as SyncTargetInterface, | ||
15 | 10 | Document, | ||
16 | 11 | errors | ||
17 | 12 | ) | ||
18 | 13 | from tools import js_bridge | ||
19 | 14 | |||
20 | 15 | |||
21 | 16 | _js_bridge = None | ||
22 | 17 | |||
23 | 18 | XHR_IMPL = [ | ||
24 | 19 | 'platform/core.js', | ||
25 | 20 | 'platform/rhino.js', | ||
26 | 21 | 'console.js', | ||
27 | 22 | 'dom.js', | ||
28 | 23 | 'event.js', | ||
29 | 24 | 'html.js', | ||
30 | 25 | 'timer.js', | ||
31 | 26 | 'xhr.js' | ||
32 | 27 | ] | ||
33 | 28 | |||
34 | 29 | |||
35 | 30 | def get_u1db_js_bridge(): | ||
36 | 31 | global _js_bridge | ||
37 | 32 | if _js_bridge is None: | ||
38 | 33 | _js_bridge = js_bridge.JsBridge() | ||
39 | 34 | if _js_bridge.rhino: | ||
40 | 35 | for xhr_impl_bit in XHR_IMPL: | ||
41 | 36 | _js_bridge.load(os.path.join('tests', | ||
42 | 37 | 'dist-env-js', xhr_impl_bit)) | ||
43 | 38 | _js_bridge.load('oauth/sha1.js') | ||
44 | 39 | _js_bridge.load('oauth/oauth.js') | ||
45 | 40 | _js_bridge.load('u1db.js') | ||
46 | 41 | _js_bridge._orig_globals = _js_bridge.global_names() | ||
47 | 42 | _js_bridge._check_escaped = os.getenv('CHECK_ESCAPED') is not None | ||
48 | 43 | atexit.register(_js_bridge.quit) | ||
49 | 44 | return _js_bridge | ||
50 | 45 | |||
51 | 46 | |||
52 | 47 | to_js = json.dumps | ||
53 | 48 | |||
54 | 49 | |||
55 | 50 | def apply_content(doc, content): | ||
56 | 51 | if content is None: | ||
57 | 52 | doc.set_json(None) | ||
58 | 53 | else: | ||
59 | 54 | doc.content = content | ||
60 | 55 | |||
61 | 56 | |||
62 | 57 | def doc_from_dic(val): | ||
63 | 58 | doc = Document(doc_id=val['doc_id'], rev=val['rev'], | ||
64 | 59 | has_conflicts=val['has_conflicts']) | ||
65 | 60 | apply_content(doc, val['content']) | ||
66 | 61 | return doc | ||
67 | 62 | |||
68 | 63 | |||
69 | 64 | class BridgedJsObject(object): | ||
70 | 65 | |||
71 | 66 | # medium level interface | ||
72 | 67 | |||
73 | 68 | def _check_for_escaped(self): | ||
74 | 69 | if self._js_bridge._check_escaped: | ||
75 | 70 | orig_globals = self._js_bridge._orig_globals | ||
76 | 71 | cur_globals = self._js_bridge.global_names() | ||
77 | 72 | if cur_globals != orig_globals: | ||
78 | 73 | raise Exception("escaped vars: %r" % | ||
79 | 74 | cur_globals.symmetric_difference(orig_globals)) | ||
80 | 75 | |||
81 | 76 | _count = 1 | ||
82 | 77 | |||
83 | 78 | def _fresh_count(self): | ||
84 | 79 | c = self._count | ||
85 | 80 | BridgedJsObject._count += 1 | ||
86 | 81 | return c | ||
87 | 82 | |||
88 | 83 | def _make_db(self, dbname): | ||
89 | 84 | dbvar = "db%d" % self._fresh_count() | ||
90 | 85 | self._js_bridge.retain(dbvar, "new U1DB(%s, true)" % to_js(dbname)) | ||
91 | 86 | self._check_for_escaped() | ||
92 | 87 | return dbvar | ||
93 | 88 | |||
94 | 89 | def _forget_ref(self, var): | ||
95 | 90 | self._js_bridge.forget([var]) | ||
96 | 91 | |||
97 | 92 | _forget_db = _forget_ref | ||
98 | 93 | |||
99 | 94 | def _make_http_sync_target(self, url): | ||
100 | 95 | tgtvar = "tgt%d" % self._fresh_count() | ||
101 | 96 | self._js_bridge.retain(tgtvar, "new U1DBHTTPSyncTarget(%s)" % to_js(url)) | ||
102 | 97 | self._check_for_escaped() | ||
103 | 98 | return tgtvar | ||
104 | 99 | |||
105 | 100 | _forget_http_sync_target = _forget_ref | ||
106 | 101 | |||
107 | 102 | def _make_synchronizer(self, db_ref, tgt_ref): | ||
108 | 103 | synchronizervar = "synchronizer%d" % self._fresh_count() | ||
109 | 104 | self._js_bridge.retain(synchronizervar, "new U1DBSynchronizer(%s, %s)" % | ||
110 | 105 | (db_ref, tgt_ref)) | ||
111 | 106 | self._check_for_escaped() | ||
112 | 107 | return synchronizervar | ||
113 | 108 | |||
114 | 109 | _forget_synchronizer = _forget_ref | ||
115 | 110 | |||
116 | 111 | def _temp_doc_expr(self, doc): | ||
117 | 112 | return "new U1DBDocument(%s,%s,%s,%s)" % ( | ||
118 | 113 | to_js(doc.doc_id), to_js(doc.rev), | ||
119 | 114 | to_js(doc.content), to_js(doc.has_conflicts)) | ||
120 | 115 | |||
121 | 116 | def _make_doc(self, doc): | ||
122 | 117 | docvar = "doc%d" % self._fresh_count() | ||
123 | 118 | self._js_bridge.retain(docvar, self._temp_doc_expr(doc)) | ||
124 | 119 | self._check_for_escaped() | ||
125 | 120 | return docvar | ||
126 | 121 | |||
127 | 122 | _forget_doc = _forget_ref | ||
128 | 123 | |||
129 | 124 | _nil_cb = "null" | ||
130 | 125 | |||
131 | 126 | def _make_cb(self, func): | ||
132 | 127 | cbvar = "cb%d" % self._fresh_count() | ||
133 | 128 | self._js_bridge.make_cb(cbvar, func) | ||
134 | 129 | return cbvar | ||
135 | 130 | |||
136 | 131 | def _make_cb_doc(self, func): | ||
137 | 132 | @functools.wraps(func) | ||
138 | 133 | def wrap(doc_dic, *args): | ||
139 | 134 | return func(doc_from_dic(doc_dic), *args) | ||
140 | 135 | return self._make_cb(wrap) | ||
141 | 136 | |||
142 | 137 | def _forget_cb(self, cb_ref): | ||
143 | 138 | self._js_bridge.forget_cb(cb_ref) | ||
144 | 139 | |||
145 | 140 | def _do_invoke(self, ref, meth_name, opts, js_args): | ||
146 | 141 | if opts: | ||
147 | 142 | js_args = js_args + [to_js(opts[0])] | ||
148 | 143 | try: | ||
149 | 144 | res = self._js_bridge.eval("%s.%s(%s)" % (ref, meth_name, | ||
150 | 145 | ', '.join(js_args))) | ||
151 | 146 | except js_bridge.JSError, js_err: | ||
152 | 147 | if hasattr(errors, js_err.name): | ||
153 | 148 | if js_err.name == "HTTPError": | ||
154 | 149 | raise errors.HTTPError(500, js_err.message) # xxx != 500 | ||
155 | 150 | raise getattr(errors, js_err.name)(js_err.message) | ||
156 | 151 | raise | ||
157 | 152 | self._check_for_escaped() | ||
158 | 153 | return res | ||
159 | 154 | |||
160 | 155 | natural_calling = True | ||
161 | 156 | |||
162 | 157 | def _invoke(self, ref, meth_name, opts, args): | ||
163 | 158 | return self._do_invoke(ref, meth_name, opts, map(to_js, args)) | ||
164 | 159 | |||
165 | 160 | def _invoke_varargs(self, ref, meth_name, opts, args, rest): | ||
166 | 161 | return self._do_invoke(ref, meth_name, opts, | ||
167 | 162 | map(to_js, args+list(rest))) | ||
168 | 163 | |||
169 | 164 | def _invoke_ret_tuple(self, ref, meth_name, opts, args): | ||
170 | 165 | return tuple(self._do_invoke(ref, meth_name, opts, map(to_js, args))) | ||
171 | 166 | |||
172 | 167 | def _invoke_ret_doc(self, ref, meth_name, opts, args): | ||
173 | 168 | dic = self._do_invoke(ref, meth_name, opts, map(to_js, args)) | ||
174 | 169 | if dic is None: | ||
175 | 170 | return None | ||
176 | 171 | return doc_from_dic(dic) | ||
177 | 172 | |||
178 | 173 | def _invoke_ret_whats_changed(self, ref, meth_name, opts, args): | ||
179 | 174 | # one single optional arg, like whats_changed | ||
180 | 175 | if self.natural_calling and not args and opts: | ||
181 | 176 | if opts[0]: | ||
182 | 177 | args = [opts[0].values()[0]] | ||
183 | 178 | opts = None | ||
184 | 179 | gen, trans_id, changes = self._do_invoke(ref, meth_name, opts, | ||
185 | 180 | map(to_js, args)) | ||
186 | 181 | return gen, trans_id, map(tuple, changes) | ||
187 | 182 | |||
188 | 183 | def _invoke_ret_docs(self, ref, meth_name, opts, args): | ||
189 | 184 | dics = self._do_invoke(ref, meth_name, opts, map(to_js, args)) | ||
190 | 185 | return map(doc_from_dic, dics) | ||
191 | 186 | |||
192 | 187 | def _invoke_varargs_ret_docs(self, ref, meth_name, opts, args, rest): | ||
193 | 188 | dics = self._do_invoke(ref, meth_name, opts, | ||
194 | 189 | map(to_js, args+list(rest))) | ||
195 | 190 | return map(doc_from_dic, dics) | ||
196 | 191 | |||
197 | 192 | def _invoke_ret_tuples(self, ref, meth_name, opts, args): | ||
198 | 193 | lst = self._do_invoke(ref, meth_name, opts, map(to_js, args)) | ||
199 | 194 | return map(tuple, lst) | ||
200 | 195 | |||
201 | 196 | def _invoke_ret_gen_docs(self, ref, meth_name, opts, args): | ||
202 | 197 | gen, dics = self._do_invoke(ref, meth_name, opts, map(to_js, args)) | ||
203 | 198 | return gen, map(doc_from_dic, dics) | ||
204 | 199 | |||
205 | 200 | def _invoke_doc(self, ref, meth_name, opts, doc_ref, args): | ||
206 | 201 | return self._do_invoke(ref, meth_name, opts, [doc_ref] + map(to_js, | ||
207 | 202 | args)) | ||
208 | 203 | |||
209 | 204 | def _invoke_doc_ret_tuple(self, ref, meth_name, opts, doc_ref, args): | ||
210 | 205 | return tuple(self._do_invoke(ref, meth_name, opts, | ||
211 | 206 | [doc_ref] + map(to_js, args))) | ||
212 | 207 | |||
213 | 208 | def _read_into_doc(self, doc_ref, doc): | ||
214 | 209 | doc.rev, doc.has_conflicts, content = self._js_bridge.eval( | ||
215 | 210 | "[%s.rev, %s.has_conflicts, %s.content]" % ((doc_ref,)*3)) | ||
216 | 211 | apply_content(doc, content) | ||
217 | 212 | |||
218 | 213 | def _invoke_sync_exchange(self, ref, docs_by_generations, | ||
219 | 214 | source_replica_uid, | ||
220 | 215 | last_known_generation, last_known_trans_id, | ||
221 | 216 | return_doc_cb_ref, ensure_cb_ref): | ||
222 | 217 | docs_by_gen_exprs = [] | ||
223 | 218 | for doc, gen, trans_id in docs_by_generations: | ||
224 | 219 | docs_by_gen_exprs.append("[%s, %s, %s]" % ( | ||
225 | 220 | self._temp_doc_expr(doc), to_js(gen), to_js(trans_id))) | ||
226 | 221 | docs_by_gen_expr = "[%s]" % ', '.join(docs_by_gen_exprs) | ||
227 | 222 | return tuple(self._do_invoke(ref, 'sync_exchange', None, | ||
228 | 223 | [docs_by_gen_expr, | ||
229 | 224 | to_js(source_replica_uid), | ||
230 | 225 | to_js(last_known_generation), | ||
231 | 226 | to_js(last_known_trans_id), | ||
232 | 227 | return_doc_cb_ref, | ||
233 | 228 | ensure_cb_ref])) | ||
234 | 229 | |||
235 | 230 | def _invoke_cb(self, ref, meth_name, cb_ref): | ||
236 | 231 | return self._do_invoke(ref, meth_name, None, [cb_ref]) | ||
237 | 232 | |||
238 | 233 | # end of medium level interface | ||
239 | 234 | |||
240 | 235 | def _parse_opts(self, opt_spec, *args): | ||
241 | 236 | if opt_spec is None: | ||
242 | 237 | return args, None | ||
243 | 238 | opt_args, defl_vals = opt_spec | ||
244 | 239 | opt = len(defl_vals) | ||
245 | 240 | opts, defl = {}, {} | ||
246 | 241 | for argname, argval, argdefl in zip(opt_args, args[-opt:], defl_vals): | ||
247 | 242 | if argval != argdefl: | ||
248 | 243 | opts[argname] = argval | ||
249 | 244 | else: | ||
250 | 245 | defl[argname] = argdefl | ||
251 | 246 | return args[:-opt], (opts, defl) | ||
252 | 247 | |||
253 | 248 | def _bridge_meth(self, name, opts, args): | ||
254 | 249 | return self._invoke(self._ref, name, opts, args) | ||
255 | 250 | |||
256 | 251 | def _bridge_meth_doc(self, name, opts, args, ret_kind=None): | ||
257 | 252 | invoke = self._invoke_doc | ||
258 | 253 | if ret_kind != None: | ||
259 | 254 | invoke = getattr(self, '_invoke_doc_ret_%s' % ret_kind) | ||
260 | 255 | doc = args[0] | ||
261 | 256 | doc_ref = self._make_doc(doc) | ||
262 | 257 | try: | ||
263 | 258 | res = invoke(self._ref, name, opts, doc_ref, args[1:]) | ||
264 | 259 | self._read_into_doc(doc_ref, doc) | ||
265 | 260 | return res | ||
266 | 261 | finally: | ||
267 | 262 | self._forget_doc(doc_ref) | ||
268 | 263 | |||
269 | 264 | _bridge_meth_doc_conv = _bridge_meth_doc | ||
270 | 265 | |||
271 | 266 | def _bridge_meth_conv(self, name, opts, args, ret_kind): | ||
272 | 267 | invoke = getattr(self, '_invoke_ret_%s' % ret_kind) | ||
273 | 268 | return invoke(self._ref, name, opts, args) | ||
274 | 269 | |||
275 | 270 | def _bridge_meth_varargs(self, name, opts, args, rest, ret_kind=None): | ||
276 | 271 | invoke = self._invoke_varargs | ||
277 | 272 | if ret_kind != None: | ||
278 | 273 | invoke = getattr(self, '_invoke_varargs_ret_%s' % ret_kind) | ||
279 | 274 | return invoke(self._ref, name, opts, args, rest) | ||
280 | 275 | |||
281 | 276 | _bridge_meth_varargs_conv = _bridge_meth_varargs | ||
282 | 277 | |||
283 | 278 | def _bridge_meth_sync_exchange(self, docs_by_generations, | ||
284 | 279 | source_replica_uid, | ||
285 | 280 | last_known_generation, last_known_trans_id, | ||
286 | 281 | return_doc_cb, ensure_callback): | ||
287 | 282 | return_doc_cb_ref = self._make_cb_doc(return_doc_cb) | ||
288 | 283 | if ensure_callback is not None: | ||
289 | 284 | ensure_cb_ref = self._make_cb(ensure_callback) | ||
290 | 285 | else: | ||
291 | 286 | ensure_cb_ref = self._nil_cb | ||
292 | 287 | try: | ||
293 | 288 | return self._invoke_sync_exchange(self._ref, docs_by_generations, | ||
294 | 289 | source_replica_uid, | ||
295 | 290 | last_known_generation, | ||
296 | 291 | last_known_trans_id, | ||
297 | 292 | return_doc_cb_ref, | ||
298 | 293 | ensure_cb_ref) | ||
299 | 294 | finally: | ||
300 | 295 | if ensure_callback is not None: | ||
301 | 296 | self._forget_cb(ensure_cb_ref) | ||
302 | 297 | self._forget_cb(return_doc_cb_ref) | ||
303 | 298 | |||
304 | 299 | |||
305 | 300 | _BRIDGE_METH_TEMPL = """ | ||
306 | 301 | def bridge_meth(self, %(args)s): | ||
307 | 302 | args, opts = self._parse_opts(%(opt_spec)r, %(args)s) | ||
308 | 303 | return self._bridge_meth%(sign)s(%(name)r, opts, args%(conv)s) | ||
309 | 304 | """ | ||
310 | 305 | |||
311 | 306 | |||
312 | 307 | def _complete_interface(interface_cls, bridge_cls, prot_exposed, skip, ret): | ||
313 | 308 | for name, val in interface_cls.__dict__.items(): | ||
314 | 309 | if name.startswith('_') and name not in prot_exposed: | ||
315 | 310 | continue | ||
316 | 311 | if name in skip: | ||
317 | 312 | continue | ||
318 | 313 | if name in bridge_cls.__dict__: | ||
319 | 314 | continue | ||
320 | 315 | spec = inspect.getargspec(getattr(interface_cls, name)) | ||
321 | 316 | assert spec.args[0] == 'self' | ||
322 | 317 | args = spec.args[1:] | ||
323 | 318 | if spec.varargs: | ||
324 | 319 | assert spec.keywords is None | ||
325 | 320 | assert False, "*args, not supported yet" | ||
326 | 321 | else: | ||
327 | 322 | opt_spec = None | ||
328 | 323 | if spec.defaults: | ||
329 | 324 | opt_spec = (args[-len(spec.defaults):], spec.defaults) | ||
330 | 325 | sign = '' | ||
331 | 326 | if args and args[0] == 'doc': | ||
332 | 327 | sign += '_doc' | ||
333 | 328 | ret_kind = ret.get(name) | ||
334 | 329 | if ret_kind: | ||
335 | 330 | sign += '_conv' | ||
336 | 331 | conv = ", %r" % ret_kind | ||
337 | 332 | else: | ||
338 | 333 | conv = '' | ||
339 | 334 | def_code = _BRIDGE_METH_TEMPL % dict(name=name, opt_spec=opt_spec, | ||
340 | 335 | sign=sign, args=', '.join(args), | ||
341 | 336 | conv=conv) | ||
342 | 337 | ns = {} | ||
343 | 338 | exec def_code in ns | ||
344 | 339 | bridge_meth = ns['bridge_meth'] | ||
345 | 340 | bridge_meth.__name__ = name | ||
346 | 341 | bridge_meth.func_defaults = spec.defaults | ||
347 | 342 | setattr(bridge_cls, name, bridge_meth) | ||
348 | 343 | |||
349 | 344 | |||
350 | 345 | class BridgedJsDatabase(BridgedJsObject): | ||
351 | 346 | |||
352 | 347 | def __init__(self, replica_uid): | ||
353 | 348 | self._js_bridge = get_u1db_js_bridge() | ||
354 | 349 | self._ref = self._make_db(replica_uid) | ||
355 | 350 | |||
356 | 351 | def _forget(self): | ||
357 | 352 | self._forget_db(self._ref) | ||
358 | 353 | |||
359 | 354 | def delete_db(self): | ||
360 | 355 | return self._bridge_meth('delete_db', None, []) | ||
361 | 356 | |||
362 | 357 | # testing/internal api | ||
363 | 358 | |||
364 | 359 | def _get_transaction_log(self): | ||
365 | 360 | return self._bridge_meth('_get_transaction_log', None, []) | ||
366 | 361 | |||
367 | 362 | def _validate_source(self, other_replica_uid, other_generation, | ||
368 | 363 | other_transaction_id): | ||
369 | 364 | return self._bridge_meth('_validate_source', None, | ||
370 | 365 | [other_replica_uid, other_generation, | ||
371 | 366 | other_transaction_id]) | ||
372 | 367 | |||
373 | 368 | def validate_gen_and_trans_id(self, generation, trans_id): | ||
374 | 369 | return self._bridge_meth('validate_gen_and_trans_id', None, | ||
375 | 370 | [generation, trans_id]) | ||
376 | 371 | |||
377 | 372 | def _get_generation_info(self): | ||
378 | 373 | return self._bridge_meth_conv('_get_generation_info', | ||
379 | 374 | None, [], ret_kind='tuple') | ||
380 | 375 | |||
381 | 376 | def create_index(self, name, *index_expressions): | ||
382 | 377 | return self._bridge_meth_varargs('create_index', None, | ||
383 | 378 | [name], index_expressions) | ||
384 | 379 | |||
385 | 380 | def get_from_index(self, name, *keys): | ||
386 | 381 | return self._bridge_meth_varargs_conv('get_from_index', | ||
387 | 382 | None, [name], keys, | ||
388 | 383 | ret_kind='docs') | ||
389 | 384 | |||
390 | 385 | def get_range_from_index(self, name, start_keys, end_keys=None): | ||
391 | 386 | return self._bridge_meth_conv('get_range_from_index', | ||
392 | 387 | None, [name, start_keys, end_keys], | ||
393 | 388 | ret_kind='docs') | ||
394 | 389 | |||
395 | 390 | |||
396 | 391 | _complete_interface(DbInterface, BridgedJsDatabase, | ||
397 | 392 | prot_exposed=set([ | ||
398 | 393 | '_put_doc_if_newer', | ||
399 | 394 | '_get_replica_gen_and_trans_id', | ||
400 | 395 | '_set_replica_gen_and_trans_id', | ||
401 | 396 | ]), | ||
402 | 397 | skip=set(['set_document_factory',]), | ||
403 | 398 | ret={ | ||
404 | 399 | 'create_doc_from_json': 'doc', | ||
405 | 400 | 'create_doc': 'doc', | ||
406 | 401 | 'get_doc': 'doc', | ||
407 | 402 | 'get_doc_conflicts': 'docs', | ||
408 | 403 | 'get_docs': 'docs', | ||
409 | 404 | 'get_all_docs': 'gen_docs', | ||
410 | 405 | 'whats_changed': 'whats_changed', | ||
411 | 406 | '_get_replica_gen_and_trans_id': 'tuple', | ||
412 | 407 | '_put_doc_if_newer': 'tuple', | ||
413 | 408 | 'list_indexes': 'tuples', | ||
414 | 409 | 'get_index_keys': 'tuples' | ||
415 | 410 | }) | ||
416 | 411 | |||
417 | 412 | |||
418 | 413 | class BridgedJsHTTPSyncTarget(BridgedJsObject): | ||
419 | 414 | |||
420 | 415 | def __init__(self, url): | ||
421 | 416 | self._js_bridge = get_u1db_js_bridge() | ||
422 | 417 | self._ref = self._make_http_sync_target(url) | ||
423 | 418 | self._cb_ref = None | ||
424 | 419 | |||
425 | 420 | def _forget(self): | ||
426 | 421 | if self._cb_ref: | ||
427 | 422 | self._forget_cb(self._cb_ref) | ||
428 | 423 | self._forget_http_sync_target(self._ref) | ||
429 | 424 | |||
430 | 425 | def set_oauth_credentials(self, consumer_key, consumer_secret, | ||
431 | 426 | token_key, token_secret): | ||
432 | 427 | self._bridge_meth('set_oauth_credentials', None, | ||
433 | 428 | [consumer_key, consumer_secret, | ||
434 | 429 | token_key, token_secret]) | ||
435 | 430 | |||
436 | 431 | def sync_exchange(self, docs_by_generations, source_replica_uid, | ||
437 | 432 | last_known_generation, last_known_trans_id, | ||
438 | 433 | return_doc_cb, ensure_callback=None): | ||
439 | 434 | return self._bridge_meth_sync_exchange(docs_by_generations, | ||
440 | 435 | source_replica_uid, | ||
441 | 436 | last_known_generation, | ||
442 | 437 | last_known_trans_id, | ||
443 | 438 | return_doc_cb, | ||
444 | 439 | ensure_callback) | ||
445 | 440 | |||
446 | 441 | # testing/internal api | ||
447 | 442 | |||
448 | 443 | def _set_trace_hook_shallow(self, cb): | ||
449 | 444 | self._cb_ref = self._make_cb(cb) | ||
450 | 445 | self._invoke_cb(self._ref, '_set_trace_hook_shallow', self._cb_ref) | ||
451 | 446 | |||
452 | 447 | |||
453 | 448 | _complete_interface(SyncTargetInterface, BridgedJsHTTPSyncTarget, | ||
454 | 449 | prot_exposed=set(), | ||
455 | 450 | skip=set(), | ||
456 | 451 | ret = { | ||
457 | 452 | 'get_sync_info': 'tuple', | ||
458 | 453 | }) | ||
459 | 454 | |||
460 | 455 | |||
461 | 456 | class BridgedJsSynchronizer(BridgedJsObject): | ||
462 | 457 | |||
463 | 458 | def __init__(self, bridged_source_db, bridged_target): | ||
464 | 459 | self._js_bridge = get_u1db_js_bridge() | ||
465 | 460 | self._ref = self._make_synchronizer(bridged_source_db._ref, | ||
466 | 461 | bridged_target._ref) | ||
467 | 462 | |||
468 | 463 | def _forget(self): | ||
469 | 464 | self._forget_synchronizer(self._ref) | ||
470 | 465 | |||
471 | 466 | def sync(self): | ||
472 | 467 | return self._bridge_meth('sync', None, []) | ||
473 | 468 | |||
474 | 469 | |||
475 | 470 | def make_database(test, replica_uid): | ||
476 | 471 | db = BridgedJsDatabase(replica_uid) | ||
477 | 472 | def cleanup(): | ||
478 | 473 | db.delete_db() | ||
479 | 474 | db._forget() | ||
480 | 475 | test.addCleanup(cleanup) | ||
481 | 476 | return db | ||
482 | 477 | |||
483 | 478 | def make_http_sync_target(test, path): | ||
484 | 479 | target = BridgedJsHTTPSyncTarget(test.getURL(path)) | ||
485 | 480 | test.addCleanup(target._forget) | ||
486 | 481 | return target | ||
487 | 482 | |||
488 | 483 | def make_synchronizer(test, source, target): | ||
489 | 484 | synchronizer = BridgedJsSynchronizer(source, target) | ||
490 | 485 | test.addCleanup(synchronizer._forget) | ||
491 | 486 | return synchronizer | ||
492 | 0 | 487 | ||
493 | === added file 'tests/setup.py' | |||
494 | --- tests/setup.py 1970-01-01 00:00:00 +0000 | |||
495 | +++ tests/setup.py 2013-08-27 10:28:13 +0000 | |||
496 | @@ -0,0 +1,6 @@ | |||
497 | 1 | from distutils.core import setup, Extension | ||
498 | 2 | |||
499 | 3 | modul = Extension("u1dbqt", sources=["tests/qt-backend-wrapper.cpp"]) | ||
500 | 4 | setup(name = "U1DbQt", version = "1.0", | ||
501 | 5 | description = "", ext_modules = [modul]) | ||
502 | 6 | |||
503 | 0 | 7 | ||
504 | === added file 'tests/test-bridged.py' | |||
505 | --- tests/test-bridged.py 1970-01-01 00:00:00 +0000 | |||
506 | +++ tests/test-bridged.py 2013-08-27 10:28:13 +0000 | |||
507 | @@ -0,0 +1,186 @@ | |||
508 | 1 | import os | ||
509 | 2 | |||
510 | 3 | from u1db import ( | ||
511 | 4 | errors, | ||
512 | 5 | sync, | ||
513 | 6 | tests, | ||
514 | 7 | ) | ||
515 | 8 | from u1db.tests import ( | ||
516 | 9 | test_backends, | ||
517 | 10 | test_remote_sync_target, | ||
518 | 11 | test_sync, | ||
519 | 12 | ) | ||
520 | 13 | from u1db.remote.cors_middleware import CORSMiddleware | ||
521 | 14 | |||
522 | 15 | from tools import serving | ||
523 | 16 | |||
524 | 17 | from bridged import ( | ||
525 | 18 | get_u1db_js_bridge, | ||
526 | 19 | BridgedJsObject, | ||
527 | 20 | make_database, | ||
528 | 21 | make_http_sync_target, | ||
529 | 22 | make_synchronizer, | ||
530 | 23 | ) | ||
531 | 24 | |||
532 | 25 | _test_count = 0 | ||
533 | 26 | |||
534 | 27 | def setUp(self): | ||
535 | 28 | if FLAG: | ||
536 | 29 | global _test_count | ||
537 | 30 | _test_count += 1 | ||
538 | 31 | get_u1db_js_bridge().flag("#%d %s" % (_test_count, self.id().split('.', 2)[2])) | ||
539 | 32 | super(self.__class__, self).setUp() | ||
540 | 33 | |||
541 | 34 | def startServer(self): | ||
542 | 35 | app = self.make_app() | ||
543 | 36 | bridge = get_u1db_js_bridge() | ||
544 | 37 | if not SAME_ORIGIN or bridge.rhino: | ||
545 | 38 | (self.server_thread, self.server, | ||
546 | 39 | self.base_url) = serving.serve(app, 0, log_server=LOG_SERVER) | ||
547 | 40 | self.addCleanup(self.server_thread.join) | ||
548 | 41 | self.addCleanup(self.server.shutdown) | ||
549 | 42 | else: | ||
550 | 43 | self.server = bridge.get_server() | ||
551 | 44 | self.base_url = bridge.get_base_url() | ||
552 | 45 | bridge.set_fallback_app(app) | ||
553 | 46 | if getattr(app, 'base_url', 1) is None: | ||
554 | 47 | app.base_url = self.base_url | ||
555 | 48 | |||
556 | 49 | SCENARIOS = [("jsbridged", { | ||
557 | 50 | "make_database_for_test": make_database, | ||
558 | 51 | })] | ||
559 | 52 | |||
560 | 53 | def skip_test(test): | ||
561 | 54 | test.skipTest("skipped: not applicable/relevant") | ||
562 | 55 | |||
563 | 56 | class BridgedAllDatabaseTests(test_backends.AllDatabaseTests): | ||
564 | 57 | scenarios = SCENARIOS | ||
565 | 58 | setUp = setUp | ||
566 | 59 | |||
567 | 60 | class BridgedLocalDatabaseTests(test_backends.LocalDatabaseTests): | ||
568 | 61 | accept_fixed_trans_id = True | ||
569 | 62 | scenarios = SCENARIOS | ||
570 | 63 | setUp = setUp | ||
571 | 64 | |||
572 | 65 | class BridgedLocalDatabaseValidateSourceGenTests(test_backends.LocalDatabaseValidateSourceGenTests): | ||
573 | 66 | scenarios = SCENARIOS | ||
574 | 67 | setUp = setUp | ||
575 | 68 | |||
576 | 69 | class BridgedLocalDatabaseValidateGenNTransIdTests(test_backends.LocalDatabaseValidateGenNTransIdTests): | ||
577 | 70 | scenarios = SCENARIOS | ||
578 | 71 | setUp = setUp | ||
579 | 72 | test_validate_gen_and_trans_id_invalid_gen = skip_test | ||
580 | 73 | |||
581 | 74 | class BridgedLocalDatabaseWithConflictsTests(test_backends.LocalDatabaseWithConflictsTests): | ||
582 | 75 | accept_fixed_trans_id = True | ||
583 | 76 | scenarios = SCENARIOS | ||
584 | 77 | setUp = setUp | ||
585 | 78 | |||
586 | 79 | class BridgedDatabaseIndexTests(test_backends.DatabaseIndexTests): | ||
587 | 80 | scenarios = SCENARIOS | ||
588 | 81 | setUp = setUp | ||
589 | 82 | |||
590 | 83 | class MyCORSMiddleware(CORSMiddleware): | ||
591 | 84 | _base_url = None | ||
592 | 85 | |||
593 | 86 | @property | ||
594 | 87 | def base_url(self): | ||
595 | 88 | return self._base_url | ||
596 | 89 | |||
597 | 90 | @base_url.setter | ||
598 | 91 | def base_url(self, value): | ||
599 | 92 | self.app.base_url = self._base_url = value | ||
600 | 93 | |||
601 | 94 | def make_http_app(state): | ||
602 | 95 | app = test_remote_sync_target.make_http_app(state) | ||
603 | 96 | bridge = get_u1db_js_bridge() | ||
604 | 97 | if not SAME_ORIGIN: | ||
605 | 98 | return MyCORSMiddleware(app, ['*']) | ||
606 | 99 | return app | ||
607 | 100 | |||
608 | 101 | def make_oauth_http_app(state): | ||
609 | 102 | app = test_remote_sync_target.make_oauth_http_app(state) | ||
610 | 103 | bridge = get_u1db_js_bridge() | ||
611 | 104 | if not SAME_ORIGIN: | ||
612 | 105 | return MyCORSMiddleware(app, ['*']) | ||
613 | 106 | return app | ||
614 | 107 | |||
615 | 108 | def oauth_http_sync_target(test, path): | ||
616 | 109 | st = make_http_sync_target(test, '~/' + path) | ||
617 | 110 | st.set_oauth_credentials(tests.consumer1.key, tests.consumer1.secret, | ||
618 | 111 | tests.token1.key, tests.token1.secret) | ||
619 | 112 | return st | ||
620 | 113 | |||
621 | 114 | class BridgedTestHttpSyncTarget(test_remote_sync_target.TestRemoteSyncTargets): | ||
622 | 115 | startServer = startServer | ||
623 | 116 | setUp = setUp | ||
624 | 117 | |||
625 | 118 | scenarios = [ | ||
626 | 119 | ('http', {'make_app_with_state': make_http_app, | ||
627 | 120 | 'make_document_for_test': tests.make_document_for_test, | ||
628 | 121 | 'sync_target': make_http_sync_target}), | ||
629 | 122 | ('oauth_http', {'make_app_with_state': | ||
630 | 123 | make_oauth_http_app, | ||
631 | 124 | 'make_document_for_test': tests.make_document_for_test, | ||
632 | 125 | 'sync_target': oauth_http_sync_target}), | ||
633 | 126 | ] | ||
634 | 127 | |||
635 | 128 | if not SAME_ORIGIN: | ||
636 | 129 | failure_scenario_exceptions = (errors.UserQuotaExceeded, | ||
637 | 130 | errors.UserQuotaExceeded) | ||
638 | 131 | |||
639 | 132 | def copy_database_for_test(test, db): | ||
640 | 133 | if isinstance(db, BridgedJsObject): | ||
641 | 134 | test.skipTest("js dbs cannot be copied") | ||
642 | 135 | return test_sync.copy_database_for_http_test(test, db) | ||
643 | 136 | |||
644 | 137 | def sync_for_test(test, db_source, db_target, trace_hook=None, trace_hook_shallow=None): | ||
645 | 138 | if isinstance(db_source, BridgedJsObject): | ||
646 | 139 | if trace_hook: | ||
647 | 140 | test.skipTest("full trace hook not supported by http target") | ||
648 | 141 | path = test._http_at[db_target] | ||
649 | 142 | target = make_http_sync_target(test, path) | ||
650 | 143 | if trace_hook_shallow: | ||
651 | 144 | target._set_trace_hook_shallow(trace_hook_shallow) | ||
652 | 145 | return make_synchronizer(test, db_source, target).sync() | ||
653 | 146 | target = db_target.get_sync_target() | ||
654 | 147 | target._set_trace_hook_shallow(trace_hook or trace_hook_shallow) | ||
655 | 148 | return sync.Synchronizer(db_source, target).sync() | ||
656 | 149 | |||
657 | 150 | class BridgedDatabaseSyncTests(test_sync.DatabaseSyncTests): | ||
658 | 151 | accept_fixed_trans_id = True | ||
659 | 152 | startServer = startServer | ||
660 | 153 | |||
661 | 154 | scenarios = [('jshttp', { | ||
662 | 155 | 'make_database_for_test': None, # see create_database_for_role | ||
663 | 156 | 'copy_database_for_test': copy_database_for_test, | ||
664 | 157 | 'make_document_for_test': tests.make_document_for_test, | ||
665 | 158 | 'make_app_with_state': make_http_app, | ||
666 | 159 | 'do_sync': sync_for_test, | ||
667 | 160 | })] | ||
668 | 161 | |||
669 | 162 | def create_database_for_role(self, replica_uid, sync_role): | ||
670 | 163 | if sync_role == 'source': | ||
671 | 164 | js_db = make_database(self, replica_uid) | ||
672 | 165 | js_db._replica_uid = replica_uid | ||
673 | 166 | return js_db | ||
674 | 167 | return test_sync.make_database_for_http_test(self, replica_uid) | ||
675 | 168 | |||
676 | 169 | setUp = setUp | ||
677 | 170 | test_optional_sync_preserve_json = skip_test | ||
678 | 171 | |||
679 | 172 | class BridgedTestDbSync(test_sync.TestDbSync): | ||
680 | 173 | startServer = startServer | ||
681 | 174 | scenarios = [ | ||
682 | 175 | ('jshttp', { | ||
683 | 176 | 'make_app_with_state': make_http_app, | ||
684 | 177 | 'make_database_for_test': make_database, | ||
685 | 178 | }), | ||
686 | 179 | ('jsoauthhttp', { | ||
687 | 180 | 'make_app_with_state': make_oauth_http_app, | ||
688 | 181 | 'make_database_for_test': make_database, | ||
689 | 182 | 'oauth': True | ||
690 | 183 | })] | ||
691 | 184 | setUp = setUp | ||
692 | 185 | |||
693 | 186 | load_tests = tests.load_with_scenarios |