Merge lp:~sil2100/unity/fix-949448-5.0 into lp:unity

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
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://code.launchpad.net/~andyrock/unity/fix-949448/+merge/102860

== 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()