1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging, os, tempfile, threading
6from autotest_lib.client.bin import test, utils
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.common_lib.cros import chrome
9
10POWER_MANAGER_SETTINGS = {
11    'plugged_dim_ms': 1000,
12    'plugged_off_ms': 5000,
13    'plugged_suspend_ms': 10000,
14    'unplugged_dim_ms': 1000,
15    'unplugged_off_ms': 5000,
16    'unplugged_suspend_ms': 10000,
17    'disable_idle_suspend': 0,
18    'ignore_external_policy': 1,
19}
20
21SUSPEND_TIMEOUT_MS = 30000
22
23
24class power_IdleSuspend(test.test):
25    """
26    Verify power manager tries to suspend while idle.
27
28    This test does not actually allow the system to suspend. Instead,
29    it replaces /sys/power/state with a pipe and waits until "mem" is
30    written to it. Such a write would normally cause suspend.
31    """
32    version = 1
33    mounts = ()
34
35    def initialize(self):
36        super(power_IdleSuspend, self).initialize()
37        self.mounts = []
38
39
40    def setup_power_manager(self):
41        """Configures powerd for the test."""
42        # create directory for temporary settings
43        self.tempdir = tempfile.mkdtemp(prefix='IdleSuspend.')
44        logging.info('using temporary directory %s', self.tempdir)
45
46        # override power manager settings
47        for key, val in POWER_MANAGER_SETTINGS.iteritems():
48            logging.info('overriding %s to %s', key, val)
49            tmp_path = '%s/%s' % (self.tempdir, key)
50            mount_path = '/usr/share/power_manager/%s' % key
51            utils.write_one_line(tmp_path, str(val))
52            utils.run('mount --bind %s %s' % (tmp_path, mount_path))
53            self.mounts.append(mount_path)
54
55        # override /sys/power/state with fifo
56        fifo_path = '%s/sys_power_state' % self.tempdir
57        os.mkfifo(fifo_path)
58        utils.run('mount --bind %s /sys/power/state' % fifo_path)
59        self.mounts.append('/sys/power/state')
60
61
62    def wait_for_suspend(self):
63        """Thread callback to watch for powerd to announce idle transition."""
64        # block reading new power state from /sys/power/state
65        sys_power_state = open('/sys/power/state')
66        self.new_power_state = sys_power_state.read()
67        logging.info('new power state: %s', self.new_power_state)
68
69
70    def run_once(self):
71        with chrome.Chrome():
72            # stop power manager before reconfiguring
73            logging.info('stopping powerd')
74            utils.run('stop powerd')
75
76            # override power manager settings
77            self.setup_power_manager()
78
79            # start thread to wait for suspend
80            self.new_power_state = None
81            thread = threading.Thread(target=self.wait_for_suspend)
82            thread.start()
83
84            # touch OOBE completed file so powerd won't ignore idle state.
85            utils.run('touch /home/chronos/.oobe_completed')
86
87            # restart powerd to pick up new settings
88            logging.info('restarting powerd')
89            utils.run('start powerd')
90
91            # wait for idle suspend
92            thread.join(SUSPEND_TIMEOUT_MS / 1000.)
93
94            if thread.is_alive():
95                # join timed out - powerd didn't write to /sys/power/state
96                raise error.TestFail('timed out waiting for suspend')
97
98            if self.new_power_state is None:
99                # probably an exception in the thread, check the log
100                raise error.TestError('reader thread crashed')
101
102            if self.new_power_state.strip() not in ['mem', 'freeze']:
103                err_str = 'bad power state written to /sys/power/state'
104                raise error.TestFail(err_str)
105
106
107    def cleanup(self):
108        # restore original power manager settings
109        for mount in self.mounts:
110            logging.info('restoring %s', mount)
111            utils.run('umount -l %s' % mount)
112
113        # restart powerd to pick up original settings
114        logging.info('restarting powerd')
115        utils.run('restart powerd')
116
117        super(power_IdleSuspend, self).cleanup()
118