Merge lp:~sil2100/unity/fix-949448-5.0 into lp:unity
- fix-949448-5.0
- Merge into trunk
Proposed by
Łukasz Zemczak
Status: | Superseded |
---|---|
Proposed branch: | lp:~sil2100/unity/fix-949448-5.0 |
Merge into: | lp:unity |
Diff against target: |
701 lines (+664/-0) (has conflicts) 3 files modified
tests/autopilot/autopilot/emulators/bamf.py.OTHER (+411/-0) tests/autopilot/unity/tests/__init__.py (+225/-0) tests/autopilot/unity/tests/test_launcher.py (+28/-0) Conflict adding files to tests/autopilot/autopilot. Created directory. Conflict because tests/autopilot/autopilot is not versioned, but has versioned children. Versioned directory. Conflict adding files to tests/autopilot/autopilot/emulators. Created directory. Conflict because tests/autopilot/autopilot/emulators is not versioned, but has versioned children. Versioned directory. Contents conflict in tests/autopilot/autopilot/emulators/bamf.py Text conflict in tests/autopilot/unity/tests/__init__.py Text conflict in tests/autopilot/unity/tests/test_launcher.py |
To merge this branch: | bzr merge lp:~sil2100/unity/fix-949448-5.0 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Unity Team | Pending | ||
Review via email: mp+106978@code.launchpad.net |
This proposal has been superseded by a proposal from 2012-05-23.
Commit message
Backported andyrock's fix from lp:unity/6.0 - Unmute launcher.
Description of the change
This branch needs to be tested. I see no other dependency changes, but I cannot get it to work with orca somehow. Could anyone else check it? Maybe I'm doing something wrong.
Original MRQ:
Copy-pasted from the original andyrock's MRQ for 6.0:
https:/
== Problem ==
Launcher is silent to screen reader users.
== Fix ==
Fix a couple of regressions. Alan Bell confirmed the fix for the launcher.
== Test ==
Not sure how we could test it.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'launcher/BamfLauncherIcon.cpp' |
2 | === modified file 'launcher/LauncherController.cpp' |
3 | === modified file 'plugins/unityshell/src/unitya11y.cpp' |
4 | === added directory 'tests/autopilot/autopilot' |
5 | === added directory 'tests/autopilot/autopilot/emulators' |
6 | === added file 'tests/autopilot/autopilot/emulators/bamf.py.OTHER' |
7 | --- tests/autopilot/autopilot/emulators/bamf.py.OTHER 1970-01-01 00:00:00 +0000 |
8 | +++ tests/autopilot/autopilot/emulators/bamf.py.OTHER 2012-05-23 10:30:27 +0000 |
9 | @@ -0,0 +1,411 @@ |
10 | +# Copyright 2011 Canonical |
11 | +# Author: Thomi Richards |
12 | +# |
13 | +# This program is free software: you can redistribute it and/or modify it |
14 | +# under the terms of the GNU General Public License version 3, as published |
15 | +# by the Free Software Foundation. |
16 | + |
17 | +"Various classes for interacting with BAMF." |
18 | + |
19 | +import dbus |
20 | +import dbus.glib |
21 | +import gio |
22 | +import gobject |
23 | +import os |
24 | +from Xlib import display, X, protocol |
25 | +from gtk import gdk |
26 | + |
27 | +from autopilot.emulators.dbus_handler import session_bus |
28 | + |
29 | +__all__ = [ |
30 | + "Bamf", |
31 | + "BamfApplication", |
32 | + "BamfWindow", |
33 | + ] |
34 | + |
35 | +_BAMF_BUS_NAME = 'org.ayatana.bamf' |
36 | +_X_DISPLAY = display.Display() |
37 | + |
38 | + |
39 | +def _filter_user_visible(win): |
40 | + """Filter out non-user-visible objects. |
41 | + |
42 | + In some cases the DBus method we need to call hasn't been registered yet, |
43 | + in which case we do the safe thing and return False. |
44 | + |
45 | + """ |
46 | + try: |
47 | + return win.user_visible |
48 | + except dbus.DBusException: |
49 | + return False |
50 | + |
51 | + |
52 | +class Bamf(object): |
53 | + """High-level class for interacting with Bamf from within a test. |
54 | + |
55 | + Use this class to inspect the state of running applications and open |
56 | + windows. |
57 | + |
58 | + """ |
59 | + |
60 | + def __init__(self): |
61 | + matcher_path = '/org/ayatana/bamf/matcher' |
62 | + self.matcher_interface_name = 'org.ayatana.bamf.matcher' |
63 | + self.matcher_proxy = session_bus.get_object(_BAMF_BUS_NAME, matcher_path) |
64 | + self.matcher_interface = dbus.Interface(self.matcher_proxy, self.matcher_interface_name) |
65 | + |
66 | + def get_running_applications(self, user_visible_only=True): |
67 | + """Get a list of the currently running applications. |
68 | + |
69 | + If user_visible_only is True (the default), only applications |
70 | + visible to the user in the switcher will be returned. |
71 | + |
72 | + """ |
73 | + apps = [BamfApplication(p) for p in self.matcher_interface.RunningApplications()] |
74 | + if user_visible_only: |
75 | + return filter(_filter_user_visible, apps) |
76 | + return apps |
77 | + |
78 | + def get_running_applications_by_desktop_file(self, desktop_file): |
79 | + """Return a list of applications that have the desktop file 'desktop_file'`. |
80 | + |
81 | + This method may return an empty list, if no applications |
82 | + are found with the specified desktop file. |
83 | + |
84 | + """ |
85 | + return [a for a in self.get_running_applications() if a.desktop_file == desktop_file] |
86 | + |
87 | + def get_application_by_xid(self, xid): |
88 | + """Return the application that has a child with the requested xid or None.""" |
89 | + |
90 | + app_path = self.matcher_interface.ApplicationForXid(xid) |
91 | + if len(app_path): |
92 | + return BamfApplication(app_path) |
93 | + return None |
94 | + |
95 | + def get_open_windows(self, user_visible_only=True): |
96 | + """Get a list of currently open windows. |
97 | + |
98 | + If user_visible_only is True (the default), only applications |
99 | + visible to the user in the switcher will be returned. |
100 | + |
101 | + The result is sorted to be in stacking order. |
102 | + |
103 | + """ |
104 | + |
105 | + windows = [BamfWindow(w) for w in self.matcher_interface.WindowStackForMonitor(-1)] |
106 | + if user_visible_only: |
107 | + windows = filter(_filter_user_visible, windows) |
108 | + # Now sort on stacking order. |
109 | + return reversed(windows) |
110 | + |
111 | + def get_window_by_xid(self, xid): |
112 | + """Get the BamfWindow that matches the provided 'xid'.""" |
113 | + windows = [BamfWindow(w) for w in self.matcher_interface.WindowPaths() if BamfWindow(w).x_id == xid] |
114 | + return windows[0] if windows else None |
115 | + |
116 | + def wait_until_application_is_running(self, desktop_file, timeout): |
117 | + """Wait until a given application is running. |
118 | + |
119 | + 'desktop_file' is the name of the application desktop file. |
120 | + 'timeout' is the maximum time to wait, in seconds. If set to |
121 | + something less than 0, this method will wait forever. |
122 | + |
123 | + This method returns true once the application is found, or false |
124 | + if the application was not found until the timeout was reached. |
125 | + """ |
126 | + desktop_file = os.path.split(desktop_file)[1] |
127 | + # python workaround since you can't assign to variables in the enclosing scope: |
128 | + # see on_timeout_reached below... |
129 | + found_app = [True] |
130 | + |
131 | + # maybe the app is running already? |
132 | + if len(self.get_running_applications_by_desktop_file(desktop_file)) == 0: |
133 | + wait_forever = timeout < 0 |
134 | + gobject_loop = gobject.MainLoop() |
135 | + |
136 | + # No, so define a callback to watch the ViewOpened signal: |
137 | + def on_view_added(bamf_path, name): |
138 | + if bamf_path.split('/')[-1].startswith('application'): |
139 | + app = BamfApplication(bamf_path) |
140 | + if desktop_file == os.path.split(app.desktop_file)[1]: |
141 | + gobject_loop.quit() |
142 | + |
143 | + # ...and one for when the user-defined timeout has been reached: |
144 | + def on_timeout_reached(): |
145 | + gobject_loop.quit() |
146 | + found_app[0] = False |
147 | + return False |
148 | + |
149 | + # need a timeout? if so, connect it: |
150 | + if not wait_forever: |
151 | + gobject.timeout_add(timeout * 1000, on_timeout_reached) |
152 | + # connect signal handler: |
153 | + session_bus.add_signal_receiver(on_view_added, 'ViewOpened') |
154 | + # pump the gobject main loop until either the correct signal is emitted, or the |
155 | + # timeout happens. |
156 | + gobject_loop.run() |
157 | + |
158 | + return found_app[0] |
159 | + |
160 | + def launch_application(self, desktop_file, files=[], wait=True): |
161 | + """Launch an application by specifying a desktop file. |
162 | + |
163 | + `files` is a list of files to pass to the application. Not all apps support this. |
164 | + |
165 | + If `wait` is True, this method will block until the application has launched. |
166 | + |
167 | + Returns the Gobject process object. if wait is True (the default), |
168 | + this method will not return until an instance of this application |
169 | + appears in the BAMF application list. |
170 | + """ |
171 | + if type(files) is not list: |
172 | + raise TypeError("files must be a list.") |
173 | + proc = gio.unix.DesktopAppInfo(desktop_file) |
174 | + proc.launch_uris(files) |
175 | + if wait: |
176 | + self.wait_until_application_is_running(desktop_file, -1) |
177 | + return proc |
178 | + |
179 | + |
180 | +class BamfApplication(object): |
181 | + """Represents an application, with information as returned by Bamf. |
182 | + |
183 | + Don't instantiate this class yourself. instead, use the methods as |
184 | + provided by the Bamf class. |
185 | + |
186 | + """ |
187 | + def __init__(self, bamf_app_path): |
188 | + self.bamf_app_path = bamf_app_path |
189 | + try: |
190 | + self._app_proxy = session_bus.get_object(_BAMF_BUS_NAME, bamf_app_path) |
191 | + self._view_iface = dbus.Interface(self._app_proxy, 'org.ayatana.bamf.view') |
192 | + self._app_iface = dbus.Interface(self._app_proxy, 'org.ayatana.bamf.application') |
193 | + except dbus.DBusException, e: |
194 | + e.message += 'bamf_app_path=%r' % (bamf_app_path) |
195 | + raise |
196 | + |
197 | + @property |
198 | + def desktop_file(self): |
199 | + """Get the application desktop file""" |
200 | + return os.path.split(self._app_iface.DesktopFile())[1] |
201 | + |
202 | + @property |
203 | + def name(self): |
204 | + """Get the application name. |
205 | + |
206 | + Note: This may change according to the current locale. If you want a unique |
207 | + string to match applications against, use the desktop_file instead. |
208 | + |
209 | + """ |
210 | + return self._view_iface.Name() |
211 | + |
212 | + @property |
213 | + def icon(self): |
214 | + """Get the application icon.""" |
215 | + return self._view_iface.Icon() |
216 | + |
217 | + @property |
218 | + def is_active(self): |
219 | + """Is the application active (i.e.- has keyboard focus)?""" |
220 | + return self._view_iface.IsActive() |
221 | + |
222 | + @property |
223 | + def is_urgent(self): |
224 | + """Is the application currently signalling urgency?""" |
225 | + return self._view_iface.IsUrgent() |
226 | + |
227 | + @property |
228 | + def user_visible(self): |
229 | + """Is this application visible to the user? |
230 | + |
231 | + Some applications (such as the panel) are hidden to the user but will |
232 | + still be returned by bamf. |
233 | + |
234 | + """ |
235 | + return self._view_iface.UserVisible() |
236 | + |
237 | + def get_windows(self): |
238 | + """Get a list of the application windows.""" |
239 | + return [BamfWindow(w) for w in self._view_iface.Children()] |
240 | + |
241 | + def __repr__(self): |
242 | + return "<BamfApplication '%s'>" % (self.name) |
243 | + |
244 | + |
245 | +class BamfWindow(object): |
246 | + """Represents an application window, as returned by Bamf. |
247 | + |
248 | + Don't instantiate this class yourself. Instead, use the appropriate methods |
249 | + in BamfApplication. |
250 | + |
251 | + """ |
252 | + def __init__(self, window_path): |
253 | + self._bamf_win_path = window_path |
254 | + self._app_proxy = session_bus.get_object(_BAMF_BUS_NAME, window_path) |
255 | + self._window_iface = dbus.Interface(self._app_proxy, 'org.ayatana.bamf.window') |
256 | + self._view_iface = dbus.Interface(self._app_proxy, 'org.ayatana.bamf.view') |
257 | + |
258 | + self._xid = int(self._window_iface.GetXid()) |
259 | + self._x_root_win = _X_DISPLAY.screen().root |
260 | + self._x_win = _X_DISPLAY.create_resource_object('window', self._xid) |
261 | + |
262 | + @property |
263 | + def x_id(self): |
264 | + """Get the X11 Window Id.""" |
265 | + return self._xid |
266 | + |
267 | + @property |
268 | + def x_win(self): |
269 | + """Get the X11 window object of the underlying window.""" |
270 | + return self._x_win |
271 | + |
272 | + @property |
273 | + def name(self): |
274 | + """Get the window name. |
275 | + |
276 | + Note: This may change according to the current locale. If you want a unique |
277 | + string to match windows against, use the x_id instead. |
278 | + |
279 | + """ |
280 | + return self._view_iface.Name() |
281 | + |
282 | + @property |
283 | + def title(self): |
284 | + """Get the window title. |
285 | + |
286 | + This may be different from the application name. |
287 | + |
288 | + Note that this may change depending on the current locale. |
289 | + |
290 | + """ |
291 | + return self._getProperty('_NET_WM_NAME') |
292 | + |
293 | + @property |
294 | + def geometry(self): |
295 | + """Get the geometry for this window. |
296 | + |
297 | + Returns a tuple containing (x, y, width, height). |
298 | + |
299 | + """ |
300 | + # FIXME: We need to use the gdk window here to get the real coordinates |
301 | + geometry = self._x_win.get_geometry() |
302 | + origin = gdk.window_foreign_new(self._xid).get_origin() |
303 | + return (origin[0], origin[1], geometry.width, geometry.height) |
304 | + |
305 | + @property |
306 | + def is_maximized(self): |
307 | + """Is the window maximized? |
308 | + |
309 | + Maximized in this case means both maximized |
310 | + vertically and horizontally. If a window is only maximized in one |
311 | + direction it is not considered maximized. |
312 | + |
313 | + """ |
314 | + win_state = self._get_window_states() |
315 | + return '_NET_WM_STATE_MAXIMIZED_VERT' in win_state and \ |
316 | + '_NET_WM_STATE_MAXIMIZED_HORZ' in win_state |
317 | + |
318 | + @property |
319 | + def application(self): |
320 | + """Get the application that owns this window. |
321 | + |
322 | + This method may return None if the window does not have an associated |
323 | + application. The 'desktop' window is one such example. |
324 | + |
325 | + """ |
326 | + # BAMF returns a list of parents since some windows don't have an |
327 | + # associated application. For these windows we return none. |
328 | + parents = self._view_iface.Parents() |
329 | + if parents: |
330 | + return BamfApplication(parents[0]) |
331 | + else: |
332 | + return None |
333 | + |
334 | + @property |
335 | + def user_visible(self): |
336 | + """Is this window visible to the user in the switcher?""" |
337 | + return self._view_iface.UserVisible() |
338 | + |
339 | + @property |
340 | + def is_hidden(self): |
341 | + """Is this window hidden? |
342 | + |
343 | + Windows are hidden when the 'Show Desktop' mode is activated. |
344 | + |
345 | + """ |
346 | + win_state = self._get_window_states() |
347 | + return '_NET_WM_STATE_HIDDEN' in win_state |
348 | + |
349 | + @property |
350 | + def is_focused(self): |
351 | + """Is this window focused?""" |
352 | + win_state = self._get_window_states() |
353 | + return '_NET_WM_STATE_FOCUSED' in win_state |
354 | + |
355 | + @property |
356 | + def is_valid(self): |
357 | + """Is this window object valid? |
358 | + |
359 | + Invalid windows are caused by windows closing during the construction of |
360 | + this object instance. |
361 | + |
362 | + """ |
363 | + return not self._x_win is None |
364 | + |
365 | + @property |
366 | + def monitor(self): |
367 | + """Returns the monitor to which the windows belongs to""" |
368 | + return self._window_iface.Monitor() |
369 | + |
370 | + @property |
371 | + def closed(self): |
372 | + """Returns True if the window has been closed""" |
373 | + # This will return False when the window is closed and then removed from BUS |
374 | + try: |
375 | + return (self._window_iface.GetXid() != self.x_id) |
376 | + except: |
377 | + return True |
378 | + |
379 | + def close(self): |
380 | + """Close the window.""" |
381 | + |
382 | + self._setProperty('_NET_CLOSE_WINDOW', [0, 0]) |
383 | + |
384 | + def set_focus(self): |
385 | + self._x_win.set_input_focus(X.RevertToParent, X.CurrentTime) |
386 | + self._x_win.configure(stack_mode=X.Above) |
387 | + |
388 | + def __repr__(self): |
389 | + return "<BamfWindow '%s'>" % (self.title if self._x_win else str(self._xid)) |
390 | + |
391 | + def _getProperty(self, _type): |
392 | + """Get an X11 property. |
393 | + |
394 | + _type is a string naming the property type. win is the X11 window object. |
395 | + |
396 | + """ |
397 | + atom = self._x_win.get_full_property(_X_DISPLAY.get_atom(_type), X.AnyPropertyType) |
398 | + if atom: |
399 | + return atom.value |
400 | + |
401 | + def _setProperty(self, _type, data, mask=None): |
402 | + if type(data) is str: |
403 | + dataSize = 8 |
404 | + else: |
405 | + # data length must be 5 - pad with 0's if it's short, truncate otherwise. |
406 | + data = (data + [0] * (5 - len(data)))[:5] |
407 | + dataSize = 32 |
408 | + |
409 | + ev = protocol.event.ClientMessage(window=self._x_win, client_type=_X_DISPLAY.get_atom(_type), data=(dataSize, data)) |
410 | + |
411 | + if not mask: |
412 | + mask = (X.SubstructureRedirectMask | X.SubstructureNotifyMask) |
413 | + self._x_root_win.send_event(ev, event_mask=mask) |
414 | + _X_DISPLAY.sync() |
415 | + |
416 | + def _get_window_states(self): |
417 | + """Return a list of strings representing the current window state.""" |
418 | + |
419 | + _X_DISPLAY.sync() |
420 | + return map(_X_DISPLAY.get_atom_name, self._getProperty('_NET_WM_STATE')) |
421 | |
422 | === modified file 'tests/autopilot/unity/emulators/icons.py' |
423 | === modified file 'tests/autopilot/unity/emulators/launcher.py' |
424 | === modified file 'tests/autopilot/unity/tests/__init__.py' |
425 | --- tests/autopilot/unity/tests/__init__.py 2012-05-16 23:02:38 +0000 |
426 | +++ tests/autopilot/unity/tests/__init__.py 2012-05-23 10:30:27 +0000 |
427 | @@ -122,3 +122,228 @@ |
428 | """ |
429 | set_log_severity(component, level) |
430 | |
431 | +<<<<<<< TREE |
432 | +======= |
433 | + |
434 | +class VideoCapturedTestCase(LoggedTestCase): |
435 | + """Video capture autopilot tests, saving the results if the test failed.""" |
436 | + |
437 | + _recording_app = '/usr/bin/recordmydesktop' |
438 | + _recording_opts = ['--no-sound', '--no-frame', '-o',] |
439 | + |
440 | + def setUp(self): |
441 | + super(VideoCapturedTestCase, self).setUp() |
442 | + global video_recording_enabled |
443 | + if video_recording_enabled and not self._have_recording_app(): |
444 | + video_recording_enabled = False |
445 | + logger.warning("Disabling video capture since '%s' is not present", self._recording_app) |
446 | + |
447 | + if video_recording_enabled: |
448 | + self._test_passed = True |
449 | + self.addOnException(self._on_test_failed) |
450 | + self.addCleanup(self._stop_video_capture) |
451 | + self._start_video_capture() |
452 | + |
453 | + def _have_recording_app(self): |
454 | + return os.path.exists(self._recording_app) |
455 | + |
456 | + def _start_video_capture(self): |
457 | + args = self._get_capture_command_line() |
458 | + self._capture_file = self._get_capture_output_file() |
459 | + self._ensure_directory_exists_but_not_file(self._capture_file) |
460 | + args.append(self._capture_file) |
461 | + logger.debug("Starting: %r", args) |
462 | + self._capture_process = Popen(args, stdout=PIPE, stderr=STDOUT) |
463 | + |
464 | + def _stop_video_capture(self): |
465 | + """Stop the video capture. If the test failed, save the resulting file.""" |
466 | + |
467 | + if self._test_passed: |
468 | + # We use kill here because we don't want the recording app to start |
469 | + # encoding the video file (since we're removing it anyway.) |
470 | + self._capture_process.kill() |
471 | + self._capture_process.wait() |
472 | + else: |
473 | + self._capture_process.terminate() |
474 | + self._capture_process.wait() |
475 | + if self._capture_process.returncode != 0: |
476 | + self.addDetail('video capture log', text_content(self._capture_process.stdout.read())) |
477 | + self._capture_process = None |
478 | + |
479 | + def _get_capture_command_line(self): |
480 | + return [self._recording_app] + self._recording_opts |
481 | + |
482 | + def _get_capture_output_file(self): |
483 | + return os.path.join(video_record_directory, '%s.ogv' % (self.shortDescription())) |
484 | + |
485 | + def _ensure_directory_exists_but_not_file(self, file_path): |
486 | + dirpath = os.path.dirname(file_path) |
487 | + if not os.path.exists(dirpath): |
488 | + os.makedirs(dirpath) |
489 | + elif os.path.exists(file_path): |
490 | + logger.warning("Video capture file '%s' already exists, deleting.", file_path) |
491 | + os.remove(file_path) |
492 | + |
493 | + def _on_test_failed(self, ex_info): |
494 | + """Called when a test fails.""" |
495 | + self._test_passed = False |
496 | + |
497 | + |
498 | +class AutopilotTestCase(VideoCapturedTestCase, KeybindingsHelper): |
499 | + """Wrapper around testtools.TestCase that takes care of some cleaning.""" |
500 | + |
501 | + run_test_with = GlibRunner |
502 | + |
503 | + KNOWN_APPS = { |
504 | + 'Character Map' : { |
505 | + 'desktop-file': 'gucharmap.desktop', |
506 | + 'process-name': 'gucharmap', |
507 | + }, |
508 | + 'Calculator' : { |
509 | + 'desktop-file': 'gcalctool.desktop', |
510 | + 'process-name': 'gcalctool', |
511 | + }, |
512 | + 'Mahjongg' : { |
513 | + 'desktop-file': 'mahjongg.desktop', |
514 | + 'process-name': 'mahjongg', |
515 | + }, |
516 | + 'Remmina' : { |
517 | + 'desktop-file': 'remmina.desktop', |
518 | + 'process-name': 'remmina', |
519 | + }, |
520 | + 'System Settings' : { |
521 | + 'desktop-file': 'gnome-control-center.desktop', |
522 | + 'process-name': 'gnome-control-center', |
523 | + }, |
524 | + 'Text Editor' : { |
525 | + 'desktop-file': 'gedit.desktop', |
526 | + 'process-name': 'gedit', |
527 | + }, |
528 | + } |
529 | + |
530 | + def setUp(self): |
531 | + super(AutopilotTestCase, self).setUp() |
532 | + self.bamf = Bamf() |
533 | + self.keyboard = Keyboard() |
534 | + self.mouse = Mouse() |
535 | + self.dash = Dash() |
536 | + self.hud = Hud() |
537 | + self.launcher = self._get_launcher_controller() |
538 | + self.panels = self._get_panel_controller() |
539 | + self.switcher = Switcher() |
540 | + self.window_manager = self._get_window_manager() |
541 | + self.workspace = WorkspaceManager() |
542 | + self.screen_geo = ScreenGeometry() |
543 | + self.addCleanup(self.workspace.switch_to, self.workspace.current_workspace) |
544 | + self.addCleanup(Keyboard.cleanup) |
545 | + self.addCleanup(Mouse.cleanup) |
546 | + |
547 | + def start_app(self, app_name, files=[], locale=None): |
548 | + """Start one of the known apps, and kill it on tear down. |
549 | + |
550 | + If files is specified, start the application with the specified files. |
551 | + If locale is specified, the locale will be set when the application is launched. |
552 | + |
553 | + The method returns the BamfApplication instance. |
554 | + |
555 | + """ |
556 | + if locale: |
557 | + os.putenv("LC_ALL", locale) |
558 | + self.addCleanup(os.unsetenv, "LC_ALL") |
559 | + logger.info("Starting application '%s' with files %r in locale %s", app_name, files, locale) |
560 | + else: |
561 | + logger.info("Starting application '%s' with files %r", app_name, files) |
562 | + |
563 | + app = self.KNOWN_APPS[app_name] |
564 | + self.bamf.launch_application(app['desktop-file'], files) |
565 | + apps = self.bamf.get_running_applications_by_desktop_file(app['desktop-file']) |
566 | + self.addCleanup(call, "kill `pidof %s`" % (app['process-name']), shell=True) |
567 | + self.assertThat(len(apps), Equals(1)) |
568 | + return apps[0] |
569 | + |
570 | + def close_all_app(self, app_name): |
571 | + """Close all instances of the app_name.""" |
572 | + app = self.KNOWN_APPS[app_name] |
573 | + pids = check_output(["pidof", app['process-name']]).split() |
574 | + if len(pids): |
575 | + call(["kill"] + pids) |
576 | + |
577 | + def get_app_instances(self, app_name): |
578 | + """Get BamfApplication instances for app_name.""" |
579 | + desktop_file = self.KNOWN_APPS[app_name]['desktop-file'] |
580 | + return self.bamf.get_running_applications_by_desktop_file(desktop_file) |
581 | + |
582 | + def app_is_running(self, app_name): |
583 | + """Returns true if an instance of the application is running.""" |
584 | + apps = self.get_app_instances(app_name) |
585 | + return len(apps) > 0 |
586 | + |
587 | + def call_gsettings_cmd(self, command, schema, *args): |
588 | + """Set a desktop wide gsettings option |
589 | + |
590 | + Using the gsettings command because there's a bug with importing |
591 | + from gobject introspection and pygtk2 simultaneously, and the Xlib |
592 | + keyboard layout bits are very unweildy. This seems like the best |
593 | + solution, even a little bit brutish. |
594 | + """ |
595 | + cmd = ['gsettings', command, schema] + list(args) |
596 | + # strip to remove the trailing \n. |
597 | + ret = check_output(cmd).strip() |
598 | + time.sleep(5) |
599 | + reset_display() |
600 | + return ret |
601 | + |
602 | + def set_unity_option(self, option_name, option_value): |
603 | + """Set an option in the unity compiz plugin options. |
604 | + |
605 | + The value will be set for the current test only. |
606 | + |
607 | + """ |
608 | + self.set_compiz_option("unityshell", option_name, option_value) |
609 | + |
610 | + def set_compiz_option(self, plugin_name, setting_name, setting_value): |
611 | + """Set setting `setting_name` in compiz plugin `plugin_name` to value `setting_value` |
612 | + for one test only. |
613 | + """ |
614 | + old_value = self._set_compiz_option(plugin_name, setting_name, setting_value) |
615 | + self.addCleanup(self._set_compiz_option, plugin_name, setting_name, old_value) |
616 | + # Allow unity time to respond to the new setting. |
617 | + time.sleep(0.5) |
618 | + |
619 | + def _set_compiz_option(self, plugin_name, option_name, option_value): |
620 | + logger.info("Setting compiz option '%s' in plugin '%s' to %r", |
621 | + option_name, plugin_name, option_value) |
622 | + plugin = Plugin(global_context, plugin_name) |
623 | + setting = Setting(plugin, option_name) |
624 | + old_value = setting.Value |
625 | + setting.Value = option_value |
626 | + global_context.Write() |
627 | + return old_value |
628 | + |
629 | + def _get_launcher_controller(self): |
630 | + controllers = LauncherController.get_all_instances() |
631 | + self.assertThat(len(controllers), Equals(1)) |
632 | + return controllers[0] |
633 | + |
634 | + def _get_panel_controller(self): |
635 | + controllers = PanelController.get_all_instances() |
636 | + self.assertThat(len(controllers), Equals(1)) |
637 | + return controllers[0] |
638 | + |
639 | + def _get_window_manager(self): |
640 | + managers = WindowManager.get_all_instances() |
641 | + self.assertThat(len(managers), Equals(1)) |
642 | + return managers[0] |
643 | + |
644 | + def assertVisibleWindowStack(self, stack_start): |
645 | + """Check that the visible window stack starts with the windows passed in. |
646 | + |
647 | + The start_stack is an iterable of BamfWindow objects. |
648 | + Minimised windows are skipped. |
649 | + |
650 | + """ |
651 | + stack = [win for win in self.bamf.get_open_windows() if not win.is_hidden] |
652 | + for pos, win in enumerate(stack_start): |
653 | + self.assertThat(stack[pos].x_id, Equals(win.x_id), |
654 | + "%r at %d does not equal %r" % (stack[pos], pos, win)) |
655 | +>>>>>>> MERGE-SOURCE |
656 | |
657 | === modified file 'tests/autopilot/unity/tests/test_launcher.py' |
658 | --- tests/autopilot/unity/tests/test_launcher.py 2012-05-17 22:49:33 +0000 |
659 | +++ tests/autopilot/unity/tests/test_launcher.py 2012-05-23 10:30:27 +0000 |
660 | @@ -660,6 +660,7 @@ |
661 | logger.info("Checking for duplicated launcher icon for application %s", test_app.name) |
662 | self.assertOnlyOneLauncherIcon(desktop_id=test_app.desktop_file) |
663 | |
664 | +<<<<<<< TREE |
665 | def test_killing_bamfdaemon_does_not_duplicate_application_xids(self): |
666 | """Killing bamfdaemon should not duplicate any xid in the model.""" |
667 | self.start_test_apps() |
668 | @@ -685,6 +686,33 @@ |
669 | |
670 | |
671 | class LauncherCaptureTests(UnityTestCase): |
672 | +======= |
673 | + def test_killing_bamfdaemon_does_not_duplicate_application_xids(self): |
674 | + """Killing bamfdaemon should not duplicate any xid in the model.""" |
675 | + self.start_test_apps() |
676 | + self.start_desktopless_test_apps() |
677 | + self.kill_and_restart_bamfdaemon() |
678 | + |
679 | + test_apps = self.get_test_apps() + self.get_desktopless_test_apps() |
680 | + |
681 | + for test_app in test_apps: |
682 | + logger.info("Checking for duplicated launcher icon for application %s", test_app.name) |
683 | + test_windows = [w.x_id for w in test_app.get_windows()] |
684 | + self.assertOnlyOneLauncherIcon(xids=test_windows) |
685 | + |
686 | + def test_killing_bamfdaemon_does_not_duplicate_any_icon_application_id(self): |
687 | + """Killing bamfdaemon should not duplicate any application ids in the model.""" |
688 | + self.start_test_apps() |
689 | + self.start_desktopless_test_apps() |
690 | + self.kill_and_restart_bamfdaemon() |
691 | + |
692 | + for icon in self.launcher.model.get_bamf_launcher_icons(): |
693 | + logger.info("Checking for duplicated launcher icon %s", icon.tooltip_text) |
694 | + self.assertOnlyOneLauncherIcon(application_id=icon.application_id) |
695 | + |
696 | + |
697 | +class LauncherCaptureTests(AutopilotTestCase): |
698 | +>>>>>>> MERGE-SOURCE |
699 | """Test the launchers ability to capture/not capture the mouse.""" |
700 | |
701 | screen_geo = ScreenGeometry() |