Title: [219657] trunk/Tools
Revision
219657
Author
[email protected]
Date
2017-07-19 10:09:32 -0700 (Wed, 19 Jul 2017)

Log Message

lint-test-expectations should be run during style checking
https://bugs.webkit.org/show_bug.cgi?id=173559
<rdar://problem/32854941>

Reviewed by David Kilzer.

Running the test expectation linter requires reading both files and lines not in the
patch because, for example, deletion of a test can cause a lint failure even though
no test expectations where modified. This means that the linter will occasionally warn
about lines which were not changed in a given patch but whose error is related to a
change made in that patch.

* Scripts/webkitpy/common/system/filesystem_mock.py:
(MockFileSystem.open_text_file_for_reading): Add 'errors' argument to mimic filesystem.
* Scripts/webkitpy/layout_tests/models/test_expectations.py:
(TestExpectationWarning): Added class to pass warnings as an object instead of a string.
(TestExpectationWarning.__init__): Construct warning with file name, line number, line
content, description of the error and the name of the associated test.
(TestExpectationWarning.__str__): Convert to string so that existing printing code works.
(TestExpectationParser.__init__): Pass shorten_filename function to
TestExpectationsParser to add a deleted file to the related_files dictionary.
(TestExpectationParser._check_test_exists): If the test does not exist, add the
missing path to the related_files dictionary.
(TestExpectationLine.__init__): Add related_files dictionary, which tracks
files and line numbers related to this test expectation line. This will allow
tracking linter errors in the style checker occurring across multiple files.
(TestExpectationsModel._already_seen_better_match): Add redundant expectation
lines to related_files dictionary.
(TestExpectations.__init__): Pass self._shorten_filename to TestExpectationParser.
(TestExpectations._report_warnings): Construct warning object instead of string
when adding to warnings list.
* Scripts/webkitpy/port/win.py: Changed logging level form warning to debug to
clean-up log.
* Scripts/webkitpy/style/checker.py:
(ProcessorBase.do_association_check): Add required function for processor classes.
(StyleProcessor):
(StyleProcessor.do_association_check): Run the TestExpectations linter when
checking for errors between associated files.
* Scripts/webkitpy/style/checkers/test_expectations.py:
(TestExpectationsChecker.check_test_expectations): Reflect changed import statements.
(TestExpectationsChecker):
(TestExpectationsChecker._should_log_linter_warning): Given a warning, a dictionary
of modified files, the current working directory and the host, determine if the linter
warning is associated with the changes.
(TestExpectationsChecker.lint_test_expectations): Lint test expectations for the
style checker.
* Scripts/webkitpy/style/filereader.py:
(TextFileReader):
(TextFileReader.__init__): Track modified files in dictionary instead of a counter.
(TextFileReader.file_count): Use dictionary to determine the number of modified files.
(TextFileReader.process_file): Track both the number of files changed and which lines
in those files were changed.
(TextFileReader.do_association_check): Run the processor's association check on all
modified or deleted files processed by TextFileReader.
(TextFileReader.delete_file): Track deleted files in _files dictionary.
(TextFileReader.count_delete_only_file): Deleted.
* Scripts/webkitpy/style/filereader_unittest.py:
(TextFileReaderTest.test_delete_file): Renamed function to reflect new function name.
(TextFileReaderTest.test_count_delete_only_file): Moved to test_delete_file.
* Scripts/webkitpy/style/main.py:
(CheckWebKitStyle.main): When running the style checker on a specific list of files,
explicitly run the association check on the file reader.
* Scripts/webkitpy/style/main_unittest.py:
(ExpectationLinterInStyleCheckerTest): Added to test the TestExpectationLinter now
embedded in the style checker.
(ExpectationLinterInStyleCheckerTest.setUp): Set up the style checker configuration.
(ExpectationLinterInStyleCheckerTest._generate_file_reader): Given a filesystem object,
construct the TextFileReader object with a StyleProcessor used to run style checks on
specific files.
(ExpectationLinterInStyleCheckerTest._generate_testing_host): Generate a host used for
testing the test expectation linter inside the style checker. This host must contain a
mock file system with the basic structure of test expectations.
(ExpectationLinterInStyleCheckerTest.test_no_linter_errors):
(ExpectationLinterInStyleCheckerTest.test_linter_duplicate_line):
(ExpectationLinterInStyleCheckerTest.test_linter_duplicate_line_no_edit):
(ExpectationLinterInStyleCheckerTest.test_linter_deleted_file):
(ExpectationLinterInStyleCheckerTest.test_linter_deleted_file_no_edit):
* Scripts/webkitpy/style/patchreader.py:
(PatchReader.check): Specify which file was deleted, run the association check.
* Scripts/webkitpy/style/patchreader_unittest.py:
(PatchReaderTest.MockTextFileReader.delete_file): Renamed count_delete_only_file.
(PatchReaderTest.MockTextFileReader.do_association_check): Added.
(PatchReaderTest.MockTextFileReader.count_delete_only_file): Renamed delete_file.

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (219656 => 219657)


