1#!/usr/bin/python3
2'''GNOME settings daemon tests for power plugin.'''
3
4__author__ = 'Martin Pitt <martin.pitt@ubuntu.com>'
5__copyright__ = '(C) 2013 Canonical Ltd.'
6__license__ = 'GPL v2 or later'
7
8import unittest
9import subprocess
10import sys
11import time
12import math
13import os
14import os.path
15import signal
16
17project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18builddir = os.environ.get('BUILDDIR', os.path.dirname(__file__))
19
20sys.path.insert(0, os.path.join(project_root, 'tests'))
21sys.path.insert(0, builddir)
22import gsdtestcase
23import gsdpowerconstants
24import gsdpowerenums
25
26import dbus
27from dbus.mainloop.glib import DBusGMainLoop
28
29DBusGMainLoop(set_as_default=True)
30
31import gi
32gi.require_version('UPowerGlib', '1.0')
33gi.require_version('UMockdev', '1.0')
34
35from gi.repository import Gio
36from gi.repository import GLib
37from gi.repository import UPowerGlib
38from gi.repository import UMockdev
39
40class PowerPluginBase(gsdtestcase.GSDTestCase):
41    '''Test the power plugin'''
42
43    COMMON_SUSPEND_METHODS=['Suspend', 'Hibernate', 'SuspendThenHibernate']
44
45    def setUp(self):
46        self.mock_external_monitor_file = os.path.join(self.workdir, 'GSD_MOCK_EXTERNAL_MONITOR')
47        os.environ['GSD_MOCK_EXTERNAL_MONITOR_FILE'] = self.mock_external_monitor_file
48
49        self.check_logind_gnome_session()
50        self.start_logind()
51        self.daemon_death_expected = False
52
53
54        # Setup umockdev testbed
55        self.testbed = UMockdev.Testbed.new()
56        os.environ['UMOCKDEV_DIR'] = self.testbed.get_root_dir()
57
58        # Create a mock backlight device
59        # Note that this function creates a different or even no backlight
60        # device based on the name of the test.
61        self.add_backlight()
62
63        if 'HAVE_SYSFS_BACKLIGHT' in os.environ and os.environ['HAVE_SYSFS_BACKLIGHT'] == '1':
64            self.skip_sysfs_backlight = False
65        else:
66            self.skip_sysfs_backlight = True
67
68        # start mock upowerd
69        (self.upowerd, self.obj_upower) = self.spawn_server_template(
70            'upower', {'DaemonVersion': '0.99', 'OnBattery': True, 'LidIsClosed': False}, stdout=subprocess.PIPE)
71        gsdtestcase.set_nonblock(self.upowerd.stdout)
72
73        # start mock gnome-shell screensaver
74        (self.screensaver, self.obj_screensaver) = self.spawn_server_template(
75            'gnome_screensaver', stdout=subprocess.PIPE)
76        gsdtestcase.set_nonblock(self.screensaver.stdout)
77
78        self.session_log_write = open(os.path.join(self.workdir, 'gnome-session.log'), 'wb', buffering=0)
79        self.session = subprocess.Popen(['gnome-session', '-f',
80                                         '-a', os.path.join(self.workdir, 'autostart'),
81                                         '--session=dummy', '--debug'],
82                                        stdout=self.session_log_write,
83                                        stderr=subprocess.STDOUT)
84
85        # wait until the daemon is on the bus
86        try:
87            self.wait_for_bus_object('org.gnome.SessionManager',
88                                     '/org/gnome/SessionManager')
89        except:
90            # on failure, print log
91            with open(self.session_log_write.name) as f:
92                print('----- session log -----\n%s\n------' % f.read())
93            raise
94
95        self.session_log = open(self.session_log_write.name, 'rb', buffering=0)
96
97        self.obj_session_mgr = self.session_bus_con.get_object(
98            'org.gnome.SessionManager', '/org/gnome/SessionManager')
99
100        self.start_mutter()
101
102        # Set up the gnome-session presence
103        obj_session_presence = self.session_bus_con.get_object(
104            'org.gnome.SessionManager', '/org/gnome/SessionManager/Presence')
105        self.obj_session_presence_props = dbus.Interface(obj_session_presence, dbus.PROPERTIES_IFACE)
106
107        # ensure that our tests don't lock the screen when the screensaver
108        # gets active
109        self.settings_screensaver = Gio.Settings(schema_id='org.gnome.desktop.screensaver')
110        self.settings_screensaver['lock-enabled'] = False
111
112        # Ensure we set up the external monitor state
113        self.set_has_external_monitor(False)
114
115        self.settings_gsd_power = Gio.Settings(schema_id='org.gnome.settings-daemon.plugins.power')
116
117        Gio.Settings.sync()
118        self.plugin_log_write = open(os.path.join(self.workdir, 'plugin_power.log'), 'wb', buffering=0)
119        # avoid painfully long delays of actions for tests
120        env = os.environ.copy()
121        # Disable PulseAudio output from libcanberra
122        env['CANBERRA_DRIVER'] = 'null'
123
124        # Use dummy script as testing backlight helper
125        env['GSD_BACKLIGHT_HELPER'] = os.path.join (project_root, 'plugins', 'power', 'test-backlight-helper')
126        if 'POWER_LD_PRELOAD' in env:
127            if 'LD_PRELOAD' in env and env['LD_PRELOAD']:
128                env['LD_PRELOAD'] = ':'.join((env['POWER_LD_PRELOAD'], env['LD_PRELOAD']))
129            else:
130                env['LD_PRELOAD'] = env['POWER_LD_PRELOAD']
131
132        # We need to redirect stdout to grab the debug messages.
133        # stderr is not needed by the testing infrastructure but is useful to
134        # see warnings and errors.
135        self.daemon = subprocess.Popen(
136            [os.path.join(builddir, 'gsd-power'), '--verbose'],
137            stdout=self.plugin_log_write,
138            env=env)
139
140        # you can use this for reading the current daemon log in tests
141        self.plugin_log = open(self.plugin_log_write.name, 'rb', buffering=0)
142
143        # wait until plugin is ready
144        timeout = 100
145        while timeout > 0:
146            time.sleep(0.1)
147            timeout -= 1
148            log = self.plugin_log.read()
149            if b'System inhibitor fd is' in log:
150                break
151
152        # always start with zero idle time
153        self.reset_idle_timer()
154
155        # flush notification log
156        self.p_notify.stdout.read()
157
158    def tearDown(self):
159
160        daemon_running = self.daemon.poll() == None
161        if daemon_running:
162            self.daemon.terminate()
163            self.daemon.wait()
164        self.plugin_log.close()
165        self.plugin_log_write.flush()
166        self.plugin_log_write.close()
167
168        self.upowerd.terminate()
169        self.upowerd.wait()
170        self.screensaver.terminate()
171        self.screensaver.wait()
172        self.stop_session()
173        self.stop_mutter()
174        self.stop_logind()
175
176        # reset all changed gsettings, so that tests are independent from each
177        # other
178        for schema in [self.settings_gsd_power, self.settings_session, self.settings_screensaver]:
179            for k in schema.list_keys():
180                schema.reset(k)
181        Gio.Settings.sync()
182
183        try:
184            os.unlink(self.mock_external_monitor_file)
185        except OSError:
186            pass
187
188        del self.testbed
189
190        # we check this at the end so that the other cleanup always happens
191        self.assertTrue(daemon_running or self.daemon_death_expected, 'daemon died during the test')
192
193    def stop_session(self):
194        '''Stop GNOME session'''
195
196        assert self.session
197        self.session.terminate()
198        self.session.wait()
199
200        self.session_log_write.flush()
201        self.session_log_write.close()
202        self.session_log.close()
203
204    def check_logind_gnome_session(self):
205        '''Check that gnome-session is built with logind support'''
206
207        path = GLib.find_program_in_path ('gnome-session')
208        assert(path)
209        (success, data) = GLib.file_get_contents (path)
210        lines = data.split(b'\n')
211        new_path = None
212        for line in lines:
213            items = line.split()
214            if items and items[0] == b'exec':
215                new_path = items[1]
216        if not new_path:
217            self.fail("could not get gnome-session's real path from %s" % path)
218        path = new_path
219        ldd = subprocess.Popen(['ldd', path], stdout=subprocess.PIPE)
220        out = ldd.communicate()[0]
221        if not b'libsystemd.so.0' in out:
222            self.fail('gnome-session is not built with logind support')
223
224    def get_status(self):
225        return self.obj_session_presence_props.Get('org.gnome.SessionManager.Presence', 'status')
226
227    def backlight_defaults(self):
228        # Hack to modify the brightness defaults before starting gsd-power.
229        # The alternative would be to create two separate test files.
230        if 'no_backlight' in self.id():
231            return None, None
232        elif 'legacy_brightness' in self.id():
233            return 15, 15
234        else:
235            return 100, 50
236
237    def add_backlight(self, _type="raw"):
238        max_brightness, brightness = self.backlight_defaults()
239
240        if max_brightness is None:
241            self.backlight = None
242            return
243
244        # Undo mangling done in GSD
245        if max_brightness >= 99:
246            max_brightness += 1
247            brightness += 1
248
249        # This needs to be done before starting gsd-power!
250        self.backlight = self.testbed.add_device('backlight', 'mock_backlight', None,
251                                                 ['type', _type,
252                                                  'max_brightness', str(max_brightness),
253                                                  'brightness', str(brightness)],
254                                                 [])
255
256    def get_brightness(self):
257        max_brightness = int(open(os.path.join(self.testbed.get_root_dir() + self.backlight, 'max_brightness')).read())
258
259        # self.backlight contains the leading slash, so os.path.join doesn't quite work
260        res = int(open(os.path.join(self.testbed.get_root_dir() + self.backlight, 'brightness')).read())
261        # Undo mangling done in GSD
262        if max_brightness >= 99:
263            res -= 1
264        return res
265
266    def set_has_external_monitor(self, external):
267        if external:
268            val = b'1'
269        else:
270            val = b'0'
271        GLib.file_set_contents (self.mock_external_monitor_file, val)
272
273    def set_composite_battery_discharging(self, icon='battery-good-symbolic'):
274        self.obj_upower.SetupDisplayDevice(
275            UPowerGlib.DeviceKind.BATTERY,
276            UPowerGlib.DeviceState.DISCHARGING,
277            50., 50., 100., # 50%, charge 50 of 100
278            0.01, 600, 0, # Discharge rate 0.01 with 600 seconds remaining, 0 time to full
279            True, # present
280            icon, UPowerGlib.DeviceLevel.NONE
281        )
282
283    def set_composite_battery_critical(self, icon='battery-caution-symbolic'):
284        self.obj_upower.SetupDisplayDevice(
285            UPowerGlib.DeviceKind.BATTERY,
286            UPowerGlib.DeviceState.DISCHARGING,
287            2., 2., 100., # 2%, charge 2 of 100
288            0.01, 60, 0, # Discharge rate 0.01 with 60 seconds remaining, 0 time to full
289            True, # present
290            icon, UPowerGlib.DeviceLevel.CRITICAL
291        )
292
293    def check_for_logout(self, timeout):
294        '''Check that logout is requested.
295
296        Fail after the given timeout.
297        '''
298        # check that it request logout
299        while timeout > 0:
300            time.sleep(1)
301            timeout -= 1
302            # check that it requested logout
303            log = self.session_log.read()
304            if log is None:
305                continue
306
307            if log and (b'GsmManager: requesting logout' in log):
308                break
309        else:
310            self.fail('timed out waiting for gnome-session logout call')
311
312    def check_no_logout(self, seconds):
313        '''Check that no logout is requested in the given time'''
314
315        # wait for specified time to ensure it didn't do anything
316        time.sleep(seconds)
317        # check that it did not logout
318        log = self.session_log.read()
319        if log:
320            self.assertFalse(b'GsmManager: requesting logout' in log, 'unexpected logout request')
321
322    def check_for_suspend(self, timeout, methods=COMMON_SUSPEND_METHODS):
323        '''Check that one of the given suspend methods are requested. Default
324        methods are Suspend() or Hibernate() but also HibernateThenSuspend()
325        is valid.
326
327        Fail after the given timeout.
328        '''
329
330        # Create a list of byte string needles to search for
331        needles = [' {} '.format(m).encode('ascii') for m in methods]
332
333        suspended = False
334
335        # check that it request suspend
336        while timeout > 0:
337            time.sleep(1)
338            timeout -= 1
339            # check that it requested suspend
340            log = self.logind.stdout.read()
341            if log is None:
342                continue
343
344            for n in needles:
345                if n in log:
346                    suspended = True
347                    break
348
349            if suspended:
350                break
351
352        if not suspended:
353            self.fail('timed out waiting for logind suspend call, methods: %s' % ', '.join(methods))
354
355    def check_for_lid_inhibited(self, timeout=0):
356        '''Check that the lid inhibitor has been added.
357
358        Fail after the given timeout.
359        '''
360        self.check_plugin_log('Adding lid switch system inhibitor', timeout,
361                              'Timed out waiting for lid inhibitor')
362
363    def check_for_lid_uninhibited(self, timeout=0):
364        '''Check that the lid inhibitor has been dropped.
365
366        Fail after the given timeout.
367        '''
368        self.check_plugin_log('uninhibiting lid close', timeout,
369                              'Timed out waiting for lid uninhibition')
370
371    def check_no_lid_uninhibited(self, timeout=0):
372        '''Check that the lid inhibitor has been dropped.
373
374        Fail after the given timeout.
375        '''
376        time.sleep(timeout)
377        # check that it requested uninhibition
378        log = self.plugin_log.read()
379
380        if b'uninhibiting lid close' in log:
381            self.fail('lid uninhibit should not have happened')
382
383    def check_no_suspend(self, seconds, methods=COMMON_SUSPEND_METHODS):
384        '''Check that no Suspend or Hibernate is requested in the given time'''
385
386        # wait for specified time to ensure it didn't do anything
387        time.sleep(seconds)
388        # check that it did not suspend or hibernate
389        log = self.logind.stdout.read()
390        if log is None:
391            return
392
393        for m in methods:
394            needle = ' {} '.format(m).encode('ascii')
395
396            self.assertFalse(needle in log, 'unexpected %s request' % m)
397
398    def check_suspend_no_hibernate(self, seconds):
399        '''Check that Suspend was requested and not Hibernate, in the given time'''
400
401        # wait for specified time to ensure it didn't do anything
402        time.sleep(seconds)
403        # check that it did suspend and didn't hibernate
404        log = self.logind.stdout.read()
405        if log:
406            self.assertTrue(b' Suspend' in log, 'missing Suspend request')
407            self.assertFalse(b' Hibernate' in log, 'unexpected Hibernate request')
408
409    def check_plugin_log(self, needle, timeout=0, failmsg=None):
410        '''Check that needle is found in the log within the given timeout.
411        Returns immediately when found.
412
413        Fail after the given timeout.
414        '''
415        if type(needle) == str:
416            needle = needle.encode('ascii')
417        # Fast path if the message was already logged
418        log = self.plugin_log.read()
419        if needle in log:
420            return
421
422        while timeout > 0:
423            time.sleep(0.5)
424            timeout -= 0.5
425
426            # read new data (lines) from the log
427            log = self.plugin_log.read()
428            if needle in log:
429                break
430        else:
431            if failmsg is not None:
432                self.fail(failmsg)
433            else:
434                self.fail('timed out waiting for needle "%s"' % needle)
435
436    def check_no_dim(self, seconds):
437        '''Check that mode is not set to dim in the given time'''
438
439        # wait for specified time to ensure it didn't do anything
440        time.sleep(seconds)
441        # check that we don't dim
442        log = self.plugin_log.read()
443        if log:
444            self.assertFalse(b'Doing a state transition: dim' in log, 'unexpected dim request')
445
446    def check_dim(self, timeout):
447        '''Check that mode is set to dim in the given time'''
448
449        self.check_plugin_log('Doing a state transition: dim', timeout,
450                              'timed out waiting for dim')
451
452    def check_undim(self, timeout):
453        '''Check that mode is set to normal in the given time'''
454
455        self.check_plugin_log('Doing a state transition: normal', timeout,
456                              'timed out waiting for normal mode')
457
458    def check_blank(self, timeout):
459        '''Check that blank is requested.
460
461        Fail after the given timeout.
462        '''
463
464        self.check_plugin_log('TESTSUITE: Blanked screen', timeout,
465                              'timed out waiting for blank')
466
467    def check_unblank(self, timeout):
468        '''Check that unblank is requested.
469
470        Fail after the given timeout.
471        '''
472
473        self.check_plugin_log('TESTSUITE: Unblanked screen', timeout,
474                              'timed out waiting for unblank')
475
476    def check_no_blank(self, seconds):
477        '''Check that no blank is requested in the given time'''
478
479        # wait for specified time to ensure it didn't blank
480        time.sleep(seconds)
481        # check that it did not blank
482        log = self.plugin_log.read()
483        self.assertFalse(b'TESTSUITE: Blanked screen' in log, 'unexpected blank request')
484
485    def check_no_unblank(self, seconds):
486        '''Check that no unblank is requested in the given time'''
487
488        # wait for specified time to ensure it didn't unblank
489        time.sleep(seconds)
490        # check that it did not unblank
491        log = self.plugin_log.read()
492        self.assertFalse(b'TESTSUITE: Unblanked screen' in log, 'unexpected unblank request')
493
494class PowerPluginTest1(PowerPluginBase):
495    def test_screensaver(self):
496        # Note that the screensaver mock object
497        # doesn't know how to get out of being active,
498        # be it if the lock is disabled, or not.
499
500        self.obj_screensaver.Lock()
501        # 0.3 second animation
502        time.sleep(1)
503        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
504
505        # blank is supposed to happen straight away
506        self.check_blank(2)
507
508        # Wait a bit for the active watch to be registered through dbus, then
509        # fake user activity and check that the screen is unblanked.
510        time.sleep(0.5)
511        self.reset_idle_timer()
512        self.check_unblank(2)
513
514        # Check for no blank before the normal blank timeout
515        self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
516        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
517
518        # and check for blank after the blank timeout
519        self.check_blank(10)
520
521        # Wait a bit for the active watch to be registered through dbus, then
522        # fake user activity and check that the screen is unblanked.
523        time.sleep(0.5)
524        self.reset_idle_timer()
525        self.check_unblank(2)
526
527        # check no blank and then blank
528        self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
529        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
530        self.check_blank(10)
531
532    def test_sleep_inactive_blank(self):
533        '''screensaver/blank interaction'''
534
535        # create suspend inhibitor which should have no effect on the idle
536        inhibit_id = self.obj_session_mgr.Inhibit(
537            'testsuite', dbus.UInt32(0), 'for testing',
538            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
539            dbus_interface='org.gnome.SessionManager')
540
541        self.obj_screensaver.SetActive(True)
542        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
543
544        # blank is supposed to happen straight away
545        self.check_blank(2)
546
547        # Wait a bit for the active watch to be registered through dbus, then
548        # fake user activity and check that the screen is unblanked.
549        time.sleep(0.5)
550        self.reset_idle_timer()
551        self.check_unblank(2)
552        if not self.skip_sysfs_backlight:
553            self.assertTrue(self.get_brightness() == gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS , 'incorrect unblanked brightness (%d != %d)' % (self.get_brightness(), gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS))
554
555        # Check for no blank before the normal blank timeout
556        self.check_no_blank(gsdpowerconstants.SCREENSAVER_TIMEOUT_BLANK - 4)
557        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
558
559        # and check for blank after the blank timeout
560        self.check_blank(10)
561
562        # Drop inhibitor
563        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
564                dbus_interface='org.gnome.SessionManager')
565
566class PowerPluginTest2(PowerPluginBase):
567    def test_screensaver_no_unblank(self):
568        '''Ensure the screensaver is not unblanked for new inhibitors.'''
569
570        # Lower idle delay a lot
571        self.settings_session['idle-delay'] = 1
572
573        # Bring down the screensaver
574        self.obj_screensaver.SetActive(True)
575        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
576
577        # Check that we blank
578        self.check_blank(2)
579
580        # Create the different possible inhibitors
581        inhibit_id = self.obj_session_mgr.Inhibit(
582            'testsuite', dbus.UInt32(0), 'for testing',
583            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE | gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND | gsdpowerenums.GSM_INHIBITOR_FLAG_LOGOUT),
584            dbus_interface='org.gnome.SessionManager')
585
586        self.check_no_unblank(2)
587
588        # Drop inhibitor
589        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
590                dbus_interface='org.gnome.SessionManager')
591
592        self.check_no_unblank(2)
593
594    def test_session_idle_delay(self):
595        '''verify that session idle delay works as expected when changed'''
596
597        # Verify that idle is set after 5 seconds
598        self.settings_session['idle-delay'] = 5
599        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
600        time.sleep(7)
601        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
602
603        # Raise the idle delay, and see that we stop being idle
604        # and get idle again after the timeout
605        self.settings_session['idle-delay'] = 10
606        # Resolve possible race condition, see also https://gitlab.gnome.org/GNOME/mutter/issues/113
607        time.sleep(0.2)
608        self.reset_idle_timer()
609        time.sleep(5)
610        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
611        time.sleep(10)
612        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
613
614        # Lower the delay again, and see that we get idle as we should
615        self.settings_session['idle-delay'] = 5
616        # Resolve possible race condition, see also https://gitlab.gnome.org/GNOME/mutter/issues/113
617        time.sleep(0.2)
618        self.reset_idle_timer()
619        time.sleep(2)
620        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
621        time.sleep(5)
622        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
623
624    def test_idle_time_reset_on_resume(self):
625        '''Check that the IDLETIME is reset when resuming'''
626
627        self.settings_screensaver['lock-enabled'] = False
628
629        # Go idle
630        self.settings_session['idle-delay'] = 5
631        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
632        time.sleep(7)
633        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_IDLE)
634
635        # Go to sleep
636        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [True], dbus_interface='org.freedesktop.DBus.Mock')
637        time.sleep(1)
638
639        # Wake up
640        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [False], dbus_interface='org.freedesktop.DBus.Mock')
641        time.sleep(1)
642
643        # And check we're not idle
644        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
645
646class PowerPluginTest3(PowerPluginBase):
647    def test_sleep_inactive_battery(self):
648        '''sleep-inactive-battery-timeout'''
649
650        self.settings_session['idle-delay'] = 2
651        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
652        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
653
654        # wait for idle delay; should not yet suspend
655        self.check_no_suspend(2)
656
657        # suspend should happen after inactive sleep timeout + 1 s notification
658        # delay + 1 s error margin
659        self.check_for_suspend(7)
660
661    def _test_suspend_no_hibernate(self):
662        '''suspend-no-hibernate'''
663
664        self.settings_session['idle-delay'] = 2
665        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
666        # Hibernate isn't possible, so it should end up suspending
667        # FIXME
668        self.settings_gsd_power['critical-battery-action'] = 'hibernate'
669
670        # wait for idle delay; should not yet hibernate
671        self.check_no_suspend(2)
672
673        # suspend should happen after inactive sleep timeout + 1 s notification
674        # delay + 1 s error margin
675        self.check_suspend_no_hibernate(7)
676
677    def test_sleep_inhibition(self):
678        '''Does not sleep under idle inhibition'''
679
680        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
681
682        self.settings_session['idle-delay'] = idle_delay
683        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
684        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
685
686        # create inhibitor
687        inhibit_id = self.obj_session_mgr.Inhibit(
688            'testsuite', dbus.UInt32(0), 'for testing',
689            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE | gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
690            dbus_interface='org.gnome.SessionManager')
691        self.check_no_suspend(idle_delay + 2)
692        self.check_no_dim(0)
693
694        # Check that we didn't go to idle either
695        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
696
697        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
698                dbus_interface='org.gnome.SessionManager')
699
700class PowerPluginTest4(PowerPluginBase):
701    def test_lock_on_lid_close(self):
702        '''Check that we do lock on lid closing, if the machine will not suspend'''
703
704        self.settings_screensaver['lock-enabled'] = True
705
706        # create inhibitor
707        inhibit_id = self.obj_session_mgr.Inhibit(
708            'testsuite', dbus.UInt32(0), 'for testing',
709            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
710            dbus_interface='org.gnome.SessionManager')
711
712        time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT)
713
714        # Close the lid
715        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
716        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
717
718        # Check that we've blanked
719        time.sleep(2)
720        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
721        self.check_blank(2)
722
723        # Drop the inhibit and see whether we suspend
724        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
725                dbus_interface='org.gnome.SessionManager')
726        # At this point logind should suspend for us
727        self.settings_screensaver['lock-enabled'] = False
728
729    def test_blank_on_lid_close(self):
730        '''Check that we do blank on lid closing, if the machine will not suspend'''
731
732        # create inhibitor
733        inhibit_id = self.obj_session_mgr.Inhibit(
734            'testsuite', dbus.UInt32(0), 'for testing',
735            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
736            dbus_interface='org.gnome.SessionManager')
737
738        time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT)
739
740        # Close the lid
741        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
742        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
743
744        # Check that we've blanked
745        self.check_blank(4)
746
747        # Drop the inhibit and see whether we suspend
748        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
749                dbus_interface='org.gnome.SessionManager')
750        # At this point logind should suspend for us
751
752    def test_unblank_on_lid_open(self):
753        '''Check that we do unblank on lid opening, if the machine will not suspend'''
754
755        # create inhibitor
756        inhibit_id = self.obj_session_mgr.Inhibit(
757            'testsuite', dbus.UInt32(0), 'for testing',
758            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_SUSPEND),
759            dbus_interface='org.gnome.SessionManager')
760
761        time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT)
762
763        # Close the lid
764        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
765        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
766
767        # Check that we've blanked
768        self.check_blank(2)
769
770        # Reopen the lid
771        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', False)
772        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
773
774        # Check for unblanking
775        self.check_unblank(2)
776
777        # Drop the inhibit
778        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
779                dbus_interface='org.gnome.SessionManager')
780
781class PowerPluginTest5(PowerPluginBase):
782    def test_dim(self):
783        '''Check that we do go to dim'''
784
785        # Wait and flush log
786        time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 1)
787        self.plugin_log.read()
788
789        idle_delay = math.ceil(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
790        self.reset_idle_timer()
791
792        self.settings_session['idle-delay'] = idle_delay
793        self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
794        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
795        # This is an absolute percentage, and our brightness is 0..100
796        dim_level = self.settings_gsd_power['idle-brightness'];
797
798        # Check that we're not idle
799        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
800
801        # Wait and check we're not idle, but dimmed
802        self.check_dim(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY + 1)
803        # Give time for the brightness to change
804        time.sleep(2)
805        if not self.skip_sysfs_backlight:
806            level = self.get_brightness();
807            self.assertTrue(level == dim_level, 'incorrect dim brightness (%d != %d)' % (level, dim_level))
808
809        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
810
811        # Bring down the screensaver
812        self.obj_screensaver.SetActive(True)
813        self.assertTrue(self.obj_screensaver.GetActive(), 'screensaver not turned on')
814
815        # Check that we blank
816        self.check_blank(2)
817
818        # Go to sleep
819        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [True], dbus_interface='org.freedesktop.DBus.Mock')
820        time.sleep(1)
821
822        # Wake up
823        self.logind_obj.EmitSignal('', 'PrepareForSleep', 'b', [False], dbus_interface='org.freedesktop.DBus.Mock')
824        time.sleep(1)
825
826        # And check that we have the pre-dim brightness
827        if not self.skip_sysfs_backlight:
828            self.assertTrue(self.get_brightness() == gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS , 'incorrect unblanked brightness (%d != %d)' % (self.get_brightness(), gsdpowerconstants.GSD_MOCK_DEFAULT_BRIGHTNESS))
829
830    def test_lid_close_inhibition(self):
831        '''Check that we correctly inhibit suspend with an external monitor'''
832
833        # Wait and flush log
834        time.sleep (gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 1)
835        self.plugin_log.read()
836
837        # Add an external monitor
838        self.set_has_external_monitor(True)
839        self.check_for_lid_inhibited(1)
840
841        # Check that we do not uninhibit with the external monitor attached
842        self.check_no_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT + 1)
843
844        # Close the lid
845        self.obj_upower.Set('org.freedesktop.UPower', 'LidIsClosed', True)
846        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
847        time.sleep(0.5)
848
849        # Unplug the external monitor
850        self.set_has_external_monitor(False)
851
852        # Check that no action happens during the safety time minus 1 second
853        self.check_no_lid_uninhibited(gsdpowerconstants.LID_CLOSE_SAFETY_TIMEOUT - 1)
854        # Check that we're uninhibited after the safety time
855        self.check_for_lid_uninhibited(4)
856
857class PowerPluginTest6(PowerPluginBase):
858    def test_notify_critical_battery(self):
859        '''action on critical battery'''
860
861        self.set_composite_battery_discharging()
862
863        time.sleep(2)
864
865        self.set_composite_battery_critical()
866
867        # Check that it was picked up
868        self.check_plugin_log('EMIT: charge-critical', 2)
869
870        # Wait a bit longer to ensure event has been fired
871        time.sleep(0.5)
872        # we should have gotten a notification now
873        notify_log = self.p_notify.stdout.read()
874
875        # verify notification
876        self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*battery critical.*"')
877
878    def test_notify_critical_battery_on_start(self):
879        '''action on critical battery on startup'''
880
881        self.set_composite_battery_critical()
882
883        # Check that it was picked up
884        self.check_plugin_log('EMIT: charge-critical', 2)
885
886        time.sleep(0.5)
887
888        # we should have gotten a notification by now
889        notify_log = self.p_notify.stdout.read()
890
891        # verify notification
892        self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* "battery-caution-symbolic" ".*battery critical.*"')
893
894    def test_notify_device_battery(self):
895        '''critical power level notification for device batteries'''
896
897        # Set internal battery to discharging
898        self.set_composite_battery_discharging()
899
900        # Add a device battery
901        bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
902        self.obj_upower.AddObject(bat2_path,
903                                  'org.freedesktop.UPower.Device',
904                                  {
905                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
906                                      'IsPresent': dbus.Boolean(True, variant_level=1),
907                                      'Model': dbus.String('Bat1', variant_level=1),
908                                      'Percentage': dbus.Double(40.0, variant_level=1),
909                                      'TimeToEmpty': dbus.Int64(1600, variant_level=1),
910                                      'EnergyFull': dbus.Double(100.0, variant_level=1),
911                                      'Energy': dbus.Double(40.0, variant_level=1),
912                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
913                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
914                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.NONE, variant_level=1),
915                                   }, dbus.Array([], signature='(ssss)'))
916
917        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
918        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
919                                   dbus_interface='org.freedesktop.DBus.Mock')
920        time.sleep(1)
921
922        # now change the mouse battery to critical charge
923        obj_bat2.Set('org.freedesktop.UPower.Device', 'TimeToEmpty',
924                     dbus.Int64(30, variant_level=1),
925                     dbus_interface=dbus.PROPERTIES_IFACE)
926        obj_bat2.Set('org.freedesktop.UPower.Device', 'Energy',
927                     dbus.Double(0.5, variant_level=1),
928                     dbus_interface=dbus.PROPERTIES_IFACE)
929        obj_bat2.Set('org.freedesktop.UPower.Device', 'WarningLevel',
930                     dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
931                     dbus_interface=dbus.PROPERTIES_IFACE)
932        obj_bat2.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
933        self.obj_upower.EmitSignal('', 'DeviceChanged', 'o', [bat2_path],
934                                   dbus_interface='org.freedesktop.DBus.Mock')
935
936        self.check_plugin_log('EMIT: charge-critical', 2)
937        time.sleep(0.5)
938
939        # we should have gotten a notification by now
940        notify_log = self.p_notify.stdout.read()
941
942        # verify notification
943        self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"')
944
945    def test_notify_device_spam(self):
946        '''no repeat notifications for device batteries'''
947
948        # Set internal battery to discharging
949        self.set_composite_battery_discharging()
950
951        # Add a device battery
952        bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
953        self.obj_upower.AddObject(bat2_path,
954                                  'org.freedesktop.UPower.Device',
955                                  {
956                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
957                                      'IsPresent': dbus.Boolean(True, variant_level=1),
958                                      'Model': dbus.String('Bat1', variant_level=1),
959                                      'Serial': dbus.String('12345678', variant_level=1),
960                                      'Percentage': dbus.Double(10.0, variant_level=1),
961                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
962                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
963                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
964                                   }, dbus.Array([], signature='(ssss)'))
965
966        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
967        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
968                                   dbus_interface='org.freedesktop.DBus.Mock')
969        time.sleep(1)
970
971        self.check_plugin_log('EMIT: charge-low', 2)
972        time.sleep(0.5)
973
974        # we should have gotten a notification by now
975        notify_log = self.p_notify.stdout.read()
976
977        # verify notification
978        self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*\([0-9.]+%\).*"')
979
980        # Disconnect mouse
981        self.obj_upower.RemoveObject(bat2_path)
982        time.sleep(0.5)
983
984        # Reconnect mouse
985        self.obj_upower.AddObject(bat2_path,
986                                  'org.freedesktop.UPower.Device',
987                                  {
988                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
989                                      'IsPresent': dbus.Boolean(True, variant_level=1),
990                                      'Model': dbus.String('Bat1', variant_level=1),
991                                      'Serial': dbus.String('12345678', variant_level=1),
992                                      'Percentage': dbus.Double(10.0, variant_level=1),
993                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
994                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
995                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
996                                   }, dbus.Array([], signature='(ssss)'))
997
998        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
999        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
1000                                   dbus_interface='org.freedesktop.DBus.Mock')
1001        time.sleep(1)
1002
1003        # we shouldn't have gotten a notification by now
1004        notify_log = self.p_notify.stdout.read()
1005        self.assertIsNone(notify_log)
1006
1007        # Disconnect mouse
1008        self.obj_upower.RemoveObject(bat2_path)
1009        time.sleep(0.5)
1010
1011        # Reconnect mouse with critical battery level
1012        self.obj_upower.AddObject(bat2_path,
1013                                  'org.freedesktop.UPower.Device',
1014                                  {
1015                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
1016                                      'IsPresent': dbus.Boolean(True, variant_level=1),
1017                                      'Model': dbus.String('Bat1', variant_level=1),
1018                                      'Serial': dbus.String('12345678', variant_level=1),
1019                                      'Percentage': dbus.Double(5.0, variant_level=1),
1020                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
1021                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
1022                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
1023                                   }, dbus.Array([], signature='(ssss)'))
1024
1025        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
1026        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
1027                                   dbus_interface='org.freedesktop.DBus.Mock')
1028        time.sleep(1)
1029
1030        # Verify new warning
1031        self.check_plugin_log('EMIT: charge-critical', 2)
1032        time.sleep(0.5)
1033
1034        # we should have gotten a notification by now
1035        notify_log = self.p_notify.stdout.read()
1036
1037        # verify notification
1038        self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*very low.* power.*\([0-9.]+%\).*"')
1039
1040    def test_notify_device_battery_coarse_level(self):
1041        '''critical power level notification for device batteries with coarse level'''
1042
1043        # Set internal battery to discharging
1044        self.set_composite_battery_discharging()
1045
1046        # Add a device battery
1047        bat2_path = '/org/freedesktop/UPower/devices/' + 'mock_MOUSE_BAT1'
1048        self.obj_upower.AddObject(bat2_path,
1049                                  'org.freedesktop.UPower.Device',
1050                                  {
1051                                      'PowerSupply': dbus.Boolean(False, variant_level=1),
1052                                      'IsPresent': dbus.Boolean(True, variant_level=1),
1053                                      'Model': dbus.String('Bat1', variant_level=1),
1054                                      'Percentage': dbus.Double(40.0, variant_level=1),
1055                                      'BatteryLevel': dbus.UInt32(UPowerGlib.DeviceLevel.LOW, variant_level=1),
1056                                      'TimeToEmpty': dbus.Int64(1600, variant_level=1),
1057                                      'EnergyFull': dbus.Double(100.0, variant_level=1),
1058                                      'Energy': dbus.Double(40.0, variant_level=1),
1059                                      'State': dbus.UInt32(UPowerGlib.DeviceState.DISCHARGING, variant_level=1),
1060                                      'Type': dbus.UInt32(UPowerGlib.DeviceKind.MOUSE, variant_level=1),
1061                                      'WarningLevel': dbus.UInt32(UPowerGlib.DeviceLevel.NONE, variant_level=1),
1062                                   }, dbus.Array([], signature='(ssss)'))
1063
1064        obj_bat2 = self.system_bus_con.get_object('org.freedesktop.UPower', bat2_path)
1065        self.obj_upower.EmitSignal('', 'DeviceAdded', 'o', [bat2_path],
1066                                   dbus_interface='org.freedesktop.DBus.Mock')
1067        time.sleep(1)
1068
1069        # now change the mouse battery to critical charge
1070        obj_bat2.Set('org.freedesktop.UPower.Device', 'TimeToEmpty',
1071                     dbus.Int64(30, variant_level=1),
1072                     dbus_interface=dbus.PROPERTIES_IFACE)
1073        obj_bat2.Set('org.freedesktop.UPower.Device', 'Energy',
1074                     dbus.Double(0.5, variant_level=1),
1075                     dbus_interface=dbus.PROPERTIES_IFACE)
1076        obj_bat2.Set('org.freedesktop.UPower.Device', 'WarningLevel',
1077                     dbus.UInt32(UPowerGlib.DeviceLevel.CRITICAL, variant_level=1),
1078                     dbus_interface=dbus.PROPERTIES_IFACE)
1079        obj_bat2.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
1080        self.obj_upower.EmitSignal('', 'DeviceChanged', 'o', [bat2_path],
1081                                   dbus_interface='org.freedesktop.DBus.Mock')
1082
1083        self.check_plugin_log('EMIT: charge-critical', 2)
1084        time.sleep(0.5)
1085
1086        # we should have gotten a notification by now
1087        notify_log = self.p_notify.stdout.read()
1088
1089        # verify notification
1090        self.assertRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*Wireless mouse .*low.* power.*"')
1091        self.assertNotRegex(notify_log, b'[0-9.]+ Notify "Power" .* ".*" ".*\([0-9.]+%\).*"')
1092
1093    def test_forced_logout(self):
1094        '''Test forced logout'''
1095
1096        self.daemon_death_expected = True
1097        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
1098
1099        self.settings_session['idle-delay'] = idle_delay
1100        self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
1101        self.settings_gsd_power['sleep-inactive-battery-type'] = 'logout'
1102
1103        self.check_for_logout(idle_delay + 2)
1104
1105        # The notification should have been received before the logout, but it's saved anyway
1106        notify_log = self.p_notify.stdout.read()
1107        self.assertTrue(b'You will soon log out because of inactivity.' in notify_log)
1108
1109    def test_forced_logout_inhibition(self):
1110        '''Test we don't force logout when inhibited'''
1111
1112        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
1113
1114        self.settings_session['idle-delay'] = idle_delay
1115        self.settings_gsd_power['sleep-inactive-battery-timeout'] = idle_delay + 1
1116        self.settings_gsd_power['sleep-inactive-battery-type'] = 'logout'
1117
1118        # create suspend inhibitor which should stop us logging out
1119        inhibit_id = self.obj_session_mgr.Inhibit(
1120            'testsuite', dbus.UInt32(0), 'for testing',
1121            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_LOGOUT),
1122            dbus_interface='org.gnome.SessionManager')
1123
1124        self.check_no_logout(idle_delay + 3)
1125
1126        # Drop inhibitor
1127        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
1128                dbus_interface='org.gnome.SessionManager')
1129
1130class PowerPluginTest7(PowerPluginBase):
1131    def test_check_missing_kbd_brightness(self):
1132        ''' https://bugzilla.gnome.org/show_bug.cgi?id=793512 '''
1133
1134        obj_gsd_power_kbd = self.session_bus_con.get_object(
1135            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
1136        obj_gsd_power_kbd_props = dbus.Interface(obj_gsd_power_kbd, dbus.PROPERTIES_IFACE)
1137
1138        # Will return -1 if gsd-power crashed, and an exception if the code caught the problem
1139        with self.assertRaises(dbus.DBusException) as exc:
1140            kbd_brightness = obj_gsd_power_kbd_props.Get('org.gnome.SettingsDaemon.Power.Keyboard', 'Brightness')
1141
1142            # We should not have arrived here, if we did then the test failed, let's print this to help debugging
1143            print('Got keyboard brightness: {}'.format(kbd_brightness))
1144
1145        self.assertEqual(exc.exception.get_dbus_message(), 'Failed to get property Brightness on interface org.gnome.SettingsDaemon.Power.Keyboard')
1146
1147    def test_inhibitor_idletime(self):
1148        ''' https://bugzilla.gnome.org/show_bug.cgi?id=705942 '''
1149
1150        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
1151
1152        self.settings_session['idle-delay'] = idle_delay
1153        self.settings_gsd_power['sleep-inactive-battery-timeout'] = 5
1154        self.settings_gsd_power['sleep-inactive-battery-type'] = 'suspend'
1155
1156        # create inhibitor
1157        inhibit_id = self.obj_session_mgr.Inhibit(
1158            'testsuite', dbus.UInt32(0), 'for testing',
1159            dbus.UInt32(gsdpowerenums.GSM_INHIBITOR_FLAG_IDLE),
1160            dbus_interface='org.gnome.SessionManager')
1161        self.check_no_suspend(idle_delay + 2)
1162        self.check_no_dim(0)
1163
1164        # Check that we didn't go to idle either
1165        self.assertEqual(self.get_status(), gsdpowerenums.GSM_PRESENCE_STATUS_AVAILABLE)
1166
1167        self.obj_session_mgr.Uninhibit(dbus.UInt32(inhibit_id),
1168                dbus_interface='org.gnome.SessionManager')
1169
1170        self.check_no_suspend(2)
1171        self.check_no_dim(0)
1172
1173        time.sleep(5)
1174
1175        self.check_suspend_no_hibernate(7)
1176
1177    def disabled_test_unindle_on_ac_plug(self):
1178        idle_delay = round(gsdpowerconstants.MINIMUM_IDLE_DIM_DELAY / gsdpowerconstants.IDLE_DELAY_TO_IDLE_DIM_MULTIPLIER)
1179        self.settings_session['idle-delay'] = idle_delay
1180
1181        # Wait for idle
1182        self.check_dim(idle_delay + 2)
1183
1184        # Plug in the AC
1185        self.obj_upower.Set('org.freedesktop.UPower', 'OnBattery', False)
1186        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
1187
1188        # Check that we undim
1189        self.check_undim(gsdpowerconstants.POWER_UP_TIME_ON_AC / 2)
1190
1191        # And wait a little more to see us dim again
1192        self.check_dim(idle_delay + 2)
1193
1194        # Unplug the AC
1195        self.obj_upower.Set('org.freedesktop.UPower', 'OnBattery', True)
1196        self.obj_upower.EmitSignal('', 'Changed', '', [], dbus_interface='org.freedesktop.DBus.Mock')
1197
1198        # Check that we undim
1199        self.check_undim(gsdpowerconstants.POWER_UP_TIME_ON_AC / 2)
1200
1201        # And wait a little more to see us dim again
1202        self.check_dim(idle_delay + 2)
1203
1204class PowerPluginTest8(PowerPluginBase):
1205    def test_brightness_stepping(self):
1206        '''Check that stepping the backlight works as expected'''
1207
1208        if self.skip_sysfs_backlight:
1209            self.skipTest("sysfs backlight support required for test")
1210
1211        obj_gsd_power = self.session_bus_con.get_object(
1212            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
1213        obj_gsd_power_screen_iface = dbus.Interface(obj_gsd_power, 'org.gnome.SettingsDaemon.Power.Screen')
1214
1215        # Each of the step calls will only return when the value was written
1216        start = time.time()
1217        # We start at 50% and step by 5% each time
1218        obj_gsd_power_screen_iface.StepUp()
1219        self.assertEqual(self.get_brightness(), 55)
1220        obj_gsd_power_screen_iface.StepUp()
1221        self.assertEqual(self.get_brightness(), 60)
1222        obj_gsd_power_screen_iface.StepUp()
1223        self.assertEqual(self.get_brightness(), 65)
1224        obj_gsd_power_screen_iface.StepUp()
1225        self.assertEqual(self.get_brightness(), 70)
1226        stop = time.time()
1227        # This needs to take more than 0.8 seconds as each write is delayed by
1228        # 0.2 seconds by the test backlight helper
1229        self.assertGreater(stop - start, 0.8)
1230
1231        # Now, the same thing should work fine if we step multiple times,
1232        # even if we are so quick that compression will happen.
1233        # Use a list to keep rack of replies (as integer is immutable and would
1234        # not be modified in the outer scope)
1235        replies = [0]
1236
1237        def handle_reply(*args):
1238            replies[0] += 1
1239
1240        def last_reply(*args):
1241            replies[0] += 1
1242            loop.quit()
1243
1244        def error_handler(*args):
1245            loop.quit()
1246
1247        start = time.time()
1248        obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
1249        obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
1250        obj_gsd_power_screen_iface.StepDown(reply_handler=handle_reply, error_handler=error_handler)
1251        obj_gsd_power_screen_iface.StepDown(reply_handler=last_reply, error_handler=error_handler)
1252        loop = GLib.MainLoop()
1253        loop.run()
1254        stop = time.time()
1255
1256        # The calls need to be returned in order. As we got the last reply, all
1257        # others must have been received too.
1258        self.assertEqual(replies[0], 4)
1259        # Four steps down, so back at 50%
1260        self.assertEqual(self.get_brightness(), 50)
1261        # And compression must have happened, so it should take less than 0.8s
1262        self.assertLess(stop - start, 0.8)
1263
1264    def test_brightness_compression(self):
1265        '''Check that compression also happens when setting the property'''
1266
1267        if self.skip_sysfs_backlight:
1268            self.skipTest("sysfs backlight support required for test")
1269
1270        # Now test that the compression works correctly.
1271        # NOTE: Relies on the implementation detail, that the property setter
1272        #       returns immediately rather than waiting for the brightness to
1273        #       be updated.
1274        # Should this ever be fixed, then this will need to be changed to use
1275        # async dbus calls similar to the stepping code
1276
1277        obj_gsd_power = self.session_bus_con.get_object(
1278            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
1279        obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
1280
1281        # Quickly ramp the brightness up
1282        for brightness in range(70, 91):
1283            obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', brightness)
1284
1285        # The brightness of 80 should be in effect after slightly more than
1286        # 0.4 seconds. If compression does not work as expected, this would take
1287        # more than 5 seconds for the 20 steps.
1288        time.sleep(2.0)
1289        self.assertEqual(self.get_brightness(), 90)
1290
1291    def test_brightness_uevent(self):
1292        if self.skip_sysfs_backlight:
1293            self.skipTest("sysfs backlight support required for test")
1294
1295        obj_gsd_power = self.session_bus_con.get_object(
1296            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
1297        obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
1298
1299        brightness = obj_gsd_power_prop_iface.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
1300        self.assertEqual(50, brightness)
1301
1302        # Check that the brightness is updated if it was changed through some
1303        # other mechanism (e.g. firmware).
1304        # Set to 80+1 because of the GSD offset (see add_backlight).
1305        self.testbed.set_attribute(self.backlight, 'brightness', '81')
1306        self.testbed.uevent(self.backlight, 'change')
1307
1308        self.check_plugin_log('GsdBacklight: Got uevent', 1, 'gsd-power did not process uevent')
1309        time.sleep(0.2)
1310
1311        brightness = obj_gsd_power_prop_iface.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
1312        self.assertEqual(80, brightness)
1313
1314    def test_brightness_step(self):
1315        if self.skip_sysfs_backlight:
1316            self.skipTest("sysfs backlight support required for test")
1317
1318        # We cannot use check_plugin_log here because the startup check already
1319        # read the relevant message.
1320        log = open(self.plugin_log_write.name, 'rb').read()
1321        self.assertIn(b'Step size for backlight is 5.', log)
1322
1323    def test_legacy_brightness_step(self):
1324        if self.skip_sysfs_backlight:
1325            self.skipTest("sysfs backlight support required for test")
1326
1327        # We cannot use check_plugin_log here because the startup check already
1328        # read the relevant message.
1329        log = open(self.plugin_log_write.name, 'rb').read()
1330        self.assertIn(b'Step size for backlight is 1.', log)
1331
1332    def test_legacy_brightness_rounding(self):
1333        if self.skip_sysfs_backlight:
1334            self.skipTest("sysfs backlight support required for test")
1335
1336        obj_gsd_power = self.session_bus_con.get_object(
1337            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
1338        obj_gsd_power_prop_iface = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
1339
1340        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 0)
1341        time.sleep(0.4)
1342        self.assertEqual(self.get_brightness(), 0)
1343        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 10)
1344        time.sleep(0.4)
1345        self.assertEqual(self.get_brightness(), 2)
1346        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 20)
1347        time.sleep(0.4)
1348        self.assertEqual(self.get_brightness(), 3)
1349        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 25)
1350        time.sleep(0.4)
1351        self.assertEqual(self.get_brightness(), 4)
1352        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 49)
1353        time.sleep(0.4)
1354        self.assertEqual(self.get_brightness(), 7)
1355        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 50)
1356        time.sleep(0.4)
1357        self.assertEqual(self.get_brightness(), 8)
1358        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 56)
1359        time.sleep(0.4)
1360        self.assertEqual(self.get_brightness(), 8)
1361        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 57)
1362        time.sleep(0.4)
1363        self.assertEqual(self.get_brightness(), 9)
1364        obj_gsd_power_prop_iface.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 98)
1365        time.sleep(0.4)
1366        self.assertEqual(self.get_brightness(), 15)
1367
1368    def test_no_backlight(self):
1369        '''Check that backlight brightness DBus api without a backlight'''
1370
1371        obj_gsd_power = self.session_bus_con.get_object(
1372            'org.gnome.SettingsDaemon.Power', '/org/gnome/SettingsDaemon/Power')
1373        obj_gsd_power_props = dbus.Interface(obj_gsd_power, dbus.PROPERTIES_IFACE)
1374        obj_gsd_power_screen = dbus.Interface(obj_gsd_power, 'org.gnome.SettingsDaemon.Power.Screen')
1375
1376        # We expect -1 to be returned
1377        brightness = obj_gsd_power_props.Get('org.gnome.SettingsDaemon.Power.Screen', 'Brightness')
1378        self.assertEqual(brightness, -1)
1379
1380        # Trying to set the brightness
1381        with self.assertRaises(dbus.DBusException) as exc:
1382            obj_gsd_power_props.Set('org.gnome.SettingsDaemon.Power.Screen', 'Brightness', 1)
1383
1384        self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
1385
1386        with self.assertRaises(dbus.DBusException) as exc:
1387            obj_gsd_power_screen.StepUp()
1388
1389        self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
1390
1391        with self.assertRaises(dbus.DBusException) as exc:
1392            obj_gsd_power_screen.StepDown()
1393
1394        self.assertEqual(exc.exception.get_dbus_message(), 'No usable backlight could be found!')
1395
1396# avoid writing to stderr
1397unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
1398