Title: [118089] trunk/Tools
Revision
118089
Author
[email protected]
Date
2012-05-22 17:41:03 -0700 (Tue, 22 May 2012)

Log Message

[Chromium-Android] Run DumpRenderTree as an apk (python part)
https://bugs.webkit.org/show_bug.cgi?id=86923

chromium_android.py is modified to support running DumpRenderTree as
an apk, so that we can run the tests (e.g. video) requiring features
implemented in Java on Android.

Reviewed by Adam Barth.

* Scripts/webkitpy/layout_tests/port/chromium_android.py:
(ChromiumAndroidPort.__init__):
(ChromiumAndroidPort.default_child_processes):
(ChromiumAndroidPort.check_wdiff):
(ChromiumAndroidPort.check_build):
(ChromiumAndroidPort.start_helper):
(ChromiumAndroidPort.skipped_layout_tests):
(ChromiumAndroidPort):
(ChromiumAndroidPort._path_to_driver):
(ChromiumAndroidPort._get_crash_log):
(ChromiumAndroidPort._push_executable):
(ChromiumAndroidPort._run_adb_command):
(ChromiumAndroidPort._get_last_stacktrace):
(ChromiumAndroidPort._get_logcat):
(ChromiumAndroidPort._setup_performance):
(ChromiumAndroidDriver):
(ChromiumAndroidDriver.__init__):
(ChromiumAndroidDriver._command_wrapper):
(ChromiumAndroidDriver.cmd_line):
(ChromiumAndroidDriver._file_exists_on_device):
(ChromiumAndroidDriver._deadlock_detector):
(ChromiumAndroidDriver._start):
(ChromiumAndroidDriver.run_test):
(ChromiumAndroidDriver.stop):
(ChromiumAndroidDriver._write_command_and_read_line):
(ChromiumAndroidDriver._output_image):
(ChromiumAndroidDriver._get_stderr):

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (118088 => 118089)


--- trunk/Tools/ChangeLog	2012-05-23 00:38:59 UTC (rev 118088)
+++ trunk/Tools/ChangeLog	2012-05-23 00:41:03 UTC (rev 118089)
@@ -1,3 +1,42 @@
+2012-05-22  Xianzhu Wang  <[email protected]>
+
+        [Chromium-Android] Run DumpRenderTree as an apk (python part)
+        https://bugs.webkit.org/show_bug.cgi?id=86923
+
+        chromium_android.py is modified to support running DumpRenderTree as
+        an apk, so that we can run the tests (e.g. video) requiring features
+        implemented in Java on Android.
+
+        Reviewed by Adam Barth.
+
+        * Scripts/webkitpy/layout_tests/port/chromium_android.py:
+        (ChromiumAndroidPort.__init__):
+        (ChromiumAndroidPort.default_child_processes):
+        (ChromiumAndroidPort.check_wdiff):
+        (ChromiumAndroidPort.check_build):
+        (ChromiumAndroidPort.start_helper):
+        (ChromiumAndroidPort.skipped_layout_tests):
+        (ChromiumAndroidPort):
+        (ChromiumAndroidPort._path_to_driver):
+        (ChromiumAndroidPort._get_crash_log):
+        (ChromiumAndroidPort._push_executable):
+        (ChromiumAndroidPort._run_adb_command):
+        (ChromiumAndroidPort._get_last_stacktrace):
+        (ChromiumAndroidPort._get_logcat):
+        (ChromiumAndroidPort._setup_performance):
+        (ChromiumAndroidDriver):
+        (ChromiumAndroidDriver.__init__):
+        (ChromiumAndroidDriver._command_wrapper):
+        (ChromiumAndroidDriver.cmd_line):
+        (ChromiumAndroidDriver._file_exists_on_device):
+        (ChromiumAndroidDriver._deadlock_detector):
+        (ChromiumAndroidDriver._start):
+        (ChromiumAndroidDriver.run_test):
+        (ChromiumAndroidDriver.stop):
+        (ChromiumAndroidDriver._write_command_and_read_line):
+        (ChromiumAndroidDriver._output_image):
+        (ChromiumAndroidDriver._get_stderr):
+
 2012-05-22  Joshua Bell  <[email protected]>
 
         [Chromium] Reverting r118084 - webkit_tests failing on chromium mac.

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py (118088 => 118089)


