1#!/usr/bin/env python
2# Copyright (C) 2010 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"""Chromium Mac implementation of the Port interface."""
31
32import logging
33import os
34import signal
35
36from webkitpy.layout_tests.port import mac
37from webkitpy.layout_tests.port import chromium
38
39from webkitpy.common.system.executive import Executive
40
41_log = logging.getLogger("webkitpy.layout_tests.port.chromium_mac")
42
43
44class ChromiumMacPort(chromium.ChromiumPort):
45    """Chromium Mac implementation of the Port class."""
46    SUPPORTED_OS_VERSIONS = ('leopard', 'snowleopard', 'future')
47
48    FALLBACK_PATHS = {
49        'leopard': [
50            'chromium-mac-leopard',
51            'chromium-mac',
52            'chromium',
53            'mac-leopard',
54            'mac-snowleopard',
55            'mac',
56        ],
57        'snowleopard': [
58            'chromium-mac',
59            'chromium',
60            'mac-snowleopard',
61            'mac',
62        ],
63        'future': [
64            'chromium-mac',
65            'chromium',
66            'mac',
67        ],
68    }
69
70    def __init__(self, port_name=None, os_version_string=None, **kwargs):
71        # We're a little generic here because this code is reused by the
72        # 'google-chrome' port as well as the 'mock-' and 'dryrun-' ports.
73        port_name = port_name or 'chromium-mac'
74        chromium.ChromiumPort.__init__(self, port_name=port_name, **kwargs)
75        if port_name.endswith('-mac'):
76            self._version = mac.os_version(os_version_string, self.SUPPORTED_OS_VERSIONS)
77            self._name = port_name + '-' + self._version
78        else:
79            self._version = port_name[port_name.index('-mac-') + 5:]
80            assert self._version in self.SUPPORTED_OS_VERSIONS
81        self._operating_system = 'mac'
82
83    def baseline_path(self):
84        if self.version() in ('snowleopard', 'future'):
85            # We treat Snow Leopard as the newest version of mac,
86            # so it gets the base dir.
87            return self._webkit_baseline_path('chromium-mac')
88        return self._webkit_baseline_path(self.name())
89
90    def baseline_search_path(self):
91        return map(self._webkit_baseline_path, self.FALLBACK_PATHS[self._version])
92
93    def check_build(self, needs_http):
94        result = chromium.ChromiumPort.check_build(self, needs_http)
95        result = self._check_wdiff_install() and result
96        if not result:
97            _log.error('For complete Mac build requirements, please see:')
98            _log.error('')
99            _log.error('    http://code.google.com/p/chromium/wiki/'
100                       'MacBuildInstructions')
101        return result
102
103    def default_child_processes(self):
104        if not self._multiprocessing_is_available:
105            # Running multiple threads in Mac Python is unstable (See
106            # https://bugs.webkit.org/show_bug.cgi?id=38553 for more info).
107            return 1
108        return chromium.ChromiumPort.default_child_processes(self)
109
110    def driver_name(self):
111        return "DumpRenderTree"
112
113    #
114    # PROTECTED METHODS
115    #
116
117    def _build_path(self, *comps):
118        if self.get_option('build_directory'):
119            return self._filesystem.join(self.get_option('build_directory'),
120                                         *comps)
121
122        path = self.path_from_chromium_base('xcodebuild', *comps)
123        if self._filesystem.exists(path):
124            return path
125        return self.path_from_webkit_base(
126            'Source', 'WebKit', 'chromium', 'xcodebuild', *comps)
127
128    def _check_wdiff_install(self):
129        try:
130            # We're ignoring the return and always returning True
131            self._executive.run_command([self._path_to_wdiff()], error_handler=Executive.ignore_error)
132        except OSError:
133            _log.warning('wdiff not found. Install using MacPorts or some '
134                         'other means')
135        return True
136
137    def _lighttpd_path(self, *comps):
138        return self.path_from_chromium_base('third_party', 'lighttpd',
139                                            'mac', *comps)
140
141    def _path_to_apache(self):
142        return '/usr/sbin/httpd'
143
144    def _path_to_apache_config_file(self):
145        return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf',
146                                     'apache2-httpd.conf')
147
148    def _path_to_lighttpd(self):
149        return self._lighttpd_path('bin', 'lighttpd')
150
151    def _path_to_lighttpd_modules(self):
152        return self._lighttpd_path('lib')
153
154    def _path_to_lighttpd_php(self):
155        return self._lighttpd_path('bin', 'php-cgi')
156
157    def _path_to_driver(self, configuration=None):
158        # FIXME: make |configuration| happy with case-sensitive file
159        # systems.
160        if not configuration:
161            configuration = self.get_option('configuration')
162        return self._build_path(configuration, self.driver_name() + '.app',
163            'Contents', 'MacOS', self.driver_name())
164
165    def _path_to_helper(self):
166        binary_name = 'LayoutTestHelper'
167        return self._build_path(self.get_option('configuration'), binary_name)
168
169    def _path_to_wdiff(self):
170        return 'wdiff'
171
172    def _shut_down_http_server(self, server_pid):
173        """Shut down the lighttpd web server. Blocks until it's fully
174        shut down.
175
176        Args:
177            server_pid: The process ID of the running server.
178        """
179        # server_pid is not set when "http_server.py stop" is run manually.
180        if server_pid is None:
181            # TODO(mmoss) This isn't ideal, since it could conflict with
182            # lighttpd processes not started by http_server.py,
183            # but good enough for now.
184            self._executive.kill_all('lighttpd')
185            self._executive.kill_all('httpd')
186        else:
187            try:
188                os.kill(server_pid, signal.SIGTERM)
189                # TODO(mmoss) Maybe throw in a SIGKILL just to be sure?
190            except OSError:
191                # Sometimes we get a bad PID (e.g. from a stale httpd.pid
192                # file), so if kill fails on the given PID, just try to
193                # 'killall' web servers.
194                self._shut_down_http_server(None)
195