Bug 469523 - xpcshell-tests: enable leak log in tinderbox (log); (Hv1) Use automatio...
[mozilla-central.git] / build / automationutils.py
blobd8516140c9767894a7fee9b8ed87865d2b50489e
2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
13 # License.
15 # The Original Code is mozilla.org code.
17 # The Initial Developer of the Original Code is The Mozilla Foundation
18 # Portions created by the Initial Developer are Copyright (C) 2009
19 # the Initial Developer. All Rights Reserved.
21 # Contributor(s):
22 # Serge Gautherie <sgautherie.bz@free.fr>
23 # Ted Mielczarek <ted.mielczarek@gmail.com>
25 # Alternatively, the contents of this file may be used under the terms of
26 # either the GNU General Public License Version 2 or later (the "GPL"), or
27 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 # in which case the provisions of the GPL or the LGPL are applicable instead
29 # of those above. If you wish to allow use of your version of this file only
30 # under the terms of either the GPL or the LGPL, and not to allow others to
31 # use your version of this file under the terms of the MPL, indicate your
32 # decision by deleting the provisions above and replace them with the notice
33 # and other provisions required by the GPL or the LGPL. If you do not delete
34 # the provisions above, a recipient may use your version of this file under
35 # the terms of any one of the MPL, the GPL or the LGPL.
37 # ***** END LICENSE BLOCK ***** */
39 import glob, logging, os, subprocess, sys
40 import re
42 __all__ = [
43 "addCommonOptions",
44 "checkForCrashes",
45 "dumpLeakLog",
46 "processLeakLog",
49 log = logging.getLogger()
51 def addCommonOptions(parser, defaults={}):
52 parser.add_option("--xre-path",
53 action = "store", type = "string", dest = "xrePath",
54 # individual scripts will set a sane default
55 default = None,
56 help = "absolute path to directory containing XRE (probably xulrunner)")
57 if 'SYMBOLS_PATH' not in defaults:
58 defaults['SYMBOLS_PATH'] = None
59 parser.add_option("--symbols-path",
60 action = "store", type = "string", dest = "symbolsPath",
61 default = defaults['SYMBOLS_PATH'],
62 help = "absolute path to directory containing breakpad symbols")
64 def checkForCrashes(dumpDir, symbolsPath, testName=None):
65 stackwalkPath = os.environ.get('MINIDUMP_STACKWALK', None)
66 # try to get the caller's filename if no test name is given
67 if testName is None:
68 try:
69 testName = os.path.basename(sys._getframe(1).f_code.co_filename)
70 except:
71 testName = "unknown"
73 foundCrash = False
74 dumps = glob.glob(os.path.join(dumpDir, '*.dmp'))
75 for d in dumps:
76 log.info("TEST-UNEXPECTED-FAIL | %s | application crashed (minidump found)", testName)
77 if symbolsPath and stackwalkPath:
78 nullfd = open(os.devnull, 'w')
79 # eat minidump_stackwalk errors
80 subprocess.call([stackwalkPath, d, symbolsPath], stderr=nullfd)
81 nullfd.close()
82 os.remove(d)
83 extra = os.path.splitext(d)[0] + ".extra"
84 if os.path.exists(extra):
85 os.remove(extra)
86 foundCrash = True
88 return foundCrash
90 def dumpLeakLog(leakLogFile, filter = False):
91 """Process the leak log, without parsing it.
93 Use this function if you want the raw log only.
94 Use it preferably with the |XPCOM_MEM_LEAK_LOG| environment variable.
95 """
97 # Don't warn (nor "info") if the log file is not there.
98 if not os.path.exists(leakLogFile):
99 return
101 leaks = open(leakLogFile, "r")
102 leakReport = leaks.read()
103 leaks.close()
105 # Only |XPCOM_MEM_LEAK_LOG| reports can be actually filtered out.
106 # Only check whether an actual leak was reported.
107 if filter and not "0 TOTAL " in leakReport:
108 return
110 # Simply copy the log.
111 log.info(leakReport.rstrip("\n"))
113 def processLeakLog(leakLogFile, leakThreshold = 0):
114 """Process the leak log, parsing it.
116 Use this function if you want an additional PASS/FAIL summary.
117 It must be used with the |XPCOM_MEM_BLOAT_LOG| environment variable.
120 if not os.path.exists(leakLogFile):
121 log.info("WARNING | automationutils.processLeakLog() | refcount logging is off, so leaks can't be detected!")
122 return
124 # Per-Inst Leaked Total Rem ...
125 # 0 TOTAL 17 192 419115886 2 ...
126 # 833 nsTimerImpl 60 120 24726 2 ...
127 lineRe = re.compile(r"^\s*\d+\s+(?P<name>\S+)\s+"
128 r"(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s+"
129 r"-?\d+\s+(?P<numLeaked>-?\d+)")
131 leaks = open(leakLogFile, "r")
132 for line in leaks:
133 matches = lineRe.match(line)
134 if (matches and
135 int(matches.group("numLeaked")) == 0 and
136 matches.group("name") != "TOTAL"):
137 continue
138 log.info(line.rstrip())
139 leaks.close()
141 leaks = open(leakLogFile, "r")
142 seenTotal = False
143 prefix = "TEST-PASS"
144 for line in leaks:
145 matches = lineRe.match(line)
146 if not matches:
147 continue
148 name = matches.group("name")
149 size = int(matches.group("size"))
150 bytesLeaked = int(matches.group("bytesLeaked"))
151 numLeaked = int(matches.group("numLeaked"))
152 if size < 0 or bytesLeaked < 0 or numLeaked < 0:
153 log.info("TEST-UNEXPECTED-FAIL | automationutils.processLeakLog() | negative leaks caught!")
154 if name == "TOTAL":
155 seenTotal = True
156 elif name == "TOTAL":
157 seenTotal = True
158 # Check for leaks.
159 if bytesLeaked < 0 or bytesLeaked > leakThreshold:
160 prefix = "TEST-UNEXPECTED-FAIL"
161 leakLog = "TEST-UNEXPECTED-FAIL | automationutils.processLeakLog() | leaked" \
162 " %d bytes during test execution" % bytesLeaked
163 elif bytesLeaked > 0:
164 leakLog = "TEST-PASS | automationutils.processLeakLog() | WARNING leaked" \
165 " %d bytes during test execution" % bytesLeaked
166 else:
167 leakLog = "TEST-PASS | automationutils.processLeakLog() | no leaks detected!"
168 # Remind the threshold if it is not 0, which is the default/goal.
169 if leakThreshold != 0:
170 leakLog += " (threshold set at %d bytes)" % leakThreshold
171 # Log the information.
172 log.info(leakLog)
173 else:
174 if numLeaked != 0:
175 if numLeaked > 1:
176 instance = "instances"
177 rest = " each (%s bytes total)" % matches.group("bytesLeaked")
178 else:
179 instance = "instance"
180 rest = ""
181 log.info("%(prefix)s | automationutils.processLeakLog() | leaked %(numLeaked)d %(instance)s of %(name)s "
182 "with size %(size)s bytes%(rest)s" %
183 { "prefix": prefix,
184 "numLeaked": numLeaked,
185 "instance": instance,
186 "name": name,
187 "size": matches.group("size"),
188 "rest": rest })
189 if not seenTotal:
190 log.info("TEST-UNEXPECTED-FAIL | automationutils.processLeakLog() | missing output line for total leaks!")
191 leaks.close()