--- trunk/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py	2012-05-23 00:38:59 UTC (rev 118088)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/port/chromium_android.py	2012-05-23 00:41:03 UTC (rev 118089)
@@ -28,11 +28,14 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 import logging
+import os
 import re
 import signal
+import shlex
+import shutil
+import threading
 import time
 
-from webkitpy.layout_tests.port import base
 from webkitpy.layout_tests.port import chromium
 from webkitpy.layout_tests.port import factory
 
@@ -45,13 +48,17 @@
 # This path is defined in base/base_paths_android.cc and
 # webkit/support/platform_support_android.cc.
 DEVICE_SOURCE_ROOT_DIR = '/data/local/tmp/'
+COMMAND_LINE_FILE = DEVICE_SOURCE_ROOT_DIR + 'chrome-native-tests-command-line'
 
+# The directory to put tools and resources of DumpRenderTree.
 DEVICE_DRT_DIR = '/data/drt/'
-DEVICE_DRT_PATH = DEVICE_DRT_DIR + 'DumpRenderTree'
-DEVICE_DRT_STDERR = DEVICE_DRT_DIR + 'DumpRenderTree.stderr'
 DEVICE_FORWARDER_PATH = DEVICE_DRT_DIR + 'forwarder'
 DEVICE_DRT_STAMP_PATH = DEVICE_DRT_DIR + 'DumpRenderTree.stamp'
 
+DRT_APP_PACKAGE = 'org.chromium.native_test'
+DRT_ACTIVITY_FULL_NAME = DRT_APP_PACKAGE + '/.ChromeNativeTestActivity'
+DRT_APP_FILE_DIR = '/data/user/0/' + DRT_APP_PACKAGE + '/files/'
+
 # This only works for single core devices so far.
 # FIXME: Find a solution for multi-core devices.
 SCALING_GOVERNOR = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"
@@ -69,6 +76,9 @@
 
 MS_TRUETYPE_FONTS_DIR = '/usr/share/fonts/truetype/msttcorefonts/'
 
