Home | History | Annotate | Download | only in logging_CrashSender
      1 # Copyright (c) 2011 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 
      5 import logging, os, re
      6 from autotest_lib.client.bin import utils
      7 from autotest_lib.client.common_lib import error
      8 from autotest_lib.client.cros.crash import crash_test
      9 
     10 
     11 _25_HOURS_AGO = -25 * 60 * 60
     12 _CRASH_SENDER_CRON_PATH = '/etc/cron.hourly/crash_sender.hourly'
     13 _DAILY_RATE_LIMIT = 32
     14 _MIN_UNIQUE_TIMES = 4
     15 _SECONDS_SEND_SPREAD = 3600
     16 
     17 class logging_CrashSender(crash_test.CrashTest):
     18     """
     19       End-to-end test of crash_sender.
     20     """
     21     version = 1
     22 
     23 
     24     def _check_hardware_info(self, result):
     25         # Get board name
     26         lsb_release = utils.read_file('/etc/lsb-release')
     27         board_match = re.search(r'CHROMEOS_RELEASE_BOARD=(.*)', lsb_release)
     28         if not ('Board: %s' % board_match.group(1)) in result['output']:
     29             raise error.TestFail('Missing board name %s in output' %
     30                                  board_match.group(1))
     31         # Get hwid
     32         with os.popen("crossystem hwid 2>/dev/null", "r") as hwid_proc:
     33             hwclass = hwid_proc.read()
     34         if not hwclass:
     35             hwclass = 'undefined'
     36         if not ('HWClass: %s' % hwclass) in result['output']:
     37             raise error.TestFail('Expected hwclass %s in output' % hwclass)
     38 
     39 
     40     def _check_simple_minidump_send(self, report, log_path=None):
     41         result = self._call_sender_one_crash(report=report)
     42         if (result['report_exists'] or
     43             result['rate_count'] != 1 or
     44             not result['send_attempt'] or
     45             not result['send_success'] or
     46             result['sleep_time'] < 0 or
     47             result['sleep_time'] >= _SECONDS_SEND_SPREAD or
     48             result['report_kind'] != 'minidump' or
     49             result['report_payload'] != self.get_crash_dir_name(
     50                 '%s.dmp' % self._FAKE_TEST_BASENAME) or
     51             result['exec_name'] != 'fake' or
     52             not 'Version: my_ver' in result['output']):
     53             raise error.TestFail('Simple minidump send failed')
     54         if log_path and not ('log: @%s' % log_path) in result['output']:
     55             raise error.TestFail('Minidump send missing log')
     56         self._check_hardware_info(result)
     57         # Also test "Image type" field.  Note that it will not be "dev" even
     58         # on a dev build because crash-test-in-progress will exist.
     59         if result['image_type']:
     60             raise error.TestFail('Image type "%s" should not exist' %
     61                                  result['image_type'])
     62         # Also test "Boot mode" field.  Note that it will not be "dev" even
     63         # when booting in dev mode because crash-test-in-progress will exist.
     64         if result['boot_mode']:
     65             raise error.TestFail('Boot mode "%s" should not exist' %
     66                                  result['boot_mode'])
     67 
     68 
     69     def _test_sender_simple_minidump(self):
     70         """Test sending a single minidump crash report."""
     71         self._check_simple_minidump_send(None)
     72 
     73 
     74     def _test_sender_simple_minidump_with_log(self):
     75         """Test that a minidump report with an auxiliary log is sent."""
     76         dmp_path = self.write_crash_dir_entry(
     77             '%s.dmp' % self._FAKE_TEST_BASENAME, '')
     78         log_path = self.write_crash_dir_entry(
     79             '%s.log' % self._FAKE_TEST_BASENAME, '')
     80         meta_path = self.write_fake_meta(
     81             '%s.meta' % self._FAKE_TEST_BASENAME, 'fake', dmp_path,
     82             log=log_path)
     83         self._check_simple_minidump_send(meta_path, log_path)
     84 
     85 
     86     def _shift_file_mtime(self, path, delta):
     87         statinfo = os.stat(path)
     88         os.utime(path, (statinfo.st_atime,
     89                         statinfo.st_mtime + delta))
     90 
     91 
     92     def _test_sender_simple_old_minidump(self):
     93         """Test that old minidumps and metadata are sent."""
     94         dmp_path = self.write_crash_dir_entry(
     95             '%s.dmp' % self._FAKE_TEST_BASENAME, '')
     96         meta_path = self.write_fake_meta(
     97             '%s.meta' % self._FAKE_TEST_BASENAME, 'fake', dmp_path)
     98         self._shift_file_mtime(dmp_path, _25_HOURS_AGO)
     99         self._shift_file_mtime(meta_path, _25_HOURS_AGO)
    100         self._check_simple_minidump_send(meta_path)
    101 
    102 
    103     def _test_sender_simple_kernel_crash(self):
    104         """Test sending a single kcrash report."""
    105         kcrash_fake_report = self.write_crash_dir_entry(
    106             'kernel.today.kcrash', '')
    107         self.write_fake_meta('kernel.today.meta',
    108                              'kernel',
    109                              kcrash_fake_report)
    110         result = self._call_sender_one_crash(report=kcrash_fake_report)
    111         if (result['report_exists'] or
    112             result['rate_count'] != 1 or
    113             not result['send_attempt'] or
    114             not result['send_success'] or
    115             result['sleep_time'] < 0 or
    116             result['sleep_time'] >= _SECONDS_SEND_SPREAD or
    117             result['report_kind'] != 'kcrash' or
    118             (result['report_payload'] !=
    119              self.get_crash_dir_name('kernel.today.kcrash')) or
    120             result['exec_name'] != 'kernel'):
    121             raise error.TestFail('Simple kcrash send failed')
    122         self._check_hardware_info(result)
    123 
    124 
    125     def _test_sender_pausing(self):
    126         """Test the sender returns immediately when the pause file is present.
    127 
    128         This is testing the sender's test functionality - if this regresses,
    129         other tests can become flaky because the cron-started sender may run
    130         asynchronously to these tests.  Disable child sending as normally
    131         this environment configuration allows our children to run in spite of
    132         the pause file."""
    133         self._set_system_sending(False)
    134         self._set_child_sending(False)
    135         result = self._call_sender_one_crash(should_fail=True)
    136         if (not result['report_exists'] or
    137             not 'Exiting early due to' in result['output'] or
    138             result['send_attempt']):
    139             raise error.TestFail('Sender did not pause')
    140 
    141 
    142     def _test_sender_reports_disabled(self):
    143         """Test that when reporting is disabled, we don't send."""
    144         result = self._call_sender_one_crash(reports_enabled=False)
    145         if (result['report_exists'] or
    146             not 'Crash reporting is disabled' in result['output'] or
    147             result['send_attempt']):
    148             raise error.TestFail('Sender did not handle reports disabled')
    149 
    150 
    151     def _test_sender_rate_limiting(self):
    152         """Test the sender properly rate limits and sends with delay."""
    153         sleep_times = []
    154         for i in range(1, _DAILY_RATE_LIMIT + 1):
    155             result = self._call_sender_one_crash()
    156             if not result['send_attempt'] or not result['send_success']:
    157                 raise error.TestFail('Crash uploader did not send on #%d' % i)
    158             if result['rate_count'] != i:
    159                 raise error.TestFail('Did not properly persist rate on #%d' % i)
    160             sleep_times.append(result['sleep_time'])
    161         logging.debug('Sleeps between sending crashes were: %s', sleep_times)
    162         unique_times = {}
    163         for i in range(0, _DAILY_RATE_LIMIT):
    164             unique_times[sleep_times[i]] = True
    165         if len(unique_times) < _MIN_UNIQUE_TIMES:
    166             raise error.TestFail('Expected at least %d unique times: %s' %
    167                                  _MIN_UNIQUE_TIMES, sleep_times)
    168         # Now the _DAILY_RATE_LIMIT ^ th send request should fail.
    169         result = self._call_sender_one_crash()
    170         if (not result['report_exists'] or
    171             not 'Cannot send more crashes' in result['output'] or
    172             result['rate_count'] != _DAILY_RATE_LIMIT):
    173             raise error.TestFail('Crash rate limiting did not take effect')
    174 
    175         # Set one rate file a day earlier and verify can send
    176         rate_files = os.listdir(self._CRASH_SENDER_RATE_DIR)
    177         rate_path = os.path.join(self._CRASH_SENDER_RATE_DIR, rate_files[0])
    178         self._shift_file_mtime(rate_path, _25_HOURS_AGO)
    179         utils.system('ls -l ' + self._CRASH_SENDER_RATE_DIR)
    180         result = self._call_sender_one_crash()
    181         if (not result['send_attempt'] or
    182             not result['send_success'] or
    183             result['rate_count'] != _DAILY_RATE_LIMIT):
    184             raise error.TestFail('Crash not sent even after 25hrs pass')
    185 
    186 
    187     def _test_sender_single_instance(self):
    188         """Test the sender fails to start when another instance is running."""
    189         with self.hold_crash_lock():
    190             result = self._call_sender_one_crash(should_fail=True)
    191             if (not 'Already running; quitting.' in result['output'] or
    192                 result['send_attempt'] or not result['report_exists']):
    193                 raise error.TestFail('Allowed multiple instances to run')
    194 
    195 
    196     def _test_sender_send_fails(self):
    197         """Test that when the send fails we try again later."""
    198         result = self._call_sender_one_crash(send_success=False)
    199         if not result['send_attempt'] or result['send_success']:
    200             raise error.TestError('Did not properly cause a send failure')
    201         if result['rate_count'] != 1:
    202             raise error.TestFail('Did not count a failed send against rate '
    203                                  'limiting')
    204         if not result['report_exists']:
    205             raise error.TestFail('Expected minidump to be saved for later '
    206                                  'sending')
    207 
    208         # Also test "Image type" field.  For testing purposes, we set it upon
    209         # mock failure.  Note that it will not be "dev" even on a dev build
    210         # because crash-test-in-progress will exist.
    211         if not result['image_type']:
    212             raise error.TestFail('Missing image type on mock failure')
    213         if result['image_type'] != 'mock-fail':
    214             raise error.TestFail('Incorrect image type on mock failure '
    215                                  '("%s" != "mock-fail")' %
    216                                  result['image_type'])
    217 
    218 
    219     def _test_sender_orphaned_files(self):
    220         """Test that payload and unknown files that are old are removed."""
    221         core_file = self.write_crash_dir_entry('random1.core', '')
    222         unknown_file = self.write_crash_dir_entry('.unknown', '')
    223         # As new files, we expect crash_sender to leave these alone.
    224         results = self._call_sender_one_crash()
    225         if ('Removing old orphaned file' in results['output'] or
    226             not os.path.exists(core_file) or
    227             not os.path.exists(unknown_file)):
    228             raise error.TestFail('New orphaned files were removed')
    229         self._shift_file_mtime(core_file, _25_HOURS_AGO)
    230         self._shift_file_mtime(unknown_file, _25_HOURS_AGO)
    231         results = self._call_sender_one_crash()
    232         if (not 'Removing old orphaned file' in results['output'] or
    233             os.path.exists(core_file) or os.path.exists(unknown_file)):
    234             raise error.TestFail(
    235                 'Old orphaned files were not removed')
    236 
    237 
    238     def _test_sender_incomplete_metadata(self):
    239         """Test that incomplete metadata file is removed once old."""
    240         dmp_file = self.write_crash_dir_entry('incomplete.1.2.3.dmp', '')
    241         meta_file = self.write_fake_meta('incomplete.1.2.3.meta',
    242                                          'unknown',
    243                                          dmp_file,
    244                                          complete=False)
    245         # As new files, we expect crash_sender to leave these alone.
    246         results = self._call_sender_one_crash()
    247         if ('Removing recent incomplete report' in results['output'] or
    248             not os.path.exists(meta_file) or
    249             not os.path.exists(dmp_file)):
    250             raise error.TestFail('New unknown files were removed')
    251         self._shift_file_mtime(meta_file, _25_HOURS_AGO)
    252         results = self._call_sender_one_crash()
    253         if (not 'Removing old incomplete metadata' in results['output'] or
    254             os.path.exists(meta_file) or os.path.exists(dmp_file)):
    255             raise error.TestFail(
    256                 'Old unknown/incomplete files were not removed')
    257 
    258 
    259     def _test_sender_missing_payload(self):
    260         meta_file = self.write_fake_meta('bad.meta',
    261                                          'unknown',
    262                                          'bad.dmp')
    263         other_file = self.write_crash_dir_entry('bad.other', '')
    264         results = self._call_sender_one_crash(report=meta_file)
    265         # Should remove this file.
    266         if (not 'Missing payload' in results['output'] or
    267             os.path.exists(meta_file) or
    268             os.path.exists(other_file)):
    269             raise error.TestFail('Missing payload case handled wrong')
    270 
    271 
    272     def _test_sender_error_type(self):
    273         dmp_file = self.write_crash_dir_entry('error_type.dmp', '')
    274         meta_file = self.write_fake_meta('error_type.meta', 'fake', dmp_file,
    275                                          complete=False)
    276         utils.write_keyval(meta_file, {"error_type": "system-issue"})
    277         utils.write_keyval(meta_file, {"done": "1"})
    278         self._set_force_official(True)  # also test this
    279         self._set_mock_developer_mode(True)  # also test "boot_mode" field
    280         result = self._call_sender_one_crash(report=meta_file)
    281         if not result['error_type']:
    282             raise error.TestFail('Missing error type')
    283         if result['error_type'] != 'system-issue':
    284             raise error.TestFail('Incorrect error type "%s"' %
    285                                  result['error_type'])
    286 
    287         # Also test force-official override by checking the image type.  Note
    288         # that it will not be "dev" even on a dev build because
    289         # crash-test-in-progress will exist.
    290         if not result['image_type']:
    291             raise error.TestFail('Missing image type when forcing official')
    292         if result['image_type'] != 'force-official':
    293             raise error.TestFail('Incorrect image type ("%s" != '
    294                                  '"force-official")' % result['image_type'])
    295 
    296         # Also test "Boot mode" field.  For testing purposes, it should
    297         # have been set to "dev" mode.
    298         if not result['boot_mode']:
    299             raise error.TestFail('Missing boot mode when mocking dev mode')
    300         if result['boot_mode'] != 'dev':
    301             raise error.TestFail('Incorrect boot mode when mocking dev mode '
    302                                  '("%s" != "dev")' % result['boot_mode'])
    303 
    304 
    305     def run_once(self):
    306         self.run_crash_tests([
    307             'sender_simple_minidump',
    308             'sender_simple_old_minidump',
    309             'sender_simple_minidump_with_log',
    310             'sender_simple_kernel_crash',
    311             'sender_pausing',
    312             'sender_reports_disabled',
    313             'sender_rate_limiting',
    314             'sender_single_instance',
    315             'sender_send_fails',
    316             'sender_orphaned_files',
    317             'sender_incomplete_metadata',
    318             'sender_missing_payload',
    319             'sender_error_type']);
    320