Bug 469523 - xpcshell-tests: enable leak log in tinderbox (log); (Fv1) Move code to automationutils.py from automation.py.in
authorSerge Gautherie <sgautherie.bz@free.fr>
Sun, 06 Sep 2009 03:08:49 +0200
changeset 32268 36cbdd2b247b75b847a39c844ad8d71a9dca292b
parent 32267 0c679f4535d6e11d5dde789c7ff5a07ebcbef98c
child 32269 bad2d2351f7f48204200b61b7d95d8a059afa131
push idunknown
push userunknown
push dateunknown
bugs469523
milestone1.9.3a1pre
Bug 469523 - xpcshell-tests: enable leak log in tinderbox (log); (Fv1) Move code to automationutils.py from automation.py.in r=ted.mielczarek
build/automation.py.in
build/automationutils.py
layout/tools/reftest/runreftest.py
testing/mochitest/runtests.py.in
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -65,18 +65,16 @@ SCRIPT_DIR = os.path.abspath(os.path.rea
            "log",
            "runApp",
            "Process",
            "initializeProfile",
            "DIST_BIN",
            "DEFAULT_APP",
            "CERTS_SRC_DIR",
            "environment",
-           "dumpLeakLog",
-           "processLeakLog",
            "IS_TEST_BUILD",
            "IS_DEBUG_BUILD",
            "SYMBOLS_PATH",
           ]
 
 # These are generated in mozilla/build/Makefile.in
 #expand DIST_BIN = __XPC_BIN_PATH__
 #expand IS_WIN32 = len("__WIN32__") != 0
@@ -427,119 +425,16 @@ def environment(env = None, xrePath = DI
 
   return env
 
 
 ###############
 # 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<name>\S+)\s+"
-                      r"(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s+"
-                      r"-?\d+\s+(?P<numLeaked>-?\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,
            debuggerInfo = None, symbolsPath = SYMBOLS_PATH):
   "Run the app, log the duration it took to execute, return the status code."
 
   if IS_TEST_BUILD and runSSLTunnel:
     # create certificate database for the profile
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -1,46 +1,191 @@
-import sys, glob, os, subprocess, logging
+#
+# ***** 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 <sgautherie.bz@free.fr>
+#   Ted Mielczarek <ted.mielczarek@gmail.com>
+#
+# 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
 
 __all__ = [
-    "addCommonOptions",
-    "checkForCrashes",
-    ]
+  "addCommonOptions",
+  "checkForCrashes",
+  "dumpLeakLog",
+  "processLeakLog",
+  ]
 
 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'))
   for d in dumps:
     log.info("TEST-UNEXPECTED-FAIL | %s | application crashed (minidump found)", testName)
     if symbolsPath and stackwalkPath:
       nullfd = open(os.devnull, 'w')
       # eat minidump_stackwalk errors
       subprocess.call([stackwalkPath, d, symbolsPath], stderr=nullfd)
       nullfd.close()
     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<name>\S+)\s+"
+                      r"(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s+"
+                      r"-?\d+\s+(?P<numLeaked>-?\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()
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -15,16 +15,17 @@
 # The Original Code is mozilla.org code.
 #
 # The Initial Developer of the Original Code is
 # Mozilla Foundation.
 # Portions created by the Initial Developer are Copyright (C) 2009
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
+#   Serge Gautherie <sgautherie.bz@free.fr>
 #   Ted Mielczarek <ted.mielczarek@gmail.com>
 #
 # 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
@@ -39,17 +40,17 @@
 """
 Runs the reftest test harness.
 """
 
 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
+from automationutils import addCommonOptions, processLeakLog
 from optparse import OptionParser
 from tempfile import mkdtemp
 
 oldcwd = os.getcwd()
 os.chdir(SCRIPT_DIRECTORY)
 
 def getFullPath(path):
   "Get an absolute path relative to oldcwd."
@@ -146,32 +147,32 @@ Are you executing $objdir/_tests/reftest
     # run once with -silent to let the extension manager do its thing
     # and then exit the app
     automation.log.info("REFTEST INFO | runreftest.py | Performing extension manager registration: start.\n")
     # Don't care about this |status|: |runApp()| reporting it should be enough.
     status = automation.runApp(None, browserEnv, options.app, profileDir,
                                ["-silent"],
                                xrePath=options.xrePath,
                                symbolsPath=options.symbolsPath)
