123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- import base64
- import errno
- import logging
- import re
- import signal
- import subprocess
- import sys
- import time
- import webbrowser
- import os
- from webkitpy.common.config import urls
- from webkitpy.common.system import executive
- from webkitpy.common.system.path import cygpath
- from webkitpy.common.system.executive import Executive, ScriptError
- from webkitpy.port import builders, server_process, Driver, DriverOutput
- from webkitpy.layout_tests.models.test_configuration import TestConfiguration
- from webkitpy.port.base import Port
- _log = logging.getLogger(__name__)
- DEFAULT_RESULT_DIR = "result"
- class OrbisPort(Port):
- port_name = 'orbis'
- @classmethod
- def default_results_directory(self):
- return DEFAULT_RESULT_DIR
- def default_test_timeout_ms(self):
- return 10 * 1000
-
- def relative_test_filename(self, filename):
- path = filename[len(self.layout_tests_dir()) + 1:]
- return path.replace('\\', '/')
- def _generate_all_test_configurations(self):
- return [TestConfiguration(version=self._version, architecture='x86', build_type=build_type) for build_type in self.ALL_BUILD_TYPES]
- def driver_name(self):
- return "WebKitTestRunnerOrbis.self"
- def _path_to_driver(self):
- return self._build_path(self.driver_name())
- def _build_directory_name(self):
- return self.get_option('platform').upper() + '_' + self.get_option('configuration')
- def layout_tests_dir(self):
- """Return the absolute path to the top of the LayoutTests directory."""
- return self._build_path().replace(self._build_directory_name(),"fsroot/LayoutTests")
- def _build_path(self, *comps):
- if self.get_option('build_directory'):
- return self._filesystem.join(self.get_option('build_directory'), *comps)
- else:
- relative_path_to_build_directory = 'orbis/WebKitTestRunnerOrbis/' + self._build_directory_name()
- return self._filesystem.join(relative_path_to_build_directory, *comps)
- def _driver_class(self):
- return OrbisDriver
- def _check_file_exists(self, path_to_file, file_description,
- override_step=None, logging=True):
- """Verify the file is present where expected or log an error.
- Args:
- file_name: The (human friendly) name or description of the file
- you're looking for (e.g., "HTTP Server"). Used for error logging.
- override_step: An optional string to be logged if the check fails.
- logging: Whether or not log the error messages."""
- if not self._filesystem.exists(path_to_file):
- if logging:
- _log.error('Unable to find %s' % file_description)
- _log.error(' at %s' % path_to_file)
- if override_step:
- _log.error(' %s' % override_step)
- _log.error('')
- return False
- return True
- def _check_apache_install(self):
- result = self._check_file_exists(self._path_to_apache(), "httpd2.exe")
- result = self._check_file_exists(self._path_to_apache_config_file(), "apache2 config file") and result
- if not result:
- _log.error(' Please install using: "sudo apt-get install apache2 libapache2-mod-php5"')
- _log.error('')
- return result
- def _check_lighttpd_install(self):
- result = self._check_file_exists(
- self._path_to_lighttpd(), "LigHTTPd executable")
- result = self._check_file_exists(self._path_to_lighttpd_php(), "PHP CGI executable") and result
- result = self._check_file_exists(self._path_to_lighttpd_modules(), "LigHTTPd modules") and result
- if not result:
- _log.error(' Please install using: "sudo apt-get install lighttpd php5-cgi"')
- _log.error('')
- return result
- def check_wdiff(self, logging=True):
- result = self._check_file_exists(self._path_to_wdiff(), 'wdiff.exe')
- if not result and logging:
- _log.error(' Please install using: "sudo apt-get install wdiff"')
- _log.error('')
- return result
-
- def _uses_apache(self):
- return False
- def _path_to_apache(self):
- return '/usr/sbin/httpd2.exe'
- def _path_to_apache_config_file(self):
- config_name = 'apache2-httpd.conf'
- return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_name)
- def start_websocket_server(self):
- pass
-
- def _path_to_lighttpd(self):
- return "/usr/sbin/lighttpd.exe"
- def _path_to_lighttpd_modules(self):
- return "/usr/lib/lighttpd"
- def _path_to_lighttpd_php(self):
- return "/usr/bin/php-cgi.exe"
- def _path_to_wdiff(self):
- return '/usr/bin/wdiff.exe'
- def _startup_time(self):
- return 4.0
- def _execution_time(self):
- # Approximate time for execution of a test
- # with WebKitTestRunnerOrbis as driver
- return 0.2
- def show_results_html_file(self, results_filename):
- # Convert cygwin path to Windows path
- win_abspath = cygpath(results_filename)
- subprocess.call(["cygstart", win_abspath])
- class OrbisDriver(Driver):
- def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
- Driver.__init__(self, port, worker_number, pixel_tests, no_timeout)
- self._driver_tempdir = port._filesystem.mkdtemp(prefix='%s-' % self._port.driver_name())
- # WebKitTestRunner can report back subprocess crashes by printing
- # "#CRASHED - PROCESSNAME". Since those can happen at any time
- # and ServerProcess won't be aware of them (since the actual tool
- # didn't crash, just a subprocess) we record the crashed subprocess name here.
- self._crashed_process_name = None
- self._crashed_pid = None
- self._proc = None
- # stderr reading is scoped on a per-test (not per-block) basis, so we store the accumulated
- # stderr output, as well as if we've seen #EOF on this driver instance.
- # FIXME: We should probably remove _read_first_block and _read_optional_image_block and
- # instead scope these locally in run_test.
- self.error_from_test = str()
- self.err_seen_eof = False
- self._server_process = None
- # FIXME: This may be unsafe, as python does not guarentee any ordering of __del__ calls
- # I believe it's possible that self._port or self._port._filesystem may already be destroyed.
- def __del__(self):
- self._port._filesystem.rmtree(str(self._driver_tempdir))
- def cmd_line(self, pixel_tests, per_test_args):
- # cmd = self._command_wrapper(self._port.get_option('wrapper'))
- # cmd.append(self._port._path_to_driver())
- # if self._port.get_option('gc_between_tests'):
- # cmd.append('--gc-between-tests')
- # if self._port.get_option('complex_text'):
- # cmd.append('--complex-text')
- # if self._port.get_option('threaded'):
- # cmd.append('--threaded')
- # if self._no_timeout:
- # cmd.append('--no-timeout')
- # # FIXME: We need to pass --timeout=SECONDS to WebKitTestRunner for WebKit2.
- # cmd.extend(self._port.get_option('additional_drt_flag', []))
- # if pixel_tests:
- # cmd.append('--pixel-tests')
- # cmd.extend(per_test_args)
- # #cmd.append('-')
- orbis_ctrl = 'orbis-run /noprogress /c:process /elf '
- path_2_driver = self._port._build_path(self._port.driver_name())
- return orbis_ctrl + path_2_driver
- def _test_shell_command(self,pixel_tests, uri, timeoutms, checksum):
- cmd = uri
- if timeoutms:
- cmd += ' ' + '--timeout' + ' ' + str(timeoutms)
- if pixel_tests:
- cmd += ' ' + '--pixel-tests'
- return cmd
- def _start(self, pixel_tests, per_test_args):
- server_name = self._port.driver_name()
- if sys.platform == 'cygwin':
- cmd = ["bash.exe"]
- if sys.platform == 'win32':
- cmd = ["cmd.exe"]
- # environment = self._port.setup_environ_for_server(server_name)
- # environment['DYLD_LIBRARY_PATH'] = self._port._build_path()
- # environment['DYLD_FRAMEWORK_PATH'] = self._port._build_path()
- # # # FIXME: We're assuming that WebKitTestRunner checks this DumpRenderTree-named environment variable.
- # environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)
- # environment['LOCAL_RESOURCE_ROOT'] = self._port.layout_tests_dir()
- self._crashed_process_name = None
- self._crashed_pid = None
- self._server_process = server_process.ServerProcess(self._port, server_name, cmd, None)
- def has_crashed(self):
- if self._server_process is None:
- return False
- if self._crashed_process_name:
- return True
- if self._server_process.has_crashed():
- self._crashed_process_name = self._server_process.name()
- self._crashed_pid = self._server_process.pid()
- return True
- return False
- def _check_for_driver_crash(self, error_line):
- if error_line == "#CRASHED\n":
- # This is used on Windows to report that the process has crashed
- # See http://trac.webkit.org/changeset/65537.
- self._crashed_process_name = self._server_process.name()
- self._crashed_pid = self._server_process.pid()
- elif error_line.startswith("#CRASHED - WebProcess"):
- # WebKitTestRunner uses this to report that the WebProcess subprocess crashed.
- pid = None
- m = re.search('pid (\d+)', error_line)
- if m:
- pid = int(m.group(1))
- self._crashed_process_name = 'WebProcess'
- self._crashed_pid = pid
- # FIXME: delete this after we're sure this code is working :)
- _log.debug('WebProcess crash, pid = %s, error_line = %s' % (str(pid), error_line))
- return True
- return self.has_crashed()
- def _command_from_driver_input(self, driver_input):
- if self.is_http_test(driver_input.test_name):
- command = self.test_to_uri(driver_input.test_name)
- else:
- #command = self._port.abspath_for_test(driver_input.test_name)
- #if sys.platform == 'cygwin':
- #command = cygpath(command)
- command = driver_input.test_name
- if driver_input.should_run_pixel_test:
- if driver_input.image_hash:
- # FIXME: Why the leading quo
- command += "'" + driver_input.image_hash
- return command + "\n"
- def _read_first_block(self, deadline):
- # returns (text_content, audio_content)
- block = self._read_block(deadline)
- if block.content_type == 'audio/wav':
- return (None, block.decoded_content)
- return (block.decoded_content, None)
- def _read_optional_image_block(self, deadline):
- # returns (image, actual_image_hash)
- block = self._read_block(deadline, wait_for_stderr_eof=True)
- if block.content and block.content_type == 'image/png':
- return (block.decoded_content, block.content_hash)
- return (None, block.content_hash)
- def _process_driver_input(self, driver_input):
- test_set = []
- for test in driver_input:
- test_set.append(test.test_name)
- return test_set
-
- def run_multiple_tests(self, driver_input, stop_when_done):
- text = []
- num_tests = len(driver_input)
- start_time = time.time()
- if not self._server_process:
- self._start(driver_input[0].should_run_pixel_test, driver_input[0].args)
- self.error_from_test = str()
- self.err_seen_eof = False
- image = None
- actual_image_hash = None
- # Check the startup time of the Orbis driver. Currently a method returning
- # a fixed value is used, in the future we can improve this at runtime
- # by executing a dummy run of WebKitTestRunnerOrbis
- time_to_sleep = self._port._startup_time() + (self._port._execution_time()* float(num_tests))
- #To support PSOrbis the path to the driver is returned as the full path to
- #WebKitTestRunnerOrbis.self include Orbis-run /c:process /elf
- driver = self.cmd_line(self._port.get_option('pixel_tests'), [])
- #To provide the options [--timeout],[--pixeltest]
- command = self._test_shell_command(driver_input[0].should_run_pixel_test,driver, (int(driver_input[0].timeout) * int(num_tests)), driver_input[0].image_hash)
- #combine driver path + test options + Test case file path [*.html]
- test_set = self._process_driver_input(driver_input)
- for test in test_set:
- command += ' ' + '/hostapp/LayoutTests/' + test
- command += '\n'
- _log.debug( "command = %s" % command)
- deadline = start_time + int(driver_input[0].timeout) / 1000.0
- # Bug33130, Adding sleep to avoid process creation error caused by
- # repeatedly invoking orbis-run
- self._server_process.write(command)
- for t in range(0,num_tests):
- self.err_seen_eof = False
- cur_text, audio = self._read_first_block(deadline) # First block is either text or audio
- if not cur_text and self._server_process.timed_out:
- break
- image, actual_image_hash = self._read_optional_image_block(deadline)
- text.append(cur_text)
- num_tests_ran = len(text)
- # We may not have read all of the output if an error (crash) occured.
- # Since some platforms output the stacktrace over error, we should
- # dump any buffered error into self.error_from_test.
- # FIXME: We may need to also read stderr until the process dies?
- self.error_from_test += self._server_process.pop_all_buffered_stderr()
- crash_log = ''
- if self.has_crashed():
- crash_log = self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, text[0], self.error_from_test,
- newer_than=start_time)
- driver_output = []
- timeout = self._server_process.timed_out
- if timeout:
- _log.info("\nTimeout: Num of tests returned: %d out of %d" % (num_tests_ran, num_tests))
- for x in range(0, num_tests_ran):
- _log.debug('')
- # Handle the tests that returned before the timeout
- driver_output.append(DriverOutput(text[x].lstrip(), image, actual_image_hash, audio,
- crash=self.has_crashed(), test_time=time.time() - start_time,
- timeout=False, error=self.error_from_test,
- crashed_process_name=self._crashed_process_name,
- crashed_pid=self._crashed_pid, crash_log=crash_log))
- for x in range(num_tests_ran, num_tests):
- text = ''
- driver_output.append(DriverOutput(text.lstrip(), image, actual_image_hash, audio,
- crash=self.has_crashed(), test_time=time.time() - start_time,
- timeout=timeout, error=self.error_from_test,
- crashed_process_name=self._crashed_process_name,
- crashed_pid=self._crashed_pid, crash_log=crash_log))
- return driver_output, num_tests_ran
- for x in range (0, num_tests):
- driver_output.append(DriverOutput(text[x].lstrip(), image, actual_image_hash, audio,
- crash=self.has_crashed(), test_time=time.time() - start_time,
- timeout=timeout, error=self.error_from_test,
- crashed_process_name=self._crashed_process_name,
- crashed_pid=self._crashed_pid, crash_log=crash_log))
- return driver_output, num_tests_ran
- def run_test(self, driver_input, stop_when_done):
- start_time = time.time()
- if not self._server_process:
- self._start(driver_input.should_run_pixel_test, driver_input.args)
- self.error_from_test = str()
- self.err_seen_eof = False
- time_to_sleep = float(self._port._startup_time()) + float(self._port._execution_time())
- #To support PSOrbis the path to the driver is returned as the full path to
- #WebKitTestRunnerOrbis.self include Orbis-run /c:process /elf
- driver = self.cmd_line(self._port.get_option('pixel_tests'), [])
- #To provide the options [--timeout],[--pixeltest]
- command = self._test_shell_command(driver_input.should_run_pixel_test,driver, driver_input.timeout, driver_input.image_hash)
- #combine driver path + test options + Test case file path [*.html]
- command += ' ' + '/hostapp/LayoutTests/' + self._command_from_driver_input(driver_input)
- _log.debug( "command = %s" % command)
- deadline = start_time + int(driver_input.timeout) / 1000.0
- self._server_process.write(command)
- text, audio = self._read_first_block(deadline) # First block is either text or audio
- image, actual_image_hash = self._read_optional_image_block(deadline) # The second (optional) block is image data.
- # We may not have read all of the output if an error (crash) occured.
- # Since some platforms output the stacktrace over error, we should
- # dump any buffered error into self.error_from_test.
- # FIXME: We may need to also read stderr until the process dies?
- self.error_from_test += self._server_process.pop_all_buffered_stderr()
- crash_log = ''
- if self.has_crashed():
- crash_log = self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, text, self.error_from_test,
- newer_than=start_time)
- timeout = self._server_process.timed_out
- # if timeout:
- #DRT doesn't have a built in timer to abort the test, so we might as well
- #kill the process directly and not wait for it to shut down cleanly (since it may not).
- # self._server_process.kill()
- return DriverOutput(text.lstrip(), image, actual_image_hash, audio,
- crash=self.has_crashed(), test_time=time.time() - start_time,
- timeout=timeout, error=self.error_from_test,
- crashed_process_name=self._crashed_process_name,
- crashed_pid=self._crashed_pid, crash_log=crash_log)
- def _read_header(self, block, line, header_text, header_attr, header_filter=None):
- if line.startswith(header_text) and getattr(block, header_attr) is None:
- value = line.split()[1]
- if header_filter:
- value = header_filter(value)
- setattr(block, header_attr, value)
- return True
- return False
- def _process_stdout_line(self, block, line):
- if (self._read_header(block, line, 'Content-Type: ', 'content_type')
- or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'encoding')
- or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
- or self._read_header(block, line, 'ActualHash: ', 'content_hash')):
- return
- #Specfic handling for PSOrbis as this comes for each test cases in the stdout
- if ('Load libSce' in line
- or 'Microsoft Windows' in line
- or 'Microsoft Corporation' in line
- or 'orbis-run' in line):
- return
- # Note, we're not reading ExpectedHash: here, but we could.
- # If the line wasn't a header, we just append it to the content.
- block.content += line
- def _strip_eof(self, line):
- if line and line.endswith("#EOF\n"):
- return line[:-5], True
- return line, False
- def _read_block(self, deadline, wait_for_stderr_eof=False):
- block = ContentBlock()
- out_seen_eof = False
- while not self.has_crashed():
- if out_seen_eof and (self.err_seen_eof or not wait_for_stderr_eof):
- break
- if self.err_seen_eof:
- out_line = self._server_process.read_stdout_line(deadline)
- err_line = None
- elif out_seen_eof:
- out_line = None
- err_line = self._server_process.read_stderr_line(deadline)
- else:
- out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(deadline)
-
- if self._server_process.timed_out or self.has_crashed():
- break
- if out_line:
- assert not out_seen_eof
- out_line, out_seen_eof = self._strip_eof(out_line)
- if err_line:
- assert not self.err_seen_eof
- err_line, self.err_seen_eof = self._strip_eof(err_line)
- if out_line:
- if out_line[-1] != "\n":
- _log.error("Last character read from DRT stdout line was not a newline! This indicates either a NRWT or DRT bug.")
- content_length_before_header_check = block._content_length
- self._process_stdout_line(block, out_line)
-
- # FIXME: Unlike HTTP, DRT dumps the content right after printing a Content-Length header.
- # Don't wait until we're done with headers, just read the binary blob right now.
- if content_length_before_header_check != block._content_length:
- block.content = self._server_process.read_stdout(deadline, block._content_length)
- if err_line:
- if self._check_for_driver_crash(err_line):
- break
- self.error_from_test += err_line
- block.decode_content()
- return block
- def start(self, pixel_tests, per_test_args):
- if not self._server_process:
- self._start(pixel_tests, per_test_args)
- def stop(self):
- if self._server_process:
- self._rebootorbis()
- self._server_process.stop()
- self._server_process = None
- def _rebootorbis(self):
- if self._server_process:
- if self.has_crashed():
- _log.debug( "crashed...reboot! wait...")
- command = 'orbis-ctrl reboot \n'
- self._server_process.write(command)
- #time delay for Orbis to complete reboot
- time.sleep(120)
- def _pause_run(self):
- if (self.num_tests % REBOOT_AFTER_NUM_TESTS) == 0:
- self._reboot_required = True
- else:
- # Bug33130, Adding sleep to avoid process creation error caused by
- # repeatedly invoking orbis-run
- time.sleep(9)
- class ContentBlock(object):
- def __init__(self):
- self.content_type = None
- self.encoding = None
- self.content_hash = None
- self._content_length = None
- # Content is treated as binary data even though the text output is usually UTF-8.
- self.content = str() # FIXME: Should be bytearray() once we require Python 2.6.
- self.decoded_content = None
- def decode_content(self):
- if self.encoding == 'base64':
- self.decoded_content = base64.b64decode(self.content)
- else:
- self.decoded_content = self.content
|