+# Timeout in seconds to wait for start/stop of DumpRenderTree.
+DRT_START_STOP_TIMEOUT_SECS = 10
+
 # List of fonts that layout tests expect, copied from DumpRenderTree/gtk/TestShellGtk.cpp.
 HOST_FONT_FILES = [
     [MS_TRUETYPE_FONTS_DIR, 'Arial.ttf'],
@@ -149,6 +159,7 @@
         self._version = 'icecreamsandwich'
         self._original_governor = None
         self._android_base_dir = None
+        self._read_fifo_proc = None
 
         self._host_port = factory.PortFactory(host).get('chromium', **kwargs)
 
@@ -165,16 +176,25 @@
         return 10 * 1000
 
     def default_child_processes(self):
-        # Currently we only use one process, but it might be helpful to use
-        # more that one process in the future to improve performance.
+        # Because of the nature of apk, we don't support more than one process.
         return 1
 
     def baseline_search_path(self):
         return map(self._webkit_baseline_path, self.FALLBACK_PATHS)
 
+    def check_wdiff(self, logging=True):
+        return self._host_port.check_wdiff(logging)
+
     def check_build(self, needs_http):
-        return self._host_port.check_build(needs_http)
+        result = chromium.ChromiumPort.check_build(self, needs_http)
+        result = self.check_wdiff() and result
+        if not result:
+            _log.error('For complete Android build requirements, please see:')
+            _log.error('')
+            _log.error('    http://code.google.com/p/chromium/wiki/AndroidBuildInstructions')
 
+        return result
+
     def check_sys_deps(self, needs_http):
         for (font_dir, font_file) in HOST_FONT_FILES:
             font_path = font_dir + font_file
@@ -202,11 +222,14 @@
         pass
 
     def start_helper(self):
+        self._run_adb_command(['root'])
         self._setup_performance()
         # Required by webkit_support::GetWebKitRootDirFilePath().
         # Other directories will be created automatically by adb push.
-        self._run_adb_command(['shell', 'mkdir', '-p',
-                               DEVICE_SOURCE_ROOT_DIR + 'chrome'])
+        self._run_adb_command(['shell', 'mkdir', '-p', DEVICE_SOURCE_ROOT_DIR + 'chrome'])
+        # Allow the DumpRenderTree app to fully access the directory.
+        # The native code needs the permission to write temporary files here.
+        self._run_adb_command(['shell', 'chmod', '777', DEVICE_SOURCE_ROOT_DIR])
 
         self._push_executable()
         self._push_fonts()
@@ -223,13 +246,15 @@
         # useful for debugging and do no harm to subsequent tests.
         self._teardown_performance()
 
-    def skipped_tests(self, test_list):
-        return base.Port._real_tests(self, [
+    def skipped_layout_tests(self, test_list):
+        return self._real_tests([
             # Canvas tests are run as virtual gpu tests.
             'fast/canvas',
             'canvas/philip',
         ])
 
+    # Overridden private functions.
+
     def _build_path(self, *comps):
         return self._host_port._build_path(*comps)
 
@@ -240,10 +265,9 @@
         return self._host_port._path_to_apache_config_file()
 
     def _path_to_driver(self, configuration=None):
-        # Returns the host path to driver which will be pushed to the device.
         if not configuration:
             configuration = self.get_option('configuration')
-        return self._build_path(configuration, 'DumpRenderTree')
+        return self._build_path(configuration, 'DumpRenderTree_apk/ChromeNativeTests-debug.apk')
 
     def _path_to_helper(self):
         return self._build_path(self.get_option('configuration'), 'forwarder')
@@ -269,23 +293,35 @@
     def _driver_class(self):
         return ChromiumAndroidDriver
 
+    def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
+        if not stdout:
+            stdout = ''
+        stdout += '********* Logcat:\n' + self._get_logcat()
+        if not stderr:
+            stderr = ''
+        stderr += '********* Tombstone file:\n' + self._get_last_stacktrace()
+        return chromium.ChromiumPort._get_crash_log(self, name, pid, stdout, stderr, newer_than)
+
+    # Local private functions.
+
     def _push_executable(self):
         drt_host_path = self._path_to_driver()
         forwarder_host_path = self._path_to_helper()
-        drt_jar_host_path = drt_host_path + '.jar'
         host_stamp = int(float(max(os.stat(drt_host_path).st_mtime,
-                                   os.stat(forwarder_host_path).st_mtime,
-                                   os.stat(drt_jar_host_path).st_mtime)))
+                                   os.stat(forwarder_host_path).st_mtime)))
         device_stamp = int(float(self._run_adb_command([
             'shell', 'cat %s 2>/dev/null || echo 0' % DEVICE_DRT_STAMP_PATH])))
         if device_stamp < host_stamp:
             _log.debug('Pushing executable')
-            self._kill_device_process(DEVICE_FORWARDER_PATH)
             self._push_to_device(forwarder_host_path, DEVICE_FORWARDER_PATH)