--- trunk/Tools/ChangeLog	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/ChangeLog	2017-07-19 17:09:32 UTC (rev 219657)
@@ -1,3 +1,89 @@
+2017-07-19  Jonathan Bedard  <[email protected]>
+
+        lint-test-expectations should be run during style checking
+        https://bugs.webkit.org/show_bug.cgi?id=173559
+        <rdar://problem/32854941>
+
+        Reviewed by David Kilzer.
+
+        Running the test expectation linter requires reading both files and lines not in the
+        patch because, for example, deletion of a test can cause a lint failure even though
+        no test expectations where modified. This means that the linter will occasionally warn
+        about lines which were not changed in a given patch but whose error is related to a
+        change made in that patch.
+
+        * Scripts/webkitpy/common/system/filesystem_mock.py:
+        (MockFileSystem.open_text_file_for_reading): Add 'errors' argument to mimic filesystem.
+        * Scripts/webkitpy/layout_tests/models/test_expectations.py:
+        (TestExpectationWarning): Added class to pass warnings as an object instead of a string.
+        (TestExpectationWarning.__init__): Construct warning with file name, line number, line
+        content, description of the error and the name of the associated test.
+        (TestExpectationWarning.__str__): Convert to string so that existing printing code works.
+        (TestExpectationParser.__init__): Pass shorten_filename function to
+        TestExpectationsParser to add a deleted file to the related_files dictionary.
+        (TestExpectationParser._check_test_exists): If the test does not exist, add the
+        missing path to the related_files dictionary.
+        (TestExpectationLine.__init__): Add related_files dictionary, which tracks
+        files and line numbers related to this test expectation line. This will allow
+        tracking linter errors in the style checker occurring across multiple files.
+        (TestExpectationsModel._already_seen_better_match): Add redundant expectation
+        lines to related_files dictionary.
+        (TestExpectations.__init__): Pass self._shorten_filename to TestExpectationParser.
+        (TestExpectations._report_warnings): Construct warning object instead of string
+        when adding to warnings list.
+        * Scripts/webkitpy/port/win.py: Changed logging level form warning to debug to
+        clean-up log.
+        * Scripts/webkitpy/style/checker.py:
+        (ProcessorBase.do_association_check): Add required function for processor classes.
+        (StyleProcessor):
+        (StyleProcessor.do_association_check): Run the TestExpectations linter when
+        checking for errors between associated files.
+        * Scripts/webkitpy/style/checkers/test_expectations.py:
+        (TestExpectationsChecker.check_test_expectations): Reflect changed import statements.
+        (TestExpectationsChecker):
+        (TestExpectationsChecker._should_log_linter_warning): Given a warning, a dictionary
+        of modified files, the current working directory and the host, determine if the linter
+        warning is associated with the changes.
+        (TestExpectationsChecker.lint_test_expectations): Lint test expectations for the
+        style checker.
+        * Scripts/webkitpy/style/filereader.py:
+        (TextFileReader):
+        (TextFileReader.__init__): Track modified files in dictionary instead of a counter.
+        (TextFileReader.file_count): Use dictionary to determine the number of modified files.
+        (TextFileReader.process_file): Track both the number of files changed and which lines
+        in those files were changed.
+        (TextFileReader.do_association_check): Run the processor's association check on all
+        modified or deleted files processed by TextFileReader.
+        (TextFileReader.delete_file): Track deleted files in _files dictionary.
+        (TextFileReader.count_delete_only_file): Deleted.
+        * Scripts/webkitpy/style/filereader_unittest.py:
+        (TextFileReaderTest.test_delete_file): Renamed function to reflect new function name.
+        (TextFileReaderTest.test_count_delete_only_file): Moved to test_delete_file.
+        * Scripts/webkitpy/style/main.py:
+        (CheckWebKitStyle.main): When running the style checker on a specific list of files,
+        explicitly run the association check on the file reader.
+        * Scripts/webkitpy/style/main_unittest.py:
+        (ExpectationLinterInStyleCheckerTest): Added to test the TestExpectationLinter now
+        embedded in the style checker.
+        (ExpectationLinterInStyleCheckerTest.setUp): Set up the style checker configuration.
+        (ExpectationLinterInStyleCheckerTest._generate_file_reader): Given a filesystem object,
+        construct the TextFileReader object with a StyleProcessor used to run style checks on
+        specific files.
+        (ExpectationLinterInStyleCheckerTest._generate_testing_host): Generate a host used for
+        testing the test expectation linter inside the style checker. This host must contain a
+        mock file system with the basic structure of test expectations.
+        (ExpectationLinterInStyleCheckerTest.test_no_linter_errors):
+        (ExpectationLinterInStyleCheckerTest.test_linter_duplicate_line):
+        (ExpectationLinterInStyleCheckerTest.test_linter_duplicate_line_no_edit):
+        (ExpectationLinterInStyleCheckerTest.test_linter_deleted_file):
+        (ExpectationLinterInStyleCheckerTest.test_linter_deleted_file_no_edit):
+        * Scripts/webkitpy/style/patchreader.py:
+        (PatchReader.check): Specify which file was deleted, run the association check.
+        * Scripts/webkitpy/style/patchreader_unittest.py:
+        (PatchReaderTest.MockTextFileReader.delete_file): Renamed count_delete_only_file.
+        (PatchReaderTest.MockTextFileReader.do_association_check): Added.
+        (PatchReaderTest.MockTextFileReader.count_delete_only_file): Renamed delete_file.
+
 2017-07-19  Yusuke Suzuki  <[email protected]>
 
         [WTF] Implement WTF::ThreadGroup

