diff options
| author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
|---|---|---|
| committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
| commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
| tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/third_party/WebKit/Tools/Scripts/webkitpy | |
| parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) | |
| download | qtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz | |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/third_party/WebKit/Tools/Scripts/webkitpy')
125 files changed, 3419 insertions, 6782 deletions
diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py index b1409d27ada..663fd67b1f0 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/bindings/main.py @@ -28,31 +28,36 @@ import tempfile from webkitpy.common.checkout.scm.detection import detect_scm_system from webkitpy.common.system.executive import ScriptError +PASS_MESSAGE = 'All tests PASS!' +FAIL_MESSAGE = """Some tests FAIL! +To update the reference files, execute: + run-bindings-tests --reset-results + +If the failures are not due to your changes, test results may be out of sync; +please rebaseline them in a separate CL, after checking that tests fail in ToT. +In CL, please set: +NOTRY=true +TBR=(someone in Source/bindings/OWNERS or WATCHLISTS:bindings) +""" + # Python compiler is incomplete; skip IDLs with unimplemented features SKIP_PYTHON = set([ - 'TestActiveDOMObject.idl', - 'TestCallback.idl', 'TestCustomAccessors.idl', - 'TestEvent.idl', - 'TestEventConstructor.idl', 'TestEventTarget.idl', 'TestException.idl', - 'TestExtendedEvent.idl', 'TestImplements.idl', 'TestInterface.idl', 'TestInterfaceImplementedAs.idl', - 'TestMediaQueryListListener.idl', 'TestNamedConstructor.idl', 'TestNode.idl', 'TestObject.idl', 'TestOverloadedConstructors.idl', 'TestPartialInterface.idl', - 'TestSerializedScriptValueInterface.idl', - 'TestTypedArray.idl', 'TestTypedefs.idl', ]) input_directory = os.path.join('bindings', 'tests', 'idls') +support_input_directory = os.path.join('bindings', 'tests', 'idls', 'testing') reference_directory = os.path.join('bindings', 'tests', 'results') reference_event_names_filename = os.path.join(reference_directory, 'EventInterfaces.in') @@ -82,178 +87,198 @@ provider = ScopedTempFileProvider() class BindingsTests(object): - def __init__(self, reset_results, test_python, executive): + def __init__(self, reset_results, test_python, verbose, executive): self.reset_results = reset_results self.test_python = test_python + self.verbose = verbose self.executive = executive _, self.interface_dependencies_filename = provider.newtempfile() + _, self.derived_sources_list_filename = provider.newtempfile() + # Generate output into the reference directory if resetting results, or + # a temp directory if not. if reset_results: - self.event_names_filename = os.path.join(reference_directory, 'EventInterfaces.in') + self.output_directory = reference_directory else: - _, self.event_names_filename = provider.newtempfile() + self.output_directory = provider.newtempdir() + self.output_directory_py = provider.newtempdir() + self.event_names_filename = os.path.join(self.output_directory, 'EventInterfaces.in') def run_command(self, cmd): - return self.executive.run_command(cmd) + output = self.executive.run_command(cmd) + if output: + print output - def generate_from_idl_pl(self, idl_file, output_directory): + def generate_from_idl_pl(self, idl_file): cmd = ['perl', '-w', '-Ibindings/scripts', + '-Ibuild/scripts', '-Icore/scripts', '-I../../JSON/out/lib/perl5', 'bindings/scripts/generate_bindings.pl', # idl include directories (path relative to generate-bindings.pl) '--include', '.', - '--outputDir', output_directory, + '--outputDir', self.output_directory, '--interfaceDependenciesFile', self.interface_dependencies_filename, - '--idlAttributesFile', 'bindings/scripts/IDLAttributes.txt', + '--idlAttributesFile', 'bindings/IDLExtendedAttributes.txt', idl_file] try: - output = self.run_command(cmd) + self.run_command(cmd) except ScriptError, e: + print 'ERROR: generate_bindings.pl: ' + os.path.basename(idl_file) print e.output return e.exit_code - if output: - print output return 0 - def generate_from_idl_py(self, idl_file, output_directory): + def generate_from_idl_py(self, idl_file): cmd = ['python', 'bindings/scripts/unstable/idl_compiler.py', - '--output-dir', output_directory, - '--idl-attributes-file', 'bindings/scripts/IDLAttributes.txt', + '--output-dir', self.output_directory_py, + '--idl-attributes-file', 'bindings/IDLExtendedAttributes.txt', '--include', '.', '--interface-dependencies-file', self.interface_dependencies_filename, idl_file] try: - output = self.run_command(cmd) + self.run_command(cmd) except ScriptError, e: + print 'ERROR: idl_compiler.py: ' + os.path.basename(idl_file) print e.output return e.exit_code - if output: - print output return 0 def generate_interface_dependencies(self): - idl_files_list_file, idl_files_list_filename = provider.newtempfile() + idl_files_list_file, main_idl_files_list_filename = provider.newtempfile() idl_paths = [os.path.join(input_directory, input_file) for input_file in os.listdir(input_directory) if input_file.endswith('.idl')] idl_files_list_contents = ''.join(idl_path + '\n' for idl_path in idl_paths) os.write(idl_files_list_file, idl_files_list_contents) + support_idl_files_list_file, support_idl_files_list_filename = provider.newtempfile() + support_idl_paths = [os.path.join(support_input_directory, input_file) + for input_file in os.listdir(support_input_directory) + if input_file.endswith('.idl')] + support_idl_files_list_contents = ''.join(idl_path + '\n' + for idl_path in support_idl_paths) + os.write(support_idl_files_list_file, support_idl_files_list_contents) # Dummy files, required by compute_dependencies but not checked _, window_constructors_file = provider.newtempfile() _, workerglobalscope_constructors_file = provider.newtempfile() _, sharedworkerglobalscope_constructors_file = provider.newtempfile() _, dedicatedworkerglobalscope_constructors_file = provider.newtempfile() + _, serviceworkersglobalscope_constructors_file = provider.newtempfile() cmd = ['python', 'bindings/scripts/compute_dependencies.py', - '--idl-files-list', idl_files_list_filename, + '--main-idl-files-list', main_idl_files_list_filename, + '--support-idl-files-list', support_idl_files_list_filename, '--interface-dependencies-file', self.interface_dependencies_filename, + '--bindings-derived-sources-file', self.derived_sources_list_filename, '--window-constructors-file', window_constructors_file, '--workerglobalscope-constructors-file', workerglobalscope_constructors_file, '--sharedworkerglobalscope-constructors-file', sharedworkerglobalscope_constructors_file, '--dedicatedworkerglobalscope-constructors-file', dedicatedworkerglobalscope_constructors_file, + '--serviceworkerglobalscope-constructors-file', serviceworkersglobalscope_constructors_file, '--event-names-file', self.event_names_filename, '--write-file-only-if-changed', '0'] - if self.reset_results: + if self.reset_results and self.verbose: print 'Reset results: EventInterfaces.in' try: - output = self.run_command(cmd) + self.run_command(cmd) except ScriptError, e: + print 'ERROR: compute_dependencies.py' print e.output return e.exit_code - if output: - print output return 0 - def identical_file(self, reference_filename, work_filename): + def identical_file(self, reference_filename, output_filename): + reference_basename = os.path.basename(reference_filename) cmd = ['diff', '-u', '-N', reference_filename, - work_filename] + output_filename] try: - output = self.run_command(cmd) + self.run_command(cmd) except ScriptError, e: + # run_command throws an exception on diff (b/c non-zero exit code) + print 'FAIL: %s' % reference_basename print e.output return False - reference_basename = os.path.basename(reference_filename) - if output: - print 'FAIL: %s' % reference_basename - print output - return False - print 'PASS: %s' % reference_basename + if self.verbose: + print 'PASS: %s' % reference_basename return True - def identical_output_directory(self, work_directory): + def identical_output_files(self, output_directory): file_pairs = [(os.path.join(reference_directory, output_file), - os.path.join(work_directory, output_file)) - for output_file in os.listdir(work_directory) + os.path.join(output_directory, output_file)) + for output_file in os.listdir(output_directory) # FIXME: add option to compiler to not generate tables if output_file != 'parsetab.py'] - return all([self.identical_file(reference_filename, work_filename) - for (reference_filename, work_filename) in file_pairs]) + return all([self.identical_file(reference_filename, output_filename) + for (reference_filename, output_filename) in file_pairs]) + + def no_excess_files(self): + generated_files = set(os.listdir(self.output_directory)) + generated_files.add('.svn') # Subversion working copy directory + excess_files = [output_file + for output_file in os.listdir(reference_directory) + if output_file not in generated_files] + if excess_files: + print ('Excess reference files! ' + '(probably cruft from renaming or deleting):\n' + + '\n'.join(excess_files)) + return False + return True def run_tests(self): - def generate_and_check_output_pl(idl_filename): - # Generate output into the reference directory if resetting - # results, or a temp directory if not. - if self.reset_results: - work_directory = reference_directory - else: - work_directory = provider.newtempdir() - idl_path = os.path.join(input_directory, idl_filename) - if self.generate_from_idl_pl(idl_path, work_directory): - return False - if self.reset_results: - print 'Reset results: %s' % input_file - return True - return self.identical_output_directory(work_directory) + # Generate output, immediately dying on failure + if self.generate_interface_dependencies(): + return False - def generate_and_check_output_py(idl_filename): - if idl_filename in SKIP_PYTHON: - print 'SKIP: %s' % idl_filename - return True - work_directory = provider.newtempdir() - idl_path = os.path.join(input_directory, idl_filename) - if self.generate_from_idl_py(idl_path, work_directory): - return False - # Detect changes - return self.identical_output_directory(work_directory) + for directory in [input_directory, support_input_directory]: + for input_filename in os.listdir(directory): + if not input_filename.endswith('.idl'): + continue + idl_path = os.path.join(directory, input_filename) + if self.generate_from_idl_pl(idl_path): + return False + if self.reset_results and self.verbose: + print 'Reset results: %s' % input_filename + if not self.test_python: + continue + if (input_filename in SKIP_PYTHON or + directory == support_input_directory): + if self.verbose: + print 'SKIP: %s' % input_filename + continue + if self.generate_from_idl_py(idl_path): + return False - if self.reset_results: - passed = True - else: - passed = self.identical_file(reference_event_names_filename, - self.event_names_filename) - passed &= all([generate_and_check_output_pl(input_file) - for input_file in os.listdir(input_directory) - if input_file.endswith('.idl')]) - print + # Detect all changes + passed = self.identical_file(reference_event_names_filename, + self.event_names_filename) + passed &= self.identical_output_files(self.output_directory) if self.test_python: - print 'Python:' - passed &= all([generate_and_check_output_py(input_file) - for input_file in os.listdir(input_directory) - if input_file.endswith('.idl')]) + if self.verbose: + print + print 'Python:' + passed &= self.identical_output_files(self.output_directory_py) + passed &= self.no_excess_files() return passed def main(self): current_scm = detect_scm_system(os.curdir) os.chdir(os.path.join(current_scm.checkout_root, 'Source')) - if self.generate_interface_dependencies(): - print 'Failed to generate interface dependencies file.' - return -1 - all_tests_passed = self.run_tests() - print if all_tests_passed: - print 'All tests PASS!' + if self.verbose: + print + print PASS_MESSAGE return 0 - print 'Some tests FAIL! (To update the reference files, execute "run-bindings-tests --reset-results")' + print + print FAIL_MESSAGE return -1 diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/__init__.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/__init__.py index f385ae4f150..ef65bee5bb7 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/__init__.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/__init__.py @@ -1,3 +1 @@ # Required for Python to search this directory for module files - -from .checkout import Checkout diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py index b6a10eeb273..881d112706c 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/baselineoptimizer_unittest.py @@ -26,218 +26,168 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import hashlib -import sys import webkitpy.thirdparty.unittest2 as unittest from webkitpy.common.checkout.baselineoptimizer import BaselineOptimizer -from webkitpy.common.system.filesystem_mock import MockFileSystem from webkitpy.common.host_mock import MockHost - - -class TestBaselineOptimizer(BaselineOptimizer): - def __init__(self, mock_results_by_directory, create_mock_files, baseline_name): - host = MockHost() - BaselineOptimizer.__init__(self, host, host.port_factory.all_port_names()) - self._mock_results_by_directory = mock_results_by_directory - self._filesystem = host.filesystem - self._port_factory = host.port_factory - self._created_mock_files = create_mock_files - self._baseline_name = baseline_name - - self._create_mock_files(mock_results_by_directory) - - # We override this method for testing so we don't have to construct an - # elaborate mock file system. - def read_results_by_directory(self, baseline_name): - if self._created_mock_files: - return super(TestBaselineOptimizer, self).read_results_by_directory(baseline_name) - return self._mock_results_by_directory - - def _move_baselines(self, baseline_name, results_by_directory, new_results_by_directory): - self.new_results_by_directory.append(new_results_by_directory) - - if self._created_mock_files: - super(TestBaselineOptimizer, self)._move_baselines(baseline_name, results_by_directory, new_results_by_directory) - return - self._mock_results_by_directory = new_results_by_directory - - def _create_mock_files(self, results_by_directory): - root = self._port_factory.get().webkit_base() - for directory in results_by_directory: - if 'virtual' in directory: - virtual_suite = self._port_factory.get().lookup_virtual_suite(self._baseline_name) - if virtual_suite: - baseline_name = self._baseline_name[len(virtual_suite.name) + 1:] - else: - baseline_name = self._baseline_name - else: - baseline_name = self._port_factory.get().lookup_virtual_test_base(self._baseline_name) - path = self._filesystem.join(root, directory, baseline_name) - self._filesystem.write_text_file(path, results_by_directory[directory]) +from webkitpy.common.webkit_finder import WebKitFinder class BaselineOptimizerTest(unittest.TestCase): - VIRTUAL_DIRECTORY = 'virtual/softwarecompositing' - - def _appendVirtualSuffix(self, results_by_directory): - new_results_by_directory = {} - for directory in results_by_directory: - new_results_by_directory[directory + '/' + self.VIRTUAL_DIRECTORY] = results_by_directory[directory] - return new_results_by_directory - - def _assertOneLevelOptimization(self, results_by_directory, expected_new_results_by_directory, baseline_name, create_mock_files=False): - baseline_optimizer = TestBaselineOptimizer(results_by_directory, create_mock_files, baseline_name) - self.assertTrue(baseline_optimizer.optimize(baseline_name)) - if type(expected_new_results_by_directory) != list: - expected_new_results_by_directory = [expected_new_results_by_directory] - self.assertEqual(baseline_optimizer.new_results_by_directory, expected_new_results_by_directory) - - def _assertOptimization(self, results_by_directory, expected_new_results_by_directory): - baseline_name = 'mock-baseline.png' - self._assertOneLevelOptimization(results_by_directory, expected_new_results_by_directory, baseline_name) - - results_by_directory = self._appendVirtualSuffix(results_by_directory) - expected_new_results_by_directory = self._appendVirtualSuffix(expected_new_results_by_directory) - baseline_name = self.VIRTUAL_DIRECTORY + '/' + baseline_name - self._assertOneLevelOptimization(results_by_directory, [expected_new_results_by_directory, expected_new_results_by_directory], baseline_name) - def test_move_baselines(self): host = MockHost() - host.filesystem.write_binary_file('/mock-checkout/LayoutTests/platform/win/another/test-expected.txt', 'result A') - host.filesystem.write_binary_file('/mock-checkout/LayoutTests/platform/mac/another/test-expected.txt', 'result A') - host.filesystem.write_binary_file('/mock-checkout/LayoutTests/another/test-expected.txt', 'result B') + host.filesystem.write_binary_file('/mock-checkout/third_party/WebKit/LayoutTests/platform/win/another/test-expected.txt', 'result A') + host.filesystem.write_binary_file('/mock-checkout/third_party/WebKit/LayoutTests/platform/mac/another/test-expected.txt', 'result A') + host.filesystem.write_binary_file('/mock-checkout/third_party/WebKit/LayoutTests/another/test-expected.txt', 'result B') baseline_optimizer = BaselineOptimizer(host, host.port_factory.all_port_names()) baseline_optimizer._move_baselines('another/test-expected.txt', { - 'LayoutTests/platform/win': 'aaa', - 'LayoutTests/platform/mac': 'aaa', - 'LayoutTests': 'bbb', + '/mock-checkout/third_party/WebKit/LayoutTests/platform/win': 'aaa', + '/mock-checkout/third_party/WebKit/LayoutTests/platform/mac': 'aaa', + '/mock-checkout/third_party/WebKit/LayoutTests': 'bbb', }, { - 'LayoutTests': 'aaa', + '/mock-checkout/third_party/WebKit/LayoutTests': 'aaa', }) - self.assertEqual(host.filesystem.read_binary_file('/mock-checkout/LayoutTests/another/test-expected.txt'), 'result A') + self.assertEqual(host.filesystem.read_binary_file('/mock-checkout/third_party/WebKit/LayoutTests/another/test-expected.txt'), 'result A') + + def _assertOptimization(self, results_by_directory, expected_new_results_by_directory, baseline_dirname=''): + host = MockHost() + fs = host.filesystem + webkit_base = WebKitFinder(fs).webkit_base() + baseline_name = 'mock-baseline-expected.txt' + + for dirname, contents in results_by_directory.items(): + path = fs.join(webkit_base, 'LayoutTests', dirname, baseline_name) + fs.write_binary_file(path, contents) + + baseline_optimizer = BaselineOptimizer(host, host.port_factory.all_port_names()) + self.assertTrue(baseline_optimizer.optimize(fs.join(baseline_dirname, baseline_name))) + + for dirname, contents in expected_new_results_by_directory.items(): + path = fs.join(webkit_base, 'LayoutTests', dirname, baseline_name) + if contents is None: + self.assertFalse(fs.exists(path)) + else: + self.assertEqual(fs.read_binary_file(path), contents) + + # Check that the files that were in the original set have been deleted where necessary. + for dirname in results_by_directory: + path = fs.join(webkit_base, 'LayoutTests', dirname, baseline_name) + if not dirname in expected_new_results_by_directory: + self.assertFalse(fs.exists(path)) def test_linux_redundant_with_win(self): self._assertOptimization({ - 'LayoutTests/platform/win': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/linux': '462d03b9c025db1b0392d7453310dbee5f9a9e74', + 'platform/win': '1', + 'platform/linux': '1', }, { - 'LayoutTests/platform/win': '462d03b9c025db1b0392d7453310dbee5f9a9e74', + 'platform/win': '1', }) def test_covers_mac_win_linux(self): self._assertOptimization({ - 'LayoutTests/platform/mac': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/win': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/linux': '462d03b9c025db1b0392d7453310dbee5f9a9e74', + 'platform/mac': '1', + 'platform/win': '1', + 'platform/linux': '1', + '': None, }, { - 'LayoutTests': '462d03b9c025db1b0392d7453310dbee5f9a9e74', + '': '1', }) def test_overwrites_root(self): self._assertOptimization({ - 'LayoutTests/platform/mac': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/win': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/linux': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests': '1', + 'platform/mac': '1', + 'platform/win': '1', + 'platform/linux': '1', + '': '2', }, { - 'LayoutTests': '462d03b9c025db1b0392d7453310dbee5f9a9e74', + '': '1', }) def test_no_new_common_directory(self): self._assertOptimization({ - 'LayoutTests/platform/mac': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/linux': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests': '1', + 'platform/mac': '1', + 'platform/linux': '1', + '': '2', }, { - 'LayoutTests/platform/mac': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/linux': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests': '1', + 'platform/mac': '1', + 'platform/linux': '1', + '': '2', }) - def test_no_common_directory(self): - self._assertOptimization({ - 'LayoutTests/platform/mac': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/chromium': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - }, { - 'LayoutTests/platform/mac': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - 'LayoutTests/platform/chromium': '462d03b9c025db1b0392d7453310dbee5f9a9e74', - }) - def test_local_optimization(self): self._assertOptimization({ - 'LayoutTests/platform/mac': '1', - 'LayoutTests/platform/linux': '1', - 'LayoutTests/platform/linux-x86': '1', + 'platform/mac': '1', + 'platform/linux': '1', + 'platform/linux-x86': '1', }, { - 'LayoutTests/platform/mac': '1', - 'LayoutTests/platform/linux': '1', + 'platform/mac': '1', + 'platform/linux': '1', }) def test_local_optimization_skipping_a_port_in_the_middle(self): self._assertOptimization({ - 'LayoutTests/platform/mac-snowleopard': '1', - 'LayoutTests/platform/win': '1', - 'LayoutTests/platform/linux-x86': '1', + 'platform/mac-snowleopard': '1', + 'platform/win': '1', + 'platform/linux-x86': '1', }, { - 'LayoutTests/platform/mac-snowleopard': '1', - 'LayoutTests/platform/win': '1', + 'platform/mac-snowleopard': '1', + 'platform/win': '1', }) def test_baseline_redundant_with_root(self): self._assertOptimization({ - 'LayoutTests/platform/mac': '1', - 'LayoutTests/platform/win': '2', - 'LayoutTests': '2', + 'platform/mac': '1', + 'platform/win': '2', + '': '2', }, { - 'LayoutTests/platform/mac': '1', - 'LayoutTests': '2', + 'platform/mac': '1', + '': '2', }) def test_root_baseline_unused(self): self._assertOptimization({ - 'LayoutTests/platform/mac': '1', - 'LayoutTests/platform/win': '2', - 'LayoutTests': '3', + 'platform/mac': '1', + 'platform/win': '2', + '': '3', }, { - 'LayoutTests/platform/mac': '1', - 'LayoutTests/platform/win': '2', + 'platform/mac': '1', + 'platform/win': '2', }) def test_root_baseline_unused_and_non_existant(self): self._assertOptimization({ - 'LayoutTests/platform/mac': '1', - 'LayoutTests/platform/win': '2', + 'platform/mac': '1', + 'platform/win': '2', }, { - 'LayoutTests/platform/mac': '1', - 'LayoutTests/platform/win': '2', + 'platform/mac': '1', + 'platform/win': '2', }) def test_virtual_root_redundant_with_actual_root(self): - baseline_name = self.VIRTUAL_DIRECTORY + '/mock-baseline.png' - hash_of_two = hashlib.sha1('2').hexdigest() - expected_result = [{'LayoutTests/virtual/softwarecompositing': hash_of_two}, {'LayoutTests': hash_of_two}] - self._assertOneLevelOptimization({ - 'LayoutTests/' + self.VIRTUAL_DIRECTORY: '2', - 'LayoutTests': '2', - }, expected_result, baseline_name, create_mock_files=True) + self._assertOptimization({ + 'virtual/softwarecompositing': '2', + 'compositing': '2', + }, { + 'virtual/softwarecompositing': None, + 'compositing': '2', + }, baseline_dirname='virtual/softwarecompositing') def test_virtual_root_redundant_with_ancestors(self): - baseline_name = self.VIRTUAL_DIRECTORY + '/mock-baseline.png' - hash_of_two = hashlib.sha1('2').hexdigest() - expected_result = [{'LayoutTests/virtual/softwarecompositing': hash_of_two}, {'LayoutTests': hash_of_two}] - self._assertOneLevelOptimization({ - 'LayoutTests/' + self.VIRTUAL_DIRECTORY: '2', - 'LayoutTests/platform/mac': '2', - 'LayoutTests/platform/win': '2', - }, expected_result, baseline_name, create_mock_files=True) + self._assertOptimization({ + 'virtual/softwarecompositing': '2', + 'platform/mac/compositing': '2', + 'platform/win/compositing': '2', + }, { + 'virtual/softwarecompositing': None, + 'compositing': '2', + }, baseline_dirname='virtual/softwarecompositing') def test_virtual_root_not_redundant_with_ancestors(self): - baseline_name = self.VIRTUAL_DIRECTORY + '/mock-baseline.png' - hash_of_two = hashlib.sha1('2').hexdigest() - expected_result = [{'LayoutTests/virtual/softwarecompositing': hash_of_two}, {'LayoutTests/platform/mac': hash_of_two}] - self._assertOneLevelOptimization({ - 'LayoutTests/' + self.VIRTUAL_DIRECTORY: '2', - 'LayoutTests/platform/mac': '2', - }, expected_result, baseline_name, create_mock_files=True) + self._assertOptimization({ + 'virtual/softwarecompositing': '2', + 'platform/mac/compositing': '1', + }, { + 'virtual/softwarecompositing': '2', + 'platform/mac/compositing': '1', + }, baseline_dirname='virtual/softwarecompositing') diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout.py deleted file mode 100644 index 6b640d0d3c4..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (c) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import StringIO - -from webkitpy.common.config import urls -from webkitpy.common.checkout.scm import CommitMessage -from webkitpy.common.checkout.deps import DEPS -from webkitpy.common.memoized import memoized -from webkitpy.common.system.executive import ScriptError - - -# This class represents the WebKit-specific parts of the checkout. -# NOTE: All paths returned from this class should be absolute. -class Checkout(object): - def __init__(self, scm, executive=None, filesystem=None): - self._scm = scm - # FIXME: We shouldn't be grabbing at private members on scm. - self._executive = executive or self._scm._executive - self._filesystem = filesystem or self._scm._filesystem - - def _modified_files_matching_predicate(self, git_commit, predicate, changed_files=None): - # SCM returns paths relative to scm.checkout_root - # Callers (especially those using the ChangeLog class) may - # expect absolute paths, so this method returns absolute paths. - if not changed_files: - changed_files = self._scm.changed_files(git_commit) - return filter(predicate, map(self._scm.absolute_path, changed_files)) - - def recent_commit_infos_for_files(self, paths): - revisions = set(sum(map(self._scm.revisions_changing_file, paths), [])) - return set(map(self.commit_info_for_revision, revisions)) - - def apply_patch(self, patch): - # It's possible that the patch was not made from the root directory. - # We should detect and handle that case. - # FIXME: Move _scm.script_path here once we get rid of all the dependencies. - # --force (continue after errors) is the common case, so we always use it. - args = [self._scm.script_path('svn-apply'), "--force"] - if patch.reviewer(): - args += ['--reviewer', patch.reviewer().full_name] - self._executive.run_command(args, input=patch.contents(), cwd=self._scm.checkout_root) - - def apply_reverse_diff(self, revision): - self._scm.apply_reverse_diff(revision) - - conflicts = self._scm.conflicted_files() - if len(conflicts): - raise ScriptError(message="Failed to apply reverse diff for revision %s because of the following conflicts:\n%s" % (revision, "\n".join(conflicts))) - - def apply_reverse_diffs(self, revision_list): - for revision in sorted(revision_list, reverse=True): - self.apply_reverse_diff(revision) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout_mock.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout_mock.py deleted file mode 100644 index ca76d57393c..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout_mock.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2011 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.common.system.filesystem_mock import MockFileSystem - - -class MockCommitMessage(object): - def message(self): - return "This is a fake commit message that is at least 50 characters." - - -class MockCheckout(object): - def __init__(self): - # FIXME: It's unclear if a MockCheckout is very useful. A normal Checkout - # with a MockSCM/MockFileSystem/MockExecutive is probably better. - self._filesystem = MockFileSystem() - - def is_path_to_changelog(self, path): - return self._filesystem.basename(path) == "ChangeLog" - - def recent_commit_infos_for_files(self, paths): - return [self.commit_info_for_revision(32)] - - def modified_changelogs(self, git_commit, changed_files=None): - # Ideally we'd return something more interesting here. The problem is - # that LandDiff will try to actually read the patch from disk! - return [] - - def commit_message_for_this_commit(self, git_commit, changed_files=None): - return MockCommitMessage() - - def apply_patch(self, patch): - pass - - def apply_reverse_diffs(self, revision): - pass diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout_unittest.py deleted file mode 100644 index fb8b108ba43..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/checkout_unittest.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import codecs -import os -import shutil -import tempfile -import webkitpy.thirdparty.unittest2 as unittest - -from .checkout import Checkout -from .scm import SCMDetector -from .scm.scm_mock import MockSCM -from webkitpy.common.webkit_finder import WebKitFinder -from webkitpy.common.system.executive import Executive, ScriptError -from webkitpy.common.system.filesystem import FileSystem # FIXME: This should not be needed. -from webkitpy.common.system.filesystem_mock import MockFileSystem -from webkitpy.common.system.executive_mock import MockExecutive -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock - - -class CheckoutTest(unittest.TestCase): - def _make_checkout(self): - return Checkout(scm=MockSCM(), filesystem=MockFileSystem(), executive=MockExecutive()) - - def test_apply_patch(self): - checkout = self._make_checkout() - checkout._executive = MockExecutive(should_log=True) - checkout._scm.script_path = lambda script: script - mock_patch = Mock() - mock_patch.contents = lambda: "foo" - mock_patch.reviewer = lambda: None - expected_logs = "MOCK run_command: ['svn-apply', '--force'], cwd=/mock-checkout, input=foo\n" - OutputCapture().assert_outputs(self, checkout.apply_patch, [mock_patch], expected_logs=expected_logs) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/__init__.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/__init__.py index f691f58e17b..9a2810c55e0 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/__init__.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/__init__.py @@ -1,8 +1,7 @@ # Required for Python to search this directory for module files # We only export public API here. -from .commitmessage import CommitMessage from .detection import SCMDetector from .git import Git, AmbiguousCommitError -from .scm import SCM, AuthenticationError, CheckoutNeedsUpdate +from .scm import SCM from .svn import SVN diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/git.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/git.py index c69a09888d7..30f1fb23c92 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/git.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/git.py @@ -32,13 +32,10 @@ import logging import os import re +from webkitpy.common.checkout.scm.scm import SCM from webkitpy.common.memoized import memoized from webkitpy.common.system.executive import Executive, ScriptError -from .commitmessage import CommitMessage -from .scm import AuthenticationError, SCM, commit_error_handler -from .svn import SVN, SVNRepository - _log = logging.getLogger(__name__) @@ -50,7 +47,7 @@ class AmbiguousCommitError(Exception): self.has_working_directory_changes = has_working_directory_changes -class Git(SCM, SVNRepository): +class Git(SCM): # Git doesn't appear to document error codes, but seems to return # 1 or 128, mostly. @@ -60,45 +57,13 @@ class Git(SCM, SVNRepository): def __init__(self, cwd, **kwargs): SCM.__init__(self, cwd, **kwargs) - self._check_git_architecture() - - def _machine_is_64bit(self): - import platform - # This only is tested on Mac. - if not platform.mac_ver()[0]: - return False - - # platform.architecture()[0] can be '64bit' even if the machine is 32bit: - # http://mail.python.org/pipermail/pythonmac-sig/2009-September/021648.html - # Use the sysctl command to find out what the processor actually supports. - return self.run(['sysctl', '-n', 'hw.cpu64bit_capable']).rstrip() == '1' - - def _executable_is_64bit(self, path): - # Again, platform.architecture() fails us. On my machine - # git_bits = platform.architecture(executable=git_path, bits='default')[0] - # git_bits is just 'default', meaning the call failed. - file_output = self.run(['file', path]) - return re.search('x86_64', file_output) - - def _check_git_architecture(self): - if not self._machine_is_64bit(): - return - - # We could path-search entirely in python or with - # which.py (http://code.google.com/p/which), but this is easier: - git_path = self.run(['which', self.executable_name]).rstrip() - if self._executable_is_64bit(git_path): - return - - webkit_dev_thread_url = "https://lists.webkit.org/pipermail/webkit-dev/2010-December/015287.html" - _log.warning("This machine is 64-bit, but the git binary (%s) does not support 64-bit.\nInstall a 64-bit git for better performance, see:\n%s\n" % (git_path, webkit_dev_thread_url)) def _run_git(self, command_args, **kwargs): full_command_args = [self.executable_name] + command_args full_kwargs = kwargs if not 'cwd' in full_kwargs: full_kwargs['cwd'] = self.checkout_root - return self.run(full_command_args, **full_kwargs) + return self._run(full_command_args, **full_kwargs) @classmethod def in_working_directory(cls, path, executive=None): @@ -116,12 +81,6 @@ class Git(SCM, SVNRepository): checkout_root = self._filesystem.join(path, checkout_root) return checkout_root - def to_object_name(self, filepath): - # FIXME: This can't be the right way to append a slash. - root_end_with_slash = self._filesystem.join(self.find_checkout_root(self._filesystem.dirname(filepath)), '') - # FIXME: This seems to want some sort of rel_path instead? - return filepath.replace(root_end_with_slash, '') - @classmethod def read_git_config(cls, key, cwd=None, executive=None): # FIXME: This should probably use cwd=self.checkout_root. @@ -131,27 +90,23 @@ class Git(SCM, SVNRepository): executive = executive or Executive() return executive.run_command([cls.executable_name, "config", "--get-all", key], error_handler=Executive.ignore_error, cwd=cwd).rstrip('\n') - @staticmethod - def commit_success_regexp(): - return "^Committed r(?P<svn_revision>\d+)$" - - def discard_local_commits(self): - self._run_git(['reset', '--hard', self.remote_branch_ref()]) + def _discard_local_commits(self): + self._run_git(['reset', '--hard', self._remote_branch_ref()]) - def local_commits(self, ref='HEAD'): - return self._run_git(['log', '--pretty=oneline', ref + '...' + self.remote_branch_ref()]).splitlines() + def _local_commits(self, ref='HEAD'): + return self._run_git(['log', '--pretty=oneline', ref + '...' + self._remote_branch_ref()]).splitlines() - def rebase_in_progress(self): + def _rebase_in_progress(self): return self._filesystem.exists(self.absolute_path(self._filesystem.join('.git', 'rebase-apply'))) def has_working_directory_changes(self): return self._run_git(['diff', 'HEAD', '--no-renames', '--name-only']) != "" - def discard_working_directory_changes(self): + def _discard_working_directory_changes(self): # Could run git clean here too, but that wouldn't match subversion self._run_git(['reset', 'HEAD', '--hard']) # Aborting rebase even though this does not match subversion - if self.rebase_in_progress(): + if self._rebase_in_progress(): self._run_git(['rebase', '--abort']) def status_command(self): @@ -185,7 +140,7 @@ class Git(SCM, SVNRepository): current_branch = self.current_branch() return self._branch_from_ref(self.read_git_config('branch.%s.merge' % current_branch, cwd=self.checkout_root, executive=self._executive).strip()) - def merge_base(self, git_commit): + def _merge_base(self, git_commit=None): if git_commit: # Rewrite UPSTREAM to the upstream branch if 'UPSTREAM' in git_commit: @@ -202,45 +157,20 @@ class Git(SCM, SVNRepository): git_commit = git_commit + "^.." + git_commit return git_commit - return self.remote_merge_base() + return self._remote_merge_base() def changed_files(self, git_commit=None): # FIXME: --diff-filter could be used to avoid the "extract_filenames" step. - status_command = [self.executable_name, 'diff', '-r', '--name-status', "--no-renames", "--no-ext-diff", "--full-index", self.merge_base(git_commit)] + status_command = [self.executable_name, 'diff', '-r', '--name-status', "--no-renames", "--no-ext-diff", "--full-index", self._merge_base(git_commit)] # FIXME: I'm not sure we're returning the same set of files that SVN.changed_files is. # Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R) - return self.run_status_and_extract_filenames(status_command, self._status_regexp("ADM")) - - def _changes_files_for_commit(self, git_commit): - # --pretty="format:" makes git show not print the commit log header, - changed_files = self._run_git(["show", "--pretty=format:", "--name-only", git_commit]).splitlines() - # instead it just prints a blank line at the top, so we skip the blank line: - return changed_files[1:] - - def changed_files_for_revision(self, revision): - commit_id = self.git_commit_from_svn_revision(revision) - return self._changes_files_for_commit(commit_id) - - def revisions_changing_file(self, path, limit=5): - # raise a script error if path does not exists to match the behavior of the svn implementation. - if not self._filesystem.exists(path): - raise ScriptError(message="Path %s does not exist." % path) - - # git rev-list head --remove-empty --limit=5 -- path would be equivalent. - commit_ids = self._run_git(["log", "--remove-empty", "--pretty=format:%H", "-%s" % limit, "--", path]).splitlines() - return filter(lambda revision: revision, map(self.svn_revision_from_git_commit, commit_ids)) + return self._run_status_and_extract_filenames(status_command, self._status_regexp("ADM")) - def conflicted_files(self): - # We do not need to pass decode_output for this diff command - # as we're passing --name-status which does not output any data. - status_command = [self.executable_name, 'diff', '--name-status', '--no-renames', '--diff-filter=U'] - return self.run_status_and_extract_filenames(status_command, self._status_regexp("U")) + def _added_files(self): + return self._run_status_and_extract_filenames(self.status_command(), self._status_regexp("A")) - def added_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("A")) - - def deleted_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("D")) + def _deleted_files(self): + return self._run_status_and_extract_filenames(self.status_command(), self._status_regexp("D")) @staticmethod def supports_local_commits(): @@ -276,8 +206,8 @@ class Git(SCM, SVNRepository): time_without_timezone = time_with_timezone - datetime.timedelta(hours=sign * int(match.group(8)), minutes=int(match.group(9))) return time_without_timezone.strftime('%Y-%m-%dT%H:%M:%SZ') - def prepend_svn_revision(self, diff): - revision = self.head_svn_revision() + def _prepend_svn_revision(self, diff): + revision = self._head_svn_revision() if not revision: return diff @@ -296,164 +226,28 @@ class Git(SCM, SVNRepository): if self._filesystem.exists(order_file): order = "-O%s" % order_file - command = [self.executable_name, 'diff', '--binary', '--no-color', "--no-ext-diff", "--full-index", "--no-renames", order, self.merge_base(git_commit), "--"] + command = [self.executable_name, 'diff', '--binary', '--no-color', "--no-ext-diff", "--full-index", "--no-renames", order, self._merge_base(git_commit), "--"] if changed_files: command += changed_files - return self.prepend_svn_revision(self.run(command, decode_output=False, cwd=self.checkout_root)) + return self._prepend_svn_revision(self._run(command, decode_output=False, cwd=self.checkout_root)) - def _run_git_svn_find_rev(self, arg): + @memoized + def svn_revision_from_git_commit(self, git_commit): # git svn find-rev always exits 0, even when the revision or commit is not found. - return self._run_git(['svn', 'find-rev', arg]).rstrip() - - def _string_to_int_or_none(self, string): try: - return int(string) + return int(self._run_git(['svn', 'find-rev', git_commit]).rstrip()) except ValueError, e: return None - @memoized - def git_commit_from_svn_revision(self, svn_revision): - # FIXME: https://bugs.webkit.org/show_bug.cgi?id=111668 - # We should change this to run git log --grep 'git-svn-id' instead - # so that we don't require git+svn to be set up. - git_commit = self._run_git_svn_find_rev('r%s' % svn_revision) - if not git_commit: - # FIXME: Alternatively we could offer to update the checkout? Or return None? - raise ScriptError(message='Failed to find git commit for revision %s, your checkout likely needs an update.' % svn_revision) - return git_commit - - @memoized - def svn_revision_from_git_commit(self, git_commit): - svn_revision = self._run_git_svn_find_rev(git_commit) - return self._string_to_int_or_none(svn_revision) - - def contents_at_revision(self, path, revision): - """Returns a byte array (str()) containing the contents - of path @ revision in the repository.""" - return self._run_git(["show", "%s:%s" % (self.git_commit_from_svn_revision(revision), path)], decode_output=False) - - def diff_for_revision(self, revision): - git_commit = self.git_commit_from_svn_revision(revision) - return self.create_patch(git_commit) - - def diff_for_file(self, path, log=None): - return self._run_git(['diff', 'HEAD', '--no-renames', '--', path]) - - def show_head(self, path): - return self._run_git(['show', 'HEAD:' + self.to_object_name(path)], decode_output=False) - - def committer_email_for_revision(self, revision): - git_commit = self.git_commit_from_svn_revision(revision) - committer_email = self._run_git(["log", "-1", "--pretty=format:%ce", git_commit]) - # Git adds an extra @repository_hash to the end of every committer email, remove it: - return committer_email.rsplit("@", 1)[0] - - def apply_reverse_diff(self, revision): - # Assume the revision is an svn revision. - git_commit = self.git_commit_from_svn_revision(revision) - # I think this will always fail due to ChangeLogs. - self._run_git(['revert', '--no-commit', git_commit], error_handler=Executive.ignore_error) - - def revert_files(self, file_paths): - self._run_git(['checkout', 'HEAD'] + file_paths) - - def _assert_can_squash(self, has_working_directory_changes): - squash = self.read_git_config('webkit-patch.commit-should-always-squash', cwd=self.checkout_root, executive=self._executive) - should_squash = squash and squash.lower() == "true" - - if not should_squash: - # Only warn if there are actually multiple commits to squash. - num_local_commits = len(self.local_commits()) - if num_local_commits > 1 or (num_local_commits > 0 and has_working_directory_changes): - raise AmbiguousCommitError(num_local_commits, has_working_directory_changes) - - def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None): - # Username is ignored during Git commits. - has_working_directory_changes = self.has_working_directory_changes() - - if git_commit: - # Special-case HEAD.. to mean working-copy changes only. - if git_commit.upper() == 'HEAD..': - if not has_working_directory_changes: - raise ScriptError(message="The working copy is not modified. --git-commit=HEAD.. only commits working copy changes.") - self.commit_locally_with_message(message) - return self._commit_on_branch(message, 'HEAD', username=username, password=password) - - # Need working directory changes to be committed so we can checkout the merge branch. - if has_working_directory_changes: - # FIXME: webkit-patch land will modify the ChangeLogs to correct the reviewer. - # That will modify the working-copy and cause us to hit this error. - # The ChangeLog modification could be made to modify the existing local commit. - raise ScriptError(message="Working copy is modified. Cannot commit individual git_commits.") - return self._commit_on_branch(message, git_commit, username=username, password=password) - - if not force_squash: - self._assert_can_squash(has_working_directory_changes) - self._run_git(['reset', '--soft', self.remote_merge_base()]) - self.commit_locally_with_message(message) - return self.push_local_commits_to_server(username=username, password=password) - def checkout_branch(self, name): self._run_git(['checkout', '-q', name]) def create_clean_branch(self, name): - self._run_git(['checkout', '-q', '-b', name, self.remote_branch_ref()]) - - def _commit_on_branch(self, message, git_commit, username=None, password=None): - branch_name = self.current_branch() - commit_ids = self.commit_ids_from_commitish_arguments([git_commit]) - - # We want to squash all this branch's commits into one commit with the proper description. - # We do this by doing a "merge --squash" into a new commit branch, then dcommitting that. - MERGE_BRANCH_NAME = 'webkit-patch-land' - self.delete_branch(MERGE_BRANCH_NAME) - - # We might be in a directory that's present in this branch but not in the - # trunk. Move up to the top of the tree so that git commands that expect a - # valid CWD won't fail after we check out the merge branch. - # FIXME: We should never be using chdir! We can instead pass cwd= to run_command/self.run! - self._filesystem.chdir(self.checkout_root) - - # Stuff our change into the merge branch. - # We wrap in a try...finally block so if anything goes wrong, we clean up the branches. - commit_succeeded = True - try: - self.create_clean_branch(MERGE_BRANCH_NAME) - - for commit in commit_ids: - # We're on a different branch now, so convert "head" to the branch name. - commit = re.sub(r'(?i)head', branch_name, commit) - # FIXME: Once changed_files and create_patch are modified to separately handle each - # commit in a commit range, commit each cherry pick so they'll get dcommitted separately. - self._run_git(['cherry-pick', '--no-commit', commit]) - - self._run_git(['commit', '-m', message]) - output = self.push_local_commits_to_server(username=username, password=password) - except Exception, e: - _log.warning("COMMIT FAILED: " + str(e)) - output = "Commit failed." - commit_succeeded = False - finally: - # And then swap back to the original branch and clean up. - self.discard_working_directory_changes() - self.checkout_branch(branch_name) - self.delete_branch(MERGE_BRANCH_NAME) - - return output - - def svn_commit_log(self, svn_revision): - svn_revision = self.strip_r_from_svn_revision(svn_revision) - return self._run_git(['svn', 'log', '-r', svn_revision]) - - def last_svn_commit_log(self): - return self._run_git(['svn', 'log', '--limit=1']) + self._run_git(['checkout', '-q', '-b', name, self._remote_branch_ref()]) def blame(self, path): return self._run_git(['blame', path]) - def svn_blame(self, path): - return self._run_git(['svn', 'blame', path]) - # Git-specific methods: def _branch_ref_exists(self, branch_ref): return self._run_git(['show-ref', '--quiet', '--verify', branch_ref], return_exit_code=True) == 0 @@ -462,10 +256,10 @@ class Git(SCM, SVNRepository): if self._branch_ref_exists('refs/heads/' + branch_name): self._run_git(['branch', '-D', branch_name]) - def remote_merge_base(self): - return self._run_git(['merge-base', self.remote_branch_ref(), 'HEAD']).strip() + def _remote_merge_base(self): + return self._run_git(['merge-base', self._remote_branch_ref(), 'HEAD']).strip() - def remote_branch_ref(self): + def _remote_branch_ref(self): # Use references so that we can avoid collisions, e.g. we don't want to operate on refs/heads/trunk if it exists. remote_branch_refs = self.read_git_config('svn-remote.svn.fetch', cwd=self.checkout_root, executive=self._executive) if not remote_branch_refs: @@ -485,49 +279,6 @@ class Git(SCM, SVNRepository): command.insert(1, '--all') self._run_git(command, input=message) - def push_local_commits_to_server(self, username=None, password=None): - dcommit_command = ['svn', 'dcommit'] - if (not username or not password) and not self.has_authorization_for_realm(self.svn_server_realm): - raise AuthenticationError(self.svn_server_host, prompt_for_password=True) - if username: - dcommit_command.extend(["--username", username]) - output = self._run_git(dcommit_command, error_handler=commit_error_handler, input=password) - return output - - # This function supports the following argument formats: - # no args : rev-list trunk..HEAD - # A..B : rev-list A..B - # A...B : error! - # A B : [A, B] (different from git diff, which would use "rev-list A..B") - def commit_ids_from_commitish_arguments(self, args): - if not len(args): - args.append('%s..HEAD' % self.remote_branch_ref()) - - commit_ids = [] - for commitish in args: - if '...' in commitish: - raise ScriptError(message="'...' is not supported (found in '%s'). Did you mean '..'?" % commitish) - elif '..' in commitish: - commit_ids += reversed(self._run_git(['rev-list', commitish]).splitlines()) - else: - # Turn single commits or branch or tag names into commit ids. - commit_ids += self._run_git(['rev-parse', '--revs-only', commitish]).splitlines() - return commit_ids - - def commit_message_for_local_commit(self, commit_id): - commit_lines = self._run_git(['cat-file', 'commit', commit_id]).splitlines() - - # Skip the git headers. - first_line_after_headers = 0 - for line in commit_lines: - first_line_after_headers += 1 - if line == "": - break - return CommitMessage(commit_lines[first_line_after_headers:]) - - def files_changed_summary_for_commit(self, commit_id): - return self._run_git(['diff-tree', '--shortstat', '--no-renames', '--no-commit-id', commit_id]) - # These methods are git specific and are meant to provide support for the Git oriented workflow # that Blink is moving towards, hence there are no equivalent methods in the SVN class. @@ -552,18 +303,18 @@ class Git(SCM, SVNRepository): if not match: raise ScriptError(message="Unable to find local branch tracking origin/master.") branch = str(match.group("branch_name")) - return self._run_git(['rev-parse', '--symbolic-full-name', branch]).strip() + return self._branch_from_ref(self._run_git(['rev-parse', '--symbolic-full-name', branch]).strip()) def is_cleanly_tracking_remote_master(self): if self.has_working_directory_changes(): return False if self.current_branch() != self._branch_tracking_remote_master(): return False - if len(self.local_commits(self._branch_tracking_remote_master())) > 0: + if len(self._local_commits(self._branch_tracking_remote_master())) > 0: return False return True def ensure_cleanly_tracking_remote_master(self): - self.discard_working_directory_changes() - self._run_git(['checkout', '-q', self._branch_tracking_remote_master().replace('refs/heads/', '', 1)]) - self.discard_local_commits() + self._discard_working_directory_changes() + self._run_git(['checkout', '-q', self._branch_tracking_remote_master()]) + self._discard_local_commits() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm.py index c57aec9fa69..daa477ea7af 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm.py @@ -39,25 +39,6 @@ from webkitpy.common.system.filesystem import FileSystem _log = logging.getLogger(__name__) -class CheckoutNeedsUpdate(ScriptError): - def __init__(self, script_args, exit_code, output, cwd): - ScriptError.__init__(self, script_args=script_args, exit_code=exit_code, output=output, cwd=cwd) - - -# FIXME: Should be moved onto SCM -def commit_error_handler(error): - if re.search("resource out of date", error.output): - raise CheckoutNeedsUpdate(script_args=error.script_args, exit_code=error.exit_code, output=error.output, cwd=error.cwd) - Executive.default_error_handler(error) - - -class AuthenticationError(Exception): - def __init__(self, server_host, prompt_for_password=False): - self.server_host = server_host - self.prompt_for_password = prompt_for_password - - - # SCM methods are expected to return paths relative to self.checkout_root. class SCM: def __init__(self, cwd, executive=None, filesystem=None): @@ -67,7 +48,7 @@ class SCM: self.checkout_root = self.find_checkout_root(self.cwd) # A wrapper used by subclasses to create processes. - def run(self, args, cwd=None, input=None, error_handler=None, return_exit_code=False, return_stderr=True, decode_output=True): + def _run(self, args, cwd=None, input=None, error_handler=None, return_exit_code=False, return_stderr=True, decode_output=True): # FIXME: We should set cwd appropriately. return self._executive.run_command(args, cwd=cwd, @@ -82,18 +63,10 @@ class SCM: def absolute_path(self, repository_relative_path): return self._filesystem.join(self.checkout_root, repository_relative_path) - # FIXME: This belongs in Checkout, not SCM. - def scripts_directory(self): - return self._filesystem.join(self.checkout_root, "Tools", "Scripts") - - # FIXME: This belongs in Checkout, not SCM. - def script_path(self, script_name): - return self._filesystem.join(self.scripts_directory(), script_name) - - def run_status_and_extract_filenames(self, status_command, status_regexp): + def _run_status_and_extract_filenames(self, status_command, status_regexp): filenames = [] # We run with cwd=self.checkout_root so that returned-paths are root-relative. - for line in self.run(status_command, cwd=self.checkout_root).splitlines(): + for line in self._run(status_command, cwd=self.checkout_root).splitlines(): match = re.search(status_regexp, line) if not match: continue @@ -102,16 +75,6 @@ class SCM: filenames.append(filename) return filenames - def strip_r_from_svn_revision(self, svn_revision): - match = re.match("^r(?P<svn_revision>\d+)", unicode(svn_revision)) - if (match): - return match.group('svn_revision') - return svn_revision - - def svn_revision_from_commit_text(self, commit_text): - match = re.search(self.commit_success_regexp(), commit_text, re.MULTILINE) - return match.group('svn_revision') - @staticmethod def _subclass_must_implement(): raise NotImplementedError("subclasses must implement") @@ -123,13 +86,6 @@ class SCM: def find_checkout_root(self, path): SCM._subclass_must_implement() - @staticmethod - def commit_success_regexp(): - SCM._subclass_must_implement() - - def status_command(self): - self._subclass_must_implement() - def add(self, path, return_exit_code=False): self.add_list([path], return_exit_code) @@ -151,22 +107,16 @@ class SCM: def changed_files(self, git_commit=None): self._subclass_must_implement() - def changed_files_for_revision(self, revision): - self._subclass_must_implement() - - def revisions_changing_file(self, path, limit=5): + def _added_files(self): self._subclass_must_implement() - def added_files(self): - self._subclass_must_implement() - - def conflicted_files(self): + def _deleted_files(self): self._subclass_must_implement() def display_name(self): self._subclass_must_implement() - def head_svn_revision(self): + def _head_svn_revision(self): return self.svn_revision(self.checkout_root) def svn_revision(self, path): @@ -176,51 +126,12 @@ class SCM: def timestamp_of_revision(self, path, revision): self._subclass_must_implement() - def create_patch(self, git_commit=None, changed_files=None): - self._subclass_must_implement() - - def committer_email_for_revision(self, revision): - self._subclass_must_implement() - - def contents_at_revision(self, path, revision): - self._subclass_must_implement() - - def diff_for_revision(self, revision): - self._subclass_must_implement() - - def diff_for_file(self, path, log=None): - self._subclass_must_implement() - - def show_head(self, path): - self._subclass_must_implement() - - def apply_reverse_diff(self, revision): - self._subclass_must_implement() - - def revert_files(self, file_paths): - self._subclass_must_implement() - - def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None): - self._subclass_must_implement() - - def svn_commit_log(self, svn_revision): - self._subclass_must_implement() - - def last_svn_commit_log(self): - self._subclass_must_implement() - def blame(self, path): self._subclass_must_implement() - def svn_blame(self, path): - self._subclass_must_implement() - def has_working_directory_changes(self): self._subclass_must_implement() - def discard_working_directory_changes(self): - self._subclass_must_implement() - #-------------------------------------------------------------------------- # Subclasses must indicate if they support local commits, # but the SCM baseclass will only call local_commits methods when this is true. @@ -228,28 +139,6 @@ class SCM: def supports_local_commits(): SCM._subclass_must_implement() - def local_commits(self): - return [] - - def has_local_commits(self): - return len(self.local_commits()) > 0 - - def discard_local_commits(self): - return - - def remote_merge_base(self): - SCM._subclass_must_implement() - def commit_locally_with_message(self, message, commit_all_working_directory_changes=True): _log.error("Your source control manager does not support local commits.") sys.exit(1) - - def local_changes_exist(self): - return (self.supports_local_commits() and self.has_local_commits()) or self.has_working_directory_changes() - - def discard_local_changes(self): - if self.has_working_directory_changes(): - self.discard_working_directory_changes() - - if self.has_local_commits(): - self.discard_local_commits() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py index 192c03c905d..d07cd700e7b 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_mock.py @@ -26,7 +26,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from webkitpy.common.checkout.scm import CommitMessage from webkitpy.common.system.filesystem_mock import MockFileSystem from webkitpy.common.system.executive_mock import MockExecutive @@ -35,7 +34,7 @@ class MockSCM(object): executable_name = "MockSCM" def __init__(self, filesystem=None, executive=None): - self.checkout_root = "/mock-checkout" + self.checkout_root = "/mock-checkout/third_party/WebKit" self.added_paths = set() self._filesystem = filesystem or MockFileSystem() self._executive = executive or MockExecutive() @@ -51,9 +50,6 @@ class MockSCM(object): def has_working_directory_changes(self): return False - def discard_working_directory_changes(self): - pass - def ensure_cleanly_tracking_remote_master(self): pass @@ -72,15 +68,6 @@ class MockSCM(object): def supports_local_commits(self): return True - def has_local_commits(self): - return False - - def discard_local_commits(self): - pass - - def discard_local_changes(self): - pass - def exists(self, path): # TestRealMain.test_real_main (and several other rebaseline tests) are sensitive to this return value. # We should make those tests more robust, but for now we just return True always (since no test needs otherwise). @@ -89,60 +76,9 @@ class MockSCM(object): def absolute_path(self, *comps): return self._filesystem.join(self.checkout_root, *comps) - def changed_files(self, git_commit=None): - return ["MockFile1"] - - def changed_files_for_revision(self, revision): - return ["MockFile1"] - - def head_svn_revision(self): - return '1234' - def svn_revision(self, path): return '5678' - def timestamp_of_revision(self, path, revision): - return '2013-02-01 08:48:05 +0000' - - def create_patch(self, git_commit, changed_files=None): - return "Patch1" - - def commit_ids_from_commitish_arguments(self, args): - return ["Commitish1", "Commitish2"] - - def committer_email_for_revision(self, revision): - return "mock@webkit.org" - - def commit_locally_with_message(self, message, commit_all_working_directory_changes=True): - pass - - def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None): - pass - - def merge_base(self, git_commit): - return None - - def commit_message_for_local_commit(self, commit_id): - if commit_id == "Commitish1": - return CommitMessage("CommitMessage1\n" \ - "https://bugs.example.org/show_bug.cgi?id=50000\n") - if commit_id == "Commitish2": - return CommitMessage("CommitMessage2\n" \ - "https://bugs.example.org/show_bug.cgi?id=50001\n") - raise Exception("Bogus commit_id in commit_message_for_local_commit.") - - def diff_for_file(self, path, log=None): - return path + '-diff' - - def diff_for_revision(self, revision): - return "DiffForRevision%s\nhttp://bugs.webkit.org/show_bug.cgi?id=12345" % revision - - def show_head(self, path): - return path - - def svn_revision_from_commit_text(self, commit_text): - return "49824" - def svn_revision_from_git_commit(self, git_commit): if git_commit == '6469e754a1': return 1234 @@ -152,6 +88,12 @@ class MockSCM(object): return 10000 return None + def timestamp_of_revision(self, path, revision): + return '2013-02-01 08:48:05 +0000' + + def commit_locally_with_message(self, message, commit_all_working_directory_changes=True): + pass + def delete(self, path): return self.delete_list([path]) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py index a9dabcce412..2b172ecff8d 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/scm_unittest.py @@ -29,464 +29,207 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import atexit -import base64 -import codecs -import getpass import os -import os.path -import re -import stat -import sys -import subprocess -import tempfile -import time -import webkitpy.thirdparty.unittest2 as unittest -import urllib import shutil -from datetime import date -from webkitpy.common.checkout.checkout import Checkout from webkitpy.common.system.executive import Executive, ScriptError -from webkitpy.common.system.filesystem_mock import MockFileSystem -from webkitpy.common.system.outputcapture import OutputCapture from webkitpy.common.system.executive_mock import MockExecutive -from .git import Git, AmbiguousCommitError -from .detection import detect_scm_system -from .scm import SCM, CheckoutNeedsUpdate, commit_error_handler, AuthenticationError -from .svn import SVN +from webkitpy.common.system.filesystem import FileSystem +from webkitpy.common.system.filesystem_mock import MockFileSystem +from webkitpy.common.checkout.scm.detection import detect_scm_system +from webkitpy.common.checkout.scm.git import Git, AmbiguousCommitError +from webkitpy.common.checkout.scm.scm import SCM +from webkitpy.common.checkout.scm.svn import SVN +import webkitpy.thirdparty.unittest2 as unittest # We cache the mock SVN repo so that we don't create it again for each call to an SVNTest or GitTest test_ method. # We store it in a global variable so that we can delete this cached repo on exit(3). -# FIXME: Remove this once we migrate to Python 2.7. Unittest in Python 2.7 supports module-specific setup and teardown functions. +original_cwd = None cached_svn_repo_path = None - -def remove_dir(path): - # Change directory to / to ensure that we aren't in the directory we want to delete. - os.chdir('/') - shutil.rmtree(path) - - -# FIXME: Remove this once we migrate to Python 2.7. Unittest in Python 2.7 supports module-specific setup and teardown functions. @atexit.register -def delete_cached_mock_repo_at_exit(): +def delete_cached_svn_repo_at_exit(): if cached_svn_repo_path: - remove_dir(cached_svn_repo_path) - -# Eventually we will want to write tests which work for both scms. (like update_webkit, changed_files, etc.) -# Perhaps through some SCMTest base-class which both SVNTest and GitTest inherit from. - -def run_command(*args, **kwargs): - # FIXME: This should not be a global static. - # New code should use Executive.run_command directly instead - return Executive().run_command(*args, **kwargs) - + os.chdir(original_cwd) + shutil.rmtree(cached_svn_repo_path) -# FIXME: This should be unified into one of the executive.py commands! -# Callers could use run_and_throw_if_fail(args, cwd=cwd, quiet=True) -def run_silent(args, cwd=None): - # Note: Not thread safe: http://bugs.python.org/issue2320 - process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) - process.communicate() # ignore output - exit_code = process.wait() - if exit_code: - raise ScriptError('Failed to run "%s" exit_code: %d cwd: %s' % (args, exit_code, cwd)) +class SCMTestBase(unittest.TestCase): + def __init__(self, *args, **kwargs): + super(SCMTestBase, self).__init__(*args, **kwargs) + self.scm = None + self.executive = None + self.fs = None + self.original_cwd = None -def write_into_file_at_path(file_path, contents, encoding="utf-8"): - if encoding: - with codecs.open(file_path, "w", encoding) as file: - file.write(contents) - else: - with open(file_path, "w") as file: - file.write(contents) + def setUp(self): + self.executive = Executive() + self.fs = FileSystem() + self.original_cwd = self.fs.getcwd() + def tearDown(self): + self._chdir(self.original_cwd) -def read_from_path(file_path, encoding="utf-8"): - with codecs.open(file_path, "r", encoding) as file: - return file.read() + def _join(self, *comps): + return self.fs.join(*comps) + def _chdir(self, path): + self.fs.chdir(path) -def _make_diff(command, *args): - # We use this wrapper to disable output decoding. diffs should be treated as - # binary files since they may include text files of multiple differnet encodings. - # FIXME: This should use an Executive. - return run_command([command, "diff"] + list(args), decode_output=False) + def _mkdir(self, path): + assert not self.fs.exists(path) + self.fs.maybe_make_directory(path) + def _mkdtemp(self, **kwargs): + return str(self.fs.mkdtemp(**kwargs)) -def _svn_diff(*args): - return _make_diff("svn", *args) + def _remove(self, path): + self.fs.remove(path) + def _rmtree(self, path): + self.fs.rmtree(path) -def _git_diff(*args): - return _make_diff("git", *args) + def _run(self, *args, **kwargs): + return self.executive.run_command(*args, **kwargs) + def _run_silent(self, args, **kwargs): + self.executive.run_and_throw_if_fail(args, quiet=True, **kwargs) -# Exists to share svn repository creation code between the git and svn tests -class SVNTestRepository(object): - @classmethod - def _svn_add(cls, path): - run_command(["svn", "add", path]) + def _write_text_file(self, path, contents): + self.fs.write_text_file(path, contents) - @classmethod - def _svn_commit(cls, message): - run_command(["svn", "commit", "--quiet", "--message", message]) + def _write_binary_file(self, path, contents): + self.fs.write_binary_file(path, contents) - @classmethod - def _setup_test_commits(cls, svn_repo_url): + def _make_diff(self, command, *args): + # We use this wrapper to disable output decoding. diffs should be treated as + # binary files since they may include text files of multiple differnet encodings. + return self._run([command, "diff"] + list(args), decode_output=False) - svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout") - run_command(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path]) + def _svn_diff(self, *args): + return self._make_diff("svn", *args) - # Add some test commits - os.chdir(svn_checkout_path) + def _git_diff(self, *args): + return self._make_diff("git", *args) - write_into_file_at_path("test_file", "test1") - cls._svn_add("test_file") - cls._svn_commit("initial commit") - - write_into_file_at_path("test_file", "test1test2") - # This used to be the last commit, but doing so broke - # GitTest.test_apply_git_patch which use the inverse diff of the last commit. - # svn-apply fails to remove directories in Git, see: - # https://bugs.webkit.org/show_bug.cgi?id=34871 - os.mkdir("test_dir") - # Slash should always be the right path separator since we use cygwin on Windows. - test_file3_path = "test_dir/test_file3" - write_into_file_at_path(test_file3_path, "third file") - cls._svn_add("test_dir") - cls._svn_commit("second commit") + def _svn_add(self, path): + self._run(["svn", "add", path]) - write_into_file_at_path("test_file", "test1test2test3\n") - write_into_file_at_path("test_file2", "second file") - cls._svn_add("test_file2") - cls._svn_commit("third commit") - - # This 4th commit is used to make sure that our patch file handling - # code correctly treats patches as binary and does not attempt to - # decode them assuming they're utf-8. - write_into_file_at_path("test_file", u"latin1 test: \u00A0\n", "latin1") - write_into_file_at_path("test_file2", u"utf-8 test: \u00A0\n", "utf-8") - cls._svn_commit("fourth commit") - - # svn does not seem to update after commit as I would expect. - run_command(['svn', 'update']) - remove_dir(svn_checkout_path) + def _svn_commit(self, message): + self._run(["svn", "commit", "--quiet", "--message", message]) # This is a hot function since it's invoked by unittest before calling each test_ method in SVNTest and # GitTest. We create a mock SVN repo once and then perform an SVN checkout from a filesystem copy of # it since it's expensive to create the mock repo. - @classmethod - def setup(cls, test_object): + def _set_up_svn_checkout(self): global cached_svn_repo_path + global original_cwd if not cached_svn_repo_path: - cached_svn_repo_path = cls._setup_mock_repo() - - test_object.temp_directory = tempfile.mkdtemp(suffix="svn_test") - test_object.svn_repo_path = os.path.join(test_object.temp_directory, "repo") - test_object.svn_repo_url = "file://%s" % test_object.svn_repo_path - test_object.svn_checkout_path = os.path.join(test_object.temp_directory, "checkout") - shutil.copytree(cached_svn_repo_path, test_object.svn_repo_path) - run_command(['svn', 'checkout', '--quiet', test_object.svn_repo_url + "/trunk", test_object.svn_checkout_path]) - - @classmethod - def _setup_mock_repo(cls): - # Create an test SVN repository - svn_repo_path = tempfile.mkdtemp(suffix="svn_test_repo") + cached_svn_repo_path = self._set_up_svn_repo() + original_cwd = self.original_cwd + + self.temp_directory = self._mkdtemp(suffix="svn_test") + self.svn_repo_path = self._join(self.temp_directory, "repo") + self.svn_repo_url = "file://%s" % self.svn_repo_path + self.svn_checkout_path = self._join(self.temp_directory, "checkout") + shutil.copytree(cached_svn_repo_path, self.svn_repo_path) + self._run(['svn', 'checkout', '--quiet', self.svn_repo_url + "/trunk", self.svn_checkout_path]) + + def _set_up_svn_repo(self): + svn_repo_path = self._mkdtemp(suffix="svn_test_repo") svn_repo_url = "file://%s" % svn_repo_path # Not sure this will work on windows # git svn complains if we don't pass --pre-1.5-compatible, not sure why: # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477 - run_command(['svnadmin', 'create', '--pre-1.5-compatible', svn_repo_path]) + self._run(['svnadmin', 'create', '--pre-1.5-compatible', svn_repo_path]) # Create a test svn checkout - svn_checkout_path = tempfile.mkdtemp(suffix="svn_test_checkout") - run_command(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path]) + svn_checkout_path = self._mkdtemp(suffix="svn_test_checkout") + self._run(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path]) # Create and checkout a trunk dir to match the standard svn configuration to match git-svn's expectations - os.chdir(svn_checkout_path) - os.mkdir('trunk') - cls._svn_add('trunk') + self._chdir(svn_checkout_path) + self._mkdir('trunk') + self._svn_add('trunk') # We can add tags and branches as well if we ever need to test those. - cls._svn_commit('add trunk') + self._svn_commit('add trunk') - # Change directory out of the svn checkout so we can delete the checkout directory. - remove_dir(svn_checkout_path) + self._rmtree(svn_checkout_path) - cls._setup_test_commits(svn_repo_url + "/trunk") + self._set_up_svn_test_commits(svn_repo_url + "/trunk") return svn_repo_path - @classmethod - def tear_down(cls, test_object): - remove_dir(test_object.temp_directory) + def _set_up_svn_test_commits(self, svn_repo_url): + svn_checkout_path = self._mkdtemp(suffix="svn_test_checkout") + self._run(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path]) - # Now that we've deleted the checkout paths, cwddir may be invalid - # Change back to a valid directory so that later calls to os.getcwd() do not fail. - if os.path.isabs(__file__): - path = os.path.dirname(__file__) - else: - path = sys.path[0] - os.chdir(detect_scm_system(path).checkout_root) + # Add some test commits + self._chdir(svn_checkout_path) + self._write_text_file("test_file", "test1") + self._svn_add("test_file") + self._svn_commit("initial commit") -# For testing the SCM baseclass directly. -class SCMClassTests(unittest.TestCase): - def setUp(self): - self.dev_null = open(os.devnull, "w") # Used to make our Popen calls quiet. + self._write_text_file("test_file", "test1test2") + # This used to be the last commit, but doing so broke + # GitTest.test_apply_git_patch which use the inverse diff of the last commit. + # svn-apply fails to remove directories in Git, see: + # https://bugs.webkit.org/show_bug.cgi?id=34871 + self._mkdir("test_dir") + # Slash should always be the right path separator since we use cygwin on Windows. + test_file3_path = "test_dir/test_file3" + self._write_text_file(test_file3_path, "third file") + self._svn_add("test_dir") + self._svn_commit("second commit") - def tearDown(self): - self.dev_null.close() - - def test_run_command_with_pipe(self): - input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) - self.assertEqual(run_command(['grep', 'bar'], input=input_process.stdout), "bar\n") - - # Test the non-pipe case too: - self.assertEqual(run_command(['grep', 'bar'], input="foo\nbar"), "bar\n") - - command_returns_non_zero = ['/bin/sh', '--invalid-option'] - # Test when the input pipe process fails. - input_process = subprocess.Popen(command_returns_non_zero, stdout=subprocess.PIPE, stderr=self.dev_null) - self.assertNotEqual(input_process.poll(), 0) - self.assertRaises(ScriptError, run_command, ['grep', 'bar'], input=input_process.stdout) - - # Test when the run_command process fails. - input_process = subprocess.Popen(['echo', 'foo\nbar'], stdout=subprocess.PIPE, stderr=self.dev_null) # grep shows usage and calls exit(2) when called w/o arguments. - self.assertRaises(ScriptError, run_command, command_returns_non_zero, input=input_process.stdout) - - def test_error_handlers(self): - git_failure_message="Merge conflict during commit: Your file or directory 'WebCore/ChangeLog' is probably out-of-date: resource out of date; try updating at /usr/local/libexec/git-core//git-svn line 469" - svn_failure_message="""svn: Commit failed (details follow): -svn: File or directory 'ChangeLog' is out of date; try updating -svn: resource out of date; try updating -""" - command_does_not_exist = ['does_not_exist', 'invalid_option'] - self.assertRaises(OSError, run_command, command_does_not_exist) - self.assertRaises(OSError, run_command, command_does_not_exist, error_handler=Executive.ignore_error) - - command_returns_non_zero = ['/bin/sh', '--invalid-option'] - self.assertRaises(ScriptError, run_command, command_returns_non_zero) - # Check if returns error text: - self.assertTrue(run_command(command_returns_non_zero, error_handler=Executive.ignore_error)) - - self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=git_failure_message)) - self.assertRaises(CheckoutNeedsUpdate, commit_error_handler, ScriptError(output=svn_failure_message)) - self.assertRaises(ScriptError, commit_error_handler, ScriptError(output='blah blah blah')) - - -# GitTest and SVNTest inherit from this so any test_ methods here will be run once for this class and then once for each subclass. -class SCMTest(unittest.TestCase): - def _create_patch(self, patch_contents): - # FIXME: This code is brittle if the Attachment API changes. - attachment = Attachment({"bug_id": 12345}, None) - attachment.contents = lambda: patch_contents - - joe_cool = Committer("Joe Cool", "joe@cool.com") - attachment.reviewer = lambda: joe_cool - - return attachment - - def _setup_webkittools_scripts_symlink(self, local_scm): - webkit_scm = detect_scm_system(os.path.dirname(os.path.abspath(__file__))) - webkit_scripts_directory = webkit_scm.scripts_directory() - local_scripts_directory = local_scm.scripts_directory() - os.mkdir(os.path.dirname(local_scripts_directory)) - os.symlink(webkit_scripts_directory, local_scripts_directory) - - # Tests which both GitTest and SVNTest should run. - # FIXME: There must be a simpler way to add these w/o adding a wrapper method to both subclasses - - def _shared_test_changed_files(self): - write_into_file_at_path("test_file", "changed content") - self.assertItemsEqual(self.scm.changed_files(), ["test_file"]) - write_into_file_at_path("test_dir/test_file3", "new stuff") - self.assertItemsEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"]) - old_cwd = os.getcwd() - os.chdir("test_dir") - # Validate that changed_files does not change with our cwd, see bug 37015. - self.assertItemsEqual(self.scm.changed_files(), ["test_dir/test_file3", "test_file"]) - os.chdir(old_cwd) - - def _shared_test_added_files(self): - write_into_file_at_path("test_file", "changed content") - self.assertItemsEqual(self.scm.added_files(), []) - - write_into_file_at_path("added_file", "new stuff") - self.scm.add("added_file") - - write_into_file_at_path("added_file3", "more new stuff") - write_into_file_at_path("added_file4", "more new stuff") - self.scm.add_list(["added_file3", "added_file4"]) - - os.mkdir("added_dir") - write_into_file_at_path("added_dir/added_file2", "new stuff") - self.scm.add("added_dir") - - # SVN reports directory changes, Git does not. - added_files = self.scm.added_files() - if "added_dir" in added_files: - added_files.remove("added_dir") - self.assertItemsEqual(added_files, ["added_dir/added_file2", "added_file", "added_file3", "added_file4"]) - - # Test also to make sure discard_working_directory_changes removes added files - self.scm.discard_working_directory_changes() - self.assertItemsEqual(self.scm.added_files(), []) - self.assertFalse(os.path.exists("added_file")) - self.assertFalse(os.path.exists("added_file3")) - self.assertFalse(os.path.exists("added_file4")) - self.assertFalse(os.path.exists("added_dir")) - - def _shared_test_changed_files_for_revision(self): - # SVN reports directory changes, Git does not. - changed_files = self.scm.changed_files_for_revision(3) - if "test_dir" in changed_files: - changed_files.remove("test_dir") - self.assertItemsEqual(changed_files, ["test_dir/test_file3", "test_file"]) - self.assertItemsEqual(self.scm.changed_files_for_revision(4), ["test_file", "test_file2"]) # Git and SVN return different orders. - self.assertItemsEqual(self.scm.changed_files_for_revision(2), ["test_file"]) - - def _shared_test_contents_at_revision(self): - self.assertEqual(self.scm.contents_at_revision("test_file", 3), "test1test2") - self.assertEqual(self.scm.contents_at_revision("test_file", 4), "test1test2test3\n") - - # Verify that contents_at_revision returns a byte array, aka str(): - self.assertEqual(self.scm.contents_at_revision("test_file", 5), u"latin1 test: \u00A0\n".encode("latin1")) - self.assertEqual(self.scm.contents_at_revision("test_file2", 5), u"utf-8 test: \u00A0\n".encode("utf-8")) - - self.assertEqual(self.scm.contents_at_revision("test_file2", 4), "second file") - # Files which don't exist: - # Currently we raise instead of returning None because detecting the difference between - # "file not found" and any other error seems impossible with svn (git seems to expose such through the return code). - self.assertRaises(ScriptError, self.scm.contents_at_revision, "test_file2", 2) - self.assertRaises(ScriptError, self.scm.contents_at_revision, "does_not_exist", 2) - - def _shared_test_revisions_changing_file(self): - self.assertItemsEqual(self.scm.revisions_changing_file("test_file"), [5, 4, 3, 2]) - self.assertRaises(ScriptError, self.scm.revisions_changing_file, "non_existent_file") - - def _shared_test_committer_email_for_revision(self): - self.assertEqual(self.scm.committer_email_for_revision(3), getpass.getuser()) # Committer "email" will be the current user - - def _shared_test_reverse_diff(self): - self._setup_webkittools_scripts_symlink(self.scm) # Git's apply_reverse_diff uses resolve-ChangeLogs - # Only test the simple case, as any other will end up with conflict markers. - self.scm.apply_reverse_diff('5') - self.assertEqual(read_from_path('test_file'), "test1test2test3\n") - - def _shared_test_diff_for_revision(self): - # Patch formats are slightly different between svn and git, so just regexp for things we know should be there. - r3_patch = self.scm.diff_for_revision(4) - self.assertRegexpMatches(r3_patch, 'test3') - self.assertNotRegexpMatches(r3_patch, 'test4') - self.assertRegexpMatches(r3_patch, 'test2') - self.assertRegexpMatches(self.scm.diff_for_revision(3), 'test2') - - def _shared_test_svn_apply_git_patch(self): - self._setup_webkittools_scripts_symlink(self.scm) - git_binary_addition = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif -new file mode 100644 -index 0000000000000000000000000000000000000000..64a9532e7794fcd791f6f12157406d90 -60151690 -GIT binary patch -literal 512 -zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c? -zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap -zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ -zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A -zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&) -zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b -zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB -z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X -z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4 -ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H - -literal 0 -HcmV?d00001 - -""" - self.checkout.apply_patch(self._create_patch(git_binary_addition)) - added = read_from_path('fizzbuzz7.gif', encoding=None) - self.assertEqual(512, len(added)) - self.assertTrue(added.startswith('GIF89a')) - self.assertIn('fizzbuzz7.gif', self.scm.changed_files()) - - # The file already exists. - self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_addition)) - - git_binary_modification = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif -index 64a9532e7794fcd791f6f12157406d9060151690..323fae03f4606ea9991df8befbb2fca7 -GIT binary patch -literal 7 -OcmYex&reD$;sO8*F9L)B - -literal 512 -zcmZ?wbhEHbRAx|MU|?iW{Kxc~?KofD;ckY;H+&5HnHl!!GQMD7h+sU{_)e9f^V3c? -zhJP##HdZC#4K}7F68@!1jfWQg2daCm-gs#3|JREDT>c+pG4L<_2;w##WMO#ysPPap -zLqpAf1OE938xAsSp4!5f-o><?VKe(#0jEcwfHGF4%M1^kRs14oVBp2ZEL{E1N<-zJ -zsfLmOtKta;2_;2c#^S1-8cf<nb!QnGl>c!Xe6RXvrEtAWBvSDTgTO1j3vA31Puw!A -zs(87q)j_mVDTqBo-P+03-P5mHCEnJ+x}YdCuS7#bCCyePUe(ynK+|4b-3qK)T?Z&) -zYG+`tl4h?GZv_$t82}X4*DTE|$;{DEiPyF@)U-1+FaX++T9H{&%cag`W1|zVP@`%b -zqiSkp6{BTpWTkCr!=<C6Q=?#~R8^JfrliAF6Q^gV9Iup8RqCXqqhqC`qsyhk<-nlB -z00f{QZvfK&|Nm#oZ0TQl`Yr$BIa6A@16O26ud7H<QM=xl`toLKnz-3h@9c9q&wm|X -z{89I|WPyD!*M?gv?q`;L=2YFeXrJQNti4?}s!zFo=5CzeBxC69xA<zrjP<wUcCRh4 -ptUl-ZG<%a~#LwkIWv&q!KSCH7tQ8cJDiw+|GV?MN)RjY50RTb-xvT&H - -""" - self.checkout.apply_patch(self._create_patch(git_binary_modification)) - modified = read_from_path('fizzbuzz7.gif', encoding=None) - self.assertEqual('foobar\n', modified) - self.assertIn('fizzbuzz7.gif', self.scm.changed_files()) - - # Applying the same modification should fail. - self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_modification)) - - git_binary_deletion = """diff --git a/fizzbuzz7.gif b/fizzbuzz7.gif -deleted file mode 100644 -index 323fae0..0000000 -GIT binary patch -literal 0 -HcmV?d00001 - -literal 7 -OcmYex&reD$;sO8*F9L)B - -""" - self.checkout.apply_patch(self._create_patch(git_binary_deletion)) - self.assertFalse(os.path.exists('fizzbuzz7.gif')) - self.assertNotIn('fizzbuzz7.gif', self.scm.changed_files()) - - # Cannot delete again. - self.assertRaises(ScriptError, self.checkout.apply_patch, self._create_patch(git_binary_deletion)) + self._write_text_file("test_file", "test1test2test3\n") + self._write_text_file("test_file2", "second file") + self._svn_add("test_file2") + self._svn_commit("third commit") + + # This 4th commit is used to make sure that our patch file handling + # code correctly treats patches as binary and does not attempt to + # decode them assuming they're utf-8. + self._write_binary_file("test_file", u"latin1 test: \u00A0\n".encode("latin-1")) + self._write_binary_file("test_file2", u"utf-8 test: \u00A0\n".encode("utf-8")) + self._svn_commit("fourth commit") + + # svn does not seem to update after commit as I would expect. + self._run(['svn', 'update']) + self._rmtree(svn_checkout_path) + + def _tear_down_svn_checkout(self): + self._rmtree(self.temp_directory) def _shared_test_add_recursively(self): - os.mkdir("added_dir") - write_into_file_at_path("added_dir/added_file", "new stuff") + self._mkdir("added_dir") + self._write_text_file("added_dir/added_file", "new stuff") self.scm.add("added_dir/added_file") - self.assertIn("added_dir/added_file", self.scm.added_files()) + self.assertIn("added_dir/added_file", self.scm._added_files()) def _shared_test_delete_recursively(self): - os.mkdir("added_dir") - write_into_file_at_path("added_dir/added_file", "new stuff") + self._mkdir("added_dir") + self._write_text_file("added_dir/added_file", "new stuff") self.scm.add("added_dir/added_file") - self.assertIn("added_dir/added_file", self.scm.added_files()) + self.assertIn("added_dir/added_file", self.scm._added_files()) self.scm.delete("added_dir/added_file") - self.assertNotIn("added_dir", self.scm.added_files()) + self.assertNotIn("added_dir", self.scm._added_files()) def _shared_test_delete_recursively_or_not(self): - os.mkdir("added_dir") - write_into_file_at_path("added_dir/added_file", "new stuff") - write_into_file_at_path("added_dir/another_added_file", "more new stuff") + self._mkdir("added_dir") + self._write_text_file("added_dir/added_file", "new stuff") + self._write_text_file("added_dir/another_added_file", "more new stuff") self.scm.add("added_dir/added_file") self.scm.add("added_dir/another_added_file") - self.assertIn("added_dir/added_file", self.scm.added_files()) - self.assertIn("added_dir/another_added_file", self.scm.added_files()) + self.assertIn("added_dir/added_file", self.scm._added_files()) + self.assertIn("added_dir/another_added_file", self.scm._added_files()) self.scm.delete("added_dir/added_file") - self.assertIn("added_dir/another_added_file", self.scm.added_files()) + self.assertIn("added_dir/another_added_file", self.scm._added_files()) def _shared_test_exists(self, scm, commit_function): - os.chdir(scm.checkout_root) + self._chdir(scm.checkout_root) self.assertFalse(scm.exists('foo.txt')) - write_into_file_at_path('foo.txt', 'some stuff') + self._write_text_file('foo.txt', 'some stuff') self.assertFalse(scm.exists('foo.txt')) scm.add('foo.txt') commit_function('adding foo') @@ -495,157 +238,33 @@ OcmYex&reD$;sO8*F9L)B commit_function('deleting foo') self.assertFalse(scm.exists('foo.txt')) - def _shared_test_head_svn_revision(self): - self.assertEqual(self.scm.head_svn_revision(), '5') - def _shared_test_move(self): - write_into_file_at_path('added_file', 'new stuff') + self._write_text_file('added_file', 'new stuff') self.scm.add('added_file') self.scm.move('added_file', 'moved_file') - self.assertIn('moved_file', self.scm.added_files()) + self.assertIn('moved_file', self.scm._added_files()) def _shared_test_move_recursive(self): - os.mkdir("added_dir") - write_into_file_at_path('added_dir/added_file', 'new stuff') - write_into_file_at_path('added_dir/another_added_file', 'more new stuff') + self._mkdir("added_dir") + self._write_text_file('added_dir/added_file', 'new stuff') + self._write_text_file('added_dir/another_added_file', 'more new stuff') self.scm.add('added_dir') self.scm.move('added_dir', 'moved_dir') - self.assertIn('moved_dir/added_file', self.scm.added_files()) - self.assertIn('moved_dir/another_added_file', self.scm.added_files()) - - -# Context manager that overrides the current timezone. -class TimezoneOverride(object): - def __init__(self, timezone_string): - self._timezone_string = timezone_string - - def __enter__(self): - if hasattr(time, 'tzset'): - self._saved_timezone = os.environ.get('TZ', None) - os.environ['TZ'] = self._timezone_string - time.tzset() - - def __exit__(self, type, value, traceback): - if hasattr(time, 'tzset'): - if self._saved_timezone: - os.environ['TZ'] = self._saved_timezone - else: - del os.environ['TZ'] - time.tzset() - - -class SVNTest(SCMTest): - - @staticmethod - def _set_date_and_reviewer(changelog_entry): - # Joe Cool matches the reviewer set in SCMTest._create_patch - changelog_entry = changelog_entry.replace('REVIEWER_HERE', 'Joe Cool') - # svn-apply will update ChangeLog entries with today's date (as in Cupertino, CA, US) - with TimezoneOverride('PST8PDT'): - return changelog_entry.replace('DATE_HERE', date.today().isoformat()) - - def test_svn_apply(self): - first_entry = """2009-10-26 Eric Seidel <eric@webkit.org> - - Reviewed by Foo Bar. - - Most awesome change ever. - - * scm_unittest.py: -""" - intermediate_entry = """2009-10-27 Eric Seidel <eric@webkit.org> - - Reviewed by Baz Bar. - - A more awesomer change yet! - - * scm_unittest.py: -""" - one_line_overlap_patch = """Index: ChangeLog -=================================================================== ---- ChangeLog (revision 5) -+++ ChangeLog (working copy) -@@ -1,5 +1,13 @@ - 2009-10-26 Eric Seidel <eric@webkit.org> -%(whitespace)s -+ Reviewed by NOBODY (OOPS!). -+ -+ Second most awesome change ever. -+ -+ * scm_unittest.py: -+ -+2009-10-26 Eric Seidel <eric@webkit.org> -+ - Reviewed by Foo Bar. -%(whitespace)s - Most awesome change ever. -""" % {'whitespace': ' '} - one_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org> - - Reviewed by REVIEWER_HERE. - - Second most awesome change ever. - - * scm_unittest.py: -""" - two_line_overlap_patch = """Index: ChangeLog -=================================================================== ---- ChangeLog (revision 5) -+++ ChangeLog (working copy) -@@ -2,6 +2,14 @@ -%(whitespace)s - Reviewed by Foo Bar. -%(whitespace)s -+ Second most awesome change ever. -+ -+ * scm_unittest.py: -+ -+2009-10-26 Eric Seidel <eric@webkit.org> -+ -+ Reviewed by Foo Bar. -+ - Most awesome change ever. -%(whitespace)s - * scm_unittest.py: -""" % {'whitespace': ' '} - two_line_overlap_entry = """DATE_HERE Eric Seidel <eric@webkit.org> - - Reviewed by Foo Bar. - - Second most awesome change ever. - - * scm_unittest.py: -""" - write_into_file_at_path('ChangeLog', first_entry) - run_command(['svn', 'add', 'ChangeLog']) - run_command(['svn', 'commit', '--quiet', '--message', 'ChangeLog commit']) - - # Patch files were created against just 'first_entry'. - # Add a second commit to make svn-apply have to apply the patches with fuzz. - changelog_contents = "%s\n%s" % (intermediate_entry, first_entry) - write_into_file_at_path('ChangeLog', changelog_contents) - run_command(['svn', 'commit', '--quiet', '--message', 'Intermediate commit']) - - self._setup_webkittools_scripts_symlink(self.scm) - self.checkout.apply_patch(self._create_patch(one_line_overlap_patch)) - expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(one_line_overlap_entry), changelog_contents) - self.assertEqual(read_from_path('ChangeLog'), expected_changelog_contents) - - self.scm.revert_files(['ChangeLog']) - self.checkout.apply_patch(self._create_patch(two_line_overlap_patch)) - expected_changelog_contents = "%s\n%s" % (self._set_date_and_reviewer(two_line_overlap_entry), changelog_contents) - self.assertEqual(read_from_path('ChangeLog'), expected_changelog_contents) + self.assertIn('moved_dir/added_file', self.scm._added_files()) + self.assertIn('moved_dir/another_added_file', self.scm._added_files()) + +class SVNTest(SCMTestBase): def setUp(self): - SVNTestRepository.setup(self) - os.chdir(self.svn_checkout_path) + super(SVNTest, self).setUp() + self._set_up_svn_checkout() + self._chdir(self.svn_checkout_path) self.scm = detect_scm_system(self.svn_checkout_path) self.scm.svn_server_realm = None - # For historical reasons, we test some checkout code here too. - self.checkout = Checkout(self.scm) def tearDown(self): - SVNTestRepository.tear_down(self) + super(SVNTest, self).tearDown() + self._tear_down_svn_checkout() def test_detect_scm_system_relative_url(self): scm = detect_scm_system(".") @@ -653,200 +272,23 @@ class SVNTest(SCMTest): # crazy magic with temp folder names that I couldn't figure out. self.assertTrue(scm.checkout_root) - def test_create_patch_is_full_patch(self): - test_dir_path = os.path.join(self.svn_checkout_path, "test_dir2") - os.mkdir(test_dir_path) - test_file_path = os.path.join(test_dir_path, 'test_file2') - write_into_file_at_path(test_file_path, 'test content') - run_command(['svn', 'add', 'test_dir2']) - - # create_patch depends on 'svn-create-patch', so make a dummy version. - scripts_path = os.path.join(self.svn_checkout_path, 'Tools', 'Scripts') - os.makedirs(scripts_path) - create_patch_path = os.path.join(scripts_path, 'svn-create-patch') - write_into_file_at_path(create_patch_path, '#!/bin/sh\necho $PWD') # We could pass -n to prevent the \n, but not all echo accept -n. - os.chmod(create_patch_path, stat.S_IXUSR | stat.S_IRUSR) - - # Change into our test directory and run the create_patch command. - os.chdir(test_dir_path) - scm = detect_scm_system(test_dir_path) - self.assertEqual(scm.checkout_root, self.svn_checkout_path) # Sanity check that detection worked right. - patch_contents = scm.create_patch() - # Our fake 'svn-create-patch' returns $PWD instead of a patch, check that it was executed from the root of the repo. - self.assertEqual("%s\n" % os.path.realpath(scm.checkout_root), patch_contents) # Add a \n because echo adds a \n. - def test_detection(self): self.assertEqual(self.scm.display_name(), "svn") self.assertEqual(self.scm.supports_local_commits(), False) - def test_apply_small_binary_patch(self): - patch_contents = """Index: test_file.swf -=================================================================== -Cannot display: file marked as a binary type. -svn:mime-type = application/octet-stream - -Property changes on: test_file.swf -___________________________________________________________________ -Name: svn:mime-type - + application/octet-stream - - -Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA== -""" - expected_contents = base64.b64decode("Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==") - self._setup_webkittools_scripts_symlink(self.scm) - patch_file = self._create_patch(patch_contents) - self.checkout.apply_patch(patch_file) - actual_contents = read_from_path("test_file.swf", encoding=None) - self.assertEqual(actual_contents, expected_contents) - - def test_apply_svn_patch(self): - patch = self._create_patch(_svn_diff("-r5:4")) - self._setup_webkittools_scripts_symlink(self.scm) - Checkout(self.scm).apply_patch(patch) - - def test_commit_logs(self): - # Commits have dates and usernames in them, so we can't just direct compare. - self.assertRegexpMatches(self.scm.last_svn_commit_log(), 'fourth commit') - self.assertRegexpMatches(self.scm.svn_commit_log(3), 'second commit') - - def _shared_test_commit_with_message(self, username=None): - write_into_file_at_path('test_file', 'more test content') - commit_text = self.scm.commit_with_message("another test commit", username) - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - def test_commit_in_subdir(self, username=None): - write_into_file_at_path('test_dir/test_file3', 'more test content') - os.chdir("test_dir") - commit_text = self.scm.commit_with_message("another test commit", username) - os.chdir("..") - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - def test_commit_text_parsing(self): - self._shared_test_commit_with_message() - - def test_commit_with_username(self): - self._shared_test_commit_with_message("dbates@webkit.org") - - def test_commit_without_authorization(self): - # FIXME: https://bugs.webkit.org/show_bug.cgi?id=111669 - # This test ends up looking in the actal $HOME/.subversion for authorization, - # which makes it fragile. For now, set it to use a realm that won't be authorized, - # but we should really plumb through a fake_home_dir here like we do in - # test_has_authorization_for_realm. - self.scm.svn_server_realm = '<http://svn.example.com:80> Example' - self.assertRaises(AuthenticationError, self._shared_test_commit_with_message) - - def test_has_authorization_for_realm_using_credentials_with_passtype(self): - credentials = """ -K 8 -passtype -V 8 -keychain -K 15 -svn:realmstring -V 39 -<http://svn.webkit.org:80> Mac OS Forge -K 8 -username -V 17 -dbates@webkit.org -END -""" - self.assertTrue(self._test_has_authorization_for_realm_using_credentials(SVN.svn_server_realm, credentials)) - - def test_has_authorization_for_realm_using_credentials_with_password(self): - credentials = """ -K 15 -svn:realmstring -V 39 -<http://svn.webkit.org:80> Mac OS Forge -K 8 -username -V 17 -dbates@webkit.org -K 8 -password -V 4 -blah -END -""" - self.assertTrue(self._test_has_authorization_for_realm_using_credentials(SVN.svn_server_realm, credentials)) - - def _test_has_authorization_for_realm_using_credentials(self, realm, credentials): - fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") - svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") - os.mkdir(svn_config_dir_path) - fake_webkit_auth_file = os.path.join(svn_config_dir_path, "fake_webkit_auth_file") - write_into_file_at_path(fake_webkit_auth_file, credentials) - result = self.scm.has_authorization_for_realm(realm, home_directory=fake_home_dir) - os.remove(fake_webkit_auth_file) - os.rmdir(svn_config_dir_path) - os.rmdir(fake_home_dir) - return result - - def test_not_have_authorization_for_realm_with_credentials_missing_password_and_passtype(self): - credentials = """ -K 15 -svn:realmstring -V 39 -<http://svn.webkit.org:80> Mac OS Forge -K 8 -username -V 17 -dbates@webkit.org -END -""" - self.assertFalse(self._test_has_authorization_for_realm_using_credentials(SVN.svn_server_realm, credentials)) - - def test_not_have_authorization_for_realm_when_missing_credentials_file(self): - fake_home_dir = tempfile.mkdtemp(suffix="fake_home_dir") - svn_config_dir_path = os.path.join(fake_home_dir, ".subversion") - os.mkdir(svn_config_dir_path) - self.assertFalse(self.scm.has_authorization_for_realm(SVN.svn_server_realm, home_directory=fake_home_dir)) - os.rmdir(svn_config_dir_path) - os.rmdir(fake_home_dir) - - def test_reverse_diff(self): - self._shared_test_reverse_diff() - - def test_diff_for_revision(self): - self._shared_test_diff_for_revision() - - def test_svn_apply_git_patch(self): - self._shared_test_svn_apply_git_patch() - - def test_changed_files(self): - self._shared_test_changed_files() - - def test_changed_files_for_revision(self): - self._shared_test_changed_files_for_revision() - - def test_added_files(self): - self._shared_test_added_files() - - def test_contents_at_revision(self): - self._shared_test_contents_at_revision() - - def test_revisions_changing_file(self): - self._shared_test_revisions_changing_file() - - def test_committer_email_for_revision(self): - self._shared_test_committer_email_for_revision() - def test_add_recursively(self): self._shared_test_add_recursively() def test_delete(self): - os.chdir(self.svn_checkout_path) + self._chdir(self.svn_checkout_path) self.scm.delete("test_file") - self.assertIn("test_file", self.scm.deleted_files()) + self.assertIn("test_file", self.scm._deleted_files()) def test_delete_list(self): - os.chdir(self.svn_checkout_path) + self._chdir(self.svn_checkout_path) self.scm.delete_list(["test_file", "test_file2"]) - self.assertIn("test_file", self.scm.deleted_files()) - self.assertIn("test_file2", self.scm.deleted_files()) + self.assertIn("test_file", self.scm._deleted_files()) + self.assertIn("test_file2", self.scm._deleted_files()) def test_delete_recursively(self): self._shared_test_delete_recursively() @@ -854,223 +296,93 @@ END def test_delete_recursively_or_not(self): self._shared_test_delete_recursively_or_not() - def test_head_svn_revision(self): - self._shared_test_head_svn_revision() - def test_move(self): self._shared_test_move() def test_move_recursive(self): self._shared_test_move_recursive() - def test_propset_propget(self): - filepath = os.path.join(self.svn_checkout_path, "test_file") - expected_mime_type = "x-application/foo-bar" - self.scm.propset("svn:mime-type", expected_mime_type, filepath) - self.assertEqual(expected_mime_type, self.scm.propget("svn:mime-type", filepath)) - - def test_show_head(self): - write_into_file_at_path("test_file", u"Hello!", "utf-8") - SVNTestRepository._svn_commit("fourth commit") - self.assertEqual("Hello!", self.scm.show_head('test_file')) - - def test_show_head_binary(self): - data = "\244" - write_into_file_at_path("binary_file", data, encoding=None) - self.scm.add("binary_file") - self.scm.commit_with_message("a test commit") - self.assertEqual(data, self.scm.show_head('binary_file')) - - def do_test_diff_for_file(self): - write_into_file_at_path('test_file', 'some content') - self.scm.commit_with_message("a test commit") - diff = self.scm.diff_for_file('test_file') - self.assertEqual(diff, "") - - write_into_file_at_path("test_file", "changed content") - diff = self.scm.diff_for_file('test_file') - self.assertIn("-some content", diff) - self.assertIn("+changed content", diff) - - def clean_bogus_dir(self): - self.bogus_dir = self.scm._bogus_dir_name() - if os.path.exists(self.bogus_dir): - shutil.rmtree(self.bogus_dir) - - def test_diff_for_file_with_existing_bogus_dir(self): - self.clean_bogus_dir() - os.mkdir(self.bogus_dir) - self.do_test_diff_for_file() - self.assertTrue(os.path.exists(self.bogus_dir)) - shutil.rmtree(self.bogus_dir) - - def test_diff_for_file_with_missing_bogus_dir(self): - self.clean_bogus_dir() - self.do_test_diff_for_file() - self.assertFalse(os.path.exists(self.bogus_dir)) - - def test_svn_lock(self): - svn_root_lock_path = ".svn/lock" - write_into_file_at_path(svn_root_lock_path, "", "utf-8") - # webkit-patch uses a Checkout object and runs update-webkit, just use svn update here. - self.assertRaises(ScriptError, run_command, ['svn', 'update']) - self.scm.discard_working_directory_changes() - self.assertFalse(os.path.exists(svn_root_lock_path)) - run_command(['svn', 'update']) # Should succeed and not raise. - def test_exists(self): - self._shared_test_exists(self.scm, self.scm.commit_with_message) +class GitTest(SCMTestBase): + def setUp(self): + super(GitTest, self).setUp() + self._set_up_git_checkouts() -class GitTest(SCMTest): + def tearDown(self): + super(GitTest, self).tearDown() + self._tear_down_git_checkouts() - def setUp(self): - """Sets up fresh git repository with one commit. Then setups a second git - repo that tracks the first one.""" - # FIXME: We should instead clone a git repo that is tracking an SVN repo. - # That better matches what we do with WebKit. - self.original_dir = os.getcwd() - - self.untracking_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout2") - run_command(['git', 'init', self.untracking_checkout_path]) - - os.chdir(self.untracking_checkout_path) - write_into_file_at_path('foo_file', 'foo') - run_command(['git', 'add', 'foo_file']) - run_command(['git', 'commit', '-am', 'dummy commit']) + def _set_up_git_checkouts(self): + """Sets up fresh git repository with one commit. Then sets up a second git repo that tracks the first one.""" + + self.untracking_checkout_path = self._mkdtemp(suffix="git_test_checkout2") + self._run(['git', 'init', self.untracking_checkout_path]) + + self._chdir(self.untracking_checkout_path) + self._write_text_file('foo_file', 'foo') + self._run(['git', 'add', 'foo_file']) + self._run(['git', 'commit', '-am', 'dummy commit']) self.untracking_scm = detect_scm_system(self.untracking_checkout_path) - self.tracking_git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") - run_command(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) - os.chdir(self.tracking_git_checkout_path) + self.tracking_git_checkout_path = self._mkdtemp(suffix="git_test_checkout") + self._run(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) + self._chdir(self.tracking_git_checkout_path) self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path) - def tearDown(self): - # Change back to a valid directory so that later calls to os.getcwd() do not fail. - os.chdir(self.original_dir) - run_command(['rm', '-rf', self.tracking_git_checkout_path]) - run_command(['rm', '-rf', self.untracking_checkout_path]) + def _tear_down_git_checkouts(self): + self._run(['rm', '-rf', self.tracking_git_checkout_path]) + self._run(['rm', '-rf', self.untracking_checkout_path]) def test_remote_branch_ref(self): - self.assertEqual(self.tracking_scm.remote_branch_ref(), 'refs/remotes/origin/master') - - os.chdir(self.untracking_checkout_path) - self.assertRaises(ScriptError, self.untracking_scm.remote_branch_ref) + self.assertEqual(self.tracking_scm._remote_branch_ref(), 'refs/remotes/origin/master') + self._chdir(self.untracking_checkout_path) + self.assertRaises(ScriptError, self.untracking_scm._remote_branch_ref) def test_multiple_remotes(self): - run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote1']) - run_command(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) - self.assertEqual(self.tracking_scm.remote_branch_ref(), 'remote1') + self._run(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote1']) + self._run(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) + self.assertEqual(self.tracking_scm._remote_branch_ref(), 'remote1') def test_create_patch(self): - write_into_file_at_path('test_file_commit1', 'contents') - run_command(['git', 'add', 'test_file_commit1']) + self._write_text_file('test_file_commit1', 'contents') + self._run(['git', 'add', 'test_file_commit1']) scm = self.tracking_scm scm.commit_locally_with_message('message') patch = scm.create_patch() self.assertNotRegexpMatches(patch, r'Subversion Revision:') - def test_orderfile(self): - os.mkdir("Tools") - os.mkdir("Source") - os.mkdir("LayoutTests") - os.mkdir("Websites") - - # Slash should always be the right path separator since we use cygwin on Windows. - Tools_ChangeLog = "Tools/ChangeLog" - write_into_file_at_path(Tools_ChangeLog, "contents") - Source_ChangeLog = "Source/ChangeLog" - write_into_file_at_path(Source_ChangeLog, "contents") - LayoutTests_ChangeLog = "LayoutTests/ChangeLog" - write_into_file_at_path(LayoutTests_ChangeLog, "contents") - Websites_ChangeLog = "Websites/ChangeLog" - write_into_file_at_path(Websites_ChangeLog, "contents") - - Tools_ChangeFile = "Tools/ChangeFile" - write_into_file_at_path(Tools_ChangeFile, "contents") - Source_ChangeFile = "Source/ChangeFile" - write_into_file_at_path(Source_ChangeFile, "contents") - LayoutTests_ChangeFile = "LayoutTests/ChangeFile" - write_into_file_at_path(LayoutTests_ChangeFile, "contents") - Websites_ChangeFile = "Websites/ChangeFile" - write_into_file_at_path(Websites_ChangeFile, "contents") - - run_command(['git', 'add', 'Tools/ChangeLog']) - run_command(['git', 'add', 'LayoutTests/ChangeLog']) - run_command(['git', 'add', 'Source/ChangeLog']) - run_command(['git', 'add', 'Websites/ChangeLog']) - run_command(['git', 'add', 'Tools/ChangeFile']) - run_command(['git', 'add', 'LayoutTests/ChangeFile']) - run_command(['git', 'add', 'Source/ChangeFile']) - run_command(['git', 'add', 'Websites/ChangeFile']) - scm = self.tracking_scm - scm.commit_locally_with_message('message') - - patch = scm.create_patch() - self.assertTrue(re.search(r'Tools/ChangeLog', patch).start() < re.search(r'Tools/ChangeFile', patch).start()) - self.assertTrue(re.search(r'Websites/ChangeLog', patch).start() < re.search(r'Websites/ChangeFile', patch).start()) - self.assertTrue(re.search(r'Source/ChangeLog', patch).start() < re.search(r'Source/ChangeFile', patch).start()) - self.assertTrue(re.search(r'LayoutTests/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start()) - - self.assertTrue(re.search(r'Source/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start()) - self.assertTrue(re.search(r'Tools/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start()) - self.assertTrue(re.search(r'Websites/ChangeLog', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start()) - - self.assertTrue(re.search(r'Source/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start()) - self.assertTrue(re.search(r'Tools/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start()) - self.assertTrue(re.search(r'Websites/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeLog', patch).start()) - - self.assertTrue(re.search(r'Source/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start()) - self.assertTrue(re.search(r'Tools/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start()) - self.assertTrue(re.search(r'Websites/ChangeFile', patch).start() < re.search(r'LayoutTests/ChangeFile', patch).start()) - def test_exists(self): scm = self.untracking_scm self._shared_test_exists(scm, scm.commit_locally_with_message) - def test_head_svn_revision(self): - scm = detect_scm_system(self.untracking_checkout_path) - # If we cloned a git repo tracking an SVN repo, this would give the same result as - # self._shared_test_head_svn_revision(). - self.assertEqual(scm.head_svn_revision(), '') - def test_rename_files(self): scm = self.tracking_scm - scm.move('foo_file', 'bar_file') scm.commit_locally_with_message('message') - patch = scm.create_patch() - self.assertNotRegexpMatches(patch, r'rename from ') - self.assertNotRegexpMatches(patch, r'rename to ') - - -class GitSVNTest(SCMTest): - - def _setup_git_checkout(self): - self.git_checkout_path = tempfile.mkdtemp(suffix="git_test_checkout") - # --quiet doesn't make git svn silent, so we use run_silent to redirect output - run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) - os.chdir(self.git_checkout_path) - - def _tear_down_git_checkout(self): - # Change back to a valid directory so that later calls to os.getcwd() do not fail. - os.chdir(self.original_dir) - run_command(['rm', '-rf', self.git_checkout_path]) +class GitSVNTest(SCMTestBase): def setUp(self): - self.original_dir = os.getcwd() - - SVNTestRepository.setup(self) - self._setup_git_checkout() + super(GitSVNTest, self).setUp() + self._set_up_svn_checkout() + self._set_up_gitsvn_checkout() self.scm = detect_scm_system(self.git_checkout_path) self.scm.svn_server_realm = None - # For historical reasons, we test some checkout code here too. - self.checkout = Checkout(self.scm) def tearDown(self): - SVNTestRepository.tear_down(self) - self._tear_down_git_checkout() + super(GitSVNTest, self).tearDown() + self._tear_down_svn_checkout() + self._tear_down_gitsvn_checkout() + + def _set_up_gitsvn_checkout(self): + self.git_checkout_path = self._mkdtemp(suffix="git_test_checkout") + # --quiet doesn't make git svn silent + self._run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) + self._chdir(self.git_checkout_path) + + def _tear_down_gitsvn_checkout(self): + self._rmtree(self.git_checkout_path) def test_detection(self): self.assertEqual(self.scm.display_name(), "git") @@ -1079,111 +391,61 @@ class GitSVNTest(SCMTest): def test_read_git_config(self): key = 'test.git-config' value = 'git-config value' - run_command(['git', 'config', key, value]) + self._run(['git', 'config', key, value]) self.assertEqual(self.scm.read_git_config(key), value) def test_local_commits(self): - test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(test_file, 'foo') - run_command(['git', 'commit', '-a', '-m', 'local commit']) + test_file = self._join(self.git_checkout_path, 'test_file') + self._write_text_file(test_file, 'foo') + self._run(['git', 'commit', '-a', '-m', 'local commit']) - self.assertEqual(len(self.scm.local_commits()), 1) + self.assertEqual(len(self.scm._local_commits()), 1) def test_discard_local_commits(self): - test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(test_file, 'foo') - run_command(['git', 'commit', '-a', '-m', 'local commit']) + test_file = self._join(self.git_checkout_path, 'test_file') + self._write_text_file(test_file, 'foo') + self._run(['git', 'commit', '-a', '-m', 'local commit']) - self.assertEqual(len(self.scm.local_commits()), 1) - self.scm.discard_local_commits() - self.assertEqual(len(self.scm.local_commits()), 0) + self.assertEqual(len(self.scm._local_commits()), 1) + self.scm._discard_local_commits() + self.assertEqual(len(self.scm._local_commits()), 0) def test_delete_branch(self): new_branch = 'foo' - run_command(['git', 'checkout', '-b', new_branch]) - self.assertEqual(run_command(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) + self._run(['git', 'checkout', '-b', new_branch]) + self.assertEqual(self._run(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) - run_command(['git', 'checkout', '-b', 'bar']) + self._run(['git', 'checkout', '-b', 'bar']) self.scm.delete_branch(new_branch) - self.assertNotRegexpMatches(run_command(['git', 'branch']), r'foo') - - def test_remote_merge_base(self): - # Diff to merge-base should include working-copy changes, - # which the diff to svn_branch.. doesn't. - test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(test_file, 'foo') - - diff_to_common_base = _git_diff(self.scm.remote_branch_ref() + '..') - diff_to_merge_base = _git_diff(self.scm.remote_merge_base()) - - self.assertNotRegexpMatches(diff_to_common_base, r'foo') - self.assertRegexpMatches(diff_to_merge_base, r'foo') + self.assertNotRegexpMatches(self._run(['git', 'branch']), r'foo') def test_rebase_in_progress(self): - svn_test_file = os.path.join(self.svn_checkout_path, 'test_file') - write_into_file_at_path(svn_test_file, "svn_checkout") - run_command(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path) + svn_test_file = self._join(self.svn_checkout_path, 'test_file') + self._write_text_file(svn_test_file, "svn_checkout") + self._run(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path) - git_test_file = os.path.join(self.git_checkout_path, 'test_file') - write_into_file_at_path(git_test_file, "git_checkout") - run_command(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort']) + git_test_file = self._join(self.git_checkout_path, 'test_file') + self._write_text_file(git_test_file, "git_checkout") + self._run(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort']) - # --quiet doesn't make git svn silent, so use run_silent to redirect output - self.assertRaises(ScriptError, run_silent, ['git', 'svn', '--quiet', 'rebase']) # Will fail due to a conflict leaving us mid-rebase. + # Should fail due to a conflict leaving us mid-rebase. + # we use self._run_slient because --quiet doesn't actually make git svn silent. + self.assertRaises(ScriptError, self._run_silent, ['git', 'svn', '--quiet', 'rebase']) - self.assertTrue(self.scm.rebase_in_progress()) + self.assertTrue(self.scm._rebase_in_progress()) # Make sure our cleanup works. - self.scm.discard_working_directory_changes() - self.assertFalse(self.scm.rebase_in_progress()) + self.scm._discard_working_directory_changes() + self.assertFalse(self.scm._rebase_in_progress()) # Make sure cleanup doesn't throw when no rebase is in progress. - self.scm.discard_working_directory_changes() - - def test_commitish_parsing(self): - # Multiple revisions are cherry-picked. - self.assertEqual(len(self.scm.commit_ids_from_commitish_arguments(['HEAD~2'])), 1) - self.assertEqual(len(self.scm.commit_ids_from_commitish_arguments(['HEAD', 'HEAD~2'])), 2) - - # ... is an invalid range specifier - self.assertRaises(ScriptError, self.scm.commit_ids_from_commitish_arguments, ['trunk...HEAD']) - - def test_commitish_order(self): - commit_range = 'HEAD~3..HEAD' - - actual_commits = self.scm.commit_ids_from_commitish_arguments([commit_range]) - expected_commits = [] - expected_commits += reversed(run_command(['git', 'rev-list', commit_range]).splitlines()) - - self.assertEqual(actual_commits, expected_commits) - - def test_apply_git_patch(self): - # We carefullly pick a diff which does not have a directory addition - # as currently svn-apply will error out when trying to remove directories - # in Git: https://bugs.webkit.org/show_bug.cgi?id=34871 - patch = self._create_patch(_git_diff('HEAD..HEAD^')) - self._setup_webkittools_scripts_symlink(self.scm) - Checkout(self.scm).apply_patch(patch) - - def test_commit_text_parsing(self): - write_into_file_at_path('test_file', 'more test content') - commit_text = self.scm.commit_with_message("another test commit") - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - def test_commit_with_message_working_copy_only(self): - write_into_file_at_path('test_file_commit1', 'more test content') - run_command(['git', 'add', 'test_file_commit1']) - commit_text = self.scm.commit_with_message("yet another test commit") - - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertRegexpMatches(svn_log, r'test_file_commit1') + self.scm._discard_working_directory_changes() def _local_commit(self, filename, contents, message): - write_into_file_at_path(filename, contents) - run_command(['git', 'add', filename]) + self._write_text_file(filename, contents) + self._run(['git', 'add', filename]) self.scm.commit_locally_with_message(message) def _one_local_commit(self): @@ -1191,8 +453,8 @@ class GitSVNTest(SCMTest): def _one_local_commit_plus_working_copy_changes(self): self._one_local_commit() - write_into_file_at_path('test_file_commit2', 'still more test content') - run_command(['git', 'add', 'test_file_commit2']) + self._write_text_file('test_file_commit2', 'still more test content') + self._run(['git', 'add', 'test_file_commit2']) def _second_local_commit(self): self._local_commit('test_file_commit2', 'still more test content', 'yet another test commit') @@ -1207,144 +469,24 @@ class GitSVNTest(SCMTest): def test_locally_commit_all_working_copy_changes(self): self._local_commit('test_file', 'test content', 'test commit') - write_into_file_at_path('test_file', 'changed test content') + self._write_text_file('test_file', 'changed test content') self.assertTrue(self.scm.has_working_directory_changes()) self.scm.commit_locally_with_message('all working copy changes') self.assertFalse(self.scm.has_working_directory_changes()) def test_locally_commit_no_working_copy_changes(self): self._local_commit('test_file', 'test content', 'test commit') - write_into_file_at_path('test_file', 'changed test content') + self._write_text_file('test_file', 'changed test content') self.assertTrue(self.scm.has_working_directory_changes()) self.assertRaises(ScriptError, self.scm.commit_locally_with_message, 'no working copy changes', False) - def test_locally_commit_selected_working_copy_changes(self): - self._local_commit('test_file_1', 'test content 1', 'test commit 1') - self._local_commit('test_file_2', 'test content 2', 'test commit 2') - write_into_file_at_path('test_file_1', 'changed test content 1') - write_into_file_at_path('test_file_2', 'changed test content 2') - self.assertTrue(self.scm.has_working_directory_changes()) - run_command(['git', 'add', 'test_file_1']) - self.scm.commit_locally_with_message('selected working copy changes', commit_all_working_directory_changes=False) - self.assertTrue(self.scm.has_working_directory_changes()) - self.assertTrue(self.scm.diff_for_file('test_file_1') == '') - self.assertFalse(self.scm.diff_for_file('test_file_2') == '') - - def test_revisions_changing_files_with_local_commit(self): - self._one_local_commit() - self.assertItemsEqual(self.scm.revisions_changing_file('test_file_commit1'), []) - - def test_commit_with_message(self): - self._one_local_commit_plus_working_copy_changes() - self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "yet another test commit") - commit_text = self.scm.commit_with_message("yet another test commit", force_squash=True) - - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertRegexpMatches(svn_log, r'test_file_commit2') - self.assertRegexpMatches(svn_log, r'test_file_commit1') - - def test_commit_with_message_git_commit(self): - self._two_local_commits() - - commit_text = self.scm.commit_with_message("another test commit", git_commit="HEAD^") - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertRegexpMatches(svn_log, r'test_file_commit1') - self.assertNotRegexpMatches(svn_log, r'test_file_commit2') - - def test_commit_with_message_git_commit_range(self): - self._three_local_commits() - - commit_text = self.scm.commit_with_message("another test commit", git_commit="HEAD~2..HEAD") - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertNotRegexpMatches(svn_log, r'test_file_commit0') - self.assertRegexpMatches(svn_log, r'test_file_commit1') - self.assertRegexpMatches(svn_log, r'test_file_commit2') - - def test_commit_with_message_only_local_commit(self): - self._one_local_commit() - commit_text = self.scm.commit_with_message("another test commit") - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertRegexpMatches(svn_log, r'test_file_commit1') - - def test_commit_with_message_multiple_local_commits_and_working_copy(self): - self._two_local_commits() - write_into_file_at_path('test_file_commit1', 'working copy change') - - self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "another test commit") - commit_text = self.scm.commit_with_message("another test commit", force_squash=True) - - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertRegexpMatches(svn_log, r'test_file_commit2') - self.assertRegexpMatches(svn_log, r'test_file_commit1') - - def test_commit_with_message_git_commit_and_working_copy(self): - self._two_local_commits() - write_into_file_at_path('test_file_commit1', 'working copy change') - self.assertRaises(ScriptError, self.scm.commit_with_message, "another test commit", git_commit="HEAD^") - - def test_commit_with_message_multiple_local_commits_always_squash(self): - run_command(['git', 'config', 'webkit-patch.commit-should-always-squash', 'true']) - self._two_local_commits() - commit_text = self.scm.commit_with_message("yet another test commit") - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertRegexpMatches(svn_log, r'test_file_commit2') - self.assertRegexpMatches(svn_log, r'test_file_commit1') - - def test_commit_with_message_multiple_local_commits(self): - self._two_local_commits() - self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "yet another test commit") - commit_text = self.scm.commit_with_message("yet another test commit", force_squash=True) - - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertRegexpMatches(svn_log, r'test_file_commit2') - self.assertRegexpMatches(svn_log, r'test_file_commit1') - - def test_commit_with_message_not_synced(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) - self._two_local_commits() - self.assertRaises(AmbiguousCommitError, self.scm.commit_with_message, "another test commit") - commit_text = self.scm.commit_with_message("another test commit", force_squash=True) - - self.assertEqual(self.scm.svn_revision_from_commit_text(commit_text), '6') - - svn_log = run_command(['git', 'svn', 'log', '--limit=1', '--verbose']) - self.assertNotRegexpMatches(svn_log, r'test_file2') - self.assertRegexpMatches(svn_log, r'test_file_commit2') - self.assertRegexpMatches(svn_log, r'test_file_commit1') - - def test_commit_with_message_not_synced_with_conflict(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) - self._local_commit('test_file2', 'asdf', 'asdf commit') - - # There's a conflict between trunk and the test_file2 modification. - self.assertRaises(ScriptError, self.scm.commit_with_message, "another test commit", force_squash=True) - - def test_upstream_branch(self): - run_command(['git', 'checkout', '-t', '-b', 'my-branch']) - run_command(['git', 'checkout', '-t', '-b', 'my-second-branch']) + def _test_upstream_branch(self): + self._run(['git', 'checkout', '-t', '-b', 'my-branch']) + self._run(['git', 'checkout', '-t', '-b', 'my-second-branch']) self.assertEqual(self.scm._upstream_branch(), 'my-branch') def test_remote_branch_ref(self): - self.assertEqual(self.scm.remote_branch_ref(), 'refs/remotes/trunk') - - def test_reverse_diff(self): - self._shared_test_reverse_diff() - - def test_diff_for_revision(self): - self._shared_test_diff_for_revision() - - def test_svn_apply_git_patch(self): - self._shared_test_svn_apply_git_patch() + self.assertEqual(self.scm._remote_branch_ref(), 'refs/remotes/trunk') def test_create_patch_local_plus_working_copy(self): self._one_local_commit_plus_working_copy_changes() @@ -1360,9 +502,9 @@ class GitSVNTest(SCMTest): self.assertRegexpMatches(patch, r'Subversion Revision: 5') def test_create_patch_after_merge(self): - run_command(['git', 'checkout', '-b', 'dummy-branch', 'trunk~3']) + self._run(['git', 'checkout', '-b', 'dummy-branch', 'trunk~3']) self._one_local_commit() - run_command(['git', 'merge', 'trunk']) + self._run(['git', 'merge', 'trunk']) patch = self.scm.create_patch() self.assertRegexpMatches(patch, r'test_file_commit1') @@ -1375,7 +517,7 @@ class GitSVNTest(SCMTest): def test_create_patch_with_rm_and_changed_files(self): self._one_local_commit_plus_working_copy_changes() - os.remove('test_file_commit1') + self._remove('test_file_commit1') patch = self.scm.create_patch() patch_with_changed_files = self.scm.create_patch(changed_files=['test_file_commit1', 'test_file_commit2']) self.assertEqual(patch, patch_with_changed_files) @@ -1406,7 +548,7 @@ class GitSVNTest(SCMTest): self.assertRegexpMatches(patch, r'test_file_commit1') def test_create_patch_not_synced(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) + self._run(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) self._two_local_commits() patch = self.scm.create_patch() self.assertNotRegexpMatches(patch, r'test_file2') @@ -1416,29 +558,24 @@ class GitSVNTest(SCMTest): def test_create_binary_patch(self): # Create a git binary patch and check the contents. test_file_name = 'binary_file' - test_file_path = os.path.join(self.git_checkout_path, test_file_name) + test_file_path = self.fs.join(self.git_checkout_path, test_file_name) file_contents = ''.join(map(chr, range(256))) - write_into_file_at_path(test_file_path, file_contents, encoding=None) - run_command(['git', 'add', test_file_name]) + self._write_binary_file(test_file_path, file_contents) + self._run(['git', 'add', test_file_name]) patch = self.scm.create_patch() self.assertRegexpMatches(patch, r'\nliteral 0\n') self.assertRegexpMatches(patch, r'\nliteral 256\n') - # Check if we can apply the created patch. - run_command(['git', 'rm', '-f', test_file_name]) - self._setup_webkittools_scripts_symlink(self.scm) - self.checkout.apply_patch(self._create_patch(patch)) - self.assertEqual(file_contents, read_from_path(test_file_path, encoding=None)) - # Check if we can create a patch from a local commit. - write_into_file_at_path(test_file_path, file_contents, encoding=None) - run_command(['git', 'add', test_file_name]) - run_command(['git', 'commit', '-m', 'binary diff']) + self._write_binary_file(test_file_path, file_contents) + self._run(['git', 'add', test_file_name]) + self._run(['git', 'commit', '-m', 'binary diff']) patch_from_local_commit = self.scm.create_patch('HEAD') self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 0\n') self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 256\n') + def test_changed_files_local_plus_working_copy(self): self._one_local_commit_plus_working_copy_changes() files = self.scm.changed_files() @@ -1481,34 +618,20 @@ class GitSVNTest(SCMTest): self.assertIn('test_file_commit1', files) def test_changed_files_not_synced(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) - self._two_local_commits() - files = self.scm.changed_files() - self.assertNotIn('test_file2', files) - self.assertIn('test_file_commit2', files) - self.assertIn('test_file_commit1', files) - - def test_changed_files_not_synced(self): - run_command(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) + self._run(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) self._two_local_commits() files = self.scm.changed_files() self.assertNotIn('test_file2', files) self.assertIn('test_file_commit2', files) self.assertIn('test_file_commit1', files) - def test_changed_files(self): - self._shared_test_changed_files() - - def test_changed_files_for_revision(self): - self._shared_test_changed_files_for_revision() - def test_changed_files_upstream(self): - run_command(['git', 'checkout', '-t', '-b', 'my-branch']) + self._run(['git', 'checkout', '-t', '-b', 'my-branch']) self._one_local_commit() - run_command(['git', 'checkout', '-t', '-b', 'my-second-branch']) + self._run(['git', 'checkout', '-t', '-b', 'my-second-branch']) self._second_local_commit() - write_into_file_at_path('test_file_commit0', 'more test content') - run_command(['git', 'add', 'test_file_commit0']) + self._write_text_file('test_file_commit0', 'more test content') + self._run(['git', 'add', 'test_file_commit0']) # equivalent to 'git diff my-branch..HEAD, should not include working changes files = self.scm.changed_files(git_commit='UPSTREAM..') @@ -1522,31 +645,19 @@ class GitSVNTest(SCMTest): self.assertIn('test_file_commit2', files) self.assertIn('test_file_commit0', files) - def test_contents_at_revision(self): - self._shared_test_contents_at_revision() - - def test_revisions_changing_file(self): - self._shared_test_revisions_changing_file() - - def test_added_files(self): - self._shared_test_added_files() - - def test_committer_email_for_revision(self): - self._shared_test_committer_email_for_revision() - def test_add_recursively(self): self._shared_test_add_recursively() def test_delete(self): self._two_local_commits() self.scm.delete('test_file_commit1') - self.assertIn("test_file_commit1", self.scm.deleted_files()) + self.assertIn("test_file_commit1", self.scm._deleted_files()) def test_delete_list(self): self._two_local_commits() self.scm.delete_list(["test_file_commit1", "test_file_commit2"]) - self.assertIn("test_file_commit1", self.scm.deleted_files()) - self.assertIn("test_file_commit2", self.scm.deleted_files()) + self.assertIn("test_file_commit1", self.scm._deleted_files()) + self.assertIn("test_file_commit2", self.scm._deleted_files()) def test_delete_recursively(self): self._shared_test_delete_recursively() @@ -1554,85 +665,22 @@ class GitSVNTest(SCMTest): def test_delete_recursively_or_not(self): self._shared_test_delete_recursively_or_not() - def test_head_svn_revision(self): - self._shared_test_head_svn_revision() - def test_move(self): self._shared_test_move() def test_move_recursive(self): self._shared_test_move_recursive() - def test_to_object_name(self): - relpath = 'test_file_commit1' - fullpath = os.path.realpath(os.path.join(self.git_checkout_path, relpath)) - self.assertEqual(relpath, self.scm.to_object_name(fullpath)) - - def test_show_head(self): - self._two_local_commits() - self.assertEqual("more test content", self.scm.show_head('test_file_commit1')) - - def test_show_head_binary(self): - self._two_local_commits() - data = "\244" - write_into_file_at_path("binary_file", data, encoding=None) - self.scm.add("binary_file") - self.scm.commit_locally_with_message("a test commit") - self.assertEqual(data, self.scm.show_head('binary_file')) - - def test_diff_for_file(self): - self._two_local_commits() - write_into_file_at_path('test_file_commit1', "Updated", encoding=None) - - diff = self.scm.diff_for_file('test_file_commit1') - cached_diff = self.scm.diff_for_file('test_file_commit1') - self.assertIn("+Updated", diff) - self.assertIn("-more test content", diff) - - self.scm.add('test_file_commit1') - - cached_diff = self.scm.diff_for_file('test_file_commit1') - self.assertIn("+Updated", cached_diff) - self.assertIn("-more test content", cached_diff) - def test_exists(self): self._shared_test_exists(self.scm, self.scm.commit_locally_with_message) -# We need to split off more of these SCM tests to use mocks instead of the filesystem. -# This class is the first part of that. -class GitTestWithMock(unittest.TestCase): - maxDiff = None - - def make_scm(self, logging_executive=False): - # We do this should_log dance to avoid logging when Git.__init__ runs sysctl on mac to check for 64-bit support. +class GitTestWithMock(SCMTestBase): + def make_scm(self): scm = Git(cwd=".", executive=MockExecutive(), filesystem=MockFileSystem()) scm.read_git_config = lambda *args, **kw: "MOCKKEY:MOCKVALUE" - scm._executive._should_log = logging_executive return scm - def test_create_patch(self): - scm = self.make_scm(logging_executive=True) - expected_stderr = """\ -MOCK run_command: ['git', 'merge-base', 'MOCKVALUE', 'HEAD'], cwd=%(checkout)s -MOCK run_command: ['git', 'diff', '--binary', '--no-color', '--no-ext-diff', '--full-index', '--no-renames', '', 'MOCK output of child process', '--'], cwd=%(checkout)s -MOCK run_command: ['git', 'rev-parse', '--show-toplevel'], cwd=%(checkout)s -MOCK run_command: ['git', 'log', '-1', '--grep=git-svn-id:', '--date=iso', './MOCK output of child process/MOCK output of child process'], cwd=%(checkout)s -""" % {'checkout': scm.checkout_root} - OutputCapture().assert_outputs(self, scm.create_patch, expected_logs=expected_stderr) - - def test_push_local_commits_to_server_with_username_and_password(self): - self.assertEqual(self.make_scm().push_local_commits_to_server(username='dbates@webkit.org', password='blah'), "MOCK output of child process") - - def test_push_local_commits_to_server_without_username_and_password(self): - self.assertRaises(AuthenticationError, self.make_scm().push_local_commits_to_server) - - def test_push_local_commits_to_server_with_username_and_without_password(self): - self.assertRaises(AuthenticationError, self.make_scm().push_local_commits_to_server, {'username': 'dbates@webkit.org'}) - - def test_push_local_commits_to_server_without_username_and_with_password(self): - self.assertRaises(AuthenticationError, self.make_scm().push_local_commits_to_server, {'password': 'blah'}) - def test_timestamp_of_revision(self): scm = self.make_scm() scm.find_checkout_root = lambda path: '' diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/svn.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/svn.py index 7c37ac8e387..54f2fa8e750 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/svn.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/checkout/scm/svn.py @@ -39,39 +39,12 @@ import tempfile from webkitpy.common.memoized import memoized from webkitpy.common.system.executive import Executive, ScriptError -from .scm import AuthenticationError, SCM, commit_error_handler +from .scm import SCM _log = logging.getLogger(__name__) -# A mixin class that represents common functionality for SVN and Git-SVN. -class SVNRepository(object): - # FIXME: These belong in common.config.urls - svn_server_host = "svn.webkit.org" - svn_server_realm = "<http://svn.webkit.org:80> Mac OS Forge" - - def has_authorization_for_realm(self, realm, home_directory=os.getenv("HOME")): - # If we are working on a file:// repository realm will be None - if realm is None: - return True - # ignore false positives for methods implemented in the mixee class. pylint: disable=E1101 - # Assumes find and grep are installed. - if not os.path.isdir(os.path.join(home_directory, ".subversion")): - return False - find_args = ["find", ".subversion", "-type", "f", "-exec", "grep", "-q", realm, "{}", ";", "-print"] - find_output = self.run(find_args, cwd=home_directory, error_handler=Executive.ignore_error).rstrip() - if not find_output or not os.path.isfile(os.path.join(home_directory, find_output)): - return False - # Subversion either stores the password in the credential file, indicated by the presence of the key "password", - # or uses the system password store (e.g. Keychain on Mac OS X) as indicated by the presence of the key "passtype". - # We assume that these keys will not coincide with the actual credential data (e.g. that a person's username - # isn't "password") so that we can use grep. - if self.run(["grep", "password", find_output], cwd=home_directory, return_exit_code=True) == 0: - return True - return self.run(["grep", "passtype", find_output], cwd=home_directory, return_exit_code=True) == 0 - - -class SVN(SCM, SVNRepository): +class SVN(SCM): executable_name = "svn" @@ -99,7 +72,7 @@ class SVN(SCM, SVNRepository): exit_code = executive.run_command(svn_info_args, cwd=path, return_exit_code=True) return (exit_code == 0) - def find_uuid(self, path): + def _find_uuid(self, path): if not self.in_working_directory(path): return None return self.value_from_svn_info(path, 'Repository UUID') @@ -115,64 +88,36 @@ class SVN(SCM, SVNRepository): return match.group('value').rstrip('\r') def find_checkout_root(self, path): - uuid = self.find_uuid(path) + uuid = self._find_uuid(path) # If |path| is not in a working directory, we're supposed to return |path|. if not uuid: return path # Search up the directory hierarchy until we find a different UUID. last_path = None while True: - if uuid != self.find_uuid(path): + if uuid != self._find_uuid(path): return last_path last_path = path (path, last_component) = self._filesystem.split(path) if last_path == path: return None - @staticmethod - def commit_success_regexp(): - return "^Committed revision (?P<svn_revision>\d+)\.$" - def _run_svn(self, args, **kwargs): - return self.run([self.executable_name] + args, **kwargs) + return self._run([self.executable_name] + args, **kwargs) @memoized - def svn_version(self): + def _svn_version(self): return self._run_svn(['--version', '--quiet']) def has_working_directory_changes(self): # FIXME: What about files which are not committed yet? return self._run_svn(["diff"], cwd=self.checkout_root, decode_output=False) != "" - def discard_working_directory_changes(self): - # Make sure there are no locks lying around from a previously aborted svn invocation. - # This is slightly dangerous, as it's possible the user is running another svn process - # on this checkout at the same time. However, it's much more likely that we're running - # under windows and svn just sucks (or the user interrupted svn and it failed to clean up). - self._run_svn(["cleanup"], cwd=self.checkout_root) - - # svn revert -R is not as awesome as git reset --hard. - # It will leave added files around, causing later svn update - # calls to fail on the bots. We make this mirror git reset --hard - # by deleting any added files as well. - added_files = reversed(sorted(self.added_files())) - # added_files() returns directories for SVN, we walk the files in reverse path - # length order so that we remove files before we try to remove the directories. - self._run_svn(["revert", "-R", "."], cwd=self.checkout_root) - for path in added_files: - # This is robust against cwd != self.checkout_root - absolute_path = self.absolute_path(path) - # Completely lame that there is no easy way to remove both types with one call. - if os.path.isdir(path): - os.rmdir(absolute_path) - else: - os.remove(absolute_path) - def status_command(self): return [self.executable_name, 'status'] def _status_regexp(self, expected_types): - field_count = 6 if self.svn_version() > "1.6" else 5 + field_count = 6 if self._svn_version() > "1.6" else 5 return "^(?P<status>[%s]).{%s} (?P<filename>.+)$" % (expected_types, field_count) def _add_parent_directories(self, path): @@ -211,33 +156,13 @@ class SVN(SCM, SVNRepository): status_command = [self.executable_name, "status"] status_command.extend(self._patch_directories) # ACDMR: Addded, Conflicted, Deleted, Modified or Replaced - return self.run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR")) - - def changed_files_for_revision(self, revision): - # As far as I can tell svn diff --summarize output looks just like svn status output. - # No file contents printed, thus utf-8 auto-decoding in self.run is fine. - status_command = [self.executable_name, "diff", "--summarize", "-c", revision] - return self.run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR")) - - def revisions_changing_file(self, path, limit=5): - revisions = [] - # svn log will exit(1) (and thus self.run will raise) if the path does not exist. - log_command = ['log', '--quiet', '--limit=%s' % limit, path] - for line in self._run_svn(log_command, cwd=self.checkout_root).splitlines(): - match = re.search('^r(?P<revision>\d+) ', line) - if not match: - continue - revisions.append(int(match.group('revision'))) - return revisions - - def conflicted_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("C")) - - def added_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("A")) - - def deleted_files(self): - return self.run_status_and_extract_filenames(self.status_command(), self._status_regexp("D")) + return self._run_status_and_extract_filenames(status_command, self._status_regexp("ACDMR")) + + def _added_files(self): + return self._run_status_and_extract_filenames(self.status_command(), self._status_regexp("A")) + + def _deleted_files(self): + return self._run_status_and_extract_filenames(self.status_command(), self._status_regexp("D")) @staticmethod def supports_local_commits(): @@ -256,7 +181,6 @@ class SVN(SCM, SVNRepository): match = re.search(r"^<date>(?P<value>.+)</date>\r?$", info_output, re.MULTILINE) return match.group('value') - # FIXME: This method should be on Checkout. def create_patch(self, git_commit=None, changed_files=None): """Returns a byte array (str()) representing the patch file. Patch files are effectively binary since they may contain @@ -265,112 +189,9 @@ class SVN(SCM, SVNRepository): return "" elif changed_files == None: changed_files = [] - return self.run([self.script_path("svn-create-patch")] + changed_files, + return self._run([self._filesystem.join(self.checkout_root, 'Tools', 'Scripts', 'svn-create-patch')] + changed_files, cwd=self.checkout_root, return_stderr=False, decode_output=False) - def committer_email_for_revision(self, revision): - return self._run_svn(["propget", "svn:author", "--revprop", "-r", revision]).rstrip() - - def contents_at_revision(self, path, revision): - """Returns a byte array (str()) containing the contents - of path @ revision in the repository.""" - remote_path = "%s/%s" % (self._repository_url(), path) - return self._run_svn(["cat", "-r", revision, remote_path], decode_output=False) - - def diff_for_revision(self, revision): - # FIXME: This should probably use cwd=self.checkout_root - return self._run_svn(['diff', '-c', revision]) - - def _bogus_dir_name(self): - rnd = ''.join(random.sample(string.ascii_letters, 5)) - if sys.platform.startswith("win"): - parent_dir = tempfile.gettempdir() - else: - parent_dir = sys.path[0] # tempdir is not secure. - return os.path.join(parent_dir, "temp_svn_config_" + rnd) - - def _setup_bogus_dir(self, log): - self._bogus_dir = self._bogus_dir_name() - if not os.path.exists(self._bogus_dir): - os.mkdir(self._bogus_dir) - self._delete_bogus_dir = True - else: - self._delete_bogus_dir = False - if log: - log.debug(' Html: temp config dir: "%s".', self._bogus_dir) - - def _teardown_bogus_dir(self, log): - if self._delete_bogus_dir: - shutil.rmtree(self._bogus_dir, True) - if log: - log.debug(' Html: removed temp config dir: "%s".', self._bogus_dir) - self._bogus_dir = None - - def diff_for_file(self, path, log=None): - self._setup_bogus_dir(log) - try: - args = ['diff'] - if self._bogus_dir: - args += ['--config-dir', self._bogus_dir] - args.append(path) - return self._run_svn(args, cwd=self.checkout_root) - finally: - self._teardown_bogus_dir(log) - - def show_head(self, path): - return self._run_svn(['cat', '-r', 'BASE', path], decode_output=False) - - def _repository_url(self): - return self.value_from_svn_info(self.checkout_root, 'URL') - - def apply_reverse_diff(self, revision): - # '-c -revision' applies the inverse diff of 'revision' - svn_merge_args = ['merge', '--non-interactive', '-c', '-%s' % revision, self._repository_url()] - _log.warning("svn merge has been known to take more than 10 minutes to complete. It is recommended you use git for rollouts.") - _log.debug("Running 'svn %s'" % " ".join(svn_merge_args)) - # FIXME: Should this use cwd=self.checkout_root? - self._run_svn(svn_merge_args) - - def revert_files(self, file_paths): - # FIXME: This should probably use cwd=self.checkout_root. - self._run_svn(['revert'] + file_paths) - - def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None): - # git-commit and force are not used by SVN. - svn_commit_args = ["commit"] - - if not username and not self.has_authorization_for_realm(self.svn_server_realm): - raise AuthenticationError(self.svn_server_host) - if username: - svn_commit_args.extend(["--username", username]) - - svn_commit_args.extend(["-m", message]) - - if changed_files: - svn_commit_args.extend(changed_files) - - return self._run_svn(svn_commit_args, cwd=self.checkout_root, error_handler=commit_error_handler) - - def svn_commit_log(self, svn_revision): - svn_revision = self.strip_r_from_svn_revision(svn_revision) - return self._run_svn(['log', '--non-interactive', '--revision', svn_revision]) - - def last_svn_commit_log(self): - # BASE is the checkout revision, HEAD is the remote repository revision - # http://svnbook.red-bean.com/en/1.0/ch03s03.html - return self.svn_commit_log('BASE') - def blame(self, path): return self._run_svn(['blame', path]) - - def svn_blame(self, path): - return self._run_svn(['blame', path]) - - def propset(self, pname, pvalue, path): - dir, base = os.path.split(path) - return self._run_svn(['pset', pname, pvalue, base], cwd=dir) - - def propget(self, pname, path): - dir, base = os.path.split(path) - return self._run_svn(['pget', pname, base], cwd=dir).encode('utf-8').rstrip("\n") diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/ports.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/ports.py deleted file mode 100644 index abea220a4c2..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/ports.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright (C) 2009, Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# WebKit's Python module for understanding the various ports - -import os -import platform -import sys - -from webkitpy.common.system.executive import Executive - - -class DeprecatedPort(object): - results_directory = "/tmp/layout-test-results" - - # Subclasses must override - port_flag_name = None - - # FIXME: This is only used by BotInfo. - def name(self): - return self.__class__ - - def flag(self): - if self.port_flag_name: - return "--port=%s" % self.port_flag_name - return None - - # We might need to pass scm into this function for scm.checkout_root - def script_path(self, script_name): - return os.path.join("Tools", "Scripts", script_name) - - def script_shell_command(self, script_name): - script_path = self.script_path(script_name) - return Executive.shell_command_for_script(script_path) - - @staticmethod - def port(port_name): - ports = { - "chromium": ChromiumPort, - "chromium-android": AndroidPort, - "chromium-xvfb": ChromiumXVFBPort, - } - return ports.get(port_name, ChromiumPort)() - - def makeArgs(self): - # FIXME: This shouldn't use a static Executive(). - args = '--makeargs="-j%s"' % Executive().cpu_count() - if os.environ.has_key('MAKEFLAGS'): - args = '--makeargs="%s"' % os.environ['MAKEFLAGS'] - return args - - def check_webkit_style_command(self): - return self.script_shell_command("check-webkit-style") - - def run_webkit_unit_tests_command(self): - return None - - def run_webkit_tests_command(self): - return self.script_shell_command("run-webkit-tests") - - def run_python_unittests_command(self): - return self.script_shell_command("test-webkitpy") - - def run_perl_unittests_command(self): - return self.script_shell_command("test-webkitperl") - - def run_bindings_tests_command(self): - return self.script_shell_command("run-bindings-tests") - - -class ChromiumPort(DeprecatedPort): - port_flag_name = "chromium" - - def run_webkit_tests_command(self): - # Note: This could be run-webkit-tests now. - command = self.script_shell_command("new-run-webkit-tests") - command.append("--chromium") - command.append("--skip-failing-tests") - return command - - def run_webkit_unit_tests_command(self): - return self.script_shell_command("run-chromium-webkit-unit-tests") - - -class AndroidPort(ChromiumPort): - port_flag_name = "chromium-android" - - -class ChromiumXVFBPort(ChromiumPort): - port_flag_name = "chromium-xvfb" - - def run_webkit_tests_command(self): - return ["xvfb-run"] + super(ChromiumXVFBPort, self).run_webkit_tests_command() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/watchlist b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/watchlist deleted file mode 100644 index e61f122d0ba..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/watchlist +++ /dev/null @@ -1,460 +0,0 @@ -# When editing this file, please run the following command to make sure you -# haven't introduced any syntax errors: -# -# ./Tools/Scripts/check-webkit-style -# -# If you want to test your regular expressions, you can edit various files and -# then try following command: -# -# ./Tools/Scripts/webkit-patch apply-watchlist-local -# -{ - "DEFINITIONS": { - "ChromiumGraphics": { - "filename": r"Source/WebCore/platform/graphics/chromium/", - }, - "ChromiumPublicApi": { - "filename": r"Source/WebKit/chromium/public/" - r"|Source/Platform/chromium/public/" - r"|Tools/DumpRenderTree/chromium/TestRunner/public", - }, - "ChromiumTestRunner": { - "filename": r"Tools/DumpRenderTree/chromium/TestRunner", - }, - "AppleMacPublicApi": { - "filename": r"Source/WebCore/bindings/objc/PublicDOMInterfaces.h" - }, - "Forms": { - "filename": r"Source/WebCore/html/HTML(DataList|FieldSet|Input|Keygen|Label|Legend|OptGroup|Option|Output|Select|TextArea)Element\." - r"|Source/WebCore/html/.*Form[A-Z].*\." - r"|Source/WebCore/html/\w*InputType\." - r"|Source/WebCore/html/shadow/(SliderThumbElement|TextControlInnerElements)\." - r"|Source/WebCore/rendering/Render(FileUploadControl|ListBox|MenuList|Slider|TextControl.*)\." - }, - "Geolocation": { - "filename": r"Source/WebCore/Modules/geolocation/" - r"|Source/WebCore/page/GeolocationClient.h" - r"|Source/WebCore/bindings/js/JSGeolocationCustom.cpp" - r"|Source/WebCore/bindings/v8/custom/V8GeolocationCustom.cpp" - r"|Source/WebCore/platform/mock/GeolocationClientMock.(h|cpp)" - r"|Source/WebKit2/WebProcess/Geolocation/", - }, - "GStreamerGraphics": { - "filename": r"Source/WebCore/platform/graphics/gstreamer/", - }, - "GStreamerAudio": { - "filename": r"Source/WebCore/platform/audio/gstreamer/", - }, - "WebIDL": { - "filename": r"Source/WebCore/(?!inspector)(?!testing).*\.idl" - }, - "ThreadingFiles": { - "filename": r"Source/JavaScriptCore/wtf/ThreadSpecific\." - r"|Source/JavaScriptCore/wtf/ThreadSafeRefCounted\." - r"|Source/JavaScriptCore/wtf/ThreadingPrimitives\." - r"|Source/JavaScriptCore/wtf/Threading\." - r"|Source/WebCore/dom/CrossThreadTask\." - r"|Source/WebCore/platform/CrossThreadCopier\.", - }, - "ThreadingUsage": { - # The intention of this regex is to detect places where people are using common threading mechanisms, - # so that one can look them over for common mistakes. This list is long and likely to get longer over time. - # Note the negative look-ahead to avoid new mentions of the files (for builds or includes). - "more": r"(AllowCrossThreadAccess|AtomicallyInitialize|CrossThreadCopier|CrossThreadRefCounted|Mutex|ReadWriteLock|ThreadCondition|ThreadSafeRefCounted|ThreadSpecific" - r"|createCallbackTask|crossThreadString|deprecatedTurnOffVerifier|threadsafeCopy)(?!\.(h|cpp))", - }, - "WatchListScript": { - "filename": r"Tools/Scripts/webkitpy/common/watchlist/", - }, - "webkitpy": { - "filename": r"Tools/Scripts/webkitpy/", - }, - "webkitperl": { - "filename": r"Tools/Scripts/webkitperl/" - r"|Tools/Scripts/webkitdirs.pm" - r"|Tools/Scripts/VCSUtils.pm" - r"|Tools/Scripts/test-webkitperl", - }, - "SVNScripts": { - "filename": r"Tools/Scripts/svn-.*", - }, - "TestFailures": { - "filename": r"Tools/BuildSlaveSupport/build.webkit.org-config/public_html/TestFailures/", - }, - "SecurityCritical": { - "more": r"[Ss]ecurityOrigin(?!\.(h|cpp))", - "less": r"[Ss]ecurityOrigin(?!\.(h|cpp))", - "filename": r"XSS|[Ss]ecurity", - }, - "XSS": { - "filename": r".*XSS", - }, - "SkiaGraphics": { - "filename": r"Source/WebCore/platform/graphics/skia/" - r"|Source/WebCore/platform/graphics/filters/", - }, - "V8Bindings": { - "filename": r"Source/WebCore/bindings/v8/", - }, - "BindingsScripts": { - "filename": r"Source/WebCore/bindings/scripts/", - }, - "FrameLoader": { - "more": r"FrameLoader\.(cpp|h)", - }, - "Loader": { - "filename": r"Source/WebCore/loader/", - }, - "Rendering": { - "filename": r"Source/WebCore/rendering/", - }, - "RenderLayers": { - "filename": r"Source/WebCore/rendering/RenderLayer*", - }, - "GraphicsLayer": { - "filename": r"Source/WebCore/platform/graphics/GraphicsLayer*", - }, - "CoreAnimation": { - "filename": r"Source/WebCore/platform/graphics/ca/", - }, - "Animation": { - "filename": r"Source/WebCore/page/animation/", - }, - "StyleChecker": { - "filename": r"Tools/Scripts/webkitpy/style/", - }, - "GtkWebKit2PublicAPI": { - "filename": r"Source/WebKit2/UIProcess/API/gtk/", - }, - "QtBuildSystem": { - # Project files for each target are intentionally left out, as those - # mostly list source and header files, which would just add noise. - "filename": r"Tools/qmake/" - r"|WebKit.pro", - }, - "QtWebKit2PublicAPI": { - "filename": r"Source/WebKit2/UIProcess/API/qt/" - r"|Source/WebKit2/UIProcess/API/cpp/qt/" - r"|Source/WebKit2/UIProcess/API/C/qt/", - }, - "QtGraphics": { - "filename": r"Source/WebCore/platform/graphics/qt/" - }, - "CoordinatedGraphics": { - "filename": r"Source/WebKit2/WebProcess/WebPage/CoordinatedGraphics/" - r"|Source/WebKit2/UIProcess/CoordinatedGraphics/" - r"|Source/WebKit2/Shared/CoordinatedGraphics/" - r"|Source/WebCore/platform/graphics/surfaces/", - }, - "TextureMapper": { - "filename": r"Source/WebCore/platform/graphics/texmap/", - }, - "OpenGL": { - "filename": r"Source/WebCore/platform/graphics/opengl/", - }, - "QtWebKit2PlatformSpecific": { - "filename": r"Source/WebKit2/.*\.(pri|pro)" - r"|Source/WebKit2/Platform/qt/" - r"|Source/WebKit2/qt/" - r"|Source/WebKit2/PluginProcess/qt/" - r"|Source/WebKit2/Platform/qt/" - r"|Source/WebKit2/Shared/API/c/qt/" - r"|Source/WebKit2/Shared/qt/" - r"|Source/WebKit2/WebProcess/InjectedBundle/qt/" - r"|Source/WebKit2/WebProcess/FullScreen/qt/" - r"|Source/WebKit2/WebProcess/WebPage/qt/" - r"|Source/WebKit2/WebProcess/qt/" - r"|Source/WebKit2/WebProcess/Plugins/Netscape/qt/" - r"|Source/WebKit2/WebProcess/Downloads/qt/" - r"|Source/WebKit2/WebProcess/WebCoreSupport/qt/" - r"|Source/WebKit2/WebProcess/Cookies/qt/" - r"|Source/WebKit2/UIProcess/qt/" - r"|Source/WebKit2/UIProcess/Plugins/qt/" - r"|Source/WebKit2/UIProcess/Launcher/qt/", - }, - "CSS": { - "filename": r"Source/WebCore/css/", - }, - "DOM": { - "filename": r"Source/WebCore/dom/", - }, - "HTML": { - "filename": r"Source/WebCore/html/", - }, - "DOMAttributes": { - "filename": r"Source/WebCore/dom/.*Attr.*" - r"|Source/WebCore/dom/NamedNodeMap\.(cpp|h|idl)" - r"|Source/WebCore/dom/Element\.(cpp|h|idl)", - }, - "EFL": { - "filename": r"Source/WebKit/efl/" - r"|Source/WebCore/platform/efl/" - r"|Source/WTF/wtf/efl/" - r"|Tools/EWebLauncher" - r"|Tools/DumpRenderTree/efl/" - r"|LayoutTests/platform/efl/", - }, - "EFLWebKit2PublicAPI": { - "filename": r"Source/WebKit2/UIProcess/API/efl/" - r"|Source/WebKit2/UIProcess/API/C/efl/", - }, - "EFLWebKit2PlatformSpecific": { - "filename": r"Source/WebKit2/.*\.(cmake|txt)" - r"|Source/WebKit2/Platform/efl/" - r"|Source/WebKit2/efl/" - r"|Source/WebKit2/Shared/API/c/efl/" - r"|Source/WebKit2/Shared/efl/" - r"|Source/WebKit2/WebProcess/InjectedBundle/efl/" - r"|Source/WebKit2/WebProcess/WebPage/efl/" - r"|Source/WebKit2/WebProcess/efl/" - r"|Source/WebKit2/WebProcess/Downloads/efl/" - r"|Source/WebKit2/WebProcess/WebCoreSupport/efl/" - r"|Source/WebKit2/UIProcess/efl/" - r"|Source/WebKit2/UIProcess/Launcher/efl/", - }, - "CMake": { - "filename": r".*CMakeLists\w*\.txt" - r"|.*\w+\.cmake" - r"|Source/cmake/", - }, - "Selectors": { - "filename": r"Source/WebCore/css/CSSSelector*" - r"|Source/WebCore/css/SelectorChecker.*" - r"|Source/WebCore/css/StyleResolver.*" - r"|Source/WebCore/dom/SelectorQuery.*", - }, - "SoupNetwork": { - "filename": r"Source/WebCore/platform/network/soup/", - }, - "ScrollingCoordinator": { - "filename": r"Source/WebCore/page/scrolling/", - }, - "WebKitGTKTranslations": { - "filename": r"Source/WebKit/gtk/po/", - }, - "Media": { - "filename": r"(Source|LayoutTests)/.*([Mm]edia|[Aa]udio|[Vv]ideo)", - }, - "MathML": { - "filename": r"(Source|LayoutTests|Websites)/.*mathml", - }, - "Editing": { - "filename": r"Source/WebCore/editing/", - }, - "BlackBerry": { - "filename": r"Source/WebKit/blackberry/" - r"|Source/WebCore/page/blackberry" - r"|Source/WebCore/history/blackberry" - r"|Source/WebCore/plugins/blackberry" - r"|Source/WebCore/editing/blackberry" - r"|Source/WebCore/Resources/blackberry" - r"|Source/WebCore/platform/image-decoders/blackberry" - r"|Source/WebCore/platform/blackberry" - r"|Source/WebCore/platform/text/blackberry" - r"|Source/WebCore/platform/network/blackberry" - r"|Source/WebCore/platform/graphics/blackberry" - r"|Source/WTF/wtf/blackberry" - r"|ManualTests/blackberry" - r"|Tools/DumpRenderTree/blackberry" - r"|LayoutTests/platform/blackberry", - }, - "NetworkInfo": { - "filename": r"Source/WebCore/Modules/networkinfo", - }, - "Battery": { - "filename": r"Source/WebCore/Modules/battery", - }, - "WTF": { - "filename": r"Source/WTF/wtf", - }, - "WebGL": { - "filename": r"Source/WebCore/html/canvas/.*WebGL.*" - r"|Source/WebCore/bindings/js/.*WebGL.*" - r"|Source/WebCore/platform/graphics/gpu" - r"|Source/WebCore/platform/graphics/opengl" - r"|Source/WebCore/platform/graphics/ANGLE.*" - r"|Source/WebCore/platform/graphics/.*GraphicsContext3D.*" - r"|Source/ThirdParty/ANGLE", - }, - "Filters": { - "filename": r"Source/WebCore/platform/graphics/filters" - r"|Source/WebCore/rendering/.*Filter.*" - r"|Source/WebCore/rendering/style/.*Filter.*" - r"|Source/WebCore/rendering/svg/.*Filter.*" - r"|Source/WebCore/svg/graphics/filters" - r"|Source/WebCore/svg/graphics/.*Filter.*", - }, - "TouchAdjustment": { - "filename": r"Source/WebCore/page/TouchAdjustment.*" - r"|LayoutTests/touchadjustment" - r"|Source/WebKit/blackberry/WebKitSupport/FatFingers.*", - }, - "SVG": { - "filename": r"Source/WebCore/svg" - r"|Source/WebCore/rendering/svg", - }, - "WebInspectorAPI": { - "filename": r"Source/WebCore/inspector/InjectedScriptSource.js" - r"|Source/WebCore/inspector/.+\.json" - r"|Source/WebCore/inspector/.+\.idl" - r"|Source/WebCore/page/Console.idl", - }, - "WebSocket": { - "filename": r"Source/WebCore/Modules/websockets" - r"|Source/WebCore/platform/network/(|.+/)SocketStream.*", - }, - "MediaStream": { - "filename": r"Source/WebCore/Modules/mediastream" - r"|Source/WebCore/platform/mediastream" - r"|LayoutTests/fast/mediastream", - }, - "Accessibility": { - "filename": r"Source/WebCore/accessibility" - r"|LayoutTests/.*accessibility", - }, - "Cairo": { - "filename": r"Source/WebCore/platform/graphics/cairo", - }, - "Harfbuzz": { - "filename": r"Source/WebCore/platform/graphics/harfbuzz", - }, - "PerformanceTests": { - "filename": r"PerformanceTests" - r"|Tools/Scripts/webkitpy/performance_tests", - }, - "GtkBuildSystem": { - "filename": r"configure.ac" - r"|.*GNUmakefile.(am|features.am.in)", - }, - "ConsoleUsage": { - "more": r"[Aa]ddConsoleMessage|reportException|logExceptionToConsole|addMessage|printErrorMessage" - }, - "ContentSecurityPolicyUsage": { - "more": r"[Cc]ontentSecurityPolicy(?!\.(h|cpp))", - }, - "ContentSecurityPolicyFiles": { - "filename": r"Source/WebCore/page/(Content|DOM)SecurityPolicy\." - r"|LayoutTests/http/tests/security/contentSecurityPolicy" - }, - "RegionsDevelopment": { - "filename": r"Source/WebCore/rendering/RenderRegion\.(h|cpp)" - r"|Source/WebCore/rendering/RenderFlowThread\.(h|cpp)" - r"|Source/WebCore/rendering/FlowThreadController\.(h|cpp)" - r"|Source/WebCore/rendering/RenderRegionSet\.(h|cpp)" - r"|Source/WebCore/rendering/RenderNamedFlowThread\.(h|cpp)" - r"|Source/WebCore/rendering/RenderBoxRegionInfo\.h" - r"|Source/WebCore/dom/WebKitNamedFlow\.(h|cpp|idl)" - r"|Source/WebCore/dom/(DOM)?NamedFlowCollection\.(h|cpp|idl)" - r"|Source/WebCore/css/WebKitCSSRegionRule\.(h|cpp|idl)" - r"|LayoutTests/fast/regions", - }, - "RegionsExpectationsMore": { - "filename": r"LayoutTests/platform/.*TestExpectations", - "more": r"fast/regions/.*\.html", - }, - "RegionsExpectationsLess": { - "filename": r"LayoutTests/platform/.*TestExpectations", - "less": r"fast/regions/.*\.html", - }, - "RegionsUsage": { - "more": r"(RenderRegion|RenderFlowThread|RenderNamedFlowThread)(?!\.(h|cpp))", - }, - "IndexedDB": { - "filename": r"Source/WebCore/Modules/indexeddb" - r"|Source/WebCore/bindings/.*IDB.*\.(h|cpp)" - r"|Source/WebCore/bindings/.*SerializedScriptValue.*\.(h|cpp)" - r"|Source/WebKit/chromium/.*IDB.*\.(h|cpp)" - r"|Source/WebCore/platform/leveldb" - r"|LayoutTests/storage/indexeddb" - r"|LayoutTests/platform/.*/storage/indexeddb", - }, - }, - "CC_RULES": { - # Note: All email addresses listed must be registered with bugzilla. - # Specifically, levin@chromium.org and levin+threading@chromium.org are - # two different accounts as far as bugzilla is concerned. - "Accessibility": [ "cfleizach@apple.com", "dmazzoni@google.com", "apinheiro@igalia.com", "jdiggs@igalia.com", "aboxhall@chromium.org" ], - "Animation" : [ "simon.fraser@apple.com", "dino@apple.com", "dstockwell@chromium.org" ], - "AppleMacPublicApi": [ "timothy@apple.com" ], - "Battery": [ "gyuyoung.kim@samsung.com" ], - "BlackBerry": [ "mifenton@rim.com", "rwlbuis@gmail.com", "tonikitoo@webkit.org" ], - "Cairo": [ "dominik.rottsches@intel.com" ], - "CMake": [ "rakuco@webkit.org", "gyuyoung.kim@samsung.com" ], - "CoordinatedGraphics" : [ "noam@webkit.org", "zeno@webkit.org", "cmarcelo@webkit.org", "luiz@webkit.org" ], - "ConsoleUsage" : [ "mkwst+watchlist@chromium.org" ], - "ContentSecurityPolicyFiles|ContentSecurityPolicyUsage" : [ "mkwst+watchlist@chromium.org" ], - "CoreAnimation" : [ "simon.fraser@apple.com" ], - "CSS": [ "alexis@webkit.org", "macpherson@chromium.org", "ojan.autocc@gmail.com", "esprehn+autocc@chromium.org"], - "ChromiumGraphics": [ "jamesr@chromium.org", "cc-bugs@chromium.org" ], - "ChromiumPublicApi": [ "abarth@webkit.org", "dglazkov@chromium.org", "fishd@chromium.org", "jamesr@chromium.org", "tkent+wkapi@chromium.org" ], - "ChromiumTestRunner": [ "jochen@chromium.org" ], - "DOM": [ "ojan.autocc@gmail.com", "esprehn+autocc@chromium.org" ], - "DOMAttributes": [ "cmarcelo@webkit.org", ], - "EFL": [ "rakuco@webkit.org", "gyuyoung.kim@samsung.com" ], - "EFLWebKit2PlatformSpecific": [ "gyuyoung.kim@samsung.com", "rakuco@webkit.org" ], - "EFLWebKit2PublicAPI": [ "gyuyoung.kim@samsung.com", "rakuco@webkit.org" ], - "Editing": [ "mifenton@rim.com" ], - "Filters": [ "dino@apple.com" ], - "Forms": [ "tkent@chromium.org", "mifenton@rim.com" ], - "FrameLoader": [ "abarth@webkit.org", "japhet@chromium.org" ], - "Geolocation": [ "benjamin@webkit.org" ], - "GraphicsLayer": [ "simon.fraser@apple.com" ], - "GStreamerGraphics": [ "alexis@webkit.org", "pnormand@igalia.com", "gns@gnome.org", "mrobinson@webkit.org" ], - "GStreamerAudio": [ "pnormand@igalia.com" ], - "GtkBuildSystem": [ "zandobersek@gmail.com" ], - "GtkWebKit2PublicAPI": [ "cgarcia@igalia.com", "gns@gnome.org", "mrobinson@webkit.org" ], - "Harfbuzz": [ "dominik.rottsches@intel.com" ], - "HTML": [ "ojan.autocc@gmail.com", "esprehn+autocc@chromium.org" ], - "IndexedDB": [ "alecflett@chromium.org", "dgrogan@chromium.org", "jsbell@chromium.org" ], - "Loader": [ "japhet@chromium.org" ], - "MathML": [ "dbarton@mathscribe.com" ], - "Media": [ "feature-media-reviews@chromium.org", "eric.carlson@apple.com", "jer.noble@apple.com" ], - "MediaStream": [ "tommyw@google.com", "hta@google.com" ], - "NetworkInfo": [ "gyuyoung.kim@samsung.com" ], - "OpenGL" : [ "noam@webkit.org", "dino@apple.com" ], - "PerformanceTests": [ "rniwa@webkit.org" ], - "QtBuildSystem" : [ "vestbo@webkit.org", "abecsi@webkit.org" ], - "QtGraphics" : [ "noam@webkit.org" ], - "QtWebKit2PlatformSpecific": [ "alexis@webkit.org", "cmarcelo@webkit.org", "abecsi@webkit.org" ], - "QtWebKit2PublicAPI": [ "alexis@webkit.org", "cmarcelo@webkit.org", "abecsi@webkit.org" ], - "RegionsDevelopment|RegionsExpectationsMore|RegionsExpectationsLess|RegionsUsage": [ "WebkitBugTracker@adobe.com" ], - "Rendering": [ "eric@webkit.org", "ojan.autocc@gmail.com", "esprehn+autocc@chromium.org" ], - "RenderLayers" : [ "simon.fraser@apple.com" ], - "SVG": ["schenney@chromium.org", "pdr@google.com", "fmalita@chromium.org", "dominik.rottsches@intel.com" ], - "SVNScripts": [ "dbates@webkit.org" ], - "ScrollingCoordinator": [ "andersca@apple.com", "jamesr@chromium.org", "tonikitoo@webkit.org", "cmarcelo@webkit.org", "luiz@webkit.org" ], - "SecurityCritical": [ "abarth@webkit.org" ], - "SkiaGraphics": [ "senorblanco@chromium.org", "junov@google.com" ], - "Selectors": [ "allan.jensen@digia.com" ], - "SoupNetwork": [ "rakuco@webkit.org", "gns@gnome.org", "mrobinson@webkit.org", "danw@gnome.org" ], - "StyleChecker": [ "levin@chromium.org", ], - "TestFailures": [ "abarth@webkit.org", "dglazkov@chromium.org", "ojan.autocc@gmail.com" ], - "TextureMapper" : [ "noam@webkit.org", "cmarcelo@webkit.org", "luiz@webkit.org" ], - "ThreadingFiles|ThreadingUsage": [ "levin+threading@chromium.org", ], - "TouchAdjustment" : [ "allan.jensen@digia.com" ], - "V8Bindings|BindingsScripts": [ "abarth@webkit.org", "japhet@chromium.org", "haraken@chromium.org" ], - "WTF": [ "benjamin@webkit.org", "ojan.autocc@gmail.com", "cmarcelo@webkit.org" ], - "WatchListScript": [ "levin+watchlist@chromium.org", ], - "WebGL": [ "dino@apple.com" ], - "WebIDL": [ "abarth@webkit.org", "ojan.autocc@gmail.com", "esprehn+autocc@chromium.org" ], - "WebInspectorAPI": [ "timothy@apple.com", "joepeck@webkit.org", "graouts@apple.com" ], - "WebKitGTKTranslations": [ "gns@gnome.org", "mrobinson@webkit.org" ], - "WebSocket": [ "yutak@chromium.org", "toyoshim+watchlist@chromium.org" ], - "XSS": [ "dbates@webkit.org", "ojan.autocc@gmail.com" ], - "webkitperl": [ "dbates@webkit.org" ], - "webkitpy": [ "abarth@webkit.org", "dpranke@chromium.org", "eric@webkit.org" ], - }, - "MESSAGE_RULES": { - "ChromiumPublicApi": [ "Please wait for approval from abarth@webkit.org, dglazkov@chromium.org, " - "fishd@chromium.org, jamesr@chromium.org or tkent@chromium.org before " - "submitting, as this patch contains changes to the Chromium public API. " - "See also https://trac.webkit.org/wiki/ChromiumWebKitAPI." ], - "AppleMacPublicApi": [ "Please wait for approval from timothy@apple.com (or another member " - "of the Apple Safari Team) before submitting " - "because this patch contains changes to the Apple Mac " - "WebKit.framework public API.", ], - "GtkWebKit2PublicAPI": [ "Thanks for the patch. If this patch contains new public API " - "please make sure it follows the guidelines for new WebKit2 GTK+ API. " - "See http://trac.webkit.org/wiki/WebKitGTK/AddingNewWebKit2API", ], - }, -} diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host.py index a8df7a5f929..7671ea6201c 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host.py @@ -31,7 +31,6 @@ import logging import os import sys -from webkitpy.common.checkout import Checkout from webkitpy.common.checkout.scm.detection import SCMDetector from webkitpy.common.memoized import memoized from webkitpy.common.net import buildbot, web @@ -48,9 +47,7 @@ class Host(SystemHost): SystemHost.__init__(self) self.web = web.Web() - # FIXME: Checkout should own the scm object. self._scm = None - self._checkout = None # Everything below this line is WebKit-specific and belongs on a higher-level object. self.buildbot = buildbot.BuildBot() @@ -128,14 +125,10 @@ class Host(SystemHost): self._engage_awesome_windows_hacks() detector = SCMDetector(self.filesystem, self.executive) self._scm = detector.default_scm(patch_directories) - self._checkout = Checkout(self.scm()) def scm(self): return self._scm - def checkout(self): - return self._checkout - def buildbot_for_builder_name(self, name): if self.port_factory.get_from_builder_name(name).is_chromium(): return self.chromium_buildbot() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py index c18a3effda9..82738bfdc82 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/host_mock.py @@ -26,7 +26,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from webkitpy.common.checkout.checkout_mock import MockCheckout from webkitpy.common.checkout.scm.scm_mock import MockSCM from webkitpy.common.net.buildbot.buildbot_mock import MockBuildBot from webkitpy.common.net.web_mock import MockWeb @@ -43,7 +42,6 @@ class MockHost(MockSystemHost): add_unit_tests_to_mock_filesystem(self.filesystem) self.web = web or MockWeb() - self._checkout = MockCheckout() self._scm = None # FIXME: we should never initialize the SCM by default, since the real # object doesn't either. This has caused at least one bug (see bug 89498). diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/message_pool.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/message_pool.py index 03056cf64fe..2e8eb7db961 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/message_pool.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/message_pool.py @@ -56,17 +56,16 @@ from webkitpy.common.system import stack_utils _log = logging.getLogger(__name__) -def get(caller, worker_factory, num_workers, worker_startup_delay_secs=0.0, host=None): +def get(caller, worker_factory, num_workers, host=None): """Returns an object that exposes a run() method that takes a list of test shards and runs them in parallel.""" - return _MessagePool(caller, worker_factory, num_workers, worker_startup_delay_secs, host) + return _MessagePool(caller, worker_factory, num_workers, host) class _MessagePool(object): - def __init__(self, caller, worker_factory, num_workers, worker_startup_delay_secs=0.0, host=None): + def __init__(self, caller, worker_factory, num_workers, host=None): self._caller = caller self._worker_factory = worker_factory self._num_workers = num_workers - self._worker_startup_delay_secs = worker_startup_delay_secs self._workers = [] self._workers_stopped = set() self._host = host @@ -107,8 +106,6 @@ class _MessagePool(object): worker = _Worker(host, self._messages_to_manager, self._messages_to_worker, self._worker_factory, worker_number, self._running_inline, self if self._running_inline else None, self._worker_log_level()) self._workers.append(worker) worker.start() - if self._worker_startup_delay_secs: - time.sleep(self._worker_startup_delay_secs) def _worker_log_level(self): log_level = logging.NOTSET @@ -209,6 +206,7 @@ class _Worker(multiprocessing.Process): self.name = 'worker/%d' % worker_number self.log_messages = [] self.log_level = log_level + self._running = False self._running_inline = running_inline self._manager = manager @@ -245,11 +243,12 @@ class _Worker(multiprocessing.Process): worker = self._worker exception_msg = "" _log.debug("%s starting" % self.name) + self._running = True try: if hasattr(worker, 'start'): worker.start() - while True: + while self._running: message = self._messages_to_worker.get() if message.from_user: worker.handle(message.name, message.src, *message.args) @@ -273,6 +272,9 @@ class _Worker(multiprocessing.Process): self._post(name='done', args=(), from_user=False) self._close() + def stop_running(self): + self._running = False + def post(self, name, *args): self._post(name, args, from_user=True) self._yield_to_manager() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/net/credentials.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/net/credentials.py deleted file mode 100644 index 7038b7e3cdb..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/net/credentials.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Python module for reading stored web credentials from the OS. - -import logging -import os -import platform -import re - -from webkitpy.common.checkout.scm import Git -from webkitpy.common.system.executive import Executive, ScriptError -from webkitpy.common.system.user import User - -try: - # Use keyring, a cross platform keyring interface, as a fallback: - # http://pypi.python.org/pypi/keyring - import keyring -except ImportError: - keyring = None - -_log = logging.getLogger(__name__) - - -class Credentials(object): - _environ_prefix = "webkit_bugzilla_" - - def __init__(self, host, git_prefix=None, executive=None, cwd=os.getcwd(), - keyring=keyring): - self.host = host - self.git_prefix = "%s." % git_prefix if git_prefix else "" - self.executive = executive or Executive() - self.cwd = cwd - self._keyring = keyring - - def _credentials_from_git(self): - try: - if not Git.in_working_directory(self.cwd): - return (None, None) - return (Git.read_git_config(self.git_prefix + "username"), - Git.read_git_config(self.git_prefix + "password")) - except OSError, e: - # Catch and ignore OSError exceptions such as "no such file - # or directory" (OSError errno 2), which imply that the Git - # command cannot be found/is not installed. - pass - return (None, None) - - def _keychain_value_with_label(self, label, source_text): - match = re.search("%s\"(?P<value>.+)\"" % label, - source_text, - re.MULTILINE) - if match: - return match.group('value') - - def _is_mac_os_x(self): - return platform.mac_ver()[0] - - def _parse_security_tool_output(self, security_output): - username = self._keychain_value_with_label("^\s*\"acct\"<blob>=", - security_output) - password = self._keychain_value_with_label("^password: ", - security_output) - return [username, password] - - def _run_security_tool(self, username=None): - security_command = [ - "/usr/bin/security", - "find-internet-password", - "-g", - "-s", - self.host, - ] - if username: - security_command += ["-a", username] - - _log.info("Reading Keychain for %s account and password. " - "Click \"Allow\" to continue..." % self.host) - try: - return self.executive.run_command(security_command) - except ScriptError: - # Failed to either find a keychain entry or somekind of OS-related - # error occured (for instance, couldn't find the /usr/sbin/security - # command). - _log.error("Could not find a keychain entry for %s." % self.host) - return None - - def _credentials_from_keychain(self, username=None): - if not self._is_mac_os_x(): - return [username, None] - - security_output = self._run_security_tool(username) - if security_output: - return self._parse_security_tool_output(security_output) - else: - return [None, None] - - def _read_environ(self, key): - environ_key = self._environ_prefix + key - return os.environ.get(environ_key.upper()) - - def _credentials_from_environment(self): - return (self._read_environ("username"), self._read_environ("password")) - - def _offer_to_store_credentials_in_keyring(self, username, password): - if not self._keyring: - return - if not User().confirm("Store password in system keyring?", User.DEFAULT_NO): - return - try: - self._keyring.set_password(self.host, username, password) - except: - pass - - def read_credentials(self, user=User): - username, password = self._credentials_from_environment() - # FIXME: We don't currently support pulling the username from one - # source and the password from a separate source. - if not username or not password: - username, password = self._credentials_from_git() - if not username or not password: - username, password = self._credentials_from_keychain(username) - - if not username: - username = user.prompt("%s login: " % self.host) - - if username and not password and self._keyring: - try: - password = self._keyring.get_password(self.host, username) - except: - pass - - if not password: - password = user.prompt_password("%s password for %s: " % (self.host, username)) - self._offer_to_store_credentials_in_keyring(username, password) - - return (username, password) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/net/credentials_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/net/credentials_unittest.py deleted file mode 100644 index 06999077b8a..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/net/credentials_unittest.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import tempfile -import webkitpy.thirdparty.unittest2 as unittest -from webkitpy.common.net.credentials import Credentials -from webkitpy.common.system.executive import Executive -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.common.system.user_mock import MockUser -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.mocktool import MockOptions -from webkitpy.common.system.executive_mock import MockExecutive - - -# FIXME: Other unit tests probably want this class. -class _TemporaryDirectory(object): - def __init__(self, **kwargs): - self._kwargs = kwargs - self._directory_path = None - - def __enter__(self): - self._directory_path = tempfile.mkdtemp(**self._kwargs) - return self._directory_path - - def __exit__(self, type, value, traceback): - os.rmdir(self._directory_path) - - -# Note: All tests should use this class instead of Credentials directly to avoid using a real Executive. -class MockedCredentials(Credentials): - def __init__(self, *args, **kwargs): - if 'executive' not in kwargs: - kwargs['executive'] = MockExecutive() - Credentials.__init__(self, *args, **kwargs) - - -class CredentialsTest(unittest.TestCase): - example_security_output = """keychain: "/Users/test/Library/Keychains/login.keychain" -class: "inet" -attributes: - 0x00000007 <blob>="bugs.webkit.org (test@webkit.org)" - 0x00000008 <blob>=<NULL> - "acct"<blob>="test@webkit.org" - "atyp"<blob>="form" - "cdat"<timedate>=0x32303039303832353233353231365A00 "20090825235216Z\000" - "crtr"<uint32>=<NULL> - "cusi"<sint32>=<NULL> - "desc"<blob>="Web form password" - "icmt"<blob>="default" - "invi"<sint32>=<NULL> - "mdat"<timedate>=0x32303039303930393137323635315A00 "20090909172651Z\000" - "nega"<sint32>=<NULL> - "path"<blob>=<NULL> - "port"<uint32>=0x00000000 - "prot"<blob>=<NULL> - "ptcl"<uint32>="htps" - "scrp"<sint32>=<NULL> - "sdmn"<blob>=<NULL> - "srvr"<blob>="bugs.webkit.org" - "type"<uint32>=<NULL> -password: "SECRETSAUCE" -""" - - def test_keychain_lookup_on_non_mac(self): - class FakeCredentials(MockedCredentials): - def _is_mac_os_x(self): - return False - credentials = FakeCredentials("bugs.webkit.org") - self.assertFalse(credentials._is_mac_os_x()) - self.assertEqual(credentials._credentials_from_keychain("foo"), ["foo", None]) - - def test_security_output_parse(self): - credentials = MockedCredentials("bugs.webkit.org") - self.assertEqual(credentials._parse_security_tool_output(self.example_security_output), ["test@webkit.org", "SECRETSAUCE"]) - - def test_security_output_parse_entry_not_found(self): - # FIXME: This test won't work if the user has a credential for foo.example.com! - credentials = Credentials("foo.example.com") - if not credentials._is_mac_os_x(): - return # This test does not run on a non-Mac. - - # Note, we ignore the captured output because it is already covered - # by the test case CredentialsTest._assert_security_call (below). - outputCapture = OutputCapture() - outputCapture.capture_output() - self.assertIsNone(credentials._run_security_tool()) - outputCapture.restore_output() - - def _assert_security_call(self, username=None): - executive_mock = Mock() - credentials = MockedCredentials("example.com", executive=executive_mock) - - expected_logs = "Reading Keychain for example.com account and password. Click \"Allow\" to continue...\n" - OutputCapture().assert_outputs(self, credentials._run_security_tool, [username], expected_logs=expected_logs) - - security_args = ["/usr/bin/security", "find-internet-password", "-g", "-s", "example.com"] - if username: - security_args += ["-a", username] - executive_mock.run_command.assert_called_with(security_args) - - def test_security_calls(self): - self._assert_security_call() - self._assert_security_call(username="foo") - - def test_credentials_from_environment(self): - credentials = MockedCredentials("example.com") - - saved_environ = os.environ.copy() - os.environ['WEBKIT_BUGZILLA_USERNAME'] = "foo" - os.environ['WEBKIT_BUGZILLA_PASSWORD'] = "bar" - username, password = credentials._credentials_from_environment() - self.assertEqual(username, "foo") - self.assertEqual(password, "bar") - os.environ = saved_environ - - def test_read_credentials_without_git_repo(self): - # FIXME: This should share more code with test_keyring_without_git_repo - class FakeCredentials(MockedCredentials): - def _is_mac_os_x(self): - return True - - def _credentials_from_keychain(self, username): - return ("test@webkit.org", "SECRETSAUCE") - - def _credentials_from_environment(self): - return (None, None) - - with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: - credentials = FakeCredentials("bugs.webkit.org", cwd=temp_dir_path) - # FIXME: Using read_credentials here seems too broad as higher-priority - # credential source could be affected by the user's environment. - self.assertEqual(credentials.read_credentials(), ("test@webkit.org", "SECRETSAUCE")) - - - def test_keyring_without_git_repo(self): - # FIXME: This should share more code with test_read_credentials_without_git_repo - class MockKeyring(object): - def get_password(self, host, username): - return "NOMNOMNOM" - - class FakeCredentials(MockedCredentials): - def _is_mac_os_x(self): - return True - - def _credentials_from_keychain(self, username): - return ("test@webkit.org", None) - - def _credentials_from_environment(self): - return (None, None) - - with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: - credentials = FakeCredentials("fake.hostname", cwd=temp_dir_path, keyring=MockKeyring()) - # FIXME: Using read_credentials here seems too broad as higher-priority - # credential source could be affected by the user's environment. - self.assertEqual(credentials.read_credentials(), ("test@webkit.org", "NOMNOMNOM")) - - def test_keyring_without_git_repo_nor_keychain(self): - class MockKeyring(object): - def get_password(self, host, username): - return "NOMNOMNOM" - - class FakeCredentials(MockedCredentials): - def _credentials_from_keychain(self, username): - return (None, None) - - def _credentials_from_environment(self): - return (None, None) - - class FakeUser(MockUser): - @classmethod - def prompt(cls, message, repeat=1, raw_input=raw_input): - return "test@webkit.org" - - @classmethod - def prompt_password(cls, message, repeat=1, raw_input=raw_input): - raise AssertionError("should not prompt for password") - - with _TemporaryDirectory(suffix="not_a_git_repo") as temp_dir_path: - credentials = FakeCredentials("fake.hostname", cwd=temp_dir_path, keyring=MockKeyring()) - # FIXME: Using read_credentials here seems too broad as higher-priority - # credential source could be affected by the user's environment. - self.assertEqual(credentials.read_credentials(FakeUser), ("test@webkit.org", "NOMNOMNOM")) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem.py index 3786c6fed22..fdf43473b13 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem.py @@ -131,6 +131,9 @@ class FileSystem(object): def listdir(self, path): return os.listdir(path) + def walk(self, top): + return os.walk(top) + def mkdtemp(self, **kwargs): """Create and return a uniquely named directory. diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_mock.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_mock.py index 91a7df40b03..70dfff3fa64 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_mock.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_mock.py @@ -96,7 +96,7 @@ class MockFileSystem(object): return home_directory + self.sep + parts[1] def path_to_module(self, module_name): - return "/mock-checkout/Tools/Scripts/" + module_name.replace('.', '/') + ".py" + return "/mock-checkout/third_party/WebKit/Tools/Scripts/" + module_name.replace('.', '/') + ".py" def chdir(self, path): path = self.normpath(path) @@ -201,25 +201,29 @@ class MockFileSystem(object): return path def listdir(self, path): + root, dirs, files = list(self.walk(path))[0] + return dirs + files + + def walk(self, top): sep = self.sep - if not self.isdir(path): - raise OSError("%s is not a directory" % path) + if not self.isdir(top): + raise OSError("%s is not a directory" % top) - if not path.endswith(sep): - path += sep + if not top.endswith(sep): + top += sep dirs = [] files = [] for f in self.files: - if self.exists(f) and f.startswith(path): - remaining = f[len(path):] + if self.exists(f) and f.startswith(top): + remaining = f[len(top):] if sep in remaining: dir = remaining[:remaining.index(sep)] if not dir in dirs: dirs.append(dir) else: files.append(remaining) - return dirs + files + return [(top[:-1], dirs, files)] def mtime(self, path): if self.exists(path): diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py index e57328470ee..4ad488baa2d 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/filesystem_unittest.py @@ -183,6 +183,15 @@ class RealFileSystemTest(unittest.TestCase, GenericFileSystemTests): self.assertEqual(fs.listdir(d), ['foo']) os.remove(new_file) + def test_walk(self): + fs = FileSystem() + with fs.mkdtemp(prefix='filesystem_unittest_') as d: + self.assertEqual(list(fs.walk(d)), [(d, [], [])]) + new_file = os.path.join(d, 'foo') + fs.write_text_file(new_file, u'foo') + self.assertEqual(list(fs.walk(d)), [(d, [], ['foo'])]) + os.remove(new_file) + def test_maybe_make_directory__success(self): fs = FileSystem() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo.py index b2c602bed35..a0c8dc347bb 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo.py @@ -131,12 +131,13 @@ class PlatformInfo(object): raise AssertionError('unrecognized platform string "%s"' % sys_platform) def _determine_mac_version(self, mac_version_string): - release_version = mac_version_string.split('.')[1] + release_version = int(mac_version_string.split('.')[1]) version_strings = { - '5': 'leopard', - '6': 'snowleopard', - '7': 'lion', - '8': 'mountainlion', + 5: 'leopard', + 6: 'snowleopard', + 7: 'lion', + 8: 'mountainlion', + 9: 'mavericks', } assert release_version >= min(version_strings.keys()) return version_strings.get(release_version, 'future') diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py index 58fbde754ef..24fa35210a6 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/system/platforminfo_unittest.py @@ -135,7 +135,8 @@ class TestPlatformInfo(unittest.TestCase): self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.6.1')).os_version, 'snowleopard') self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.7.1')).os_version, 'lion') self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.8.1')).os_version, 'mountainlion') - self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.9.0')).os_version, 'future') + self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.9.0')).os_version, 'mavericks') + self.assertEqual(self.make_info(fake_sys('darwin'), fake_platform('10.10.0')).os_version, 'future') self.assertEqual(self.make_info(fake_sys('linux2')).os_version, 'lucid') diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/webkit_finder.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/webkit_finder.py index d94bf6d4118..f267f0dae3a 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/webkit_finder.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/webkit_finder.py @@ -37,6 +37,7 @@ class WebKitFinder(object): self._sys_path = sys.path self._env_path = os.environ['PATH'].split(os.pathsep) self._webkit_base = None + self._chromium_base = None self._depot_tools = None def webkit_base(self): @@ -56,9 +57,17 @@ class WebKitFinder(object): self._webkit_base = self._filesystem.normpath(module_path[0:tools_index - 1]) return self._webkit_base + def chromium_base(self): + if not self._chromium_base: + self._chromium_base = self._filesystem.dirname(self._filesystem.dirname(self.webkit_base())) + return self._chromium_base + def path_from_webkit_base(self, *comps): return self._filesystem.join(self.webkit_base(), *comps) + def path_from_chromium_base(self, *comps): + return self._filesystem.join(self.chromium_base(), *comps) + def path_to_script(self, script_name): """Returns the relative path to the script from the top of the WebKit tree.""" # This is intentionally relative in order to force callers to consider what diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/bisect_test_ordering.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/bisect_test_ordering.py new file mode 100644 index 00000000000..a10ed15fa9a --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/bisect_test_ordering.py @@ -0,0 +1,170 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import logging +import math +import optparse +import os +import subprocess +import sys + +from webkitpy.common.system.executive import Executive +from webkitpy.common.system.filesystem import FileSystem +from webkitpy.common.webkit_finder import WebKitFinder + +_log = logging.getLogger(__name__) + + +class Bucket(object): + def __init__(self, tests): + self.tests = tests + + def size(self): + return len(self.tests) + + +class Bisector(object): + + def __init__(self, tests, is_debug): + self.executive = Executive() + self.tests = tests + self.expected_failure = tests[-1] + self.is_debug = is_debug + self.webkit_finder = WebKitFinder(FileSystem()) + + def bisect(self): + if self.test_fails_in_isolation(): + self.buckets = [Bucket([self.expected_failure])] + print '%s fails when run in isolation.' % self.expected_failure + self.print_result() + return 0 + if not self.test_fails(self.tests): + _log.error('%s does not fail' % self.expected_failure) + return 1 + # Split the list of test into buckets. Each bucket has at least one test required to cause + # the expected failure at the end. Split buckets in half until there are only buckets left + # with one item in them. + self.buckets = [Bucket(self.tests[:-1]), Bucket([self.expected_failure])] + while not self.is_done(): + self.print_progress() + self.split_largest_bucket() + self.print_result() + self.verify_non_flaky() + return 0 + + def test_fails_in_isolation(self): + return self.test_bucket_list_fails([Bucket([self.expected_failure])]) + + def verify_non_flaky(self): + print 'Verifying the failure is not flaky by running 10 times.' + count_failures = 0 + for i in range(0, 10): + if self.test_bucket_list_fails(self.buckets): + count_failures += 1 + print 'Failed %d/10 times' % count_failures + + def print_progress(self): + count = 0 + for bucket in self.buckets: + count += len(bucket.tests) + print '%d tests left, %d buckets' % (count, len(self.buckets)) + + def print_result(self): + tests = [] + for bucket in self.buckets: + tests += bucket.tests + extra_args = ' --debug' if self.is_debug else '' + print 'run-webkit-tests%s --child-processes=1 --order=none %s' % (extra_args, " ".join(tests)) + + def is_done(self): + for bucket in self.buckets: + if bucket.size() > 1: + return False + return True + + def split_largest_bucket(self): + index = 0 + largest_index = 0 + largest_size = 0 + for bucket in self.buckets: + if bucket.size() > largest_size: + largest_index = index + largest_size = bucket.size() + index += 1 + + bucket_to_split = self.buckets[largest_index] + halfway_point = int(largest_size / 2) + first_half = Bucket(bucket_to_split.tests[:halfway_point]) + second_half = Bucket(bucket_to_split.tests[halfway_point:]) + + buckets_before = self.buckets[:largest_index] + buckets_after = self.buckets[largest_index + 1:] + + # Do the second half first because it tends to be faster because the http tests are front-loaded and slow. + new_buckets = buckets_before + [second_half] + buckets_after + if self.test_bucket_list_fails(new_buckets): + self.buckets = new_buckets + return + + new_buckets = buckets_before + [first_half] + buckets_after + if self.test_bucket_list_fails(new_buckets): + self.buckets = new_buckets + return + + self.buckets = buckets_before + [first_half, second_half] + buckets_after + + def test_bucket_list_fails(self, buckets): + tests = [] + for bucket in buckets: + tests += bucket.tests + return self.test_fails(tests) + + def test_fails(self, tests): + extra_args = ['--debug'] if self.is_debug else [] + path_to_run_webkit_tests = self.webkit_finder.path_from_webkit_base('Tools', 'Scripts', 'run-webkit-tests') + output = self.executive.popen([path_to_run_webkit_tests, '--child-processes', '1', '--order', 'none', '--no-retry', '--no-show-results', '--verbose'] + extra_args + tests, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + failure_string = self.expected_failure + ' failed' + if failure_string in output.stderr.read(): + return True + return False + + +def main(argv): + logging.basicConfig() + + option_parser = optparse.OptionParser() + option_parser.add_option('--test-list', action='store', help='file that list tests to bisect. The last test in the list is the expected failure.', metavar='FILE'), + option_parser.add_option('--debug', action='store_true', default=False, help='whether to use a debug build'), + options, args = option_parser.parse_args(argv) + + tests = open(options.test_list).read().strip().split('\n') + bisector = Bisector(tests, is_debug=options.debug) + return bisector.bisect() + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/__init__.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/__init__.py new file mode 100644 index 00000000000..ef65bee5bb7 --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/__init__.py @@ -0,0 +1 @@ +# Required for Python to search this directory for module files diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader.py new file mode 100644 index 00000000000..0728d8abb06 --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader.py @@ -0,0 +1,97 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import logging + + +_log = logging.getLogger(__name__) + + +class DumpReader(object): + """Base class for breakpad dump readers.""" + + def __init__(self, host, build_dir): + self._host = host + self._build_dir = build_dir + + def check_is_functional(self): + """This routine must be implemented by subclasses. + + Returns True if this reader is functional.""" + raise NotImplementedError() + + def crash_dumps_directory(self): + return self._host.filesystem.join(self._build_dir, 'crash-dumps') + + def clobber_old_results(self): + if self._host.filesystem.isdir(self.crash_dumps_directory()): + self._host.filesystem.rmtree(self.crash_dumps_directory()) + + def look_for_new_crash_logs(self, crashed_processes, start_time): + if not crashed_processes: + return None + + if not self.check_is_functional(): + return None + + pid_to_minidump = dict() + for root, dirs, files in self._host.filesystem.walk(self.crash_dumps_directory()): + for dmp in [f for f in files if f.endswith(self._file_extension())]: + dmp_file = self._host.filesystem.join(root, dmp) + if self._host.filesystem.mtime(dmp_file) < start_time: + continue + pid = self._get_pid_from_dump(dmp_file) + if pid: + pid_to_minidump[pid] = dmp_file + + result = dict() + for test, process_name, pid in crashed_processes: + if str(pid) in pid_to_minidump: + stack = self._get_stack_from_dump(pid_to_minidump[str(pid)]) + if stack: + result[test] = stack + + return result + + def _get_pid_from_dump(self, dump_file): + """This routine must be implemented by subclasses. + + This routine returns the PID of the crashed process that produced the given dump_file.""" + raise NotImplementedError() + + def _get_stack_from_dump(self, dump_file): + """This routine must be implemented by subclasses. + + Returns the stack stored in the given breakpad dump_file.""" + raise NotImplementedError() + + def _file_extension(self): + """This routine must be implemented by subclasses. + + Returns the file extension of crash dumps written by breakpad.""" + raise NotImplementedError() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_multipart.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_multipart.py new file mode 100644 index 00000000000..e0148df4947 --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_multipart.py @@ -0,0 +1,172 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import cgi +import logging + +from webkitpy.common.webkit_finder import WebKitFinder +from webkitpy.layout_tests.breakpad.dump_reader import DumpReader + + +_log = logging.getLogger(__name__) + + +class DumpReaderMultipart(DumpReader): + """Base class for Linux and Android breakpad dump reader.""" + + def __init__(self, host, build_dir): + super(DumpReaderMultipart, self).__init__(host, build_dir) + self._webkit_finder = WebKitFinder(host.filesystem) + self._breakpad_tools_available = None + + def check_is_functional(self): + return self._check_breakpad_tools_available() + + def _get_pid_from_dump(self, dump_file): + dump = self._read_dump(dump_file) + if not dump: + return None + if 'pid' in dump: + return dump['pid'][0] + return None + + def _get_stack_from_dump(self, dump_file): + dump = self._read_dump(dump_file) + if not dump: + return None + if not 'upload_file_minidump' in dump: + return None + + self._generate_breakpad_symbols_if_necessary() + f, temp_name = self._host.filesystem.open_binary_tempfile('dmp') + f.write("\r\n".join(dump['upload_file_minidump'])) + f.close() + + cmd = [self._path_to_minidump_stackwalk(), temp_name, self._symbols_dir()] + try: + stack = self._host.executive.run_command(cmd, return_stderr=False) + except: + _log.warning('Failed to execute "%s"' % ' '.join(cmd)) + stack = None + finally: + self._host.filesystem.remove(temp_name) + return stack + + def _read_dump(self, dump_file): + with self._host.filesystem.open_binary_file_for_reading(dump_file) as f: + boundary = f.readline().strip()[2:] + f.seek(0) + data = cgi.parse_multipart(f, {'boundary': boundary}) + return data + return None + + def _check_breakpad_tools_available(self): + if self._breakpad_tools_available != None: + return self._breakpad_tools_available + + REQUIRED_BREAKPAD_TOOLS = [ + 'dump_syms', + 'minidump_stackwalk', + ] + result = True + for binary in REQUIRED_BREAKPAD_TOOLS: + full_path = self._host.filesystem.join(self._build_dir, binary) + if not self._host.filesystem.exists(full_path): + result = False + _log.error('Unable to find %s' % binary) + _log.error(' at %s' % full_path) + + if not result: + _log.error(" Could not find breakpad tools, unexpected crashes won't be symbolized") + _log.error(' Did you build the target blink_tests?') + _log.error('') + + self._breakpad_tools_available = result + return self._breakpad_tools_available + + def _path_to_minidump_stackwalk(self): + return self._host.filesystem.join(self._build_dir, "minidump_stackwalk") + + def _path_to_generate_breakpad_symbols(self): + return self._webkit_finder.path_from_chromium_base("components", "breakpad", "tools", "generate_breakpad_symbols.py") + + def _symbols_dir(self): + return self._host.filesystem.join(self._build_dir, 'content_shell.syms') + + def _generate_breakpad_symbols_if_necessary(self): + if self._host.filesystem.exists(self._symbols_dir()): + needs_update = False + symbols_mtime = self._host.filesystem.mtime(self._symbols_dir()) + for binary in self._binaries_to_symbolize(): + full_path = self._host.filesystem.join(self._build_dir, binary) + if self._host.filesystem.mtime(full_path) >= symbols_mtime: + needs_update = True + if not needs_update: + return + + _log.debug("Regenerating breakpad symbols") + self._host.filesystem.rmtree(self._symbols_dir()) + + for binary in self._binaries_to_symbolize(): + full_path = self._host.filesystem.join(self._build_dir, binary) + cmd = [ + self._path_to_generate_breakpad_symbols(), + '--binary=%s' % full_path, + '--symbols-dir=%s' % self._symbols_dir(), + '--build-dir=%s' % self._build_dir, + ] + try: + self._host.executive.run_command(cmd) + except: + _log.error('Failed to execute "%s"' % ' '.join(cmd)) + + def _binaries_to_symbolize(self): + """This routine must be implemented by subclasses. + + Returns an array of binaries that need to be symbolized.""" + raise NotImplementedError() + + +class DumpReaderLinux(DumpReaderMultipart): + """Linux breakpad dump reader.""" + + def _binaries_to_symbolize(self): + return ['content_shell', 'libTestNetscapePlugIn.so', 'libffmpegsumo.so', 'libosmesa.so'] + + def _file_extension(self): + return 'dmp' + + +class DumpReaderAndroid(DumpReaderMultipart): + """Android breakpad dump reader.""" + + def _binaries_to_symbolize(self): + return ['lib/libcontent_shell_content_view.so'] + + def _file_extension(self): + return 'dmp' diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_multipart_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_multipart_unittest.py new file mode 100644 index 00000000000..63fa0dece8f --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_multipart_unittest.py @@ -0,0 +1,109 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import webkitpy.thirdparty.unittest2 as unittest + +import cgi + +from webkitpy.common.host_mock import MockHost +from webkitpy.common.system.executive_mock import MockExecutive +from webkitpy.layout_tests.breakpad.dump_reader_multipart import DumpReaderMultipart + + +class TestDumpReaderMultipart(unittest.TestCase): + _MULTIPART_DUMP = [ + '--boundary', + 'Content-Disposition: form-data; name="prod"', + '', + 'content_shell', + '--boundary', + 'Content-Disposition: form-data; name="pid"', + '', + '4711', + '--boundary', + 'Content-Disposition: form-data; name="upload_file_minidump"; filename="dump"', + 'Content-Type: application/octet-stream', + '', + 'MDMP', + '--boundary--', + ] + + def test_check_is_functional_breakpad_tools_not_found(self): + host = MockHost() + + build_dir = "/mock-checkout/out/Debug" + host.filesystem.maybe_make_directory(build_dir) + dump_reader = DumpReaderMultipart(host, build_dir) + dump_reader._file_extension = lambda: 'dmp' + dump_reader._binaries_to_symbolize = lambda: ['content_shell'] + + self.assertFalse(dump_reader.check_is_functional()) + + def test_get_pid_from_dump(self): + host = MockHost() + + dump_file = '/crash-dumps/dump.dmp' + expected_pid = '4711' + host.filesystem.write_text_file(dump_file, "\r\n".join(TestDumpReaderMultipart._MULTIPART_DUMP)) + build_dir = "/mock-checkout/out/Debug" + host.filesystem.maybe_make_directory(build_dir) + host.filesystem.exists = lambda x: True + + # The mock file object returned by open_binary_file_for_reading doesn't + # have readline(), however, the real File object does. + host.filesystem.open_binary_file_for_reading = host.filesystem.open_text_file_for_reading + dump_reader = DumpReaderMultipart(host, build_dir) + dump_reader._file_extension = lambda: 'dmp' + dump_reader._binaries_to_symbolize = lambda: ['content_shell'] + + self.assertTrue(dump_reader.check_is_functional()) + self.assertEqual(expected_pid, dump_reader._get_pid_from_dump(dump_file)) + + def test_get_stack_from_dump(self): + host = MockHost() + + dump_file = '/crash-dumps/dump.dmp' + host.filesystem.write_text_file(dump_file, "\r\n".join(TestDumpReaderMultipart._MULTIPART_DUMP)) + build_dir = "/mock-checkout/out/Debug" + host.filesystem.maybe_make_directory(build_dir) + host.filesystem.exists = lambda x: True + + # The mock file object returned by open_binary_file_for_reading doesn't + # have readline(), however, the real File object does. + host.filesystem.open_binary_file_for_reading = host.filesystem.open_text_file_for_reading + dump_reader = DumpReaderMultipart(host, build_dir) + dump_reader._file_extension = lambda: 'dmp' + dump_reader._binaries_to_symbolize = lambda: ['content_shell'] + + self.assertTrue(dump_reader.check_is_functional()) + self.assertEqual("MOCK output of child process", dump_reader._get_stack_from_dump(dump_file)) + self.assertEqual(2, len(host.executive.calls)) + cmd_line = " ".join(host.executive.calls[0]) + self.assertIn('generate_breakpad_symbols.py', cmd_line) + cmd_line = " ".join(host.executive.calls[1]) + self.assertIn('minidump_stackwalk', cmd_line) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_win.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_win.py new file mode 100644 index 00000000000..6d193f34d39 --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_win.py @@ -0,0 +1,119 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import logging +import os +import shlex + +from webkitpy.layout_tests.breakpad.dump_reader import DumpReader + + +_log = logging.getLogger(__name__) + + +class DumpReaderWin(DumpReader): + """DumpReader for windows breakpad.""" + + def __init__(self, host, build_dir): + super(DumpReaderWin, self).__init__(host, build_dir) + self._cdb_available = None + + def check_is_functional(self): + return self._check_cdb_available() + + def _file_extension(self): + return 'txt' + + def _get_pid_from_dump(self, dump_file): + with self._host.filesystem.open_text_file_for_reading(dump_file) as f: + crash_keys = dict([l.split(':', 1) for l in f.read().splitlines()]) + if 'pid' in crash_keys: + return crash_keys['pid'] + return None + + def _get_stack_from_dump(self, dump_file): + minidump = dump_file[:-3] + 'dmp' + cmd = [self._cdb_path, '-y', self._build_dir, '-c', '.ecxr;k30;q', '-z', minidump] + try: + stack = self._host.executive.run_command(cmd) + except: + _log.warning('Failed to execute "%s"' % ' '.join(cmd)) + else: + return stack + return None + + def _check_cdb_available(self): + """Checks whether we can use cdb to symbolize minidumps.""" + if self._cdb_available != None: + return self._cdb_available + + CDB_LOCATION_TEMPLATES = [ + '%s\\Debugging Tools For Windows', + '%s\\Debugging Tools For Windows (x86)', + '%s\\Debugging Tools For Windows (x64)', + '%s\\Windows Kits\\8.0\\Debuggers\\x86', + '%s\\Windows Kits\\8.0\\Debuggers\\x64', + ] + + program_files_directories = ['C:\\Program Files'] + program_files = os.environ.get('ProgramFiles') + if program_files: + program_files_directories.append(program_files) + program_files = os.environ.get('ProgramFiles(x86)') + if program_files: + program_files_directories.append(program_files) + + possible_cdb_locations = [] + for template in CDB_LOCATION_TEMPLATES: + for program_files in program_files_directories: + possible_cdb_locations.append(template % program_files) + + gyp_defines = os.environ.get('GYP_DEFINES', []) + if gyp_defines: + gyp_defines = shlex.split(gyp_defines) + if 'windows_sdk_path' in gyp_defines: + possible_cdb_locations.append([ + '%s\\Debuggers\\x86' % gyp_defines['windows_sdk_path'], + '%s\\Debuggers\\x64' % gyp_defines['windows_sdk_path'], + ]) + + for cdb_path in possible_cdb_locations: + cdb = self._host.filesystem.join(cdb_path, 'cdb.exe') + try: + _ = self._host.executive.run_command([cdb, '-version']) + except: + pass + else: + self._cdb_path = cdb + self._cdb_available = True + return self._cdb_available + + _log.warning("CDB is not installed; can't symbolize minidumps.") + _log.warning('') + self._cdb_available = False + return self._cdb_available diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_win_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_win_unittest.py new file mode 100644 index 00000000000..b36d7bf193e --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/breakpad/dump_reader_win_unittest.py @@ -0,0 +1,77 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import webkitpy.thirdparty.unittest2 as unittest + +from webkitpy.common.host_mock import MockHost +from webkitpy.common.system.executive_mock import MockExecutive +from webkitpy.layout_tests.breakpad.dump_reader_win import DumpReaderWin + + +class TestDumpReaderWin(unittest.TestCase): + def test_check_is_functional_cdb_not_found(self): + host = MockHost() + host.executive = MockExecutive(should_throw=True) + + build_dir = "/mock-checkout/out/Debug" + host.filesystem.maybe_make_directory(build_dir) + dump_reader = DumpReaderWin(host, build_dir) + + self.assertFalse(dump_reader.check_is_functional()) + + def test_get_pid_from_dump(self): + host = MockHost() + + dump_file = '/crash-dumps/dump.txt' + expected_pid = '4711' + host.filesystem.write_text_file(dump_file, 'channel:\npid:%s\nplat:Win32\nprod:content_shell\n' % expected_pid) + build_dir = "/mock-checkout/out/Debug" + host.filesystem.maybe_make_directory(build_dir) + dump_reader = DumpReaderWin(host, build_dir) + + self.assertTrue(dump_reader.check_is_functional()) + self.assertEqual(expected_pid, dump_reader._get_pid_from_dump(dump_file)) + + def test_get_stack_from_dump(self): + host = MockHost() + + dump_file = '/crash-dumps/dump.dmp' + real_dump_file = '/crash-dumps/dump.dmp' + host.filesystem.write_text_file(dump_file, 'product:content_shell\n') + host.filesystem.write_binary_file(real_dump_file, 'MDMP') + build_dir = "/mock-checkout/out/Debug" + host.filesystem.maybe_make_directory(build_dir) + dump_reader = DumpReaderWin(host, build_dir) + + self.assertTrue(dump_reader.check_is_functional()) + host.executive.calls = [] + self.assertEqual("MOCK output of child process", dump_reader._get_stack_from_dump(dump_file)) + self.assertEqual(1, len(host.executive.calls)) + cmd_line = " ".join(host.executive.calls[0]) + self.assertIn('cdb.exe', cmd_line) + self.assertIn(real_dump_file, cmd_line) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner.py index d4a269a2846..de73218df88 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner.py @@ -80,6 +80,7 @@ class LayoutTestRunner(object): self._expectations = expectations self._test_inputs = test_inputs self._retrying = retrying + self._shards_to_redo = [] # FIXME: rename all variables to test_run_results or some such ... run_results = TestRunResults(self._expectations, len(test_inputs) + len(tests_to_skip)) @@ -112,8 +113,14 @@ class LayoutTestRunner(object): start_time = time.time() try: - with message_pool.get(self, self._worker_factory, num_workers, self._port.worker_startup_delay_secs(), self._port.host) as pool: + with message_pool.get(self, self._worker_factory, num_workers, self._port.host) as pool: pool.run(('test_list', shard.name, shard.test_inputs) for shard in all_shards) + + if self._shards_to_redo: + num_workers -= len(self._shards_to_redo) + if num_workers > 0: + with message_pool.get(self, self._worker_factory, num_workers, self._port.host) as pool: + pool.run(('test_list', shard.name, shard.test_inputs) for shard in self._shards_to_redo) except TestRunInterruptedException, e: _log.warning(e.reason) run_results.interrupted = True @@ -171,10 +178,12 @@ class LayoutTestRunner(object): exp_str = self._expectations.get_expectations_string(result.test_name) got_str = self._expectations.expectation_to_string(result.type) - run_results.add(result, expected, self._test_is_slow(result.test_name)) + if result.device_failed: + self._printer.print_finished_test(result, False, exp_str, "Aborted") + return + run_results.add(result, expected, self._test_is_slow(result.test_name)) self._printer.print_finished_test(result, expected, exp_str, got_str) - self._interrupt_if_at_failure_limits(run_results) def handle(self, name, source, *args): @@ -192,6 +201,10 @@ class LayoutTestRunner(object): def _handle_finished_test(self, worker_name, result, log_messages=[]): self._update_summary_with_result(self._current_run_results, result) + def _handle_device_failed(self, worker_name, list_name, remaining_tests): + _log.warning("%s has failed" % worker_name) + if remaining_tests: + self._shards_to_redo.append(TestShard(list_name, remaining_tests)) class Worker(object): def __init__(self, caller, results_directory, options): @@ -226,8 +239,13 @@ class Worker(object): def handle(self, name, source, test_list_name, test_inputs): assert name == 'test_list' - for test_input in test_inputs: - self._run_test(test_input, test_list_name) + for i, test_input in enumerate(test_inputs): + device_failed = self._run_test(test_input, test_list_name) + if device_failed: + self._caller.post('device_failed', test_list_name, test_inputs[i:]) + self._caller.stop_running() + return + self._caller.post('finished_test_list', test_list_name) def _update_test_input(self, test_input): @@ -250,12 +268,20 @@ class Worker(object): self._update_test_input(test_input) test_timeout_sec = self._timeout(test_input) start = time.time() - self._caller.post('started_test', test_input, test_timeout_sec) + device_failed = False if self._driver and self._driver.has_crashed(): self._kill_driver() if not self._driver: self._driver = self._port.create_driver(self._worker_number) + + if not self._driver: + # FIXME: Is this the best way to handle a device crashing in the middle of the test, or should we create + # a new failure type? + device_failed = True + return device_failed + + self._caller.post('started_test', test_input, test_timeout_sec) result = single_test_runner.run_single_test(self._port, self._options, self._results_directory, self._name, self._driver, test_input, stop_when_done) @@ -264,10 +290,9 @@ class Worker(object): result.total_run_time = time.time() - start result.test_number = self._num_tests self._num_tests += 1 - self._caller.post('finished_test', result) - self._clean_up_after_test(test_input, result) + return result.device_failed def stop(self): _log.debug("%s cleaning up" % self._name) @@ -352,7 +377,7 @@ class Sharder(object): return self._shard_in_two(test_inputs) elif fully_parallel: return self._shard_every_file(test_inputs) - return self._shard_by_directory(test_inputs, num_workers) + return self._shard_by_directory(test_inputs) def _shard_in_two(self, test_inputs): """Returns two lists of shards, one with all the tests requiring a lock and one with the rest. @@ -381,18 +406,30 @@ class Sharder(object): This mode gets maximal parallelism at the cost of much higher flakiness.""" locked_shards = [] unlocked_shards = [] + virtual_inputs = [] + for test_input in test_inputs: # Note that we use a '.' for the shard name; the name doesn't really # matter, and the only other meaningful value would be the filename, # which would be really redundant. if test_input.requires_lock: locked_shards.append(TestShard('.', [test_input])) + elif test_input.test_name.startswith('virtual'): + # This violates the spirit of sharding every file, but in practice, since the + # virtual test suites require a different commandline flag and thus a restart + # of content_shell, it's too slow to shard them fully. + virtual_inputs.append(test_input) else: unlocked_shards.append(TestShard('.', [test_input])) - return locked_shards, unlocked_shards + locked_virtual_shards, unlocked_virtual_shards = self._shard_by_directory(virtual_inputs) + + # The locked shards still need to be limited to self._max_locked_shards in order to not + # overload the http server for the http tests. + return (self._resize_shards(locked_virtual_shards + locked_shards, self._max_locked_shards, 'locked_shard'), + unlocked_virtual_shards + unlocked_shards) - def _shard_by_directory(self, test_inputs, num_workers): + def _shard_by_directory(self, test_inputs): """Returns two lists of shards, each shard containing all the files in a directory. This is the default mode, and gets as much parallelism as we can while diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py index c43716a3563..b3156bbc445 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/layout_test_runner_unittest.py @@ -169,7 +169,8 @@ class SharderTests(unittest.TestCase): "ietestcenter/Javascript/11.1.5_4-4-c-1.html", "dom/html/level2/html/HTMLAnchorElement06.html", "perf/object-keys.html", - "virtual/threaded/test.html", + "virtual/threaded/dir/test.html", + "virtual/threaded/fast/foo/test.html", ] def get_test_input(self, test_file): @@ -203,7 +204,8 @@ class SharderTests(unittest.TestCase): 'http/tests/xmlhttprequest/supported-xml-content-types.html', 'perf/object-keys.html'])]) self.assert_shards(unlocked, - [('virtual/threaded', ['virtual/threaded/test.html']), + [('virtual/threaded/dir', ['virtual/threaded/dir/test.html']), + ('virtual/threaded/fast/foo', ['virtual/threaded/fast/foo/test.html']), ('animations', ['animations/keyframes.html']), ('dom/html/level2/html', ['dom/html/level2/html/HTMLAnchorElement03.html', 'dom/html/level2/html/HTMLAnchorElement06.html']), @@ -211,20 +213,23 @@ class SharderTests(unittest.TestCase): ('ietestcenter/Javascript', ['ietestcenter/Javascript/11.1.5_4-4-c-1.html'])]) def test_shard_every_file(self): - locked, unlocked = self.get_shards(num_workers=2, fully_parallel=True) + locked, unlocked = self.get_shards(num_workers=2, fully_parallel=True, max_locked_shards=2) self.assert_shards(locked, - [('.', ['http/tests/websocket/tests/unicode.htm']), - ('.', ['http/tests/security/view-source-no-refresh.html']), - ('.', ['http/tests/websocket/tests/websocket-protocol-ignored.html']), - ('.', ['http/tests/xmlhttprequest/supported-xml-content-types.html']), - ('.', ['perf/object-keys.html'])]), + [('locked_shard_1', + ['http/tests/websocket/tests/unicode.htm', + 'http/tests/security/view-source-no-refresh.html', + 'http/tests/websocket/tests/websocket-protocol-ignored.html']), + ('locked_shard_2', + ['http/tests/xmlhttprequest/supported-xml-content-types.html', + 'perf/object-keys.html'])]), self.assert_shards(unlocked, - [('.', ['animations/keyframes.html']), + [('virtual/threaded/dir', ['virtual/threaded/dir/test.html']), + ('virtual/threaded/fast/foo', ['virtual/threaded/fast/foo/test.html']), + ('.', ['animations/keyframes.html']), ('.', ['fast/css/display-none-inline-style-change-crash.html']), ('.', ['dom/html/level2/html/HTMLAnchorElement03.html']), ('.', ['ietestcenter/Javascript/11.1.5_4-4-c-1.html']), - ('.', ['dom/html/level2/html/HTMLAnchorElement06.html']), - ('.', ['virtual/threaded/test.html'])]) + ('.', ['dom/html/level2/html/HTMLAnchorElement06.html'])]) def test_shard_in_two(self): locked, unlocked = self.get_shards(num_workers=1, fully_parallel=False) @@ -242,7 +247,8 @@ class SharderTests(unittest.TestCase): 'dom/html/level2/html/HTMLAnchorElement03.html', 'ietestcenter/Javascript/11.1.5_4-4-c-1.html', 'dom/html/level2/html/HTMLAnchorElement06.html', - 'virtual/threaded/test.html'])]) + 'virtual/threaded/dir/test.html', + 'virtual/threaded/fast/foo/test.html'])]) def test_shard_in_two_has_no_locked_shards(self): locked, unlocked = self.get_shards(num_workers=1, fully_parallel=False, diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py index 4cd1f5b4fd7..7254c099cbe 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py @@ -50,7 +50,6 @@ from webkitpy.layout_tests.models import test_expectations from webkitpy.layout_tests.models import test_failures from webkitpy.layout_tests.models import test_run_results from webkitpy.layout_tests.models.test_input import TestInput -from webkitpy.layout_tests.models.test_run_results import INTERRUPTED_EXIT_STATUS _log = logging.getLogger(__name__) @@ -151,9 +150,11 @@ class Manager(object): def _set_up_run(self, test_names): self._printer.write_update("Checking build ...") - if self._options.build and not self._port.check_build(self.needs_servers(test_names), self._printer): - _log.error("Build check failed") - return False + if self._options.build: + exit_code = self._port.check_build(self.needs_servers(test_names), self._printer) + if exit_code: + _log.error("Build check failed") + return exit_code # This must be started before we check the system dependencies, # since the helper may do things to make the setup correct. @@ -164,9 +165,10 @@ class Manager(object): # Check that the system dependencies (themes, fonts, ...) are correct. if not self._options.nocheck_sys_deps: self._printer.write_update("Checking system dependencies ...") - if not self._port.check_sys_deps(self.needs_servers(test_names)): + exit_code = self._port.check_sys_deps(self.needs_servers(test_names)) + if exit_code: self._port.stop_helper() - return False + return exit_code if self._options.clobber_old_results: self._clobber_old_results() @@ -175,7 +177,7 @@ class Manager(object): self._port.host.filesystem.maybe_make_directory(self._results_directory) self._port.setup_test_run() - return True + return test_run_results.OK_EXIT_STATUS def run(self, args): """Run the tests and return a RunDetails object with the results.""" @@ -185,7 +187,7 @@ class Manager(object): paths, test_names = self._collect_tests(args) except IOError: # This is raised if --test-list doesn't exist - return test_run_results.RunDetails(exit_code=-1) + return test_run_results.RunDetails(exit_code=test_run_results.NO_TESTS_EXIT_STATUS) self._printer.write_update("Parsing expectations ...") self._expectations = test_expectations.TestExpectations(self._port, test_names) @@ -196,10 +198,11 @@ class Manager(object): # Check to make sure we're not skipping every test. if not tests_to_run: _log.critical('No tests to run.') - return test_run_results.RunDetails(exit_code=-1) + return test_run_results.RunDetails(exit_code=test_run_results.NO_TESTS_EXIT_STATUS) - if not self._set_up_run(tests_to_run): - return test_run_results.RunDetails(exit_code=-1) + exit_code = self._set_up_run(tests_to_run) + if exit_code: + return test_run_results.RunDetails(exit_code=exit_code) # Don't retry failures if an explicit list of tests was passed in. if self._options.retry_failures is None: @@ -212,7 +215,7 @@ class Manager(object): self._start_servers(tests_to_run) initial_results = self._run_tests(tests_to_run, tests_to_skip, self._options.repeat_each, self._options.iterations, - int(self._options.child_processes), retrying=False) + self._port.num_workers(int(self._options.child_processes)), retrying=False) # Don't retry failures when interrupted by user or failures limit exception. should_retry_failures = should_retry_failures and not (initial_results.interrupted or initial_results.keyboard_interrupted) @@ -237,7 +240,7 @@ class Manager(object): # Some crash logs can take a long time to be written out so look # for new logs after the test run finishes. - _log.debug("looking for new crash logs") + self._printer.write_update("looking for new crash logs") self._look_for_new_crash_logs(initial_results, start_time) if retry_results: self._look_for_new_crash_logs(retry_results, start_time) @@ -254,7 +257,7 @@ class Manager(object): results_path = self._filesystem.join(self._results_directory, "results.html") self._copy_results_html_file(results_path) if initial_results.keyboard_interrupted: - exit_code = INTERRUPTED_EXIT_STATUS + exit_code = test_run_results.INTERRUPTED_EXIT_STATUS else: if self._options.show_results and (exit_code or (self._options.full_results_html and initial_results.total_failures)): self._port.show_results_html_file(results_path) @@ -353,6 +356,9 @@ class Manager(object): if self._filesystem.isdir(self._filesystem.join(layout_tests_dir, dirname)): self._filesystem.rmtree(self._filesystem.join(self._results_directory, dirname)) + # Port specific clean-up. + self._port.clobber_old_port_specific_results() + def _tests_to_retry(self, run_results): return [result.test_name for result in run_results.unexpected_results_by_name.values() if result.type != test_expectations.PASS] diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py index 115428ecf9b..0c029234c68 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/controllers/single_test_runner.py @@ -32,7 +32,7 @@ import re import time from webkitpy.layout_tests.controllers import test_result_writer -from webkitpy.layout_tests.port.driver import DriverInput, DriverOutput +from webkitpy.layout_tests.port.driver import DeviceFailure, DriverInput, DriverOutput from webkitpy.layout_tests.models import test_expectations from webkitpy.layout_tests.models import test_failures from webkitpy.layout_tests.models.test_results import TestResult @@ -43,7 +43,11 @@ _log = logging.getLogger(__name__) def run_single_test(port, options, results_directory, worker_name, driver, test_input, stop_when_done): runner = SingleTestRunner(port, options, results_directory, worker_name, driver, test_input, stop_when_done) - return runner.run() + try: + return runner.run() + except DeviceFailure as e: + _log.error("device failed: %s", str(e)) + return TestResult(test_input.test_name, device_failed=True) class SingleTestRunner(object): @@ -91,7 +95,20 @@ class SingleTestRunner(object): image_hash = None if self._should_fetch_expected_checksum(): image_hash = self._port.expected_checksum(self._test_name) - return DriverInput(self._test_name, self._timeout, image_hash, self._should_run_pixel_test) + + test_base = self._port.lookup_virtual_test_base(self._test_name) + if test_base: + # If the file actually exists under the virtual dir, we want to use it (largely for virtual references), + # but we want to use the extra command line args either way. + if self._filesystem.exists(self._port.abspath_for_test(self._test_name)): + test_name = self._test_name + else: + test_name = test_base + args = self._port.lookup_virtual_test_args(self._test_name) + else: + test_name = self._test_name + args = [] + return DriverInput(test_name, self._timeout, image_hash, self._should_run_pixel_test, args) def run(self): if self._reference_files: @@ -122,7 +139,8 @@ class SingleTestRunner(object): # FIXME: It the test crashed or timed out, it might be better to avoid # to write new baselines. self._overwrite_baselines(driver_output) - return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(), pid=driver_output.pid) + return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(), + pid=driver_output.pid) _render_tree_dump_pattern = re.compile(r"^layer at \(\d+,\d+\) size \d+x\d+\n") @@ -215,13 +233,15 @@ class SingleTestRunner(object): if driver_output.crash: # Don't continue any more if we already have a crash. # In case of timeouts, we continue since we still want to see the text and image output. - return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(), pid=driver_output.pid) + return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(), + pid=driver_output.pid) failures.extend(self._compare_text(expected_driver_output.text, driver_output.text)) failures.extend(self._compare_audio(expected_driver_output.audio, driver_output.audio)) if self._should_run_pixel_test: failures.extend(self._compare_image(expected_driver_output, driver_output)) - return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(), pid=driver_output.pid) + return TestResult(self._test_name, failures, driver_output.test_time, driver_output.has_stderr(), + pid=driver_output.pid) def _compare_text(self, expected_text, actual_text): failures = [] @@ -300,7 +320,8 @@ class SingleTestRunner(object): for expectation, reference_filename in putAllMismatchBeforeMatch(self._reference_files): reference_test_name = self._port.relative_test_filename(reference_filename) reference_test_names.append(reference_test_name) - reference_output = self._driver.run_test(DriverInput(reference_test_name, self._timeout, None, should_run_pixel_test=True), self._stop_when_done) + driver_input = DriverInput(reference_test_name, self._timeout, image_hash=None, should_run_pixel_test=True, args=self._port.lookup_virtual_test_args(reference_test_name)) + reference_output = self._driver.run_test(driver_input, self._stop_when_done) test_result = self._compare_output_with_reference(reference_output, test_output, reference_filename, expectation == '!=') if (expectation == '!=' and test_result.failures) or (expectation == '==' and not test_result.failures): @@ -313,7 +334,9 @@ class SingleTestRunner(object): # FIXME: We don't really deal with a mix of reftest types properly. We pass in a set() to reftest_type # and only really handle the first of the references in the result. reftest_type = list(set([reference_file[0] for reference_file in self._reference_files])) - return TestResult(self._test_name, test_result.failures, total_test_time + test_result.test_run_time, test_result.has_stderr, reftest_type=reftest_type, pid=test_result.pid, references=reference_test_names) + return TestResult(self._test_name, test_result.failures, total_test_time + test_result.test_run_time, + test_result.has_stderr, reftest_type=reftest_type, pid=test_result.pid, + references=reference_test_names) def _compare_output_with_reference(self, reference_driver_output, actual_driver_output, reference_filename, mismatch): total_test_time = reference_driver_output.test_time + actual_driver_output.test_time diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/layout_tests_mover_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/layout_tests_mover_unittest.py index 733529c9053..2ad3806a8d1 100755 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/layout_tests_mover_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/layout_tests_mover_unittest.py @@ -33,15 +33,15 @@ from webkitpy.common.host_mock import MockHost from webkitpy.common.checkout.scm.scm_mock import MockSCM from webkitpy.common.system.filesystem_mock import MockFileSystem from webkitpy.layout_tests.layout_tests_mover import LayoutTestsMover -from webkitpy.layout_tests.port.chromium import ChromiumPort +from webkitpy.layout_tests.port import base -class MockPort(ChromiumPort): +class MockPort(base.Port): def __init__(self, **kwargs): # This sets up a mock FileSystem and SCM using that FileSystem. host = MockHost() - ChromiumPort.__init__(self, host, host.port_factory.all_port_names()[0], **kwargs) + super(MockPort, self).__init__(host, host.port_factory.all_port_names()[0], **kwargs) host.filesystem.maybe_make_directory(self._absolute_path('platform')) host.filesystem.maybe_make_directory(self._absolute_path('existing_directory')) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py index 310d901a74d..eeb4a949809 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py @@ -203,7 +203,7 @@ class TestExpectationParser(object): # FIXME: Update the original specifiers and remove this once the old syntax is gone. _configuration_tokens_list = [ - 'Mac', 'SnowLeopard', 'Lion', 'Retina', 'MountainLion', + 'Mac', 'SnowLeopard', 'Lion', 'Retina', 'MountainLion', 'Mavericks', 'Win', 'XP', 'Win7', 'Linux', 'Android', diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_results.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_results.py index d6fd10b1818..984f30a37e0 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_results.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_results.py @@ -38,7 +38,7 @@ class TestResult(object): def loads(string): return cPickle.loads(string) - def __init__(self, test_name, failures=None, test_run_time=None, has_stderr=False, reftest_type=None, pid=None, references=None): + def __init__(self, test_name, failures=None, test_run_time=None, has_stderr=False, reftest_type=None, pid=None, references=None, device_failed=False): self.test_name = test_name self.failures = failures or [] self.test_run_time = test_run_time or 0 # The time taken to execute the test itself. @@ -46,6 +46,7 @@ class TestResult(object): self.reftest_type = reftest_type or [] self.pid = pid self.references = references or [] + self.device_failed = device_failed # FIXME: Setting this in the constructor makes this class hard to mutate. self.type = test_failures.determine_result_type(failures) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py index 6e905d99020..95146472146 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py @@ -37,8 +37,34 @@ from webkitpy.layout_tests.models import test_failures _log = logging.getLogger(__name__) +OK_EXIT_STATUS = 0 + +# This matches what the shell does on POSIX. INTERRUPTED_EXIT_STATUS = signal.SIGINT + 128 +# POSIX limits status codes to 0-255. Normally run-webkit-tests returns the number +# of tests that failed. These indicate exceptional conditions triggered by the +# script itself, so we count backwards from 255 (aka -1) to enumerate them. +SYS_DEPS_EXIT_STATUS = 252 +NO_TESTS_EXIT_STATUS = 253 +NO_DEVICES_EXIT_STATUS = 254 +UNEXPECTED_ERROR_EXIT_STATUS = 255 + +ERROR_CODES = ( + INTERRUPTED_EXIT_STATUS, + SYS_DEPS_EXIT_STATUS, + NO_TESTS_EXIT_STATUS, + NO_DEVICES_EXIT_STATUS, + UNEXPECTED_ERROR_EXIT_STATUS, +) + + +class TestRunException(Exception): + def __init__(self, code, msg): + self.code = code + self.msg = msg + + class TestRunResults(object): def __init__(self, expectations, num_tests): self.total = num_tests @@ -181,14 +207,9 @@ def summarize_results(port_obj, expectations, initial_results, retry_results, en num_flaky += 1 elif retry_results: retry_result_type = retry_results.unexpected_results_by_name[test_name].type - if result_type != retry_result_type: - if enabled_pixel_tests_in_retry and result_type == test_expectations.TEXT and retry_result_type == test_expectations.IMAGE_PLUS_TEXT: - num_regressions += 1 - else: - num_flaky += 1 + num_regressions += 1 + if not keywords[retry_result_type] in actual: actual.append(keywords[retry_result_type]) - else: - num_regressions += 1 else: num_regressions += 1 diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/__init__.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/__init__.py index b2a50844c42..cc7fa86da13 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/__init__.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/__init__.py @@ -31,5 +31,5 @@ import builders # Why is this in port? from base import Port # It's possible we don't need to export this virtual baseclass outside the module. -from driver import Driver, DriverInput, DriverOutput +from driver import DeviceFailure, Driver, DriverInput, DriverOutput from factory import platform_options, configuration_options diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py index 5837ac20c0b..d3267f9a00f 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android.py @@ -30,6 +30,7 @@ import copy import logging import os import re +import signal import sys import subprocess import threading @@ -37,7 +38,10 @@ import time from multiprocessing.pool import ThreadPool -from webkitpy.layout_tests.port import chromium +from webkitpy.common.system.executive import ScriptError +from webkitpy.layout_tests.breakpad.dump_reader_multipart import DumpReaderAndroid +from webkitpy.layout_tests.models import test_run_results +from webkitpy.layout_tests.port import base from webkitpy.layout_tests.port import linux from webkitpy.layout_tests.port import driver from webkitpy.layout_tests.port import factory @@ -172,8 +176,14 @@ class ContentShellDriverDetails(): def command_line_file(self): return '/data/local/tmp/content-shell-command-line' - def additional_command_line_flags(self): - return ['--dump-render-tree', '--encode-binary'] + def device_crash_dumps_directory(self): + return '/data/local/tmp/content-shell-crash-dumps' + + def additional_command_line_flags(self, use_breakpad): + flags = ['--dump-render-tree', '--encode-binary'] + if use_breakpad: + flags.extend(['--enable-crash-reporter', '--crash-dumps-dir=%s' % self.device_crash_dumps_directory()]) + return flags def device_directory(self): return DEVICE_SOURCE_ROOT_DIR + 'content_shell/' @@ -193,7 +203,7 @@ class AndroidCommands(object): def file_exists(self, full_path): assert full_path.startswith('/') - return self.run(['shell', 'ls', full_path]).strip() == full_path + return self.run(['shell', 'ls', '-d', full_path]).strip() == full_path def push(self, host_path, device_path, ignore_error=False): return self.run(['push', host_path, device_path], ignore_error=ignore_error) @@ -206,6 +216,12 @@ class AndroidCommands(object): if chmod: self.run(['shell', 'chmod', chmod, device_path]) + def restart_adb(self): + pids = self.extract_pids('adbd') + if pids: + output = self.run(['shell', 'kill', '-' + str(signal.SIGTERM)] + pids) + self.run(['wait-for-device']) + def restart_as_root(self): output = self.run(['root']) if 'adbd is already running as root' in output: @@ -216,6 +232,21 @@ class AndroidCommands(object): self.run(['wait-for-device']) + def extract_pids(self, process_name): + pids = [] + output = self.run(['shell', 'ps']) + for line in output.splitlines(): + data = line.split() + try: + if process_name in data[-1]: # name is in the last column + if process_name == data[-1]: + pids.insert(0, data[1]) # PID is in the second column + else: + pids.append(data[1]) + except IndexError: + pass + return pids + def run(self, command, ignore_error=False): self._log_debug('Run adb command: ' + str(command)) if ignore_error: @@ -223,8 +254,7 @@ class AndroidCommands(object): else: error_handler = None - result = self._executive.run_command(self.adb_command() + command, error_handler=error_handler, - debug_logging=self._debug_logging) + result = self._executive.run_command(self.adb_command() + command, error_handler=error_handler, debug_logging=self._debug_logging) # We limit the length to avoid outputting too verbose commands, such as "adb logcat". self._log_debug('Run adb result: ' + result[:80]) @@ -325,7 +355,7 @@ class AndroidDevices(object): error_handler=executive.ignore_error, debug_logging=self._debug_logging) devices = re_device.findall(result) if not devices: - raise AssertionError('Unable to find attached Android devices. ADB output: %s' % result) + return [] for device_serial in sorted(devices): commands = AndroidCommands(executive, device_serial, self._debug_logging) @@ -340,9 +370,6 @@ class AndroidDevices(object): self._usable_devices.append(commands) - if not self._usable_devices: - raise AssertionError('No usable devices are available for running layout tests.') - return self._usable_devices def get_device(self, executive, device_index): @@ -361,9 +388,9 @@ class AndroidDevices(object): # Private methods def _battery_level_for_device(self, commands): battery_status = commands.run(['shell', 'dumpsys', 'battery']) - if 'Error' in battery_status: + if 'Error' in battery_status or "Can't find service: battery" in battery_status: _log.warning('Unable to read the battery level from device with serial "%s".' % commands.get_serial()) - return 100 + return 0 return int(re.findall('level: (\d+)', battery_status)[0]) @@ -372,7 +399,7 @@ class AndroidDevices(object): return 'mScreenOn=true' in power_status or 'mScreenOn=SCREEN_ON_BIT' in power_status -class AndroidPort(chromium.ChromiumPort): +class AndroidPort(base.Port): port_name = 'android' # Avoid initializing the adb path [worker count]+1 times by storing it as a static member. @@ -380,7 +407,7 @@ class AndroidPort(chromium.ChromiumPort): SUPPORTED_VERSIONS = ('android') - FALLBACK_PATHS = { 'android': [ 'android' ] + linux.LinuxPort.latest_platform_fallback_path() } + FALLBACK_PATHS = {'icecreamsandwich': ['android'] + linux.LinuxPort.latest_platform_fallback_path()} # Android has aac and mp3 codecs built in. PORT_HAS_AUDIO_CODECS_BUILT_IN = True @@ -394,6 +421,9 @@ class AndroidPort(chromium.ChromiumPort): self._host_port = factory.PortFactory(host).get('chromium', **kwargs) self._server_process_constructor = self._android_server_process_constructor + if not self.get_option('disable_breakpad'): + self._dump_reader = DumpReaderAndroid(host, self._build_path()) + if self.driver_name() != self.CONTENT_SHELL_NAME: raise AssertionError('Layout tests on Android only support content_shell as the driver.') @@ -429,7 +459,7 @@ class AndroidPort(chromium.ChromiumPort): return self._build_path(MD5SUM_HOST_FILE_NAME) def additional_drt_flag(self): - return self._driver_details.additional_command_line_flags() + return self._driver_details.additional_command_line_flags(use_breakpad=not self.get_option('disable_breakpad')) def default_timeout_ms(self): # Android platform has less computing power than desktop platforms. @@ -442,33 +472,47 @@ class AndroidPort(chromium.ChromiumPort): return 0.0 def default_child_processes(self): - usable_device_count = len(self._devices.usable_devices(self._executive)) - if not usable_device_count: - raise AssertionError('There are no devices available to run the layout tests on.') - - return usable_device_count - - def default_baseline_search_path(self): - return map(self._webkit_baseline_path, self.FALLBACK_PATHS['android']) + usable_devices = self._devices.usable_devices(self._executive) + if not usable_devices: + raise test_run_results.TestRunException(test_run_results.NO_DEVICES_EXIT_STATUS, "Unable to find any attached Android devices.") + return len(usable_devices) def check_wdiff(self, logging=True): return self._host_port.check_wdiff(logging) def check_build(self, needs_http, printer): - result = super(AndroidPort, self).check_build(needs_http, printer) - result = self._check_file_exists(self.path_to_md5sum(), 'md5sum utility') and result + exit_status = super(AndroidPort, self).check_build(needs_http, printer) + if exit_status: + return exit_status + + result = self._check_file_exists(self.path_to_md5sum(), 'md5sum utility') result = self._check_file_exists(self.path_to_md5sum_host(), 'md5sum host utility') and result result = self._check_file_exists(self.path_to_forwarder(), 'forwarder utility') and result - if result: - # FIXME: We should figure out how to handle failures here better. - self._check_devices(printer) + + if not result: + # There is a race condition in adb at least <= 4.3 on Linux that causes it to go offline periodically + # We set the processor affinity for any running adb process to attempt to work around this. + # See crbug.com/268450 + if self.host.platform.is_linux(): + pids = self._executive.running_pids(lambda name: 'adb' in name) + if not pids: + # Apparently adb is not running, which is unusual. Running any adb command should start it. + self._executive.run_command(['adb', 'devices']) + pids = self._executive.running_pids(lambda name: 'adb' in name) + if not pids: + _log.error("The adb daemon does not appear to be running.") + return False + + for pid in pids: + self._executive.run_command(['taskset', '-p', '-c', '0', str(pid)]) if not result: _log.error('For complete Android build requirements, please see:') _log.error('') _log.error(' http://code.google.com/p/chromium/wiki/AndroidBuildInstructions') + return test_run_results.UNEXPECTED_ERROR_EXIT_STATUS - return result + return self._check_devices(printer) def _check_devices(self, printer): # Printer objects aren't threadsafe, so we need to protect calls to them. @@ -496,14 +540,18 @@ class AndroidPort(chromium.ChromiumPort): log_safely("preparing device", throttled=False) try: d._setup_test(log_safely) + log_safely("device prepared", throttled=False) + except (ScriptError, driver.DeviceFailure) as e: + lock.acquire() + _log.warning("[%s] failed to prepare_device: %s" % (serial, str(e))) + lock.release() except KeyboardInterrupt: if pool: pool.terminate() - log_safely("device prepared", throttled=False) # FIXME: It would be nice if we knew how many workers we needed. num_workers = self.default_child_processes() - num_child_processes = self.get_option('child_processes') + num_child_processes = int(self.get_option('child_processes')) if num_child_processes: num_workers = min(num_workers, num_child_processes) if num_workers > 1: @@ -516,6 +564,11 @@ class AndroidPort(chromium.ChromiumPort): else: setup_device(0) + if not self._devices.prepared_devices(): + _log.error('Could not prepare any devices for testing.') + return test_run_results.NO_DEVICES_EXIT_STATUS + return test_run_results.OK_EXIT_STATUS + def setup_test_run(self): super(AndroidPort, self).setup_test_run() @@ -527,6 +580,9 @@ class AndroidPort(chromium.ChromiumPort): # We were called with --no-build, so assume the devices are up to date. self._options.prepared_devices = [d.get_serial() for d in self._devices.usable_devices(self.host.executive)] + def num_workers(self, requested_num_workers): + return min(len(self._options.prepared_devices), requested_num_workers) + def check_sys_deps(self, needs_http): for (font_dirs, font_file, package) in HOST_FONT_FILES: exists = False @@ -537,8 +593,8 @@ class AndroidPort(chromium.ChromiumPort): break if not exists: _log.error('You are missing %s under %s. Try installing %s. See build instructions.' % (font_file, font_dirs, package)) - return False - return True + return test_run_results.SYS_DEPS_EXIT_STATUS + return test_run_results.OK_EXIT_STATUS def requires_http_server(self): """Chromium Android runs tests on devices, and uses the HTTP server to @@ -563,6 +619,10 @@ class AndroidPort(chromium.ChromiumPort): # Override to return the actual test driver's command line. return self.create_driver(0)._android_driver_cmd_line(self.get_option('pixel_tests'), []) + def clobber_old_port_specific_results(self): + if not self.get_option('disable_breakpad'): + self._dump_reader.clobber_old_results() + # Overridden protected methods. def _build_path(self, *comps): @@ -747,6 +807,8 @@ class ChromiumAndroidDriver(driver.Driver): self._android_commands = android_devices.get_device(port._executive, worker_number) self._driver_details = driver_details self._debug_logging = self._port._debug_logging + self._created_cmd_line = False + self._device_failed = False # FIXME: If we taught ProfileFactory about "target" devices we could # just use the logic in Driver instead of duplicating it here. @@ -768,6 +830,7 @@ class ChromiumAndroidDriver(driver.Driver): def __del__(self): self._teardown_performance() + self._clean_up_cmd_line() super(ChromiumAndroidDriver, self).__del__() def _update_kallsyms_cache(self, output_dir): @@ -814,7 +877,7 @@ class ChromiumAndroidDriver(driver.Driver): self._md5sum_path = self._port.path_to_md5sum() if not self._android_commands.file_exists(MD5SUM_DEVICE_PATH): if not self._android_commands.push(self._md5sum_path, MD5SUM_DEVICE_PATH): - raise AssertionError('Could not push md5sum to device') + self._abort('Could not push md5sum to device') self._push_executable(log_callback) self._push_fonts(log_callback) @@ -828,6 +891,7 @@ class ChromiumAndroidDriver(driver.Driver): if self._android_devices.is_device_prepared(self._android_commands.get_serial()): return + self._android_commands.restart_adb() self._android_commands.restart_as_root() self._setup_md5sum_and_push_data_if_needed(log_callback) self._setup_performance() @@ -850,12 +914,16 @@ class ChromiumAndroidDriver(driver.Driver): def _log_error(self, message): _log.error('[%s] %s' % (self._android_commands.get_serial(), message)) + def _log_warning(self, message): + _log.warning('[%s] %s' % (self._android_commands.get_serial(), message)) + def _log_debug(self, message): if self._debug_logging: _log.debug('[%s] %s' % (self._android_commands.get_serial(), message)) def _abort(self, message): - raise AssertionError('[%s] %s' % (self._android_commands.get_serial(), message)) + self._device_failed = True + raise driver.DeviceFailure('[%s] %s' % (self._android_commands.get_serial(), message)) @staticmethod def _extract_hashes_from_md5sum_output(md5sum_output): @@ -914,23 +982,37 @@ class ChromiumAndroidDriver(driver.Driver): self._push_file_if_needed(self._port.layout_tests_dir() + '/' + resource, DEVICE_LAYOUT_TESTS_DIR + resource, log_callback) def _get_last_stacktrace(self): - tombstones = self._android_commands.run(['shell', 'ls', '-n', '/data/tombstones']) - if not tombstones or tombstones.startswith('/data/tombstones: No such file or directory'): + tombstones = self._android_commands.run(['shell', 'ls', '-n', '/data/tombstones/tombstone_*']) + if not tombstones or tombstones.startswith('/data/tombstones/tombstone_*: No such file or directory'): self._log_error('The driver crashed, but no tombstone found!') return '' + + if tombstones.startswith('/data/tombstones/tombstone_*: Permission denied'): + # FIXME: crbug.com/321489 ... figure out why this happens. + self._log_error('The driver crashed, but we could not read the tombstones!') + return '' + tombstones = tombstones.rstrip().split('\n') - last_tombstone = tombstones[0].split() - for tombstone in tombstones[1:]: + last_tombstone = None + for tombstone in tombstones: # Format of fields: # 0 1 2 3 4 5 6 # permission uid gid size date time filename # -rw------- 1000 1000 45859 2011-04-13 06:00 tombstone_00 fields = tombstone.split() - if (fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]): + if len(fields) != 7: + self._log_warning("unexpected line in tombstone output, skipping: '%s'" % tombstone) + continue + + if not last_tombstone or fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]: last_tombstone = fields else: break + if not last_tombstone: + self._log_error('The driver crashed, but we could not find any valid tombstone!') + return '' + # Use Android tool vendor/google/tools/stack to convert the raw # stack trace into a human readable format, if needed. # It takes a long time, so don't do it here. @@ -961,6 +1043,12 @@ class ChromiumAndroidDriver(driver.Driver): if not stderr: stderr = '' stderr += '********* [%s] Tombstone file:\n%s' % (self._android_commands.get_serial(), self._get_last_stacktrace()) + + if not self._port.get_option('disable_breakpad'): + crashes = self._pull_crash_dumps_from_device() + for crash in crashes: + stderr += '********* [%s] breakpad minidump %s:\n%s' % (self._port.host.filesystem.basename(crash), self._android_commands.get_serial(), self._port._dump_reader._get_stack_from_dump(crash)) + return super(ChromiumAndroidDriver, self)._get_crash_log(stdout, stderr, newer_than) def cmd_line(self, pixel_tests, per_test_args): @@ -992,14 +1080,6 @@ class ChromiumAndroidDriver(driver.Driver): not self._android_commands.file_exists(self._out_fifo_path) and not self._android_commands.file_exists(self._err_fifo_path)) - def run_test(self, driver_input, stop_when_done): - base = self._port.lookup_virtual_test_base(driver_input.test_name) - if base: - driver_input = copy.copy(driver_input) - driver_input.args = self._port.lookup_virtual_test_args(driver_input.test_name) - driver_input.test_name = base - return super(ChromiumAndroidDriver, self).run_test(driver_input, stop_when_done) - def start(self, pixel_tests, per_test_args): # We override the default start() so that we can call _android_driver_cmd_line() # instead of cmd_line(). @@ -1016,18 +1096,23 @@ class ChromiumAndroidDriver(driver.Driver): super(ChromiumAndroidDriver, self).start(pixel_tests, per_test_args) def _start(self, pixel_tests, per_test_args): - assert self._android_devices.is_device_prepared(self._android_commands.get_serial()) + if not self._android_devices.is_device_prepared(self._android_commands.get_serial()): + raise driver.DeviceFailure("%s is not prepared in _start()" % self._android_commands.get_serial()) for retries in range(3): - if self._start_once(pixel_tests, per_test_args): - return + try: + if self._start_once(pixel_tests, per_test_args): + return + except ScriptError as e: + self._abort('ScriptError("%s") in _start()' % str(e)) + self._log_error('Failed to start the content_shell application. Retries=%d. Log:%s' % (retries, self._get_logcat())) self.stop() time.sleep(2) self._abort('Failed to start the content_shell application multiple times. Giving up.') def _start_once(self, pixel_tests, per_test_args): - super(ChromiumAndroidDriver, self)._start(pixel_tests, per_test_args) + super(ChromiumAndroidDriver, self)._start(pixel_tests, per_test_args, wait_for_ready=False) self._log_debug('Starting forwarder') self._forwarder_process = self._port._server_process_constructor( @@ -1039,7 +1124,21 @@ class ChromiumAndroidDriver(driver.Driver): return False self._android_commands.run(['logcat', '-c']) + + cmd_line_file_path = self._driver_details.command_line_file() + original_cmd_line_file_path = cmd_line_file_path + '.orig' + if self._android_commands.file_exists(cmd_line_file_path) and not self._android_commands.file_exists(original_cmd_line_file_path): + # We check for both the normal path and the backup because we do not want to step + # on the backup. Otherwise, we'd clobber the backup whenever we changed the + # command line during the run. + self._android_commands.run(['shell', 'mv', cmd_line_file_path, original_cmd_line_file_path]) + self._android_commands.run(['shell', 'echo'] + self._android_driver_cmd_line(pixel_tests, per_test_args) + ['>', self._driver_details.command_line_file()]) + self._created_cmd_line = True + + self._android_commands.run(['shell', 'rm', '-rf', self._driver_details.device_crash_dumps_directory()]) + self._android_commands.mkdir(self._driver_details.device_crash_dumps_directory(), chmod='777') + start_result = self._android_commands.run(['shell', 'am', 'start', '-e', 'RunInSubThread', '-n', self._driver_details.activity_name()]) if start_result.find('Exception') != -1: self._log_error('Failed to start the content_shell application. Exception:\n' + start_result) @@ -1111,7 +1210,10 @@ class ChromiumAndroidDriver(driver.Driver): return self._pid_from_android_ps_output(ps_output, self._driver_details.package_name()) def stop(self): - self._android_commands.run(['shell', 'am', 'force-stop', self._driver_details.package_name()]) + if not self._device_failed: + # Do not try to stop the application if there's something wrong with the device; adb may hang. + # FIXME: crbug.com/305040. Figure out if it's really hanging (and why). + self._android_commands.run(['shell', 'am', 'force-stop', self._driver_details.package_name()]) if self._read_stdout_process: self._read_stdout_process.kill() @@ -1129,7 +1231,41 @@ class ChromiumAndroidDriver(driver.Driver): if self._android_devices.is_device_prepared(self._android_commands.get_serial()): if not ChromiumAndroidDriver._loop_with_timeout(self._remove_all_pipes, DRIVER_START_STOP_TIMEOUT_SECS): - raise AssertionError('Failed to remove fifo files. May be locked.') + self._abort('Failed to remove fifo files. May be locked.') + + self._clean_up_cmd_line() + + def _pull_crash_dumps_from_device(self): + result = [] + if not self._android_commands.file_exists(self._driver_details.device_crash_dumps_directory()): + return result + dumps = self._android_commands.run(['shell', 'ls', self._driver_details.device_crash_dumps_directory()]) + for dump in dumps.splitlines(): + device_dump = '%s/%s' % (self._driver_details.device_crash_dumps_directory(), dump) + local_dump = self._port._filesystem.join(self._port._dump_reader.crash_dumps_directory(), dump) + + # FIXME: crbug.com/321489. Figure out why these commands would fail ... + err = self._android_commands.run(['shell', 'chmod', '777', device_dump]) + if not err: + self._android_commands.pull(device_dump, local_dump) + if not err: + self._android_commands.run(['shell', 'rm', '-f', device_dump]) + + if self._port._filesystem.exists(local_dump): + result.append(local_dump) + return result + + def _clean_up_cmd_line(self): + if not self._created_cmd_line: + return + + cmd_line_file_path = self._driver_details.command_line_file() + original_cmd_line_file_path = cmd_line_file_path + '.orig' + if self._android_commands.file_exists(original_cmd_line_file_path): + self._android_commands.run(['shell', 'mv', original_cmd_line_file_path, cmd_line_file_path]) + elif self._android_commands.file_exists(cmd_line_file_path): + self._android_commands.run(['shell', 'rm', cmd_line_file_path]) + self._created_cmd_line = False def _command_from_driver_input(self, driver_input): command = super(ChromiumAndroidDriver, self)._command_from_driver_input(driver_input) @@ -1148,16 +1284,3 @@ class ChromiumAndroidDriver(driver.Driver): if last_char in ('#', '$'): return last_char = current_char - - def _wait_for_server_process_output(self, server_process, deadline, text): - output = '' - line = server_process.read_stdout_line(deadline) - while not server_process.timed_out and not server_process.has_crashed() and not text in line.rstrip(): - output += line - line = server_process.read_stdout_line(deadline) - - if server_process.timed_out or server_process.has_crashed(): - _log.error('Failed to start the %s process: \n%s' % (server_process.name(), output)) - return False - - return True diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py index 56248ed2977..01761c78cfb 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/android_unittest.py @@ -37,11 +37,17 @@ from webkitpy.common.system.executive_mock import MockExecutive2 from webkitpy.common.system.systemhost_mock import MockSystemHost from webkitpy.layout_tests.port import android -from webkitpy.layout_tests.port import chromium_port_testcase +from webkitpy.layout_tests.port import port_testcase from webkitpy.layout_tests.port import driver from webkitpy.layout_tests.port import driver_unittest from webkitpy.tool.mocktool import MockOptions +# Type of tombstone test which the mocked Android Debug Bridge should execute. +VALID_TOMBSTONE_TEST_TYPE = 0 +NO_FILES_TOMBSTONE_TEST_TYPE = 1 +NO_PERMISSION_TOMBSTONE_TEST_TYPE = 2 +INVALID_ENTRY_TOMBSTONE_TEST_TYPE = 3 +INVALID_ENTRIES_TOMBSTONE_TEST_TYPE = 4 # Any "adb" commands will be interpret by this class instead of executing actual # commansd on the file system, which we don't want to do. @@ -49,6 +55,7 @@ class MockAndroidDebugBridge: def __init__(self, device_count): self._device_count = device_count self._last_command = None + self._tombstone_output = None # Local public methods. @@ -79,12 +86,20 @@ class MockAndroidDebugBridge: return 'mockoutput' if len(args) > 5 and args[5] == 'power': return 'mScreenOn=true' + if len(args) > 5 and args[4] == 'cat' and args[5].find('tombstone') != -1: + return 'tombstone content' + if len(args) > 6 and args[4] == 'ls' and args[6].find('tombstone') != -1: + assert self._tombstone_output, 'Tombstone output needs to have been set by the test.' + return self._tombstone_output return '' def last_command(self): return self._last_command + def set_tombstone_output(self, output): + self._tombstone_output = output + # Local private methods. def _get_device_output(self): @@ -133,8 +148,8 @@ class AndroidCommandsTest(unittest.TestCase): def test_convenience_methods(self): android_commands = self.make_android_commands(1, '123456789ABCDEF0') - android_commands.file_exists('/tombstones') - self.assertEquals('adb -s 123456789ABCDEF0 shell ls /tombstones', self._mock_executive.last_command()) + android_commands.file_exists('/some_directory') + self.assertEquals('adb -s 123456789ABCDEF0 shell ls -d /some_directory', self._mock_executive.last_command()) android_commands.push('foo', 'bar') self.assertEquals('adb -s 123456789ABCDEF0 push foo bar', self._mock_executive.last_command()) @@ -143,7 +158,7 @@ class AndroidCommandsTest(unittest.TestCase): self.assertEquals('adb -s 123456789ABCDEF0 pull bar foo', self._mock_executive.last_command()) -class AndroidPortTest(chromium_port_testcase.ChromiumPortTestCase): +class AndroidPortTest(port_testcase.PortTestCase): port_name = 'android' port_maker = android.AndroidPort @@ -156,9 +171,12 @@ class AndroidPortTest(chromium_port_testcase.ChromiumPortTestCase): def test_check_build(self): host = MockSystemHost() host.filesystem.exists = lambda p: True - port = self.make_port(host=host) - port.check_build(needs_http=True, printer=chromium_port_testcase.FakePrinter()) + port = self.make_port(host=host, options=MockOptions(child_processes=1)) + port.check_build(needs_http=True, printer=port_testcase.FakePrinter()) + def test_check_sys_deps(self): + # FIXME: Do something useful here, but testing the full logic would be hard. + pass def make_wdiff_available(self, port): port._wdiff_available = True @@ -237,3 +255,73 @@ class ChromiumAndroidTwoPortsTest(unittest.TestCase): self.assertEqual(1, port0.driver_cmd_line().count('--foo=bar')) self.assertEqual(0, port1.driver_cmd_line().count('--create-stdin-fifo')) + + +class ChromiumAndroidDriverTombstoneTest(unittest.TestCase): + EXPECTED_STACKTRACE = '-rw------- 1000 1000 3604 2013-11-19 16:16 tombstone_10\ntombstone content' + + def setUp(self): + self._mock_adb = MockAndroidDebugBridge(1) + self._mock_executive = MockExecutive2(run_command_fn=self._mock_adb.run_command) + + self._port = android.AndroidPort(MockSystemHost(executive=self._mock_executive), 'android') + self._driver = android.ChromiumAndroidDriver(self._port, worker_number=0, + pixel_tests=True, driver_details=android.ContentShellDriverDetails(), android_devices=self._port._devices) + + self._errors = [] + self._driver._log_error = lambda msg: self._errors.append(msg) + + self._warnings = [] + self._driver._log_warning = lambda msg: self._warnings.append(msg) + + # Tests that we return an empty string and log an error when no tombstones could be found. + def test_no_tombstones_found(self): + self._mock_adb.set_tombstone_output('/data/tombstones/tombstone_*: No such file or directory') + stacktrace = self._driver._get_last_stacktrace() + + self.assertEqual(1, len(self._errors)) + self.assertEqual('The driver crashed, but no tombstone found!', self._errors[0]) + self.assertEqual('', stacktrace) + + # Tests that an empty string will be returned if we cannot read the tombstone files. + def test_insufficient_tombstone_permission(self): + self._mock_adb.set_tombstone_output('/data/tombstones/tombstone_*: Permission denied') + stacktrace = self._driver._get_last_stacktrace() + + self.assertEqual(1, len(self._errors)) + self.assertEqual('The driver crashed, but we could not read the tombstones!', self._errors[0]) + self.assertEqual('', stacktrace) + + # Tests that invalid "ls" output will throw a warning when listing the tombstone files. + def test_invalid_tombstone_list_entry_format(self): + self._mock_adb.set_tombstone_output('-rw------- 1000 1000 3604 2013-11-19 16:15 tombstone_00\n' + + '-- invalid entry --\n' + + '-rw------- 1000 1000 3604 2013-11-19 16:16 tombstone_10') + stacktrace = self._driver._get_last_stacktrace() + + self.assertEqual(1, len(self._warnings)) + self.assertEqual(ChromiumAndroidDriverTombstoneTest.EXPECTED_STACKTRACE, stacktrace) + + # Tests the case in which we can't find any valid tombstone entries at all. The tombstone + # output used for the mock misses the permission part. + def test_invalid_tombstone_list(self): + self._mock_adb.set_tombstone_output('1000 1000 3604 2013-11-19 16:15 tombstone_00\n' + + '1000 1000 3604 2013-11-19 16:15 tombstone_01\n' + + '1000 1000 3604 2013-11-19 16:15 tombstone_02') + stacktrace = self._driver._get_last_stacktrace() + + self.assertEqual(3, len(self._warnings)) + self.assertEqual(1, len(self._errors)) + self.assertEqual('The driver crashed, but we could not find any valid tombstone!', self._errors[0]) + self.assertEqual('', stacktrace) + + # Tests that valid tombstone listings will return the contents of the most recent file. + def test_read_valid_tombstone_file(self): + self._mock_adb.set_tombstone_output('-rw------- 1000 1000 3604 2013-11-19 16:15 tombstone_00\n' + + '-rw------- 1000 1000 3604 2013-11-19 16:16 tombstone_10\n' + + '-rw------- 1000 1000 3604 2013-11-19 16:15 tombstone_02') + stacktrace = self._driver._get_last_stacktrace() + + self.assertEqual(0, len(self._warnings)) + self.assertEqual(0, len(self._errors)) + self.assertEqual(ChromiumAndroidDriverTombstoneTest.EXPECTED_STACKTRACE, stacktrace) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py index 4637f585f04..f2ece188dd1 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base.py @@ -56,6 +56,7 @@ from webkitpy.common.system.path import cygpath from webkitpy.common.system.systemhost import SystemHost from webkitpy.common.webkit_finder import WebKitFinder from webkitpy.layout_tests.layout_package.bot_test_expectations import BotTestExpectationsFactory +from webkitpy.layout_tests.models import test_run_results from webkitpy.layout_tests.models.test_configuration import TestConfiguration from webkitpy.layout_tests.port import config as port_config from webkitpy.layout_tests.port import driver @@ -89,6 +90,69 @@ class Port(object): # True if the port as aac and mp3 codecs built in. PORT_HAS_AUDIO_CODECS_BUILT_IN = False + ALL_SYSTEMS = ( + ('snowleopard', 'x86'), + ('lion', 'x86'), + + # FIXME: We treat Retina (High-DPI) devices as if they are running + # a different operating system version. This isn't accurate, but will work until + # we need to test and support baselines across multiple O/S versions. + ('retina', 'x86'), + + ('mountainlion', 'x86'), + ('mavericks', 'x86'), + ('xp', 'x86'), + ('win7', 'x86'), + ('lucid', 'x86'), + ('lucid', 'x86_64'), + # FIXME: Technically this should be 'arm', but adding a third architecture type breaks TestConfigurationConverter. + # If we need this to be 'arm' in the future, then we first have to fix TestConfigurationConverter. + ('icecreamsandwich', 'x86'), + ) + + ALL_BASELINE_VARIANTS = [ + 'mac-mavericks', 'mac-mountainlion', 'mac-retina', 'mac-lion', 'mac-snowleopard', + 'win-win7', 'win-xp', + 'linux-x86_64', 'linux-x86', + ] + + CONFIGURATION_SPECIFIER_MACROS = { + 'mac': ['snowleopard', 'lion', 'retina', 'mountainlion', 'mavericks'], + 'win': ['xp', 'win7'], + 'linux': ['lucid'], + 'android': ['icecreamsandwich'], + } + + DEFAULT_BUILD_DIRECTORIES = ('out',) + + # overridden in subclasses. + FALLBACK_PATHS = {} + + SUPPORTED_VERSIONS = [] + + @classmethod + def latest_platform_fallback_path(cls): + return cls.FALLBACK_PATHS[cls.SUPPORTED_VERSIONS[-1]] + + @classmethod + def _static_build_path(cls, filesystem, build_directory, chromium_base, configuration, comps): + if build_directory: + return filesystem.join(build_directory, configuration, *comps) + + hits = [] + for directory in cls.DEFAULT_BUILD_DIRECTORIES: + base_dir = filesystem.join(chromium_base, directory, configuration) + path = filesystem.join(base_dir, *comps) + if filesystem.exists(path): + hits.append((filesystem.mtime(path), path)) + + if hits: + hits.sort(reverse=True) + return hits[0][1] # Return the newest file found. + + # We have to default to something, so pick the last one. + return filesystem.join(base_dir, *comps) + @classmethod def determine_full_port_name(cls, host, options, port_name): """Return a fully-specified port name that can be used to construct objects.""" @@ -123,6 +187,7 @@ class Port(object): self._image_differ = None self._server_process_constructor = server_process.ServerProcess # overridable for testing self._http_lock = None # FIXME: Why does this live on the port object? + self._dump_reader = None # Python's Popen has a bug that causes any pipes opened to a # process that can't be executed to be leaked. Since this @@ -161,8 +226,7 @@ class Port(object): return False def default_pixel_tests(self): - # FIXME: Disable until they are run by default on build.webkit.org. - return False + return True def default_smoke_test_only(self): return False @@ -196,14 +260,10 @@ class Port(object): def default_max_locked_shards(self): """Return the number of "locked" shards to run in parallel (like the http tests).""" - return 1 - - def worker_startup_delay_secs(self): - # FIXME: If we start workers up too quickly, DumpRenderTree appears - # to thrash on something and time out its first few tests. Until - # we can figure out what's going on, sleep a bit in between - # workers. See https://bugs.webkit.org/show_bug.cgi?id=79147 . - return 0.1 + max_locked_shards = int(self.default_child_processes()) / 4 + if not max_locked_shards: + return 1 + return max_locked_shards def baseline_path(self): """Return the absolute path to the directory to store new baselines in for this port.""" @@ -231,11 +291,7 @@ class Port(object): def default_baseline_search_path(self): """Return a list of absolute paths to directories to search under for baselines. The directories are searched in order.""" - search_paths = [] - search_paths.append(self.name()) - if self.name() != self.port_name: - search_paths.append(self.port_name) - return map(self._webkit_baseline_path, search_paths) + return map(self._webkit_baseline_path, self.FALLBACK_PATHS[self.version()]) @memoized def _compare_baseline(self): @@ -245,20 +301,55 @@ class Port(object): return factory.get(target_port).default_baseline_search_path() return [] - def check_build(self, needs_http, printer): - """This routine is used to ensure that the build is up to date - and all the needed binaries are present.""" - if self.get_option('build'): - return False - if not self._check_driver(): - return False - if self.get_option('pixel_tests'): - if not self.check_image_diff(): - return False - if not self._check_port_build(): + 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_build(self, needs_http, printer): + result = True + + dump_render_tree_binary_path = self._path_to_driver() + result = self._check_file_exists(dump_render_tree_binary_path, + 'test driver') and result + if not result and self.get_option('build'): + result = self._check_driver_build_up_to_date( + self.get_option('configuration')) + else: + _log.error('') + + helper_path = self._path_to_helper() + if helper_path: + result = self._check_file_exists(helper_path, + 'layout test helper') and result + + if self.get_option('pixel_tests'): + result = self.check_image_diff( + 'To override, invoke with --no-pixel-tests') and result + + # It's okay if pretty patch and wdiff aren't available, but we will at least log messages. + self._pretty_patch_available = self.check_pretty_patch() + self._wdiff_available = self.check_wdiff() + + if self._dump_reader: + result = self._dump_reader.check_is_functional() and result + + return test_run_results.OK_EXIT_STATUS if result else test_run_results.UNEXPECTED_ERROR_EXIT_STATUS + def _check_driver(self): driver_path = self._path_to_driver() if not self._filesystem.exists(driver_path): @@ -276,9 +367,21 @@ class Port(object): This step can be skipped with --nocheck-sys-deps. Returns whether the system is properly configured.""" - if needs_http: - return self.check_httpd() - return True + cmd = [self._path_to_driver(), '--check-layout-test-sys-deps'] + + local_error = ScriptError() + + def error_handler(script_error): + local_error.exit_code = script_error.exit_code + + output = self._executive.run_command(cmd, error_handler=error_handler) + if local_error.exit_code: + _log.error('System dependencies check failed.') + _log.error('To override, invoke with --nocheck-sys-deps') + _log.error('') + _log.error(output) + return test_run_results.SYS_DEPS_EXIT_STATUS + return test_run_results.OK_EXIT_STATUS def check_image_diff(self, override_step=None, logging=True): """This routine is used to check whether image_diff binary exists.""" @@ -640,6 +743,26 @@ class Port(object): def is_test_file(filesystem, dirname, filename): return Port._has_supported_extension(filesystem, filename) and not Port.is_reference_html_file(filesystem, dirname, filename) + ALL_TEST_TYPES = ['audio', 'harness', 'pixel', 'ref', 'text', 'unknown'] + + def test_type(self, test_name): + fs = self._filesystem + if fs.exists(self.expected_filename(test_name, '.png')): + return 'pixel' + if fs.exists(self.expected_filename(test_name, '.wav')): + return 'audio' + if self.reference_files(test_name): + return 'ref' + txt = self.expected_text(test_name) + if txt: + if 'layer at (0,0) size 800x600' in txt: + return 'pixel' + for line in txt.splitlines(): + if line.startswith('FAIL') or line.startswith('TIMEOUT') or line.startswith('PASS'): + return 'harness' + return 'text' + return 'unknown' + def test_key(self, test_name): """Turns a test name into a list with two sublists, the natural key of the dirname, and the natural key of the basename. @@ -734,6 +857,9 @@ class Port(object): def path_from_webkit_base(self, *comps): return self._webkit_finder.path_from_webkit_base(*comps) + def path_from_chromium_base(self, *comps): + return self._webkit_finder.path_from_chromium_base(*comps) + def path_to_script(self, script_name): return self._webkit_finder.path_to_script(script_name) @@ -783,7 +909,7 @@ class Port(object): return False def is_chromium(self): - return False + return True def name(self): """Returns a name that uniquely identifies this particular type of port @@ -844,13 +970,26 @@ class Port(object): def default_results_directory(self): """Absolute path to the default place to store the test results.""" - # Results are store relative to the built products to make it easy - # to have multiple copies of webkit checked out and built. - return self._build_path('layout-test-results') + try: + return self.path_from_chromium_base('webkit', self.get_option('configuration'), 'layout-test-results') + except AssertionError: + return self._build_path('layout-test-results') def setup_test_run(self): """Perform port-specific work at the beginning of a test run.""" - pass + # Delete the disk cache if any to ensure a clean test run. + dump_render_tree_binary_path = self._path_to_driver() + cachedir = self._filesystem.dirname(dump_render_tree_binary_path) + cachedir = self._filesystem.join(cachedir, "cache") + if self._filesystem.exists(cachedir): + self._filesystem.rmtree(cachedir) + + if self._dump_reader: + self._filesystem.maybe_make_directory(self._dump_reader.crash_dumps_directory()) + + def num_workers(self, requested_num_workers): + """Returns the number of available workers (possibly less than the number requested).""" + return requested_num_workers def clean_up_test_run(self): """Perform port-specific work at the end of a test run.""" @@ -880,6 +1019,8 @@ class Port(object): 'CHROME_DEVEL_SANDBOX', 'CHROME_IPC_LOGGING', 'ASAN_OPTIONS', + 'VALGRIND_LIB', + 'VALGRIND_LIB_INNER', ] if self.host.platform.is_linux() or self.host.platform.is_freebsd(): variables_to_copy += [ @@ -900,6 +1041,7 @@ class Port(object): if self.host.platform.is_win(): variables_to_copy += [ 'PATH', + 'GYP_DEFINES', # Required to locate win sdk. ] if self.host.platform.is_cygwin(): variables_to_copy += [ @@ -930,7 +1072,15 @@ class Port(object): """If a port needs to reconfigure graphics settings or do other things to ensure a known test configuration, it should override this method.""" - pass + helper_path = self._path_to_helper() + if helper_path: + _log.debug("Starting layout helper %s" % helper_path) + # Note: Not thread safe: http://bugs.python.org/issue2320 + self._helper = self._executive.popen([helper_path], + stdin=self._executive.PIPE, stdout=self._executive.PIPE, stderr=None) + is_ready = self._helper.stdout.readline() + if not is_ready.startswith('ready'): + _log.error("layout_test_helper failed to be ready") def requires_http_server(self): """Does the port require an HTTP server for running tests? This could @@ -972,7 +1122,16 @@ class Port(object): """Shut down the test helper if it is running. Do nothing if it isn't, or it isn't available. If a port overrides start_helper() it must override this routine as well.""" - pass + if self._helper: + _log.debug("Stopping layout test helper") + try: + self._helper.stdin.write("x\n") + self._helper.stdin.close() + self._helper.wait() + except IOError, e: + pass + finally: + self._helper = None def stop_http_server(self): """Shut down the http server if it is running. Do nothing if it isn't.""" @@ -1013,7 +1172,7 @@ class Port(object): Returns a dictionary, each key representing a macro term ('win', for example), and value being a list of valid configuration specifiers (such as ['xp', 'vista', 'win7']).""" - return {} + return self.CONFIGURATION_SPECIFIER_MACROS def all_baseline_variants(self): """Returns a list of platform names sufficient to cover all the baselines. @@ -1021,7 +1180,41 @@ class Port(object): The list should be sorted so that a later platform will reuse an earlier platform's baselines if they are the same (e.g., 'snowleopard' should precede 'leopard').""" - raise NotImplementedError + return self.ALL_BASELINE_VARIANTS + + def _generate_all_test_configurations(self): + """Returns a sequence of the TestConfigurations the port supports.""" + # By default, we assume we want to test every graphics type in + # every configuration on every system. + test_configurations = [] + for version, architecture in self.ALL_SYSTEMS: + for build_type in self.ALL_BUILD_TYPES: + test_configurations.append(TestConfiguration(version, architecture, build_type)) + return test_configurations + + try_builder_names = frozenset([ + 'linux_layout', + 'mac_layout', + 'win_layout', + 'linux_layout_rel', + 'mac_layout_rel', + 'win_layout_rel', + ]) + + def warn_if_bug_missing_in_test_expectations(self): + return True + + def _port_specific_expectations_files(self): + paths = [] + paths.append(self.path_from_chromium_base('skia', 'skia_test_expectations.txt')) + paths.append(self._filesystem.join(self.layout_tests_dir(), 'NeverFixTests')) + paths.append(self._filesystem.join(self.layout_tests_dir(), 'StaleTestExpectations')) + paths.append(self._filesystem.join(self.layout_tests_dir(), 'SlowTests')) + + builder_name = self.get_option('builder_name', 'DUMMY_BUILDER_NAME') + if builder_name == 'DUMMY_BUILDER_NAME' or '(deps)' in builder_name or builder_name in self.try_builder_names: + paths.append(self.path_from_chromium_base('webkit', 'tools', 'layout_tests', 'test_expectations.txt')) + return paths def expectations_dict(self): """Returns an OrderedDict of name -> expectations strings. @@ -1067,29 +1260,13 @@ class Port(object): _log.warning("Unexpected ignore mode: '%s'." % ignore_mode) return {} - def _port_specific_expectations_files(self): - # Unlike baseline_search_path, we only want to search [WK2-PORT, PORT-VERSION, PORT] and any directories - # included via --additional-platform-directory, not the full casade. - search_paths = [self.port_name] - - non_wk2_name = self.name().replace('-wk2', '') - if non_wk2_name != self.port_name: - search_paths.append(non_wk2_name) - - search_paths.extend(self.get_option("additional_platform_directory", [])) - - return [self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in search_paths] - def expectations_files(self): return [self.path_to_generic_test_expectations_file()] + self._port_specific_expectations_files() def repository_paths(self): - """Returns a list of (repository_name, repository_path) tuples of its depending code base. - By default it returns a list that only contains a ('WebKit', <webkitRepositoryPath>) tuple.""" - - # We use LayoutTest directory here because webkit_base isn't a part of WebKit repository in Chromium port - # where turnk isn't checked out as a whole. - return [('blink', self.layout_tests_dir())] + """Returns a list of (repository_name, repository_path) tuples of its depending code base.""" + return [('blink', self.layout_tests_dir()), + ('chromium', self.path_from_chromium_base('build'))] _WDIFF_DEL = '##WDIFF_DEL##' _WDIFF_ADD = '##WDIFF_ADD##' @@ -1182,6 +1359,9 @@ class Port(object): def default_configuration(self): return self._config.default_configuration() + def clobber_old_port_specific_results(self): + pass + # # PROTECTED ROUTINES # @@ -1219,7 +1399,7 @@ class Port(object): if self._is_redhat_based(): return 'fedora-httpd-' + self._apache_version() + '.conf' if self._is_debian_based(): - return 'apache2-debian-httpd.conf' + return 'debian-httpd-' + self._apache_version() + '.conf' # All platforms use apache2 except for CYGWIN (and Mac OS X Tiger and prior, which we no longer support). return "apache2-httpd.conf" @@ -1239,14 +1419,6 @@ class Port(object): config_file_name = self._apache_config_file_name_for_platform(sys.platform) return self._filesystem.join(self.layout_tests_dir(), 'http', 'conf', config_file_name) - def _build_path(self, *comps): - build_directory = self.get_option('build_directory') - if build_directory: - root_directory = self._filesystem.join(build_directory, self.get_option('configuration')) - else: - root_directory = self._config.build_directory(self.get_option('configuration')) - return self._filesystem.join(self._filesystem.abspath(root_directory), *comps) - def _path_to_driver(self, configuration=None): """Returns the full path to the test driver.""" return self._build_path(self.driver_name()) @@ -1302,17 +1474,21 @@ class Port(object): given platform.""" return self._filesystem.join(self.layout_tests_dir(), 'platform', platform) - # FIXME: Belongs on a Platform object. - def _generate_all_test_configurations(self): - """Generates a list of TestConfiguration instances, representing configurations - for a platform across all OSes, architectures, build and graphics types.""" - raise NotImplementedError('Port._generate_all_test_configurations') - def _driver_class(self): """Returns the port's driver implementation.""" return driver.Driver def _get_crash_log(self, name, pid, stdout, stderr, newer_than): + if stderr and 'AddressSanitizer' in stderr: + # Running the AddressSanitizer take a lot of memory, so we need to + # serialize access to it across all the concurrently running drivers. + + # FIXME: investigate using LLVM_SYMBOLIZER_PATH here to reduce the overhead. + asan_filter_path = self.path_from_chromium_base('tools', 'valgrind', 'asan', 'asan_symbolize.py') + if self._filesystem.exists(asan_filter_path): + output = self._executive.run_command(['flock', sys.executable, asan_filter_path], input=stderr, decode_output=False) + stderr = self._executive.run_command(['c++filt'], input=output, decode_output=False) + name_str = name or '<unknown process name>' pid_str = str(pid or '<unknown>') stdout_lines = (stdout or '<empty>').decode('utf8', 'replace').splitlines() @@ -1331,7 +1507,76 @@ class Port(object): pass def virtual_test_suites(self): - return [] + return [ + VirtualTestSuite('gpu', + 'fast/canvas', + ['--enable-accelerated-2d-canvas']), + VirtualTestSuite('gpu', + 'canvas/philip', + ['--enable-accelerated-2d-canvas']), + VirtualTestSuite('threaded', + 'compositing/visibility', + ['--enable-threaded-compositing']), + VirtualTestSuite('threaded', + 'compositing/webgl', + ['--enable-threaded-compositing']), + VirtualTestSuite('gpu', + 'fast/hidpi', + ['--force-compositing-mode']), + VirtualTestSuite('softwarecompositing', + 'compositing', + ['--enable-software-compositing', '--disable-gpu-compositing'], + use_legacy_naming=True), + VirtualTestSuite('deferred', + 'fast/images', + ['--enable-deferred-image-decoding', '--enable-per-tile-painting', '--force-compositing-mode']), + VirtualTestSuite('deferred', + 'inspector/timeline', + ['--enable-deferred-image-decoding', '--enable-per-tile-painting', '--force-compositing-mode']), + VirtualTestSuite('gpu/compositedscrolling/overflow', + 'compositing/overflow', + ['--enable-accelerated-overflow-scroll'], + use_legacy_naming=True), + VirtualTestSuite('gpu/compositedscrolling/scrollbars', + 'scrollbars', + ['--enable-accelerated-overflow-scroll'], + use_legacy_naming=True), + VirtualTestSuite('threaded', + 'animations', + ['--enable-threaded-compositing']), + VirtualTestSuite('threaded', + 'transitions', + ['--enable-threaded-compositing']), + VirtualTestSuite('legacy-animations-engine', + 'animations', + ['--disable-web-animations-css']), + VirtualTestSuite('legacy-animations-engine', + 'transitions', + ['--disable-web-animations-css']), + VirtualTestSuite('stable', + 'webexposed', + ['--stable-release-mode']), + VirtualTestSuite('stable', + 'media/stable', + ['--stable-release-mode']), + VirtualTestSuite('android', + 'fullscreen', + ['--force-compositing-mode', '--allow-webui-compositing', '--enable-threaded-compositing', + '--enable-fixed-position-compositing', '--enable-accelerated-overflow-scroll', '--enable-accelerated-scrollable-frames', + '--enable-composited-scrolling-for-frames', '--enable-gesture-tap-highlight', '--enable-pinch', + '--enable-overlay-fullscreen-video', '--enable-overlay-scrollbars', '--enable-overscroll-notifications', + '--enable-fixed-layout', '--enable-viewport', '--disable-canvas-aa', + '--disable-composited-antialiasing']), + VirtualTestSuite('implsidepainting', + 'inspector/timeline', + ['--enable-threaded-compositing', '--enable-impl-side-painting', '--force-compositing-mode']), + VirtualTestSuite('fasttextautosizing', + 'fast/text-autosizing', + ['--enable-fast-text-autosizing']), + VirtualTestSuite('serviceworker', + 'http/tests/serviceworker', + ['--enable-service-worker']), + ] @memoized def populated_virtual_test_suites(self): @@ -1445,10 +1690,53 @@ class Port(object): return cygpath(path) return path + def _build_path(self, *comps): + return self._build_path_with_configuration(None, *comps) + + def _build_path_with_configuration(self, configuration, *comps): + # Note that we don't do the option caching that the + # base class does, because finding the right directory is relatively + # fast. + configuration = configuration or self.get_option('configuration') + return self._static_build_path(self._filesystem, self.get_option('build_directory'), + self.path_from_chromium_base(), configuration, comps) + + def _check_driver_build_up_to_date(self, configuration): + if configuration in ('Debug', 'Release'): + try: + debug_path = self._path_to_driver('Debug') + release_path = self._path_to_driver('Release') + + debug_mtime = self._filesystem.mtime(debug_path) + release_mtime = self._filesystem.mtime(release_path) + + if (debug_mtime > release_mtime and configuration == 'Release' or + release_mtime > debug_mtime and configuration == 'Debug'): + most_recent_binary = 'Release' if configuration == 'Debug' else 'Debug' + _log.warning('You are running the %s binary. However the %s binary appears to be more recent. ' + 'Please pass --%s.', configuration, most_recent_binary, most_recent_binary.lower()) + _log.warning('') + # This will fail if we don't have both a debug and release binary. + # That's fine because, in this case, we must already be running the + # most up-to-date one. + except OSError: + pass + return True + + def _chromium_baseline_path(self, platform): + if platform is None: + platform = self.name() + return self.path_from_webkit_base('LayoutTests', 'platform', platform) class VirtualTestSuite(object): - def __init__(self, name, base, args, tests=None): - self.name = name + def __init__(self, name, base, args, use_legacy_naming=False, tests=None): + if use_legacy_naming: + self.name = 'virtual/' + name + else: + if name.find('/') != -1: + _log.error("Virtual test suites names cannot contain /'s: %s" % name) + return + self.name = 'virtual/' + name + '/' + base self.base = base self.args = args self.tests = tests or set() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py index 5e2718d4a23..3778c04466f 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/base_unittest.py @@ -43,6 +43,7 @@ from webkitpy.common.system.executive_mock import MockExecutive, MockExecutive2 from webkitpy.common.system.systemhost_mock import MockSystemHost from webkitpy.layout_tests.port import Port, Driver, DriverOutput +from webkitpy.layout_tests.port.base import VirtualTestSuite from webkitpy.layout_tests.port.test import add_unit_tests_to_mock_filesystem, TestPort class PortTest(unittest.TestCase): @@ -214,14 +215,14 @@ class PortTest(unittest.TestCase): def test_nonexistant_expectations(self): port = self.make_port(port_name='foo') - port.expectations_files = lambda: ['/mock-checkout/LayoutTests/platform/exists/TestExpectations', '/mock-checkout/LayoutTests/platform/nonexistant/TestExpectations'] - port._filesystem.write_text_file('/mock-checkout/LayoutTests/platform/exists/TestExpectations', '') - self.assertEqual('\n'.join(port.expectations_dict().keys()), '/mock-checkout/LayoutTests/platform/exists/TestExpectations') + port.expectations_files = lambda: ['/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations', '/mock-checkout/third_party/WebKit/LayoutTests/platform/nonexistant/TestExpectations'] + port._filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations', '') + self.assertEqual('\n'.join(port.expectations_dict().keys()), '/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations') def test_additional_expectations(self): port = self.make_port(port_name='foo') port.port_name = 'foo' - port._filesystem.write_text_file('/mock-checkout/LayoutTests/platform/foo/TestExpectations', '') + port._filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/TestExpectations', '') port._filesystem.write_text_file( '/tmp/additional-expectations-1.txt', 'content1\n') port._filesystem.write_text_file( @@ -231,15 +232,15 @@ class PortTest(unittest.TestCase): port._options.additional_expectations = [ '/tmp/additional-expectations-1.txt'] - self.assertEqual('\n'.join(port.expectations_dict().values()), '\ncontent1\n') + self.assertEqual('\n'.join(port.expectations_dict().values()), 'content1\n') port._options.additional_expectations = [ '/tmp/nonexistent-file', '/tmp/additional-expectations-1.txt'] - self.assertEqual('\n'.join(port.expectations_dict().values()), '\ncontent1\n') + self.assertEqual('\n'.join(port.expectations_dict().values()), 'content1\n') port._options.additional_expectations = [ '/tmp/additional-expectations-1.txt', '/tmp/additional-expectations-2.txt'] - self.assertEqual('\n'.join(port.expectations_dict().values()), '\ncontent1\n\ncontent2\n') + self.assertEqual('\n'.join(port.expectations_dict().values()), 'content1\n\ncontent2\n') def test_additional_env_var(self): port = self.make_port(options=optparse.Values({'additional_env_var': ['FOO=BAR', 'BAR=FOO']})) @@ -444,3 +445,23 @@ class KeyCompareTest(unittest.TestCase): self.assert_cmp('/ab', '/a/a/b', -1) self.assert_cmp('/a/a/b', '/ab', 1) self.assert_cmp('/foo-bar/baz', '/foo/baz', -1) + + +class VirtualTestSuiteTest(unittest.TestCase): + def test_basic(self): + suite = VirtualTestSuite('suite', 'base/foo', ['--args']) + self.assertEqual(suite.name, 'virtual/suite/base/foo') + self.assertEqual(suite.base, 'base/foo') + self.assertEqual(suite.args, ['--args']) + + def test_no_slash(self): + suite = VirtualTestSuite('suite/bar', 'base/foo', ['--args']) + self.assertFalse(hasattr(suite, 'name')) + self.assertFalse(hasattr(suite, 'base')) + self.assertFalse(hasattr(suite, 'args')) + + def test_legacy(self): + suite = VirtualTestSuite('suite/bar', 'base/foo', ['--args'], use_legacy_naming=True) + self.assertEqual(suite.name, 'virtual/suite/bar') + self.assertEqual(suite.base, 'base/foo') + self.assertEqual(suite.args, ['--args']) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/builders.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/builders.py index c07c82b791d..47439ea97fe 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/builders.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/builders.py @@ -51,6 +51,7 @@ _exact_matches = { "WebKit Mac10.7 (dbg)": {"port_name": "mac-lion"}, "WebKit Mac10.8": {"port_name": "mac-mountainlion"}, "WebKit Mac10.8 (retina)": {"port_name": "mac-retina"}, + "WebKit Mac10.9": {"port_name": "mac-mavericks"}, "WebKit Android (Nexus4)": {"port_name": "android"}, } @@ -64,6 +65,7 @@ _deps_builders = { "mac-snowleopard": "WebKit Mac10.6 (deps)", "mac-lion": "WebKit Mac10.6 (deps)", "mac-mountainlion": "WebKit Mac10.6 (deps)", + "mac-mavericks": "WebKit Mac10.6 (deps)", "mac-retina": "WebKit Mac10.6 (deps)", } diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium.py deleted file mode 100644 index 68286acc06d..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium.py +++ /dev/null @@ -1,422 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Chromium implementations of the Port interface.""" - -import base64 -import errno -import logging -import re -import signal -import subprocess -import sys -import time - -from webkitpy.common.system import executive -from webkitpy.layout_tests.models.test_configuration import TestConfiguration -from webkitpy.layout_tests.port.base import Port, VirtualTestSuite - - -_log = logging.getLogger(__name__) - - -class ChromiumPort(Port): - """Abstract base class for Chromium implementations of the Port class.""" - - ALL_SYSTEMS = ( - ('snowleopard', 'x86'), - ('lion', 'x86'), - - # FIXME: We treat Retina (High-DPI) devices as if they are running - # a different operating system version. This isn't accurate, but will work until - # we need to test and support baselines across multiple O/S versions. - ('retina', 'x86'), - - ('mountainlion', 'x86'), - ('xp', 'x86'), - ('win7', 'x86'), - ('lucid', 'x86'), - ('lucid', 'x86_64'), - # FIXME: Technically this should be 'arm', but adding a third architecture type breaks TestConfigurationConverter. - # If we need this to be 'arm' in the future, then we first have to fix TestConfigurationConverter. - ('icecreamsandwich', 'x86'), - ) - - ALL_BASELINE_VARIANTS = [ - 'mac-mountainlion', 'mac-retina', 'mac-lion', 'mac-snowleopard', - 'win-win7', 'win-xp', - 'linux-x86_64', 'linux-x86', - ] - - CONFIGURATION_SPECIFIER_MACROS = { - 'mac': ['snowleopard', 'lion', 'retina', 'mountainlion'], - 'win': ['xp', 'win7'], - 'linux': ['lucid'], - 'android': ['icecreamsandwich'], - } - - DEFAULT_BUILD_DIRECTORIES = ('out',) - - # overridden in subclasses. - FALLBACK_PATHS = {} - - @classmethod - def latest_platform_fallback_path(cls): - return cls.FALLBACK_PATHS[cls.SUPPORTED_VERSIONS[-1]] - - @classmethod - def _static_build_path(cls, filesystem, build_directory, chromium_base, webkit_base, configuration, comps): - if build_directory: - return filesystem.join(build_directory, configuration, *comps) - - hits = [] - for directory in cls.DEFAULT_BUILD_DIRECTORIES: - base_dir = filesystem.join(chromium_base, directory, configuration) - path = filesystem.join(base_dir, *comps) - if filesystem.exists(path): - hits.append((filesystem.mtime(path), path)) - - for directory in cls.DEFAULT_BUILD_DIRECTORIES: - base_dir = filesystem.join(webkit_base, directory, configuration) - path = filesystem.join(base_dir, *comps) - if filesystem.exists(path): - hits.append((filesystem.mtime(path), path)) - - if hits: - hits.sort(reverse=True) - return hits[0][1] # Return the newest file found. - - # We have to default to something, so pick the last one. - return filesystem.join(base_dir, *comps) - - @classmethod - def _chromium_base_dir(cls, filesystem): - module_path = filesystem.path_to_module(cls.__module__) - offset = module_path.find('third_party') - if offset == -1: - return filesystem.join(module_path[0:module_path.find('Tools')], 'Source', 'WebKit', 'chromium') - else: - return module_path[0:offset] - - def __init__(self, host, port_name, **kwargs): - super(ChromiumPort, self).__init__(host, port_name, **kwargs) - # All sub-classes override this, but we need an initial value for testing. - self._chromium_base_dir_path = None - - def is_chromium(self): - return True - - def default_max_locked_shards(self): - """Return the number of "locked" shards to run in parallel (like the http tests).""" - max_locked_shards = int(self.default_child_processes()) / 4 - if not max_locked_shards: - return 1 - return max_locked_shards - - def default_pixel_tests(self): - return True - - def default_baseline_search_path(self): - return map(self._webkit_baseline_path, self.FALLBACK_PATHS[self.version()]) - - 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_build(self, needs_http, printer): - result = True - - dump_render_tree_binary_path = self._path_to_driver() - result = self._check_file_exists(dump_render_tree_binary_path, - 'test driver') and result - if result and self.get_option('build'): - result = self._check_driver_build_up_to_date( - self.get_option('configuration')) - else: - _log.error('') - - helper_path = self._path_to_helper() - if helper_path: - result = self._check_file_exists(helper_path, - 'layout test helper') and result - - if self.get_option('pixel_tests'): - result = self.check_image_diff( - 'To override, invoke with --no-pixel-tests') and result - - # It's okay if pretty patch and wdiff aren't available, but we will at least log messages. - self._pretty_patch_available = self.check_pretty_patch() - self._wdiff_available = self.check_wdiff() - - return result - - def check_sys_deps(self, needs_http): - result = super(ChromiumPort, self).check_sys_deps(needs_http) - - cmd = [self._path_to_driver(), '--check-layout-test-sys-deps'] - - local_error = executive.ScriptError() - - def error_handler(script_error): - local_error.exit_code = script_error.exit_code - - output = self._executive.run_command(cmd, error_handler=error_handler) - if local_error.exit_code: - _log.error('System dependencies check failed.') - _log.error('To override, invoke with --nocheck-sys-deps') - _log.error('') - _log.error(output) - return False - return result - - def path_from_chromium_base(self, *comps): - """Returns the full path to path made by joining the top of the - Chromium source tree and the list of path components in |*comps|.""" - if self._chromium_base_dir_path is None: - self._chromium_base_dir_path = self._chromium_base_dir(self._filesystem) - return self._filesystem.join(self._chromium_base_dir_path, *comps) - - def setup_environ_for_server(self, server_name=None): - clean_env = super(ChromiumPort, self).setup_environ_for_server(server_name) - # Webkit Linux (valgrind layout) bot needs these envvars. - self._copy_value_from_environ_if_set(clean_env, 'VALGRIND_LIB') - self._copy_value_from_environ_if_set(clean_env, 'VALGRIND_LIB_INNER') - return clean_env - - def default_results_directory(self): - try: - return self.path_from_chromium_base('webkit', self.get_option('configuration'), 'layout-test-results') - except AssertionError: - return self._build_path('layout-test-results') - - def setup_test_run(self): - super(ChromiumPort, self).setup_test_run() - # Delete the disk cache if any to ensure a clean test run. - dump_render_tree_binary_path = self._path_to_driver() - cachedir = self._filesystem.dirname(dump_render_tree_binary_path) - cachedir = self._filesystem.join(cachedir, "cache") - if self._filesystem.exists(cachedir): - self._filesystem.rmtree(cachedir) - - def start_helper(self): - helper_path = self._path_to_helper() - if helper_path: - _log.debug("Starting layout helper %s" % helper_path) - # Note: Not thread safe: http://bugs.python.org/issue2320 - self._helper = subprocess.Popen([helper_path], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None) - is_ready = self._helper.stdout.readline() - if not is_ready.startswith('ready'): - _log.error("layout_test_helper failed to be ready") - - def stop_helper(self): - if self._helper: - _log.debug("Stopping layout test helper") - try: - self._helper.stdin.write("x\n") - self._helper.stdin.close() - self._helper.wait() - except IOError, e: - pass - finally: - self._helper = None - - def configuration_specifier_macros(self): - return self.CONFIGURATION_SPECIFIER_MACROS - - def all_baseline_variants(self): - return self.ALL_BASELINE_VARIANTS - - def _generate_all_test_configurations(self): - """Returns a sequence of the TestConfigurations the port supports.""" - # By default, we assume we want to test every graphics type in - # every configuration on every system. - test_configurations = [] - for version, architecture in self.ALL_SYSTEMS: - for build_type in self.ALL_BUILD_TYPES: - test_configurations.append(TestConfiguration(version, architecture, build_type)) - return test_configurations - - try_builder_names = frozenset([ - 'linux_layout', - 'mac_layout', - 'win_layout', - 'linux_layout_rel', - 'mac_layout_rel', - 'win_layout_rel', - ]) - - def warn_if_bug_missing_in_test_expectations(self): - return True - - def _port_specific_expectations_files(self): - paths = [] - paths.append(self.path_from_chromium_base('skia', 'skia_test_expectations.txt')) - paths.append(self._filesystem.join(self.layout_tests_dir(), 'NeverFixTests')) - paths.append(self._filesystem.join(self.layout_tests_dir(), 'SlowTests')) - - builder_name = self.get_option('builder_name', 'DUMMY_BUILDER_NAME') - if builder_name == 'DUMMY_BUILDER_NAME' or '(deps)' in builder_name or builder_name in self.try_builder_names: - paths.append(self.path_from_chromium_base('webkit', 'tools', 'layout_tests', 'test_expectations.txt')) - return paths - - def repository_paths(self): - repos = super(ChromiumPort, self).repository_paths() - repos.append(('chromium', self.path_from_chromium_base('build'))) - return repos - - def _get_crash_log(self, name, pid, stdout, stderr, newer_than): - if stderr and 'AddressSanitizer' in stderr: - # Running the AddressSanitizer take a lot of memory, so we need to - # serialize access to it across all the concurrently running drivers. - - # FIXME: investigate using LLVM_SYMBOLIZER_PATH here to reduce the overhead. - asan_filter_path = self.path_from_chromium_base('tools', 'valgrind', 'asan', 'asan_symbolize.py') - if self._filesystem.exists(asan_filter_path): - output = self._executive.run_command(['flock', sys.executable, asan_filter_path], input=stderr, decode_output=False) - stderr = self._executive.run_command(['c++filt'], input=output, decode_output=False) - - return super(ChromiumPort, self)._get_crash_log(name, pid, stdout, stderr, newer_than) - - def virtual_test_suites(self): - return [ - VirtualTestSuite('virtual/gpu/fast/canvas', - 'fast/canvas', - ['--enable-accelerated-2d-canvas']), - VirtualTestSuite('virtual/gpu/canvas/philip', - 'canvas/philip', - ['--enable-accelerated-2d-canvas']), - VirtualTestSuite('virtual/threaded/compositing/visibility', - 'compositing/visibility', - ['--enable-threaded-compositing']), - VirtualTestSuite('virtual/threaded/compositing/webgl', - 'compositing/webgl', - ['--enable-threaded-compositing']), - VirtualTestSuite('virtual/gpu/fast/hidpi', - 'fast/hidpi', - ['--force-compositing-mode']), - VirtualTestSuite('virtual/softwarecompositing', - 'compositing', - ['--enable-software-compositing', '--disable-gpu-compositing']), - VirtualTestSuite('virtual/deferred/fast/images', - 'fast/images', - ['--enable-deferred-image-decoding', '--enable-per-tile-painting', '--force-compositing-mode']), - VirtualTestSuite('virtual/gpu/compositedscrolling/overflow', - 'compositing/overflow', - ['--enable-accelerated-overflow-scroll']), - VirtualTestSuite('virtual/gpu/compositedscrolling/scrollbars', - 'scrollbars', - ['--enable-accelerated-overflow-scroll']), - VirtualTestSuite('virtual/threaded/animations', - 'animations', - ['--enable-threaded-compositing']), - VirtualTestSuite('virtual/threaded/transitions', - 'transitions', - ['--enable-threaded-compositing']), - VirtualTestSuite('virtual/web-animations-css/animations', - 'animations', - ['--enable-web-animations-css']), - VirtualTestSuite('virtual/stable/webexposed', - 'webexposed', - ['--stable-release-mode']), - VirtualTestSuite('virtual/stable/media', - 'media/stable', - ['--stable-release-mode']), - VirtualTestSuite('virtual/android/fullscreen', - 'fullscreen', - ['--force-compositing-mode', '--allow-webui-compositing', '--enable-threaded-compositing', - '--enable-fixed-position-compositing', '--enable-accelerated-overflow-scroll', '--enable-accelerated-scrollable-frames', - '--enable-composited-scrolling-for-frames', '--enable-gesture-tap-highlight', '--enable-pinch', - '--enable-overlay-fullscreen-video', '--enable-overlay-scrollbars', '--enable-overscroll-notifications', - '--enable-fixed-layout', '--enable-viewport', '--disable-canvas-aa', - '--disable-composited-antialiasing']), - ] - - # - # PROTECTED METHODS - # - # These routines should only be called by other methods in this file - # or any subclasses. - # - - def _build_path(self, *comps): - return self._build_path_with_configuration(None, *comps) - - def _build_path_with_configuration(self, configuration, *comps): - # Note that we don't do the option caching that the - # base class does, because finding the right directory is relatively - # fast. - configuration = configuration or self.get_option('configuration') - return self._static_build_path(self._filesystem, self.get_option('build_directory'), - self.path_from_chromium_base(), self.path_from_webkit_base(), configuration, comps) - - def _path_to_image_diff(self): - binary_name = 'image_diff' - return self._build_path(binary_name) - - def _check_driver_build_up_to_date(self, configuration): - if configuration in ('Debug', 'Release'): - try: - debug_path = self._path_to_driver('Debug') - release_path = self._path_to_driver('Release') - - debug_mtime = self._filesystem.mtime(debug_path) - release_mtime = self._filesystem.mtime(release_path) - - if (debug_mtime > release_mtime and configuration == 'Release' or - release_mtime > debug_mtime and configuration == 'Debug'): - most_recent_binary = 'Release' if configuration == 'Debug' else 'Debug' - _log.warning('You are running the %s binary. However the %s binary appears to be more recent. ' - 'Please pass --%s.', configuration, most_recent_binary, most_recent_binary.lower()) - _log.warning('') - # This will fail if we don't have both a debug and release binary. - # That's fine because, in this case, we must already be running the - # most up-to-date one. - except OSError: - pass - return True - - def _chromium_baseline_path(self, platform): - if platform is None: - platform = self.name() - return self.path_from_webkit_base('LayoutTests', 'platform', platform) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py deleted file mode 100644 index ade4233fbb9..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium_port_testcase.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.common.system import logtesting -from webkitpy.common.system.executive_mock import MockExecutive2 -from webkitpy.common.system.systemhost_mock import MockSystemHost -from webkitpy.tool.mocktool import MockOptions - -import android -import linux -import mac -import win - -from webkitpy.layout_tests.models.test_configuration import TestConfiguration -from webkitpy.layout_tests.port import port_testcase - - -class FakePrinter(object): - def write_update(self, msg): - pass - - def write_throttled_update(self, msg): - pass - - -class ChromiumPortTestCase(port_testcase.PortTestCase): - - def test_check_build(self): - port = self.make_port() - port.check_build(needs_http=True, printer=FakePrinter()) - - def test_default_max_locked_shards(self): - port = self.make_port() - port.default_child_processes = lambda: 16 - self.assertEqual(port.default_max_locked_shards(), 4) - port.default_child_processes = lambda: 2 - self.assertEqual(port.default_max_locked_shards(), 1) - - def test_default_pixel_tests(self): - self.assertEqual(self.make_port().default_pixel_tests(), True) - - def test_missing_symbol_to_skipped_tests(self): - # Test that we get the chromium skips and not the webkit default skips - port = self.make_port() - skip_dict = port._missing_symbol_to_skipped_tests() - if port.PORT_HAS_AUDIO_CODECS_BUILT_IN: - self.assertEqual(skip_dict, {}) - else: - self.assertTrue('ff_mp3_decoder' in skip_dict) - self.assertFalse('WebGLShader' in skip_dict) - - def test_all_test_configurations(self): - """Validate the complete set of configurations this port knows about.""" - port = self.make_port() - self.assertEqual(set(port.all_test_configurations()), set([ - TestConfiguration('snowleopard', 'x86', 'debug'), - TestConfiguration('snowleopard', 'x86', 'release'), - TestConfiguration('lion', 'x86', 'debug'), - TestConfiguration('lion', 'x86', 'release'), - TestConfiguration('retina', 'x86', 'debug'), - TestConfiguration('retina', 'x86', 'release'), - TestConfiguration('mountainlion', 'x86', 'debug'), - TestConfiguration('mountainlion', 'x86', 'release'), - TestConfiguration('xp', 'x86', 'debug'), - TestConfiguration('xp', 'x86', 'release'), - TestConfiguration('win7', 'x86', 'debug'), - TestConfiguration('win7', 'x86', 'release'), - TestConfiguration('lucid', 'x86', 'debug'), - TestConfiguration('lucid', 'x86', 'release'), - TestConfiguration('lucid', 'x86_64', 'debug'), - TestConfiguration('lucid', 'x86_64', 'release'), - TestConfiguration('icecreamsandwich', 'x86', 'debug'), - TestConfiguration('icecreamsandwich', 'x86', 'release'), - ])) - - class TestMacPort(mac.MacPort): - def __init__(self, options=None): - options = options or MockOptions() - mac.MacPort.__init__(self, MockSystemHost(os_name='mac', os_version='leopard'), 'mac-leopard', options=options) - - def default_configuration(self): - self.default_configuration_called = True - return 'default' - - class TestAndroidPort(android.AndroidPort): - def __init__(self, options=None): - options = options or MockOptions() - android.AndroidPort.__init__(self, MockSystemHost(os_name='android', os_version='icecreamsandwich'), 'android', options=options) - - def default_configuration(self): - self.default_configuration_called = True - return 'default' - - class TestLinuxPort(linux.LinuxPort): - def __init__(self, options=None): - options = options or MockOptions() - linux.LinuxPort.__init__(self, MockSystemHost(os_name='linux', os_version='lucid'), 'linux-x86', options=options) - - def default_configuration(self): - self.default_configuration_called = True - return 'default' - - class TestWinPort(win.WinPort): - def __init__(self, options=None): - options = options or MockOptions() - win.WinPort.__init__(self, MockSystemHost(os_name='win', os_version='xp'), 'win-xp', options=options) - - def default_configuration(self): - self.default_configuration_called = True - return 'default' - - def test_default_configuration(self): - mock_options = MockOptions() - port = ChromiumPortTestCase.TestLinuxPort(options=mock_options) - self.assertEqual(mock_options.configuration, 'default') # pylint: disable=E1101 - self.assertTrue(port.default_configuration_called) - - mock_options = MockOptions(configuration=None) - port = ChromiumPortTestCase.TestLinuxPort(mock_options) - self.assertEqual(mock_options.configuration, 'default') # pylint: disable=E1101 - self.assertTrue(port.default_configuration_called) - - def test_diff_image(self): - class TestPort(ChromiumPortTestCase.TestLinuxPort): - def _path_to_image_diff(self): - return "/path/to/image_diff" - - port = ChromiumPortTestCase.TestLinuxPort() - mock_image_diff = "MOCK Image Diff" - - def mock_run_command(args): - port._filesystem.write_binary_file(args[4], mock_image_diff) - return 1 - - # Images are different. - port._executive = MockExecutive2(run_command_fn=mock_run_command) - self.assertEqual(mock_image_diff, port.diff_image("EXPECTED", "ACTUAL")[0]) - - # Images are the same. - port._executive = MockExecutive2(exit_code=0) - self.assertEqual(None, port.diff_image("EXPECTED", "ACTUAL")[0]) - - # There was some error running image_diff. - port._executive = MockExecutive2(exit_code=2) - exception_raised = False - try: - port.diff_image("EXPECTED", "ACTUAL") - except ValueError, e: - exception_raised = True - self.assertFalse(exception_raised) - - def test_diff_image_crashed(self): - port = ChromiumPortTestCase.TestLinuxPort() - port._executive = MockExecutive2(exit_code=2) - self.assertEqual(port.diff_image("EXPECTED", "ACTUAL"), (None, 'image diff returned an exit code of 2')) - - def test_expectations_files(self): - port = self.make_port() - port.port_name = 'chromium' - - generic_path = port.path_to_generic_test_expectations_file() - chromium_overrides_path = port.path_from_chromium_base( - 'webkit', 'tools', 'layout_tests', 'test_expectations.txt') - never_fix_tests_path = port._filesystem.join(port.layout_tests_dir(), 'NeverFixTests') - slow_tests_path = port._filesystem.join(port.layout_tests_dir(), 'SlowTests') - skia_overrides_path = port.path_from_chromium_base( - 'skia', 'skia_test_expectations.txt') - - port._filesystem.write_text_file(skia_overrides_path, 'dummay text') - - port._options.builder_name = 'DUMMY_BUILDER_NAME' - self.assertEqual(port.expectations_files(), [generic_path, skia_overrides_path, never_fix_tests_path, slow_tests_path, chromium_overrides_path]) - - port._options.builder_name = 'builder (deps)' - self.assertEqual(port.expectations_files(), [generic_path, skia_overrides_path, never_fix_tests_path, slow_tests_path, chromium_overrides_path]) - - # A builder which does NOT observe the Chromium test_expectations, - # but still observes the Skia test_expectations... - port._options.builder_name = 'builder' - self.assertEqual(port.expectations_files(), [generic_path, skia_overrides_path, never_fix_tests_path, slow_tests_path]) - - def test_expectations_ordering(self): - # since we don't implement self.port_name in ChromiumPort. - pass - - -class ChromiumPortLoggingTest(logtesting.LoggingTestCase): - def test_check_sys_deps(self): - port = ChromiumPortTestCase.TestLinuxPort() - - # Success - port._executive = MockExecutive2(exit_code=0) - self.assertTrue(port.check_sys_deps(needs_http=False)) - - # Failure - port._executive = MockExecutive2(exit_code=1, - output='testing output failure') - self.assertFalse(port.check_sys_deps(needs_http=False)) - self.assertLog([ - 'ERROR: System dependencies check failed.\n', - 'ERROR: To override, invoke with --nocheck-sys-deps\n', - 'ERROR: \n', - 'ERROR: testing output failure\n']) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py deleted file mode 100644 index 66a791fcf42..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/chromium_unittest.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import StringIO -import time - -from webkitpy.common.system import logtesting -from webkitpy.common.system.executive_mock import MockExecutive2 -from webkitpy.common.system.systemhost_mock import MockSystemHost -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.mocktool import MockOptions - -import chromium - -from webkitpy.layout_tests.port import chromium_port_testcase -from webkitpy.layout_tests.port.driver import DriverInput - - -class ChromiumPortLoggingTest(logtesting.LoggingTestCase): - - # FIXME: put this someplace more useful - def test_check_sys_deps(self): - port = chromium_port_testcase.ChromiumPortTestCase.TestLinuxPort() - - # Success - port._executive = MockExecutive2(exit_code=0) - self.assertTrue(port.check_sys_deps(needs_http=False)) - - # Failure - port._executive = MockExecutive2(exit_code=1, - output='testing output failure') - self.assertFalse(port.check_sys_deps(needs_http=False)) - self.assertLog([ - 'ERROR: System dependencies check failed.\n', - 'ERROR: To override, invoke with --nocheck-sys-deps\n', - 'ERROR: \n', - 'ERROR: testing output failure\n']) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver.py index ddbb3db11f0..e7a439d04ba 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver.py @@ -42,13 +42,16 @@ from webkitpy.common.system.profiler import ProfilerFactory _log = logging.getLogger(__name__) +DRIVER_START_TIMEOUT_SECS = 30 + + class DriverInput(object): - def __init__(self, test_name, timeout, image_hash, should_run_pixel_test, args=None): + def __init__(self, test_name, timeout, image_hash, should_run_pixel_test, args): self.test_name = test_name self.timeout = timeout # in ms self.image_hash = image_hash self.should_run_pixel_test = should_run_pixel_test - self.args = args or [] + self.args = args class DriverOutput(object): @@ -78,6 +81,10 @@ class DriverOutput(object): return bool(self.error) +class DeviceFailure(Exception): + pass + + class Driver(object): """object for running test(s) using content_shell or other driver.""" @@ -135,13 +142,6 @@ class Driver(object): Returns a DriverOutput object. """ - base = self._port.lookup_virtual_test_base(driver_input.test_name) - if base: - virtual_driver_input = copy.copy(driver_input) - virtual_driver_input.test_name = base - virtual_driver_input.args = self._port.lookup_virtual_test_args(driver_input.test_name) - return self.run_test(virtual_driver_input, stop_when_done) - start_time = time.time() self.start(driver_input.should_run_pixel_test, driver_input.args) test_begin_time = time.time() @@ -260,7 +260,7 @@ class Driver(object): environment = self._profiler.adjusted_environment(environment) return environment - def _start(self, pixel_tests, per_test_args): + def _start(self, pixel_tests, per_test_args, wait_for_ready=True): self.stop() self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % self._port.driver_name()) server_name = self._port.driver_name() @@ -273,6 +273,24 @@ class Driver(object): self._server_process.start() self._current_cmd_line = cmd_line + if wait_for_ready: + deadline = time.time() + DRIVER_START_TIMEOUT_SECS + if not self._wait_for_server_process_output(self._server_process, deadline, '#READY'): + _log.error("content_shell took too long to startup.") + + def _wait_for_server_process_output(self, server_process, deadline, text): + output = '' + line = server_process.read_stdout_line(deadline) + while not server_process.timed_out and not server_process.has_crashed() and not text in line.rstrip(): + output += line + line = server_process.read_stdout_line(deadline) + + if server_process.timed_out or server_process.has_crashed(): + _log.error('Failed to start the %s process: \n%s' % (server_process.name(), output)) + return False + + return True + def _run_post_start_tasks(self): # Remote drivers may override this to delay post-start tasks until the server has ack'd. if self._profiler: @@ -284,7 +302,7 @@ class Driver(object): def stop(self): if self._server_process: - self._server_process.stop(self._port.driver_stop_timeout()) + self._server_process.stop() self._server_process = None if self._profiler: self._profiler.profile_after_exit() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py index b0f4a6b7235..9dd5b255e12 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py @@ -42,7 +42,7 @@ from webkitpy.tool.mocktool import MockOptions class DriverTest(unittest.TestCase): def make_port(self): port = Port(MockSystemHost(), 'test', MockOptions(configuration='Release')) - port._config.build_directory = lambda configuration: '/mock-build' + port._config.build_directory = lambda configuration: '/mock-checkout/out/' + configuration return port def _assert_wrapper(self, wrapper_string, expected_wrapper): @@ -82,6 +82,7 @@ class DriverTest(unittest.TestCase): "#EOF", ]) content_block = driver._read_block(0) + self.assertEqual(content_block.content, '') self.assertEqual(content_block.content_type, 'my_type') self.assertEqual(content_block.encoding, 'none') self.assertEqual(content_block.content_hash, 'foobar') @@ -125,9 +126,9 @@ class DriverTest(unittest.TestCase): def test_no_timeout(self): port = TestWebKitPort() - port._config.build_directory = lambda configuration: '/mock-build' + port._config.build_directory = lambda configuration: '/mock-checkout/out/' + configuration driver = Driver(port, 0, pixel_tests=True, no_timeout=True) - self.assertEqual(driver.cmd_line(True, []), ['/mock-build/content_shell', '--no-timeout', '--dump-render-tree', '-']) + self.assertEqual(driver.cmd_line(True, []), ['/mock-checkout/out/Release/content_shell', '--no-timeout', '--dump-render-tree', '-']) def test_check_for_driver_crash(self): port = TestWebKitPort() @@ -146,7 +147,7 @@ class DriverTest(unittest.TestCase): def has_crashed(self): return self.crashed - def stop(self, timeout): + def stop(self, timeout=0.0): pass def assert_crash(driver, error_line, crashed, name, pid, unresponsive=False): diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py index 741cc0de5b3..d582210680f 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux.py @@ -30,7 +30,9 @@ import logging import re from webkitpy.common.webkit_finder import WebKitFinder -from webkitpy.layout_tests.port import chromium +from webkitpy.layout_tests.breakpad.dump_reader_multipart import DumpReaderLinux +from webkitpy.layout_tests.models import test_run_results +from webkitpy.layout_tests.port import base from webkitpy.layout_tests.port import win from webkitpy.layout_tests.port import config @@ -38,7 +40,7 @@ from webkitpy.layout_tests.port import config _log = logging.getLogger(__name__) -class LinuxPort(chromium.ChromiumPort): +class LinuxPort(base.Port): port_name = 'linux' SUPPORTED_VERSIONS = ('x86', 'x86_64') @@ -46,14 +48,15 @@ class LinuxPort(chromium.ChromiumPort): FALLBACK_PATHS = { 'x86_64': [ 'linux' ] + win.WinPort.latest_platform_fallback_path() } FALLBACK_PATHS['x86'] = ['linux-x86'] + FALLBACK_PATHS['x86_64'] - DEFAULT_BUILD_DIRECTORIES = ('sconsbuild', 'out') + DEFAULT_BUILD_DIRECTORIES = ('out',) @classmethod def _determine_driver_path_statically(cls, host, options): config_object = config.Config(host.executive, host.filesystem) build_directory = getattr(options, 'build_directory', None) - webkit_base = WebKitFinder(host.filesystem).webkit_base() - chromium_base = cls._chromium_base_dir(host.filesystem) + finder = WebKitFinder(host.filesystem) + webkit_base = finder.webkit_base() + chromium_base = finder.chromium_base() driver_name = getattr(options, 'driver_name', None) if driver_name is None: driver_name = cls.CONTENT_SHELL_NAME @@ -61,7 +64,7 @@ class LinuxPort(chromium.ChromiumPort): configuration = options.configuration else: configuration = config_object.default_configuration() - return cls._static_build_path(host.filesystem, build_directory, chromium_base, webkit_base, configuration, [driver_name]) + return cls._static_build_path(host.filesystem, build_directory, chromium_base, configuration, [driver_name]) @staticmethod def _determine_architecture(filesystem, executive, driver_path): @@ -90,19 +93,20 @@ class LinuxPort(chromium.ChromiumPort): return port_name def __init__(self, host, port_name, **kwargs): - chromium.ChromiumPort.__init__(self, host, port_name, **kwargs) + super(LinuxPort, self).__init__(host, port_name, **kwargs) (base, arch) = port_name.rsplit('-', 1) assert base == 'linux' assert arch in self.SUPPORTED_VERSIONS assert port_name in ('linux', 'linux-x86', 'linux-x86_64') self._version = 'lucid' # We only support lucid right now. self._architecture = arch + if not self.get_option('disable_breakpad'): + self._dump_reader = DumpReaderLinux(host, self._build_path()) def additional_drt_flag(self): flags = super(LinuxPort, self).additional_drt_flag() - # FIXME: Temporarily disable the sandbox on Linux until we can get - # stacktraces via breakpad. http://crbug.com/247431 - flags += ['--no-sandbox'] + if not self.get_option('disable_breakpad'): + flags += ['--enable-crash-reporter', '--crash-dumps-dir=%s' % self._dump_reader.crash_dumps_directory()] return flags def default_baseline_search_path(self): @@ -113,16 +117,35 @@ class LinuxPort(chromium.ChromiumPort): return [self._build_path('libffmpegsumo.so')] def check_build(self, needs_http, printer): - result = chromium.ChromiumPort.check_build(self, needs_http, printer) - if not result: + result = super(LinuxPort, self).check_build(needs_http, printer) + + if result: _log.error('For complete Linux build requirements, please see:') _log.error('') _log.error(' http://code.google.com/p/chromium/wiki/LinuxBuildInstructions') return result + def look_for_new_crash_logs(self, crashed_processes, start_time): + if self.get_option('disable_breakpad'): + return None + return self._dump_reader.look_for_new_crash_logs(crashed_processes, start_time) + + def clobber_old_port_specific_results(self): + if not self.get_option('disable_breakpad'): + self._dump_reader.clobber_old_results() + def operating_system(self): return 'linux' + def virtual_test_suites(self): + result = super(LinuxPort, self).virtual_test_suites() + result.extend([ + base.VirtualTestSuite('linux-subpixel', + 'platform/linux/fast/text/subpixel', + ['--enable-webkit-text-subpixel-positioning']), + ]) + return result + # # PROTECTED METHODS # diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py index f8c3ba5e668..d6b7d8bc074 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/linux_unittest.py @@ -33,10 +33,10 @@ from webkitpy.common.system.systemhost_mock import MockSystemHost from webkitpy.tool.mocktool import MockOptions from webkitpy.layout_tests.port import linux -from webkitpy.layout_tests.port import chromium_port_testcase +from webkitpy.layout_tests.port import port_testcase -class LinuxPortTest(chromium_port_testcase.ChromiumPortTestCase): +class LinuxPortTest(port_testcase.PortTestCase): port_name = 'linux' port_maker = linux.LinuxPort @@ -93,15 +93,11 @@ class LinuxPortTest(chromium_port_testcase.ChromiumPortTestCase): def test_build_path(self): # Test that optional paths are used regardless of whether they exist. options = MockOptions(configuration='Release', build_directory='/foo') - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out/Release'], '/foo/Release') + self.assert_build_path(options, ['/mock-checkout/out/Release'], '/foo/Release') # Test that optional relative paths are returned unmodified. options = MockOptions(configuration='Release', build_directory='foo') - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out/Release'], 'foo/Release') - - # Test that we prefer the legacy dir over the new dir. - options = MockOptions(configuration='Release', build_directory=None) - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/sconsbuild/Release', '/mock-checkout/Source/WebKit/chromium/out/Release'], '/mock-checkout/Source/WebKit/chromium/sconsbuild/Release') + self.assert_build_path(options, ['/mock-checkout/out/Release'], 'foo/Release') def test_driver_name_option(self): self.assertTrue(self.make_port()._path_to_driver().endswith('content_shell')) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py index c925584b489..af1c6de62bc 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac.py @@ -31,24 +31,29 @@ import logging import signal -from webkitpy.layout_tests.port import chromium +from webkitpy.layout_tests.port import base _log = logging.getLogger(__name__) -class MacPort(chromium.ChromiumPort): - SUPPORTED_VERSIONS = ('snowleopard', 'lion', 'retina', 'mountainlion') +class MacPort(base.Port): + SUPPORTED_VERSIONS = ('snowleopard', 'lion', 'retina', 'mountainlion', 'mavericks') port_name = 'mac' - FALLBACK_PATHS = { 'mountainlion': [ 'mac' ]} - FALLBACK_PATHS['lion'] = ['mac-lion'] + FALLBACK_PATHS['mountainlion'] - FALLBACK_PATHS['snowleopard'] = ['mac-snowleopard'] + FALLBACK_PATHS['lion'] - # FIXME: We treat Retina (High-DPI) devices as if they are running - # a different operating system version. This isn't accurate, but will work until - # we need to test and support baselines across multiple O/S versions. + # a different operating system version. This is lame and should be fixed. + # Note that the retina versions fallback to the non-retina versions and so no + # baselines are shared between retina versions; this keeps the fallback graph as a tree + # and maximizes the number of baselines we can share that way. + # We also currently only support Retina on 10.8; we need to either upgrade to 10.9 or support both. + + FALLBACK_PATHS = {} + FALLBACK_PATHS['mavericks'] = ['mac'] + FALLBACK_PATHS['mountainlion'] = ['mac-mountainlion'] + FALLBACK_PATHS['mavericks'] FALLBACK_PATHS['retina'] = ['mac-retina'] + FALLBACK_PATHS['mountainlion'] + FALLBACK_PATHS['lion'] = ['mac-lion'] + FALLBACK_PATHS['mountainlion'] + FALLBACK_PATHS['snowleopard'] = ['mac-snowleopard'] + FALLBACK_PATHS['lion'] DEFAULT_BUILD_DIRECTORIES = ('xcodebuild', 'out') @@ -57,13 +62,17 @@ class MacPort(chromium.ChromiumPort): @classmethod def determine_full_port_name(cls, host, options, port_name): if port_name.endswith('mac'): + if host.platform.os_version in ('future',): + version = 'mavericks' + else: + version = host.platform.os_version if host.platform.is_highdpi(): - return "mac-retina" - return port_name + '-' + host.platform.os_version + version = 'retina' + return port_name + '-' + version return port_name def __init__(self, host, port_name, **kwargs): - chromium.ChromiumPort.__init__(self, host, port_name, **kwargs) + super(MacPort, self).__init__(host, port_name, **kwargs) self._version = port_name[port_name.index('mac-') + len('mac-'):] assert self._version in self.SUPPORTED_VERSIONS @@ -71,8 +80,8 @@ class MacPort(chromium.ChromiumPort): return [self._build_path('ffmpegsumo.so')] def check_build(self, needs_http, printer): - result = chromium.ChromiumPort.check_build(self, needs_http, printer) - if not result: + result = super(MacPort, self).check_build(needs_http, printer) + if result: _log.error('For complete Mac build requirements, please see:') _log.error('') _log.error(' http://code.google.com/p/chromium/wiki/MacBuildInstructions') diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py index 225c786350a..abf2b248d58 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py @@ -29,11 +29,11 @@ import webkitpy.thirdparty.unittest2 as unittest from webkitpy.layout_tests.port import mac -from webkitpy.layout_tests.port import chromium_port_testcase +from webkitpy.layout_tests.port import port_testcase from webkitpy.tool.mocktool import MockOptions -class MacPortTest(chromium_port_testcase.ChromiumPortTestCase): +class MacPortTest(port_testcase.PortTestCase): os_name = 'mac' os_version = 'snowleopard' port_name = 'mac' @@ -44,7 +44,7 @@ class MacPortTest(chromium_port_testcase.ChromiumPortTestCase): self.assertEqual(expected, port.name()) def test_versions(self): - self.assertTrue(self.make_port().name() in ('mac-snowleopard', 'mac-lion', 'mac-mountainlion')) + self.assertTrue(self.make_port().name() in ('mac-snowleopard', 'mac-lion', 'mac-mountainlion', 'mac-mavericks')) self.assert_name(None, 'snowleopard', 'mac-snowleopard') self.assert_name('mac', 'snowleopard', 'mac-snowleopard') @@ -53,6 +53,8 @@ class MacPortTest(chromium_port_testcase.ChromiumPortTestCase): self.assert_name(None, 'lion', 'mac-lion') self.assert_name(None, 'mountainlion', 'mac-mountainlion') + self.assert_name(None, 'mavericks', 'mac-mavericks') + self.assert_name(None, 'future', 'mac-mavericks') self.assert_name('mac', 'lion', 'mac-lion') self.assertRaises(AssertionError, self.assert_name, None, 'tiger', 'should-raise-assertion-so-this-value-does-not-matter') @@ -65,6 +67,9 @@ class MacPortTest(chromium_port_testcase.ChromiumPortTestCase): self.assertEqual(port.baseline_path(), port._webkit_baseline_path('mac-lion')) port = self.make_port(port_name='mac-mountainlion') + self.assertEqual(port.baseline_path(), port._webkit_baseline_path('mac-mountainlion')) + + port = self.make_port(port_name='mac-mavericks') self.assertEqual(port.baseline_path(), port._webkit_baseline_path('mac')) def test_operating_system(self): @@ -73,15 +78,15 @@ class MacPortTest(chromium_port_testcase.ChromiumPortTestCase): def test_build_path(self): # Test that optional paths are used regardless of whether they exist. options = MockOptions(configuration='Release', build_directory='/foo') - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out/Release'], '/foo/Release') + self.assert_build_path(options, ['/mock-checkout/out/Release'], '/foo/Release') # Test that optional relative paths are returned unmodified. options = MockOptions(configuration='Release', build_directory='foo') - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out/Release'], 'foo/Release') + self.assert_build_path(options, ['/mock-checkout/out/Release'], 'foo/Release') # Test that we prefer the legacy dir over the new dir. options = MockOptions(configuration='Release', build_directory=None) - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/xcodebuild/Release', '/mock-checkout/Source/WebKit/chromium/out/Release'], '/mock-checkout/Source/WebKit/chromium/xcodebuild/Release') + self.assert_build_path(options, ['/mock-checkout/xcodebuild/Release', '/mock-checkout/out/Release'], '/mock-checkout/xcodebuild/Release') def test_build_path_timestamps(self): options = MockOptions(configuration='Release', build_directory=None) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py index 7992a00a12c..1541fd9098e 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt.py @@ -41,6 +41,7 @@ import logging import optparse import os import sys +import types # Since we execute this script directly as part of the unit tests, we need to ensure # that Tools/Scripts is in sys.path for the next imports to work correctly. @@ -48,6 +49,7 @@ script_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os. if script_dir not in sys.path: sys.path.append(script_dir) +from webkitpy.common import read_checksum_from_png from webkitpy.common.system.systemhost import SystemHost from webkitpy.layout_tests.port.driver import DriverInput, DriverOutput from webkitpy.layout_tests.port.factory import PortFactory @@ -64,6 +66,8 @@ class MockDRTPort(object): def __init__(self, host, port_name, **kwargs): self.__delegate = PortFactory(host).get(port_name.replace('mock-', ''), **kwargs) + self.__delegate_driver_class = self.__delegate._driver_class + self.__delegate._driver_class = types.MethodType(self._driver_class, self.__delegate) def __getattr__(self, name): return getattr(self.__delegate, name) @@ -74,18 +78,17 @@ class MockDRTPort(object): def check_sys_deps(self, needs_http): return True - def _driver_class(self): + def _driver_class(self, delegate): return self._mocked_driver_maker - @staticmethod - def _mocked_driver_maker(port, worker_number, pixel_tests, no_timeout=False): - path_to_this_file = port.host.filesystem.abspath(__file__.replace('.pyc', '.py')) - driver = port.__delegate._driver_class()(port, worker_number, pixel_tests, no_timeout) - driver.cmd_line = port._overriding_cmd_line(driver.cmd_line, - port.__delegate._path_to_driver(), + def _mocked_driver_maker(self, port, worker_number, pixel_tests, no_timeout=False): + path_to_this_file = self.host.filesystem.abspath(__file__.replace('.pyc', '.py')) + driver = self.__delegate_driver_class()(self, worker_number, pixel_tests, no_timeout) + driver.cmd_line = self._overriding_cmd_line(driver.cmd_line, + self.__delegate._path_to_driver(), sys.executable, path_to_this_file, - port.__delegate.name()) + self.__delegate.name()) return driver @staticmethod @@ -122,47 +125,44 @@ class MockDRTPort(object): def release_http_lock(self): pass - def show_results_html_file(self, results_filename): - pass - def _make_wdiff_available(self): self.__delegate._wdiff_available = True + def setup_environ_for_server(self, server_name): + env = self.__delegate.setup_environ_for_server() + # We need to propagate PATH down so the python code can find the checkout. + env['PATH'] = os.environ['PATH'] + return env + + def lookup_virtual_test_args(self, test_name): + suite = self.__delegate.lookup_virtual_suite(test_name) + return suite.args + ['--virtual-test-suite-name', suite.name, '--virtual-test-suite-base', suite.base] def main(argv, host, stdin, stdout, stderr): """Run the tests.""" options, args = parse_options(argv) - if options.test_shell: - drt = MockTestShell(options, args, host, stdin, stdout, stderr) - else: - drt = MockDRT(options, args, host, stdin, stdout, stderr) + drt = MockDRT(options, args, host, stdin, stdout, stderr) return drt.run() def parse_options(argv): - # FIXME: We have to do custom arg parsing instead of using the optparse - # module. First, Chromium and non-Chromium DRTs have a different argument - # syntax. Chromium uses --pixel-tests=<path>, and non-Chromium uses - # --pixel-tests as a boolean flag. Second, we don't want to have to list - # every command line flag DRT accepts, but optparse complains about - # unrecognized flags. At some point it might be good to share a common - # DRT options class between this file and webkit.py and chromium.py - # just to get better type checking. - platform_index = argv.index('--platform') - platform = argv[platform_index + 1] - - pixel_tests = False - pixel_path = None - test_shell = '--test-shell' in argv - if test_shell: - for arg in argv: - if arg.startswith('--pixel-tests'): - pixel_tests = True - pixel_path = arg[len('--pixel-tests='):] - else: - pixel_tests = '--pixel-tests' in argv - options = optparse.Values({'test_shell': test_shell, 'platform': platform, 'pixel_tests': pixel_tests, 'pixel_path': pixel_path}) + # We do custom arg parsing instead of using the optparse module + # because we don't want to have to list every command line flag DRT + # accepts, and optparse complains about unrecognized flags. + + def get_arg(arg_name): + if arg_name in argv: + index = argv.index(arg_name) + return argv[index + 1] + return None + + options = optparse.Values({ + 'actual_directory': get_arg('--actual-directory'), + 'platform': get_arg('--platform'), + 'virtual_test_suite_base': get_arg('--virtual-test-suite-base'), + 'virtual_test_suite_name': get_arg('--virtual-test-suite-name'), + }) return (options, argv) @@ -195,21 +195,28 @@ class MockDRT(object): def input_from_line(self, line): vals = line.strip().split("'") - if len(vals) == 1: - uri = vals[0] - checksum = None - else: - uri = vals[0] - checksum = vals[1] + uri = vals[0] + checksum = None + should_run_pixel_tests = False + if len(vals) == 2 and vals[1] == '--pixel-test': + should_run_pixel_tests = True + elif len(vals) == 3 and vals[1] == '--pixel-test': + should_run_pixel_tests = True + checksum = vals[2] + elif len(vals) != 1: + raise NotImplementedError + if uri.startswith('http://') or uri.startswith('https://'): test_name = self._driver.uri_to_test(uri) else: test_name = self._port.relative_test_filename(uri) - return DriverInput(test_name, 0, checksum, self._options.pixel_tests) + return DriverInput(test_name, 0, checksum, should_run_pixel_tests, args=[]) def output_for_test(self, test_input, is_reftest): port = self._port + if self._options.virtual_test_suite_name: + test_input.test_name = test_input.test_name.replace(self._options.virtual_test_suite_base, self._options.virtual_test_suite_name) actual_text = port.expected_text(test_input.test_name) actual_audio = port.expected_audio(test_input.test_name) actual_image = None @@ -223,10 +230,25 @@ class MockDRT(object): actual_text = 'not reference text\n' actual_checksum = 'not-mock-checksum' actual_image = 'not blank' - elif self._options.pixel_tests and test_input.image_hash: + elif test_input.should_run_pixel_test and test_input.image_hash: actual_checksum = port.expected_checksum(test_input.test_name) actual_image = port.expected_image(test_input.test_name) + if self._options.actual_directory: + actual_path = port._filesystem.join(self._options.actual_directory, test_input.test_name) + root, _ = port._filesystem.splitext(actual_path) + text_path = root + '-actual.txt' + if port._filesystem.exists(text_path): + actual_text = port._filesystem.read_binary_file(text_path) + audio_path = root + '-actual.wav' + if port._filesystem.exists(audio_path): + actual_audio = port._filesystem.read_binary_file(audio_path) + image_path = root + '-actual.png' + if port._filesystem.exists(image_path): + actual_image = port._filesystem.read_binary_file(image_path) + with port._filesystem.open_binary_file_for_reading(image_path) as filehandle: + actual_checksum = read_checksum_from_png.read_checksum(filehandle) + return DriverOutput(actual_text, actual_image, actual_checksum, actual_audio) def write_test_output(self, test_input, output, is_reftest): @@ -234,6 +256,7 @@ class MockDRT(object): self._stdout.write('Content-Type: audio/wav\n') self._stdout.write('Content-Transfer-Encoding: base64\n') self._stdout.write(base64.b64encode(output.audio)) + self._stdout.write('\n') else: self._stdout.write('Content-Type: text/plain\n') # FIXME: Note that we don't ensure there is a trailing newline! @@ -243,7 +266,7 @@ class MockDRT(object): self._stdout.write('#EOF\n') - if self._options.pixel_tests and output.image_hash: + if test_input.should_run_pixel_test and output.image_hash: self._stdout.write('\n') self._stdout.write('ActualHash: %s\n' % output.image_hash) self._stdout.write('ExpectedHash: %s\n' % test_input.image_hash) @@ -257,43 +280,6 @@ class MockDRT(object): self._stderr.flush() -class MockTestShell(MockDRT): - def input_from_line(self, line): - vals = line.strip().split() - if len(vals) == 3: - uri, timeout, checksum = vals - else: - uri, timeout = vals - checksum = None - - test_name = self._driver.uri_to_test(uri) - return DriverInput(test_name, timeout, checksum, self._options.pixel_tests) - - def output_for_test(self, test_input, is_reftest): - # FIXME: This is a hack to make virtual tests work. Need something more general. - original_test_name = test_input.test_name - if '--enable-accelerated-2d-canvas' in self._args and 'canvas' in test_input.test_name: - test_input.test_name = 'platform/chromium/virtual/gpu/' + test_input.test_name - output = super(MockTestShell, self).output_for_test(test_input, is_reftest) - test_input.test_name = original_test_name - return output - - def write_test_output(self, test_input, output, is_reftest): - self._stdout.write("#URL:%s\n" % self._driver.test_to_uri(test_input.test_name)) - if self._options.pixel_tests and output.image_hash: - self._stdout.write("#MD5:%s\n" % output.image_hash) - if output.image: - self._host.filesystem.maybe_make_directory(self._host.filesystem.dirname(self._options.pixel_path)) - self._host.filesystem.write_binary_file(self._options.pixel_path, output.image) - if output.text: - self._stdout.write(output.text) - - if output.text and not output.text.endswith('\n'): - self._stdout.write('\n') - self._stdout.write('#EOF\n') - self._stdout.flush() - - if __name__ == '__main__': # Note that the Mock in MockDRT refers to the fact that it is emulating a # real DRT, and as such, it needs access to a real SystemHost, not a MockSystemHost. diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py index 1dde3b99514..f577b2e6f98 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/mock_drt_unittest.py @@ -59,6 +59,9 @@ class MockDRTPortTest(port_testcase.PortTestCase): def test_check_sys_deps(self): pass + def test_default_max_locked_shards(self): + pass + def test_diff_image(self): pass @@ -76,20 +79,16 @@ class MockDRTPortTest(port_testcase.PortTestCase): class MockDRTTest(unittest.TestCase): - def input_line(self, port, test_name, checksum=None): + def input_line(self, port, test_name, pixel_tests, checksum=None): url = port.create_driver(0).test_to_uri(test_name) if url.startswith('file://'): url = url[len('file://'):] - + if pixel_tests: + url += "'--pixel-test" if checksum: - return url + "'" + checksum + '\n' + url += "'" + checksum return url + '\n' - def extra_args(self, pixel_tests): - if pixel_tests: - return ['--pixel-tests', '-'] - return ['-'] - def make_drt(self, options, args, host, stdin, stdout, stderr): return mock_drt.MockDRT(options, args, host, stdin, stdout, stderr) @@ -99,7 +98,7 @@ class MockDRTTest(unittest.TestCase): if not expected_checksum: expected_checksum = port.expected_checksum(test_name) if not drt_input: - drt_input = self.input_line(port, test_name, expected_checksum) + drt_input = self.input_line(port, test_name, pixel_tests, expected_checksum) text_output = expected_text or port.expected_text(test_name) or '' if not drt_output: @@ -127,7 +126,7 @@ class MockDRTTest(unittest.TestCase): drt_input, drt_output = self.make_input_output(port, test_name, pixel_tests, expected_checksum, drt_output, drt_input=None, expected_text=expected_text) - args = ['--platform', port_name] + self.extra_args(pixel_tests) + args = ['--dump-render-tree', '--platform', port_name, '-'] stdin = newstringio.StringIO(drt_input) stdout = newstringio.StringIO() stderr = newstringio.StringIO() @@ -141,7 +140,7 @@ class MockDRTTest(unittest.TestCase): # We use the StringIO.buflist here instead of getvalue() because # the StringIO might be a mix of unicode/ascii and 8-bit strings. self.assertEqual(stdout.buflist, drt_output) - self.assertEqual(stderr.getvalue(), '' if options.test_shell else '#EOF\n') + self.assertEqual(stderr.getvalue(), '#EOF\n') def test_main(self): host = MockSystemHost() @@ -149,7 +148,7 @@ class MockDRTTest(unittest.TestCase): stdin = newstringio.StringIO() stdout = newstringio.StringIO() stderr = newstringio.StringIO() - res = mock_drt.main(['--platform', 'test'] + self.extra_args(False), + res = mock_drt.main(['--dump-render-tree', '--platform', 'test', '-'], host, stdin, stdout, stderr) self.assertEqual(res, 0) self.assertEqual(stdout.getvalue(), '') @@ -184,66 +183,19 @@ class MockDRTTest(unittest.TestCase): self.assertTest('failures/expected/missing_text.html', True) def test_reftest_match(self): - self.assertTest('passes/reftest.html', False, expected_checksum='mock-checksum', expected_text='reference text\n') self.assertTest('passes/reftest.html', True, expected_checksum='mock-checksum', expected_text='reference text\n') def test_reftest_mismatch(self): - self.assertTest('passes/mismatch.html', False, expected_checksum='mock-checksum', expected_text='reference text\n') self.assertTest('passes/mismatch.html', True, expected_checksum='mock-checksum', expected_text='reference text\n') - -class MockTestShellTest(MockDRTTest): - def extra_args(self, pixel_tests): - if pixel_tests: - return ['--pixel-tests=/tmp/png_result0.png'] - return [] - - def make_drt(self, options, args, host, stdin, stdout, stderr): - options.test_shell = True - - # We have to set these by hand because --platform test won't trigger - # the Chromium code paths. - options.pixel_path = '/tmp/png_result0.png' - options.pixel_tests = True - - return mock_drt.MockTestShell(options, args, host, stdin, stdout, stderr) - - def input_line(self, port, test_name, checksum=None): - url = port.create_driver(0).test_to_uri(test_name) - if checksum: - return url + ' 6000 ' + checksum + '\n' - return url + ' 6000\n' - - def expected_output(self, port, test_name, pixel_tests, text_output, expected_checksum): - url = port.create_driver(0).test_to_uri(test_name) - output = ['#URL:%s\n' % url] - if expected_checksum: - output.append('#MD5:%s\n' % expected_checksum) - if text_output: - output.append(text_output) - if not text_output.endswith('\n'): - output.append('\n') - output.append('#EOF\n') - return output - - def test_pixeltest__fails(self): - host = MockSystemHost() - url = '#URL:file://' - url = url + '%s/failures/expected/image_checksum.html' % PortFactory(host).get('test').layout_tests_dir() - self.assertTest('failures/expected/image_checksum.html', pixel_tests=True, - expected_checksum='image_checksum', - drt_output=[url + '\n', - '#MD5:image_checksum-checksum\n', - 'image_checksum-txt', - '\n', - '#EOF\n'], - host=host) - self.assertEqual(host.filesystem.written_files, - {'/tmp/png_result0.png': 'image_checksum\x8a-pngtEXtchecksum\x00image_checksum-checksum'}) - - def test_test_shell_parse_options(self): - options, args = mock_drt.parse_options(['--platform', 'mac', '--test-shell', - '--pixel-tests=/tmp/png_result0.png']) - self.assertTrue(options.test_shell) - self.assertTrue(options.pixel_tests) - self.assertEqual(options.pixel_path, '/tmp/png_result0.png') + def test_audio(self): + self.assertTest('passes/audio.html', pixel_tests=True, + drt_output=['Content-Type: audio/wav\n', + 'Content-Transfer-Encoding: base64\n', + 'YXVkaW8td2F2', + '\n', + '#EOF\n', + '#EOF\n']) + + def test_virtual(self): + self.assertTest('virtual/passes/text.html', True) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py index 05a50016a81..1886a66a8a3 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/port_testcase.py @@ -36,11 +36,12 @@ import sys import time import webkitpy.thirdparty.unittest2 as unittest -from webkitpy.common.system.executive_mock import MockExecutive +from webkitpy.common.system.executive_mock import MockExecutive, MockExecutive2 from webkitpy.common.system.filesystem_mock import MockFileSystem from webkitpy.common.system.outputcapture import OutputCapture from webkitpy.common.system.systemhost_mock import MockSystemHost -from webkitpy.layout_tests.port.base import Port +from webkitpy.layout_tests.models import test_run_results +from webkitpy.layout_tests.port.base import Port, TestConfiguration from webkitpy.layout_tests.port.server_process_mock import MockServerProcess from webkitpy.layout_tests.servers import http_server_base from webkitpy.tool.mocktool import MockOptions @@ -68,6 +69,15 @@ class TestWebKitPort(Port): return ["accessibility", ] +class FakePrinter(object): + def write_update(self, msg): + pass + + def write_throttled_update(self, msg): + pass + + + class PortTestCase(unittest.TestCase): """Tests that all Port implementations must pass.""" HTTP_PORTS = (8000, 8080, 8443) @@ -91,10 +101,38 @@ class PortTestCase(unittest.TestCase): def make_wdiff_available(self, port): port._wdiff_available = True + def test_check_build(self): + port = self.make_port() + port._check_file_exists = lambda path, desc: True + if port._dump_reader: + port._dump_reader.check_is_functional = lambda: True + port._options.build = True + port._check_driver_build_up_to_date = lambda config: True + oc = OutputCapture() + try: + oc.capture_output() + self.assertEqual(port.check_build(needs_http=True, printer=FakePrinter()), + test_run_results.OK_EXIT_STATUS) + finally: + out, err, logs = oc.restore_output() + self.assertIn('pretty patches', logs) # We should get a warning about PrettyPatch being missing, + self.assertNotIn('build requirements', logs) # but not the driver itself. + + port._check_file_exists = lambda path, desc: False + port._check_driver_build_up_to_date = lambda config: False + try: + oc.capture_output() + self.assertEqual(port.check_build(needs_http=True, printer=FakePrinter()), + test_run_results.UNEXPECTED_ERROR_EXIT_STATUS) + finally: + out, err, logs = oc.restore_output() + self.assertIn('pretty patches', logs) # And, hereere we should get warnings about both. + self.assertIn('build requirements', logs) + def test_default_max_locked_shards(self): port = self.make_port() port.default_child_processes = lambda: 16 - self.assertEqual(port.default_max_locked_shards(), 1) + self.assertEqual(port.default_max_locked_shards(), 4) port.default_child_processes = lambda: 2 self.assertEqual(port.default_max_locked_shards(), 1) @@ -158,20 +196,39 @@ class PortTestCase(unittest.TestCase): self.assertEqual(port.diff_image('foo', ''), ('foo', None)) def test_diff_image(self): + def _path_to_image_diff(): + return "/path/to/image_diff" + port = self.make_port() - self.proc = None + port._path_to_image_diff = _path_to_image_diff + + mock_image_diff = "MOCK Image Diff" + + def mock_run_command(args): + port._filesystem.write_binary_file(args[4], mock_image_diff) + return 1 - def make_proc(port, nm, cmd, env): - self.proc = MockServerProcess(port, nm, cmd, env, lines=['diff: 100% failed\n', 'diff: 100% failed\n']) - return self.proc + # Images are different. + port._executive = MockExecutive2(run_command_fn=mock_run_command) + self.assertEqual(mock_image_diff, port.diff_image("EXPECTED", "ACTUAL")[0]) - port._server_process_constructor = make_proc - port.setup_test_run() - self.assertEqual(port.diff_image('foo', 'bar'), ('', 100.0, None)) + # Images are the same. + port._executive = MockExecutive2(exit_code=0) + self.assertEqual(None, port.diff_image("EXPECTED", "ACTUAL")[0]) - port.clean_up_test_run() - self.assertTrue(self.proc.stopped) - self.assertEqual(port._image_differ, None) + # There was some error running image_diff. + port._executive = MockExecutive2(exit_code=2) + exception_raised = False + try: + port.diff_image("EXPECTED", "ACTUAL") + except ValueError, e: + exception_raised = True + self.assertFalse(exception_raised) + + def test_diff_image_crashed(self): + port = self.make_port() + port._executive = MockExecutive2(exit_code=2) + self.assertEqual(port.diff_image("EXPECTED", "ACTUAL"), (None, 'image diff returned an exit code of 2')) def test_check_wdiff(self): port = self.make_port() @@ -191,15 +248,45 @@ class PortTestCase(unittest.TestCase): self.assertEqual(diff_txt, port._wdiff_error_html) self.assertFalse(port.wdiff_available()) + def test_missing_symbol_to_skipped_tests(self): + # Test that we get the chromium skips and not the webkit default skips + port = self.make_port() + skip_dict = port._missing_symbol_to_skipped_tests() + if port.PORT_HAS_AUDIO_CODECS_BUILT_IN: + self.assertEqual(skip_dict, {}) + else: + self.assertTrue('ff_mp3_decoder' in skip_dict) + self.assertFalse('WebGLShader' in skip_dict) + def test_test_configuration(self): port = self.make_port() self.assertTrue(port.test_configuration()) def test_all_test_configurations(self): + """Validate the complete set of configurations this port knows about.""" port = self.make_port() - self.assertTrue(len(port.all_test_configurations()) > 0) - self.assertTrue(port.test_configuration() in port.all_test_configurations(), "%s not in %s" % (port.test_configuration(), port.all_test_configurations())) - + self.assertEqual(set(port.all_test_configurations()), set([ + TestConfiguration('snowleopard', 'x86', 'debug'), + TestConfiguration('snowleopard', 'x86', 'release'), + TestConfiguration('lion', 'x86', 'debug'), + TestConfiguration('lion', 'x86', 'release'), + TestConfiguration('retina', 'x86', 'debug'), + TestConfiguration('retina', 'x86', 'release'), + TestConfiguration('mountainlion', 'x86', 'debug'), + TestConfiguration('mountainlion', 'x86', 'release'), + TestConfiguration('mavericks', 'x86', 'debug'), + TestConfiguration('mavericks', 'x86', 'release'), + TestConfiguration('xp', 'x86', 'debug'), + TestConfiguration('xp', 'x86', 'release'), + TestConfiguration('win7', 'x86', 'debug'), + TestConfiguration('win7', 'x86', 'release'), + TestConfiguration('lucid', 'x86', 'debug'), + TestConfiguration('lucid', 'x86', 'release'), + TestConfiguration('lucid', 'x86_64', 'debug'), + TestConfiguration('lucid', 'x86_64', 'release'), + TestConfiguration('icecreamsandwich', 'x86', 'debug'), + TestConfiguration('icecreamsandwich', 'x86', 'release'), + ])) def test_get_crash_log(self): port = self.make_port() self.assertEqual(port._get_crash_log(None, None, None, None, newer_than=None), @@ -234,6 +321,38 @@ class PortTestCase(unittest.TestCase): port.host.filesystem.maybe_make_directory(directory) self.assertEqual(port._build_path(), expected_path) + def test_expectations_files(self): + port = self.make_port() + + generic_path = port.path_to_generic_test_expectations_file() + chromium_overrides_path = port.path_from_chromium_base( + 'webkit', 'tools', 'layout_tests', 'test_expectations.txt') + never_fix_tests_path = port._filesystem.join(port.layout_tests_dir(), 'NeverFixTests') + stale_tests_path = port._filesystem.join(port.layout_tests_dir(), 'StaleTestExpectations') + slow_tests_path = port._filesystem.join(port.layout_tests_dir(), 'SlowTests') + skia_overrides_path = port.path_from_chromium_base( + 'skia', 'skia_test_expectations.txt') + + port._filesystem.write_text_file(skia_overrides_path, 'dummay text') + + port._options.builder_name = 'DUMMY_BUILDER_NAME' + self.assertEqual(port.expectations_files(), [generic_path, skia_overrides_path, never_fix_tests_path, stale_tests_path, slow_tests_path, chromium_overrides_path]) + + port._options.builder_name = 'builder (deps)' + self.assertEqual(port.expectations_files(), [generic_path, skia_overrides_path, never_fix_tests_path, stale_tests_path, slow_tests_path, chromium_overrides_path]) + + # A builder which does NOT observe the Chromium test_expectations, + # but still observes the Skia test_expectations... + port._options.builder_name = 'builder' + self.assertEqual(port.expectations_files(), [generic_path, skia_overrides_path, never_fix_tests_path, stale_tests_path, slow_tests_path]) + + def test_check_sys_deps(self): + port = self.make_port() + port._executive = MockExecutive2(exit_code=0) + self.assertEqual(port.check_sys_deps(needs_http=False), test_run_results.OK_EXIT_STATUS) + port._executive = MockExecutive2(exit_code=1, output='testing output failure') + self.assertEqual(port.check_sys_deps(needs_http=False), test_run_results.SYS_DEPS_EXIT_STATUS) + def test_expectations_ordering(self): port = self.make_port() for path in port.expectations_files(): @@ -275,29 +394,6 @@ class PortTestCase(unittest.TestCase): result_directories = set(TestWebKitPort(symbols_string=symbols_string)._skipped_tests_for_unsupported_features(test_list=['webaudio/codec-tests/mp3/foo.html'])) self.assertEqual(result_directories, expected_directories) - def test_expectations_files(self): - port = TestWebKitPort() - - def platform_dirs(port): - return [port.host.filesystem.basename(port.host.filesystem.dirname(f)) for f in port.expectations_files()] - - self.assertEqual(platform_dirs(port), ['LayoutTests', 'testwebkitport']) - - port = TestWebKitPort(port_name="testwebkitport-version") - self.assertEqual(platform_dirs(port), ['LayoutTests', 'testwebkitport', 'testwebkitport-version']) - - port = TestWebKitPort(port_name="testwebkitport-version", - options=MockOptions(additional_platform_directory=["internal-testwebkitport"])) - self.assertEqual(platform_dirs(port), ['LayoutTests', 'testwebkitport', 'testwebkitport-version', 'internal-testwebkitport']) - - def test_test_expectations(self): - # Check that we read the expectations file - host = MockSystemHost() - host.filesystem.write_text_file('/mock-checkout/LayoutTests/platform/testwebkitport/TestExpectations', - 'BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = FAIL\n') - port = TestWebKitPort(host=host) - self.assertEqual(''.join(port.expectations_dict().values()), 'BUG_TESTEXPECTATIONS SKIP : fast/html/article-element.html = FAIL\n') - def _assert_config_file_for_platform(self, port, platform, config_file): self.assertEqual(port._apache_config_file_name_for_platform(platform), config_file) @@ -327,7 +423,8 @@ class PortTestCase(unittest.TestCase): port = TestWebKitPort() port._is_debian_based = lambda: True - self._assert_config_file_for_platform(port, 'linux2', 'apache2-debian-httpd.conf') + port._apache_version = lambda: '2.2' + self._assert_config_file_for_platform(port, 'linux2', 'debian-httpd-2.2.conf') self._assert_config_file_for_platform(port, 'mac', 'apache2-httpd.conf') self._assert_config_file_for_platform(port, 'win32', 'apache2-httpd.conf') # win32 isn't a supported sys.platform. AppleWin/WinCairo/WinCE ports all use cygwin. @@ -348,7 +445,7 @@ class PortTestCase(unittest.TestCase): # Mock out _apache_config_file_name_for_platform to ignore the passed sys.platform value. port._apache_config_file_name_for_platform = lambda platform: 'httpd.conf' - self.assertEqual(port._path_to_apache_config_file(), '/mock-checkout/LayoutTests/http/conf/httpd.conf') + self.assertEqual(port._path_to_apache_config_file(), '/mock-checkout/third_party/WebKit/LayoutTests/http/conf/httpd.conf') # Check that even if we mock out _apache_config_file_name, the environment variable takes precedence. saved_environ = os.environ.copy() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process.py index 58ccb5c7ad3..f9ec1a7dc54 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process.py @@ -363,7 +363,7 @@ class ServerProcess(object): if not self._proc: self._start() - def stop(self, timeout_secs=3.0): + def stop(self, timeout_secs=0.0): if not self._proc: return (None, None) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process_mock.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process_mock.py index 99cf2d5757b..607bc516617 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process_mock.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/server_process_mock.py @@ -30,7 +30,7 @@ class MockServerProcess(object): def __init__(self, port_obj=None, name=None, cmd=None, env=None, universal_newlines=False, treat_no_data_as_crash=False, logging=False, lines=None, crashed=False): self.timed_out = False - self.lines = lines or [] + self.lines = lines or ['#READY'] self.crashed = crashed self.writes = [] self.cmd = cmd @@ -71,7 +71,7 @@ class MockServerProcess(object): def start(self): self.started = True - def stop(self, kill_directly=False): + def stop(self, timeout_sec=0.0): self.stopped = True return diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/test.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/test.py index 8257e324181..1afbf5b9f3d 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/test.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/test.py @@ -31,9 +31,10 @@ import copy import sys import time -from webkitpy.layout_tests.port import Port, Driver, DriverOutput +from webkitpy.layout_tests.port import DeviceFailure, Driver, DriverOutput, Port from webkitpy.layout_tests.port.base import VirtualTestSuite from webkitpy.layout_tests.models.test_configuration import TestConfiguration +from webkitpy.layout_tests.models import test_run_results from webkitpy.common.system.filesystem_mock import MockFileSystem from webkitpy.common.system.crashlogs import CrashLogs @@ -51,6 +52,7 @@ class TestInstance(object): self.error = '' self.timeout = False self.is_reftest = False + self.device_failure = False # The values of each field are treated as raw byte strings. They # will be converted to unicode strings where appropriate using @@ -98,18 +100,19 @@ class TestList(object): return self.tests[item] # -# These numbers may need to be updated whenever we add or delete tests. +# These numbers may need to be updated whenever we add or delete tests. This includes virtual tests. # -TOTAL_TESTS = 105 -TOTAL_SKIPS = 25 +TOTAL_TESTS = 110 +TOTAL_SKIPS = 28 UNEXPECTED_PASSES = 1 -UNEXPECTED_FAILURES = 23 +UNEXPECTED_FAILURES = 25 def unit_test_list(): tests = TestList() tests.add('failures/expected/crash.html', crash=True) tests.add('failures/expected/exception.html', exception=True) + tests.add('failures/expected/device_failure.html', device_failure=True) tests.add('failures/expected/timeout.html', timeout=True) tests.add('failures/expected/missing_text.html', expected_text=None) tests.add('failures/expected/needsrebaseline.html', actual_text='needsrebaseline text') @@ -141,6 +144,7 @@ def unit_test_list(): tests.add('failures/expected/newlines_with_excess_CR.html', expected_text="foo\r\r\r\n", actual_text="foo\n") tests.add('failures/expected/text.html', actual_text='text_fail-png') + tests.add('failures/expected/crash_then_text.html') tests.add('failures/expected/skip_text.html', actual_text='text diff') tests.add('failures/flaky/text.html') tests.add('failures/unexpected/missing_text.html', expected_text=None) @@ -171,6 +175,7 @@ layer at (0,0) size 800x34 actual_checksum='text-image-checksum_fail-checksum') tests.add('failures/unexpected/skip_pass.html') tests.add('failures/unexpected/text.html', actual_text='text_fail-txt') + tests.add('failures/unexpected/text_then_crash.html') tests.add('failures/unexpected/timeout.html', timeout=True) tests.add('http/tests/passes/text.html') tests.add('http/tests/passes/image.html') @@ -199,6 +204,10 @@ layer at (0,0) size 800x34 # For reftests. tests.add_reftest('passes/reftest.html', 'passes/reftest-expected.html', same_image=True) + + # This adds a different virtual reference to ensure that that also works. + tests.add('virtual/passes/reftest-expected.html', actual_checksum='xxx', actual_image='XXX', is_reftest=True) + tests.add_reftest('passes/mismatch.html', 'passes/mismatch-expected-mismatch.html', same_image=False) tests.add_reftest('passes/svgreftest.svg', 'passes/svgreftest-expected.svg', same_image=True) tests.add_reftest('passes/xhtreftest.xht', 'passes/xhtreftest-expected.html', same_image=True) @@ -272,6 +281,7 @@ def add_unit_tests_to_mock_filesystem(filesystem): if not filesystem.exists('/mock-checkout/LayoutTests/TestExpectations'): filesystem.write_text_file('/mock-checkout/LayoutTests/TestExpectations', """ Bug(test) failures/expected/crash.html [ Crash ] +Bug(test) failures/expected/crash_then_text.html [ Failure ] Bug(test) failures/expected/image.html [ ImageOnlyFailure ] Bug(test) failures/expected/needsrebaseline.html [ NeedsRebaseline ] Bug(test) failures/expected/needsmanualrebaseline.html [ NeedsManualRebaseline ] @@ -290,6 +300,7 @@ Bug(test) failures/expected/text.html [ Failure ] Bug(test) failures/expected/timeout.html [ Timeout ] Bug(test) failures/expected/keyboard.html [ WontFix ] Bug(test) failures/expected/exception.html [ WontFix ] +Bug(test) failures/expected/device_failure.html [ WontFix ] Bug(test) failures/unexpected/pass.html [ Failure ] Bug(test) passes/skipped/skip.html [ Skip ] Bug(test) passes/text.html [ Pass ] @@ -354,6 +365,14 @@ class TestPort(Port): 'test-win-win7', 'test-win-xp', ) + FALLBACK_PATHS = { + 'xp': ['test-win-win7', 'test-win-xp'], + 'win7': ['test-win-win7'], + 'leopard': ['test-mac-leopard', 'test-mac-snowleopard'], + 'snowleopard': ['test-mac-snowleopard'], + 'lucid': ['test-linux-x86_64', 'test-win-win7'], + } + @classmethod def determine_full_port_name(cls, host, options, port_name): if port_name == 'test': @@ -392,6 +411,11 @@ class TestPort(Port): } self._version = version_map[self._name] + def repository_paths(self): + """Returns a list of (repository_name, repository_path) tuples of its depending code base.""" + # FIXME: We override this just to keep the perf tests happy. + return [('blink', self.layout_tests_dir())] + def buildbot_archives_baselines(self): return self._name != 'test-win-xp' @@ -403,27 +427,14 @@ class TestPort(Port): # the mock_drt Driver. We return something, but make sure it's useless. return 'MOCK _path_to_driver' - def baseline_search_path(self): - search_paths = { - 'test-mac-snowleopard': ['test-mac-snowleopard'], - 'test-mac-leopard': ['test-mac-leopard', 'test-mac-snowleopard'], - 'test-win-win7': ['test-win-win7'], - 'test-win-xp': ['test-win-xp', 'test-win-win7'], - 'test-linux-x86_64': ['test-linux-x86_64', 'test-win-win7'], - } - return [self._webkit_baseline_path(d) for d in search_paths[self.name()]] - def default_child_processes(self): return 1 - def worker_startup_delay_secs(self): - return 0 - def check_build(self, needs_http, printer): - return True + return test_run_results.OK_EXIT_STATUS def check_sys_deps(self, needs_http): - return True + return test_run_results.OK_EXIT_STATUS def default_configuration(self): return 'Release' @@ -506,6 +517,9 @@ class TestPort(Port): def path_to_generic_test_expectations_file(self): return self._generic_expectations_path + def _port_specific_expectations_files(self): + return [self._filesystem.join(self._webkit_baseline_path(d), 'TestExpectations') for d in ['test', 'test-win-xp']] + def all_test_configurations(self): """Returns a sequence of the TestConfigurations the port supports.""" # By default, we assume we want to test every graphics type in @@ -539,8 +553,8 @@ class TestPort(Port): def virtual_test_suites(self): return [ - VirtualTestSuite('virtual/passes', 'passes', ['--virtual-arg']), - VirtualTestSuite('virtual/skipped', 'failures/expected', ['--virtual-arg2']), + VirtualTestSuite('passes', 'passes', ['--virtual-arg'], use_legacy_naming=True), + VirtualTestSuite('skipped', 'failures/expected', ['--virtual-arg2'], use_legacy_naming=True), ] @@ -558,13 +572,6 @@ class TestDriver(Driver): return [self._port._path_to_driver()] + [pixel_tests_flag] + self._port.get_option('additional_drt_flag', []) + per_test_args def run_test(self, driver_input, stop_when_done): - base = self._port.lookup_virtual_test_base(driver_input.test_name) - if base: - virtual_driver_input = copy.copy(driver_input) - virtual_driver_input.test_name = base - virtual_driver_input.args = self._port.lookup_virtual_test_args(driver_input.test_name) - return self.run_test(virtual_driver_input, stop_when_done) - if not self.started: self.started = True self.pid = TestDriver.next_pid @@ -578,14 +585,36 @@ class TestDriver(Driver): raise KeyboardInterrupt if test.exception: raise ValueError('exception from ' + test_name) + if test.device_failure: + raise DeviceFailure('device failure in ' + test_name) audio = None actual_text = test.actual_text + crash = test.crash + web_process_crash = test.web_process_crash - if 'flaky' in test_name and not test_name in self._port._flakes: + if 'flaky/text.html' in test_name and not test_name in self._port._flakes: self._port._flakes.add(test_name) actual_text = 'flaky text failure' + if 'crash_then_text.html' in test_name: + if test_name in self._port._flakes: + actual_text = 'text failure' + else: + self._port._flakes.add(test_name) + crashed_process_name = self._port.driver_name() + crashed_pid = 1 + crash = True + + if 'text_then_crash.html' in test_name: + if test_name in self._port._flakes: + crashed_process_name = self._port.driver_name() + crashed_pid = 1 + crash = True + else: + self._port._flakes.add(test_name) + actual_text = 'text failure' + if actual_text and test_args and test_name == 'passes/args.html': actual_text = actual_text + ' ' + ' '.join(test_args) @@ -593,10 +622,10 @@ class TestDriver(Driver): audio = base64.b64decode(test.actual_audio) crashed_process_name = None crashed_pid = None - if test.crash: + if crash: crashed_process_name = self._port.driver_name() crashed_pid = 1 - elif test.web_process_crash: + elif web_process_crash: crashed_process_name = 'WebProcess' crashed_pid = 2 @@ -613,7 +642,7 @@ class TestDriver(Driver): else: image = test.actual_image return DriverOutput(actual_text, image, test.actual_checksum, audio, - crash=test.crash or test.web_process_crash, crashed_process_name=crashed_process_name, + crash=(crash or web_process_crash), crashed_process_name=crashed_process_name, crashed_pid=crashed_pid, crash_log=crash_log, test_time=time.time() - start_time, timeout=test.timeout, error=test.error, pid=self.pid) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win.py index df9db13fbc4..d421db93ad8 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win.py @@ -31,13 +31,16 @@ import os import logging -import chromium +from webkitpy.layout_tests.breakpad.dump_reader_win import DumpReaderWin +from webkitpy.layout_tests.models import test_run_results +from webkitpy.layout_tests.port import base +from webkitpy.layout_tests.servers import crash_service _log = logging.getLogger(__name__) -class WinPort(chromium.ChromiumPort): +class WinPort(base.Port): port_name = 'win' # FIXME: Figure out how to unify this with base.TestConfiguration.all_systems()? @@ -61,12 +64,42 @@ class WinPort(chromium.ChromiumPort): return port_name def __init__(self, host, port_name, **kwargs): - chromium.ChromiumPort.__init__(self, host, port_name, **kwargs) + super(WinPort, self).__init__(host, port_name, **kwargs) self._version = port_name[port_name.index('win-') + len('win-'):] assert self._version in self.SUPPORTED_VERSIONS, "%s is not in %s" % (self._version, self.SUPPORTED_VERSIONS) + if not self.get_option('disable_breakpad'): + self._dump_reader = DumpReaderWin(host, self._build_path()) + self._crash_service = None + self._crash_service_available = None + + def additional_drt_flag(self): + flags = super(WinPort, self).additional_drt_flag() + if not self.get_option('disable_breakpad'): + flags += ['--enable-crash-reporter', '--crash-dumps-dir=%s' % self._dump_reader.crash_dumps_directory()] + return flags + + def setup_test_run(self): + super(WinPort, self).setup_test_run() + + if not self.get_option('disable_breakpad'): + assert not self._crash_service, 'Already running a crash service' + if self._crash_service_available == None: + self._crash_service_available = self._check_crash_service_available() + if not self._crash_service_available: + return + service = crash_service.CrashService(self, self._dump_reader.crash_dumps_directory()) + service.start() + self._crash_service = service + + def clean_up_test_run(self): + super(WinPort, self).clean_up_test_run() + + if self._crash_service: + self._crash_service.stop() + self._crash_service = None def setup_environ_for_server(self, server_name=None): - env = chromium.ChromiumPort.setup_environ_for_server(self, server_name) + env = super(WinPort, self).setup_environ_for_server(server_name) # FIXME: lighttpd depends on some environment variable we're not whitelisting. # We should add the variable to an explicit whitelist in base.Port. @@ -92,8 +125,13 @@ class WinPort(chromium.ChromiumPort): return [] def check_build(self, needs_http, printer): - result = chromium.ChromiumPort.check_build(self, needs_http, printer) - if not result: + result = super(WinPort, self).check_build(needs_http, printer) + + self._crash_service_available = self._check_crash_service_available() + if not self._crash_service_available: + result = test_run_results.UNEXPECTED_ERROR_EXIT_STATUS + + if result: _log.error('For complete Windows build requirements, please see:') _log.error('') _log.error(' http://dev.chromium.org/developers/how-tos/build-instructions-windows') @@ -139,9 +177,31 @@ class WinPort(chromium.ChromiumPort): binary_name = 'LayoutTestHelper.exe' return self._build_path(binary_name) + def _path_to_crash_service(self): + binary_name = 'content_shell_crash_service.exe' + return self._build_path(binary_name) + def _path_to_image_diff(self): binary_name = 'image_diff.exe' return self._build_path(binary_name) def _path_to_wdiff(self): return self.path_from_chromium_base('third_party', 'cygwin', 'bin', 'wdiff.exe') + + def _check_crash_service_available(self): + """Checks whether the crash service binary is present.""" + result = self._check_file_exists(self._path_to_crash_service(), "content_shell_crash_service.exe") + if not result: + _log.error(" Could not find crash service, unexpected crashes won't be symbolized.") + _log.error(' Did you build the target all_webkit?') + _log.error('') + return result + + def look_for_new_crash_logs(self, crashed_processes, start_time): + if self.get_option('disable_breakpad'): + return None + return self._dump_reader.look_for_new_crash_logs(crashed_processes, start_time) + + def clobber_old_port_specific_results(self): + if not self.get_option('disable_breakpad'): + self._dump_reader.clobber_old_results() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py index cfe6a0786f6..66962ae2145 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/port/win_unittest.py @@ -32,12 +32,12 @@ import webkitpy.thirdparty.unittest2 as unittest from webkitpy.common.system import outputcapture from webkitpy.common.system.executive_mock import MockExecutive from webkitpy.common.system.filesystem_mock import MockFileSystem -from webkitpy.layout_tests.port import chromium_port_testcase +from webkitpy.layout_tests.port import port_testcase from webkitpy.layout_tests.port import win from webkitpy.tool.mocktool import MockOptions -class WinPortTest(chromium_port_testcase.ChromiumPortTestCase): +class WinPortTest(port_testcase.PortTestCase): port_name = 'win' port_maker = win.WinPort os_name = 'win' @@ -59,12 +59,12 @@ class WinPortTest(chromium_port_testcase.ChromiumPortTestCase): def test_setup_environ_for_server_cygpath(self): port = self.make_port() env = port.setup_environ_for_server(port.driver_name()) - self.assertEqual(env['CYGWIN_PATH'], '/mock-checkout/Source/WebKit/chromium/third_party/cygwin/bin') + self.assertEqual(env['CYGWIN_PATH'], '/mock-checkout/third_party/cygwin/bin') def test_setup_environ_for_server_register_cygwin(self): port = self.make_port(options=MockOptions(register_cygwin=True, results_directory='/')) port._executive = MockExecutive(should_log=True) - expected_logs = "MOCK run_command: ['/mock-checkout/Source/WebKit/chromium/third_party/cygwin/setup_mount.bat'], cwd=None\n" + expected_logs = "MOCK run_command: ['/mock-checkout/third_party/cygwin/setup_mount.bat'], cwd=None\n" output = outputcapture.OutputCapture() output.assert_outputs(self, port.setup_environ_for_server, expected_logs=expected_logs) @@ -100,15 +100,15 @@ class WinPortTest(chromium_port_testcase.ChromiumPortTestCase): def test_build_path(self): # Test that optional paths are used regardless of whether they exist. options = MockOptions(configuration='Release', build_directory='/foo') - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out/Release'], '/foo/Release') + self.assert_build_path(options, ['/mock-checkout/out/Release'], '/foo/Release') # Test that optional relative paths are returned unmodified. options = MockOptions(configuration='Release', build_directory='foo') - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/out/Release'], 'foo/Release') + self.assert_build_path(options, ['/mock-checkout/out/Release'], 'foo/Release') # Test that we prefer the legacy dir over the new dir. options = MockOptions(configuration='Release', build_directory=None) - self.assert_build_path(options, ['/mock-checkout/Source/WebKit/chromium/build/Release', '/mock-checkout/Source/WebKit/chromium/out'], '/mock-checkout/Source/WebKit/chromium/build/Release') + self.assert_build_path(options, ['/mock-checkout/build/Release', '/mock-checkout/out'], '/mock-checkout/build/Release') def test_build_path_timestamps(self): options = MockOptions(configuration='Release', build_directory=None) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/comments.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/print_layout_test_types.py index 771953e698c..af80c749577 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/comments.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/print_layout_test_types.py @@ -1,5 +1,6 @@ -# Copyright (c) 2009 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. +#!/usr/bin/python +# +# Copyright (C) 2013 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -26,17 +27,28 @@ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# A tool for automating dealing with bugzilla, posting patches, committing -# patches, etc. -from webkitpy.common.config import urls +import optparse + +from webkitpy.layout_tests.controllers import layout_test_finder + +def main(host, argv): + port = host.port_factory.get() -def bug_comment_from_svn_revision(svn_revision): - return "Committed r%s: <%s>" % (svn_revision, urls.view_revision_url(svn_revision)) + parser = optparse.OptionParser() + parser.add_option('--test-list', action='append') + parser.add_option('--type', action='append', + help='limit to tests of type X (valid values %s)' % port.ALL_TEST_TYPES) + options, args = parser.parse_args(argv) + finder = layout_test_finder.LayoutTestFinder(port, options) + _, tests = finder.find_tests(options, args) -def bug_comment_from_commit_text(scm, commit_text): - svn_revision = scm.svn_revision_from_commit_text(commit_text) - return bug_comment_from_svn_revision(svn_revision) + for test_name in tests: + test_type = port.test_type(test_name) + if options.type: + if test_type in options.type: + host.print_(test_name) + else: + host.print_(test_name, test_type) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/ports_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/print_layout_test_types_unittest.py index 319158625fb..8d980a40334 100644..100755 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/common/config/ports_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/print_layout_test_types_unittest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2009, Google Inc. All rights reserved. +# Copyright (C) 2013 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -28,13 +28,32 @@ import webkitpy.thirdparty.unittest2 as unittest -from webkitpy.common.config.ports import * +from webkitpy.common.host_mock import MockHost +from webkitpy.layout_tests.print_layout_test_types import main -class DeprecatedPortTest(unittest.TestCase): - def test_chromium_port(self): - self.assertEqual(ChromiumPort().flag(), "--port=chromium") - self.assertEqual(ChromiumPort().run_webkit_tests_command(), DeprecatedPort().script_shell_command("new-run-webkit-tests") + ["--chromium", "--skip-failing-tests"]) +class PrintLayoutTestTimesTest(unittest.TestCase): - def test_chromium_xvfb_port(self): - self.assertEqual(ChromiumXVFBPort().run_webkit_tests_command(), ['xvfb-run'] + DeprecatedPort().script_shell_command('new-run-webkit-tests') + ['--chromium', '--skip-failing-tests']) + def check(self, args, expected_output, files=None): + host = MockHost() + files = files or {} + for path, contents in files.items(): + host.filesystem.write_binary_file(path, contents) + orig_get = host.port_factory.get + host.port_factory.get = lambda *args, **kwargs: orig_get('test') + main(host, args) + self.assertEqual(host.stdout.getvalue(), expected_output) + + def test_test_list(self): + files = {'/tmp/test_list': 'passes/image.html'} + self.check(['--test-list', '/tmp/test_list'], 'passes/image.html pixel\n', files=files) + + def test_type(self): + self.check(['--type', 'audio', 'passes'], 'passes/audio.html\n') + + def test_basic(self): + self.check(['failures/unexpected/missing_image.html', 'passes/image.html', 'passes/audio.html', 'passes/reftest.html'], + 'failures/unexpected/missing_image.html text\n' + 'passes/image.html pixel\n' + 'passes/audio.html audio\n' + 'passes/reftest.html ref\n') diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py index 1f22807b5ab..c92e13172c9 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py @@ -36,7 +36,7 @@ import traceback from webkitpy.common.host import Host from webkitpy.layout_tests.controllers.manager import Manager -from webkitpy.layout_tests.models.test_run_results import INTERRUPTED_EXIT_STATUS +from webkitpy.layout_tests.models import test_run_results from webkitpy.layout_tests.port import configuration_options, platform_options from webkitpy.layout_tests.views import buildbot_results from webkitpy.layout_tests.views import printing @@ -45,10 +45,6 @@ from webkitpy.layout_tests.views import printing _log = logging.getLogger(__name__) -# This is a randomly chosen exit code that can be tested against to -# indicate that an unexpected exception occurred. -EXCEPTIONAL_EXIT_STATUS = 254 - def main(argv, stdout, stderr): options, args = parse_args(argv) @@ -71,23 +67,26 @@ def main(argv, stdout, stderr): except NotImplementedError, e: # FIXME: is this the best way to handle unsupported port names? print >> stderr, str(e) - return EXCEPTIONAL_EXIT_STATUS + return test_run_results.UNEXPECTED_ERROR_EXIT_STATUS try: run_details = run(port, options, args, stderr) - if run_details.exit_code != -1 and not run_details.initial_results.keyboard_interrupted: + if run_details.exit_code not in test_run_results.ERROR_CODES and not run_details.initial_results.keyboard_interrupted: bot_printer = buildbot_results.BuildBotPrinter(stdout, options.debug_rwt_logging) bot_printer.print_results(run_details) return run_details.exit_code # We need to still handle KeyboardInterrupt, atleast for webkitpy unittest cases. except KeyboardInterrupt: - return INTERRUPTED_EXIT_STATUS + return test_run_results.INTERRUPTED_EXIT_STATUS + except test_run_results.TestRunException as e: + print >> stderr, e.msg + return e.code except BaseException as e: if isinstance(e, Exception): print >> stderr, '\n%s raised: %s' % (e.__class__.__name__, str(e)) traceback.print_exc(file=stderr) - return EXCEPTIONAL_EXIT_STATUS + return test_run_results.UNEXPECTED_ERROR_EXIT_STATUS def parse_args(args): @@ -252,6 +251,8 @@ def parse_args(args): help="Output per-test profile information, using the specified profiler."), optparse.make_option("--driver-logging", action="store_true", help="Print detailed logging of the driver/content_shell"), + optparse.make_option("--disable-breakpad", action="store_true", + help="Don't use breakpad to symbolize unexpected crashes."), ])) option_group_definitions.append(("Miscellaneous Options", [ diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py index 3b36471ec0b..1bf13fb5308 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests_integrationtest.py @@ -50,7 +50,7 @@ from webkitpy.common.host_mock import MockHost from webkitpy.layout_tests import port from webkitpy.layout_tests import run_webkit_tests -from webkitpy.layout_tests.models.test_run_results import INTERRUPTED_EXIT_STATUS +from webkitpy.layout_tests.models import test_run_results from webkitpy.layout_tests.port import Port from webkitpy.layout_tests.port import test from webkitpy.test.skip import skip_if @@ -283,6 +283,12 @@ class RunTest(unittest.TestCase, StreamTestingMixin): self.assertRaises(BaseException, logging_run, ['--child-processes', '2', '--skipped=ignore', 'failures/expected/exception.html', 'passes/text.html'], tests_included=True, shared_port=False) + def test_device_failure(self): + # Test that we handle a device going offline during a test properly. + details, regular_output, _ = logging_run(['failures/expected/device_failure.html'], tests_included=True) + self.assertEqual(details.exit_code, 0) + self.assertTrue('worker/0 has failed' in regular_output.getvalue()) + def test_full_results_html(self): host = MockHost() details, _, _ = logging_run(['--full-results-html'], host=host) @@ -293,7 +299,7 @@ class RunTest(unittest.TestCase, StreamTestingMixin): # Note that this also tests running a test marked as SKIP if # you specify it explicitly. details, _, _ = logging_run(['failures/expected/keyboard.html', '--child-processes', '1'], tests_included=True) - self.assertEqual(details.exit_code, INTERRUPTED_EXIT_STATUS) + self.assertEqual(details.exit_code, test_run_results.INTERRUPTED_EXIT_STATUS) if self.should_test_processes: _, regular_output, _ = logging_run(['failures/expected/keyboard.html', 'passes/text.html', '--child-processes', '2', '--skipped=ignore'], tests_included=True, shared_port=False) @@ -301,12 +307,12 @@ class RunTest(unittest.TestCase, StreamTestingMixin): def test_no_tests_found(self): details, err, _ = logging_run(['resources'], tests_included=True) - self.assertEqual(details.exit_code, -1) + self.assertEqual(details.exit_code, test_run_results.NO_TESTS_EXIT_STATUS) self.assertContains(err, 'No tests to run.\n') def test_no_tests_found_2(self): details, err, _ = logging_run(['foo'], tests_included=True) - self.assertEqual(details.exit_code, -1) + self.assertEqual(details.exit_code, test_run_results.NO_TESTS_EXIT_STATUS) self.assertContains(err, 'No tests to run.\n') def test_natural_order(self): @@ -463,7 +469,7 @@ class RunTest(unittest.TestCase, StreamTestingMixin): self.assertEqual(['passes/text.html'], tests_run) host.filesystem.remove(filename) details, err, user = logging_run(['--test-list=%s' % filename], tests_included=True, host=host) - self.assertEqual(details.exit_code, -1) + self.assertEqual(details.exit_code, test_run_results.NO_TESTS_EXIT_STATUS) self.assertNotEmpty(err) def test_test_list_with_prefix(self): @@ -521,6 +527,25 @@ class RunTest(unittest.TestCase, StreamTestingMixin): self.assertTrue(json_string.find('"num_regressions":2') != -1) self.assertTrue(json_string.find('"num_flaky":0') != -1) + def test_different_failure_on_retry(self): + # This tests that if a test fails two different ways -- both unexpected + # -- we treat it as a failure rather than a flaky result. We use the + # initial failure for simplicity and consistency w/ the flakiness + # dashboard, even if the second failure is worse. + + details, err, _ = logging_run(['--retry-failures', 'failures/unexpected/text_then_crash.html'], tests_included=True) + self.assertEqual(details.exit_code, 1) + self.assertEqual(details.summarized_failing_results['tests']['failures']['unexpected']['text_then_crash.html']['actual'], + 'TEXT CRASH') + + # If we get a test that fails two different ways -- but the second one is expected -- + # we should treat it as a flaky result and report the initial unexpected failure type + # to the dashboard. However, the test should be considered passing. + details, err, _ = logging_run(['--retry-failures', 'failures/expected/crash_then_text.html'], tests_included=True) + self.assertEqual(details.exit_code, 0) + self.assertEqual(details.summarized_failing_results['tests']['failures']['expected']['crash_then_text.html']['actual'], + 'CRASH FAIL') + def test_pixel_test_directories(self): host = MockHost() @@ -623,7 +648,7 @@ class RunTest(unittest.TestCase, StreamTestingMixin): host = MockHost() details, err, _ = logging_run(['--debug-rwt-logging', 'failures/unexpected'], tests_included=True, host=host) - self.assertEqual(details.exit_code, 17) # FIXME: This should be a constant in test.py . + self.assertEqual(details.exit_code, test.UNEXPECTED_FAILURES - 7) # FIXME: This should be a constant in test.py . self.assertTrue('Retrying' in err.getvalue()) def test_retrying_default_value_test_list(self): @@ -638,7 +663,7 @@ class RunTest(unittest.TestCase, StreamTestingMixin): filename = '/tmp/foo.txt' host.filesystem.write_text_file(filename, 'failures') details, err, _ = logging_run(['--debug-rwt-logging', '--test-list=%s' % filename], tests_included=True, host=host) - self.assertEqual(details.exit_code, 17) + self.assertEqual(details.exit_code, test.UNEXPECTED_FAILURES - 7) self.assertTrue('Retrying' in err.getvalue()) def test_retrying_and_flaky_tests(self): @@ -738,6 +763,10 @@ class RunTest(unittest.TestCase, StreamTestingMixin): # The list of references should be empty since the test crashed and we didn't run any references. self.assertEqual(test_results[0].references, []) + def test_reftest_with_virtual_reference(self): + _, err, _ = logging_run(['--details', 'virtual/passes/reftest.html'], tests_included=True) + self.assertTrue('ref: virtual/passes/reftest-expected.html' in err.getvalue()) + def test_additional_platform_directory(self): self.assertTrue(passing_run(['--additional-platform-directory', '/tmp/foo'])) self.assertTrue(passing_run(['--additional-platform-directory', '/tmp/../foo'])) @@ -784,7 +813,7 @@ class RunTest(unittest.TestCase, StreamTestingMixin): stderr = StringIO.StringIO() res = run_webkit_tests.main(['--platform', 'foo'], stdout, stderr) - self.assertEqual(res, run_webkit_tests.EXCEPTIONAL_EXIT_STATUS) + self.assertEqual(res, test_run_results.UNEXPECTED_ERROR_EXIT_STATUS) self.assertEqual(stdout.getvalue(), '') self.assertTrue('unsupported platform' in stderr.getvalue()) @@ -796,7 +825,7 @@ class RunTest(unittest.TestCase, StreamTestingMixin): port_name = 'mac-lion' out = StringIO.StringIO() err = StringIO.StringIO() - self.assertEqual(run_webkit_tests.main(['--platform', port_name, 'fast/harness/results.html'], out, err), -1) + self.assertEqual(run_webkit_tests.main(['--platform', port_name, 'fast/harness/results.html'], out, err), test_run_results.UNEXPECTED_ERROR_EXIT_STATUS) def test_verbose_in_child_processes(self): # When we actually run multiple processes, we may have to reconfigure logging in the @@ -962,15 +991,6 @@ class PortTest(unittest.TestCase): def disabled_test_mac_lion(self): self.assert_mock_port_works('mac-lion') - def disabled_test_mac_lion_in_test_shell_mode(self): - self.assert_mock_port_works('mac-lion', args=['--additional-drt-flag=--test-shell']) - - def disabled_test_qt_linux(self): - self.assert_mock_port_works('qt-linux') - - def disabled_test_mac_lion(self): - self.assert_mock_port_works('mac-lion') - class MainTest(unittest.TestCase): def test_exception_handling(self): @@ -983,7 +1003,7 @@ class MainTest(unittest.TestCase): def successful_run(port, options, args, stderr): class FakeRunDetails(object): - exit_code = -1 + exit_code = test_run_results.UNEXPECTED_ERROR_EXIT_STATUS return FakeRunDetails() @@ -995,14 +1015,14 @@ class MainTest(unittest.TestCase): try: run_webkit_tests.run = interrupting_run res = run_webkit_tests.main([], stdout, stderr) - self.assertEqual(res, INTERRUPTED_EXIT_STATUS) + self.assertEqual(res, test_run_results.INTERRUPTED_EXIT_STATUS) run_webkit_tests.run = successful_run res = run_webkit_tests.main(['--platform', 'test'], stdout, stderr) - self.assertEqual(res, -1) + self.assertEqual(res, test_run_results.UNEXPECTED_ERROR_EXIT_STATUS) run_webkit_tests.run = exception_raising_run res = run_webkit_tests.main([], stdout, stderr) - self.assertEqual(res, run_webkit_tests.EXCEPTIONAL_EXIT_STATUS) + self.assertEqual(res, test_run_results.UNEXPECTED_ERROR_EXIT_STATUS) finally: run_webkit_tests.run = orig_run_fn diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py index eb64d82990e..e00a9a0cb44 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/apache_http_server.py @@ -60,7 +60,7 @@ class LayoutTestApacheHttpd(http_server_base.HttpServerBase): self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name) test_dir = self._port_obj.layout_tests_dir() - js_test_resources_dir = self._filesystem.join(test_dir, "fast", "js", "resources") + js_test_resources_dir = self._filesystem.join(test_dir, "resources") media_resources_dir = self._filesystem.join(test_dir, "media") mime_types_path = self._filesystem.join(test_dir, "http", "conf", "mime.types") cert_file = self._filesystem.join(test_dir, "http", "conf", "webkit-httpd.pem") diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/crash_service.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/crash_service.py new file mode 100644 index 00000000000..08ead87d1c2 --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/crash_service.py @@ -0,0 +1,87 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""A class to help start/stop the crash service used by layout tests.""" + +import logging +import os +import time + +from webkitpy.layout_tests.servers import http_server_base + + +_log = logging.getLogger(__name__) + + +class CrashService(http_server_base.HttpServerBase): + + def __init__(self, port_obj, crash_dumps_dir): + """Args: + crash_dumps_dir: the absolute path to the directory where to store crash dumps + """ + # Webkit tests + http_server_base.HttpServerBase.__init__(self, port_obj) + self._name = 'CrashService' + self._crash_dumps_dir = crash_dumps_dir + + self._pid_file = self._filesystem.join(self._runtime_path, '%s.pid' % self._name) + + def _spawn_process(self): + start_cmd = [self._port_obj._path_to_crash_service(), + '--dumps-dir=%s' % self._crash_dumps_dir, + '--no-window'] + _log.debug('Starting crash service, cmd = "%s"' % " ".join(start_cmd)) + process = self._executive.popen(start_cmd, shell=False, stderr=self._executive.PIPE) + pid = process.pid + self._filesystem.write_text_file(self._pid_file, str(pid)) + return pid + + def _stop_running_server(self): + # FIXME: It would be nice if we had a cleaner way of killing this process. + # Currently we throw away the process object created in _spawn_process, + # since there doesn't appear to be any way to kill the server any more + # cleanly using it than just killing the pid, and we need to support + # killing a pid directly anyway for run-webkit-httpd and run-webkit-websocketserver. + self._wait_for_action(self._check_and_kill) + if self._filesystem.exists(self._pid_file): + self._filesystem.remove(self._pid_file) + + def _check_and_kill(self): + if self._executive.check_running_pid(self._pid): + host = self._port_obj.host + if host.platform.is_win() and not host.platform.is_cygwin(): + # FIXME: https://bugs.webkit.org/show_bug.cgi?id=106838 + # We need to kill all of the child processes as well as the + # parent, so we can't use executive.kill_process(). + # + # If this is actually working, we should figure out a clean API. + self._executive.run_command(["taskkill.exe", "/f", "/t", "/pid", self._pid], error_handler=self._executive.ignore_error) + else: + self._executive.kill_process(self._pid) + return False + return True diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/crash_service_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/crash_service_unittest.py new file mode 100644 index 00000000000..4204cb46967 --- /dev/null +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/crash_service_unittest.py @@ -0,0 +1,81 @@ +# Copyright (C) 2013 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import re +import sys +import webkitpy.thirdparty.unittest2 as unittest + +from webkitpy.common.host_mock import MockHost +from webkitpy.layout_tests.port import test +from webkitpy.layout_tests.servers.crash_service import CrashService +from webkitpy.layout_tests.servers.http_server_base import ServerError + + +class TestCrashService(unittest.TestCase): + def test_start_cmd(self): + # Fails on win - see https://bugs.webkit.org/show_bug.cgi?id=84726 + if sys.platform in ('cygwin', 'win32'): + return + + host = MockHost() + test_port = test.TestPort(host) + test_port._path_to_crash_service = lambda: "/mock/crash_service" + + server = CrashService(test_port, "/mock/crash_dumps_dir") + self.assertRaises(ServerError, server.start) + + def test_win32_start_and_stop(self): + host = MockHost() + test_port = test.TestPort(host) + test_port._path_to_crash_service = lambda: "/mock/crash_service" + + host.platform.is_win = lambda: True + host.platform.is_cygwin = lambda: False + + server = CrashService(test_port, "/mock/crash_dumps_dir") + server._check_that_all_ports_are_available = lambda: True + server._is_server_running_on_all_ports = lambda: True + + server.start() + self.assertNotEquals(host.executive.calls, []) + + def wait_for_action(action): + if action(): + return True + return action() + + def mock_returns(return_values): + def return_value_thunk(*args, **kwargs): + return return_values.pop(0) + return return_value_thunk + + host.executive.check_running_pid = mock_returns([True, False]) + server._wait_for_action = wait_for_action + + server.stop() + self.assertEqual(['taskkill.exe', '/f', '/t', '/pid', 42], host.executive.calls[1]) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server.py index 604f76b8951..1fbf1321231 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server.py @@ -65,7 +65,7 @@ class Lighttpd(http_server_base.HttpServerBase): self._layout_tests_dir = self._port_obj.layout_tests_dir() self._webkit_tests = os.path.join(self._layout_tests_dir, 'http', 'tests') - self._js_test_resource = os.path.join(self._layout_tests_dir, 'fast', 'js', 'resources') + self._js_test_resource = os.path.join(self._layout_tests_dir, 'resources') self._media_resource = os.path.join(self._layout_tests_dir, 'media') # Self generated certificate for SSL server (for client cert get diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py index 5507d989057..740cd2b5692 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/http_server_unittest.py @@ -45,7 +45,7 @@ class TestHttpServer(unittest.TestCase): host = MockHost() test_port = test.TestPort(host) host.filesystem.write_text_file( - "/mock-checkout/Tools/Scripts/webkitpy/layout_tests/servers/lighttpd.conf", "Mock Config\n") + "/mock-checkout/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/lighttpd.conf", "Mock Config\n") host.filesystem.write_text_file( "/usr/lib/lighttpd/liblightcomp.dylib", "Mock dylib") @@ -57,7 +57,7 @@ class TestHttpServer(unittest.TestCase): config_file = host.filesystem.read_text_file("/mock/output_dir/lighttpd.conf") self.assertEqual(re.findall(r"alias.url.+", config_file), [ - 'alias.url = ( "/js-test-resources" => "/test.checkout/LayoutTests/fast/js/resources" )', + 'alias.url = ( "/js-test-resources" => "/test.checkout/LayoutTests/resources" )', 'alias.url += ( "/mock/one-additional-dir" => "/mock-checkout/one-additional-dir" )', 'alias.url += ( "/mock/another-additional-dir" => "/mock-checkout/one-additional-dir" )', 'alias.url += ( "/media-resources" => "/test.checkout/LayoutTests/media" )', @@ -67,7 +67,7 @@ class TestHttpServer(unittest.TestCase): host = MockHost() test_port = test.TestPort(host) host.filesystem.write_text_file( - "/mock-checkout/Tools/Scripts/webkitpy/layout_tests/servers/lighttpd.conf", "Mock Config\n") + "/mock-checkout/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/lighttpd.conf", "Mock Config\n") host.filesystem.write_text_file( "/usr/lib/lighttpd/liblightcomp.dylib", "Mock dylib") diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py index 2ffdc321dc6..9f47dee0d32 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/servers/websocket_server.py @@ -93,11 +93,11 @@ class PyWebSocket(http_server.Lighttpd): # The port objects are supposed to abstract this. if self._root: self._layout_tests = self._filesystem.abspath(self._root) - self._web_socket_tests = self._filesystem.abspath(self._filesystem.join(self._root, 'http', 'tests', 'websocket', 'tests')) + self._web_socket_tests = self._filesystem.abspath(self._filesystem.join(self._root, 'http', 'tests', 'websocket')) else: try: self._layout_tests = self._port_obj.layout_tests_dir() - self._web_socket_tests = self._filesystem.join(self._layout_tests, 'http', 'tests', 'websocket', 'tests') + self._web_socket_tests = self._filesystem.join(self._layout_tests, 'http', 'tests', 'websocket') except: self._web_socket_tests = None @@ -126,10 +126,9 @@ class PyWebSocket(http_server.Lighttpd): python_interp, '-u', pywebsocket_script, '--server-host', 'localhost', '--port', str(self._port), - # FIXME: Don't we have a self._port_obj.layout_test_path? - '--document-root', self._filesystem.join(self._layout_tests, 'http', 'tests'), + '--document-root', self._web_socket_tests, '--scan-dir', self._web_socket_tests, - '--cgi-paths', '/websocket/tests', + '--cgi-paths', '/', '--log-file', error_log, ] diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing.py index 55b045f1775..598f638a81f 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing.py @@ -77,7 +77,7 @@ class Printer(object): def print_config(self, results_directory): self._print_default("Using port '%s'" % self._port.name()) self._print_default("Test configuration: %s" % self._port.test_configuration()) - self._print_default("Placing test results in %s" % results_directory) + self._print_default("View the test results at file://%s/results.html" % results_directory) # FIXME: should these options be in printing_options? if self._options.new_baseline: @@ -388,8 +388,13 @@ class Printer(object): self._print_default(' base: %s' % base) self._print_default(' args: %s' % args) - for extension in ('.txt', '.png', '.wav'): - self._print_baseline(test_name, extension) + references = self._port.reference_files(test_name) + if references: + for _, filename in references: + self._print_default(' ref: %s' % self._port.relative_test_filename(filename)) + else: + for extension in ('.txt', '.png', '.wav'): + self._print_baseline(test_name, extension) self._print_default(' exp: %s' % exp_str) self._print_default(' got: %s' % got_str) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing_unittest.py index 1554cc1e959..a28639add64 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/views/printing_unittest.py @@ -132,7 +132,7 @@ class Testprinter(unittest.TestCase): printer.print_config('/tmp') self.assertIn("Using port 'test-mac-leopard'", err.getvalue()) self.assertIn('Test configuration: <leopard, x86, release>', err.getvalue()) - self.assertIn('Placing test results in /tmp', err.getvalue()) + self.assertIn('View the test results at file:///tmp', err.getvalue()) self.assertIn('Baseline search path: test-mac-leopard -> test-mac-snowleopard -> generic', err.getvalue()) self.assertIn('Using Release build', err.getvalue()) self.assertIn('Pixel tests enabled', err.getvalue()) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftest.py index 72e1e6aa68c..7abd6d7b836 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftest.py @@ -163,6 +163,7 @@ class PerfTest(object): _metrics_regex = re.compile(r'^(?P<metric>Time|Malloc|JS Heap):') _statistics_keys = ['avg', 'median', 'stdev', 'min', 'max', 'unit', 'values'] _score_regex = re.compile(r'^(?P<key>' + r'|'.join(_statistics_keys) + r')\s+(?P<value>([0-9\.]+(,\s+)?)+)\s*(?P<unit>.*)') + _console_regex = re.compile(r'^CONSOLE MESSAGE:') def _run_with_driver(self, driver, time_out_ms): output = self.run_single(driver, self.test_path(), time_out_ms) @@ -175,6 +176,7 @@ class PerfTest(object): description_match = self._description_regex.match(line) metric_match = self._metrics_regex.match(line) score = self._score_regex.match(line) + console_match = self._console_regex.match(line) if description_match: self._description = description_match.group('description') @@ -186,6 +188,9 @@ class PerfTest(object): metric = self._ensure_metrics(current_metric, score.group('unit')) metric.append_group(map(lambda value: float(value), score.group('value').split(', '))) + elif console_match: + # Ignore console messages such as deprecation warnings. + continue else: _log.error('ERROR: ' + line) return False @@ -199,7 +204,7 @@ class PerfTest(object): return self._metrics[metric_name] def run_single(self, driver, test_path, time_out_ms, should_run_pixel_test=False): - return driver.run_test(DriverInput(test_path, time_out_ms, image_hash=None, should_run_pixel_test=should_run_pixel_test), stop_when_done=False) + return driver.run_test(DriverInput(test_path, time_out_ms, image_hash=None, should_run_pixel_test=should_run_pixel_test, args=[]), stop_when_done=False) def run_failed(self, output): if output.error: diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py index 5e6aec7d170..2b2b2d0f513 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py @@ -183,7 +183,7 @@ class PerfTestsRunner(object): def write_throttled_update(self, msg): pass - if not self._port.check_build(needs_http=needs_http, printer=FakePrinter()): + if self._port.check_build(needs_http=needs_http, printer=FakePrinter()): _log.error("Build not up to date for %s" % self._port._path_to_driver()) return self.EXIT_CODE_BAD_BUILD diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checker.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checker.py index f7cd3e5ef4f..e7a2dec01ae 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checker.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checker.py @@ -144,7 +144,7 @@ _PATH_RULES_SPECIFIER = [ ([# Jinja templates: files have .cpp or .h extensions, but contain # template code, which can't be handled, so disable tests. "Source/bindings/templates", - "Source/core/scripts/templates"], + "Source/build/scripts/templates"], ["-"]), ([# IDL compiler reference output diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp.py index 806f9413470..52519527f81 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp.py @@ -36,7 +36,6 @@ """Support for check-webkit-style.""" -import codecs import math # for log import os import os.path @@ -47,9 +46,7 @@ import sys import unicodedata from webkitpy.common.memoized import memoized - -# The key to use to provide a class to fake loading a header file. -INCLUDE_IO_INJECTION_KEY = 'include_header_io' +from webkitpy.common.system.filesystem import FileSystem # Headers that we consider STL headers. _STL_HEADERS = frozenset([ @@ -121,12 +118,6 @@ _OTHER_HEADER = 2 _MOC_HEADER = 3 -# A dictionary of items customize behavior for unit test. For example, -# INCLUDE_IO_INJECTION_KEY allows providing a custom io class which allows -# for faking a header file. -_unit_test_config = {} - - # The regexp compilation caching is inlined in all regexp functions for # performance reasons; factoring it out into a separate function turns out # to be noticeably expensive. @@ -3284,11 +3275,9 @@ def check_language(filename, clean_lines, line_number, file_extension, include_s error(line_number, 'runtime/unsigned', 1, 'Omit int when using unsigned') - # Check that we're not using static_cast<Text*>. - if search(r'\bstatic_cast<Text\*>', line): - error(line_number, 'readability/check', 4, - 'Consider using toText helper function in WebCore/dom/Text.h ' - 'instead of static_cast<Text*>') + # Check for usage of static_cast<Classname*>. + check_for_object_static_cast(filename, line_number, line, error) + def check_identifier_name_in_declaration(filename, line_number, line, file_state, error): """Checks if identifier names contain any underscores. @@ -3421,6 +3410,144 @@ def check_identifier_name_in_declaration(filename, line_number, line, file_state number_of_identifiers += 1 line = line[matched.end():] + +def check_for_toFoo_definition(filename, pattern, error): + """ Reports for using static_cast instead of toFoo convenience function. + + This function will output warnings to make sure you are actually using + the added toFoo conversion functions rather than directly hard coding + the static_cast<Classname*> call. For example, you should toHTMLELement(Node*) + to convert Node* to HTMLElement*, instead of static_cast<HTMLElement*>(Node*) + + Args: + filename: The name of the header file in which to check for toFoo definition. + pattern: The conversion function pattern to grep for. + error: The function to call with any errors found. + """ + def get_abs_filepath(filename): + fileSystem = FileSystem() + base_dir = fileSystem.path_to_module(FileSystem.__module__).split('WebKit', 1)[0] + base_dir = ''.join((base_dir, 'WebKit/Source')) + for root, dirs, names in os.walk(base_dir): + if filename in names: + return os.path.join(root, filename) + return None + + def grep(lines, pattern, error): + matches = [] + function_state = None + for line_number in xrange(lines.num_lines()): + line = (lines.elided[line_number]).rstrip() + try: + if pattern in line: + if not function_state: + function_state = _FunctionState(1) + detect_functions(lines, line_number, function_state, error) + # Exclude the match of dummy conversion function. Dummy function is just to + # catch invalid conversions and shouldn't be part of possible alternatives. + result = re.search(r'%s(\s+)%s' % ("void", pattern), line) + if not result: + matches.append([line, function_state.body_start_position.row, function_state.end_position.row + 1]) + function_state = None + except UnicodeDecodeError: + # There would be no non-ascii characters in the codebase ever. The only exception + # would be comments/copyright text which might have non-ascii characters. Hence, + # it is prefectly safe to catch the UnicodeDecodeError and just pass the line. + pass + + return matches + + def check_in_mock_header(filename, matches=None): + if not filename == 'Foo.h': + return False + + header_file = None + try: + header_file = CppChecker.fs.read_text_file(filename) + except IOError: + return False + line_number = 0 + for line in header_file: + line_number += 1 + matched = re.search(r'\btoFoo\b', line) + if matched: + matches.append(['toFoo', line_number, line_number + 3]) + return True + + # For unit testing only, avoid header search and lookup locally. + matches = [] + mock_def_found = check_in_mock_header(filename, matches) + if mock_def_found: + return matches + + # Regular style check flow. Search for actual header file & defs. + file_path = get_abs_filepath(filename) + if not file_path: + return None + try: + f = open(file_path) + clean_lines = CleansedLines(f.readlines()) + finally: + f.close() + + # Make a list of all genuine alternatives to static_cast. + matches = grep(clean_lines, pattern, error) + return matches + + +def check_for_object_static_cast(processing_file, line_number, line, error): + """Checks for a Cpp-style static cast on objects by looking for the pattern. + + Args: + processing_file: The name of the processing file. + line_number: The number of the line to check. + line: The line of code to check. + error: The function to call with any errors found. + """ + matched = search(r'\bstatic_cast<(\s*\w*:?:?\w+\s*\*+\s*)>', line) + if not matched: + return + + class_name = re.sub('[\*]', '', matched.group(1)) + class_name = class_name.strip() + # Ignore (for now) when the casting is to void*, + if class_name == 'void': + return + + namespace_pos = class_name.find(':') + if not namespace_pos == -1: + class_name = class_name[namespace_pos + 2:] + + header_file = ''.join((class_name, '.h')) + matches = check_for_toFoo_definition(header_file, ''.join(('to', class_name)), error) + # Ignore (for now) if not able to find the header where toFoo might be defined. + # TODO: Handle cases where Classname might be defined in some other header or cpp file. + if matches is None: + return + + report_error = True + # Ensure found static_cast instance is not from within toFoo definition itself. + if (os.path.basename(processing_file) == header_file): + for item in matches: + if line_number in range(item[1], item[2]): + report_error = False + break + + if report_error: + if len(matches): + # toFoo is defined - enforce using it. + # TODO: Suggest an appropriate toFoo from the alternatives present in matches. + error(line_number, 'runtime/casting', 4, + 'static_cast of class objects is not allowed. Use to%s defined in %s.' % + (class_name, header_file)) + else: + # No toFoo defined - enforce definition & usage. + # TODO: Automate the generation of toFoo() to avoid any slippages ever. + error(line_number, 'runtime/casting', 4, + 'static_cast of class objects is not allowed. Add to%s in %s and use it instead.' % + (class_name, header_file)) + + def check_c_style_cast(line_number, line, raw_line, cast_type, pattern, error): """Checks for a C-style cast by looking for the pattern. @@ -3593,7 +3720,7 @@ def files_belong_to_same_module(filename_cpp, filename_h): return files_belong_to_same_module, common_path -def update_include_state(filename, include_state, io=codecs): +def update_include_state(filename, include_state): """Fill up the include_state with new includes found from the file. Args: @@ -3604,10 +3731,9 @@ def update_include_state(filename, include_state, io=codecs): Returns: True if a header was succesfully added. False otherwise. """ - io = _unit_test_config.get(INCLUDE_IO_INJECTION_KEY, codecs) header_file = None try: - header_file = io.open(filename, 'r', 'utf8', 'replace') + header_file = CppChecker.fs.read_text_file(filename) except IOError: return False line_number = 0 @@ -3875,8 +4001,10 @@ class CppChecker(object): 'whitespace/todo', ]) + fs = None + def __init__(self, file_path, file_extension, handle_style_error, - min_confidence): + min_confidence, fs=None): """Create a CppChecker instance. Args: @@ -3888,6 +4016,7 @@ class CppChecker(object): self.file_path = file_path self.handle_style_error = handle_style_error self.min_confidence = min_confidence + CppChecker.fs = fs or FileSystem() # Useful for unit testing. def __eq__(self, other): @@ -3914,9 +4043,6 @@ class CppChecker(object): # FIXME: Remove this function (requires refactoring unit tests). -def process_file_data(filename, file_extension, lines, error, min_confidence, unit_test_config): - global _unit_test_config - _unit_test_config = unit_test_config - checker = CppChecker(filename, file_extension, error, min_confidence) +def process_file_data(filename, file_extension, lines, error, min_confidence, fs=None): + checker = CppChecker(filename, file_extension, error, min_confidence, fs) checker.check(lines) - _unit_test_config = {} diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py index ab53f175ab6..6df53dac3bc 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py @@ -35,7 +35,6 @@ # FIXME: Add a good test that tests UpdateIncludeState. -import codecs import os import random import re @@ -43,6 +42,7 @@ import webkitpy.thirdparty.unittest2 as unittest import cpp as cpp_style from cpp import CppChecker from ..filter import FilterConfiguration +from webkitpy.common.system.filesystem import FileSystem # This class works as an error collector and replaces cpp_style.Error # function for the unit tests. We also verify each category we see @@ -99,17 +99,6 @@ class ErrorCollector: sys.exit('FATAL ERROR: There are no tests for category "%s"' % category) -# This class is a lame mock of codecs. We do not verify filename, mode, or -# encoding, but for the current use case it is not needed. -class MockIo: - def __init__(self, mock_file): - self.mock_file = mock_file - - def open(self, unused_filename, unused_mode, unused_encoding, _): # NOLINT - # (lint doesn't like open as a method name) - return self.mock_file - - class CppFunctionsTest(unittest.TestCase): """Supports testing functions that do not need CppStyleTestBase.""" @@ -246,16 +235,16 @@ class CppStyleTestBase(unittest.TestCase): # Helper function to avoid needing to explicitly pass confidence # in all the unit test calls to cpp_style.process_file_data(). - def process_file_data(self, filename, file_extension, lines, error, unit_test_config={}): + def process_file_data(self, filename, file_extension, lines, error, fs=None): """Call cpp_style.process_file_data() with the min_confidence.""" return cpp_style.process_file_data(filename, file_extension, lines, - error, self.min_confidence, unit_test_config) + error, self.min_confidence, fs) - def perform_lint(self, code, filename, basic_error_rules, unit_test_config={}, lines_to_check=None): + def perform_lint(self, code, filename, basic_error_rules, fs=None, lines_to_check=None): error_collector = ErrorCollector(self.assertTrue, FilterConfiguration(basic_error_rules), lines_to_check) lines = code.split('\n') extension = filename.split('.')[1] - self.process_file_data(filename, extension, lines, error_collector, unit_test_config) + self.process_file_data(filename, extension, lines, error_collector, fs) return error_collector.results() # Perform lint on single line of input and return the error message. @@ -304,11 +293,15 @@ class CppStyleTestBase(unittest.TestCase): return self.perform_lint(code, 'test.cpp', basic_error_rules) # Only include what you use errors. - def perform_include_what_you_use(self, code, filename='foo.h', io=codecs): + def perform_include_what_you_use(self, code, filename='foo.h', fs=None): basic_error_rules = ('-', '+build/include_what_you_use') - unit_test_config = {cpp_style.INCLUDE_IO_INJECTION_KEY: io} - return self.perform_lint(code, filename, basic_error_rules, unit_test_config) + return self.perform_lint(code, filename, basic_error_rules, fs) + + def perform_avoid_static_cast_of_objects(self, code, filename='foo.cpp', fs=None): + basic_error_rules = ('-', + '+runtime/casting') + return self.perform_lint(code, filename, basic_error_rules, fs) # Perform lint and compare the error message with "expected_message". def assert_lint(self, code, expected_message, file_name='foo.cpp'): @@ -764,13 +757,44 @@ class CppStyleTest(CppStyleTestBase): self.assert_language_rules_check('foo.cpp', statement, error_message) self.assert_language_rules_check('foo.h', statement, error_message) - # Test for static_cast readability. - def test_static_cast_readability(self): - self.assert_lint( - 'Text* x = static_cast<Text*>(foo);', - 'Consider using toText helper function in WebCore/dom/Text.h ' - 'instead of static_cast<Text*>' - ' [readability/check] [4]') + # Tests for static_cast readability. + def test_static_cast_on_objects_with_toFoo(self): + mock_header_contents = ['inline Foo* toFoo(Bar* bar)'] + fs = FileSystem() + orig_read_text_file_fn = fs.read_text_file + + def mock_read_text_file_fn(path): + return mock_header_contents + + try: + fs.read_text_file = mock_read_text_file_fn + message = self.perform_avoid_static_cast_of_objects( + 'Foo* x = static_cast<Foo*>(bar);', + filename='casting.cpp', + fs=fs) + self.assertEqual(message, 'static_cast of class objects is not allowed. Use toFoo defined in Foo.h.' + ' [runtime/casting] [4]') + finally: + fs.read_text_file = orig_read_text_file_fn + + def test_static_cast_on_objects_without_toFoo(self): + mock_header_contents = ['inline FooBar* toFooBar(Bar* bar)'] + fs = FileSystem() + orig_read_text_file_fn = fs.read_text_file + + def mock_read_text_file_fn(path): + return mock_header_contents + + try: + fs.read_text_file = mock_read_text_file_fn + message = self.perform_avoid_static_cast_of_objects( + 'Foo* x = static_cast<Foo*>(bar);', + filename='casting.cpp', + fs=fs) + self.assertEqual(message, 'static_cast of class objects is not allowed. Add toFoo in Foo.h and use it instead.' + ' [runtime/casting] [4]') + finally: + fs.read_text_file = orig_read_text_file_fn # We cannot test this functionality because of difference of # function definitions. Anyway, we may never enable this. @@ -997,43 +1021,53 @@ class CppStyleTest(CppStyleTestBase): # Test the UpdateIncludeState code path. mock_header_contents = ['#include "blah/foo.h"', '#include "blah/bar.h"'] - message = self.perform_include_what_you_use( - '#include "config.h"\n' - '#include "blah/a.h"\n', - filename='blah/a.cpp', - io=MockIo(mock_header_contents)) - self.assertEqual(message, '') - - mock_header_contents = ['#include <set>'] - message = self.perform_include_what_you_use( - '''#include "config.h" - #include "blah/a.h" - - std::set<int> foo;''', - filename='blah/a.cpp', - io=MockIo(mock_header_contents)) - self.assertEqual(message, '') - - # If there's just a .cpp and the header can't be found then it's ok. - message = self.perform_include_what_you_use( - '''#include "config.h" - #include "blah/a.h" - - std::set<int> foo;''', - filename='blah/a.cpp') - self.assertEqual(message, '') - - # Make sure we find the headers with relative paths. - mock_header_contents = [''] - message = self.perform_include_what_you_use( - '''#include "config.h" - #include "%s%sa.h" - - std::set<int> foo;''' % (os.path.basename(os.getcwd()), os.path.sep), - filename='a.cpp', - io=MockIo(mock_header_contents)) - self.assertEqual(message, 'Add #include <set> for set<> ' - '[build/include_what_you_use] [4]') + fs = FileSystem() + orig_read_text_file_fn = fs.read_text_file + + def mock_read_text_file_fn(path): + return mock_header_contents + + try: + fs.read_text_file = mock_read_text_file_fn + message = self.perform_include_what_you_use( + '#include "config.h"\n' + '#include "blah/a.h"\n', + filename='blah/a.cpp', + fs=fs) + self.assertEqual(message, '') + + mock_header_contents = ['#include <set>'] + message = self.perform_include_what_you_use( + '''#include "config.h" + #include "blah/a.h" + + std::set<int> foo;''', + filename='blah/a.cpp', + fs=fs) + self.assertEqual(message, '') + + # If there's just a .cpp and the header can't be found then it's ok. + message = self.perform_include_what_you_use( + '''#include "config.h" + #include "blah/a.h" + + std::set<int> foo;''', + filename='blah/a.cpp') + self.assertEqual(message, '') + + # Make sure we find the headers with relative paths. + mock_header_contents = [''] + message = self.perform_include_what_you_use( + '''#include "config.h" + #include "%s%sa.h" + + std::set<int> foo;''' % (os.path.basename(os.getcwd()), os.path.sep), + filename='a.cpp', + fs=fs) + self.assertEqual(message, 'Add #include <set> for set<> ' + '[build/include_what_you_use] [4]') + finally: + fs.read_text_file = orig_read_text_file_fn def test_files_belong_to_same_module(self): f = cpp_style.files_belong_to_same_module diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/python.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/python.py index 2aabb4ddcfb..f09638ca6a2 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/python.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/style/checkers/python.py @@ -75,7 +75,9 @@ class PythonChecker(object): wkf = WebKitFinder(FileSystem()) executive = Executive() env = os.environ.copy() - env['PYTHONPATH'] = ('%s%s%s' % (wkf.path_from_webkit_base('Tools', 'Scripts'), + env['PYTHONPATH'] = ('%s%s%s%s%s' % (wkf.path_from_webkit_base('Tools', 'Scripts'), + os.pathsep, + wkf.path_from_webkit_base('Source', 'build', 'scripts'), os.pathsep, wkf.path_from_webkit_base('Tools', 'Scripts', 'webkitpy', 'thirdparty'))) return executive.run_command([sys.executable, wkf.path_from_depot_tools_base('pylint.py'), diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py index 6ba1544071b..bc493c8d92a 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/extensions.py @@ -33,6 +33,7 @@ from mod_pywebsocket import util from mod_pywebsocket.http_header_util import quote_if_necessary +# The list of available server side extension processor classes. _available_processors = {} _compression_extension_names = [] @@ -40,6 +41,8 @@ _compression_extension_names = [] class ExtensionProcessorInterface(object): def __init__(self, request): + self._logger = util.get_class_logger(self) + self._request = request self._active = True @@ -62,12 +65,14 @@ class ExtensionProcessorInterface(object): return None def get_extension_response(self): - if self._active: - response = self._get_extension_response_internal() - if response is None: - self._active = False - return response - return None + if not self._active: + self._logger.debug('Extension %s is deactivated', self.name()) + return None + + response = self._get_extension_response_internal() + if response is None: + self._active = False + return response def _setup_stream_options_internal(self, stream_options): pass @@ -450,10 +455,10 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-08 """ - _S2C_MAX_WINDOW_BITS_PARAM = 's2c_max_window_bits' - _S2C_NO_CONTEXT_TAKEOVER_PARAM = 's2c_no_context_takeover' - _C2S_MAX_WINDOW_BITS_PARAM = 'c2s_max_window_bits' - _C2S_NO_CONTEXT_TAKEOVER_PARAM = 'c2s_no_context_takeover' + _SERVER_MAX_WINDOW_BITS_PARAM = 'server_max_window_bits' + _SERVER_NO_CONTEXT_TAKEOVER_PARAM = 'server_no_context_takeover' + _CLIENT_MAX_WINDOW_BITS_PARAM = 'client_max_window_bits' + _CLIENT_NO_CONTEXT_TAKEOVER_PARAM = 'client_no_context_takeover' def __init__(self, request, draft08=True): """Construct PerMessageDeflateExtensionProcessor @@ -468,8 +473,8 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): ExtensionProcessorInterface.__init__(self, request) self._logger = util.get_class_logger(self) - self._preferred_c2s_max_window_bits = None - self._c2s_no_context_takeover = False + self._preferred_client_max_window_bits = None + self._client_no_context_takeover = False self._draft08 = draft08 @@ -479,129 +484,133 @@ class PerMessageDeflateExtensionProcessor(ExtensionProcessorInterface): def _get_extension_response_internal(self): if self._draft08: for name in self._request.get_parameter_names(): - if name not in [self._S2C_MAX_WINDOW_BITS_PARAM, - self._S2C_NO_CONTEXT_TAKEOVER_PARAM, - self._C2S_MAX_WINDOW_BITS_PARAM]: + if name not in [self._SERVER_MAX_WINDOW_BITS_PARAM, + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, + self._CLIENT_MAX_WINDOW_BITS_PARAM]: self._logger.debug('Unknown parameter: %r', name) return None else: # Any unknown parameter will be just ignored. pass - s2c_max_window_bits = None - if self._request.has_parameter(self._S2C_MAX_WINDOW_BITS_PARAM): - s2c_max_window_bits = self._request.get_parameter_value( - self._S2C_MAX_WINDOW_BITS_PARAM) + server_max_window_bits = None + if self._request.has_parameter(self._SERVER_MAX_WINDOW_BITS_PARAM): + server_max_window_bits = self._request.get_parameter_value( + self._SERVER_MAX_WINDOW_BITS_PARAM) try: - s2c_max_window_bits = _parse_window_bits(s2c_max_window_bits) + server_max_window_bits = _parse_window_bits( + server_max_window_bits) except ValueError, e: self._logger.debug('Bad %s parameter: %r', - self._S2C_MAX_WINDOW_BITS_PARAM, + self._SERVER_MAX_WINDOW_BITS_PARAM, e) return None - s2c_no_context_takeover = self._request.has_parameter( - self._S2C_NO_CONTEXT_TAKEOVER_PARAM) - if (s2c_no_context_takeover and + server_no_context_takeover = self._request.has_parameter( + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) + if (server_no_context_takeover and self._request.get_parameter_value( - self._S2C_NO_CONTEXT_TAKEOVER_PARAM) is not None): + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM) is not None): self._logger.debug('%s parameter must not have a value: %r', - self._S2C_NO_CONTEXT_TAKEOVER_PARAM, - s2c_no_context_takeover) + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, + server_no_context_takeover) return None - # c2s_max_window_bits from a client indicates whether the client can - # accept c2s_max_window_bits from a server or not. - client_c2s_max_window_bits = self._request.has_parameter( - self._C2S_MAX_WINDOW_BITS_PARAM) + # client_max_window_bits from a client indicates whether the client can + # accept client_max_window_bits from a server or not. + client_client_max_window_bits = self._request.has_parameter( + self._CLIENT_MAX_WINDOW_BITS_PARAM) if (self._draft08 and - client_c2s_max_window_bits and + client_client_max_window_bits and self._request.get_parameter_value( - self._C2S_MAX_WINDOW_BITS_PARAM) is not None): + self._CLIENT_MAX_WINDOW_BITS_PARAM) is not None): self._logger.debug('%s parameter must not have a value in a ' 'client\'s opening handshake: %r', - self._C2S_MAX_WINDOW_BITS_PARAM, - client_c2s_max_window_bits) + self._CLIENT_MAX_WINDOW_BITS_PARAM, + client_client_max_window_bits) return None self._rfc1979_deflater = util._RFC1979Deflater( - s2c_max_window_bits, s2c_no_context_takeover) + server_max_window_bits, server_no_context_takeover) # Note that we prepare for incoming messages compressed with window - # bits upto 15 regardless of the c2s_max_window_bits value to be sent - # to the client. + # bits upto 15 regardless of the client_max_window_bits value to be + # sent to the client. self._rfc1979_inflater = util._RFC1979Inflater() self._framer = _PerMessageDeflateFramer( - s2c_max_window_bits, s2c_no_context_takeover) + server_max_window_bits, server_no_context_takeover) self._framer.set_bfinal(False) self._framer.set_compress_outgoing_enabled(True) response = common.ExtensionParameter(self._request.name()) - if s2c_max_window_bits is not None: + if server_max_window_bits is not None: response.add_parameter( - self._S2C_MAX_WINDOW_BITS_PARAM, str(s2c_max_window_bits)) + self._SERVER_MAX_WINDOW_BITS_PARAM, + str(server_max_window_bits)) - if s2c_no_context_takeover: + if server_no_context_takeover: response.add_parameter( - self._S2C_NO_CONTEXT_TAKEOVER_PARAM, None) + self._SERVER_NO_CONTEXT_TAKEOVER_PARAM, None) - if self._preferred_c2s_max_window_bits is not None: - if self._draft08 and not client_c2s_max_window_bits: + if self._preferred_client_max_window_bits is not None: + if self._draft08 and not client_client_max_window_bits: self._logger.debug('Processor is configured to use %s but ' 'the client cannot accept it', - self._C2S_MAX_WINDOW_BITS_PARAM) + self._CLIENT_MAX_WINDOW_BITS_PARAM) return None response.add_parameter( - self._C2S_MAX_WINDOW_BITS_PARAM, - str(self._preferred_c2s_max_window_bits)) + self._CLIENT_MAX_WINDOW_BITS_PARAM, + str(self._preferred_client_max_window_bits)) - if self._c2s_no_context_takeover: + if self._client_no_context_takeover: response.add_parameter( - self._C2S_NO_CONTEXT_TAKEOVER_PARAM, None) + self._CLIENT_NO_CONTEXT_TAKEOVER_PARAM, None) self._logger.debug( 'Enable %s extension (' - 'request: s2c_max_window_bits=%s; s2c_no_context_takeover=%r, ' - 'response: c2s_max_window_bits=%s; c2s_no_context_takeover=%r)' % + 'request: server_max_window_bits=%s; ' + 'server_no_context_takeover=%r, ' + 'response: client_max_window_bits=%s; ' + 'client_no_context_takeover=%r)' % (self._request.name(), - s2c_max_window_bits, - s2c_no_context_takeover, - self._preferred_c2s_max_window_bits, - self._c2s_no_context_takeover)) + server_max_window_bits, + server_no_context_takeover, + self._preferred_client_max_window_bits, + self._client_no_context_takeover)) return response def _setup_stream_options_internal(self, stream_options): self._framer.setup_stream_options(stream_options) - def set_c2s_max_window_bits(self, value): - """If this option is specified, this class adds the c2s_max_window_bits - extension parameter to the handshake response, but doesn't reduce the - LZ77 sliding window size of its inflater. I.e., you can use this for - testing client implementation but cannot reduce memory usage of this - class. + def set_client_max_window_bits(self, value): + """If this option is specified, this class adds the + client_max_window_bits extension parameter to the handshake response, + but doesn't reduce the LZ77 sliding window size of its inflater. + I.e., you can use this for testing client implementation but cannot + reduce memory usage of this class. If this method has been called with True and an offer without the - c2s_max_window_bits extension parameter is received, + client_max_window_bits extension parameter is received, - (When processing the permessage-deflate extension) this processor declines the request. - (When processing the permessage-compress extension) this processor accepts the request. """ - self._preferred_c2s_max_window_bits = value + self._preferred_client_max_window_bits = value - def set_c2s_no_context_takeover(self, value): + def set_client_no_context_takeover(self, value): """If this option is specified, this class adds the - c2s_no_context_takeover extension parameter to the handshake response, - but doesn't reset inflater for each message. I.e., you can use this for - testing client implementation but cannot reduce memory usage of this - class. + client_no_context_takeover extension parameter to the handshake + response, but doesn't reset inflater for each message. I.e., you can + use this for testing client implementation but cannot reduce memory + usage of this class. """ - self._c2s_no_context_takeover = value + self._client_no_context_takeover = value def set_bfinal(self, value): self._framer.set_bfinal(value) @@ -886,6 +895,11 @@ _available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor def get_extension_processor(extension_request): + """Given an ExtensionParameter representing an extension offer received + from a client, configures and returns an instance of the corresponding + extension processor class. + """ + processor_class = _available_processors.get(extension_request.name()) if processor_class is None: return None diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py index 2bf3b0c286f..d270bf09347 100755 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/mod_pywebsocket/standalone.py @@ -583,8 +583,9 @@ class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): self._logger.debug('%r', e) raise socket.error(1, '%r' % e) cert = accepted_socket.get_peer_certificate() - self._logger.debug('Client cert subject: %r', - cert.get_subject().get_components()) + if cert is not None: + self._logger.debug('Client cert subject: %r', + cert.get_subject().get_components()) accepted_socket = _StandaloneSSLConnection(accepted_socket) else: raise ValueError('No TLS support module is available') @@ -661,6 +662,99 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): CGIHTTPServer.CGIHTTPRequestHandler.__init__( self, request, client_address, server) + def _xhr_send_benchmark_helper(self): + content_length = int(self.headers.getheader('Content-Length')) + + self._logger.debug('Requested to receive %s bytes', content_length) + + RECEIVE_BLOCK_SIZE = 1024 * 1024 + + bytes_to_receive = content_length + while bytes_to_receive > 0: + bytes_to_receive_in_this_loop = bytes_to_receive + if bytes_to_receive_in_this_loop > RECEIVE_BLOCK_SIZE: + bytes_to_receive_in_this_loop = RECEIVE_BLOCK_SIZE + received_data = self.rfile.read(bytes_to_receive_in_this_loop) + for c in received_data: + if c != 'a': + self._logger.debug('Request body verification failed') + return + bytes_to_receive -= len(received_data) + if bytes_to_receive < 0: + self._logger.debug('Received %d more bytes than expected' % + (-bytes_to_receive)) + return + + # Return the number of received bytes back to the client. + response_body = '%d' % content_length + self.wfile.write( + 'HTTP/1.1 200 OK\r\n' + 'Content-Type: text/html\r\n' + 'Content-Length: %d\r\n' + '\r\n%s' % (len(response_body), response_body)) + self.wfile.flush() + + def _xhr_receive_benchmark_helper(self): + content_length = self.headers.getheader('Content-Length') + request_body = self.rfile.read(int(content_length)) + + request_array = request_body.split(' ') + if len(request_array) < 2: + self._logger.debug('Malformed request body: %r', request_body) + return + + # Parse the size parameter. + bytes_to_send = request_array[0] + try: + bytes_to_send = int(bytes_to_send) + except ValueError, e: + self._logger.debug('Malformed size parameter: %r', bytes_to_send) + return + self._logger.debug('Requested to send %s bytes', bytes_to_send) + + # Parse the transfer encoding parameter. + chunked_mode = False + mode_parameter = request_array[1] + if mode_parameter == 'chunked': + self._logger.debug('Requested chunked transfer encoding') + chunked_mode = True + elif mode_parameter != 'none': + self._logger.debug('Invalid mode parameter: %r', mode_parameter) + return + + # Write a header + response_header = ( + 'HTTP/1.1 200 OK\r\n' + 'Content-Type: application/octet-stream\r\n') + if chunked_mode: + response_header += 'Transfer-Encoding: chunked\r\n\r\n' + else: + response_header += ( + 'Content-Length: %d\r\n\r\n' % bytes_to_send) + self.wfile.write(response_header) + self.wfile.flush() + + # Write a body + SEND_BLOCK_SIZE = 1024 * 1024 + + while bytes_to_send > 0: + bytes_to_send_in_this_loop = bytes_to_send + if bytes_to_send_in_this_loop > SEND_BLOCK_SIZE: + bytes_to_send_in_this_loop = SEND_BLOCK_SIZE + + if chunked_mode: + self.wfile.write('%x\r\n' % bytes_to_send_in_this_loop) + self.wfile.write('a' * bytes_to_send_in_this_loop) + if chunked_mode: + self.wfile.write('\r\n') + self.wfile.flush() + + bytes_to_send -= bytes_to_send_in_this_loop + + if chunked_mode: + self.wfile.write('0\r\n\r\n') + self.wfile.flush() + def parse_request(self): """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. @@ -693,6 +787,16 @@ class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): return True host, port, resource = http_header_util.parse_uri(self.path) + + # Special paths for XMLHttpRequest benchmark + xhr_benchmark_helper_prefix = '/073be001e10950692ccbf3a2ad21c245' + if resource == (xhr_benchmark_helper_prefix + '_send'): + self._xhr_send_benchmark_helper() + return False + if resource == (xhr_benchmark_helper_prefix + '_receive'): + self._xhr_receive_benchmark_helper() + return False + if resource is None: self._logger.info('Invalid URI: %r', self.path) self._logger.info('Fallback to CGIHTTPRequestHandler') diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer.py index 657a2392339..684da06d003 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer.py @@ -182,7 +182,10 @@ class CommitAnnouncer(SingleServerIRCBot): return '%s committed "%s" %s %s%s' % (email, subject, first_url, svn_url, red_flag_message) def _post(self, message): - self.connection.execute_delayed(0, lambda: self.connection.privmsg(channel, message)) + self.connection.execute_delayed(0, lambda: self.connection.privmsg(channel, self._sanitize_string(message))) + + def _sanitize_string(self, message): + return message.encode('ascii', 'backslashreplace') class CommitAnnouncerThread(threading.Thread): diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer_unittest.py index c6adb0170ec..faf10848a2c 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/bot/commitannouncer_unittest.py @@ -206,3 +206,8 @@ Review URL: https://codereview.chromium.org/123456 git-svn-id: svn://svn.chromium.org/blink/trunk@456789 bbb929c8-8fbe-4397-9dbb-9b2b20218538 """)) + + def test_sanitize_string(self): + bot = CommitAnnouncer(MockTool(), "test_password") + self.assertEqual('normal ascii', bot._sanitize_string('normal ascii')) + self.assertEqual('uni\\u0441ode!', bot._sanitize_string(u'uni\u0441ode!')) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/__init__.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/__init__.py index 39f15361a1b..64720f53202 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/__init__.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/__init__.py @@ -1,7 +1,6 @@ # Required for Python to search this directory for module files from webkitpy.tool.commands.commitannouncer import CommitAnnouncerCommand -from webkitpy.tool.commands.download import * from webkitpy.tool.commands.flakytests import FlakyTests from webkitpy.tool.commands.gardenomatic import GardenOMatic from webkitpy.tool.commands.prettydiff import PrettyDiff diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py index 98c33d0ea32..3beb121d9d8 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/commitannouncer.py @@ -38,6 +38,7 @@ _log = logging.getLogger(__name__) class CommitAnnouncerCommand(AbstractDeclarativeCommand): name = "commit-announcer" help_text = "Start an IRC bot for announcing new git commits." + show_in_main_help = True def __init__(self): options = [ diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/download.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/download.py deleted file mode 100644 index f26e09e482d..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/download.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) 2009, 2011 Google Inc. All rights reserved. -# Copyright (c) 2009 Apple Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging - -from webkitpy.tool import steps - -from webkitpy.common.config import urls -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand -from webkitpy.tool.commands.stepsequence import StepSequence -from webkitpy.tool.comments import bug_comment_from_commit_text -from webkitpy.tool.grammar import pluralize -from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand - -_log = logging.getLogger(__name__) - - -class Clean(AbstractSequencedCommand): - name = "clean" - help_text = "Clean the working copy" - steps = [ - steps.DiscardLocalChanges, - ] - - def _prepare_state(self, options, args, tool): - options.force_clean = True - - -class CheckStyleLocal(AbstractSequencedCommand): - name = "check-style-local" - help_text = "Run check-webkit-style on the current working directory diff" - steps = [ - steps.CheckStyle, - ] diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/download_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/download_unittest.py deleted file mode 100644 index ce2c7f33a0f..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/download_unittest.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2009, 2011 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.commands.commandtest import CommandsTest -from webkitpy.tool.commands.download import * -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.common.checkout.checkout_mock import MockCheckout - - -class DownloadCommandsTest(CommandsTest): - maxDiff = None - - def _default_options(self): - options = MockOptions() - options.build = True - options.build_style = True - options.check_style = True - options.check_style_filter = None - options.clean = True - options.close_bug = True - options.force_clean = False - options.non_interactive = False - options.parent_command = 'MOCK parent command' - options.quiet = False - options.test = True - options.update = True - return options diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py index cdd98b81124..3a97cd61cd7 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/flakytests.py @@ -34,6 +34,7 @@ from webkitpy.layout_tests.models.test_expectations import TestExpectationParser class FlakyTests(AbstractDeclarativeCommand): name = "flaky-tests" help_text = "Generate FlakyTests file from the flakiness dashboard" + show_in_main_help = True def execute(self, options, args, tool): port = tool.port_factory.get() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py index e1ebe9b88dd..0a3a703db0e 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/gardenomatic.py @@ -30,6 +30,7 @@ from webkitpy.tool.servers.gardeningserver import GardeningHTTPServer class GardenOMatic(AbstractRebaseliningCommand): name = "garden-o-matic" help_text = "Command for gardening the WebKit tree." + show_in_main_help = True def __init__(self): super(GardenOMatic, self).__init__(options=(self.platform_options + [ diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py index 0c3d8cd2e89..7d2f3f56352 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queries.py @@ -45,6 +45,7 @@ _log = logging.getLogger(__name__) class CrashLog(AbstractDeclarativeCommand): name = "crash-log" help_text = "Print the newest crash log for the given process" + show_in_main_help = True long_help = """Finds the newest crash log matching the given process name and PID and prints it to stdout.""" argument_names = "PROCESS_NAME [PID]" @@ -60,6 +61,7 @@ and PID and prints it to stdout.""" class PrintExpectations(AbstractDeclarativeCommand): name = 'print-expectations' help_text = 'Print the expected result for the given test(s) on the given port(s)' + show_in_main_help = True def __init__(self): options = [ @@ -151,6 +153,7 @@ class PrintExpectations(AbstractDeclarativeCommand): class PrintBaselines(AbstractDeclarativeCommand): name = 'print-baselines' help_text = 'Prints the baseline locations for given test(s) on the given port(s)' + show_in_main_help = True def __init__(self): options = [ diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queuestest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queuestest.py deleted file mode 100644 index 9812eb70723..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/queuestest.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.common.net.bugzilla import Attachment -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.common.system.executive import ScriptError -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.commands.stepsequence import StepSequenceErrorHandler -from webkitpy.tool.mocktool import MockTool - - -class MockQueueEngine(object): - def __init__(self, name, queue, wakeup_event, seconds_to_sleep): - pass - - def run(self): - pass - - -class QueuesTest(unittest.TestCase): - # This is _patch1 in mocktool.py - mock_work_item = MockTool().bugs.fetch_attachment(10000) - - def assert_outputs(self, func, func_name, args, expected_stdout, expected_stderr, expected_exceptions, expected_logs): - exception = None - if expected_exceptions and func_name in expected_exceptions: - exception = expected_exceptions[func_name] - - logs = None - if expected_logs and func_name in expected_logs: - logs = expected_logs[func_name] - - OutputCapture().assert_outputs(self, - func, - args=args, - expected_stdout=expected_stdout.get(func_name, ""), - expected_stderr=expected_stderr.get(func_name, ""), - expected_exception=exception, - expected_logs=logs) - - def _default_begin_work_queue_stderr(self, name): - string_replacements = {"name": name} - return "MOCK: update_status: %(name)s Starting Queue\n" % string_replacements - - def _default_begin_work_queue_logs(self, name): - checkout_dir = '/mock-checkout' - string_replacements = {"name": name, 'checkout_dir': checkout_dir} - return "CAUTION: %(name)s will discard all local changes in \"%(checkout_dir)s\"\nRunning WebKit %(name)s.\nMOCK: update_status: %(name)s Starting Queue\n" % string_replacements - - def assert_queue_outputs(self, queue, args=None, work_item=None, expected_stdout=None, expected_stderr=None, expected_exceptions=None, expected_logs=None, options=None, tool=None): - if not tool: - tool = MockTool() - # This is a hack to make it easy for callers to not have to setup a custom MockFileSystem just to test the commit-queue - # the cq tries to read the layout test results, and will hit a KeyError in MockFileSystem if we don't do this. - tool.filesystem.write_text_file('/mock-results/full_results.json', "") - if not expected_stdout: - expected_stdout = {} - if not expected_stderr: - expected_stderr = {} - if not args: - args = [] - if not options: - options = Mock() - options.port = None - if not work_item: - work_item = self.mock_work_item - tool.user.prompt = lambda message: "yes" - - queue.execute(options, args, tool, engine=MockQueueEngine) - - self.assert_outputs(queue.queue_log_path, "queue_log_path", [], expected_stdout, expected_stderr, expected_exceptions, expected_logs) - self.assert_outputs(queue.work_item_log_path, "work_item_log_path", [work_item], expected_stdout, expected_stderr, expected_exceptions, expected_logs) - self.assert_outputs(queue.begin_work_queue, "begin_work_queue", [], expected_stdout, expected_stderr, expected_exceptions, expected_logs) - self.assert_outputs(queue.should_continue_work_queue, "should_continue_work_queue", [], expected_stdout, expected_stderr, expected_exceptions, expected_logs) - self.assert_outputs(queue.next_work_item, "next_work_item", [], expected_stdout, expected_stderr, expected_exceptions, expected_logs) - self.assert_outputs(queue.process_work_item, "process_work_item", [work_item], expected_stdout, expected_stderr, expected_exceptions, expected_logs) - self.assert_outputs(queue.handle_unexpected_error, "handle_unexpected_error", [work_item, "Mock error message"], expected_stdout, expected_stderr, expected_exceptions, expected_logs) - # Should we have a different function for testing StepSequenceErrorHandlers? - if isinstance(queue, StepSequenceErrorHandler): - self.assert_outputs(queue.handle_script_error, "handle_script_error", [tool, {"patch": self.mock_work_item}, ScriptError(message="ScriptError error message", script_args="MockErrorCommand", output="MOCK output")], expected_stdout, expected_stderr, expected_exceptions, expected_logs) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py index a4a952b6e60..70dfd28d13c 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py @@ -227,6 +227,7 @@ class RebaselineTest(BaseInternalRebaselineCommand): class OptimizeBaselines(AbstractRebaseliningCommand): name = "optimize-baselines" help_text = "Reshuffles the baselines for the given tests to use as litte space on disk as possible." + show_in_main_help = True argument_names = "TEST_NAMES" def __init__(self): @@ -255,6 +256,7 @@ class OptimizeBaselines(AbstractRebaseliningCommand): class AnalyzeBaselines(AbstractRebaseliningCommand): name = "analyze-baselines" help_text = "Analyzes the baselines for the given tests and prints results that are identical." + show_in_main_help = True argument_names = "TEST_NAMES" def __init__(self): @@ -492,6 +494,7 @@ class RebaselineJson(AbstractParallelRebaselineCommand): class RebaselineExpectations(AbstractParallelRebaselineCommand): name = "rebaseline-expectations" help_text = "Rebaselines the tests indicated in TestExpectations." + show_in_main_help = True def __init__(self): super(RebaselineExpectations, self).__init__(options=[ @@ -539,6 +542,7 @@ class RebaselineExpectations(AbstractParallelRebaselineCommand): class Rebaseline(AbstractParallelRebaselineCommand): name = "rebaseline" help_text = "Rebaseline tests with results from the build bots. Shows the list of failing tests on the builders if no test names are provided." + show_in_main_help = True argument_names = "[TEST_NAMES]" def __init__(self): @@ -677,14 +681,19 @@ class AutoRebaseline(AbstractParallelRebaselineCommand): has_any_needs_rebaseline_lines = False for line in tool.scm().blame(expectations_file_path).split("\n"): - if "NeedsRebaseline" not in line: + comment_index = line.find("#") + if comment_index == -1: + comment_index = len(line) + line_without_comments = re.sub(r"\s+", " ", line[:comment_index].strip()) + + if "NeedsRebaseline" not in line_without_comments: continue if not has_any_needs_rebaseline_lines: self._start_new_log_entry(log_server) has_any_needs_rebaseline_lines = True - parsed_line = re.match("^(\S*)[^(]*\((\S*).*?([^ ]*)\ \[[^[]*$", line) + parsed_line = re.match("^(\S*)[^(]*\((\S*).*?([^ ]*)\ \[[^[]*$", line_without_comments) commit_hash = parsed_line.group(1) svn_revision = tool.scm().svn_revision_from_git_commit(commit_hash) @@ -703,7 +712,7 @@ class AutoRebaseline(AbstractParallelRebaselineCommand): revision = svn_revision author = parsed_line.group(2) - bugs.update(re.findall("crbug\.com\/(\d+)", line)) + bugs.update(re.findall("crbug\.com\/(\d+)", line_without_comments)) tests.add(test) if len(tests) >= self.MAX_LINES_TO_REBASELINE: @@ -841,6 +850,7 @@ class AutoRebaseline(AbstractParallelRebaselineCommand): class RebaselineOMatic(AbstractDeclarativeCommand): name = "rebaseline-o-matic" help_text = "Calls webkit-patch auto-rebaseline in a loop." + show_in_main_help = True SLEEP_TIME_IN_SECONDS = 30 diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py index cf8aa8a4c3b..957ce59cd61 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline_unittest.py @@ -224,8 +224,8 @@ class TestRebaselineTest(_BaseTestCase): def test_baseline_directory(self): command = self.command - self.assertMultiLineEqual(command._baseline_directory("WebKit Mac10.7"), "/mock-checkout/LayoutTests/platform/mac-lion") - self.assertMultiLineEqual(command._baseline_directory("WebKit Mac10.6"), "/mock-checkout/LayoutTests/platform/mac-snowleopard") + self.assertMultiLineEqual(command._baseline_directory("WebKit Mac10.7"), "/mock-checkout/third_party/WebKit/LayoutTests/platform/mac-lion") + self.assertMultiLineEqual(command._baseline_directory("WebKit Mac10.6"), "/mock-checkout/third_party/WebKit/LayoutTests/platform/mac-snowleopard") def test_rebaseline_updates_expectations_file_noop(self): self._zero_out_test_expectations() @@ -273,7 +273,7 @@ Bug(A) [ Debug ] : fast/css/large-list-of-rules-crash.html [ Failure ] self.command._rebaseline_test("WebKit Linux", "userscripts/another-test.html", "txt", None) - self.assertDictEqual(self.command._scm_changes, {'add': ['/mock-checkout/LayoutTests/platform/linux/userscripts/another-test-expected.txt'], 'delete': []}) + self.assertDictEqual(self.command._scm_changes, {'add': ['/mock-checkout/third_party/WebKit/LayoutTests/platform/linux/userscripts/another-test-expected.txt'], 'delete': []}) def test_rebaseline_test_internal_with_port_that_lacks_buildbot(self): self.tool.executive = MockExecutive2() @@ -443,7 +443,7 @@ class TestRebaselineJsonUpdatesExpectationsFiles(_BaseTestCase): self.command._rebaseline(options, {"userscripts/first-test.html": {"WebKit Mac10.7": ["txt", "png"]}}) new_expectations = self._read(self.lion_expectations_path) - self.assertMultiLineEqual(new_expectations, "Bug(x) [ MountainLion Retina SnowLeopard ] userscripts/first-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/first-test.html [ ImageOnlyFailure ]\n") + self.assertMultiLineEqual(new_expectations, "Bug(x) [ Mavericks MountainLion Retina SnowLeopard ] userscripts/first-test.html [ ImageOnlyFailure ]\nbug(z) [ Linux ] userscripts/first-test.html [ ImageOnlyFailure ]\n") def test_rebaseline_updates_expectations_file_all_platforms(self): options = MockOptions(optimize=False, verbose=True, results_directory=None) @@ -455,7 +455,7 @@ class TestRebaselineJsonUpdatesExpectationsFiles(_BaseTestCase): self.command._rebaseline(options, {"userscripts/first-test.html": {"WebKit Mac10.7": ["txt", "png"]}}) new_expectations = self._read(self.lion_expectations_path) - self.assertMultiLineEqual(new_expectations, "Bug(x) [ Android Linux MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n") + self.assertMultiLineEqual(new_expectations, "Bug(x) [ Android Linux Mavericks MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n") def test_rebaseline_handles_platform_skips(self): # This test is just like test_rebaseline_updates_expectations_file_all_platforms(), @@ -471,7 +471,7 @@ class TestRebaselineJsonUpdatesExpectationsFiles(_BaseTestCase): self.command._rebaseline(options, {"userscripts/first-test.html": {"WebKit Mac10.7": ["txt", "png"]}}) new_expectations = self._read(self.lion_expectations_path) - self.assertMultiLineEqual(new_expectations, "Bug(x) [ Linux MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n") + self.assertMultiLineEqual(new_expectations, "Bug(x) [ Linux Mavericks MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n") def test_rebaseline_handles_skips_in_file(self): # This test is like test_Rebaseline_handles_platform_skips, except that the @@ -491,7 +491,7 @@ class TestRebaselineJsonUpdatesExpectationsFiles(_BaseTestCase): new_expectations = self._read(self.lion_expectations_path) self.assertMultiLineEqual(new_expectations, - ("Bug(x) [ Linux MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n" + ("Bug(x) [ Linux Mavericks MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n" "Bug(y) [ Android ] userscripts/first-test.html [ Skip ]\n")) def test_rebaseline_handles_smoke_tests(self): @@ -509,7 +509,7 @@ class TestRebaselineJsonUpdatesExpectationsFiles(_BaseTestCase): self.command._rebaseline(options, {"userscripts/first-test.html": {"WebKit Mac10.7": ["txt", "png"]}}) new_expectations = self._read(self.lion_expectations_path) - self.assertMultiLineEqual(new_expectations, "Bug(x) [ Linux MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n") + self.assertMultiLineEqual(new_expectations, "Bug(x) [ Linux Mavericks MountainLion Retina SnowLeopard Win ] userscripts/first-test.html [ ImageOnlyFailure ]\n") @@ -784,6 +784,7 @@ TBR=foo@chromium.org def test_execute(self): def blame(path): return """ +6469e754a1 path/to/TestExpectations (foobarbaz1@chromium.org 2013-06-14 20:18:46 +0000 11) # Test NeedsRebaseline being in a comment doesn't bork parsing. 6469e754a1 path/to/TestExpectations (foobarbaz1@chromium.org 2013-06-14 20:18:46 +0000 11) crbug.com/24182 [ Debug ] path/to/norebaseline.html [ ImageOnlyFailure ] 6469e754a1 path/to/TestExpectations (foobarbaz1@chromium.org 2013-04-28 04:52:41 +0000 13) Bug(foo) fast/dom/prototype-taco.html [ NeedsRebaseline ] 6469e754a1 path/to/TestExpectations (foobarbaz1@chromium.org 2013-06-14 20:18:46 +0000 11) crbug.com/24182 [ SnowLeopard ] fast/dom/prototype-strawberry.html [ NeedsRebaseline ] diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaselineserver.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaselineserver.py index ffc03b858dd..2448f872400 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaselineserver.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaselineserver.py @@ -50,6 +50,7 @@ class TestConfig(object): class RebaselineServer(AbstractLocalServerCommand): name = "rebaseline-server" help_text = __doc__ + show_in_main_help = True argument_names = "/path/to/results/directory" server = RebaselineHTTPServer diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/stepsequence.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/stepsequence.py index 894771276e1..c48c2c4d493 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/stepsequence.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/stepsequence.py @@ -31,7 +31,6 @@ import sys from webkitpy.tool import steps -from webkitpy.common.checkout.scm import CheckoutNeedsUpdate from webkitpy.common.system.executive import ScriptError _log = logging.getLogger(__name__) @@ -79,12 +78,6 @@ class StepSequence(object): state = {} try: self._run(tool, options, state) - except CheckoutNeedsUpdate, e: - _log.info("Commit failed because the checkout is out of date. Please update and try again.") - if options.parent_command: - command = tool.command_by_name(options.parent_command) - command.handle_checkout_needs_update(tool, state, options, e) - self.exit_after_handled_error(e) except ScriptError, e: if not options.quiet: _log.error(e.message_with_output()) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/main.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/main.py index 96eaf946ac3..dcd236eed12 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/main.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/main.py @@ -33,7 +33,6 @@ from optparse import make_option import os import threading -from webkitpy.common.config.ports import DeprecatedPort from webkitpy.common.host import Host from webkitpy.tool.multicommandtool import MultiCommandTool from webkitpy.tool import commands @@ -43,8 +42,6 @@ class WebKitPatch(MultiCommandTool, Host): global_options = [ make_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="enable all logging"), make_option("-d", "--directory", action="append", dest="patch_directories", default=[], help="Directory to look at for changed files"), - make_option("--seconds-to-sleep", action="store", default=120, type="int", help="Number of seconds to sleep in the task queue."), - make_option("--port", action="store", dest="port", default=None, help="Specify a port (e.g., mac, qt, gtk, ...)."), ] def __init__(self, path): @@ -52,10 +49,6 @@ class WebKitPatch(MultiCommandTool, Host): Host.__init__(self) self._path = path self.wakeup_event = threading.Event() - self._deprecated_port = None - - def deprecated_port(self): - return self._deprecated_port def path(self): return self._path @@ -70,8 +63,6 @@ class WebKitPatch(MultiCommandTool, Host): # FIXME: This may be unnecessary since we pass global options to all commands during execute() as well. def handle_global_options(self, options): self.initialize_scm(options.patch_directories) - # If options.port is None, we'll get the default port for this platform. - self._deprecated_port = DeprecatedPort.port(options.port) def should_execute_command(self, command): if command.requires_local_commits and not self.scm().supports_local_commits(): diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py index ffeef175105..defb9cd15b8 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/servers/rebaselineserver_unittest.py @@ -290,10 +290,11 @@ class GetBaselinesTest(unittest.TestCase): def get_test_config(test_files=[], result_files=[]): - # We could grab this from port.layout_tests_dir(), but instantiating a fully mocked port is a pain. - layout_tests_directory = "/mock-checkout/LayoutTests" - results_directory = '/WebKitBuild/Debug/layout-test-results' host = MockHost() + port = host.port_factory.get() + layout_tests_directory = port.layout_tests_dir() + results_directory = port.results_directory() + for file in test_files: host.filesystem.write_binary_file(host.filesystem.join(layout_tests_directory, file), '') for file in result_files: @@ -301,6 +302,7 @@ def get_test_config(test_files=[], result_files=[]): class TestMacPort(Port): port_name = "mac" + FALLBACK_PATHS = {'': ['mac']} return TestConfig( TestMacPort(host, 'mac'), diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/__init__.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/__init__.py index bc35b8f476c..261606dcd0f 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/__init__.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/__init__.py @@ -27,19 +27,5 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # FIXME: Is this the right way to do this? -from webkitpy.tool.steps.addsvnmimetypeforpng import AddSvnMimetypeForPng -from webkitpy.tool.steps.checkstyle import CheckStyle -from webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory -from webkitpy.tool.steps.commit import Commit from webkitpy.tool.steps.confirmdiff import ConfirmDiff -from webkitpy.tool.steps.discardlocalchanges import DiscardLocalChanges -from webkitpy.tool.steps.ensurebugisopenandassigned import EnsureBugIsOpenAndAssigned -from webkitpy.tool.steps.ensurelocalcommitifneeded import EnsureLocalCommitIfNeeded -from webkitpy.tool.steps.haslanded import HasLanded -from webkitpy.tool.steps.obsoletepatches import ObsoletePatches from webkitpy.tool.steps.options import Options -from webkitpy.tool.steps.postdiff import PostDiff -from webkitpy.tool.steps.promptforbugortitle import PromptForBugOrTitle -from webkitpy.tool.steps.reopenbugafterrollout import ReopenBugAfterRollout -from webkitpy.tool.steps.revertrevision import RevertRevision -from webkitpy.tool.steps.runtests import RunTests diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/abstractstep.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/abstractstep.py index ff8a752b889..9d7f2e07373 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/abstractstep.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/abstractstep.py @@ -40,33 +40,6 @@ class AbstractStep(object): def _exit(self, code): sys.exit(code) - def _changed_files(self, state): - return self.cached_lookup(state, "changed_files") - - _well_known_keys = { - # FIXME: Should this use state.get('bug_id') or state.get('patch').bug_id() like UpdateChangeLogsWithReviewer does? - "bug": lambda self, state: self._tool.bugs.fetch_bug(state["bug_id"]), - # bug_title can either be a new title given by the user, or one from an existing bug. - "bug_title": lambda self, state: self.cached_lookup(state, 'bug').title(), - "changed_files": lambda self, state: self._tool.scm().changed_files(self._options.git_commit), - "diff": lambda self, state: self._tool.scm().create_patch(self._options.git_commit, changed_files=self._changed_files(state)), - # Absolute path to ChangeLog files. - "changelogs": lambda self, state: self._tool.checkout().modified_changelogs(self._options.git_commit, changed_files=self._changed_files(state)), - } - - def cached_lookup(self, state, key, promise=None): - if state.get(key): - return state[key] - if not promise: - promise = self._well_known_keys.get(key) - state[key] = promise(self, state) - return state[key] - - def did_modify_checkout(self, state): - state["diff"] = None - state["changelogs"] = None - state["changed_files"] = None - @classmethod def options(cls): return [ diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng.py deleted file mode 100644 index 0ef0fed4629..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (C) 2012 Balazs Ankes (bank@inf.u-szeged.hu) University of Szeged -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.common import checksvnconfigfile -from webkitpy.common.checkout.scm.detection import SCMDetector -from webkitpy.common.system.systemhost import SystemHost - -_log = logging.getLogger(__name__) - - -class AddSvnMimetypeForPng(AbstractStep): - def __init__(self, tool, options, host=None, scm=None): - self._tool = tool - self._options = options - self._host = host or SystemHost() - self._fs = self._host.filesystem - self._detector = scm or SCMDetector(self._fs, self._host.executive).detect_scm_system(self._fs.getcwd()) - - def run(self, state): - png_files = self._check_pngs(self._changed_files(state)) - - if png_files: - detection = self._detector.display_name() - - if detection == "git": - (file_missing, autoprop_missing, png_missing) = checksvnconfigfile.check(self._host, self._fs) - config_file_path = checksvnconfigfile.config_file_path(self._host, self._fs) - - if file_missing: - _log.info("There is no SVN config file. The svn:mime-type of pngs won't set.") - if not self._tool.user.confirm("Are you sure you want to continue?", default="n"): - self._exit(1) - elif autoprop_missing and png_missing: - _log.info(checksvnconfigfile.errorstr_autoprop(config_file_path) + checksvnconfigfile.errorstr_png(config_file_path)) - if not self._tool.user.confirm("Do you want to continue?", default="n"): - self._exit(1) - elif autoprop_missing: - _log.info(checksvnconfigfile.errorstr_autoprop(config_file_path)) - if not self._tool.user.confirm("Do you want to continue?", default="n"): - self._exit(1) - elif png_missing: - _log.info(checksvnconfigfile.errorstr_png(config_file_path)) - if not self._tool.user.confirm("Do you want to continue?", default="n"): - self._exit(1) - - elif detection == "svn": - for filename in png_files: - if self._detector.exists(filename) and self._detector.propget('svn:mime-type', filename) != 'image/png': - print "Adding image/png mime-type to %s" % filename - self._detector.propset('svn:mime-type', 'image/png', filename) - - def _check_pngs(self, changed_files): - png_files = [] - for filename in changed_files: - if filename.endswith('.png'): - png_files.append(filename) - return png_files diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py deleted file mode 100644 index 991b95b877c..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/addsvnmimetypeforpng_unittest.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (C) 2012 Balazs Ankes (bank@inf.u-szeged.hu) University of Szeged -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.tool.steps.addsvnmimetypeforpng import AddSvnMimetypeForPng -from webkitpy.common.system.filesystem_mock import MockFileSystem -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.common.system.systemhost_mock import MockSystemHost -from webkitpy.common.system.outputcapture import OutputCapture - - -class MockSCMDetector(object): - - def __init__(self, scm): - self._scm = scm - - def display_name(self): - return self._scm - - -class AddSvnMimetypeForPngTest(unittest.TestCase): - def test_run(self): - capture = OutputCapture() - options = MockOptions(git_commit='MOCK git commit') - - files = {'/Users/mock/.subversion/config': 'enable-auto-props = yes\n*.png = svn:mime-type=image/png'} - fs = MockFileSystem(files) - scm = MockSCMDetector('git') - - step = AddSvnMimetypeForPng(MockTool(), options, MockSystemHost(os_name='linux', filesystem=fs), scm) - state = { - "changed_files": ["test.png", "test.txt"], - } - try: - capture.assert_outputs(self, step.run, [state]) - except SystemExit, e: - self.assertEqual(e.code, 1) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/applypatch.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/applypatch.py deleted file mode 100644 index 6c80b355a97..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/applypatch.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - -_log = logging.getLogger(__name__) - - -class ApplyPatch(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.non_interactive, - ] - - def run(self, state): - _log.info("Processing patch %s from bug %s." % (state["patch"].id(), state["patch"].bug_id())) - self._tool.checkout().apply_patch(state["patch"]) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/checkstyle.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/checkstyle.py deleted file mode 100644 index c699ed542f7..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/checkstyle.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.common.system.executive import ScriptError -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - -class CheckStyle(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.non_interactive, - Options.check_style, - Options.check_style_filter, - Options.git_commit, - ] - - def run(self, state): - if not self._options.check_style: - return - - args = [] - if self._options.git_commit: - args.append("--git-commit") - args.append(self._options.git_commit) - - args.append("--diff-files") - args.extend(self._changed_files(state)) - - if self._options.check_style_filter: - args.append("--filter") - args.append(self._options.check_style_filter) - - try: - self._tool.executive.run_and_throw_if_fail(self._tool.deprecated_port().check_webkit_style_command() + args, cwd=self._tool.scm().checkout_root) - except ScriptError, e: - if self._options.non_interactive: - # We need to re-raise the exception here to have the - # style-queue do the right thing. - raise e - if not self._tool.user.confirm("Are you sure you want to continue?"): - self._exit(1) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py deleted file mode 100644 index ddf7bed022d..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.executive import ScriptError - - -class CleanWorkingDirectory(AbstractStep): - - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.force_clean, - Options.clean, - ] - - def run(self, state): - if not self._options.clean: - return - - if self._tool.scm().has_working_directory_changes() and not self._options.force_clean: - raise ScriptError("Working directory has changes, pass --force-clean to continue.") - - self._tool.scm().discard_working_directory_changes() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py deleted file mode 100644 index 1b8fd929339..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/cleanworkingdirectory_unittest.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.cleanworkingdirectory import CleanWorkingDirectory -from webkitpy.common.system.executive import ScriptError - - -class CleanWorkingDirectoryTest(unittest.TestCase): - def test_run_working_directory_changes_no_force(self): - tool = MockTool() - tool._scm = Mock() - step = CleanWorkingDirectory(tool, MockOptions(clean=True, force_clean=False)) - tool._scm.has_working_directory_changes = lambda: True - self.assertRaises(ScriptError, step.run, {}) - self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 0) - - def test_run_working_directory_changes_force(self): - tool = MockTool() - tool._scm = Mock() - step = CleanWorkingDirectory(tool, MockOptions(clean=True, force_clean=True)) - tool._scm.has_working_directory_changes = lambda: True - step.run({}) - self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 1) - - def test_run_no_local_changes(self): - tool = MockTool() - tool._scm = Mock() - step = CleanWorkingDirectory(tool, MockOptions(clean=True, force_clean=False)) - tool._scm.has_working_directory_changes = lambda: False - tool._scm.has_local_commits = lambda: False - step.run({}) - self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 1) - - def test_no_clean(self): - tool = MockTool() - tool._scm = Mock() - step = CleanWorkingDirectory(tool, MockOptions(clean=False)) - step.run({}) - self.assertEqual(tool._scm.discard_working_directory_changes.call_count, 0) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/commit.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/commit.py deleted file mode 100644 index c0e42fd9939..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/commit.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging -import sys - -from webkitpy.common.checkout.scm import AuthenticationError, AmbiguousCommitError -from webkitpy.common.config import urls -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.system.user import User -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - -_log = logging.getLogger(__name__) - - -class Commit(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.non_interactive, - ] - - def _commit_warning(self, error): - return ('There are %s local commits (and possibly changes in the working directory. ' - 'Everything will be committed as a single commit. ' - 'To avoid this prompt, set "git config webkit-patch.commit-should-always-squash true".' % ( - error.num_local_commits)) - - def _check_test_expectations(self, changed_files): - test_expectations_files = [filename for filename in changed_files if filename.endswith('TestExpectations')] - if not test_expectations_files: - return - - args = ["--diff-files"] - args.extend(test_expectations_files) - try: - self._tool.executive.run_and_throw_if_fail(self._tool.deprecated_port().check_webkit_style_command() + args, cwd=self._tool.scm().checkout_root) - except ScriptError, e: - if self._options.non_interactive: - raise - if not self._tool.user.confirm("Are you sure you want to continue?", default="n"): - self._exit(1) - - def run(self, state): - self._commit_message = self._tool.checkout().commit_message_for_this_commit(self._options.git_commit).message() - if len(self._commit_message) < 10: - raise Exception("Attempted to commit with a commit message shorter than 10 characters. Either your patch is missing a ChangeLog or webkit-patch may have a bug.") - - self._check_test_expectations(self._changed_files(state)) - self._state = state - - username = None - password = None - force_squash = self._options.non_interactive - - num_tries = 0 - while num_tries < 3: - num_tries += 1 - - try: - scm = self._tool.scm() - commit_text = scm.commit_with_message(self._commit_message, git_commit=self._options.git_commit, username=username, password=password, force_squash=force_squash, changed_files=self._changed_files(state)) - svn_revision = scm.svn_revision_from_commit_text(commit_text) - _log.info("Committed r%s: <%s>" % (svn_revision, urls.view_revision_url(svn_revision))) - self._state["commit_text"] = commit_text - break; - except AmbiguousCommitError, e: - if self._tool.user.confirm(self._commit_warning(e)): - force_squash = True - else: - # This will correctly interrupt the rest of the commit process. - raise ScriptError(message="Did not commit") - except AuthenticationError, e: - if self._options.non_interactive: - raise ScriptError(message="Authentication required") - username = self._tool.user.prompt("%s login: " % e.server_host, repeat=5) - if not username: - raise ScriptError("You need to specify the username on %s to perform the commit as." % e.server_host) - if e.prompt_for_password: - password = self._tool.user.prompt_password("%s password for %s: " % (e.server_host, username), repeat=5) - if not password: - raise ScriptError("You need to specify the password for %s on %s to perform the commit." % (username, e.server_host)) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py deleted file mode 100644 index 567e0bb9e44..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/commit_unittest.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (C) 2012 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.common.system.executive import ScriptError -from webkitpy.common.system.executive_mock import MockExecutive -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.commit import Commit - - -class CommitTest(unittest.TestCase): - def _test_check_test_expectations(self, filename): - capture = OutputCapture() - options = MockOptions() - options.git_commit = "" - options.non_interactive = True - - tool = MockTool() - tool.user = None # Will cause any access of tool.user to raise an exception. - step = Commit(tool, options) - state = { - "changed_files": [filename + "XXX"], - } - - tool.executive = MockExecutive(should_log=True, should_throw_when_run=False) - expected_logs = "Committed r49824: <http://trac.webkit.org/changeset/49824>\n" - capture.assert_outputs(self, step.run, [state], expected_logs=expected_logs) - - state = { - "changed_files": ["platform/chromium/" + filename], - } - expected_logs = """MOCK run_and_throw_if_fail: ['mock-check-webkit-style', '--diff-files', 'platform/chromium/%s'], cwd=/mock-checkout -Committed r49824: <http://trac.webkit.org/changeset/49824> -""" % filename - capture.assert_outputs(self, step.run, [state], expected_logs=expected_logs) - - tool.executive = MockExecutive(should_log=True, should_throw_when_run=set(["platform/chromium/" + filename])) - self.assertRaises(ScriptError, capture.assert_outputs, self, step.run, [state]) - - def test_check_test_expectations(self): - self._test_check_test_expectations('TestExpectations') diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/confirmdiff.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/confirmdiff.py index ba0478b1fe4..6cab6107dba 100644 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/confirmdiff.py +++ b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/confirmdiff.py @@ -45,13 +45,13 @@ class ConfirmDiff(AbstractStep): Options.confirm, ] - def _show_pretty_diff(self, diff): + def _show_pretty_diff(self): if not self._tool.user.can_open_url(): return None try: pretty_patch = PrettyPatch(self._tool.executive) - pretty_diff_file = pretty_patch.pretty_diff_file(diff) + pretty_diff_file = pretty_patch.pretty_diff_file(self.diff()) url = "file://%s" % urllib.quote(pretty_diff_file.name) self._tool.user.open_url(url) # We return the pretty_diff_file here because we need to keep the @@ -62,13 +62,15 @@ class ConfirmDiff(AbstractStep): except OSError, e: _log.warning("PrettyPatch unavailable.") + def diff(self): + changed_files = self._tool.scm().changed_files(self._options.git_commit) + return self._tool.scm().create_patch(self._options.git_commit, + changed_files=changed_files) + def run(self, state): if not self._options.confirm: return - diff = self.cached_lookup(state, "diff") - pretty_diff_file = self._show_pretty_diff(diff) - if not pretty_diff_file: - self._tool.user.page(diff) + pretty_diff_file = self._show_pretty_diff() diff_correct = self._tool.user.confirm("Was that diff correct?") if pretty_diff_file: pretty_diff_file.close() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges.py deleted file mode 100644 index 8a84cc7024f..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.executive import ScriptError - - -class DiscardLocalChanges(AbstractStep): - - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.clean, - Options.force_clean, - ] - - def run(self, state): - if not self._options.clean: - return - - if not self._options.force_clean: - if self._tool.scm().has_working_directory_changes(): - raise ScriptError("Working directory has changes, pass --force-clean to continue.") - if self._tool.scm().has_local_commits(): - raise ScriptError("Repository has local commits, pass --force-clean to continue.") - self._tool.scm().discard_local_changes() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges_unittest.py deleted file mode 100644 index 35d5f24971e..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/discardlocalchanges_unittest.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.thirdparty.mock import Mock -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.discardlocalchanges import DiscardLocalChanges -from webkitpy.common.system.executive import ScriptError - - -class DiscardLocalChangesTest(unittest.TestCase): - def test_skip_on_clean(self): - tool = MockTool() - tool._scm = Mock() - step = DiscardLocalChanges(tool, MockOptions(clean=False)) - step.run({}) - self.assertEqual(tool._scm.discard_local_changes.call_count, 0) - - def test_working_changes_exist_with_force(self): - tool = MockTool() - tool._scm = Mock() - tool._scm.has_working_directory_changes = lambda: True - tool._scm.has_local_commits = lambda: False - step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True)) - step.run({}) - self.assertEqual(tool._scm.discard_local_changes.call_count, 1) - - def test_local_commits_exist_with_force(self): - tool = MockTool() - tool._scm = Mock() - tool._scm.has_working_directory_changes = lambda: False - tool._scm.has_local_commits = lambda: True - step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True)) - step.run({}) - self.assertEqual(tool._scm.discard_local_changes.call_count, 1) - - def test_local_commits_and_working_changes_exist_with_force(self): - tool = MockTool() - tool._scm = Mock() - tool._scm.has_working_directory_changes = lambda: True - tool._scm.has_local_commits = lambda: True - step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True)) - step.run({}) - self.assertEqual(tool._scm.discard_local_changes.call_count, 1) - - def test_no_changes_exist_with_force(self): - tool = MockTool() - tool._scm = Mock() - tool._scm.has_working_directory_changes = lambda: False - tool._scm.has_local_commits = lambda: False - step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=True)) - step.run({}) - self.assertEqual(tool._scm.discard_local_changes.call_count, 1) - - def test_error_working_changes_exist_without_force(self): - tool = MockTool() - tool._scm = Mock() - tool._scm.has_working_directory_changes = lambda: True - tool._scm.has_local_commits = lambda: False - step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=False)) - self.assertRaises(ScriptError, step.run, {}) - self.assertEqual(tool._scm.discard_local_changes.call_count, 0) - - def test_error_local_commits_exist_without_force(self): - tool = MockTool() - tool._scm = Mock() - tool._scm.has_working_directory_changes = lambda: False - tool._scm.has_local_commits = lambda: True - step = DiscardLocalChanges(tool, MockOptions(clean=True, force_clean=False)) - self.assertRaises(ScriptError, step.run, {}) - self.assertEqual(tool._scm.discard_local_changes.call_count, 0) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/ensurebugisopenandassigned.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/ensurebugisopenandassigned.py deleted file mode 100644 index 54f90b6d7a2..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/ensurebugisopenandassigned.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.tool.steps.abstractstep import AbstractStep - - -class EnsureBugIsOpenAndAssigned(AbstractStep): - def run(self, state): - bug = self.cached_lookup(state, "bug") - if bug.is_unassigned(): - self._tool.bugs.reassign_bug(bug.id()) - - if bug.is_closed(): - # FIXME: We should probably pass this message in somehow? - # Right now this step is only used before PostDiff steps, so this is OK. - self._tool.bugs.reopen_bug(bug.id(), "Reopening to attach new patch.") diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/ensurelocalcommitifneeded.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/ensurelocalcommitifneeded.py deleted file mode 100644 index 388d2a229b5..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/ensurelocalcommitifneeded.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging -import sys - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - -_log = logging.getLogger(__name__) - - -class EnsureLocalCommitIfNeeded(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.local_commit, - ] - - def run(self, state): - if self._options.local_commit and not self._tool.scm().supports_local_commits(): - _log.error("--local-commit passed, but %s does not support local commits" % self._tool.scm().display_name()) - sys.exit(1) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/haslanded.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/haslanded.py deleted file mode 100644 index b0692b32b27..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/haslanded.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import cStringIO as StringIO -import logging -import sys -import re -import tempfile - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.common.system.executive import Executive, ScriptError -from webkitpy.common.checkout import diff_parser - -from webkitpy.tool.steps import confirmdiff - -_log = logging.getLogger(__name__) - - -class HasLanded(confirmdiff.ConfirmDiff): - - @classmethod - def convert_to_svn(cls, diff): - lines = StringIO.StringIO(diff).readlines() - convert = diff_parser.get_diff_converter(lines) - return "".join(convert(x) for x in lines) - - @classmethod - def strip_change_log(cls, diff): - output = [] - skipping = False - for line in StringIO.StringIO(diff).readlines(): - indexline = re.match("^Index: ([^\\n]*/)?([^/\\n]*)$", line) - if skipping and indexline: - skipping = False - if indexline and indexline.group(2) == "ChangeLog": - skipping = True - if not skipping: - output.append(line) - return "".join(output) - - @classmethod - def diff_diff(cls, diff1, diff2, diff1_suffix, diff2_suffix, executive=None): - # Now this is where it gets complicated, we need to compare our diff to the diff at landed_revision. - diff1_patch = tempfile.NamedTemporaryFile(suffix=diff1_suffix + '.patch') - diff1_patch.write(diff1) - diff1_patch.flush() - - # Check if there are any differences in the patch that don't happen - diff2_patch = tempfile.NamedTemporaryFile(suffix=diff2_suffix + '.patch') - diff2_patch.write(diff2) - diff2_patch.flush() - - # Diff the two diff's together... - if not executive: - executive = Executive() - - try: - return executive.run_command( - ["interdiff", diff1_patch.name, diff2_patch.name], decode_output=False) - except ScriptError, e: - _log.warning("Unable to find interdiff util (part of GNU difftools package) which is required.") - raise - - def run(self, state): - # Check if there are changes first - if not self._tool.scm().local_changes_exist(): - _log.warn("No local changes found, exiting.") - return True - - # Check if there is a SVN revision in the bug from the commit queue - landed_revision = self.cached_lookup(state, "bug").commit_revision() - if not landed_revision: - raise ScriptError("Unable to find landed message in associated bug.") - - # Now this is there it gets complicated, we need to compare our diff to the diff at landed_revision. - landed_diff_bin = self._tool.scm().diff_for_revision(landed_revision) - landed_diff_trimmed = self.strip_change_log(self.convert_to_svn(landed_diff_bin)) - - # Check if there are any differences in the patch that don't happen - local_diff_bin = self._tool.scm().create_patch() - local_diff_trimmed = self.strip_change_log(self.convert_to_svn(local_diff_bin)) - - # Diff the two diff's together... - diff_diff = self.diff_diff(landed_diff_trimmed, local_diff_trimmed, - '-landed', '-local', - executive=self._tool.executive) - - with self._show_pretty_diff(diff_diff) as pretty_diff_file: - if not pretty_diff_file: - self._tool.user.page(diff_diff) - - if self._tool.user.confirm("May I discard local changes?"): - # Discard changes if the user confirmed we should - _log.warn("Discarding changes as requested.") - self._tool.scm().discard_local_changes() diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/haslanded_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/haslanded_unittest.py deleted file mode 100644 index e1779829df2..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/haslanded_unittest.py +++ /dev/null @@ -1,299 +0,0 @@ -# Copyright (C) 2009 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest -import subprocess - -from webkitpy.tool.steps.haslanded import HasLanded - - -class HasLandedTest(unittest.TestCase): - maxDiff = None - - @unittest.skipUnless(subprocess.call('which interdiff', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0, "requires interdiff") - def test_run(self): - # These patches require trailing whitespace to remain valid patches. - diff1 = """\ -Index: a.py -=================================================================== ---- a.py -+++ a.py -@@ -1,3 +1,5 @@ - A - B - C -+D -+E -Index: b.py -=================================================================== ---- b.py 2013-01-21 15:20:59.693887185 +1100 -+++ b.py 2013-01-21 15:22:24.382555711 +1100 -@@ -1,3 +1,5 @@ - 1 - 2 - 3 -+4 -+5 -""" - - diff1_add_line = """\ -Index: a.py -=================================================================== ---- a.py -+++ a.py -@@ -1,3 +1,6 @@ - A - B - C -+D -+E -+F -Index: b.py -=================================================================== ---- b.py -+++ b.py -@@ -1,3 +1,5 @@ - 1 - 2 - 3 -+4 -+5 -""" - - diff1_remove_line = """\ -Index: a.py -=================================================================== ---- a.py -+++ a.py -@@ -1,3 +1,4 @@ - A - B - C -+D -Index: b.py -=================================================================== ---- b.py -+++ b.py -@@ -1,3 +1,5 @@ - 1 - 2 - 3 -+4 -+5 -""" - - diff1_add_file = diff1 + """\ -Index: c.py -=================================================================== ---- c.py -+++ c.py -@@ -1,3 +1,5 @@ - 1 - 2 - 3 -+4 -+5 -""" - - diff1_remove_file = """\ -Index: a.py -=================================================================== ---- a.py -+++ a.py -@@ -1,3 +1,5 @@ - A - B - C -+D -+E -""" - self.assertMultiLineEqual( - HasLanded.diff_diff(diff1, diff1_add_line, '', 'add-line'), - """\ -diff -u a.py a.py ---- a.py -+++ a.py -@@ -5,0 +6 @@ -+F -""") - - self.assertMultiLineEqual( - HasLanded.diff_diff(diff1, diff1_remove_line, '', 'remove-line'), - """\ -diff -u a.py a.py ---- a.py -+++ a.py -@@ -5 +4,0 @@ --E -""") - self.assertMultiLineEqual( - HasLanded.diff_diff(diff1, diff1_add_file, '', 'add-file'), - """\ -only in patch2: -unchanged: ---- c.py -+++ c.py -@@ -1,3 +1,5 @@ - 1 - 2 - 3 -+4 -+5 -""") - self.assertMultiLineEqual( - HasLanded.diff_diff(diff1, diff1_remove_file, '', 'remove-file'), - """\ -reverted: ---- b.py 2013-01-21 15:22:24.382555711 +1100 -+++ b.py 2013-01-21 15:20:59.693887185 +1100 -@@ -1,5 +1,3 @@ - 1 - 2 - 3 --4 --5 -""") - - def test_convert_to_svn_and_strip_change_log(self): - # These patches require trailing whitespace to remain valid patches. - testbefore1 = HasLanded.convert_to_svn("""\ -diff --git a/Tools/ChangeLog b/Tools/ChangeLog -index 219ba72..0390b73 100644 ---- a/Tools/ChangeLog -+++ b/Tools/ChangeLog -@@ -1,3 +1,32 @@ -+2013-01-17 Tim 'mithro' Ansell <mithro@mithis.com> -+ -+ Adding "has-landed" command to webkit-patch which allows a person to -+ Reviewed by NOBODY (OOPS!). -+ - 2013-01-20 Tim 'mithro' Ansell <mithro@mithis.com> - - Extend diff_parser to support the --full-index output. -diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -index 4bf8ec6..3a128cb 100644 ---- a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -+++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -@@ -28,6 +28,8 @@ -+import re -+ - from .attachment import Attachment - -""") - testafter1 = HasLanded.convert_to_svn("""\ -diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -index 4bf8ec6..3a128cb 100644 ---- a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -+++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -@@ -28,6 +28,8 @@ -+import re -+ - from .attachment import Attachment - -diff --git a/Tools/ChangeLog b/Tools/ChangeLog -index 219ba72..0390b73 100644 ---- a/Tools/ChangeLog -+++ b/Tools/ChangeLog -@@ -1,3 +1,32 @@ -+2013-01-17 Tim 'mithro' Ansell <mithro@mithis.com> -+ -+ Adding "has-landed" command to webkit-patch which allows a person to -+ Reviewed by NOBODY (OOPS!). -+ - 2013-01-20 Tim 'mithro' Ansell <mithro@mithis.com> - - Extend diff_parser to support the --full-index output. -""") - testexpected1 = """\ -Index: Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -=================================================================== ---- Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -+++ Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -@@ -28,6 +28,8 @@ -+import re -+ - from .attachment import Attachment - -""" - testmiddle1 = HasLanded.convert_to_svn("""\ -diff --git a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -index 4bf8ec6..3a128cb 100644 ---- a/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -+++ b/Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -@@ -28,6 +28,8 @@ -+import re -+ - from .attachment import Attachment - -diff --git a/ChangeLog b/ChangeLog -index 219ba72..0390b73 100644 ---- a/ChangeLog -+++ b/ChangeLog -@@ -1,3 +1,32 @@ -+2013-01-17 Tim 'mithro' Ansell <mithro@mithis.com> -+ -+ Adding "has-landed" command to webkit-patch which allows a person to -+ Reviewed by NOBODY (OOPS!). -+ - 2013-01-20 Tim 'mithro' Ansell <mithro@mithis.com> - - Extend diff_parser to support the --full-index output. -diff --git a/Tools/Scripts/webkitpy/common/other.py b/Tools/Scripts/webkitpy/common/other.py -index 4bf8ec6..3a128cb 100644 ---- a/Tools/Scripts/webkitpy/common/other.py -+++ b/Tools/Scripts/webkitpy/common/other.py -@@ -28,6 +28,8 @@ -+import re -+ - from .attachment import Attachment - -""") - testexpected2 = """\ -Index: Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -=================================================================== ---- Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -+++ Tools/Scripts/webkitpy/common/net/bugzilla/bug.py -@@ -28,6 +28,8 @@ -+import re -+ - from .attachment import Attachment - -Index: Tools/Scripts/webkitpy/common/other.py -=================================================================== ---- Tools/Scripts/webkitpy/common/other.py -+++ Tools/Scripts/webkitpy/common/other.py -@@ -28,6 +28,8 @@ -+import re -+ - from .attachment import Attachment - -""" - - self.assertMultiLineEqual(testexpected1, HasLanded.strip_change_log(testbefore1)) - self.assertMultiLineEqual(testexpected1, HasLanded.strip_change_log(testafter1)) - self.assertMultiLineEqual(testexpected2, HasLanded.strip_change_log(testmiddle1)) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/metastep.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/metastep.py deleted file mode 100644 index 361a8a5e555..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/metastep.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.tool.steps.abstractstep import AbstractStep - - -# FIXME: Unify with StepSequence? I'm not sure yet which is the better design. -class MetaStep(AbstractStep): - substeps = [] # Override in subclasses - def __init__(self, tool, options): - AbstractStep.__init__(self, tool, options) - self._step_instances = [] - for step_class in self.substeps: - self._step_instances.append(step_class(tool, options)) - - @staticmethod - def _collect_options_from_steps(steps): - collected_options = [] - for step in steps: - collected_options = collected_options + step.options() - return collected_options - - @classmethod - def options(cls): - return cls._collect_options_from_steps(cls.substeps) - - def run(self, state): - for step in self._step_instances: - step.run(state) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/obsoletepatches.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/obsoletepatches.py deleted file mode 100644 index f4894aea290..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/obsoletepatches.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging - -from webkitpy.tool.grammar import pluralize -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - -_log = logging.getLogger(__name__) - - -class ObsoletePatches(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.obsolete_patches, - ] - - def run(self, state): - if not self._options.obsolete_patches: - return - bug_id = state["bug_id"] - patches = self._tool.bugs.fetch_bug(bug_id).patches() - if not patches: - return - _log.info("Obsoleting %s on bug %s" % (pluralize("old patch", len(patches)), bug_id)) - for patch in patches: - self._tool.bugs.obsolete_attachment(patch.id()) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/postdiff.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/postdiff.py deleted file mode 100644 index fc5c443fd6f..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/postdiff.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options - - -class PostDiff(AbstractStep): - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.description, - Options.comment, - Options.review, - Options.request_commit, - Options.open_bug, - ] - - def run(self, state): - diff = self.cached_lookup(state, "diff") - description = self._options.description or "Patch" - comment_text = self._options.comment - bug_id = state["bug_id"] - - self._tool.bugs.add_patch_to_bug(bug_id, diff, description, comment_text=comment_text, mark_for_review=self._options.review, mark_for_commit_queue=self._options.request_commit) - if self._options.open_bug: - self._tool.user.open_url(self._tool.bugs.bug_url_for_bug_id(bug_id)) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/promptforbugortitle.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/promptforbugortitle.py deleted file mode 100644 index 3114891bcbe..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/promptforbugortitle.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.tool.steps.abstractstep import AbstractStep - - -class PromptForBugOrTitle(AbstractStep): - def run(self, state): - # No need to prompt if we alrady have the bug_id. - if state.get("bug_id"): - return - user_response = self._tool.user.prompt("Please enter a bug number or a title for a new bug:\n") - # If the user responds with a number, we assume it's bug number. - # Otherwise we assume it's a bug subject. - try: - state["bug_id"] = int(user_response) - except ValueError, TypeError: - state["bug_title"] = user_response - # FIXME: This is kind of a lame description. - state["bug_description"] = user_response diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/reopenbugafterrollout.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/reopenbugafterrollout.py deleted file mode 100644 index cf713a449af..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/reopenbugafterrollout.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging - -from webkitpy.tool.comments import bug_comment_from_commit_text -from webkitpy.tool.steps.abstractstep import AbstractStep - -_log = logging.getLogger(__name__) - - -class ReopenBugAfterRollout(AbstractStep): - def run(self, state): - commit_comment = bug_comment_from_commit_text(self._tool.scm(), state["commit_text"]) - comment_text = "Reverted r%s for reason:\n\n%s\n\n%s" % (state["revision"], state["reason"], commit_comment) - - bug_id = state["bug_id"] - if not bug_id: - _log.info(comment_text) - _log.info("No bugs were updated.") - return - self._tool.bugs.reopen_bug(bug_id, comment_text) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/revertrevision.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/revertrevision.py deleted file mode 100644 index a61037908aa..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/revertrevision.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from webkitpy.tool.steps.abstractstep import AbstractStep - - -class RevertRevision(AbstractStep): - def run(self, state): - self._tool.checkout().apply_reverse_diffs(state["revision_list"]) - self.did_modify_checkout(state) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/runtests.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/runtests.py deleted file mode 100644 index 937eb01440d..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/runtests.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging -import os -import platform -import sys -from webkitpy.tool.steps.abstractstep import AbstractStep -from webkitpy.tool.steps.options import Options -from webkitpy.common.system.executive import ScriptError - -_log = logging.getLogger(__name__) - -class RunTests(AbstractStep): - # FIXME: This knowledge really belongs in the commit-queue. - NON_INTERACTIVE_FAILURE_LIMIT_COUNT = 30 - - @classmethod - def options(cls): - return AbstractStep.options() + [ - Options.build_style, - Options.test, - Options.non_interactive, - Options.quiet, - ] - - def run(self, state): - if not self._options.test: - return - - if not self._options.non_interactive: - # FIXME: We should teach the commit-queue and the EWS how to run these tests. - - python_unittests_command = self._tool.deprecated_port().run_python_unittests_command() - if python_unittests_command: - _log.info("Running Python unit tests") - self._tool.executive.run_and_throw_if_fail(python_unittests_command, cwd=self._tool.scm().checkout_root) - - perl_unittests_command = self._tool.deprecated_port().run_perl_unittests_command() - if perl_unittests_command: - _log.info("Running Perl unit tests") - self._tool.executive.run_and_throw_if_fail(perl_unittests_command, cwd=self._tool.scm().checkout_root) - - bindings_tests_command = self._tool.deprecated_port().run_bindings_tests_command() - if bindings_tests_command: - _log.info("Running bindings generation tests") - args = bindings_tests_command - try: - self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root) - except ScriptError, e: - _log.info("Error running run-bindings-tests: %s" % e.message_with_output()) - - webkit_unit_tests_command = self._tool.deprecated_port().run_webkit_unit_tests_command() - if webkit_unit_tests_command: - _log.info("Running WebKit unit tests") - args = webkit_unit_tests_command - try: - self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root) - except ScriptError, e: - _log.info("Error running webkit_unit_tests: %s" % e.message_with_output()) - - - _log.info("Running run-webkit-tests") - args = self._tool.deprecated_port().run_webkit_tests_command() - if self._options.non_interactive: - args.extend([ - "--no-new-test-results", - "--no-show-results", - "--exit-after-n-failures=%s" % self.NON_INTERACTIVE_FAILURE_LIMIT_COUNT, - ]) - - # old-run-webkit-tests does not support --skip-failing-tests - # Using --quiet one Windows fails when we try to use /dev/null, disabling for now until we find a fix - if sys.platform != "cygwin": - args.append("--quiet") - args.append("--skip-failing-tests") - else: - args.append("--no-build"); - - if self._options.quiet: - args.append("--quiet") - - self._tool.executive.run_and_throw_if_fail(args, cwd=self._tool.scm().checkout_root) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py deleted file mode 100644 index 89ca932d885..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/runtests_unittest.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2011 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import platform -import sys -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.tool.mocktool import MockOptions, MockTool -from webkitpy.tool.steps.runtests import RunTests - -class RunTestsTest(unittest.TestCase): - def test_webkit_run_unit_tests(self): - tool = MockTool(log_executive=True) - tool._deprecated_port.run_python_unittests_command = lambda: None - tool._deprecated_port.run_perl_unittests_command = lambda: None - step = RunTests(tool, MockOptions(test=True, non_interactive=True, quiet=False)) - - if sys.platform != "cygwin": - expected_logs = """Running bindings generation tests -MOCK run_and_throw_if_fail: ['mock-run-bindings-tests'], cwd=/mock-checkout -Running WebKit unit tests -MOCK run_and_throw_if_fail: ['mock-run-webkit-unit-tests'], cwd=/mock-checkout -Running run-webkit-tests -MOCK run_and_throw_if_fail: ['mock-run-webkit-tests', '--no-new-test-results', '--no-show-results', '--exit-after-n-failures=30', '--quiet', '--skip-failing-tests'], cwd=/mock-checkout -""" - else: - expected_logs = """Running bindings generation tests -MOCK run_and_throw_if_fail: ['mock-run-bindings-tests'], cwd=/mock-checkout -Running WebKit unit tests -MOCK run_and_throw_if_fail: ['mock-run-webkit-unit-tests'], cwd=/mock-checkout -Running run-webkit-tests -MOCK run_and_throw_if_fail: ['mock-run-webkit-tests', '--no-new-test-results', '--no-show-results', '--exit-after-n-failures=30', '--no-build'], cwd=/mock-checkout -""" - - OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs) diff --git a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/steps_unittest.py b/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/steps_unittest.py deleted file mode 100644 index e735d663325..00000000000 --- a/chromium/third_party/WebKit/Tools/Scripts/webkitpy/tool/steps/steps_unittest.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (C) 2010 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import webkitpy.thirdparty.unittest2 as unittest - -from webkitpy.common.system.outputcapture import OutputCapture -from webkitpy.common.config.ports import DeprecatedPort -from webkitpy.tool.mocktool import MockOptions, MockTool - -from webkitpy.tool import steps - -class StepsTest(unittest.TestCase): - def _step_options(self): - options = MockOptions() - options.non_interactive = True - options.port = 'MOCK port' - options.quiet = True - options.test = True - return options - - def _run_step(self, step, tool=None, options=None, state=None): - if not tool: - tool = MockTool() - if not options: - options = self._step_options() - if not state: - state = {} - step(tool, options).run(state) - - def test_prompt_for_bug_or_title_step(self): - tool = MockTool() - tool.user.prompt = lambda message: 50000 - self._run_step(steps.PromptForBugOrTitle, tool=tool) - - def _assert_step_output_with_bug(self, step, bug_id, expected_logs, options=None): - state = {'bug_id': bug_id} - OutputCapture().assert_outputs(self, self._run_step, [step, MockTool(), options, state], expected_logs=expected_logs) - - def test_runtests_args(self): - mock_options = self._step_options() - mock_options.non_interactive = False - step = steps.RunTests(MockTool(log_executive=True), mock_options) - tool = MockTool(log_executive=True) - # FIXME: We shouldn't use a real port-object here, but there is too much to mock at the moment. - tool._deprecated_port = DeprecatedPort() - step = steps.RunTests(tool, mock_options) - expected_logs = """Running Python unit tests -MOCK run_and_throw_if_fail: ['Tools/Scripts/test-webkitpy'], cwd=/mock-checkout -Running Perl unit tests -MOCK run_and_throw_if_fail: ['Tools/Scripts/test-webkitperl'], cwd=/mock-checkout -Running bindings generation tests -MOCK run_and_throw_if_fail: ['Tools/Scripts/run-bindings-tests'], cwd=/mock-checkout -Running run-webkit-tests -MOCK run_and_throw_if_fail: ['Tools/Scripts/run-webkit-tests', '--quiet'], cwd=/mock-checkout -""" - OutputCapture().assert_outputs(self, step.run, [{}], expected_logs=expected_logs) |
