qemu/tests/qemu-iotests/linters.py
<<
>>
Prefs
   1# Copyright (C) 2020 Red Hat, Inc.
   2#
   3# This program is free software; you can redistribute it and/or modify
   4# it under the terms of the GNU General Public License as published by
   5# the Free Software Foundation; either version 2 of the License, or
   6# (at your option) any later version.
   7#
   8# This program is distributed in the hope that it will be useful,
   9# but WITHOUT ANY WARRANTY; without even the implied warranty of
  10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11# GNU General Public License for more details.
  12#
  13# You should have received a copy of the GNU General Public License
  14# along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15
  16import os
  17import re
  18import subprocess
  19import sys
  20from typing import List, Mapping, Optional
  21
  22
  23# TODO: Empty this list!
  24SKIP_FILES = (
  25    '030', '040', '041', '044', '045', '055', '056', '057', '065', '093',
  26    '096', '118', '124', '132', '136', '139', '147', '148', '149',
  27    '151', '152', '155', '163', '165', '194', '196', '202',
  28    '203', '205', '206', '207', '208', '210', '211', '212', '213', '216',
  29    '218', '219', '224', '228', '234', '235', '236', '237', '238',
  30    '240', '242', '245', '246', '248', '255', '256', '257', '258', '260',
  31    '262', '264', '266', '274', '277', '280', '281', '295', '296', '298',
  32    '299', '302', '303', '304', '307',
  33    'nbd-fault-injector.py', 'qcow2.py', 'qcow2_format.py', 'qed.py'
  34)
  35
  36
  37def is_python_file(filename):
  38    if not os.path.isfile(filename):
  39        return False
  40
  41    if filename.endswith('.py'):
  42        return True
  43
  44    with open(filename, encoding='utf-8') as f:
  45        try:
  46            first_line = f.readline()
  47            return re.match('^#!.*python', first_line) is not None
  48        except UnicodeDecodeError:  # Ignore binary files
  49            return False
  50
  51
  52def get_test_files() -> List[str]:
  53    named_tests = [f'tests/{entry}' for entry in os.listdir('tests')]
  54    check_tests = set(os.listdir('.') + named_tests) - set(SKIP_FILES)
  55    return list(filter(is_python_file, check_tests))
  56
  57
  58def run_linter(
  59        tool: str,
  60        args: List[str],
  61        env: Optional[Mapping[str, str]] = None,
  62        suppress_output: bool = False,
  63) -> None:
  64    """
  65    Run a python-based linting tool.
  66
  67    :param suppress_output: If True, suppress all stdout/stderr output.
  68    :raise CalledProcessError: If the linter process exits with failure.
  69    """
  70    subprocess.run(
  71        ('python3', '-m', tool, *args),
  72        env=env,
  73        check=True,
  74        stdout=subprocess.PIPE if suppress_output else None,
  75        stderr=subprocess.STDOUT if suppress_output else None,
  76        universal_newlines=True,
  77    )
  78
  79
  80def main() -> None:
  81    """
  82    Used by the Python CI system as an entry point to run these linters.
  83    """
  84    def show_usage() -> None:
  85        print(f"Usage: {sys.argv[0]} < --mypy | --pylint >", file=sys.stderr)
  86        sys.exit(1)
  87
  88    if len(sys.argv) != 2:
  89        show_usage()
  90
  91    files = get_test_files()
  92
  93    if sys.argv[1] == '--pylint':
  94        run_linter('pylint', files)
  95    elif sys.argv[1] == '--mypy':
  96        # mypy bug #9852; disable incremental checking as a workaround.
  97        args = ['--no-incremental'] + files
  98        run_linter('mypy', args)
  99    else:
 100        print(f"Unrecognized argument: '{sys.argv[1]}'", file=sys.stderr)
 101        show_usage()
 102
 103
 104if __name__ == '__main__':
 105    main()
 106