diff-cover 10.0.0__tar.gz → 10.2.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.0.0 → diff_cover-10.2.0}/PKG-INFO +15 -4
  2. {diff_cover-10.0.0 → diff_cover-10.2.0}/README.rst +13 -1
  3. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/diff_cover_tool.py +28 -4
  4. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/diff_quality_tool.py +22 -4
  5. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/git_path.py +4 -0
  6. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/report_generator.py +49 -10
  7. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/violationsreporters/base.py +13 -7
  8. {diff_cover-10.0.0 → diff_cover-10.2.0}/pyproject.toml +4 -4
  9. diff_cover-10.2.0/tests/conftest.py +17 -0
  10. diff_cover-10.2.0/tests/fixtures/add_json_report_float.json +49 -0
  11. diff_cover-10.2.0/tests/fixtures/pycodestyle_violations_report_float.json +40 -0
  12. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_integration.py +34 -0
  13. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_report_generator.py +31 -0
  14. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_violations_reporter.py +43 -0
  15. {diff_cover-10.0.0 → diff_cover-10.2.0}/LICENSE +0 -0
  16. {diff_cover-10.0.0 → diff_cover-10.2.0}/NOTICE +0 -0
  17. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/__init__.py +0 -0
  18. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/command_runner.py +0 -0
  19. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/config_parser.py +0 -0
  20. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/diff_reporter.py +0 -0
  21. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/git_diff.py +0 -0
  22. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/hook.py +0 -0
  23. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/hookspecs.py +0 -0
  24. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/snippets.py +0 -0
  25. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/console_coverage_report.txt +0 -0
  26. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/console_quality_report.txt +0 -0
  27. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/external_style.css +0 -0
  28. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/github_coverage_annotations.txt +0 -0
  29. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/html_coverage_report.html +0 -0
  30. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/html_quality_report.html +0 -0
  31. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/markdown_coverage_report.md +0 -0
  32. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/markdown_quality_report.md +0 -0
  33. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/snippet_content.html +0 -0
  34. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/snippet_content.md +0 -0
  35. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/snippet_content.txt +0 -0
  36. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/templates/snippet_style.html +0 -0
  37. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/util.py +0 -0
  38. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/violationsreporters/__init__.py +0 -0
  39. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/violationsreporters/java_violations_reporter.py +0 -0
  40. {diff_cover-10.0.0 → diff_cover-10.2.0}/diff_cover/violationsreporters/violations_reporter.py +0 -0
  41. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/__init__.py +0 -0
  42. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/add_console_report.txt +0 -0
  43. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/add_html_report.html +0 -0
  44. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/add_json_report.json +0 -0
  45. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/add_markdown_report.md +0 -0
  46. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/changed_console_report.txt +0 -0
  47. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/changed_html_report.html +0 -0
  48. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/coverage.xml +0 -0
  49. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/coverage1.xml +0 -0
  50. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/coverage2.xml +0 -0
  51. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/coverage_missing_lines.xml +0 -0
  52. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/cpp_functions_console_report.txt +0 -0
  53. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/cpp_functions_coverage.lcov +0 -0
  54. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/delete_console_report.txt +0 -0
  55. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/delete_html_report.html +0 -0
  56. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/dotnet_coverage.xml +0 -0
  57. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/empty.txt +0 -0
  58. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/empty_pycodestyle_violations.txt +0 -0
  59. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/expand_console_report.txt +0 -0
  60. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/external_css_html_report.html +0 -0
  61. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/external_style.css +0 -0
  62. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_add.txt +0 -0
  63. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_changed.txt +0 -0
  64. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_code_dupe.txt +0 -0
  65. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_cpp.txt +0 -0
  66. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_cpp_functions.txt +0 -0
  67. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_delete.txt +0 -0
  68. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_dotnet.txt +0 -0
  69. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_external_css.txt +0 -0
  70. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_lua.txt +0 -0
  71. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_moved.txt +0 -0
  72. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_mult.txt +0 -0
  73. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_python.txt +0 -0
  74. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_subdir.txt +0 -0
  75. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_typescript.txt +0 -0
  76. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_typescript_branches.txt +0 -0
  77. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_unicode.txt +0 -0
  78. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_violations.txt +0 -0
  79. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/git_diff_violations_two_files.txt +0 -0
  80. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/hello.py +0 -0
  81. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/hi.py +0 -0
  82. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/html_report.html +0 -0
  83. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/html_report_empty.html +0 -0
  84. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/html_report_one_snippet.html +0 -0
  85. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/html_report_two_snippets.html +0 -0
  86. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/lcov.info +0 -0
  87. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/lua_console_report.txt +0 -0
  88. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/luacoverage.xml +0 -0
  89. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/markdown_report_one_snippet.md +0 -0
  90. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/markdown_report_two_snippets.md +0 -0
  91. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/moved_console_report.txt +0 -0
  92. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/moved_coverage.xml +0 -0
  93. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/moved_html_report.html +0 -0
  94. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/mult_inputs_console_report.txt +0 -0
  95. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/mult_inputs_html_report.html +0 -0
  96. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pycodestyle_report.txt +0 -0
  97. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pycodestyle_violations_report.html +0 -0
  98. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pycodestyle_violations_report.json +0 -0
  99. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pycodestyle_violations_report.md +0 -0
  100. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pycodestyle_violations_report.txt +0 -0
  101. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pycodestyle_violations_report_external_css.html +0 -0
  102. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pyflakes_two_files.txt +0 -0
  103. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pyflakes_violations_report.html +0 -0
  104. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pyflakes_violations_report.txt +0 -0
  105. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pylint_dupe.txt +0 -0
  106. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pylint_dupe_violations_report.txt +0 -0
  107. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pylint_report.txt +0 -0
  108. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pylint_violations_console_report.txt +0 -0
  109. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pylint_violations_report.html +0 -0
  110. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pylint_violations_report.txt +0 -0
  111. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/pylintrc +0 -0
  112. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/real_cpp_console_report.txt +0 -0
  113. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/real_cpp_coverage.lcov +0 -0
  114. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/real_python_console_report.txt +0 -0
  115. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/real_python_coverage.lcov +0 -0
  116. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/real_typescript_console_report.txt +0 -0
  117. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/real_typescript_coverage.lcov +0 -0
  118. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/show_uncovered_lines_console.txt +0 -0
  119. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet.css +0 -0
  120. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_8859.py +0 -0
  121. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_arabic_output.html +0 -0
  122. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_default.html +0 -0
  123. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_invalid_violations.html +0 -0
  124. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_list.html +0 -0
  125. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_list.md +0 -0
  126. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_list2.md +0 -0
  127. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_list3.md +0 -0
  128. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_no_filename_ext.html +0 -0
  129. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_src.py +0 -0
  130. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_src2.cpp +0 -0
  131. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_src3.cpp +0 -0
  132. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_unicode.html +0 -0
  133. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_unicode.py +0 -0
  134. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/snippet_unicode_html_output.html +0 -0
  135. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/subdir_coverage_console_report.txt +0 -0
  136. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/subdir_coverage_html_report.html +0 -0
  137. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/test_src.txt +0 -0
  138. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/typescript_branches_console_report.txt +0 -0
  139. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/typescript_branches_coverage.lcov +0 -0
  140. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/unicode_console_report.txt +0 -0
  141. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/unicode_coverage.xml +0 -0
  142. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/unicode_html_report.html +0 -0
  143. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/unicode_test_src.txt +0 -0
  144. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/fixtures/violations_test_file.py +0 -0
  145. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/helpers.py +0 -0
  146. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/snippet_list_unicode.html +0 -0
  147. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_clover_violations_reporter/test.xml +0 -0
  148. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_clover_violations_reporter.py +0 -0
  149. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_config_parser.py +0 -0
  150. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_diff_cover_main.py +0 -0
  151. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_diff_cover_tool.py +0 -0
  152. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_diff_quality_main.py +0 -0
  153. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_diff_reporter.py +0 -0
  154. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_git_diff.py +0 -0
  155. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_git_diff_file.py +0 -0
  156. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_git_path.py +0 -0
  157. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_java_violations_reporter.py +0 -0
  158. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_snippets.py +0 -0
  159. {diff_cover-10.0.0 → diff_cover-10.2.0}/tests/test_util.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: diff_cover
