From 247906f078674b3834b7ce1681bc6eefc78d1fbd Mon Sep 17 00:00:00 2001 From: Ed Morley Date: Tue, 19 Mar 2013 17:01:12 +0000 Subject: [PATCH] Bring automationutils.py up to date with the fixes from bug 808410, bug 813577, bug 850681; a=test-only --- build/automationutils.py | 201 ++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 107 deletions(-) diff --git a/build/automationutils.py b/build/automationutils.py index f0c37d946d70..9ba00a5f6241 100644 --- a/build/automationutils.py +++ b/build/automationutils.py @@ -146,24 +146,23 @@ def checkForCrashes(dumpDir, symbolsPath, testName=None): if len(dumps) == 0: return False - foundCrash = False - removeSymbolsPath = False - - # If our symbols are at a remote URL, download them now - if symbolsPath and isURL(symbolsPath): - print "Downloading symbols from: " + symbolsPath - removeSymbolsPath = True - # Get the symbols and write them to a temporary zipfile - data = urllib2.urlopen(symbolsPath) - symbolsFile = tempfile.TemporaryFile() - symbolsFile.write(data.read()) - # extract symbols to a temporary directory (which we'll delete after - # processing all crashes) - symbolsPath = tempfile.mkdtemp() - zfile = ZipFileReader(symbolsFile) - zfile.extractall(symbolsPath) - try: + removeSymbolsPath = False + + # If our symbols are at a remote URL, download them now + if symbolsPath and isURL(symbolsPath): + print "Downloading symbols from: " + symbolsPath + removeSymbolsPath = True + # Get the symbols and write them to a temporary zipfile + data = urllib2.urlopen(symbolsPath) + symbolsFile = tempfile.TemporaryFile() + symbolsFile.write(data.read()) + # extract symbols to a temporary directory (which we'll delete after + # processing all crashes) + symbolsPath = tempfile.mkdtemp() + zfile = ZipFileReader(symbolsFile) + zfile.extractall(symbolsPath) + for d in dumps: stackwalkOutput = [] stackwalkOutput.append("Crash dump filename: " + d) @@ -216,12 +215,11 @@ def checkForCrashes(dumpDir, symbolsPath, testName=None): extra = os.path.splitext(d)[0] + ".extra" if os.path.exists(extra): os.remove(extra) - foundCrash = True finally: if removeSymbolsPath: shutil.rmtree(symbolsPath) - return foundCrash + return True def getFullPath(directory, path): "Get an absolute path relative to 'directory'." @@ -285,9 +283,8 @@ def dumpLeakLog(leakLogFile, filter = False): if not os.path.exists(leakLogFile): return - leaks = open(leakLogFile, "r") - leakReport = leaks.read() - leaks.close() + with open(leakLogFile, "r") as leaks: + leakReport = leaks.read() # Only |XPCOM_MEM_LEAK_LOG| reports can be actually filtered out. # Only check whether an actual leak was reported. @@ -297,9 +294,8 @@ def dumpLeakLog(leakLogFile, filter = False): # Simply copy the log. log.info(leakReport.rstrip("\n")) -def processSingleLeakFile(leakLogFileName, PID, processType, leakThreshold): - """Process a single leak log, corresponding to the specified - process PID and type. +def processSingleLeakFile(leakLogFileName, processType, leakThreshold): + """Process a single leak log. """ # Per-Inst Leaked Total Rem ... @@ -310,85 +306,75 @@ def processSingleLeakFile(leakLogFileName, PID, processType, leakThreshold): r"-?\d+\s+(?P-?\d+)") processString = "" - if PID and processType: - processString = "| %s process %s " % (processType, PID) - leaks = open(leakLogFileName, "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() + if processType: + # eg 'plugin' + processString = " %s process:" % processType - leaks = open(leakLogFileName, "r") - seenTotal = False crashedOnPurpose = False - prefix = "TEST-PASS" - numObjects = 0 - for line in leaks: - if line.find("purposefully crash") > -1: - crashedOnPurpose = True - 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 %s| automationutils.processLeakLog() | negative leaks caught!" % - processString) + totalBytesLeaked = None + leakAnalysis = [] + leakedObjectNames = [] + with open(leakLogFileName, "r") as leaks: + for line in leaks: + if line.find("purposefully crash") > -1: + crashedOnPurpose = True + matches = lineRe.match(line) + if not matches: + # eg: the leak table header row + log.info(line.rstrip()) + continue + name = matches.group("name") + size = int(matches.group("size")) + bytesLeaked = int(matches.group("bytesLeaked")) + numLeaked = int(matches.group("numLeaked")) + # Output the raw line from the leak log table if it is the TOTAL row, + # or is for an object row that has been leaked. + if numLeaked != 0 or name == "TOTAL": + log.info(line.rstrip()) + # Analyse the leak log, but output later or it will interrupt the leak table 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 %s| automationutils.processLeakLog() | leaked" \ - " %d bytes during test execution" % (processString, bytesLeaked) - elif bytesLeaked > 0: - leakLog = "TEST-PASS %s| automationutils.processLeakLog() | WARNING leaked" \ - " %d bytes during test execution" % (processString, bytesLeaked) - else: - leakLog = "TEST-PASS %s| automationutils.processLeakLog() | no leaks detected!" \ - % processString - # 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 = "" - numObjects += 1 - if numObjects > 5: - # don't spam brief tinderbox logs with tons of leak output - prefix = "TEST-INFO" - log.info("%(prefix)s %(process)s| automationutils.processLeakLog() | leaked %(numLeaked)d %(instance)s of %(name)s " - "with size %(size)s bytes%(rest)s" % - { "prefix": prefix, - "process": processString, - "numLeaked": numLeaked, - "instance": instance, - "name": name, - "size": matches.group("size"), - "rest": rest }) - if not seenTotal: - if crashedOnPurpose: - log.info("INFO | automationutils.processLeakLog() | process %s was " \ - "deliberately crashed and thus has no leak log" % PID) - else: - log.info("WARNING | automationutils.processLeakLog() | missing output line for total leaks!") - leaks.close() + totalBytesLeaked = bytesLeaked + if size < 0 or bytesLeaked < 0 or numLeaked < 0: + leakAnalysis.append("TEST-UNEXPECTED-FAIL | leakcheck |%s negative leaks caught!" + % processString) + continue + if name != "TOTAL" and numLeaked != 0: + leakedObjectNames.append(name) + leakAnalysis.append("TEST-INFO | leakcheck |%s leaked %d %s (%s bytes)" + % (processString, numLeaked, name, bytesLeaked)) + log.info('\n'.join(leakAnalysis)) + if totalBytesLeaked is None: + # We didn't see a line with name 'TOTAL' + if crashedOnPurpose: + log.info("TEST-INFO | leakcheck |%s deliberate crash and thus no leak log" + % processString) + else: + # TODO: This should be a TEST-UNEXPECTED-FAIL, but was changed to a warning + # due to too many intermittent failures (see bug 831223). + log.info("WARNING | leakcheck |%s missing output line for total leaks!" + % processString) + return + + if totalBytesLeaked == 0: + log.info("TEST-PASS | leakcheck |%s no leaks detected!" % processString) + return + + # totalBytesLeaked was seen and is non-zero. + if totalBytesLeaked > leakThreshold: + # Fail the run if we're over the threshold (which defaults to 0) + prefix = "TEST-UNEXPECTED-FAIL" + else: + prefix = "WARNING" + # Create a comma delimited string of the first N leaked objects found, + # to aid with bug summary matching in TBPL. Note: The order of the objects + # had no significance (they're sorted alphabetically). + maxSummaryObjects = 5 + leakedObjectSummary = ', '.join(leakedObjectNames[:maxSummaryObjects]) + if len(leakedObjectNames) > maxSummaryObjects: + leakedObjectSummary += ', ...' + log.info("%s | leakcheck |%s %d bytes leaked (%s)" + % (prefix, processString, totalBytesLeaked, leakedObjectSummary)) def processLeakLog(leakLogFile, leakThreshold = 0): """Process the leak log, including separate leak logs created @@ -399,25 +385,26 @@ def processLeakLog(leakLogFile, leakThreshold = 0): """ if not os.path.exists(leakLogFile): - log.info("WARNING | automationutils.processLeakLog() | refcount logging is off, so leaks can't be detected!") + log.info("WARNING | leakcheck | refcount logging is off, so leaks can't be detected!") return + if leakThreshold != 0: + log.info("TEST-INFO | leakcheck | threshold set at %d bytes" % leakThreshold) + (leakLogFileDir, leakFileBase) = os.path.split(leakLogFile) - pidRegExp = re.compile(r".*?_([a-z]*)_pid(\d*)$") + fileNameRegExp = re.compile(r".*?_([a-z]*)_pid\d*$") if leakFileBase[-4:] == ".log": leakFileBase = leakFileBase[:-4] - pidRegExp = re.compile(r".*?_([a-z]*)_pid(\d*).log$") + fileNameRegExp = re.compile(r".*?_([a-z]*)_pid\d*.log$") for fileName in os.listdir(leakLogFileDir): if fileName.find(leakFileBase) != -1: thisFile = os.path.join(leakLogFileDir, fileName) - processPID = 0 processType = None - m = pidRegExp.search(fileName) + m = fileNameRegExp.search(fileName) if m: processType = m.group(1) - processPID = m.group(2) - processSingleLeakFile(thisFile, processPID, processType, leakThreshold) + processSingleLeakFile(thisFile, processType, leakThreshold) def replaceBackSlashes(input): return input.replace('\\', '/')