webkitpy: Refactor port code for devices
[WebKit-https.git] / Tools / Scripts / webkitpy / port / device_port.py
1 # Copyright (C) 2018 Apple Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
13 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
16 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
20 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 import logging
24 import traceback
25
26 from webkitpy.common.memoized import memoized
27 from webkitpy.layout_tests.models.test_configuration import TestConfiguration
28 from webkitpy.port.darwin import DarwinPort
29 from webkitpy.port.simulator_process import SimulatorProcess
30
31
32 _log = logging.getLogger(__name__)
33
34
35 class DevicePort(DarwinPort):
36
37     DEVICE_MANAGER = None
38
39     def __init__(self, *args, **kwargs):
40         super(DevicePort, self).__init__(*args, **kwargs)
41         self._test_runner_process_constructor = SimulatorProcess
42         self._printing_cmd_line = False
43
44     def driver_cmd_line_for_logging(self):
45         # Avoid creating/connecting to devices just for command line logging.
46         self._printing_cmd_line = True
47         result = super(DevicePort, self).driver_cmd_line_for_logging()
48         self._printing_cmd_line = False
49         return result
50
51     def _generate_all_test_configurations(self):
52         configurations = []
53         for build_type in self.ALL_BUILD_TYPES:
54             for architecture in self.ARCHITECTURES:
55                 configurations.append(TestConfiguration(version=self.version_name(), architecture=architecture, build_type=build_type))
56         return configurations
57
58     @memoized
59     def child_processes(self):
60         return int(self.get_option('child_processes'))
61
62     def driver_name(self):
63         if self.get_option('driver_name'):
64             return self.get_option('driver_name')
65         if self.get_option('webkit_test_runner'):
66             return 'WebKitTestRunnerApp.app'
67         return 'DumpRenderTree.app'
68
69     # A device is the target host for a specific worker number.
70     def target_host(self, worker_number=None):
71         if self._printing_cmd_line or worker_number is None:
72             return self.host
73         if self.DEVICE_MANAGER is None:
74             raise RuntimeError('No device manager for specified port')
75         if self.DEVICE_MANAGER.INITIALIZED_DEVICES is None:
76             raise RuntimeError('No initialized devices for testing')
77         return self.DEVICE_MANAGER.INITIALIZED_DEVICES[worker_number]
78
79     def devices(self):
80         if self.DEVICE_MANAGER is None:
81             return []
82         if self.DEVICE_MANAGER.INITIALIZED_DEVICES is None:
83             return []
84         return self.DEVICE_MANAGER.INITIALIZED_DEVICES
85
86     def _create_devices(self, device_class):
87         raise NotImplementedError
88
89     # Despite their names, these flags do not actually get passed all the way down to webkit-build.
90     def _build_driver_flags(self):
91         return ['--sdk', self.SDK] + (['ARCHS=%s' % self.architecture()] if self.architecture() else [])
92
93     def _install(self):
94         if not self.get_option('install'):
95             _log.debug('Skipping installation')
96             return
97
98         for i in xrange(self.child_processes()):
99             device = self.target_host(i)
100             _log.debug('Installing to {}'.format(device))
101             # Without passing DYLD_LIBRARY_PATH, libWebCoreTestSupport cannot be loaded and DRT/WKTR will crash pre-launch,
102             # leaving a crash log which will be picked up in results. DYLD_FRAMEWORK_PATH is needed to prevent an early crash.
103             if not device.install_app(self._path_to_driver(), {'DYLD_LIBRARY_PATH': self._build_path(), 'DYLD_FRAMEWORK_PATH': self._build_path()}):
104                 raise RuntimeError('Failed to install app {} on device {}'.format(self._path_to_driver(), device.udid))
105             if not device.install_dylibs(self._build_path()):
106                 raise RuntimeError('Failed to install dylibs at {} on device {}'.format(self._build_path(), device.udid))
107
108     def setup_test_run(self, device_class=None):
109         self._create_devices(device_class)
110         self._install()
111
112         for i in xrange(self.child_processes()):
113             host = self.target_host(i)
114             host.prepare_for_testing(
115                 self.ports_to_forward(),
116                 self.app_identifier_from_bundle(self._path_to_driver()),
117                 self.layout_tests_dir(),
118             )
119             self._crash_logs_to_skip_for_host[host] = host.filesystem.files_under(self.path_to_crash_logs())
120
121     def clean_up_test_run(self):
122         super(DevicePort, self).clean_up_test_run()
123
124         # Best effort to let every device teardown before throwing any exceptions here.
125         # Failure to teardown devices can leave things in a bad state.
126         exception_list = []
127         for i in xrange(self.child_processes()):
128             device = self.target_host(i)
129             if not device:
130                 continue
131             try:
132                 device.finished_testing()
133             except BaseException as e:
134                 trace = traceback.format_exc()
135                 if isinstance(e, Exception):
136                     exception_list.append([e, trace])
137                 else:
138                     exception_list.append([Exception('Exception while tearing down {}'.format(device)), trace])
139
140         if len(exception_list) == 1:
141             raise
142         if len(exception_list) > 1:
143             print('\n')
144             for exception in exception_list:
145                 _log.error('{} raised: {}'.format(exception[0].__class__.__name__, exception[0]))
146                 _log.error(exception[1])
147                 _log.error('--------------------------------------------------')
148
149             raise RuntimeError('Multiple failures when teardown devices')
150
151     def did_spawn_worker(self, worker_number):
152         super(DevicePort, self).did_spawn_worker(worker_number)
153
154         self.target_host(worker_number).release_worker_resources()
155
156     def setup_environ_for_server(self, server_name=None):
157         env = super(DevicePort, self).setup_environ_for_server(server_name)
158         if server_name == self.driver_name() and self.get_option('guard_malloc'):
159             self._append_value_colon_separated(env, 'DYLD_INSERT_LIBRARIES', '/usr/lib/libgmalloc.dylib')
160             self._append_value_colon_separated(env, '__XPC_DYLD_INSERT_LIBRARIES', '/usr/lib/libgmalloc.dylib')
161         env['XML_CATALOG_FILES'] = ''  # work around missing /etc/catalog <rdar://problem/4292995>
162         return env