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 |