Skip to content

Commit

Permalink
Merge pull request #66 from Shoobx/master
Browse files Browse the repository at this point in the history
added diff-cover level exclude files (some func taken from flake8)
  • Loading branch information
Bachmann1234 committed Nov 15, 2017
2 parents 606147d + 3b56252 commit 97c9443
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 11 deletions.
52 changes: 49 additions & 3 deletions diff_cover/diff_reporter.py
Expand Up @@ -4,6 +4,7 @@
from __future__ import unicode_literals
from abc import ABCMeta, abstractmethod
from diff_cover.git_diff import GitDiffError
import fnmatch
import os
import re

Expand All @@ -14,13 +15,15 @@ class BaseDiffReporter(object):
"""

__metaclass__ = ABCMeta
_exclude = None

def __init__(self, name):
def __init__(self, name, exclude=None):
"""
Provide a `name` for the diff report, which will
be included in the diff coverage report.
"""
self._name = name
self._exclude = exclude

@abstractmethod
def src_paths_changed(self):
Expand Down Expand Up @@ -49,13 +52,54 @@ def name(self):
"""
return self._name

def _fnmatch(self, filename, patterns, default=True):
"""Wrap :func:`fnmatch.fnmatch` to add some functionality.
:param str filename:
Name of the file we're trying to match.
:param list patterns:
Patterns we're using to try to match the filename.
:param bool default:
The default value if patterns is empty
:returns:
True if a pattern matches the filename, False if it doesn't.
``default`` if patterns is empty.
"""
if not patterns:
return default
return any(fnmatch.fnmatch(filename, pattern) for pattern in patterns)

def _is_path_excluded(self, path):
"""
Check if a path is excluded.
:param str path:
Path to check against the exclude patterns.
:returns:
True if there are exclude patterns and the path matches,
otherwise False.
"""
exclude = self._exclude
if not exclude:
return False
basename = os.path.basename(path)
if self._fnmatch(basename, exclude):
return True

absolute_path = os.path.abspath(path)
match = self._fnmatch(absolute_path, exclude)
return match


class GitDiffReporter(BaseDiffReporter):
"""
Query information from a Git diff between branches.
"""