3
- Version: 10.0.0
3
+ Version: 10.2.0
4
4
  Summary: Run coverage and linting reports on diffs
5
5
  License: Apache-2.0
6
6
  Author: See Contributors
7
- Requires-Python: >=3.9
7
+ Requires-Python: >=3.10
8
8
  Classifier: Development Status :: 5 - Production/Stable
9
9
  Classifier: Environment :: Console
10
10
  Classifier: Intended Audience :: Developers
@@ -12,7 +12,6 @@ Classifier: License :: OSI Approved :: Apache Software License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
@@ -88,7 +87,7 @@ To install the latest release:
88
87
 
89
88
  .. code:: bash
90
89
 
91
- pip install diff_cover
90
+ pip install diff-cover
92
91
 
93
92
 
94
93
  To install the development version:
@@ -252,6 +251,18 @@ You can then run ``diff-cover`` with the diff file as an argument:
252
251
 
253
252
  diff-cover coverage.xml --diff-file=diff.txt
254
253
 
254
+ Total Percent Formatting
255
+ ------------------------
256
+
257
+ By default, ``diff-cover`` and ``diff-quality`` round the total coverage/quality
258
+ percentage to an integer. To keep up to two decimal places, use
259
+ ``--total-percent-float``:
260
+
261
+ .. code:: bash
262
+
263
+ diff-cover coverage.xml --total-percent-float
264
+ diff-quality --violations=pycodestyle --total-percent-float
265
+
255
266
  Fail Under
