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, signal, time
6
7import common
8from autotest_lib.client.bin import test, utils
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.cros import constants, cros_logging, cros_ui, login
11
12class desktopui_HangDetector(test.test):
13    """
14    This class enables browser process hang detection, simulates a hang
15    by sending a SIGSTOP to the browser, and then checks to see that it
16    got killed and restarted successfully -- without the UI getting bounced.
17    """
18    version = 1
19
20
21    def initialize(self):
22        self._pauser = cros_logging.LogRotationPauser()
23        self._pauser.begin()
24
25
26    def _get_oldest_pid_by_name(self, name):
27        try:
28            pid = utils.get_oldest_pid_by_name(name)
29            logging.debug('Found %d for %s', pid, name)
30        except error.CmdError as e:
31            raise error.TestError('Could not find pid of %s: %r' % (name, e))
32        except ValueError as e:
33            raise error.TestError('Got bad pid looking up %s: %r' % (name, e))
34        if not pid:
35            raise error.TestError('Got no pid looking up %s' % name)
36        return pid
37
38
39    def run_once(self):
40        # Create magic file to enable browser liveness checking and
41        # bounce the session manager to pick up the flag file.
42        cros_ui.stop()
43        os.mknod(constants.ENABLE_BROWSER_HANG_DETECTION_FILE)
44        cros_ui.start()
45
46        browser_pid = self._get_oldest_pid_by_name(constants.BROWSER)
47        sm_pid = self._get_oldest_pid_by_name(constants.SESSION_MANAGER)
48
49        # Reading the log is the best way to watch for the hang detector.
50        reader = cros_logging.LogReader()
51        reader.set_start_by_current()
52
53        # To simulate a hang, STOP the browser and wait for it to get
54        # hit by the session manager.  It won't actually exit until it gets
55        # a SIGCONT, though.
56        try:
57            os.kill(browser_pid, signal.SIGSTOP)  # Simulate hang.
58        except OSError as e:
59            raise error.TestError('Cannot STOP browser: %r' % e)
60
61        # Watch for hang detection.
62        utils.poll_for_condition(
63            condition=lambda: reader.can_find('Aborting browser process.'),
64            exception=utils.TimeoutError('Waiting for hang detector.'),
65            sleep_interval=5,
66            timeout=60)
67
68        try:
69            os.kill(browser_pid, signal.SIGCONT)  # Allow browser to die.
70        except OSError as e:
71            raise error.TestError('Cannot CONT browser: %r' % e)
72
73        # Wait for old browser process to be gone.
74        utils.poll_for_condition(
75            condition= lambda: utils.pid_is_alive(browser_pid),
76            exception=utils.TimeoutError(
77                'Browser does not seem to have restarted!'),
78            timeout=60)
79
80        # Wait for new browser to come up.
81        login.wait_for_browser()
82        if sm_pid != self._get_oldest_pid_by_name(constants.SESSION_MANAGER):
83            raise error.TestFail('session_manager seems to have restarted')
84
85
86    def cleanup(self):
87        if os.path.exists(constants.ENABLE_BROWSER_HANG_DETECTION_FILE):
88            os.remove(constants.ENABLE_BROWSER_HANG_DETECTION_FILE)
89        self._pauser.end()
90