Merge lp:~autopilot/autopilot/experimental into lp:autopilot
- experimental
- Merge into trunk
Status: | Merged | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Thomi Richards | ||||||||||||||||||||||||
Approved revision: | 348 | ||||||||||||||||||||||||
Merged at revision: | 325 | ||||||||||||||||||||||||
Proposed branch: | lp:~autopilot/autopilot/experimental | ||||||||||||||||||||||||
Merge into: | lp:autopilot | ||||||||||||||||||||||||
Diff against target: |
3821 lines (+2175/-293) 61 files modified
autopilot/__init__.py (+4/-3) autopilot/globals.py (+9/-2) autopilot/ibus.py (+1/-1) autopilot/input/_X11.py (+7/-2) autopilot/input/_osk.py (+5/-0) autopilot/input/_uinput.py (+5/-0) autopilot/introspection/__init__.py (+14/-3) autopilot/introspection/backends.py (+2/-2) autopilot/introspection/constants.py (+1/-1) autopilot/introspection/dbus.py (+95/-115) autopilot/introspection/types.py (+619/-0) autopilot/introspection/utilities.py (+6/-0) autopilot/keybindings.py (+6/-2) autopilot/matchers/__init__.py (+1/-1) autopilot/process/_bamf.py (+10/-8) autopilot/testcase.py (+5/-4) autopilot/tests/functional/test_ap_apps.py (+31/-22) autopilot/tests/functional/test_autopilot_functional.py (+6/-4) autopilot/tests/functional/test_dbus_query.py (+15/-2) autopilot/tests/functional/test_input_stack.py (+7/-2) autopilot/tests/functional/test_introspection_features.py (+2/-1) autopilot/tests/functional/test_open_window.py (+1/-1) autopilot/tests/functional/test_process_emulator.py (+1/-1) autopilot/tests/unit/test_command_line_args.py (+9/-2) autopilot/tests/unit/test_custom_exceptions.py (+1/-1) autopilot/tests/unit/test_introspection_features.py (+36/-5) autopilot/tests/unit/test_matchers.py (+41/-30) autopilot/tests/unit/test_pick_backend.py (+1/-1) autopilot/tests/unit/test_platform.py (+7/-1) autopilot/tests/unit/test_types.py (+579/-0) autopilot/tests/unit/test_version_utility_fns.py (+2/-2) autopilot/utilities.py (+6/-7) autopilot/vis/bus_enumerator.py (+5/-5) autopilot/vis/main_window.py (+20/-12) autopilot/vis/objectproperties.py (+8/-2) autopilot/vis/resources.py (+1/-1) bin/autopilot (+21/-20) debian/changelog (+7/-0) debian/control (+90/-12) debian/python-autopilot-tests.install (+3/-0) debian/python-autopilot-tests.pyinstall (+0/-3) debian/python-autopilot-vis.install (+1/-0) debian/python-autopilot-vis.pyinstall (+0/-1) debian/python-autopilot.install (+6/-0) debian/python-autopilot.pyinstall (+0/-6) debian/python3-autopilot-tests.install (+3/-0) debian/python3-autopilot-trace.install (+1/-0) debian/python3-autopilot-vis.install (+1/-0) debian/python3-autopilot.docs (+2/-0) debian/python3-autopilot.install (+9/-0) debian/python3-autopilot.postinst (+24/-0) debian/rules (+30/-2) docs/_templates/indexcontent.html (+6/-0) docs/api/introspection.rst (+3/-0) docs/appendix/appendix.rst (+9/-0) docs/appendix/protocol.rst (+366/-0) docs/conf.py (+3/-1) docs/contents.rst (+1/-0) docs/porting/porting.rst (+18/-0) docs/tutorial/advanced_autopilot.rst (+1/-1) setup.py (+1/-1) |
||||||||||||||||||||||||
To merge this branch: | bzr merge lp:~autopilot/autopilot/experimental | ||||||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Martin Pitt (community) | Approve | ||
Review via email: mp+185672@code.launchpad.net |
Commit message
New Hotness for autopilot 1.4.
Description of the change
This branch contains the 'new hotness' for autopilot 1.4. Specifically:
- better introspection type support.
- python 3 support!
- 347. By Thomi Richards
-
Merged trunk, resolved conflicts.
PS Jenkins bot (ps-jenkins) wrote : | # |
Martin Pitt (pitti) wrote : | # |
Big Plus for documenting the wire protocol and porting instructions!
- 348. By Thomi Richards
-
PEP8 fixes.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:347
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Martin Pitt (pitti) wrote : | # |
Strictly speaking this would need to grow "Breaks: libautopilot-gtk (<< 1.4), libautopilot-qt (<< 1.4)" to both p-ap and p3-ap, as these are only recommends and not depends:.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:348
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:347
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'autopilot/__init__.py' |
2 | --- autopilot/__init__.py 2013-07-22 04:42:45 +0000 |
3 | +++ autopilot/__init__.py 2013-09-16 17:17:50 +0000 |
4 | @@ -20,7 +20,7 @@ |
5 | from argparse import ArgumentParser, REMAINDER, Action |
6 | import subprocess |
7 | |
8 | -version = '1.3.1' |
9 | +version = '1.4.0' |
10 | |
11 | |
12 | class BackendException(RuntimeError): |
13 | @@ -30,7 +30,7 @@ |
14 | def __init__(self, original_exception): |
15 | super(BackendException, self).__init__( |
16 | "Error while initialising backend. Original exception was: " + |
17 | - original_exception.message) |
18 | + str(original_exception)) |
19 | self.original_exception = original_exception |
20 | |
21 | |
22 | @@ -179,7 +179,8 @@ |
23 | "${Version}", |
24 | "--show", |
25 | "python-autopilot", |
26 | - ] |
27 | + ], |
28 | + universal_newlines=True |
29 | ).strip() |
30 | except subprocess.CalledProcessError: |
31 | return None |
32 | |
33 | === modified file 'autopilot/globals.py' |
34 | --- autopilot/globals.py 2013-07-22 04:14:41 +0000 |
35 | +++ autopilot/globals.py 2013-09-16 17:17:50 +0000 |
36 | @@ -19,7 +19,14 @@ |
37 | |
38 | |
39 | from __future__ import absolute_import |
40 | -from StringIO import StringIO |
41 | + |
42 | +try: |
43 | + # Python 2 |
44 | + from StringIO import StringIO |
45 | +except ImportError: |
46 | + # Python 3 |
47 | + from io import StringIO |
48 | + |
49 | from autopilot.utilities import LogFormatter, CleanupRegistered |
50 | from testtools.content import text_content |
51 | import subprocess |
52 | @@ -203,7 +210,7 @@ |
53 | """ |
54 | if type(enable_recording) is not bool: |
55 | raise TypeError("enable_recording must be a boolean.") |
56 | - if not isinstance(record_dir, basestring): |
57 | + if not isinstance(record_dir, str): |
58 | raise TypeError("record_dir must be a string.") |
59 | |
60 | _video_logger.enable_recording(enable_recording) |
61 | |
62 | === modified file 'autopilot/ibus.py' |
63 | --- autopilot/ibus.py 2013-07-30 00:16:11 +0000 |
64 | +++ autopilot/ibus.py 2013-09-16 17:17:50 +0000 |
65 | @@ -94,7 +94,7 @@ |
66 | raise TypeError("engine_list must be a list of valid engine names.") |
67 | available_engines = get_available_input_engines() |
68 | for engine in engine_list: |
69 | - if not isinstance(engine, basestring): |
70 | + if not isinstance(engine, str): |
71 | raise TypeError("Engines in engine_list must all be strings.") |
72 | if engine not in available_engines: |
73 | raise ValueError( |
74 | |
75 | === modified file 'autopilot/input/_X11.py' |
76 | --- autopilot/input/_X11.py 2013-09-04 03:44:07 +0000 |
77 | +++ autopilot/input/_X11.py 2013-09-16 17:17:50 +0000 |
78 | @@ -27,6 +27,7 @@ |
79 | from __future__ import absolute_import |
80 | |
81 | import logging |
82 | +import sys |
83 | from time import sleep |
84 | |
85 | from autopilot.display import is_point_on_any_screen, move_mouse_to_screen |
86 | @@ -45,6 +46,10 @@ |
87 | _DISPLAY = None |
88 | logger = logging.getLogger(__name__) |
89 | |
90 | +# py2 compatible alias for py3 |
91 | +if sys.version >= '3': |
92 | + basestring = str |
93 | + |
94 | |
95 | def get_display(): |
96 | """Return the Xlib display object. |
97 | @@ -333,8 +338,8 @@ |
98 | sync, |
99 | X.CurrentTime, |
100 | X.NONE, |
101 | - x=x, |
102 | - y=y) |
103 | + x=int(x), |
104 | + y=int(y)) |
105 | get_display().sync() |
106 | sleep(time_between_events) |
107 | |
108 | |
109 | === modified file 'autopilot/input/_osk.py' |
110 | --- autopilot/input/_osk.py 2013-08-23 00:23:40 +0000 |
111 | +++ autopilot/input/_osk.py 2013-09-16 17:17:50 +0000 |
112 | @@ -18,6 +18,7 @@ |
113 | # |
114 | |
115 | import logging |
116 | +import sys |
117 | from time import sleep |
118 | from contextlib import contextmanager |
119 | |
120 | @@ -31,6 +32,10 @@ |
121 | |
122 | logger = logging.getLogger(__name__) |
123 | |
124 | +# py2 compatible alias for py3 |
125 | +if sys.version >= '3': |
126 | + basestring = str |
127 | + |
128 | |
129 | class Keyboard(KeyboardBase): |
130 | |
131 | |
132 | === modified file 'autopilot/input/_uinput.py' |
133 | --- autopilot/input/_uinput.py 2013-07-24 08:26:10 +0000 |
134 | +++ autopilot/input/_uinput.py 2013-09-16 17:17:50 +0000 |
135 | @@ -29,6 +29,7 @@ |
136 | from time import sleep |
137 | from evdev import UInput, ecodes as e |
138 | import os.path |
139 | +import sys |
140 | |
141 | logger = logging.getLogger(__name__) |
142 | |
143 | @@ -37,6 +38,10 @@ |
144 | |
145 | _PRESSED_KEYS = [] |
146 | |
147 | +# py2 compatible alias for py3 |
148 | +if sys.version >= '3': |
149 | + basestring = str |
150 | + |
151 | |
152 | def _get_devnode_path(): |
153 | """Provide a fallback uinput node for devices which don't support udev""" |
154 | |
155 | === modified file 'autopilot/introspection/__init__.py' |
156 | --- autopilot/introspection/__init__.py 2013-07-30 15:08:30 +0000 |
157 | +++ autopilot/introspection/__init__.py 2013-09-16 17:17:50 +0000 |
158 | @@ -33,6 +33,7 @@ |
159 | from time import sleep |
160 | from functools import partial |
161 | import os |
162 | +import sys |
163 | |
164 | from autopilot.introspection.backends import DBusAddress |
165 | from autopilot.introspection.constants import ( |
166 | @@ -62,6 +63,10 @@ |
167 | # Keep track of known connections during search |
168 | connection_list = [] |
169 | |
170 | +# py2 compatible alias for py3 |
171 | +if sys.version >= '3': |
172 | + basestring = str |
173 | + |
174 | |
175 | class ProcessSearchError(RuntimeError): |
176 | pass |
177 | @@ -77,7 +82,10 @@ |
178 | # any non-dynamically linked executables, which we may need to fix further |
179 | # down the line. |
180 | try: |
181 | - ldd_output = subprocess.check_output(["ldd", app_path]).strip().lower() |
182 | + ldd_output = subprocess.check_output( |
183 | + ["ldd", app_path], |
184 | + universal_newlines=True |
185 | + ).strip().lower() |
186 | except subprocess.CalledProcessError as e: |
187 | raise RuntimeError(e) |
188 | if 'libqtcore' in ldd_output or 'libqt5core' in ldd_output: |
189 | @@ -115,9 +123,11 @@ |
190 | cwd = kwargs.pop('launch_dir', None) |
191 | capture_output = kwargs.pop('capture_output', True) |
192 | if kwargs: |
193 | + arglist = [repr(k) for k in kwargs.keys()] |
194 | + arglist.sort() |
195 | raise ValueError( |
196 | "Unknown keyword arguments: %s." % |
197 | - (', '.join(repr(k) for k in kwargs.keys()))) |
198 | + (', '.join(arglist))) |
199 | |
200 | path, args = launcher.prepare_environment(application, list(arguments)) |
201 | |
202 | @@ -164,6 +174,7 @@ |
203 | stderr=cap_mode, |
204 | close_fds=True, |
205 | preexec_fn=os.setsid, |
206 | + universal_newlines=True, |
207 | **kwargs |
208 | ) |
209 | return process |
210 | @@ -263,7 +274,7 @@ |
211 | if application_name: |
212 | app_name_check_fn = lambda i: get_classname_from_path( |
213 | i.introspection_iface.GetState('/')[0][0]) == application_name |
214 | - dbus_addresses = filter(app_name_check_fn, dbus_addresses) |
215 | + dbus_addresses = list(filter(app_name_check_fn, dbus_addresses)) |
216 | |
217 | if dbus_addresses is None or len(dbus_addresses) == 0: |
218 | raise ProcessSearchError("Search criteria returned no results") |
219 | |
220 | === modified file 'autopilot/introspection/backends.py' |
221 | --- autopilot/introspection/backends.py 2013-07-30 17:31:50 +0000 |
222 | +++ autopilot/introspection/backends.py 2013-09-16 17:17:50 +0000 |
223 | @@ -91,9 +91,9 @@ |
224 | |
225 | @property |
226 | def introspection_iface(self): |
227 | - if not isinstance(self._addr_tuple.connection, basestring): |
228 | + if not isinstance(self._addr_tuple.connection, str): |
229 | raise TypeError("Service name must be a string.") |
230 | - if not isinstance(self._addr_tuple.object_path, basestring): |
231 | + if not isinstance(self._addr_tuple.object_path, str): |
232 | raise TypeError("Object name must be a string") |
233 | |
234 | if not self._check_pid_running(): |
235 | |
236 | === modified file 'autopilot/introspection/constants.py' |
237 | --- autopilot/introspection/constants.py 2013-05-13 22:47:50 +0000 |
238 | +++ autopilot/introspection/constants.py 2013-09-16 17:17:50 +0000 |
239 | @@ -25,4 +25,4 @@ |
240 | AP_INTROSPECTION_IFACE = 'com.canonical.Autopilot.Introspection' |
241 | DBUS_INTROSPECTION_IFACE = 'org.freedesktop.DBus.Introspectable' |
242 | |
243 | -CURRENT_WIRE_PROTOCOL_VERSION = "1.3" |
244 | +CURRENT_WIRE_PROTOCOL_VERSION = "1.4" |
245 | |
246 | === modified file 'autopilot/introspection/dbus.py' |
247 | --- autopilot/introspection/dbus.py 2013-08-09 01:29:23 +0000 |
248 | +++ autopilot/introspection/dbus.py 2013-09-16 17:17:50 +0000 |
249 | @@ -30,10 +30,11 @@ |
250 | from contextlib import contextmanager |
251 | import logging |
252 | import re |
253 | -from testtools.matchers import Equals |
254 | -from time import sleep |
255 | +import sys |
256 | from uuid import uuid4 |
257 | |
258 | +from autopilot.introspection.types import create_value_instance |
259 | +from autopilot.introspection.utilities import translate_state_keys |
260 | from autopilot.utilities import Timer, get_debug_logger |
261 | |
262 | |
263 | @@ -41,8 +42,16 @@ |
264 | logger = logging.getLogger(__name__) |
265 | |
266 | |
267 | +# py2 compatible alias for py3 |
268 | +if sys.version >= '3': |
269 | + _PY3 = True |
270 | + basestring = str |
271 | +else: |
272 | + _PY3 = False |
273 | + |
274 | + |
275 | class StateNotFoundError(RuntimeError): |
276 | - """Raised when a piece of state information from unity is not found.""" |
277 | + """Raised when a piece of state information is not found.""" |
278 | |
279 | message = "State not found for class with name '{}' and id '{}'." |
280 | |
281 | @@ -81,17 +90,11 @@ |
282 | |
283 | """ |
284 | global _object_registry |
285 | - for cls in _object_registry[proxy_object._id].itervalues(): |
286 | + for cls in _object_registry[proxy_object._id].values(): |
287 | if type(proxy_object) is not cls: |
288 | cls._Backend = None |
289 | |
290 | |
291 | -def translate_state_keys(state_dict): |
292 | - """Translates the *state_dict* passed in so the keys are usable as python |
293 | - attributes.""" |
294 | - return {k.replace('-', '_'): v for k, v in state_dict.iteritems()} |
295 | - |
296 | - |
297 | def get_classname_from_path(object_path): |
298 | return object_path.split("/")[-1] |
299 | |
300 | @@ -100,7 +103,7 @@ |
301 | """Return true if *instance* satisifies all the filters present in |
302 | kwargs.""" |
303 | with instance.no_automatic_refreshing(): |
304 | - for attr, val in kwargs.iteritems(): |
305 | + for attr, val in kwargs.items(): |
306 | if not hasattr(instance, attr) or getattr(instance, attr) != val: |
307 | # Either attribute is not present, or is present but with |
308 | # the wrong value - don't add this instance to the results |
309 | @@ -108,8 +111,14 @@ |
310 | return False |
311 | return True |
312 | |
313 | - |
314 | -class DBusIntrospectionObject(object): |
315 | +DBusIntrospectionObjectBase = IntrospectableObjectMetaclass( |
316 | + 'DBusIntrospectionObjectBase', |
317 | + (object,), |
318 | + {} |
319 | +) |
320 | + |
321 | + |
322 | +class DBusIntrospectionObject(DBusIntrospectionObjectBase): |
323 | """A class that supports transparent data retrieval from the application |
324 | under test. |
325 | |
326 | @@ -120,8 +129,6 @@ |
327 | |
328 | """ |
329 | |
330 | - __metaclass__ = IntrospectableObjectMetaclass |
331 | - |
332 | _Backend = None |
333 | |
334 | def __init__(self, state_dict, path): |
335 | @@ -139,91 +146,20 @@ |
336 | |
337 | """ |
338 | self.__state = {} |
339 | - for key, value in translate_state_keys(state_dict).iteritems(): |
340 | + for key, value in translate_state_keys(state_dict).items(): |
341 | # don't store id in state dictionary -make it a proper instance |
342 | # attribute |
343 | if key == 'id': |
344 | - self.id = value |
345 | - self.__state[key] = self._make_attribute(key, value) |
346 | - |
347 | - def _make_attribute(self, name, value): |
348 | - """Make an attribute for *value*, patched with the wait_for |
349 | - function.""" |
350 | - |
351 | - def wait_for(self, expected_value, timeout=10): |
352 | - """Wait up to 10 seconds for our value to change to |
353 | - *expected_value*. |
354 | - |
355 | - *expected_value* can be a testtools.matcher. Matcher subclass (like |
356 | - LessThan, for example), or an ordinary value. |
357 | - |
358 | - This works by refreshing the value using repeated dbus calls. |
359 | - |
360 | - :raises: **RuntimeError** if the attribute was not equal to the |
361 | - expected value after 10 seconds. |
362 | - |
363 | - """ |
364 | - # It's guaranteed that our value is up to date, since __getattr__ |
365 | - # calls refresh_state. This if statement stops us waiting if the |
366 | - # value is already what we expect: |
367 | - if self == expected_value: |
368 | - return |
369 | - |
370 | - def make_unicode(value): |
371 | - if isinstance(value, str): |
372 | - return unicode(value.decode('utf8')) |
373 | - return value |
374 | - |
375 | - if hasattr(expected_value, 'expected'): |
376 | - expected_value.expected = make_unicode(expected_value.expected) |
377 | - |
378 | - # unfortunately not all testtools matchers derive from the Matcher |
379 | - # class, so we can't use issubclass, isinstance for this: |
380 | - match_fun = getattr(expected_value, 'match', None) |
381 | - is_matcher = match_fun and callable(match_fun) |
382 | - if not is_matcher: |
383 | - expected_value = Equals(expected_value) |
384 | - |
385 | - time_left = timeout |
386 | - while True: |
387 | - _, new_state = self.parent.get_new_state() |
388 | - new_state = translate_state_keys(new_state) |
389 | - new_value = make_unicode(new_state[self.name]) |
390 | - # Support for testtools.matcher classes: |
391 | - mismatch = expected_value.match(new_value) |
392 | - if mismatch: |
393 | - failure_msg = mismatch.describe() |
394 | - else: |
395 | - self.parent._set_properties(new_state) |
396 | - return |
397 | - |
398 | - if time_left >= 1: |
399 | - sleep(1) |
400 | - time_left -= 1 |
401 | - else: |
402 | - sleep(time_left) |
403 | - break |
404 | - |
405 | - raise AssertionError( |
406 | - "After %.1f seconds test on %s.%s failed: %s" % ( |
407 | - timeout, self.parent.__class__.__name__, self.name, |
408 | - failure_msg)) |
409 | - |
410 | - # This looks like magic, but it's really not. We're creating a new type |
411 | - # on the fly that derives from the type of 'value' with a couple of |
412 | - # extra attributes: wait_for is the wait_for method above. 'parent' and |
413 | - # 'name' are needed by the wait_for method. |
414 | - # |
415 | - # We can't use traditional meta-classes here, since the type we're |
416 | - # deriving from is only known at call time, not at parse time (we could |
417 | - # override __call__ in the meta class, but that doesn't buy us anything |
418 | - # extra). |
419 | - # |
420 | - # A better way to do this would be with functools.partial, which I |
421 | - # tried initially, but doesn't work well with bound methods. |
422 | - t = type(value) |
423 | - attrs = {'wait_for': wait_for, 'parent': self, 'name': name} |
424 | - return type(t.__name__, (t,), attrs)(value) |
425 | + self.id = int(value[1]) |
426 | + try: |
427 | + self.__state[key] = create_value_instance(value, self, key) |
428 | + except ValueError as e: |
429 | + logger.warning( |
430 | + "While constructing attribute '%s.%s': %s", |
431 | + self.__class__.__name__, |
432 | + key, |
433 | + str(e) |
434 | + ) |
435 | |
436 | def get_children_by_type(self, desired_type, **kwargs): |
437 | """Get a list of children of the specified type. |
438 | @@ -302,6 +238,19 @@ |
439 | children = [self.make_introspection_object(i) for i in state_dicts] |
440 | return children |
441 | |
442 | + def get_parent(self): |
443 | + """Returns the parent of this object. |
444 | + |
445 | + If this object has no parent (i.e.- it is the root of the introspection |
446 | + tree). Then it returns itself. |
447 | + |
448 | + """ |
449 | + query = self.get_class_query_string() + "/.." |
450 | + parent_state_dicts = self.get_state_by_path(query) |
451 | + |
452 | + parent = self.make_introspection_object(parent_state_dicts[0]) |
453 | + return parent |
454 | + |
455 | def select_single(self, type_name='*', **kwargs): |
456 | """Get a single node from the introspection tree, with type equal to |
457 | *type_name* and (optionally) matching the keyword filters present in |
458 | @@ -390,8 +339,9 @@ |
459 | "Selecting objects of %s with attributes: %r", |
460 | 'any type' if type_name == '*' else 'type ' + type_name, kwargs) |
461 | |
462 | - first_param = '' |
463 | - for k, v in kwargs.iteritems(): |
464 | + server_side_filters = [] |
465 | + client_side_filters = {} |
466 | + for k, v in kwargs.items(): |
467 | # LP Bug 1209029: The XPathSelect protocol does not allow all valid |
468 | # node names or values. We need to decide here whether the filter |
469 | # parameters are going to work on the backend or not. If not, we |
470 | @@ -399,25 +349,33 @@ |
471 | # _is_valid_server_side_filter_param function (below) for the |
472 | # specific requirements. |
473 | if _is_valid_server_side_filter_param(k, v): |
474 | - first_param = '[{}={}]'.format(k, v) |
475 | - kwargs.pop(k) |
476 | - break |
477 | - query_path = "%s//%s%s" % (self.get_class_query_string(), |
478 | - type_name, |
479 | - first_param) |
480 | + server_side_filters.append( |
481 | + _get_filter_string_for_key_value_pair(k, v) |
482 | + ) |
483 | + else: |
484 | + client_side_filters[k] = v |
485 | + filter_str = '[{}]'.format(','.join(server_side_filters)) \ |
486 | + if server_side_filters else "" |
487 | + query_path = "%s//%s%s" % ( |
488 | + self.get_class_query_string(), |
489 | + type_name, |
490 | + filter_str |
491 | + ) |
492 | |
493 | state_dicts = self.get_state_by_path(query_path) |
494 | instances = [self.make_introspection_object(i) for i in state_dicts] |
495 | - return filter(lambda i: object_passes_filters(i, **kwargs), instances) |
496 | + return [i for i in instances |
497 | + if object_passes_filters(i, **client_side_filters)] |
498 | |
499 | def refresh_state(self): |
500 | - """Refreshes the object's state from unity. |
501 | + """Refreshes the object's state. |
502 | |
503 | You should probably never have to call this directly. Autopilot |
504 | automatically retrieves new state every time this object's attributes |
505 | are read. |
506 | |
507 | - :raises: **StateNotFound** if the object in unity has been destroyed. |
508 | + :raises: **StateNotFound** if the object in the application under test |
509 | + has been destroyed. |
510 | |
511 | """ |
512 | _, new_state = self.get_new_state() |
513 | @@ -563,9 +521,32 @@ |
514 | |
515 | """ |
516 | return ( |
517 | - isinstance(value, str) and |
518 | + type(value) in (str, int, bool) and |
519 | re.match(r'^[a-zA-Z0-9_\-]+( [a-zA-Z0-9_\-])*$', key) is not None and |
520 | - re.match(r'^[a-zA-Z0-9_\-]+( [a-zA-Z0-9_\-])*$', value) is not None) |
521 | + (type(value) != int or -2**31 <= value <= 2**31 - 1)) |
522 | + |
523 | + |
524 | +def _get_filter_string_for_key_value_pair(key, value): |
525 | + """Return a string representing the filter query for this key/value pair. |
526 | + |
527 | + The value must be suitable for server-side filtering. Raises ValueError if |
528 | + this is not the case. |
529 | + |
530 | + """ |
531 | + if isinstance(value, str): |
532 | + if _PY3: |
533 | + escaped_value = value.encode("unicode_escape")\ |
534 | + .decode('ASCII')\ |
535 | + .replace("'", "\\'") |
536 | + else: |
537 | + escaped_value = value.encode("string_escape") |
538 | + # note: string_escape codec escapes "'" but not '"'... |
539 | + escaped_value = escaped_value.replace('"', r'\"') |
540 | + return '{}="{}"'.format(key, escaped_value) |
541 | + elif isinstance(value, int) or isinstance(value, bool): |
542 | + return "{}={}".format(key, repr(value)) |
543 | + else: |
544 | + raise ValueError("Unsupported value type: {}".format(type(value))) |
545 | |
546 | |
547 | class _CustomEmulatorMeta(IntrospectableObjectMetaclass): |
548 | @@ -583,9 +564,10 @@ |
549 | d['_id'] = uuid4() |
550 | return super(_CustomEmulatorMeta, cls).__new__(cls, name, bases, d) |
551 | |
552 | - |
553 | -class CustomEmulatorBase(DBusIntrospectionObject): |
554 | - |
555 | +CustomEmulatorBase = _CustomEmulatorMeta('CustomEmulatorBase', |
556 | + (DBusIntrospectionObject, ), |
557 | + {}) |
558 | +CustomEmulatorBase.__doc__ = \ |
559 | """This class must be used as a base class for any custom emulators defined |
560 | within a test case. |
561 | |
562 | @@ -593,5 +575,3 @@ |
563 | Tutorial Section :ref:`custom_emulators` |
564 | Information on how to write custom emulators. |
565 | """ |
566 | - |
567 | - __metaclass__ = _CustomEmulatorMeta |
568 | |
569 | === added file 'autopilot/introspection/types.py' |
570 | --- autopilot/introspection/types.py 1970-01-01 00:00:00 +0000 |
571 | +++ autopilot/introspection/types.py 2013-09-16 17:17:50 +0000 |
572 | @@ -0,0 +1,619 @@ |
573 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
574 | +# |
575 | +# Autopilot Functional Test Tool |
576 | +# Copyright (C) 2013 Canonical |
577 | +# |
578 | +# This program is free software: you can redistribute it and/or modify |
579 | +# it under the terms of the GNU General Public License as published by |
580 | +# the Free Software Foundation, either version 3 of the License, or |
581 | +# (at your option) any later version. |
582 | +# |
583 | +# This program is distributed in the hope that it will be useful, |
584 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
585 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
586 | +# GNU General Public License for more details. |
587 | +# |
588 | +# You should have received a copy of the GNU General Public License |
589 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
590 | +# |
591 | + |
592 | + |
593 | +""" |
594 | +Autopilot proxy type support. |
595 | +============================= |
596 | + |
597 | +This module defines the classes that are used for all attributes on proxy |
598 | +objects. All proxy objects contain attributes that transparently mirror the |
599 | +values present in the application under test. Autopilot takes care of keeping |
600 | +these values up to date. |
601 | + |
602 | +Object attributes fall into two categories. Attributes that are a single |
603 | +string, boolean, or integer property are sent directly across DBus. These are |
604 | +called "plain" types, and are stored in autopilot as instnaces of the |
605 | +:class:`PlainType` class. Attributes that are more complex (a rectangle, for |
606 | +example) are called "complex" types, and are split into several component |
607 | +values, sent across dbus, and are then reconstituted in autopilot into useful |
608 | +objects. |
609 | + |
610 | +""" |
611 | + |
612 | +from __future__ import absolute_import |
613 | + |
614 | +from datetime import datetime, time |
615 | +import dbus |
616 | +import logging |
617 | +from testtools.matchers import Equals |
618 | +from time import sleep |
619 | + |
620 | +from autopilot.introspection.utilities import translate_state_keys |
621 | + |
622 | + |
623 | +logger = logging.getLogger(__name__) |
624 | + |
625 | + |
626 | +class ValueType(object): |
627 | + |
628 | + """Store constants for different special types that autopilot understands. |
629 | + |
630 | + DO NOT add items here unless you have documented them correctly in |
631 | + docs/appendix/protocol.rst. |
632 | + |
633 | + """ |
634 | + PLAIN = 0 |
635 | + RECTANGLE = 1 |
636 | + POINT = 2 |
637 | + SIZE = 3 |
638 | + COLOR = 4 |
639 | + DATETIME = 5 |
640 | + TIME = 6 |
641 | + UNKNOWN = -1 |
642 | + |
643 | + |
644 | +def create_value_instance(value, parent, name): |
645 | + """Create an object that exposes the interesing part of the value |
646 | + specified, given the value_type_id. |
647 | + |
648 | + :param parent: The object this attribute belongs to. |
649 | + :param name: The name of this attribute. |
650 | + :param value: The value array from DBus. |
651 | + |
652 | + """ |
653 | + type_dict = { |
654 | + ValueType.PLAIN: _make_plain_type, |
655 | + ValueType.RECTANGLE: Rectangle, |
656 | + ValueType.POINT: Point, |
657 | + ValueType.SIZE: Size, |
658 | + ValueType.DATETIME: DateTime, |
659 | + ValueType.TIME: Time, |
660 | + ValueType.UNKNOWN: _make_plain_type, |
661 | + } |
662 | + type_id = value[0] |
663 | + value = value[1:] |
664 | + |
665 | + if type_id not in type_dict: |
666 | + logger.warning("Unknown type id %d", type_id) |
667 | + type_id = ValueType.UNKNOWN |
668 | + |
669 | + type_class = type_dict.get(type_id, None) |
670 | + if type_id == ValueType.UNKNOWN: |
671 | + value = [dbus.Array(value)] |
672 | + if len(value) == 0: |
673 | + raise ValueError("Cannot create attribute, no data supplied") |
674 | + return type_class(*value, parent=parent, name=name) |
675 | + |
676 | + |
677 | +class TypeBase(object): |
678 | + |
679 | + def wait_for(self, expected_value, timeout=10): |
680 | + """Wait up to 10 seconds for our value to change to |
681 | + *expected_value*. |
682 | + |
683 | + *expected_value* can be a testtools.matcher. Matcher subclass (like |
684 | + LessThan, for example), or an ordinary value. |
685 | + |
686 | + This works by refreshing the value using repeated dbus calls. |
687 | + |
688 | + :raises AssertionError: if the attribute was not equal to the |
689 | + expected value after 10 seconds. |
690 | + |
691 | + :raises RuntimeError: if the attribute you called this on was not |
692 | + constructed as part of an object. |
693 | + |
694 | + """ |
695 | + # It's guaranteed that our value is up to date, since __getattr__ |
696 | + # calls refresh_state. This if statement stops us waiting if the |
697 | + # value is already what we expect: |
698 | + if self == expected_value: |
699 | + return |
700 | + |
701 | + if self.name is None or self.parent is None: |
702 | + raise RuntimeError( |
703 | + "This variable was not constructed as part of " |
704 | + "an object. The wait_for method cannot be used." |
705 | + ) |
706 | + |
707 | + def make_unicode(value): |
708 | + if isinstance(value, bytes): |
709 | + return value.decode('utf8') |
710 | + return value |
711 | + |
712 | + if hasattr(expected_value, 'expected'): |
713 | + expected_value.expected = make_unicode(expected_value.expected) |
714 | + |
715 | + # unfortunately not all testtools matchers derive from the Matcher |
716 | + # class, so we can't use issubclass, isinstance for this: |
717 | + match_fun = getattr(expected_value, 'match', None) |
718 | + is_matcher = match_fun and callable(match_fun) |
719 | + if not is_matcher: |
720 | + expected_value = Equals(expected_value) |
721 | + |
722 | + time_left = timeout |
723 | + while True: |
724 | + # TODO: These next three lines are duplicated from the parent... |
725 | + # can we just have this code once somewhere? |
726 | + _, new_state = self.parent.get_new_state() |
727 | + new_state = translate_state_keys(new_state) |
728 | + new_value = make_unicode(new_state[self.name][1]) # [1] is the val |
729 | + # Support for testtools.matcher classes: |
730 | + mismatch = expected_value.match(new_value) |
731 | + if mismatch: |
732 | + failure_msg = mismatch.describe() |
733 | + else: |
734 | + self.parent._set_properties(new_state) |
735 | + return |
736 | + |
737 | + if time_left >= 1: |
738 | + sleep(1) |
739 | + time_left -= 1 |
740 | + else: |
741 | + sleep(time_left) |
742 | + break |
743 | + |
744 | + raise AssertionError( |
745 | + "After %.1f seconds test on %s.%s failed: %s" % ( |
746 | + timeout, self.parent.__class__.__name__, self.name, |
747 | + failure_msg)) |
748 | + |
749 | + |
750 | +class PlainType(TypeBase): |
751 | + |
752 | + """Plain type support in autopilot proxy objects. |
753 | + |
754 | + Instances of this class will be used for all plain attrubites. The word |
755 | + "plain" in this context means anything that's marshalled as a string, |
756 | + boolean or integer type across dbus. |
757 | + |
758 | + Instances of these classes can be used just like the underlying type. For |
759 | + example, given an object property called 'length' that is marshalled over |
760 | + dbus as an integer value, the following will be true:: |
761 | + |
762 | + >>> isinstance(object.length, PlainType) |
763 | + True |
764 | + >>> isinstance(object.length, int) |
765 | + True |
766 | + >>> print(object.length) |
767 | + 123 |
768 | + >>> print(object.length + 32) |
769 | + 155 |
770 | + |
771 | + However, a special case exists for boolean values: because you cannot |
772 | + subclass from the 'bool' type, the following check will fail ( |
773 | + ``object.visible`` is a boolean property):: |
774 | + |
775 | + >>> isinstance(object.visible, bool) |
776 | + False |
777 | + |
778 | + However boolean values will behave exactly as you expect them to. |
779 | + |
780 | + """ |
781 | + |
782 | + def __new__(cls, value, parent=None, name=None): |
783 | + return _make_plain_type(value, parent=parent, name=name) |
784 | + |
785 | + |
786 | +def _make_plain_type(value, parent=None, name=None): |
787 | + new_type_name = type(value).__name__ |
788 | + new_type_bases = (type(value), PlainType) |
789 | + new_type_dict = dict(parent=parent, name=name) |
790 | + new_type = type(new_type_name, new_type_bases, new_type_dict) |
791 | + return new_type(value) |
792 | + |
793 | + |
794 | +def _array_packed_type(num_args): |
795 | + """Return a base class that accepts 'num_args' and is packed into a dbus |
796 | + Array type. |
797 | + |
798 | + """ |
799 | + class _ArrayPackedType(dbus.Array, TypeBase): |
800 | + |
801 | + def __init__(self, *args, **kwargs): |
802 | + if len(args) != self._required_arg_count: |
803 | + raise ValueError( |
804 | + "%s must be constructed with %d arguments, not %d" |
805 | + % ( |
806 | + self.__class__.__name__, |
807 | + self._required_arg_count, |
808 | + len(args) |
809 | + ) |
810 | + ) |
811 | + super(_ArrayPackedType, self).__init__(args) |
812 | + # TODO: pop instead of get, and raise on unknown kwarg |
813 | + self.parent = kwargs.get("parent", None) |
814 | + self.name = kwargs.get("name", None) |
815 | + return type( |
816 | + "_ArrayPackedType_{}".format(num_args), |
817 | + (_ArrayPackedType,), |
818 | + dict(_required_arg_count=num_args) |
819 | + ) |
820 | + |
821 | + |
822 | +class Rectangle(_array_packed_type(4)): |
823 | + |
824 | + """The RectangleType class represents a rectangle in cartesian space. |
825 | + |
826 | + To construct a rectangle, pass the x, y, width and height parameters in to |
827 | + the class constructor:: |
828 | + |
829 | + my_rect = Rectangle(12,13,100,150) |
830 | + |
831 | + These attributes can be accessed either using named attributes, or via |
832 | + sequence indexes:: |
833 | + |
834 | + >>> my_rect.x == my_rect[0] == 12 |
835 | + True |
836 | + >>> my_rect.y == my_rect[1] == 13 |
837 | + True |
838 | + >>> my_rect.w == my_rect[2] == 100 |
839 | + True |
840 | + >>> my_rect.h == my_rect[3] == 150 |
841 | + True |
842 | + |
843 | + You may also access the width and height values using the ``width`` and |
844 | + ``height`` properties:: |
845 | + |
846 | + >>> my_rect.width == my_rect.w |
847 | + True |
848 | + >>> my_rect.height == my_rect.h |
849 | + True |
850 | + |
851 | + Rectangles can be compared using ``==`` and ``!=``, either to another |
852 | + Rectangle instance, or to any mutable sequence type:: |
853 | + |
854 | + >>> my_rect == [12, 13, 100, 150] |
855 | + True |
856 | + >>> my_rect != Rectangle(1,2,3,4) |
857 | + True |
858 | + |
859 | + """ |
860 | + |
861 | + @property |
862 | + def x(self): |
863 | + return self[0] |
864 | + |
865 | + @property |
866 | + def y(self): |
867 | + return self[1] |
868 | + |
869 | + @property |
870 | + def w(self): |
871 | + return self[2] |
872 | + |
873 | + @property |
874 | + def width(self): |
875 | + return self[2] |
876 | + |
877 | + @property |
878 | + def h(self): |
879 | + return self[3] |
880 | + |
881 | + @property |
882 | + def height(self): |
883 | + return self[3] |
884 | + |
885 | + |
886 | +class Point(_array_packed_type(2)): |
887 | + |
888 | + """The Point class represents a 2D point in cartesian space. |
889 | + |
890 | + To construct a Point, pass in the x, y parameters to the class |
891 | + constructor:: |
892 | + |
893 | + >>> my_point = Point(50,100) |
894 | + |
895 | + These attributes can be accessed either using named attributes, or via |
896 | + sequence indexes:: |
897 | + |
898 | + >>> my_point.x == my_point[0] == 50 |
899 | + True |
900 | + >>> my_point.y == my_point[1] == 100 |
901 | + True |
902 | + |
903 | + Point instances can be compared using ``==`` and ``!=``, either to another |
904 | + Point instance, or to any mutable sequence type with the correct number of |
905 | + items:: |
906 | + |
907 | + >>> my_point == [50, 100] |
908 | + True |
909 | + >>> my_point != Point(5, 10) |
910 | + True |
911 | + |
912 | + """ |
913 | + |
914 | + @property |
915 | + def x(self): |
916 | + return self[0] |
917 | + |
918 | + @property |
919 | + def y(self): |
920 | + return self[1] |
921 | + |
922 | + |
923 | +class Size(_array_packed_type(2)): |
924 | + |
925 | + """The Size class represents a 2D size in cartesian space. |
926 | + |
927 | + To construct a Size, pass in the width, height parameters to the class |
928 | + constructor:: |
929 | + |
930 | + >>> my_size = Size(50,100) |
931 | + |
932 | + These attributes can be accessed either using named attributes, or via |
933 | + sequence indexes:: |
934 | + |
935 | + >>> my_size.width == my_size.w == my_size[0] == 50 |
936 | + True |
937 | + >>> my_size.height == my_size.h == my_size[1] == 100 |
938 | + True |
939 | + |
940 | + Size instances can be compared using ``==`` and ``!=``, either to another |
941 | + Size instance, or to any mutable sequence type with the correct number of |
942 | + items:: |
943 | + |
944 | + >>> my_size == [50, 100] |
945 | + True |
946 | + >>> my_size != Size(5, 10) |
947 | + True |
948 | + |
949 | + """ |
950 | + |
951 | + @property |
952 | + def w(self): |
953 | + return self[0] |
954 | + |
955 | + @property |
956 | + def width(self): |
957 | + return self[0] |
958 | + |
959 | + @property |
960 | + def h(self): |
961 | + return self[1] |
962 | + |
963 | + @property |
964 | + def height(self): |
965 | + return self[1] |
966 | + |
967 | + |
968 | +class Color(_array_packed_type(4)): |
969 | + |
970 | + """The Color class represents an RGBA Color. |
971 | + |
972 | + To construct a Color, pass in the red, green, blue and alpha parameters to |
973 | + the class constructor:: |
974 | + |
975 | + >>> my_color = Color(50, 100, 200, 255) |
976 | + |
977 | + These attributes can be accessed either using named attributes, or via |
978 | + sequence indexes:: |
979 | + |
980 | + >>> my_color.red == my_color[0] == 50 |
981 | + True |
982 | + >>> my_color.green == my_color[1] == 100 |
983 | + True |
984 | + >>> my_color.blue == my_color[2] == 200 |
985 | + True |
986 | + >>> my_color.alpha == my_color[3] == 255 |
987 | + True |
988 | + |
989 | + Color instances can be compared using ``==`` and ``!=``, either to another |
990 | + Color instance, or to any mutable sequence type with the correct number of |
991 | + items:: |
992 | + |
993 | + >>> my_color == [50, 100, 200, 255] |
994 | + True |
995 | + >>> my_color != Color(5, 10, 0, 0) |
996 | + True |
997 | + |
998 | + """ |
999 | + |
1000 | + @property |
1001 | + def red(self): |
1002 | + return self[0] |
1003 | + |
1004 | + @property |
1005 | + def green(self): |
1006 | + return self[1] |
1007 | + |
1008 | + @property |
1009 | + def blue(self): |
1010 | + return self[2] |
1011 | + |
1012 | + @property |
1013 | + def alpha(self): |
1014 | + return self[3] |
1015 | + |
1016 | + |
1017 | +class DateTime(_array_packed_type(1)): |
1018 | + |
1019 | + """The DateTime class represents a date and time in the UTC timezone. |
1020 | + |
1021 | + DateTime is constructed by passing a unix timestamp in to the constructor. |
1022 | + Timestamps are expressed as the number of seconds since 1970-01-01T00:00:00 |
1023 | + in the UTC timezone:: |
1024 | + |
1025 | + >>> my_dt = DateTime(1377209927) |
1026 | + |
1027 | + This timestamp can always be accessed either using index access or via a |
1028 | + named property:: |
1029 | + |
1030 | + >>> my_dt[0] == my_dt.timestamp == 1377209927 |
1031 | + True |
1032 | + |
1033 | + DateTime objects also expose the usual named properties you would expect on |
1034 | + a date/time object:: |
1035 | + |
1036 | + >>> my_dt.year |
1037 | + 2013 |
1038 | + >>> my_dt.month |
1039 | + 8 |
1040 | + >>> my_dt.day |
1041 | + 22 |
1042 | + >>> my_dt.hour |
1043 | + 22 |
1044 | + >>> my_dt.minute |
1045 | + 18 |
1046 | + >>> my_dt.second |
1047 | + 47 |
1048 | + |
1049 | + Two DateTime objects can be compared for equality:: |
1050 | + |
1051 | + >>> my_dt == DateTime(1377209927) |
1052 | + True |
1053 | + |
1054 | + You can also compare a DateTime with any mutable sequence type containing |
1055 | + the timestamp (although this probably isn't very useful for test authors):: |
1056 | + |
1057 | + >>> my_dt == [1377209927] |
1058 | + True |
1059 | + |
1060 | + Finally, you can also compare a DateTime instance with a python datetime |
1061 | + instance:: |
1062 | + |
1063 | + >>> my_datetime = datetime.datetime.fromutctimestamp(1377209927) |
1064 | + True |
1065 | + |
1066 | + DateTime instances can be converted to datetime instances: |
1067 | + |
1068 | + >>> isinstance(my_dt.datetime, datetime.datetime) |
1069 | + True |
1070 | + |
1071 | + """ |
1072 | + def __init__(self, *args, **kwargs): |
1073 | + super(DateTime, self).__init__(*args, **kwargs) |
1074 | + self._cached_dt = datetime.utcfromtimestamp(self[0]) |
1075 | + |
1076 | + @property |
1077 | + def year(self): |
1078 | + return self._cached_dt.year |
1079 | + |
1080 | + @property |
1081 | + def month(self): |
1082 | + return self._cached_dt.month |
1083 | + |
1084 | + @property |
1085 | + def day(self): |
1086 | + return self._cached_dt.day |
1087 | + |
1088 | + @property |
1089 | + def hour(self): |
1090 | + return self._cached_dt.hour |
1091 | + |
1092 | + @property |
1093 | + def minute(self): |
1094 | + return self._cached_dt.minute |
1095 | + |
1096 | + @property |
1097 | + def second(self): |
1098 | + return self._cached_dt.second |
1099 | + |
1100 | + @property |
1101 | + def timestamp(self): |
1102 | + return self[0] |
1103 | + |
1104 | + @property |
1105 | + def datetime(self): |
1106 | + return self._cached_dt |
1107 | + |
1108 | + def __eq__(self, other): |
1109 | + if isinstance(other, datetime): |
1110 | + return other == self._cached_dt |
1111 | + return super(DateTime, self).__eq__(other) |
1112 | + |
1113 | + |
1114 | +class Time(_array_packed_type(4)): |
1115 | + |
1116 | + """The Time class represents a time, without a date component. |
1117 | + |
1118 | + You can construct a Time instnace by passing the hours, minutes, seconds, |
1119 | + and milliseconds to the class constructor:: |
1120 | + |
1121 | + >>> my_time = Time(12, 34, 01, 23) |
1122 | + |
1123 | + The values passed in must be valid for their positions (ie..- 0-23 for |
1124 | + hours, 0-59 for minutes and seconds, and 0-999 for milliseconds). Passing |
1125 | + invalid values will cause a ValueError to be raised. |
1126 | + |
1127 | + The hours, minutes, seconds, and milliseconds can be accessed using either |
1128 | + index access or named properties:: |
1129 | + |
1130 | + >>> my_time.hours == my_time[0] == 12 |
1131 | + True |
1132 | + >>> my_time.minutes == my_time[1] == 34 |
1133 | + True |
1134 | + >>> my_time.seconds == my_time[2] == 01 |
1135 | + True |
1136 | + >>> my_time.milliseconds == my_time[3] == 23 |
1137 | + True |
1138 | + |
1139 | + Time instances can be compared to other time instances, any mutable |
1140 | + sequence containing four integers, or datetime.time instances:: |
1141 | + |
1142 | + >>> my_time == Time(12, 34, 01, 23) |
1143 | + True |
1144 | + >>> my_time == Time(1,2,3,4) |
1145 | + False |
1146 | + |
1147 | + >>> my_time == [12, 34, 01, 23] |
1148 | + True |
1149 | + |
1150 | + >>> my_time == datetime.time(12, 34, 01, 23000) |
1151 | + True |
1152 | + |
1153 | + Note that the Time class stores milliseconds, while the ``datettime.time`` |
1154 | + class stores microseconds. |
1155 | + |
1156 | + Finally, you can get a ``datetime.time`` instance from a Time instance:: |
1157 | + |
1158 | + >>> isinstance(my_time.time, datetime.time) |
1159 | + True |
1160 | + |
1161 | + """ |
1162 | + |
1163 | + def __init__(self, *args, **kwargs): |
1164 | + super(Time, self).__init__(*args, **kwargs) |
1165 | + # datetime.time uses microseconds, instead of mulliseconds: |
1166 | + self._cached_time = time(self[0], self[1], self[2], self[3] * 1000) |
1167 | + |
1168 | + @property |
1169 | + def hour(self): |
1170 | + return self._cached_time.hour |
1171 | + |
1172 | + @property |
1173 | + def minute(self): |
1174 | + return self._cached_time.minute |
1175 | + |
1176 | + @property |
1177 | + def second(self): |
1178 | + return self._cached_time.second |
1179 | + |
1180 | + @property |
1181 | + def millisecond(self): |
1182 | + return self._cached_time.microsecond / 1000 |
1183 | + |
1184 | + @property |
1185 | + def time(self): |
1186 | + return self._cached_time |
1187 | + |
1188 | + def __eq__(self, other): |
1189 | + if isinstance(other, time): |
1190 | + return other == self._cached_time |
1191 | + return super(DateTime, self).__eq__(other) |
1192 | |
1193 | === modified file 'autopilot/introspection/utilities.py' |
1194 | --- autopilot/introspection/utilities.py 2013-07-30 15:08:30 +0000 |
1195 | +++ autopilot/introspection/utilities.py 2013-09-16 17:17:50 +0000 |
1196 | @@ -39,3 +39,9 @@ |
1197 | bus_obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') |
1198 | bus_iface = Interface(bus_obj, 'org.freedesktop.DBus') |
1199 | return bus_iface.GetConnectionUnixProcessID(connection_name) |
1200 | + |
1201 | + |
1202 | +def translate_state_keys(state_dict): |
1203 | + """Translates the *state_dict* passed in so the keys are usable as python |
1204 | + attributes.""" |
1205 | + return {k.replace('-', '_'): v for k, v in state_dict.items()} |
1206 | |
1207 | === modified file 'autopilot/keybindings.py' |
1208 | --- autopilot/keybindings.py 2013-09-16 14:37:29 +0000 |
1209 | +++ autopilot/keybindings.py 2013-09-16 17:17:50 +0000 |
1210 | @@ -36,8 +36,8 @@ |
1211 | from __future__ import absolute_import |
1212 | |
1213 | import logging |
1214 | -from types import NoneType |
1215 | import re |
1216 | +import sys |
1217 | |
1218 | from autopilot.input import Keyboard |
1219 | from autopilot.utilities import Silence |
1220 | @@ -45,6 +45,10 @@ |
1221 | logger = logging.getLogger(__name__) |
1222 | |
1223 | |
1224 | +# py2 compatible alias for py3 |
1225 | +if sys.version >= '3': |
1226 | + basestring = str |
1227 | + |
1228 | # |
1229 | # Fill this dictionary with keybindings we want to store. |
1230 | # |
1231 | @@ -262,7 +266,7 @@ |
1232 | keyboard emulator. |
1233 | |
1234 | """ |
1235 | - if type(delay) not in (float, NoneType): |
1236 | + if delay is not None and type(delay) != float: |
1237 | raise TypeError( |
1238 | "delay parameter must be a float if it is defined.") |
1239 | if delay: |
1240 | |
1241 | === modified file 'autopilot/matchers/__init__.py' |
1242 | --- autopilot/matchers/__init__.py 2013-08-06 20:15:11 +0000 |
1243 | +++ autopilot/matchers/__init__.py 2013-09-16 17:17:50 +0000 |
1244 | @@ -108,7 +108,7 @@ |
1245 | try: |
1246 | wait_fun(self.matcher, self.timeout) |
1247 | except AssertionError as e: |
1248 | - return Mismatch(unicode(e)) |
1249 | + return Mismatch(str(e)) |
1250 | return None |
1251 | |
1252 | def __str__(self): |
1253 | |
1254 | === modified file 'autopilot/process/_bamf.py' |
1255 | --- autopilot/process/_bamf.py 2013-08-12 16:11:03 +0000 |
1256 | +++ autopilot/process/_bamf.py 2013-09-16 17:17:50 +0000 |
1257 | @@ -168,11 +168,11 @@ |
1258 | [new_windows.extend(a.get_windows()) for a in apps] |
1259 | filter_fn = lambda w: w.x_id not in [ |
1260 | c.x_id for c in existing_windows] |
1261 | - new_wins = filter(filter_fn, new_windows) |
1262 | + new_wins = list(filter(filter_fn, new_windows)) |
1263 | if new_wins: |
1264 | assert len(new_wins) == 1 |
1265 | return new_wins[0] |
1266 | - except DBusException: |
1267 | + except dbus.DBusException: |
1268 | pass |
1269 | sleep(1) |
1270 | return None |
1271 | @@ -223,7 +223,7 @@ |
1272 | apps = [Application(p) for p in |
1273 | self.matcher_interface.RunningApplications()] |
1274 | if user_visible_only: |
1275 | - return filter(_filter_user_visible, apps) |
1276 | + return list(filter(_filter_user_visible, apps)) |
1277 | return apps |
1278 | |
1279 | def get_running_applications_by_desktop_file(self, desktop_file): |
1280 | @@ -255,7 +255,7 @@ |
1281 | windows = [Window(w) for w in |
1282 | self.matcher_interface.WindowStackForMonitor(-1)] |
1283 | if user_visible_only: |
1284 | - windows = filter(_filter_user_visible, windows) |
1285 | + windows = list(filter(_filter_user_visible, windows)) |
1286 | # Now sort on stacking order. |
1287 | # We explicitly convert to a list from an iterator since tests |
1288 | # frequently try and use len() on return values from these methods. |
1289 | @@ -349,8 +349,8 @@ |
1290 | self._app_proxy, 'org.ayatana.bamf.view') |
1291 | self._app_iface = dbus.Interface( |
1292 | self._app_proxy, 'org.ayatana.bamf.application') |
1293 | - except dbus.DBusException, e: |
1294 | - e.message += 'bamf_app_path=%r' % (bamf_app_path) |
1295 | + except dbus.DBusException as e: |
1296 | + e.args += ('bamf_app_path=%r' % (bamf_app_path),) |
1297 | raise |
1298 | |
1299 | @property |
1300 | @@ -413,6 +413,8 @@ |
1301 | return "<Application '%s'>" % (self.name) |
1302 | |
1303 | def __eq__(self, other): |
1304 | + if other is None: |
1305 | + return False |
1306 | return self.desktop_file == other.desktop_file |
1307 | |
1308 | |
1309 | @@ -611,5 +613,5 @@ |
1310 | """Return a list of strings representing the current window state.""" |
1311 | |
1312 | get_display().sync() |
1313 | - return map(get_display().get_atom_name, self._getProperty( |
1314 | - '_NET_WM_STATE')) |
1315 | + return [get_display().get_atom_name(p) |
1316 | + for p in self._getProperty('_NET_WM_STATE')] |
1317 | |
1318 | === modified file 'autopilot/testcase.py' |
1319 | --- autopilot/testcase.py 2013-07-29 10:29:37 +0000 |
1320 | +++ autopilot/testcase.py 2013-09-16 17:17:50 +0000 |
1321 | @@ -251,7 +251,8 @@ |
1322 | data is retrievable via this object. |
1323 | |
1324 | """ |
1325 | - app_path = subprocess.check_output(['which', application]).strip() |
1326 | + app_path = subprocess.check_output(['which', application], |
1327 | + universal_newlines=True).strip() |
1328 | # Get a launcher, tests can override this if they need: |
1329 | launcher_hint = kwargs.pop('app_type', '') |
1330 | launcher = None |
1331 | @@ -294,8 +295,8 @@ |
1332 | new_apps = [] |
1333 | for i in range(10): |
1334 | current_apps = self.process_manager.get_running_applications() |
1335 | - new_apps = filter( |
1336 | - lambda i: i not in self._app_snapshot, current_apps) |
1337 | + new_apps = list(filter( |
1338 | + lambda i: i not in self._app_snapshot, current_apps)) |
1339 | if not new_apps: |
1340 | self._app_snapshot = None |
1341 | return |
1342 | @@ -399,7 +400,7 @@ |
1343 | if not kwargs: |
1344 | raise ValueError("At least one keyword argument must be present.") |
1345 | |
1346 | - for prop_name, desired_value in kwargs.iteritems(): |
1347 | + for prop_name, desired_value in kwargs.items(): |
1348 | none_val = object() |
1349 | attr = getattr(obj, prop_name, none_val) |
1350 | if attr == none_val: |
1351 | |
1352 | === modified file 'autopilot/tests/functional/test_ap_apps.py' |
1353 | --- autopilot/tests/functional/test_ap_apps.py 2013-07-30 15:08:30 +0000 |
1354 | +++ autopilot/tests/functional/test_ap_apps.py 2013-09-16 17:17:50 +0000 |
1355 | @@ -23,9 +23,10 @@ |
1356 | import stat |
1357 | import subprocess |
1358 | import logging |
1359 | +import sys |
1360 | from mock import patch |
1361 | from tempfile import mktemp |
1362 | -from testtools.matchers import raises, LessThan, Equals |
1363 | +from testtools.matchers import raises, LessThan |
1364 | from textwrap import dedent |
1365 | from time import sleep |
1366 | |
1367 | @@ -37,6 +38,10 @@ |
1368 | ) |
1369 | |
1370 | |
1371 | +# backwards compatible alias for Python 3 |
1372 | +if sys.version > '3': |
1373 | + xrange = range |
1374 | + |
1375 | logger = logging.getLogger(__name__) |
1376 | |
1377 | |
1378 | @@ -90,14 +95,14 @@ |
1379 | |
1380 | """ |
1381 | path = self.write_script(dedent("""\ |
1382 | - #!/usr/bin/python |
1383 | + #!%s |
1384 | |
1385 | from time import sleep |
1386 | |
1387 | while True: |
1388 | - print "Still running" |
1389 | + print("Still running") |
1390 | sleep(1) |
1391 | - """)) |
1392 | + """ % sys.executable)) |
1393 | |
1394 | expected_error = "Search criteria returned no results" |
1395 | self.assertThat( |
1396 | @@ -123,14 +128,14 @@ |
1397 | |
1398 | """ |
1399 | path = self.write_script(dedent("""\ |
1400 | - #!/usr/bin/python |
1401 | + #!%s |
1402 | |
1403 | from time import sleep |
1404 | import sys |
1405 | |
1406 | sleep(5) |
1407 | sys.exit(1) |
1408 | - """)) |
1409 | + """ % sys.executable)) |
1410 | |
1411 | expected_error = "Process exited with exit code: 1" |
1412 | self.assertThat( |
1413 | @@ -148,14 +153,14 @@ |
1414 | |
1415 | """ |
1416 | path = self.write_script(dedent("""\ |
1417 | - #!/usr/bin/python |
1418 | + #!%s |
1419 | |
1420 | from time import sleep |
1421 | import sys |
1422 | |
1423 | sleep(1) |
1424 | sys.exit(1) |
1425 | - """)) |
1426 | + """ % sys.executable)) |
1427 | start = datetime.datetime.now() |
1428 | |
1429 | try: |
1430 | @@ -177,7 +182,7 @@ |
1431 | |
1432 | """ |
1433 | path = self.write_script(dedent("""\ |
1434 | - #!/usr/bin/python |
1435 | + #!%s |
1436 | from PyQt4.QtGui import QMainWindow, QApplication |
1437 | from PyQt4.QtCore import QTimer |
1438 | |
1439 | @@ -188,7 +193,7 @@ |
1440 | win.show() |
1441 | QTimer.singleShot(8000, app.exit) |
1442 | app.exec_() |
1443 | - """)) |
1444 | + """ % sys.executable)) |
1445 | app_proxy = self.launch_test_application(path, app_type='qt') |
1446 | self.assertTrue(app_proxy is not None) |
1447 | |
1448 | @@ -214,7 +219,8 @@ |
1449 | # We cannot use 'which', as qtchooser installs wrappers - we need to |
1450 | # check in the actual library paths |
1451 | env = subprocess.check_output( |
1452 | - ['qtchooser', '-qt=' + version, '-print-env']).split('\n') |
1453 | + ['qtchooser', '-qt=' + version, '-print-env'], |
1454 | + universal_newlines=True).split('\n') |
1455 | for i in env: |
1456 | if i.find('QTTOOLDIR') >= 0: |
1457 | path = i.lstrip("QTTOOLDIR=").strip('"') + "/" + name |
1458 | @@ -226,7 +232,8 @@ |
1459 | def _find_qt_binary_old(self, version, name): |
1460 | # Check for the existence of the binary the old way |
1461 | try: |
1462 | - path = subprocess.check_output(['which', 'qmlviewer']).strip() |
1463 | + path = subprocess.check_output(['which', 'qmlviewer'], |
1464 | + universal_newlines=True).strip() |
1465 | except subprocess.CalledProcessError: |
1466 | path = None |
1467 | return path |
1468 | @@ -236,7 +243,9 @@ |
1469 | |
1470 | try: |
1471 | qtversions = subprocess.check_output( |
1472 | - ['qtchooser', '-list-versions']).split('\n') |
1473 | + ['qtchooser', '-list-versions'], |
1474 | + universal_newlines=True |
1475 | + ).split('\n') |
1476 | check_func = self._find_qt_binary_chooser |
1477 | except OSError: |
1478 | # This means no qtchooser is installed, so let's check for |
1479 | @@ -268,7 +277,7 @@ |
1480 | |
1481 | def test_can_launch_qt_script(self): |
1482 | path = self.write_script(dedent("""\ |
1483 | - #!/usr/bin/python |
1484 | + #!%s |
1485 | from PyQt4.QtGui import QMainWindow, QApplication |
1486 | from sys import argv |
1487 | |
1488 | @@ -276,13 +285,13 @@ |
1489 | win = QMainWindow() |
1490 | win.show() |
1491 | app.exec_() |
1492 | - """)) |
1493 | + """ % sys.executable)) |
1494 | app_proxy = self.launch_test_application(path, app_type='qt') |
1495 | self.assertTrue(app_proxy is not None) |
1496 | |
1497 | def test_can_launch_wrapper_script(self): |
1498 | path = self.write_script(dedent("""\ |
1499 | - #!/usr/bin/python |
1500 | + #!%s |
1501 | from PyQt4.QtGui import QMainWindow, QApplication |
1502 | from sys import argv |
1503 | |
1504 | @@ -290,7 +299,7 @@ |
1505 | win = QMainWindow() |
1506 | win.show() |
1507 | app.exec_() |
1508 | - """)) |
1509 | + """ % sys.executable)) |
1510 | wrapper_path = self.write_script(dedent("""\ |
1511 | #!/bin/sh |
1512 | |
1513 | @@ -309,7 +318,7 @@ |
1514 | |
1515 | try: |
1516 | self.app_path = subprocess.check_output( |
1517 | - ['which', 'gnome-mahjongg']).strip() |
1518 | + ['which', 'gnome-mahjongg'], universal_newlines=True).strip() |
1519 | except subprocess.CalledProcessError: |
1520 | self.skip("gnome-mahjongg not found.") |
1521 | |
1522 | @@ -319,27 +328,27 @@ |
1523 | |
1524 | def test_can_launch_gtk_script(self): |
1525 | path = self.write_script(dedent("""\ |
1526 | - #!/usr/bin/python |
1527 | + #!%s |
1528 | from gi.repository import Gtk |
1529 | |
1530 | win = Gtk.Window() |
1531 | win.connect("delete-event", Gtk.main_quit) |
1532 | win.show_all() |
1533 | Gtk.main() |
1534 | - """)) |
1535 | + """ % sys.executable)) |
1536 | app_proxy = self.launch_test_application(path, app_type='gtk') |
1537 | self.assertTrue(app_proxy is not None) |
1538 | |
1539 | def test_can_launch_wrapper_script(self): |
1540 | path = self.write_script(dedent("""\ |
1541 | - #!/usr/bin/python |
1542 | + #!%s |
1543 | from gi.repository import Gtk |
1544 | |
1545 | win = Gtk.Window() |
1546 | win.connect("delete-event", Gtk.main_quit) |
1547 | win.show_all() |
1548 | Gtk.main() |
1549 | - """)) |
1550 | + """ % sys.executable)) |
1551 | wrapper_path = self.write_script(dedent("""\ |
1552 | #!/bin/sh |
1553 | |
1554 | |
1555 | === modified file 'autopilot/tests/functional/test_autopilot_functional.py' |
1556 | --- autopilot/tests/functional/test_autopilot_functional.py 2013-08-29 06:46:41 +0000 |
1557 | +++ autopilot/tests/functional/test_autopilot_functional.py 2013-09-16 17:17:50 +0000 |
1558 | @@ -99,7 +99,8 @@ |
1559 | environment_patch['PYTHONPATH'] = ap_base_path |
1560 | bin_path = os.path.join(ap_base_path, 'bin', 'autopilot') |
1561 | if not os.path.exists(bin_path): |
1562 | - bin_path = subprocess.check_output(['which', 'autopilot']).strip() |
1563 | + bin_path = subprocess.check_output(['which', 'autopilot'], |
1564 | + universal_newlines=True).strip() |
1565 | logger.info( |
1566 | "Not running from source, setting bin_path to %s", bin_path) |
1567 | |
1568 | @@ -119,6 +120,7 @@ |
1569 | env=environ, |
1570 | stdout=subprocess.PIPE, |
1571 | stderr=subprocess.PIPE, |
1572 | + universal_newlines=True, |
1573 | ) |
1574 | |
1575 | stdout, stderr = process.communicate() |
1576 | @@ -173,7 +175,7 @@ |
1577 | |
1578 | if type(tests) is not list: |
1579 | raise TypeError("tests must be a list, not %r" % type(tests)) |
1580 | - if not isinstance(output, basestring): |
1581 | + if not isinstance(output, str): |
1582 | raise TypeError("output must be a string, not %r" % type(output)) |
1583 | |
1584 | test_names = ''.join([' %s\n' % t for t in sorted(tests)]) |
1585 | @@ -738,7 +740,7 @@ |
1586 | |
1587 | self.assertThat(code, Equals(1)) |
1588 | self.assertTrue(os.path.exists(output_file_path)) |
1589 | - log_contents = unicode(open(output_file_path, encoding='utf-8').read()) |
1590 | + log_contents = open(output_file_path, encoding='utf-8').read() |
1591 | self.assertThat( |
1592 | log_contents, |
1593 | Contains(u'\xa1pl\u0279oM \u01ddpo\u0254\u0131u\u2229 oll\u01ddH')) |
1594 | @@ -773,7 +775,7 @@ |
1595 | |
1596 | self.assertThat(code, Equals(1)) |
1597 | self.assertTrue(os.path.exists(output_file_path)) |
1598 | - log_contents = unicode(open(output_file_path, encoding='utf-8').read()) |
1599 | + log_contents = open(output_file_path, encoding='utf-8').read() |
1600 | self.assertThat( |
1601 | log_contents, |
1602 | Contains(u'\xa1pl\u0279oM \u01ddpo\u0254\u0131u\u2229 oll\u01ddH')) |
1603 | |
1604 | === modified file 'autopilot/tests/functional/test_dbus_query.py' |
1605 | --- autopilot/tests/functional/test_dbus_query.py 2013-07-29 10:29:37 +0000 |
1606 | +++ autopilot/tests/functional/test_dbus_query.py 2013-09-16 17:17:50 +0000 |
1607 | @@ -73,6 +73,18 @@ |
1608 | main_window = app.select_single('QMainWindow') |
1609 | self.assertThat(main_window, NotEquals(None)) |
1610 | |
1611 | + def test_can_select_parent_of_root(self): |
1612 | + """Must be able to select the parent of the root object.""" |
1613 | + root = self.start_fully_featured_app() |
1614 | + root_parent = root.get_parent() |
1615 | + self.assertThat(root.id, Equals(root_parent.id)) |
1616 | + |
1617 | + def test_can_select_parent_of_normal_node(self): |
1618 | + root = self.start_fully_featured_app() |
1619 | + main_window = root.select_single('QMainWindow') |
1620 | + window_parent = main_window.get_parent() |
1621 | + self.assertThat(window_parent.id, Equals(root.id)) |
1622 | + |
1623 | def test_single_select_on_object(self): |
1624 | """Must be able to select a single unique child of an object.""" |
1625 | app = self.start_fully_featured_app() |
1626 | @@ -156,7 +168,7 @@ |
1627 | def test_select_many_only_using_parameters(self): |
1628 | app = self.start_fully_featured_app() |
1629 | many_help_menus = app.select_many(title='Help') |
1630 | - self.assertThat(len(many_help_menus), Equals(2)) |
1631 | + self.assertThat(len(many_help_menus), Equals(1)) |
1632 | |
1633 | def test_select_many_with_no_parameter_matches_returns_empty_list(self): |
1634 | app = self.start_fully_featured_app() |
1635 | @@ -172,7 +184,8 @@ |
1636 | super(DbusCustomBusTests, self).setUp() |
1637 | |
1638 | def _enable_custom_dbus_bus(self): |
1639 | - p = subprocess.Popen(['dbus-launch'], stdout=subprocess.PIPE) |
1640 | + p = subprocess.Popen(['dbus-launch'], stdout=subprocess.PIPE, |
1641 | + universal_newlines=True) |
1642 | output = p.communicate() |
1643 | results = output[0].split("\n") |
1644 | dbus_pid = int(results[1].split("=")[1]) |
1645 | |
1646 | === modified file 'autopilot/tests/functional/test_input_stack.py' |
1647 | --- autopilot/tests/functional/test_input_stack.py 2013-09-04 03:44:07 +0000 |
1648 | +++ autopilot/tests/functional/test_input_stack.py 2013-09-16 17:17:50 +0000 |
1649 | @@ -27,11 +27,13 @@ |
1650 | from unittest import SkipTest |
1651 | from mock import patch |
1652 | |
1653 | +from autopilot.display import Display |
1654 | from autopilot import platform |
1655 | from autopilot.testcase import AutopilotTestCase, multiply_scenarios |
1656 | from autopilot.input import Keyboard, Mouse, Pointer, Touch |
1657 | from autopilot.input._common import get_center_point |
1658 | from autopilot.matchers import Eventually |
1659 | +from autopilot.testcase import AutopilotTestCase, multiply_scenarios |
1660 | from autopilot.utilities import on_test_started |
1661 | |
1662 | |
1663 | @@ -274,9 +276,12 @@ |
1664 | LP bug #1195499. |
1665 | |
1666 | """ |
1667 | + screen_geometry = Display.create().get_screen_geometry(0) |
1668 | device = Mouse.create() |
1669 | - device.move(10, 10.6) |
1670 | - self.assertEqual(device.position(), (10, 10)) |
1671 | + target_x = screen_geometry[0] + 10 |
1672 | + target_y = screen_geometry[1] + 10.6 |
1673 | + device.move(target_x, target_y) |
1674 | + self.assertEqual(device.position(), (target_x, int(target_y))) |
1675 | |
1676 | @patch('autopilot.platform.model', new=lambda *args: "Not Desktop", ) |
1677 | def test_mouse_creation_on_device_raises_useful_error(self): |
1678 | |
1679 | === modified file 'autopilot/tests/functional/test_introspection_features.py' |
1680 | --- autopilot/tests/functional/test_introspection_features.py 2013-07-14 09:22:48 +0000 |
1681 | +++ autopilot/tests/functional/test_introspection_features.py 2013-09-16 17:17:50 +0000 |
1682 | @@ -104,7 +104,8 @@ |
1683 | |
1684 | def launch_test_qml(self): |
1685 | arch = subprocess.check_output( |
1686 | - ["dpkg-architecture", "-qDEB_HOST_MULTIARCH"]).strip() |
1687 | + ["dpkg-architecture", "-qDEB_HOST_MULTIARCH"], |
1688 | + universal_newlines=True).strip() |
1689 | qml_path = tempfile.mktemp(suffix='.qml') |
1690 | open(qml_path, 'w').write(self.test_qml) |
1691 | self.addCleanup(os.remove, qml_path) |
1692 | |
1693 | === modified file 'autopilot/tests/functional/test_open_window.py' |
1694 | --- autopilot/tests/functional/test_open_window.py 2013-07-14 09:17:37 +0000 |
1695 | +++ autopilot/tests/functional/test_open_window.py 2013-09-16 17:17:50 +0000 |
1696 | @@ -31,7 +31,7 @@ |
1697 | class OpenWindowTests(AutopilotTestCase): |
1698 | |
1699 | scenarios = [ |
1700 | - (k, {'app_name': k}) for k in ProcessManager.KNOWN_APPS.iterkeys()] |
1701 | + (k, {'app_name': k}) for k in ProcessManager.KNOWN_APPS.keys()] |
1702 | |
1703 | def test_open_window(self): |
1704 | """self.start_app_window must open a new window of the given app.""" |
1705 | |
1706 | === modified file 'autopilot/tests/functional/test_process_emulator.py' |
1707 | --- autopilot/tests/functional/test_process_emulator.py 2013-07-24 04:45:13 +0000 |
1708 | +++ autopilot/tests/functional/test_process_emulator.py 2013-09-16 17:17:50 +0000 |
1709 | @@ -96,7 +96,7 @@ |
1710 | try: |
1711 | process_manager = ProcessManager.create(preferred_backend="BAMF") |
1712 | except BackendException as e: |
1713 | - self.skip("Test is only for BAMF backend ({}).".format(e.message)) |
1714 | + self.skip("Test is only for BAMF backend ({}).".format(str(e))) |
1715 | |
1716 | process_manager.start_app('Calculator') |
1717 | |
1718 | |
1719 | === modified file 'autopilot/tests/unit/test_command_line_args.py' |
1720 | --- autopilot/tests/unit/test_command_line_args.py 2013-07-22 03:40:32 +0000 |
1721 | +++ autopilot/tests/unit/test_command_line_args.py 2013-09-16 17:17:50 +0000 |
1722 | @@ -23,7 +23,14 @@ |
1723 | |
1724 | |
1725 | from mock import patch |
1726 | -from StringIO import StringIO |
1727 | + |
1728 | +try: |
1729 | + # Python 2 |
1730 | + from StringIO import StringIO |
1731 | +except ImportError: |
1732 | + # Python 3 |
1733 | + from io import StringIO |
1734 | + |
1735 | from testtools import TestCase |
1736 | from testtools.matchers import Equals |
1737 | from unittest import expectedFailure |
1738 | @@ -34,7 +41,7 @@ |
1739 | class CommandLineArgsTests(TestCase): |
1740 | |
1741 | def parse_args(self, args): |
1742 | - if isinstance(args, basestring): |
1743 | + if isinstance(args, str): |
1744 | args = args.split() |
1745 | try: |
1746 | return parse_arguments(args) |
1747 | |
1748 | === modified file 'autopilot/tests/unit/test_custom_exceptions.py' |
1749 | --- autopilot/tests/unit/test_custom_exceptions.py 2013-07-22 03:39:30 +0000 |
1750 | +++ autopilot/tests/unit/test_custom_exceptions.py 2013-09-16 17:17:50 +0000 |
1751 | @@ -30,7 +30,7 @@ |
1752 | """BackendException must be able to wrap another exception instance.""" |
1753 | err = BackendException(RuntimeError("Hello World")) |
1754 | self.assertThat(err.original_exception, IsInstance(RuntimeError)) |
1755 | - self.assertThat(err.original_exception.message, Equals("Hello World")) |
1756 | + self.assertThat(str(err.original_exception), Equals("Hello World")) |
1757 | |
1758 | def test_dunder_str(self): |
1759 | err = BackendException(RuntimeError("Hello World")) |
1760 | |
1761 | === modified file 'autopilot/tests/unit/test_introspection_features.py' |
1762 | --- autopilot/tests/unit/test_introspection_features.py 2013-08-08 20:33:14 +0000 |
1763 | +++ autopilot/tests/unit/test_introspection_features.py 2013-09-16 17:17:50 +0000 |
1764 | @@ -24,8 +24,9 @@ |
1765 | |
1766 | |
1767 | from autopilot.introspection.dbus import ( |
1768 | + _get_filter_string_for_key_value_pair, |
1769 | + _is_valid_server_side_filter_param, |
1770 | CustomEmulatorBase, |
1771 | - _is_valid_server_side_filter_param, |
1772 | ) |
1773 | |
1774 | |
1775 | @@ -69,10 +70,19 @@ |
1776 | scenarios = [ |
1777 | ('should work', dict(key='keyname', value='value', result=True)), |
1778 | ('invalid key', dict(key='k e', value='value', result=False)), |
1779 | - ('invalid value', dict(key='key', value='v e', result=False)), |
1780 | - ('invalid value2', dict(key='key', value='v?e', result=False)), |
1781 | - ('invalid value3', dict(key='key', value='1/2', result=False)), |
1782 | - ('invalid value type', dict(key='key', value=False, result=False)), |
1783 | + ('string value', dict(key='key', value='v e', result=True)), |
1784 | + ('string value2', dict(key='key', value='v?e', result=True)), |
1785 | + ('string value3', dict(key='key', value='1/2."!@#*&^%', result=True)), |
1786 | + ('bool value', dict(key='key', value=False, result=True)), |
1787 | + ('int value', dict(key='key', value=123, result=True)), |
1788 | + ('int value2', dict(key='key', value=-123, result=True)), |
1789 | + ('float value', dict(key='key', value=1.0, result=False)), |
1790 | + ('dict value', dict(key='key', value={}, result=False)), |
1791 | + ('obj value', dict(key='key', value=TestCase, result=False)), |
1792 | + ('int overflow 1', dict(key='key', value=-2147483648, result=True)), |
1793 | + ('int overflow 2', dict(key='key', value=-2147483649, result=False)), |
1794 | + ('int overflow 3', dict(key='key', value=2147483647, result=True)), |
1795 | + ('int overflow 4', dict(key='key', value=2147483648, result=False)), |
1796 | ] |
1797 | |
1798 | def test_valid_server_side_param(self): |
1799 | @@ -80,3 +90,24 @@ |
1800 | _is_valid_server_side_filter_param(self.key, self.value), |
1801 | Equals(self.result) |
1802 | ) |
1803 | + |
1804 | + |
1805 | +class ServerSideParameterFilterStringTests(TestWithScenarios, TestCase): |
1806 | + |
1807 | + scenarios = [ |
1808 | + ('bool true', dict(k='visible', v=True, r="visible=True")), |
1809 | + ('bool false', dict(k='visible', v=False, r="visible=False")), |
1810 | + ('int +ve', dict(k='size', v=123, r="size=123")), |
1811 | + ('int -ve', dict(k='prio', v=-12, r="prio=-12")), |
1812 | + ('simple string', dict(k='Name', v="btn1", r="Name=\"btn1\"")), |
1813 | + ('string space', dict(k='Name', v="a b c ", r="Name=\"a b c \"")), |
1814 | + ('str escapes', dict( |
1815 | + k='a', |
1816 | + v="\a\b\f\n\r\t\v\\", |
1817 | + r=r'a="\x07\x08\x0c\n\r\t\x0b\\"')), |
1818 | + ('escape quotes', dict(k='b', v="'", r='b="\\' + "'" + '"')), |
1819 | + ] |
1820 | + |
1821 | + def test_query_string(self): |
1822 | + s = _get_filter_string_for_key_value_pair(self.k, self.v) |
1823 | + self.assertThat(s, Equals(self.r)) |
1824 | |
1825 | === modified file 'autopilot/tests/unit/test_matchers.py' |
1826 | --- autopilot/tests/unit/test_matchers.py 2013-08-07 03:18:32 +0000 |
1827 | +++ autopilot/tests/unit/test_matchers.py 2013-09-16 17:17:50 +0000 |
1828 | @@ -20,9 +20,12 @@ |
1829 | |
1830 | from __future__ import absolute_import |
1831 | |
1832 | +import sys |
1833 | + |
1834 | from autopilot.introspection.dbus import DBusIntrospectionObject |
1835 | from autopilot.matchers import Eventually |
1836 | |
1837 | +from contextlib import contextmanager |
1838 | import dbus |
1839 | from testscenarios import TestWithScenarios |
1840 | from testtools import TestCase |
1841 | @@ -30,7 +33,6 @@ |
1842 | Contains, |
1843 | Equals, |
1844 | IsInstance, |
1845 | - LessThan, |
1846 | MatchesException, |
1847 | Mismatch, |
1848 | Raises, |
1849 | @@ -38,6 +40,23 @@ |
1850 | from time import time |
1851 | |
1852 | |
1853 | +if sys.version >= '3': |
1854 | + unicode = str |
1855 | + |
1856 | + |
1857 | +@contextmanager |
1858 | +def expected_runtime(tmin, tmax): |
1859 | + start = time() |
1860 | + try: |
1861 | + yield |
1862 | + finally: |
1863 | + elapsed_time = abs(time() - start) |
1864 | + if not tmin < elapsed_time < tmax: |
1865 | + raise AssertionError( |
1866 | + "Runtime of %f is not between %f and %f" |
1867 | + % (elapsed_time, tmin, tmax)) |
1868 | + |
1869 | + |
1870 | def make_fake_attribute_with_result(result, attribute_type='wait_for'): |
1871 | """Make a fake attribute with the given result. |
1872 | |
1873 | @@ -58,13 +77,15 @@ |
1874 | return lambda: result |
1875 | elif attribute_type == 'wait_for': |
1876 | if isinstance(result, unicode): |
1877 | - obj = FakeObject(dict(id=123, attr=dbus.String(result))) |
1878 | + obj = FakeObject(dict(id=[0, 123], attr=[0, dbus.String(result)])) |
1879 | return obj.attr |
1880 | - elif isinstance(result, str): |
1881 | - obj = FakeObject(dict(id=123, attr=dbus.UTF8String(result))) |
1882 | + elif isinstance(result, bytes): |
1883 | + obj = FakeObject( |
1884 | + dict(id=[0, 123], attr=[0, dbus.UTF8String(result)]) |
1885 | + ) |
1886 | return obj.attr |
1887 | else: |
1888 | - obj = FakeObject(dict(id=123, attr=dbus.Boolean(result))) |
1889 | + obj = FakeObject(dict(id=[0, 123], attr=[0, dbus.Boolean(result)])) |
1890 | return obj.attr |
1891 | |
1892 | |
1893 | @@ -84,37 +105,31 @@ |
1894 | ('wait_for', dict(attribute_type='wait_for')), |
1895 | ] |
1896 | |
1897 | - def test_eventually_matcher_returns_Mismatch(self): |
1898 | + def test_eventually_matcher_returns_mismatch(self): |
1899 | """Eventually matcher must return a Mismatch.""" |
1900 | attr = make_fake_attribute_with_result(False, self.attribute_type) |
1901 | - e = Eventually(Equals(True)).match(lambda: attr) |
1902 | + e = Eventually(Equals(True)).match(attr) |
1903 | |
1904 | self.assertThat(e, IsInstance(Mismatch)) |
1905 | |
1906 | def test_eventually_default_timeout(self): |
1907 | """Eventually matcher must default to 10 second timeout.""" |
1908 | attr = make_fake_attribute_with_result(False, self.attribute_type) |
1909 | - start = time() |
1910 | - Eventually(Equals(True)).match(attr) |
1911 | - # max error of 1 second seems reasonable: |
1912 | - self.assertThat(abs(time() - start - 10.0), LessThan(1)) |
1913 | + with expected_runtime(9.5, 11.0): |
1914 | + Eventually(Equals(True)).match(attr) |
1915 | |
1916 | def test_eventually_passes_immeadiately(self): |
1917 | """Eventually matcher must not wait if the assertion passes |
1918 | initially.""" |
1919 | - start = time() |
1920 | attr = make_fake_attribute_with_result(True, self.attribute_type) |
1921 | - Eventually(Equals(True)).match(attr) |
1922 | - # max error of 1 second seems reasonable: |
1923 | - self.assertThat(abs(time() - start), LessThan(1)) |
1924 | + with expected_runtime(0.0, 1.0): |
1925 | + Eventually(Equals(True)).match(attr) |
1926 | |
1927 | def test_eventually_matcher_allows_non_default_timeout(self): |
1928 | """Eventually matcher must allow a non-default timeout value.""" |
1929 | - start = time() |
1930 | attr = make_fake_attribute_with_result(False, self.attribute_type) |
1931 | - Eventually(Equals(True), timeout=5).match(attr) |
1932 | - # max error of 1 second seems reasonable: |
1933 | - self.assertThat(abs(time() - start - 5.0), LessThan(1)) |
1934 | + with expected_runtime(4.5, 6.0): |
1935 | + Eventually(Equals(True), timeout=5).match(attr) |
1936 | |
1937 | def test_mismatch_message_has_correct_timeout_value(self): |
1938 | """The mismatch value must have the correct timeout value in it.""" |
1939 | @@ -134,20 +149,16 @@ |
1940 | |
1941 | def test_match_with_expected_value_unicode(self): |
1942 | """The expected unicode value matches new value string.""" |
1943 | - start = time() |
1944 | attr = make_fake_attribute_with_result( |
1945 | - unicode(u'\u963f\u5e03\u4ece'), 'wait_for') |
1946 | - Eventually(Equals(str("阿布从"))).match(attr) |
1947 | - #this should not take more than 1 second |
1948 | - self.assertThat(abs(time() - start), LessThan(1)) |
1949 | + u'\u963f\u5e03\u4ece', 'wait_for') |
1950 | + with expected_runtime(0.0, 1.0): |
1951 | + Eventually(Equals("阿布从")).match(attr) |
1952 | |
1953 | def test_match_with_new_value_unicode(self): |
1954 | """new value with unicode must match expected value string.""" |
1955 | - start = time() |
1956 | attr = make_fake_attribute_with_result(str("阿布从"), 'wait_for') |
1957 | - Eventually(Equals(unicode(u'\u963f\u5e03\u4ece'))).match(attr) |
1958 | - # this should not take more than 1 second |
1959 | - self.assertThat(abs(time() - start), LessThan(1)) |
1960 | + with expected_runtime(0.0, 1.0): |
1961 | + Eventually(Equals(u'\u963f\u5e03\u4ece')).match(attr) |
1962 | |
1963 | def test_mismatch_with_bool(self): |
1964 | """The mismatch value must fail boolean values.""" |
1965 | @@ -160,7 +171,7 @@ |
1966 | """The mismatch value must fail with str and unicode mix.""" |
1967 | attr = make_fake_attribute_with_result(str("阿布从1"), 'wait_for') |
1968 | mismatch = Eventually(Equals( |
1969 | - unicode(u'\u963f\u5e03\u4ece')), timeout=.5).match(attr) |
1970 | + u'\u963f\u5e03\u4ece'), timeout=.5).match(attr) |
1971 | self.assertThat( |
1972 | mismatch.describe(), Contains('failed')) |
1973 | |
1974 | @@ -169,6 +180,6 @@ |
1975 | self.skip("mismatch Contains returns ascii error") |
1976 | attr = make_fake_attribute_with_result(str("阿布从1"), 'wait_for') |
1977 | mismatch = Eventually(Equals( |
1978 | - unicode(u'\u963f\u5e03\u4ece')), timeout=.5).match(attr) |
1979 | + u'\u963f\u5e03\u4ece'), timeout=.5).match(attr) |
1980 | self.assertThat( |
1981 | mismatch.describe(), Contains("阿布从11")) |
1982 | |
1983 | === modified file 'autopilot/tests/unit/test_pick_backend.py' |
1984 | --- autopilot/tests/unit/test_pick_backend.py 2013-07-22 03:30:31 +0000 |
1985 | +++ autopilot/tests/unit/test_pick_backend.py 2013-09-16 17:17:50 +0000 |
1986 | @@ -107,7 +107,7 @@ |
1987 | raised = True |
1988 | self.assertTrue(hasattr(e, 'original_exception')) |
1989 | self.assertThat(e.original_exception, IsInstance(ValueError)) |
1990 | - self.assertThat(e.original_exception.message, Equals("Foo")) |
1991 | + self.assertThat(str(e.original_exception), Equals("Foo")) |
1992 | self.assertTrue(raised) |
1993 | |
1994 | def test_failure_of_all_backends(self): |
1995 | |
1996 | === modified file 'autopilot/tests/unit/test_platform.py' |
1997 | --- autopilot/tests/unit/test_platform.py 2013-07-24 00:07:54 +0000 |
1998 | +++ autopilot/tests/unit/test_platform.py 2013-09-16 17:17:50 +0000 |
1999 | @@ -21,7 +21,13 @@ |
2000 | |
2001 | import autopilot.platform as platform |
2002 | |
2003 | -from StringIO import StringIO |
2004 | +try: |
2005 | + # Python 2 |
2006 | + from StringIO import StringIO |
2007 | +except ImportError: |
2008 | + # Python 3 |
2009 | + from io import StringIO |
2010 | + |
2011 | from testtools import TestCase |
2012 | from testtools.matchers import Equals |
2013 | |
2014 | |
2015 | === added file 'autopilot/tests/unit/test_types.py' |
2016 | --- autopilot/tests/unit/test_types.py 1970-01-01 00:00:00 +0000 |
2017 | +++ autopilot/tests/unit/test_types.py 2013-09-16 17:17:50 +0000 |
2018 | @@ -0,0 +1,579 @@ |
2019 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2020 | +# |
2021 | +# Autopilot Functional Test Tool |
2022 | +# Copyright (C) 2013 Canonical |
2023 | +# |
2024 | +# This program is free software: you can redistribute it and/or modify |
2025 | +# it under the terms of the GNU General Public License as published by |
2026 | +# the Free Software Foundation, either version 3 of the License, or |
2027 | +# (at your option) any later version. |
2028 | +# |
2029 | +# This program is distributed in the hope that it will be useful, |
2030 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2031 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2032 | +# GNU General Public License for more details. |
2033 | +# |
2034 | +# You should have received a copy of the GNU General Public License |
2035 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2036 | +# |
2037 | + |
2038 | +from __future__ import absolute_import |
2039 | + |
2040 | +from datetime import datetime, time |
2041 | +from mock import patch |
2042 | +from testscenarios import TestWithScenarios |
2043 | +from testtools import TestCase |
2044 | +from testtools.matchers import Equals, IsInstance, NotEquals, raises |
2045 | +import dbus |
2046 | + |
2047 | +from autopilot.introspection.types import ( |
2048 | + Color, |
2049 | + create_value_instance, |
2050 | + DateTime, |
2051 | + PlainType, |
2052 | + Point, |
2053 | + Rectangle, |
2054 | + Size, |
2055 | + Time, |
2056 | + ValueType, |
2057 | +) |
2058 | +from autopilot.introspection.dbus import DBusIntrospectionObject |
2059 | + |
2060 | + |
2061 | +class FakeObject(object): |
2062 | + |
2063 | + def __init__(self): |
2064 | + self.get_new_state_called = False |
2065 | + self.set_properties_called = False |
2066 | + |
2067 | + def get_new_state(self): |
2068 | + self.get_new_state_called = True |
2069 | + |
2070 | + def _set_properties(self, state): |
2071 | + self.set_properties_called = True |
2072 | + |
2073 | + |
2074 | +class PlainTypeTests(TestWithScenarios, TestCase): |
2075 | + |
2076 | + scenarios = [ |
2077 | + ('bool true', dict(t=dbus.Boolean, v=True)), |
2078 | + ('bool false', dict(t=dbus.Boolean, v=False)), |
2079 | + ('int16 +ve', dict(t=dbus.Int16, v=123)), |
2080 | + ('int16 -ve', dict(t=dbus.Int16, v=-23000)), |
2081 | + ('int32 +ve', dict(t=dbus.Int32, v=30000000)), |
2082 | + ('int32 -ve', dict(t=dbus.Int32, v=-3002050)), |
2083 | + ('int64 +ve', dict(t=dbus.Int64, v=234234002050)), |
2084 | + ('int64 -ve', dict(t=dbus.Int64, v=-234234002050)), |
2085 | + ('string', dict(t=dbus.String, v="Hello World")), |
2086 | + ] |
2087 | + |
2088 | + def test_can_construct(self): |
2089 | + p = PlainType(self.t(self.v)) |
2090 | + |
2091 | + self.assertThat(p, Equals(self.v)) |
2092 | + self.assertThat(hasattr(p, 'wait_for'), Equals(True)) |
2093 | + self.assertThat(p, IsInstance(self.t)) |
2094 | + |
2095 | + |
2096 | +class RectangleTypeTests(TestCase): |
2097 | + |
2098 | + def test_can_construct_rectangle(self): |
2099 | + r = Rectangle(1, 2, 3, 4) |
2100 | + self.assertThat(r, IsInstance(dbus.Array)) |
2101 | + |
2102 | + def test_rectangle_has_xywh_properties(self): |
2103 | + r = Rectangle(1, 2, 3, 4) |
2104 | + |
2105 | + self.assertThat(r.x, Equals(1)) |
2106 | + self.assertThat(r.y, Equals(2)) |
2107 | + self.assertThat(r.w, Equals(3)) |
2108 | + self.assertThat(r.width, Equals(3)) |
2109 | + self.assertThat(r.h, Equals(4)) |
2110 | + self.assertThat(r.height, Equals(4)) |
2111 | + |
2112 | + def test_rectangle_has_slice_access(self): |
2113 | + r = Rectangle(1, 2, 3, 4) |
2114 | + |
2115 | + self.assertThat(r[0], Equals(1)) |
2116 | + self.assertThat(r[1], Equals(2)) |
2117 | + self.assertThat(r[2], Equals(3)) |
2118 | + self.assertThat(r[3], Equals(4)) |
2119 | + |
2120 | + def test_equality_with_rectangle(self): |
2121 | + r1 = Rectangle(1, 2, 3, 4) |
2122 | + r2 = Rectangle(1, 2, 3, 4) |
2123 | + |
2124 | + self.assertThat(r1, Equals(r2)) |
2125 | + |
2126 | + def test_equality_with_list(self): |
2127 | + r1 = Rectangle(1, 2, 3, 4) |
2128 | + r2 = [1, 2, 3, 4] |
2129 | + |
2130 | + self.assertThat(r1, Equals(r2)) |
2131 | + |
2132 | + |
2133 | +class PointTypeTests(TestCase): |
2134 | + |
2135 | + def test_can_construct_point(self): |
2136 | + r = Point(1, 2) |
2137 | + self.assertThat(r, IsInstance(dbus.Array)) |
2138 | + |
2139 | + def test_point_has_xy_properties(self): |
2140 | + r = Point(1, 2) |
2141 | + |
2142 | + self.assertThat(r.x, Equals(1)) |
2143 | + self.assertThat(r.y, Equals(2)) |
2144 | + |
2145 | + def test_point_has_slice_access(self): |
2146 | + r = Point(1, 2) |
2147 | + |
2148 | + self.assertThat(r[0], Equals(1)) |
2149 | + self.assertThat(r[1], Equals(2)) |
2150 | + |
2151 | + def test_equality_with_point(self): |
2152 | + p1 = Point(1, 2) |
2153 | + p2 = Point(1, 2) |
2154 | + |
2155 | + self.assertThat(p1, Equals(p2)) |
2156 | + |
2157 | + def test_equality_with_list(self): |
2158 | + p1 = Point(1, 2) |
2159 | + p2 = [1, 2] |
2160 | + |
2161 | + self.assertThat(p1, Equals(p2)) |
2162 | + |
2163 | + |
2164 | +class SizeTypeTests(TestCase): |
2165 | + |
2166 | + def test_can_construct_size(self): |
2167 | + r = Size(1, 2) |
2168 | + self.assertThat(r, IsInstance(dbus.Array)) |
2169 | + |
2170 | + def test_size_has_wh_properties(self): |
2171 | + r = Size(1, 2) |
2172 | + |
2173 | + self.assertThat(r.w, Equals(1)) |
2174 | + self.assertThat(r.width, Equals(1)) |
2175 | + self.assertThat(r.h, Equals(2)) |
2176 | + self.assertThat(r.height, Equals(2)) |
2177 | + |
2178 | + def test_size_has_slice_access(self): |
2179 | + r = Size(1, 2) |
2180 | + |
2181 | + self.assertThat(r[0], Equals(1)) |
2182 | + self.assertThat(r[1], Equals(2)) |
2183 | + |
2184 | + def test_equality_with_size(self): |
2185 | + s1 = Size(50, 100) |
2186 | + s2 = Size(50, 100) |
2187 | + |
2188 | + self.assertThat(s1, Equals(s2)) |
2189 | + |
2190 | + def test_equality_with_list(self): |
2191 | + s1 = Size(50, 100) |
2192 | + s2 = [50, 100] |
2193 | + |
2194 | + self.assertThat(s1, Equals(s2)) |
2195 | + |
2196 | + |
2197 | +class ColorTypeTests(TestCase): |
2198 | + |
2199 | + def test_can_construct_color(self): |
2200 | + r = Color(123, 234, 55, 255) |
2201 | + self.assertThat(r, IsInstance(dbus.Array)) |
2202 | + |
2203 | + def test_color_has_rgba_properties(self): |
2204 | + r = Color(123, 234, 55, 255) |
2205 | + |
2206 | + self.assertThat(r.red, Equals(123)) |
2207 | + self.assertThat(r.green, Equals(234)) |
2208 | + self.assertThat(r.blue, Equals(55)) |
2209 | + self.assertThat(r.alpha, Equals(255)) |
2210 | + |
2211 | + def test_color_has_slice_access(self): |
2212 | + r = Color(123, 234, 55, 255) |
2213 | + |
2214 | + self.assertThat(r[0], Equals(123)) |
2215 | + self.assertThat(r[1], Equals(234)) |
2216 | + self.assertThat(r[2], Equals(55)) |
2217 | + self.assertThat(r[3], Equals(255)) |
2218 | + |
2219 | + def test_eqiality_with_color(self): |
2220 | + c1 = Color(123, 234, 55, 255) |
2221 | + c2 = Color(123, 234, 55, 255) |
2222 | + |
2223 | + self.assertThat(c1, Equals(c2)) |
2224 | + |
2225 | + def test_eqiality_with_list(self): |
2226 | + c1 = Color(123, 234, 55, 255) |
2227 | + c2 = [123, 234, 55, 255] |
2228 | + |
2229 | + self.assertThat(c1, Equals(c2)) |
2230 | + |
2231 | + |
2232 | +class DateTimeTests(TestCase): |
2233 | + |
2234 | + def test_can_construct_datetime(self): |
2235 | + dt = DateTime(1377209927) |
2236 | + self.assertThat(dt, IsInstance(dbus.Array)) |
2237 | + |
2238 | + def test_datetime_has_slice_access(self): |
2239 | + dt = DateTime(1377209927) |
2240 | + |
2241 | + self.assertThat(dt[0], Equals(1377209927)) |
2242 | + |
2243 | + def test_datetime_has_properties(self): |
2244 | + dt = DateTime(1377209927) |
2245 | + |
2246 | + self.assertThat(dt.timestamp, Equals(1377209927)) |
2247 | + self.assertThat(dt.year, Equals(2013)) |
2248 | + self.assertThat(dt.month, Equals(8)) |
2249 | + self.assertThat(dt.day, Equals(22)) |
2250 | + self.assertThat(dt.hour, Equals(22)) |
2251 | + self.assertThat(dt.minute, Equals(18)) |
2252 | + self.assertThat(dt.second, Equals(47)) |
2253 | + |
2254 | + def test_equality_with_datetime(self): |
2255 | + dt1 = DateTime(1377209927) |
2256 | + dt2 = DateTime(1377209927) |
2257 | + |
2258 | + self.assertThat(dt1, Equals(dt2)) |
2259 | + |
2260 | + def test_equality_with_list(self): |
2261 | + dt1 = DateTime(1377209927) |
2262 | + dt2 = [1377209927] |
2263 | + |
2264 | + self.assertThat(dt1, Equals(dt2)) |
2265 | + |
2266 | + def test_equality_with_datetime(self): |
2267 | + dt1 = DateTime(1377209927) |
2268 | + dt2 = datetime.utcfromtimestamp(1377209927) |
2269 | + dt3 = datetime.utcfromtimestamp(1377209928) |
2270 | + |
2271 | + self.assertThat(dt1, Equals(dt2)) |
2272 | + self.assertThat(dt1, NotEquals(dt3)) |
2273 | + |
2274 | + def test_can_convert_to_datetime(self): |
2275 | + dt1 = DateTime(1377209927) |
2276 | + |
2277 | + self.assertThat(dt1.datetime, IsInstance(datetime)) |
2278 | + |
2279 | + |
2280 | +class TimeTests(TestCase): |
2281 | + |
2282 | + def test_can_construct_time(self): |
2283 | + dt = Time(0, 0, 0, 0) |
2284 | + self.assertThat(dt, IsInstance(dbus.Array)) |
2285 | + |
2286 | + def test_time_has_slice_access(self): |
2287 | + dt = Time(0, 1, 2, 3) |
2288 | + |
2289 | + self.assertThat(dt[0], Equals(0)) |
2290 | + self.assertThat(dt[1], Equals(1)) |
2291 | + self.assertThat(dt[2], Equals(2)) |
2292 | + self.assertThat(dt[3], Equals(3)) |
2293 | + |
2294 | + def test_time_has_properties(self): |
2295 | + dt = Time(0, 1, 2, 3) |
2296 | + |
2297 | + self.assertThat(dt.hour, Equals(0)) |
2298 | + self.assertThat(dt.minute, Equals(1)) |
2299 | + self.assertThat(dt.second, Equals(2)) |
2300 | + self.assertThat(dt.millisecond, Equals(3)) |
2301 | + |
2302 | + def test_equality_with_time(self): |
2303 | + dt1 = Time(0, 1, 2, 3) |
2304 | + dt2 = Time(0, 1, 2, 3) |
2305 | + dt3 = Time(4, 1, 2, 3) |
2306 | + |
2307 | + self.assertThat(dt1, Equals(dt2)) |
2308 | + self.assertThat(dt1, NotEquals(dt3)) |
2309 | + |
2310 | + def test_equality_with_time(self): |
2311 | + dt1 = Time(0, 1, 2, 3) |
2312 | + dt2 = [0, 1, 2, 3] |
2313 | + dt3 = [4, 1, 2, 3] |
2314 | + |
2315 | + self.assertThat(dt1, Equals(dt2)) |
2316 | + self.assertThat(dt1, NotEquals(dt3)) |
2317 | + |
2318 | + def test_equality_with_time(self): |
2319 | + dt1 = Time(2, 3, 4, 5) |
2320 | + dt2 = time(2, 3, 4, 5000) |
2321 | + dt3 = time(5, 4, 3, 2000) |
2322 | + |
2323 | + self.assertThat(dt1, Equals(dt2)) |
2324 | + self.assertThat(dt1, NotEquals(dt3)) |
2325 | + |
2326 | + def test_can_convert_to_time(self): |
2327 | + dt1 = Time(1, 2, 3, 4) |
2328 | + |
2329 | + self.assertThat(dt1.time, IsInstance(time)) |
2330 | + |
2331 | + |
2332 | +class CreateValueInstanceTests(TestCase): |
2333 | + |
2334 | + """Tests to check that create_value_instance does the right thing.""" |
2335 | + |
2336 | + def test_plain_string(self): |
2337 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.String("Hello")]) |
2338 | + attr = create_value_instance(data, None, None) |
2339 | + |
2340 | + self.assertThat(attr, Equals("Hello")) |
2341 | + self.assertThat(attr, IsInstance(PlainType)) |
2342 | + |
2343 | + def test_plain_boolean(self): |
2344 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.Boolean(False)]) |
2345 | + attr = create_value_instance(data, None, None) |
2346 | + |
2347 | + self.assertThat(attr, Equals(False)) |
2348 | + self.assertThat(attr, IsInstance(PlainType)) |
2349 | + |
2350 | + def test_plain_int16(self): |
2351 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.Int16(-2**14)]) |
2352 | + attr = create_value_instance(data, None, None) |
2353 | + |
2354 | + self.assertThat(attr, Equals(-2**14)) |
2355 | + self.assertThat(attr, IsInstance(PlainType)) |
2356 | + |
2357 | + def test_plain_int32(self): |
2358 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.Int32(-2**30)]) |
2359 | + attr = create_value_instance(data, None, None) |
2360 | + |
2361 | + self.assertThat(attr, Equals(-2**30)) |
2362 | + self.assertThat(attr, IsInstance(PlainType)) |
2363 | + |
2364 | + def test_plain_int64(self): |
2365 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.Int64(-2**40)]) |
2366 | + attr = create_value_instance(data, None, None) |
2367 | + |
2368 | + self.assertThat(attr, Equals(-2**40)) |
2369 | + self.assertThat(attr, IsInstance(PlainType)) |
2370 | + |
2371 | + def test_plain_uint16(self): |
2372 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.UInt16(2**14)]) |
2373 | + attr = create_value_instance(data, None, None) |
2374 | + |
2375 | + self.assertThat(attr, Equals(2**14)) |
2376 | + self.assertThat(attr, IsInstance(PlainType)) |
2377 | + |
2378 | + def test_plain_uint32(self): |
2379 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.UInt32(2**30)]) |
2380 | + attr = create_value_instance(data, None, None) |
2381 | + |
2382 | + self.assertThat(attr, Equals(2**30)) |
2383 | + self.assertThat(attr, IsInstance(PlainType)) |
2384 | + |
2385 | + def test_plain_uint64(self): |
2386 | + data = dbus.Array([dbus.Int32(ValueType.PLAIN), dbus.UInt64(2**40)]) |
2387 | + attr = create_value_instance(data, None, None) |
2388 | + |
2389 | + self.assertThat(attr, Equals(2**40)) |
2390 | + self.assertThat(attr, IsInstance(PlainType)) |
2391 | + |
2392 | + def test_plain_array(self): |
2393 | + data = dbus.Array([ |
2394 | + dbus.Int32(ValueType.PLAIN), |
2395 | + dbus.Array([ |
2396 | + dbus.String("Hello"), |
2397 | + dbus.String("World") |
2398 | + ]) |
2399 | + ]) |
2400 | + attr = create_value_instance(data, None, None) |
2401 | + self.assertThat(attr, Equals(["Hello", "World"])) |
2402 | + self.assertThat(attr, IsInstance(PlainType)) |
2403 | + |
2404 | + def test_rectangle(self): |
2405 | + data = dbus.Array( |
2406 | + [ |
2407 | + dbus.Int32(ValueType.RECTANGLE), |
2408 | + dbus.Int32(0), |
2409 | + dbus.Int32(10), |
2410 | + dbus.Int32(20), |
2411 | + dbus.Int32(30), |
2412 | + ] |
2413 | + ) |
2414 | + |
2415 | + attr = create_value_instance(data, None, None) |
2416 | + |
2417 | + self.assertThat(attr, IsInstance(Rectangle)) |
2418 | + |
2419 | + def test_invalid_rectanlge(self): |
2420 | + data = dbus.Array( |
2421 | + [ |
2422 | + dbus.Int32(ValueType.RECTANGLE), |
2423 | + dbus.Int32(0), |
2424 | + ] |
2425 | + ) |
2426 | + |
2427 | + fn = lambda: create_value_instance(data, None, None) |
2428 | + |
2429 | + self.assertThat(fn, raises( |
2430 | + ValueError("Rectangle must be constructed with 4 arguments, not 1") |
2431 | + )) |
2432 | + |
2433 | + def test_point(self): |
2434 | + data = dbus.Array( |
2435 | + [ |
2436 | + dbus.Int32(ValueType.POINT), |
2437 | + dbus.Int32(0), |
2438 | + dbus.Int32(10), |
2439 | + ] |
2440 | + ) |
2441 | + |
2442 | + attr = create_value_instance(data, None, None) |
2443 | + |
2444 | + self.assertThat(attr, IsInstance(Point)) |
2445 | + |
2446 | + def test_invalid_point(self): |
2447 | + data = dbus.Array( |
2448 | + [ |
2449 | + dbus.Int32(ValueType.POINT), |
2450 | + dbus.Int32(0), |
2451 | + dbus.Int32(0), |
2452 | + dbus.Int32(0), |
2453 | + ] |
2454 | + ) |
2455 | + |
2456 | + fn = lambda: create_value_instance(data, None, None) |
2457 | + |
2458 | + self.assertThat(fn, raises( |
2459 | + ValueError("Point must be constructed with 2 arguments, not 3") |
2460 | + )) |
2461 | + |
2462 | + def test_size(self): |
2463 | + data = dbus.Array( |
2464 | + [ |
2465 | + dbus.Int32(ValueType.SIZE), |
2466 | + dbus.Int32(0), |
2467 | + dbus.Int32(10), |
2468 | + ] |
2469 | + ) |
2470 | + |
2471 | + attr = create_value_instance(data, None, None) |
2472 | + |
2473 | + self.assertThat(attr, IsInstance(Size)) |
2474 | + |
2475 | + def test_invalid_size(self): |
2476 | + data = dbus.Array( |
2477 | + [ |
2478 | + dbus.Int32(ValueType.SIZE), |
2479 | + dbus.Int32(0), |
2480 | + ] |
2481 | + ) |
2482 | + |
2483 | + fn = lambda: create_value_instance(data, None, None) |
2484 | + |
2485 | + self.assertThat(fn, raises( |
2486 | + ValueError("Size must be constructed with 2 arguments, not 1") |
2487 | + )) |
2488 | + |
2489 | + def test_date_time(self): |
2490 | + data = dbus.Array( |
2491 | + [ |
2492 | + dbus.Int32(ValueType.DATETIME), |
2493 | + dbus.Int32(0), |
2494 | + ] |
2495 | + ) |
2496 | + |
2497 | + attr = create_value_instance(data, None, None) |
2498 | + |
2499 | + self.assertThat(attr, IsInstance(DateTime)) |
2500 | + |
2501 | + def test_invalid_date_time(self): |
2502 | + data = dbus.Array( |
2503 | + [ |
2504 | + dbus.Int32(ValueType.DATETIME), |
2505 | + dbus.Int32(0), |
2506 | + dbus.Int32(0), |
2507 | + dbus.Int32(0), |
2508 | + ] |
2509 | + ) |
2510 | + |
2511 | + fn = lambda: create_value_instance(data, None, None) |
2512 | + |
2513 | + self.assertThat(fn, raises( |
2514 | + ValueError("DateTime must be constructed with 1 arguments, not 3") |
2515 | + )) |
2516 | + |
2517 | + def test_time(self): |
2518 | + data = dbus.Array( |
2519 | + [ |
2520 | + dbus.Int32(ValueType.TIME), |
2521 | + dbus.Int32(0), |
2522 | + dbus.Int32(0), |
2523 | + dbus.Int32(0), |
2524 | + dbus.Int32(0), |
2525 | + ] |
2526 | + ) |
2527 | + |
2528 | + attr = create_value_instance(data, None, None) |
2529 | + |
2530 | + self.assertThat(attr, IsInstance(Time)) |
2531 | + |
2532 | + def test_invalid_time(self): |
2533 | + data = dbus.Array( |
2534 | + [ |
2535 | + dbus.Int32(ValueType.TIME), |
2536 | + dbus.Int32(0), |
2537 | + dbus.Int32(0), |
2538 | + dbus.Int32(0), |
2539 | + ] |
2540 | + ) |
2541 | + |
2542 | + fn = lambda: create_value_instance(data, None, None) |
2543 | + |
2544 | + self.assertThat(fn, raises( |
2545 | + ValueError("Time must be constructed with 4 arguments, not 3") |
2546 | + )) |
2547 | + |
2548 | + def test_unknown_type_id(self): |
2549 | + """Unknown type Ids should result in a plain type, along with a log |
2550 | + message. |
2551 | + |
2552 | + """ |
2553 | + |
2554 | + data = dbus.Array( |
2555 | + [ |
2556 | + dbus.Int32(543), |
2557 | + dbus.Int32(0), |
2558 | + dbus.Boolean(False), |
2559 | + dbus.String("Hello World") |
2560 | + ] |
2561 | + ) |
2562 | + attr = create_value_instance(data, None, None) |
2563 | + self.assertThat(attr, IsInstance(PlainType)) |
2564 | + self.assertThat(attr, IsInstance(dbus.Array)) |
2565 | + self.assertThat(attr, Equals([0, False, "Hello World"])) |
2566 | + |
2567 | + def test_invalid_no_data(self): |
2568 | + data = dbus.Array( |
2569 | + [ |
2570 | + dbus.Int32(0), |
2571 | + ] |
2572 | + ) |
2573 | + fn = lambda: create_value_instance(data, None, None) |
2574 | + |
2575 | + self.assertThat(fn, raises( |
2576 | + ValueError("Cannot create attribute, no data supplied") |
2577 | + )) |
2578 | + |
2579 | + |
2580 | +class DBusIntrospectionObjectTests(TestCase): |
2581 | + |
2582 | + @patch('autopilot.introspection.dbus.logger.warning') |
2583 | + def test_dbus_introspection_object_logs_bad_data(self, error_logger): |
2584 | + """The DBusIntrospectionObject class must log an error when it gets |
2585 | + bad data from the autopilot backend. |
2586 | + |
2587 | + """ |
2588 | + my_obj = DBusIntrospectionObject( |
2589 | + dict(foo=[0]), |
2590 | + '/some/dummy/path' |
2591 | + ) |
2592 | + error_logger.assert_called_once_with( |
2593 | + "While constructing attribute '%s.%s': %s", |
2594 | + "DBusIntrospectionObject", |
2595 | + "foo", |
2596 | + "Cannot create attribute, no data supplied" |
2597 | + ) |
2598 | |
2599 | === modified file 'autopilot/tests/unit/test_version_utility_fns.py' |
2600 | --- autopilot/tests/unit/test_version_utility_fns.py 2013-07-22 03:14:42 +0000 |
2601 | +++ autopilot/tests/unit/test_version_utility_fns.py 2013-09-16 17:17:50 +0000 |
2602 | @@ -43,7 +43,7 @@ |
2603 | """The _get_package_installed_version function must return None when |
2604 | subprocess raises an error while calling dpkg-query. |
2605 | """ |
2606 | - def raise_error(*args): |
2607 | + def raise_error(*args, **kwargs): |
2608 | raise subprocess.CalledProcessError(1, "dpkg-query") |
2609 | with patch('subprocess.check_output', new=raise_error): |
2610 | self.assertThat(_get_package_installed_version(), Equals(None)) |
2611 | @@ -54,7 +54,7 @@ |
2612 | |
2613 | """ |
2614 | with patch('subprocess.check_output', |
2615 | - new=lambda *a: "1.3daily13.05.22\n"): |
2616 | + new=lambda *a, **kwargs: "1.3daily13.05.22\n"): |
2617 | self.assertThat( |
2618 | _get_package_installed_version(), Equals("1.3daily13.05.22")) |
2619 | |
2620 | |
2621 | === modified file 'autopilot/utilities.py' |
2622 | --- autopilot/utilities.py 2013-09-04 04:53:40 +0000 |
2623 | +++ autopilot/utilities.py 2013-09-16 17:17:50 +0000 |
2624 | @@ -34,13 +34,14 @@ |
2625 | |
2626 | def _pick_backend(backends, preferred_backend): |
2627 | """Pick a backend and return an instance of it.""" |
2628 | - possible_backends = backends.keys() |
2629 | + possible_backends = list(backends.keys()) |
2630 | get_debug_logger().debug( |
2631 | "Possible backends: %s", ','.join(possible_backends)) |
2632 | if preferred_backend: |
2633 | if preferred_backend in possible_backends: |
2634 | - possible_backends.sort( |
2635 | - lambda a, b: -1 if a == preferred_backend else 0) |
2636 | + # make preferred_backend the first list item |
2637 | + possible_backends.remove(preferred_backend) |
2638 | + possible_backends.insert(0, preferred_backend) |
2639 | else: |
2640 | raise RuntimeError("Unknown backend '%s'" % (preferred_backend)) |
2641 | failure_reasons = [] |
2642 | @@ -67,7 +68,7 @@ |
2643 | ... |
2644 | |
2645 | """ |
2646 | - def __init__(self, stdout=os.devnull, stderr=os.devnull, mode='w'): |
2647 | + def __init__(self, stdout=os.devnull, stderr=os.devnull, mode='wb'): |
2648 | self.outfiles = stdout, stderr |
2649 | self.combine = (stdout == stderr) |
2650 | self.mode = mode |
2651 | @@ -309,9 +310,7 @@ |
2652 | return class_object |
2653 | |
2654 | |
2655 | -class CleanupRegistered(object): |
2656 | - |
2657 | - __metaclass__ = _TestCleanupMeta |
2658 | +CleanupRegistered = _TestCleanupMeta('CleanupRegistered', (object,), {}) |
2659 | |
2660 | |
2661 | def action_on_test_start(test_instance): |
2662 | |
2663 | === modified file 'autopilot/vis/bus_enumerator.py' |
2664 | --- autopilot/vis/bus_enumerator.py 2013-07-14 08:17:17 +0000 |
2665 | +++ autopilot/vis/bus_enumerator.py 2013-09-16 17:17:50 +0000 |
2666 | @@ -79,7 +79,7 @@ |
2667 | |
2668 | def get_found_connections(self): |
2669 | """Get a list of found connection names. This may not be up to date.""" |
2670 | - return self._data.keys() |
2671 | + return list(self._data.keys()) |
2672 | |
2673 | def get_found_objects(self, connection_string): |
2674 | """Get a list of found objects for a particular connection name. |
2675 | @@ -87,9 +87,9 @@ |
2676 | This may be out of date. |
2677 | |
2678 | """ |
2679 | - if connection_string not in self._data.keys(): |
2680 | + if connection_string not in self._data: |
2681 | raise KeyError("%s not in results" % connection_string) |
2682 | - return self._data[connection_string].keys() |
2683 | + return list(self._data[connection_string].keys()) |
2684 | |
2685 | def get_found_interfaces(self, connection_string, object_path): |
2686 | """Get a list of found interfaces for a particular connection name and |
2687 | @@ -98,9 +98,9 @@ |
2688 | This may be out of date. |
2689 | |
2690 | """ |
2691 | - if connection_string not in self._data.keys(): |
2692 | + if connection_string not in self._data: |
2693 | raise KeyError("connection %s not in results" % connection_string) |
2694 | - if object_path not in self._data[connection_string].keys(): |
2695 | + if object_path not in self._data[connection_string]: |
2696 | raise KeyError( |
2697 | "object %s not in results for connection %s" % |
2698 | (object_path, connection_string)) |
2699 | |
2700 | === modified file 'autopilot/vis/main_window.py' |
2701 | --- autopilot/vis/main_window.py 2013-07-14 08:13:25 +0000 |
2702 | +++ autopilot/vis/main_window.py 2013-09-16 17:17:50 +0000 |
2703 | @@ -20,6 +20,7 @@ |
2704 | |
2705 | from __future__ import absolute_import |
2706 | |
2707 | +import sys |
2708 | import dbus |
2709 | from PyQt4 import QtGui, QtCore |
2710 | |
2711 | @@ -33,6 +34,8 @@ |
2712 | from autopilot.vis.objectproperties import TreeNodeDetailWidget |
2713 | from autopilot.vis.resources import get_qt_icon |
2714 | |
2715 | +_PY3 = sys.version >= '3' |
2716 | + |
2717 | |
2718 | class MainWindow(QtGui.QMainWindow): |
2719 | def __init__(self, dbus_bus): |
2720 | @@ -44,8 +47,12 @@ |
2721 | |
2722 | def readSettings(self): |
2723 | settings = QtCore.QSettings() |
2724 | - self.restoreGeometry(settings.value("geometry").toByteArray()) |
2725 | - self.restoreState(settings.value("windowState").toByteArray()) |
2726 | + if _PY3: |
2727 | + self.restoreGeometry(settings.value("geometry").data()) |
2728 | + self.restoreState(settings.value("windowState").data()) |
2729 | + else: |
2730 | + self.restoreGeometry(settings.value("geometry").toByteArray()) |
2731 | + self.restoreState(settings.value("windowState").toByteArray()) |
2732 | |
2733 | def closeEvent(self, event): |
2734 | settings = QtCore.QSettings() |
2735 | @@ -90,17 +97,16 @@ |
2736 | def update_selectable_interfaces(self): |
2737 | selected_text = self.connection_list.currentText() |
2738 | self.connection_list.clear() |
2739 | - self.connection_list.addItem("Please select a connection", |
2740 | - QtCore.QVariant(None)) |
2741 | - for name, proxy_obj in self.selectable_interfaces.iteritems(): |
2742 | + self.connection_list.addItem("Please select a connection", None) |
2743 | + for name, proxy_obj in self.selectable_interfaces.items(): |
2744 | if isinstance(proxy_obj, QtObjectProxyMixin): |
2745 | self.connection_list.addItem( |
2746 | get_qt_icon(), |
2747 | name, |
2748 | - QtCore.QVariant(proxy_obj) |
2749 | + proxy_obj |
2750 | ) |
2751 | else: |
2752 | - self.connection_list.addItem(name, QtCore.QVariant(proxy_obj)) |
2753 | + self.connection_list.addItem(name, proxy_obj) |
2754 | |
2755 | prev_selected = self.connection_list.findText(selected_text, |
2756 | QtCore.Qt.MatchExactly) |
2757 | @@ -109,7 +115,9 @@ |
2758 | self.connection_list.setCurrentIndex(prev_selected) |
2759 | |
2760 | def conn_list_activated(self, index): |
2761 | - dbus_details = self.connection_list.itemData(index).toPyObject() |
2762 | + dbus_details = self.connection_list.itemData(index) |
2763 | + if not _PY3: |
2764 | + dbus_details = dbus_details.toPyObject() |
2765 | if dbus_details: |
2766 | self.tree_model = VisTreeModel(dbus_details) |
2767 | self.tree_view.setModel(self.tree_model) |
2768 | @@ -211,16 +219,16 @@ |
2769 | |
2770 | def data(self, index, role): |
2771 | if not index.isValid(): |
2772 | - return QtCore.QVariant() |
2773 | + return None |
2774 | if role == QtCore.Qt.DisplayRole: |
2775 | - return QtCore.QVariant(index.internalPointer().name) |
2776 | + return index.internalPointer().name |
2777 | |
2778 | def headerData(self, column, orientation, role): |
2779 | if (orientation == QtCore.Qt.Horizontal and |
2780 | role == QtCore.Qt.DisplayRole): |
2781 | try: |
2782 | - return QtCore.QVariant("Tree Node") |
2783 | + return "Tree Node" |
2784 | except IndexError: |
2785 | pass |
2786 | |
2787 | - return QtCore.QVariant() |
2788 | + return None |
2789 | |
2790 | === modified file 'autopilot/vis/objectproperties.py' |
2791 | --- autopilot/vis/objectproperties.py 2013-07-14 08:10:50 +0000 |
2792 | +++ autopilot/vis/objectproperties.py 2013-09-16 17:17:50 +0000 |
2793 | @@ -20,8 +20,11 @@ |
2794 | |
2795 | """Code for introspection tree object properties.""" |
2796 | |
2797 | +import sys |
2798 | + |
2799 | from PyQt4 import QtGui, QtCore |
2800 | |
2801 | + |
2802 | from autopilot.vis.resources import get_qt_icon, dbus_string_rep |
2803 | from autopilot.introspection.qt import QtObjectProxyMixin |
2804 | |
2805 | @@ -94,7 +97,10 @@ |
2806 | def __init__(self, *args, **kwargs): |
2807 | super(PropertyView, self).__init__(*args, **kwargs) |
2808 | |
2809 | - header_titles = QtCore.QStringList(["Name", "Value"]) |
2810 | + if sys.version >= '3': |
2811 | + header_titles = ["Name", "Value"] |
2812 | + else: |
2813 | + header_titles = QtCore.QStringList(["Name", "Value"]) |
2814 | self.details_layout = QtGui.QVBoxLayout(self) |
2815 | |
2816 | self.table_view = QtGui.QTableWidget() |
2817 | @@ -122,7 +128,7 @@ |
2818 | details_string = dbus_string_rep(object_details[key]) |
2819 | item_name = QtGui.QTableWidgetItem(key) |
2820 | item_details = QtGui.QTableWidgetItem( |
2821 | - details_string.decode('utf-8')) |
2822 | + details_string) |
2823 | self.table_view.setItem(i, 0, item_name) |
2824 | self.table_view.setItem(i, 1, item_details) |
2825 | self.table_view.setSortingEnabled(True) |
2826 | |
2827 | === modified file 'autopilot/vis/resources.py' |
2828 | --- autopilot/vis/resources.py 2013-07-14 08:05:09 +0000 |
2829 | +++ autopilot/vis/resources.py 2013-09-16 17:17:50 +0000 |
2830 | @@ -32,7 +32,7 @@ |
2831 | if isinstance(dbus_type, dbus.Boolean): |
2832 | return repr(bool(dbus_type)) |
2833 | if isinstance(dbus_type, dbus.String): |
2834 | - return dbus_type.encode('utf-8', errors='ignore') |
2835 | + return dbus_type.encode('utf-8', errors='ignore').decode('utf-8') |
2836 | if (isinstance(dbus_type, dbus.Int16) or |
2837 | isinstance(dbus_type, dbus.UInt16) or |
2838 | isinstance(dbus_type, dbus.Int32) or |
2839 | |
2840 | === modified file 'bin/autopilot' |
2841 | --- bin/autopilot 2013-08-01 15:33:53 +0000 |
2842 | +++ bin/autopilot 2013-09-16 17:17:50 +0000 |
2843 | @@ -66,21 +66,21 @@ |
2844 | |
2845 | suite_names = ["%s.%s" % (t.__module__, t.__class__.__name__) |
2846 | for t in test_list_fn()] |
2847 | - unique_suite_names = OrderedDict.fromkeys(suite_names).keys() |
2848 | + unique_suite_names = list(OrderedDict.fromkeys(suite_names).keys()) |
2849 | num_tests = len(unique_suite_names) |
2850 | total_title = "suites" |
2851 | - print " %s" % ("\n ".join(unique_suite_names)) |
2852 | + print(" %s" % ("\n ".join(unique_suite_names))) |
2853 | else: |
2854 | for test in test_list_fn(): |
2855 | has_scenarios = (hasattr(test, "scenarios") |
2856 | and type(test.scenarios) is list) |
2857 | if has_scenarios: |
2858 | num_tests += len(test.scenarios) |
2859 | - print " *%d %s" % (len(test.scenarios), test.id()) |
2860 | + print(" *%d %s" % (len(test.scenarios), test.id())) |
2861 | else: |
2862 | num_tests += 1 |
2863 | - print " ", test.id() |
2864 | - print "\n\n %d total %s." % (num_tests, total_title) |
2865 | + print(" " + test.id()) |
2866 | + print("\n\n %d total %s." % (num_tests, total_title)) |
2867 | |
2868 | |
2869 | def run_tests(args): |
2870 | @@ -89,7 +89,7 @@ |
2871 | |
2872 | if args.random_order: |
2873 | shuffle(test_suite._tests) |
2874 | - print "Running tests in random order" |
2875 | + print("Running tests in random order") |
2876 | |
2877 | import autopilot.globals |
2878 | |
2879 | @@ -104,8 +104,8 @@ |
2880 | stdout=subprocess.PIPE |
2881 | ) |
2882 | if call_ret_code != 0: |
2883 | - print "ERROR: The application 'recordmydesktop' needs to be " \ |
2884 | - "installed to record failing jobs." |
2885 | + print("ERROR: The application 'recordmydesktop' needs to be " |
2886 | + "installed to record failing jobs.") |
2887 | exit(1) |
2888 | autopilot.globals.configure_video_recording(True, |
2889 | args.record_directory, |
2890 | @@ -181,7 +181,7 @@ |
2891 | datetime.now().strftime("%d.%m.%y-%H%M%S") |
2892 | ) |
2893 | log_file = os.path.join(log_file, default_log) |
2894 | - print "Using default log filename: %s " % default_log |
2895 | + print("Using default log filename: %s " % default_log) |
2896 | if args.format == 'xml': |
2897 | _output_stream = open(log_file, 'w') |
2898 | else: |
2899 | @@ -216,7 +216,7 @@ |
2900 | def load_test_suite_from_name(test_names): |
2901 | """Returns a test suite object given a dotted test names.""" |
2902 | loader = TestLoader() |
2903 | - if isinstance(test_names, basestring): |
2904 | + if isinstance(test_names, str): |
2905 | test_names = list(test_names) |
2906 | elif not isinstance(test_names, list): |
2907 | raise TypeError("test_names must be either a string or list, not %r" |
2908 | @@ -241,7 +241,7 @@ |
2909 | all_tests = TestSuite(tests) |
2910 | |
2911 | test_dirs = ", ".join(sorted(test_package_locations)) |
2912 | - print "Loading tests from: %s\n" % test_dirs |
2913 | + print("Loading tests from: %s\n" % test_dirs) |
2914 | sys.stdout.flush() |
2915 | |
2916 | requested_tests = {} |
2917 | @@ -256,7 +256,7 @@ |
2918 | try: |
2919 | test.debug() |
2920 | except Exception as e: |
2921 | - print e |
2922 | + print(e) |
2923 | else: |
2924 | test_id = test.id() |
2925 | if any([test_id.startswith(name) for name in test_names]): |
2926 | @@ -287,9 +287,10 @@ |
2927 | app_name = args.application[0] |
2928 | if not isabs(app_name) or not exists(app_name): |
2929 | try: |
2930 | - app_name = subprocess.check_output(["which", app_name]).strip() |
2931 | + app_name = subprocess.check_output(["which", app_name], |
2932 | + universal_newlines=True).strip() |
2933 | except subprocess.CalledProcessError: |
2934 | - print "Error: cannot find application '%s'" % (app_name) |
2935 | + print("Error: cannot find application '%s'" % (app_name)) |
2936 | exit(1) |
2937 | |
2938 | # We now have a full path to the application. |
2939 | @@ -298,21 +299,21 @@ |
2940 | try: |
2941 | launcher = get_application_launcher(app_name) |
2942 | except RuntimeError as e: |
2943 | - print "Error detecting launcher: %s" % e.message |
2944 | - print "(Perhaps use the '-i' argument to specify an interface.)" |
2945 | + print("Error detecting launcher: %s" % str(e)) |
2946 | + print("(Perhaps use the '-i' argument to specify an interface.)") |
2947 | exit(1) |
2948 | else: |
2949 | launcher = get_application_launcher_from_string_hint(args.interface) |
2950 | if launcher is None: |
2951 | - print "Error: Could not determine introspection type to use for " \ |
2952 | - "application '%s'." % app_name |
2953 | - print "(Perhaps use the '-i' argument to specify an interface.)" |
2954 | + print("Error: Could not determine introspection type to use for " \ |
2955 | + "application '%s'." % app_name) |
2956 | + print("(Perhaps use the '-i' argument to specify an interface.)") |
2957 | exit(1) |
2958 | |
2959 | try: |
2960 | launch_application(launcher, *args.application, capture_output=False) |
2961 | except RuntimeError as e: |
2962 | - print "Error: " + e.message |
2963 | + print("Error: " + str(e)) |
2964 | exit(1) |
2965 | |
2966 | |
2967 | |
2968 | === modified file 'debian/changelog' |
2969 | --- debian/changelog 2013-09-06 10:03:58 +0000 |
2970 | +++ debian/changelog 2013-09-16 17:17:50 +0000 |
2971 | @@ -1,3 +1,10 @@ |
2972 | +autopilot (1.4-0ubuntu1) saucy; urgency=low |
2973 | + |
2974 | + [ Thomi Richards ] |
2975 | + * Update to new DBus wire protocol, bump autopilot version number. |
2976 | + |
2977 | + -- Thomi Richards <thomi.richards@canonical.com> Fri, 16 Aug 2013 08:47:12 +1200 |
2978 | + |
2979 | autopilot (1.3.1+13.10.20130906.1-0ubuntu1) saucy; urgency=low |
2980 | |
2981 | [ Christopher Lee ] |
2982 | |
2983 | === modified file 'debian/control' |
2984 | --- debian/control 2013-09-09 21:34:21 +0000 |
2985 | +++ debian/control 2013-09-16 17:17:50 +0000 |
2986 | @@ -4,24 +4,37 @@ |
2987 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
2988 | XSBC-Original-Maintainer: Thomi Richards <thomi.richards@canonical.com> |
2989 | Build-Depends: debhelper (>= 9.0.0), |
2990 | - gir1.2-ibus-1.0, |
2991 | + dh-python, |
2992 | + dvipng, |
2993 | gir1.2-gconf-2.0, |
2994 | gir1.2-gtk-3.0, |
2995 | - python (>= 2.6), |
2996 | + gir1.2-ibus-1.0, |
2997 | + graphviz, |
2998 | + liblttng-ust-dev, |
2999 | + python-all-dev (>= 2.6), |
3000 | + python3-all-dev (>= 3.3), |
3001 | python-dbus, |
3002 | - python-dev, |
3003 | python-debian, |
3004 | + python-gi, |
3005 | python-mock, |
3006 | python-setuptools, |
3007 | python-sphinx, |
3008 | + python-testscenarios, |
3009 | python-testtools, |
3010 | - python-testscenarios, |
3011 | python-xlib, |
3012 | - python-gi, |
3013 | - liblttng-ust-dev, |
3014 | + python3-dbus, |
3015 | + python3-gi, |
3016 | + python3-mock, |
3017 | + python3-setuptools, |
3018 | + python3-testscenarios, |
3019 | + python3-testtools, |
3020 | + python3-xlib, |
3021 | + texlive-latex-extra, |
3022 | Standards-Version: 3.9.4 |
3023 | Homepage: https://launchpad.net/autopilot |
3024 | Vcs-bzr: https://code.launchpad.net/+branch/ubuntu/autopilot |
3025 | +X-Python-Version: >= 2.7 |
3026 | +X-Python3-Version: >= 3.3 |
3027 | |
3028 | Package: python-autopilot |
3029 | Architecture: all |
3030 | @@ -33,8 +46,8 @@ |
3031 | python-testtools, |
3032 | udev, |
3033 | Recommends: autopilot-desktop | autopilot-touch, |
3034 | - libautopilot-gtk (>= 1.3), |
3035 | - libautopilot-qt (>= 1.3), |
3036 | + libautopilot-gtk (>= 1.4), |
3037 | + libautopilot-qt (>= 1.4), |
3038 | python-autopilot-trace, |
3039 | recordmydesktop, |
3040 | Description: Utility to write and run integration tests easily |
3041 | @@ -43,6 +56,31 @@ |
3042 | keyboard. It also provides a lot of utilities linked to the X server |
3043 | and detecting applications. |
3044 | |
3045 | +Package: python3-autopilot |
3046 | +Architecture: all |
3047 | +Depends: ${misc:Depends}, |
3048 | + ${python3:Depends}, |
3049 | + python3-dbus, |
3050 | + python3-junitxml, |
3051 | + python3-testscenarios, |
3052 | + python3-testtools, |
3053 | + udev, |
3054 | +Recommends: python3-xlib, |
3055 | + python3-evdev, |
3056 | + gir1.2-gconf-2.0, |
3057 | + gir1.2-glib-2.0, |
3058 | + gir1.2-gtk-3.0, |
3059 | + gir1.2-ibus-1.0, |
3060 | + libautopilot-gtk (>= 1.4), |
3061 | + libautopilot-qt (>= 1.4), |
3062 | + python3-autopilot-trace, |
3063 | + recordmydesktop, |
3064 | +Description: Utility to write and run integration tests easily (Python 3) |
3065 | + The autopilot engine enables to ease the writing of python tests |
3066 | + for your application manipulating your inputs like the mouse and |
3067 | + keyboard. It also provides a lot of utilities linked to the X server |
3068 | + and detecting applications. |
3069 | + |
3070 | Package: python-autopilot-trace |
3071 | Architecture: any |
3072 | Depends: ${misc:Depends}, |
3073 | @@ -53,12 +91,21 @@ |
3074 | autopilot tests. This is useful when using autopilot to exercise |
3075 | an instrumented application. |
3076 | |
3077 | +Package: python3-autopilot-trace |
3078 | +Architecture: any |
3079 | +Depends: ${misc:Depends}, |
3080 | + ${shlibs:Depends}, |
3081 | + python3-autopilot, |
3082 | +Description: Support for tracing in autopilot (Python 3) |
3083 | + This package contains the binary lttng trace point for tracing |
3084 | + autopilot tests. This is useful when using autopilot to exercise |
3085 | + an instrumented application. |
3086 | |
3087 | Package: autopilot-touch |
3088 | Architecture: any |
3089 | Section: metapackages |
3090 | Depends: ${misc:Depends}, |
3091 | - libautopilot-qt (>= 1.3) [!powerpc], |
3092 | + libautopilot-qt (>= 1.4) [!powerpc], |
3093 | python-autopilot, |
3094 | python-evdev, |
3095 | python-ubuntu-platform-api [armhf], |
3096 | @@ -91,7 +138,20 @@ |
3097 | python-qt4, |
3098 | python-qt4-dbus, |
3099 | Description: The visualisation application for Autopilot. |
3100 | - The Autopilot vis tool allows you to inspect an application introspectioin |
3101 | + The Autopilot vis tool allows you to inspect an application introspection |
3102 | + tree. It is a useful tool for test authors, but not required to run autopilot |
3103 | + tests. |
3104 | + |
3105 | +Package: python3-autopilot-vis |
3106 | +Architecture: all |
3107 | +Depends: ${misc:Depends}, |
3108 | + ${python3:Depends}, |
3109 | + python3-autopilot, |
3110 | + python3-dbus, |
3111 | + python3-pyqt4, |
3112 | + python3-dbus.mainloop.qt, |
3113 | +Description: The visualisation application for Autopilot (Python 3) |
3114 | + The Autopilot vis tool allows you to inspect an application introspection |
3115 | tree. It is a useful tool for test authors, but not required to run autopilot |
3116 | tests. |
3117 | |
3118 | @@ -99,8 +159,8 @@ |
3119 | Architecture: all |
3120 | Depends: ${misc:Depends}, |
3121 | ${python:Depends}, |
3122 | - libautopilot-gtk, |
3123 | - libautopilot-qt, |
3124 | + libautopilot-gtk (>= 1.4), |
3125 | + libautopilot-qt (>= 1.4), |
3126 | python-autopilot, |
3127 | python-evdev, |
3128 | python-mock, |
3129 | @@ -112,3 +172,21 @@ |
3130 | You can use this package to verify that autopilot is functioning |
3131 | correctly, or to copy the techniques used in the autopilot tests |
3132 | themselves. |
3133 | + |
3134 | +Package: python3-autopilot-tests |
3135 | +Architecture: all |
3136 | +Depends: ${misc:Depends}, |
3137 | + ${python3:Depends}, |
3138 | + libautopilot-gtk (>= 1.4), |
3139 | + libautopilot-qt (>= 1.4), |
3140 | + python3-autopilot, |
3141 | + python3-evdev, |
3142 | + python3-mock, |
3143 | + python-windowmocker, |
3144 | + qmlscene, |
3145 | + recordmydesktop, |
3146 | +Description: Tests for the autopilot functional test tool. (Python 3) |
3147 | + This package contains tests for the python3-autopilot package. |
3148 | + You can use this package to verify that autopilot is functioning |
3149 | + correctly, or to copy the techniques used in the autopilot tests |
3150 | + themselves. |
3151 | |
3152 | === added file 'debian/python-autopilot-tests.install' |
3153 | --- debian/python-autopilot-tests.install 1970-01-01 00:00:00 +0000 |
3154 | +++ debian/python-autopilot-tests.install 2013-09-16 17:17:50 +0000 |
3155 | @@ -0,0 +1,3 @@ |
3156 | +usr/lib/python2*/*/autopilot/tests/*.py |
3157 | +usr/lib/python2*/*/autopilot/tests/unit/*.py |
3158 | +usr/lib/python2*/*/autopilot/tests/functional/*.py |
3159 | |
3160 | === removed file 'debian/python-autopilot-tests.pyinstall' |
3161 | --- debian/python-autopilot-tests.pyinstall 2013-05-08 23:43:42 +0000 |
3162 | +++ debian/python-autopilot-tests.pyinstall 1970-01-01 00:00:00 +0000 |
3163 | @@ -1,3 +0,0 @@ |
3164 | -autopilot/tests/*.py |
3165 | -autopilot/tests/unit/*.py |
3166 | -autopilot/tests/functional/*.py |
3167 | |
3168 | === added file 'debian/python-autopilot-vis.install' |
3169 | --- debian/python-autopilot-vis.install 1970-01-01 00:00:00 +0000 |
3170 | +++ debian/python-autopilot-vis.install 2013-09-16 17:17:50 +0000 |
3171 | @@ -0,0 +1,1 @@ |
3172 | +usr/lib/python2*/*/autopilot/vis/*.py |
3173 | |
3174 | === removed file 'debian/python-autopilot-vis.pyinstall' |
3175 | --- debian/python-autopilot-vis.pyinstall 2013-04-30 16:49:02 +0000 |
3176 | +++ debian/python-autopilot-vis.pyinstall 1970-01-01 00:00:00 +0000 |
3177 | @@ -1,1 +0,0 @@ |
3178 | -autopilot/vis/*.py |
3179 | |
3180 | === modified file 'debian/python-autopilot.install' |
3181 | --- debian/python-autopilot.install 2013-05-04 01:01:06 +0000 |
3182 | +++ debian/python-autopilot.install 2013-09-16 17:17:50 +0000 |
3183 | @@ -1,2 +1,8 @@ |
3184 | +usr/lib/python2*/*/autopilot/*.py |
3185 | +usr/lib/python2*/*/autopilot/display/*.py |
3186 | +usr/lib/python2*/*/autopilot/input/*.py |
3187 | +usr/lib/python2*/*/autopilot/introspection/*.py |
3188 | +usr/lib/python2*/*/autopilot/matchers/*.py |
3189 | +usr/lib/python2*/*/autopilot/process/*.py |
3190 | usr/bin/autopilot /usr/bin/ |
3191 | debian/61-autopilot-uinput.rules /lib/udev/rules.d |
3192 | |
3193 | === removed file 'debian/python-autopilot.pyinstall' |
3194 | --- debian/python-autopilot.pyinstall 2013-04-30 16:49:02 +0000 |
3195 | +++ debian/python-autopilot.pyinstall 1970-01-01 00:00:00 +0000 |
3196 | @@ -1,6 +0,0 @@ |
3197 | -autopilot/*.py |
3198 | -autopilot/display/*.py |
3199 | -autopilot/input/*.py |
3200 | -autopilot/introspection/*.py |
3201 | -autopilot/matchers/*.py |
3202 | -autopilot/process/*.py |
3203 | |
3204 | === added file 'debian/python3-autopilot-tests.install' |
3205 | --- debian/python3-autopilot-tests.install 1970-01-01 00:00:00 +0000 |
3206 | +++ debian/python3-autopilot-tests.install 2013-09-16 17:17:50 +0000 |
3207 | @@ -0,0 +1,3 @@ |
3208 | +usr/lib/python3*/*/autopilot/tests/*.py |
3209 | +usr/lib/python3*/*/autopilot/tests/unit/*.py |
3210 | +usr/lib/python3*/*/autopilot/tests/functional/*.py |
3211 | |
3212 | === added file 'debian/python3-autopilot-trace.install' |
3213 | --- debian/python3-autopilot-trace.install 1970-01-01 00:00:00 +0000 |
3214 | +++ debian/python3-autopilot-trace.install 2013-09-16 17:17:50 +0000 |
3215 | @@ -0,0 +1,1 @@ |
3216 | +usr/lib/python3*/*/autopilot/tracepoint.*.so |
3217 | |
3218 | === added file 'debian/python3-autopilot-vis.install' |
3219 | --- debian/python3-autopilot-vis.install 1970-01-01 00:00:00 +0000 |
3220 | +++ debian/python3-autopilot-vis.install 2013-09-16 17:17:50 +0000 |
3221 | @@ -0,0 +1,1 @@ |
3222 | +usr/lib/python3*/*/autopilot/vis/*.py |
3223 | |
3224 | === added file 'debian/python3-autopilot.docs' |
3225 | --- debian/python3-autopilot.docs 1970-01-01 00:00:00 +0000 |
3226 | +++ debian/python3-autopilot.docs 2013-09-16 17:17:50 +0000 |
3227 | @@ -0,0 +1,2 @@ |
3228 | +docs/_build/html/ |
3229 | + |
3230 | |
3231 | === added file 'debian/python3-autopilot.install' |
3232 | --- debian/python3-autopilot.install 1970-01-01 00:00:00 +0000 |
3233 | +++ debian/python3-autopilot.install 2013-09-16 17:17:50 +0000 |
3234 | @@ -0,0 +1,9 @@ |
3235 | +usr/bin/autopilot /usr/bin/ |
3236 | +debian/61-autopilot-uinput.rules /lib/udev/rules.d |
3237 | +usr/lib/python3*/*/autopilot/*.py |
3238 | +usr/lib/python3*/*/autopilot/display/*.py |
3239 | +usr/lib/python3*/*/autopilot/input/*.py |
3240 | +usr/lib/python3*/*/autopilot/introspection/*.py |
3241 | +usr/lib/python3*/*/autopilot/matchers/*.py |
3242 | +usr/lib/python3*/*/autopilot/process/*.py |
3243 | + |
3244 | |
3245 | === added file 'debian/python3-autopilot.postinst' |
3246 | --- debian/python3-autopilot.postinst 1970-01-01 00:00:00 +0000 |
3247 | +++ debian/python3-autopilot.postinst 2013-09-16 17:17:50 +0000 |
3248 | @@ -0,0 +1,24 @@ |
3249 | +#!/bin/sh |
3250 | +# postinst for autopilot |
3251 | + |
3252 | +set -e |
3253 | + |
3254 | +AUTOPILOT_GROUP=autopilot |
3255 | + |
3256 | +if [ "$1" = configure ]; then |
3257 | + # Add the autopilot group |
3258 | + addgroup --quiet \ |
3259 | + --system \ |
3260 | + --no-create-home \ |
3261 | + "$AUTOPILOT_GROUP" || true |
3262 | + |
3263 | + # Add each sudo user to the autopilot group |
3264 | + for u in $(getent group sudo | sed -e "s/^.*://" -e "s/,/ /g"); do |
3265 | + adduser --quiet "$u" "$AUTOPILOT_GROUP" > /dev/null || true |
3266 | + done |
3267 | + |
3268 | + # udev rules were installed, trigger creation of the /dev node |
3269 | + udevadm trigger --action=change |
3270 | +fi |
3271 | + |
3272 | +#DEBHELPER# |
3273 | |
3274 | === modified file 'debian/rules' |
3275 | --- debian/rules 2013-05-30 12:00:24 +0000 |
3276 | +++ debian/rules 2013-09-16 17:17:50 +0000 |
3277 | @@ -5,14 +5,42 @@ |
3278 | # export DH_VERBOSE=1 |
3279 | |
3280 | %: |
3281 | - dh $@ --with python2 |
3282 | + dh $@ --with python2,python3 |
3283 | + |
3284 | +override_dh_auto_clean: |
3285 | + set -ex; for python in $(shell py3versions -r); do \ |
3286 | + $$python setup.py clean || true; \ |
3287 | + done |
3288 | + dh_auto_clean |
3289 | + rm -rf build |
3290 | |
3291 | override_dh_auto_build: |
3292 | sphinx-build -b html -W docs/ docs/_build/html/ |
3293 | + set -ex; for python in $(shell py3versions -r); do \ |
3294 | + $$python setup.py build; \ |
3295 | + done |
3296 | dh_auto_build |
3297 | |
3298 | override_dh_auto_test: |
3299 | +ifeq (, $(findstring nocheck, $(DEB_BUILD_OPTIONS))) |
3300 | python -m testtools.run discover autopilot.tests.unit |
3301 | + python3 -m testtools.run discover autopilot.tests.unit |
3302 | +endif |
3303 | + |
3304 | +override_dh_auto_install: |
3305 | + set -ex; for python in $(shell py3versions -r); do \ |
3306 | + $$python setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb; \ |
3307 | + done |
3308 | + dh_auto_install |
3309 | |
3310 | override_dh_install: |
3311 | - dh_install --list-missing |
3312 | + # note, egg-info isn't important unless we have entry_points or other |
3313 | + # things that runtime wants to inspect |
3314 | + dh_install -X.pyc -X.egg-info --fail-missing |
3315 | + # provide python3-ized runner and avoid file conflicts |
3316 | + set -e; if [ -d debian/python3-autopilot ]; then \ |
3317 | + cd debian/python3-autopilot; \ |
3318 | + mv usr/bin/autopilot usr/bin/autopilot-py3; \ |
3319 | + sed -i '1 s/python$$/python3/' usr/bin/autopilot-py3; \ |
3320 | + mv lib/udev/rules.d/61-autopilot-uinput.rules lib/udev/rules.d/61-autopilot-py3-uinput.rules; \ |
3321 | + fi |
3322 | |
3323 | === modified file 'docs/_templates/indexcontent.html' |
3324 | --- docs/_templates/indexcontent.html 2013-06-14 03:31:10 +0000 |
3325 | +++ docs/_templates/indexcontent.html 2013-09-16 17:17:50 +0000 |
3326 | @@ -38,6 +38,12 @@ |
3327 | <span class="linkdescr">How to port your tests from earlier versions of Autopilot.</span> |
3328 | </p> |
3329 | </td> |
3330 | + <td> |
3331 | + <p class="biglink"> |
3332 | + <a class="biglink" href="{{ pathto("appendix/appendix") }}">Appendices</a><br/> |
3333 | + <span class="linkdescr">Technical documentation that doesn't fit anywhere else.</span> |
3334 | + </p> |
3335 | + </td> |
3336 | </tr> |
3337 | </table> |
3338 | |
3339 | |
3340 | === modified file 'docs/api/introspection.rst' |
3341 | --- docs/api/introspection.rst 2013-06-25 23:10:27 +0000 |
3342 | +++ docs/api/introspection.rst 2013-09-16 17:17:50 +0000 |
3343 | @@ -4,3 +4,6 @@ |
3344 | .. automodule:: autopilot.introspection |
3345 | :members: CustomEmulatorBase, DBusIntrospectionObject, get_proxy_object_for_existing_process |
3346 | |
3347 | + |
3348 | +.. automodule:: autopilot.introspection.types |
3349 | + :members: PlainType, Rectangle, Point, Size, DateTime, Time |
3350 | |
3351 | === added directory 'docs/appendix' |
3352 | === added file 'docs/appendix/appendix.rst' |
3353 | --- docs/appendix/appendix.rst 1970-01-01 00:00:00 +0000 |
3354 | +++ docs/appendix/appendix.rst 2013-09-16 17:17:50 +0000 |
3355 | @@ -0,0 +1,9 @@ |
3356 | +Appendices |
3357 | +########### |
3358 | + |
3359 | +These technical documents describe features of the autopilot ecosystem that test authors probably don't need to know about, but may be useful to developers of autopilot itself. |
3360 | + |
3361 | +.. toctree:: |
3362 | + :maxdepth: 2 |
3363 | + |
3364 | + protocol |
3365 | |
3366 | === added file 'docs/appendix/protocol.rst' |
3367 | --- docs/appendix/protocol.rst 1970-01-01 00:00:00 +0000 |
3368 | +++ docs/appendix/protocol.rst 2013-09-16 17:17:50 +0000 |
3369 | @@ -0,0 +1,366 @@ |
3370 | +XPathSelect Query Protocol |
3371 | +########################## |
3372 | + |
3373 | + |
3374 | +This document defines the protocol used between autopilot and the application under test. In almost all cases, the application under test needs no knowledge of this protocol, since it is handled by one of the UI toolkit specific autopilot drivers (we support both Gtk and Qt). If you are a test author, you should be aware that this document likely contains no useful information for you! |
3375 | + |
3376 | +.. contents:: |
3377 | + |
3378 | +**Who should read this document:** |
3379 | + |
3380 | + * People wanting to hack on autopilot itself. |
3381 | + * People wanting to hack on xpathselect. |
3382 | + * People wanting to use autopilot to test an application not supported by the current autopilot UI toolkit drivers. |
3383 | + * People wanting to write a new UI toolkit driver. |
3384 | + |
3385 | + |
3386 | +DBus Interface |
3387 | +============== |
3388 | + |
3389 | +Every application under test must register itself on a DBus bus. Traditionally this is the DBus session bus, but autopilot supports any DBus bus that the user running autopilot has access to. Applications may choose to us ea well-known connection name, but it is not required. |
3390 | + |
3391 | +The only requirement for the DBus connection is that the ``com.canonical.Autopilot.Introspection`` interface is presented on exactly one exported object. The interface has two methods: |
3392 | + |
3393 | + * ``GetVersion()``. The ``GetVersion`` method takes no parameters, and returns a string describing the DBus wire protocol version being used by the application under test. Autopilot will refuse to connect to DBus wire protocol versions that are different than the expected version. The current version string is described later in this document. The version string should be in the format "X.Y", where ``X``, ``Y`` are the major and minor version numbers, respectively. |
3394 | + |
3395 | + * ``GetState(...)``. The ``GetState`` method takes a single string parameter, which is the "XPath Query". The format of that string parameter, and the return value, are the subject of the rest of this document. |
3396 | + |
3397 | + |
3398 | +Object Trees |
3399 | +============ |
3400 | + |
3401 | +Autopilot assumes that the application under test is constructed of a tree of objects. This is true for both Gtk and Qt applications, where the tree usually starts with an "application" object, and roughly follows widget stacking order. |
3402 | + |
3403 | +The purpose of the XPathSelect protocol is to allow autopilot to select the parts of the application that it is interested in. |
3404 | + |
3405 | +Autopilot passes a query string to the ``GetState`` method, and the application replies with the objects selected (if any), and their internal state details. |
3406 | + |
3407 | +The object tree is a tree of objects. Objects have several properties that are worth mentioning: |
3408 | + |
3409 | + * Each object *must* have a name that can be used as a python identifier. This is usually the class name or widget type (for example, ``QPushButton``, in a Qt GUI application). |
3410 | + * Each object *must* have an attribute named "id", whose value is an integer that is guaranteed to be unique for that object (note however that the same object may appear multiple times in the introspection tree in different places. In this case, the object id must remain consistent between each appearance). |
3411 | + * Objects *may* have more attributes. Each attribute name must be a string that can be represented as a python variable name. Attribute values can be any type that is representable over dbus. |
3412 | + * Objects *may* have zero or more child objects. |
3413 | + |
3414 | +This tree of objects is known as an "introspection tree" throughout the autopilot documentation. |
3415 | + |
3416 | +Selecting Objects |
3417 | +================= |
3418 | + |
3419 | +Objects in a tree are selected in a very similar fashion to files in a filesystem. The ``/`` character is used to separate between levels of the tree. |
3420 | + |
3421 | +Selecting the Root Object |
3422 | ++++++++++++++++++++++++++ |
3423 | + |
3424 | +A shortcut exists for selecting the root object in the introspection tree. A query of ``/`` will always return the root object. This is used when autopilot does not yet know the name of, the root object. |
3425 | + |
3426 | +Absolute Queries |
3427 | +++++++++++++++++ |
3428 | + |
3429 | +Absolute queries specify the entire path to the object, or objects autopilot is interested in. They must start with ``/`` and specify object names, separated by ``/`` characters. For example: |
3430 | + |
3431 | +.. list-table:: **XPathSelect Absolute Queries** |
3432 | + :header-rows: 1 |
3433 | + |
3434 | + * - Query: |
3435 | + - Selects: |
3436 | + * - ``/`` |
3437 | + - The root object (see above). |
3438 | + * - ``/foo`` |
3439 | + - The root object, which must be named 'foo'. |
3440 | + * - ``/foo/bar`` |
3441 | + - Object 'bar', which is a direct child of the root object 'foo'. |
3442 | + |
3443 | + |
3444 | +Using absolute queries, it is possible to select nodes in a tree of objects. However, there is no requirement for an absolute query path to match to exactly one object in the tree. For example, given a tree that looks like this: |
3445 | + |
3446 | +.. graphviz:: |
3447 | + |
3448 | + digraph foo { |
3449 | + node [shape=box]; |
3450 | + a [label="foo"]; |
3451 | + b [label="bar"]; |
3452 | + c [label="bar"]; |
3453 | + d [label="bar"]; |
3454 | + |
3455 | + a -> b; |
3456 | + a -> c; |
3457 | + a -> d; |
3458 | + } |
3459 | + |
3460 | +a query of ``/foo/bar`` will select two objects. This is allowed, but not always what we want. There are several ways to avoid this, they will be covered later in this document. |
3461 | + |
3462 | +Relative Queries |
3463 | +++++++++++++++++ |
3464 | + |
3465 | +Absolute queries are very fast for the application under test to process, and are used whenever autopilot knows where the object it wants to look at exists within the introspection tree. However, sometimes we need to be able to retrieve all the objects of a certain type within the tree. XPathSelect understands relative queries, which will select objects of a specified type anywhere in the tree. For example: |
3466 | + |
3467 | +.. list-table:: **XPathSelect Relative Queries** |
3468 | + :header-rows: 1 |
3469 | + |
3470 | + * - Query: |
3471 | + - Selects: |
3472 | + * - ``//foo`` |
3473 | + - All objects named 'foo', anywhere in the tree. |
3474 | + |
3475 | +Relative queries are much slower for the application under test to process, since the entire introspection tree must be searched for the objects that match the search criteria. Additionally, relative queries can generate a large amount of data that has to be sent across DBus, which can slow things down further. |
3476 | + |
3477 | +Mixed Queries |
3478 | ++++++++++++++ |
3479 | + |
3480 | +Absolute and relative queries can be mixed. All the relative queries in the above table will search the entire object tree. However, sometimes you only want to search part of the object tree, in which case you can use a mixed query: |
3481 | + |
3482 | +.. list-table:: **XPathSelect Mixed Queries** |
3483 | + :header-rows: 1 |
3484 | + |
3485 | + * - Query: |
3486 | + - Selects: |
3487 | + * - ``/foo/bar//baz`` |
3488 | + - Select all objects named 'baz' which are in the tree beneath '/foo/bar' |
3489 | + * - ``/foo/far//bar/baz`` |
3490 | + - Select all 'baz' objects which are immeadiate children of a 'bar' object, which itself is in the subtree beneath '/foo/far'. |
3491 | + |
3492 | +As you can see, mixed queries can get reasonably complicated. |
3493 | + |
3494 | +Attribute Queries |
3495 | ++++++++++++++++++ |
3496 | + |
3497 | +Sometimes we want to select an object whose attributes match certain values. For example, if the application under test is a Qt application, we may want to retrieve a list of 'QPushButton' objects whose 'active' attribute is set to ``True``. |
3498 | + |
3499 | +The XPathSelect query protocol supports three value types for attributes: |
3500 | + |
3501 | + * Boolean attribute values are represented as ``True`` or ``False``. |
3502 | + |
3503 | + * String attribute values are represented as strings inside double quote characters. The XPathSelect library understands the common character escape codes, as well as the ``\x__`` hexidecimal escape sequence (For exmaple: ``"\x41"`` would evaluate to a string with a single character 'A'.). |
3504 | + |
3505 | + * Integer attribute values are supported. Integers may use a sign (either '+' or '-'). The sign may be omitted for positive numbers. The range for integer values is from :math:`-2^{32}` to :math:`2^{31}-1`. |
3506 | + |
3507 | +Attribute queries are done inside square brackets (``[...]``) next to the object they apply to. The following table lists a number of attribute queries, as examples of what can be achieved. |
3508 | + |
3509 | +.. list-table:: **XPathSelect Attribute Queries** |
3510 | + :header-rows: 1 |
3511 | + |
3512 | + * - Query: |
3513 | + - Selects: |
3514 | + * - ``//QPushButton[active=True]`` |
3515 | + - Select all ``QPushbutton`` objects whose "active" attribute is set to True. |
3516 | + * - ``//QPushButton[label="Deploy Robots!"]`` |
3517 | + - Select all ``QPushButton`` objects whose labels are set to the string "Deploy Robots". |
3518 | + * - ``//QPushButton[label="Deploy Robots!",active=True]`` |
3519 | + - Select all ``QPushButton`` objects whose labels are set to the string "Deploy Robots", *and* whose "active" attribute is set to True. |
3520 | + * - ``//QSpinBox[value=-10]`` |
3521 | + - Select all ``QSpinBox`` objects whose value attribute is set to -10. |
3522 | + |
3523 | +.. note:: |
3524 | + While the XPathSelect protocol has a fairly limited list of supported types for attribute matching queries, it is important to note that autopilot transparently supports matching object attributes of any type. Autopilot will send attribute filters to the application under test using the XPathSelect protocol only if the attribute filters are supported by XPathSelect. In all other cases, the filtering will be done within autopilot. At worst, the test author may notice that some queries take longer than others. |
3525 | + |
3526 | +Wildcard Nodes |
3527 | +============== |
3528 | + |
3529 | +As well as selecting a node in the introspection tree by node name, one can also use ``*`` to select any node. However, there are a few restrictions on this feature, to stop the inadvertent selection of the entire tree. |
3530 | + |
3531 | +Selecting All Children |
3532 | +++++++++++++++++++++++ |
3533 | + |
3534 | +Wildcard nodes are often used to select all the children of a particular object. For example, if the query ``/path/to/object[id=123]`` returns the parent object you are interested in, then the query ``/path/to/object[id=123]/*`` will return all the immediate children of that object. |
3535 | + |
3536 | +Selecting Nodes based on Attributes |
3537 | ++++++++++++++++++++++++++++++++++++ |
3538 | + |
3539 | +The second use of wildcard nodes is to select nodes based purely on their attributes, rather than their type. For example, to select every object with a 'visible' property set to 'True', the following query will work: ``//*[visible=True]``. However, care must be taken - this query will traverse the entire introspection tree, and may take a long time. Additionally, a large amount of data may be returned over DBus. |
3540 | + |
3541 | +Invalid Wildcard Queries |
3542 | +++++++++++++++++++++++++ |
3543 | + |
3544 | +The wildcard specifier may only be used after a search separator if you have also specified attribute filters. For example, all the following queries are invalid: |
3545 | + |
3546 | +**Invalid Queries** |
3547 | + |
3548 | +* ``//*`` |
3549 | + |
3550 | +* ``/path/to/some/node//*`` |
3551 | + |
3552 | +* ``//node//*`` |
3553 | + |
3554 | +However, the following queries are all valid: |
3555 | + |
3556 | +**Valid Queries** |
3557 | + |
3558 | +* ``//node/*`` |
3559 | + |
3560 | +* ``/node//*[key="value"]`` |
3561 | + |
3562 | +* ``//node//*[key=True]`` |
3563 | + |
3564 | +Returning State Data |
3565 | +==================== |
3566 | + |
3567 | +Once the application under test has parsed the XPathSleect query, and has a list (possibly empty!) of objects that match the given query, it must serialize those objects back across DBus as the return value from the ``GetState`` method. The data structure used is reasonably complex, and is described below: |
3568 | + |
3569 | +* At the top level, the return type must be an array of objects. Each item in the array represents an object that matched the supplied query. If no objects matched the supplied query, an empty array must be returned. |
3570 | + |
3571 | + * Each object is a DBus structure that has two parts: a string, and a map. The string specifies the full tree path to the object being returned (for example "/path/to/object"). |
3572 | + |
3573 | + * The map represents the object state, and is a map of strings to arrays. The keys in this map are property names (for example "visible"). |
3574 | + |
3575 | + * The arrays represents the property value. It contains at least two parts, a value type id (see below for a list of these ids and what they mean), and one or more values. The values can be any type representable over dbus. Some values may actually be arrays of values, for example. |
3576 | + |
3577 | +.. graphviz:: |
3578 | + |
3579 | + digraph dbus_data { |
3580 | + node [shape=record]; |
3581 | + |
3582 | + objects [label="Object|<f1>Object|..."]; |
3583 | + object2 [label="Object_name|<f1>Object_state"]; |
3584 | + object_state [label="property|<f0>property|..."] |
3585 | + property [label="key|value_type|value|..."] |
3586 | + |
3587 | + objects:f1 -> object2; |
3588 | + object2:f1 -> object_state; |
3589 | + object_state:f0 -> property; |
3590 | + } |
3591 | + |
3592 | + |
3593 | +Valid IDs |
3594 | ++++++++++ |
3595 | + |
3596 | +The following table lists the various type Ids, and their meaning. |
3597 | + |
3598 | +.. list-table:: **Autopilot Type IDs and their meaning** |
3599 | + :header-rows: 1 |
3600 | + :widths: 5 90 |
3601 | + |
3602 | + * - Type ID: |
3603 | + - Meaning: |
3604 | + * - 0 |
3605 | + - Simple Type. The value is a DBus integer, boolean, or string, and that is exactly how it should be represented to the user. |
3606 | + * - 1 |
3607 | + - Rectangle. The next four values are all integers, and represent a rectangle in cartesian space. The four numbers must represent the x, y, width and height of the rectangle, respectively. Autopilot will likely present the four values as 'x', 'y', 'w' and 'h' to test authors. Autopilot makes no assumptions about the coordinate space used. |
3608 | + * - 2 |
3609 | + - Point. The next two values are integers, and represent an X, Y, point in catesian space. |
3610 | + * - 3 |
3611 | + - Size. The next two value are integers, and represent a width,height pair, describing a size in catesian space. |
3612 | + * - 4 |
3613 | + - Color. The next four values are all integers, and represent the red, green, blue, and alpha components of the color, respectively. Each component is bounded between 0 and 255. |
3614 | + * - 5 |
3615 | + - Date or Date/Time. The next value is an integer representing the number of seconds since the unix epoch (1970-01-011 00:00:00), UTC time. |
3616 | + * - 6 |
3617 | + - Time. The next values are all integers, and represent hours, minutes, seconds, milliseconds. |
3618 | + |
3619 | +Special Attributes |
3620 | +++++++++++++++++++ |
3621 | + |
3622 | +Most attributes that are returned will be attributes of the UI toolkit class itself. However, there are two special attributes: |
3623 | + |
3624 | +* The ``id`` attribute *must* be present, and must contain an integer number. This number must be unique for this instance of the object. This number must also be within the range suitable for integer parameter matching. |
3625 | + |
3626 | +* The ``Children`` attribute *may* be present if the object being serialized has any children in the introspection tree. If it is present, it must be an array of strings, where each string is the class name of the immediate child object. |
3627 | + |
3628 | +* The ``globalRect`` property should be present for any components that have an on-screen presence. It should be a 4-element array containing the x,y,w,h values of the items on-screen coordinates. Note that these coordinates should be in screen-space, rather than local application space. |
3629 | + |
3630 | +Example GetState Return Values |
3631 | +++++++++++++++++++++++++++++++ |
3632 | + |
3633 | +All the examples in this section have had whitespace added to make them more readable. |
3634 | + |
3635 | +**Example 1: Unity Shell** |
3636 | + |
3637 | +Query: ``/`` |
3638 | + |
3639 | +Return Value:: |
3640 | + |
3641 | + [ |
3642 | + ( |
3643 | + '/Unity', |
3644 | + { |
3645 | + 'id': 0L, |
3646 | + 'Children': |
3647 | + [ |
3648 | + 'DashController', |
3649 | + 'HudController', |
3650 | + 'LauncherController', |
3651 | + 'PanelController', |
3652 | + 'Screen' |
3653 | + 'SessionController', |
3654 | + 'ShortcutController', |
3655 | + 'SwitcherController', |
3656 | + 'WindowManager', |
3657 | + ] |
3658 | + } |
3659 | + ) |
3660 | + ] |
3661 | + |
3662 | + |
3663 | +**Example 2: Qt Creator Menu** |
3664 | + |
3665 | +This is a much larger object, and shows the ``globalRect`` attribute. |
3666 | + |
3667 | +Query: ``/QtCreator/QMenu[objectName="Project.Menu.Session"]`` |
3668 | + |
3669 | +Return Value:: |
3670 | + |
3671 | + [ |
3672 | + ( |
3673 | + '/QtCreator/QMenu', |
3674 | + { |
3675 | + '_autopilot_id': 2L, |
3676 | + 'acceptDrops': False, |
3677 | + 'accessibleDescription': '', |
3678 | + 'accessibleName': '', |
3679 | + 'autoFillBackground': False, |
3680 | + 'baseSize': [0, 0], |
3681 | + 'Children': ['QAction'], |
3682 | + 'childrenRect': [0, 0, 0, 0], |
3683 | + 'contextMenuPolicy': 1, |
3684 | + 'enabled': True, |
3685 | + 'focus': False, |
3686 | + 'focusPolicy': 0, |
3687 | + 'frameGeometry': [0, 0, 100, 30], |
3688 | + 'frameSize': [100, 30], |
3689 | + 'fullScreen': False, |
3690 | + 'geometry': [0, 0, 100, 30], |
3691 | + 'globalRect': [0, 0, 100, 30], |
3692 | + 'height': 30, |
3693 | + 'id': 2L, |
3694 | + 'inputMethodHints': 0, |
3695 | + 'isActiveWindow': False, |
3696 | + 'layoutDirection': 0, |
3697 | + 'maximized': False, |
3698 | + 'maximumHeight': 16777215, |
3699 | + 'maximumSize': [16777215, 16777215] |
3700 | + 'maximumWidth': 16777215, |
3701 | + 'minimized': False, |
3702 | + 'minimumHeight': 0, |
3703 | + 'minimumSize': [0, 0], |
3704 | + 'minimumSizeHint': [-1, -1], |
3705 | + 'minimumWidth': 0, |
3706 | + 'modal': False, |
3707 | + 'mouseTracking': True, |
3708 | + 'normalGeometry': [0, 0, 0, 0], |
3709 | + 'objectName': 'Project.Menu.Session', |
3710 | + 'pos': [0, 0], |
3711 | + 'rect': [0, 0, 100, 30], |
3712 | + 'separatorsCollapsible': True, |
3713 | + 'size': [100, 30], |
3714 | + 'sizeHint': [379, 205], |
3715 | + 'sizeIncrement': [0, 0], |
3716 | + 'statusTip': '', |
3717 | + 'styleSheet': '', |
3718 | + 'tearOffEnabled': False, |
3719 | + 'title': '', |
3720 | + 'toolTip': '', |
3721 | + 'updatesEnabled': True, |
3722 | + 'visible': False, |
3723 | + 'whatsThis': '', |
3724 | + 'width': 100, |
3725 | + 'windowFilePath': '', |
3726 | + 'windowIconText': '', |
3727 | + 'windowModality': 0, |
3728 | + 'windowModified': False, |
3729 | + 'windowOpacity': 1.0, |
3730 | + 'windowTitle': '', |
3731 | + 'x': 0, |
3732 | + 'y': 0, |
3733 | + } |
3734 | + ) |
3735 | + ] |
3736 | |
3737 | === modified file 'docs/conf.py' |
3738 | --- docs/conf.py 2013-07-25 05:47:36 +0000 |
3739 | +++ docs/conf.py 2013-09-16 17:17:50 +0000 |
3740 | @@ -50,6 +50,8 @@ |
3741 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |
3742 | extensions = [ |
3743 | 'sphinx.ext.autodoc', |
3744 | + 'sphinx.ext.graphviz', |
3745 | + 'sphinx.ext.pngmath', |
3746 | 'sphinx.ext.viewcode', |
3747 | 'otto', |
3748 | ] |
3749 | @@ -84,7 +86,7 @@ |
3750 | except ImportError: |
3751 | # If we don't have python-debian installed, guess a coarse-grained version |
3752 | # string |
3753 | - version = '1.3' |
3754 | + version = '1.4' |
3755 | |
3756 | # The full version, including alpha/beta/rc tags. |
3757 | release = version |
3758 | |
3759 | === modified file 'docs/contents.rst' |
3760 | --- docs/contents.rst 2013-06-14 02:05:36 +0000 |
3761 | +++ docs/contents.rst 2013-09-16 17:17:50 +0000 |
3762 | @@ -11,3 +11,4 @@ |
3763 | porting/porting |
3764 | faq/faq |
3765 | faq/contribute |
3766 | + appendix/appendix |
3767 | |
3768 | === modified file 'docs/porting/porting.rst' |
3769 | --- docs/porting/porting.rst 2013-04-27 02:59:44 +0000 |
3770 | +++ docs/porting/porting.rst 2013-09-16 17:17:50 +0000 |
3771 | @@ -12,6 +12,24 @@ |
3772 | |
3773 | Autopilot versions earlier than 1.2 were not publicly announced, and were only used within Canonical. For that reason, this document assumes that version 1.2 is the lowest version of autopilot present `"in the wild"`. |
3774 | |
3775 | +Porting to Autopilot v1.4.x |
3776 | +=========================== |
3777 | + |
3778 | +The 1.4 release contains several changes that required a break in the DBus wire protocol between autopilot and the applications under test. Most of these changes require no change to test code. |
3779 | + |
3780 | +Gtk Tests and Boolean Parameters |
3781 | +++++++++++++++++++++++++++++++++ |
3782 | + |
3783 | +Version 1.3 of the autopilot-gtk backend contained `a bug <https://bugs.launchpad.net/autopilot-gtk/+bug/1214249>`_ that caused all Boolean properties to be exported as integers instead of boolean values. This in turn meant that test code would fail to return the correct objects when using selection criteria such as:: |
3784 | + |
3785 | + visible_buttons = app.select_many("GtkPushButton", visible=True) |
3786 | + |
3787 | +and instead had to write something like this:: |
3788 | + |
3789 | + visible_buttons = app.select_many("GtkPushButton", visible=1) |
3790 | + |
3791 | +This bug has now been fixed, and using the integer selection will fail. |
3792 | + |
3793 | Porting to Autopilot v1.3.x |
3794 | =========================== |
3795 | |
3796 | |
3797 | === modified file 'docs/tutorial/advanced_autopilot.rst' |
3798 | --- docs/tutorial/advanced_autopilot.rst 2013-08-23 04:07:36 +0000 |
3799 | +++ docs/tutorial/advanced_autopilot.rst 2013-09-16 17:17:50 +0000 |
3800 | @@ -236,7 +236,7 @@ |
3801 | >>> try: |
3802 | ... kbd = Keyboard.create("uinput") |
3803 | ... except RuntimeError as e: |
3804 | - ... print "Unable to create keyboard:", e |
3805 | + ... print("Unable to create keyboard: " + str(e)) |
3806 | ... |
3807 | Unable to create keyboard: Unknown backend 'uinput' |
3808 | |
3809 | |
3810 | === modified file 'setup.py' |
3811 | --- setup.py 2013-07-24 05:56:06 +0000 |
3812 | +++ setup.py 2013-09-16 17:17:50 +0000 |
3813 | @@ -28,7 +28,7 @@ |
3814 | except ImportError: |
3815 | # If we don't have python-debian installed, guess a coarse-grained version |
3816 | # string |
3817 | - version = '1.3.1' |
3818 | + version = '1.4.0' |
3819 | |
3820 | autopilot_tracepoint = Extension( |
3821 | 'autopilot.tracepoint', |
FAILED: Continuous integration, rev:346 jenkins. qa.ubuntu. com/job/ autopilot- ci/221/ jenkins. qa.ubuntu. com/job/ autopilot- saucy-amd64- ci/149/ console jenkins. qa.ubuntu. com/job/ autopilot- saucy-armhf- ci/149/ console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ autopilot- ci/221/ rebuild
http://