256
267
  ----------
257
268
 
@@ -55,7 +55,7 @@ To install the latest release:
55
55
 
56
56
  .. code:: bash
57
57
 
58
- pip install diff_cover
58
+ pip install diff-cover
59
59
 
60
60
 
61
61
  To install the development version:
@@ -219,6 +219,18 @@ You can then run ``diff-cover`` with the diff file as an argument:
219
219
 
220
220
  diff-cover coverage.xml --diff-file=diff.txt
221
221
 
222
+ Total Percent Formatting
223
+ ------------------------
224
+
225
+ By default, ``diff-cover`` and ``diff-quality`` round the total coverage/quality
226
+ percentage to an integer. To keep up to two decimal places, use
227
+ ``--total-percent-float``:
228
+
229
+ .. code:: bash
230
+
231
+ diff-cover coverage.xml --total-percent-float
232
+ diff-quality --violations=pycodestyle --total-percent-float
233
+
222
234
  Fail Under
223
235
  ----------
224
236
 
@@ -51,6 +51,9 @@ EXPAND_COVERAGE_REPORT = (
51
51
  INCLUDE_UNTRACKED_HELP = "Include untracked files"
52
52
  CONFIG_FILE_HELP = "The configuration file to use"
53
53
  DIFF_FILE_HELP = "The diff file to use"
54
+ TOTAL_PERCENT_FLOAT_HELP = (
55
+ "Show total coverage/quality as a float rounded to 2 decimal places"
56
+ )
54
57
 
55
58
  LOGGER = logging.getLogger(__name__)
56
59
 
@@ -181,6 +184,12 @@ def parse_coverage_args(argv):
181
184
  )
182
185
 
183
186
  parser.add_argument("--diff-file", type=str, default=None, help=DIFF_FILE_HELP)
187
+ parser.add_argument(
188
+ "--total-percent-float",
189
+ action="store_true",
190
+ default=None,
191
+ help=TOTAL_PERCENT_FLOAT_HELP,
192
+ )
184
193
 
185
194
  defaults = {
186
195
  "show_uncovered": False,
@@ -194,6 +203,7 @@ def parse_coverage_args(argv):
194
203
  "diff_range_notation": "...",
195
204
  "quiet": False,
196
205
  "expand_coverage_report": False,
206
+ "total_percent_float": False,
197
207
  }
198
208
 
199
209
  return get_config(parser=parser, argv=argv, defaults=defaults, tool=Tool.DIFF_COVER)
@@ -214,6 +224,7 @@ def generate_coverage_report(
214
224
  quiet=False,
215
225
  show_uncovered=False,
216
226
  expand_coverage_report=False,
227
+ total_percent_float=False,
217
228
  ):
218
229
  """
219
230
  Generate the diff coverage report, using kwargs from `parse_args()`.
@@ -251,7 +262,9 @@ def generate_coverage_report(
251
262
  css_url = css_file
252
263
  if css_url is not None:
253
264
  css_url = os.path.relpath(css_file, os.path.dirname(html_report))
254
- reporter = HtmlReportGenerator(coverage, diff, css_url=css_url)
265
+ reporter = HtmlReportGenerator(
266
+ coverage, diff, css_url=css_url, total_percent_float=total_percent_float
267
+ )
255
268
  with open_file(html_report, "wb") as output_file:
256
269
  reporter.generate_report(output_file)
257
270
  if css_file is not None:
@@ -260,13 +273,17 @@ def generate_coverage_report(
260
273
 
261
274
  if "json" in report_formats:
262
275
  json_report = report_formats["json"] or JSON_REPORT_DEFAULT_PATH
263
- reporter = JsonReportGenerator(coverage, diff)
276
+ reporter = JsonReportGenerator(
277
+ coverage, diff, total_percent_float=total_percent_float
278
+ )
264
279
  with open_file(json_report, "wb") as output_file:
265
280
  reporter.generate_report(output_file)
266
281
 
267
282
  if "markdown" in report_formats:
268
283
  markdown_report = report_formats["markdown"] or MARKDOWN_REPORT_DEFAULT_PATH
269
- reporter = MarkdownReportGenerator(coverage, diff)
284
+ reporter = MarkdownReportGenerator(
285
+ coverage, diff, total_percent_float=total_percent_float
286
+ )
270
287
  with open_file(markdown_report, "wb") as output_file:
271
288
  reporter.generate_report(output_file)
272
289
 
@@ -276,11 +293,17 @@ def generate_coverage_report(
276
293
  coverage,
277
294
  diff,
278
295
  report_formats["github-annotations"],
296
+ total_percent_float=total_percent_float,
279
297
  )
280
298
  reporter.generate_report(sys.stdout.buffer)
281
299
 
282
300
  # Generate the report for stdout
283
- reporter = StringReportGenerator(coverage, diff, show_uncovered)
301
+ reporter = StringReportGenerator(
302
+ coverage,
303
+ diff,
304
+ show_uncovered,
305
+ total_percent_float=total_percent_float,
306
+ )
284
307
  output_file = io.BytesIO() if quiet else sys.stdout.buffer
285
308
 
286
309
  # Generate the report
@@ -376,6 +399,7 @@ def main(argv=None, directory=None):
376
399
  quiet=quiet,
377
400
  show_uncovered=arg_dict["show_uncovered"],
378
401
  expand_coverage_report=arg_dict["expand_coverage_report"],
402
+ total_percent_float=arg_dict["total_percent_float"],
379
403
  )
380
404
 
381
405
  if percent_covered >= fail_under:
@@ -30,6 +30,7 @@ from diff_cover.diff_cover_tool import (
30
30
  JSON_REPORT_DEFAULT_PATH,
31
31
  MARKDOWN_REPORT_DEFAULT_PATH,
32
32
  QUIET_HELP,
33
+ TOTAL_PERCENT_FLOAT_HELP,
33
34
  format_type,
34
35
  handle_old_format,
35
36
  )
@@ -200,6 +201,12 @@ def parse_quality_args(argv):
200
201
  parser.add_argument(
201
202
  "--report-root-path", help=REPORT_ROOT_PATH_HELP, metavar="ROOT_PATH"
202
203
  )
204
+ parser.add_argument(
205
+ "--total-percent-float",
206
+ action="store_true",
207
+ default=None,
208
+ help=TOTAL_PERCENT_FLOAT_HELP,
209
+ )
203
210
 
204
211
  defaults = {
205
212
  "ignore_whitespace": False,
@@ -211,6 +218,7 @@ def parse_quality_args(argv):
211
218
  "ignore_unstaged": False,
212
219
  "ignore_untracked": False,
213
220
  "quiet": False,
221
+ "total_percent_float": False,
214
222
  }
215
223
 
216
224
  return get_config(
@@ -230,6 +238,7 @@ def generate_quality_report(
230
238
  exclude=None,
231
239
  include=None,
232
240
  quiet=False,
241
+ total_percent_float=False,
233
242
  ):
234
243
  """
235
244
  Generate the quality report, using kwargs from `parse_args()`.
@@ -253,7 +262,9 @@ def generate_quality_report(
253
262
  css_url = css_file
254
263
  if css_url is not None:
255
264
  css_url = os.path.relpath(css_file, os.path.dirname(html_report))
256
- reporter = HtmlQualityReportGenerator(tool, diff, css_url=css_url)
265
+ reporter = HtmlQualityReportGenerator(
266
+ tool, diff, css_url=css_url, total_percent_float=total_percent_float
267
+ )
257
268
  with open_file(html_report, "wb") as output_file:
258
269
  reporter.generate_report(output_file)
259
270
  if css_file is not None:
@@ -262,18 +273,24 @@ def generate_quality_report(
262
273
 
263
274
  if "json" in report_formats:
264
275
  json_report = report_formats["json"] or JSON_REPORT_DEFAULT_PATH
265
- reporter = JsonReportGenerator(tool, diff)
276
+ reporter = JsonReportGenerator(
277
+ tool, diff, total_percent_float=total_percent_float
278
+ )
266
279
  with open_file(json_report, "wb") as output_file:
267
280
  reporter.generate_report(output_file)
268
281
 
269
282
  if "markdown" in report_formats:
270
283
  markdown_report = report_formats["markdown"] or MARKDOWN_REPORT_DEFAULT_PATH
271
- reporter = MarkdownQualityReportGenerator(tool, diff)
284
+ reporter = MarkdownQualityReportGenerator(
285
+ tool, diff, total_percent_float=total_percent_float
286
+ )
272
287
  with open_file(markdown_report, "wb") as output_file:
273
288
  reporter.generate_report(output_file)
274
289
 
275
290
  # Generate the report for stdout
276
- reporter = StringQualityReportGenerator(tool, diff)
291
+ reporter = StringQualityReportGenerator(
292
+ tool, diff, total_percent_float=total_percent_float
293
+ )
277
294
  output_file = io.BytesIO() if quiet else sys.stdout.buffer
278
295
  reporter.generate_report(output_file)
279
296
 
@@ -367,6 +384,7 @@ def main(argv=None, directory=None):
367
384
  exclude=arg_dict["exclude"],
368
385
  include=arg_dict["include"],
369
386
  quiet=quiet,
387
+ total_percent_float=arg_dict["total_percent_float"],
370
388
  )
371
389
  if percent_passing >= fail_under:
372
390
  return 0
@@ -36,6 +36,10 @@ class GitPathTool:
36
36
  """
37
37
  Returns git_diff_path relative to cwd.
38
38
  """
39
+ # If GitPathTool hasn't been initialized, return the path unchanged
40
+ if cls._cwd is None or cls._root is None:
41
+ return git_diff_path
42
+
39
43
  # Remove git_root from src_path for searching the correct filename
40
44
  # If cwd is `/home/user/work/diff-cover/diff_cover`
41
45
  # and src_path is `diff_cover/violations_reporter.py`
@@ -42,7 +42,7 @@ class BaseReportGenerator(ABC):
42
42
  Generate a diff coverage report.
43
43
  """
44
44
 
45
- def __init__(self, violations_reporter, diff_reporter):
45
+ def __init__(self, violations_reporter, diff_reporter, total_percent_float=False):
46
46
  """
47
47
  Configure the report generator to build a report
48
48
  from `violations_reporter` (of type BaseViolationReporter)
@@ -50,6 +50,7 @@ class BaseReportGenerator(ABC):
50
50
  """
51
51
  self._violations = violations_reporter
52
52
  self._diff = diff_reporter
53
+ self._total_percent_float = total_percent_float
53
54
  self._diff_violations_dict = None
54
55
 
55
56
  self._cache_violations = None
@@ -169,9 +170,12 @@ class BaseReportGenerator(ABC):
169
170
 
170
171
  if total_lines > 0:
171
172
  num_covered = total_lines - self.total_num_violations()
172
- return int(float(num_covered) / total_lines * 100)
173
+ total_percent = float(num_covered) / total_lines * 100
174
+ if self._total_percent_float:
175
+ return round(total_percent, 2)
176
+ return int(total_percent)
173
177
 
174
- return 100
178
+ return 100.0 if self._total_percent_float else 100
175
179
 
176
180
  def num_changed_lines(self):
177
181
  """Returns the number of changed lines."""
@@ -285,8 +289,18 @@ class TemplateReportGenerator(BaseReportGenerator):
285
289
  # that they want to include source file snippets.
286
290
  include_snippets = False
287
291
 
288
- def __init__(self, violations_reporter, diff_reporter, css_url=None):
289
- super().__init__(violations_reporter, diff_reporter)
292
+ def __init__(
293
+ self,
294
+ violations_reporter,
295
+ diff_reporter,
296
+ css_url=None,
297
+ total_percent_float=False,
298
+ ):
299
+ super().__init__(
300
+ violations_reporter,
301
+ diff_reporter,
302
+ total_percent_float=total_percent_float,
303
+ )
290
304
  self.css_url = css_url
291
305
 
292
306
  def generate_report(self, output_file):
@@ -347,7 +361,12 @@ class TemplateReportGenerator(BaseReportGenerator):
347
361
  snippet_style = None
348
362
 
349
363
  context = super().report_dict()
350
- context.update({"css_url": self.css_url, "snippet_style": snippet_style})
364
+ context.update(
365
+ {
366
+ "css_url": self.css_url,
367
+ "snippet_style": snippet_style,
368
+ }
369
+ )
351
370
 
352
371
  return context
353
372
 
@@ -413,8 +432,18 @@ class StringReportGenerator(TemplateReportGenerator):
413
432
 
414
433
  template_path = "console_coverage_report.txt"
415
434
 
416
- def __init__(self, violations_reporter, diff_reporter, show_uncovered=False):
417
- super().__init__(violations_reporter, diff_reporter)
435
+ def __init__(
436
+ self,
437
+ violations_reporter,
438
+ diff_reporter,
439
+ show_uncovered=False,
440
+ total_percent_float=False,
441
+ ):
442
+ super().__init__(
443
+ violations_reporter,
444
+ diff_reporter,
445
+ total_percent_float=total_percent_float,
446
+ )
418
447
  self.include_snippets = show_uncovered
419
448
 
420
449
 
@@ -429,8 +458,18 @@ class GitHubAnnotationsReportGenerator(TemplateReportGenerator):
429
458
 
430
459
  template_path = "github_coverage_annotations.txt"
431
460
 
432
- def __init__(self, violations_reporter, diff_reporter, annotations_type):
433
- super().__init__(violations_reporter, diff_reporter)
461
+ def __init__(
462
+ self,
463
+ violations_reporter,
464
+ diff_reporter,
465
+ annotations_type,
466
+ total_percent_float=False,
467
+ ):
468
+ super().__init__(
469
+ violations_reporter,
470
+ diff_reporter,
471
+ total_percent_float=total_percent_float,
472
+ )
434
473
  self.annotations_type = annotations_type
435
474
 
436
475
  def _context(self):
@@ -6,6 +6,7 @@ from abc import ABC, abstractmethod
6
6
  from collections import defaultdict, namedtuple
7
7
 
8
8
  from diff_cover.command_runner import execute, run_command_for_code
9
+ from diff_cover.git_path import GitPathTool
9
10
  from diff_cover.util import to_unix_path
10
11
 
11
12
  Violation = namedtuple("Violation", "line, message")
@@ -152,14 +153,19 @@ class QualityReporter(BaseViolationReporter):
152
153
  if not any(src_path.endswith(ext) for ext in self.driver.supported_extensions):
153
154
  return []
154
155
 
155
- if src_path not in self.violations_dict:
156
+ # `src_path` is relative to the git root. We convert it to be relative to
157
+ # the current working directory, since quality tools report paths relative
158
+ # to the current working directory.
159
+ relative_src_path = to_unix_path(GitPathTool.relative_path(src_path))
160
+
161
+ if relative_src_path not in self.violations_dict:
156
162
  if self.reports:
157
163
  self.violations_dict = self.driver.parse_reports(self.reports)
158
- return self.violations_dict[src_path]
164
+ return self.violations_dict[relative_src_path]
159
165
 
160
- if not os.path.exists(src_path):
161
- self.violations_dict[src_path] = []
162
- return self.violations_dict[src_path]
166
+ if not os.path.exists(relative_src_path):
167
+ self.violations_dict[relative_src_path] = []
168
+ return self.violations_dict[relative_src_path]
163
169
 
164
170
  if self.driver_tool_installed is None:
165
171
  self.driver_tool_installed = self.driver.installed()
@@ -170,13 +176,13 @@ class QualityReporter(BaseViolationReporter):
170
176
  if self.options:
171
177
  for arg in self.options.split():
172
178
  command.append(arg)
173
- command.append(src_path.encode(sys.getfilesystemencoding()))
179
+ command.append(relative_src_path.encode(sys.getfilesystemencoding()))
174
180
 
175
181
  stdout, stderr = execute(command, self.driver.exit_codes)
176
182
  output = stderr if self.driver.output_stderr else stdout
177
183
  self.violations_dict.update(self.driver.parse_reports([output]))
178
184
 
179
- return self.violations_dict[src_path]
185
+ return self.violations_dict[relative_src_path]
180
186
 
181
187
  def measured_lines(self, src_path):
182
188
  """
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "diff_cover"
3
- version = "10.0.0"
3
+ version = "10.2.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"
@@ -40,7 +40,7 @@ diff-cover = 'diff_cover.diff_cover_tool:main'
40
40
  diff-quality = 'diff_cover.diff_quality_tool:main'
41
41
 
42
42
  [tool.poetry.dependencies]
43
- python = ">=3.9"
43
+ python = ">=3.10"
44
44
  Pygments = "^2.19.1"
45
45
  Jinja2 = ">=2.7.1"
46
46
  pluggy = ">=0.13.1,<2"
@@ -55,12 +55,12 @@ pytest-xdist = "^3.6.1"
55
55
  pycodestyle = ">=2.9.1"
56
56
  flake8-pyproject = "^1.2.3"
57
57
  pyflakes = "^3.3.2"
58
- pylint = "^3.3.4"
58
+ pylint = ">=3.3.4,<5.0.0"
59
59
  pylint-pytest = "^1.1.8"
60
60
  pydocstyle = "^6.1.1"
61
61
  black = "^25.1.0"
62
62
  isort = "^6.0.1"
63
- doc8 = "1.1.2"
63
+ doc8 = "2.0.0"
64
64
  ruff = ">=0.11.10,<0.15.0"
65
65
 
66
66
  [tool.poetry.extras]
@@ -0,0 +1,17 @@
1
+ import pytest
2
+
3
+ from diff_cover.git_path import GitPathTool
4
+
5
+
6
+ @pytest.fixture(autouse=True)
7
+ def reset_git_path_tool():
8
+ """Reset GitPathTool before each test to ensure test isolation.
9
+
10
+ GitPathTool uses class variables (_cwd and _root) that persist across tests.
11
+ This fixture ensures each test starts with a clean state.
12
+ """
13
+ GitPathTool._cwd = None
14
+ GitPathTool._root = None
15
+ yield
16
+ GitPathTool._cwd = None
17
+ GitPathTool._root = None
@@ -0,0 +1,49 @@
1
+ {
2
+ "report_name": "XML",
3
+ "diff_name": "origin/main...HEAD, staged and unstaged changes",
4
+ "src_stats": {
5
+ "test_src.txt": {
6
+ "percent_covered": 50.0,
7
+ "violation_lines": [
8
+ 2,
9
+ 4,
10
+ 6,
11
+ 8,
12
+ 10
13
+ ],
14
+ "covered_lines": [
15
+ 1,
16
+ 3,
17
+ 5,
18
+ 7,
19
+ 9
20
+ ],
21
+ "violations": [
22
+ [
23
+ 2,
24
+ null
25
+ ],
26
+ [
27
+ 4,
28
+ null
29
+ ],
30
+ [
31
+ 6,
32
+ null
33
+ ],
34
+ [
35
+ 8,
36
+ null
37
+ ],
38
+ [
39
+ 10,
40
+ null
41
+ ]
42
+ ]
43
+ }
44
+ },
45
+ "total_num_lines": 10,
46
+ "total_num_violations": 5,
47
+ "total_percent_covered": 50.0,
48
+ "num_changed_lines": 10
49
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "report_name": "pycodestyle",
3
+ "diff_name": "origin/main...HEAD, staged and unstaged changes",
4
+ "src_stats": {
5
+ "violations_test_file.py": {
6
+ "percent_covered": 66.66666666666667,
7
+ "violation_lines": [
8
+ 2,
9
+ 6,
10
+ 11
11
+ ],
12
+ "covered_lines": [
13
+ 1,
14
+ 7,
15
+ 8,
16
+ 9,
17
+ 10,
18
+ 12
19
+ ],
20
+ "violations": [
21
+ [
22
+ 2,
23
+ "E225 missing whitespace around operator"
24
+ ],
25
+ [
26
+ 6,
27
+ "E302 expected 2 blank lines, found 0"
28
+ ],
29
+ [
30
+ 11,
31
+ "E225 missing whitespace around operator"
32
+ ]
33
+ ]
34
+ }
35
+ },
36
+ "total_num_lines": 9,
37
+ "total_num_violations": 3,
38
+ "total_percent_covered": 66.67,
39
+ "num_changed_lines": 9
40
+ }
@@ -186,6 +186,22 @@ class TestDiffCoverIntegration:
186
186
  compare_json("add_json_report.json", "dummy/diff_coverage.json")
187
187
  compare_markdown("add_markdown_report.md", "dummy/diff_coverage.md")
188
188
 
189
+ def test_json_report_total_percent_float(self, runbin, patch_git_command):
190
+ patch_git_command.set_stdout("git_diff_add.txt")
191
+ assert (
192
+ runbin(
193
+ [
194
+ "coverage.xml",
195
+ "--format",
196
+ "json:dummy/diff_coverage.json",
197
+ "--total-percent-float",
198
+ "-q",
199
+ ]
200
+ )
201
+ == 0
202
+ )
203
+ compare_json("add_json_report_float.json", "dummy/diff_coverage.json")
204
+
189
205
  def test_all_reports_with_stdout(self, runbin, patch_git_command, capsys):
190
206
  patch_git_command.set_stdout("git_diff_add.txt")
191
207
  assert (
@@ -610,6 +626,24 @@ class TestDiffQualityIntegration:
610
626
  compare_json("pycodestyle_violations_report.json", "dummy/diff_coverage.json")
611
627
  compare_markdown("pycodestyle_violations_report.md", "dummy/diff_coverage.md")
612
628
 
629
+ def test_json_report_total_percent_float(self, runbin, patch_git_command):
630
+ patch_git_command.set_stdout("git_diff_violations.txt")
631
+ assert (
632
+ runbin(
633
+ [
634
+ "--violations=pycodestyle",
635
+ "--format",
636
+ "json:dummy/diff_coverage.json",
637
+ "--total-percent-float",
638
+ "-q",
639
+ ]
640
+ )
641
+ == 0
642
+ )
643
+ compare_json(
644
+ "pycodestyle_violations_report_float.json", "dummy/diff_coverage.json"
645
+ )
646
+
613
647
  def test_added_file_pyflakes_html(self, runbin, patch_git_command):
614
648
  patch_git_command.set_stdout("git_diff_violations.txt")
615
649
  assert (
@@ -212,6 +212,11 @@ class TestSimpleReportGenerator(BaseReportGeneratorTest):
212
212
  # individually.
213
213
  assert self.report.total_percent_covered() == 66
214
214
 
215
+ @pytest.mark.usefixtures("use_default_values")
216
+ def test_total_percent_covered_float(self, coverage, diff):
217
+ report = SimpleReportGenerator(coverage, diff, total_percent_float=True)
218
+ assert report.total_percent_covered() == pytest.approx(66.67)
219
+
215
220
 
216
221
  class TestTemplateReportGenerator(BaseReportGeneratorTest):
217
222
 
@@ -334,6 +339,24 @@ class TestJsonReportGenerator(BaseReportGeneratorTest):
334
339
 
335
340
  self.assert_report(expected)
336
341
 
342
+ @pytest.mark.usefixtures("use_default_values")
343
+ def test_total_percent_covered_float(self, coverage, diff):
344
+ output = BytesIO()
345
+ report = JsonReportGenerator(coverage, diff, total_percent_float=True)
346
+ report.generate_report(output)
347
+ report_data = json.loads(output.getvalue().decode("utf-8"))
348
+ assert isinstance(report_data["total_percent_covered"], float)
349
+ assert report_data["total_percent_covered"] == pytest.approx(66.67)
350
+
351
+ @pytest.mark.usefixtures("use_default_values")
352
+ def test_total_percent_covered_int(self, coverage, diff):
353
+ output = BytesIO()
354
+ report = JsonReportGenerator(coverage, diff)
355
+ report.generate_report(output)
356
+ report_data = json.loads(output.getvalue().decode("utf-8"))
357
+ assert isinstance(report_data["total_percent_covered"], int)
358
+ assert report_data["total_percent_covered"] == 66
359
+
337
360
 
338
361
  class TestStringReportGenerator(BaseReportGeneratorTest):
339
362
 
@@ -406,6 +429,14 @@ class TestStringReportGenerator(BaseReportGeneratorTest):
406
429
 
407
430
  self.assert_report(expected)
408
431
 
432
+ @pytest.mark.usefixtures("use_default_values")
433
+ def test_total_percent_covered_float(self, coverage, diff):
434
+ output = BytesIO()
435
+ report = StringReportGenerator(coverage, diff, total_percent_float=True)
436
+ report.generate_report(output)
437
+ report_output = output.getvalue().decode("utf-8")
438
+ assert "Coverage: 66.67%" in report_output
439
+
409
440
 
410
441
  class TestGitHubAnnotationsReportGenerator(BaseReportGeneratorTest):
411
442