-    # We don't care to call |automation.processLeakLog()| for this step.
+    # We don't care to call |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.
     # The file is not there if leak logging was not enabled in the application build.
     if os.path.exists(leakLogFile):
       os.remove(leakLogFile)
 
     # then again to actually run reftest
     automation.log.info("REFTEST INFO | runreftest.py | Running tests: start.\n")
     reftestlist = getFullPath(args[0])
     status = automation.runApp(None, browserEnv, options.app, profileDir,
                                ["-reftest", reftestlist],
                                xrePath=options.xrePath,
                                symbolsPath=options.symbolsPath)
-    automation.processLeakLog(leakLogFile, options.leakThreshold)
+    processLeakLog(leakLogFile, options.leakThreshold)
     automation.log.info("\nREFTEST INFO | runreftest.py | Running tests: end.")
   finally:
     if profileDir is not None:
       shutil.rmtree(profileDir)
   sys.exit(status)
 
 def copyExtraFilesToProfile(options, profileDir):
   "Copy extra files or dirs specified on the command line to the testing profile."
--- a/testing/mochitest/runtests.py.in
+++ b/testing/mochitest/runtests.py.in
@@ -17,16 +17,17 @@
 # The Initial Developer of the Original Code is
 # Mozilla Foundation.
 # Portions created by the Initial Developer are Copyright (C) 1998
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Robert Sayre <sayrer@gmail.com>
 #   Jeff Walden <jwalden+bmo@mit.edu>
+#   Serge Gautherie <sgautherie.bz@free.fr>
 #
 # 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
@@ -47,17 +48,17 @@ import os
 import os.path
 import sys
 import time
 import shutil
 from urllib import quote_plus as encodeURIComponent
 import urllib2
 import commands
 import automation
-from automationutils import addCommonOptions
+from automationutils import addCommonOptions, processLeakLog
 
 # Path to the test script on the server
 TEST_SERVER_HOST = "localhost:8888"
 TEST_PATH = "/tests/"
 CHROME_PATH = "/redirect.html";
 A11Y_PATH = "/redirect-a11y.html"
 TESTS_URL = "http://" + TEST_SERVER_HOST + TEST_PATH
 CHROMETESTS_URL = "http://" + TEST_SERVER_HOST + CHROME_PATH
@@ -468,17 +469,17 @@ Are you executing $objdir/_tests/testing
   # run once with -silent to let the extension manager do its thing
   # and then exit the app
   automation.log.info("INFO | runtests.py | Performing extension manager registration: start.\n")
   # Don't care about this |status|: |runApp()| reporting it should be enough.
   status = automation.runApp(None, browserEnv, options.app,
                              PROFILE_DIRECTORY, ["-silent"],
                              xrePath = options.xrePath,
                              symbolsPath=options.symbolsPath)
-  # We don't care to call |automation.processLeakLog()| for this step.
+  # We don't care to call |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.
   # The file is not there if leak logging was not enabled in the application build.
   if os.path.exists(LEAK_REPORT_FILE):
     os.remove(LEAK_REPORT_FILE)
 
   # then again to actually run reftest
@@ -491,17 +492,17 @@ Are you executing $objdir/_tests/testing
                              certPath=options.certPath,
                              debuggerInfo=debuggerInfo,
                              symbolsPath=options.symbolsPath)
 
   # Server's no longer needed, and perhaps more importantly, anything it might
   # spew to console shouldn't disrupt the leak information table we print next.
   server.stop()
 
-  automation.processLeakLog(LEAK_REPORT_FILE, options.leakThreshold)
+  processLeakLog(LEAK_REPORT_FILE, options.leakThreshold)
   automation.log.info("\nINFO | runtests.py | Running tests: end.")
 
   # delete the profile and manifest
   os.remove(manifest)
 
   # hanging due to non-halting threads is no fun; assume we hit the errors we
   # were going to hit already and exit.
   sys.exit(status)