-            self._push_to_device(drt_host_path, DEVICE_DRT_PATH)
-            self._push_to_device(drt_host_path + '.pak', DEVICE_DRT_PATH + '.pak')
-            self._push_to_device(drt_host_path + '_resources', DEVICE_DRT_PATH + '_resources')
-            self._push_to_device(drt_jar_host_path, DEVICE_DRT_PATH + '.jar')
+            self._run_adb_command(['uninstall', DRT_APP_PACKAGE])
+            install_result = self._run_adb_command(['install', drt_host_path])
+            if install_result.find('Success') == -1:
+                raise AssertionError('Failed to install %s onto device: %s' % (drt_host_path, install_result))
+            self._push_to_device(self._build_path(self.get_option('configuration'), 'DumpRenderTree.pak'),
+                                 DEVICE_DRT_DIR + 'DumpRenderTree.pak')
+            self._push_to_device(self._build_path(self.get_option('configuration'), 'DumpRenderTree_resources'),
+                                 DEVICE_DRT_DIR + 'DumpRenderTree_resources')
             # Version control of test resources is dependent on executables,
             # because we will always rebuild executables when resources are
             # updated.
@@ -329,11 +365,14 @@
         self._run_adb_command(['shell', 'echo %d > %sVERSION' % (version, dir)])
 
     def _run_adb_command(self, cmd, ignore_error=False):
+        _log.debug('Run adb command: ' + str(cmd))
         if ignore_error:
             error_handler = self._executive.ignore_error
         else:
             error_handler = None
-        return self._executive.run_command(self._adb_command + cmd, error_handler=error_handler)
+        result = self._executive.run_command(self._adb_command + cmd, error_handler=error_handler)
+        _log.debug('Run adb result:\n' + result)
+        return result
 
     def _copy_device_file(self, from_file, to_file, ignore_error=False):
         # 'cp' is unavailable on Android, so use 'dd' instead.
@@ -345,17 +384,7 @@
     def _pull_from_device(self, device_path, host_path, ignore_error=False):
         return self._run_adb_command(['pull', device_path, host_path], ignore_error)
 
-    def _kill_device_process(self, name):
-        ps_result = self._run_adb_command(['shell', 'ps']).split('\n')
-        for line in ps_result:
-            if line.find(name) > 0:
-                pid = line.split()[1]
-                self._run_adb_command(['shell', 'kill', pid])
-
-    def get_stderr(self):
-        return self._run_adb_command(['shell', 'cat', DEVICE_DRT_STDERR], ignore_error=True)
-
-    def get_last_stacktrace(self):
+    def _get_last_stacktrace(self):
         tombstones = self._run_adb_command(['shell', 'ls', '-n', '/data/tombstones'])
         if not tombstones or tombstones.startswith('/data/tombstones: No such file or directory'):
             _log.error('DRT crashed, but no tombstone found!')
@@ -376,13 +405,18 @@
         # Use Android tool vendor/google/tools/stack to convert the raw
         # stack trace into a human readable format, if needed.
         # It takes a long time, so don't do it here.
-        return self._run_adb_command(['shell', 'cat', '/data/tombstones/' + last_tombstone[6]])
+        return '%s\n%s' % (' '.join(last_tombstone),
+                           self._run_adb_command(['shell', 'cat', '/data/tombstones/' + last_tombstone[6]]))
 
+    def _get_logcat(self):
+        return self._run_adb_command(['logcat', '-d'])
+
     def _setup_performance(self):
         # Disable CPU scaling and drop ram cache to reduce noise in tests
         if not self._original_governor:
-            self._original_governor = self._run_adb_command(['shell', 'cat', SCALING_GOVERNOR])
-            self._run_adb_command(['shell', 'echo', 'performance', '>', SCALING_GOVERNOR])
+            self._original_governor = self._run_adb_command(['shell', 'cat', SCALING_GOVERNOR], ignore_error=True)
+            if self._original_governor:
+                self._run_adb_command(['shell', 'echo', 'performance', '>', SCALING_GOVERNOR])
 
     def _teardown_performance(self):
         if self._original_governor:
@@ -391,120 +425,170 @@
 
 
 class ChromiumAndroidDriver(chromium.ChromiumDriver):
