| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 # heapcheck_test.py | 6 # heapcheck_test.py |
| 7 | 7 |
| 8 """Wrapper for running the test under heapchecker and analyzing the output.""" | 8 """Wrapper for running the test under heapchecker and analyzing the output.""" |
| 9 | 9 |
| 10 import datetime | 10 import datetime |
| 11 import logging | 11 import logging |
| 12 import os | 12 import os |
| 13 import re | 13 import re |
| 14 | 14 |
| 15 import common | 15 import common |
| 16 import google.path_utils | 16 import google.path_utils |
| 17 import suppressions | 17 import suppressions |
| 18 | 18 |
| 19 | 19 |
| 20 class HeapcheckWrapper(object): | 20 class HeapcheckWrapper(object): |
| 21 TMP_FILE = 'heapcheck.log' | 21 TMP_FILE = 'heapcheck.log' |
| 22 SANITY_TEST_SUPPRESSION = "Heapcheck sanity test" |
| 22 | 23 |
| 23 def __init__(self, supp_files): | 24 def __init__(self, supp_files): |
| 24 self._mode = 'strict' | 25 self._mode = 'strict' |
| 25 self._timeout = 1200 | 26 self._timeout = 1200 |
| 26 self._nocleanup_on_exit = False | 27 self._nocleanup_on_exit = False |
| 27 self._suppressions = [] | 28 self._suppressions = [] |
| 28 for fname in supp_files: | 29 for fname in supp_files: |
| 29 self._suppressions.extend(suppressions.ReadSuppressionsFromFile(fname)) | 30 self._suppressions.extend(suppressions.ReadSuppressionsFromFile(fname)) |
| 30 if os.path.exists(self.TMP_FILE): | 31 if os.path.exists(self.TMP_FILE): |
| 31 os.remove(self.TMP_FILE) | 32 os.remove(self.TMP_FILE) |
| (...skipping 19 matching lines...) Expand all Loading... |
| 51 '/../../third_party/tcmalloc/chromium/src/pprof') | 52 '/../../third_party/tcmalloc/chromium/src/pprof') |
| 52 self.PutEnvAndLog('LD_PRELOAD', '/usr/lib/debug/libstdc++.so') | 53 self.PutEnvAndLog('LD_PRELOAD', '/usr/lib/debug/libstdc++.so') |
| 53 | 54 |
| 54 common.RunSubprocess(proc, self._timeout) | 55 common.RunSubprocess(proc, self._timeout) |
| 55 | 56 |
| 56 # Always return true, even if running the subprocess failed. We depend on | 57 # Always return true, even if running the subprocess failed. We depend on |
| 57 # Analyze to determine if the run was valid. (This behaviour copied from | 58 # Analyze to determine if the run was valid. (This behaviour copied from |
| 58 # the purify_test.py script.) | 59 # the purify_test.py script.) |
| 59 return True | 60 return True |
| 60 | 61 |
| 61 def Analyze(self, log_lines): | 62 def Analyze(self, log_lines, check_sanity=False): |
| 62 """Analyzes the app's output and applies suppressions to the reports. | 63 """Analyzes the app's output and applies suppressions to the reports. |
| 63 | 64 |
| 64 Analyze() searches the logs for leak reports and tries to apply | 65 Analyze() searches the logs for leak reports and tries to apply |
| 65 suppressions to them. Unsuppressed reports and other log messages are | 66 suppressions to them. Unsuppressed reports and other log messages are |
| 66 dumped as is. | 67 dumped as is. |
| 67 | 68 |
| 69 If |check_sanity| is True, the list of suppressed reports is searched for a |
| 70 report starting with SANITY_TEST_SUPPRESSION. If there isn't one, Analyze |
| 71 returns 2 regardless of the unsuppressed reports. |
| 72 |
| 68 Args: | 73 Args: |
| 69 log_lines: An iterator over the app's log lines. | 74 log_lines: An iterator over the app's log lines. |
| 75 check_sanity: A flag that determines whether we should check the tool's |
| 76 sanity. |
| 70 Returns: | 77 Returns: |
| 71 1, if unsuppressed reports remain in the output, 0 otherwise. | 78 2, if the sanity check fails, |
| 79 1, if unsuppressed reports remain in the output and the sanity check |
| 80 passes, |
| 81 0, if all the errors are suppressed and the sanity check passes. |
| 72 """ | 82 """ |
| 73 leak_report = re.compile( | 83 leak_report = re.compile( |
| 74 'Leak of ([0-9]*) bytes in ([0-9]*) objects allocated from:') | 84 'Leak of ([0-9]*) bytes in ([0-9]*) objects allocated from:') |
| 75 stack_line = re.compile('\s*@\s*0x[0-9a-fA-F]*\s*(\S*)') | 85 stack_line = re.compile('\s*@\s*[0-9a-fA-F]*\s*(\S*)') |
| 76 return_code = 0 | 86 return_code = 0 |
| 77 # leak signature: [number of bytes, number of objects] | 87 # leak signature: [number of bytes, number of objects] |
| 78 cur_leak_signature = None | 88 cur_leak_signature = None |
| 79 cur_stack = [] | 89 cur_stack = [] |
| 80 cur_report = [] | 90 cur_report = [] |
| 81 # Statistics grouped by suppression description: | 91 # Statistics grouped by suppression description: |
| 82 # [hit count, bytes, objects]. | 92 # [hit count, bytes, objects]. |
| 83 used_suppressions = {} | 93 used_suppressions = {} |
| 84 for line in log_lines: | 94 for line in log_lines: |
| 85 line = line.rstrip() # remove the trailing \n | 95 line = line.rstrip() # remove the trailing \n |
| (...skipping 29 matching lines...) Expand all Loading... |
| 115 used_suppressions[description] = [1] + cur_leak_signature | 125 used_suppressions[description] = [1] + cur_leak_signature |
| 116 cur_stack = [] | 126 cur_stack = [] |
| 117 cur_report = [] | 127 cur_report = [] |
| 118 cur_leak_signature = None | 128 cur_leak_signature = None |
| 119 match = leak_report.match(line) | 129 match = leak_report.match(line) |
| 120 if match: | 130 if match: |
| 121 cur_leak_signature = map(int, match.groups()) | 131 cur_leak_signature = map(int, match.groups()) |
| 122 else: | 132 else: |
| 123 print line | 133 print line |
| 124 # Print the list of suppressions used. | 134 # Print the list of suppressions used. |
| 135 is_sane = False |
| 125 if used_suppressions: | 136 if used_suppressions: |
| 126 print | 137 print |
| 127 print '-----------------------------------------------------' | 138 print '-----------------------------------------------------' |
| 128 print 'Suppressions used:' | 139 print 'Suppressions used:' |
| 129 print ' count bytes objects name' | 140 print ' count bytes objects name' |
| 130 histo = {} | 141 histo = {} |
| 131 for description in used_suppressions: | 142 for description in used_suppressions: |
| 143 if description.startswith(HeapcheckWrapper.SANITY_TEST_SUPPRESSION): |
| 144 is_sane = True |
| 132 hits, bytes, objects = used_suppressions[description] | 145 hits, bytes, objects = used_suppressions[description] |
| 133 line = '%8d %8d %8d %s' % (hits, bytes, objects, description) | 146 line = '%8d %8d %8d %s' % (hits, bytes, objects, description) |
| 134 if hits in histo: | 147 if hits in histo: |
| 135 histo[hits].append(line) | 148 histo[hits].append(line) |
| 136 else: | 149 else: |
| 137 histo[hits] = [line] | 150 histo[hits] = [line] |
| 138 keys = histo.keys() | 151 keys = histo.keys() |
| 139 keys.sort() | 152 keys.sort() |
| 140 for count in keys: | 153 for count in keys: |
| 141 for line in histo[count]: | 154 for line in histo[count]: |
| 142 print line | 155 print line |
| 143 print '-----------------------------------------------------' | 156 print '-----------------------------------------------------' |
| 157 if is_sane: |
| 158 return return_code |
| 159 else: |
| 160 logging.error("Sanity check failed") |
| 161 return 2 |
| 144 | 162 |
| 145 return return_code | 163 def RunTestsAndAnalyze(self, check_sanity): |
| 146 | |
| 147 def RunTestsAndAnalyze(self): | |
| 148 self.Execute() | 164 self.Execute() |
| 149 log_file = file(self.TMP_FILE, 'r') | 165 log_file = file(self.TMP_FILE, 'r') |
| 150 ret = self.Analyze(log_file) | 166 ret = self.Analyze(log_file, check_sanity) |
| 151 log_file.close() | 167 log_file.close() |
| 152 return ret | 168 return ret |
| 153 | 169 |
| 154 def Main(self, args): | 170 def Main(self, args, check_sanity=False): |
| 155 self._args = args | 171 self._args = args |
| 156 start = datetime.datetime.now() | 172 start = datetime.datetime.now() |
| 157 retcode = -1 | 173 retcode = -1 |
| 158 retcode = self.RunTestsAndAnalyze() | 174 retcode = self.RunTestsAndAnalyze(check_sanity) |
| 159 end = datetime.datetime.now() | 175 end = datetime.datetime.now() |
| 160 seconds = (end - start).seconds | 176 seconds = (end - start).seconds |
| 161 hours = seconds / 3600 | 177 hours = seconds / 3600 |
| 162 seconds %= 3600 | 178 seconds %= 3600 |
| 163 minutes = seconds / 60 | 179 minutes = seconds / 60 |
| 164 seconds %= 60 | 180 seconds %= 60 |
| 165 logging.info('elapsed time: %02d:%02d:%02d', hours, minutes, seconds) | 181 logging.info('elapsed time: %02d:%02d:%02d', hours, minutes, seconds) |
| 166 return retcode | 182 return retcode |
| 167 | 183 |
| 168 | 184 |
| 169 def RunTool(args, supp_files): | 185 def RunTool(args, supp_files, module): |
| 170 tool = HeapcheckWrapper(supp_files) | 186 tool = HeapcheckWrapper(supp_files) |
| 171 return tool.Main(args[1:]) | 187 MODULES_TO_SANITY_CHECK = ["base"] |
| 188 check_sanity = module in MODULES_TO_SANITY_CHECK |
| 189 return tool.Main(args[1:], check_sanity) |
| OLD | NEW |