OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2006-2008 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 # purify_test.py | 6 # purify_test.py |
7 | 7 |
8 '''Runs an exe through Valgrind and puts the intermediate files in a | 8 '''Runs an exe through Valgrind and puts the intermediate files in a |
9 directory. | 9 directory. |
10 ''' | 10 ''' |
11 | 11 |
12 import datetime | 12 import datetime |
13 import glob | 13 import glob |
14 import logging | 14 import logging |
15 import optparse | 15 import optparse |
16 import os | 16 import os |
17 import re | |
18 import shutil | 17 import shutil |
19 import sys | 18 import sys |
20 import time | |
21 | |
22 import google.path_utils | |
23 | 19 |
24 import common | 20 import common |
25 | 21 |
26 import valgrind_analyze | 22 import valgrind_analyze |
27 | 23 |
28 rmtree = shutil.rmtree | 24 rmtree = shutil.rmtree |
29 | 25 |
30 class Valgrind(): | 26 class Valgrind(object): |
| 27 |
| 28 """Abstract class for running Valgrind. |
| 29 |
| 30 Always subclass this and implement ValgrindCommand() with platform specific |
| 31 stuff. |
| 32 """ |
| 33 |
31 TMP_DIR = "valgrind.tmp" | 34 TMP_DIR = "valgrind.tmp" |
32 | 35 |
33 def __init__(self): | 36 def __init__(self): |
34 self._suppressions_files = [] | 37 self._suppressions_files = [] |
| 38 # If we have a valgrind.tmp directory, we failed to cleanup last time. |
| 39 if os.path.exists(self.TMP_DIR): |
| 40 shutil.rmtree(self.TMP_DIR) |
| 41 os.mkdir(self.TMP_DIR) |
35 | 42 |
36 def CreateOptionParser(self): | 43 def CreateOptionParser(self): |
37 self._parser = optparse.OptionParser("usage: %prog [options] <program to " | 44 self._parser = optparse.OptionParser("usage: %prog [options] <program to " |
38 "test>") | 45 "test>") |
39 self._parser.add_option("-e", "--echo_to_stdout", | |
40 dest="echo_to_stdout", action="store_true", default=False, | |
41 help="echo purify output to standard output") | |
42 self._parser.add_option("-t", "--timeout", | 46 self._parser.add_option("-t", "--timeout", |
43 dest="timeout", metavar="TIMEOUT", default=10000, | 47 dest="timeout", metavar="TIMEOUT", default=10000, |
44 help="timeout in seconds for the run (default 10000)") | 48 help="timeout in seconds for the run (default 10000)") |
45 self._parser.add_option("", "--source_dir", | 49 self._parser.add_option("", "--source_dir", |
46 help="path to top of source tree for this build" | 50 help="path to top of source tree for this build" |
47 "(used to normalize source paths in baseline)") | 51 "(used to normalize source paths in baseline)") |
48 self._parser.add_option("", "--suppressions", default=["."], | 52 self._parser.add_option("", "--suppressions", default=["."], |
49 action="append", | 53 action="append", |
50 help="path to a valgrind suppression file") | 54 help="path to a valgrind suppression file") |
51 self._parser.add_option("", "--generate_suppressions", action="store_true", | 55 self._parser.add_option("", "--generate_suppressions", action="store_true", |
52 default=False, | 56 default=False, |
53 help="Skip analysis and generate suppressions") | 57 help="Skip analysis and generate suppressions") |
54 self._parser.description = __doc__ | 58 self._parser.description = __doc__ |
55 | 59 |
56 def ParseArgv(self): | 60 def ParseArgv(self): |
57 self.CreateOptionParser() | 61 self.CreateOptionParser() |
58 self._options, self._args = self._parser.parse_args() | 62 self._options, self._args = self._parser.parse_args() |
59 self._timeout = int(self._options.timeout) | 63 self._timeout = int(self._options.timeout) |
60 self._suppressions = self._options.suppressions | 64 self._suppressions = self._options.suppressions |
61 self._generate_suppressions = self._options.generate_suppressions | 65 self._generate_suppressions = self._options.generate_suppressions |
62 self._source_dir = self._options.source_dir | 66 self._source_dir = self._options.source_dir |
63 return True | 67 return True |
64 | 68 |
65 def Setup(self): | 69 def Setup(self): |
66 return self.ParseArgv() | 70 return self.ParseArgv() |
67 | 71 |
| 72 def ValgrindCommand(self): |
| 73 """Get the valgrind command to run.""" |
| 74 raise RuntimeError, "Never use Valgrind directly. Always subclass and " \ |
| 75 "implement ValgrindCommand() at least" |
| 76 |
68 def Execute(self): | 77 def Execute(self): |
69 ''' Execute the app to be tested after successful instrumentation. | 78 ''' Execute the app to be tested after successful instrumentation. |
70 Full execution command-line provided by subclassers via proc.''' | 79 Full execution command-line provided by subclassers via proc.''' |
71 logging.info("starting execution...") | 80 logging.info("starting execution...") |
72 # note that self._args begins with the exe to be run | |
73 # TODO(erg): We probably want to get a version of valgrind that supports | |
74 # the "--track-origins" option... | |
75 proc = ["valgrind", "--smc-check=all", "--leak-check=full", | |
76 "--num-callers=30"] | |
77 | 81 |
78 # Either generate suppressions or load them. | 82 proc = self.ValgrindCommand() |
79 if self._generate_suppressions: | |
80 proc += ["--gen-suppressions=all"] | |
81 else: | |
82 proc += ["--xml=yes"] | |
83 | |
84 suppression_count = 0 | |
85 for suppression_file in self._suppressions: | |
86 if os.path.exists(suppression_file): | |
87 suppression_count += 1 | |
88 proc += ["--suppressions=%s" % suppression_file] | |
89 | |
90 if not suppression_count: | |
91 logging.warning("WARNING: NOT USING SUPPRESSIONS!") | |
92 | |
93 proc += ["--log-file=" + self.TMP_DIR + "/valgrind.%p"] + self._args | |
94 | |
95 # If we have a valgrind.tmp directory, we failed to cleanup last time. | |
96 if os.path.exists(self.TMP_DIR): | |
97 shutil.rmtree(self.TMP_DIR) | |
98 os.mkdir(self.TMP_DIR) | |
99 common.RunSubprocess(proc, self._timeout) | 83 common.RunSubprocess(proc, self._timeout) |
100 | 84 |
101 # Always return true, even if running the subprocess failed. We depend on | 85 # Always return true, even if running the subprocess failed. We depend on |
102 # Analyze to determine if the run was valid. (This behaviour copied from | 86 # Analyze to determine if the run was valid. (This behaviour copied from |
103 # the purify_test.py script.) | 87 # the purify_test.py script.) |
104 return True | 88 return True |
105 | 89 |
106 def Analyze(self): | 90 def Analyze(self): |
107 # Glob all the files in the "valgrind.tmp" directory | 91 # Glob all the files in the "valgrind.tmp" directory |
108 filenames = glob.glob(self.TMP_DIR + "/valgrind.*") | 92 filenames = glob.glob(self.TMP_DIR + "/valgrind.*") |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
144 end = datetime.datetime.now() | 128 end = datetime.datetime.now() |
145 seconds = (end - start).seconds | 129 seconds = (end - start).seconds |
146 hours = seconds / 3600 | 130 hours = seconds / 3600 |
147 seconds = seconds % 3600 | 131 seconds = seconds % 3600 |
148 minutes = seconds / 60 | 132 minutes = seconds / 60 |
149 seconds = seconds % 60 | 133 seconds = seconds % 60 |
150 logging.info("elapsed time: %02d:%02d:%02d" % (hours, minutes, seconds)) | 134 logging.info("elapsed time: %02d:%02d:%02d" % (hours, minutes, seconds)) |
151 return retcode | 135 return retcode |
152 | 136 |
153 | 137 |
| 138 class ValgrindLinux(Valgrind): |
| 139 |
| 140 """Valgrind on Linux.""" |
| 141 |
| 142 def __init__(self): |
| 143 Valgrind.__init__(self) |
| 144 |
| 145 def ValgrindCommand(self): |
| 146 """Get the valgrind command to run.""" |
| 147 # note that self._args begins with the exe to be run |
| 148 # TODO(erg): We probably want to get a version of valgrind that supports |
| 149 # the "--track-origins" option... |
| 150 proc = ["valgrind", "--smc-check=all", "--leak-check=full", |
| 151 "--num-callers=30"] |
| 152 |
| 153 # Either generate suppressions or load them. |
| 154 if self._generate_suppressions: |
| 155 proc += ["--gen-suppressions=all"] |
| 156 else: |
| 157 proc += ["--xml=yes"] |
| 158 |
| 159 suppression_count = 0 |
| 160 for suppression_file in self._suppressions: |
| 161 if os.path.exists(suppression_file): |
| 162 suppression_count += 1 |
| 163 proc += ["--suppressions=%s" % suppression_file] |
| 164 |
| 165 if not suppression_count: |
| 166 logging.warning("WARNING: NOT USING SUPPRESSIONS!") |
| 167 |
| 168 proc += ["--log-file=" + self.TMP_DIR + "/valgrind.%p"] + self._args |
| 169 return proc |
| 170 |
| 171 |
| 172 class ValgrindMac(Valgrind): |
| 173 |
| 174 """Valgrind on Mac OS X. |
| 175 |
| 176 Valgrind on OS X does not support suppressions (yet). |
| 177 """ |
| 178 |
| 179 def __init__(self): |
| 180 Valgrind.__init__(self) |
| 181 |
| 182 def ValgrindCommand(self): |
| 183 """Get the valgrind command to run.""" |
| 184 proc = ["valgrind", "--leak-check=full"] |
| 185 proc += ["--log-file=" + self.TMP_DIR + "/valgrind.%p"] + self._args |
| 186 return proc |
| 187 |
| 188 def Analyze(self): |
| 189 # TODO(nirnimesh): Implement analysis later. Valgrind on Mac is new so |
| 190 # analysis might not be useful until we have stable output from valgring |
| 191 return 0 |
| 192 |
154 | 193 |
155 if __name__ == "__main__": | 194 if __name__ == "__main__": |
156 valgrind = Valgrind() | 195 if sys.platform == 'darwin': # Mac |
157 retcode = valgrind.Main() | 196 valgrind = ValgrindMac() |
158 sys.exit(retcode) | 197 retcode = valgrind.Main() |
159 | 198 sys.exit(retcode) |
160 | 199 elif sys.platform == 'linux2': # Linux |
| 200 valgrind = ValgrindLinux() |
| 201 retcode = valgrind.Main() |
| 202 sys.exit(retcode) |
| 203 else: |
| 204 logging.error("Unknown platform: %s" % sys.platform) |
| 205 sys.exit(1) |
OLD | NEW |