Merge lp:~thomir-deactivatedaccount/autopilot/trunk-fix-ProcessSearchError-message into lp:autopilot
- trunk-fix-ProcessSearchError-message
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Christopher Lee | ||||
Approved revision: | 420 | ||||
Merged at revision: | 417 | ||||
Proposed branch: | lp:~thomir-deactivatedaccount/autopilot/trunk-fix-ProcessSearchError-message | ||||
Merge into: | lp:autopilot | ||||
Diff against target: |
494 lines (+416/-19) 3 files modified
autopilot/introspection/__init__.py (+98/-14) autopilot/tests/functional/test_ap_apps.py (+1/-2) autopilot/tests/unit/test_introspection_features.py (+317/-3) |
||||
To merge this branch: | bzr merge lp:~thomir-deactivatedaccount/autopilot/trunk-fix-ProcessSearchError-message | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Christopher Lee (community) | Approve | ||
Review via email:
|
Commit message
Improve the error message in the ProcessSearchError exception.
Description of the change
Improve the error message in the ProcessSearchError exception.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
- 419. By Thomi Richards
-
Fix failing functional test.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:419
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 420. By Thomi Richards
-
Fixed issues from code review.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:420
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'autopilot/introspection/__init__.py' |
2 | --- autopilot/introspection/__init__.py 2014-01-16 23:28:01 +0000 |
3 | +++ autopilot/introspection/__init__.py 2014-01-20 20:59:59 +0000 |
4 | @@ -33,6 +33,7 @@ |
5 | from functools import partial |
6 | import os |
7 | import psutil |
8 | +from six import u |
9 | |
10 | from autopilot.dbus_handler import ( |
11 | get_session_bus, |
12 | @@ -143,14 +144,7 @@ |
13 | ``process.pid != pid``. |
14 | |
15 | """ |
16 | - if process is not None: |
17 | - if pid is None: |
18 | - pid = process.pid |
19 | - elif pid != process.pid: |
20 | - raise RuntimeError("Supplied PID and process.pid do not match.") |
21 | - |
22 | - if pid is not None and not _pid_is_running(pid): |
23 | - raise ProcessSearchError("PID %d could not be found" % pid) |
24 | + pid = _check_process_and_pid_details(process, pid) |
25 | |
26 | dbus_addresses = _get_dbus_addresses_from_search_parameters( |
27 | pid, |
28 | @@ -160,19 +154,109 @@ |
29 | process |
30 | ) |
31 | |
32 | - if application_name: |
33 | - app_name_check_fn = lambda i: get_classname_from_path( |
34 | - i.introspection_iface.GetState('/')[0][0]) == application_name |
35 | - dbus_addresses = list(filter(app_name_check_fn, dbus_addresses)) |
36 | + dbus_addresses = _maybe_filter_connections_by_app_name( |
37 | + application_name, |
38 | + dbus_addresses |
39 | + ) |
40 | |
41 | if dbus_addresses is None or len(dbus_addresses) == 0: |
42 | - raise ProcessSearchError("Search criteria returned no results") |
43 | + criteria_string = _get_search_criteria_string_representation( |
44 | + pid, |
45 | + dbus_bus, |
46 | + connection_name, |
47 | + process, |
48 | + object_path, |
49 | + application_name |
50 | + ) |
51 | + message_string = "Search criteria (%s) returned no results" % \ |
52 | + (criteria_string) |
53 | + raise ProcessSearchError(message_string) |
54 | if len(dbus_addresses) > 1: |
55 | - raise RuntimeError("Search criteria returned multiple results") |
56 | + criteria_string = _get_search_criteria_string_representation( |
57 | + pid, |
58 | + dbus_bus, |
59 | + connection_name, |
60 | + process, |
61 | + object_path, |
62 | + application_name |
63 | + ) |
64 | + message_string = "Search criteria (%s) returned multiple results" % \ |
65 | + (criteria_string) |
66 | + raise RuntimeError(message_string) |
67 | |
68 | return _make_proxy_object(dbus_addresses[0], emulator_base) |
69 | |
70 | |
71 | +def _check_process_and_pid_details(process=None, pid=None): |
72 | + """Do error checking on process and pid specification. |
73 | + |
74 | + :raises RuntimeError: if both process and pid are specified, but the |
75 | + process's 'pid' attribute is different to the pid attribute specified. |
76 | + :raises ProcessSearchError: if the process specified is not running. |
77 | + :returns: the pid to use in all search queries. |
78 | + |
79 | + """ |
80 | + if process is not None: |
81 | + if pid is None: |
82 | + pid = process.pid |
83 | + elif pid != process.pid: |
84 | + raise RuntimeError("Supplied PID and process.pid do not match.") |
85 | + |
86 | + if pid is not None and not _pid_is_running(pid): |
87 | + raise ProcessSearchError("PID %d could not be found" % pid) |
88 | + return pid |
89 | + |
90 | + |
91 | +def _maybe_filter_connections_by_app_name(application_name, dbus_addresses): |
92 | + """Filter `dbus_addresses` by the application name exported, if |
93 | + `application_name` has been specified. |
94 | + |
95 | + :returns: a filtered list of connections. |
96 | + """ |
97 | + |
98 | + if application_name: |
99 | + dbus_addresses = [ |
100 | + a for a in dbus_addresses |
101 | + if _get_application_name_from_dbus_address(a) == application_name |
102 | + ] |
103 | + return dbus_addresses |
104 | + |
105 | + |
106 | +def _get_application_name_from_dbus_address(dbus_address): |
107 | + """Return the application name from a dbus_address object.""" |
108 | + return get_classname_from_path( |
109 | + dbus_address.introspection_iface.GetState('/')[0][0] |
110 | + ) |
111 | + |
112 | + |
113 | +def _get_search_criteria_string_representation( |
114 | + pid=None, dbus_bus=None, connection_name=None, process=None, |
115 | + object_path=None, application_name=None): |
116 | + """Get a string representation of the search criteria. |
117 | + |
118 | + Used to represent the search criteria to users in error messages. |
119 | + """ |
120 | + description_parts = [] |
121 | + if pid is not None: |
122 | + description_parts.append(u('pid = %d') % pid) |
123 | + if dbus_bus is not None: |
124 | + description_parts.append(u("dbus bus = '%s'") % dbus_bus) |
125 | + if connection_name is not None: |
126 | + description_parts.append( |
127 | + u("connection name = '%s'") % connection_name |
128 | + ) |
129 | + if object_path is not None: |
130 | + description_parts.append(u("object path = '%s'") % object_path) |
131 | + if application_name is not None: |
132 | + description_parts.append( |
133 | + u("application name = '%s'") % application_name |
134 | + ) |
135 | + if process is not None: |
136 | + description_parts.append(u("process object = '%r'") % process) |
137 | + |
138 | + return ", ".join(description_parts) |
139 | + |
140 | + |
141 | def _get_dbus_addresses_from_search_parameters( |
142 | pid, dbus_bus, connection_name, object_path, process): |
143 | """Returns a list of :py:class: `DBusAddress` for all successfully matched |
144 | |
145 | === modified file 'autopilot/tests/functional/test_ap_apps.py' |
146 | --- autopilot/tests/functional/test_ap_apps.py 2013-12-16 03:55:22 +0000 |
147 | +++ autopilot/tests/functional/test_ap_apps.py 2014-01-20 20:59:59 +0000 |
148 | @@ -105,10 +105,9 @@ |
149 | sleep(1) |
150 | """ % sys.executable)) |
151 | |
152 | - expected_error = "Search criteria returned no results" |
153 | self.assertThat( |
154 | lambda: self.launch_test_application(path, app_type='qt'), |
155 | - raises(ProcessSearchError(expected_error)) |
156 | + raises(ProcessSearchError) |
157 | ) |
158 | |
159 | def test_creating_app_for_non_running_app_fails(self): |
160 | |
161 | === modified file 'autopilot/tests/unit/test_introspection_features.py' |
162 | --- autopilot/tests/unit/test_introspection_features.py 2013-11-07 22:15:29 +0000 |
163 | +++ autopilot/tests/unit/test_introspection_features.py 2014-01-20 20:59:59 +0000 |
164 | @@ -33,9 +33,22 @@ |
165 | raises, |
166 | ) |
167 | from testscenarios import TestWithScenarios |
168 | -from six import StringIO |
169 | - |
170 | - |
171 | +from six import StringIO, u, PY3 |
172 | +from contextlib import contextmanager |
173 | +if PY3: |
174 | + from contextlib import ExitStack |
175 | +else: |
176 | + from contextlib2 import ExitStack |
177 | + |
178 | + |
179 | +from autopilot.introspection import ( |
180 | + _check_process_and_pid_details, |
181 | + _get_application_name_from_dbus_address, |
182 | + _get_search_criteria_string_representation, |
183 | + _maybe_filter_connections_by_app_name, |
184 | + get_proxy_object_for_existing_process, |
185 | + ProcessSearchError, |
186 | +) |
187 | from autopilot.introspection.dbus import ( |
188 | _get_filter_string_for_key_value_pair, |
189 | _is_valid_server_side_filter_param, |
190 | @@ -279,3 +292,304 @@ |
191 | path: '/some/path' |
192 | text: 'Hello' |
193 | """)) |
194 | + |
195 | + |
196 | +class ProcessSearchErrorStringRepTests(TestCase): |
197 | + |
198 | + """Various tests for the _get_search_criteria_string_representation |
199 | + function. |
200 | + |
201 | + """ |
202 | + |
203 | + def test_get_string_rep_defaults_to_empty_string(self): |
204 | + observed = _get_search_criteria_string_representation() |
205 | + self.assertEqual("", observed) |
206 | + |
207 | + def test_pid(self): |
208 | + self.assertEqual( |
209 | + u('pid = 123'), |
210 | + _get_search_criteria_string_representation(pid=123) |
211 | + ) |
212 | + |
213 | + def test_dbus_bus(self): |
214 | + self.assertEqual( |
215 | + u("dbus bus = 'foo'"), |
216 | + _get_search_criteria_string_representation(dbus_bus='foo') |
217 | + ) |
218 | + |
219 | + def test_connection_name(self): |
220 | + self.assertEqual( |
221 | + u("connection name = 'foo'"), |
222 | + _get_search_criteria_string_representation(connection_name='foo') |
223 | + ) |
224 | + |
225 | + def test_object_path(self): |
226 | + self.assertEqual( |
227 | + u("object path = 'foo'"), |
228 | + _get_search_criteria_string_representation(object_path='foo') |
229 | + ) |
230 | + |
231 | + def test_application_name(self): |
232 | + self.assertEqual( |
233 | + u("application name = 'foo'"), |
234 | + _get_search_criteria_string_representation(application_name='foo') |
235 | + ) |
236 | + |
237 | + def test_process_object(self): |
238 | + class FakeProcess(object): |
239 | + |
240 | + def __repr__(self): |
241 | + return 'foo' |
242 | + process = FakeProcess() |
243 | + self.assertEqual( |
244 | + u("process object = 'foo'"), |
245 | + _get_search_criteria_string_representation(process=process) |
246 | + ) |
247 | + |
248 | + def test_all_parameters_combined(self): |
249 | + class FakeProcess(object): |
250 | + |
251 | + def __repr__(self): |
252 | + return 'foo' |
253 | + process = FakeProcess() |
254 | + observed = _get_search_criteria_string_representation( |
255 | + pid=123, |
256 | + dbus_bus='session_bus', |
257 | + connection_name='com.Canonical.Unity', |
258 | + object_path='/com/Canonical/Autopilot', |
259 | + application_name='MyApp', |
260 | + process=process |
261 | + ) |
262 | + expected = "pid = 123, dbus bus = 'session_bus', " \ |
263 | + "connection name = 'com.Canonical.Unity', " \ |
264 | + "object path = '/com/Canonical/Autopilot', " \ |
265 | + "application name = 'MyApp', process object = 'foo'" |
266 | + self.assertEqual(expected, observed) |
267 | + |
268 | + |
269 | +class ProcessAndPidErrorCheckingTests(TestCase): |
270 | + |
271 | + def test_raises_ProcessSearchError_when_process_is_not_running(self): |
272 | + with patch('autopilot.introspection._pid_is_running') as pir: |
273 | + pir.return_value = False |
274 | + |
275 | + self.assertThat( |
276 | + lambda: _check_process_and_pid_details(pid=123), |
277 | + raises(ProcessSearchError("PID 123 could not be found")) |
278 | + ) |
279 | + |
280 | + def test_raises_RuntimeError_when_pid_and_process_disagree(self): |
281 | + mock_process = Mock() |
282 | + mock_process.pid = 1 |
283 | + |
284 | + self.assertThat( |
285 | + lambda: _check_process_and_pid_details(mock_process, 2), |
286 | + raises(RuntimeError("Supplied PID and process.pid do not match.")) |
287 | + ) |
288 | + |
289 | + def test_returns_pid_when_specified(self): |
290 | + expected = self.getUniqueInteger() |
291 | + with patch('autopilot.introspection._pid_is_running') as pir: |
292 | + pir.return_value = True |
293 | + |
294 | + observed = _check_process_and_pid_details(pid=expected) |
295 | + |
296 | + self.assertEqual(expected, observed) |
297 | + |
298 | + def test_returns_process_pid_attr_when_specified(self): |
299 | + fake_process = Mock() |
300 | + fake_process.pid = self.getUniqueInteger() |
301 | + |
302 | + with patch('autopilot.introspection._pid_is_running') as pir: |
303 | + pir.return_value = True |
304 | + observed = _check_process_and_pid_details(fake_process) |
305 | + |
306 | + self.assertEqual(fake_process.pid, observed) |
307 | + |
308 | + def test_returns_None_when_neither_parameters_present(self): |
309 | + self.assertEqual( |
310 | + None, |
311 | + _check_process_and_pid_details() |
312 | + ) |
313 | + |
314 | + def test_returns_pid_when_both_specified(self): |
315 | + fake_process = Mock() |
316 | + fake_process.pid = self.getUniqueInteger() |
317 | + with patch('autopilot.introspection._pid_is_running') as pir: |
318 | + pir.return_value = True |
319 | + observed = _check_process_and_pid_details( |
320 | + fake_process, |
321 | + fake_process.pid |
322 | + ) |
323 | + self.assertEqual(fake_process.pid, observed) |
324 | + |
325 | + |
326 | +class ApplicationFilteringTests(TestCase): |
327 | + |
328 | + def get_mock_dbus_address_with_application_name(slf, app_name): |
329 | + mock_dbus_address = Mock() |
330 | + mock_dbus_address.introspection_iface.GetState.return_value = ( |
331 | + ('/' + app_name, {}), |
332 | + ) |
333 | + return mock_dbus_address |
334 | + |
335 | + def test_can_extract_application_name(self): |
336 | + mock_connection = self.get_mock_dbus_address_with_application_name( |
337 | + 'SomeAppName' |
338 | + ) |
339 | + self.assertEqual( |
340 | + 'SomeAppName', |
341 | + _get_application_name_from_dbus_address(mock_connection) |
342 | + ) |
343 | + |
344 | + def test_maybe_filter_returns_addresses_when_app_name_not_specified(self): |
345 | + self.assertEqual( |
346 | + [], |
347 | + _maybe_filter_connections_by_app_name(None, []) |
348 | + ) |
349 | + |
350 | + def test_maybe_filter_works_with_partial_match(self): |
351 | + mock_connections = [ |
352 | + self.get_mock_dbus_address_with_application_name('Match'), |
353 | + self.get_mock_dbus_address_with_application_name('Mismatch'), |
354 | + ] |
355 | + expected = mock_connections[:1] |
356 | + observed = _maybe_filter_connections_by_app_name( |
357 | + 'Match', |
358 | + mock_connections |
359 | + ) |
360 | + self.assertEqual(expected, observed) |
361 | + |
362 | + def test_maybe_filter_works_with_no_match(self): |
363 | + mock_connections = [ |
364 | + self.get_mock_dbus_address_with_application_name('Mismatch1'), |
365 | + self.get_mock_dbus_address_with_application_name('Mismatch2'), |
366 | + ] |
367 | + expected = [] |
368 | + observed = _maybe_filter_connections_by_app_name( |
369 | + 'Match', |
370 | + mock_connections |
371 | + ) |
372 | + self.assertEqual(expected, observed) |
373 | + |
374 | + def test_maybe_filter_works_with_full_match(self): |
375 | + mock_connections = [ |
376 | + self.get_mock_dbus_address_with_application_name('Match'), |
377 | + self.get_mock_dbus_address_with_application_name('Match'), |
378 | + ] |
379 | + expected = mock_connections |
380 | + observed = _maybe_filter_connections_by_app_name( |
381 | + 'Match', |
382 | + mock_connections |
383 | + ) |
384 | + self.assertEqual(expected, observed) |
385 | + |
386 | + |
387 | +class ProxyObjectGenerationTests(TestCase): |
388 | + |
389 | + @contextmanager |
390 | + def mock_all_child_calls(self): |
391 | + mock_dict = {} |
392 | + with ExitStack() as all_the_mocks: |
393 | + mock_dict['check_process'] = all_the_mocks.enter_context( |
394 | + patch( |
395 | + 'autopilot.introspection._check_process_and_pid_details' |
396 | + ) |
397 | + ) |
398 | + mock_dict['get_addresses'] = all_the_mocks.enter_context( |
399 | + patch( |
400 | + 'autopilot.introspection.' |
401 | + '_get_dbus_addresses_from_search_parameters' |
402 | + ) |
403 | + ) |
404 | + mock_dict['filter_addresses'] = all_the_mocks.enter_context( |
405 | + patch( |
406 | + 'autopilot.introspection.' |
407 | + '_maybe_filter_connections_by_app_name' |
408 | + ) |
409 | + ) |
410 | + mock_dict['make_proxy_object'] = all_the_mocks.enter_context( |
411 | + patch( |
412 | + 'autopilot.introspection._make_proxy_object' |
413 | + ) |
414 | + ) |
415 | + yield mock_dict |
416 | + |
417 | + def test_makes_child_calls(self): |
418 | + """Mock out all child functions, and assert that they're called. |
419 | + |
420 | + This test is somewhat ugly, and should be refactored once the search |
421 | + criteria has been refactored into a separate object, rather than a |
422 | + bunch of named parameters. |
423 | + |
424 | + """ |
425 | + with self.mock_all_child_calls() as mocks: |
426 | + fake_address_list = [Mock()] |
427 | + mocks['get_addresses'].return_value = fake_address_list |
428 | + mocks['filter_addresses'].return_value = fake_address_list |
429 | + |
430 | + get_proxy_object_for_existing_process() |
431 | + |
432 | + self.assertEqual( |
433 | + 1, |
434 | + mocks['check_process'].call_count |
435 | + ) |
436 | + self.assertEqual( |
437 | + 1, |
438 | + mocks['get_addresses'].call_count |
439 | + ) |
440 | + self.assertEqual( |
441 | + 1, |
442 | + mocks['make_proxy_object'].call_count |
443 | + ) |
444 | + |
445 | + def test_raises_ProcessSearchError(self): |
446 | + """Function must raise ProcessSearchError if no addresses are found. |
447 | + |
448 | + This test is somewhat ugly, and should be refactored once the search |
449 | + criteria has been refactored into a separate object, rather than a |
450 | + bunch of named parameters. |
451 | + |
452 | + """ |
453 | + with self.mock_all_child_calls() as mocks: |
454 | + fake_address_list = [Mock()] |
455 | + mocks['check_process'].return_value = 123 |
456 | + mocks['get_addresses'].return_value = fake_address_list |
457 | + mocks['filter_addresses'].return_value = [] |
458 | + |
459 | + self.assertThat( |
460 | + lambda: get_proxy_object_for_existing_process(), |
461 | + raises( |
462 | + ProcessSearchError( |
463 | + "Search criteria (pid = 123, dbus bus = 'session', " |
464 | + "object path = " |
465 | + "'/com/canonical/Autopilot/Introspection') returned " |
466 | + "no results" |
467 | + ) |
468 | + ) |
469 | + ) |
470 | + |
471 | + def test_raises_RuntimeError(self): |
472 | + """Function must raise RuntimeError if several addresses are found. |
473 | + |
474 | + This test is somewhat ugly, and should be refactored once the search |
475 | + criteria has been refactored into a separate object, rather than a |
476 | + bunch of named parameters. |
477 | + |
478 | + """ |
479 | + with self.mock_all_child_calls() as mocks: |
480 | + fake_address_list = [Mock(), Mock()] |
481 | + mocks['get_addresses'].return_value = fake_address_list |
482 | + mocks['filter_addresses'].return_value = fake_address_list |
483 | + |
484 | + self.assertThat( |
485 | + lambda: get_proxy_object_for_existing_process(), |
486 | + raises( |
487 | + RuntimeError( |
488 | + "Search criteria (pid = 1, dbus bus = 'session', " |
489 | + "object path = " |
490 | + "'/com/canonical/Autopilot/Introspection') " |
491 | + "returned multiple results" |
492 | + ) |
493 | + ) |
494 | + ) |
FAILED: Continuous integration, rev:418 jenkins. qa.ubuntu. com/job/ autopilot- ci/410/ jenkins. qa.ubuntu. com/job/ autopilot- trusty- amd64-ci/ 136 jenkins. qa.ubuntu. com/job/ autopilot- trusty- amd64-ci/ 136/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ autopilot- trusty- armhf-ci/ 136 jenkins. qa.ubuntu. com/job/ autopilot- trusty- armhf-ci/ 136/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 2458 jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-trusty/ 2146 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/2460 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/2460/ artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/autopilot- ci/410/ rebuild
http://