Modified: trunk/Tools/Scripts/webkitpy/common/system/filesystem_mock.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/common/system/filesystem_mock.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/common/system/filesystem_mock.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -332,7 +332,7 @@
         self.files[path] = contents
         self.written_files[path] = contents
 
-    def open_text_file_for_reading(self, path):
+    def open_text_file_for_reading(self, path, errors='strict'):
         if self.files[path] is None:
             self._raise_not_found(path)
         return ReadableTextFileObject(self, path, self.files[path])

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/models/test_expectations.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -61,6 +61,20 @@
         return 'ParseError(warnings=%s)' % self.warnings
 
 
+class TestExpectationWarning(object):
+    def __init__(self, filename, line_number, line, error, test=None):
+        self.filename = filename
+        self.line_number = line_number
+        self.line = line
+        self.error = error
+        self.test = test
+
+        self.related_files = {}
+
+    def __str__(self):
+        return '{}:{} {} {}'.format(self.filename, self.line_number, self.error, self.test if self.test else self.line)
+
+
 class TestExpectationParser(object):
     """Provides parsing facilities for lines in the test_expectation.txt file."""
 
@@ -78,7 +92,7 @@
 
     MISSING_BUG_WARNING = 'Test lacks BUG modifier.'
 
-    def __init__(self, port, full_test_list, allow_rebaseline_modifier):
+    def __init__(self, port, full_test_list, allow_rebaseline_modifier, shorten_filename=str):
         self._port = port
         self._test_configuration_converter = TestConfigurationConverter(set(port.all_test_configurations()), port.configuration_specifier_macros())
         if full_test_list is None:
