pax_global_header00006660000000000000000000000064132347475030014522gustar00rootroot0000000000000052 comment=992d7b061a573fe37ed12020c5b0fcf1f45c11e3 pytest-pylint-0.8.0/000077500000000000000000000000001323474750300143745ustar00rootroot00000000000000pytest-pylint-0.8.0/.coveragerc000066400000000000000000000000601323474750300165110ustar00rootroot00000000000000[run] source = . omit = .tox/* setup.py pytest-pylint-0.8.0/.gitignore000066400000000000000000000013271323474750300163670ustar00rootroot00000000000000# 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 nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # PyEnv .python-version pytest-pylint-0.8.0/.travis.yml000066400000000000000000000002651323474750300165100ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.6" install: - "pip install tox-travis tox coveralls" - "pip install -e ." script: tox after_success: "coveralls" pytest-pylint-0.8.0/LICENSE000066400000000000000000000020661323474750300154050ustar00rootroot00000000000000The 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.8.0/MANIFEST.in000066400000000000000000000000431323474750300161270ustar00rootroot00000000000000include README.rst include LICENSE pytest-pylint-0.8.0/README.rst000066400000000000000000000033641323474750300160710ustar00rootroot00000000000000pytest pylint ------------- .. image:: https://img.shields.io/travis/carsongee/pytest-pylint.svg :target: https://travis-ci.org/carsongee/orcoursetrion .. 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://img.shields.io/pypi/dm/pytest-pylint.svg :target: https://pypi.python.org/pypi/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 This would use the pylintrc file at /my/pyrc and only error on pylint Errors and Failures. 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 `_ Releases ======== 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.8.0/pylintrc000066400000000000000000000000421323474750300161570ustar00rootroot00000000000000[TYPECHECK] ignored-classes=pytestpytest-pylint-0.8.0/pytest_pylint.py000066400000000000000000000146101323474750300176770ustar00rootroot00000000000000"""Pylint plugin for py.test""" from __future__ import unicode_literals from __future__ import absolute_import from __future__ import print_function from os import sep from os.path import exists, join, dirname from six.moves.configparser import ( # pylint: disable=import-error ConfigParser, NoSectionError, NoOptionError ) from pylint import lint from pylint.config import PYLINTRC from pylint.interfaces import IReporter from pylint.reporters import BaseReporter import pytest class PyLintException(Exception): """Exception to raise if a file has a specified pylint error""" pass 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 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 pytest_addoption(parser): """Add all our command line options""" group = parser.getgroup("general") group.addoption( "--pylint", action="store_true", default=False, help="run pylint on all" ) 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).' ) def pytest_sessionstart(session): """Storing pylint settings on the session""" session.pylint_files = set() session.pylint_messages = {} session.pylint_config = None session.pylintrc_file = None session.pylint_ignore = [] session.pylint_msg_template = None config = session.config # 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) if pylintrc_file and exists(pylintrc_file): session.pylintrc_file = pylintrc_file session.pylint_config = ConfigParser() session.pylint_config.read(pylintrc_file) try: ignore_string = session.pylint_config.get('MASTER', 'ignore') if ignore_string: session.pylint_ignore = ignore_string.split(',') except (NoSectionError, NoOptionError): pass try: session.pylint_msg_template = session.pylint_config.get( 'REPORTS', 'msg-template' ) except (NoSectionError, NoOptionError): pass def pytest_collect_file(path, parent): """Collect files on which pylint should run""" config = parent.config if not config.option.pylint: return None if path.ext != ".py": return None rel_path = get_rel_path(path.strpath, parent.fspath.strpath) if parent.pylint_config is None: parent.pylint_files.add(rel_path) # No pylintrc, therefore no ignores, so return the item. return PyLintItem(path, parent) if not any(basename in rel_path for basename in parent.pylint_ignore): parent.pylint_files.add(rel_path) return PyLintItem( path, parent, parent.pylint_msg_template, parent.pylintrc_file ) return None def pytest_collection_finish(session): """Lint collected files and store messages on session.""" if not session.pylint_files: return reporter = ProgrammaticReporter() # Build argument list for pylint args_list = list(session.pylint_files) if session.pylintrc_file: args_list.append('--rcfile={0}'.format( session.pylintrc_file )) print('-' * 65) print('Linting files') # Run pylint over the collected files. result = lint.Run(args_list, reporter=reporter, exit=False) messages = result.linter.reporter.data # Stores the messages in a dictionary for lookup in tests. for message in messages: if message.path not in session.pylint_messages: session.pylint_messages[message.path] = [] session.pylint_messages[message.path].append(message) print('-' * 65) class PyLintItem(pytest.Item, pytest.File): """pylint test running class.""" # pylint doesn't deal well with dynamic modules and there isn't an # astng plugin for pylint in pypi yet, so we'll have to disable # the checks. # pylint: disable=no-member,super-on-old-class def __init__(self, fspath, parent, msg_format=None, pylintrc_file=None): super(PyLintItem, self).__init__(fspath, parent) self.add_marker("pylint") self.rel_path = get_rel_path(fspath.strpath, parent.fspath.strpath) if msg_format is None: self._msg_format = '{C}:{line:3d},{column:2d}: {msg} ({symbol})' else: self._msg_format = msg_format self.pylintrc_file = pylintrc_file def runtest(self): """Check the pylint messages to see if any errors were reported.""" reported_errors = [] for error in self.session.pylint_messages.get(self.rel_path, []): if error.C in self.config.option.pylint_error_types: reported_errors.append( error.format(self._msg_format) ) if reported_errors: raise PyLintException('\n'.join(reported_errors)) def repr_failure(self, excinfo): """Handle any test failures by checkint that they were ours.""" if excinfo.errisinstance(PyLintException): return excinfo.value.args[0] return super(PyLintItem, self).repr_failure(excinfo) def reportinfo(self): """Generate our test report""" return self.fspath, None, "[pylint] {0}".format(self.name) pytest-pylint-0.8.0/setup.py000066400000000000000000000017021323474750300161060ustar00rootroot00000000000000# -*- 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.8.0', author='Carson Gee', author_email='x@carsongee.com', url='https://github.com/carsongee/pytest-pylint', py_modules=['pytest_pylint'], entry_points={'pytest11': ['pylint = pytest_pylint']}, install_requires=['pytest>=2.7', 'pylint>=1.4.5', 'six'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], ) pytest-pylint-0.8.0/test_pytest_pylint.py000066400000000000000000000057301323474750300207410ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Unit testing module for pytest-pylti plugin """ 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() 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_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(""" [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() def test_pylintrc_ignore(testdir): """Verify that a pylintrc file with ignores will work.""" 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() def test_pylintrc_msg_template(testdir): """Verify that msg-template from pylintrc file is handled.""" 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_get_rel_path(): """ Verify our relative path function. """ from pytest_pylint import get_rel_path 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 pytest-pylint-0.8.0/tox.ini000066400000000000000000000004551323474750300157130ustar00rootroot00000000000000[tox] envlist = py27,py34,py35,py36 skip_missing_interpreters = true [testenv] usedevelop = true deps = pytest pytest-pep8 coverage commands = coverage erase coverage run -m py.test {posargs} coverage report coverage html -d htmlcov [pytest] addopts = --pylint --pep8