3 # Copyright (C) 2011, 2012, 2017 Igalia S.L.
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.
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.
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.
25 from signal import alarm, signal, SIGALRM, SIGKILL, SIGSEGV
27 top_level_directory = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
28 sys.path.insert(0, os.path.join(top_level_directory, "Tools", "glib"))
30 from webkitpy.common.host import Host
31 from webkitpy.common.test_expectations import TestExpectations
34 class TestTimeout(Exception):
38 class TestRunner(object):
41 def __init__(self, port, options, tests=[]):
42 self._options = options
44 self._build_type = "Debug" if self._options.debug else "Release"
45 common.set_build_types((self._build_type,))
46 self._port = Host().port_factory.get(port)
47 self._driver = self._create_driver()
49 self._programs_path = common.binary_build_path()
50 expectations_file = os.path.join(common.top_level_path(), "Tools", "TestWebKitAPI", "glib", "TestExpectations.json")
51 self._expectations = TestExpectations(self._port.name(), expectations_file, self._build_type)
52 self._tests = self._get_tests(tests)
53 self._disabled_tests = []
55 def _test_programs_base_dir(self):
56 return os.path.join(self._programs_path, "TestWebKitAPI")
58 def _get_tests_from_dir(self, test_dir):
59 if not os.path.isdir(test_dir):
63 for test_file in os.listdir(test_dir):
64 if not test_file.lower().startswith("test"):
66 test_path = os.path.join(test_dir, test_file)
67 if os.path.isfile(test_path) and os.access(test_path, os.X_OK):
68 tests.append(test_path)
71 def _get_tests(self, initial_tests):
73 for test in initial_tests:
74 if os.path.isdir(test):
75 tests.extend(self._get_tests_from_dir(test))
82 for test_dir in self.TEST_DIRS:
83 absolute_test_dir = os.path.join(self._test_programs_base_dir(), test_dir)
84 tests.extend(self._get_tests_from_dir(absolute_test_dir))
87 def _create_driver(self, port_options=[]):
88 self._port._display_server = self._options.display_server
89 driver = self._port.create_driver(worker_number=0, no_timeout=True)._make_driver(pixel_tests=False)
90 if not driver.check_driver(self._port):
91 raise RuntimeError("Failed to check driver %s" % driver.__class__.__name__)
94 def _setup_testing_environment(self):
95 self._test_env = self._driver._setup_environ_for_test()
96 self._test_env["TEST_WEBKIT_API_WEBKIT2_RESOURCES_PATH"] = common.top_level_path("Tools", "TestWebKitAPI", "Tests", "WebKit")
97 self._test_env["TEST_WEBKIT_API_WEBKIT2_INJECTED_BUNDLE_PATH"] = common.library_build_path()
98 self._test_env["WEBKIT_EXEC_PATH"] = self._programs_path
102 def _tear_down_testing_environment(self):
106 def _test_cases_to_skip(self, test_program):
107 if self._options.skipped_action != 'skip':
110 return self._expectations.skipped_subtests(os.path.basename(test_program))
112 def _should_run_test_program(self, test_program):
113 for disabled_test in self._disabled_tests:
114 if test_program.endswith(disabled_test):
117 if self._options.skipped_action != 'skip':
120 return os.path.basename(test_program) not in self._expectations.skipped_tests()
122 def _kill_process(self, pid):
124 os.kill(pid, SIGKILL)
126 # Process already died.
130 def _start_timeout(timeout):
134 def _alarm_handler(signum, frame):
137 signal(SIGALRM, _alarm_handler)
141 def _stop_timeout(timeout):
147 def _waitpid(self, pid):
150 dummy, status = os.waitpid(pid, 0)
151 if os.WIFSIGNALED(status):
152 return -os.WTERMSIG(status)
153 if os.WIFEXITED(status):
154 return os.WEXITSTATUS(status)
156 # Should never happen
157 raise RuntimeError("Unknown child exit status!")
158 except (OSError, IOError) as e:
159 if e.errno == errno.EINTR:
161 if e.errno == errno.ECHILD:
162 # This happens if SIGCLD is set to be ignored or waiting
163 # for child processes has otherwise been disabled for our
164 # process. This child is dead, we can't get the status.
168 def _run_test_glib(self, test_program):
169 command = ['gtester', '-k']
170 if self._options.verbose:
171 command.append('--verbose')
172 for test_case in self._test_cases_to_skip(test_program):
173 command.extend(['-s', test_case])
174 command.append(test_program)
176 timeout = self._options.timeout
177 test = os.path.join(os.path.basename(os.path.dirname(test_program)), os.path.basename(test_program))
178 if self._expectations.is_slow(os.path.basename(test_program)):
181 test_context = {"child-pid": -1, "did-timeout": False, "current_test": None}
183 def parse_line(line, test_context=test_context):
187 match = re.search(r'\(pid=(?P<child_pid>[0-9]+)\)', line)
189 test_context["child-pid"] = int(match.group('child_pid'))
190 sys.stdout.write(line)
193 def set_test_result(test, result):
195 if test_context["did-timeout"] and result == "FAIL":
196 test_context[test] = "TIMEOUT"
198 test_context[test] = result
200 test_context[test] = 'PASS'
201 test_context["did-timeout"] = False
202 test_context["current_test"] = None
203 self._stop_timeout(timeout)
204 self._start_timeout(timeout)
206 normalized_line = line.strip().replace(' ', '')
207 if not normalized_line:
210 if normalized_line[0] == '/':
211 test, result = normalized_line.split(':', 1)
212 if result in ["OK", "FAIL"]:
213 set_test_result(test, result)
215 test_context["current_test"] = test
216 elif normalized_line in ["OK", "FAIL"]:
217 set_test_result(test_context["current_test"], normalized_line)
219 sys.stdout.write(line)
221 pid, fd = os.forkpty()
223 os.execvpe(command[0], command, self._test_env)
226 self._start_timeout(timeout)
230 common.parse_output_lines(fd, parse_line)
233 assert test_context["child-pid"] > 0
234 self._kill_process(test_context["child-pid"])
235 test_context["child-pid"] = -1
236 test_context["did-timeout"] = True
238 self._stop_timeout(timeout)
239 del test_context["child-pid"]
240 del test_context["did-timeout"]
241 del test_context["current_test"]
246 def _get_tests_from_google_test_suite(self, test_program):
248 output = subprocess.check_output([test_program, '--gtest_list_tests'], env=self._test_env)
249 except subprocess.CalledProcessError:
250 sys.stderr.write("ERROR: could not list available tests for binary %s.\n" % (test_program))
254 skipped_test_cases = self._test_cases_to_skip(test_program)
258 for line in output.split('\n'):
259 if not line.startswith(' '):
263 test_name = prefix + line.strip()
264 if not test_name in skipped_test_cases:
265 tests.append(test_name)
268 def _run_google_test(self, test_program, subtest):
269 command = [test_program, '--gtest_filter=%s' % (subtest)]
270 timeout = self._options.timeout
271 if self._expectations.is_slow(os.path.basename(test_program), subtest):
274 pid, fd = os.forkpty()
276 os.execvpe(command[0], command, self._test_env)
279 self._start_timeout(timeout)
281 common.parse_output_lines(fd, sys.stdout.write)
282 status = self._waitpid(pid)
284 self._kill_process(pid)
285 return {subtest: "TIMEOUT"}
287 self._stop_timeout(timeout)
289 if status == -SIGSEGV:
290 sys.stdout.write("**CRASH** %s\n" % subtest)
292 return {subtest: "CRASH"}
295 return {subtest: "FAIL"}
297 return {subtest: "PASS"}
299 def _run_google_test_suite(self, test_program):
301 for subtest in self._get_tests_from_google_test_suite(test_program):
302 result.update(self._run_google_test(test_program, subtest))
305 def is_glib_test(self, test_program):
306 raise NotImplementedError
308 def is_google_test(self, test_program):
309 raise NotImplementedError
311 def _run_test(self, test_program):
312 if self.is_glib_test(test_program):
313 return self._run_test_glib(test_program)
315 if self.is_google_test(test_program):
316 return self._run_google_test_suite(test_program)
322 sys.stderr.write("ERROR: tests not found in %s.\n" % (self._test_programs_base_dir()))
326 if not self._setup_testing_environment():
329 # Remove skipped tests now instead of when we find them, because
330 # some tests might be skipped while setting up the test environment.
331 self._tests = [test for test in self._tests if self._should_run_test_program(test)]
338 for test in self._tests:
339 results = self._run_test(test)
340 for test_case, result in results.iteritems():
341 if result in self._expectations.get_expectation(os.path.basename(test), test_case):
345 failed_tests.setdefault(test, []).append(test_case)
346 elif result == "TIMEOUT":
347 timed_out_tests.setdefault(test, []).append(test_case)
348 elif result == "CRASH":
349 crashed_tests.setdefault(test, []).append(test_case)
350 elif result == "PASS":
351 passed_tests.setdefault(test, []).append(test_case)
353 self._tear_down_testing_environment()
355 def report(tests, title, base_dir):
358 sys.stdout.write("\nUnexpected %s (%d)\n" % (title, sum(len(value) for value in tests.itervalues())))
360 sys.stdout.write(" %s\n" % (test.replace(base_dir, '', 1)))
361 for test_case in tests[test]:
362 sys.stdout.write(" %s\n" % (test_case))
365 report(failed_tests, "failures", self._test_programs_base_dir())
366 report(crashed_tests, "crashes", self._test_programs_base_dir())
367 report(timed_out_tests, "timeouts", self._test_programs_base_dir())
368 report(passed_tests, "passes", self._test_programs_base_dir())
370 return len(failed_tests) + len(timed_out_tests)
373 def add_options(option_parser):
374 option_parser.add_option('-r', '--release',
375 action='store_true', dest='release',
376 help='Run in Release')
377 option_parser.add_option('-d', '--debug',
378 action='store_true', dest='debug',
380 option_parser.add_option('-v', '--verbose',
381 action='store_true', dest='verbose',
382 help='Run gtester in verbose mode')
383 option_parser.add_option('--skipped', action='store', dest='skipped_action',
384 choices=['skip', 'ignore', 'only'], default='skip',
385 metavar='skip|ignore|only',
386 help='Specifies how to treat the skipped tests')
387 option_parser.add_option('-t', '--timeout',
388 action='store', type='int', dest='timeout', default=10,
389 help='Time in seconds until a test times out')