Bug 1354232 - Enable LSAN Leak detection in wpt, r=ahal,mccr8 draft
authorJames Graham <james@hoppipolla.co.uk>
Wed, 16 May 2018 14:16:28 +0100
changeset 805835 392fa83fce2af5237f98b8d4231596d0ced311b8
parent 805834 43408d37349ce2f9cbce7a257dff63b565a264d7
child 805836 ed0c132aae56b98cae6965a2acc2adff0954665e
push id112776
push userbmo:james@hoppipolla.co.uk
push dateFri, 08 Jun 2018 15:53:57 +0000
reviewersahal, mccr8
bugs1354232
milestone62.0a1
Bug 1354232 - Enable LSAN Leak detection in wpt, r=ahal,mccr8 MozReview-Commit-ID: 1pPvYi5NQk8
testing/web-platform/moz.build
testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
--- a/testing/web-platform/moz.build
+++ b/testing/web-platform/moz.build
@@ -9,25 +9,29 @@ WEB_PLATFORM_TESTS_MANIFESTS += [
     ('mozilla/meta/MANIFEST.json', 'mozilla/tests/')
 ]
 
 TEST_HARNESS_FILES['web-platform'] += [
     'mach_commands_base.py',
     'mach_test_package_commands.py',
     'outbound/**',
     'runtests.py',
-    'wptrunner.ini'
+    'wptrunner.ini',
 ]
 
-TEST_HARNESS_FILES['web-platform'].certs = [
+TEST_HARNESS_FILES['web-platform'].certs += [
     'certs/cacert.pem',
     'certs/web-platform.test.key',
     'certs/web-platform.test.pem',
 ]
 
+TEST_HARNESS_FILES['web-platform'].prefs += [
+    '/build/sanitizers/lsan_suppressions.txt',
+]
+
 with Files("**"):
     SCHEDULES.exclusive = [
         'web-platform-tests',
         'web-platform-tests-reftests',
         'web-platform-tests-wdspec',
     ]
 
 with Files("moz.build"):
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -6,17 +6,17 @@ import subprocess
 import sys
 import tempfile
 
 import mozinfo
 import mozleak
 from mozprocess import ProcessHandler
 from mozprofile import FirefoxProfile, Preferences
 from mozrunner import FirefoxRunner
-from mozrunner.utils import get_stack_fixer_function
+from mozrunner.utils import test_environment, get_stack_fixer_function
 from mozcrash import mozcrash
 
 from .base import (get_free_port,
                    Browser,
                    ExecutorBrowser,
                    require_arg,
                    cmd_arg,
                    browser_command)
@@ -74,16 +74,17 @@ def browser_kwargs(test_type, run_info_d
             "ca_certificate_path": kwargs["ssl_env"].ca_cert_path(),
             "e10s": kwargs["gecko_e10s"],
             "stackfix_dir": kwargs["stackfix_dir"],
             "binary_args": kwargs["binary_args"],
             "timeout_multiplier": get_timeout_multiplier(test_type,
                                                          run_info_data,
                                                          **kwargs),
             "leak_check": kwargs["leak_check"],
+            "asan": run_info_data.get("asan"),
             "stylo_threads": kwargs["stylo_threads"],
             "chaos_mode_flags": kwargs["chaos_mode_flags"],
             "config": kwargs["config"]}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     executor_kwargs = base_executor_kwargs(test_type, server_config,
@@ -144,18 +145,18 @@ def update_properties():
 class FirefoxBrowser(Browser):
     used_ports = set()
     init_timeout = 60
     shutdown_timeout = 60
 
     def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None,
                  symbols_path=None, stackwalk_binary=None, certutil_binary=None,
                  ca_certificate_path=None, e10s=False, stackfix_dir=None,
-                 binary_args=None, timeout_multiplier=None, leak_check=False, stylo_threads=1,
-                 chaos_mode_flags=None, config=None):
+                 binary_args=None, timeout_multiplier=None, leak_check=False, asan=False,
+                 stylo_threads=1, chaos_mode_flags=None, config=None):
         Browser.__init__(self, logger)
         self.binary = binary
         self.prefs_root = prefs_root
         self.test_type = test_type
         self.extra_prefs = extra_prefs
         self.marionette_port = None
         self.runner = None
         self.debug_info = debug_info
