Home | History | Annotate | Download | only in strict_enum_value_checker
      1 #!/usr/bin/env python
      2 # Copyright 2014 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import difflib
      7 import os
      8 import re
      9 import unittest
     10 
     11 from strict_enum_value_checker import StrictEnumValueChecker
     12 
     13 class MockLogging(object):
     14   def __init__(self):
     15     self.lines = []
     16 
     17   def info(self, message):
     18     self.lines.append(message)
     19 
     20   def debug(self, message):
     21     self.lines.append(message)
     22 
     23 class MockInputApi(object):
     24   def __init__(self):
     25     self.re = re
     26     self.os_path = os.path
     27     self.files = []
     28     self.is_committing = False
     29     self.logging = MockLogging()
     30 
     31   def AffectedFiles(self, include_deletes=None):
     32     return self.files
     33 
     34 
     35 class MockOutputApi(object):
     36   class PresubmitResult(object):
     37     def __init__(self, message, items=None, long_text=""):
     38       self.message = message
     39       self.items = items
     40       self.long_text = long_text
     41 
     42   class PresubmitError(PresubmitResult):
     43     def __init__(self, message, items, long_text=""):
     44       MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
     45       self.type = "error"
     46 
     47   class PresubmitPromptWarning(PresubmitResult):
     48     def __init__(self, message, items, long_text=""):
     49       MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
     50       self.type = "warning"
     51 
     52   class PresubmitNotifyResult(PresubmitResult):
     53     def __init__(self, message, items, long_text=""):
     54       MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
     55       self.type = "notify"
     56 
     57 
     58 class MockFile(object):
     59   def __init__(self, local_path, old_contents, new_contents):
     60     self._local_path = local_path
     61     self._new_contents = new_contents
     62     self._old_contents = old_contents
     63     self._cached_changed_contents = None
     64 
     65   def ChangedContents(self):
     66     return self._changed_contents
     67 
     68   def NewContents(self):
     69     return self._new_contents
     70 
     71   def LocalPath(self):
     72     return self._local_path
     73 
     74   def IsDirectory(self):
     75     return False
     76 
     77   def GenerateScmDiff(self):
     78     result = ""
     79     for line in difflib.unified_diff(self._old_contents, self._new_contents,
     80                                      self._local_path, self._local_path):
     81       result += line
     82     return result
     83 
     84   # NOTE: This method is a copy of ChangeContents method of AffectedFile in
     85   # presubmit_support.py
     86   def ChangedContents(self):
     87     """Returns a list of tuples (line number, line text) of all new lines.
     88 
     89      This relies on the scm diff output describing each changed code section
     90      with a line of the form
     91 
     92      ^@@ <old line num>,<old size> <new line num>,<new size> @@$
     93     """
     94     if self._cached_changed_contents is not None:
     95       return self._cached_changed_contents[:]
     96     self._cached_changed_contents = []
     97     line_num = 0
     98 
     99     if self.IsDirectory():
    100       return []
    101 
    102     for line in self.GenerateScmDiff().splitlines():
    103       m = re.match(r"^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@", line)
    104       if m:
    105         line_num = int(m.groups(1)[0])
    106         continue
    107       if line.startswith("+") and not line.startswith("++"):
    108         self._cached_changed_contents.append((line_num, line[1:]))
    109       if not line.startswith("-"):
    110         line_num += 1
    111     return self._cached_changed_contents[:]
    112 
    113 
    114 class MockChange(object):
    115   def __init__(self, changed_files):
    116     self._changed_files = changed_files
    117 
    118   def LocalPaths(self):
    119     return self._changed_files
    120 
    121 
    122 class StrictEnumValueCheckerTest(unittest.TestCase):
    123   TEST_FILE_PATTERN = "changed_file_%s.h"
    124   MOCK_FILE_LOCAL_PATH = "mock_enum.h"
    125   START_MARKER = "enum MockEnum {"
    126   END_MARKER = "  mBoundary"
    127 
    128   def _ReadTextFileContents(self, path):
    129     """Given a path, returns a list of strings corresponding to the text lines
    130     in the file. Reads files in text format.
    131 
    132     """
    133     fo = open(path, "r")
    134     try:
    135       contents = fo.readlines()
    136     finally:
    137       fo.close()
    138     return contents
    139 
    140   def _ReadInputFile(self):
    141     return self._ReadTextFileContents("mock_enum.h")
    142 
    143   def _PrepareTest(self, new_file_path):
    144     old_contents = self._ReadInputFile()
    145     if not new_file_path:
    146       new_contents = []
    147     else:
    148       new_contents = self._ReadTextFileContents(new_file_path)
    149     input_api = MockInputApi()
    150     mock_file = MockFile(self.MOCK_FILE_LOCAL_PATH,
    151                          old_contents,
    152                          new_contents)
    153     input_api.files.append(mock_file)
    154     output_api = MockOutputApi()
    155     return input_api, output_api
    156 
    157   def _RunTest(self, new_file_path):
    158     input_api, output_api = self._PrepareTest(new_file_path)
    159     checker = StrictEnumValueChecker(input_api, output_api, self.START_MARKER,
    160                                      self.END_MARKER, self.MOCK_FILE_LOCAL_PATH)
    161     results = checker.Run()
    162     return results
    163 
    164   def testDeleteFile(self):
    165     results = self._RunTest(new_file_path=None)
    166     # TODO(rpaquay) How to check it's the expected warning?'
    167     self.assertEquals(1, len(results),
    168                       "We should get a single warning about file deletion.")
    169 
    170   def testSimpleValidEdit(self):
    171     results = self._RunTest(self.TEST_FILE_PATTERN % "1")
    172     # TODO(rpaquay) How to check it's the expected warning?'
    173     self.assertEquals(0, len(results),
    174                       "We should get no warning for simple edits.")
    175 
    176   def testSingleDeletionOfEntry(self):
    177     results = self._RunTest(self.TEST_FILE_PATTERN % "2")
    178     # TODO(rpaquay) How to check it's the expected warning?'
    179     self.assertEquals(1, len(results),
    180                       "We should get a warning for an entry deletion.")
    181 
    182   def testSingleRenameOfEntry(self):
    183     results = self._RunTest(self.TEST_FILE_PATTERN % "3")
    184     # TODO(rpaquay) How to check it's the expected warning?'
    185     self.assertEquals(1, len(results),
    186                       "We should get a warning for an entry rename, even "
    187                       "though it is not optimal.")
    188 
    189   def testMissingEnumStartOfEntry(self):
    190     results = self._RunTest(self.TEST_FILE_PATTERN % "4")
    191     # TODO(rpaquay) How to check it's the expected warning?'
    192     self.assertEquals(1, len(results),
    193                       "We should get a warning for a missing enum marker.")
    194 
    195   def testMissingEnumEndOfEntry(self):
    196     results = self._RunTest(self.TEST_FILE_PATTERN % "5")
    197     # TODO(rpaquay) How to check it's the expected warning?'
    198     self.assertEquals(1, len(results),
    199                       "We should get a warning for a missing enum marker.")
    200 
    201   def testInvertedEnumMarkersOfEntry(self):
    202     results = self._RunTest(self.TEST_FILE_PATTERN % "6")
    203     # TODO(rpaquay) How to check it's the expected warning?'
    204     self.assertEquals(1, len(results),
    205                       "We should get a warning for inverted enum markers.")
    206 
    207   def testMultipleInvalidEdits(self):
    208     results = self._RunTest(self.TEST_FILE_PATTERN % "7")
    209     # TODO(rpaquay) How to check it's the expected warning?'
    210     self.assertEquals(3, len(results),
    211                       "We should get 3 warnings (one per edit).")
    212 
    213   def testSingleInvalidInserts(self):
    214     results = self._RunTest(self.TEST_FILE_PATTERN % "8")
    215     # TODO(rpaquay) How to check it's the expected warning?'
    216     self.assertEquals(1, len(results),
    217                       "We should get a warning for a single invalid "
    218                       "insertion inside the enum.")
    219 
    220   def testMulitpleValidInserts(self):
    221     results = self._RunTest(self.TEST_FILE_PATTERN % "9")
    222     # TODO(rpaquay) How to check it's the expected warning?'
    223     self.assertEquals(0, len(results),
    224                       "We should not get a warning mulitple valid edits")
    225 
    226   def testSingleValidDeleteOutsideOfEnum(self):
    227     results = self._RunTest(self.TEST_FILE_PATTERN % "10")
    228     # TODO(rpaquay) How to check it's the expected warning?'
    229     self.assertEquals(0, len(results),
    230                       "We should not get a warning for a deletion outside of "
    231                       "the enum")
    232 
    233 
    234 if __name__ == '__main__':
    235   unittest.main()
    236