Skip to content

Commit cbda42b

Browse files
authored
Merge pull request #86 from carsongee/rc/0.14.0
Rc/0.14.0
2 parents 2cb3191 + 78ea69c commit cbda42b

File tree

5 files changed

+135
-10
lines changed

5 files changed

+135
-10
lines changed

README.rst

+9
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ This code is heavily based on
4949
Releases
5050
========
5151

52+
0.14.0
53+
~~~~~~
54+
55+
- Added support for Pylint's ignore-patterns for regex based ignores
56+
thanks to `khokhlin <https://github.com/khokhlin>`_
57+
- pytest-pylint now caches successful pylint checks to speedup test
58+
reruns when files haven't changed thanks to `yanqd0
59+
<https://github.com/yanqd0>`_
60+
5261
0.13.0
5362
~~~~~~
5463

pylintrc

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
[TYPECHECK]
2-
ignored-classes=pytest
2+
3+
ignored-classes = pytest
4+
5+
6+
[MESSAGES CONTROL]
7+
8+
disable = useless-object-inheritance

pytest_pylint.py

+67-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Pylint plugin for py.test"""
22
from __future__ import absolute_import, print_function, unicode_literals
3+
import re
34
from os import sep
45
from os.path import exists, join, dirname
56
import sys
@@ -15,6 +16,8 @@
1516
from pylint.reporters import BaseReporter
1617
import pytest
1718

19+
HISTKEY = 'pylint/mtimes'
20+
1821

1922
class PyLintException(Exception):
2023
"""Exception to raise if a file has a specified pylint error"""
@@ -104,6 +107,7 @@ def pytest_sessionstart(session):
104107
session.pylint_config = None
105108
session.pylintrc_file = None
106109
session.pylint_ignore = []
110+
session.pylint_ignore_patterns = []
107111
session.pylint_msg_template = None
108112
config = session.config
109113

@@ -118,12 +122,20 @@ def pytest_sessionstart(session):
118122
session.pylintrc_file = pylintrc_file
119123
session.pylint_config = ConfigParser()
120124
session.pylint_config.read(pylintrc_file)
125+
121126
try:
122127
ignore_string = session.pylint_config.get('MASTER', 'ignore')
123128
if ignore_string:
124129
session.pylint_ignore = ignore_string.split(',')
125130
except (NoSectionError, NoOptionError):
126131
pass
132+
133+
try:
134+
session.pylint_ignore_patterns = session.pylint_config.get(
135+
'MASTER', 'ignore-patterns')
136+
except (NoSectionError, NoOptionError):
137+
pass
138+
127139
try:
128140
session.pylint_msg_template = session.pylint_config.get(
129141
'REPORTS', 'msg-template'
@@ -132,12 +144,46 @@ def pytest_sessionstart(session):
132144
pass
133145

134146

135-
def include_file(path, ignore_list):
147+
def include_file(path, ignore_list, ignore_patterns=None):
136148
"""Checks if a file should be included in the collection."""
149+
if ignore_patterns:
150+
for pattern in ignore_patterns:
151+
if re.match(pattern, path):
152+
return False
137153
parts = path.split(sep)
138154
return not set(parts) & set(ignore_list)
139155

140156

157+
def pytest_configure(config):
158+
"""
159+
Add a plugin to cache file mtimes.
160+
161+
:param _pytest.config.Config config: pytest config object
162+
"""
163+
if config.option.pylint:
164+
config.pylint = PylintPlugin(config)
165+
config.pluginmanager.register(config.pylint)
166+
config.addinivalue_line('markers', "pylint: Tests which run pylint.")
167+
168+
169+
class PylintPlugin(object):
170+
"""
171+
A Plugin object for pylint, which loads and records file mtimes.
172+
"""
173+
# pylint: disable=too-few-public-methods
174+
175+
def __init__(self, config):
176+
self.mtimes = config.cache.get(HISTKEY, {})
177+
178+
def pytest_sessionfinish(self, session):
179+
"""
180+
Save file mtimes to pytest cache.
181+
182+
:param _pytest.main.Session session: the pytest session object
183+
"""
184+
session.config.cache.set(HISTKEY, self.mtimes)
185+
186+
141187
def pytest_collect_file(path, parent):
142188
"""Collect files on which pylint should run"""
143189
config = parent.session.config
@@ -148,16 +194,18 @@ def pytest_collect_file(path, parent):
148194
rel_path = get_rel_path(path.strpath, parent.session.fspath.strpath)
149195
session = parent.session
150196
if session.pylint_config is None:
151-
session.pylint_files.add(rel_path)
152197
# No pylintrc, therefore no ignores, so return the item.
153-
return PyLintItem(path, parent)
154-
155-
if include_file(rel_path, session.pylint_ignore):
156-
session.pylint_files.add(rel_path)
157-
return PyLintItem(
198+
item = PyLintItem(path, parent)
199+
elif include_file(rel_path, session.pylint_ignore,
200+
session.pylint_ignore_patterns):
201+
item = PyLintItem(
158202
path, parent, session.pylint_msg_template, session.pylintrc_file
159203
)
160-
return None
204+
else:
205+
return None
206+
if not item.should_skip:
207+
session.pylint_files.add(rel_path)
208+
return item
161209

162210

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

218266
self.pylintrc_file = pylintrc_file
267+
self.__mtime = self.fspath.mtime()
268+
prev_mtime = self.config.pylint.mtimes.get(self.nodeid, 0)
269+
self.should_skip = (prev_mtime == self.__mtime)
270+
271+
def setup(self):
272+
"""Mark unchanged files as SKIPPED."""
273+
if self.should_skip:
274+
pytest.skip("file(s) previously passed pylint checks")
219275

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

287+
# Update the cache if the item passed pylint.
288+
self.config.pylint.mtimes[self.nodeid] = self.__mtime
289+
231290
def repr_failure(self, excinfo):
232291
"""Handle any test failures by checkint that they were ours."""
233292
if excinfo.errisinstance(PyLintException):

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
description='pytest plugin to check source code with pylint',
1414
long_description=open("README.rst").read(),
1515
license='MIT',
16-
version='0.13.0',
16+
version='0.14.0',
1717
author='Carson Gee',
1818
author_email='[email protected]',
1919
url='https://github.com/carsongee/pytest-pylint',

test_pytest_pylint.py

+51
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,54 @@ def test_include_path():
187187
assert include_file("part_it/other/filename.py", ignore_list) is True
188188
assert include_file("random/part_it/filename.py", ignore_list) is True
189189
assert include_file("random/other/part_it.py", ignore_list) is True
190+
191+
192+
def test_pylint_ignore_patterns():
193+
"""Test if the ignore-patterns is working"""
194+
from pytest_pylint import include_file
195+
ignore_patterns = [
196+
"first.*",
197+
".*second",
198+
"^third.*fourth$",
199+
"part",
200+
"base.py"
201+
]
202+
203+
# Default includes
204+
assert include_file("random", [], ignore_patterns) is True
205+
assert include_file("random/filename", [], ignore_patterns) is True
206+
assert include_file("random/other/filename", [], ignore_patterns) is True
207+
208+
# Pattern matches
209+
assert include_file("first1", [], ignore_patterns) is False
210+
assert include_file("first", [], ignore_patterns) is False
211+
assert include_file("_second", [], ignore_patterns) is False
212+
assert include_file("second_", [], ignore_patterns) is False
213+
assert include_file("second_", [], ignore_patterns) is False
214+
assert include_file("third fourth", [], ignore_patterns) is False
215+
assert include_file("_third fourth_", [], ignore_patterns) is True
216+
assert include_file("part", [], ignore_patterns) is False
217+
assert include_file("1part2", [], ignore_patterns) is True
218+
assert include_file("base.py", [], ignore_patterns) is False
219+
220+
221+
def test_skip_checked_files(testdir):
222+
"""
223+
Test a file twice which can pass pylint.
224+
The 2nd time should be skipped.
225+
"""
226+
testdir.makepyfile(
227+
'#!/usr/bin/env python',
228+
'"""A hello world script."""',
229+
'',
230+
'from __future__ import print_function',
231+
'',
232+
'print("Hello world!") # pylint: disable=missing-final-newline',
233+
)
234+
# The 1st time should be passed
235+
result = testdir.runpytest('--pylint')
236+
assert '1 passed' in result.stdout.str()
237+
238+
# The 2nd time should be skipped
239+
result = testdir.runpytest('--pylint')
240+
assert '1 skipped' in result.stdout.str()

0 commit comments

Comments
 (0)