diff --git a/diff_cover/diff_cover_tool.py b/diff_cover/diff_cover_tool.py index e51f1c6c..37ced210 100644 --- a/diff_cover/diff_cover_tool.py +++ b/diff_cover/diff_cover_tool.py @@ -18,14 +18,14 @@ from diff_cover.diff_reporter import GitDiffReporter from diff_cover.git_diff import GitDiffTool from diff_cover.git_path import GitPathTool -from diff_cover.report_generator import HtmlReportGenerator, StringReportGenerator, JsonReportGenerator +from diff_cover.report_generator import HtmlReportGenerator, StringReportGenerator from diff_cover.violationsreporters.violations_reporter import XmlCoverageReporter HTML_REPORT_HELP = "Diff coverage HTML output" -JSON_REPORT_HELP = "Diff coverage JSON output" COMPARE_BRANCH_HELP = "Branch to compare" CSS_FILE_HELP = "Write CSS into an external file" FAIL_UNDER_HELP = "Returns an error code if coverage or quality score is below this value" +MIN_DIFF_LINE_HELP = "Ignore --fail-under if the changed lines in diff are less than this value" IGNORE_STAGED_HELP = "Ignores staged changes" IGNORE_UNSTAGED_HELP = "Ignores unstaged changes" EXCLUDE_HELP = "Exclude files, more patterns supported" @@ -44,11 +44,10 @@ def parse_coverage_args(argv): { 'coverage_xml': COVERAGE_XML, 'html_report': None | HTML_REPORT, - 'json_report': None | JSON_REPORT, 'external_css_file': None | CSS_FILE, } - where `COVERAGE_XML`, `HTML_REPORT`, `JSON_REPORT`, and `CSS_FILE` are paths. + where `COVERAGE_XML`, `HTML_REPORT`, and `CSS_FILE` are paths. The path strings may or may not exist. """ @@ -61,9 +60,7 @@ def parse_coverage_args(argv): nargs='+' ) - output_format = parser.add_mutually_exclusive_group() - - output_format.add_argument( + parser.add_argument( '--html-report', metavar='FILENAME', type=str, @@ -71,14 +68,6 @@ def parse_coverage_args(argv): help=HTML_REPORT_HELP ) - output_format.add_argument( - '--json-report', - metavar='FILENAME', - type=str, - default=None, - help=JSON_REPORT_HELP - ) - parser.add_argument( '--external-css-file', metavar='FILENAME', @@ -103,6 +92,14 @@ def parse_coverage_args(argv): help=FAIL_UNDER_HELP ) + parser.add_argument( + '--min-diff-line', + metavar='SCORE', + type=int, + default='0', + help=MIN_DIFF_LINE_HELP + ) + parser.add_argument( '--ignore-staged', action='store_true', @@ -154,7 +151,6 @@ def parse_coverage_args(argv): def generate_coverage_report(coverage_xml, compare_branch, html_report=None, css_file=None, - json_report=None, ignore_staged=False, ignore_unstaged=False, exclude=None, src_roots=None, diff_range_notation=None): """ @@ -180,17 +176,12 @@ def generate_coverage_report(coverage_xml, compare_branch, with open(css_file, "wb") as output_file: reporter.generate_css(output_file) - elif json_report is not None: - reporter = JsonReportGenerator(coverage, diff) - with open(json_report, "wb") as output_file: - reporter.generate_report(output_file) - reporter = StringReportGenerator(coverage, diff) output_file = sys.stdout if six.PY2 else sys.stdout.buffer # Generate the report reporter.generate_report(output_file) - return reporter.total_percent_covered() + return reporter.total_num_lines(), reporter.total_percent_covered() def main(argv=None, directory=None): @@ -207,11 +198,11 @@ def main(argv=None, directory=None): arg_dict = parse_coverage_args(argv[1:]) GitPathTool.set_cwd(directory) fail_under = arg_dict.get('fail_under') - percent_covered = generate_coverage_report( + min_diff_line = arg_dict.get('min_diff_line') + total_diff_line, percent_covered = generate_coverage_report( arg_dict['coverage_xml'], arg_dict['compare_branch'], html_report=arg_dict['html_report'], - json_report=arg_dict['json_report'], css_file=arg_dict['external_css_file'], ignore_staged=arg_dict['ignore_staged'], ignore_unstaged=arg_dict['ignore_unstaged'], @@ -220,7 +211,7 @@ def main(argv=None, directory=None): diff_range_notation=arg_dict['diff_range_notation'] ) - if percent_covered >= fail_under: + if total_diff_line <= min_diff_line or percent_covered >= fail_under: return 0 else: LOGGER.error("Failure. Coverage is below {}%.".format(fail_under)) diff --git a/diff_cover/report_generator.py b/diff_cover/report_generator.py index 80eb9e13..249990ce 100644 --- a/diff_cover/report_generator.py +++ b/diff_cover/report_generator.py @@ -7,7 +7,6 @@ from jinja2_pluralize import pluralize_dj from diff_cover.snippets import Snippet import six -import json class DiffViolations(object): @@ -168,44 +167,15 @@ def _diff_violations(self): """ if not self._diff_violations_dict: self._diff_violations_dict = { - src_path: DiffViolations( - self._violations.violations(src_path), - self._violations.measured_lines(src_path), - self._diff.lines_changed(src_path), - ) - for src_path in self._diff.src_paths_changed() + src_path: DiffViolations( + self._violations.violations(src_path), + self._violations.measured_lines(src_path), + self._diff.lines_changed(src_path), + ) + for src_path in self._diff.src_paths_changed() } return self._diff_violations_dict - def report_dict(self): - src_stats = { - src: self._src_path_stats(src) for src in self.src_paths() - } - - return { - 'report_name': self.coverage_report_name(), - 'diff_name': self.diff_report_name(), - 'src_stats': src_stats, - 'total_num_lines': self.total_num_lines(), - 'total_num_violations': self.total_num_violations(), - 'total_percent_covered': self.total_percent_covered(), - } - - def _src_path_stats(self, src_path): - """ - Return a dict of statistics for the source file at `src_path`. - """ - - # Find violation lines - violation_lines = self.violation_lines(src_path) - violations = sorted(self._diff_violations()[src_path].violations) - - return { - 'percent_covered': self.percent_covered(src_path), - 'violation_lines': violation_lines, - 'violations': violations, - } - # Set up the template environment TEMPLATE_LOADER = PackageLoader(__package__) @@ -216,15 +186,6 @@ def _src_path_stats(self, src_path): TEMPLATE_ENV.filters['pluralize'] = pluralize_dj -class JsonReportGenerator(BaseReportGenerator): - def generate_report(self, output_file): - json_report_str = json.dumps(self.report_dict()) - - # all report generators are expected to write raw bytes, so we encode - # the json - output_file.write(json_report_str.encode('utf-8')) - - class TemplateReportGenerator(BaseReportGenerator): """ Reporter that uses a template to generate the report. @@ -293,6 +254,11 @@ def _context(self): } """ + # Calculate the information to pass to the template + src_stats = { + src: self._src_path_stats(src) for src in self.src_paths() + } + # Include snippet style info if we're displaying # source code snippets if self.INCLUDE_SNIPPETS: @@ -300,13 +266,16 @@ def _context(self): else: snippet_style = None - context = super(TemplateReportGenerator, self).report_dict() - context.update({ + return { 'css_url': self.css_url, + 'report_name': self.coverage_report_name(), + 'diff_name': self.diff_report_name(), + 'src_stats': src_stats, + 'total_num_lines': self.total_num_lines(), + 'total_num_violations': self.total_num_violations(), + 'total_percent_covered': self.total_percent_covered(), 'snippet_style': snippet_style - }) - - return context + } @staticmethod def combine_adjacent_lines(line_numbers): @@ -338,25 +307,30 @@ def combine_adjacent_lines(line_numbers): return combined_list def _src_path_stats(self, src_path): + """ + Return a dict of statistics for the source file at `src_path`. + """ - stats = super(TemplateReportGenerator, self)._src_path_stats(src_path) + # Find violation lines + violation_lines = self.violation_lines(src_path) + violations = sorted(self._diff_violations()[src_path].violations) # Load source snippets (if the report will display them) # If we cannot load the file, then fail gracefully if self.INCLUDE_SNIPPETS: try: - snippets = Snippet.load_snippets_html(src_path, stats['violation_lines']) + snippets = Snippet.load_snippets_html(src_path, violation_lines) except IOError: snippets = [] else: snippets = [] - stats.update({ - 'snippets_html': snippets, - 'violation_lines': TemplateReportGenerator.combine_adjacent_lines(stats['violation_lines']), - }) - - return stats + return { + 'percent_covered': self.percent_covered(src_path), + 'violation_lines': TemplateReportGenerator.combine_adjacent_lines(violation_lines), + 'violations': violations, + 'snippets_html': snippets + } class StringReportGenerator(TemplateReportGenerator):