1# Copyright 2015 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Top-level presubmit script for src/components/cronet. 6 7See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts 8for more details about the presubmit API built into depot_tools. 9""" 10 11import os 12 13def _PyLintChecks(input_api, output_api): 14 pylint_checks = input_api.canned_checks.GetPylint(input_api, output_api, 15 extra_paths_list=_GetPathsToPrepend(input_api), pylintrc='pylintrc', 16 version='2.7') 17 return input_api.RunTests(pylint_checks) 18 19 20def _GetPathsToPrepend(input_api): 21 current_dir = input_api.PresubmitLocalPath() 22 chromium_src_dir = input_api.os_path.join(current_dir, '..', '..') 23 return [ 24 input_api.os_path.join(chromium_src_dir, 'components'), 25 input_api.os_path.join(chromium_src_dir, 'tools', 'perf'), 26 input_api.os_path.join(chromium_src_dir, 'build', 'android'), 27 input_api.os_path.join(chromium_src_dir, 'build', 'android', 'gyp'), 28 input_api.os_path.join(chromium_src_dir, 29 'mojo', 'public', 'tools', 'bindings', 'pylib'), 30 input_api.os_path.join(chromium_src_dir, 'net', 'tools', 'net_docs'), 31 input_api.os_path.join(chromium_src_dir, 'tools'), 32 input_api.os_path.join(chromium_src_dir, 'third_party'), 33 input_api.os_path.join(chromium_src_dir, 34 'third_party', 'catapult', 'telemetry'), 35 input_api.os_path.join(chromium_src_dir, 36 'third_party', 'catapult', 'devil'), 37 input_api.os_path.join(chromium_src_dir, 38 'third_party', 'catapult', 'common', 'py_utils'), 39 ] 40 41 42def _PackageChecks(input_api, output_api): 43 """Verify API classes are in org.chromium.net package, and implementation 44 classes are not in org.chromium.net package.""" 45 api_packages = ['org.chromium.net', 'org.chromium.net.apihelpers'] 46 api_packages_regex = '(' + '|'.join(api_packages) + ')' 47 api_file_pattern = input_api.re.compile( 48 r'^components/cronet/android/api/.*\.(java|template)$') 49 impl_file_pattern = input_api.re.compile( 50 r'^components/cronet/android/java/.*\.(java|template)$') 51 invalid_api_package_pattern = input_api.re.compile( 52 r'^package (?!' + api_packages_regex + ';)') 53 invalid_impl_package_pattern = input_api.re.compile( 54 r'^package ' + api_packages_regex + ';') 55 56 source_filter = lambda path: input_api.FilterSourceFile(path, 57 files_to_check=[r'^components/cronet/android/.*\.(java|template)$']) 58 59 problems = [] 60 for f in input_api.AffectedSourceFiles(source_filter): 61 local_path = f.LocalPath() 62 for line_number, line in f.ChangedContents(): 63 if (api_file_pattern.search(local_path)): 64 if (invalid_api_package_pattern.search(line)): 65 problems.append( 66 '%s:%d\n %s' % (local_path, line_number, line.strip())) 67 elif (impl_file_pattern.search(local_path)): 68 if (invalid_impl_package_pattern.search(line)): 69 problems.append( 70 '%s:%d\n %s' % (local_path, line_number, line.strip())) 71 72 if problems: 73 return [output_api.PresubmitError( 74 'API classes must be in org.chromium.net package, and implementation\n' 75 'classes must not be in org.chromium.net package.', 76 problems)] 77 return [] 78 79 80def _RunToolsUnittests(input_api, output_api): 81 return input_api.canned_checks.RunUnitTestsInDirectory( 82 input_api, output_api, 83 '.', 84 [ r'^tools_unittest\.py$']) 85 86 87def _ChangeAffectsCronetTools(change): 88 """ Returns |true| if the change may affect Cronet tools. """ 89 90 for path in change.LocalPaths(): 91 if path.startswith(os.path.join('components', 'cronet', 'tools')): 92 return True 93 return False 94 95GOOD_CHANGE_ID_TXT = 'good_change_id' 96BAD_CHANGE_ID_TXT = 'bad_change_id' 97BUG_TXT = 'bugs' 98COMMENT_TXT = 'comment' 99 100def _GetBreakagesFilePathIfChanged(change): 101 """ Returns |true| if the change may affect the breakages file. """ 102 103 for file in change.AffectedFiles(include_deletes=False): 104 if file.LocalPath().endswith('breakages.json'): 105 return file 106 return None 107 108def _IsValidChangeId(input_api, change_id): 109 """ Returns |true| if the change_id is not valid. 110 111 Validity means starting with the letter I followed by 40 hex chars. 112 """ 113 if (input_api.re.fullmatch(r'^I[0-9a-fA-F]{40}$', change_id) 114 and not input_api.re.fullmatch(r'^I00*$', change_id)): 115 return True 116 return False 117 118def _GetInvalidChangeIdText(input_api, breakage, key): 119 if key not in breakage: 120 return '' 121 if not _IsValidChangeId(input_api, breakage[key]): 122 return '\t - entry has invalid %s: %s\n' % (key, breakage[key]) 123 return '' 124 125def _GetMissingKeyText(breakage, key): 126 if key in breakage: 127 return '' 128 return '\t - entry is missing the "%s" key\n' % key 129 130def _GetGoodWithoutBadChangeIdText(breakage): 131 if GOOD_CHANGE_ID_TXT in breakage and BAD_CHANGE_ID_TXT not in breakage: 132 return '\t - entry cannot have %s without %s\n' % \ 133 (GOOD_CHANGE_ID_TXT, BAD_CHANGE_ID_TXT) 134 return '' 135 136def _GetUnknownKeyText(breakage): 137 unknown_keys = [] 138 for key in breakage: 139 if (key.startswith('_') or # ignore comments 140 key == BAD_CHANGE_ID_TXT or 141 key == GOOD_CHANGE_ID_TXT or 142 key == BUG_TXT or 143 key == COMMENT_TXT): 144 continue 145 unknown_keys.append(key) 146 147 if unknown_keys: 148 return '\t - entry contains unknown key(s): %s. Expected either %s, %s, ' \ 149 '%s or %s.\n' % \ 150 (unknown_keys, BAD_CHANGE_ID_TXT, GOOD_CHANGE_ID_TXT, BUG_TXT, 151 COMMENT_TXT) 152 return '' 153 154def _BreakageFileChecks(input_api, output_api, file): 155 """Verify that the change_ids listed in the breakages file are valid.""" 156 breakages = input_api.json.loads(input_api.ReadFile(file))["breakages"] 157 problems = [] 158 for i, breakage in enumerate(breakages): 159 problem = "" 160 # ensures that the entries, where existing are valid and that there are no 161 # unknown keys. 162 problem += _GetInvalidChangeIdText(input_api, breakage, BAD_CHANGE_ID_TXT) 163 problem += _GetInvalidChangeIdText(input_api, breakage, GOOD_CHANGE_ID_TXT) 164 problem += _GetGoodWithoutBadChangeIdText(breakage) 165 problem += _GetMissingKeyText(breakage, BUG_TXT) 166 problem += _GetUnknownKeyText(breakage) 167 168 if problem: 169 problems.append('Breakage Entry %d: \n%s' % (i, problem)) 170 171 if problems: 172 return [output_api.PresubmitError( 173 'The breakages.json file contains invalid entries.\n' 174 'Please cross-check the entries.', 175 problems)] 176 return [] 177 178def CheckChangeOnUpload(input_api, output_api): 179 results = [] 180 results.extend(_PyLintChecks(input_api, output_api)) 181 results.extend(_PackageChecks(input_api, output_api)) 182 if _ChangeAffectsCronetTools(input_api.change): 183 results.extend(_RunToolsUnittests(input_api, output_api)) 184 breakages_file = _GetBreakagesFilePathIfChanged(input_api.change) 185 if breakages_file: 186 results.extend(_BreakageFileChecks(input_api, output_api, breakages_file)) 187 return results 188 189 190def CheckChangeOnCommit(input_api, output_api): 191 return _RunToolsUnittests(input_api, output_api) 192