зеркало из https://github.com/mozilla/gecko-dev.git
Bring automationutils.py up to date with the fixes from bug 808410, bug 813577, bug 850681; a=test-only
This commit is contained in:
Родитель
1958029cad
Коммит
247906f078
|
@ -146,24 +146,23 @@ def checkForCrashes(dumpDir, symbolsPath, testName=None):
|
||||||
if len(dumps) == 0:
|
if len(dumps) == 0:
|
||||||
return False
|
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:
|
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:
|
for d in dumps:
|
||||||
stackwalkOutput = []
|
stackwalkOutput = []
|
||||||
stackwalkOutput.append("Crash dump filename: " + d)
|
stackwalkOutput.append("Crash dump filename: " + d)
|
||||||
|
@ -216,12 +215,11 @@ def checkForCrashes(dumpDir, symbolsPath, testName=None):
|
||||||
extra = os.path.splitext(d)[0] + ".extra"
|
extra = os.path.splitext(d)[0] + ".extra"
|
||||||
if os.path.exists(extra):
|
if os.path.exists(extra):
|
||||||
os.remove(extra)
|
os.remove(extra)
|
||||||
foundCrash = True
|
|
||||||
finally:
|
finally:
|
||||||
if removeSymbolsPath:
|
if removeSymbolsPath:
|
||||||
shutil.rmtree(symbolsPath)
|
shutil.rmtree(symbolsPath)
|
||||||
|
|
||||||
return foundCrash
|
return True
|
||||||
|
|
||||||
def getFullPath(directory, path):
|
def getFullPath(directory, path):
|
||||||
"Get an absolute path relative to 'directory'."
|
"Get an absolute path relative to 'directory'."
|
||||||
|
@ -285,9 +283,8 @@ def dumpLeakLog(leakLogFile, filter = False):
|
||||||
if not os.path.exists(leakLogFile):
|
if not os.path.exists(leakLogFile):
|
||||||
return
|
return
|
||||||
|
|
||||||
leaks = open(leakLogFile, "r")
|
with open(leakLogFile, "r") as leaks:
|
||||||
leakReport = leaks.read()
|
leakReport = leaks.read()
|
||||||
leaks.close()
|
|
||||||
|
|
||||||
# Only |XPCOM_MEM_LEAK_LOG| reports can be actually filtered out.
|
# Only |XPCOM_MEM_LEAK_LOG| reports can be actually filtered out.
|
||||||
# Only check whether an actual leak was reported.
|
# Only check whether an actual leak was reported.
|
||||||
|
@ -297,9 +294,8 @@ def dumpLeakLog(leakLogFile, filter = False):
|
||||||
# Simply copy the log.
|
# Simply copy the log.
|
||||||
log.info(leakReport.rstrip("\n"))
|
log.info(leakReport.rstrip("\n"))
|
||||||
|
|
||||||
def processSingleLeakFile(leakLogFileName, PID, processType, leakThreshold):
|
def processSingleLeakFile(leakLogFileName, processType, leakThreshold):
|
||||||
"""Process a single leak log, corresponding to the specified
|
"""Process a single leak log.
|
||||||
process PID and type.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Per-Inst Leaked Total Rem ...
|
# Per-Inst Leaked Total Rem ...
|
||||||
|
@ -310,85 +306,75 @@ def processSingleLeakFile(leakLogFileName, PID, processType, leakThreshold):
|
||||||
r"-?\d+\s+(?P<numLeaked>-?\d+)")
|
r"-?\d+\s+(?P<numLeaked>-?\d+)")
|
||||||
|
|
||||||
processString = ""
|
processString = ""
|
||||||
if PID and processType:
|
if processType:
|
||||||
processString = "| %s process %s " % (processType, PID)
|
# eg 'plugin'
|
||||||
leaks = open(leakLogFileName, "r")
|
processString = " %s process:" % processType
|
||||||
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(leakLogFileName, "r")
|
|
||||||
seenTotal = False
|
|
||||||
crashedOnPurpose = False
|
crashedOnPurpose = False
|
||||||
prefix = "TEST-PASS"
|
totalBytesLeaked = None
|
||||||
numObjects = 0
|
leakAnalysis = []
|
||||||
for line in leaks:
|
leakedObjectNames = []
|
||||||
if line.find("purposefully crash") > -1:
|
with open(leakLogFileName, "r") as leaks:
|
||||||
crashedOnPurpose = True
|
for line in leaks:
|
||||||
matches = lineRe.match(line)
|
if line.find("purposefully crash") > -1:
|
||||||
if not matches:
|
crashedOnPurpose = True
|
||||||
continue
|
matches = lineRe.match(line)
|
||||||
name = matches.group("name")
|
if not matches:
|
||||||
size = int(matches.group("size"))
|
# eg: the leak table header row
|
||||||
bytesLeaked = int(matches.group("bytesLeaked"))
|
log.info(line.rstrip())
|
||||||
numLeaked = int(matches.group("numLeaked"))
|
continue
|
||||||
if size < 0 or bytesLeaked < 0 or numLeaked < 0:
|
name = matches.group("name")
|
||||||
log.info("TEST-UNEXPECTED-FAIL %s| automationutils.processLeakLog() | negative leaks caught!" %
|
size = int(matches.group("size"))
|
||||||
processString)
|
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":
|
if name == "TOTAL":
|
||||||
seenTotal = True
|
totalBytesLeaked = bytesLeaked
|
||||||
elif name == "TOTAL":
|
if size < 0 or bytesLeaked < 0 or numLeaked < 0:
|
||||||
seenTotal = True
|
leakAnalysis.append("TEST-UNEXPECTED-FAIL | leakcheck |%s negative leaks caught!"
|
||||||
# Check for leaks.
|
% processString)
|
||||||
if bytesLeaked < 0 or bytesLeaked > leakThreshold:
|
continue
|
||||||
prefix = "TEST-UNEXPECTED-FAIL"
|
if name != "TOTAL" and numLeaked != 0:
|
||||||
leakLog = "TEST-UNEXPECTED-FAIL %s| automationutils.processLeakLog() | leaked" \
|
leakedObjectNames.append(name)
|
||||||
" %d bytes during test execution" % (processString, bytesLeaked)
|
leakAnalysis.append("TEST-INFO | leakcheck |%s leaked %d %s (%s bytes)"
|
||||||
elif bytesLeaked > 0:
|
% (processString, numLeaked, name, bytesLeaked))
|
||||||
leakLog = "TEST-PASS %s| automationutils.processLeakLog() | WARNING leaked" \
|
log.info('\n'.join(leakAnalysis))
|
||||||
" %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()
|
|
||||||
|
|
||||||
|
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):
|
def processLeakLog(leakLogFile, leakThreshold = 0):
|
||||||
"""Process the leak log, including separate leak logs created
|
"""Process the leak log, including separate leak logs created
|
||||||
|
@ -399,25 +385,26 @@ def processLeakLog(leakLogFile, leakThreshold = 0):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not os.path.exists(leakLogFile):
|
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
|
return
|
||||||
|
|
||||||
|
if leakThreshold != 0:
|
||||||
|
log.info("TEST-INFO | leakcheck | threshold set at %d bytes" % leakThreshold)
|
||||||
|
|
||||||
(leakLogFileDir, leakFileBase) = os.path.split(leakLogFile)
|
(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":
|
if leakFileBase[-4:] == ".log":
|
||||||
leakFileBase = leakFileBase[:-4]
|
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):
|
for fileName in os.listdir(leakLogFileDir):
|
||||||
if fileName.find(leakFileBase) != -1:
|
if fileName.find(leakFileBase) != -1:
|
||||||
thisFile = os.path.join(leakLogFileDir, fileName)
|
thisFile = os.path.join(leakLogFileDir, fileName)
|
||||||
processPID = 0
|
|
||||||
processType = None
|
processType = None
|
||||||
m = pidRegExp.search(fileName)
|
m = fileNameRegExp.search(fileName)
|
||||||
if m:
|
if m:
|
||||||
processType = m.group(1)
|
processType = m.group(1)
|
||||||
processPID = m.group(2)
|
processSingleLeakFile(thisFile, processType, leakThreshold)
|
||||||
processSingleLeakFile(thisFile, processPID, processType, leakThreshold)
|
|
||||||
|
|
||||||
def replaceBackSlashes(input):
|
def replaceBackSlashes(input):
|
||||||
return input.replace('\\', '/')
|
return input.replace('\\', '/')
|
||||||
|
|
Загрузка…
Ссылка в новой задаче