Some perf tests time out when ran by run-perf-tests
[WebKit-https.git] / Tools / Scripts / webkitpy / layout_tests / port / base.py
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 Google name 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 """Abstract base class of Port-specific entrypoints for the layout tests
31 test infrastructure (the Port and Driver classes)."""
32
33 from __future__ import with_statement
34
35 import cgi
36 import difflib
37 import errno
38 import os
39 import re
40
41 from webkitpy.common.memoized import memoized
42 from webkitpy.common.system import path
43
44
45 # Handle Python < 2.6 where multiprocessing isn't available.
46 try:
47     import multiprocessing
48 except ImportError:
49     multiprocessing = None
50
51 from webkitpy.common import find_files
52 from webkitpy.common.system import logutils
53 from webkitpy.common.system.executive import ScriptError
54 from webkitpy.common.system.systemhost import SystemHost
55 from webkitpy.layout_tests import read_checksum_from_png
56 from webkitpy.layout_tests.models.test_configuration import TestConfiguration
57 from webkitpy.layout_tests.port import config as port_config
58 from webkitpy.layout_tests.port import driver
59 from webkitpy.layout_tests.port import http_lock
60 from webkitpy.layout_tests.servers import apache_http_server
61 from webkitpy.layout_tests.servers import http_server
62 from webkitpy.layout_tests.servers import websocket_server
63
64 _log = logutils.get_logger(__file__)
65
66
67 class DummyOptions(object):
68     """Fake implementation of optparse.Values. Cloned from webkitpy.tool.mocktool.MockOptions."""
69
70     def __init__(self, *args, **kwargs):
71         # The caller can set option values using keyword arguments. We don't
72         # set any values by default because we don't know how this
73         # object will be used. Generally speaking unit tests should
74         # subclass this or provider wrapper functions that set a common
75         # set of options.
76         for key, value in kwargs.items():
77             self.__dict__[key] = value
78
79
80 # FIXME: This class should merge with WebKitPort now that Chromium behaves mostly like other webkit ports.
81 class Port(object):
82     """Abstract class for Port-specific hooks for the layout_test package."""
83
84     # Subclasses override this. This should indicate the basic implementation
85     # part of the port name, e.g., 'chromium-mac', 'win', 'gtk'; there is probably (?)
86     # one unique value per class.
87
88     # FIXME: We should probably rename this to something like 'implementation_name'.
89     port_name = None
90
91     # Test names resemble unix relative paths, and use '/' as a directory separator.
92     TEST_PATH_SEPARATOR = '/'
93
94     ALL_BUILD_TYPES = ('debug', 'release')
95
96     @classmethod
97     def determine_full_port_name(cls, host, options, port_name):
98         """Return a fully-specified port name that can be used to construct objects."""
99         # Subclasses will usually override this.
100         return cls.port_name
101
102     def __init__(self, host, port_name=None, options=None, config=None, **kwargs):
103
104         # This value may be different from cls.port_name by having version modifiers
105         # and other fields appended to it (for example, 'qt-arm' or 'mac-wk2').
106
107         # FIXME: port_name should be a required parameter. It isn't yet because lots of tests need to be updatd.
108         self._name = port_name or self.port_name
109
110         # These are default values that should be overridden in a subclasses.
111         self._version = ''
112         self._architecture = 'x86'
113         self._graphics_type = 'cpu'
114
115         # FIXME: Ideally we'd have a package-wide way to get a
116         # well-formed options object that had all of the necessary
117         # options defined on it.
118         self._options = options or DummyOptions()
119
120         self.host = host
121         self._executive = host.executive
122         self._filesystem = host.filesystem
123         self._config = config or port_config.Config(self._executive, self._filesystem)
124
125         self._helper = None
126         self._http_server = None
127         self._websocket_server = None
128         self._http_lock = None  # FIXME: Why does this live on the port object?
129
130         # Python's Popen has a bug that causes any pipes opened to a
131         # process that can't be executed to be leaked.  Since this
132         # code is specifically designed to tolerate exec failures
133         # to gracefully handle cases where wdiff is not installed,
134         # the bug results in a massive file descriptor leak. As a
135         # workaround, if an exec failure is ever experienced for
136         # wdiff, assume it's not available.  This will leak one
137         # file descriptor but that's better than leaking each time
138         # wdiff would be run.
139         #
140         # http://mail.python.org/pipermail/python-list/
141         #    2008-August/505753.html
142         # http://bugs.python.org/issue3210
143         self._wdiff_available = None
144
145         # FIXME: prettypatch.py knows this path, why is it copied here?
146         self._pretty_patch_path = self.path_from_webkit_base("Websites", "bugs.webkit.org", "PrettyPatch", "prettify.rb")
147         self._pretty_patch_available = None
148
149         if not hasattr(options, 'configuration') or not options.configuration:
150             self.set_option_default('configuration', self.default_configuration())
151         self._test_configuration = None
152         self._reftest_list = {}
153         self._multiprocessing_is_available = (multiprocessing is not None)
154         self._results_directory = None
155
156     def wdiff_available(self):
157         if self._wdiff_available is None:
158             self._wdiff_available = self.check_wdiff(logging=False)
159         return self._wdiff_available
160
161     def pretty_patch_available(self):
162         if self._pretty_patch_available is None:
163             self._pretty_patch_available = self.check_pretty_patch(logging=False)
164         return self._pretty_patch_available
165
166     def default_child_processes(self):
167         """Return the number of DumpRenderTree instances to use for this port."""
168         cpu_count = self._executive.cpu_count()
169         # Make sure we have enough ram to support that many instances:
170         free_memory = self.host.platform.free_bytes_memory()
171         if free_memory:
172             bytes_per_drt = 200 * 1024 * 1024  # Assume each DRT needs 200MB to run.
173             supportable_instances = max(free_memory / bytes_per_drt, 1)  # Always use one process, even if we don't have space for it.
174             if supportable_instances < cpu_count:
175                 # FIXME: The Printer isn't initialized when this is called, so using _log would just show an unitialized logger error.
176                 print "This machine could support %s child processes, but only has enough memory for %s." % (cpu_count, supportable_instances)
177             return min(supportable_instances, cpu_count)
178         return cpu_count
179
180     def default_worker_model(self):
181         if self._multiprocessing_is_available:
182             return 'processes'
183         return 'inline'
184
185     def baseline_path(self):
186         """Return the absolute path to the directory to store new baselines in for this port."""
187         baseline_search_paths = self.get_option('additional_platform_directory', []) + self.baseline_search_path()
188         return baseline_search_paths[0]
189
190     def baseline_search_path(self):
191         """Return a list of absolute paths to directories to search under for
192         baselines. The directories are searched in order."""
193         raise NotImplementedError('Port.baseline_search_path')
194
195     def check_build(self, needs_http):
196         """This routine is used to ensure that the build is up to date
197         and all the needed binaries are present."""
198         raise NotImplementedError('Port.check_build')
199
200     def check_sys_deps(self, needs_http):
201         """If the port needs to do some runtime checks to ensure that the
202         tests can be run successfully, it should override this routine.
203         This step can be skipped with --nocheck-sys-deps.
204
205         Returns whether the system is properly configured."""
206         if needs_http:
207             return self.check_httpd()
208         return True
209
210     def check_image_diff(self, override_step=None, logging=True):
211         """This routine is used to check whether image_diff binary exists."""
212         raise NotImplementedError('Port.check_image_diff')
213
214     def check_pretty_patch(self, logging=True):
215         """Checks whether we can use the PrettyPatch ruby script."""
216         try:
217             _ = self._executive.run_command(['ruby', '--version'])
218         except OSError, e:
219             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
220                 if logging:
221                     _log.error("Ruby is not installed; can't generate pretty patches.")
222                     _log.error('')
223                 return False
224
225         if not self._filesystem.exists(self._pretty_patch_path):
226             if logging:
227                 _log.error("Unable to find %s; can't generate pretty patches." % self._pretty_patch_path)
228                 _log.error('')
229             return False
230
231         return True
232
233     def check_wdiff(self, logging=True):
234         if not self._path_to_wdiff():
235             # Don't need to log here since this is the port choosing not to use wdiff.
236             return False
237
238         try:
239             _ = self._executive.run_command([self._path_to_wdiff(), '--help'])
240         except OSError:
241             if logging:
242                 _log.error("wdiff is not installed.")
243             return False
244
245         return True
246
247     def check_httpd(self):
248         if self._uses_apache():
249             httpd_path = self._path_to_apache()
250         else:
251             httpd_path = self._path_to_lighttpd()
252
253         try:
254             server_name = self._filesystem.basename(httpd_path)
255             env = self.setup_environ_for_server(server_name)
256             if self._executive.run_command([httpd_path, "-v"], env=env, return_exit_code=True) != 0:
257                 _log.error("httpd seems broken. Cannot run http tests.")
258                 return False
259             return True
260         except OSError:
261             _log.error("No httpd found. Cannot run http tests.")
262             return False
263
264     def compare_text(self, expected_text, actual_text):
265         """Return whether or not the two strings are *not* equal. This
266         routine is used to diff text output.
267
268         While this is a generic routine, we include it in the Port
269         interface so that it can be overriden for testing purposes."""
270         return expected_text != actual_text
271
272     def compare_audio(self, expected_audio, actual_audio):
273         # FIXME: If we give this method a better name it won't need this docstring (e.g. are_audio_results_equal()).
274         """Return whether the two audio files are *not* equal."""
275         return expected_audio != actual_audio
276
277     def diff_image(self, expected_contents, actual_contents, tolerance=None):
278         """Compare two images and return a tuple of an image diff, and a percentage difference (0-100).
279
280         |tolerance| should be a percentage value (0.0 - 100.0).
281         If it is omitted, the port default tolerance value is used.
282         """
283         raise NotImplementedError('Port.diff_image')
284
285
286     def diff_text(self, expected_text, actual_text,
287                   expected_filename, actual_filename):
288         """Returns a string containing the diff of the two text strings
289         in 'unified diff' format.
290
291         While this is a generic routine, we include it in the Port
292         interface so that it can be overriden for testing purposes."""
293
294         # The filenames show up in the diff output, make sure they're
295         # raw bytes and not unicode, so that they don't trigger join()
296         # trying to decode the input.
297         def to_raw_bytes(string_value):
298             if isinstance(string_value, unicode):
299                 return string_value.encode('utf-8')
300             return string_value
301         expected_filename = to_raw_bytes(expected_filename)
302         actual_filename = to_raw_bytes(actual_filename)
303         diff = difflib.unified_diff(expected_text.splitlines(True),
304                                     actual_text.splitlines(True),
305                                     expected_filename,
306                                     actual_filename)
307         return ''.join(diff)
308
309     def is_crash_reporter(self, process_name):
310         return False
311
312     def check_for_leaks(self, process_name, process_pid):
313         # Subclasses should check for leaks in the running process
314         # and print any necessary warnings if leaks are found.
315         # FIXME: We should consider moving much of this logic into
316         # Executive and make it platform-specific instead of port-specific.
317         pass
318
319     def print_leaks_summary(self):
320         # Subclasses can override this to print a summary of leaks found
321         # while running the layout tests.
322         pass
323
324     def driver_name(self):
325         """Returns the name of the actual binary that is performing the test,
326         so that it can be referred to in log messages. In most cases this
327         will be DumpRenderTree, but if a port uses a binary with a different
328         name, it can be overridden here."""
329         return "DumpRenderTree"
330
331     def expected_baselines(self, test_name, suffix, all_baselines=False):
332         """Given a test name, finds where the baseline results are located.
333
334         Args:
335         test_name: name of test file (usually a relative path under LayoutTests/)
336         suffix: file suffix of the expected results, including dot; e.g.
337             '.txt' or '.png'.  This should not be None, but may be an empty
338             string.
339         all_baselines: If True, return an ordered list of all baseline paths
340             for the given platform. If False, return only the first one.
341         Returns
342         a list of ( platform_dir, results_filename ), where
343             platform_dir - abs path to the top of the results tree (or test
344                 tree)
345             results_filename - relative path from top of tree to the results
346                 file
347             (port.join() of the two gives you the full path to the file,
348                 unless None was returned.)
349         Return values will be in the format appropriate for the current
350         platform (e.g., "\\" for path separators on Windows). If the results
351         file is not found, then None will be returned for the directory,
352         but the expected relative pathname will still be returned.
353
354         This routine is generic but lives here since it is used in
355         conjunction with the other baseline and filename routines that are
356         platform specific.
357         """
358         baseline_filename = self._filesystem.splitext(test_name)[0] + '-expected' + suffix
359         baseline_search_path = self.get_option('additional_platform_directory', []) + self.baseline_search_path()
360
361         baselines = []
362         for platform_dir in baseline_search_path:
363             if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
364                 baselines.append((platform_dir, baseline_filename))
365
366             if not all_baselines and baselines:
367                 return baselines
368
369         # If it wasn't found in a platform directory, return the expected
370         # result in the test directory, even if no such file actually exists.
371         platform_dir = self.layout_tests_dir()
372         if self._filesystem.exists(self._filesystem.join(platform_dir, baseline_filename)):
373             baselines.append((platform_dir, baseline_filename))
374
375         if baselines:
376             return baselines
377
378         return [(None, baseline_filename)]
379
380     def expected_filename(self, test_name, suffix):
381         """Given a test name, returns an absolute path to its expected results.
382
383         If no expected results are found in any of the searched directories,
384         the directory in which the test itself is located will be returned.
385         The return value is in the format appropriate for the platform
386         (e.g., "\\" for path separators on windows).
387
388         Args:
389         test_name: name of test file (usually a relative path under LayoutTests/)
390         suffix: file suffix of the expected results, including dot; e.g. '.txt'
391             or '.png'.  This should not be None, but may be an empty string.
392         platform: the most-specific directory name to use to build the
393             search list of directories, e.g., 'chromium-win', or
394             'chromium-cg-mac-leopard' (we follow the WebKit format)
395
396         This routine is generic but is implemented here to live alongside
397         the other baseline and filename manipulation routines.
398         """
399         # FIXME: The [0] here is very mysterious, as is the destructured return.
400         platform_dir, baseline_filename = self.expected_baselines(test_name, suffix)[0]
401         if platform_dir:
402             return self._filesystem.join(platform_dir, baseline_filename)
403         return self._filesystem.join(self.layout_tests_dir(), baseline_filename)
404
405     def expected_checksum(self, test_name):
406         """Returns the checksum of the image we expect the test to produce, or None if it is a text-only test."""
407         png_path = self.expected_filename(test_name, '.png')
408
409         if self._filesystem.exists(png_path):
410             with self._filesystem.open_binary_file_for_reading(png_path) as filehandle:
411                 return read_checksum_from_png.read_checksum(filehandle)
412
413         return None
414
415     def expected_image(self, test_name):
416         """Returns the image we expect the test to produce."""
417         baseline_path = self.expected_filename(test_name, '.png')
418         if not self._filesystem.exists(baseline_path):
419             return None
420         return self._filesystem.read_binary_file(baseline_path)
421
422     def expected_audio(self, test_name):
423         baseline_path = self.expected_filename(test_name, '.wav')
424         if not self._filesystem.exists(baseline_path):
425             return None
426         return self._filesystem.read_binary_file(baseline_path)
427
428     def expected_text(self, test_name):
429         """Returns the text output we expect the test to produce, or None
430         if we don't expect there to be any text output.
431         End-of-line characters are normalized to '\n'."""
432         # FIXME: DRT output is actually utf-8, but since we don't decode the
433         # output from DRT (instead treating it as a binary string), we read the
434         # baselines as a binary string, too.
435         baseline_path = self.expected_filename(test_name, '.txt')
436         if not self._filesystem.exists(baseline_path):
437             baseline_path = self.expected_filename(test_name, '.webarchive')
438             if not self._filesystem.exists(baseline_path):
439                 return None
440         text = self._filesystem.read_binary_file(baseline_path)
441         return text.replace("\r\n", "\n")
442
443     def _get_reftest_list(self, test_name):
444         dirname = self._filesystem.join(self.layout_tests_dir(), self._filesystem.dirname(test_name))
445         if dirname not in self._reftest_list:
446             self._reftest_list[dirname] = Port._parse_reftest_list(self._filesystem, dirname)
447         return self._reftest_list[dirname]
448
449     @staticmethod
450     def _parse_reftest_list(filesystem, test_dirpath):
451         reftest_list_path = filesystem.join(test_dirpath, 'reftest.list')
452         if not filesystem.isfile(reftest_list_path):
453             return None
454         reftest_list_file = filesystem.read_text_file(reftest_list_path)
455
456         parsed_list = {}
457         for line in reftest_list_file.split('\n'):
458             line = re.sub('#.+$', '', line)
459             split_line = line.split()
460             if len(split_line) < 3:
461                 continue
462             expectation_type, test_file, ref_file = split_line
463             parsed_list.setdefault(filesystem.join(test_dirpath, test_file), []).append((expectation_type, filesystem.join(test_dirpath, ref_file)))
464         return parsed_list
465
466     def reference_files(self, test_name):
467         """Return a list of expectation (== or !=) and filename pairs"""
468
469         reftest_list = self._get_reftest_list(test_name)
470         if not reftest_list:
471             expected_filenames = [('==', self.expected_filename(test_name, '.html')), ('!=', self.expected_filename(test_name, '-mismatch.html'))]
472             return [(expectation, filename) for expectation, filename in expected_filenames if self._filesystem.exists(filename)]
473
474         return reftest_list.get(self._filesystem.join(self.layout_tests_dir(), test_name), [])
475
476     def is_reftest(self, test_name):
477         reftest_list = self._get_reftest_list(test_name)
478         if not reftest_list:
479             has_expected = self._filesystem.exists(self.expected_filename(test_name, '.html'))
480             return has_expected or self._filesystem.exists(self.expected_filename(test_name, '-mismatch.html'))
481         filename = self._filesystem.join(self.layout_tests_dir(), test_name)
482         return filename in reftest_list
483
484     def tests(self, paths):
485         """Return the list of tests found."""
486         # When collecting test cases, skip these directories
487         skipped_directories = set(['.svn', '_svn', 'resources', 'script-tests', 'reference', 'reftest'])
488         files = find_files.find(self._filesystem, self.layout_tests_dir(), paths, skipped_directories, Port._is_test_file)
489         return set([self.relative_test_filename(f) for f in files])
490
491     # When collecting test cases, we include any file with these extensions.
492     _supported_file_extensions = set(['.html', '.shtml', '.xml', '.xhtml', '.pl',
493                                       '.htm', '.php', '.svg', '.mht'])
494
495     @staticmethod
496     def is_reference_html_file(filesystem, dirname, filename):
497         if filename.startswith('ref-') or filename.endswith('notref-'):
498             return True
499         filename_wihout_ext, unused = filesystem.splitext(filename)
500         for suffix in ['-expected', '-expected-mismatch', '-ref', '-notref']:
501             if filename_wihout_ext.endswith(suffix):
502                 return True
503         return False
504
505     @staticmethod
506     def _has_supported_extension(filesystem, filename):
507         """Return true if filename is one of the file extensions we want to run a test on."""
508         extension = filesystem.splitext(filename)[1]
509         return extension in Port._supported_file_extensions
510
511     @staticmethod
512     def _is_test_file(filesystem, dirname, filename):
513         return Port._has_supported_extension(filesystem, filename) and not Port.is_reference_html_file(filesystem, dirname, filename)
514
515     def test_dirs(self):
516         """Returns the list of top-level test directories."""
517         layout_tests_dir = self.layout_tests_dir()
518         return filter(lambda x: self._filesystem.isdir(self._filesystem.join(layout_tests_dir, x)),
519                       self._filesystem.listdir(layout_tests_dir))
520
521     def test_isdir(self, test_name):
522         """Return True if the test name refers to a directory of tests."""
523         # Used by test_expectations.py to apply rules to whole directories.
524         test_path = self.abspath_for_test(test_name)
525         return self._filesystem.isdir(test_path)
526
527     def test_exists(self, test_name):
528         """Return True if the test name refers to an existing test or baseline."""
529         # Used by test_expectations.py to determine if an entry refers to a
530         # valid test and by printing.py to determine if baselines exist.
531         test_path = self.abspath_for_test(test_name)
532         return self._filesystem.exists(test_path)
533
534     def split_test(self, test_name):
535         """Splits a test name into the 'directory' part and the 'basename' part."""
536         index = test_name.rfind(self.TEST_PATH_SEPARATOR)
537         if index < 1:
538             return ('', test_name)
539         return (test_name[0:index], test_name[index:])
540
541     def normalize_test_name(self, test_name):
542         """Returns a normalized version of the test name or test directory."""
543         if self.test_isdir(test_name) and not test_name.endswith('/'):
544             return test_name + '/'
545         return test_name
546
547     def driver_cmd_line(self):
548         """Prints the DRT command line that will be used."""
549         driver = self.create_driver(0)
550         return driver.cmd_line()
551
552     def update_baseline(self, baseline_path, data):
553         """Updates the baseline for a test.
554
555         Args:
556             baseline_path: the actual path to use for baseline, not the path to
557               the test. This function is used to update either generic or
558               platform-specific baselines, but we can't infer which here.
559             data: contents of the baseline.
560         """
561         self._filesystem.write_binary_file(baseline_path, data)
562
563     def layout_tests_dir(self):
564         """Return the absolute path to the top of the LayoutTests directory."""
565         return self.path_from_webkit_base('LayoutTests')
566
567     def perf_tests_dir(self):
568         """Return the absolute path to the top of the PerformanceTests directory."""
569         return self.path_from_webkit_base('PerformanceTests')
570
571     def webkit_base(self):
572         return self._filesystem.abspath(self.path_from_webkit_base('.'))
573
574     def skipped_layout_tests(self):
575         return []
576
577     def skipped_perf_tests(self):
578         return []
579
580     def skipped_tests(self):
581         return []
582
583     def skips_layout_test(self, test_name):
584         """Figures out if the givent test is being skipped or not.
585
586         Test categories are handled as well."""
587         for test_or_category in self.skipped_layout_tests():
588             if test_or_category == test_name:
589                 return True
590             category = self._filesystem.join(self.layout_tests_dir(), test_or_category)
591             if self._filesystem.isdir(category) and test_name.startswith(test_or_category):
592                 return True
593         return False
594
595     def skips_perf_test(self, test_name):
596         for test_or_category in self.skipped_perf_tests():
597             if test_or_category == test_name:
598                 return True
599             category = self._filesystem.join(self.perf_tests_dir(), test_or_category)
600             if self._filesystem.isdir(category) and test_name.startswith(test_or_category):
601                 return True
602         return False
603
604     def maybe_make_directory(self, *comps):
605         """Creates the specified directory if it doesn't already exist."""
606         self._filesystem.maybe_make_directory(*comps)
607
608     def name(self):
609         """Returns a name that uniquely identifies this particular type of port
610         (e.g., "mac-snowleopard" or "chromium-gpu-linux-x86_x64" and can be passed
611         to factory.get() to instantiate the port."""
612         return self._name
613
614     def real_name(self):
615         # FIXME: Seems this is only used for MockDRT and should be removed.
616         """Returns the name of the port as passed to the --platform command line argument."""
617         return self.name()
618
619     def operating_system(self):
620         # Subclasses should override this default implementation.
621         return 'mac'
622
623     def version(self):
624         """Returns a string indicating the version of a given platform, e.g.
625         'leopard' or 'xp'.
626
627         This is used to help identify the exact port when parsing test
628         expectations, determining search paths, and logging information."""
629         return self._version
630
631     def graphics_type(self):
632         """Returns whether the port uses accelerated graphics ('gpu') or not ('cpu')."""
633         return self._graphics_type
634
635     def architecture(self):
636         return self._architecture
637
638     def get_option(self, name, default_value=None):
639         # FIXME: Eventually we should not have to do a test for
640         # hasattr(), and we should be able to just do
641         # self.options.value. See additional FIXME in the constructor.
642         if hasattr(self._options, name):
643             return getattr(self._options, name)
644         return default_value
645
646     def set_option_default(self, name, default_value):
647         # FIXME: Callers could also use optparse_parser.Values.ensure_value,
648         # since this should always be a optparse_parser.Values object.
649         if not hasattr(self._options, name) or getattr(self._options, name) is None:
650             return setattr(self._options, name, default_value)
651
652     def path_from_webkit_base(self, *comps):
653         """Returns the full path to path made by joining the top of the
654         WebKit source tree and the list of path components in |*comps|."""
655         return self._config.path_from_webkit_base(*comps)
656
657     def path_to_test_expectations_file(self):
658         """Update the test expectations to the passed-in string.
659
660         This is used by the rebaselining tool. Raises NotImplementedError
661         if the port does not use expectations files."""
662         raise NotImplementedError('Port.path_to_test_expectations_file')
663
664     def relative_test_filename(self, filename):
665         """Returns a test_name a realtive unix-style path for a filename under the LayoutTests
666         directory. Filenames outside the LayoutTests directory should raise
667         an error."""
668         # Ports that run on windows need to override this method to deal with
669         # filenames with backslashes in them.
670         assert filename.startswith(self.layout_tests_dir()), "%s did not start with %s" % (filename, self.layout_tests_dir())
671         return filename[len(self.layout_tests_dir()) + 1:]
672
673     def relative_perf_test_filename(self, filename):
674         assert filename.startswith(self.perf_tests_dir()), "%s did not start with %s" % (filename, self.perf_tests_dir())
675         return filename[len(self.perf_tests_dir()) + 1:]
676
677     def abspath_for_test(self, test_name):
678         """Returns the full path to the file for a given test name. This is the
679         inverse of relative_test_filename()."""
680         return self._filesystem.normpath(self._filesystem.join(self.layout_tests_dir(), test_name))
681
682     def results_directory(self):
683         """Absolute path to the place to store the test results (uses --results-directory)."""
684         if not self._results_directory:
685             option_val = self.get_option('results_directory') or self.default_results_directory()
686             self._results_directory = self._filesystem.abspath(option_val)
687         return self._results_directory
688
689     def default_results_directory(self):
690         """Absolute path to the default place to store the test results."""
691         raise NotImplementedError()
692
693     def setup_test_run(self):
694         """Perform port-specific work at the beginning of a test run."""
695         pass
696
697     # FIXME: os.environ access should be moved to onto a common/system class to be more easily mockable.
698     def _value_or_default_from_environ(self, name, default=None):
699         if name in os.environ:
700             return os.environ[name]
701         return default
702
703     def _copy_value_from_environ_if_set(self, clean_env, name):
704         if name in os.environ:
705             clean_env[name] = os.environ[name]
706
707     def setup_environ_for_server(self, server_name=None):
708         # We intentionally copy only a subset of os.environ when
709         # launching subprocesses to ensure consistent test results.
710         clean_env = {}
711         variables_to_copy = [
712             # For Linux:
713             'XAUTHORITY',
714             'HOME',
715             'LANG',
716             'LD_LIBRARY_PATH',
717             'DBUS_SESSION_BUS_ADDRESS',
718
719             # Darwin:
720             'DYLD_LIBRARY_PATH',
721             'HOME',
722
723             # CYGWIN:
724             'HOMEDRIVE',
725             'HOMEPATH',
726             '_NT_SYMBOL_PATH',
727
728             # Windows:
729             'PATH',
730         ]
731         for variable in variables_to_copy:
732             self._copy_value_from_environ_if_set(clean_env, variable)
733
734         # For Linux:
735         clean_env['DISPLAY'] = self._value_or_default_from_environ('DISPLAY', ':1')
736         return clean_env
737
738     def show_results_html_file(self, results_filename):
739         """This routine should display the HTML file pointed at by
740         results_filename in a users' browser."""
741         return self.host.user.open_url(path.abspath_to_uri(results_filename))
742
743     def create_driver(self, worker_number, no_timeout=False):
744         """Return a newly created Driver subclass for starting/stopping the test driver."""
745         return driver.DriverProxy(self, worker_number, self._driver_class(), pixel_tests=self.get_option('pixel_tests'), no_timeout=no_timeout)
746
747     def start_helper(self):
748         """If a port needs to reconfigure graphics settings or do other
749         things to ensure a known test configuration, it should override this
750         method."""
751         pass
752
753     def start_http_server(self):
754         """Start a web server. Raise an error if it can't start or is already running.
755
756         Ports can stub this out if they don't need a web server to be running."""
757         assert not self._http_server, 'Already running an http server.'
758
759         if self._uses_apache():
760             server = apache_http_server.LayoutTestApacheHttpd(self, self.results_directory())
761         else:
762             server = http_server.Lighttpd(self, self.results_directory())
763
764         server.start()
765         self._http_server = server
766
767     def start_websocket_server(self):
768         """Start a web server. Raise an error if it can't start or is already running.
769
770         Ports can stub this out if they don't need a websocket server to be running."""
771         assert not self._websocket_server, 'Already running a websocket server.'
772
773         server = websocket_server.PyWebSocket(self, self.results_directory())
774         server.start()
775         self._websocket_server = server
776
777     def acquire_http_lock(self):
778         self._http_lock = http_lock.HttpLock(None, filesystem=self._filesystem, executive=self._executive)
779         self._http_lock.wait_for_httpd_lock()
780
781     def stop_helper(self):
782         """Shut down the test helper if it is running. Do nothing if
783         it isn't, or it isn't available. If a port overrides start_helper()
784         it must override this routine as well."""
785         pass
786
787     def stop_http_server(self):
788         """Shut down the http server if it is running. Do nothing if it isn't."""
789         if self._http_server:
790             self._http_server.stop()
791             self._http_server = None
792
793     def stop_websocket_server(self):
794         """Shut down the websocket server if it is running. Do nothing if it isn't."""
795         if self._websocket_server:
796             self._websocket_server.stop()
797             self._websocket_server = None
798
799     def release_http_lock(self):
800         if self._http_lock:
801             self._http_lock.cleanup_http_lock()
802
803     def exit_code_from_summarized_results(self, unexpected_results):
804         """Given summarized results, compute the exit code to be returned by new-run-webkit-tests.
805         Bots turn red when this function returns a non-zero value. By default, return the number of regressions
806         to avoid turning bots red by flaky failures, unexpected passes, and missing results"""
807         # Don't turn bots red for flaky failures, unexpected passes, and missing results.
808         return unexpected_results['num_regressions']
809
810     #
811     # TEST EXPECTATION-RELATED METHODS
812     #
813
814     def test_configuration(self):
815         """Returns the current TestConfiguration for the port."""
816         if not self._test_configuration:
817             self._test_configuration = TestConfiguration(self._version, self._architecture, self._options.configuration.lower(), self._graphics_type)
818         return self._test_configuration
819
820     # FIXME: Belongs on a Platform object.
821     @memoized
822     def all_test_configurations(self):
823         """Returns a list of TestConfiguration instances, representing all available
824         test configurations for this port."""
825         return self._generate_all_test_configurations()
826
827     # FIXME: Belongs on a Platform object.
828     def configuration_specifier_macros(self):
829         """Ports may provide a way to abbreviate configuration specifiers to conveniently
830         refer to them as one term or alias specific values to more generic ones. For example:
831
832         (xp, vista, win7) -> win # Abbreviate all Windows versions into one namesake.
833         (lucid) -> linux  # Change specific name of the Linux distro to a more generic term.
834
835         Returns a dictionary, each key representing a macro term ('win', for example),
836         and value being a list of valid configuration specifiers (such as ['xp', 'vista', 'win7'])."""
837         return {}
838
839     def all_baseline_variants(self):
840         """Returns a list of platform names sufficient to cover all the baselines.
841
842         The list should be sorted so that a later platform  will reuse
843         an earlier platform's baselines if they are the same (e.g.,
844         'snowleopard' should precede 'leopard')."""
845         raise NotImplementedError
846
847     def uses_test_expectations_file(self):
848         # This is different from checking test_expectations() is None, because
849         # some ports have Skipped files which are returned as part of test_expectations().
850         return self._filesystem.exists(self.path_to_test_expectations_file())
851
852     def test_expectations(self):
853         """Returns the test expectations for this port.
854
855         Basically this string should contain the equivalent of a
856         test_expectations file. See test_expectations.py for more details."""
857         return self._filesystem.read_text_file(self.path_to_test_expectations_file())
858
859     def test_expectations_overrides(self):
860         """Returns an optional set of overrides for the test_expectations.
861
862         This is used by ports that have code in two repositories, and where
863         it is possible that you might need "downstream" expectations that
864         temporarily override the "upstream" expectations until the port can
865         sync up the two repos."""
866         return None
867
868     def test_repository_paths(self):
869         """Returns a list of (repository_name, repository_path) tuples
870         of its depending code base.  By default it returns a list that only
871         contains a ('webkit', <webkitRepossitoryPath>) tuple.
872         """
873         return [('webkit', self.layout_tests_dir())]
874
875
876     _WDIFF_DEL = '##WDIFF_DEL##'
877     _WDIFF_ADD = '##WDIFF_ADD##'
878     _WDIFF_END = '##WDIFF_END##'
879
880     def _format_wdiff_output_as_html(self, wdiff):
881         wdiff = cgi.escape(wdiff)
882         wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
883         wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
884         wdiff = wdiff.replace(self._WDIFF_END, "</span>")
885         html = "<head><style>.del { background: #faa; } "
886         html += ".add { background: #afa; }</style></head>"
887         html += "<pre>%s</pre>" % wdiff
888         return html
889
890     def _wdiff_command(self, actual_filename, expected_filename):
891         executable = self._path_to_wdiff()
892         return [executable,
893                 "--start-delete=%s" % self._WDIFF_DEL,
894                 "--end-delete=%s" % self._WDIFF_END,
895                 "--start-insert=%s" % self._WDIFF_ADD,
896                 "--end-insert=%s" % self._WDIFF_END,
897                 actual_filename,
898                 expected_filename]
899
900     @staticmethod
901     def _handle_wdiff_error(script_error):
902         # Exit 1 means the files differed, any other exit code is an error.
903         if script_error.exit_code != 1:
904             raise script_error
905
906     def _run_wdiff(self, actual_filename, expected_filename):
907         """Runs wdiff and may throw exceptions.
908         This is mostly a hook for unit testing."""
909         # Diffs are treated as binary as they may include multiple files
910         # with conflicting encodings.  Thus we do not decode the output.
911         command = self._wdiff_command(actual_filename, expected_filename)
912         wdiff = self._executive.run_command(command, decode_output=False,
913             error_handler=self._handle_wdiff_error)
914         return self._format_wdiff_output_as_html(wdiff)
915
916     def wdiff_text(self, actual_filename, expected_filename):
917         """Returns a string of HTML indicating the word-level diff of the
918         contents of the two filenames. Returns an empty string if word-level
919         diffing isn't available."""
920         if not self.wdiff_available():
921             return ""
922         try:
923             # It's possible to raise a ScriptError we pass wdiff invalid paths.
924             return self._run_wdiff(actual_filename, expected_filename)
925         except OSError, e:
926             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
927                 # Silently ignore cases where wdiff is missing.
928                 self._wdiff_available = False
929                 return ""
930             raise
931
932     # This is a class variable so we can test error output easily.
933     _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
934
935     def pretty_patch_text(self, diff_path):
936         if self._pretty_patch_available is None:
937             self._pretty_patch_available = self.check_pretty_patch(logging=False)
938         if not self._pretty_patch_available:
939             return self._pretty_patch_error_html
940         command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path),
941                    self._pretty_patch_path, diff_path)
942         try:
943             # Diffs are treated as binary (we pass decode_output=False) as they
944             # may contain multiple files of conflicting encodings.
945             return self._executive.run_command(command, decode_output=False)
946         except OSError, e:
947             # If the system is missing ruby log the error and stop trying.
948             self._pretty_patch_available = False
949             _log.error("Failed to run PrettyPatch (%s): %s" % (command, e))
950             return self._pretty_patch_error_html
951         except ScriptError, e:
952             # If ruby failed to run for some reason, log the command
953             # output and stop trying.
954             self._pretty_patch_available = False
955             _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
956             return self._pretty_patch_error_html
957
958     def default_configuration(self):
959         return self._config.default_configuration()
960
961     #
962     # PROTECTED ROUTINES
963     #
964     # The routines below should only be called by routines in this class
965     # or any of its subclasses.
966     #
967
968     def _uses_apache(self):
969         return True
970
971     def _path_to_apache(self):
972         """Returns the full path to the apache binary.
973
974         This is needed only by ports that use the apache_http_server module."""
975         raise NotImplementedError('Port.path_to_apache')
976
977     def _path_to_apache_config_file(self):
978         """Returns the full path to the apache binary.
979
980         This is needed only by ports that use the apache_http_server module."""
981         raise NotImplementedError('Port.path_to_apache_config_file')
982
983     def _path_to_driver(self, configuration=None):
984         """Returns the full path to the test driver (DumpRenderTree)."""
985         raise NotImplementedError('Port._path_to_driver')
986
987     def _path_to_webcore_library(self):
988         """Returns the full path to a built copy of WebCore."""
989         raise NotImplementedError('Port.path_to_webcore_library')
990
991     def _path_to_helper(self):
992         """Returns the full path to the layout_test_helper binary, which
993         is used to help configure the system for the test run, or None
994         if no helper is needed.
995
996         This is likely only used by start/stop_helper()."""
997         raise NotImplementedError('Port._path_to_helper')
998
999     def _path_to_image_diff(self):
1000         """Returns the full path to the image_diff binary, or None if it is not available.
1001
1002         This is likely used only by diff_image()"""
1003         raise NotImplementedError('Port.path_to_image_diff')
1004
1005     def _path_to_lighttpd(self):
1006         """Returns the path to the LigHTTPd binary.
1007
1008         This is needed only by ports that use the http_server.py module."""
1009         raise NotImplementedError('Port._path_to_lighttpd')
1010
1011     def _path_to_lighttpd_modules(self):
1012         """Returns the path to the LigHTTPd modules directory.
1013
1014         This is needed only by ports that use the http_server.py module."""
1015         raise NotImplementedError('Port._path_to_lighttpd_modules')
1016
1017     def _path_to_lighttpd_php(self):
1018         """Returns the path to the LigHTTPd PHP executable.
1019
1020         This is needed only by ports that use the http_server.py module."""
1021         raise NotImplementedError('Port._path_to_lighttpd_php')
1022
1023     def _path_to_wdiff(self):
1024         """Returns the full path to the wdiff binary, or None if it is not available.
1025
1026         This is likely used only by wdiff_text()"""
1027         raise NotImplementedError('Port._path_to_wdiff')
1028
1029     def _webkit_baseline_path(self, platform):
1030         """Return the  full path to the top of the baseline tree for a
1031         given platform."""
1032         return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
1033
1034     # FIXME: Belongs on a Platform object.
1035     def _generate_all_test_configurations(self):
1036         """Generates a list of TestConfiguration instances, representing configurations
1037         for a platform across all OSes, architectures, build and graphics types."""
1038         raise NotImplementedError('Port._generate_test_configurations')
1039
1040     def _driver_class(self):
1041         """Returns the port's driver implementation."""
1042         raise NotImplementedError('Port._driver_class')