@@ -86,6 +100,7 @@
         else:
             self._full_test_list = set(full_test_list)
         self._allow_rebaseline_modifier = allow_rebaseline_modifier
+        self._shorten_filename = shorten_filename
 
     def parse(self, filename, expectations_string):
         expectation_lines = []
@@ -186,6 +201,8 @@
             # time you update TestExpectations without syncing
             # the LayoutTests directory
             expectation_line.warnings.append('Path does not exist.')
+            expected_path = self._shorten_filename(self._port.abspath_for_test(expectation_line.name))
+            expectation_line.related_files[expected_path] = None
             return False
         return True
 
@@ -381,6 +398,7 @@
         self.comment = None
         self.matching_tests = []
         self.warnings = []
+        self.related_files = {}  # Dictionary of files to lines number in that file which may have caused the list of warnings.
         self.not_applicable_to_current_platform = False
 
     def is_invalid(self):
@@ -701,31 +719,38 @@
             expectation_line.warnings.append('Duplicate or ambiguous entry lines %s:%d and %s:%d.' % (
                 self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number,
                 self._shorten_filename(expectation_line.filename), expectation_line.line_number))
-            return True
 
-        if prev_expectation_line.matching_configurations >= expectation_line.matching_configurations:
+        elif prev_expectation_line.matching_configurations >= expectation_line.matching_configurations:
             expectation_line.warnings.append('More specific entry for %s on line %s:%d overrides line %s:%d.' % (expectation_line.name,
                 self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number,
                 self._shorten_filename(expectation_line.filename), expectation_line.line_number))
             # FIXME: return False if we want more specific to win.
-            return True
 
-        if prev_expectation_line.matching_configurations <= expectation_line.matching_configurations:
+        elif prev_expectation_line.matching_configurations <= expectation_line.matching_configurations:
             expectation_line.warnings.append('More specific entry for %s on line %s:%d overrides line %s:%d.' % (expectation_line.name,
                 self._shorten_filename(expectation_line.filename), expectation_line.line_number,
                 self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number))
-            return True
 
-        if prev_expectation_line.matching_configurations & expectation_line.matching_configurations:
+        elif prev_expectation_line.matching_configurations & expectation_line.matching_configurations:
             expectation_line.warnings.append('Entries for %s on lines %s:%d and %s:%d match overlapping sets of configurations.' % (expectation_line.name,
                 self._shorten_filename(prev_expectation_line.filename), prev_expectation_line.line_number,
                 self._shorten_filename(expectation_line.filename), expectation_line.line_number))
-            return True
 
-        # Configuration sets are disjoint, then.
-        return False
+        else:
+            # Configuration sets are disjoint.
+            return False
 
+        if expectation_line.related_files.get(self._shorten_filename(prev_expectation_line.filename), None) is None:
+            expectation_line.related_files[self._shorten_filename(prev_expectation_line.filename)] = []
+        expectation_line.related_files[self._shorten_filename(prev_expectation_line.filename)].append(prev_expectation_line.line_number)
 
