blob: 681a87a4112c53a6c8a3b6452184223b98d78b56 [file] [log] [blame]
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging, os, re
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import crash_test
_25_HOURS_AGO = -25 * 60 * 60
_CRASH_SENDER_CRON_PATH = '/etc/cron.hourly/crash_sender.hourly'
_DAILY_RATE_LIMIT = 32
_MIN_UNIQUE_TIMES = 4
_HWCLASS_PATH = '/sys/devices/platform/chromeos_acpi/HWID'
_SECONDS_SEND_SPREAD = 3600
class logging_CrashSender(crash_test.CrashTest):
version = 1
def _check_hardware_info(self, result):
# Get board name
lsb_release = utils.read_file('/etc/lsb-release')
board_match = re.search(r'CHROMEOS_RELEASE_BOARD=(.*)', lsb_release)
if not ('Board: %s' % board_match.group(1)) in result['output']:
raise error.TestFail('Missing board name %s in output' %
board_match.group(1))
# Get hwid
hwclass = 'undefined'
if os.path.exists(_HWCLASS_PATH):
hwclass = utils.read_file(_HWCLASS_PATH)
if not ('HWClass: %s' % hwclass) in result['output']:
raise error.TestFail('Expected hwclass %s in output' % hwclass)
def _check_simple_minidump_send(self, report, log_path=None):
result = self._call_sender_one_crash(report=report)
if (result['report_exists'] or
result['rate_count'] != 1 or
not result['send_attempt'] or
not result['send_success'] or
result['sleep_time'] < 0 or
result['sleep_time'] >= _SECONDS_SEND_SPREAD or
result['report_kind'] != 'minidump' or
result['report_payload'] != '/var/spool/crash/fake.dmp' or
result['exec_name'] != 'fake' or
not 'Version: my_ver' in result['output']):
raise error.TestFail('Simple minidump send failed')
if log_path and not ('log: @%s' % log_path) in result['output']:
raise error.TestFail('Minidump send missing log')
self._check_hardware_info(result)
def _test_sender_simple_minidump(self):
"""Test sending a single minidump crash report."""
self._check_simple_minidump_send(None)
def _test_sender_simple_minidump_with_log(self):
"""Test that a minidump report with an auxiliary log is sent."""
dmp_path = self.write_crash_dir_entry('fake.dmp', '')
log_path = self.write_crash_dir_entry('fake.log', '')
meta_path = self.write_fake_meta('fake.meta', 'fake', dmp_path,
log=log_path)
self._check_simple_minidump_send(meta_path, log_path)
def _shift_file_mtime(self, path, delta):
statinfo = os.stat(path)
os.utime(path, (statinfo.st_atime,
statinfo.st_mtime + delta))
def _test_sender_simple_old_minidump(self):
"""Test that old minidumps and metadata are sent."""
dmp_path = self.write_crash_dir_entry('fake.dmp', '')
meta_path = self.write_fake_meta('fake.meta', 'fake', dmp_path)
self._shift_file_mtime(dmp_path, _25_HOURS_AGO)
self._shift_file_mtime(meta_path, _25_HOURS_AGO)
self._check_simple_minidump_send(meta_path)
def _test_sender_simple_kernel_crash(self):
"""Test sending a single kcrash report."""
kcrash_fake_report = self.write_crash_dir_entry(
'kernel.today.kcrash', '')
self.write_fake_meta('kernel.today.meta',
'kernel',
kcrash_fake_report)
result = self._call_sender_one_crash(report=kcrash_fake_report)
if (result['report_exists'] or
result['rate_count'] != 1 or
not result['send_attempt'] or
not result['send_success'] or
result['sleep_time'] < 0 or
result['sleep_time'] >= _SECONDS_SEND_SPREAD or
result['report_kind'] != 'kcrash' or
(result['report_payload'] !=
'/var/spool/crash/kernel.today.kcrash') or
result['exec_name'] != 'kernel'):
raise error.TestFail('Simple kcrash send failed')
self._check_hardware_info(result)
def _test_sender_pausing(self):
"""Test the sender returns immediately when the pause file is present.
This is testing the sender's test functionality - if this regresses,
other tests can become flaky because the cron-started sender may run
asynchronously to these tests. Disable child sending as normally
this environment configuration allows our children to run in spite of
the pause file."""
self._set_system_sending(False)
self._set_child_sending(False)
result = self._call_sender_one_crash()
if (not result['report_exists'] or
not 'Exiting early due to' in result['output'] or
result['send_attempt']):
raise error.TestFail('Sender did not pause')
def _test_sender_reports_disabled(self):
"""Test that when reporting is disabled, we don't send."""
result = self._call_sender_one_crash(reports_enabled=False)
if (result['report_exists'] or
not 'Uploading is disabled' in result['output'] or
result['send_attempt']):
raise error.TestFail('Sender did not handle reports disabled')
def _test_sender_rate_limiting(self):
"""Test the sender properly rate limits and sends with delay."""
sleep_times = []
for i in range(1, _DAILY_RATE_LIMIT + 1):
result = self._call_sender_one_crash()
if not result['send_attempt'] or not result['send_success']:
raise error.TestFail('Crash uploader did not send on #%d' % i)
if result['rate_count'] != i:
raise error.TestFail('Did not properly persist rate on #%d' % i)
sleep_times.append(result['sleep_time'])
logging.debug('Sleeps between sending crashes were: %s' % sleep_times)
unique_times = {}
for i in range(0, _DAILY_RATE_LIMIT):
unique_times[sleep_times[i]] = True
if len(unique_times) < _MIN_UNIQUE_TIMES:
raise error.TestFail('Expected at least %d unique times: %s' %
_MIN_UNIQUE_TIMES, sleep_times)
# Now the _DAILY_RATE_LIMIT ^ th send request should fail.
result = self._call_sender_one_crash()
if (not result['report_exists'] or
not 'Cannot send more crashes' in result['output'] or
result['rate_count'] != _DAILY_RATE_LIMIT):
raise error.TestFail('Crash rate limiting did not take effect')
# Set one rate file a day earlier and verify can send
rate_files = os.listdir(self._CRASH_SENDER_RATE_DIR)
rate_path = os.path.join(self._CRASH_SENDER_RATE_DIR, rate_files[0])
self._shift_file_mtime(rate_path, _25_HOURS_AGO)
utils.system('ls -l ' + self._CRASH_SENDER_RATE_DIR)
result = self._call_sender_one_crash()
if (not result['send_attempt'] or
not result['send_success'] or
result['rate_count'] != _DAILY_RATE_LIMIT):
raise error.TestFail('Crash not sent even after 25hrs pass')
def _test_sender_single_instance(self):
"""Test the sender fails to start when another instance is running.
Here we rely on the sender not checking the other running pid
is of the same instance.
"""
utils.open_write_close(self._CRASH_SENDER_RUN_PATH, str(os.getpid()))
result = self._call_sender_one_crash()
if (not 'Already running.' in result['output'] or
result['send_attempt'] or not result['report_exists']):
raise error.TestFail('Allowed multiple instances to run')
os.remove(self._CRASH_SENDER_RUN_PATH)
def _test_sender_send_fails(self):
"""Test that when the send fails we try again later."""
result = self._call_sender_one_crash(send_success=False)
if not result['send_attempt'] or result['send_success']:
raise error.TestError('Did not properly cause a send failure')
if result['rate_count'] != 1:
raise error.TestFail('Did not count a failed send against rate '
'limiting')
if not result['report_exists']:
raise error.TestFail('Expected minidump to be saved for later '
'sending')
def _test_sender_orphaned_files(self):
"""Test that payload and unknown files that are old are removed."""
core_file = self.write_crash_dir_entry('random1.core', '')
unknown_file = self.write_crash_dir_entry('.unknown', '')
# As new files, we expect crash_sender to leave these alone.
results = self._call_sender_one_crash()
if ('Removing old orphaned file' in results['output'] or
not os.path.exists(core_file) or
not os.path.exists(unknown_file)):
raise error.TestFail('New orphaned files were removed')
self._shift_file_mtime(core_file, _25_HOURS_AGO)
self._shift_file_mtime(unknown_file, _25_HOURS_AGO)
results = self._call_sender_one_crash()
if (not 'Removing old orphaned file' in results['output'] or
os.path.exists(core_file) or os.path.exists(unknown_file)):
raise error.TestFail(
'Old orphaned files were not removed')
def _test_sender_incomplete_metadata(self):
"""Test that incomplete metadata file is removed once old."""
dmp_file = self.write_crash_dir_entry('incomplete.dmp', '')
meta_file = self.write_fake_meta('incomplete.meta',
'unknown',
dmp_file,
complete=False)
# As new files, we expect crash_sender to leave these alone.
results = self._call_sender_one_crash()
if ('Removing recent incomplete report' in results['output'] or
not os.path.exists(meta_file) or
not os.path.exists(dmp_file)):
raise error.TestFail('New unknown files were removed')
self._shift_file_mtime(meta_file, _25_HOURS_AGO)
results = self._call_sender_one_crash()
if (not 'Removing old incomplete metadata' in results['output'] or
os.path.exists(meta_file) or os.path.exists(dmp_file)):
raise error.TestFail(
'Old unknown/incomplete files were not removed')
def _test_sender_missing_payload(self):
meta_file = self.write_fake_meta('bad.meta',
'unknown',
'bad.dmp')
other_file = self.write_crash_dir_entry('bad.other', '')
results = self._call_sender_one_crash(report=meta_file)
# Should remove this file.
if (not 'Missing payload' in results['output'] or
os.path.exists(meta_file) or
os.path.exists(other_file)):
raise error.TestFail('Missing payload case handled wrong')
def _test_cron_runs(self):
"""Test sender runs successfully as part of the hourly cron job.
Assuming we've run test_sender_simple which shows that a minidump
gets removed as part of sending, we run the cron job (which is
asynchronous) and wait for that file to be removed to just verify
the job eventually runs the sender."""
minidump = self._prepare_sender_one_crash(send_success=True,
reports_enabled=True,
report=None)
if not os.path.exists(minidump):
raise error.TestError('minidump not created')
self._log_reader.set_start_by_current()
utils.system(_CRASH_SENDER_CRON_PATH)
self.wait_for_sender_completion()
if os.path.exists(minidump):
raise error.TestFail('minidump was not removed')
crash_sender_log = self._log_reader.get_logs()
logging.debug('Contents of crash sender log: ' + crash_sender_log)
result = self._parse_sender_output(crash_sender_log)
logging.debug('Result of crash send: %s' % result)
if not result['send_attempt'] or not result['send_success']:
raise error.TestFail('Cron simple run test failed')
def run_once(self):
self.run_crash_tests([
'sender_simple_minidump',
'sender_simple_old_minidump',
'sender_simple_minidump_with_log',
'sender_simple_kernel_crash',
'sender_pausing',
'sender_reports_disabled',
'sender_rate_limiting',
'sender_single_instance',
'sender_send_fails',
'sender_orphaned_files',
'sender_incomplete_metadata',
'sender_missing_payload',
'cron_runs'])