#!/usr/bin/env python
# Copyright (C) 2010 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the Google name nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Dummy Port implementation used for testing."""
from __future__ import with_statement
import base64
import time
from webkitpy.common.system import filesystem_mock
from webkitpy.tool import mocktool
import base
# This sets basic expectations for a test. Each individual expectation
# can be overridden by a keyword argument in TestList.add().
class TestInstance:
def __init__(self, name):
self.name = name
self.base = name[(name.rfind("/") + 1):name.rfind(".html")]
self.crash = False
self.exception = False
self.hang = False
self.keyboard = False
self.error = ''
self.timeout = False
self.is_reftest = False
# The values of each field are treated as raw byte strings. They
# will be converted to unicode strings where appropriate using
# MockFileSystem.read_text_file().
self.actual_text = self.base + '-txt'
self.actual_checksum = self.base + '-checksum'
# We add the '\x8a' for the image file to prevent the value from
# being treated as UTF-8 (the character is invalid)
self.actual_image = self.base + '\x8a' + '-png'
self.expected_text = self.actual_text
self.expected_checksum = self.actual_checksum
self.expected_image = self.actual_image
self.actual_audio = None
self.expected_audio = None
# This is an in-memory list of tests, what we want them to produce, and
# what we want to claim are the expected results.
class TestList:
def __init__(self):
self.tests = {}
def add(self, name, **kwargs):
test = TestInstance(name)
for key, value in kwargs.items():
test.__dict__[key] = value
self.tests[name] = test
def add_reftest(self, name, reference_name, same_image):
self.add(name, actual_checksum='xxx', actual_image='XXX', is_reftest=True)
if same_image:
self.add(reference_name, actual_checksum='xxx', actual_image='XXX', is_reftest=True)
else:
self.add(reference_name, actual_checksum='yyy', actual_image='YYY', is_reftest=True)
def keys(self):
return self.tests.keys()
def __contains__(self, item):
return item in self.tests
def __getitem__(self, item):
return self.tests[item]
def unit_test_list():
tests = TestList()
tests.add('failures/expected/checksum.html',
actual_checksum='checksum_fail-checksum')
tests.add('failures/expected/crash.html', crash=True)
tests.add('failures/expected/exception.html', exception=True)
tests.add('failures/expected/timeout.html', timeout=True)
tests.add('failures/expected/hang.html', hang=True)
tests.add('failures/expected/missing_text.html', expected_text=None)
tests.add('failures/expected/image.html',
actual_image='image_fail-png',
expected_image='image-png')
tests.add('failures/expected/image_checksum.html',
actual_checksum='image_checksum_fail-checksum',
actual_image='image_checksum_fail-png')
tests.add('failures/expected/audio.html',
actual_audio=base64.b64encode('audio_fail-wav'), expected_audio='audio-wav',
actual_text=None, expected_text=None,
actual_image=None, expected_image=None,
actual_checksum=None, expected_checksum=None)
tests.add('failures/expected/keyboard.html', keyboard=True)
tests.add('failures/expected/missing_check.html',
expected_checksum=None,
expected_image=None)
tests.add('failures/expected/missing_image.html', expected_image=None)
tests.add('failures/expected/missing_audio.html', expected_audio=None,
actual_text=None, expected_text=None,
actual_image=None, expected_image=None,
actual_checksum=None, expected_checksum=None)
tests.add('failures/expected/missing_text.html', expected_text=None)
tests.add('failures/expected/newlines_leading.html',
expected_text="\nfoo\n", actual_text="foo\n")
tests.add('failures/expected/newlines_trailing.html',
expected_text="foo\n\n", actual_text="foo\n")
tests.add('failures/expected/newlines_with_excess_CR.html',
expected_text="foo\r\r\r\n", actual_text="foo\n")
tests.add('failures/expected/text.html', actual_text='text_fail-png')
tests.add('failures/unexpected/crash.html', crash=True)
tests.add('failures/unexpected/text-image-checksum.html',
actual_text='text-image-checksum_fail-txt',
actual_checksum='text-image-checksum_fail-checksum')
tests.add('failures/unexpected/timeout.html', timeout=True)
tests.add('http/tests/passes/text.html')
tests.add('http/tests/passes/image.html')
tests.add('http/tests/ssl/text.html')
tests.add('passes/error.html', error='stuff going to stderr')
tests.add('passes/image.html')
tests.add('passes/audio.html',
actual_audio=base64.b64encode('audio-wav'), expected_audio='audio-wav',
actual_text=None, expected_text=None,
actual_image=None, expected_image=None,
actual_checksum=None, expected_checksum=None)
tests.add('passes/platform_image.html')
tests.add('passes/checksum_in_image.html',
expected_checksum=None,
expected_image='tEXtchecksum\x00checksum_in_image-checksum')
# Text output files contain "\r\n" on Windows. This may be
# helpfully filtered to "\r\r\n" by our Python/Cygwin tooling.
tests.add('passes/text.html',
expected_text='\nfoo\n\n', actual_text='\nfoo\r\n\r\r\n')
# For reftests.
tests.add_reftest('passes/reftest.html', 'passes/reftest-expected.html', same_image=True)
tests.add_reftest('passes/mismatch.html', 'passes/mismatch-expected-mismatch.html', same_image=False)
tests.add_reftest('failures/expected/reftest.html', 'failures/expected/reftest-expected.html', same_image=False)
tests.add_reftest('failures/expected/mismatch.html', 'failures/expected/mismatch-expected-mismatch.html', same_image=True)
tests.add_reftest('failures/unexpected/reftest.html', 'failures/unexpected/reftest-expected.html', same_image=False)
tests.add_reftest('failures/unexpected/mismatch.html', 'failures/unexpected/mismatch-expected-mismatch.html', same_image=True)
# FIXME: Add a reftest which crashes.
tests.add('websocket/tests/passes/text.html')
return tests
# Here we use a non-standard location for the layout tests, to ensure that
# this works. The path contains a '.' in the name because we've seen bugs
# related to this before.
LAYOUT_TEST_DIR = '/test.checkout/LayoutTests'
# Here we synthesize an in-memory filesystem from the test list
# in order to fully control the test output and to demonstrate that
# we don't need a real filesystem to run the tests.
def unit_test_filesystem(files=None):
"""Return the FileSystem object used by the unit tests."""
test_list = unit_test_list()
files = files or {}
def add_file(files, test, suffix, contents):
dirname = test.name[0:test.name.rfind('/')]
base = test.base
path = LAYOUT_TEST_DIR + '/' + dirname + '/' + base + suffix
files[path] = contents
# Add each test and the expected output, if any.
for test in test_list.tests.values():
add_file(files, test, '.html', '')
if test.is_reftest:
continue
if test.actual_audio:
add_file(files, test, '-expected.wav', test.expected_audio)
continue
add_file(files, test, '-expected.txt', test.expected_text)
add_file(files, test, '-expected.checksum', test.expected_checksum)
add_file(files, test, '-expected.png', test.expected_image)
# Add the test_expectations file.
files[LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt'] = """
WONTFIX : failures/expected/checksum.html = IMAGE
WONTFIX : failures/expected/crash.html = CRASH
// This one actually passes because the checksums will match.
WONTFIX : failures/expected/image.html = PASS
WONTFIX : failures/expected/audio.html = AUDIO
WONTFIX : failures/expected/image_checksum.html = IMAGE
WONTFIX : failures/expected/mismatch.html = IMAGE
WONTFIX : failures/expected/missing_check.html = MISSING PASS
WONTFIX : failures/expected/missing_image.html = MISSING PASS
WONTFIX : failures/expected/missing_audio.html = MISSING PASS
WONTFIX : failures/expected/missing_text.html = MISSING PASS
WONTFIX : failures/expected/newlines_leading.html = TEXT
WONTFIX : failures/expected/newlines_trailing.html = TEXT
WONTFIX : failures/expected/newlines_with_excess_CR.html = TEXT
WONTFIX : failures/expected/reftest.html = IMAGE
WONTFIX : failures/expected/text.html = TEXT
WONTFIX : failures/expected/timeout.html = TIMEOUT
WONTFIX SKIP : failures/expected/hang.html = TIMEOUT
WONTFIX SKIP : failures/expected/keyboard.html = CRASH
WONTFIX SKIP : failures/expected/exception.html = CRASH
"""
# Add in a file should be ignored by test_files.find().
files[LAYOUT_TEST_DIR + 'userscripts/resources/iframe.html'] = 'iframe'
fs = filesystem_mock.MockFileSystem(files)
fs._tests = test_list
return fs
class TestPort(base.Port):
"""Test implementation of the Port interface."""
ALL_BASELINE_VARIANTS = (
'test-mac-snowleopard', 'test-mac-leopard',
'test-win-win7', 'test-win-vista', 'test-win-xp',
'test-linux-x86',
)
def __init__(self, port_name=None, user=None, filesystem=None, **kwargs):
if not port_name or port_name == 'test':
port_name = 'test-mac-leopard'
user = user or mocktool.MockUser()
filesystem = filesystem or unit_test_filesystem()
base.Port.__init__(self, port_name=port_name, filesystem=filesystem, user=user,
**kwargs)
self._results_directory = None
assert filesystem._tests
self._tests = filesystem._tests
self._operating_system = 'mac'
if port_name.startswith('test-win'):
self._operating_system = 'win'
elif port_name.startswith('test-linux'):
self._operating_system = 'linux'
version_map = {
'test-win-xp': 'xp',
'test-win-win7': 'win7',
'test-win-vista': 'vista',
'test-mac-leopard': 'leopard',
'test-mac-snowleopard': 'snowleopard',
'test-linux-x86': '',
}
self._version = version_map[port_name]
self._expectations_path = LAYOUT_TEST_DIR + '/platform/test/test_expectations.txt'
def _path_to_driver(self):
# This routine shouldn't normally be called, but it is called by
# the mock_drt Driver. We return something, but make sure it's useless.
return 'junk'
def baseline_path(self):
# We don't bother with a fallback path.
return self._filesystem.join(self.layout_tests_dir(), 'platform', self.name())
def baseline_search_path(self):
search_paths = {
'test-mac-snowleopard': ['test-mac-snowleopard'],
'test-mac-leopard': ['test-mac-leopard', 'test-mac-snowleopard'],
'test-win-win7': ['test-win-win7'],
'test-win-vista': ['test-win-vista', 'test-win-win7'],
'test-win-xp': ['test-win-xp', 'test-win-vista', 'test-win-win7'],
'test-linux-x86': ['test-linux', 'test-win-win7'],
}
return [self._webkit_baseline_path(d) for d in search_paths[self.name()]]
def default_child_processes(self):
return 1
def default_worker_model(self):
return 'inline'
def check_build(self, needs_http):
return True
def default_configuration(self):
return 'Release'
def diff_image(self, expected_contents, actual_contents,
diff_filename=None):
diffed = actual_contents != expected_contents
if diffed and diff_filename:
self._filesystem.write_binary_file(diff_filename,
"< %s\n---\n> %s\n" % (expected_contents, actual_contents))
return diffed
def layout_tests_dir(self):
return LAYOUT_TEST_DIR
def name(self):
return self._name
def _path_to_wdiff(self):
return None
def default_results_directory(self):
return '/tmp/layout-test-results'
def setup_test_run(self):
pass
def create_driver(self, worker_number):
return TestDriver(self, worker_number)
def start_http_server(self):
pass
def start_websocket_server(self):
pass
def stop_http_server(self):
pass
def stop_websocket_server(self):
pass
def path_to_test_expectations_file(self):
return self._expectations_path
def all_baseline_variants(self):
return self.ALL_BASELINE_VARIANTS
# FIXME: These next two routines are copied from base.py with
# the calls to path.abspath_to_uri() removed. We shouldn't have
# to do this.
def filename_to_uri(self, filename):
"""Convert a test file (which is an absolute path) to a URI."""
LAYOUTTEST_HTTP_DIR = "http/tests/"
LAYOUTTEST_WEBSOCKET_DIR = "http/tests/websocket/tests/"
relative_path = self.relative_test_filename(filename)
port = None
use_ssl = False
if (relative_path.startswith(LAYOUTTEST_WEBSOCKET_DIR)
or relative_path.startswith(LAYOUTTEST_HTTP_DIR)):
relative_path = relative_path[len(LAYOUTTEST_HTTP_DIR):]
port = 8000
# Make http/tests/local run as local files. This is to mimic the
# logic in run-webkit-tests.
#
# TODO(dpranke): remove the media reference and the SSL reference?
if (port and not relative_path.startswith("local/") and
not relative_path.startswith("media/")):
if relative_path.startswith("ssl/"):
port += 443
protocol = "https"
else:
protocol = "http"
return "%s://127.0.0.1:%u/%s" % (protocol, port, relative_path)
return "file://" + self._filesystem.abspath(filename)
def uri_to_test_name(self, uri):
"""Return the base layout test name for a given URI.
This returns the test name for a given URI, e.g., if you passed in
"file:///src/LayoutTests/fast/html/keygen.html" it would return
"fast/html/keygen.html".
"""
test = uri
if uri.startswith("file:///"):
prefix = "file://" + self.layout_tests_dir() + "/"
return test[len(prefix):]
if uri.startswith("http://127.0.0.1:8880/"):
# websocket tests
return test.replace('http://127.0.0.1:8880/', '')
if uri.startswith("http://"):
# regular HTTP test
return test.replace('http://127.0.0.1:8000/', 'http/tests/')
if uri.startswith("https://"):
return test.replace('https://127.0.0.1:8443/', 'http/tests/')
raise NotImplementedError('unknown url type: %s' % uri)
class TestDriver(base.Driver):
"""Test/Dummy implementation of the DumpRenderTree interface."""
def __init__(self, port, worker_number):
self._port = port
def cmd_line(self):
return [self._port._path_to_driver()] + self._port.get_option('additional_drt_flag', [])
def poll(self):
return True
def run_test(self, test_input):
start_time = time.time()
test_name = self._port.relative_test_filename(test_input.filename)
test = self._port._tests[test_name]
if test.keyboard:
raise KeyboardInterrupt
if test.exception:
raise ValueError('exception from ' + test_name)
if test.hang:
time.sleep((float(test_input.timeout) * 4) / 1000.0)
audio = None
if test.actual_audio:
audio = base64.b64decode(test.actual_audio)
return base.DriverOutput(test.actual_text, test.actual_image,
test.actual_checksum, audio, crash=test.crash,
test_time=time.time() - start_time, timeout=test.timeout, error=test.error)
def start(self):
pass
def stop(self):
pass