+    # The controller may start multiple drivers during test, but for now we
+    # don't support multiple Android activities, so only one driver can be
+    # started at a time.
+    _started_driver = None
+
     def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
         chromium.ChromiumDriver.__init__(self, port, worker_number, pixel_tests, no_timeout)
-        self._device_image_path = None
-        self._drt_return_parser = re.compile('#DRT_RETURN (\d+)')
+        self._in_fifo_path = DRT_APP_FILE_DIR + 'DumpRenderTree.in'
+        self._out_fifo_path = DRT_APP_FILE_DIR + 'DumpRenderTree.out'
+        self._err_file_path = DRT_APP_FILE_DIR + 'DumpRenderTree.err'
+        self._restart_after_killed = False
+        self._read_fifo_proc = None
 
-    def _start(self, pixel_tests, per_test_args):
-        # Convert the original command line into to two parts:
-        # - the 'adb shell' command line to start an interactive adb shell;
-        # - the DumpRenderTree command line to send to the adb shell.
-        original_cmd = self.cmd_line(pixel_tests, per_test_args)
-        shell_cmd = []
-        drt_args = []
-        path_to_driver = self._port._path_to_driver()
-        reading_args_before_driver = True
+    def _command_wrapper(cls, wrapper_option):
+        # Ignore command wrapper which is not applicable on Android.
+        return []
+
+    def cmd_line(self, pixel_tests, per_test_args):
+        original_cmd = chromium.ChromiumDriver.cmd_line(self, pixel_tests, per_test_args)
+        cmd = []
         for param in original_cmd:
-            if reading_args_before_driver:
-                if param == path_to_driver:
-                    reading_args_before_driver = False
-                else:
-                    shell_cmd.append(param)
-            else:
-                if param.startswith('--pixel-tests='):
-                    if not self._device_image_path:
-                        self._device_image_path = DEVICE_DRT_DIR + self._port.host.filesystem.basename(self._image_path)
-                    param = '--pixel-tests=' + self._device_image_path
-                drt_args.append(param)
+            if param.startswith('--pixel-tests='):
+                self._device_image_path = DRT_APP_FILE_DIR + self._port.host.filesystem.basename(self._image_path)
+                param = '--pixel-tests=' + self._device_image_path
+            cmd.append(param)
 
-        shell_cmd += self._port._adb_command
-        shell_cmd.append('shell')
-        retries = 0
-        while True:
-            _log.debug('Starting adb shell for DumpRenderTree: ' + ' '.join(shell_cmd))
-            executive = self._port.host.executive
-            self._proc = executive.popen(shell_cmd, stdin=executive.PIPE, stdout=executive.PIPE, stderr=executive.STDOUT,
-                                         close_fds=True, universal_newlines=True)
-            # Read back the shell prompt to ensure adb shell ready.
-            self._read_prompt()
-            # Some tests rely on this to produce proper number format etc.,
-            # e.g. fast/speech/input-appearance-numberandspeech.html.
-            self._write_command_and_read_line("export LC_CTYPE='en_US'\n")
-            self._write_command_and_read_line("export CLASSPATH='/data/drt/DumpRenderTree.jar'\n")
+        cmd.append('--in-fifo=' + self._in_fifo_path)
+        cmd.append('--out-fifo=' + self._out_fifo_path)
+        cmd.append('--err-file=' + self._err_file_path)
+        return cmd
 
-            # When DumpRenderTree crashes, the Android debuggerd will stop the
-            # process before dumping stack to log/tombstone file and terminating
-            # the process. Sleep 1 second (long enough for debuggerd to dump
-            # stack) before exiting the shell to ensure the process has quit,
-            # otherwise the exit will fail because "You have stopped jobs".
-            drt_cmd = '%s %s 2>%s;echo "#DRT_RETURN $?";sleep 1;exit\n' % (DEVICE_DRT_PATH, ' '.join(drt_args), DEVICE_DRT_STDERR)
-            _log.debug('Starting DumpRenderTree: ' + drt_cmd)
+    def _file_exists_on_device(self, full_file_path):
+        assert full_file_path.startswith('/')
+        return self._port._run_adb_command(['shell', 'ls', full_file_path]).strip() == full_file_path
 
