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
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.
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 ***** */
2
# This Source Code Form is subject to the terms of the Mozilla Public
3
# License, v. 2.0. If a copy of the MPL was not distributed with this
4
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
39
6
from __future__ import with_statement
40
7
import glob, logging, os, platform, shutil, subprocess, sys, tempfile, urllib2, zipfile
42
9
from urlparse import urlparse
43
from operator import itemgetter
452
417
return ["arch", "-arch", "i386"] + cmd
453
418
# otherwise just execute the command normally
456
class ShutdownLeakLogger(object):
458
Parses the mochitest run log when running a debug build, assigns all leaked
459
DOM windows (that are still around after test suite shutdown, despite running
460
the GC) to the tests that created them and prints leak statistics.
464
def __init__(self, logger):
467
self.leakedWindows = {}
468
self.leakedDocShells = set()
469
self.currentTest = None
470
self.seenShutdown = False
473
if line[2:11] == "DOMWINDOW":
474
self._logWindow(line)
475
elif line[2:10] == "DOCSHELL":
476
self._logDocShell(line)
477
elif line.startswith("TEST-START"):
478
fileName = line.split(" ")[-1].strip().replace("chrome://mochitests/content/browser/", "")
479
self.currentTest = {"fileName": fileName, "windows": set(), "docShells": set()}
480
elif line.startswith("INFO TEST-END"):
481
# don't track a test if no windows or docShells leaked
482
if self.currentTest["windows"] or self.currentTest["docShells"]:
483
self.tests.append(self.currentTest)
484
self.currentTest = None
485
elif line.startswith("INFO TEST-START | Shutdown"):
486
self.seenShutdown = True
489
leakingTests = self._parseLeakingTests()
492
totalWindows = sum(len(test["leakedWindows"]) for test in leakingTests)
493
totalDocShells = sum(len(test["leakedDocShells"]) for test in leakingTests)
494
msgType = "INFO" if totalWindows + totalDocShells <= self.MAX_LEAK_COUNT else "UNEXPECTED-FAIL"
495
self.logger.info("TEST-%s | ShutdownLeaks | leaked %d DOMWindow(s) and %d DocShell(s) until shutdown", msgType, totalWindows, totalDocShells)
497
for test in leakingTests:
498
self.logger.info("\n[%s]", test["fileName"])
500
for url, count in self._zipLeakedWindows(test["leakedWindows"]):
501
self.logger.info(" %d window(s) [url = %s]", count, url)
503
if test["leakedDocShells"]:
504
self.logger.info(" %d docShell(s)", len(test["leakedDocShells"]))
506
def _logWindow(self, line):
507
created = line[:2] == "++"
508
id = self._parseValue(line, "serial")
510
# log line has invalid format
515
windows = self.currentTest["windows"]
520
elif self.seenShutdown and not created:
521
self.leakedWindows[id] = self._parseValue(line, "url")
523
def _logDocShell(self, line):
524
created = line[:2] == "++"
525
id = self._parseValue(line, "id")
527
# log line has invalid format
532
docShells = self.currentTest["docShells"]
536
docShells.discard(id)
537
elif self.seenShutdown and not created:
538
self.leakedDocShells.add(id)
540
def _parseValue(self, line, name):
541
match = re.search("\[%s = (.+?)\]" % name, line)
543
return match.group(1)
545
def _parseLeakingTests(self):
548
for test in self.tests:
549
test["leakedWindows"] = [self.leakedWindows[id] for id in test["windows"] if id in self.leakedWindows]
550
test["leakedDocShells"] = [id for id in test["docShells"] if id in self.leakedDocShells]
551
test["leakCount"] = len(test["leakedWindows"]) + len(test["leakedDocShells"])
553
if test["leakCount"]:
554
leakingTests.append(test)
556
return sorted(leakingTests, key=itemgetter("leakCount"), reverse=True)
558
def _zipLeakedWindows(self, leakedWindows):
562
for url in leakedWindows:
563
if not url in counted:
564
counts.append((url, leakedWindows.count(url)))
567
return sorted(counts, key=itemgetter(1), reverse=True)