# 2. pointing to some files that are pushed to the device for tests that
# don't work on file-over-http (e.g. blob protocol tests).
DEVICE_LAYOUT_TESTS_DIR = (DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/LayoutTests/')
+FILE_TEST_URI_PREFIX = 'file://' + DEVICE_LAYOUT_TESTS_DIR
# Test resources that need to be accessed as files directly.
# Each item can be the relative path of a directory or a file.
# Other directories will be created automatically by adb push.
self._run_adb_command(['shell', 'mkdir', '-p',
DEVICE_SOURCE_ROOT_DIR + 'chrome'])
+
self._push_executable()
self._push_fonts()
self._setup_system_font_for_test()
def _shut_down_http_server(self, pid):
return self._host_port._shut_down_http_server(pid)
+ def _driver_class(self):
+ return ChromiumAndroidDriver
+
def _push_executable(self):
drt_host_path = self._path_to_driver()
forwarder_host_path = self._path_to_helper()
except:
return False
+ def _update_version(self, dir, version):
+ self._run_adb_command(['shell', 'echo %d > %sVERSION' % (version, dir)])
+
def _run_adb_command(self, cmd, ignore_error=False):
if ignore_error:
error_handler = self._executive.ignore_error
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):
+ tombstones = self._run_adb_command(['shell', 'ls', '-n', '/data/tombstones'])
+ tombstones = tombstones.rstrip().split('\n')
+ if not tombstones:
+ return ''
+ last_tombstone = tombstones[0].split()
+ for tombstone in tombstones[1:]:
+ # Format of fields:
+ # 0 1 2 3 4 5 6
+ # permission uid gid size date time filename
+ # -rw------- 1000 1000 45859 2011-04-13 06:00 tombstone_00
+ fields = tombstone.split()
+ if (fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]):
+ last_tombstone = fields
+ else:
+ break
+
+ # 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]])
+
def _setup_performance(self):
# Disable CPU scaling and drop ram cache to reduce noise in tests
if not self._original_governor:
if self._original_governor:
self._run_adb_command(['shell', 'echo', self._original_governor, SCALING_GOVERNOR])
self._original_governor = None
+
+
+class ChromiumAndroidDriver(chromium.ChromiumDriver):
+ def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
+ chromium.ChromiumDriver.__init__(self, port, worker_number, pixel_tests, no_timeout)
+ if self._image_path:
+ self._device_image_path = DEVICE_DRT_DIR + port.host.filesystem.basename(self._image_path)
+
+ def _start(self):
+ # 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()
+ shell_cmd = []
+ drt_args = []
+ path_to_driver = self._port._path_to_driver()
+ reading_args_before_driver = True
+ 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='):
+ param = '--pixel-tests=' + self._device_image_path
+ drt_args.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)
+ # Read back the shell prompt ('# ') to ensure adb shell ready.
+ prompt = self._proc.stdout.read(2)
+ assert(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")
+
+ # 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;sleep 1;exit\n' % (DEVICE_DRT_PATH, ' '.join(drt_args), DEVICE_DRT_STDERR)
+ _log.debug('Starting DumpRenderTree: ' + drt_cmd)
+
+ # 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()
+
+ 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 run_test(self, driver_input):
+ driver_output = chromium.ChromiumDriver.run_test(self, driver_input)
+ # FIXME: Retrieve stderr from the target.
+ if driver_output.crash:
+ # 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()
+ return driver_output
+
+ def stop(self):
+ _log.debug('Stopping DumpRenderTree')
+ 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)
+
+ def _test_shell_command(self, uri, timeout_ms, checksum):
+ if uri.startswith('file:///'):
+ # Convert the host uri to a device uri. See comment of
+ # DEVICE_LAYOUT_TESTS_DIR for details.
+ # Not overriding Port.filename_to_uri() because we don't want the
+ # links in the html report point to device paths.
+ uri = FILE_TEST_URI_PREFIX + self.uri_to_test(uri)
+ return chromium.ChromiumDriver._test_shell_command(self, uri, timeout_ms, checksum)
+
+ 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
+ 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))
+ 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