xref: /aosp_15_r20/external/autotest/client/cros/crash/crash_test.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li
5*9c5db199SXin Liimport glob
6*9c5db199SXin Liimport logging
7*9c5db199SXin Liimport os
8*9c5db199SXin Liimport random
9*9c5db199SXin Liimport re
10*9c5db199SXin Liimport shutil
11*9c5db199SXin Liimport time
12*9c5db199SXin Li
13*9c5db199SXin Liimport common
14*9c5db199SXin Lifrom autotest_lib.client.bin import test, utils
15*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
16*9c5db199SXin Lifrom autotest_lib.client.cros import constants, cros_logging
17*9c5db199SXin Li
18*9c5db199SXin Li
19*9c5db199SXin Li_CRASH_RUN_STATE_DIR = '/run/crash_reporter'
20*9c5db199SXin Li
21*9c5db199SXin Li
22*9c5db199SXin Liclass FilterOut:
23*9c5db199SXin Li    """contextmanager-compatible class to block certain crashes during tests."""
24*9c5db199SXin Li
25*9c5db199SXin Li    def __init__(self, name):
26*9c5db199SXin Li        self._FILTER_OUT = _CRASH_RUN_STATE_DIR + '/filter-out'
27*9c5db199SXin Li        self.name = name
28*9c5db199SXin Li
29*9c5db199SXin Li    def __enter__(self):
30*9c5db199SXin Li        """Writes the given parameter to the filter-out file.
31*9c5db199SXin Li
32*9c5db199SXin Li        This is used to ignore crashes in which we have no interest.
33*9c5db199SXin Li        """
34*9c5db199SXin Li        utils.open_write_close(self._FILTER_OUT, self.name)
35*9c5db199SXin Li
36*9c5db199SXin Li    def __exit__(self, ex_type, value, traceback):
37*9c5db199SXin Li        """Remove the filter-out file.
38*9c5db199SXin Li
39*9c5db199SXin Li        Next time the crash reporter is invoked, it will not filter crashes."""
40*9c5db199SXin Li        os.remove(self._FILTER_OUT)
41*9c5db199SXin Li        # Do *not* handle any exception
42*9c5db199SXin Li        return False
43*9c5db199SXin Li
44*9c5db199SXin Li
45*9c5db199SXin Liclass CrashTest(test.test):
46*9c5db199SXin Li    """
47*9c5db199SXin Li    This class deals with running crash tests, which are tests which crash a
48*9c5db199SXin Li    user-space program (or the whole machine) and generate a core dump. We
49*9c5db199SXin Li    want to check that the correct crash dump is available and can be
50*9c5db199SXin Li    retrieved.
51*9c5db199SXin Li
52*9c5db199SXin Li    Chromium OS has a crash sender which checks for new crash data and sends
53*9c5db199SXin Li    it to a server. This crash data is used to track software quality and find
54*9c5db199SXin Li    bugs. The system crash sender normally is always running, but can be paused
55*9c5db199SXin Li    by creating _PAUSE_FILE. When crash sender sees this, it pauses operation.
56*9c5db199SXin Li
57*9c5db199SXin Li    For testing purposes we sometimes want to run the crash sender manually.
58*9c5db199SXin Li    In this case we can pass the --ignore_pause_file flag and run the crash
59*9c5db199SXin Li    sender manually.
60*9c5db199SXin Li
61*9c5db199SXin Li    Also for testing we sometimes want to mock out the crash sender, and just
62*9c5db199SXin Li    have it pretend to succeed or fail. The _MOCK_CRASH_SENDING file is used
63*9c5db199SXin Li    for this. If it doesn't exist, then the crash sender runs normally. If
64*9c5db199SXin Li    it exists but is empty, the crash sender will succeed (but actually do
65*9c5db199SXin Li    nothing). If the file contains something, then the crash sender will fail.
66*9c5db199SXin Li
67*9c5db199SXin Li    If the user consents to sending crash tests, then the _CONSENT_FILE will
68*9c5db199SXin Li    exist in the home directory. This test needs to create this file for the
69*9c5db199SXin Li    crash sending to work. The metrics daemon caches the consent state for
70*9c5db199SXin Li    1 second, so we need to sleep for more than that after changing it to be
71*9c5db199SXin Li    sure it picks up the change.
72*9c5db199SXin Li
73*9c5db199SXin Li    Crash reports are rate limited to a certain number of reports each 24
74*9c5db199SXin Li    hours. If the maximum number has already been sent then reports are held
75*9c5db199SXin Li    until later. This is administered by a directory _CRASH_SENDER_RATE_DIR
76*9c5db199SXin Li    which contains one temporary file for each time a report is sent.
77*9c5db199SXin Li
78*9c5db199SXin Li    The class provides the ability to push a consent file. This disables
79*9c5db199SXin Li    consent for this test but allows it to be popped back at later. This
80*9c5db199SXin Li    makes nested tests easier. If _automatic_consent_saving is True (the
81*9c5db199SXin Li    default) then consent will be pushed at the start and popped at the end.
82*9c5db199SXin Li
83*9c5db199SXin Li    Interesting variables:
84*9c5db199SXin Li        _log_reader: the log reader used for reading log files
85*9c5db199SXin Li        _leave_crash_sending: True to enable crash sending on exit from the
86*9c5db199SXin Li            test, False to disable it. (Default True).
87*9c5db199SXin Li        _automatic_consent_saving: True to push the consent at the start of
88*9c5db199SXin Li            the test and pop it afterwards. (Default True).
89*9c5db199SXin Li
90*9c5db199SXin Li    Useful places to look for more information are:
91*9c5db199SXin Li
92*9c5db199SXin Li    chromeos/src/platform/crash-reporter/crash_sender
93*9c5db199SXin Li        - sender script which crash crash reporter to create reports, then
94*9c5db199SXin Li
95*9c5db199SXin Li    chromeos/src/platform/crash-reporter/
96*9c5db199SXin Li        - crash reporter program
97*9c5db199SXin Li    """
98*9c5db199SXin Li
99*9c5db199SXin Li
100*9c5db199SXin Li    _CONSENT_FILE = '/home/chronos/Consent To Send Stats'
101*9c5db199SXin Li    _CORE_PATTERN = '/proc/sys/kernel/core_pattern'
102*9c5db199SXin Li    _LOCK_CORE_PATTERN = '/proc/sys/kernel/lock_core_pattern'
103*9c5db199SXin Li    _CRASH_REPORTER_PATH = '/sbin/crash_reporter'
104*9c5db199SXin Li    _CRASH_SENDER_PATH = '/sbin/crash_sender'
105*9c5db199SXin Li    _CRASH_SENDER_RATE_DIR = '/var/lib/crash_sender'
106*9c5db199SXin Li    _CRASH_SENDER_LOCK_PATH = '/run/lock/crash_sender'
107*9c5db199SXin Li    _CRASH_TEST_IN_PROGRESS = _CRASH_RUN_STATE_DIR + '/crash-test-in-progress'
108*9c5db199SXin Li    _MOCK_CRASH_SENDING = _CRASH_RUN_STATE_DIR + '/mock-crash-sending'
109*9c5db199SXin Li    _FILTER_IN = _CRASH_RUN_STATE_DIR + '/filter-in'
110*9c5db199SXin Li    _PAUSE_FILE = '/var/lib/crash_sender_paused'
111*9c5db199SXin Li    _SYSTEM_CRASH_DIR = '/var/spool/crash'
112*9c5db199SXin Li    _FALLBACK_USER_CRASH_DIR = '/home/chronos/crash'
113*9c5db199SXin Li    _REBOOT_VAULT_CRASH_DIR = '/mnt/stateful_partition/reboot_vault/crash'
114*9c5db199SXin Li    _USER_CRASH_DIRS = '/home/chronos/u-*/crash'
115*9c5db199SXin Li    _USER_CRASH_DIR_REGEX = re.compile('/home/chronos/u-([a-f0-9]+)/crash')
116*9c5db199SXin Li
117*9c5db199SXin Li    # Matches kDefaultMaxUploadBytes
118*9c5db199SXin Li    _MAX_CRASH_SIZE = 1024 * 1024
119*9c5db199SXin Li
120*9c5db199SXin Li    # Use the same file format as crash does normally:
121*9c5db199SXin Li    # <basename>.#.#.#.#.meta
122*9c5db199SXin Li    _FAKE_TEST_BASENAME = 'fake.1.2.3.4'
123*9c5db199SXin Li
124*9c5db199SXin Li    def _set_system_sending(self, is_enabled):
125*9c5db199SXin Li        """Sets whether or not the system crash_sender is allowed to run.
126*9c5db199SXin Li
127*9c5db199SXin Li        This is done by creating or removing _PAUSE_FILE.
128*9c5db199SXin Li
129*9c5db199SXin Li        crash_sender may still be allowed to run if _call_sender_one_crash is
130*9c5db199SXin Li        called with 'ignore_pause=True'.
131*9c5db199SXin Li
132*9c5db199SXin Li        @param is_enabled: True to enable crash_sender, False to disable it.
133*9c5db199SXin Li        """
134*9c5db199SXin Li        if is_enabled:
135*9c5db199SXin Li            if os.path.exists(self._PAUSE_FILE):
136*9c5db199SXin Li                os.remove(self._PAUSE_FILE)
137*9c5db199SXin Li        else:
138*9c5db199SXin Li            utils.system('touch ' + self._PAUSE_FILE)
139*9c5db199SXin Li
140*9c5db199SXin Li    def _remove_all_files_in_dir(self, d):
141*9c5db199SXin Li        """Recursively remove all of the files in |d|, without removing |d|.
142*9c5db199SXin Li      """
143*9c5db199SXin Li        try:
144*9c5db199SXin Li            root, dirs, files = next(os.walk(d))
145*9c5db199SXin Li        except StopIteration:
146*9c5db199SXin Li            return
147*9c5db199SXin Li        for path in files:
148*9c5db199SXin Li            os.remove(os.path.join(root, path))
149*9c5db199SXin Li        for path in dirs:
150*9c5db199SXin Li            shutil.rmtree(os.path.join(root, path))
151*9c5db199SXin Li
152*9c5db199SXin Li
153*9c5db199SXin Li    def _reset_rate_limiting(self):
154*9c5db199SXin Li        """Reset the count of crash reports sent today.
155*9c5db199SXin Li
156*9c5db199SXin Li        This clears the contents of the rate limiting directory which has
157*9c5db199SXin Li        the effect of reseting our count of crash reports sent.
158*9c5db199SXin Li        """
159*9c5db199SXin Li        self._remove_all_files_in_dir(self._CRASH_SENDER_RATE_DIR)
160*9c5db199SXin Li
161*9c5db199SXin Li
162*9c5db199SXin Li    def _clear_spooled_crashes(self):
163*9c5db199SXin Li        """Clears system and user crash directories.
164*9c5db199SXin Li
165*9c5db199SXin Li        This will remove all crash reports which are waiting to be sent.
166*9c5db199SXin Li        """
167*9c5db199SXin Li        self._remove_all_files_in_dir(self._SYSTEM_CRASH_DIR)
168*9c5db199SXin Li        self._remove_all_files_in_dir(self._REBOOT_VAULT_CRASH_DIR)
169*9c5db199SXin Li        for d in glob.glob(self._USER_CRASH_DIRS):
170*9c5db199SXin Li            self._remove_all_files_in_dir(d)
171*9c5db199SXin Li        self._remove_all_files_in_dir(self._FALLBACK_USER_CRASH_DIR)
172*9c5db199SXin Li
173*9c5db199SXin Li
174*9c5db199SXin Li    def _kill_running_sender(self):
175*9c5db199SXin Li        """Kill the the crash_sender process if running."""
176*9c5db199SXin Li        utils.system('pkill -9 -e --exact crash_sender', ignore_status=True)
177*9c5db199SXin Li
178*9c5db199SXin Li
179*9c5db199SXin Li    def _set_sending_mock(self, mock_enabled):
180*9c5db199SXin Li        """Enables / disables mocking of the sending process.
181*9c5db199SXin Li
182*9c5db199SXin Li        This uses the _MOCK_CRASH_SENDING file to achieve its aims. See notes
183*9c5db199SXin Li        at the top.
184*9c5db199SXin Li
185*9c5db199SXin Li        @param mock_enabled: If True, mocking is enabled, else it is disabled.
186*9c5db199SXin Li        """
187*9c5db199SXin Li        if mock_enabled:
188*9c5db199SXin Li            data = ''
189*9c5db199SXin Li            logging.info('Setting sending mock')
190*9c5db199SXin Li            utils.open_write_close(self._MOCK_CRASH_SENDING, data)
191*9c5db199SXin Li        else:
192*9c5db199SXin Li            utils.system('rm -f ' + self._MOCK_CRASH_SENDING)
193*9c5db199SXin Li
194*9c5db199SXin Li
195*9c5db199SXin Li    def _set_consent(self, has_consent):
196*9c5db199SXin Li        """Sets whether or not we have consent to send crash reports.
197*9c5db199SXin Li
198*9c5db199SXin Li        This creates or deletes the _CONSENT_FILE to control whether
199*9c5db199SXin Li        crash_sender will consider that it has consent to send crash reports.
200*9c5db199SXin Li        It also copies a policy blob with the proper policy setting.
201*9c5db199SXin Li
202*9c5db199SXin Li        @param has_consent: True to indicate consent, False otherwise
203*9c5db199SXin Li        """
204*9c5db199SXin Li        autotest_cros_dir = os.path.join(os.path.dirname(__file__), '..')
205*9c5db199SXin Li        if has_consent:
206*9c5db199SXin Li            if os.path.isdir(constants.DEVICESETTINGS_DIR):
207*9c5db199SXin Li                # Create policy file that enables metrics/consent.
208*9c5db199SXin Li                shutil.copy('%s/mock_metrics_on.policy' % autotest_cros_dir,
209*9c5db199SXin Li                            constants.SIGNED_POLICY_FILE)
210*9c5db199SXin Li                shutil.copy('%s/mock_metrics_owner.key' % autotest_cros_dir,
211*9c5db199SXin Li                            constants.OWNER_KEY_FILE)
212*9c5db199SXin Li            # Create deprecated consent file.  This is created *after* the
213*9c5db199SXin Li            # policy file in order to avoid a race condition where chrome
214*9c5db199SXin Li            # might remove the consent file if the policy's not set yet.
215*9c5db199SXin Li            # We create it as a temp file first in order to make the creation
216*9c5db199SXin Li            # of the consent file, owned by chronos, atomic.
217*9c5db199SXin Li            # See crosbug.com/18413.
218*9c5db199SXin Li            temp_file = self._CONSENT_FILE + '.tmp';
219*9c5db199SXin Li            utils.open_write_close(temp_file, 'test-consent')
220*9c5db199SXin Li            utils.system('chown chronos:chronos "%s"' % (temp_file))
221*9c5db199SXin Li            shutil.move(temp_file, self._CONSENT_FILE)
222*9c5db199SXin Li            logging.info('Created %s', self._CONSENT_FILE)
223*9c5db199SXin Li        else:
224*9c5db199SXin Li            if os.path.isdir(constants.DEVICESETTINGS_DIR):
225*9c5db199SXin Li                # Create policy file that disables metrics/consent.
226*9c5db199SXin Li                shutil.copy('%s/mock_metrics_off.policy' % autotest_cros_dir,
227*9c5db199SXin Li                            constants.SIGNED_POLICY_FILE)
228*9c5db199SXin Li                shutil.copy('%s/mock_metrics_owner.key' % autotest_cros_dir,
229*9c5db199SXin Li                            constants.OWNER_KEY_FILE)
230*9c5db199SXin Li            # Remove deprecated consent file.
231*9c5db199SXin Li            utils.system('rm -f "%s"' % (self._CONSENT_FILE))
232*9c5db199SXin Li        # Ensure cached consent state is updated.
233*9c5db199SXin Li        time.sleep(2)
234*9c5db199SXin Li
235*9c5db199SXin Li
236*9c5db199SXin Li    def _set_crash_test_in_progress(self, in_progress):
237*9c5db199SXin Li        if in_progress:
238*9c5db199SXin Li            utils.open_write_close(self._CRASH_TEST_IN_PROGRESS, 'in-progress')
239*9c5db199SXin Li            logging.info('Created %s', self._CRASH_TEST_IN_PROGRESS)
240*9c5db199SXin Li        else:
241*9c5db199SXin Li            utils.system('rm -f "%s"' % (self._CRASH_TEST_IN_PROGRESS))
242*9c5db199SXin Li
243*9c5db199SXin Li
244*9c5db199SXin Li    def _get_pushed_consent_file_path(self):
245*9c5db199SXin Li        """Returns filename of the pushed consent file."""
246*9c5db199SXin Li        return os.path.join(self.bindir, 'pushed_consent')
247*9c5db199SXin Li
248*9c5db199SXin Li
249*9c5db199SXin Li    def _get_pushed_policy_file_path(self):
250*9c5db199SXin Li        """Returns filename of the pushed policy file."""
251*9c5db199SXin Li        return os.path.join(self.bindir, 'pushed_policy')
252*9c5db199SXin Li
253*9c5db199SXin Li
254*9c5db199SXin Li    def _get_pushed_owner_key_file_path(self):
255*9c5db199SXin Li        """Returns filename of the pushed owner.key file."""
256*9c5db199SXin Li        return os.path.join(self.bindir, 'pushed_owner_key')
257*9c5db199SXin Li
258*9c5db199SXin Li
259*9c5db199SXin Li    def _push_consent(self):
260*9c5db199SXin Li        """Push the consent file, thus disabling consent.
261*9c5db199SXin Li
262*9c5db199SXin Li        The consent files can be created in the new test if required. Call
263*9c5db199SXin Li        _pop_consent() to restore the original state.
264*9c5db199SXin Li        """
265*9c5db199SXin Li        if os.path.exists(self._CONSENT_FILE):
266*9c5db199SXin Li            shutil.move(self._CONSENT_FILE,
267*9c5db199SXin Li                        self._get_pushed_consent_file_path())
268*9c5db199SXin Li        if os.path.exists(constants.SIGNED_POLICY_FILE):
269*9c5db199SXin Li            shutil.move(constants.SIGNED_POLICY_FILE,
270*9c5db199SXin Li                        self._get_pushed_policy_file_path())
271*9c5db199SXin Li        if os.path.exists(constants.OWNER_KEY_FILE):
272*9c5db199SXin Li            shutil.move(constants.OWNER_KEY_FILE,
273*9c5db199SXin Li                        self._get_pushed_owner_key_file_path())
274*9c5db199SXin Li        # Ensure cached consent state is updated.
275*9c5db199SXin Li        time.sleep(2)
276*9c5db199SXin Li
277*9c5db199SXin Li
278*9c5db199SXin Li    def _pop_consent(self):
279*9c5db199SXin Li        """Pop the consent files, enabling/disabling consent as it was before
280*9c5db199SXin Li        we pushed the consent."""
281*9c5db199SXin Li        if os.path.exists(self._get_pushed_consent_file_path()):
282*9c5db199SXin Li            shutil.move(self._get_pushed_consent_file_path(),
283*9c5db199SXin Li                        self._CONSENT_FILE)
284*9c5db199SXin Li        else:
285*9c5db199SXin Li            utils.system('rm -f "%s"' % self._CONSENT_FILE)
286*9c5db199SXin Li        if os.path.exists(self._get_pushed_policy_file_path()):
287*9c5db199SXin Li            shutil.move(self._get_pushed_policy_file_path(),
288*9c5db199SXin Li                        constants.SIGNED_POLICY_FILE)
289*9c5db199SXin Li        else:
290*9c5db199SXin Li            utils.system('rm -f "%s"' % constants.SIGNED_POLICY_FILE)
291*9c5db199SXin Li        if os.path.exists(self._get_pushed_owner_key_file_path()):
292*9c5db199SXin Li            shutil.move(self._get_pushed_owner_key_file_path(),
293*9c5db199SXin Li                        constants.OWNER_KEY_FILE)
294*9c5db199SXin Li        else:
295*9c5db199SXin Li            utils.system('rm -f "%s"' % constants.OWNER_KEY_FILE)
296*9c5db199SXin Li        # Ensure cached consent state is updated.
297*9c5db199SXin Li        time.sleep(2)
298*9c5db199SXin Li
299*9c5db199SXin Li
300*9c5db199SXin Li    def _get_crash_dir(self, username, force_user_crash_dir=False):
301*9c5db199SXin Li        """Returns crash directory for process running as the given user.
302*9c5db199SXin Li
303*9c5db199SXin Li        @param username: Unix user of the crashing process.
304*9c5db199SXin Li        @param force_user_crash_dir: Regardless of |username|, return the crash
305*9c5db199SXin Li                                     directory of the current user session, or
306*9c5db199SXin Li                                     the fallback directory if no sessions.
307*9c5db199SXin Li        """
308*9c5db199SXin Li        if username in ('root', 'crash') and not force_user_crash_dir:
309*9c5db199SXin Li            return self._SYSTEM_CRASH_DIR
310*9c5db199SXin Li        else:
311*9c5db199SXin Li            dirs = glob.glob(self._USER_CRASH_DIRS)
312*9c5db199SXin Li            return dirs[0] if dirs else self._FALLBACK_USER_CRASH_DIR
313*9c5db199SXin Li
314*9c5db199SXin Li
315*9c5db199SXin Li    def _canonicalize_crash_dir(self, crash_dir):
316*9c5db199SXin Li        """Converts /home/chronos crash directory to /home/user counterpart.
317*9c5db199SXin Li
318*9c5db199SXin Li        @param crash_dir: A path of the form /home/chronos/u-<hash>/crash.
319*9c5db199SXin Li        @returns /home/user/<hash>/crash, or |crash_dir| on form mismatch.
320*9c5db199SXin Li        """
321*9c5db199SXin Li        match = re.match(self._USER_CRASH_DIR_REGEX, crash_dir)
322*9c5db199SXin Li        return ('/home/user/%s/crash' % match.group(1)) if match else crash_dir
323*9c5db199SXin Li
324*9c5db199SXin Li
325*9c5db199SXin Li    def _initialize_crash_reporter(self, lock_core_pattern):
326*9c5db199SXin Li        """Start up the crash reporter.
327*9c5db199SXin Li
328*9c5db199SXin Li        @param lock_core_pattern: lock core pattern during initialization.
329*9c5db199SXin Li        """
330*9c5db199SXin Li
331*9c5db199SXin Li        if not lock_core_pattern:
332*9c5db199SXin Li            self._set_crash_test_in_progress(False)
333*9c5db199SXin Li        utils.system('%s --init' % self._CRASH_REPORTER_PATH)
334*9c5db199SXin Li        if not lock_core_pattern:
335*9c5db199SXin Li            self._set_crash_test_in_progress(True)
336*9c5db199SXin Li            # Completely disable crash_reporter from generating crash dumps
337*9c5db199SXin Li            # while any tests are running, otherwise a crashy system can make
338*9c5db199SXin Li            # these tests flaky.
339*9c5db199SXin Li            self.enable_crash_filtering('none')
340*9c5db199SXin Li
341*9c5db199SXin Li
342*9c5db199SXin Li    def get_crash_dir_name(self, name):
343*9c5db199SXin Li        """Return the full path for |name| inside the system crash directory."""
344*9c5db199SXin Li        return os.path.join(self._SYSTEM_CRASH_DIR, name)
345*9c5db199SXin Li
346*9c5db199SXin Li
347*9c5db199SXin Li    def write_crash_dir_entry(self, name, contents):
348*9c5db199SXin Li        """Writes a file to the system crash directory.
349*9c5db199SXin Li
350*9c5db199SXin Li        This writes a file to _SYSTEM_CRASH_DIR with the given name. This is
351*9c5db199SXin Li        used to insert new crash dump files for testing purposes.
352*9c5db199SXin Li
353*9c5db199SXin Li        If contents is not a string, binary data is assumed.
354*9c5db199SXin Li
355*9c5db199SXin Li        @param name: Name of file to write.
356*9c5db199SXin Li        @param contents: String/binary data to write to the file.
357*9c5db199SXin Li        """
358*9c5db199SXin Li        entry = self.get_crash_dir_name(name)
359*9c5db199SXin Li        if not os.path.exists(self._SYSTEM_CRASH_DIR):
360*9c5db199SXin Li            os.makedirs(self._SYSTEM_CRASH_DIR)
361*9c5db199SXin Li
362*9c5db199SXin Li        is_binary = not isinstance(contents, str)
363*9c5db199SXin Li        utils.open_write_close(entry, contents, is_binary)
364*9c5db199SXin Li
365*9c5db199SXin Li        return entry
366*9c5db199SXin Li
367*9c5db199SXin Li
368*9c5db199SXin Li    def write_fake_meta(self, name, exec_name, payload, complete=True):
369*9c5db199SXin Li        """Writes a fake meta entry to the system crash directory.
370*9c5db199SXin Li
371*9c5db199SXin Li        @param name: Name of file to write.
372*9c5db199SXin Li        @param exec_name: Value for exec_name item.
373*9c5db199SXin Li        @param payload: Value for payload item.
374*9c5db199SXin Li        @param complete: True to close off the record, otherwise leave it
375*9c5db199SXin Li                incomplete.
376*9c5db199SXin Li        """
377*9c5db199SXin Li        last_line = ''
378*9c5db199SXin Li        if complete:
379*9c5db199SXin Li            last_line = 'done=1\n'
380*9c5db199SXin Li        contents = ('exec_name=%s\n'
381*9c5db199SXin Li                    'ver=my_ver\n'
382*9c5db199SXin Li                    'payload=%s\n'
383*9c5db199SXin Li                    '%s' % (exec_name, payload,
384*9c5db199SXin Li                            last_line))
385*9c5db199SXin Li        return self.write_crash_dir_entry(name, contents)
386*9c5db199SXin Li
387*9c5db199SXin Li    def _get_dmp_contents(self):
388*9c5db199SXin Li        """Creates the contents of the dmp file for our made crashes.
389*9c5db199SXin Li
390*9c5db199SXin Li        The dmp file contents are deliberately large and hard-to-compress. This
391*9c5db199SXin Li        ensures logging_CrashSender hits its bytes/day cap before its sends/day
392*9c5db199SXin Li        cap.
393*9c5db199SXin Li        """
394*9c5db199SXin Li        return bytearray(
395*9c5db199SXin Li                [random.randint(0, 255) for n in range(self._MAX_CRASH_SIZE)])
396*9c5db199SXin Li
397*9c5db199SXin Li
398*9c5db199SXin Li    def _prepare_sender_one_crash(self,
399*9c5db199SXin Li                                  reports_enabled,
400*9c5db199SXin Li                                  report):
401*9c5db199SXin Li        """Create metadata for a fake crash report.
402*9c5db199SXin Li
403*9c5db199SXin Li        This enabled mocking of the crash sender, then creates a fake
404*9c5db199SXin Li        crash report for testing purposes.
405*9c5db199SXin Li
406*9c5db199SXin Li        @param reports_enabled: True to enable consent to that reports will be
407*9c5db199SXin Li                sent.
408*9c5db199SXin Li        @param report: Report to use for crash, if None we create one.
409*9c5db199SXin Li        """
410*9c5db199SXin Li        self._set_sending_mock(mock_enabled=True)
411*9c5db199SXin Li        self._set_consent(reports_enabled)
412*9c5db199SXin Li        if report is None:
413*9c5db199SXin Li            # Use the same file format as crash does normally:
414*9c5db199SXin Li            # <basename>.#.#.#.meta
415*9c5db199SXin Li            payload = os.path.basename(
416*9c5db199SXin Li                    self.write_crash_dir_entry(
417*9c5db199SXin Li                            '%s.dmp' % self._FAKE_TEST_BASENAME,
418*9c5db199SXin Li                            self._get_dmp_contents()))
419*9c5db199SXin Li            report = self.write_fake_meta(
420*9c5db199SXin Li                '%s.meta' % self._FAKE_TEST_BASENAME, 'fake', payload)
421*9c5db199SXin Li        return report
422*9c5db199SXin Li
423*9c5db199SXin Li
424*9c5db199SXin Li    def _parse_sender_output(self, output):
425*9c5db199SXin Li        """Parse the log output from the crash_sender script.
426*9c5db199SXin Li
427*9c5db199SXin Li        This script can run on the logs from either a mocked or true
428*9c5db199SXin Li        crash send. It looks for one and only one crash from output.
429*9c5db199SXin Li        Non-crash anomalies should be ignored since there're just noise
430*9c5db199SXin Li        during running the test.
431*9c5db199SXin Li
432*9c5db199SXin Li        @param output: output from the script
433*9c5db199SXin Li
434*9c5db199SXin Li        @returns A dictionary with these values:
435*9c5db199SXin Li            exec_name: name of executable which crashed
436*9c5db199SXin Li            image_type: type of image ("dev","test",...), if given
437*9c5db199SXin Li            boot_mode: current boot mode ("dev",...), if given
438*9c5db199SXin Li            meta_path: path to the report metadata file
439*9c5db199SXin Li            output: the output from the script, copied
440*9c5db199SXin Li            report_kind: kind of report sent (minidump vs kernel)
441*9c5db199SXin Li            send_attempt: did the script attempt to send a crash.
442*9c5db199SXin Li            send_success: if it attempted, was the crash send successful.
443*9c5db199SXin Li            sig: signature of the report, if given.
444*9c5db199SXin Li            sleep_time: if it attempted, how long did it sleep before
445*9c5db199SXin Li              sending (if mocked, how long would it have slept)
446*9c5db199SXin Li        """
447*9c5db199SXin Li        anomaly_types = (
448*9c5db199SXin Li            'kernel_suspend_warning',
449*9c5db199SXin Li            'kernel_warning',
450*9c5db199SXin Li            'kernel_wifi_warning',
451*9c5db199SXin Li            'selinux_violation',
452*9c5db199SXin Li            'service_failure',
453*9c5db199SXin Li        )
454*9c5db199SXin Li
455*9c5db199SXin Li        def crash_sender_search(regexp, output):
456*9c5db199SXin Li            """Narrow search to lines from crash_sender."""
457*9c5db199SXin Li            return re.search(r'crash_sender\[\d+\]:\s+' + regexp, output)
458*9c5db199SXin Li
459*9c5db199SXin Li        before_first_crash = None
460*9c5db199SXin Li        while True:
461*9c5db199SXin Li            crash_header = crash_sender_search(
462*9c5db199SXin Li                'Considering metadata (\S+)',
463*9c5db199SXin Li                output
464*9c5db199SXin Li            )
465*9c5db199SXin Li            if not crash_header:
466*9c5db199SXin Li                break
467*9c5db199SXin Li            if before_first_crash is None:
468*9c5db199SXin Li                before_first_crash = output[:crash_header.start()]
469*9c5db199SXin Li            meta_considered = crash_header.group(1)
470*9c5db199SXin Li            is_anomaly = any(x in meta_considered for x in anomaly_types)
471*9c5db199SXin Li            if is_anomaly:
472*9c5db199SXin Li                # If it's an anomaly, skip this header, and look for next
473*9c5db199SXin Li                # one.
474*9c5db199SXin Li                output = output[crash_header.end():]
475*9c5db199SXin Li            else:
476*9c5db199SXin Li                # If it's not an anomaly, skip everything before this
477*9c5db199SXin Li                # header.
478*9c5db199SXin Li                output = output[crash_header.start():]
479*9c5db199SXin Li                break
480*9c5db199SXin Li        if before_first_crash:
481*9c5db199SXin Li            output = before_first_crash + output
482*9c5db199SXin Li        logging.debug('Filtered sender output to parse:\n%s', output)
483*9c5db199SXin Li
484*9c5db199SXin Li        sleep_match = crash_sender_search('Scheduled to send in (\d+)s', output)
485*9c5db199SXin Li        send_attempt = sleep_match is not None
486*9c5db199SXin Li        if send_attempt:
487*9c5db199SXin Li            sleep_time = int(sleep_match.group(1))
488*9c5db199SXin Li        else:
489*9c5db199SXin Li            sleep_time = None
490*9c5db199SXin Li
491*9c5db199SXin Li        meta_match = crash_sender_search('Metadata: (\S+) \((\S+)\)', output)
492*9c5db199SXin Li        if meta_match:
493*9c5db199SXin Li            meta_path = meta_match.group(1)
494*9c5db199SXin Li            report_kind = meta_match.group(2)
495*9c5db199SXin Li        else:
496*9c5db199SXin Li            meta_path = None
497*9c5db199SXin Li            report_kind = None
498*9c5db199SXin Li
499*9c5db199SXin Li        payload_match = crash_sender_search('Payload: (\S+)', output)
500*9c5db199SXin Li        if payload_match:
501*9c5db199SXin Li            report_payload = payload_match.group(1)
502*9c5db199SXin Li        else:
503*9c5db199SXin Li            report_payload = None
504*9c5db199SXin Li
505*9c5db199SXin Li        exec_name_match = crash_sender_search('Exec name: (\S+)', output)
506*9c5db199SXin Li        if exec_name_match:
507*9c5db199SXin Li            exec_name = exec_name_match.group(1)
508*9c5db199SXin Li        else:
509*9c5db199SXin Li            exec_name = None
510*9c5db199SXin Li
511*9c5db199SXin Li        sig_match = crash_sender_search('sig: (\S+)', output)
512*9c5db199SXin Li        if sig_match:
513*9c5db199SXin Li            sig = sig_match.group(1)
514*9c5db199SXin Li        else:
515*9c5db199SXin Li            sig = None
516*9c5db199SXin Li
517*9c5db199SXin Li        image_type_match = crash_sender_search('Image type: (\S+)', output)
518*9c5db199SXin Li        if image_type_match:
519*9c5db199SXin Li            image_type = image_type_match.group(1)
520*9c5db199SXin Li        else:
521*9c5db199SXin Li            image_type = None
522*9c5db199SXin Li
523*9c5db199SXin Li        boot_mode_match = crash_sender_search('Boot mode: (\S+)', output)
524*9c5db199SXin Li        if boot_mode_match:
525*9c5db199SXin Li            boot_mode = boot_mode_match.group(1)
526*9c5db199SXin Li        else:
527*9c5db199SXin Li            boot_mode = None
528*9c5db199SXin Li
529*9c5db199SXin Li        send_success = 'Mocking successful send' in output
530*9c5db199SXin Li        return {'exec_name': exec_name,
531*9c5db199SXin Li                'report_kind': report_kind,
532*9c5db199SXin Li                'meta_path': meta_path,
533*9c5db199SXin Li                'report_payload': report_payload,
534*9c5db199SXin Li                'send_attempt': send_attempt,
535*9c5db199SXin Li                'send_success': send_success,
536*9c5db199SXin Li                'sig': sig,
537*9c5db199SXin Li                'image_type': image_type,
538*9c5db199SXin Li                'boot_mode': boot_mode,
539*9c5db199SXin Li                'sleep_time': sleep_time,
540*9c5db199SXin Li                'output': output}
541*9c5db199SXin Li
542*9c5db199SXin Li
543*9c5db199SXin Li    def wait_for_sender_completion(self):
544*9c5db199SXin Li        """Wait for crash_sender to complete.
545*9c5db199SXin Li
546*9c5db199SXin Li        Wait for no crash_sender's last message to be placed in the
547*9c5db199SXin Li        system log before continuing and for the process to finish.
548*9c5db199SXin Li        Otherwise we might get only part of the output."""
549*9c5db199SXin Li        utils.poll_for_condition(
550*9c5db199SXin Li            lambda: self._log_reader.can_find('crash_sender done.'),
551*9c5db199SXin Li            timeout=60,
552*9c5db199SXin Li            exception=error.TestError(
553*9c5db199SXin Li              'Timeout waiting for crash_sender to emit done: ' +
554*9c5db199SXin Li              self._log_reader.get_logs()))
555*9c5db199SXin Li        utils.poll_for_condition(
556*9c5db199SXin Li            lambda: utils.system('pgrep crash_sender',
557*9c5db199SXin Li                                 ignore_status=True) != 0,
558*9c5db199SXin Li            timeout=60,
559*9c5db199SXin Li            exception=error.TestError(
560*9c5db199SXin Li                'Timeout waiting for crash_sender to finish: ' +
561*9c5db199SXin Li                self._log_reader.get_logs()))
562*9c5db199SXin Li
563*9c5db199SXin Li
564*9c5db199SXin Li    def _call_sender_one_crash(self, reports_enabled=True, report=None):
565*9c5db199SXin Li        """Call the crash sender script to mock upload one crash.
566*9c5db199SXin Li
567*9c5db199SXin Li        @param reports_enabled: Has the user consented to sending crash reports.
568*9c5db199SXin Li        @param report: report to use for crash, if None we create one.
569*9c5db199SXin Li
570*9c5db199SXin Li        @returns a dictionary describing the result with the keys
571*9c5db199SXin Li          from _parse_sender_output, as well as:
572*9c5db199SXin Li            report_exists: does the minidump still exist after calling
573*9c5db199SXin Li              send script
574*9c5db199SXin Li            rate_count: how many crashes have been uploaded in the past
575*9c5db199SXin Li              24 hours.
576*9c5db199SXin Li        """
577*9c5db199SXin Li        report = self._prepare_sender_one_crash(reports_enabled,
578*9c5db199SXin Li                                                report)
579*9c5db199SXin Li        self._log_reader.set_start_by_current()
580*9c5db199SXin Li        script_output = ""
581*9c5db199SXin Li        try:
582*9c5db199SXin Li            script_output = utils.system_output(
583*9c5db199SXin Li                '%s --ignore_pause_file 2>&1' % (self._CRASH_SENDER_PATH),
584*9c5db199SXin Li                ignore_status=False)
585*9c5db199SXin Li        except error.CmdError as err:
586*9c5db199SXin Li            raise error.TestFail('"%s" returned an unexpected non-zero '
587*9c5db199SXin Li                                 'value (%s).'
588*9c5db199SXin Li                                 % (err.command, err.result_obj.exit_status))
589*9c5db199SXin Li
590*9c5db199SXin Li        self.wait_for_sender_completion()
591*9c5db199SXin Li        output = self._log_reader.get_logs()
592*9c5db199SXin Li        logging.debug('Crash sender message output:\n %s', output)
593*9c5db199SXin Li
594*9c5db199SXin Li        if script_output != '':
595*9c5db199SXin Li            logging.debug('crash_sender stdout/stderr: %s', script_output)
596*9c5db199SXin Li
597*9c5db199SXin Li        if os.path.exists(report):
598*9c5db199SXin Li            report_exists = True
599*9c5db199SXin Li            os.remove(report)
600*9c5db199SXin Li        else:
601*9c5db199SXin Li            report_exists = False
602*9c5db199SXin Li        if os.path.exists(self._CRASH_SENDER_RATE_DIR):
603*9c5db199SXin Li            rate_count = len([
604*9c5db199SXin Li                name for name in os.listdir(self._CRASH_SENDER_RATE_DIR)
605*9c5db199SXin Li                if os.path.isfile(os.path.join(self._CRASH_SENDER_RATE_DIR,
606*9c5db199SXin Li                                               name))
607*9c5db199SXin Li            ])
608*9c5db199SXin Li        else:
609*9c5db199SXin Li            rate_count = 0
610*9c5db199SXin Li
611*9c5db199SXin Li        result = self._parse_sender_output(output)
612*9c5db199SXin Li        result['report_exists'] = report_exists
613*9c5db199SXin Li        result['rate_count'] = rate_count
614*9c5db199SXin Li
615*9c5db199SXin Li        # Show the result for debugging but remove 'output' key
616*9c5db199SXin Li        # since it's large and earlier in debug output.
617*9c5db199SXin Li        debug_result = dict(result)
618*9c5db199SXin Li        del debug_result['output']
619*9c5db199SXin Li        logging.debug('Result of send (besides output): %s', debug_result)
620*9c5db199SXin Li
621*9c5db199SXin Li        return result
622*9c5db199SXin Li
623*9c5db199SXin Li
624*9c5db199SXin Li    def enable_crash_filtering(self, name):
625*9c5db199SXin Li        """Writes the given parameter to the filter-in file.
626*9c5db199SXin Li
627*9c5db199SXin Li        This is used to collect only crashes in which we have an interest.
628*9c5db199SXin Li
629*9c5db199SXin Li        @param new_parameter: The filter to write to the file, if any.
630*9c5db199SXin Li        """
631*9c5db199SXin Li        utils.open_write_close(self._FILTER_IN, name)
632*9c5db199SXin Li
633*9c5db199SXin Li
634*9c5db199SXin Li    def disable_crash_filtering(self):
635*9c5db199SXin Li        """Remove the filter-in file.
636*9c5db199SXin Li
637*9c5db199SXin Li        Next time the crash reporter is invoked, it will not filter crashes."""
638*9c5db199SXin Li        os.remove(self._FILTER_IN)
639*9c5db199SXin Li
640*9c5db199SXin Li
641*9c5db199SXin Li    def initialize(self):
642*9c5db199SXin Li        """Initalize the test."""
643*9c5db199SXin Li        test.test.initialize(self)
644*9c5db199SXin Li        self._log_reader = cros_logging.make_system_log_reader()
645*9c5db199SXin Li        self._leave_crash_sending = True
646*9c5db199SXin Li        self._automatic_consent_saving = True
647*9c5db199SXin Li        self.enable_crash_filtering('none')
648*9c5db199SXin Li        self._set_crash_test_in_progress(True)
649*9c5db199SXin Li
650*9c5db199SXin Li
651*9c5db199SXin Li    def cleanup(self):
652*9c5db199SXin Li        """Cleanup after the test.
653*9c5db199SXin Li
654*9c5db199SXin Li        We reset things back to the way we think they should be. This is
655*9c5db199SXin Li        intended to allow the system to continue normal operation.
656*9c5db199SXin Li
657*9c5db199SXin Li        Some variables silently change the behavior:
658*9c5db199SXin Li            _automatic_consent_saving: if True, we pop the consent file.
659*9c5db199SXin Li            _leave_crash_sending: True to enable crash sending, False to
660*9c5db199SXin Li                disable it
661*9c5db199SXin Li        """
662*9c5db199SXin Li        self._reset_rate_limiting()
663*9c5db199SXin Li        self._clear_spooled_crashes()
664*9c5db199SXin Li        self._set_system_sending(self._leave_crash_sending)
665*9c5db199SXin Li        self._set_sending_mock(mock_enabled=False)
666*9c5db199SXin Li        if self._automatic_consent_saving:
667*9c5db199SXin Li            self._pop_consent()
668*9c5db199SXin Li        self._set_crash_test_in_progress(False)
669*9c5db199SXin Li
670*9c5db199SXin Li        # Re-initialize crash reporter to clear any state left over
671*9c5db199SXin Li        # (e.g. core_pattern)
672*9c5db199SXin Li        self._initialize_crash_reporter(True)
673*9c5db199SXin Li
674*9c5db199SXin Li        self.disable_crash_filtering()
675*9c5db199SXin Li
676*9c5db199SXin Li        test.test.cleanup(self)
677*9c5db199SXin Li
678*9c5db199SXin Li
679*9c5db199SXin Li    def run_crash_tests(self,
680*9c5db199SXin Li                        test_names,
681*9c5db199SXin Li                        initialize_crash_reporter=False,
682*9c5db199SXin Li                        clear_spool_first=True,
683*9c5db199SXin Li                        must_run_all=True,
684*9c5db199SXin Li                        lock_core_pattern=False):
685*9c5db199SXin Li        """Run crash tests defined in this class.
686*9c5db199SXin Li
687*9c5db199SXin Li        @param test_names: Array of test names.
688*9c5db199SXin Li        @param initialize_crash_reporter: Should set up crash reporter for every
689*9c5db199SXin Li                run.
690*9c5db199SXin Li        @param clear_spool_first: Clear all spooled user/system crashes before
691*9c5db199SXin Li                starting the test.
692*9c5db199SXin Li        @param must_run_all: Should make sure every test in this class is
693*9c5db199SXin Li                mentioned in test_names.
694*9c5db199SXin Li        @param lock_core_pattern: Lock core_pattern while initializing
695*9c5db199SXin Li                crash_reporter.
696*9c5db199SXin Li        """
697*9c5db199SXin Li        if self._automatic_consent_saving:
698*9c5db199SXin Li            self._push_consent()
699*9c5db199SXin Li
700*9c5db199SXin Li        if must_run_all:
701*9c5db199SXin Li            # Check test_names is complete
702*9c5db199SXin Li            for attr in dir(self):
703*9c5db199SXin Li                if attr.find('_test_') == 0:
704*9c5db199SXin Li                    test_name = attr[6:]
705*9c5db199SXin Li                    if not test_name in test_names:
706*9c5db199SXin Li                        raise error.TestError('Test %s is missing' % test_name)
707*9c5db199SXin Li
708*9c5db199SXin Li        for test_name in test_names:
709*9c5db199SXin Li            logging.info(('=' * 20) + ('Running %s' % test_name) + ('=' * 20))
710*9c5db199SXin Li            if initialize_crash_reporter:
711*9c5db199SXin Li                self._initialize_crash_reporter(lock_core_pattern)
712*9c5db199SXin Li            # Disable crash_sender from running, kill off any running ones.
713*9c5db199SXin Li            # We set a flag to crash_sender when invoking it manually to avoid
714*9c5db199SXin Li            # our invocations being paused.
715*9c5db199SXin Li            self._set_system_sending(False)
716*9c5db199SXin Li            self._kill_running_sender()
717*9c5db199SXin Li            self._reset_rate_limiting()
718*9c5db199SXin Li            if clear_spool_first:
719*9c5db199SXin Li                self._clear_spooled_crashes()
720*9c5db199SXin Li
721*9c5db199SXin Li            # Call the test function
722*9c5db199SXin Li            getattr(self, '_test_' + test_name)()
723*9c5db199SXin Li
724*9c5db199SXin Li        # Clear the intentional crashes, so that the server won't automatically
725*9c5db199SXin Li        # report crash as failure.
726*9c5db199SXin Li        self._clear_spooled_crashes()
727