@@ -171,33 +172,38 @@ class FirefoxBrowser(Browser):
             self.stack_fixer = get_stack_fixer_function(stackfix_dir,
                                                         self.symbols_path)
         else:
             self.stack_fixer = None
 
         if timeout_multiplier:
             self.init_timeout = self.init_timeout * timeout_multiplier
 
+        self.asan = asan
+        self.leak_check = leak_check
         self.leak_report_file = None
-        self.leak_check = leak_check
+        self.lsan_handler = None
+        if self.asan:
+            self.lsan_handler = mozleak.LSANLeaks(logger)
         self.stylo_threads = stylo_threads
         self.chaos_mode_flags = chaos_mode_flags
 
     def settings(self, test):
         return {"check_leaks": self.leak_check and not test.leaks}
 
     def start(self, **kwargs):
         if self.marionette_port is None:
             self.marionette_port = get_free_port(2828, exclude=self.used_ports)
             self.used_ports.add(self.marionette_port)
 
-        env = os.environ.copy()
-        env["MOZ_CRASHREPORTER"] = "1"
-        env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
-        env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
+        env = test_environment(xrePath=os.path.dirname(self.binary),
+                               debugger=self.debug_info is not None,
+                               log=self.logger,
+                               lsanPath=self.prefs_root)
+
         env["STYLO_THREADS"] = str(self.stylo_threads)
         if self.chaos_mode_flags is not None:
             env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
 
         preferences = self.load_prefs()
 
         self.profile = FirefoxProfile(preferences=preferences)
         self.profile.set_preferences({"marionette.port": self.marionette_port,
@@ -287,30 +293,31 @@ class FirefoxBrowser(Browser):
                             break
             except OSError:
                 # This can happen on Windows if the process is already dead
                 pass
         self.logger.debug("stopped")
 
     def process_leaks(self):
         self.logger.debug("PROCESS LEAKS %s" % self.leak_report_file)
-        if self.leak_report_file is None:
-            return
-        mozleak.process_leak_log(
-            self.leak_report_file,
-            leak_thresholds={
-                "default": 0,
-                "tab": 10000,  # See dependencies of bug 1051230.
-                # GMP rarely gets a log, but when it does, it leaks a little.
-                "geckomediaplugin": 20000,
-            },
-            ignore_missing_leaks=["geckomediaplugin"],
-            log=self.logger,
-            stack_fixer=self.stack_fixer
-        )
+        if self.lsan_handler:
+            self.lsan_handler.process()
+        if self.leak_report_file is not None:
+            mozleak.process_leak_log(
+                self.leak_report_file,
+                leak_thresholds={
+                    "default": 0,
+                    "tab": 10000,  # See dependencies of bug 1051230.
+                    # GMP rarely gets a log, but when it does, it leaks a little.
+                    "geckomediaplugin": 20000,
+                },
+                ignore_missing_leaks=["geckomediaplugin"],
+                log=self.logger,
+                stack_fixer=self.stack_fixer
+            )
 
     def pid(self):
         if self.runner.process_handler is None:
             return None
 
         try:
             return self.runner.process_handler.pid
         except AttributeError:
@@ -319,16 +326,18 @@ class FirefoxBrowser(Browser):
     def on_output(self, line):
         """Write a line of output from the firefox process to the log"""
         if "GLib-GObject-CRITICAL" in line:
             return
         if line:
             data = line.decode("utf8", "replace")
             if self.stack_fixer:
                 data = self.stack_fixer(data)
+            if self.lsan_handler:
+                self.lsan_handler.log(data)
             self.logger.process_output(self.pid(),
                                       data,
                                       command=" ".join(self.runner.command))
 
     def is_alive(self):
         if self.runner:
             return self.runner.is_running()
         return False