| Index: client/site_tests/logging_UserCrash/logging_UserCrash.py
|
| diff --git a/client/site_tests/logging_UserCrash/logging_UserCrash.py b/client/site_tests/logging_UserCrash/logging_UserCrash.py
|
| index 6055e43ea57003c2da4f16124552ffb527e2587c..cda67d93e10ffc5ae981bf51f80e35aea5aec028 100644
|
| --- a/client/site_tests/logging_UserCrash/logging_UserCrash.py
|
| +++ b/client/site_tests/logging_UserCrash/logging_UserCrash.py
|
| @@ -4,27 +4,14 @@
|
|
|
| import grp, logging, os, pwd, re, stat, subprocess
|
| from signal import SIGSEGV
|
| -from autotest_lib.client.bin import site_log_reader, site_utils, test
|
| +from autotest_lib.client.bin import site_crash_test, site_utils, test
|
| from autotest_lib.client.common_lib import error, utils
|
|
|
| -_CONSENT_FILE = '/home/chronos/Consent To Send Stats'
|
| -_CRASH_REPORTER_PATH = '/sbin/crash_reporter'
|
| -_CRASH_SENDER_PATH = '/sbin/crash_sender'
|
| -_CRASH_SENDER_CRON_PATH = '/etc/cron.hourly/crash_sender.hourly'
|
| -_CRASH_SENDER_RATE_DIR = '/var/lib/crash_sender'
|
| -_CRASH_SENDER_RUN_PATH = '/var/run/crash_sender.pid'
|
| _CORE_PATTERN = '/proc/sys/kernel/core_pattern'
|
| -_DAILY_RATE_LIMIT = 8
|
| _LEAVE_CORE_PATH = '/etc/leave_core'
|
| -_MIN_UNIQUE_TIMES = 4
|
| -_MOCK_CRASH_SENDING = '/tmp/mock-crash-sending'
|
| -_PAUSE_FILE = '/tmp/pause-crash-sending'
|
| -_SECONDS_SEND_SPREAD = 3600
|
| -_SYSTEM_CRASH_DIR = '/var/spool/crash'
|
| -_USER_CRASH_DIR = '/home/chronos/user/crash'
|
|
|
|
|
| -class logging_UserCrash(test.test):
|
| +class logging_UserCrash(site_crash_test.CrashTest):
|
| version = 1
|
|
|
|
|
| @@ -33,213 +20,11 @@ class logging_UserCrash(test.test):
|
| utils.system('make clean all')
|
|
|
|
|
| - def _set_sending(self, is_enabled):
|
| - if is_enabled:
|
| - if os.path.exists(_PAUSE_FILE):
|
| - os.remove(_PAUSE_FILE)
|
| - else:
|
| - utils.system('touch ' + _PAUSE_FILE)
|
| -
|
| -
|
| - def _reset_rate_limiting(self):
|
| - utils.system('rm -rf ' + _CRASH_SENDER_RATE_DIR)
|
| -
|
| -
|
| - def _clear_spooled_crashes(self):
|
| - utils.system('rm -rf ' + _SYSTEM_CRASH_DIR)
|
| - utils.system('rm -rf ' + _USER_CRASH_DIR)
|
| -
|
| -
|
| - def _kill_running_sender(self):
|
| - if not os.path.exists(_CRASH_SENDER_RUN_PATH):
|
| - return
|
| - running_pid = int(utils.read_file(_CRASH_SENDER_RUN_PATH))
|
| - logging.warning('Detected running crash sender (%d), killing' %
|
| - running_pid)
|
| - utils.system('kill -9 %d' % running_pid)
|
| - os.remove(_CRASH_SENDER_RUN_PATH)
|
| -
|
| -
|
| - def _set_sending_mock(self, mock_enabled, send_success=True):
|
| - if mock_enabled:
|
| - if send_success:
|
| - data = ''
|
| - else:
|
| - data = '1'
|
| - logging.info('Setting sending mock')
|
| - utils.open_write_close(_MOCK_CRASH_SENDING, data)
|
| - else:
|
| - utils.system('rm -f ' + _MOCK_CRASH_SENDING)
|
| -
|
| -
|
| - def _set_consent(self, has_consent):
|
| - if has_consent:
|
| - utils.open_write_close(_CONSENT_FILE, 'test-consent')
|
| - logging.info('Created ' + _CONSENT_FILE)
|
| - else:
|
| - utils.system('rm -f "%s"' % (_CONSENT_FILE))
|
| -
|
| -
|
| - def _get_pushed_consent_file_path(self):
|
| - return os.path.join(self.bindir, 'pushed_consent')
|
| -
|
| -
|
| - def _push_consent(self):
|
| - if os.path.exists(_CONSENT_FILE):
|
| - os.rename(_CONSENT_FILE, self._get_pushed_consent_file_path())
|
| -
|
| -
|
| - def _pop_consent(self):
|
| - self._set_consent(False)
|
| - if os.path.exists(self._get_pushed_consent_file_path()):
|
| - os.rename(self._get_pushed_consent_file_path(), _CONSENT_FILE)
|
| -
|
| -
|
| - def _get_crash_dir(self, username):
|
| - if username == 'chronos':
|
| - return _USER_CRASH_DIR
|
| - else:
|
| - return _SYSTEM_CRASH_DIR
|
| -
|
| -
|
| - def _initialize_crash_reporter(self):
|
| - utils.system('%s --init --nounclean_check' % _CRASH_REPORTER_PATH)
|
| -
|
| -
|
| - def initialize(self):
|
| - test.test.initialize(self)
|
| - self._log_reader = site_log_reader.LogReader()
|
| -
|
| -
|
| - def cleanup(self):
|
| - self._reset_rate_limiting()
|
| - self._clear_spooled_crashes()
|
| - test.test.cleanup(self)
|
| -
|
| -
|
| - def _create_fake_crash_dir_entry(self, name):
|
| - entry = os.path.join(_SYSTEM_CRASH_DIR, name)
|
| - if not os.path.exists(_SYSTEM_CRASH_DIR):
|
| - os.makedirs(_SYSTEM_CRASH_DIR)
|
| - utils.system('touch ' + entry)
|
| - return entry
|
| -
|
| -
|
| - def _prepare_sender_one_crash(self,
|
| - send_success,
|
| - reports_enabled,
|
| - username,
|
| - minidump):
|
| - self._set_sending_mock(mock_enabled=True, send_success=send_success)
|
| - self._set_consent(reports_enabled)
|
| - if minidump is None:
|
| - minidump = self._create_fake_crash_dir_entry('fake.dmp')
|
| - return minidump
|
| -
|
| -
|
| - def _parse_sender_output(self, output):
|
| - """Parse the log output from the crash_sender script.
|
| -
|
| - This script can run on the logs from either a mocked or true
|
| - crash send.
|
| -
|
| - Args:
|
| - output: output from the script
|
| -
|
| - Returns:
|
| - A dictionary with these values:
|
| - send_attempt: did the script attempt to send a crash.
|
| - send_success: if it attempted, was the crash send successful.
|
| - sleep_time: if it attempted, how long did it sleep before
|
| - sending (if mocked, how long would it have slept)
|
| - output: the output from the script, copied
|
| - """
|
| - sleep_match = re.search('Scheduled to send in (\d+)s', output)
|
| - send_attempt = sleep_match is not None
|
| - if send_attempt:
|
| - sleep_time = int(sleep_match.group(1))
|
| - else:
|
| - sleep_time = None
|
| - send_success = 'Mocking successful send' in output
|
| - return {'send_attempt': send_attempt,
|
| - 'send_success': send_success,
|
| - 'sleep_time': sleep_time,
|
| - 'output': output}
|
| -
|
| -
|
| - def _call_sender_one_crash(self,
|
| - send_success=True,
|
| - reports_enabled=True,
|
| - username='root',
|
| - minidump=None):
|
| - """Call the crash sender script to mock upload one crash.
|
| -
|
| - Args:
|
| - send_success: Mock a successful send if true
|
| - reports_enabled: Has the user consented to sending crash reports.
|
| - username: user to emulate a crash from
|
| - minidump: minidump to use for crash, if None we create one.
|
| -
|
| - Returns:
|
| - Returns a dictionary describing the result with the keys
|
| - from _parse_sender_output, as well as:
|
| - minidump_exists: does the minidump still exist after calling
|
| - send script
|
| - rate_count: how many crashes have been uploaded in the past
|
| - 24 hours.
|
| - """
|
| - minidump = self._prepare_sender_one_crash(send_success,
|
| - reports_enabled,
|
| - username,
|
| - minidump)
|
| - self._log_reader.set_start_by_current()
|
| - script_output = utils.system_output(
|
| - '/bin/sh -c "%s" 2>&1' % _CRASH_SENDER_PATH,
|
| - ignore_status=True)
|
| - # Wait for up to 2s for no crash_sender to be running,
|
| - # otherwise me might get only part of the output.
|
| - site_utils.poll_for_condition(
|
| - lambda: utils.system('pgrep crash_sender',
|
| - ignore_status=True) != 0,
|
| - timeout=2,
|
| - exception=error.TestError(
|
| - 'Timeout waiting for crash_sender to finish: ' +
|
| - self._log_reader.get_logs()))
|
| -
|
| - output = self._log_reader.get_logs()
|
| - logging.debug('Crash sender message output:\n' + output)
|
| - if script_output != '':
|
| - raise error.TestFail(
|
| - 'Unexpected crash_sender stdout/stderr: ' + script_output)
|
| -
|
| - if os.path.exists(minidump):
|
| - minidump_exists = True
|
| - os.remove(minidump)
|
| - else:
|
| - minidump_exists = False
|
| - if os.path.exists(_CRASH_SENDER_RATE_DIR):
|
| - rate_count = len(os.listdir(_CRASH_SENDER_RATE_DIR))
|
| - else:
|
| - rate_count = 0
|
| -
|
| - result = self._parse_sender_output(output)
|
| - result['minidump_exists'] = minidump_exists
|
| - result['rate_count'] = rate_count
|
| -
|
| - # Show the result for debugging but remove 'output' key
|
| - # since it's large and earlier in debug output.
|
| - debug_result = dict(result)
|
| - del debug_result['output']
|
| - logging.debug('Result of send (besides output): %s' % debug_result)
|
| -
|
| - return result
|
| -
|
| -
|
| def _test_reporter_startup(self):
|
| """Test that the core_pattern is set up by crash reporter."""
|
| output = utils.read_file(_CORE_PATTERN).rstrip()
|
| expected_core_pattern = ('|%s --signal=%%s --pid=%%p' %
|
| - _CRASH_REPORTER_PATH)
|
| + self._CRASH_REPORTER_PATH)
|
| if output != expected_core_pattern:
|
| raise error.TestFail('core pattern should have been %s, not %s' %
|
| (expected_core_pattern, output))
|
| @@ -254,160 +39,13 @@ class logging_UserCrash(test.test):
|
| def _test_reporter_shutdown(self):
|
| """Test the crash_reporter shutdown code works."""
|
| self._log_reader.set_start_by_current()
|
| - utils.system('%s --clean_shutdown' % _CRASH_REPORTER_PATH)
|
| + utils.system('%s --clean_shutdown' % self._CRASH_REPORTER_PATH)
|
| output = utils.read_file(_CORE_PATTERN).rstrip()
|
| if output != 'core':
|
| raise error.TestFail('core pattern should have been core, not %s' %
|
| output)
|
|
|
|
|
| - def _test_sender_simple(self):
|
| - """Test sending a single crash."""
|
| - self._set_sending(True)
|
| - result = self._call_sender_one_crash()
|
| - if (result['minidump_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):
|
| - raise error.TestFail('Simple send failed')
|
| -
|
| -
|
| - 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."""
|
| - self._set_sending(False)
|
| - result = self._call_sender_one_crash()
|
| - if (not result['minidump_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."""
|
| - self._set_sending(True)
|
| - result = self._call_sender_one_crash(reports_enabled=False)
|
| - if (result['minidump_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."""
|
| - self._set_sending(True)
|
| - 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['minidump_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(_CRASH_SENDER_RATE_DIR)
|
| - rate_path = os.path.join(_CRASH_SENDER_RATE_DIR, rate_files[0])
|
| - statinfo = os.stat(rate_path)
|
| - os.utime(rate_path, (statinfo.st_atime,
|
| - statinfo.st_mtime - (60 * 60 * 25)))
|
| - utils.system('ls -l ' + _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.
|
| - """
|
| - self._set_sending(True)
|
| - utils.open_write_close(_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['minidump_exists']):
|
| - raise error.TestFail('Allowed multiple instances to run')
|
| - os.remove(_CRASH_SENDER_RUN_PATH)
|
| -
|
| -
|
| - def _test_sender_send_fails(self):
|
| - """Test that when the send fails we try again later."""
|
| - self._set_sending(True)
|
| - 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['minidump_exists']:
|
| - raise error.TestFail('Expected minidump to be saved for later '
|
| - 'sending')
|
| -
|
| -
|
| - def _test_sender_leaves_core_files(self):
|
| - """Test that a core file is left in the send directory.
|
| -
|
| - Core files will only persist for developer/testing images. We
|
| - should never remove such a file."""
|
| - self._set_sending(True)
|
| - # Call prepare function to make sure the directory exists.
|
| - core_name = 'something.ending.with.core'
|
| - core_path = self._create_fake_crash_dir_entry(core_name)
|
| - result = self._call_sender_one_crash()
|
| - if not 'Ignoring core file.' in result['output']:
|
| - raise error.TestFail('Expected ignoring core file message')
|
| - if not os.path.exists(core_path):
|
| - raise error.TestFail('Core file was removed')
|
| -
|
| -
|
| - 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."""
|
| - self._set_sending(True)
|
| - minidump = self._prepare_sender_one_crash(send_success=True,
|
| - reports_enabled=True,
|
| - username='root',
|
| - minidump=None)
|
| - if not os.path.exists(minidump):
|
| - raise error.TestError('minidump not created')
|
| - utils.system(_CRASH_SENDER_CRON_PATH)
|
| - self._log_reader.set_start_by_current()
|
| - site_utils.poll_for_condition(
|
| - lambda: not os.path.exists(minidump),
|
| - desc='minidump to be 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)
|
| - if not result['send_attempt'] or not result['send_success']:
|
| - raise error.TestFail('Cron simple run test failed')
|
| -
|
| -
|
| def _prepare_crasher(self):
|
| """Extract the crasher and set its permissions.
|
|
|
| @@ -762,17 +400,9 @@ class logging_UserCrash(test.test):
|
| # non-root, non-chronos user.
|
|
|
| def run_once(self):
|
| - test_names = [
|
| + self.run_crash_tests([
|
| 'reporter_startup',
|
| 'reporter_shutdown',
|
| - 'sender_simple',
|
| - 'sender_pausing',
|
| - 'sender_reports_disabled',
|
| - 'sender_rate_limiting',
|
| - 'sender_single_instance',
|
| - 'sender_send_fails',
|
| - 'sender_leaves_core_files',
|
| - 'cron_runs',
|
| 'no_crash',
|
| 'chronos_breakpad_crasher',
|
| 'chronos_nobreakpad_crasher',
|
| @@ -780,28 +410,4 @@ class logging_UserCrash(test.test):
|
| 'root_nobreakpad_crasher',
|
| 'core_file_removed_in_production',
|
| 'core_file_persists_in_debug',
|
| - ]
|
| -
|
| - self._push_consent()
|
| -
|
| - # Sanity check test_names is complete
|
| - for attr in dir(self):
|
| - if attr.find('_test_') == 0:
|
| - test_name = attr[6:]
|
| - if not test_name in test_names:
|
| - raise error.TestError('Test %s is missing' % test_name)
|
| -
|
| - for test_name in test_names:
|
| - logging.info(('=' * 20) + ('Running %s' % test_name) + ('=' * 20))
|
| - self._initialize_crash_reporter()
|
| - self._kill_running_sender()
|
| - self._reset_rate_limiting()
|
| - self._clear_spooled_crashes()
|
| - self._set_sending(False)
|
| - getattr(self, '_test_' + test_name)()
|
| -
|
| -
|
| - def cleanup(self):
|
| - self._set_sending(True)
|
| - self._set_sending_mock(mock_enabled=False)
|
| - self._pop_consent()
|
| + ])
|
|
|