orbis.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. import base64
  2. import errno
  3. import logging
  4. import re
  5. import signal
  6. import subprocess
  7. import sys
  8. import time
  9. import webbrowser
  10. import os
  11. from webkitpy.common.config import urls
  12. from webkitpy.common.system import executive
  13. from webkitpy.common.system.path import cygpath
  14. from webkitpy.common.system.executive import Executive, ScriptError
  15. from webkitpy.port import builders, server_process, Driver, DriverOutput
  16. from webkitpy.layout_tests.models.test_configuration import TestConfiguration
  17. from webkitpy.port.base import Port
  18. _log = logging.getLogger(__name__)
  19. DEFAULT_RESULT_DIR = "result"
  20. class OrbisPort(Port):
  21. port_name = 'orbis'
  22. @classmethod
  23. def default_results_directory(self):
  24. return DEFAULT_RESULT_DIR
  25. def default_test_timeout_ms(self):
  26. return 10 * 1000
  27. def relative_test_filename(self, filename):
  28. path = filename[len(self.layout_tests_dir()) + 1:]
  29. return path.replace('\\', '/')
  30. def _generate_all_test_configurations(self):
  31. return [TestConfiguration(version=self._version, architecture='x86', build_type=build_type) for build_type in self.ALL_BUILD_TYPES]
  32. def driver_name(self):
  33. return "WebKitTestRunnerOrbis.self"
  34. def _path_to_driver(self):
  35. return self._build_path(self.driver_name())
  36. def _build_directory_name(self):
  37. return self.get_option('platform').upper() + '_' + self.get_option('configuration')
  38. def layout_tests_dir(self):
  39. """Return the absolute path to the top of the LayoutTests directory."""
  40. return self._build_path().replace(self._build_directory_name(),"fsroot/LayoutTests")
  41. def _build_path(self, *comps):
  42. if self.get_option('build_directory'):
  43. return self._filesystem.join(self.get_option('build_directory'), *comps)
  44. else:
  45. relative_path_to_build_directory = 'orbis/WebKitTestRunnerOrbis/' + self._build_directory_name()
  46. return self._filesystem.join(relative_path_to_build_directory, *comps)
  47. def _driver_class(self):
  48. return OrbisDriver
  49. def _check_file_exists(self, path_to_file, file_description,
  50. override_step=None, logging=True):
  51. """Verify the file is present where expected or log an error.
  52. Args:
  53. file_name: The (human friendly) name or description of the file
  54. you're looking for (e.g., "HTTP Server"). Used for error logging.
  55. override_step: An optional string to be logged if the check fails.
  56. logging: Whether or not log the error messages."""
  57. if not self._filesystem.exists(path_to_file):
  58. if logging:
  59. _log.error('Unable to find %s' % file_description)
  60. _log.error(' at %s' % path_to_file)
  61. if override_step:
  62. _log.error(' %s' % override_step)
  63. _log.error('')
  64. return False
  65. return True
  66. def _check_apache_install(self):
  67. result = self._check_file_exists(self._path_to_apache(), "httpd2.exe")
  68. result = self._check_file_exists(self._path_to_apache_config_file(), "apache2 config file") and result
  69. if not result:
  70. _log.error(' Please install using: "sudo apt-get install apache2 libapache2-mod-php5"')
  71. _log.error('')
  72. return result
  73. def _check_lighttpd_install(self):
  74. result = self._check_file_exists(
  75. self._path_to_lighttpd(), "LigHTTPd executable")
  76. result = self._check_file_exists(self._path_to_lighttpd_php(), "PHP CGI executable") and result
  77. result = self._check_file_exists(self._path_to_lighttpd_modules(), "LigHTTPd modules") and result
  78. if not result:
  79. _log.error(' Please install using: "sudo apt-get install lighttpd php5-cgi"')
  80. _log.error('')
  81. return result
  82. def check_wdiff(self, logging=True):
  83. result = self._check_file_exists(self._path_to_wdiff(), 'wdiff.exe')
  84. if not result and logging:
  85. _log.error(' Please install using: "sudo apt-get install wdiff"')
  86. _log.error('')
  87. return result
  88. def _uses_apache(self):
  89. return False
  90. def _path_to_apache(self):
  91. return '/usr/sbin/httpd2.exe'
  92. def _path_to_apache_config_file(self):
  93. config_name = 'apache2-httpd.conf'
  94. return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_name)
  95. def start_websocket_server(self):
  96. pass
  97. def _path_to_lighttpd(self):
  98. return "/usr/sbin/lighttpd.exe"
  99. def _path_to_lighttpd_modules(self):
  100. return "/usr/lib/lighttpd"
  101. def _path_to_lighttpd_php(self):
  102. return "/usr/bin/php-cgi.exe"
  103. def _path_to_wdiff(self):
  104. return '/usr/bin/wdiff.exe'
  105. def _startup_time(self):
  106. return 4.0
  107. def _execution_time(self):
  108. # Approximate time for execution of a test
  109. # with WebKitTestRunnerOrbis as driver
  110. return 0.2
  111. def show_results_html_file(self, results_filename):
  112. # Convert cygwin path to Windows path
  113. win_abspath = cygpath(results_filename)
  114. subprocess.call(["cygstart", win_abspath])
  115. class OrbisDriver(Driver):
  116. def __init__(self, port, worker_number, pixel_tests, no_timeout=False):
  117. Driver.__init__(self, port, worker_number, pixel_tests, no_timeout)
  118. self._driver_tempdir = port._filesystem.mkdtemp(prefix='%s-' % self._port.driver_name())
  119. # WebKitTestRunner can report back subprocess crashes by printing
  120. # "#CRASHED - PROCESSNAME". Since those can happen at any time
  121. # and ServerProcess won't be aware of them (since the actual tool
  122. # didn't crash, just a subprocess) we record the crashed subprocess name here.
  123. self._crashed_process_name = None
  124. self._crashed_pid = None
  125. self._proc = None
  126. # stderr reading is scoped on a per-test (not per-block) basis, so we store the accumulated
  127. # stderr output, as well as if we've seen #EOF on this driver instance.
  128. # FIXME: We should probably remove _read_first_block and _read_optional_image_block and
  129. # instead scope these locally in run_test.
  130. self.error_from_test = str()
  131. self.err_seen_eof = False
  132. self._server_process = None
  133. # FIXME: This may be unsafe, as python does not guarentee any ordering of __del__ calls
  134. # I believe it's possible that self._port or self._port._filesystem may already be destroyed.
  135. def __del__(self):
  136. self._port._filesystem.rmtree(str(self._driver_tempdir))
  137. def cmd_line(self, pixel_tests, per_test_args):
  138. # cmd = self._command_wrapper(self._port.get_option('wrapper'))
  139. # cmd.append(self._port._path_to_driver())
  140. # if self._port.get_option('gc_between_tests'):
  141. # cmd.append('--gc-between-tests')
  142. # if self._port.get_option('complex_text'):
  143. # cmd.append('--complex-text')
  144. # if self._port.get_option('threaded'):
  145. # cmd.append('--threaded')
  146. # if self._no_timeout:
  147. # cmd.append('--no-timeout')
  148. # # FIXME: We need to pass --timeout=SECONDS to WebKitTestRunner for WebKit2.
  149. # cmd.extend(self._port.get_option('additional_drt_flag', []))
  150. # if pixel_tests:
  151. # cmd.append('--pixel-tests')
  152. # cmd.extend(per_test_args)
  153. # #cmd.append('-')
  154. orbis_ctrl = 'orbis-run /noprogress /c:process /elf '
  155. path_2_driver = self._port._build_path(self._port.driver_name())
  156. return orbis_ctrl + path_2_driver
  157. def _test_shell_command(self,pixel_tests, uri, timeoutms, checksum):
  158. cmd = uri
  159. if timeoutms:
  160. cmd += ' ' + '--timeout' + ' ' + str(timeoutms)
  161. if pixel_tests:
  162. cmd += ' ' + '--pixel-tests'
  163. return cmd
  164. def _start(self, pixel_tests, per_test_args):
  165. server_name = self._port.driver_name()
  166. if sys.platform == 'cygwin':
  167. cmd = ["bash.exe"]
  168. if sys.platform == 'win32':
  169. cmd = ["cmd.exe"]
  170. # environment = self._port.setup_environ_for_server(server_name)
  171. # environment['DYLD_LIBRARY_PATH'] = self._port._build_path()
  172. # environment['DYLD_FRAMEWORK_PATH'] = self._port._build_path()
  173. # # # FIXME: We're assuming that WebKitTestRunner checks this DumpRenderTree-named environment variable.
  174. # environment['DUMPRENDERTREE_TEMP'] = str(self._driver_tempdir)
  175. # environment['LOCAL_RESOURCE_ROOT'] = self._port.layout_tests_dir()
  176. self._crashed_process_name = None
  177. self._crashed_pid = None
  178. self._server_process = server_process.ServerProcess(self._port, server_name, cmd, None)
  179. def has_crashed(self):
  180. if self._server_process is None:
  181. return False
  182. if self._crashed_process_name:
  183. return True
  184. if self._server_process.has_crashed():
  185. self._crashed_process_name = self._server_process.name()
  186. self._crashed_pid = self._server_process.pid()
  187. return True
  188. return False
  189. def _check_for_driver_crash(self, error_line):
  190. if error_line == "#CRASHED\n":
  191. # This is used on Windows to report that the process has crashed
  192. # See http://trac.webkit.org/changeset/65537.
  193. self._crashed_process_name = self._server_process.name()
  194. self._crashed_pid = self._server_process.pid()
  195. elif error_line.startswith("#CRASHED - WebProcess"):
  196. # WebKitTestRunner uses this to report that the WebProcess subprocess crashed.
  197. pid = None
  198. m = re.search('pid (\d+)', error_line)
  199. if m:
  200. pid = int(m.group(1))
  201. self._crashed_process_name = 'WebProcess'
  202. self._crashed_pid = pid
  203. # FIXME: delete this after we're sure this code is working :)
  204. _log.debug('WebProcess crash, pid = %s, error_line = %s' % (str(pid), error_line))
  205. return True
  206. return self.has_crashed()
  207. def _command_from_driver_input(self, driver_input):
  208. if self.is_http_test(driver_input.test_name):
  209. command = self.test_to_uri(driver_input.test_name)
  210. else:
  211. #command = self._port.abspath_for_test(driver_input.test_name)
  212. #if sys.platform == 'cygwin':
  213. #command = cygpath(command)
  214. command = driver_input.test_name
  215. if driver_input.should_run_pixel_test:
  216. if driver_input.image_hash:
  217. # FIXME: Why the leading quo
  218. command += "'" + driver_input.image_hash
  219. return command + "\n"
  220. def _read_first_block(self, deadline):
  221. # returns (text_content, audio_content)
  222. block = self._read_block(deadline)
  223. if block.content_type == 'audio/wav':
  224. return (None, block.decoded_content)
  225. return (block.decoded_content, None)
  226. def _read_optional_image_block(self, deadline):
  227. # returns (image, actual_image_hash)
  228. block = self._read_block(deadline, wait_for_stderr_eof=True)
  229. if block.content and block.content_type == 'image/png':
  230. return (block.decoded_content, block.content_hash)
  231. return (None, block.content_hash)
  232. def _process_driver_input(self, driver_input):
  233. test_set = []
  234. for test in driver_input:
  235. test_set.append(test.test_name)
  236. return test_set
  237. def run_multiple_tests(self, driver_input, stop_when_done):
  238. text = []
  239. num_tests = len(driver_input)
  240. start_time = time.time()
  241. if not self._server_process:
  242. self._start(driver_input[0].should_run_pixel_test, driver_input[0].args)
  243. self.error_from_test = str()
  244. self.err_seen_eof = False
  245. image = None
  246. actual_image_hash = None
  247. # Check the startup time of the Orbis driver. Currently a method returning
  248. # a fixed value is used, in the future we can improve this at runtime
  249. # by executing a dummy run of WebKitTestRunnerOrbis
  250. time_to_sleep = self._port._startup_time() + (self._port._execution_time()* float(num_tests))
  251. #To support PSOrbis the path to the driver is returned as the full path to
  252. #WebKitTestRunnerOrbis.self include Orbis-run /c:process /elf
  253. driver = self.cmd_line(self._port.get_option('pixel_tests'), [])
  254. #To provide the options [--timeout],[--pixeltest]
  255. 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)
  256. #combine driver path + test options + Test case file path [*.html]
  257. test_set = self._process_driver_input(driver_input)
  258. for test in test_set:
  259. command += ' ' + '/hostapp/LayoutTests/' + test
  260. command += '\n'
  261. _log.debug( "command = %s" % command)
  262. deadline = start_time + int(driver_input[0].timeout) / 1000.0
  263. # Bug33130, Adding sleep to avoid process creation error caused by
  264. # repeatedly invoking orbis-run
  265. self._server_process.write(command)
  266. for t in range(0,num_tests):
  267. self.err_seen_eof = False
  268. cur_text, audio = self._read_first_block(deadline) # First block is either text or audio
  269. if not cur_text and self._server_process.timed_out:
  270. break
  271. image, actual_image_hash = self._read_optional_image_block(deadline)
  272. text.append(cur_text)
  273. num_tests_ran = len(text)
  274. # We may not have read all of the output if an error (crash) occured.
  275. # Since some platforms output the stacktrace over error, we should
  276. # dump any buffered error into self.error_from_test.
  277. # FIXME: We may need to also read stderr until the process dies?
  278. self.error_from_test += self._server_process.pop_all_buffered_stderr()
  279. crash_log = ''
  280. if self.has_crashed():
  281. crash_log = self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, text[0], self.error_from_test,
  282. newer_than=start_time)
  283. driver_output = []
  284. timeout = self._server_process.timed_out
  285. if timeout:
  286. _log.info("\nTimeout: Num of tests returned: %d out of %d" % (num_tests_ran, num_tests))
  287. for x in range(0, num_tests_ran):
  288. _log.debug('')
  289. # Handle the tests that returned before the timeout
  290. driver_output.append(DriverOutput(text[x].lstrip(), image, actual_image_hash, audio,
  291. crash=self.has_crashed(), test_time=time.time() - start_time,
  292. timeout=False, error=self.error_from_test,
  293. crashed_process_name=self._crashed_process_name,
  294. crashed_pid=self._crashed_pid, crash_log=crash_log))
  295. for x in range(num_tests_ran, num_tests):
  296. text = ''
  297. driver_output.append(DriverOutput(text.lstrip(), image, actual_image_hash, audio,
  298. crash=self.has_crashed(), test_time=time.time() - start_time,
  299. timeout=timeout, error=self.error_from_test,
  300. crashed_process_name=self._crashed_process_name,
  301. crashed_pid=self._crashed_pid, crash_log=crash_log))
  302. return driver_output, num_tests_ran
  303. for x in range (0, num_tests):
  304. driver_output.append(DriverOutput(text[x].lstrip(), image, actual_image_hash, audio,
  305. crash=self.has_crashed(), test_time=time.time() - start_time,
  306. timeout=timeout, error=self.error_from_test,
  307. crashed_process_name=self._crashed_process_name,
  308. crashed_pid=self._crashed_pid, crash_log=crash_log))
  309. return driver_output, num_tests_ran
  310. def run_test(self, driver_input, stop_when_done):
  311. start_time = time.time()
  312. if not self._server_process:
  313. self._start(driver_input.should_run_pixel_test, driver_input.args)
  314. self.error_from_test = str()
  315. self.err_seen_eof = False
  316. time_to_sleep = float(self._port._startup_time()) + float(self._port._execution_time())
  317. #To support PSOrbis the path to the driver is returned as the full path to
  318. #WebKitTestRunnerOrbis.self include Orbis-run /c:process /elf
  319. driver = self.cmd_line(self._port.get_option('pixel_tests'), [])
  320. #To provide the options [--timeout],[--pixeltest]
  321. command = self._test_shell_command(driver_input.should_run_pixel_test,driver, driver_input.timeout, driver_input.image_hash)
  322. #combine driver path + test options + Test case file path [*.html]
  323. command += ' ' + '/hostapp/LayoutTests/' + self._command_from_driver_input(driver_input)
  324. _log.debug( "command = %s" % command)
  325. deadline = start_time + int(driver_input.timeout) / 1000.0
  326. self._server_process.write(command)
  327. text, audio = self._read_first_block(deadline) # First block is either text or audio
  328. image, actual_image_hash = self._read_optional_image_block(deadline) # The second (optional) block is image data.
  329. # We may not have read all of the output if an error (crash) occured.
  330. # Since some platforms output the stacktrace over error, we should
  331. # dump any buffered error into self.error_from_test.
  332. # FIXME: We may need to also read stderr until the process dies?
  333. self.error_from_test += self._server_process.pop_all_buffered_stderr()
  334. crash_log = ''
  335. if self.has_crashed():
  336. crash_log = self._port._get_crash_log(self._crashed_process_name, self._crashed_pid, text, self.error_from_test,
  337. newer_than=start_time)
  338. timeout = self._server_process.timed_out
  339. # if timeout:
  340. #DRT doesn't have a built in timer to abort the test, so we might as well
  341. #kill the process directly and not wait for it to shut down cleanly (since it may not).
  342. # self._server_process.kill()
  343. return DriverOutput(text.lstrip(), image, actual_image_hash, audio,
  344. crash=self.has_crashed(), test_time=time.time() - start_time,
  345. timeout=timeout, error=self.error_from_test,
  346. crashed_process_name=self._crashed_process_name,
  347. crashed_pid=self._crashed_pid, crash_log=crash_log)
  348. def _read_header(self, block, line, header_text, header_attr, header_filter=None):
  349. if line.startswith(header_text) and getattr(block, header_attr) is None:
  350. value = line.split()[1]
  351. if header_filter:
  352. value = header_filter(value)
  353. setattr(block, header_attr, value)
  354. return True
  355. return False
  356. def _process_stdout_line(self, block, line):
  357. if (self._read_header(block, line, 'Content-Type: ', 'content_type')
  358. or self._read_header(block, line, 'Content-Transfer-Encoding: ', 'encoding')
  359. or self._read_header(block, line, 'Content-Length: ', '_content_length', int)
  360. or self._read_header(block, line, 'ActualHash: ', 'content_hash')):
  361. return
  362. #Specfic handling for PSOrbis as this comes for each test cases in the stdout
  363. if ('Load libSce' in line
  364. or 'Microsoft Windows' in line
  365. or 'Microsoft Corporation' in line
  366. or 'orbis-run' in line):
  367. return
  368. # Note, we're not reading ExpectedHash: here, but we could.
  369. # If the line wasn't a header, we just append it to the content.
  370. block.content += line
  371. def _strip_eof(self, line):
  372. if line and line.endswith("#EOF\n"):
  373. return line[:-5], True
  374. return line, False
  375. def _read_block(self, deadline, wait_for_stderr_eof=False):
  376. block = ContentBlock()
  377. out_seen_eof = False
  378. while not self.has_crashed():
  379. if out_seen_eof and (self.err_seen_eof or not wait_for_stderr_eof):
  380. break
  381. if self.err_seen_eof:
  382. out_line = self._server_process.read_stdout_line(deadline)
  383. err_line = None
  384. elif out_seen_eof:
  385. out_line = None
  386. err_line = self._server_process.read_stderr_line(deadline)
  387. else:
  388. out_line, err_line = self._server_process.read_either_stdout_or_stderr_line(deadline)
  389. if self._server_process.timed_out or self.has_crashed():
  390. break
  391. if out_line:
  392. assert not out_seen_eof
  393. out_line, out_seen_eof = self._strip_eof(out_line)
  394. if err_line:
  395. assert not self.err_seen_eof
  396. err_line, self.err_seen_eof = self._strip_eof(err_line)
  397. if out_line:
  398. if out_line[-1] != "\n":
  399. _log.error("Last character read from DRT stdout line was not a newline! This indicates either a NRWT or DRT bug.")
  400. content_length_before_header_check = block._content_length
  401. self._process_stdout_line(block, out_line)
  402. # FIXME: Unlike HTTP, DRT dumps the content right after printing a Content-Length header.
  403. # Don't wait until we're done with headers, just read the binary blob right now.
  404. if content_length_before_header_check != block._content_length:
  405. block.content = self._server_process.read_stdout(deadline, block._content_length)
  406. if err_line:
  407. if self._check_for_driver_crash(err_line):
  408. break
  409. self.error_from_test += err_line
  410. block.decode_content()
  411. return block
  412. def start(self, pixel_tests, per_test_args):
  413. if not self._server_process:
  414. self._start(pixel_tests, per_test_args)
  415. def stop(self):
  416. if self._server_process:
  417. self._rebootorbis()
  418. self._server_process.stop()
  419. self._server_process = None
  420. def _rebootorbis(self):
  421. if self._server_process:
  422. if self.has_crashed():
  423. _log.debug( "crashed...reboot! wait...")
  424. command = 'orbis-ctrl reboot \n'
  425. self._server_process.write(command)
  426. #time delay for Orbis to complete reboot
  427. time.sleep(120)
  428. def _pause_run(self):
  429. if (self.num_tests % REBOOT_AFTER_NUM_TESTS) == 0:
  430. self._reboot_required = True
  431. else:
  432. # Bug33130, Adding sleep to avoid process creation error caused by
  433. # repeatedly invoking orbis-run
  434. time.sleep(9)
  435. class ContentBlock(object):
  436. def __init__(self):
  437. self.content_type = None
  438. self.encoding = None
  439. self.content_hash = None
  440. self._content_length = None
  441. # Content is treated as binary data even though the text output is usually UTF-8.
  442. self.content = str() # FIXME: Should be bytearray() once we require Python 2.6.
  443. self.decoded_content = None
  444. def decode_content(self):
  445. if self.encoding == 'base64':
  446. self.decoded_content = base64.b64decode(self.content)
  447. else:
  448. self.decoded_content = self.content