+        if prev_expectation_line.related_files.get(self._shorten_filename(expectation_line.filename), None) is None:
+            prev_expectation_line.related_files[self._shorten_filename(expectation_line.filename)] = []
+        prev_expectation_line.related_files[self._shorten_filename(expectation_line.filename)].append(prev_expectation_line.line_number)
+
+        return True
+
+
 class TestExpectations(object):
     """Test expectations consist of lines with specifications of what
     to expect from layout test cases. The test cases can be directories
@@ -853,7 +878,7 @@
         self._test_config = port.test_configuration()
         self._is_lint_mode = expectations_to_lint is not None
         self._model = TestExpectationsModel(self._shorten_filename)
-        self._parser = TestExpectationParser(port, tests, self._is_lint_mode)
+        self._parser = TestExpectationParser(port, tests, self._is_lint_mode, self._shorten_filename)
         self._port = port
         self._skipped_tests_warnings = []
         self._expectations = []
@@ -938,8 +963,14 @@
         warnings = []
         for expectation in self._expectations:
             for warning in expectation.warnings:
-                warnings.append('%s:%d %s %s' % (self._shorten_filename(expectation.filename), expectation.line_number,
-                                warning, expectation.name if expectation.expectations else expectation.original_string))
+                warning = TestExpectationWarning(
+                    self._shorten_filename(expectation.filename),
+                    expectation.line_number,
+                    expectation.original_string,
+                    warning,
+                    expectation.name if expectation.expectations else None)
+                warning.related_files = expectation.related_files
+                warnings.append(warning)
 
         if warnings:
             self._has_warnings = True

Modified: trunk/Tools/Scripts/webkitpy/port/win.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/port/win.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/port/win.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -49,7 +49,7 @@
     import _winreg
     import win32com.client
 except ImportError:
-    _log.warn("Not running on native Windows.")
+    _log.debug("Not running on native Windows.")
 
 
 class WinPort(ApplePort):

Modified: trunk/Tools/Scripts/webkitpy/style/checker.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/checker.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/checker.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -60,6 +60,7 @@
 from filter import FilterConfiguration
 from optparser import ArgumentParser
 from optparser import DefaultCommandOptionValues
+from webkitpy.common.host import Host
 from webkitpy.common.system.logutils import configure_logging as _configure_logging
 from webkitpy.port.config import apple_additions
 
@@ -811,7 +812,23 @@
         """
         raise NotImplementedError('Subclasses should implement.')
 