-            # Wait until DRT echos '#READY'.
-            output = ''
-            (line, crash) = self._write_command_and_read_line(drt_cmd)
-            while not crash and line.rstrip() != '#READY':
-                if line == '':  # EOF or crashed
-                    crash = True
-                else:
-                    output += line
-                    (line, crash) = self._write_command_and_read_line()
+    def _deadlock_detector(self, pids, normal_startup_event):
+        time.sleep(DRT_START_STOP_TIMEOUT_SECS)
+        if not normal_startup_event.is_set():
+            # If normal_startup_event is not set in time, the main thread must be blocked at
+            # reading/writing the fifo. Kill the fifo reading/writing processes to let the
+            # main thread escape from the deadlocked state. After that, the main thread will
+            # treat this as a crash.
+            for i in pids:
+                self._port._executive.kill_process(i)
+        # Otherwise the main thread has been proceeded normally. This thread just exits silently.
 
-            if crash:
-                # Sometimes the device is in unstable state (may be out of
-                # memory?) and kills DumpRenderTree just after it is started.
-                # Try to stop and start it again.
-                _log.error('Failed to start DumpRenderTree: \n%s\n%s\n' % (output, self._port.get_stderr()))
-                self.stop()
-                retries += 1
-                if retries > 2:
-                    raise AssertionError('Failed multiple times to start DumpRenderTree')
-            else:
-                return
+    def _start(self, pixel_tests, per_test_args):
+        if ChromiumAndroidDriver._started_driver:
+            ChromiumAndroidDriver._started_driver.stop()
 
+        ChromiumAndroidDriver._started_driver = self
+
+        self._port._run_adb_command(['logcat', '-c'])
+        self._port._run_adb_command(['shell', 'echo'] + self.cmd_line(pixel_tests, per_test_args) + ['>', COMMAND_LINE_FILE])
+        self._port._run_adb_command(['shell', 'am', 'start', '-n', DRT_ACTIVITY_FULL_NAME])
+        seconds = 0
+        while (not self._file_exists_on_device(self._in_fifo_path) or
+               not self._file_exists_on_device(self._out_fifo_path) or
+               not self._file_exists_on_device(self._err_file_path)):
+            time.sleep(1)
+            seconds += 1
+            if seconds >= DRT_START_STOP_TIMEOUT_SECS:
+                _log.error('Failed to start DumpRenderTreeApplication. Log:\n' + self._port._get_logcat())
+                raise AssertionError('Failed to start DumpRenderTree application.')
+
+        shell_cmd = self._port._adb_command + ['shell']
+        executive = self._port._executive
+        # Start a process to send command through the input fifo of the DumpRenderTree app.
+        # This process must be run as an interactive adb shell because the normal adb shell doesn't support stdin.
+        self._proc = executive.popen(shell_cmd, stdin=executive.PIPE, stdout=executive.PIPE, universal_newlines=True)
+        # Read back the shell prompt to ensure adb shell ready.
+        self._read_prompt()
+        _log.debug('Interactive shell started')
+
+        # Start a process to read from the output fifo of the DumpRenderTree app and print to stdout.
+        _log.debug('Redirecting stdout to ' + self._out_fifo_path)
+        self._read_fifo_proc = executive.popen(shell_cmd + ['cat', self._out_fifo_path],
+                                               stdout=executive.PIPE, universal_newlines=True)
+
+        _log.debug('Redirecting stdin to ' + self._in_fifo_path)
+        (line, crash) = self._write_command_and_read_line('cat >%s\n' % self._in_fifo_path)
+
+        # Combine the two unidirectional pipes into one bidirectional pipe to make _write_command_and_read_line() etc
+        # work with self._proc.
+        self._proc.stdout.close()
+        self._proc.stdout = self._read_fifo_proc.stdout
+
+        # Start a thread to kill the pipe reading/writing processes on deadlock of the fifos during startup.
+        normal_startup_event = threading.Event()
+        threading.Thread(target=self._deadlock_detector,
+                         args=([self._proc.pid, self._read_fifo_proc.pid], normal_startup_event)).start()
+
+        output = ''
+        while not crash and line.rstrip() != '#READY':
+            output += line
+            (line, crash) = self._write_command_and_read_line()
+
+        if crash:
+            # DumpRenderTree crashes during startup, or when the deadlock detector detected
+            # deadlock and killed the fifo reading/writing processes.
+            _log.error('Failed to start DumpRenderTree: \n%s\nLog:\n%s' % (output, self._port._get_logcat()))
+            self.stop()
+            raise AssertionError('Failed to start DumpRenderTree application')
+        else:
+            # Inform the deadlock detector that the startup is successful without deadlock.
+            normal_startup_event.set()
+            return
+
     def run_test(self, driver_input):
         driver_output = chromium.ChromiumDriver.run_test(self, driver_input)
