1*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2023 Google Inc. 2*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 3*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file. 4*c8dee2aaSAndroid Build Coastguard Worker 5*c8dee2aaSAndroid Build Coastguard Worker# This is a copy of PRESUBMIT_test_mocks.py from the Chromium project. 6*c8dee2aaSAndroid Build Coastguard Worker 7*c8dee2aaSAndroid Build Coastguard Workerfrom collections import defaultdict 8*c8dee2aaSAndroid Build Coastguard Workerimport fnmatch 9*c8dee2aaSAndroid Build Coastguard Workerimport json 10*c8dee2aaSAndroid Build Coastguard Workerimport os 11*c8dee2aaSAndroid Build Coastguard Workerimport re 12*c8dee2aaSAndroid Build Coastguard Workerimport subprocess 13*c8dee2aaSAndroid Build Coastguard Workerimport sys 14*c8dee2aaSAndroid Build Coastguard Worker 15*c8dee2aaSAndroid Build Coastguard Worker 16*c8dee2aaSAndroid Build Coastguard Workerdef _ReportErrorFileAndLine(filename, line_num, dummy_line): 17*c8dee2aaSAndroid Build Coastguard Worker """Default error formatter for _FindNewViolationsOfRule.""" 18*c8dee2aaSAndroid Build Coastguard Worker return '%s:%s' % (filename, line_num) 19*c8dee2aaSAndroid Build Coastguard Worker 20*c8dee2aaSAndroid Build Coastguard Worker 21*c8dee2aaSAndroid Build Coastguard Workerclass MockCannedChecks(object): 22*c8dee2aaSAndroid Build Coastguard Worker def _FindNewViolationsOfRule(self, callable_rule, input_api, 23*c8dee2aaSAndroid Build Coastguard Worker source_file_filter=None, 24*c8dee2aaSAndroid Build Coastguard Worker error_formatter=_ReportErrorFileAndLine): 25*c8dee2aaSAndroid Build Coastguard Worker """Find all newly introduced violations of a per-line rule (a callable). 26*c8dee2aaSAndroid Build Coastguard Worker 27*c8dee2aaSAndroid Build Coastguard Worker Arguments: 28*c8dee2aaSAndroid Build Coastguard Worker callable_rule: a callable taking a file extension and line of input and 29*c8dee2aaSAndroid Build Coastguard Worker returning True if the rule is satisfied and False if there was a 30*c8dee2aaSAndroid Build Coastguard Worker problem. 31*c8dee2aaSAndroid Build Coastguard Worker input_api: object to enumerate the affected files. 32*c8dee2aaSAndroid Build Coastguard Worker source_file_filter: a filter to be passed to the input api. 33*c8dee2aaSAndroid Build Coastguard Worker error_formatter: a callable taking (filename, line_number, line) and 34*c8dee2aaSAndroid Build Coastguard Worker returning a formatted error string. 35*c8dee2aaSAndroid Build Coastguard Worker 36*c8dee2aaSAndroid Build Coastguard Worker Returns: 37*c8dee2aaSAndroid Build Coastguard Worker A list of the newly-introduced violations reported by the rule. 38*c8dee2aaSAndroid Build Coastguard Worker """ 39*c8dee2aaSAndroid Build Coastguard Worker errors = [] 40*c8dee2aaSAndroid Build Coastguard Worker for f in input_api.AffectedFiles(include_deletes=False, 41*c8dee2aaSAndroid Build Coastguard Worker file_filter=source_file_filter): 42*c8dee2aaSAndroid Build Coastguard Worker # For speed, we do two passes, checking first the full file. Shelling out 43*c8dee2aaSAndroid Build Coastguard Worker # to the SCM to determine the changed region can be quite expensive on 44*c8dee2aaSAndroid Build Coastguard Worker # Win32. Assuming that most files will be kept problem-free, we can 45*c8dee2aaSAndroid Build Coastguard Worker # skip the SCM operations most of the time. 46*c8dee2aaSAndroid Build Coastguard Worker extension = str(f.LocalPath()).rsplit('.', 1)[-1] 47*c8dee2aaSAndroid Build Coastguard Worker if all(callable_rule(extension, line) for line in f.NewContents()): 48*c8dee2aaSAndroid Build Coastguard Worker # No violation found in full text: can skip considering diff. 49*c8dee2aaSAndroid Build Coastguard Worker continue 50*c8dee2aaSAndroid Build Coastguard Worker 51*c8dee2aaSAndroid Build Coastguard Worker for line_num, line in f.ChangedContents(): 52*c8dee2aaSAndroid Build Coastguard Worker if not callable_rule(extension, line): 53*c8dee2aaSAndroid Build Coastguard Worker errors.append(error_formatter( 54*c8dee2aaSAndroid Build Coastguard Worker f.LocalPath(), line_num, line)) 55*c8dee2aaSAndroid Build Coastguard Worker 56*c8dee2aaSAndroid Build Coastguard Worker return errors 57*c8dee2aaSAndroid Build Coastguard Worker 58*c8dee2aaSAndroid Build Coastguard Worker 59*c8dee2aaSAndroid Build Coastguard Workerclass MockInputApi(object): 60*c8dee2aaSAndroid Build Coastguard Worker """Mock class for the InputApi class. 61*c8dee2aaSAndroid Build Coastguard Worker 62*c8dee2aaSAndroid Build Coastguard Worker This class can be used for unittests for presubmit by initializing the files 63*c8dee2aaSAndroid Build Coastguard Worker attribute as the list of changed files. 64*c8dee2aaSAndroid Build Coastguard Worker """ 65*c8dee2aaSAndroid Build Coastguard Worker 66*c8dee2aaSAndroid Build Coastguard Worker DEFAULT_FILES_TO_SKIP = () 67*c8dee2aaSAndroid Build Coastguard Worker 68*c8dee2aaSAndroid Build Coastguard Worker def __init__(self): 69*c8dee2aaSAndroid Build Coastguard Worker self.canned_checks = MockCannedChecks() 70*c8dee2aaSAndroid Build Coastguard Worker self.fnmatch = fnmatch 71*c8dee2aaSAndroid Build Coastguard Worker self.json = json 72*c8dee2aaSAndroid Build Coastguard Worker self.re = re 73*c8dee2aaSAndroid Build Coastguard Worker self.os_path = os.path 74*c8dee2aaSAndroid Build Coastguard Worker self.platform = sys.platform 75*c8dee2aaSAndroid Build Coastguard Worker self.python_executable = sys.executable 76*c8dee2aaSAndroid Build Coastguard Worker self.python3_executable = sys.executable 77*c8dee2aaSAndroid Build Coastguard Worker self.platform = sys.platform 78*c8dee2aaSAndroid Build Coastguard Worker self.subprocess = subprocess 79*c8dee2aaSAndroid Build Coastguard Worker self.sys = sys 80*c8dee2aaSAndroid Build Coastguard Worker self.files = [] 81*c8dee2aaSAndroid Build Coastguard Worker self.is_committing = False 82*c8dee2aaSAndroid Build Coastguard Worker self.change = MockChange([]) 83*c8dee2aaSAndroid Build Coastguard Worker self.presubmit_local_path = os.path.dirname(__file__) 84*c8dee2aaSAndroid Build Coastguard Worker self.is_windows = sys.platform == 'win32' 85*c8dee2aaSAndroid Build Coastguard Worker self.no_diffs = False 86*c8dee2aaSAndroid Build Coastguard Worker # Although this makes assumptions about command line arguments used by test 87*c8dee2aaSAndroid Build Coastguard Worker # scripts that create mocks, it is a convenient way to set up the verbosity 88*c8dee2aaSAndroid Build Coastguard Worker # via the input api. 89*c8dee2aaSAndroid Build Coastguard Worker self.verbose = '--verbose' in sys.argv 90*c8dee2aaSAndroid Build Coastguard Worker 91*c8dee2aaSAndroid Build Coastguard Worker def CreateMockFileInPath(self, f_list): 92*c8dee2aaSAndroid Build Coastguard Worker self.os_path.exists = lambda x: x in f_list 93*c8dee2aaSAndroid Build Coastguard Worker 94*c8dee2aaSAndroid Build Coastguard Worker def AffectedFiles(self, file_filter=None, include_deletes=True): 95*c8dee2aaSAndroid Build Coastguard Worker for file in self.files: 96*c8dee2aaSAndroid Build Coastguard Worker if file_filter and not file_filter(file): 97*c8dee2aaSAndroid Build Coastguard Worker continue 98*c8dee2aaSAndroid Build Coastguard Worker if not include_deletes and file.Action() == 'D': 99*c8dee2aaSAndroid Build Coastguard Worker continue 100*c8dee2aaSAndroid Build Coastguard Worker yield file 101*c8dee2aaSAndroid Build Coastguard Worker 102*c8dee2aaSAndroid Build Coastguard Worker def RightHandSideLines(self, source_file_filter=None): 103*c8dee2aaSAndroid Build Coastguard Worker affected_files = self.AffectedSourceFiles(source_file_filter) 104*c8dee2aaSAndroid Build Coastguard Worker for af in affected_files: 105*c8dee2aaSAndroid Build Coastguard Worker lines = af.ChangedContents() 106*c8dee2aaSAndroid Build Coastguard Worker for line in lines: 107*c8dee2aaSAndroid Build Coastguard Worker yield (af, line[0], line[1]) 108*c8dee2aaSAndroid Build Coastguard Worker 109*c8dee2aaSAndroid Build Coastguard Worker def AffectedSourceFiles(self, file_filter=None): 110*c8dee2aaSAndroid Build Coastguard Worker return self.AffectedFiles(file_filter=file_filter) 111*c8dee2aaSAndroid Build Coastguard Worker 112*c8dee2aaSAndroid Build Coastguard Worker def FilterSourceFile(self, file, 113*c8dee2aaSAndroid Build Coastguard Worker files_to_check=(), files_to_skip=()): 114*c8dee2aaSAndroid Build Coastguard Worker local_path = file.LocalPath() 115*c8dee2aaSAndroid Build Coastguard Worker found_in_files_to_check = not files_to_check 116*c8dee2aaSAndroid Build Coastguard Worker if files_to_check: 117*c8dee2aaSAndroid Build Coastguard Worker if type(files_to_check) is str: 118*c8dee2aaSAndroid Build Coastguard Worker raise TypeError( 119*c8dee2aaSAndroid Build Coastguard Worker 'files_to_check should be an iterable of strings') 120*c8dee2aaSAndroid Build Coastguard Worker for pattern in files_to_check: 121*c8dee2aaSAndroid Build Coastguard Worker compiled_pattern = re.compile(pattern) 122*c8dee2aaSAndroid Build Coastguard Worker if compiled_pattern.match(local_path): 123*c8dee2aaSAndroid Build Coastguard Worker found_in_files_to_check = True 124*c8dee2aaSAndroid Build Coastguard Worker break 125*c8dee2aaSAndroid Build Coastguard Worker if files_to_skip: 126*c8dee2aaSAndroid Build Coastguard Worker if type(files_to_skip) is str: 127*c8dee2aaSAndroid Build Coastguard Worker raise TypeError( 128*c8dee2aaSAndroid Build Coastguard Worker 'files_to_skip should be an iterable of strings') 129*c8dee2aaSAndroid Build Coastguard Worker for pattern in files_to_skip: 130*c8dee2aaSAndroid Build Coastguard Worker compiled_pattern = re.compile(pattern) 131*c8dee2aaSAndroid Build Coastguard Worker if compiled_pattern.match(local_path): 132*c8dee2aaSAndroid Build Coastguard Worker return False 133*c8dee2aaSAndroid Build Coastguard Worker return found_in_files_to_check 134*c8dee2aaSAndroid Build Coastguard Worker 135*c8dee2aaSAndroid Build Coastguard Worker def LocalPaths(self): 136*c8dee2aaSAndroid Build Coastguard Worker return [file.LocalPath() for file in self.files] 137*c8dee2aaSAndroid Build Coastguard Worker 138*c8dee2aaSAndroid Build Coastguard Worker def PresubmitLocalPath(self): 139*c8dee2aaSAndroid Build Coastguard Worker return self.presubmit_local_path 140*c8dee2aaSAndroid Build Coastguard Worker 141*c8dee2aaSAndroid Build Coastguard Worker def ReadFile(self, filename, mode='r'): 142*c8dee2aaSAndroid Build Coastguard Worker if hasattr(filename, 'AbsoluteLocalPath'): 143*c8dee2aaSAndroid Build Coastguard Worker filename = filename.AbsoluteLocalPath() 144*c8dee2aaSAndroid Build Coastguard Worker for file_ in self.files: 145*c8dee2aaSAndroid Build Coastguard Worker if file_.LocalPath() == filename: 146*c8dee2aaSAndroid Build Coastguard Worker return '\n'.join(file_.NewContents()) 147*c8dee2aaSAndroid Build Coastguard Worker # Otherwise, file is not in our mock API. 148*c8dee2aaSAndroid Build Coastguard Worker raise IOError("No such file or directory: '%s'" % filename) 149*c8dee2aaSAndroid Build Coastguard Worker 150*c8dee2aaSAndroid Build Coastguard Worker 151*c8dee2aaSAndroid Build Coastguard Workerclass MockOutputApi(object): 152*c8dee2aaSAndroid Build Coastguard Worker """Mock class for the OutputApi class. 153*c8dee2aaSAndroid Build Coastguard Worker 154*c8dee2aaSAndroid Build Coastguard Worker An instance of this class can be passed to presubmit unittests for outputting 155*c8dee2aaSAndroid Build Coastguard Worker various types of results. 156*c8dee2aaSAndroid Build Coastguard Worker """ 157*c8dee2aaSAndroid Build Coastguard Worker 158*c8dee2aaSAndroid Build Coastguard Worker class PresubmitResult(object): 159*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, message, items=None, long_text=''): 160*c8dee2aaSAndroid Build Coastguard Worker self.message = message 161*c8dee2aaSAndroid Build Coastguard Worker self.items = items 162*c8dee2aaSAndroid Build Coastguard Worker self.long_text = long_text 163*c8dee2aaSAndroid Build Coastguard Worker 164*c8dee2aaSAndroid Build Coastguard Worker def __repr__(self): 165*c8dee2aaSAndroid Build Coastguard Worker return self.message 166*c8dee2aaSAndroid Build Coastguard Worker 167*c8dee2aaSAndroid Build Coastguard Worker class PresubmitError(PresubmitResult): 168*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, message, items=None, long_text=''): 169*c8dee2aaSAndroid Build Coastguard Worker MockOutputApi.PresubmitResult.__init__( 170*c8dee2aaSAndroid Build Coastguard Worker self, message, items, long_text) 171*c8dee2aaSAndroid Build Coastguard Worker self.type = 'error' 172*c8dee2aaSAndroid Build Coastguard Worker 173*c8dee2aaSAndroid Build Coastguard Worker class PresubmitPromptWarning(PresubmitResult): 174*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, message, items=None, long_text=''): 175*c8dee2aaSAndroid Build Coastguard Worker MockOutputApi.PresubmitResult.__init__( 176*c8dee2aaSAndroid Build Coastguard Worker self, message, items, long_text) 177*c8dee2aaSAndroid Build Coastguard Worker self.type = 'warning' 178*c8dee2aaSAndroid Build Coastguard Worker 179*c8dee2aaSAndroid Build Coastguard Worker class PresubmitNotifyResult(PresubmitResult): 180*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, message, items=None, long_text=''): 181*c8dee2aaSAndroid Build Coastguard Worker MockOutputApi.PresubmitResult.__init__( 182*c8dee2aaSAndroid Build Coastguard Worker self, message, items, long_text) 183*c8dee2aaSAndroid Build Coastguard Worker self.type = 'notify' 184*c8dee2aaSAndroid Build Coastguard Worker 185*c8dee2aaSAndroid Build Coastguard Worker class PresubmitPromptOrNotify(PresubmitResult): 186*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, message, items=None, long_text=''): 187*c8dee2aaSAndroid Build Coastguard Worker MockOutputApi.PresubmitResult.__init__( 188*c8dee2aaSAndroid Build Coastguard Worker self, message, items, long_text) 189*c8dee2aaSAndroid Build Coastguard Worker self.type = 'promptOrNotify' 190*c8dee2aaSAndroid Build Coastguard Worker 191*c8dee2aaSAndroid Build Coastguard Worker def __init__(self): 192*c8dee2aaSAndroid Build Coastguard Worker self.more_cc = [] 193*c8dee2aaSAndroid Build Coastguard Worker 194*c8dee2aaSAndroid Build Coastguard Worker def AppendCC(self, more_cc): 195*c8dee2aaSAndroid Build Coastguard Worker self.more_cc.append(more_cc) 196*c8dee2aaSAndroid Build Coastguard Worker 197*c8dee2aaSAndroid Build Coastguard Worker 198*c8dee2aaSAndroid Build Coastguard Workerclass MockFile(object): 199*c8dee2aaSAndroid Build Coastguard Worker """Mock class for the File class. 200*c8dee2aaSAndroid Build Coastguard Worker 201*c8dee2aaSAndroid Build Coastguard Worker This class can be used to form the mock list of changed files in 202*c8dee2aaSAndroid Build Coastguard Worker MockInputApi for presubmit unittests. 203*c8dee2aaSAndroid Build Coastguard Worker """ 204*c8dee2aaSAndroid Build Coastguard Worker 205*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, local_path, new_contents, old_contents=None, action='A', 206*c8dee2aaSAndroid Build Coastguard Worker scm_diff=None): 207*c8dee2aaSAndroid Build Coastguard Worker self._local_path = local_path 208*c8dee2aaSAndroid Build Coastguard Worker self._new_contents = new_contents 209*c8dee2aaSAndroid Build Coastguard Worker self._changed_contents = [(i + 1, l) 210*c8dee2aaSAndroid Build Coastguard Worker for i, l in enumerate(new_contents)] 211*c8dee2aaSAndroid Build Coastguard Worker self._action = action 212*c8dee2aaSAndroid Build Coastguard Worker if scm_diff: 213*c8dee2aaSAndroid Build Coastguard Worker self._scm_diff = scm_diff 214*c8dee2aaSAndroid Build Coastguard Worker else: 215*c8dee2aaSAndroid Build Coastguard Worker self._scm_diff = ( 216*c8dee2aaSAndroid Build Coastguard Worker "--- /dev/null\n+++ %s\n@@ -0,0 +1,%d @@\n" % 217*c8dee2aaSAndroid Build Coastguard Worker (local_path, len(new_contents))) 218*c8dee2aaSAndroid Build Coastguard Worker for l in new_contents: 219*c8dee2aaSAndroid Build Coastguard Worker self._scm_diff += "+%s\n" % l 220*c8dee2aaSAndroid Build Coastguard Worker self._old_contents = old_contents 221*c8dee2aaSAndroid Build Coastguard Worker 222*c8dee2aaSAndroid Build Coastguard Worker def Action(self): 223*c8dee2aaSAndroid Build Coastguard Worker return self._action 224*c8dee2aaSAndroid Build Coastguard Worker 225*c8dee2aaSAndroid Build Coastguard Worker def ChangedContents(self): 226*c8dee2aaSAndroid Build Coastguard Worker return self._changed_contents 227*c8dee2aaSAndroid Build Coastguard Worker 228*c8dee2aaSAndroid Build Coastguard Worker def NewContents(self, flush_cache=False): 229*c8dee2aaSAndroid Build Coastguard Worker return self._new_contents 230*c8dee2aaSAndroid Build Coastguard Worker 231*c8dee2aaSAndroid Build Coastguard Worker def LocalPath(self): 232*c8dee2aaSAndroid Build Coastguard Worker return self._local_path 233*c8dee2aaSAndroid Build Coastguard Worker 234*c8dee2aaSAndroid Build Coastguard Worker def AbsoluteLocalPath(self): 235*c8dee2aaSAndroid Build Coastguard Worker return self._local_path 236*c8dee2aaSAndroid Build Coastguard Worker 237*c8dee2aaSAndroid Build Coastguard Worker def GenerateScmDiff(self): 238*c8dee2aaSAndroid Build Coastguard Worker return self._scm_diff 239*c8dee2aaSAndroid Build Coastguard Worker 240*c8dee2aaSAndroid Build Coastguard Worker def OldContents(self): 241*c8dee2aaSAndroid Build Coastguard Worker return self._old_contents 242*c8dee2aaSAndroid Build Coastguard Worker 243*c8dee2aaSAndroid Build Coastguard Worker def rfind(self, p): 244*c8dee2aaSAndroid Build Coastguard Worker """os.path.basename is called on MockFile so we need an rfind method.""" 245*c8dee2aaSAndroid Build Coastguard Worker return self._local_path.rfind(p) 246*c8dee2aaSAndroid Build Coastguard Worker 247*c8dee2aaSAndroid Build Coastguard Worker def __getitem__(self, i): 248*c8dee2aaSAndroid Build Coastguard Worker """os.path.basename is called on MockFile so we need a get method.""" 249*c8dee2aaSAndroid Build Coastguard Worker return self._local_path[i] 250*c8dee2aaSAndroid Build Coastguard Worker 251*c8dee2aaSAndroid Build Coastguard Worker def __len__(self): 252*c8dee2aaSAndroid Build Coastguard Worker """os.path.basename is called on MockFile so we need a len method.""" 253*c8dee2aaSAndroid Build Coastguard Worker return len(self._local_path) 254*c8dee2aaSAndroid Build Coastguard Worker 255*c8dee2aaSAndroid Build Coastguard Worker def replace(self, altsep, sep): 256*c8dee2aaSAndroid Build Coastguard Worker """os.path.basename is called on MockFile so we need a replace method.""" 257*c8dee2aaSAndroid Build Coastguard Worker return self._local_path.replace(altsep, sep) 258*c8dee2aaSAndroid Build Coastguard Worker 259*c8dee2aaSAndroid Build Coastguard Worker 260*c8dee2aaSAndroid Build Coastguard Workerclass MockAffectedFile(MockFile): 261*c8dee2aaSAndroid Build Coastguard Worker def AbsoluteLocalPath(self): 262*c8dee2aaSAndroid Build Coastguard Worker return self._local_path 263*c8dee2aaSAndroid Build Coastguard Worker 264*c8dee2aaSAndroid Build Coastguard Worker 265*c8dee2aaSAndroid Build Coastguard Workerclass MockChange(object): 266*c8dee2aaSAndroid Build Coastguard Worker """Mock class for Change class. 267*c8dee2aaSAndroid Build Coastguard Worker 268*c8dee2aaSAndroid Build Coastguard Worker This class can be used in presubmit unittests to mock the query of the 269*c8dee2aaSAndroid Build Coastguard Worker current change. 270*c8dee2aaSAndroid Build Coastguard Worker """ 271*c8dee2aaSAndroid Build Coastguard Worker 272*c8dee2aaSAndroid Build Coastguard Worker def __init__(self, changed_files): 273*c8dee2aaSAndroid Build Coastguard Worker self._changed_files = changed_files 274*c8dee2aaSAndroid Build Coastguard Worker self.author_email = None 275*c8dee2aaSAndroid Build Coastguard Worker self.footers = defaultdict(list) 276*c8dee2aaSAndroid Build Coastguard Worker 277*c8dee2aaSAndroid Build Coastguard Worker def LocalPaths(self): 278*c8dee2aaSAndroid Build Coastguard Worker return self._changed_files 279*c8dee2aaSAndroid Build Coastguard Worker 280*c8dee2aaSAndroid Build Coastguard Worker def AffectedFiles(self, include_dirs=False, include_deletes=True, 281*c8dee2aaSAndroid Build Coastguard Worker file_filter=None): 282*c8dee2aaSAndroid Build Coastguard Worker return self._changed_files 283*c8dee2aaSAndroid Build Coastguard Worker 284*c8dee2aaSAndroid Build Coastguard Worker def GitFootersFromDescription(self): 285*c8dee2aaSAndroid Build Coastguard Worker return self.footers 286