1*9c5db199SXin Li#!/usr/bin/env vpython 2*9c5db199SXin Li 3*9c5db199SXin Li# [VPYTHON:BEGIN] 4*9c5db199SXin Li# # Third party dependencies. These are only listed because pylint itself needs 5*9c5db199SXin Li# # them. Feel free to add/remove anything here. 6*9c5db199SXin Li# 7*9c5db199SXin Li# wheel: < 8*9c5db199SXin Li# name: "infra/python/wheels/configparser-py2_py3" 9*9c5db199SXin Li# version: "version:3.5.0" 10*9c5db199SXin Li# > 11*9c5db199SXin Li# wheel: < 12*9c5db199SXin Li# name: "infra/python/wheels/futures-py2_py3" 13*9c5db199SXin Li# version: "version:3.1.1" 14*9c5db199SXin Li# > 15*9c5db199SXin Li# wheel: < 16*9c5db199SXin Li# name: "infra/python/wheels/isort-py2_py3" 17*9c5db199SXin Li# version: "version:4.3.4" 18*9c5db199SXin Li# > 19*9c5db199SXin Li# wheel: < 20*9c5db199SXin Li# name: "infra/python/wheels/wrapt/${vpython_platform}" 21*9c5db199SXin Li# version: "version:1.10.11" 22*9c5db199SXin Li# > 23*9c5db199SXin Li# wheel: < 24*9c5db199SXin Li# name: "infra/python/wheels/backports_functools_lru_cache-py2_py3" 25*9c5db199SXin Li# version: "version:1.5" 26*9c5db199SXin Li# > 27*9c5db199SXin Li# wheel: < 28*9c5db199SXin Li# name: "infra/python/wheels/lazy-object-proxy/${vpython_platform}" 29*9c5db199SXin Li# version: "version:1.3.1" 30*9c5db199SXin Li# > 31*9c5db199SXin Li# wheel: < 32*9c5db199SXin Li# name: "infra/python/wheels/singledispatch-py2_py3" 33*9c5db199SXin Li# version: "version:3.4.0.3" 34*9c5db199SXin Li# > 35*9c5db199SXin Li# wheel: < 36*9c5db199SXin Li# name: "infra/python/wheels/enum34-py2" 37*9c5db199SXin Li# version: "version:1.1.6" 38*9c5db199SXin Li# > 39*9c5db199SXin Li# wheel: < 40*9c5db199SXin Li# name: "infra/python/wheels/mccabe-py2_py3" 41*9c5db199SXin Li# version: "version:0.6.1" 42*9c5db199SXin Li# > 43*9c5db199SXin Li# wheel: < 44*9c5db199SXin Li# name: "infra/python/wheels/six-py2_py3" 45*9c5db199SXin Li# version: "version:1.10.0" 46*9c5db199SXin Li# > 47*9c5db199SXin Li# 48*9c5db199SXin Li# # Pylint dependencies. 49*9c5db199SXin Li# 50*9c5db199SXin Li# wheel: < 51*9c5db199SXin Li# name: "infra/python/wheels/astroid-py2_py3" 52*9c5db199SXin Li# version: "version:1.6.6" 53*9c5db199SXin Li# > 54*9c5db199SXin Li# 55*9c5db199SXin Li# wheel: < 56*9c5db199SXin Li# name: "infra/python/wheels/pylint-py2_py3" 57*9c5db199SXin Li# version: "version:1.9.5-45a720817e4de1df2f173c7e4029e176" 58*9c5db199SXin Li# > 59*9c5db199SXin Li# [VPYTHON:END] 60*9c5db199SXin Li 61*9c5db199SXin Li""" 62*9c5db199SXin LiWrapper to patch pylint library functions to suit autotest. 63*9c5db199SXin Li 64*9c5db199SXin LiThis script is invoked as part of the presubmit checks for autotest python 65*9c5db199SXin Lifiles. It runs pylint on a list of files that it obtains either through 66*9c5db199SXin Lithe command line or from an environment variable set in pre-upload.py. 67*9c5db199SXin Li 68*9c5db199SXin LiExample: 69*9c5db199SXin Lirun_pylint.py filename.py 70*9c5db199SXin Li""" 71*9c5db199SXin Li 72*9c5db199SXin Lifrom __future__ import absolute_import 73*9c5db199SXin Lifrom __future__ import division 74*9c5db199SXin Lifrom __future__ import print_function 75*9c5db199SXin Li 76*9c5db199SXin Liimport fnmatch 77*9c5db199SXin Liimport logging 78*9c5db199SXin Liimport os 79*9c5db199SXin Liimport re 80*9c5db199SXin Liimport sys 81*9c5db199SXin Li 82*9c5db199SXin Liimport common 83*9c5db199SXin Lifrom autotest_lib.client.common_lib import autotemp, revision_control 84*9c5db199SXin Li 85*9c5db199SXin Li# Do a basic check to see if pylint is even installed. 86*9c5db199SXin Litry: 87*9c5db199SXin Li import pylint 88*9c5db199SXin Li from pylint import __version__ as pylint_version 89*9c5db199SXin Liexcept ImportError: 90*9c5db199SXin Li print ("Unable to import pylint, it may need to be installed." 91*9c5db199SXin Li " Run 'sudo aptitude install pylint' if you haven't already.") 92*9c5db199SXin Li raise 93*9c5db199SXin Li 94*9c5db199SXin Lipylint_version_parsed = tuple(map(int, pylint_version.split('.'))) 95*9c5db199SXin Li 96*9c5db199SXin Li# some files make pylint blow up, so make sure we ignore them 97*9c5db199SXin LiSKIPLIST = ['/site-packages/*', '/contrib/*', '/frontend/afe/management.py'] 98*9c5db199SXin Li 99*9c5db199SXin Liimport astroid 100*9c5db199SXin Liimport pylint.lint 101*9c5db199SXin Lifrom pylint.checkers import base, imports, variables 102*9c5db199SXin Liimport six 103*9c5db199SXin Lifrom six.moves import filter 104*9c5db199SXin Lifrom six.moves import map 105*9c5db199SXin Lifrom six.moves import zip 106*9c5db199SXin Li 107*9c5db199SXin Li# need to put autotest root dir on sys.path so pylint will be happy 108*9c5db199SXin Liautotest_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 109*9c5db199SXin Lisys.path.insert(0, autotest_root) 110*9c5db199SXin Li 111*9c5db199SXin Li# patch up pylint import checker to handle our importing magic 112*9c5db199SXin LiROOT_MODULE = 'autotest_lib.' 113*9c5db199SXin Li 114*9c5db199SXin Li# A list of modules for pylint to ignore, specifically, these modules 115*9c5db199SXin Li# are imported for their side-effects and are not meant to be used. 116*9c5db199SXin Li_IGNORE_MODULES=['common', 'frontend_test_utils', 117*9c5db199SXin Li 'setup_django_environment', 118*9c5db199SXin Li 'setup_django_lite_environment', 119*9c5db199SXin Li 'setup_django_readonly_environment', 'setup_test_environment',] 120*9c5db199SXin Li 121*9c5db199SXin Li 122*9c5db199SXin Liclass pylint_error(Exception): 123*9c5db199SXin Li """ 124*9c5db199SXin Li Error raised when pylint complains about a file. 125*9c5db199SXin Li """ 126*9c5db199SXin Li 127*9c5db199SXin Li 128*9c5db199SXin Liclass run_pylint_error(pylint_error): 129*9c5db199SXin Li """ 130*9c5db199SXin Li Error raised when an assumption made in this file is violated. 131*9c5db199SXin Li """ 132*9c5db199SXin Li 133*9c5db199SXin Li 134*9c5db199SXin Lidef patch_modname(modname): 135*9c5db199SXin Li """ 136*9c5db199SXin Li Patches modname so we can make sense of autotest_lib modules. 137*9c5db199SXin Li 138*9c5db199SXin Li @param modname: name of a module, contains '.' 139*9c5db199SXin Li @return modified modname string. 140*9c5db199SXin Li """ 141*9c5db199SXin Li if modname.startswith(ROOT_MODULE) or modname.startswith(ROOT_MODULE[:-1]): 142*9c5db199SXin Li modname = modname[len(ROOT_MODULE):] 143*9c5db199SXin Li return modname 144*9c5db199SXin Li 145*9c5db199SXin Li 146*9c5db199SXin Lidef patch_consumed_list(to_consume=None, consumed=None): 147*9c5db199SXin Li """ 148*9c5db199SXin Li Patches the consumed modules list to ignore modules with side effects. 149*9c5db199SXin Li 150*9c5db199SXin Li Autotest relies on importing certain modules solely for their side 151*9c5db199SXin Li effects. Pylint doesn't understand this and flags them as unused, since 152*9c5db199SXin Li they're not referenced anywhere in the code. To overcome this we need 153*9c5db199SXin Li to transplant said modules into the dictionary of modules pylint has 154*9c5db199SXin Li already seen, before pylint checks it. 155*9c5db199SXin Li 156*9c5db199SXin Li @param to_consume: a dictionary of names pylint needs to see referenced. 157*9c5db199SXin Li @param consumed: a dictionary of names that pylint has seen referenced. 158*9c5db199SXin Li """ 159*9c5db199SXin Li ignore_modules = [] 160*9c5db199SXin Li if (to_consume is not None and consumed is not None): 161*9c5db199SXin Li ignore_modules = [module_name for module_name in _IGNORE_MODULES 162*9c5db199SXin Li if module_name in to_consume] 163*9c5db199SXin Li 164*9c5db199SXin Li for module_name in ignore_modules: 165*9c5db199SXin Li consumed[module_name] = to_consume[module_name] 166*9c5db199SXin Li del to_consume[module_name] 167*9c5db199SXin Li 168*9c5db199SXin Li 169*9c5db199SXin Liclass CustomImportsChecker(imports.ImportsChecker): 170*9c5db199SXin Li """Modifies stock imports checker to suit autotest.""" 171*9c5db199SXin Li def visit_importfrom(self, node): 172*9c5db199SXin Li """Patches modnames so pylints understands autotest_lib.""" 173*9c5db199SXin Li node.modname = patch_modname(node.modname) 174*9c5db199SXin Li return super(CustomImportsChecker, self).visit_importfrom(node) 175*9c5db199SXin Li 176*9c5db199SXin Li 177*9c5db199SXin Liclass CustomVariablesChecker(variables.VariablesChecker): 178*9c5db199SXin Li """Modifies stock variables checker to suit autotest.""" 179*9c5db199SXin Li 180*9c5db199SXin Li def visit_module(self, node): 181*9c5db199SXin Li """ 182*9c5db199SXin Li Unflag 'import common'. 183*9c5db199SXin Li 184*9c5db199SXin Li _to_consume eg: [({to reference}, {referenced}, 'scope type')] 185*9c5db199SXin Li Enteries are appended to this list as we drill deeper in scope. 186*9c5db199SXin Li If we ever come across a module to ignore, we immediately move it 187*9c5db199SXin Li to the consumed list. 188*9c5db199SXin Li 189*9c5db199SXin Li @param node: node of the ast we're currently checking. 190*9c5db199SXin Li """ 191*9c5db199SXin Li super(CustomVariablesChecker, self).visit_module(node) 192*9c5db199SXin Li scoped_names = self._to_consume.pop() 193*9c5db199SXin Li # The type of the object has changed in pylint 1.8.2 194*9c5db199SXin Li if pylint_version_parsed >= (1, 8, 2): 195*9c5db199SXin Li patch_consumed_list(scoped_names.to_consume,scoped_names.consumed) 196*9c5db199SXin Li else: 197*9c5db199SXin Li patch_consumed_list(scoped_names[0],scoped_names[1]) 198*9c5db199SXin Li self._to_consume.append(scoped_names) 199*9c5db199SXin Li 200*9c5db199SXin Li def visit_importfrom(self, node): 201*9c5db199SXin Li """Patches modnames so pylints understands autotest_lib.""" 202*9c5db199SXin Li node.modname = patch_modname(node.modname) 203*9c5db199SXin Li return super(CustomVariablesChecker, self).visit_importfrom(node) 204*9c5db199SXin Li 205*9c5db199SXin Li def visit_expr(self, node): 206*9c5db199SXin Li """ 207*9c5db199SXin Li Flag exceptions instantiated but not used. 208*9c5db199SXin Li 209*9c5db199SXin Li https://crbug.com/1005893 210*9c5db199SXin Li """ 211*9c5db199SXin Li if not isinstance(node.value, astroid.Call): 212*9c5db199SXin Li return 213*9c5db199SXin Li func = node.value.func 214*9c5db199SXin Li try: 215*9c5db199SXin Li cls = next(func.infer()) 216*9c5db199SXin Li except astroid.InferenceError: 217*9c5db199SXin Li return 218*9c5db199SXin Li if not isinstance(cls, astroid.ClassDef): 219*9c5db199SXin Li return 220*9c5db199SXin Li if any(x for x in cls.ancestors() if x.name == 'BaseException'): 221*9c5db199SXin Li self.add_message('W0104', node=node, line=node.fromlineno) 222*9c5db199SXin Li 223*9c5db199SXin Li 224*9c5db199SXin Liclass CustomDocStringChecker(base.DocStringChecker): 225*9c5db199SXin Li """Modifies stock docstring checker to suit Autotest doxygen style.""" 226*9c5db199SXin Li 227*9c5db199SXin Li def visit_module(self, node): 228*9c5db199SXin Li """ 229*9c5db199SXin Li Don't visit imported modules when checking for docstrings. 230*9c5db199SXin Li 231*9c5db199SXin Li @param node: the node we're visiting. 232*9c5db199SXin Li """ 233*9c5db199SXin Li pass 234*9c5db199SXin Li 235*9c5db199SXin Li 236*9c5db199SXin Li def visit_functiondef(self, node): 237*9c5db199SXin Li """ 238*9c5db199SXin Li Don't request docstrings for commonly overridden autotest functions. 239*9c5db199SXin Li 240*9c5db199SXin Li @param node: node of the ast we're currently checking. 241*9c5db199SXin Li """ 242*9c5db199SXin Li 243*9c5db199SXin Li # Even plain functions will have a parent, which is the 244*9c5db199SXin Li # module they're in, and a frame, which is the context 245*9c5db199SXin Li # of said module; They need not however, always have 246*9c5db199SXin Li # ancestors. 247*9c5db199SXin Li if (node.name in ('run_once', 'initialize', 'cleanup') and 248*9c5db199SXin Li hasattr(node.parent.frame(), 'ancestors') and 249*9c5db199SXin Li any(ancestor.name == 'base_test' for ancestor in 250*9c5db199SXin Li node.parent.frame().ancestors())): 251*9c5db199SXin Li return 252*9c5db199SXin Li 253*9c5db199SXin Li if _is_test_case_method(node): 254*9c5db199SXin Li return 255*9c5db199SXin Li 256*9c5db199SXin Li super(CustomDocStringChecker, self).visit_functiondef(node) 257*9c5db199SXin Li 258*9c5db199SXin Li 259*9c5db199SXin Li @staticmethod 260*9c5db199SXin Li def _should_skip_arg(arg): 261*9c5db199SXin Li """ 262*9c5db199SXin Li @return: True if the argument given by arg is allowlisted, and does 263*9c5db199SXin Li not require a "@param" docstring. 264*9c5db199SXin Li """ 265*9c5db199SXin Li return arg in ('self', 'cls', 'args', 'kwargs', 'dargs') 266*9c5db199SXin Li 267*9c5db199SXin Libase.DocStringChecker = CustomDocStringChecker 268*9c5db199SXin Liimports.ImportsChecker = CustomImportsChecker 269*9c5db199SXin Livariables.VariablesChecker = CustomVariablesChecker 270*9c5db199SXin Li 271*9c5db199SXin Li 272*9c5db199SXin Lidef batch_check_files(file_paths, base_opts): 273*9c5db199SXin Li """ 274*9c5db199SXin Li Run pylint on a list of files so we get consolidated errors. 275*9c5db199SXin Li 276*9c5db199SXin Li @param file_paths: a list of file paths. 277*9c5db199SXin Li @param base_opts: a list of pylint config options. 278*9c5db199SXin Li 279*9c5db199SXin Li @returns pylint return code 280*9c5db199SXin Li 281*9c5db199SXin Li @raises: pylint_error if pylint finds problems with a file 282*9c5db199SXin Li in this commit. 283*9c5db199SXin Li """ 284*9c5db199SXin Li if not file_paths: 285*9c5db199SXin Li return 0 286*9c5db199SXin Li 287*9c5db199SXin Li pylint_runner = pylint.lint.Run(list(base_opts) + list(file_paths), 288*9c5db199SXin Li exit=False) 289*9c5db199SXin Li return pylint_runner.linter.msg_status 290*9c5db199SXin Li 291*9c5db199SXin Li 292*9c5db199SXin Lidef should_check_file(file_path): 293*9c5db199SXin Li """ 294*9c5db199SXin Li Don't check skiplisted or non .py files. 295*9c5db199SXin Li 296*9c5db199SXin Li @param file_path: abs path of file to check. 297*9c5db199SXin Li @return: True if this file is a non-skiplisted python file. 298*9c5db199SXin Li """ 299*9c5db199SXin Li file_path = os.path.abspath(file_path) 300*9c5db199SXin Li if file_path.endswith('.py'): 301*9c5db199SXin Li return all(not fnmatch.fnmatch(file_path, '*' + pattern) 302*9c5db199SXin Li for pattern in SKIPLIST) 303*9c5db199SXin Li return False 304*9c5db199SXin Li 305*9c5db199SXin Li 306*9c5db199SXin Lidef check_file(file_path, base_opts): 307*9c5db199SXin Li """ 308*9c5db199SXin Li Invokes pylint on files after confirming that they're not block listed. 309*9c5db199SXin Li 310*9c5db199SXin Li @param base_opts: pylint base options. 311*9c5db199SXin Li @param file_path: path to the file we need to run pylint on. 312*9c5db199SXin Li 313*9c5db199SXin Li @returns pylint return code 314*9c5db199SXin Li """ 315*9c5db199SXin Li if not isinstance(file_path, six.string_types): 316*9c5db199SXin Li raise TypeError('expected a string as filepath, got %s'% 317*9c5db199SXin Li type(file_path)) 318*9c5db199SXin Li 319*9c5db199SXin Li if should_check_file(file_path): 320*9c5db199SXin Li pylint_runner = pylint.lint.Run(base_opts + [file_path], exit=False) 321*9c5db199SXin Li 322*9c5db199SXin Li return pylint_runner.linter.msg_status 323*9c5db199SXin Li 324*9c5db199SXin Li return 0 325*9c5db199SXin Li 326*9c5db199SXin Li 327*9c5db199SXin Lidef visit(arg, dirname, filenames): 328*9c5db199SXin Li """ 329*9c5db199SXin Li Visit function invoked in check_dir. 330*9c5db199SXin Li 331*9c5db199SXin Li @param arg: arg from os.walk.path 332*9c5db199SXin Li @param dirname: dir from os.walk.path 333*9c5db199SXin Li @param filenames: files in dir from os.walk.path 334*9c5db199SXin Li """ 335*9c5db199SXin Li for filename in filenames: 336*9c5db199SXin Li arg.append(os.path.join(dirname, filename)) 337*9c5db199SXin Li 338*9c5db199SXin Li 339*9c5db199SXin Lidef check_dir(dir_path, base_opts): 340*9c5db199SXin Li """ 341*9c5db199SXin Li Calls visit on files in dir_path. 342*9c5db199SXin Li 343*9c5db199SXin Li @param base_opts: pylint base options. 344*9c5db199SXin Li @param dir_path: path to directory. 345*9c5db199SXin Li 346*9c5db199SXin Li @returns pylint return code 347*9c5db199SXin Li """ 348*9c5db199SXin Li files = [] 349*9c5db199SXin Li 350*9c5db199SXin Li os.walk(dir_path, visit, files) 351*9c5db199SXin Li 352*9c5db199SXin Li return batch_check_files(files, base_opts) 353*9c5db199SXin Li 354*9c5db199SXin Li 355*9c5db199SXin Lidef extend_baseopts(base_opts, new_opt): 356*9c5db199SXin Li """ 357*9c5db199SXin Li Replaces an argument in base_opts with a cmd line argument. 358*9c5db199SXin Li 359*9c5db199SXin Li @param base_opts: original pylint_base_opts. 360*9c5db199SXin Li @param new_opt: new cmd line option. 361*9c5db199SXin Li """ 362*9c5db199SXin Li for args in base_opts: 363*9c5db199SXin Li if new_opt in args: 364*9c5db199SXin Li base_opts.remove(args) 365*9c5db199SXin Li base_opts.append(new_opt) 366*9c5db199SXin Li 367*9c5db199SXin Li 368*9c5db199SXin Lidef get_cmdline_options(args_list, pylint_base_opts, rcfile): 369*9c5db199SXin Li """ 370*9c5db199SXin Li Parses args_list and extends pylint_base_opts. 371*9c5db199SXin Li 372*9c5db199SXin Li Command line arguments might include options mixed with files. 373*9c5db199SXin Li Go through this list and filter out the options, if the options are 374*9c5db199SXin Li specified in the pylintrc file we cannot replace them and the file 375*9c5db199SXin Li needs to be edited. If the options are already a part of 376*9c5db199SXin Li pylint_base_opts we replace them, and if not we append to 377*9c5db199SXin Li pylint_base_opts. 378*9c5db199SXin Li 379*9c5db199SXin Li @param args_list: list of files/pylint args passed in through argv. 380*9c5db199SXin Li @param pylint_base_opts: default pylint options. 381*9c5db199SXin Li @param rcfile: text from pylint_rc. 382*9c5db199SXin Li """ 383*9c5db199SXin Li for args in args_list: 384*9c5db199SXin Li if args.startswith('--'): 385*9c5db199SXin Li opt_name = args[2:].split('=')[0] 386*9c5db199SXin Li extend_baseopts(pylint_base_opts, args) 387*9c5db199SXin Li args_list.remove(args) 388*9c5db199SXin Li 389*9c5db199SXin Li 390*9c5db199SXin Lidef git_show_to_temp_file(commit, original_file, new_temp_file): 391*9c5db199SXin Li """ 392*9c5db199SXin Li 'Git shows' the file in original_file to a tmp file with 393*9c5db199SXin Li the name new_temp_file. We need to preserve the filename 394*9c5db199SXin Li as it gets reflected in pylints error report. 395*9c5db199SXin Li 396*9c5db199SXin Li @param commit: commit hash of the commit we're running repo upload on. 397*9c5db199SXin Li @param original_file: the path to the original file we'd like to run 398*9c5db199SXin Li 'git show' on. 399*9c5db199SXin Li @param new_temp_file: new_temp_file is the path to a temp file we write the 400*9c5db199SXin Li output of 'git show' into. 401*9c5db199SXin Li """ 402*9c5db199SXin Li git_repo = revision_control.GitRepo(common.autotest_dir, None, None, 403*9c5db199SXin Li common.autotest_dir) 404*9c5db199SXin Li 405*9c5db199SXin Li with open(new_temp_file, 'w') as f: 406*9c5db199SXin Li output = git_repo.gitcmd('show --no-ext-diff %s:%s' 407*9c5db199SXin Li % (commit, original_file), 408*9c5db199SXin Li ignore_status=False).stdout 409*9c5db199SXin Li f.write(output) 410*9c5db199SXin Li 411*9c5db199SXin Li 412*9c5db199SXin Lidef check_committed_files(work_tree_files, commit, pylint_base_opts): 413*9c5db199SXin Li """ 414*9c5db199SXin Li Get a list of files corresponding to the commit hash. 415*9c5db199SXin Li 416*9c5db199SXin Li The contents of a file in the git work tree can differ from the contents 417*9c5db199SXin Li of a file in the commit we mean to upload. To work around this we run 418*9c5db199SXin Li pylint on a temp file into which we've 'git show'n the committed version 419*9c5db199SXin Li of each file. 420*9c5db199SXin Li 421*9c5db199SXin Li @param work_tree_files: list of files in this commit specified by their 422*9c5db199SXin Li absolute path. 423*9c5db199SXin Li @param commit: hash of the commit this upload applies to. 424*9c5db199SXin Li @param pylint_base_opts: a list of pylint config options. 425*9c5db199SXin Li 426*9c5db199SXin Li @returns pylint return code 427*9c5db199SXin Li """ 428*9c5db199SXin Li files_to_check = filter(should_check_file, work_tree_files) 429*9c5db199SXin Li 430*9c5db199SXin Li # Map the absolute path of each file so it's relative to the autotest repo. 431*9c5db199SXin Li # All files that are a part of this commit should have an abs path within 432*9c5db199SXin Li # the autotest repo, so this regex should never fail. 433*9c5db199SXin Li work_tree_files = [re.search(r'%s/(.*)' % common.autotest_dir, f).group(1) 434*9c5db199SXin Li for f in files_to_check] 435*9c5db199SXin Li 436*9c5db199SXin Li tempdir = None 437*9c5db199SXin Li try: 438*9c5db199SXin Li tempdir = autotemp.tempdir() 439*9c5db199SXin Li temp_files = [os.path.join(tempdir.name, file_path.split('/')[-1:][0]) 440*9c5db199SXin Li for file_path in work_tree_files] 441*9c5db199SXin Li 442*9c5db199SXin Li for file_tuple in zip(work_tree_files, temp_files): 443*9c5db199SXin Li git_show_to_temp_file(commit, *file_tuple) 444*9c5db199SXin Li # Only check if we successfully git showed all files in the commit. 445*9c5db199SXin Li return batch_check_files(temp_files, pylint_base_opts) 446*9c5db199SXin Li finally: 447*9c5db199SXin Li if tempdir: 448*9c5db199SXin Li tempdir.clean() 449*9c5db199SXin Li 450*9c5db199SXin Li 451*9c5db199SXin Lidef _is_test_case_method(node): 452*9c5db199SXin Li """Determine if the given function node is a method of a TestCase. 453*9c5db199SXin Li 454*9c5db199SXin Li We simply check for 'TestCase' being one of the parent classes in the mro of 455*9c5db199SXin Li the containing class. 456*9c5db199SXin Li 457*9c5db199SXin Li @params node: A function node. 458*9c5db199SXin Li """ 459*9c5db199SXin Li if not hasattr(node.parent.frame(), 'ancestors'): 460*9c5db199SXin Li return False 461*9c5db199SXin Li 462*9c5db199SXin Li parent_class_names = {x.name for x in node.parent.frame().ancestors()} 463*9c5db199SXin Li return 'TestCase' in parent_class_names 464*9c5db199SXin Li 465*9c5db199SXin Li 466*9c5db199SXin Lidef main(): 467*9c5db199SXin Li """Main function checks each file in a commit for pylint violations.""" 468*9c5db199SXin Li 469*9c5db199SXin Li # For now all error/warning/refactor/convention exceptions except those in 470*9c5db199SXin Li # the enable string are disabled. 471*9c5db199SXin Li # W0611: All imported modules (except common) need to be used. 472*9c5db199SXin Li # W1201: Logging methods should take the form 473*9c5db199SXin Li # logging.<loggingmethod>(format_string, format_args...); and not 474*9c5db199SXin Li # logging.<loggingmethod>(format_string % (format_args...)) 475*9c5db199SXin Li # C0111: Docstring needed. Also checks @param for each arg. 476*9c5db199SXin Li # C0112: Non-empty Docstring needed. 477*9c5db199SXin Li # Ideally we would like to enable as much as we can, but if we did so at 478*9c5db199SXin Li # this stage anyone who makes a tiny change to a file will be tasked with 479*9c5db199SXin Li # cleaning all the lint in it. See chromium-os:37364. 480*9c5db199SXin Li 481*9c5db199SXin Li # Note: 482*9c5db199SXin Li # 1. There are three major sources of E1101/E1103/E1120 false positives: 483*9c5db199SXin Li # * common_lib.enum.Enum objects 484*9c5db199SXin Li # * DB model objects (scheduler models are the worst, but Django models 485*9c5db199SXin Li # also generate some errors) 486*9c5db199SXin Li # 2. Docstrings are optional on private methods, and any methods that begin 487*9c5db199SXin Li # with either 'set_' or 'get_'. 488*9c5db199SXin Li pylint_rc = os.path.join(os.path.dirname(os.path.abspath(__file__)), 489*9c5db199SXin Li 'pylintrc') 490*9c5db199SXin Li 491*9c5db199SXin Li no_docstring_rgx = r'((_.*)|(set_.*)|(get_.*))' 492*9c5db199SXin Li if pylint_version_parsed >= (0, 21): 493*9c5db199SXin Li pylint_base_opts = ['--rcfile=%s' % pylint_rc, 494*9c5db199SXin Li '--reports=no', 495*9c5db199SXin Li '--disable=W,R,E,C,F', 496*9c5db199SXin Li '--enable=W0104,W0611,W1201,C0111,C0112,E0602,' 497*9c5db199SXin Li 'W0601,E0633', 498*9c5db199SXin Li '--no-docstring-rgx=%s' % no_docstring_rgx,] 499*9c5db199SXin Li else: 500*9c5db199SXin Li all_failures = 'error,warning,refactor,convention' 501*9c5db199SXin Li pylint_base_opts = ['--disable-msg-cat=%s' % all_failures, 502*9c5db199SXin Li '--reports=no', 503*9c5db199SXin Li '--include-ids=y', 504*9c5db199SXin Li '--ignore-docstrings=n', 505*9c5db199SXin Li '--no-docstring-rgx=%s' % no_docstring_rgx,] 506*9c5db199SXin Li 507*9c5db199SXin Li # run_pylint can be invoked directly with command line arguments, 508*9c5db199SXin Li # or through a presubmit hook which uses the arguments in pylintrc. In the 509*9c5db199SXin Li # latter case no command line arguments are passed. If it is invoked 510*9c5db199SXin Li # directly without any arguments, it should check all files in the cwd. 511*9c5db199SXin Li args_list = sys.argv[1:] 512*9c5db199SXin Li if args_list: 513*9c5db199SXin Li get_cmdline_options(args_list, 514*9c5db199SXin Li pylint_base_opts, 515*9c5db199SXin Li open(pylint_rc).read()) 516*9c5db199SXin Li return batch_check_files(args_list, pylint_base_opts) 517*9c5db199SXin Li elif os.environ.get('PRESUBMIT_FILES') is not None: 518*9c5db199SXin Li return check_committed_files( 519*9c5db199SXin Li os.environ.get('PRESUBMIT_FILES').split('\n'), 520*9c5db199SXin Li os.environ.get('PRESUBMIT_COMMIT'), 521*9c5db199SXin Li pylint_base_opts) 522*9c5db199SXin Li else: 523*9c5db199SXin Li return check_dir('.', pylint_base_opts) 524*9c5db199SXin Li 525*9c5db199SXin Li 526*9c5db199SXin Liif __name__ == '__main__': 527*9c5db199SXin Li try: 528*9c5db199SXin Li ret = main() 529*9c5db199SXin Li 530*9c5db199SXin Li sys.exit(ret) 531*9c5db199SXin Li except pylint_error as e: 532*9c5db199SXin Li logging.error(e) 533*9c5db199SXin Li sys.exit(1) 534