-
-        drt_return = self._get_drt_return_value(driver_output.error)
-        if drt_return is not None:
-            _log.debug('DumpRenderTree return value: %d' % drt_return)
-        # FIXME: Retrieve stderr from the target.
         if driver_output.crash:
-            # When Android is OOM, it sends a SIGKILL signal to DRT. DRT
-            # is stopped silently and regarded as crashed. Re-run the test for
+            # When Android is OOM, DRT process may be killed by ActivityManager or system OOM.
+            # It looks like a crash but there is no fatal signal logged. Re-run the test for
             # such crash.
-            if drt_return == 128 + signal.SIGKILL:
+            # To test: adb shell am force-stop org.chromium.native_test,
+            # or kill -11 pid twice or three times to simulate a fatal crash.
+            if self._port._get_logcat().find('Fatal signal') == -1:
+                self._restart_after_killed = True
                 self._port._drt_retry_after_killed += 1
                 if self._port._drt_retry_after_killed > 10:
                     raise AssertionError('DumpRenderTree is killed by Android for too many times!')
-                _log.error('DumpRenderTree is killed by SIGKILL. Retry the test (%d).' % self._port._drt_retry_after_killed)
+                _log.error('DumpRenderTree is killed by system (%d).' % self._port._drt_retry_after_killed)
                 self.stop()
                 # Sleep 10 seconds to let system recover.
                 time.sleep(10)
                 return self.run_test(driver_input)
-            # Fetch the stack trace from the tombstone file.
-            # FIXME: sometimes the crash doesn't really happen so that no
-            # tombstone is generated. In that case we fetch the wrong stack
-            # trace.
-            driver_output.error += self._port.get_last_stacktrace().encode('ascii', 'ignore')
-            driver_output.error += self._port._run_adb_command(['logcat', '-d']).encode('ascii', 'ignore')
+
+        self._restart_after_killed = False
+        driver_output.error += self._get_stderr()
         return driver_output
 
     def stop(self):
-        _log.debug('Stopping DumpRenderTree')
+        if ChromiumAndroidDriver._started_driver != self:
+            return
+        ChromiumAndroidDriver._started_driver = None
+
+        self._port._run_adb_command(['shell', 'am', 'force-stop', DRT_APP_PACKAGE])
+
+        if self._read_fifo_proc:
+            self._port._executive.kill_process(self._read_fifo_proc.pid)
+            self._read_fifo_proc = None
+
+        # Here duplicate some logic in ChromiumDriver.stop() instead of directly calling it,
+        # because our pipe reading/writing processes won't quit by itself on close of the pipes.
         if self._proc:
