run-perf-tests ignore Skipped list on chromium
[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 _tests_from_skipped_file_contents(self, skipped_file_contents):
578         tests_to_skip = []
579         for line in skipped_file_contents.split('\n'):
580             line = line.strip()
581             line = line.rstrip('/')  # Best to normalize directory names to not include the trailing slash.
582             if line.startswith('#') or not len(line):
583                 continue
584             tests_to_skip.append(line)
585         return tests_to_skip
586
587     def _expectations_from_skipped_files(self, skipped_file_paths):
588         tests_to_skip = []
589         for search_path in skipped_file_paths:
590             filename = self._filesystem.join(self._webkit_baseline_path(search_path), "Skipped")
591             if not self._filesystem.exists(filename):
592                 _log.debug("Skipped does not exist: %s" % filename)
593                 continue
594             _log.debug("Using Skipped file: %s" % filename)
595             skipped_file_contents = self._filesystem.read_text_file(filename)
596             tests_to_skip.extend(self._tests_from_skipped_file_contents(skipped_file_contents))
597         return tests_to_skip
598
599     @memoized
600     def skipped_perf_tests(self):
601         return self._expectations_from_skipped_files([self.perf_tests_dir()])
602
603     def skipped_tests(self):
604         return []
605
606     def skips_layout_test(self, test_name):
607         """Figures out if the givent test is being skipped or not.
608
609         Test categories are handled as well."""
610         for test_or_category in self.skipped_layout_tests():
611             if test_or_category == test_name:
612                 return True
613             category = self._filesystem.join(self.layout_tests_dir(), test_or_category)
614             if self._filesystem.isdir(category) and test_name.startswith(test_or_category):
615                 return True
616         return False
617
618     def skips_perf_test(self, test_name):
619         for test_or_category in self.skipped_perf_tests():
620             if test_or_category == test_name:
621                 return True
622             category = self._filesystem.join(self.perf_tests_dir(), test_or_category)
623             if self._filesystem.isdir(category) and test_name.startswith(test_or_category):
624                 return True
625         return False
626
627     def maybe_make_directory(self, *comps):
628         """Creates the specified directory if it doesn't already exist."""
629         self._filesystem.maybe_make_directory(*comps)
630
631     def name(self):
632         """Returns a name that uniquely identifies this particular type of port
633         (e.g., "mac-snowleopard" or "chromium-gpu-linux-x86_x64" and can be passed
634         to factory.get() to instantiate the port."""
635         return self._name
636
637     def real_name(self):
638         # FIXME: Seems this is only used for MockDRT and should be removed.
639         """Returns the name of the port as passed to the --platform command line argument."""
640         return self.name()
641
642     def operating_system(self):
643         # Subclasses should override this default implementation.
644         return 'mac'
645
646     def version(self):
647         """Returns a string indicating the version of a given platform, e.g.
648         'leopard' or 'xp'.
649
650         This is used to help identify the exact port when parsing test
651         expectations, determining search paths, and logging information."""
652         return self._version
653
654     def graphics_type(self):
655         """Returns whether the port uses accelerated graphics ('gpu') or not ('cpu')."""
656         return self._graphics_type
657
658     def architecture(self):
659         return self._architecture
660
661     def get_option(self, name, default_value=None):
662         # FIXME: Eventually we should not have to do a test for
663         # hasattr(), and we should be able to just do
664         # self.options.value. See additional FIXME in the constructor.
665         if hasattr(self._options, name):
666             return getattr(self._options, name)
667         return default_value
668
669     def set_option_default(self, name, default_value):
670         # FIXME: Callers could also use optparse_parser.Values.ensure_value,
671         # since this should always be a optparse_parser.Values object.
672         if not hasattr(self._options, name) or getattr(self._options, name) is None:
673             return setattr(self._options, name, default_value)
674
675     def path_from_webkit_base(self, *comps):
676         """Returns the full path to path made by joining the top of the
677         WebKit source tree and the list of path components in |*comps|."""
678         return self._config.path_from_webkit_base(*comps)
679
680     def path_to_test_expectations_file(self):
681         """Update the test expectations to the passed-in string.
682
683         This is used by the rebaselining tool. Raises NotImplementedError
684         if the port does not use expectations files."""
685         raise NotImplementedError('Port.path_to_test_expectations_file')
686
687     def relative_test_filename(self, filename):
688         """Returns a test_name a realtive unix-style path for a filename under the LayoutTests
689         directory. Filenames outside the LayoutTests directory should raise
690         an error."""
691         # Ports that run on windows need to override this method to deal with
692         # filenames with backslashes in them.
693         assert filename.startswith(self.layout_tests_dir()), "%s did not start with %s" % (filename, self.layout_tests_dir())
694         return filename[len(self.layout_tests_dir()) + 1:]
695
696     def relative_perf_test_filename(self, filename):
697         assert filename.startswith(self.perf_tests_dir()), "%s did not start with %s" % (filename, self.perf_tests_dir())
698         return filename[len(self.perf_tests_dir()) + 1:]
699
700     def abspath_for_test(self, test_name):
701         """Returns the full path to the file for a given test name. This is the
702         inverse of relative_test_filename()."""
703         return self._filesystem.normpath(self._filesystem.join(self.layout_tests_dir(), test_name))
704
705     def results_directory(self):
706         """Absolute path to the place to store the test results (uses --results-directory)."""
707         if not self._results_directory:
708             option_val = self.get_option('results_directory') or self.default_results_directory()
709             self._results_directory = self._filesystem.abspath(option_val)
710         return self._results_directory
711
712     def default_results_directory(self):
713         """Absolute path to the default place to store the test results."""
714         raise NotImplementedError()
715
716     def setup_test_run(self):
717         """Perform port-specific work at the beginning of a test run."""
718         pass
719
720     # FIXME: os.environ access should be moved to onto a common/system class to be more easily mockable.
721     def _value_or_default_from_environ(self, name, default=None):
722         if name in os.environ:
723             return os.environ[name]
724         return default
725
726     def _copy_value_from_environ_if_set(self, clean_env, name):
727         if name in os.environ:
728             clean_env[name] = os.environ[name]
729
730     def setup_environ_for_server(self, server_name=None):
731         # We intentionally copy only a subset of os.environ when
732         # launching subprocesses to ensure consistent test results.
733         clean_env = {}
734         variables_to_copy = [
735             # For Linux:
736             'XAUTHORITY',
737             'HOME',
738             'LANG',
739             'LD_LIBRARY_PATH',
740             'DBUS_SESSION_BUS_ADDRESS',
741
742             # Darwin:
743             'DYLD_LIBRARY_PATH',
744             'HOME',
745
746             # CYGWIN:
747             'HOMEDRIVE',
748             'HOMEPATH',
749             '_NT_SYMBOL_PATH',
750
751             # Windows:
752             'PATH',
753         ]
754         for variable in variables_to_copy:
755             self._copy_value_from_environ_if_set(clean_env, variable)
756
757         # For Linux:
758         clean_env['DISPLAY'] = self._value_or_default_from_environ('DISPLAY', ':1')
759         return clean_env
760
761     def show_results_html_file(self, results_filename):
762         """This routine should display the HTML file pointed at by
763         results_filename in a users' browser."""
764         return self.host.user.open_url(path.abspath_to_uri(results_filename))
765
766     def create_driver(self, worker_number, no_timeout=False):
767         """Return a newly created Driver subclass for starting/stopping the test driver."""
768         return driver.DriverProxy(self, worker_number, self._driver_class(), pixel_tests=self.get_option('pixel_tests'), no_timeout=no_timeout)
769
770     def start_helper(self):
771         """If a port needs to reconfigure graphics settings or do other
772         things to ensure a known test configuration, it should override this
773         method."""
774         pass
775
776     def start_http_server(self):
777         """Start a web server. Raise an error if it can't start or is already running.
778
779         Ports can stub this out if they don't need a web server to be running."""
780         assert not self._http_server, 'Already running an http server.'
781
782         if self._uses_apache():
783             server = apache_http_server.LayoutTestApacheHttpd(self, self.results_directory())
784         else:
785             server = http_server.Lighttpd(self, self.results_directory())
786
787         server.start()
788         self._http_server = server
789
790     def start_websocket_server(self):
791         """Start a web server. Raise an error if it can't start or is already running.
792
793         Ports can stub this out if they don't need a websocket server to be running."""
794         assert not self._websocket_server, 'Already running a websocket server.'
795
796         server = websocket_server.PyWebSocket(self, self.results_directory())
797         server.start()
798         self._websocket_server = server
799
800     def acquire_http_lock(self):
801         self._http_lock = http_lock.HttpLock(None, filesystem=self._filesystem, executive=self._executive)
802         self._http_lock.wait_for_httpd_lock()
803
804     def stop_helper(self):
805         """Shut down the test helper if it is running. Do nothing if
806         it isn't, or it isn't available. If a port overrides start_helper()
807         it must override this routine as well."""
808         pass
809
810     def stop_http_server(self):
811         """Shut down the http server if it is running. Do nothing if it isn't."""
812         if self._http_server:
813             self._http_server.stop()
814             self._http_server = None
815
816     def stop_websocket_server(self):
817         """Shut down the websocket server if it is running. Do nothing if it isn't."""
818         if self._websocket_server:
819             self._websocket_server.stop()
820             self._websocket_server = None
821
822     def release_http_lock(self):
823         if self._http_lock:
824             self._http_lock.cleanup_http_lock()
825
826     def exit_code_from_summarized_results(self, unexpected_results):
827         """Given summarized results, compute the exit code to be returned by new-run-webkit-tests.
828         Bots turn red when this function returns a non-zero value. By default, return the number of regressions
829         to avoid turning bots red by flaky failures, unexpected passes, and missing results"""
830         # Don't turn bots red for flaky failures, unexpected passes, and missing results.
831         return unexpected_results['num_regressions']
832
833     #
834     # TEST EXPECTATION-RELATED METHODS
835     #
836
837     def test_configuration(self):
838         """Returns the current TestConfiguration for the port."""
839         if not self._test_configuration:
840             self._test_configuration = TestConfiguration(self._version, self._architecture, self._options.configuration.lower(), self._graphics_type)
841         return self._test_configuration
842
843     # FIXME: Belongs on a Platform object.
844     @memoized
845     def all_test_configurations(self):
846         """Returns a list of TestConfiguration instances, representing all available
847         test configurations for this port."""
848         return self._generate_all_test_configurations()
849
850     # FIXME: Belongs on a Platform object.
851     def configuration_specifier_macros(self):
852         """Ports may provide a way to abbreviate configuration specifiers to conveniently
853         refer to them as one term or alias specific values to more generic ones. For example:
854
855         (xp, vista, win7) -> win # Abbreviate all Windows versions into one namesake.
856         (lucid) -> linux  # Change specific name of the Linux distro to a more generic term.
857
858         Returns a dictionary, each key representing a macro term ('win', for example),
859         and value being a list of valid configuration specifiers (such as ['xp', 'vista', 'win7'])."""
860         return {}
861
862     def all_baseline_variants(self):
863         """Returns a list of platform names sufficient to cover all the baselines.
864
865         The list should be sorted so that a later platform  will reuse
866         an earlier platform's baselines if they are the same (e.g.,
867         'snowleopard' should precede 'leopard')."""
868         raise NotImplementedError
869
870     def uses_test_expectations_file(self):
871         # This is different from checking test_expectations() is None, because
872         # some ports have Skipped files which are returned as part of test_expectations().
873         return self._filesystem.exists(self.path_to_test_expectations_file())
874
875     def test_expectations(self):
876         """Returns the test expectations for this port.
877
878         Basically this string should contain the equivalent of a
879         test_expectations file. See test_expectations.py for more details."""
880         return self._filesystem.read_text_file(self.path_to_test_expectations_file())
881
882     def test_expectations_overrides(self):
883         """Returns an optional set of overrides for the test_expectations.
884
885         This is used by ports that have code in two repositories, and where
886         it is possible that you might need "downstream" expectations that
887         temporarily override the "upstream" expectations until the port can
888         sync up the two repos."""
889         return None
890
891     def test_repository_paths(self):
892         """Returns a list of (repository_name, repository_path) tuples
893         of its depending code base.  By default it returns a list that only
894         contains a ('webkit', <webkitRepossitoryPath>) tuple.
895         """
896         return [('webkit', self.layout_tests_dir())]
897
898
899     _WDIFF_DEL = '##WDIFF_DEL##'
900     _WDIFF_ADD = '##WDIFF_ADD##'
901     _WDIFF_END = '##WDIFF_END##'
902
903     def _format_wdiff_output_as_html(self, wdiff):
904         wdiff = cgi.escape(wdiff)
905         wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
906         wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
907         wdiff = wdiff.replace(self._WDIFF_END, "</span>")
908         html = "<head><style>.del { background: #faa; } "
909         html += ".add { background: #afa; }</style></head>"
910         html += "<pre>%s</pre>" % wdiff
911         return html
912
913     def _wdiff_command(self, actual_filename, expected_filename):
914         executable = self._path_to_wdiff()
915         return [executable,
916                 "--start-delete=%s" % self._WDIFF_DEL,
917                 "--end-delete=%s" % self._WDIFF_END,
918                 "--start-insert=%s" % self._WDIFF_ADD,
919                 "--end-insert=%s" % self._WDIFF_END,
920                 actual_filename,
921                 expected_filename]
922
923     @staticmethod
924     def _handle_wdiff_error(script_error):
925         # Exit 1 means the files differed, any other exit code is an error.
926         if script_error.exit_code != 1:
927             raise script_error
928
929     def _run_wdiff(self, actual_filename, expected_filename):
930         """Runs wdiff and may throw exceptions.
931         This is mostly a hook for unit testing."""
932         # Diffs are treated as binary as they may include multiple files
933         # with conflicting encodings.  Thus we do not decode the output.
934         command = self._wdiff_command(actual_filename, expected_filename)
935         wdiff = self._executive.run_command(command, decode_output=False,
936             error_handler=self._handle_wdiff_error)
937         return self._format_wdiff_output_as_html(wdiff)
938
939     def wdiff_text(self, actual_filename, expected_filename):
940         """Returns a string of HTML indicating the word-level diff of the
941         contents of the two filenames. Returns an empty string if word-level
942         diffing isn't available."""
943         if not self.wdiff_available():
944             return ""
945         try:
946             # It's possible to raise a ScriptError we pass wdiff invalid paths.
947             return self._run_wdiff(actual_filename, expected_filename)
948         except OSError, e:
949             if e.errno in [errno.ENOENT, errno.EACCES, errno.ECHILD]:
950                 # Silently ignore cases where wdiff is missing.
951                 self._wdiff_available = False
952                 return ""
953             raise
954
955     # This is a class variable so we can test error output easily.
956     _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
957
958     def pretty_patch_text(self, diff_path):
959         if self._pretty_patch_available is None:
960             self._pretty_patch_available = self.check_pretty_patch(logging=False)
961         if not self._pretty_patch_available:
962             return self._pretty_patch_error_html
963         command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_path),
964                    self._pretty_patch_path, diff_path)
965         try:
966             # Diffs are treated as binary (we pass decode_output=False) as they
967             # may contain multiple files of conflicting encodings.
968             return self._executive.run_command(command, decode_output=False)
969         except OSError, e:
970             # If the system is missing ruby log the error and stop trying.
971             self._pretty_patch_available = False
972             _log.error("Failed to run PrettyPatch (%s): %s" % (command, e))
973             return self._pretty_patch_error_html
974         except ScriptError, e:
975             # If ruby failed to run for some reason, log the command
976             # output and stop trying.
977             self._pretty_patch_available = False
978             _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
979             return self._pretty_patch_error_html
980
981     def default_configuration(self):
982         return self._config.default_configuration()
983
984     #
985     # PROTECTED ROUTINES
986     #
987     # The routines below should only be called by routines in this class
988     # or any of its subclasses.
989     #
990
991     def _uses_apache(self):
992         return True
993
994     def _path_to_apache(self):
995         """Returns the full path to the apache binary.
996
997         This is needed only by ports that use the apache_http_server module."""
998         raise NotImplementedError('Port.path_to_apache')
999
1000     def _path_to_apache_config_file(self):
1001         """Returns the full path to the apache binary.
1002
1003         This is needed only by ports that use the apache_http_server module."""
1004         raise NotImplementedError('Port.path_to_apache_config_file')
1005
1006     def _path_to_driver(self, configuration=None):
1007         """Returns the full path to the test driver (DumpRenderTree)."""
1008         raise NotImplementedError('Port._path_to_driver')
1009
1010     def _path_to_webcore_library(self):
1011         """Returns the full path to a built copy of WebCore."""
1012         raise NotImplementedError('Port.path_to_webcore_library')
1013
1014     def _path_to_helper(self):
1015         """Returns the full path to the layout_test_helper binary, which
1016         is used to help configure the system for the test run, or None
1017         if no helper is needed.
1018
1019         This is likely only used by start/stop_helper()."""
1020         raise NotImplementedError('Port._path_to_helper')
1021
1022     def _path_to_image_diff(self):
1023         """Returns the full path to the image_diff binary, or None if it is not available.
1024
1025         This is likely used only by diff_image()"""
1026         raise NotImplementedError('Port.path_to_image_diff')
1027
1028     def _path_to_lighttpd(self):
1029         """Returns the path to the LigHTTPd binary.
1030
1031         This is needed only by ports that use the http_server.py module."""
1032         raise NotImplementedError('Port._path_to_lighttpd')
1033
1034     def _path_to_lighttpd_modules(self):
1035         """Returns the path to the LigHTTPd modules directory.
1036
1037         This is needed only by ports that use the http_server.py module."""
1038         raise NotImplementedError('Port._path_to_lighttpd_modules')
1039
1040     def _path_to_lighttpd_php(self):
1041         """Returns the path to the LigHTTPd PHP executable.
1042
1043         This is needed only by ports that use the http_server.py module."""
1044         raise NotImplementedError('Port._path_to_lighttpd_php')
1045
1046     def _path_to_wdiff(self):
1047         """Returns the full path to the wdiff binary, or None if it is not available.
1048
1049         This is likely used only by wdiff_text()"""
1050         raise NotImplementedError('Port._path_to_wdiff')
1051
1052     def _webkit_baseline_path(self, platform):
1053         """Return the  full path to the top of the baseline tree for a
1054         given platform."""
1055         return self._filesystem.join(self.layout_tests_dir(), 'platform', platform)
1056
1057     # FIXME: Belongs on a Platform object.
1058     def _generate_all_test_configurations(self):
1059         """Generates a list of TestConfiguration instances, representing configurations
1060         for a platform across all OSes, architectures, build and graphics types."""
1061         raise NotImplementedError('Port._generate_test_configurations')
1062
1063     def _driver_class(self):
1064         """Returns the port's driver implementation."""
1065         raise NotImplementedError('Port._driver_class')