You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			263 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			263 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
# Copyright 2021 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 logging
 | 
						|
import os
 | 
						|
import re
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
 | 
						|
from presubmit_canned_checks import _ReportErrorFileAndLine
 | 
						|
 | 
						|
 | 
						|
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.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.logging = logging.getLogger('PRESUBMIT')
 | 
						|
 | 
						|
  def CreateMockFileInPath(self, f_list):
 | 
						|
    self.os_path.exists = lambda x: x in f_list
 | 
						|
 | 
						|
  def AffectedFiles(self, file_filter=None, include_deletes=False):
 | 
						|
    for file in self.files: # pylint: disable=redefined-builtin
 | 
						|
      if file_filter and not file_filter(file):
 | 
						|
        continue
 | 
						|
      if not include_deletes and file.Action() == 'D':
 | 
						|
        continue
 | 
						|
      yield file
 | 
						|
 | 
						|
  def AffectedSourceFiles(self, file_filter=None):
 | 
						|
    return self.AffectedFiles(file_filter=file_filter)
 | 
						|
 | 
						|
  def FilterSourceFile(self, file, # pylint: disable=redefined-builtin
 | 
						|
                       files_to_check=(), files_to_skip=()):
 | 
						|
    local_path = file.LocalPath()
 | 
						|
    found_in_files_to_check = not files_to_check
 | 
						|
    if files_to_check:
 | 
						|
      if isinstance(files_to_check, 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.search(local_path):
 | 
						|
          found_in_files_to_check = True
 | 
						|
          break
 | 
						|
    if files_to_skip:
 | 
						|
      if isinstance(files_to_skip, 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.search(local_path):
 | 
						|
          return False
 | 
						|
    return found_in_files_to_check
 | 
						|
 | 
						|
  def LocalPaths(self):
 | 
						|
    return [file.LocalPath() for file in self.files] # pylint: disable=redefined-builtin
 | 
						|
 | 
						|
  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 outputing
 | 
						|
  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.extend(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, description=''):
 | 
						|
    self._changed_files = changed_files
 | 
						|
    self.footers = defaultdict(list)
 | 
						|
    self._description = description
 | 
						|
 | 
						|
  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
 | 
						|
 | 
						|
  def DescriptionText(self):
 | 
						|
    return self._description
 |