• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2010 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
6from autotest_lib.client.bin import utils
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.cros import cros_ui, upstart
9from autotest_lib.client.cros.crash import user_crash_test
10
11
12_COLLECTION_ERROR_SIGNATURE = 'crash_reporter-user-collection'
13_CORE2MD_PATH = '/usr/bin/core2md'
14_LEAVE_CORE_PATH = '/root/.leave_core'
15_MAX_CRASH_DIRECTORY_SIZE = 32
16
17
18class logging_UserCrash(user_crash_test.UserCrashTest):
19    """Verifies crash reporting for user processes."""
20    version = 1
21
22
23    def _test_reporter_startup(self):
24        """Test that the core_pattern is set up by crash reporter."""
25        # Turn off crash filtering so we see the original setting.
26        self.disable_crash_filtering()
27        output = utils.read_file(self._CORE_PATTERN).rstrip()
28        expected_core_pattern = ('|%s --user=%%P:%%s:%%u:%%e' %
29                                 self._CRASH_REPORTER_PATH)
30        if output != expected_core_pattern:
31            raise error.TestFail('core pattern should have been %s, not %s' %
32                                 (expected_core_pattern, output))
33
34        self._log_reader.set_start_by_reboot(-1)
35
36        if not self._log_reader.can_find('Enabling user crash handling'):
37            raise error.TestFail(
38                'user space crash handling was not started during last boot')
39
40
41    def _test_reporter_shutdown(self):
42        """Test the crash_reporter shutdown code works."""
43        self._log_reader.set_start_by_current()
44        utils.system('%s --clean_shutdown' % self._CRASH_REPORTER_PATH)
45        output = utils.read_file(self._CORE_PATTERN).rstrip()
46        if output != 'core':
47            raise error.TestFail('core pattern should have been core, not %s' %
48                                 output)
49
50
51    def _test_no_crash(self):
52        """Test that the crasher can exit normally."""
53        self._log_reader.set_start_by_current()
54        result = self._run_crasher_process_and_analyze(username='root',
55                                                       cause_crash=False)
56        if (result['crashed'] or
57            result['crash_reporter_caught'] or
58            result['returncode'] != 0):
59            raise error.TestFail('Normal exit of program with dumper failed')
60
61
62    def _test_chronos_crasher(self):
63        """Test a user space crash when running as chronos is handled."""
64        self._check_crashing_process('chronos')
65
66
67    def _test_chronos_crasher_no_consent(self):
68        """Test that without consent no files are stored."""
69        results = self._check_crashing_process('chronos', consent=False)
70
71
72    def _test_root_crasher(self):
73        """Test a user space crash when running as root is handled."""
74        self._check_crashing_process('root')
75
76
77    def _test_root_crasher_no_consent(self):
78        """Test that without consent no files are stored."""
79        results = self._check_crashing_process('root', consent=False)
80
81
82    def _check_filter_crasher(self, should_receive):
83        self._log_reader.set_start_by_current()
84        crasher_basename = os.path.basename(self._crasher_path)
85        utils.system(self._crasher_path, ignore_status=True);
86        if should_receive:
87            to_find = 'Received crash notification for ' + crasher_basename
88        else:
89            to_find = 'Ignoring crash from ' + crasher_basename
90        utils.poll_for_condition(
91            lambda: self._log_reader.can_find(to_find),
92            timeout=10,
93            exception=error.TestError(
94              'Timeout waiting for: ' + to_find + ' in ' +
95              self._log_reader.get_logs()))
96
97
98    def _test_crash_filtering(self):
99        """Test that crash filtering (a feature needed for testing) works."""
100        crasher_basename = os.path.basename(self._crasher_path)
101        self._log_reader.set_start_by_current()
102
103        self.enable_crash_filtering('none')
104        self._check_filter_crasher(False)
105
106        self.enable_crash_filtering('sleep')
107        self._check_filter_crasher(False)
108
109        self.disable_crash_filtering()
110        self._check_filter_crasher(True)
111
112
113    def _test_max_enqueued_crashes(self):
114        """Test that _MAX_CRASH_DIRECTORY_SIZE is enforced."""
115        self._log_reader.set_start_by_current()
116        username = 'root'
117
118        crash_dir = self._get_crash_dir(username)
119        full_message = ('Crash directory %s already full with %d pending '
120                        'reports' % (crash_dir, _MAX_CRASH_DIRECTORY_SIZE))
121
122        # Fill up the queue.
123        for i in range(0, _MAX_CRASH_DIRECTORY_SIZE):
124          result = self._run_crasher_process(username)
125          if not result['crashed']:
126            raise error.TestFail('failure while setting up queue: %d' %
127                                 result['returncode'])
128          if self._log_reader.can_find(full_message):
129            raise error.TestFail('unexpected full message: ' + full_message)
130
131        crash_dir_size = len(os.listdir(crash_dir))
132        # For debugging
133        utils.system('ls -l %s' % crash_dir)
134        logging.info('Crash directory had %d entries', crash_dir_size)
135
136        # Crash a bunch more times, but make sure no new reports
137        # are enqueued.
138        for i in range(0, 10):
139          self._log_reader.set_start_by_current()
140          result = self._run_crasher_process(username)
141          logging.info('New log messages: %s', self._log_reader.get_logs())
142          if not result['crashed']:
143            raise error.TestFail('failure after setting up queue: %d' %
144                                 result['returncode'])
145          utils.poll_for_condition(
146              lambda: self._log_reader.can_find(full_message),
147              timeout=20,
148              exception=error.TestFail('expected full message: ' +
149                                       full_message))
150          if crash_dir_size != len(os.listdir(crash_dir)):
151            utils.system('ls -l %s' % crash_dir)
152            raise error.TestFail('expected no new files (now %d were %d)',
153                                 len(os.listdir(crash_dir)),
154                                 crash_dir_size)
155
156
157    def _check_collection_failure(self, test_option, failure_string):
158        # Add parameter to core_pattern.
159        old_core_pattern = utils.read_file(self._CORE_PATTERN)[:-1]
160        try:
161            utils.system('echo "%s %s" > %s' % (old_core_pattern, test_option,
162                                                self._CORE_PATTERN))
163            result = self._run_crasher_process_and_analyze('root',
164                                                           consent=True)
165            self._check_crashed_and_caught(result)
166            if not self._log_reader.can_find(failure_string):
167                raise error.TestFail('Did not find fail string in log %s' %
168                                     failure_string)
169            if result['minidump']:
170                raise error.TestFail('failed collection resulted in minidump')
171            if not result['log']:
172                raise error.TestFail('failed collection had no log')
173            log_contents = utils.read_file(result['log'])
174            logging.debug('Log contents were: %s', log_contents)
175            if not failure_string in log_contents:
176                raise error.TestFail('Expected logged error '
177                                     '\"%s\" was \"%s\"' %
178                                     (failure_string, log_contents))
179            # Verify we are generating appropriate diagnostic output.
180            if ((not '===ps output===' in log_contents) or
181                (not '===meminfo===' in log_contents)):
182                raise error.TestFail('Expected full logs, got: ' + log_contents)
183            self._check_generated_report_sending(result['meta'],
184                                                 result['log'],
185                                                 result['basename'],
186                                                 'log',
187                                                 _COLLECTION_ERROR_SIGNATURE)
188        finally:
189            utils.system('echo "%s" > %s' % (old_core_pattern,
190                                             self._CORE_PATTERN))
191
192
193    def _test_core2md_failure(self):
194        self._check_collection_failure('--core2md_failure',
195                                       'Problem during %s [result=1]: Usage:' %
196                                       _CORE2MD_PATH)
197
198
199    def _test_internal_directory_failure(self):
200        self._check_collection_failure('--directory_failure',
201                                       'Purposefully failing to create')
202
203
204    def _test_crash_logs_creation(self):
205        # Copy and rename crasher to trigger crash_reporter_logs.conf rule.
206        logs_triggering_crasher = os.path.join(os.path.dirname(self.bindir),
207                                               'crash_log_test')
208        result = self._run_crasher_process_and_analyze(
209            'root', crasher_path=logs_triggering_crasher)
210        self._check_crashed_and_caught(result)
211        contents = utils.read_file(result['log'])
212        if contents != 'hello world\n':
213            raise error.TestFail('Crash log contents unexpected: %s' % contents)
214        if not ('log=' + result['log']) in utils.read_file(result['meta']):
215            raise error.TestFail('Meta file does not reference log')
216
217
218    def _test_crash_log_infinite_recursion(self):
219        # Copy and rename crasher to trigger crash_reporter_logs.conf rule.
220        recursion_triggering_crasher = os.path.join(
221            os.path.dirname(self.bindir), 'crash_log_recursion_test')
222        # The configuration file hardcodes this path, so make sure it's still
223        # the same.
224        if (recursion_triggering_crasher !=
225            '/usr/local/autotest/tests/crash_log_recursion_test'):
226          raise error.TestError('Path to recursion test changed')
227        # Simply completing this command means that we avoided
228        # infinite recursion.
229        result = self._run_crasher_process(
230            'root', crasher_path=recursion_triggering_crasher)
231
232
233    def _check_core_file_persisting(self, expect_persist):
234        self._log_reader.set_start_by_current()
235
236        result = self._run_crasher_process('root')
237
238        if not result['crashed']:
239            raise error.TestFail('crasher did not crash')
240
241        crash_contents = os.listdir(self._get_crash_dir('root'))
242
243        logging.debug('Contents of crash directory: %s', crash_contents)
244        logging.debug('Log messages: %s', self._log_reader.get_logs())
245
246        if expect_persist:
247            if not self._log_reader.can_find('Leaving core file at'):
248                raise error.TestFail('Missing log message')
249            expected_core_files = 1
250        else:
251            if self._log_reader.can_find('Leaving core file at'):
252                raise error.TestFail('Unexpected log message')
253            expected_core_files = 0
254
255        dmp_files = 0
256        core_files = 0
257        for filename in crash_contents:
258            if filename.endswith('.dmp'):
259                dmp_files += 1
260            if filename.endswith('.core'):
261                core_files += 1
262
263        if dmp_files != 1:
264            raise error.TestFail('Should have been exactly 1 dmp file')
265        if core_files != expected_core_files:
266            raise error.TestFail('Should have been exactly %d core files' %
267                                 expected_core_files)
268
269
270    def _test_core_file_removed_in_production(self):
271        """Test that core files do not stick around for production builds."""
272        # Avoid remounting / rw by instead creating a tmpfs in /root and
273        # populating it with everything but the
274        utils.system('tar -cvz -C /root -f /tmp/root.tgz .')
275        utils.system('mount -t tmpfs tmpfs /root')
276        try:
277            utils.system('tar -xvz -C /root -f /tmp/root.tgz .')
278            os.remove(_LEAVE_CORE_PATH)
279            if os.path.exists(_LEAVE_CORE_PATH):
280                raise error.TestFail('.leave_core file did not disappear')
281            self._check_core_file_persisting(False)
282        finally:
283            os.system('umount /root')
284
285
286    def initialize(self):
287        user_crash_test.UserCrashTest.initialize(self)
288
289        # If the device has a GUI, return the device to the sign-in screen, as
290        # some tests will fail inside a user session.
291        if upstart.has_service('ui'):
292            cros_ui.restart()
293
294
295    # TODO(kmixter): Test crashing a process as ntp or some other
296    # non-root, non-chronos user.
297
298    def run_once(self):
299        self._prepare_crasher()
300        self._populate_symbols()
301
302        # Run the test once without re-initializing
303        # to catch problems with the default crash reporting setup
304        self.run_crash_tests(['reporter_startup'],
305                              initialize_crash_reporter=False,
306                              must_run_all=False)
307
308        self.run_crash_tests(['reporter_startup',
309                              'reporter_shutdown',
310                              'no_crash',
311                              'chronos_crasher',
312                              'chronos_crasher_no_consent',
313                              'root_crasher',
314                              'root_crasher_no_consent',
315                              'crash_filtering',
316                              'max_enqueued_crashes',
317                              'core2md_failure',
318                              'internal_directory_failure',
319                              'crash_logs_creation',
320                              'crash_log_infinite_recursion',
321                              'core_file_removed_in_production'],
322                              initialize_crash_reporter=True)
323