+    def do_association_check(self, files, cwd, host=Host()):
+        """It may be the case that changes in a file cause style errors to
+        occur elsewhere in the modified file or in other files (most notably,
+        this is the case for the test expectations linter). The association check
+        is designed to take find issues which require parsing of files outside
+        those provided to the processor.
 
+        Args:
+            files: A dictionary of file names to lists of lines modified in the provided
+            file.  Files which have been removed will also be in the dictionary, they
+            will map to 'None'
+            cwd: Current working directory, assumed to be the root of the scm repository
+            host: Current host for testing
+        """
+        raise NotImplementedError('Subclasses should implement.')
+
+
 class StyleProcessor(ProcessorBase):
 
     """A ProcessorBase for checking style.
@@ -918,3 +935,7 @@
         _log.debug("Using class: " + checker.__class__.__name__)
 
         checker.check(lines)
+
+    def do_association_check(self, files, cwd, host=Host()):
+        _log.debug("Running TestExpectations linter")
+        TestExpectationsChecker.lint_test_expectations(files, self._configuration, cwd, self._increment_error_count, host=host)

Modified: trunk/Tools/Scripts/webkitpy/style/checkers/test_expectations.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/checkers/test_expectations.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/checkers/test_expectations.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -36,7 +36,8 @@
 
 from common import TabChecker
 from webkitpy.common.host import Host
-from webkitpy.layout_tests.models.test_expectations import TestExpectationParser
+from webkitpy.layout_tests.models import test_expectations
+from webkitpy.style.error_handlers import DefaultStyleErrorHandler
 
 
 _log = logging.getLogger(__name__)
@@ -78,7 +79,7 @@
         pass
 
     def check_test_expectations(self, expectations_str, tests=None):
-        parser = TestExpectationParser(self._port_obj, tests, allow_rebaseline_modifier=False)
+        parser = test_expectations.TestExpectationParser(self._port_obj, tests, allow_rebaseline_modifier=False)
         expectations = parser.parse('expectations', expectations_str)
 
         level = 5
@@ -96,3 +97,52 @@
 
         # Warn tabs in lines as well
         self.check_tabs(lines)
+
+    @staticmethod
+    def _should_log_linter_warning(warning, files, cwd, host):
+        # Case 1, the line the warning was tied to is in our patch.
+        abs_filename = host.filesystem.join(cwd, warning.filename)
+        if abs_filename in files and (files[abs_filename] is None or warning.line_number in files[abs_filename]):
+            return True
+
+        for file, lines in warning.related_files.iteritems():
+            abs_filename = host.filesystem.join(cwd, file)
+            if abs_filename in files:
+                # Case 2, a file associated with the warning is in our patch
+                # Note that this will really only happen if you delete a test.
+                if lines is None:
+                    return True
+
+                # Case 3, a line associated with the warning is in our patch.
+                for line in lines:
+                    if line in files[abs_filename]:
+                        return True
+        return False
+
+    @staticmethod
+    def lint_test_expectations(files, configuration, cwd, increment_error_count=lambda: 0, line_numbers=None, host=Host()):
+        error_count = 0
+        files_linted = set()
+        ports_to_lint = [host.port_factory.get(name) for name in host.port_factory.all_port_names()]
+        for port in ports_to_lint:
+            for expectations_file in port.expectations_dict().keys():
+                style_error_handler = DefaultStyleErrorHandler(
+                    expectations_file,
+                    configuration,
+                    increment_error_count,
+                    line_numbers)
+
+                try:
+                    if expectations_file in files_linted:
+                        continue
+                    expectations = test_expectations.TestExpectations(
+                        port,
+                        expectations_to_lint={expectations_file: port.expectations_dict()[expectations_file]})
+                    expectations.parse_all_expectations()
+                except test_expectations.ParseError as e:
+                    for warning in e.warnings:
+                        if TestExpectationsChecker._should_log_linter_warning(warning, files, cwd, host):
+                            style_error_handler(warning.line_number, 'test/expectations', 5, warning.error)
+                            error_count += 1
+                files_linted.add(expectations_file)
+        return error_count

Modified: trunk/Tools/Scripts/webkitpy/style/filereader.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/filereader.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/filereader.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -33,7 +33,9 @@
 import logging
 import sys
 
+from webkitpy.common.host import Host
 
+
 _log = logging.getLogger(__name__)
 
 
@@ -62,9 +64,13 @@
 
         self.filesystem = filesystem
         self._processor = processor
-        self.file_count = 0
+        self._files = {}
         self.delete_only_file_count = 0
 
+    @property
+    def file_count(self):
+        return len(self._files) - self.delete_only_file_count
+
     def _read_lines(self, file_path):
         """Read the file at a path, and return its lines.
 
@@ -103,7 +109,13 @@
           SystemExit: If no file at file_path exists.
 
         """
-        self.file_count += 1
+        abs_file_path = self.filesystem.abspath(file_path)
+        if abs_file_path not in self._files:
+            self._files[abs_file_path] = None
+        if 'line_numbers' in kwargs:
+            if self._files[abs_file_path] is None:
+                self._files[abs_file_path] = []
+            self._files[abs_file_path] = self._files[abs_file_path] + kwargs['line_numbers']
 
         if not self.filesystem.exists(file_path) and file_path != "-":
             _log.error("File does not exist: '%s'" % file_path)
@@ -135,7 +147,10 @@
             else:
                 self.process_file(path)
 
-    def count_delete_only_file(self):
+    def do_association_check(self, cwd, host=Host()):
+        self._processor.do_association_check(self._files, cwd, host=host)
+
+    def delete_file(self, file_path=None):
         """Count up files that contains only deleted lines.
 
         Files which has no modified or newly-added lines don't need
