diff --git a/build/automation.py.in b/build/automation.py.in index 9e35cc2fe31..685b620d7cf 100644 --- a/build/automation.py.in +++ b/build/automation.py.in @@ -70,6 +70,8 @@ __all__ = [ "DEFAULT_APP", "CERTS_SRC_DIR", "environment", + "dumpLeakLog", + "processLeakLog", "IS_TEST_BUILD", "IS_DEBUG_BUILD", "SYMBOLS_PATH", @@ -430,6 +432,109 @@ def environment(env = None, xrePath = DIST_BIN, crashreporter = True): # RUN THE APP # ############### +def dumpLeakLog(leakLogFile, filter = False): + """Process the leak log, without parsing it. + + Use this function if you want the raw log only. + Use it preferably with the |XPCOM_MEM_LEAK_LOG| environment variable. + """ + + # Don't warn (nor "info") if the log file is not there. + if not os.path.exists(leakLogFile): + return + + leaks = open(leakLogFile, "r") + leakReport = leaks.read() + leaks.close() + + # Only |XPCOM_MEM_LEAK_LOG| reports can be actually filtered out. + # Only check whether an actual leak was reported. + if filter and not "0 TOTAL " in leakReport: + return + + # Simply copy the log. + log.info(leakReport.rstrip("\n")) + +def processLeakLog(leakLogFile, leakThreshold = 0): + """Process the leak log, parsing it. + + Use this function if you want an additional PASS/FAIL summary. + It must be used with the |XPCOM_MEM_BLOAT_LOG| environment variable. + """ + + if not os.path.exists(leakLogFile): + log.info("WARNING refcount logging is off, so leaks can't be detected!") + return + + # Per-Inst Leaked Total Rem ... + # 0 TOTAL 17 192 419115886 2 ... + # 833 nsTimerImpl 60 120 24726 2 ... + lineRe = re.compile(r"^\s*\d+\s+(?P\S+)\s+" + r"(?P-?\d+)\s+(?P-?\d+)\s+" + r"-?\d+\s+(?P-?\d+)") + + leaks = open(leakLogFile, "r") + for line in leaks: + matches = lineRe.match(line) + if (matches and + int(matches.group("numLeaked")) == 0 and + matches.group("name") != "TOTAL"): + continue + log.info(line.rstrip()) + leaks.close() + + leaks = open(leakLogFile, "r") + seenTotal = False + prefix = "TEST-PASS" + for line in leaks: + matches = lineRe.match(line) + if not matches: + continue + name = matches.group("name") + size = int(matches.group("size")) + bytesLeaked = int(matches.group("bytesLeaked")) + numLeaked = int(matches.group("numLeaked")) + if size < 0 or bytesLeaked < 0 or numLeaked < 0: + log.info("TEST-UNEXPECTED-FAIL | runtests-leaks | negative leaks caught!") + if name == "TOTAL": + seenTotal = True + elif name == "TOTAL": + seenTotal = True + # Check for leaks. + if bytesLeaked < 0 or bytesLeaked > leakThreshold: + prefix = "TEST-UNEXPECTED-FAIL" + leakLog = "TEST-UNEXPECTED-FAIL | runtests-leaks | leaked" \ + " %d bytes during test execution" % bytesLeaked + elif bytesLeaked > 0: + leakLog = "TEST-PASS | runtests-leaks | WARNING leaked" \ + " %d bytes during test execution" % bytesLeaked + else: + leakLog = "TEST-PASS | runtests-leaks | no leaks detected!" + # Remind the threshold if it is not 0, which is the default/goal. + if leakThreshold != 0: + leakLog += " (threshold set at %d bytes)" % leakThreshold + # Log the information. + log.info(leakLog) + else: + if numLeaked != 0: + if numLeaked > 1: + instance = "instances" + rest = " each (%s bytes total)" % matches.group("bytesLeaked") + else: + instance = "instance" + rest = "" + log.info("%(prefix)s | runtests-leaks | leaked %(numLeaked)d %(instance)s of %(name)s " + "with size %(size)s bytes%(rest)s" % + { "prefix": prefix, + "numLeaked": numLeaked, + "instance": instance, + "name": name, + "size": matches.group("size"), + "rest": rest }) + if not seenTotal: + log.info("TEST-UNEXPECTED-FAIL | runtests-leaks | missing output line for total leaks!") + leaks.close() + def runApp(testURL, env, app, profileDir, extraArgs, runSSLTunnel = False, utilityPath = DIST_BIN, xrePath = DIST_BIN, certPath = CERTS_SRC_DIR, diff --git a/build/automationutils.py b/build/automationutils.py index e870f6be8f6..a76a992f641 100644 --- a/build/automationutils.py +++ b/build/automationutils.py @@ -1,74 +1,33 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is The Mozilla Foundation -# Portions created by the Initial Developer are Copyright (C) 2009 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Serge Gautherie -# Ted Mielczarek -# -# Alternatively, the contents of this file may be used under the terms of -# either the GNU General Public License Version 2 or later (the "GPL"), or -# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** */ - -import glob, logging, os, subprocess, sys -import re +import sys, glob, os, subprocess, logging __all__ = [ - "addCommonOptions", - "checkForCrashes", - "dumpLeakLog", - "processLeakLog", - ] + "addCommonOptions", + "checkForCrashes", + ] log = logging.getLogger() def addCommonOptions(parser, defaults={}): - parser.add_option("--xre-path", - action = "store", type = "string", dest = "xrePath", - # individual scripts will set a sane default - default = None, - help = "absolute path to directory containing XRE (probably xulrunner)") - if 'SYMBOLS_PATH' not in defaults: - defaults['SYMBOLS_PATH'] = None - parser.add_option("--symbols-path", - action = "store", type = "string", dest = "symbolsPath", - default = defaults['SYMBOLS_PATH'], - help = "absolute path to directory containing breakpad symbols") + parser.add_option("--xre-path", + action = "store", type = "string", dest = "xrePath", + # individual scripts will set a sane default + default = None, + help = "absolute path to directory containing XRE (probably xulrunner)") + if 'SYMBOLS_PATH' not in defaults: + defaults['SYMBOLS_PATH'] = None + parser.add_option("--symbols-path", + action = "store", type = "string", dest = "symbolsPath", + default = defaults['SYMBOLS_PATH'], + help = "absolute path to directory containing breakpad symbols") def checkForCrashes(dumpDir, symbolsPath, testName=None): stackwalkPath = os.environ.get('MINIDUMP_STACKWALK', None) # try to get the caller's filename if no test name is given if testName is None: - try: - testName = os.path.basename(sys._getframe(1).f_code.co_filename) - except: - testName = "unknown" + try: + testName = os.path.basename(sys._getframe(1).f_code.co_filename) + except: + testName = "unknown" foundCrash = False dumps = glob.glob(os.path.join(dumpDir, '*.dmp')) @@ -82,110 +41,6 @@ def checkForCrashes(dumpDir, symbolsPath, testName=None): os.remove(d) extra = os.path.splitext(d)[0] + ".extra" if os.path.exists(extra): - os.remove(extra) + os.remove(extra) foundCrash = True - return foundCrash - -def dumpLeakLog(leakLogFile, filter = False): - """Process the leak log, without parsing it. - - Use this function if you want the raw log only. - Use it preferably with the |XPCOM_MEM_LEAK_LOG| environment variable. - """ - - # Don't warn (nor "info") if the log file is not there. - if not os.path.exists(leakLogFile): - return - - leaks = open(leakLogFile, "r") - leakReport = leaks.read() - leaks.close() - - # Only |XPCOM_MEM_LEAK_LOG| reports can be actually filtered out. - # Only check whether an actual leak was reported. - if filter and not "0 TOTAL " in leakReport: - return - - # Simply copy the log. - log.info(leakReport.rstrip("\n")) - -def processLeakLog(leakLogFile, leakThreshold = 0): - """Process the leak log, parsing it. - - Use this function if you want an additional PASS/FAIL summary. - It must be used with the |XPCOM_MEM_BLOAT_LOG| environment variable. - """ - - if not os.path.exists(leakLogFile): - log.info("WARNING refcount logging is off, so leaks can't be detected!") - return - - # Per-Inst Leaked Total Rem ... - # 0 TOTAL 17 192 419115886 2 ... - # 833 nsTimerImpl 60 120 24726 2 ... - lineRe = re.compile(r"^\s*\d+\s+(?P\S+)\s+" - r"(?P-?\d+)\s+(?P-?\d+)\s+" - r"-?\d+\s+(?P-?\d+)") - - leaks = open(leakLogFile, "r") - for line in leaks: - matches = lineRe.match(line) - if (matches and - int(matches.group("numLeaked")) == 0 and - matches.group("name") != "TOTAL"): - continue - log.info(line.rstrip()) - leaks.close() - - leaks = open(leakLogFile, "r") - seenTotal = False - prefix = "TEST-PASS" - for line in leaks: - matches = lineRe.match(line) - if not matches: - continue - name = matches.group("name") - size = int(matches.group("size")) - bytesLeaked = int(matches.group("bytesLeaked")) - numLeaked = int(matches.group("numLeaked")) - if size < 0 or bytesLeaked < 0 or numLeaked < 0: - log.info("TEST-UNEXPECTED-FAIL | runtests-leaks | negative leaks caught!") - if name == "TOTAL": - seenTotal = True - elif name == "TOTAL": - seenTotal = True - # Check for leaks. - if bytesLeaked < 0 or bytesLeaked > leakThreshold: - prefix = "TEST-UNEXPECTED-FAIL" - leakLog = "TEST-UNEXPECTED-FAIL | runtests-leaks | leaked" \ - " %d bytes during test execution" % bytesLeaked - elif bytesLeaked > 0: - leakLog = "TEST-PASS | runtests-leaks | WARNING leaked" \ - " %d bytes during test execution" % bytesLeaked - else: - leakLog = "TEST-PASS | runtests-leaks | no leaks detected!" - # Remind the threshold if it is not 0, which is the default/goal. - if leakThreshold != 0: - leakLog += " (threshold set at %d bytes)" % leakThreshold - # Log the information. - log.info(leakLog) - else: - if numLeaked != 0: - if numLeaked > 1: - instance = "instances" - rest = " each (%s bytes total)" % matches.group("bytesLeaked") - else: - instance = "instance" - rest = "" - log.info("%(prefix)s | runtests-leaks | leaked %(numLeaked)d %(instance)s of %(name)s " - "with size %(size)s bytes%(rest)s" % - { "prefix": prefix, - "numLeaked": numLeaked, - "instance": instance, - "name": name, - "size": matches.group("size"), - "rest": rest }) - if not seenTotal: - log.info("TEST-UNEXPECTED-FAIL | runtests-leaks | missing output line for total leaks!") - leaks.close() diff --git a/layout/tools/reftest/runreftest.py b/layout/tools/reftest/runreftest.py index 7ff31b0552d..019160e146b 100644 --- a/layout/tools/reftest/runreftest.py +++ b/layout/tools/reftest/runreftest.py @@ -20,7 +20,6 @@ # the Initial Developer. All Rights Reserved. # # Contributor(s): -# Serge Gautherie # Ted Mielczarek # # Alternatively, the contents of this file may be used under the terms of @@ -45,7 +44,7 @@ import sys, shutil, os, os.path SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))) sys.path.append(SCRIPT_DIRECTORY) import automation -from automationutils import addCommonOptions, processLeakLog +from automationutils import addCommonOptions from optparse import OptionParser from tempfile import mkdtemp @@ -152,7 +151,7 @@ Are you executing $objdir/_tests/reftest/runreftest.py?""" \ ["-silent"], xrePath=options.xrePath, symbolsPath=options.symbolsPath) - # We don't care to call |processLeakLog()| for this step. + # We don't care to call |automation.processLeakLog()| for this step. automation.log.info("\nREFTEST INFO | runreftest.py | Performing extension manager registration: end.") # Remove the leak detection file so it can't "leak" to the tests run. @@ -167,7 +166,7 @@ Are you executing $objdir/_tests/reftest/runreftest.py?""" \ ["-reftest", reftestlist], xrePath=options.xrePath, symbolsPath=options.symbolsPath) - processLeakLog(leakLogFile, options.leakThreshold) + automation.processLeakLog(leakLogFile, options.leakThreshold) automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.") finally: if profileDir is not None: diff --git a/testing/mochitest/runtests.py.in b/testing/mochitest/runtests.py.in index 2801678f2f0..a1b02e3e329 100644 --- a/testing/mochitest/runtests.py.in +++ b/testing/mochitest/runtests.py.in @@ -22,7 +22,6 @@ # Contributor(s): # Robert Sayre # Jeff Walden -# Serge Gautherie # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or @@ -53,7 +52,7 @@ from urllib import quote_plus as encodeURIComponent import urllib2 import commands import automation -from automationutils import addCommonOptions, processLeakLog +from automationutils import addCommonOptions # Path to the test script on the server TEST_SERVER_HOST = "localhost:8888" @@ -474,7 +473,7 @@ Are you executing $objdir/_tests/testing/mochitest/runtests.py?""" PROFILE_DIRECTORY, ["-silent"], xrePath = options.xrePath, symbolsPath=options.symbolsPath) - # We don't care to call |processLeakLog()| for this step. + # We don't care to call |automation.processLeakLog()| for this step. automation.log.info("\nINFO | runtests.py | Performing extension manager registration: end.") # Remove the leak detection file so it can't "leak" to the tests run. @@ -497,7 +496,7 @@ Are you executing $objdir/_tests/testing/mochitest/runtests.py?""" # spew to console shouldn't disrupt the leak information table we print next. server.stop() - processLeakLog(LEAK_REPORT_FILE, options.leakThreshold) + automation.processLeakLog(LEAK_REPORT_FILE, options.leakThreshold) automation.log.info("\nINFO | runtests.py | Running tests: end.") # delete the profile and manifest