Bug 1506928 - [raptor] Use signal handler for safe shutdown. r=rwood,tarek,davehunt
☠☠ backed out by f478c61c1bc9 ☠ ☠
authorHenrik Skupin <mail@hskupin.info>
Tue, 16 Apr 2019 00:12:28 +0200
changeset 470022 98c7524647ae009fc69e434ef8dddd2c3fb72e81
parent 470021 097b31242a5abad203858c7c0f3a2ea96e09ad8c
child 470023 15e3076c8198ed305d02c594d3c27052cb496983
push id35885
push userapavel@mozilla.com
push dateThu, 18 Apr 2019 21:36:48 +0000
treeherdermozilla-central@b44914767f72 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrwood, tarek, davehunt
bugs1506928
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1506928 - [raptor] Use signal handler for safe shutdown. r=rwood,tarek,davehunt Differential Revision: https://phabricator.services.mozilla.com/D25752
testing/raptor/raptor/raptor.py
--- a/testing/raptor/raptor/raptor.py
+++ b/testing/raptor/raptor/raptor.py
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import json
 import os
 import posixpath
 import shutil
+import signal
 import sys
 import tempfile
 import time
 
 import mozcrash
 import mozinfo
 from mozdevice import ADBDevice
 from mozlog import commandline, get_default_logger
@@ -56,23 +57,37 @@ from gecko_profile import GeckoProfile
 from gen_test_config import gen_test_config
 from outputhandler import OutputHandler
 from manifest import get_raptor_test_list
 from power import init_android_power_test, finish_android_power_test
 from results import RaptorResultsHandler
 from utils import view_gecko_profile
 
 