@@ -142,4 +157,6 @@
         to check style, but should be treated as checked. For that
         purpose, we just count up the number of such files.
         """
+        if file_path:
+            self._files[self.filesystem.abspath(file_path)] = None
         self.delete_only_file_count += 1

Modified: trunk/Tools/Scripts/webkitpy/style/filereader_unittest.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/filereader_unittest.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/filereader_unittest.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -150,8 +150,8 @@
                      (['foo'], file_path1, None)]
         self._assert_file_reader(processed, 2)
 
-    def test_count_delete_only_file(self):
-        self._file_reader.count_delete_only_file()
+    def test_delete_file(self):
+        self._file_reader.delete_file()
         delete_only_file_count = self._file_reader.delete_only_file_count
         self.assertEqual(delete_only_file_count, 1)
 

Modified: trunk/Tools/Scripts/webkitpy/style/main.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/main.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/main.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -147,6 +147,7 @@
 
         if paths and not options.diff_files:
             file_reader.process_paths(paths)
+            file_reader.do_association_check(host.scm().checkout_root)
         else:
             changed_files = paths if options.diff_files else None
             patch = host.scm().create_patch(options.git_commit, changed_files=changed_files, git_index=options.git_index)

Modified: trunk/Tools/Scripts/webkitpy/style/main_unittest.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/main_unittest.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/main_unittest.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -20,11 +20,18 @@
 # 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 optparse
 import unittest
 
+import webkitpy.style.checker as checker
+
 from main import change_directory
+from webkitpy.common.host import Host
 from webkitpy.common.system.filesystem_mock import MockFileSystem
+from webkitpy.common.system.outputcapture import OutputCaptureScope
 from webkitpy.common.system.logtesting import LogTesting
+from webkitpy.style.checker import StyleProcessor
+from webkitpy.style.filereader import TextFileReader
 
 
 class ChangeDirectoryTest(unittest.TestCase):
@@ -73,3 +80,100 @@
 
 """]
         self._assert_result(paths, paths, log_messages, self._original_directory)
