| Index: build/android/pylib/debug_info.py
|
| diff --git a/build/android/pylib/debug_info.py b/build/android/pylib/debug_info.py
|
| deleted file mode 100644
|
| index 6f0f55a33f2eafa74dd38d4b5f9625aaf5ce2cc3..0000000000000000000000000000000000000000
|
| --- a/build/android/pylib/debug_info.py
|
| +++ /dev/null
|
| @@ -1,196 +0,0 @@
|
| -# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -"""Collect debug info for a test."""
|
| -
|
| -import datetime
|
| -import logging
|
| -import os
|
| -import re
|
| -import shutil
|
| -import string
|
| -import subprocess
|
| -import tempfile
|
| -
|
| -import cmd_helper
|
| -
|
| -
|
| -TOMBSTONE_DIR = '/data/tombstones/'
|
| -
|
| -
|
| -class GTestDebugInfo(object):
|
| - """A helper class to collect related debug information for a gtest.
|
| -
|
| - Debug info is collected in two steps:
|
| - - first, object(s) of this class (one per device), accumulate logs
|
| - and screenshots in tempdir.
|
| - - once the test has finished, call ZipAndCleanResults to create
|
| - a zip containing the logs from all devices, and clean them up.
|
| -
|
| - Args:
|
| - adb: ADB interface the tests are using.
|
| - device: Serial# of the Android device in which the specified gtest runs.
|
| - testsuite_name: Name of the specified gtest.
|
| - gtest_filter: Test filter used by the specified gtest.
|
| - """
|
| -
|
| - def __init__(self, adb, device, testsuite_name, gtest_filter):
|
| - """Initializes the DebugInfo class for a specified gtest."""
|
| - self.adb = adb
|
| - self.device = device
|
| - self.testsuite_name = testsuite_name
|
| - self.gtest_filter = gtest_filter
|
| - self.logcat_process = None
|
| - self.has_storage = False
|
| - self.log_dir = os.path.join(tempfile.gettempdir(),
|
| - 'gtest_debug_info',
|
| - self.testsuite_name,
|
| - self.device)
|
| - if not os.path.exists(self.log_dir):
|
| - os.makedirs(self.log_dir)
|
| - self.log_file_name = os.path.join(self.log_dir,
|
| - self._GeneratePrefixName() + '_log.txt')
|
| - self.old_crash_files = self._ListCrashFiles()
|
| -
|
| - def _GetSignatureFromGTestFilter(self):
|
| - """Gets a signature from gtest_filter.
|
| -
|
| - Signature is used to identify the tests from which we collect debug
|
| - information.
|
| -
|
| - Returns:
|
| - A signature string. Returns 'all' if there is no gtest filter.
|
| - """
|
| - if not self.gtest_filter:
|
| - return 'all'
|
| - filename_chars = "-_()%s%s" % (string.ascii_letters, string.digits)
|
| - signature = ''.join(c for c in self.gtest_filter if c in filename_chars)
|
| - if len(signature) > 64:
|
| - # The signature can't be too long, as it'll be part of a file name.
|
| - signature = signature[:64]
|
| - return signature
|
| -
|
| - def _GeneratePrefixName(self):
|
| - """Generates a prefix name for debug information of the test.
|
| -
|
| - The prefix name consists of the following:
|
| - (1) root name of test_suite_base.
|
| - (2) device serial number.
|
| - (3) prefix of filter signature generate from gtest_filter.
|
| - (4) date & time when calling this method.
|
| -
|
| - Returns:
|
| - Name of the log file.
|
| - """
|
| - return (os.path.splitext(self.testsuite_name)[0] + '_' + self.device + '_' +
|
| - self._GetSignatureFromGTestFilter() + '_' +
|
| - datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S-%f'))
|
| -
|
| - def StartRecordingLog(self, clear=True, filters=['*:v']):
|
| - """Starts recording logcat output to a file.
|
| -
|
| - This call should come before running test, with calling StopRecordingLog
|
| - following the tests.
|
| -
|
| - Args:
|
| - clear: True if existing log output should be cleared.
|
| - filters: A list of logcat filters to be used.
|
| - """
|
| - self.StopRecordingLog()
|
| - if clear:
|
| - cmd_helper.RunCmd(['adb', '-s', self.device, 'logcat', '-c'])
|
| - logging.info('Start dumping log to %s ...', self.log_file_name)
|
| - command = 'adb -s %s logcat -v threadtime %s > %s' % (self.device,
|
| - ' '.join(filters),
|
| - self.log_file_name)
|
| - self.logcat_process = subprocess.Popen(command, shell=True)
|
| -
|
| - def StopRecordingLog(self):
|
| - """Stops an existing logcat recording subprocess."""
|
| - if not self.logcat_process:
|
| - return
|
| - # Cannot evaluate directly as 0 is a possible value.
|
| - if self.logcat_process.poll() is None:
|
| - self.logcat_process.kill()
|
| - self.logcat_process = None
|
| - logging.info('Finish log dump.')
|
| -
|
| - def TakeScreenshot(self, identifier_mark):
|
| - """Takes a screen shot from current specified device.
|
| -
|
| - Args:
|
| - identifier_mark: A string to identify the screen shot DebugInfo will take.
|
| - It will be part of filename of the screen shot. Empty
|
| - string is acceptable.
|
| - Returns:
|
| - Returns the file name on the host of the screenshot if successful,
|
| - None otherwise.
|
| - """
|
| - assert isinstance(identifier_mark, str)
|
| - screenshot_path = os.path.join(os.getenv('ANDROID_HOST_OUT', ''),
|
| - 'bin',
|
| - 'screenshot2')
|
| - if not os.path.exists(screenshot_path):
|
| - logging.error('Failed to take screen shot from device %s', self.device)
|
| - return None
|
| - shot_path = os.path.join(self.log_dir, ''.join([self._GeneratePrefixName(),
|
| - identifier_mark,
|
| - '_screenshot.png']))
|
| - re_success = re.compile(re.escape('Success.'), re.MULTILINE)
|
| - if re_success.findall(cmd_helper.GetCmdOutput([screenshot_path, '-s',
|
| - self.device, shot_path])):
|
| - logging.info('Successfully took a screen shot to %s', shot_path)
|
| - return shot_path
|
| - logging.error('Failed to take screen shot from device %s', self.device)
|
| - return None
|
| -
|
| - def _ListCrashFiles(self):
|
| - """Collects crash files from current specified device.
|
| -
|
| - Returns:
|
| - A dict of crash files in format {"name": (size, lastmod), ...}.
|
| - """
|
| - return self.adb.ListPathContents(TOMBSTONE_DIR)
|
| -
|
| - def ArchiveNewCrashFiles(self):
|
| - """Archives the crash files newly generated until calling this method."""
|
| - current_crash_files = self._ListCrashFiles()
|
| - files = []
|
| - for f in current_crash_files:
|
| - if f not in self.old_crash_files:
|
| - files += [f]
|
| - elif current_crash_files[f] != self.old_crash_files[f]:
|
| - # Tombstones dir can only have maximum 10 files, so we need to compare
|
| - # size and timestamp information of file if the file exists.
|
| - files += [f]
|
| - if files:
|
| - logging.info('New crash file(s):%s' % ' '.join(files))
|
| - for f in files:
|
| - self.adb.Adb().Pull(TOMBSTONE_DIR + f,
|
| - os.path.join(self.log_dir, f))
|
| -
|
| - @staticmethod
|
| - def ZipAndCleanResults(dest_dir, dump_file_name):
|
| - """A helper method to zip all debug information results into a dump file.
|
| -
|
| - Args:
|
| - dest_dir: Dir path in where we put the dump file.
|
| - dump_file_name: Desired name of the dump file. This method makes sure
|
| - '.zip' will be added as ext name.
|
| - """
|
| - if not dest_dir or not dump_file_name:
|
| - return
|
| - cmd_helper.RunCmd(['mkdir', '-p', dest_dir])
|
| - log_basename = os.path.basename(dump_file_name)
|
| - log_zip_file = os.path.join(dest_dir,
|
| - os.path.splitext(log_basename)[0] + '.zip')
|
| - logging.info('Zipping debug dumps into %s ...', log_zip_file)
|
| - # Add new dumps into the zip file. The zip may exist already if previous
|
| - # gtest also dumps the debug information. It's OK since we clean up the old
|
| - # dumps in each build step.
|
| - log_src_dir = os.path.join(tempfile.gettempdir(), 'gtest_debug_info')
|
| - cmd_helper.RunCmd(['zip', '-q', '-r', log_zip_file, log_src_dir])
|
| - assert os.path.exists(log_zip_file)
|
| - assert os.path.exists(log_src_dir)
|
| - shutil.rmtree(log_src_dir)
|
|
|