-            # Send an explicit QUIT command because closing the pipe can't let
-            # DumpRenderTree on Android quit immediately.
-            try:
-                self._proc.stdin.write('QUIT\n')
-            except IOError:
-                # The pipe has already been closed, indicating abnormal
-                # situation occurred. Wait a while to allow the device to
-                # recover. *fingers crossed*
-                time.sleep(1)
-        chromium.ChromiumDriver.stop(self)
+            self._proc.stdin.close()
+            self._proc.stdout.close()
+            if self._proc.stderr:
+                self._proc.stderr.close()
+            self._port._executive.kill_process(self._proc.pid)
+            if self._proc.poll() is not None:
+                self._proc.wait()
+            self._proc = None
 
+        seconds = 0
+        while (self._file_exists_on_device(self._in_fifo_path) or
+               self._file_exists_on_device(self._out_fifo_path) or
+               self._file_exists_on_device(self._err_file_path)):
+            time.sleep(1)
+            self._port._run_adb_command(['shell', 'rm', self._in_fifo_path, self._out_fifo_path, self._err_file_path])
+            seconds += 1
+            if seconds >= DRT_START_STOP_TIMEOUT_SECS:
+                raise AssertionError('Failed to remove fifo files. May be locked.')
+
     def _test_shell_command(self, uri, timeout_ms, checksum):
         if uri.startswith('file:///'):
             # Convert the host uri to a device uri. See comment of
@@ -517,31 +601,26 @@
     def _write_command_and_read_line(self, input=None):
         (line, crash) = chromium.ChromiumDriver._write_command_and_read_line(self, input)
         url_marker = '#URL:'
-        if not crash and line.startswith(url_marker) and line.find(FILE_TEST_URI_PREFIX) == len(url_marker):
-            # Convert the device test uri back to host uri otherwise
-            # chromium.ChromiumDriver.run_test() will complain.
-            line = '#URL:file://%s/%s' % (self._port.layout_tests_dir(), line[len(url_marker) + len(FILE_TEST_URI_PREFIX):])
-        if not crash and self._has_crash_hint(line):
-            crash = True
+        if not crash:
+            if line.startswith(url_marker) and line.find(FILE_TEST_URI_PREFIX) == len(url_marker):
+                # Convert the device test uri back to host uri otherwise
+                # chromium.ChromiumDriver.run_test() will complain.
+                line = '#URL:file://%s/%s' % (self._port.layout_tests_dir(), line[len(url_marker) + len(FILE_TEST_URI_PREFIX):])
+            # chromium.py uses "line == '' and self._proc.poll() is not None" to detect crash,
+            # but on Android "not line" is enough because self._proc.poll() seems not reliable.
+            if not line:
+                crash = True
         return (line, crash)
 
     def _output_image(self):
         if self._image_path:
-            _log.debug('pulling from device: %s to %s' % (self._device_image_path, self._image_path))
+            _log.debug('Pulling from device: %s to %s' % (self._device_image_path, self._image_path))
             self._port._pull_from_device(self._device_image_path, self._image_path, ignore_error=True)
         return chromium.ChromiumDriver._output_image(self)
 
-    def _has_crash_hint(self, line):
-        # When DRT crashes, it sends a signal to Android Debuggerd, like
-        # SIGSEGV, SIGFPE, etc. When Debuggerd receives the signal, it stops DRT
-        # (which causes Shell to output a message), and dumps the stack strace.
-        # We use the Shell output as a crash hint.
-        return line is not None and line.find('[1] + Stopped (signal)') >= 0
+    def _get_stderr(self):
+        return self._port._run_adb_command(['shell', 'cat', self._err_file_path], ignore_error=True)
 
-    def _get_drt_return_value(self, error):
-        return_match = self._drt_return_parser.search(error)
-        return None if (return_match is None) else int(return_match.group(1))
-
     def _read_prompt(self):
         last_char = ''
         while True:
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to