media/webrtc/trunk/build/android/pylib/debug_info.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
michael@0 2 # Use of this source code is governed by a BSD-style license that can be
michael@0 3 # found in the LICENSE file.
michael@0 4
michael@0 5 """Collect debug info for a test."""
michael@0 6
michael@0 7 import datetime
michael@0 8 import logging
michael@0 9 import os
michael@0 10 import re
michael@0 11 import shutil
michael@0 12 import string
michael@0 13 import subprocess
michael@0 14 import tempfile
michael@0 15
michael@0 16 import cmd_helper
michael@0 17
michael@0 18
michael@0 19 TOMBSTONE_DIR = '/data/tombstones/'
michael@0 20
michael@0 21
michael@0 22 class GTestDebugInfo(object):
michael@0 23 """A helper class to collect related debug information for a gtest.
michael@0 24
michael@0 25 Debug info is collected in two steps:
michael@0 26 - first, object(s) of this class (one per device), accumulate logs
michael@0 27 and screenshots in tempdir.
michael@0 28 - once the test has finished, call ZipAndCleanResults to create
michael@0 29 a zip containing the logs from all devices, and clean them up.
michael@0 30
michael@0 31 Args:
michael@0 32 adb: ADB interface the tests are using.
michael@0 33 device: Serial# of the Android device in which the specified gtest runs.
michael@0 34 testsuite_name: Name of the specified gtest.
michael@0 35 gtest_filter: Test filter used by the specified gtest.
michael@0 36 """
michael@0 37
michael@0 38 def __init__(self, adb, device, testsuite_name, gtest_filter):
michael@0 39 """Initializes the DebugInfo class for a specified gtest."""
michael@0 40 self.adb = adb
michael@0 41 self.device = device
michael@0 42 self.testsuite_name = testsuite_name
michael@0 43 self.gtest_filter = gtest_filter
michael@0 44 self.logcat_process = None
michael@0 45 self.has_storage = False
michael@0 46 self.log_dir = os.path.join(tempfile.gettempdir(),
michael@0 47 'gtest_debug_info',
michael@0 48 self.testsuite_name,
michael@0 49 self.device)
michael@0 50 if not os.path.exists(self.log_dir):
michael@0 51 os.makedirs(self.log_dir)
michael@0 52 self.log_file_name = os.path.join(self.log_dir,
michael@0 53 self._GeneratePrefixName() + '_log.txt')
michael@0 54 self.old_crash_files = self._ListCrashFiles()
michael@0 55
michael@0 56 def _GetSignatureFromGTestFilter(self):
michael@0 57 """Gets a signature from gtest_filter.
michael@0 58
michael@0 59 Signature is used to identify the tests from which we collect debug
michael@0 60 information.
michael@0 61
michael@0 62 Returns:
michael@0 63 A signature string. Returns 'all' if there is no gtest filter.
michael@0 64 """
michael@0 65 if not self.gtest_filter:
michael@0 66 return 'all'
michael@0 67 filename_chars = "-_()%s%s" % (string.ascii_letters, string.digits)
michael@0 68 signature = ''.join(c for c in self.gtest_filter if c in filename_chars)
michael@0 69 if len(signature) > 64:
michael@0 70 # The signature can't be too long, as it'll be part of a file name.
michael@0 71 signature = signature[:64]
michael@0 72 return signature
michael@0 73
michael@0 74 def _GeneratePrefixName(self):
michael@0 75 """Generates a prefix name for debug information of the test.
michael@0 76
michael@0 77 The prefix name consists of the following:
michael@0 78 (1) root name of test_suite_base.
michael@0 79 (2) device serial number.
michael@0 80 (3) prefix of filter signature generate from gtest_filter.
michael@0 81 (4) date & time when calling this method.
michael@0 82
michael@0 83 Returns:
michael@0 84 Name of the log file.
michael@0 85 """
michael@0 86 return (os.path.splitext(self.testsuite_name)[0] + '_' + self.device + '_' +
michael@0 87 self._GetSignatureFromGTestFilter() + '_' +
michael@0 88 datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S-%f'))
michael@0 89
michael@0 90 def StartRecordingLog(self, clear=True, filters=['*:v']):
michael@0 91 """Starts recording logcat output to a file.
michael@0 92
michael@0 93 This call should come before running test, with calling StopRecordingLog
michael@0 94 following the tests.
michael@0 95
michael@0 96 Args:
michael@0 97 clear: True if existing log output should be cleared.
michael@0 98 filters: A list of logcat filters to be used.
michael@0 99 """
michael@0 100 self.StopRecordingLog()
michael@0 101 if clear:
michael@0 102 cmd_helper.RunCmd(['adb', '-s', self.device, 'logcat', '-c'])
michael@0 103 logging.info('Start dumping log to %s ...', self.log_file_name)
michael@0 104 command = 'adb -s %s logcat -v threadtime %s > %s' % (self.device,
michael@0 105 ' '.join(filters),
michael@0 106 self.log_file_name)
michael@0 107 self.logcat_process = subprocess.Popen(command, shell=True)
michael@0 108
michael@0 109 def StopRecordingLog(self):
michael@0 110 """Stops an existing logcat recording subprocess."""
michael@0 111 if not self.logcat_process:
michael@0 112 return
michael@0 113 # Cannot evaluate directly as 0 is a possible value.
michael@0 114 if self.logcat_process.poll() is None:
michael@0 115 self.logcat_process.kill()
michael@0 116 self.logcat_process = None
michael@0 117 logging.info('Finish log dump.')
michael@0 118
michael@0 119 def TakeScreenshot(self, identifier_mark):
michael@0 120 """Takes a screen shot from current specified device.
michael@0 121
michael@0 122 Args:
michael@0 123 identifier_mark: A string to identify the screen shot DebugInfo will take.
michael@0 124 It will be part of filename of the screen shot. Empty
michael@0 125 string is acceptable.
michael@0 126 Returns:
michael@0 127 Returns the file name on the host of the screenshot if successful,
michael@0 128 None otherwise.
michael@0 129 """
michael@0 130 assert isinstance(identifier_mark, str)
michael@0 131 screenshot_path = os.path.join(os.getenv('ANDROID_HOST_OUT', ''),
michael@0 132 'bin',
michael@0 133 'screenshot2')
michael@0 134 if not os.path.exists(screenshot_path):
michael@0 135 logging.error('Failed to take screen shot from device %s', self.device)
michael@0 136 return None
michael@0 137 shot_path = os.path.join(self.log_dir, ''.join([self._GeneratePrefixName(),
michael@0 138 identifier_mark,
michael@0 139 '_screenshot.png']))
michael@0 140 re_success = re.compile(re.escape('Success.'), re.MULTILINE)
michael@0 141 if re_success.findall(cmd_helper.GetCmdOutput([screenshot_path, '-s',
michael@0 142 self.device, shot_path])):
michael@0 143 logging.info('Successfully took a screen shot to %s', shot_path)
michael@0 144 return shot_path
michael@0 145 logging.error('Failed to take screen shot from device %s', self.device)
michael@0 146 return None
michael@0 147
michael@0 148 def _ListCrashFiles(self):
michael@0 149 """Collects crash files from current specified device.
michael@0 150
michael@0 151 Returns:
michael@0 152 A dict of crash files in format {"name": (size, lastmod), ...}.
michael@0 153 """
michael@0 154 return self.adb.ListPathContents(TOMBSTONE_DIR)
michael@0 155
michael@0 156 def ArchiveNewCrashFiles(self):
michael@0 157 """Archives the crash files newly generated until calling this method."""
michael@0 158 current_crash_files = self._ListCrashFiles()
michael@0 159 files = []
michael@0 160 for f in current_crash_files:
michael@0 161 if f not in self.old_crash_files:
michael@0 162 files += [f]
michael@0 163 elif current_crash_files[f] != self.old_crash_files[f]:
michael@0 164 # Tombstones dir can only have maximum 10 files, so we need to compare
michael@0 165 # size and timestamp information of file if the file exists.
michael@0 166 files += [f]
michael@0 167 if files:
michael@0 168 logging.info('New crash file(s):%s' % ' '.join(files))
michael@0 169 for f in files:
michael@0 170 self.adb.Adb().Pull(TOMBSTONE_DIR + f,
michael@0 171 os.path.join(self.log_dir, f))
michael@0 172
michael@0 173 @staticmethod
michael@0 174 def ZipAndCleanResults(dest_dir, dump_file_name):
michael@0 175 """A helper method to zip all debug information results into a dump file.
michael@0 176
michael@0 177 Args:
michael@0 178 dest_dir: Dir path in where we put the dump file.
michael@0 179 dump_file_name: Desired name of the dump file. This method makes sure
michael@0 180 '.zip' will be added as ext name.
michael@0 181 """
michael@0 182 if not dest_dir or not dump_file_name:
michael@0 183 return
michael@0 184 cmd_helper.RunCmd(['mkdir', '-p', dest_dir])
michael@0 185 log_basename = os.path.basename(dump_file_name)
michael@0 186 log_zip_file = os.path.join(dest_dir,
michael@0 187 os.path.splitext(log_basename)[0] + '.zip')
michael@0 188 logging.info('Zipping debug dumps into %s ...', log_zip_file)
michael@0 189 # Add new dumps into the zip file. The zip may exist already if previous
michael@0 190 # gtest also dumps the debug information. It's OK since we clean up the old
michael@0 191 # dumps in each build step.
michael@0 192 log_src_dir = os.path.join(tempfile.gettempdir(), 'gtest_debug_info')
michael@0 193 cmd_helper.RunCmd(['zip', '-q', '-r', log_zip_file, log_src_dir])
michael@0 194 assert os.path.exists(log_zip_file)
michael@0 195 assert os.path.exists(log_src_dir)
michael@0 196 shutil.rmtree(log_src_dir)

mercurial