Skip to content

Commit

Permalink
Merge pull request #86 from carsongee/rc/0.14.0
Browse files Browse the repository at this point in the history
Rc/0.14.0
  • Loading branch information
carsongee authored Jan 15, 2019
2 parents 2cb3191 + 78ea69c commit cbda42b
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 10 deletions.
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ This code is heavily based on
Releases
========

0.14.0
~~~~~~

- Added support for Pylint's ignore-patterns for regex based ignores
thanks to `khokhlin <https://github.com/khokhlin>`_
- pytest-pylint now caches successful pylint checks to speedup test
reruns when files haven't changed thanks to `yanqd0
<https://github.com/yanqd0>`_

0.13.0
~~~~~~

Expand Down
8 changes: 7 additions & 1 deletion pylintrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
[TYPECHECK]
ignored-classes=pytest

ignored-classes = pytest


[MESSAGES CONTROL]

disable = useless-object-inheritance
75 changes: 67 additions & 8 deletions pytest_pylint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Pylint plugin for py.test"""
from __future__ import absolute_import, print_function, unicode_literals
import re
from os import sep
from os.path import exists, join, dirname
import sys
Expand All @@ -15,6 +16,8 @@
from pylint.reporters import BaseReporter
import pytest

HISTKEY = 'pylint/mtimes'


class PyLintException(Exception):
"""Exception to raise if a file has a specified pylint error"""
Expand Down Expand Up @@ -104,6 +107,7 @@ def pytest_sessionstart(session):
session.pylint_config = None
session.pylintrc_file = None
session.pylint_ignore = []
session.pylint_ignore_patterns = []
session.pylint_msg_template = None
config = session.config

Expand All @@ -118,12 +122,20 @@ def pytest_sessionstart(session):
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_ignore_patterns = session.pylint_config.get(
'MASTER', 'ignore-patterns')
except (NoSectionError, NoOptionError):
pass

try:
session.pylint_msg_template = session.pylint_config.get(
'REPORTS', 'msg-template'
Expand All @@ -132,12 +144,46 @@ def pytest_sessionstart(session):
pass


def include_file(path, ignore_list):
def 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)


def pytest_configure(config):
"""
Add a plugin to cache file mtimes.
:param _pytest.config.Config config: pytest config object
"""
if config.option.pylint:
config.pylint = PylintPlugin(config)
config.pluginmanager.register(config.pylint)
config.addinivalue_line('markers', "pylint: Tests which run pylint.")


class PylintPlugin(object):
"""
A Plugin object for pylint, which loads and records file mtimes.
"""
# pylint: disable=too-few-public-methods

def __init__(self, config):
self.mtimes = config.cache.get(HISTKEY, {})

def pytest_sessionfinish(self, session):
"""
Save file mtimes to pytest cache.
:param _pytest.main.Session session: the pytest session object
"""
session.config.cache.set(HISTKEY, self.mtimes)


def pytest_collect_file(path, parent):
"""Collect files on which pylint should run"""
config = parent.session.config
Expand All @@ -148,16 +194,18 @@ def pytest_collect_file(path, parent):
rel_path = get_rel_path(path.strpath, parent.session.fspath.strpath)
session = parent.session
if session.pylint_config is None:
session.pylint_files.add(rel_path)
# No pylintrc, therefore no ignores, so return the item.
return PyLintItem(path, parent)

if include_file(rel_path, session.pylint_ignore):
session.pylint_files.add(rel_path)
return PyLintItem(
item = PyLintItem(path, parent)
elif include_file(rel_path, session.pylint_ignore,
session.pylint_ignore_patterns):
item = PyLintItem(
path, parent, session.pylint_msg_template, session.pylintrc_file
)
return None
else:
return None
if not item.should_skip:
session.pylint_files.add(rel_path)
return item


def pytest_collection_finish(session):
Expand Down Expand Up @@ -216,6 +264,14 @@ def __init__(self, fspath, parent, msg_format=None, pylintrc_file=None):
self._msg_format = msg_format

self.pylintrc_file = pylintrc_file
self.__mtime = self.fspath.mtime()
prev_mtime = self.config.pylint.mtimes.get(self.nodeid, 0)
self.should_skip = (prev_mtime == self.__mtime)

def setup(self):
"""Mark unchanged files as SKIPPED."""
if self.should_skip:
pytest.skip("file(s) previously passed pylint checks")

def runtest(self):
"""Check the pylint messages to see if any errors were reported."""
Expand All @@ -228,6 +284,9 @@ def runtest(self):
if reported_errors:
raise PyLintException('\n'.join(reported_errors))

# Update the cache if the item passed pylint.
self.config.pylint.mtimes[self.nodeid] = self.__mtime

def repr_failure(self, excinfo):
"""Handle any test failures by checkint that they were ours."""
if excinfo.errisinstance(PyLintException):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
description='pytest plugin to check source code with pylint',
long_description=open("README.rst").read(),
license='MIT',
version='0.13.0',
version='0.14.0',
author='Carson Gee',
author_email='x@carsongee.com',
url='https://github.com/carsongee/pytest-pylint',
Expand Down
51 changes: 51 additions & 0 deletions test_pytest_pylint.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,54 @@ def test_include_path():
assert include_file("part_it/other/filename.py", ignore_list) is True
assert include_file("random/part_it/filename.py", ignore_list) is True
assert include_file("random/other/part_it.py", ignore_list) is True


def test_pylint_ignore_patterns():
"""Test if the ignore-patterns is working"""
from pytest_pylint import include_file
ignore_patterns = [
"first.*",
".*second",
"^third.*fourth$",
"part",
"base.py"
]

# Default includes
assert include_file("random", [], ignore_patterns) is True
assert include_file("random/filename", [], ignore_patterns) is True
assert include_file("random/other/filename", [], ignore_patterns) is True

# Pattern matches
assert include_file("first1", [], ignore_patterns) is False
assert include_file("first", [], ignore_patterns) is False
assert include_file("_second", [], ignore_patterns) is False
assert include_file("second_", [], ignore_patterns) is False
assert include_file("second_", [], ignore_patterns) is False
assert include_file("third fourth", [], ignore_patterns) is False
assert include_file("_third fourth_", [], ignore_patterns) is True
assert include_file("part", [], ignore_patterns) is False
assert include_file("1part2", [], ignore_patterns) is True
assert include_file("base.py", [], ignore_patterns) is False


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()

0 comments on commit cbda42b

Please sign in to comment.