+class SignalHandler:
+
+    def __init__(self):
+        signal.signal(signal.SIGINT, self.handle_signal)
+        signal.signal(signal.SIGTERM, self.handle_signal)
+
+    def handle_signal(self, signum, frame):
+        raise SignalHandlerException("Program aborted due to signal %s" % signum)
+
+
+class SignalHandlerException(Exception):
+    pass
+
+
 class Raptor(object):
     """Container class for Raptor"""
 
     def __init__(self, app, binary, run_local=False, obj_path=None,
                  gecko_profile=False, gecko_profile_interval=None, gecko_profile_entries=None,
                  symbols_path=None, host=None, power_test=False, is_release_build=False,
-                 debug_mode=False, post_startup_delay=None, **kwargs):
+                 debug_mode=False, post_startup_delay=None, interrupt_handler=None, **kwargs):
 
         # Override the magic --host HOST_IP with the value of the environment variable.
         if host == 'HOST_IP':
             host = os.environ['HOST_IP']
 
         self.config = {
             'app': app,
             'binary': binary,
@@ -94,16 +109,17 @@ class Raptor(object):
         self.playback = None
         self.benchmark = None
         self.benchmark_port = 0
         self.gecko_profiler = None
         self.post_startup_delay = post_startup_delay
         self.device = None
         self.profile_class = app
         self.firefox_android_apps = FIREFOX_ANDROID_APPS
+        self.interrupt_handler = interrupt_handler
 
         # debug mode is currently only supported when running locally
         self.debug_mode = debug_mode if self.config['run_local'] else False
 
         # if running debug-mode reduce the pause after browser startup
         if self.debug_mode:
             self.post_startup_delay = min(self.post_startup_delay, 3000)
             self.log.info("debug-mode enabled, reducing post-browser startup pause to %d ms"
@@ -372,41 +388,50 @@ class RaptorDesktop(Raptor):
         self.output_handler.proc = proc
 
         # give our control server the browser process so it can shut it down later
         self.control_server.browser_proc = proc
 
     def run_test(self, test, timeout=None):
         self.run_test_setup(test)
 
-        if test.get('playback') is not None:
-            self.start_playback(test)
-
         if self.config['host'] not in ('localhost', '127.0.0.1'):
             self.delete_proxy_settings_from_profile()
 
-        # now start the browser/app under test
-        self.launch_desktop_browser(test)
+        try:
+            # start mitmproxy if requested
+            if test.get('playback') is not None:
+                self.start_playback(test)
+
+            # start the browser/app under test
+            self.launch_desktop_browser(test)
 
-        # set our control server flag to indicate we are running the browser/app
-        self.control_server._finished = False
+            # set our control server flag to indicate we are running the browser/app
+            self.control_server._finished = False
 
-        self.wait_for_test_finish(test, timeout)
+            self.wait_for_test_finish(test, timeout)
 
-        self.run_test_teardown()
+        finally:
+            self.run_test_teardown()
 
-        # browser should be closed by now but this is a backup-shutdown (if not in debug-mode)
-        if not self.debug_mode:
-            if self.runner.is_running():
-                self.runner.stop()
-        else:
-            # in debug mode, and running locally, leave the browser running
-            if self.config['run_local']:
-                self.log.info("* debug-mode enabled - please shutdown the browser manually...")
-                self.runner.wait(timeout=None)
+            # browser should be closed by now but this is a backup-shutdown (if not in debug-mode)
+            if not self.debug_mode:
+                if self.runner.is_running():
+                    self.runner.stop()
+            else:
+                # in debug mode, and running locally, leave the browser running
+                if self.config['run_local']:
+                    self.log.info("* debug-mode enabled - please shutdown the browser manually...")
+                    self.runner.wait(timeout=None)
+
+    def run_test_teardown(self):
+        self.log.info("stopping %s" % self.config['app'])
+        self.runner.stop()
+
+        super(RaptorDesktop, self).run_test_teardown()
 
     def check_for_crashes(self):
         try:
             self.runner.check_for_crashes()
         except NotImplementedError:  # not implemented for Chrome
             pass
 
     def clean_up(self):
@@ -623,20 +648,30 @@ class RaptorAndroid(Raptor):
                 self.log.info("copying %s to %s" % (_source, _dest))
                 shutil.copyfile(_source, _dest)
             else:
                 self.log.critical("unable to find ssl cert db file: %s" % _source)
 
     def run_test(self, test, timeout=None):
         # tests will be run warm (i.e. NO browser restart between page-cycles)
         # unless otheriwse specified in the test INI by using 'cold = true'
-        if test.get('cold', False) is True:
-            self.run_test_cold(test, timeout)
-        else:
-            self.run_test_warm(test, timeout)
+        try:
+            if test.get('cold', False) is True:
+                self.run_test_cold(test, timeout)
+            else:
+                self.run_test_warm(test, timeout)
+
+        except SignalHandlerException:
+            self.device.stop_application(self.config['binary'])
+
+        finally:
+            if self.config['power_test']:
+                finish_android_power_test(self, test['name'])
+
+            self.run_test_teardown()
 
     def run_test_cold(self, test, timeout=None):
         '''
         Run the Raptor test but restart the entire browser app between page-cycles.
 
         Note: For page-load tests, playback will only be started once - at the beginning of all
         browser cycles, and then stopped after all cycles are finished. The proxy is set via prefs
         in the browser profile so those will need to be set again in each new profile/cycle.
@@ -716,21 +751,16 @@ class RaptorAndroid(Raptor):
 
             self.wait_for_test_finish(test, timeout)
 
             # in debug mode, and running locally, leave the browser running
             if self.debug_mode and self.config['run_local']:
                 self.log.info("* debug-mode enabled - please shutdown the browser manually...")
                 self.runner.wait(timeout=None)
 
-        if self.config['power_test']:
-            finish_android_power_test(self, test['name'])
-
-        self.run_test_teardown()
-
     def run_test_warm(self, test, timeout=None):
         self.log.info("test %s is running in warm mode; browser will NOT be restarted between "
                       "page cycles" % test['name'])
         if self.config['power_test']:
             init_android_power_test(self)
 
         self.run_test_setup(test)
         self.create_raptor_sdcard_folder()
@@ -749,26 +779,30 @@ class RaptorAndroid(Raptor):
         # now start the browser/app under test
         self.launch_firefox_android_app(test['name'])
 
         # set our control server flag to indicate we are running the browser/app
         self.control_server._finished = False
 
         self.wait_for_test_finish(test, timeout)
 
-        if self.config['power_test']:
-            finish_android_power_test(self, test['name'])
-
-        self.run_test_teardown()
-
         # in debug mode, and running locally, leave the browser running
         if self.debug_mode and self.config['run_local']:
             self.log.info("* debug-mode enabled - please shutdown the browser manually...")
             self.runner.wait(timeout=None)
 
+    def run_test_teardown(self):
+        self.log.info('removing reverse socket connections')
+        self.device.remove_socket_connections('reverse')
+
+        self.log.info("stopping %s" % self.config['app'])
+        self.device.stop_application(self.config['binary'])
+
+        super(RaptorAndroid, self).run_test_teardown()
+
     def check_for_crashes(self):
         # Turn off verbose to prevent logcat from being inserted into the main log.
         verbose = self.device._verbose
         self.device._verbose = False
         logcat = self.device.get_logcat()
         self.device._verbose = verbose
         if logcat:
             if mozcrash.check_for_java_exception(logcat, "raptor"):
@@ -835,27 +869,32 @@ def main(args=sys.argv[1:]):
                           gecko_profile_interval=args.gecko_profile_interval,
                           gecko_profile_entries=args.gecko_profile_entries,
                           symbols_path=args.symbols_path,
                           host=args.host,
                           power_test=args.power_test,
                           is_release_build=args.is_release_build,
                           debug_mode=args.debug_mode,
                           post_startup_delay=args.post_startup_delay,
-                          activity=args.activity)
+                          activity=args.activity,
+                          interrupt_handler=SignalHandler(),
+                          )
 
     raptor.create_browser_profile()
     raptor.create_browser_handler()
     raptor.start_control_server()
 
-    for next_test in raptor_test_list:
-        raptor.run_test(next_test, timeout=int(next_test['page_timeout']))
+    try:
+        for next_test in raptor_test_list:
+            raptor.run_test(next_test, timeout=int(next_test['page_timeout']))
 
-    success = raptor.process_results(raptor_test_names)
-    raptor.clean_up()
+        success = raptor.process_results(raptor_test_names)
+
+    finally:
+        raptor.clean_up()
 
     if not success:
         # didn't get test results; test timed out or crashed, etc. we want job to fail
         LOG.critical("TEST-UNEXPECTED-FAIL: no raptor test results were found for %s" %
                      ', '.join(raptor_test_names))
         os.sys.exit(1)
 
     # if we have results but one test page timed out (i.e. one tp6 test page didn't load