Merge lp:~thomir-deactivatedaccount/autopilot/fix-state-not-found into lp:autopilot/1.3

Proposed by Thomi Richards
Status: Merged
Approved by: Martin Pitt
Approved revision: 334
Merged at revision: 327
Proposed branch: lp:~thomir-deactivatedaccount/autopilot/fix-state-not-found
Merge into: lp:autopilot/1.3
Diff against target: 165 lines (+124/-10)
2 files modified
autopilot/introspection/dbus.py (+49/-10)
autopilot/tests/unit/test_exceptions.py (+75/-0)
To merge this branch: bzr merge lp:~thomir-deactivatedaccount/autopilot/fix-state-not-found
Reviewer Review Type Date Requested Status
Martin Pitt (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+185675@code.launchpad.net

Commit message

Update StateNotFoundErrror exception to give more informative messages.

Description of the change

This branch makes a few trivial changes to the StateNotFoundError exception class, and adds tests that cover that class.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
333. By Thomi Richards

Fix unicode string literals in test.

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

Nitpick: Constructions like

  lambda: StateNotFoundError()

are redundant, just saying "StateNotFoundError" should suffice.

56 + def __str__(self):
57 + return self.message

This does the wrong thing for Python 2, as you initialize .message with an unicode, so you don't actually return a str (which is bytes in Python 2).

133 + err.message,

I think we shouldn't make .message an official API. In Python 3 exceptions don't have it any more, instead you'd just do str().

175 + unicode(err),

That will fail in Python 3. It's okay for the 1.3 branch, but must be skipped for trunk when running under Py3.

review: Needs Fixing
334. By Thomi Richards

Fixes from code review.

Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Fixed - this is landing in 1.3, so if it's incompatible in python 3, we'll fix that when we forward-port it to trunk.

Revision history for this message
Martin Pitt (pitti) wrote :

LGTM, thanks for the cleanups.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'autopilot/introspection/dbus.py'
2--- autopilot/introspection/dbus.py 2013-09-13 16:26:18 +0000
3+++ autopilot/introspection/dbus.py 2013-09-16 13:42:21 +0000
4@@ -42,13 +42,52 @@
5
6
7 class StateNotFoundError(RuntimeError):
8- """Raised when a piece of state information from unity is not found."""
9-
10- message = "State not found for class with name '{}' and id '{}'."
11-
12- def __init__(self, class_name, class_id):
13- super(StateNotFoundError, self).__init__(
14- self.message.format(class_name, class_id))
15+
16+ """Raised when a piece of state information is not found.
17+
18+ This exception is commonly raised when the application has destroyed (or
19+ not yet created) the object you are trying to access in autopilot. This
20+ typically happens for a number of possible reasons:
21+
22+ * The UI widget you are trying to access with
23+ :py:met:`DBusIntrospectionObject.select_single` or
24+ :py:met:`DBusIntrospectionObject.select_single` does not exist yet.
25+ * The UI widget you are trying to access has been destroyed by the
26+ application.
27+
28+ """
29+
30+ def __init__(self, class_name=None, **filters):
31+ """Construct a StateNotFoundError.
32+
33+ :raises ValueError: if neither the class name not keyword arguments
34+ are specified.
35+
36+ """
37+ if class_name is None and not filters:
38+ raise ValueError("Must specify either class name or filters.")
39+
40+ if class_name is None:
41+ self._message = \
42+ u"State not found with filters {}.".format(
43+ repr(filters)
44+ )
45+ elif not filters:
46+ self._message = u"State not found for class '{}'.".format(
47+ class_name
48+ )
49+ else:
50+ self._message = \
51+ u"State not found for class '{}' and filters {}.".format(
52+ class_name,
53+ repr(filters)
54+ )
55+
56+ def __str__(self):
57+ return self._message.encode('utf8')
58+
59+ def __unicode__(self):
60+ return self._message
61
62
63 class IntrospectableObjectMetaclass(type):
64@@ -326,10 +365,10 @@
65 of the appropriate type (the latter case is for overridden emulator
66 classes).
67
68- :raises: **ValueError** if the query returns more than one item. *If
69+ :raises ValueError: if the query returns more than one item. *If
70 you want more than one item, use select_many instead*.
71
72- :raises: **TypeError** if neither *type_name* or keyword filters are
73+ :raises TypeError: if neither *type_name* or keyword filters are
74 provided.
75
76 .. seealso::
77@@ -504,7 +543,7 @@
78 try:
79 return self.get_state_by_path(self.get_class_query_string())[0]
80 except IndexError:
81- raise StateNotFoundError(self.__class__.__name__, self.id)
82+ raise StateNotFoundError(self.__class__.__name__, id=self.id)
83
84 def get_class_query_string(self):
85 """Get the XPath query string required to refresh this class's
86
87=== added file 'autopilot/tests/unit/test_exceptions.py'
88--- autopilot/tests/unit/test_exceptions.py 1970-01-01 00:00:00 +0000
89+++ autopilot/tests/unit/test_exceptions.py 2013-09-16 13:42:21 +0000
90@@ -0,0 +1,75 @@
91+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
92+#
93+# Autopilot Functional Test Tool
94+# Copyright (C) 2013 Canonical
95+#
96+# This program is free software: you can redistribute it and/or modify
97+# it under the terms of the GNU General Public License as published by
98+# the Free Software Foundation, either version 3 of the License, or
99+# (at your option) any later version.
100+#
101+# This program is distributed in the hope that it will be useful,
102+# but WITHOUT ANY WARRANTY; without even the implied warranty of
103+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
104+# GNU General Public License for more details.
105+#
106+# You should have received a copy of the GNU General Public License
107+# along with this program. If not, see <http://www.gnu.org/licenses/>.
108+#
109+
110+
111+from testtools import TestCase
112+from testtools.matchers import raises, Equals
113+
114+from autopilot.introspection.dbus import StateNotFoundError
115+
116+
117+class StateNotFoundTests(TestCase):
118+
119+ def test_requires_class_name_to_construct(self):
120+ """You must pass a class name in to the StateNotFoundError exception
121+ class initialiser in order to construct it.
122+
123+ """
124+ self.assertThat(
125+ StateNotFoundError,
126+ raises(ValueError("Must specify either class name or filters."))
127+ )
128+
129+ def test_can_be_constructed_with_class_name_only(self):
130+ """Must be able to construct error class with a class name only."""
131+ err = StateNotFoundError("MyClass")
132+ self.assertThat(
133+ str(err),
134+ Equals("State not found for class 'MyClass'.")
135+ )
136+ self.assertThat(
137+ unicode(err),
138+ Equals(u"State not found for class 'MyClass'.")
139+ )
140+
141+ def test_can_be_constructed_with_filters_only(self):
142+ """Must be able to construct exception with filters only."""
143+ err = StateNotFoundError(foo="bar")
144+ self.assertThat(
145+ str(err),
146+ Equals("State not found with filters {'foo': 'bar'}.")
147+ )
148+ self.assertThat(
149+ unicode(err),
150+ Equals(u"State not found with filters {'foo': 'bar'}.")
151+ )
152+
153+ def test_can_be_constructed_with_class_name_and_filters(self):
154+ """Must be able to construct with both class name and filters."""
155+ err = StateNotFoundError('MyClass', foo="bar")
156+ self.assertThat(
157+ str(err),
158+ Equals("State not found for class 'MyClass'"
159+ " and filters {'foo': 'bar'}.")
160+ )
161+ self.assertThat(
162+ unicode(err),
163+ Equals(u"State not found for class 'MyClass'"
164+ " and filters {'foo': 'bar'}.")
165+ )

Subscribers

People subscribed via source and target branches

to all changes: