diff-cover 10.3.0__tar.gz → 10.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. {diff_cover-10.3.0 → diff_cover-10.4.0}/PKG-INFO +1 -1
  2. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/diff_cover_tool.py +17 -1
  3. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/violationsreporters/violations_reporter.py +46 -16
  4. {diff_cover-10.3.0 → diff_cover-10.4.0}/pyproject.toml +1 -1
  5. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_diff_cover_tool.py +10 -0
  6. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_violations_reporter.py +69 -0
  7. {diff_cover-10.3.0 → diff_cover-10.4.0}/LICENSE +0 -0
  8. {diff_cover-10.3.0 → diff_cover-10.4.0}/NOTICE +0 -0
  9. {diff_cover-10.3.0 → diff_cover-10.4.0}/README.rst +0 -0
  10. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/__init__.py +0 -0
  11. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/command_runner.py +0 -0
  12. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/config_parser.py +0 -0
  13. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/diff_quality_tool.py +0 -0
  14. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/diff_reporter.py +0 -0
  15. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/git_diff.py +0 -0
  16. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/git_path.py +0 -0
  17. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/hook.py +0 -0
  18. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/hookspecs.py +0 -0
  19. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/report_generator.py +0 -0
  20. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/snippets.py +0 -0
  21. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/console_coverage_report.txt +0 -0
  22. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/console_quality_report.txt +0 -0
  23. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/external_style.css +0 -0
  24. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/github_coverage_annotations.txt +0 -0
  25. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/html_coverage_report.html +0 -0
  26. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/html_quality_report.html +0 -0
  27. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/markdown_coverage_report.md +0 -0
  28. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/markdown_quality_report.md +0 -0
  29. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/snippet_content.html +0 -0
  30. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/snippet_content.md +0 -0
  31. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/snippet_content.txt +0 -0
  32. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/templates/snippet_style.html +0 -0
  33. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/util.py +0 -0
  34. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/violationsreporters/__init__.py +0 -0
  35. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/violationsreporters/base.py +0 -0
  36. {diff_cover-10.3.0 → diff_cover-10.4.0}/diff_cover/violationsreporters/java_violations_reporter.py +0 -0
  37. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/__init__.py +0 -0
  38. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/conftest.py +0 -0
  39. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/add_console_report.txt +0 -0
  40. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/add_html_report.html +0 -0
  41. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/add_json_report.json +0 -0
  42. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/add_json_report_float.json +0 -0
  43. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/add_markdown_report.md +0 -0
  44. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/changed_console_report.txt +0 -0
  45. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/changed_html_report.html +0 -0
  46. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/coverage.xml +0 -0
  47. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/coverage1.xml +0 -0
  48. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/coverage2.xml +0 -0
  49. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/coverage_missing_lines.xml +0 -0
  50. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/cpp_functions_console_report.txt +0 -0
  51. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/cpp_functions_coverage.lcov +0 -0
  52. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/delete_console_report.txt +0 -0
  53. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/delete_html_report.html +0 -0
  54. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/dotnet_coverage.xml +0 -0
  55. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/empty.txt +0 -0
  56. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/empty_pycodestyle_violations.txt +0 -0
  57. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/expand_console_report.txt +0 -0
  58. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/external_css_html_report.html +0 -0
  59. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/external_style.css +0 -0
  60. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_add.txt +0 -0
  61. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_changed.txt +0 -0
  62. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_code_dupe.txt +0 -0
  63. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_cpp.txt +0 -0
  64. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_cpp_functions.txt +0 -0
  65. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_delete.txt +0 -0
  66. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_dotnet.txt +0 -0
  67. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_external_css.txt +0 -0
  68. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_lua.txt +0 -0
  69. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_moved.txt +0 -0
  70. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_mult.txt +0 -0
  71. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_python.txt +0 -0
  72. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_subdir.txt +0 -0
  73. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_typescript.txt +0 -0
  74. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_typescript_branches.txt +0 -0
  75. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_unicode.txt +0 -0
  76. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_violations.txt +0 -0
  77. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/git_diff_violations_two_files.txt +0 -0
  78. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/hello.py +0 -0
  79. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/hi.py +0 -0
  80. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/html_report.html +0 -0
  81. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/html_report_empty.html +0 -0
  82. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/html_report_one_snippet.html +0 -0
  83. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/html_report_two_snippets.html +0 -0
  84. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/lcov.info +0 -0
  85. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/lua_console_report.txt +0 -0
  86. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/luacoverage.xml +0 -0
  87. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/markdown_report_one_snippet.md +0 -0
  88. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/markdown_report_two_snippets.md +0 -0
  89. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/moved_console_report.txt +0 -0
  90. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/moved_coverage.xml +0 -0
  91. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/moved_html_report.html +0 -0
  92. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/mult_inputs_console_report.txt +0 -0
  93. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/mult_inputs_html_report.html +0 -0
  94. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pycodestyle_report.txt +0 -0
  95. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pycodestyle_violations_report.html +0 -0
  96. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pycodestyle_violations_report.json +0 -0
  97. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pycodestyle_violations_report.md +0 -0
  98. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pycodestyle_violations_report.txt +0 -0
  99. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pycodestyle_violations_report_external_css.html +0 -0
  100. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pycodestyle_violations_report_float.json +0 -0
  101. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pyflakes_two_files.txt +0 -0
  102. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pyflakes_violations_report.html +0 -0
  103. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pyflakes_violations_report.txt +0 -0
  104. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pylint_dupe.txt +0 -0
  105. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pylint_dupe_violations_report.txt +0 -0
  106. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pylint_report.txt +0 -0
  107. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pylint_violations_console_report.txt +0 -0
  108. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pylint_violations_report.html +0 -0
  109. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pylint_violations_report.txt +0 -0
  110. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/pylintrc +0 -0
  111. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/real_cpp_console_report.txt +0 -0
  112. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/real_cpp_coverage.lcov +0 -0
  113. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/real_python_console_report.txt +0 -0
  114. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/real_python_coverage.lcov +0 -0
  115. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/real_typescript_console_report.txt +0 -0
  116. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/real_typescript_coverage.lcov +0 -0
  117. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/show_uncovered_lines_console.txt +0 -0
  118. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet.css +0 -0
  119. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_8859.py +0 -0
  120. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_arabic_output.html +0 -0
  121. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_default.html +0 -0
  122. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_invalid_violations.html +0 -0
  123. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_list.html +0 -0
  124. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_list.md +0 -0
  125. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_list2.md +0 -0
  126. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_list3.md +0 -0
  127. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_no_filename_ext.html +0 -0
  128. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_src.py +0 -0
  129. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_src2.cpp +0 -0
  130. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_src3.cpp +0 -0
  131. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_unicode.html +0 -0
  132. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_unicode.py +0 -0
  133. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/snippet_unicode_html_output.html +0 -0
  134. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/subdir_coverage_console_report.txt +0 -0
  135. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/subdir_coverage_html_report.html +0 -0
  136. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/test_src.txt +0 -0
  137. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/typescript_branches_console_report.txt +0 -0
  138. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/typescript_branches_coverage.lcov +0 -0
  139. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/unicode_console_report.txt +0 -0
  140. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/unicode_coverage.xml +0 -0
  141. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/unicode_html_report.html +0 -0
  142. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/unicode_test_src.txt +0 -0
  143. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/fixtures/violations_test_file.py +0 -0
  144. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/helpers.py +0 -0
  145. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/snippet_list_unicode.html +0 -0
  146. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_clover_violations_reporter/test.xml +0 -0
  147. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_clover_violations_reporter.py +0 -0
  148. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_config_parser.py +0 -0
  149. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_diff_cover_main.py +0 -0
  150. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_diff_quality_main.py +0 -0
  151. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_diff_reporter.py +0 -0
  152. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_git_diff.py +0 -0
  153. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_git_diff_file.py +0 -0
  154. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_git_path.py +0 -0
  155. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_integration.py +0 -0
  156. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_java_violations_reporter.py +0 -0
  157. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_report_generator.py +0 -0
  158. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_snippets.py +0 -0
  159. {diff_cover-10.3.0 → diff_cover-10.4.0}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: diff_cover
3
- Version: 10.3.0
3
+ Version: 10.4.0
4
4
  Summary: Run coverage and linting reports on diffs
5
5
  License: Apache-2.0
6
6
  Author: See Contributors
@@ -52,6 +52,10 @@ SHOW_COVERED_HELP = (
52
52
  EXPAND_COVERAGE_REPORT = (
53
53
  "Append missing lines in coverage reports based on the hits of the previous line."
54
54
  )
55
+ BRANCH_COVERAGE_HELP = (
56
+ "Treat partially covered branches as uncovered. Requires a coverage report "
57
+ 'with branch coverage data (Cobertura branch="true" condition-coverage).'
58
+ )
55
59
  INCLUDE_UNTRACKED_HELP = "Include untracked files"
56
60
  CONFIG_FILE_HELP = "The configuration file to use"
57
61
  DIFF_FILE_HELP = "The diff file to use"
@@ -117,6 +121,13 @@ def parse_coverage_args(argv):
117
121
  help=EXPAND_COVERAGE_REPORT,
118
122
  )
119
123
 
124
+ parser.add_argument(
125
+ "--branch-coverage",
126
+ action="store_true",
127
+ default=None,
128
+ help=BRANCH_COVERAGE_HELP,
129
+ )
130
+
120
131
  parser.add_argument(
121
132
  "--external-css-file",
122
133
  metavar="FILENAME",
@@ -215,6 +226,7 @@ def parse_coverage_args(argv):
215
226
  "diff_range_notation": "...",
216
227
  "quiet": False,
217
228
  "expand_coverage_report": False,
229
+ "branch_coverage": False,
218
230
  "total_percent_float": False,
219
231
  }
220
232
 
@@ -237,6 +249,7 @@ def generate_coverage_report(
237
249
  show_uncovered=False,
238
250
  show_covered=False,
239
251
  expand_coverage_report=False,
252
+ branch_coverage=False,
240
253
  total_percent_float=False,
241
254
  ):
242
255
  """
@@ -265,7 +278,9 @@ def generate_coverage_report(
265
278
  if xml_roots and lcov_roots:
266
279
  raise ValueError("Mixing LCov and XML reports is not supported yet")
267
280
  if xml_roots:
268
- coverage = XmlCoverageReporter(xml_roots, src_roots, expand_coverage_report)
281
+ coverage = XmlCoverageReporter(
282
+ xml_roots, src_roots, expand_coverage_report, branch_coverage
283
+ )
269
284
  else:
270
285
  coverage = LcovCoverageReporter(lcov_roots, src_roots)
271
286
 
@@ -417,6 +432,7 @@ def main(argv=None, directory=None):
417
432
  show_uncovered=arg_dict["show_uncovered"],
418
433
  show_covered=arg_dict["show_covered"],
419
434
  expand_coverage_report=arg_dict["expand_coverage_report"],
435
+ branch_coverage=arg_dict["branch_coverage"],
420
436
  total_percent_float=arg_dict["total_percent_float"],
421
437
  )
422
438
 
@@ -24,7 +24,13 @@ class XmlCoverageReporter(BaseViolationReporter):
24
24
  Query information from a Cobertura|Clover|JaCoCo XML coverage report.
25
25
  """
26
26
 
27
- def __init__(self, xml_roots, src_roots=None, expand_coverage_report=False):
27
+ def __init__(
28
+ self,
29
+ xml_roots,
30
+ src_roots=None,
31
+ expand_coverage_report=False,
32
+ branch_coverage=False,
33
+ ):
28
34
  """
29
35
  Load the XML coverage report represented
30
36
  by the cElementTree with root element `xml_root`.
@@ -42,6 +48,11 @@ class XmlCoverageReporter(BaseViolationReporter):
42
48
 
43
49
  self._src_roots = src_roots or [""]
44
50
  self._expand_coverage_report = expand_coverage_report
51
+ self._branch_coverage = branch_coverage
52
+
53
+ # Pulls the "(covered/total)" pair out of Cobertura's
54
+ # condition-coverage attribute, e.g. "50% (1/2)".
55
+ self._cobertura_condition_coverage_re = re.compile(r"\((\d+)/(\d+)\)")
45
56
 
46
57
  def _get_xml_classes(self, xml_document):
47
58
  """
@@ -239,23 +250,19 @@ class XmlCoverageReporter(BaseViolationReporter):
239
250
  {_hits: last_hit_number, _number: line_number}
240
251
  )
241
252
 
242
- # First case, need to define violations initially
243
- if violations is None:
244
- violations = {
245
- Violation(int(line.get(_number)), None)
246
- for line in line_nodes
247
- if int(line.get(_hits, 0)) == 0
248
- }
253
+ new_violations = {
254
+ Violation(int(line.get(_number)), None)
255
+ for line in line_nodes
256
+ if self._is_violation(line, _hits)
257
+ }
249
258
 
250
- # If we already have a violations set,
251
- # take the intersection of the new
252
- # violations set and its old self
259
+ # The first report defines the violations set. Each subsequent
260
+ # report narrows it, since we only keep violations that show up
261
+ # in every report.
262
+ if violations is None:
263
+ violations = new_violations
253
264
  else:
254
- violations = violations & {
255
- Violation(int(line.get(_number)), None)
256
- for line in line_nodes
257
- if int(line.get(_hits, 0)) == 0
258
- }
265
+ violations = violations & new_violations
259
266
 
260
267
  # Measured is the union of itself and the new measured
261
268
  measured = measured | {int(line.get(_number)) for line in line_nodes}
@@ -267,6 +274,29 @@ class XmlCoverageReporter(BaseViolationReporter):
267
274
 
268
275
  self._info_cache[src_path] = (violations, measured)
269
276
 
277
+ def _is_violation(self, line, hits_attr):
278
+ """
279
+ Return whether a coverage `line` node counts as a violation.
280
+
281
+ A line is a violation if it was never executed, or -- when branch
282
+ coverage is enabled -- if it is a partially covered branch.
283
+ """
284
+ if int(line.get(hits_attr, 0)) == 0:
285
+ return True
286
+ return self._branch_coverage and self._is_partial_branch(line)
287
+
288
+ def _is_partial_branch(self, line):
289
+ """Return whether a Cobertura `line` node is a partially covered branch."""
290
+ if line.get("branch") != "true":
291
+ return False
292
+ match = self._cobertura_condition_coverage_re.search(
293
+ line.get("condition-coverage", "")
294
+ )
295
+ if not match:
296
+ return False
297
+ covered, total = int(match.group(1)), int(match.group(2))
298
+ return covered < total
299
+
270
300
  def violations(self, src_path):
271
301
  """
272
302
  See base class comments.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "diff_cover"
3
- version = "10.3.0"
3
+ version = "10.4.0"
4
4
  description = "Run coverage and linting reports on diffs"
5
5
  authors = ["See Contributors"]
6
6
  homepage = "https://github.com/Bachmann1234/diff-cover"
@@ -47,6 +47,16 @@ def test_show_covered_flag():
47
47
  assert arg_dict["show_covered"] is True
48
48
 
49
49
 
50
+ def test_branch_coverage_defaults_false():
51
+ arg_dict = parse_coverage_args(["reports/coverage.xml"])
52
+ assert arg_dict["branch_coverage"] is False
53
+
54
+
55
+ def test_branch_coverage_flag():
56
+ arg_dict = parse_coverage_args(["reports/coverage.xml", "--branch-coverage"])
57
+ assert arg_dict["branch_coverage"] is True
58
+
59
+
50
60
  def test_parse_with_multiple_reports():
51
61
  argv = [
52
62
  "reports/coverage.xml",
@@ -333,6 +333,75 @@ class TestXmlCoverageReporterTest:
333
333
 
334
334
  assert set() == coverage.violations("file1.java")
335
335
 
336
+ def test_partial_branch_is_violation_when_branch_coverage_enabled(self):
337
+ # A line that executed (hits=1) but only exercised 1 of its 2
338
+ # branches. With branch coverage enabled this should count as a
339
+ # violation, even though the statement itself ran.
340
+ xml = etree.fromstring("""<?xml version="1.0"?>
341
+ <coverage>
342
+ <packages><classes>
343
+ <class filename="file1.py">
344
+ <lines>
345
+ <line number="1" hits="1"/>
346
+ <line number="2" hits="1" branch="true"
347
+ condition-coverage="50% (1/2)"/>
348
+ <line number="3" hits="1" branch="true"
349
+ condition-coverage="100% (2/2)"/>
350
+ </lines>
351
+ </class>
352
+ </classes></packages>
353
+ </coverage>
354
+ """)
355
+
356
+ coverage = XmlCoverageReporter([xml], branch_coverage=True)
357
+
358
+ # Line 2 is a partially covered branch -> violation.
359
+ # Line 3 is a fully covered branch, line 1 a plain statement -> covered.
360
+ assert coverage.violations("file1.py") == {Violation(2, None)}
361
+ assert coverage.measured_lines("file1.py") == {1, 2, 3}
362
+
363
+ def test_partial_branch_ignored_by_default(self):
364
+ # Without branch coverage enabled, an executed-but-partially-covered
365
+ # branch stays covered, preserving the historical behaviour.
366
+ xml = etree.fromstring("""<?xml version="1.0"?>
367
+ <coverage>
368
+ <packages><classes>
369
+ <class filename="file1.py">
370
+ <lines>
371
+ <line number="1" hits="1"/>
372
+ <line number="2" hits="1" branch="true"
373
+ condition-coverage="50% (1/2)"/>
374
+ </lines>
375
+ </class>
376
+ </classes></packages>
377
+ </coverage>
378
+ """)
379
+
380
+ coverage = XmlCoverageReporter([xml])
381
+
382
+ assert coverage.violations("file1.py") == set()
383
+ assert coverage.measured_lines("file1.py") == {1, 2}
384
+
385
+ def test_uncovered_branch_line_is_single_violation(self):
386
+ # A line that is both never executed (hits=0) and a partial branch is
387
+ # reported once, not twice.
388
+ xml = etree.fromstring("""<?xml version="1.0"?>
389
+ <coverage>
390
+ <packages><classes>
391
+ <class filename="file1.py">
392
+ <lines>
393
+ <line number="1" hits="0" branch="true"
394
+ condition-coverage="0% (0/2)"/>
395
+ </lines>
396
+ </class>
397
+ </classes></packages>
398
+ </coverage>
399
+ """)
400
+
401
+ coverage = XmlCoverageReporter([xml], branch_coverage=True)
402
+
403
+ assert coverage.violations("file1.py") == {Violation(1, None)}
404
+
336
405
  def _coverage_xml(self, file_paths, violations, measured, source_paths=None):
337
406
  """
338
407
  Build an XML tree with source files specified by `file_paths`.
File without changes
File without changes
File without changes