Add actual tests around statistics module

Also refactor our statistics module to be a bit smarter and less
namedtuple happy. The Statistic class had no reason to be a tuple,
I have no clue why I wrote it that way last night.
This commit is contained in:
Ian Cordasco 2016-07-12 08:21:57 -05:00
parent 1a722bbe7b
commit 2d3f062191
No known key found for this signature in database
GPG key ID: 656D3395E4A9791A
2 changed files with 109 additions and 25 deletions

View file

@ -18,11 +18,9 @@ class Statistics(object):
flake8.style_guide.Error
"""
key = Key.create_from(error)
if key in self._store:
statistic = self._store[key]
else:
statistic = Statistic.create_from(error)
self._store[key] = statistic.increment()
if key not in self._store:
self._store[key] = Statistic.create_from(error)
self._store[key].increment()
def statistics_for(self, prefix, filename=None):
"""Generate statistics for the prefix and filename.
@ -54,48 +52,67 @@ class Statistics(object):
class Key(collections.namedtuple('Key', ['filename', 'code'])):
"""Simple key structure for the Statistics dictionary.
To make things clearer, easier to read, and more understandable, we use a
namedtuple here for all Keys in the underlying dictionary for the
Statistics object.
"""
__slots__ = ()
@classmethod
def create_from(cls, error):
"""Create a Key from :class:`flake8.style_guide.Error`."""
return cls(
filename=error.filename,
code=error.code,
)
def matches(self, prefix, filename):
"""Determine if this key matches some constraints.
:param str prefix:
The error code prefix that this key's error code should start with.
:param str filename:
The filename that we potentially want to match on. This can be
None to only match on error prefix.
:returns:
True if the Key's code starts with the prefix and either filename
is None, or the Key's filename matches the value passed in.
:rtype:
bool
"""
return (self.code.startswith(prefix) and
(filename is None or
self.filename == filename))
_Statistic = collections.namedtuple('Statistic', [
'error_code',
'filename',
'message',
'count',
])
class Statistic(object):
"""Simple wrapper around the logic of each statistic.
Instead of maintaining a simple but potentially hard to reason about
tuple, we create a namedtuple which has attributes and a couple
convenience methods on it.
"""
class Statistic(_Statistic):
__slots__ = ()
def __init__(self, error_code, filename, message, count):
"""Initialize our Statistic."""
self.error_code = error_code
self.filename = filename
self.message = message
self.count = count
@classmethod
def create_from(cls, error):
"""Create a Statistic from a :class:`flake8.style_guide.Error`."""
return cls(
error_code=error.code,
filename=error.filename,
message=error.message,
message=error.text,
count=0,
)
def increment(self):
return Statistic(
error_code=self.error_code,
filename=self.filename,
message=self.message,
count=self.count + 1,
)
del _Statistic
"""Increment the number of times we've seen this error in this file."""
self.count += 1

View file

@ -1,5 +1,72 @@
"""Tests for the statistics module in Flake8."""
import pytest
from flake8 import statistics as stats
from flake8 import style_guide
DEFAULT_ERROR_CODE = 'E100'
DEFAULT_FILENAME = 'file.py'
DEFAULT_TEXT = 'Default text'
def test_nothing():
pass
def make_error(**kwargs):
"""Create errors with a bunch of default values."""
return style_guide.Error(
code=kwargs.pop('code', DEFAULT_ERROR_CODE),
filename=kwargs.pop('filename', DEFAULT_FILENAME),
line_number=kwargs.pop('line_number', 1),
column_number=kwargs.pop('column_number', 1),
text=kwargs.pop('text', DEFAULT_TEXT),
physical_line=None,
)
def test_key_creation():
"""Verify how we create Keys from Errors."""
key = stats.Key.create_from(make_error())
assert key == (DEFAULT_FILENAME, DEFAULT_ERROR_CODE)
assert key.filename == DEFAULT_FILENAME
assert key.code == DEFAULT_ERROR_CODE
@pytest.mark.parametrize('code, filename, args, expected_result', [
# Error prefix matches
('E123', 'file000.py', ('E', None), True),
('E123', 'file000.py', ('E1', None), True),
('E123', 'file000.py', ('E12', None), True),
('E123', 'file000.py', ('E123', None), True),
# Error prefix and filename match
('E123', 'file000.py', ('E', 'file000.py'), True),
('E123', 'file000.py', ('E1', 'file000.py'), True),
('E123', 'file000.py', ('E12', 'file000.py'), True),
('E123', 'file000.py', ('E123', 'file000.py'), True),
# Error prefix does not match
('E123', 'file000.py', ('W', None), False),
# Error prefix matches but filename does not
('E123', 'file000.py', ('E', 'file001.py'), False),
# Error prefix does not match but filename does
('E123', 'file000.py', ('W', 'file000.py'), False),
# Neither error prefix match nor filename
('E123', 'file000.py', ('W', 'file001.py'), False),
])
def test_key_matching(code, filename, args, expected_result):
"""Verify Key#matches behaves as we expect with fthe above input."""
key = stats.Key.create_from(make_error(code=code, filename=filename))
assert key.matches(*args) is expected_result
def test_statistic_creation():
"""Verify how we create Statistic objects from Errors."""
stat = stats.Statistic.create_from(make_error())
assert stat.error_code == DEFAULT_ERROR_CODE
assert stat.message == DEFAULT_TEXT
assert stat.filename == DEFAULT_FILENAME
assert stat.count == 0
def test_statistic_increment():
"""Verify we update the count."""
stat = stats.Statistic.create_from(make_error())
assert stat.count == 0
stat.increment()
assert stat.count == 1