123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- # Copyright 2014 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- from collections import defaultdict
- import fnmatch
- import json
- import os
- import re
- import subprocess
- import sys
- # TODO(dcheng): It's kind of horrible that this is copy and pasted from
- # presubmit_canned_checks.py, but it's far easier than any of the alternatives.
- def _ReportErrorFileAndLine(filename, line_num, dummy_line):
- """Default error formatter for _FindNewViolationsOfRule."""
- return '%s:%s' % (filename, line_num)
- class MockCannedChecks(object):
- def _FindNewViolationsOfRule(self, callable_rule, input_api,
- source_file_filter=None,
- error_formatter=_ReportErrorFileAndLine):
- """Find all newly introduced violations of a per-line rule (a callable).
- Arguments:
- callable_rule: a callable taking a file extension and line of input and
- returning True if the rule is satisfied and False if there was a
- problem.
- input_api: object to enumerate the affected files.
- source_file_filter: a filter to be passed to the input api.
- error_formatter: a callable taking (filename, line_number, line) and
- returning a formatted error string.
- Returns:
- A list of the newly-introduced violations reported by the rule.
- """
- errors = []
- for f in input_api.AffectedFiles(include_deletes=False,
- file_filter=source_file_filter):
- # For speed, we do two passes, checking first the full file. Shelling out
- # to the SCM to determine the changed region can be quite expensive on
- # Win32. Assuming that most files will be kept problem-free, we can
- # skip the SCM operations most of the time.
- extension = str(f.LocalPath()).rsplit('.', 1)[-1]
- if all(callable_rule(extension, line) for line in f.NewContents()):
- continue # No violation found in full text: can skip considering diff.
- for line_num, line in f.ChangedContents():
- if not callable_rule(extension, line):
- errors.append(error_formatter(f.LocalPath(), line_num, line))
- return errors
- class MockInputApi(object):
- """Mock class for the InputApi class.
- This class can be used for unittests for presubmit by initializing the files
- attribute as the list of changed files.
- """
- DEFAULT_FILES_TO_SKIP = ()
- def __init__(self):
- self.canned_checks = MockCannedChecks()
- self.fnmatch = fnmatch
- self.json = json
- self.re = re
- self.os_path = os.path
- self.platform = sys.platform
- self.python_executable = sys.executable
- self.python3_executable = sys.executable
- self.platform = sys.platform
- self.subprocess = subprocess
- self.sys = sys
- self.files = []
- self.is_committing = False
- self.change = MockChange([])
- self.presubmit_local_path = os.path.dirname(__file__)
- self.is_windows = sys.platform == 'win32'
- self.no_diffs = False
- def CreateMockFileInPath(self, f_list):
- self.os_path.exists = lambda x: x in f_list
- def AffectedFiles(self, file_filter=None, include_deletes=True):
- for file in self.files:
- if file_filter and not file_filter(file):
- continue
- if not include_deletes and file.Action() == 'D':
- continue
- yield file
- def RightHandSideLines(self, source_file_filter=None):
- affected_files = self.AffectedSourceFiles(source_file_filter)
- for af in affected_files:
- lines = af.ChangedContents()
- for line in lines:
- yield (af, line[0], line[1])
- def AffectedSourceFiles(self, file_filter=None):
- return self.AffectedFiles(file_filter=file_filter)
- def FilterSourceFile(self, file,
- files_to_check=(), files_to_skip=()):
- local_path = file.LocalPath()
- found_in_files_to_check = not files_to_check
- if files_to_check:
- if type(files_to_check) is str:
- raise TypeError('files_to_check should be an iterable of strings')
- for pattern in files_to_check:
- compiled_pattern = re.compile(pattern)
- if compiled_pattern.match(local_path):
- found_in_files_to_check = True
- break
- if files_to_skip:
- if type(files_to_skip) is str:
- raise TypeError('files_to_skip should be an iterable of strings')
- for pattern in files_to_skip:
- compiled_pattern = re.compile(pattern)
- if compiled_pattern.match(local_path):
- return False
- return found_in_files_to_check
- def LocalPaths(self):
- return [file.LocalPath() for file in self.files]
- def PresubmitLocalPath(self):
- return self.presubmit_local_path
- def ReadFile(self, filename, mode='rU'):
- if hasattr(filename, 'AbsoluteLocalPath'):
- filename = filename.AbsoluteLocalPath()
- for file_ in self.files:
- if file_.LocalPath() == filename:
- return '\n'.join(file_.NewContents())
- # Otherwise, file is not in our mock API.
- raise IOError("No such file or directory: '%s'" % filename)
- class MockOutputApi(object):
- """Mock class for the OutputApi class.
- An instance of this class can be passed to presubmit unittests for outputting
- various types of results.
- """
- class PresubmitResult(object):
- def __init__(self, message, items=None, long_text=''):
- self.message = message
- self.items = items
- self.long_text = long_text
- def __repr__(self):
- return self.message
- class PresubmitError(PresubmitResult):
- def __init__(self, message, items=None, long_text=''):
- MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
- self.type = 'error'
- class PresubmitPromptWarning(PresubmitResult):
- def __init__(self, message, items=None, long_text=''):
- MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
- self.type = 'warning'
- class PresubmitNotifyResult(PresubmitResult):
- def __init__(self, message, items=None, long_text=''):
- MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
- self.type = 'notify'
- class PresubmitPromptOrNotify(PresubmitResult):
- def __init__(self, message, items=None, long_text=''):
- MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
- self.type = 'promptOrNotify'
- def __init__(self):
- self.more_cc = []
- def AppendCC(self, more_cc):
- self.more_cc.append(more_cc)
- class MockFile(object):
- """Mock class for the File class.
- This class can be used to form the mock list of changed files in
- MockInputApi for presubmit unittests.
- """
- def __init__(self, local_path, new_contents, old_contents=None, action='A',
- scm_diff=None):
- self._local_path = local_path
- self._new_contents = new_contents
- self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
- self._action = action
- if scm_diff:
- self._scm_diff = scm_diff
- else:
- self._scm_diff = (
- "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" %
- (local_path, len(new_contents)))
- for l in new_contents:
- self._scm_diff += "+%s\n" % l
- self._old_contents = old_contents
- def Action(self):
- return self._action
- def ChangedContents(self):
- return self._changed_contents
- def NewContents(self):
- return self._new_contents
- def LocalPath(self):
- return self._local_path
- def AbsoluteLocalPath(self):
- return self._local_path
- def GenerateScmDiff(self):
- return self._scm_diff
- def OldContents(self):
- return self._old_contents
- def rfind(self, p):
- """os.path.basename is called on MockFile so we need an rfind method."""
- return self._local_path.rfind(p)
- def __getitem__(self, i):
- """os.path.basename is called on MockFile so we need a get method."""
- return self._local_path[i]
- def __len__(self):
- """os.path.basename is called on MockFile so we need a len method."""
- return len(self._local_path)
- def replace(self, altsep, sep):
- """os.path.basename is called on MockFile so we need a replace method."""
- return self._local_path.replace(altsep, sep)
- class MockAffectedFile(MockFile):
- def AbsoluteLocalPath(self):
- return self._local_path
- class MockChange(object):
- """Mock class for Change class.
- This class can be used in presubmit unittests to mock the query of the
- current change.
- """
- def __init__(self, changed_files):
- self._changed_files = changed_files
- self.author_email = None
- self.footers = defaultdict(list)
- def LocalPaths(self):
- return self._changed_files
- def AffectedFiles(self, include_dirs=False, include_deletes=True,
- file_filter=None):
- return self._changed_files
- def GitFootersFromDescription(self):
- return self.footers
|