Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Side by Side Diff: third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py

Issue 2582293004: Remove use of wdiff from layout test runner. (Closed)
Patch Set: Rebased Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright (C) 2010 Google Inc. All rights reserved. 1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 # 2 #
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer 10 # copyright notice, this list of conditions and the following disclaimer
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
169 169
170 self._http_server = None 170 self._http_server = None
171 self._websocket_server = None 171 self._websocket_server = None
172 self._is_wptserve_enabled = getattr(options, 'enable_wptserve', False) 172 self._is_wptserve_enabled = getattr(options, 'enable_wptserve', False)
173 self._wpt_server = None 173 self._wpt_server = None
174 self._image_differ = None 174 self._image_differ = None
175 self.server_process_constructor = server_process.ServerProcess # overri dable for testing 175 self.server_process_constructor = server_process.ServerProcess # overri dable for testing
176 self._http_lock = None # FIXME: Why does this live on the port object? 176 self._http_lock = None # FIXME: Why does this live on the port object?
177 self._dump_reader = None 177 self._dump_reader = None
178 178
179 # Python's Popen has a bug that causes any pipes opened to a
180 # process that can't be executed to be leaked. Since this
181 # code is specifically designed to tolerate exec failures
182 # to gracefully handle cases where wdiff is not installed,
183 # the bug results in a massive file descriptor leak. As a
184 # workaround, if an exec failure is ever experienced for
185 # wdiff, assume it's not available. This will leak one
186 # file descriptor but that's better than leaking each time
187 # wdiff would be run.
188 #
189 # http://mail.python.org/pipermail/python-list/
190 # 2008-August/505753.html
191 # http://bugs.python.org/issue3210
192 self._wdiff_available = None
193
194 # FIXME: prettypatch.py knows this path, why is it copied here? 179 # FIXME: prettypatch.py knows this path, why is it copied here?
195 self._pretty_patch_path = self.path_from_webkit_base("Tools", "Scripts", "webkitruby", "PrettyPatch", "prettify.rb") 180 self._pretty_patch_path = self.path_from_webkit_base("Tools", "Scripts", "webkitruby", "PrettyPatch", "prettify.rb")
196 self._pretty_patch_available = None 181 self._pretty_patch_available = None
197 182
198 if not hasattr(options, 'configuration') or not options.configuration: 183 if not hasattr(options, 'configuration') or not options.configuration:
199 self.set_option_default('configuration', self.default_configuration( )) 184 self.set_option_default('configuration', self.default_configuration( ))
200 if not hasattr(options, 'target') or not options.target: 185 if not hasattr(options, 'target') or not options.target:
201 self.set_option_default('target', self._options.configuration) 186 self.set_option_default('target', self._options.configuration)
202 self._test_configuration = None 187 self._test_configuration = None
203 self._reftest_list = {} 188 self._reftest_list = {}
(...skipping 27 matching lines...) Expand all
231 # Debug is usually 2x-3x slower than Release. 216 # Debug is usually 2x-3x slower than Release.
232 return 3 * timeout_ms 217 return 3 * timeout_ms
233 return timeout_ms 218 return timeout_ms
234 219
235 def driver_stop_timeout(self): 220 def driver_stop_timeout(self):
236 """Returns the amount of time in seconds to wait before killing the proc ess in driver.stop().""" 221 """Returns the amount of time in seconds to wait before killing the proc ess in driver.stop()."""
237 # We want to wait for at least 3 seconds, but if we are really slow, we want to be slow on cleanup as 222 # We want to wait for at least 3 seconds, but if we are really slow, we want to be slow on cleanup as
238 # well (for things like ASAN, Valgrind, etc.) 223 # well (for things like ASAN, Valgrind, etc.)
239 return 3.0 * float(self.get_option('time_out_ms', '0')) / self.default_t imeout_ms() 224 return 3.0 * float(self.get_option('time_out_ms', '0')) / self.default_t imeout_ms()
240 225
241 def wdiff_available(self):
242 if self._wdiff_available is None:
243 self._wdiff_available = self.check_wdiff(more_logging=False)
244 return self._wdiff_available
245
246 def pretty_patch_available(self): 226 def pretty_patch_available(self):
247 if self._pretty_patch_available is None: 227 if self._pretty_patch_available is None:
248 self._pretty_patch_available = self.check_pretty_patch(more_logging= False) 228 self._pretty_patch_available = self.check_pretty_patch(more_logging= False)
249 return self._pretty_patch_available 229 return self._pretty_patch_available
250 230
251 def default_batch_size(self): 231 def default_batch_size(self):
252 """Return the default batch size to use for this port.""" 232 """Return the default batch size to use for this port."""
253 if self.get_option('enable_sanitizer'): 233 if self.get_option('enable_sanitizer'):
254 # ASAN/MSAN/TSAN use more memory than regular content_shell. Their 234 # ASAN/MSAN/TSAN use more memory than regular content_shell. Their
255 # memory usage may also grow over time, up to a certain point. 235 # memory usage may also grow over time, up to a certain point.
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
339 'test driver') and result 319 'test driver') and result
340 if not result and self.get_option('build'): 320 if not result and self.get_option('build'):
341 result = self._check_driver_build_up_to_date( 321 result = self._check_driver_build_up_to_date(
342 self.get_option('configuration')) 322 self.get_option('configuration'))
343 else: 323 else:
344 _log.error('') 324 _log.error('')
345 325
346 if self.get_option('pixel_tests'): 326 if self.get_option('pixel_tests'):
347 result = self.check_image_diff() and result 327 result = self.check_image_diff() and result
348 328
349 # It's okay if pretty patch and wdiff aren't available, but we will at l east log messages. 329 # It's okay if pretty patch isn't available, but we will at least log me ssages.
350 self._pretty_patch_available = self.check_pretty_patch() 330 self._pretty_patch_available = self.check_pretty_patch()
351 self._wdiff_available = self.check_wdiff()
352 331
353 if self._dump_reader: 332 if self._dump_reader:
354 result = self._dump_reader.check_is_functional() and result 333 result = self._dump_reader.check_is_functional() and result
355 334
356 if needs_http: 335 if needs_http:
357 result = self.check_httpd() and result 336 result = self.check_httpd() and result
358 337
359 return test_run_results.OK_EXIT_STATUS if result else test_run_results.U NEXPECTED_ERROR_EXIT_STATUS 338 return test_run_results.OK_EXIT_STATUS if result else test_run_results.U NEXPECTED_ERROR_EXIT_STATUS
360 339
361 def _check_driver(self): 340 def _check_driver(self):
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
419 return False 398 return False
420 399
421 if not self._filesystem.exists(self._pretty_patch_path): 400 if not self._filesystem.exists(self._pretty_patch_path):
422 if more_logging: 401 if more_logging:
423 _log.warning("Unable to find %s; can't generate pretty patches." , self._pretty_patch_path) 402 _log.warning("Unable to find %s; can't generate pretty patches." , self._pretty_patch_path)
424 _log.warning('') 403 _log.warning('')
425 return False 404 return False
426 405
427 return True 406 return True
428 407
429 def check_wdiff(self, more_logging=True):
430 if not self._path_to_wdiff():
431 # Don't need to log here since this is the port choosing not to use wdiff.
432 return False
433
434 try:
435 _ = self._executive.run_command([self._path_to_wdiff(), '--help'])
436 except OSError:
437 if more_logging:
438 message = self._wdiff_missing_message()
439 if message:
440 for line in message.splitlines():
441 _log.warning(' ' + line)
442 _log.warning('')
443 return False
444
445 return True
446
447 def _wdiff_missing_message(self):
448 return 'wdiff is not installed; please install it to generate word-by-wo rd diffs.'
449
450 def check_httpd(self): 408 def check_httpd(self):
451 httpd_path = self.path_to_apache() 409 httpd_path = self.path_to_apache()
452 if httpd_path: 410 if httpd_path:
453 try: 411 try:
454 env = self.setup_environ_for_server() 412 env = self.setup_environ_for_server()
455 if self._executive.run_command([httpd_path, "-v"], env=env, retu rn_exit_code=True) != 0: 413 if self._executive.run_command([httpd_path, "-v"], env=env, retu rn_exit_code=True) != 0:
456 _log.error("httpd seems broken. Cannot run http tests.") 414 _log.error("httpd seems broken. Cannot run http tests.")
457 return False 415 return False
458 return True 416 return True
459 except OSError: 417 except OSError:
(...skipping 910 matching lines...) Expand 10 before | Expand all | Expand 10 after
1370 ] 1328 ]
1371 if self.is_wptserve_enabled(): 1329 if self.is_wptserve_enabled():
1372 paths.append(self._filesystem.join(self.layout_tests_dir(), 'WPTServ eExpectations')) 1330 paths.append(self._filesystem.join(self.layout_tests_dir(), 'WPTServ eExpectations'))
1373 paths.extend(self._flag_specific_expectations_files()) 1331 paths.extend(self._flag_specific_expectations_files())
1374 return paths 1332 return paths
1375 1333
1376 def repository_path(self): 1334 def repository_path(self):
1377 """Returns the repository path for the chromium code base.""" 1335 """Returns the repository path for the chromium code base."""
1378 return self.path_from_chromium_base('build') 1336 return self.path_from_chromium_base('build')
1379 1337
1380 _WDIFF_DEL = '##WDIFF_DEL##'
1381 _WDIFF_ADD = '##WDIFF_ADD##'
1382 _WDIFF_END = '##WDIFF_END##'
1383
1384 def _format_wdiff_output_as_html(self, wdiff):
1385 wdiff = cgi.escape(wdiff)
1386 wdiff = wdiff.replace(self._WDIFF_DEL, "<span class=del>")
1387 wdiff = wdiff.replace(self._WDIFF_ADD, "<span class=add>")
1388 wdiff = wdiff.replace(self._WDIFF_END, "</span>")
1389 html = "<head><style>.del { background: #faa; } "
1390 html += ".add { background: #afa; }</style></head>"
1391 html += "<pre>%s</pre>" % wdiff
1392 return html
1393
1394 def _wdiff_command(self, actual_filename, expected_filename):
1395 executable = self._path_to_wdiff()
1396 return [executable,
1397 "--start-delete=%s" % self._WDIFF_DEL,
1398 "--end-delete=%s" % self._WDIFF_END,
1399 "--start-insert=%s" % self._WDIFF_ADD,
1400 "--end-insert=%s" % self._WDIFF_END,
1401 actual_filename,
1402 expected_filename]
1403
1404 @staticmethod
1405 def _handle_wdiff_error(script_error):
1406 # Exit 1 means the files differed, any other exit code is an error.
1407 if script_error.exit_code != 1:
1408 raise script_error
1409
1410 def _run_wdiff(self, actual_filename, expected_filename):
1411 """Runs wdiff and may throw exceptions.
1412 This is mostly a hook for unit testing.
1413 """
1414 # Diffs are treated as binary as they may include multiple files
1415 # with conflicting encodings. Thus we do not decode the output.
1416 command = self._wdiff_command(actual_filename, expected_filename)
1417 wdiff = self._executive.run_command(command, decode_output=False,
1418 error_handler=self._handle_wdiff_err or)
1419 return self._format_wdiff_output_as_html(wdiff)
1420
1421 _wdiff_error_html = "Failed to run wdiff, see error log."
1422
1423 def wdiff_text(self, actual_filename, expected_filename):
1424 """Returns a string of HTML indicating the word-level diff of the
1425 contents of the two filenames. Returns an empty string if word-level
1426 diffing isn't available.
1427 """
1428 if not self.wdiff_available():
1429 return ""
1430 try:
1431 # It's possible to raise a ScriptError we pass wdiff invalid paths.
1432 return self._run_wdiff(actual_filename, expected_filename)
1433 except OSError as error:
1434 if error.errno in (errno.ENOENT, errno.EACCES, errno.ECHILD):
1435 # Silently ignore cases where wdiff is missing.
1436 self._wdiff_available = False
1437 return ""
1438 raise
1439 except ScriptError as error:
1440 _log.error("Failed to run wdiff: %s", error)
1441 self._wdiff_available = False
1442 return self._wdiff_error_html
1443
1444 # This is a class variable so we can test error output easily. 1338 # This is a class variable so we can test error output easily.
1445 _pretty_patch_error_html = "Failed to run PrettyPatch, see error log." 1339 _pretty_patch_error_html = "Failed to run PrettyPatch, see error log."
1446 1340
1447 def pretty_patch_text(self, diff_path): 1341 def pretty_patch_text(self, diff_path):
1448 if self._pretty_patch_available is None: 1342 if self._pretty_patch_available is None:
1449 self._pretty_patch_available = self.check_pretty_patch(more_logging= False) 1343 self._pretty_patch_available = self.check_pretty_patch(more_logging= False)
1450 if not self._pretty_patch_available: 1344 if not self._pretty_patch_available:
1451 return self._pretty_patch_error_html 1345 return self._pretty_patch_error_html
1452 command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_pat h), 1346 command = ("ruby", "-I", self._filesystem.dirname(self._pretty_patch_pat h),
1453 self._pretty_patch_path, diff_path) 1347 self._pretty_patch_path, diff_path)
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
1526 """Returns the full path to the test driver.""" 1420 """Returns the full path to the test driver."""
1527 return self._build_path(target, self.driver_name()) 1421 return self._build_path(target, self.driver_name())
1528 1422
1529 def _path_to_image_diff(self): 1423 def _path_to_image_diff(self):
1530 """Returns the full path to the image_diff binary, or None if it is not available. 1424 """Returns the full path to the image_diff binary, or None if it is not available.
1531 1425
1532 This is likely used only by diff_image() 1426 This is likely used only by diff_image()
1533 """ 1427 """
1534 return self._build_path('image_diff') 1428 return self._build_path('image_diff')
1535 1429
1536 @memoized
1537 def _path_to_wdiff(self):
1538 """Returns the full path to the wdiff binary, or None if it is not avail able.
1539
1540 This is likely used only by wdiff_text()
1541 """
1542 for path in ("/usr/bin/wdiff", "/usr/bin/dwdiff"):
1543 if self._filesystem.exists(path):
1544 return path
1545 return None
1546
1547 def _absolute_baseline_path(self, platform_dir): 1430 def _absolute_baseline_path(self, platform_dir):
1548 """Return the absolute path to the top of the baseline tree for a 1431 """Return the absolute path to the top of the baseline tree for a
1549 given platform directory. 1432 given platform directory.
1550 """ 1433 """
1551 return self._filesystem.join(self.layout_tests_dir(), 'platform', platfo rm_dir) 1434 return self._filesystem.join(self.layout_tests_dir(), 'platform', platfo rm_dir)
1552 1435
1553 def _driver_class(self): 1436 def _driver_class(self):
1554 """Returns the port's driver implementation.""" 1437 """Returns the port's driver implementation."""
1555 return driver.Driver 1438 return driver.Driver
1556 1439
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
1773 1656
1774 def __init__(self, base, args, reference_args=None): 1657 def __init__(self, base, args, reference_args=None):
1775 self.name = base 1658 self.name = base
1776 self.base = base 1659 self.base = base
1777 self.args = args 1660 self.args = args
1778 self.reference_args = args if reference_args is None else reference_args 1661 self.reference_args = args if reference_args is None else reference_args
1779 self.tests = set() 1662 self.tests = set()
1780 1663
1781 def __repr__(self): 1664 def __repr__(self):
1782 return "PhysicalTestSuite('%s', '%s', %s, %s)" % (self.name, self.base, self.args, self.reference_args) 1665 return "PhysicalTestSuite('%s', '%s', %s, %s)" % (self.name, self.base, self.args, self.reference_args)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698