pax_global_header00006660000000000000000000000064137521637440014526gustar00rootroot0000000000000052 comment=9cd8e51c0dcc51e1454b82c8fb0de0e1de16562c pytest-pylint-0.18.0/000077500000000000000000000000001375216374400144615ustar00rootroot00000000000000pytest-pylint-0.18.0/.coveragerc000066400000000000000000000001001375216374400165710ustar00rootroot00000000000000[run] source = . parallel = True omit = .tox/* setup.py pytest-pylint-0.18.0/.gitignore000066400000000000000000000014341375216374400164530ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache .pytest_cache/ nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # PyEnv .python-version # Virtualenv .venv # Pycharm .idea # Vscode .vscodepytest-pylint-0.18.0/.travis.yml000066400000000000000000000003651375216374400165760ustar00rootroot00000000000000language: python dist: xenial matrix: include: - python: 3.9 - python: 3.8 - python: 3.7 - python: 3.6 install: - "pip install tox-travis tox==3.14.6 coveralls" - "pip install -e ." script: tox after_success: "coveralls" pytest-pylint-0.18.0/DEVELOPMENT.rst000066400000000000000000000057251375216374400170060ustar00rootroot00000000000000pytest pylint ------------- How it works ============ Helpers for running pylint with py.test and have configurable rule types (i.e. Convention, Warn, and Error) fail the build. You can also specify a pylintrc file. How it works We have a thin plugin wrapper that is installed through setup.py hooks as `pylint`. This wrapper uses pytest_addoption and pytest_configure to decide to configure and register the real plugin PylintPlygin Once it is registered in `pytest_configure`, the hooks already executed by previous plugins will run. For instance, in case PylintPlugin had `pytest_addoption` implemented, which runs before `pytest_configure` in the hook cycle, it would be executed once PylintPlugin got registered. PylintPlugin uses the `pytest_collect_file` hook which is called wih every file available in the test target dir. This hook collects all the file pylint should run on, in this case files with extension ".py". `pytest_collect_file` hook returns a collection of Node, or None. In py.test context, Node being a base class that defines py.test Collection Tree. A Node can be a subclass of Collector, which has children, or an Item, which is a leaf node. A practical example would be, a Python test file (Collector), can have multiple test functions (multiple Items) For this plugin, the relatioship of File to Item is one to one, one file represents one pylint result. From that, there are two important classes: PyLintFile, and PyLintItem. PyLintFile represents a python file, extension ".py", that was collected based on target directory as mentioned previously. PyLintItem represents one file which pylint was ran or will run. Back to PylintPlugin, `pytest_collection_finish` hook will run after the collection phase where pylint will be ran on the collected files. Based on the ProgrammaticReporter, the result is stored in a dictionary with the file relative path of the file being the key, and a list of errors related to the file. All PylintFile returned during `pytest_collect_file`, returns an one element list of PyLintItem. The Item implements runtest method which will get the pylint messages per file and expose to the user. Development Environment ======================= Suggestion 1 ~~~~~~~~~~~~ Use `pyenv `_, and install all the versions supported by the plugin. Double-check on `tox.ini `_. .. code-block:: shell pyenv install 3.5.9 pyenv install 3.6.10 pyenv install 3.7.7 pyenv install 3.8.2 Set the installed versions as global, that will allow tox to find all of them. .. code-block:: shell pyenv global 3.5.9 3.6.10 3.7.7 3.8.2 Create virtualenv, install dependencies, run tests, and tox: .. code-block:: shell python3.8 -m venv .pytest_pylint source .pytest_pylint/bin/activate pip install --upgrade setuptools pip tox python setup.py install python setup.py test tox The development environment is complete.pytest-pylint-0.18.0/LICENSE000066400000000000000000000020661375216374400154720ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Carson Gee Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pytest-pylint-0.18.0/MANIFEST.in000066400000000000000000000002071375216374400162160ustar00rootroot00000000000000include README.rst include LICENSE include pytest_pylint graft pytest_pylint/tests global-exclude __pycache__ global-exclude *.py[cod] pytest-pylint-0.18.0/README.rst000066400000000000000000000112221375216374400161460ustar00rootroot00000000000000pytest pylint ------------- .. image:: https://img.shields.io/travis/carsongee/pytest-pylint.svg :target: https://travis-ci.org/carsongee/pytest-pylint .. image:: https://img.shields.io/coveralls/carsongee/pytest-pylint.svg :target: https://coveralls.io/r/carsongee/pytest-pylint .. image:: https://img.shields.io/pypi/v/pytest-pylint.svg :target: https://pypi.python.org/pypi/pytest-pylint .. image:: https://anaconda.org/conda-forge/pytest-pylint/badges/version.svg :target: https://anaconda.org/conda-forge/pytest-pylint .. image:: https://anaconda.org/conda-forge/pytest-pylint/badges/downloads.svg :target: https://anaconda.org/conda-forge/pytest-pylint .. image:: https://img.shields.io/pypi/l/pytest-pylint.svg :target: https://pypi.python.org/pypi/pytest-pylint Run pylint with pytest and have configurable rule types (i.e. Convention, Warn, and Error) fail the build. You can also specify a pylintrc file. Sample Usage ============ .. code-block:: shell py.test --pylint would be the most simple usage and would run pylint for all error messages. .. code-block:: shell py.test --pylint --pylint-rcfile=/my/pyrc --pylint-error-types=EF --pylint-jobs=4 This would use the pylintrc file at /my/pyrc, only error on pylint Errors and Failures, and use 4 cores for running pylint. You can restrict your test run to only perform pylint checks and not any other tests by typing: .. code-block:: shell py.test --pylint -m pylint Acknowledgements ================ This code is heavily based on `pytest-flakes `__ Development =========== If you want to help development, there is `overview documentation `_ Releases ======== 0.18.0 ~~~~~~ - Added support for creating missing folders when using ``--pylint-output-file`` - Now when pylint's ``ignore_patterns`` is blank, we don't ignore all files - Added cache invalidation when your pylintrc changes - Verified support for latest pytest and Python 3.9 - Corrected badly named nodes (duplicated path) thanks to `yanqd0 `__ - Added tests to source distribution thanks to `sbraz `__ 0.17.0 ~~~~~~ - Added support for latest pylint API >=2.5.1 0.16.1 ~~~~~~ - Corrected documentation and correctly pinned dependencies properly 0.16.0 ~~~~~~ - Switched to new ``from_parent`` API and added development documentation `dineshtrivedi `_ - Added support for toml based configuration of pylint thanks to `michael-k `_ 0.15.1 ~~~~~~ - Made `--no-pylint` functional again 0.15.0 ~~~~~~ - Added support for Python 3.8 thanks to `michael-k `_ - Implemented option to output Pylint results to a reports file thanks to `jose-lpa `_ - Refactored into simpler plugin structure 0.14.1 ~~~~~~ - Corrected pytest-pylint to properly support ``-p no:cacheprovider`` thanks to `yanqd0 `__ 0.14.0 ~~~~~~ - Added support for Pylint's ignore-patterns for regex based ignores thanks to `khokhlin `__ - pytest-pylint now caches successful pylint checks to speedup test reruns when files haven't changed thanks to `yanqd0 `__ 0.13.0 ~~~~~~ - Python 3.7 compatibility verified - Ignore paths no longer match partial names thanks to `heoga `__ 0.12.3 ~~~~~~ - `jamur2 `__ corrected issue where file paths where not being output properly on lint failures. 0.12.2 ~~~~~~ - Resolved issue where failing files weren't reported thanks to reports from `skirpichev `__ and `jamur2 `__ 0.12.1 ~~~~~~ - Corrected a bug preventing this plugin from working with py.test >= 3.7.0. 0.12.0 ~~~~~~ - `jwkvam `__ added progress output during linting. 0.11.0 ~~~~~~ - Added option ``--no-pylint`` to override ``--pylint`` for cases when it's turned on by default. 0.10.0 ~~~~~~ - `jwkvam `__ provided support for pylint 2.0 0.9.0 ~~~~~ - `noisecapella `__ added an option to run pylint with multiple processes 0.8.0 ~~~~~ - `bdrung `__ corrected inconsitent returns in a function - Dropped Python 3.3 support 0.7.1 ~~~~~ - Corrected path issue reported by `Kargathia `_ 0.7.0 ~~~~~ - Linting is performed before tests which enables code duplication checks to work along with a performance boost, thanks to @heoga pytest-pylint-0.18.0/pylintrc000066400000000000000000000000461375216374400162500ustar00rootroot00000000000000[TYPECHECK] ignored-classes = pytest pytest-pylint-0.18.0/pytest_pylint/000077500000000000000000000000001375216374400174105ustar00rootroot00000000000000pytest-pylint-0.18.0/pytest_pylint/__init__.py000066400000000000000000000000001375216374400215070ustar00rootroot00000000000000pytest-pylint-0.18.0/pytest_pylint/plugin.py000066400000000000000000000312751375216374400212700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ pytest plugins. Both pylint wrapper and PylintPlugin """ from collections import defaultdict from configparser import ConfigParser, NoSectionError, NoOptionError from os import makedirs from os.path import getmtime, exists, join, dirname from pylint import lint from pylint.config import PYLINTRC import pytest import toml from .pylint_util import ProgrammaticReporter from .util import get_rel_path, PyLintException, should_include_file HISTKEY = 'pylint/mtimes' PYLINT_CONFIG_CACHE_KEY = 'pylintrc' FILL_CHARS = 80 MARKER = 'pylint' def pytest_addoption(parser): """Add all our command line options""" group = parser.getgroup("pylint") group.addoption( "--pylint", action="store_true", default=False, help="run pylint on all" ) group.addoption( "--no-pylint", action="store_true", default=False, help="disable running pylint " ) group.addoption( '--pylint-rcfile', default=None, help='Location of RC file if not pylintrc' ) group.addoption( '--pylint-error-types', default='CRWEF', help='The types of pylint errors to consider failures by letter' ', default is all of them (CRWEF).' ) group.addoption( '--pylint-jobs', default=None, help='Specify number of processes to use for pylint' ) group.addoption( '--pylint-output-file', default=None, help='Path to a file where Pylint report will be printed to.' ) group.addoption( '--pylint-ignore', default=None, help='Files/directories that will be ignored' ) group.addoption( '--pylint-ignore-patterns', default=None, help='Files/directories patterns that will be ignored' ) def pytest_configure(config): """ Add plugin class. :param _pytest.config.Config config: pytest config object """ config.addinivalue_line( 'markers', "{0}: Tests which run pylint.".format(MARKER) ) if config.option.pylint and not config.option.no_pylint: pylint_plugin = PylintPlugin(config) config.pluginmanager.register(pylint_plugin) class PylintPlugin: """ The core plugin for pylint """ # pylint: disable=too-many-instance-attributes def __init__(self, config): if hasattr(config, 'cache'): self.mtimes = config.cache.get(HISTKEY, {}) else: self.mtimes = {} self.pylint_files = set() self.pylint_messages = defaultdict(list) self.pylint_config = None self.pylintrc_file = None self.pylint_ignore = [] self.pylint_ignore_patterns = [] self.pylint_msg_template = None def pytest_configure(self, config): """Configure pytest after it is already enabled""" # Find pylintrc to check ignore list pylintrc_file = config.option.pylint_rcfile or PYLINTRC if pylintrc_file and not exists(pylintrc_file): # The directory of pytest.ini got a chance pylintrc_file = join(dirname(str(config.inifile)), pylintrc_file) # Try getting ignores from pylintrc since we use pytest # collection methods and not pylint's internal mechanism if pylintrc_file and exists(pylintrc_file): self.pylintrc_file = pylintrc_file # Check if pylint config has a different filename or date # and invalidate the cache if it has changed. pylint_mtime = getmtime(pylintrc_file) cache_key = PYLINT_CONFIG_CACHE_KEY + pylintrc_file cache_value = self.mtimes.get(cache_key) if cache_value is None or cache_value < pylint_mtime: self.mtimes = {} self.mtimes[cache_key] = pylint_mtime if pylintrc_file.endswith(".toml"): self._load_pyproject_toml(pylintrc_file) else: self._load_rc_file(pylintrc_file) # Command line arguments take presedence over rcfile ones if set if config.option.pylint_ignore is not None: self.pylint_ignore = config.option.pylint_ignore.split(',') if config.option.pylint_ignore_patterns is not None: self.pylint_ignore_patterns = ( config.option.pylint_ignore_patterns.split(',') ) def _load_rc_file(self, pylintrc_file): self.pylint_config = ConfigParser() self.pylint_config.read(pylintrc_file) try: ignore_string = self.pylint_config.get('MASTER', 'ignore') if ignore_string: self.pylint_ignore = ignore_string.split(',') except (NoSectionError, NoOptionError): pass try: ignore_patterns = self.pylint_config.get( 'MASTER', 'ignore-patterns' ) if ignore_patterns: self.pylint_ignore_patterns = ignore_patterns.split(',') except (NoSectionError, NoOptionError): pass try: self.pylint_msg_template = self.pylint_config.get( 'REPORTS', 'msg-template' ) except (NoSectionError, NoOptionError): pass def _load_pyproject_toml(self, pylintrc_file): with open(pylintrc_file, "r") as f_p: try: content = toml.load(f_p) except (TypeError, toml.decoder.TomlDecodeError): return try: self.pylint_config = content["tool"]["pylint"] except KeyError: return master_section = {} reports_section = {} for key, value in self.pylint_config.items(): if not master_section and key.lower() == "master": master_section = value elif not reports_section and key.lower() == "reports": reports_section = value ignore = master_section.get("ignore") if ignore: self.pylint_ignore = ( ignore.split(",") if isinstance(ignore, str) else ignore ) self.pylint_ignore_patterns = ( master_section.get("ignore-patterns") or [] ) self.pylint_msg_template = reports_section.get("msg-template") def pytest_sessionfinish(self, session): """ Save file mtimes to pytest cache. :param _pytest.main.Session session: the pytest session object """ if hasattr(session.config, 'cache'): session.config.cache.set(HISTKEY, self.mtimes) def pytest_collect_file(self, path, parent): """Collect files on which pylint should run""" if path.ext != ".py": return None rel_path = get_rel_path(path.strpath, parent.session.fspath.strpath) if should_include_file( rel_path, self.pylint_ignore, self.pylint_ignore_patterns ): item = PylintFile.from_parent( parent, fspath=path, plugin=self ) else: return None # Check the cache if we should run it if not item.should_skip: self.pylint_files.add(rel_path) return item def pytest_collection_finish(self, session): """Lint collected files""" if not self.pylint_files: return jobs = session.config.option.pylint_jobs reporter = ProgrammaticReporter() # Build argument list for pylint args_list = list(self.pylint_files) if self.pylintrc_file: args_list.append('--rcfile={0}'.format( self.pylintrc_file )) if jobs is not None: args_list.append('-j') args_list.append(jobs) # These allow the user to override the pylint configuration's # ignore list if self.pylint_ignore: args_list.append( '--ignore={0}'.format(','.join(self.pylint_ignore)) ) if self.pylint_ignore_patterns: args_list.append( '--ignore-patterns={0}'.format( ','.join(self.pylint_ignore_patterns) ) ) print('-' * FILL_CHARS) print('Linting files') # Run pylint over the collected files. # Pylint has changed APIs, but we support both # pylint: disable=unexpected-keyword-arg try: # pylint >= 2.5.1 API result = lint.Run(args_list, reporter=reporter, exit=False) except TypeError: # pylint < 2.5.1 API result = lint.Run(args_list, reporter=reporter, do_exit=False) except RuntimeError: return messages = result.linter.reporter.data # Stores the messages in a dictionary for lookup in tests. for message in messages: self.pylint_messages[message.path].append(message) print('-' * FILL_CHARS) class PylintFile(pytest.File): """File that pylint will run on.""" rel_path = None # : str plugin = None # : PylintPlugin should_skip = False # : bool mtime = None # : float @classmethod def from_parent(cls, parent, *, fspath, plugin): # We add the ``plugin`` kwarg to get plugin level information so the # signature differs # pylint: disable=arguments-differ _self = getattr(super(), 'from_parent', cls)(parent, fspath=fspath) _self.plugin = plugin _self.rel_path = get_rel_path( fspath.strpath, parent.session.fspath.strpath ) _self.mtime = fspath.mtime() prev_mtime = _self.plugin.mtimes.get(_self.rel_path, 0) _self.should_skip = (prev_mtime == _self.mtime) return _self def collect(self): """Create a PyLintItem for the File.""" yield PyLintItem.from_parent( parent=self, name='PYLINT' ) class PyLintItem(pytest.Item): """pylint test running class.""" parent = None # : PylintFile plugin = None # : PylintPlugin def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.add_marker(MARKER) self.plugin = self.parent.plugin msg_format = self.plugin.pylint_msg_template if msg_format is None: self._msg_format = '{C}:{line:3d},{column:2d}: {msg} ({symbol})' else: self._msg_format = msg_format @classmethod def from_parent(cls, parent, **kw): return getattr(super(), 'from_parent', cls)(parent, **kw) def setup(self): """Mark unchanged files as SKIPPED.""" if self.parent.should_skip: pytest.skip("file(s) previously passed pylint checks") def runtest(self): """Check the pylint messages to see if any errors were reported.""" pylint_output_file = self.config.option.pylint_output_file def _loop_errors(writer): reported_errors = [] for error in self.plugin.pylint_messages.get( self.parent.rel_path, [] ): if error.C in self.config.option.pylint_error_types: reported_errors.append( error.format(self._msg_format) ) writer( '{error_path}:{error_line}: [{error_msg_id}' '({error_symbol}), {error_obj}] ' '{error_msg}\n'.format( error_path=error.path, error_line=error.line, error_msg_id=error.msg_id, error_symbol=error.symbol, error_obj=error.obj, error_msg=error.msg, ) ) return reported_errors if pylint_output_file: output_dir = dirname(pylint_output_file) if output_dir: makedirs(output_dir, exist_ok=True) with open(pylint_output_file, 'a') as _file: reported_errors = _loop_errors(writer=_file.write) else: reported_errors = _loop_errors(writer=lambda *args, **kwargs: None) if reported_errors: raise PyLintException('\n'.join(reported_errors)) # Update the cache if the item passed pylint. self.plugin.mtimes[self.parent.rel_path] = self.parent.mtime def repr_failure(self, excinfo, style=None): """Handle any test failures by checking that they were ours.""" # pylint: disable=arguments-differ if excinfo.errisinstance(PyLintException): return excinfo.value.args[0] return super().repr_failure(excinfo) def reportinfo(self): """Generate our test report""" return self.fspath, None, "[pylint] {0}".format(self.parent.rel_path) pytest-pylint-0.18.0/pytest_pylint/pylint_util.py000066400000000000000000000021601375216374400223350ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Pylint reporter classes.""" import sys from pylint.interfaces import IReporter from pylint.reporters import BaseReporter class ProgrammaticReporter(BaseReporter): """Reporter that replaces output with storage in list of dictionaries""" __implements__ = IReporter extension = 'prog' def __init__(self, output=None): BaseReporter.__init__(self, output) self.current_module = None self.data = [] def add_message(self, msg_id, location, msg): """Deprecated, but required""" raise NotImplementedError def handle_message(self, msg): """Get message and append to our data structure""" self.data.append(msg) def _display(self, layout): """launch layouts display""" def on_set_current_module(self, module, filepath): """Hook called when a module starts to be analysed.""" print('.', end='') sys.stdout.flush() def on_close(self, stats, previous_stats): """Hook called when all modules finished analyzing.""" # print a new line when pylint is finished print('') pytest-pylint-0.18.0/pytest_pylint/tests/000077500000000000000000000000001375216374400205525ustar00rootroot00000000000000pytest-pylint-0.18.0/pytest_pylint/tests/test_pytest_pylint.py000066400000000000000000000273671375216374400251310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Unit testing module for pytest-pylint plugin """ import os import re from textwrap import dedent from unittest import mock import pytest pytest_plugins = ('pytester',) # pylint: disable=invalid-name def test_basic(testdir): """Verify basic pylint checks""" testdir.makepyfile('import sys') result = testdir.runpytest('--pylint') assert 'Missing module docstring' in result.stdout.str() assert 'Unused import sys' in result.stdout.str() assert 'Final newline missing' in result.stdout.str() assert 'passed, ' not in result.stdout.str() assert '1 failed' in result.stdout.str() assert 'Linting files' in result.stdout.str() def test_nodeid(testdir): """Verify our nodeid adds a suffix""" testdir.makepyfile(app='import sys') result = testdir.runpytest('--pylint', '--collectonly', '--verbose') for expected in '', '': assert expected in result.stdout.str() def test_nodeid_no_dupepath(testdir): """Verify we don't duplicate the node path in our node id.""" testdir.makepyfile(app='import sys') result = testdir.runpytest('--pylint', '--verbose') assert re.search( r'^FAILED\s+app\.py::PYLINT$', result.stdout.str(), flags=re.MULTILINE ) def test_subdirectories(testdir): """Verify pylint checks files in subdirectories""" subdir = testdir.mkpydir('mymodule') testfile = subdir.join("test_file.py") testfile.write('import sys') result = testdir.runpytest('--pylint') assert '[pylint] mymodule/test_file.py' in result.stdout.str() assert 'Missing module docstring' in result.stdout.str() assert 'Unused import sys' in result.stdout.str() assert 'Final newline missing' in result.stdout.str() assert '1 failed' in result.stdout.str() assert 'Linting files' in result.stdout.str() def test_disable(testdir): """Verify basic pylint checks""" testdir.makepyfile('import sys') result = testdir.runpytest('--pylint --no-pylint') assert 'Final newline missing' not in result.stdout.str() assert 'Linting files' not in result.stdout.str() def test_error_control(testdir): """Verify that error types are configurable""" testdir.makepyfile('import sys') result = testdir.runpytest('--pylint', '--pylint-error-types=EF') assert '1 passed' in result.stdout.str() def test_pylintrc_file(testdir): """Verify that a specified pylint rc file will work.""" rcfile = testdir.makefile( '.rc', """ [FORMAT] max-line-length=3 """ ) testdir.makepyfile('import sys') result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert 'Line too long (10/3)' in result.stdout.str() def test_pylintrc_file_toml(testdir): """Verify that pyproject.toml can be used as a pylint rc file.""" rcfile = testdir.makefile( '.toml', pylint=""" [tool.pylint.FORMAT] max-line-length = "3" """ ) testdir.makepyfile('import sys') result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) # Parsing changed from integer to string in pylint >=2.5. Once # support is dropped <2.5 this is removable if 'should be of type int' in result.stdout.str(): rcfile = testdir.makefile( '.toml', pylint=""" [tool.pylint.FORMAT] max-line-length = 3 """ ) result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert 'Line too long (10/3)' in result.stdout.str() def test_pylintrc_file_beside_ini(testdir): """ Verify that a specified pylint rc file will work what placed into pytest ini dir. """ non_cwd_dir = testdir.mkdir('non_cwd_dir') rcfile = non_cwd_dir.join('foo.rc') rcfile.write( """ [FORMAT] max-line-length=3 """) inifile = non_cwd_dir.join('foo.ini') inifile.write(dedent( """ [pytest] addopts = --pylint --pylint-rcfile={0} """.format(rcfile.basename) )) pyfile = testdir.makepyfile('import sys') result = testdir.runpytest( pyfile.strpath ) assert 'Line too long (10/3)' not in result.stdout.str() result = testdir.runpytest( '-c', inifile.strpath, pyfile.strpath ) assert 'Line too long (10/3)' in result.stdout.str() @pytest.mark.parametrize("rcformat", ("ini", "toml", "simple_toml")) def test_pylintrc_ignore(testdir, rcformat): """Verify that a pylintrc file with ignores will work.""" if rcformat == "toml": rcfile = testdir.makefile( '.toml', """ [tool.pylint.master] ignore = ["test_pylintrc_ignore.py", "foo.py"] """ ) elif rcformat == "simple_toml": rcfile = testdir.makefile( '.toml', """ [tool.pylint.MASTER] ignore = "test_pylintrc_ignore.py,foo.py" """ ) else: rcfile = testdir.makefile( '.rc', """ [MASTER] ignore = test_pylintrc_ignore.py """ ) testdir.makepyfile('import sys') result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert 'collected 0 items' in result.stdout.str() @pytest.mark.parametrize("rcformat", ("ini", "toml")) def test_pylintrc_msg_template(testdir, rcformat): """Verify that msg-template from pylintrc file is handled.""" if rcformat == "toml": rcfile = testdir.makefile( '.toml', """ [tool.pylint.REPORTS] msg-template = "start {msg_id} end" """ ) else: rcfile = testdir.makefile( '.rc', """ [REPORTS] msg-template=start {msg_id} end """ ) testdir.makepyfile('import sys') result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert 'start W0611 end' in result.stdout.str() def test_multiple_jobs(testdir): """ Assert that the jobs argument is passed through to pylint if provided """ testdir.makepyfile('import sys') with mock.patch('pytest_pylint.plugin.lint.Run') as run_mock: jobs = 0 testdir.runpytest( '--pylint', '--pylint-jobs={0}'.format(jobs) ) assert run_mock.call_count == 1 assert run_mock.call_args[0][0][-2:] == ['-j', str(jobs)] def test_no_multiple_jobs(testdir): """ If no jobs argument is specified it should not appear in pylint arguments """ testdir.makepyfile('import sys') with mock.patch('pytest_pylint.plugin.lint.Run') as run_mock: testdir.runpytest('--pylint') assert run_mock.call_count == 1 assert '-j' not in run_mock.call_args[0][0] def test_skip_checked_files(testdir): """ Test a file twice which can pass pylint. The 2nd time should be skipped. """ testdir.makepyfile( '#!/usr/bin/env python', '"""A hello world script."""', '', 'from __future__ import print_function', '', 'print("Hello world!") # pylint: disable=missing-final-newline', ) # The 1st time should be passed result = testdir.runpytest('--pylint') assert '1 passed' in result.stdout.str() # The 2nd time should be skipped result = testdir.runpytest('--pylint') assert '1 skipped' in result.stdout.str() # Always be passed when cacheprovider disabled result = testdir.runpytest('--pylint', '-p', 'no:cacheprovider') assert '1 passed' in result.stdout.str() def test_invalidate_cache_when_config_changes(testdir): """If pylintrc changes, no cache should apply.""" rcfile = testdir.makefile( '.rc', '[MESSAGES CONTROL]', 'disable=missing-final-newline' ) testdir.makepyfile('"""hi."""') result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert '1 passed' in result.stdout.str() result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert '1 skipped' in result.stdout.str() # Change RC file entirely result = testdir.runpytest('--pylint') assert '1 failed' in result.stdout.str() # Change contents of RC file result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert '1 passed' in result.stdout.str() with open(rcfile, 'w'): pass result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath) ) assert '1 failed' in result.stdout.str() def test_output_file(testdir): """Verify pylint report output""" testdir.makepyfile('import sys') testdir.runpytest('--pylint', '--pylint-output-file=pylint.report') output_file = os.path.join(testdir.tmpdir.strpath, 'pylint.report') assert os.path.isfile(output_file) with open(output_file, 'r') as _file: report = _file.read() assert ( 'test_output_file.py:1: [C0304(missing-final-newline), ] Final ' 'newline missing' ) in report assert ( 'test_output_file.py:1: [C0111(missing-docstring), ] Missing ' 'module docstring' ) in report or ( 'test_output_file.py:1: [C0114(missing-module-docstring), ] Missing ' 'module docstring' ) in report assert ( 'test_output_file.py:1: [W0611(unused-import), ] Unused import sys' ) in report def test_output_file_makes_dirs(testdir): """Verify output works with folders properly.""" testdir.makepyfile('import sys') output_path = os.path.join('reports', 'pylint.report') testdir.runpytest( '--pylint', '--pylint-output-file={}'.format(output_path) ) output_file = os.path.join(testdir.tmpdir.strpath, output_path) assert os.path.isfile(output_file) # Run again to make sure we don't crash trying to make a dir that exists testdir.runpytest( '--pylint', '--pylint-output-file={}'.format(output_path) ) @pytest.mark.parametrize('arg_opt_name, arg_opt_value', [ ('ignore', 'test_cmd_line_ignore.py'), ('ignore-patterns', '.+_ignore.py'), ], ids=['ignore', 'ignore-patterns']) def test_cmd_line_ignore(testdir, arg_opt_name, arg_opt_value): """Verify that cmd line args ignores will work.""" testdir.makepyfile(test_cmd_line_ignore='import sys') result = testdir.runpytest( '--pylint', '--pylint-{0}={1}'.format(arg_opt_name, arg_opt_value) ) assert 'collected 0 items' in result.stdout.str() assert 'Unused import sys' not in result.stdout.str() @pytest.mark.parametrize('arg_opt_name, arg_opt_value', [ ('ignore', 'test_cmd_line_ignore_pri_arg.py'), ('ignore-patterns', '.*arg.py$'), ], ids=['ignore', 'ignore-patterns']) def test_cmd_line_ignore_pri(testdir, arg_opt_name, arg_opt_value): """ Verify that command line ignores and patterns take priority over rcfile ignores. """ file_ignore = 'test_cmd_line_ignore_pri_file.py' cmd_arg_ignore = 'test_cmd_line_ignore_pri_arg.py' cmd_line_ignore = arg_opt_value rcfile = testdir.makefile( '.rc', """ [MASTER] {0} = {1},foo """.format(arg_opt_name, file_ignore) ) testdir.makepyfile(**{ file_ignore: 'import sys', cmd_arg_ignore: 'import os', }) result = testdir.runpytest( '--pylint', '--pylint-rcfile={0}'.format(rcfile.strpath), '--pylint-{0}={1}'.format(arg_opt_name, cmd_line_ignore), '-s', ) assert 'collected 1 item' in result.stdout.str() assert 'Unused import sys' in result.stdout.str() pytest-pylint-0.18.0/pytest_pylint/tests/test_util.py000066400000000000000000000056241375216374400231470ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Unit testing module for pytest-pylint util.py module """ from pytest_pylint.util import get_rel_path, should_include_file def test_get_rel_path(): """ Verify our relative path function. """ correct_rel_path = 'How/Are/You/blah.py' path = '/Hi/How/Are/You/blah.py' parent_path = '/Hi/' assert get_rel_path(path, parent_path) == correct_rel_path parent_path = '/Hi' assert get_rel_path(path, parent_path) == correct_rel_path def test_should_include_path(): """ Files should only be included in the list if none of the directories on it's path, of the filename, match an entry in the ignore list. """ ignore_list = [ "first", "second", "third", "part", "base.py" ] # Default includes. assert should_include_file("random", ignore_list) is True assert should_include_file("random/filename", ignore_list) is True assert should_include_file("random/other/filename", ignore_list) is True # Basic ignore matches. assert should_include_file("first/filename", ignore_list) is False assert should_include_file("random/base.py", ignore_list) is False # Part on paths. assert should_include_file("part/second/filename.py", ignore_list) is False assert should_include_file("random/part/filename.py", ignore_list) is False assert should_include_file("random/second/part.py", ignore_list) is False # Part as substring on paths. assert should_include_file( "part_it/other/filename.py", ignore_list ) is True assert should_include_file( "random/part_it/filename.py", ignore_list ) is True assert should_include_file("random/other/part_it.py", ignore_list) is True def test_pylint_ignore_patterns(): """Test if the ignore-patterns is working""" ignore_patterns = [ "first.*", ".*second", "^third.*fourth$", "part", "base.py" ] # Default includes assert should_include_file("random", [], ignore_patterns) is True assert should_include_file("random/filename", [], ignore_patterns) is True assert should_include_file( "random/other/filename", [], ignore_patterns ) is True # Pattern matches assert should_include_file("first1", [], ignore_patterns) is False assert should_include_file("first", [], ignore_patterns) is False assert should_include_file("_second", [], ignore_patterns) is False assert should_include_file("second_", [], ignore_patterns) is False assert should_include_file("second_", [], ignore_patterns) is False assert should_include_file("third fourth", [], ignore_patterns) is False assert should_include_file("_third fourth_", [], ignore_patterns) is True assert should_include_file("part", [], ignore_patterns) is False assert should_include_file("1part2", [], ignore_patterns) is True assert should_include_file("base.py", [], ignore_patterns) is False pytest-pylint-0.18.0/pytest_pylint/util.py000066400000000000000000000015151375216374400207410ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Utility functions for gathering files, etc. """ import re from os import sep class PyLintException(Exception): """Exception to raise if a file has a specified pylint error""" def get_rel_path(path, parent_path): """ Give the path to object relative to ``parent_path``. """ replaced_path = path.replace(parent_path, '', 1) if replaced_path[0] == sep: rel_path = replaced_path[1:] else: rel_path = replaced_path return rel_path def should_include_file(path, ignore_list, ignore_patterns=None): """Checks if a file should be included in the collection.""" if ignore_patterns: for pattern in ignore_patterns: if re.match(pattern, path): return False parts = path.split(sep) return not set(parts) & set(ignore_list) pytest-pylint-0.18.0/setup.cfg000066400000000000000000000000261375216374400163000ustar00rootroot00000000000000[aliases] test=pytest pytest-pylint-0.18.0/setup.py000066400000000000000000000021041375216374400161700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ pytest-pylint ============= Plugin for py.test for doing pylint tests """ from setuptools import setup setup( name='pytest-pylint', description='pytest plugin to check source code with pylint', long_description=open("README.rst").read(), license='MIT', version='0.18.0', author='Carson Gee', author_email='x@carsongee.com', url='https://github.com/carsongee/pytest-pylint', packages=['pytest_pylint'], entry_points={'pytest11': ['pylint = pytest_pylint.plugin']}, python_requires=">=3.5", install_requires=['pytest>=5.4', 'pylint>=2.3.0', 'toml>=0.7.1'], setup_requires=['pytest-runner'], tests_require=['coverage', 'pytest-flake8'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', ], ) pytest-pylint-0.18.0/tox.ini000066400000000000000000000017651375216374400160050ustar00rootroot00000000000000[tox] envlist = py3{6, 7, 8, 9}-pylint{23, 24, 25, latest, master}-pytest{54, latest, master}, coverage skip_missing_interpreters = true [testenv] usedevelop = true deps = pylint23: pylint>=2.3,<2.4 pylint24: pylint>=2.4,<2.5 pylint25: pylint>=2.5,<2.6 pylintlatest: pylint pylintmaster: git+https://github.com/PyCQA/pylint.git@master#egg=pylint pylintmaster: git+https://github.com/PyCQA/astroid.git@master#egg=astroid pytest54: pytest>=5.4,<5.5 pytestlatest: pytest pytestmaster: git+https://github.com/pytest-dev/pytest.git@master#egg=pytest pytest-flake8 coverage commands = coverage run -m py.test {posargs} [testenv:coverage] depends = py3{5, 6, 7, 8}-pylint{23, 24, 25, latest, master}-pytest{54, latest, master} commands = coverage combine coverage report coverage html -d htmlcov [travis] python = 3.5: py35, coverage 3.6: py36, coverage 3.7: py37, coverage 3.8: py38, coverage [pytest] addopts = --pylint --flake8 markers = flake8