def __init__(self, compare_branch='origin/master', git_diff=None, ignore_staged=None, ignore_unstaged=None, supported_extensions=None):
def __init__(self, compare_branch='origin/master', git_diff=None,
ignore_staged=None, ignore_unstaged=None,
supported_extensions=None,
exclude=None):
"""
Configure the reporter to use `git_diff` as the wrapper
for the `git diff` tool. (Should have same interface
Expand All @@ -76,7 +120,7 @@ def __init__(self, compare_branch='origin/master', git_diff=None, ignore_staged=
# Apply and + changes to the last option
name += " and " + options[-1] + " changes"

super(GitDiffReporter, self).__init__(name)
super(GitDiffReporter, self).__init__(name, exclude)

self._compare_branch = compare_branch
self._git_diff_tool = git_diff
Expand Down Expand Up @@ -154,6 +198,8 @@ def _git_diff(self):
diff_dict = self._parse_diff_str(diff_str)

for src_path in diff_dict.keys():
if self._is_path_excluded(src_path):
continue
# If no _supported_extensions provided, or extension present: process
root, extension = os.path.splitext(src_path)
extension = extension[1:].lower()
Expand Down
34 changes: 34 additions & 0 deletions diff_cover/tests/test_args.py
Expand Up @@ -72,6 +72,23 @@ def test_parse_invalid_arg(self):
with nostderr():
parse_coverage_args(argv)

def test_parse_with_exclude(self):
argv = ['reports/coverage.xml']
arg_dict = parse_coverage_args(argv)
self.assertEqual(arg_dict.get('exclude'), None)

argv = ['reports/coverage.xml', '--exclude', 'noneed/*.py']

arg_dict = parse_coverage_args(argv)
self.assertEqual(arg_dict.get('exclude'), ['noneed/*.py'])

argv = ['reports/coverage.xml', '--exclude', 'noneed/*.py',
'other/**/*.py']

arg_dict = parse_coverage_args(argv)
self.assertEqual(arg_dict.get('exclude'),
['noneed/*.py', 'other/**/*.py'])


class ParseQualityArgsTest(unittest.TestCase):

Expand Down Expand Up @@ -138,6 +155,23 @@ def test_parse_invalid_arg(self):
print("args = {0}".format(argv))
parse_quality_args(argv)

def test_parse_with_exclude(self):
argv = ['--violations', 'pep8']
arg_dict = parse_quality_args(argv)
self.assertEqual(arg_dict.get('exclude'), None)

argv = ['--violations', 'pep8', '--exclude', 'noneed/*.py']

arg_dict = parse_quality_args(argv)
self.assertEqual(arg_dict.get('exclude'), ['noneed/*.py'])

argv = ['--violations', 'pep8', '--exclude', 'noneed/*.py',
'other/**/*.py']

arg_dict = parse_quality_args(argv)
self.assertEqual(arg_dict.get('exclude'),
['noneed/*.py', 'other/**/*.py'])


class MainTest(unittest.TestCase):
"""Tests for the main() function in tool.py"""
Expand Down
36 changes: 35 additions & 1 deletion diff_cover/tests/test_diff_reporter.py
Expand Up @@ -53,6 +53,26 @@ def test_name_ignore_staged_and_unstaged(self):
'origin/master...HEAD'
)

def test_git_exclude(self):
self.diff = GitDiffReporter(git_diff=self._git_diff, exclude=['file1.py'])

# Configure the git diff output
self._set_git_diff_output(
git_diff_output({'subdir1/file1.py': line_numbers(3, 10) + line_numbers(34, 47)}),
git_diff_output({'subdir2/file2.py': line_numbers(3, 10), 'file3.py': [0]}),
git_diff_output(dict(), deleted_files=['README.md'])
)

# Get the source paths in the diff
source_paths = self.diff.src_paths_changed()

# Validate the source paths
# They should be in alphabetical order
self.assertEqual(len(source_paths), 3)
self.assertEqual('file3.py', source_paths[0])
self.assertEqual('README.md', source_paths[1])
self.assertEqual('subdir2/file2.py', source_paths[2])

def test_git_source_paths(self):

# Configure the git diff output
Expand Down Expand Up @@ -447,7 +467,6 @@ def test_ignore_unstaged_inclusion(self):
self.assertEqual(2, len(self.diff._get_included_diff_results()))
self.assertEqual(['', ''], self.diff._get_included_diff_results())


def test_ignore_staged_and_unstaged_inclusion(self):
self.diff = GitDiffReporter(git_diff=self._git_diff, ignore_staged=True, ignore_unstaged=True)

Expand All @@ -458,6 +477,21 @@ def test_ignore_staged_and_unstaged_inclusion(self):
self.assertEqual(1, len(self.diff._get_included_diff_results()))
self.assertEqual([''], self.diff._get_included_diff_results())

def test_fnmatch(self):
"""Verify that our fnmatch wrapper works as expected."""
self.assertEqual(self.diff._fnmatch('foo.py', []), True)
self.assertEqual(self.diff._fnmatch('foo.py', ['*.pyc']), False)
self.assertEqual(self.diff._fnmatch('foo.pyc', ['*.pyc']), True)
self.assertEqual(
self.diff._fnmatch('foo.pyc', ['*.swp', '*.pyc', '*.py']), True)

def test_fnmatch_returns_the_default_with_empty_default(self):
"""The default parameter should be returned when no patterns are given.
"""
sentinel = object()
self.assertTrue(
self.diff._fnmatch('file.py', [], default=sentinel) is sentinel)

def _set_git_diff_output(self, committed_diff,
staged_diff, unstaged_diff):
"""
Expand Down
45 changes: 38 additions & 7 deletions diff_cover/tool.py
Expand Up @@ -46,6 +46,7 @@
FAIL_UNDER_HELP = "Returns an error code if coverage or quality score is below this value"
IGNORE_STAGED_HELP = "Ignores staged changes"
IGNORE_UNSTAGED_HELP = "Ignores unstaged changes"
EXCLUDE_HELP = "Exclude files, more patterns supported"


LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -105,8 +106,8 @@ def parse_coverage_args(argv):
type=float,
default='0',
help=FAIL_UNDER_HELP
)
)

parser.add_argument(
'--ignore-staged',
action='store_true',
Expand All @@ -121,6 +122,14 @@ def parse_coverage_args(argv):
help=IGNORE_UNSTAGED_HELP
)

parser.add_argument(
'--exclude',
metavar='EXCLUDE',
type=str,
nargs='+',
help=EXCLUDE_HELP
)

return vars(parser.parse_args(argv))


Expand Down Expand Up @@ -196,7 +205,7 @@ def parse_quality_args(argv):
default='0',
help=FAIL_UNDER_HELP
)

parser.add_argument(
'--ignore-staged',
action='store_true',
Expand All @@ -211,14 +220,27 @@ def parse_quality_args(argv):
help=IGNORE_UNSTAGED_HELP
)

parser.add_argument(
'--exclude',
metavar='EXCLUDE',
type=str,
nargs='+',
help=EXCLUDE_HELP
)

return vars(parser.parse_args(argv))


def generate_coverage_report(coverage_xml, compare_branch, html_report=None, css_file=None, ignore_staged=False, ignore_unstaged=False):
def generate_coverage_report(coverage_xml, compare_branch,
html_report=None, css_file=None,
ignore_staged=False, ignore_unstaged=False,
exclude=None):
"""
Generate the diff coverage report, using kwargs from `parse_args()`.
"""
diff = GitDiffReporter(compare_branch, git_diff=GitDiffTool(), ignore_staged=ignore_staged, ignore_unstaged=ignore_unstaged)
diff = GitDiffReporter(
compare_branch, git_diff=GitDiffTool(), ignore_staged=ignore_staged,
ignore_unstaged=ignore_unstaged, exclude=exclude)

xml_roots = [cElementTree.parse(xml_root) for xml_root in coverage_xml]
coverage = XmlCoverageReporter(xml_roots)
Expand All @@ -243,11 +265,18 @@ def generate_coverage_report(coverage_xml, compare_branch, html_report=None, css
return reporter.total_percent_covered()


def generate_quality_report(tool, compare_branch, html_report=None, css_file=None, ignore_staged=False, ignore_unstaged=False):
def generate_quality_report(tool, compare_branch,
html_report=None, css_file=None,
ignore_staged=False, ignore_unstaged=False,
exclude=None):
"""
Generate the quality report, using kwargs from `parse_args()`.
"""
diff = GitDiffReporter(compare_branch, git_diff=GitDiffTool(), ignore_staged=ignore_staged, ignore_unstaged=ignore_unstaged, supported_extensions=tool.driver.supported_extensions)
diff = GitDiffReporter(
compare_branch, git_diff=GitDiffTool(),
ignore_staged=ignore_staged, ignore_unstaged=ignore_unstaged,
supported_extensions=tool.driver.supported_extensions,
exclude=exclude)

if html_report is not None:
css_url = css_file
Expand Down Expand Up @@ -302,6 +331,7 @@ def main(argv=None, directory=None):
css_file=arg_dict['external_css_file'],
ignore_staged=arg_dict['ignore_staged'],
ignore_unstaged=arg_dict['ignore_unstaged'],
exclude=arg_dict['exclude'],
)

if percent_covered >= fail_under:
Expand Down Expand Up @@ -343,6 +373,7 @@ def main(argv=None, directory=None):
css_file=arg_dict['external_css_file'],
ignore_staged=arg_dict['ignore_staged'],
ignore_unstaged=arg_dict['ignore_unstaged'],
exclude=arg_dict['exclude'],
)
if percent_passing >= fail_under:
return 0
Expand Down

0 comments on commit 97c9443

Please sign in to comment.