IndexedDB: Introduce putWithIndexKeys and calculate them in the renderer
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / port / chromium_android.py
1 #!/usr/bin/env python
2 # Copyright (C) 2012 Google Inc. All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 import logging
31 import os
32 import re
33 import signal
34 import shlex
35 import shutil
36 import threading
37 import time
38
39 from webkitpy.layout_tests.port import chromium
40 from webkitpy.layout_tests.port import factory
41
42
43 _log = logging.getLogger(__name__)
44
45
46 # The root directory for test resources, which has the same structure as the
47 # source root directory of Chromium.
48 # This path is defined in base/base_paths_android.cc and
49 # webkit/support/platform_support_android.cc.
50 DEVICE_SOURCE_ROOT_DIR = '/data/local/tmp/'
51 COMMAND_LINE_FILE = DEVICE_SOURCE_ROOT_DIR + 'chrome-native-tests-command-line'
52
53 # The directory to put tools and resources of DumpRenderTree.
54 DEVICE_DRT_DIR = '/data/drt/'
55 DEVICE_FORWARDER_PATH = DEVICE_DRT_DIR + 'forwarder'
56 DEVICE_DRT_STAMP_PATH = DEVICE_DRT_DIR + 'DumpRenderTree.stamp'
57
58 DRT_APP_PACKAGE = 'org.chromium.native_test'
59 DRT_ACTIVITY_FULL_NAME = DRT_APP_PACKAGE + '/.ChromeNativeTestActivity'
60 DRT_APP_DIR = '/data/user/0/' + DRT_APP_PACKAGE + '/'
61 DRT_APP_FILES_DIR = DRT_APP_DIR + 'files/'
62 DRT_APP_CACHE_DIR = DRT_APP_DIR + 'cache/'
63
64 # This only works for single core devices so far.
65 # FIXME: Find a solution for multi-core devices.
66 SCALING_GOVERNOR = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"
67
68 # All the test cases are still served to DumpRenderTree through file protocol,
69 # but we use a file-to-http feature to bridge the file request to host's http
70 # server to get the real test files and corresponding resources.
71 TEST_PATH_PREFIX = '/all-tests'
72
73 # All ports the Android forwarder to forward.
74 # 8000, 8080 and 8443 are for http/https tests.
75 # 8880 and 9323 are for websocket tests
76 # (see http_server.py, apache_http_server.py and websocket_server.py).
77 FORWARD_PORTS = '8000 8080 8443 8880 9323'
78
79 MS_TRUETYPE_FONTS_DIR = '/usr/share/fonts/truetype/msttcorefonts/'
80
81 # Timeout in seconds to wait for start/stop of DumpRenderTree.
82 DRT_START_STOP_TIMEOUT_SECS = 10
83
84 # List of fonts that layout tests expect, copied from DumpRenderTree/gtk/TestShellX11.cpp.
85 HOST_FONT_FILES = [
86     [MS_TRUETYPE_FONTS_DIR, 'Arial.ttf'],
87     [MS_TRUETYPE_FONTS_DIR, 'Arial_Bold.ttf'],
88     [MS_TRUETYPE_FONTS_DIR, 'Arial_Bold_Italic.ttf'],
89     [MS_TRUETYPE_FONTS_DIR, 'Arial_Italic.ttf'],
90     [MS_TRUETYPE_FONTS_DIR, 'Comic_Sans_MS.ttf'],
91     [MS_TRUETYPE_FONTS_DIR, 'Comic_Sans_MS_Bold.ttf'],
92     [MS_TRUETYPE_FONTS_DIR, 'Courier_New.ttf'],
93     [MS_TRUETYPE_FONTS_DIR, 'Courier_New_Bold.ttf'],
94     [MS_TRUETYPE_FONTS_DIR, 'Courier_New_Bold_Italic.ttf'],
95     [MS_TRUETYPE_FONTS_DIR, 'Courier_New_Italic.ttf'],
96     [MS_TRUETYPE_FONTS_DIR, 'Georgia.ttf'],
97     [MS_TRUETYPE_FONTS_DIR, 'Georgia_Bold.ttf'],
98     [MS_TRUETYPE_FONTS_DIR, 'Georgia_Bold_Italic.ttf'],
99     [MS_TRUETYPE_FONTS_DIR, 'Georgia_Italic.ttf'],
100     [MS_TRUETYPE_FONTS_DIR, 'Impact.ttf'],
101     [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS.ttf'],
102     [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS_Bold.ttf'],
103     [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS_Bold_Italic.ttf'],
104     [MS_TRUETYPE_FONTS_DIR, 'Trebuchet_MS_Italic.ttf'],
105     [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman.ttf'],
106     [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman_Bold.ttf'],
107     [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman_Bold_Italic.ttf'],
108     [MS_TRUETYPE_FONTS_DIR, 'Times_New_Roman_Italic.ttf'],
109     [MS_TRUETYPE_FONTS_DIR, 'Verdana.ttf'],
110     [MS_TRUETYPE_FONTS_DIR, 'Verdana_Bold.ttf'],
111     [MS_TRUETYPE_FONTS_DIR, 'Verdana_Bold_Italic.ttf'],
112     [MS_TRUETYPE_FONTS_DIR, 'Verdana_Italic.ttf'],
113     # The Microsoft font EULA
114     ['/usr/share/doc/ttf-mscorefonts-installer/', 'READ_ME!.gz'],
115     ['/usr/share/fonts/truetype/ttf-dejavu/', 'DejaVuSans.ttf'],
116 ]
117 # Should increase this version after changing HOST_FONT_FILES.
118 FONT_FILES_VERSION = 2
119
120 DEVICE_FONTS_DIR = DEVICE_DRT_DIR + 'fonts/'
121
122 # The layout tests directory on device, which has two usages:
123 # 1. as a virtual path in file urls that will be bridged to HTTP.
124 # 2. pointing to some files that are pushed to the device for tests that
125 # don't work on file-over-http (e.g. blob protocol tests).
126 DEVICE_LAYOUT_TESTS_DIR = (DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/LayoutTests/')
127 FILE_TEST_URI_PREFIX = 'file://' + DEVICE_LAYOUT_TESTS_DIR
128
129 # Test resources that need to be accessed as files directly.
130 # Each item can be the relative path of a directory or a file.
131 TEST_RESOURCES_TO_PUSH = [
132     # Blob tests need to access files directly.
133     'editing/pasteboard/resources',
134     'fast/files/resources',
135     'http/tests/local/resources',
136     'http/tests/local/formdata/resources',
137     # User style URLs are accessed as local files in webkit_support.
138     'http/tests/security/resources/cssStyle.css',
139     # Media tests need to access audio/video as files.
140     'media/content',
141     'compositing/resources/video.mp4',
142 ]
143
144
145 class ChromiumAndroidPort(chromium.ChromiumPort):
146     port_name = 'chromium-android'
147
148     FALLBACK_PATHS = [
149         'chromium-android',
150         'chromium-linux',
151         'chromium-win',
152         'chromium',
153         'win',
154         'mac',
155     ]
156
157     def __init__(self, host, port_name, **kwargs):
158         chromium.ChromiumPort.__init__(self, host, port_name, **kwargs)
159
160         # FIXME: Stop using test_shell mode: https://bugs.webkit.org/show_bug.cgi?id=88542
161         if not hasattr(self._options, 'additional_drt_flag'):
162             self._options.additional_drt_flag = []
163         if not '--test-shell' in self._options.additional_drt_flag:
164             self._options.additional_drt_flag.append('--test-shell')
165
166         # The Chromium port for Android always uses the hardware GPU path.
167         self._options.enable_hardware_gpu = True
168
169         self._operating_system = 'android'
170         self._version = 'icecreamsandwich'
171         self._original_governor = None
172         self._android_base_dir = None
173         self._read_fifo_proc = None
174
175         self._host_port = factory.PortFactory(host).get('chromium', **kwargs)
176
177         self._adb_command = ['adb']
178         adb_args = self.get_option('adb_args')
179         if adb_args:
180             self._adb_command += shlex.split(adb_args)
181         self._drt_retry_after_killed = 0
182
183     def default_timeout_ms(self):
184         # Android platform has less computing power than desktop platforms.
185         # Using 10 seconds allows us to pass most slow tests which are not
186         # marked as slow tests on desktop platforms.
187         return 10 * 1000
188
189     def default_child_processes(self):
190         # Because of the nature of apk, we don't support more than one process.
191         return 1
192
193     def baseline_search_path(self):
194         return map(self._webkit_baseline_path, self.FALLBACK_PATHS)
195
196     def check_wdiff(self, logging=True):
197         return self._host_port.check_wdiff(logging)
198
199     def check_build(self, needs_http):
200         result = chromium.ChromiumPort.check_build(self, needs_http)
201         result = self.check_wdiff() and result
202         if not result:
203             _log.error('For complete Android build requirements, please see:')
204             _log.error('')
205             _log.error('    http://code.google.com/p/chromium/wiki/AndroidBuildInstructions')
206
207         return result
208
209     def check_sys_deps(self, needs_http):
210         for (font_dir, font_file) in HOST_FONT_FILES:
211             font_path = font_dir + font_file
212             if not self._check_file_exists(font_path, 'font file'):
213                 _log.error('You are missing %s. Try installing msttcorefonts. '
214                            'See build instructions.' % font_path)
215                 return False
216         return True
217
218     # FIXME: Remove this function when chromium-android is fully upstream.
219     def expectations_files(self):
220         android_expectations_file = self.path_from_webkit_base('LayoutTests', 'platform', 'chromium', 'test_expectations_android.txt')
221         return super(ChromiumAndroidPort, self).expectations_files() + [android_expectations_file]
222
223     def test_expectations(self):
224         # Automatically apply all expectation rules of chromium-linux to
225         # chromium-android.
226         # FIXME: This is a temporary measure to reduce the manual work when
227         # updating WebKit. This method should be removed when we merge
228         # test_expectations_android.txt into TestExpectations.
229         expectations = chromium.ChromiumPort.test_expectations(self)
230         return expectations.replace('LINUX ', 'LINUX ANDROID ')
231
232     def start_http_server(self, additional_dirs=None, number_of_servers=0):
233         # The http server runs during the whole testing period, so ignore this call.
234         pass
235
236     def stop_http_server(self):
237         # Same as start_http_server().
238         pass
239
240     def setup_test_run(self):
241         self._run_adb_command(['root'])
242         self._setup_performance()
243         # Required by webkit_support::GetWebKitRootDirFilePath().
244         # Other directories will be created automatically by adb push.
245         self._run_adb_command(['shell', 'mkdir', '-p', DEVICE_SOURCE_ROOT_DIR + 'chrome'])
246         # Allow the DumpRenderTree app to fully access the directory.
247         # The native code needs the permission to write temporary files here.
248         self._run_adb_command(['shell', 'chmod', '777', DEVICE_SOURCE_ROOT_DIR])
249
250         self._push_executable()
251         self._push_fonts()
252         self._synchronize_datetime()
253
254         # Delete the disk cache if any to ensure a clean test run.
255         # This is like what's done in ChromiumPort.setup_test_run but on the device.
256         self._run_adb_command(['shell', 'rm', '-r', DRT_APP_CACHE_DIR])
257
258         # Start the HTTP server so that the device can access the test cases.
259         chromium.ChromiumPort.start_http_server(self, additional_dirs={TEST_PATH_PREFIX: self.layout_tests_dir()})
260
261         _log.debug('Starting forwarder')
262         self._run_adb_command(['shell', '%s %s' % (DEVICE_FORWARDER_PATH, FORWARD_PORTS)])
263
264     def clean_up_test_run(self):
265         # Leave the forwarder and tests httpd server there because they are
266         # useful for debugging and do no harm to subsequent tests.
267         self._teardown_performance()
268
269     def skipped_layout_tests(self, test_list):
270         return self._real_tests([
271             # Canvas tests are run as virtual gpu tests.
272             'fast/canvas',
273             'canvas/philip',
274         ])
275
276     # Overridden private functions.
277
278     def _build_path(self, *comps):
279         return self._host_port._build_path(*comps)
280
281     def _path_to_apache(self):
282         return self._host_port._path_to_apache()
283
284     def _path_to_apache_config_file(self):
285         return self._host_port._path_to_apache_config_file()
286
287     def _path_to_driver(self, configuration=None):
288         if not configuration:
289             configuration = self.get_option('configuration')
290         return self._build_path(configuration, 'DumpRenderTree_apk/DumpRenderTree-debug.apk')
291
292     def _path_to_helper(self):
293         return None
294
295     def _path_to_forwarder(self):
296         return self._build_path(self.get_option('configuration'), 'forwarder')
297
298     def _path_to_image_diff(self):
299         return self._host_port._path_to_image_diff()
300
301     def _path_to_lighttpd(self):
302         return self._host_port._path_to_lighttpd()
303
304     def _path_to_lighttpd_modules(self):
305         return self._host_port._path_to_lighttpd_modules()
306
307     def _path_to_lighttpd_php(self):
308         return self._host_port._path_to_lighttpd_php()
309
310     def _path_to_wdiff(self):
311         return self._host_port._path_to_wdiff()
312
313     def _shut_down_http_server(self, pid):
314         return self._host_port._shut_down_http_server(pid)
315
316     def _driver_class(self):
317         return ChromiumAndroidDriver
318
319     def _get_crash_log(self, name, pid, stdout, stderr, newer_than):
320         if not stdout:
321             stdout = ''
322         stdout += '********* Logcat:\n' + self._get_logcat()
323         if not stderr:
324             stderr = ''
325         stderr += '********* Tombstone file:\n' + self._get_last_stacktrace()
326         return chromium.ChromiumPort._get_crash_log(self, name, pid, stdout, stderr, newer_than)
327
328     # Local private functions.
329
330     def _push_executable(self):
331         drt_host_path = self._path_to_driver()
332         forwarder_host_path = self._path_to_forwarder()
333         host_stamp = int(float(max(os.stat(drt_host_path).st_mtime,
334                                    os.stat(forwarder_host_path).st_mtime)))
335         device_stamp = int(float(self._run_adb_command([
336             'shell', 'cat %s 2>/dev/null || echo 0' % DEVICE_DRT_STAMP_PATH])))
337         if device_stamp < host_stamp:
338             _log.debug('Pushing executable')
339             self._push_to_device(forwarder_host_path, DEVICE_FORWARDER_PATH)
340             self._run_adb_command(['uninstall', DRT_APP_PACKAGE])
341             install_result = self._run_adb_command(['install', drt_host_path])
342             if install_result.find('Success') == -1:
343                 raise AssertionError('Failed to install %s onto device: %s' % (drt_host_path, install_result))
344             self._push_to_device(self._build_path(self.get_option('configuration'), 'DumpRenderTree.pak'),
345                                  DEVICE_DRT_DIR + 'DumpRenderTree.pak')
346             self._push_to_device(self._build_path(self.get_option('configuration'), 'DumpRenderTree_resources'),
347                                  DEVICE_DRT_DIR + 'DumpRenderTree_resources')
348             self._push_to_device(self._build_path(self.get_option('configuration'), 'android_main_fonts.xml'),
349                                  DEVICE_DRT_DIR + 'android_main_fonts.xml')
350             self._push_to_device(self._build_path(self.get_option('configuration'), 'android_fallback_fonts.xml'),
351                                  DEVICE_DRT_DIR + 'android_fallback_fonts.xml')
352             # Version control of test resources is dependent on executables,
353             # because we will always rebuild executables when resources are
354             # updated.
355             self._push_test_resources()
356             self._run_adb_command(['shell', 'echo %d >%s' % (host_stamp, DEVICE_DRT_STAMP_PATH)])
357
358     def _push_fonts(self):
359         if not self._check_version(DEVICE_FONTS_DIR, FONT_FILES_VERSION):
360             _log.debug('Pushing fonts')
361             path_to_ahem_font = self._build_path(self.get_option('configuration'), 'AHEM____.TTF')
362             self._push_to_device(path_to_ahem_font, DEVICE_FONTS_DIR + 'AHEM____.TTF')
363             for (host_dir, font_file) in HOST_FONT_FILES:
364                 self._push_to_device(host_dir + font_file, DEVICE_FONTS_DIR + font_file)
365             self._link_device_file('/system/fonts/DroidSansFallback.ttf', DEVICE_FONTS_DIR + 'DroidSansFallback.ttf')
366             self._update_version(DEVICE_FONTS_DIR, FONT_FILES_VERSION)
367
368     def _push_test_resources(self):
369         _log.debug('Pushing test resources')
370         for resource in TEST_RESOURCES_TO_PUSH:
371             self._push_to_device(self.layout_tests_dir() + '/' + resource, DEVICE_LAYOUT_TESTS_DIR + resource)
372
373     def _synchronize_datetime(self):
374         # The date/time between host and device may not be synchronized.
375         # We need to make them synchronized, otherwise tests might fail.
376         try:
377             # Get seconds since 1970-01-01 00:00:00 UTC.
378             host_datetime = self._executive.run_command(['date', '-u', '+%s'])
379         except:
380             # Reset to 1970-01-01 00:00:00 UTC.
381             host_datetime = 0
382         self._run_adb_command(['shell', 'date -u %s' % (host_datetime)])
383
384     def _check_version(self, dir, version):
385         assert(dir.endswith('/'))
386         try:
387             device_version = int(self._run_adb_command(['shell', 'cat %sVERSION || echo 0' % dir]))
388             return device_version == version
389         except:
390             return False
391
392     def _update_version(self, dir, version):
393         self._run_adb_command(['shell', 'echo %d > %sVERSION' % (version, dir)])
394
395     def _run_adb_command(self, cmd, ignore_error=False):
396         _log.debug('Run adb command: ' + str(cmd))
397         if ignore_error:
398             error_handler = self._executive.ignore_error
399         else:
400             error_handler = None
401         result = self._executive.run_command(self._adb_command + cmd, error_handler=error_handler)
402         _log.debug('Run adb result:\n' + result)
403         return result
404
405     def _link_device_file(self, from_file, to_file, ignore_error=False):
406         # rm to_file first to make sure that ln succeeds.
407         self._run_adb_command(['shell', 'rm', to_file], ignore_error)
408         return self._run_adb_command(['shell', 'ln', '-s', from_file, to_file], ignore_error)
409
410     def _push_to_device(self, host_path, device_path, ignore_error=False):
411         return self._run_adb_command(['push', host_path, device_path], ignore_error)
412
413     def _pull_from_device(self, device_path, host_path, ignore_error=False):
414         return self._run_adb_command(['pull', device_path, host_path], ignore_error)
415
416     def _get_last_stacktrace(self):
417         tombstones = self._run_adb_command(['shell', 'ls', '-n', '/data/tombstones'])
418         if not tombstones or tombstones.startswith('/data/tombstones: No such file or directory'):
419             _log.error('DRT crashed, but no tombstone found!')
420             return ''
421         tombstones = tombstones.rstrip().split('\n')
422         last_tombstone = tombstones[0].split()
423         for tombstone in tombstones[1:]:
424             # Format of fields:
425             # 0          1      2      3     4          5     6
426             # permission uid    gid    size  date       time  filename
427             # -rw------- 1000   1000   45859 2011-04-13 06:00 tombstone_00
428             fields = tombstone.split()
429             if (fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]):
430                 last_tombstone = fields
431             else:
432                 break
433
434         # Use Android tool vendor/google/tools/stack to convert the raw
435         # stack trace into a human readable format, if needed.
436         # It takes a long time, so don't do it here.
437         return '%s\n%s' % (' '.join(last_tombstone),
438                            self._run_adb_command(['shell', 'cat', '/data/tombstones/' + last_tombstone[6]]))
439
440     def _get_logcat(self):
441         return self._run_adb_command(['logcat', '-d'])
442
443     def _setup_performance(self):
444         # Disable CPU scaling and drop ram cache to reduce noise in tests
445         if not self._original_governor:
446             self._original_governor = self._run_adb_command(['shell', 'cat', SCALING_GOVERNOR], ignore_error=True)
447             if self._original_governor:
448                 self._run_adb_command(['shell', 'echo', 'performance', '>', SCALING_GOVERNOR])
449
450     def _teardown_performance(self):
451         if self._original_governor:
452             self._run_adb_command(['shell', 'echo', self._original_governor, SCALING_GOVERNOR])
453         self._original_governor = None
454
455
456 class ChromiumAndroidDriver(chromium.ChromiumDriver):
457     # The controller may start multiple drivers during test, but for now we
458     # don't support multiple Android activities, so only one driver can be
459     # started at a time.
460     _started_driver = None
461
462     def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
463         chromium.ChromiumDriver.__init__(self, port, worker_number, pixel_tests, no_timeout)
464         self._in_fifo_path = DRT_APP_FILES_DIR + 'DumpRenderTree.in'
465         self._out_fifo_path = DRT_APP_FILES_DIR + 'DumpRenderTree.out'
466         self._err_file_path = DRT_APP_FILES_DIR + 'DumpRenderTree.err'
467         self._restart_after_killed = False
468         self._read_fifo_proc = None
469
470     def _command_wrapper(cls, wrapper_option):
471         # Ignore command wrapper which is not applicable on Android.
472         return []
473
474     def cmd_line(self, pixel_tests, per_test_args):
475         original_cmd = chromium.ChromiumDriver.cmd_line(self, pixel_tests, per_test_args)
476         cmd = []
477         for param in original_cmd:
478             if param.startswith('--pixel-tests='):
479                 self._device_image_path = DRT_APP_FILES_DIR + self._port.host.filesystem.basename(self._image_path)
480                 param = '--pixel-tests=' + self._device_image_path
481             cmd.append(param)
482
483         cmd.append('--in-fifo=' + self._in_fifo_path)
484         cmd.append('--out-fifo=' + self._out_fifo_path)
485         cmd.append('--err-file=' + self._err_file_path)
486         return cmd
487
488     def _file_exists_on_device(self, full_file_path):
489         assert full_file_path.startswith('/')
490         return self._port._run_adb_command(['shell', 'ls', full_file_path]).strip() == full_file_path
491
492     def _deadlock_detector(self, pids, normal_startup_event):
493         time.sleep(DRT_START_STOP_TIMEOUT_SECS)
494         if not normal_startup_event.is_set():
495             # If normal_startup_event is not set in time, the main thread must be blocked at
496             # reading/writing the fifo. Kill the fifo reading/writing processes to let the
497             # main thread escape from the deadlocked state. After that, the main thread will
498             # treat this as a crash.
499             for i in pids:
500                 self._port._executive.kill_process(i)
501         # Otherwise the main thread has been proceeded normally. This thread just exits silently.
502
503     def _start(self, pixel_tests, per_test_args):
504         if ChromiumAndroidDriver._started_driver:
505             ChromiumAndroidDriver._started_driver.stop()
506
507         ChromiumAndroidDriver._started_driver = self
508
509         retries = 0
510         while not self._start_once(pixel_tests, per_test_args):
511             _log.error('Failed to start DumpRenderTree application. Log:\n' + self._port._get_logcat())
512             retries += 1
513             if retries >= 3:
514                 raise AssertionError('Failed to start DumpRenderTree application multiple times. Give up.')
515             self.stop()
516             time.sleep(2)
517
518     def _start_once(self, pixel_tests, per_test_args):
519         self._port._run_adb_command(['logcat', '-c'])
520         self._port._run_adb_command(['shell', 'echo'] + self.cmd_line(pixel_tests, per_test_args) + ['>', COMMAND_LINE_FILE])
521         start_result = self._port._run_adb_command(['shell', 'am', 'start', '-e', 'RunInSubThread', '-n', DRT_ACTIVITY_FULL_NAME])
522         if start_result.find('Exception') != -1:
523             _log.error('Failed to start DumpRenderTree application. Exception:\n' + start_result)
524             return False
525
526         seconds = 0
527         while (not self._file_exists_on_device(self._in_fifo_path) or
528                not self._file_exists_on_device(self._out_fifo_path) or
529                not self._file_exists_on_device(self._err_file_path)):
530             time.sleep(1)
531             seconds += 1
532             if seconds >= DRT_START_STOP_TIMEOUT_SECS:
533                 return False
534
535         shell_cmd = self._port._adb_command + ['shell']
536         executive = self._port._executive
537         # Start a process to send command through the input fifo of the DumpRenderTree app.
538         # This process must be run as an interactive adb shell because the normal adb shell doesn't support stdin.
539         self._proc = executive.popen(shell_cmd, stdin=executive.PIPE, stdout=executive.PIPE, universal_newlines=True)
540         # Read back the shell prompt to ensure adb shell ready.
541         self._read_prompt()
542         _log.debug('Interactive shell started')
543
544         # Start a process to read from the output fifo of the DumpRenderTree app and print to stdout.
545         _log.debug('Redirecting stdout to ' + self._out_fifo_path)
546         self._read_fifo_proc = executive.popen(shell_cmd + ['cat', self._out_fifo_path],
547                                                stdout=executive.PIPE, universal_newlines=True)
548
549         _log.debug('Redirecting stdin to ' + self._in_fifo_path)
550         (line, crash) = self._write_command_and_read_line('cat >%s\n' % self._in_fifo_path)
551
552         # Combine the two unidirectional pipes into one bidirectional pipe to make _write_command_and_read_line() etc
553         # work with self._proc.
554         self._proc.stdout.close()
555         self._proc.stdout = self._read_fifo_proc.stdout
556
557         # Start a thread to kill the pipe reading/writing processes on deadlock of the fifos during startup.
558         normal_startup_event = threading.Event()
559         threading.Thread(target=self._deadlock_detector,
560                          args=([self._proc.pid, self._read_fifo_proc.pid], normal_startup_event)).start()
561
562         output = ''
563         while not crash and line.rstrip() != '#READY':
564             output += line
565             (line, crash) = self._write_command_and_read_line()
566
567         if crash:
568             # DumpRenderTree crashes during startup, or when the deadlock detector detected
569             # deadlock and killed the fifo reading/writing processes.
570             _log.error('Failed to start DumpRenderTree: \n%s\nLog:\n%s' % (output, self._port._get_logcat()))
571             self.stop()
572             raise AssertionError('Failed to start DumpRenderTree application')
573         else:
574             # Inform the deadlock detector that the startup is successful without deadlock.
575             normal_startup_event.set()
576             return True
577
578     def run_test(self, driver_input):
579         driver_output = chromium.ChromiumDriver.run_test(self, driver_input)
580         if driver_output.crash:
581             # When Android is OOM, DRT process may be killed by ActivityManager or system OOM.
582             # It looks like a crash but there is no fatal signal logged. Re-run the test for
583             # such crash.
584             # To test: adb shell am force-stop org.chromium.native_test,
585             # or kill -11 pid twice or three times to simulate a fatal crash.
586             if self._port._get_logcat().find('Fatal signal') == -1:
587                 self._restart_after_killed = True
588                 self._port._drt_retry_after_killed += 1
589                 if self._port._drt_retry_after_killed > 10:
590                     raise AssertionError('DumpRenderTree is killed by Android for too many times!')
591                 _log.error('DumpRenderTree is killed by system (%d).' % self._port._drt_retry_after_killed)
592                 self.stop()
593                 # Sleep 10 seconds to let system recover.
594                 time.sleep(10)
595                 return self.run_test(driver_input)
596
597         self._restart_after_killed = False
598         driver_output.error += self._get_stderr()
599         return driver_output
600
601     def stop(self):
602         if ChromiumAndroidDriver._started_driver != self:
603             return
604         ChromiumAndroidDriver._started_driver = None
605
606         self._port._run_adb_command(['shell', 'am', 'force-stop', DRT_APP_PACKAGE])
607
608         if self._read_fifo_proc:
609             self._port._executive.kill_process(self._read_fifo_proc.pid)
610             self._read_fifo_proc = None
611
612         # Here duplicate some logic in ChromiumDriver.stop() instead of directly calling it,
613         # because our pipe reading/writing processes won't quit by itself on close of the pipes.
614         if self._proc:
615             self._proc.stdin.close()
616             self._proc.stdout.close()
617             if self._proc.stderr:
618                 self._proc.stderr.close()
619             self._port._executive.kill_process(self._proc.pid)
620             if self._proc.poll() is not None:
621                 self._proc.wait()
622             self._proc = None
623
624         seconds = 0
625         while (self._file_exists_on_device(self._in_fifo_path) or
626                self._file_exists_on_device(self._out_fifo_path) or
627                self._file_exists_on_device(self._err_file_path)):
628             time.sleep(1)
629             self._port._run_adb_command(['shell', 'rm', self._in_fifo_path, self._out_fifo_path, self._err_file_path])
630             seconds += 1
631             if seconds >= DRT_START_STOP_TIMEOUT_SECS:
632                 raise AssertionError('Failed to remove fifo files. May be locked.')
633
634     def _test_shell_command(self, uri, timeout_ms, checksum):
635         if uri.startswith('file:///'):
636             # Convert the host uri to a device uri. See comment of
637             # DEVICE_LAYOUT_TESTS_DIR for details.
638             # Not overriding Port.filename_to_uri() because we don't want the
639             # links in the html report point to device paths.
640             uri = FILE_TEST_URI_PREFIX + self.uri_to_test(uri)
641         return chromium.ChromiumDriver._test_shell_command(self, uri, timeout_ms, checksum)
642
643     def _write_command_and_read_line(self, input=None):
644         (line, crash) = chromium.ChromiumDriver._write_command_and_read_line(self, input)
645         url_marker = '#URL:'
646         if not crash:
647             if line.startswith(url_marker) and line.find(FILE_TEST_URI_PREFIX) == len(url_marker):
648                 # Convert the device test uri back to host uri otherwise
649                 # chromium.ChromiumDriver.run_test() will complain.
650                 line = '#URL:file://%s/%s' % (self._port.layout_tests_dir(), line[len(url_marker) + len(FILE_TEST_URI_PREFIX):])
651             # chromium.py uses "line == '' and self._proc.poll() is not None" to detect crash,
652             # but on Android "not line" is enough because self._proc.poll() seems not reliable.
653             if not line:
654                 crash = True
655         return (line, crash)
656
657     def _output_image(self):
658         if self._image_path:
659             _log.debug('Pulling from device: %s to %s' % (self._device_image_path, self._image_path))
660             self._port._pull_from_device(self._device_image_path, self._image_path, ignore_error=True)
661         return chromium.ChromiumDriver._output_image(self)
662
663     def _get_stderr(self):
664         return self._port._run_adb_command(['shell', 'cat', self._err_file_path], ignore_error=True)
665
666     def _read_prompt(self):
667         last_char = ''
668         while True:
669             current_char = self._proc.stdout.read(1)
670             if current_char == ' ':
671                 if last_char == '#':
672                     return
673                 if last_char == '$':
674                     raise AssertionError('Adbd is not running as root')
675             last_char = current_char