From 2fc54e9d8919fc64ec292999fa0ab18138032029 Mon Sep 17 00:00:00 2001 From: Tim Krins Date: Fri, 10 Jan 2025 21:43:12 +0000 Subject: [PATCH 1/6] Add --github-warning-annotations argument and template --- diff_cover/diff_cover_tool.py | 16 ++++++++++++++++ diff_cover/report_generator.py | 8 ++++++++ .../github_coverage_warning_annotations.txt | 9 +++++++++ 3 files changed, 33 insertions(+) create mode 100644 diff_cover/templates/github_coverage_warning_annotations.txt diff --git a/diff_cover/diff_cover_tool.py b/diff_cover/diff_cover_tool.py index 97b4204f..44ff7e73 100644 --- a/diff_cover/diff_cover_tool.py +++ b/diff_cover/diff_cover_tool.py @@ -15,6 +15,7 @@ JsonReportGenerator, MarkdownReportGenerator, StringReportGenerator, + GitHubWarningAnnotationsReportGenerator, ) from diff_cover.violationsreporters.violations_reporter import ( LcovCoverageReporter, @@ -24,6 +25,7 @@ HTML_REPORT_HELP = "Diff coverage HTML output" JSON_REPORT_HELP = "Diff coverage JSON output" MARKDOWN_REPORT_HELP = "Diff coverage Markdown output" +GITHUB_WARNING_ANNOTATIONS_HELP = "Diff coverage GitHub warning annotations output" COMPARE_BRANCH_HELP = "Branch to compare" CSS_FILE_HELP = "Write CSS into an external file" FAIL_UNDER_HELP = ( @@ -92,6 +94,13 @@ def parse_coverage_args(argv): help=MARKDOWN_REPORT_HELP, ) + parser.add_argument( + "--github-warning-annotations", + metavar="FILENAME", + type=str, + help=GITHUB_WARNING_ANNOTATIONS_HELP, + ) + parser.add_argument( "--show-uncovered", action="store_true", default=None, help=SHOW_UNCOVERED ) @@ -207,6 +216,7 @@ def generate_coverage_report( css_file=None, json_report=None, markdown_report=None, + github_warning_annotations=None, ignore_staged=False, ignore_unstaged=False, include_untracked=False, @@ -269,6 +279,11 @@ def generate_coverage_report( with open(markdown_report, "wb") as output_file: reporter.generate_report(output_file) + if github_warning_annotations is not None: + reporter = GitHubWarningAnnotationsReportGenerator(coverage, diff) + with open(github_warning_annotations, "wb") as output_file: + reporter.generate_report(output_file) + # Generate the report for stdout reporter = StringReportGenerator(coverage, diff, show_uncovered) output_file = io.BytesIO() if quiet else sys.stdout.buffer @@ -311,6 +326,7 @@ def main(argv=None, directory=None): html_report=arg_dict["html_report"], json_report=arg_dict["json_report"], markdown_report=arg_dict["markdown_report"], + github_warning_annotations=arg_dict["github_warning_annotations"], css_file=arg_dict["external_css_file"], ignore_staged=arg_dict["ignore_staged"], ignore_unstaged=arg_dict["ignore_unstaged"], diff --git a/diff_cover/report_generator.py b/diff_cover/report_generator.py index 132f9d93..1f20b143 100644 --- a/diff_cover/report_generator.py +++ b/diff_cover/report_generator.py @@ -419,6 +419,14 @@ def __init__(self, violations_reporter, diff_reporter, show_uncovered=False): super().__init__(violations_reporter, diff_reporter) self.include_snippets = show_uncovered +class GitHubWarningAnnotationsReportGenerator(TemplateReportGenerator): + """ + Generate a diff coverage report for GitHub warning annotations. + https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-warning-message + """ + + template_path = "github_coverage_warning_annotations.txt" + class HtmlReportGenerator(TemplateReportGenerator): """ diff --git a/diff_cover/templates/github_coverage_warning_annotations.txt b/diff_cover/templates/github_coverage_warning_annotations.txt new file mode 100644 index 00000000..38fd30bd --- /dev/null +++ b/diff_cover/templates/github_coverage_warning_annotations.txt @@ -0,0 +1,9 @@ +{% if src_stats %} +{% for src_path, stats in src_stats|dictsort %} +{% if stats.percent_covered < 100 %} +{% for line in stats.violation_lines %} +::warning file={{ src_path }},line={{ line }},title=Missing Coverage::Line {{ line }} missing coverage +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} From 2522160b100df0180d50788612e099dcd5d3cf64 Mon Sep 17 00:00:00 2001 From: Tim Krins Date: Fri, 10 Jan 2025 21:49:59 +0000 Subject: [PATCH 2/6] Add test for GitHubWarningAnnotationsReportGenerator --- tests/test_report_generator.py | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_report_generator.py b/tests/test_report_generator.py index 926d82bb..3f052428 100644 --- a/tests/test_report_generator.py +++ b/tests/test_report_generator.py @@ -15,6 +15,7 @@ MarkdownReportGenerator, StringReportGenerator, TemplateReportGenerator, + GitHubWarningAnnotationsReportGenerator, ) from diff_cover.violationsreporters.violations_reporter import ( BaseViolationReporter, @@ -415,6 +416,42 @@ def test_empty_report(self): self.assert_report(expected) +class TestGitHubWarningAnnotationsReportGenerator(BaseReportGeneratorTest): + REPORT_GENERATOR_CLASS = GitHubWarningAnnotationsReportGenerator + + def test_generate_report(self): + # Generate a default report + self.use_default_values() + + # Verify that we got the expected string + expected = dedent( + """ + ::warning file=file1.py,line=10-11,title=Missing Coverage::Line 10-11 missing coverage + ::warning file=subdir/file2.py,line=10-11,title=Missing Coverage::Line 10-11 missing coverage + """ + ).strip() + + self.assert_report(expected) + + def test_hundred_percent(self): + # Have the dependencies return an empty report + self.set_src_paths_changed(["file.py"]) + self.set_lines_changed("file.py", list(range(0, 100))) + self.set_violations("file.py", []) + self.set_measured("file.py", [2]) + + expected = "" + + self.assert_report(expected) + + def test_empty_report(self): + # Have the dependencies return an empty report + # (this is the default) + + expected = "" + + self.assert_report(expected) + class TestHtmlReportGenerator(BaseReportGeneratorTest): REPORT_GENERATOR_CLASS = HtmlReportGenerator From 7eac78e58b71fee2f34fe9c7b9a356f1a26b4a05 Mon Sep 17 00:00:00 2001 From: Tim Krins Date: Fri, 10 Jan 2025 23:26:28 +0000 Subject: [PATCH 3/6] Print GitHub annotations to stdout --- diff_cover/diff_cover_tool.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/diff_cover/diff_cover_tool.py b/diff_cover/diff_cover_tool.py index 44ff7e73..8422ad9b 100644 --- a/diff_cover/diff_cover_tool.py +++ b/diff_cover/diff_cover_tool.py @@ -25,7 +25,7 @@ HTML_REPORT_HELP = "Diff coverage HTML output" JSON_REPORT_HELP = "Diff coverage JSON output" MARKDOWN_REPORT_HELP = "Diff coverage Markdown output" -GITHUB_WARNING_ANNOTATIONS_HELP = "Diff coverage GitHub warning annotations output" +GITHUB_WARNING_ANNOTATIONS_HELP = "Print diff coverage GitHub warning annotations to the console" COMPARE_BRANCH_HELP = "Branch to compare" CSS_FILE_HELP = "Write CSS into an external file" FAIL_UNDER_HELP = ( @@ -96,8 +96,8 @@ def parse_coverage_args(argv): parser.add_argument( "--github-warning-annotations", - metavar="FILENAME", - type=str, + action="store_true", + default=None, help=GITHUB_WARNING_ANNOTATIONS_HELP, ) @@ -216,7 +216,7 @@ def generate_coverage_report( css_file=None, json_report=None, markdown_report=None, - github_warning_annotations=None, + github_warning_annotations=False, ignore_staged=False, ignore_unstaged=False, include_untracked=False, @@ -279,10 +279,9 @@ def generate_coverage_report( with open(markdown_report, "wb") as output_file: reporter.generate_report(output_file) - if github_warning_annotations is not None: + if github_warning_annotations: reporter = GitHubWarningAnnotationsReportGenerator(coverage, diff) - with open(github_warning_annotations, "wb") as output_file: - reporter.generate_report(output_file) + reporter.generate_report(sys.stdout.buffer) # Generate the report for stdout reporter = StringReportGenerator(coverage, diff, show_uncovered) From f12ee55b941af7d558446098f692a8aff0f2cb5f Mon Sep 17 00:00:00 2001 From: Tim Krins Date: Sat, 11 Jan 2025 00:04:45 +0000 Subject: [PATCH 4/6] Support multiline Github annotations and add test for single line --- .../github_coverage_warning_annotations.txt | 3 ++- tests/test_report_generator.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/diff_cover/templates/github_coverage_warning_annotations.txt b/diff_cover/templates/github_coverage_warning_annotations.txt index 38fd30bd..13c0646b 100644 --- a/diff_cover/templates/github_coverage_warning_annotations.txt +++ b/diff_cover/templates/github_coverage_warning_annotations.txt @@ -2,7 +2,8 @@ {% for src_path, stats in src_stats|dictsort %} {% if stats.percent_covered < 100 %} {% for line in stats.violation_lines %} -::warning file={{ src_path }},line={{ line }},title=Missing Coverage::Line {{ line }} missing coverage +{% set splitLines = line.split("-") %} +::warning file={{ src_path }},line={{ splitLines[0] }}{% if splitLines[1] %},endLine={{ splitLines[1] }}{% endif %},title=Missing Coverage::Line {{ line }} missing coverage {% endfor %} {% endif %} {% endfor %} diff --git a/tests/test_report_generator.py b/tests/test_report_generator.py index 3f052428..b8c1e1b7 100644 --- a/tests/test_report_generator.py +++ b/tests/test_report_generator.py @@ -426,8 +426,23 @@ def test_generate_report(self): # Verify that we got the expected string expected = dedent( """ - ::warning file=file1.py,line=10-11,title=Missing Coverage::Line 10-11 missing coverage - ::warning file=subdir/file2.py,line=10-11,title=Missing Coverage::Line 10-11 missing coverage + ::warning file=file1.py,line=10,endLine=11,title=Missing Coverage::Line 10-11 missing coverage + ::warning file=subdir/file2.py,line=10,endLine=11,title=Missing Coverage::Line 10-11 missing coverage + """ + ).strip() + + self.assert_report(expected) + + def test_single_line(self): + self.set_src_paths_changed(["file.py"]) + self.set_lines_changed("file.py", list(range(0, 100))) + self.set_violations("file.py", [Violation(10, None)]) + self.set_measured("file.py", [2]) + + # Verify that we got the expected string + expected = dedent( + """ + ::warning file=file.py,line=10,title=Missing Coverage::Line 10 missing coverage """ ).strip() From bc245b06a8566c5df4560712ff755bda119db9db Mon Sep 17 00:00:00 2001 From: Tim Krins Date: Sun, 12 Jan 2025 11:21:06 +0000 Subject: [PATCH 5/6] Switch argument to use `store_false` --- diff_cover/diff_cover_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff_cover/diff_cover_tool.py b/diff_cover/diff_cover_tool.py index 8422ad9b..e179d40a 100644 --- a/diff_cover/diff_cover_tool.py +++ b/diff_cover/diff_cover_tool.py @@ -96,7 +96,7 @@ def parse_coverage_args(argv): parser.add_argument( "--github-warning-annotations", - action="store_true", + action="store_false", default=None, help=GITHUB_WARNING_ANNOTATIONS_HELP, ) From 46661afe0bf04de9b33780bdd842ba09e74ef75f Mon Sep 17 00:00:00 2001 From: Tim Krins Date: Tue, 21 Jan 2025 14:04:50 +0000 Subject: [PATCH 6/6] Switch store_false to store_true as I misunderstood the default --- diff_cover/diff_cover_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff_cover/diff_cover_tool.py b/diff_cover/diff_cover_tool.py index e179d40a..8422ad9b 100644 --- a/diff_cover/diff_cover_tool.py +++ b/diff_cover/diff_cover_tool.py @@ -96,7 +96,7 @@ def parse_coverage_args(argv): parser.add_argument( "--github-warning-annotations", - action="store_false", + action="store_true", default=None, help=GITHUB_WARNING_ANNOTATIONS_HELP, )