run-gtk-tests 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (C) 2011, 2012 Igalia S.L.
  4. #
  5. # This library is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU Library General Public
  7. # License as published by the Free Software Foundation; either
  8. # version 2 of the License, or (at your option) any later version.
  9. #
  10. # This library is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. # Library General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Library General Public License
  16. # along with this library; see the file COPYING.LIB. If not, write to
  17. # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  18. # Boston, MA 02110-1301, USA.
  19. import subprocess
  20. import os
  21. import sys
  22. import optparse
  23. import re
  24. from signal import alarm, signal, SIGALRM, SIGKILL
  25. from gi.repository import Gio, GLib
  26. top_level_directory = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
  27. sys.path.append(os.path.join(top_level_directory, "Tools", "jhbuild"))
  28. sys.path.append(os.path.join(top_level_directory, "Tools", "gtk"))
  29. import common
  30. import jhbuildutils
  31. class SkippedTest:
  32. ENTIRE_SUITE = None
  33. def __init__(self, test, test_case, reason, bug=None):
  34. self.test = test
  35. self.test_case = test_case
  36. self.reason = reason
  37. self.bug = bug
  38. def __str__(self):
  39. skipped_test_str = "%s" % self.test
  40. if not(self.skip_entire_suite()):
  41. skipped_test_str += " [%s]" % self.test_case
  42. skipped_test_str += ": %s " % self.reason
  43. if self.bug is not None:
  44. skipped_test_str += "(https://bugs.webkit.org/show_bug.cgi?id=%d)" % self.bug
  45. return skipped_test_str
  46. def skip_entire_suite(self):
  47. return self.test_case == SkippedTest.ENTIRE_SUITE
  48. class TestTimeout(Exception):
  49. pass
  50. class TestRunner:
  51. TEST_DIRS = [ "unittests", "WebKit2APITests", "TestWebKitAPI" ]
  52. SKIPPED = [
  53. SkippedTest("unittests/testdownload", "/webkit/download/not-found", "Test fails in GTK Linux 64-bit Release bot", 82329),
  54. SkippedTest("unittests/testwebinspector", "/webkit/webinspector/close-and-inspect", "Test is flaky in GTK Linux 32-bit Release bot", 82869),
  55. SkippedTest("unittests/testwebresource", "/webkit/webresource/loading", "Test fails", 104689),
  56. SkippedTest("unittests/testwebresource", "/webkit/webresource/sub_resource_loading", "Test fails in GTK Linux 64-bit Release bot", 82330),
  57. SkippedTest("unittests/testwebview", "/webkit/webview/icon-uri", "Test times out in GTK Linux 64-bit Release bot", 82328),
  58. SkippedTest("unittests/testatk", "/webkit/atk/getTextInParagraphAndBodyModerate", "Test fails", 105538),
  59. SkippedTest("WebKit2APITests/TestInspectorServer", SkippedTest.ENTIRE_SUITE, "Test times out", 105866),
  60. SkippedTest("WebKit2APITests/TestResources", "/webkit2/WebKitWebView/resources", "Test is flaky in GTK Linux 32-bit Release bot", 82868),
  61. SkippedTest("WebKit2APITests/TestWebKitAccessibility", "/webkit2/WebKitAccessibility/atspi-basic-hierarchy", "Test fails", 100408),
  62. SkippedTest("WebKit2APITests/TestWebKitWebView", SkippedTest.ENTIRE_SUITE, "Test times out after r150890", 117689),
  63. SkippedTest("WebKit2APITests/TestContextMenu", SkippedTest.ENTIRE_SUITE, "Test times out after r150890", 117689),
  64. SkippedTest("WebKit2APITests/TestWebKitFaviconDatabase", SkippedTest.ENTIRE_SUITE, "Test times out", 117690),
  65. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.CanHandleRequest", "Test fails", 88453),
  66. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.MouseMoveAfterCrash", "Test is flaky", 85066),
  67. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.NewFirstVisuallyNonEmptyLayoutForImages", "Test is flaky", 85066),
  68. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.NewFirstVisuallyNonEmptyLayoutFrames", "Test fails", 85037),
  69. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.RestoreSessionStateContainingFormData", "Session State is not implemented in GTK+ port", 84960),
  70. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.SpacebarScrolling", "Test fails", 84961),
  71. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.WKConnection", "Tests fail and time out out", 84959),
  72. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.WKPageGetScaleFactorNotZero", "Test fails and times out", 88455),
  73. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.ForceRepaint", "Test times out", 105532),
  74. SkippedTest("TestWebKitAPI/TestWebKit2", "WebKit2.ReloadPageAfterCrash", "Test flakily times out", 110129),
  75. ]
  76. def __init__(self, options, tests=[]):
  77. self._options = options
  78. self._build_type = "Debug" if self._options.debug else "Release"
  79. self._programs_path = common.build_path_for_build_types((self._build_type,), "Programs")
  80. self._tests = self._get_tests(tests)
  81. self._skipped_tests = TestRunner.SKIPPED
  82. if not sys.stdout.isatty():
  83. self._tty_colors_pattern = re.compile("\033\[[0-9;]*m")
  84. # These SPI daemons need to be active for the accessibility tests to work.
  85. self._spi_registryd = None
  86. self._spi_bus_launcher = None
  87. def _get_tests(self, tests):
  88. if tests:
  89. return tests
  90. tests = []
  91. for test_dir in self.TEST_DIRS:
  92. absolute_test_dir = os.path.join(self._programs_path, test_dir)
  93. if not os.path.isdir(absolute_test_dir):
  94. continue
  95. for test_file in os.listdir(absolute_test_dir):
  96. if not test_file.lower().startswith("test"):
  97. continue
  98. test_path = os.path.join(self._programs_path, test_dir, test_file)
  99. if os.path.isfile(test_path) and os.access(test_path, os.X_OK):
  100. tests.append(test_path)
  101. return tests
  102. def _lookup_atspi2_binary(self, filename):
  103. exec_prefix = common.pkg_config_file_variable('atspi-2', 'exec_prefix')
  104. if not exec_prefix:
  105. return None
  106. for path in ['libexec', 'lib/at-spi2-core', 'lib32/at-spi2-core', 'lib64/at-spi2-core']:
  107. filepath = os.path.join(exec_prefix, path, filename)
  108. if os.path.isfile(filepath):
  109. return filepath
  110. return None
  111. def _start_accessibility_daemons(self):
  112. spi_bus_launcher_path = self._lookup_atspi2_binary('at-spi-bus-launcher')
  113. spi_registryd_path = self._lookup_atspi2_binary('at-spi2-registryd')
  114. if not spi_bus_launcher_path or not spi_registryd_path:
  115. return False
  116. try:
  117. self._ally_bus_launcher = subprocess.Popen([spi_bus_launcher_path], env=self._test_env)
  118. except:
  119. sys.stderr.write("Failed to launch the accessibility bus\n")
  120. sys.stderr.flush()
  121. return False
  122. # We need to wait until the SPI bus is launched before trying to start the SPI
  123. # registry, so we spin a main loop until the bus name appears on DBus.
  124. loop = GLib.MainLoop()
  125. Gio.bus_watch_name(Gio.BusType.SESSION, 'org.a11y.Bus', Gio.BusNameWatcherFlags.NONE,
  126. lambda *args: loop.quit(), None)
  127. loop.run()
  128. try:
  129. self._spi_registryd = subprocess.Popen([spi_registryd_path], env=self._test_env)
  130. except:
  131. sys.stderr.write("Failed to launch the accessibility registry\n")
  132. sys.stderr.flush()
  133. return False
  134. return True
  135. def _setup_testing_environment(self):
  136. self._test_env = os.environ
  137. self._test_env["DISPLAY"] = self._options.display
  138. self._test_env["WEBKIT_INSPECTOR_PATH"] = os.path.abspath(os.path.join(self._programs_path, 'resources', 'inspector'))
  139. self._test_env['GSETTINGS_BACKEND'] = 'memory'
  140. self._test_env["TEST_WEBKIT_API_WEBKIT2_RESOURCES_PATH"] = common.top_level_path("Tools", "TestWebKitAPI", "Tests", "WebKit2")
  141. self._test_env["TEST_WEBKIT_API_WEBKIT2_INJECTED_BUNDLE_PATH"] = common.build_path_for_build_types((self._build_type,), "Libraries")
  142. self._test_env["WEBKIT_EXEC_PATH"] = self._programs_path
  143. try:
  144. self._xvfb = subprocess.Popen(["Xvfb", self._options.display, "-screen", "0", "800x600x24", "-nolisten", "tcp"],
  145. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  146. except Exception as e:
  147. sys.stderr.write("Failed to run Xvfb: %s\n" % e)
  148. sys.stderr.flush()
  149. return False
  150. # If we cannot start the accessibility daemons, we can just skip the accessibility tests.
  151. if not self._start_accessibility_daemons():
  152. print "Could not start accessibility bus, so skipping TestWebKitAccessibility"
  153. self._skipped_tests.append(SkippedTest("WebKit2APITests/TestWebKitAccessibility", SkippedTest.ENTIRE_SUITE, "Could not start accessibility bus"))
  154. return True
  155. def _tear_down_testing_environment(self):
  156. if self._spi_registryd:
  157. self._spi_registryd.terminate()
  158. if self._spi_bus_launcher:
  159. self._spi_bus_launcher.terminate()
  160. self._xvfb.terminate()
  161. def _test_cases_to_skip(self, test_program):
  162. if self._options.skipped_action != 'skip':
  163. return []
  164. test_cases = []
  165. for skipped in self._skipped_tests:
  166. if test_program.endswith(skipped.test) and not skipped.skip_entire_suite():
  167. test_cases.append(skipped.test_case)
  168. return test_cases
  169. def _should_run_test_program(self, test_program):
  170. # This is not affected by the command-line arguments, since programs are skipped for
  171. # problems in the harness, such as failing to start the accessibility bus.
  172. for skipped in self._skipped_tests:
  173. if test_program.endswith(skipped.test) and skipped.skip_entire_suite():
  174. return False
  175. return True
  176. def _get_child_pid_from_test_output(self, output):
  177. if not output:
  178. return -1
  179. match = re.search(r'\(pid=(?P<child_pid>[0-9]+)\)', output)
  180. if not match:
  181. return -1
  182. return int(match.group('child_pid'))
  183. def _kill_process(self, pid):
  184. try:
  185. os.kill(pid, SIGKILL)
  186. except OSError:
  187. # Process already died.
  188. pass
  189. def _run_test_command(self, command, timeout=-1):
  190. def alarm_handler(signum, frame):
  191. raise TestTimeout
  192. child_pid = [-1]
  193. def parse_line(line, child_pid = child_pid):
  194. if child_pid[0] == -1:
  195. child_pid[0] = self._get_child_pid_from_test_output(line)
  196. if sys.stdout.isatty():
  197. sys.stdout.write(line)
  198. else:
  199. sys.stdout.write(self._tty_colors_pattern.sub('', line.replace('\r', '')))
  200. def waitpid(pid):
  201. while True:
  202. try:
  203. return os.waitpid(pid, 0)
  204. except (OSError, IOError) as e:
  205. if e.errno == errno.EINTR:
  206. continue
  207. raise
  208. def return_code_from_exit_status(status):
  209. if os.WIFSIGNALED(status):
  210. return -os.WTERMSIG(status)
  211. elif os.WIFEXITED(status):
  212. return os.WEXITSTATUS(status)
  213. else:
  214. # Should never happen
  215. raise RuntimeError("Unknown child exit status!")
  216. pid, fd = os.forkpty()
  217. if pid == 0:
  218. os.execvpe(command[0], command, self._test_env)
  219. sys.exit(0)
  220. if timeout > 0:
  221. signal(SIGALRM, alarm_handler)
  222. alarm(timeout)
  223. try:
  224. common.parse_output_lines(fd, parse_line)
  225. if timeout > 0:
  226. alarm(0)
  227. except TestTimeout:
  228. self._kill_process(pid)
  229. if child_pid[0] > 0:
  230. self._kill_process(child_pid[0])
  231. raise
  232. try:
  233. dummy, status = waitpid(pid)
  234. except OSError as e:
  235. if e.errno != errno.ECHILD:
  236. raise
  237. # This happens if SIGCLD is set to be ignored or waiting
  238. # for child processes has otherwise been disabled for our
  239. # process. This child is dead, we can't get the status.
  240. status = 0
  241. return not return_code_from_exit_status(status)
  242. def _run_test_glib(self, test_program):
  243. tester_command = ['gtester']
  244. if self._options.verbose:
  245. tester_command.append('--verbose')
  246. for test_case in self._test_cases_to_skip(test_program):
  247. tester_command.extend(['-s', test_case])
  248. tester_command.append(test_program)
  249. return self._run_test_command(tester_command, self._options.timeout)
  250. def _run_test_google(self, test_program):
  251. tester_command = [test_program]
  252. skipped_tests_cases = self._test_cases_to_skip(test_program)
  253. if skipped_tests_cases:
  254. tester_command.append("--gtest_filter=-%s" % ":".join(skipped_tests_cases))
  255. return self._run_test_command(tester_command, self._options.timeout)
  256. def _run_test(self, test_program):
  257. if "unittests" in test_program or "WebKit2APITests" in test_program:
  258. return self._run_test_glib(test_program)
  259. if "TestWebKitAPI" in test_program:
  260. return self._run_test_google(test_program)
  261. return False
  262. def run_tests(self):
  263. if not self._tests:
  264. sys.stderr.write("ERROR: tests not found in %s.\n" % (self._programs_path))
  265. sys.stderr.flush()
  266. return 1
  267. if not self._setup_testing_environment():
  268. return 1
  269. # Remove skipped tests now instead of when we find them, because
  270. # some tests might be skipped while setting up the test environment.
  271. self._tests = [test for test in self._tests if self._should_run_test_program(test)]
  272. failed_tests = []
  273. timed_out_tests = []
  274. try:
  275. for test in self._tests:
  276. success = True
  277. try:
  278. success = self._run_test(test)
  279. except TestTimeout:
  280. sys.stdout.write("TEST: %s: TIMEOUT\n" % test)
  281. sys.stdout.flush()
  282. timed_out_tests.append(test)
  283. if not success:
  284. failed_tests.append(test)
  285. finally:
  286. self._tear_down_testing_environment()
  287. if failed_tests:
  288. names = [test.replace(self._programs_path, '', 1) for test in failed_tests]
  289. sys.stdout.write("Tests failed (%d): %s\n" % (len(names), ", ".join(names)))
  290. sys.stdout.flush()
  291. if timed_out_tests:
  292. names = [test.replace(self._programs_path, '', 1) for test in timed_out_tests]
  293. sys.stdout.write("Tests that timed out (%d): %s\n" % (len(names), ", ".join(names)))
  294. sys.stdout.flush()
  295. if self._skipped_tests and self._options.skipped_action == 'skip':
  296. sys.stdout.write("Tests skipped (%d):\n%s\n" %
  297. (len(self._skipped_tests),
  298. "\n".join([str(skipped) for skipped in self._skipped_tests])))
  299. sys.stdout.flush()
  300. return len(failed_tests) + len(timed_out_tests)
  301. if __name__ == "__main__":
  302. if not jhbuildutils.enter_jhbuild_environment_if_available("gtk"):
  303. print "***"
  304. print "*** Warning: jhbuild environment not present. Run update-webkitgtk-libs before build-webkit to ensure proper testing."
  305. print "***"
  306. option_parser = optparse.OptionParser(usage='usage: %prog [options] [test...]')
  307. option_parser.add_option('-r', '--release',
  308. action='store_true', dest='release',
  309. help='Run in Release')
  310. option_parser.add_option('-d', '--debug',
  311. action='store_true', dest='debug',
  312. help='Run in Debug')
  313. option_parser.add_option('-v', '--verbose',
  314. action='store_true', dest='verbose',
  315. help='Run gtester in verbose mode')
  316. option_parser.add_option('--display', action='store', dest='display', default=':55',
  317. help='Display to run Xvfb')
  318. option_parser.add_option('--skipped', action='store', dest='skipped_action',
  319. choices=['skip', 'ignore', 'only'], default='skip',
  320. metavar='skip|ignore|only',
  321. help='Specifies how to treat the skipped tests')
  322. option_parser.add_option('-t', '--timeout',
  323. action='store', type='int', dest='timeout', default=10,
  324. help='Time in seconds until a test times out')
  325. options, args = option_parser.parse_args()
  326. sys.exit(TestRunner(options, args).run_tests())