+
+
+class ExpectationLinterInStyleCheckerTest(unittest.TestCase):
+
+    def setUp(self):
+        parser = checker.check_webkit_style_parser()
+        (_, options) = parser.parse([])
+        self._style_checker_configuration = checker.check_webkit_style_configuration(options)
+
+    def _generate_file_reader(self, file_system):
+        style_processor = StyleProcessor(self._style_checker_configuration)
+        return TextFileReader(file_system, style_processor)
+
+    def _generate_testing_host(self, files={}):
+        host = Host()
+        expectation_files = files
+
+        host.filesystem = MockFileSystem(dirs=['/mock-checkout/LayoutTests'])
+        options = optparse.Values()
+        setattr(options, 'layout_tests_dir', '/mock-checkout/LayoutTests')
+
+        all_ports = [host.port_factory.get(name, options=options) for name in host.port_factory.all_port_names()]
+        for port in all_ports:
+            for path in port.expectations_files():
+                if path not in expectation_files:
+                    expectation_files[path] = '# Empty expectation file\n'
+
+        expectation_files['/mock-checkout/LayoutTests/css1/test.html'] = 'Test'
+        expectation_files['/mock-checkout/LayoutTests/css1/test-expected.txt'] = 'Test Expectation'
+        host.filesystem = MockFileSystem(files=expectation_files)
+        return host
+
+    def test_no_linter_errors(self):
+        host = self._generate_testing_host()
+
+        scope = OutputCaptureScope()
+        with scope:
+            file_reader = self._generate_file_reader(host.filesystem)
+            file_reader.do_association_check('/mock-checkout', host)
+        self.assertEqual(scope.captured_output, ('', '', ''))
+
+    def test_linter_duplicate_line(self):
+        files = {
+            '/mock-checkout/LayoutTests/TestExpectations':
+            '# TestExpectations\ncss1/test.html [ Failure ]\ncss1/test.html [ Failure ]\n'}
+        host = self._generate_testing_host(files)
+
+        scope = OutputCaptureScope()
+        with scope:
+            file_reader = self._generate_file_reader(host.filesystem)
+            file_reader.process_paths({'/mock-checkout/LayoutTests/TestExpectations'})
+            file_reader.do_association_check('/mock-checkout', host)
+        self.assertEqual(
+            scope.captured_output,
+            ('', '', '/mock-checkout/LayoutTests/TestExpectations:3:  Duplicate or ambiguous entry lines LayoutTests/TestExpectations:2 and LayoutTests/TestExpectations:3.  [test/expectations] [5]\n'))
+
+    def test_linter_duplicate_line_no_edit(self):
+        files = {
+            '/mock-checkout/LayoutTests/TestExpectations':
+            '# TestExpectations\ncss1/test.html [ Failure ]\ncss1/test.html [ Failure ]\n'}
+        host = self._generate_testing_host(files)
+
+        scope = OutputCaptureScope()
+        with scope:
+            file_reader = self._generate_file_reader(host.filesystem)
+            file_reader.process_paths(['/mock-checkout/LayoutTests/platform/ios/TestExpectations'])
+            file_reader.do_association_check('/mock-checkout', host)
+        self.assertEqual(scope.captured_output, ('', '', ''))
+
+    def test_linter_deleted_file(self):
+        files = {
+            '/mock-checkout/LayoutTests/TestExpectations':
+            '# TestExpectations\ncss1/deleted-test.html [ Failure ]\n'}
+        host = self._generate_testing_host(files)
+        host = self._generate_testing_host(files)
+
+        scope = OutputCaptureScope()
+        with scope:
+            file_reader = self._generate_file_reader(host.filesystem)
+            file_reader.delete_file('/mock-checkout/LayoutTests/css1/deleted-test.html')
+            file_reader.do_association_check('/mock-checkout', host)
+        self.assertEqual(
+            scope.captured_output,
+            ('', '', '/mock-checkout/LayoutTests/TestExpectations:2:  Path does not exist.  [test/expectations] [5]\n'))
+
+    def test_linter_deleted_file_no_edit(self):
+        files = {
+            '/mock-checkout/LayoutTests/TestExpectations':
+            '# TestExpectations\ncss1/deleted-test.html [ Failure ]\n'}
+        host = self._generate_testing_host(files)
+
+        scope = OutputCaptureScope()
+        with scope:
+            file_reader = self._generate_file_reader(host.filesystem)
+            file_reader.delete_file('/mock-checkout/LayoutTests/css1/other-deleted-test.html')
+            file_reader.do_association_check('/mock-checkout', host)
+        self.assertEqual(scope.captured_output, ('', '', ''))

Modified: trunk/Tools/Scripts/webkitpy/style/patchreader.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/patchreader.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/patchreader.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -77,7 +77,8 @@
                 # Don't check files which contain only deleted lines
                 # as they can never add style errors. However, mark them as
                 # processed so that we count up number of such files.
-                self._text_file_reader.count_delete_only_file()
+                self._text_file_reader.delete_file(path)
                 continue
 
             self._text_file_reader.process_file(file_path=path, line_numbers=line_numbers)
+        self._text_file_reader.do_association_check(fs.getcwd())

Modified: trunk/Tools/Scripts/webkitpy/style/patchreader_unittest.py (219656 => 219657)


--- trunk/Tools/Scripts/webkitpy/style/patchreader_unittest.py	2017-07-19 16:42:53 UTC (rev 219656)
+++ trunk/Tools/Scripts/webkitpy/style/patchreader_unittest.py	2017-07-19 17:09:32 UTC (rev 219657)
@@ -45,14 +45,17 @@
             self.passed_to_process_file = []
             """A list of (file_path, line_numbers) pairs."""
             self.delete_only_file_count = 0
-            """A number of times count_delete_only_file() called"""
+            """A number of times delete_file() called"""
 
         def process_file(self, file_path, line_numbers):
             self.passed_to_process_file.append((file_path, line_numbers))
 
-        def count_delete_only_file(self):
+        def delete_file(self, file_path=None):
             self.delete_only_file_count += 1
 
+        def do_association_check(self, cwd):
+            pass
+
     def setUp(self):
         file_reader = self.MockTextFileReader()
         self._file_reader = file_reader
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to