Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(180)

Side by Side Diff: build/android/pylib/debug_info.py

Issue 10777017: Android: further simplication for test runners. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Patchset Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | build/android/pylib/single_test_runner.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Collect debug info for a test.""" 5 """Collect debug info for a test."""
6 6
7 import datetime 7 import datetime
8 import logging 8 import logging
9 import os 9 import os
10 import re 10 import re
(...skipping 11 matching lines...) Expand all
22 class GTestDebugInfo(object): 22 class GTestDebugInfo(object):
23 """A helper class to get relate debug information for a gtest. 23 """A helper class to get relate debug information for a gtest.
24 24
25 Args: 25 Args:
26 adb: ADB interface the tests are using. 26 adb: ADB interface the tests are using.
27 device: Serial# of the Android device in which the specified gtest runs. 27 device: Serial# of the Android device in which the specified gtest runs.
28 testsuite_name: Name of the specified gtest. 28 testsuite_name: Name of the specified gtest.
29 gtest_filter: Test filter used by the specified gtest. 29 gtest_filter: Test filter used by the specified gtest.
30 """ 30 """
31 31
32 def __init__(self, adb, device, testsuite_name, gtest_filter, 32 def __init__(self, adb, device, testsuite_name, gtest_filter):
33 collect_new_crashes=True):
34 """Initializes the DebugInfo class for a specified gtest.""" 33 """Initializes the DebugInfo class for a specified gtest."""
35 self.adb = adb 34 self.adb = adb
36 self.device = device 35 self.device = device
37 self.testsuite_name = testsuite_name 36 self.testsuite_name = testsuite_name
38 self.gtest_filter = gtest_filter 37 self.gtest_filter = gtest_filter
39 self.logcat_process = None 38 self.logcat_process = None
40 self.has_storage = False 39 self.has_storage = False
41 self.log_dir = None 40 self.log_dir = os.path.join(tempfile.gettempdir(),
42 self.log_file_name = None 41 'gtest_debug_info',
43 self.collect_new_crashes = collect_new_crashes 42 self.testsuite_name,
44 self.old_crash_files = self.ListCrashFiles() 43 self.device)
45 44 if not os.path.exists(self.log_dir):
46 def InitStorage(self): 45 os.makedirs(self.log_dir)
John Grabowski 2012/07/13 18:17:44 cleanup when done? No guarantee ZipAndCleanup() i
bulach 2012/07/16 08:51:16 good point, but I think the only way to achieve th
47 """Initializes the storage in where we put the debug information."""
48 if self.has_storage:
49 return
50 self.has_storage = True
51 self.log_dir = tempfile.mkdtemp()
52 self.log_file_name = os.path.join(self.log_dir, 46 self.log_file_name = os.path.join(self.log_dir,
53 self._GeneratePrefixName() + '_log.txt') 47 self._GeneratePrefixName() + '_log.txt')
54
55 def CleanupStorage(self):
56 """Cleans up the storage in where we put the debug information."""
57 if not self.has_storage:
58 return
59 self.has_storage = False
60 assert os.path.exists(self.log_dir)
61 shutil.rmtree(self.log_dir)
62 self.log_dir = None
63 self.log_file_name = None 48 self.log_file_name = None
gone 2012/07/14 00:33:36 Doesn't 48 cancel out 46?
bulach 2012/07/16 08:51:16 Done.
64 49 self.old_crash_files = self.ListCrashFiles()
65 def GetStoragePath(self):
66 """Returns the path in where we store the debug information."""
67 self.InitStorage()
68 return self.log_dir
69 50
70 def _GetSignatureFromGTestFilter(self): 51 def _GetSignatureFromGTestFilter(self):
71 """Gets a signature from gtest_filter. 52 """Gets a signature from gtest_filter.
72 53
73 Signature is used to identify the tests from which we collect debug 54 Signature is used to identify the tests from which we collect debug
74 information. 55 information.
75 56
76 Returns: 57 Returns:
77 A signature string. Returns 'all' if there is no gtest filter. 58 A signature string. Returns 'all' if there is no gtest filter.
78 """ 59 """
79 if not self.gtest_filter: 60 if not self.gtest_filter:
80 return 'all' 61 return 'all'
81 filename_chars = "-_()%s%s" % (string.ascii_letters, string.digits) 62 filename_chars = "-_()%s%s" % (string.ascii_letters, string.digits)
82 return ''.join(c for c in self.gtest_filter if c in filename_chars) 63 return ''.join(c for c in self.gtest_filter if c in filename_chars)
83 64
84 def _GeneratePrefixName(self): 65 def _GeneratePrefixName(self):
85 """Generates a prefix name for debug information of the test. 66 """Generates a prefix name for debug information of the test.
86 67
87 The prefix name consists of the following: 68 The prefix name consists of the following:
88 (1) root name of test_suite_base. 69 (1) root name of test_suite_base.
89 (2) device serial number. 70 (2) device serial number.
90 (3) filter signature generate from gtest_filter. 71 (3) prefix of filter signature generate from gtest_filter.
91 (4) date & time when calling this method. 72 (4) date & time when calling this method.
92 73
93 Returns: 74 Returns:
94 Name of the log file. 75 Name of the log file.
95 """ 76 """
96 return (os.path.splitext(self.testsuite_name)[0] + '_' + self.device + '_' + 77 return (os.path.splitext(self.testsuite_name)[0] + '_' + self.device + '_' +
97 self._GetSignatureFromGTestFilter() + '_' + 78 self._GetSignatureFromGTestFilter()[:64] + '_' +
John Grabowski 2012/07/13 18:17:44 Seems a little fragile. How about GetSignatureFrom
bulach 2012/07/16 08:51:16 agreed. but again, not sure if picking the nth ite
98 datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S-%f')) 79 datetime.datetime.utcnow().strftime('%Y-%m-%d-%H-%M-%S-%f'))
99 80
100 def StartRecordingLog(self, clear=True, filters=['*:v']): 81 def StartRecordingLog(self, clear=True, filters=['*:v']):
101 """Starts recording logcat output to a file. 82 """Starts recording logcat output to a file.
102 83
103 This call should come before running test, with calling StopRecordingLog 84 This call should come before running test, with calling StopRecordingLog
104 following the tests. 85 following the tests.
105 86
106 Args: 87 Args:
107 clear: True if existing log output should be cleared. 88 clear: True if existing log output should be cleared.
108 filters: A list of logcat filters to be used. 89 filters: A list of logcat filters to be used.
109 """ 90 """
110 self.InitStorage()
111 self.StopRecordingLog() 91 self.StopRecordingLog()
112 if clear: 92 if clear:
113 cmd_helper.RunCmd(['adb', 'logcat', '-c']) 93 cmd_helper.RunCmd(['adb', '-s', self.device, 'logcat', '-c'])
114 logging.info('Start dumping log to %s ...' % self.log_file_name) 94 logging.info('Start dumping log to %s ...' % self.log_file_name)
115 command = 'adb logcat -v threadtime %s > %s' % (' '.join(filters), 95 command = 'adb -s %s logcat -v threadtime %s > %s' % (self.device,
116 self.log_file_name) 96 ' '.join(filters),
97 self.log_file_name)
117 self.logcat_process = subprocess.Popen(command, shell=True) 98 self.logcat_process = subprocess.Popen(command, shell=True)
118 99
119 def StopRecordingLog(self): 100 def StopRecordingLog(self):
120 """Stops an existing logcat recording subprocess.""" 101 """Stops an existing logcat recording subprocess."""
121 if not self.logcat_process: 102 if not self.logcat_process:
122 return 103 return
123 # Cannot evaluate directly as 0 is a possible value. 104 # Cannot evaluate directly as 0 is a possible value.
124 if self.logcat_process.poll() is None: 105 if self.logcat_process.poll() is None:
125 self.logcat_process.kill() 106 self.logcat_process.kill()
126 self.logcat_process = None 107 self.logcat_process = None
127 logging.info('Finish log dump.') 108 logging.info('Finish log dump.')
128 109
129 def TakeScreenshot(self, identifier_mark): 110 def TakeScreenshot(self, identifier_mark):
130 """Takes a screen shot from current specified device. 111 """Takes a screen shot from current specified device.
131 112
132 Args: 113 Args:
133 identifier_mark: A string to identify the screen shot DebugInfo will take. 114 identifier_mark: A string to identify the screen shot DebugInfo will take.
134 It will be part of filename of the screen shot. Empty 115 It will be part of filename of the screen shot. Empty
135 string is acceptable. 116 string is acceptable.
136 Returns: 117 Returns:
137 Returns the file name on the host of the screenshot if successful, 118 Returns the file name on the host of the screenshot if successful,
138 None otherwise. 119 None otherwise.
139 """ 120 """
140 self.InitStorage()
141 assert isinstance(identifier_mark, str) 121 assert isinstance(identifier_mark, str)
122 screenshot_path = os.path.join(os.getenv('ANDROID_HOST_OUT', ''),
123 'bin',
124 'screenshot2')
125 if not os.path.exists(screenshot_path):
126 logging.error('Failed to take screen shot from device %s', self.device)
127 return None
142 shot_path = os.path.join(self.log_dir, ''.join([self._GeneratePrefixName(), 128 shot_path = os.path.join(self.log_dir, ''.join([self._GeneratePrefixName(),
143 identifier_mark, 129 identifier_mark,
144 '_screenshot.png'])) 130 '_screenshot.png']))
145 screenshot_path = os.path.join(os.getenv('ANDROID_HOST_OUT'), 'bin',
146 'screenshot2')
147 re_success = re.compile(re.escape('Success.'), re.MULTILINE) 131 re_success = re.compile(re.escape('Success.'), re.MULTILINE)
148 if re_success.findall(cmd_helper.GetCmdOutput([screenshot_path, '-s', 132 if re_success.findall(cmd_helper.GetCmdOutput([screenshot_path, '-s',
149 self.device, shot_path])): 133 self.device, shot_path])):
150 logging.info("Successfully took a screen shot to %s" % shot_path) 134 logging.info('Successfully took a screen shot to %s', shot_path)
151 return shot_path 135 return shot_path
152 logging.error('Failed to take screen shot from device %s' % self.device) 136 logging.error('Failed to take screen shot from device %s', self.device)
153 return None 137 return None
154 138
155 def ListCrashFiles(self): 139 def ListCrashFiles(self):
156 """Collects crash files from current specified device. 140 """Collects crash files from current specified device.
157 141
158 Returns: 142 Returns:
159 A dict of crash files in format {"name": (size, lastmod), ...}. 143 A dict of crash files in format {"name": (size, lastmod), ...}.
160 """ 144 """
161 if not self.collect_new_crashes:
162 return {}
163 return self.adb.ListPathContents(TOMBSTONE_DIR) 145 return self.adb.ListPathContents(TOMBSTONE_DIR)
164 146
165 def ArchiveNewCrashFiles(self): 147 def ArchiveNewCrashFiles(self):
166 """Archives the crash files newly generated until calling this method.""" 148 """Archives the crash files newly generated until calling this method."""
167 if not self.collect_new_crashes:
168 return
169 current_crash_files = self.ListCrashFiles() 149 current_crash_files = self.ListCrashFiles()
170 files = [] 150 files = []
171 for f in current_crash_files: 151 for f in current_crash_files:
172 if f not in self.old_crash_files: 152 if f not in self.old_crash_files:
173 files += [f] 153 files += [f]
174 elif current_crash_files[f] != self.old_crash_files[f]: 154 elif current_crash_files[f] != self.old_crash_files[f]:
175 # Tomestones dir can only have maximum 10 files, so we need to compare 155 # Tombstones dir can only have maximum 10 files, so we need to compare
176 # size and timestamp information of file if the file exists. 156 # size and timestamp information of file if the file exists.
177 files += [f] 157 files += [f]
178 if files: 158 if files:
179 logging.info('New crash file(s):%s' % ' '.join(files)) 159 logging.info('New crash file(s):%s' % ' '.join(files))
180 for f in files: 160 for f in files:
181 self.adb.Adb().Pull(TOMBSTONE_DIR + f, 161 self.adb.Adb().Pull(TOMBSTONE_DIR + f,
182 os.path.join(self.GetStoragePath(), f)) 162 os.path.join(self.log_dir, f))
183 163
184 @staticmethod 164 @staticmethod
185 def ZipAndCleanResults(dest_dir, dump_file_name, debug_info_list): 165 def ZipAndCleanResults(dest_dir, dump_file_name):
186 """A helper method to zip all debug information results into a dump file. 166 """A helper method to zip all debug information results into a dump file.
187 167
188 Args: 168 Args:
189 dest-dir: Dir path in where we put the dump file. 169 dest-dir: Dir path in where we put the dump file.
190 dump_file_name: Desired name of the dump file. This method makes sure 170 dump_file_name: Desired name of the dump file. This method makes sure
191 '.zip' will be added as ext name. 171 '.zip' will be added as ext name.
192 debug_info_list: List of all debug info objects.
193 """ 172 """
194 if not dest_dir or not dump_file_name or not debug_info_list: 173 if not dest_dir or not dump_file_name:
195 return 174 return
196 cmd_helper.RunCmd(['mkdir', '-p', dest_dir]) 175 cmd_helper.RunCmd(['mkdir', '-p', dest_dir])
197 log_basename = os.path.basename(dump_file_name) 176 log_basename = os.path.basename(dump_file_name)
198 log_file = os.path.join(dest_dir, 177 log_zip_file = os.path.join(dest_dir,
199 os.path.splitext(log_basename)[0] + '.zip') 178 os.path.splitext(log_basename)[0] + '.zip')
200 logging.info('Zipping debug dumps into %s ...' % log_file) 179 logging.info('Zipping debug dumps into %s ...', log_zip_file)
201 for d in debug_info_list:
202 d.ArchiveNewCrashFiles()
203 # Add new dumps into the zip file. The zip may exist already if previous 180 # Add new dumps into the zip file. The zip may exist already if previous
204 # gtest also dumps the debug information. It's OK since we clean up the old 181 # gtest also dumps the debug information. It's OK since we clean up the old
205 # dumps in each build step. 182 # dumps in each build step.
206 cmd_helper.RunCmd(['zip', '-q', '-r', log_file, 183 log_src_dir = os.path.join(tempfile.gettempdir(), 'gtest_debug_info')
207 ' '.join([d.GetStoragePath() for d in debug_info_list])]) 184 cmd_helper.RunCmd(['zip', '-q', '-r', log_zip_file, log_src_dir])
208 assert os.path.exists(log_file) 185 assert os.path.exists(log_zip_file)
209 for debug_info in debug_info_list: 186 assert os.path.exists(log_src_dir)
210 debug_info.CleanupStorage() 187 shutil.rmtree(log_src_dir)
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/single_test_runner.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698