Merge lp:~autopilot/autopilot/experimental into lp:autopilot

Proposed by Thomi Richards
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
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!

To post a comment you must log in.
lp:~autopilot/autopilot/experimental updated
347. By Thomi Richards

Merged trunk, resolved conflicts.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Martin Pitt (pitti) wrote :

Big Plus for documenting the wire protocol and porting instructions!

review: Approve
lp:~autopilot/autopilot/experimental updated
348. By Thomi Richards

PEP8 fixes.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
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:.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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',

Subscribers

People subscribed via source and target branches