diff-cover 10.5.0__tar.gz → 10.6.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 (163) hide show
  1. diff_cover-10.6.0/.gitignore +50 -0
  2. {diff_cover-10.5.0 → diff_cover-10.6.0}/PKG-INFO +27 -20
  3. {diff_cover-10.5.0 → diff_cover-10.6.0}/README.rst +14 -7
  4. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/command_runner.py +32 -1
  5. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/diff_cover_tool.py +21 -4
  6. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/diff_quality_tool.py +21 -4
  7. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/git_diff.py +4 -3
  8. diff_cover-10.6.0/diff_cover/violationsreporters/clover.py +84 -0
  9. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/violationsreporters/violations_reporter.py +12 -34
  10. diff_cover-10.6.0/pyproject.toml +138 -0
  11. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pycodestyle_violations_report.html +1 -1
  12. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pycodestyle_violations_report_external_css.html +1 -1
  13. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pyflakes_violations_report.html +1 -1
  14. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pylint_violations_report.html +1 -1
  15. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_clover_violations_reporter.py +2 -3
  16. diff_cover-10.6.0/tests/test_command_runner_missing_executable.py +92 -0
  17. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_diff_cover_tool.py +21 -0
  18. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_diff_quality_main.py +22 -1
  19. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_integration.py +34 -0
  20. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_java_violations_reporter.py +44 -14
  21. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_report_generator.py +3 -10
  22. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_violations_reporter.py +170 -38
  23. diff_cover-10.5.0/pyproject.toml +0 -184
  24. {diff_cover-10.5.0 → diff_cover-10.6.0}/LICENSE +0 -0
  25. {diff_cover-10.5.0 → diff_cover-10.6.0}/NOTICE +0 -0
  26. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/__init__.py +0 -0
  27. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/config_parser.py +0 -0
  28. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/diff_reporter.py +0 -0
  29. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/git_path.py +0 -0
  30. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/hook.py +0 -0
  31. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/hookspecs.py +0 -0
  32. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/report_generator.py +0 -0
  33. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/snippets.py +0 -0
  34. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/console_coverage_report.txt +0 -0
  35. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/console_quality_report.txt +0 -0
  36. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/external_style.css +0 -0
  37. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/github_coverage_annotations.txt +0 -0
  38. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/html_coverage_report.html +0 -0
  39. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/html_quality_report.html +0 -0
  40. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/markdown_coverage_report.md +0 -0
  41. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/markdown_quality_report.md +0 -0
  42. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/snippet_content.html +0 -0
  43. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/snippet_content.md +0 -0
  44. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/snippet_content.txt +0 -0
  45. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/templates/snippet_style.html +0 -0
  46. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/util.py +0 -0
  47. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/violationsreporters/__init__.py +0 -0
  48. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/violationsreporters/base.py +0 -0
  49. {diff_cover-10.5.0 → diff_cover-10.6.0}/diff_cover/violationsreporters/java_violations_reporter.py +0 -0
  50. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/__init__.py +0 -0
  51. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/conftest.py +0 -0
  52. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/add_console_report.txt +0 -0
  53. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/add_html_report.html +0 -0
  54. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/add_json_report.json +0 -0
  55. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/add_json_report_float.json +0 -0
  56. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/add_markdown_report.md +0 -0
  57. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/changed_console_report.txt +0 -0
  58. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/changed_html_report.html +0 -0
  59. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/coverage.xml +0 -0
  60. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/coverage1.xml +0 -0
  61. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/coverage2.xml +0 -0
  62. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/coverage_missing_lines.xml +0 -0
  63. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/cpp_functions_console_report.txt +0 -0
  64. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/cpp_functions_coverage.lcov +0 -0
  65. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/delete_console_report.txt +0 -0
  66. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/delete_html_report.html +0 -0
  67. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/dotnet_coverage.xml +0 -0
  68. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/empty.txt +0 -0
  69. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/empty_pycodestyle_violations.txt +0 -0
  70. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/expand_console_report.txt +0 -0
  71. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/external_css_html_report.html +0 -0
  72. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/external_style.css +0 -0
  73. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_add.txt +0 -0
  74. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_changed.txt +0 -0
  75. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_code_dupe.txt +0 -0
  76. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_cpp.txt +0 -0
  77. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_cpp_functions.txt +0 -0
  78. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_delete.txt +0 -0
  79. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_dotnet.txt +0 -0
  80. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_external_css.txt +0 -0
  81. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_lua.txt +0 -0
  82. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_moved.txt +0 -0
  83. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_mult.txt +0 -0
  84. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_python.txt +0 -0
  85. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_subdir.txt +0 -0
  86. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_typescript.txt +0 -0
  87. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_typescript_branches.txt +0 -0
  88. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_unicode.txt +0 -0
  89. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_violations.txt +0 -0
  90. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/git_diff_violations_two_files.txt +0 -0
  91. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/hello.py +0 -0
  92. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/hi.py +0 -0
  93. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/html_report.html +0 -0
  94. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/html_report_empty.html +0 -0
  95. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/html_report_one_snippet.html +0 -0
  96. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/html_report_two_snippets.html +0 -0
  97. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/lcov.info +0 -0
  98. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/lua_console_report.txt +0 -0
  99. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/luacoverage.xml +0 -0
  100. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/markdown_report_one_snippet.md +0 -0
  101. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/markdown_report_two_snippets.md +0 -0
  102. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/moved_console_report.txt +0 -0
  103. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/moved_coverage.xml +0 -0
  104. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/moved_html_report.html +0 -0
  105. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/mult_inputs_console_report.txt +0 -0
  106. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/mult_inputs_html_report.html +0 -0
  107. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pycodestyle_report.txt +0 -0
  108. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pycodestyle_violations_report.json +0 -0
  109. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pycodestyle_violations_report.md +0 -0
  110. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pycodestyle_violations_report.txt +0 -0
  111. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pycodestyle_violations_report_float.json +0 -0
  112. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pyflakes_two_files.txt +0 -0
  113. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pyflakes_violations_report.txt +0 -0
  114. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pylint_dupe.txt +0 -0
  115. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pylint_dupe_violations_report.txt +0 -0
  116. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pylint_report.txt +0 -0
  117. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pylint_violations_console_report.txt +0 -0
  118. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pylint_violations_report.txt +0 -0
  119. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/pylintrc +0 -0
  120. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/real_cpp_console_report.txt +0 -0
  121. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/real_cpp_coverage.lcov +0 -0
  122. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/real_python_console_report.txt +0 -0
  123. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/real_python_coverage.lcov +0 -0
  124. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/real_typescript_console_report.txt +0 -0
  125. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/real_typescript_coverage.lcov +0 -0
  126. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/show_uncovered_lines_console.txt +0 -0
  127. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet.css +0 -0
  128. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_8859.py +0 -0
  129. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_arabic_output.html +0 -0
  130. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_default.html +0 -0
  131. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_invalid_violations.html +0 -0
  132. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_list.html +0 -0
  133. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_list.md +0 -0
  134. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_list2.md +0 -0
  135. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_list3.md +0 -0
  136. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_no_filename_ext.html +0 -0
  137. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_src.py +0 -0
  138. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_src2.cpp +0 -0
  139. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_src3.cpp +0 -0
  140. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_unicode.html +0 -0
  141. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_unicode.py +0 -0
  142. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/snippet_unicode_html_output.html +0 -0
  143. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/subdir_coverage_console_report.txt +0 -0
  144. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/subdir_coverage_html_report.html +0 -0
  145. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/test_src.txt +0 -0
  146. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/typescript_branches_console_report.txt +0 -0
  147. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/typescript_branches_coverage.lcov +0 -0
  148. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/unicode_console_report.txt +0 -0
  149. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/unicode_coverage.xml +0 -0
  150. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/unicode_html_report.html +0 -0
  151. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/unicode_test_src.txt +0 -0
  152. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/fixtures/violations_test_file.py +0 -0
  153. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/helpers.py +0 -0
  154. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/snippet_list_unicode.html +0 -0
  155. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_clover_violations_reporter/test.xml +0 -0
  156. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_config_parser.py +0 -0
  157. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_diff_cover_main.py +0 -0
  158. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_diff_reporter.py +0 -0
  159. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_git_diff.py +0 -0
  160. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_git_diff_file.py +0 -0
  161. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_git_path.py +0 -0
  162. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_snippets.py +0 -0
  163. {diff_cover-10.5.0 → diff_cover-10.6.0}/tests/test_util.py +0 -0
@@ -0,0 +1,50 @@
1
+ *.py[cod]
2
+ .DS_Store
3
+
4
+ .idea
5
+ venv
6
+ 27venv
7
+ .venv
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Packages
13
+ *.egg
14
+ *.egg-info
15
+ dist
16
+ build
17
+ eggs
18
+ parts
19
+ bin
20
+ var
21
+ sdist
22
+ develop-eggs
23
+ .installed.cfg
24
+ lib
25
+ lib64
26
+ __pycache__
27
+ .pytest_cache
28
+
29
+ # Installer logs
30
+ pip-log.txt
31
+
32
+ # Unit test / coverage reports
33
+ .coverage*
34
+ .tox
35
+ /coverage.xml
36
+ /report.html
37
+
38
+ # Translations
39
+ *.mo
40
+
41
+ # Mr Developer
42
+ .mr.developer.cfg
43
+ .project
44
+ .pydevproject
45
+ .python-version
46
+
47
+ # VS Code
48
+ .vscode/*
49
+ *.code-workspaces
50
+ .history/
@@ -1,14 +1,16 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.5
2
2
  Name: diff_cover
3
- Version: 10.5.0
3
+ Version: 10.6.0
4
4
  Summary: Run coverage and linting reports on diffs
5
- License: Apache-2.0
5
+ Project-URL: Homepage, https://github.com/Bachmann1234/diff-cover
6
+ Project-URL: Repository, https://github.com/Bachmann1234/diff-cover
6
7
  Author: See Contributors
7
- Requires-Python: >=3.10
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ License-File: NOTICE
8
11
  Classifier: Development Status :: 5 - Production/Stable
9
12
  Classifier: Environment :: Console
10
13
  Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: Apache Software License
12
14
  Classifier: Operating System :: OS Independent
13
15
  Classifier: Programming Language :: Python
14
16
  Classifier: Programming Language :: Python :: 3
@@ -20,14 +22,13 @@ Classifier: Programming Language :: Python :: 3.14
20
22
  Classifier: Programming Language :: Python :: Implementation :: CPython
21
23
  Classifier: Topic :: Software Development :: Quality Assurance
22
24
  Classifier: Topic :: Software Development :: Testing
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: chardet>=3.0.0
27
+ Requires-Dist: jinja2>=2.7.1
28
+ Requires-Dist: pluggy<2,>=0.13.1
29
+ Requires-Dist: pygments<3,>=2.19.1
23
30
  Provides-Extra: toml
24
- Requires-Dist: Jinja2 (>=2.7.1)
25
- Requires-Dist: Pygments (>=2.19.1,<3.0.0)
26
- Requires-Dist: chardet (>=3.0.0)
27
- Requires-Dist: pluggy (>=0.13.1,<2)
28
- Requires-Dist: tomli (>=1.2.1) ; extra == "toml"
29
- Project-URL: Homepage, https://github.com/Bachmann1234/diff-cover
30
- Project-URL: Repository, https://github.com/Bachmann1234/diff-cover
31
+ Requires-Dist: tomli>=1.2.1; extra == 'toml'
31
32
  Description-Content-Type: text/x-rst
32
33
 
33
34
  diff-cover |pypi-version| |conda-version| |build-status|
@@ -96,8 +97,11 @@ To install the development version:
96
97
 
97
98
  git clone https://github.com/Bachmann1234/diff-cover.git
98
99
  cd diff-cover
99
- poetry install
100
- poetry shell
100
+ uv sync
101
+
102
+ This creates a ``.venv`` with the project installed. Run commands through it with
103
+ ``uv run``, for example ``uv run diff-cover --help``, or activate it the usual way
104
+ with ``source .venv/bin/activate``.
101
105
 
102
106
 
103
107
  Getting Started
@@ -508,17 +512,21 @@ Support for a New Quality Checker".
508
512
  Setting Up For Development
509
513
  ~~~~~~~~~~~~~~~~~~~~~~~~~~
510
514
 
511
- This project is managed with `poetry` this can be installed with `pip`
512
- poetry manages a python virtual environment and organizes dependencies. It also
513
- packages this project.
515
+ This project is managed with `uv <https://docs.astral.sh/uv/>`_. uv provisions the
516
+ Python toolchain and a project virtual environment, resolves and locks dependencies,
517
+ and packages this project. Install it by following the
518
+ `uv installation guide <https://docs.astral.sh/uv/getting-started/installation/>`_.
514
519
 
515
520
  .. code:: bash
516
521
 
517
- pip install poetry
522
+ uv sync
523
+
524
+ This creates ``.venv`` with all runtime and development dependencies. Run project
525
+ commands through ``uv run``, for example the full verification suite:
518
526
 
519
527
  .. code:: bash
520
528
 
521
- poetry install
529
+ uv run ./verify.sh
522
530
 
523
531
  I would also suggest running this command after. This will make it so git blame ignores the commit
524
532
  that formatted the entire codebase.
@@ -649,4 +657,3 @@ Originally created with the support of `edX
649
657
  .. |build-status| image:: https://github.com/bachmann1234/diff_cover/actions/workflows/verify.yaml/badge.svg?branch=main
650
658
  :target: https://github.com/Bachmann1234/diff_cover/actions/workflows/verify.yaml
651
659
  :alt: Build Status
652
-
@@ -64,8 +64,11 @@ To install the development version:
64
64
 
65
65
  git clone https://github.com/Bachmann1234/diff-cover.git
66
66
  cd diff-cover
67
- poetry install
68
- poetry shell
67
+ uv sync
68
+
69
+ This creates a ``.venv`` with the project installed. Run commands through it with
70
+ ``uv run``, for example ``uv run diff-cover --help``, or activate it the usual way
71
+ with ``source .venv/bin/activate``.
69
72
 
70
73
 
71
74
  Getting Started
@@ -476,17 +479,21 @@ Support for a New Quality Checker".
476
479
  Setting Up For Development
477
480
  ~~~~~~~~~~~~~~~~~~~~~~~~~~
478
481
 
479
- This project is managed with `poetry` this can be installed with `pip`
480
- poetry manages a python virtual environment and organizes dependencies. It also
481
- packages this project.
482
+ This project is managed with `uv <https://docs.astral.sh/uv/>`_. uv provisions the
483
+ Python toolchain and a project virtual environment, resolves and locks dependencies,
484
+ and packages this project. Install it by following the
485
+ `uv installation guide <https://docs.astral.sh/uv/getting-started/installation/>`_.
482
486
 
483
487
  .. code:: bash
484
488
 
485
- pip install poetry
489
+ uv sync
490
+
491
+ This creates ``.venv`` with all runtime and development dependencies. Run project
492
+ commands through ``uv run``, for example the full verification suite:
486
493
 
487
494
  .. code:: bash
488
495
 
489
- poetry install
496
+ uv run ./verify.sh
490
497
 
491
498
  I would also suggest running this command after. This will make it so git blame ignores the commit
492
499
  that formatted the entire codebase.
@@ -1,6 +1,8 @@
1
1
  import subprocess
2
2
  import sys
3
3
 
4
+ GIT_INSTALL_URL = "https://git-scm.com/book/en/v2/Getting-Started-Installing-Git"
5
+
4
6
 
5
7
  class CommandError(Exception):
6
8
  """
@@ -8,6 +10,16 @@ class CommandError(Exception):
8
10
  """
9
11
 
10
12
 
13
+ class ExecutableNotFoundError(CommandError):
14
+ """
15
+ Error raised when the executable of a command cannot be found at all.
16
+
17
+ This is a `CommandError` so that existing handling keeps working, but a
18
+ distinct type so the command line tools can tell "your tooling is not
19
+ installed" apart from "the command ran and failed".
20
+ """
21
+
22
+
11
23
  def execute(command, exit_codes=None):
12
24
  """Execute provided command returning the stdout
13
25
  Args:
@@ -24,7 +36,12 @@ def execute(command, exit_codes=None):
24
36
  exit_codes = [0]
25
37
 
26
38
  stdout_pipe = subprocess.PIPE
27
- with subprocess.Popen(command, stdout=stdout_pipe, stderr=stdout_pipe) as process:
39
+ try:
40
+ popen = subprocess.Popen(command, stdout=stdout_pipe, stderr=stdout_pipe)
41
+ except FileNotFoundError as exc:
42
+ raise ExecutableNotFoundError(_executable_not_found_message(command)) from exc
43
+
44
+ with popen as process:
28
45
  try:
29
46
  stdout, stderr = process.communicate()
30
47
  except OSError:
@@ -52,6 +69,20 @@ def run_command_for_code(command):
52
69
  return process.returncode
53
70
 
54
71
 
72
+ def _executable_not_found_message(command):
73
+ """
74
+ Explain that the executable of `command` is not installed or not on PATH.
75
+ """
76
+ executable = _ensure_unicode(command[0]) if command else ""
77
+ message = (
78
+ f"'{executable}' was not found. diff-cover needs '{executable}' to be "
79
+ "installed and on your PATH in order to run."
80
+ )
81
+ if executable == "git":
82
+ message += f" See {GIT_INSTALL_URL} for installation instructions."
83
+ return message
84
+
85
+
55
86
  def _ensure_unicode(text):
56
87
  """
57
88
  Ensures the text passed in becomes unicode
@@ -7,6 +7,7 @@ import warnings
7
7
  import xml.etree.ElementTree as etree
8
8
 
9
9
  from diff_cover import DESCRIPTION, VERSION
10
+ from diff_cover.command_runner import ExecutableNotFoundError
10
11
  from diff_cover.config_parser import Tool, get_config
11
12
  from diff_cover.diff_reporter import GitDiffReporter
12
13
  from diff_cover.git_diff import GitDiffFileTool, GitDiffTool
@@ -33,6 +34,7 @@ CSS_FILE_HELP = "Write CSS into an external file"
33
34
  FAIL_UNDER_HELP = (
34
35
  "Returns an error code if coverage or quality score is below this value"
35
36
  )
37
+ MINIMUM_CHANGE_HELP = "Always pass if total lines of change is below this value"
36
38
  IGNORE_STAGED_HELP = "Ignores staged changes"
37
39
  IGNORE_UNSTAGED_HELP = "Ignores unstaged changes"
38
40
  IGNORE_WHITESPACE = "When getting a diff ignore any and all whitespace"
@@ -155,6 +157,13 @@ def parse_coverage_args(argv):
155
157
  parser.add_argument(
156
158
  "--fail-under", metavar="SCORE", type=float, default=None, help=FAIL_UNDER_HELP
157
159
  )
160
+ parser.add_argument(
161
+ "--minimum-change",
162
+ metavar="LINES",
163
+ type=int,
164
+ default=None,
165
+ help=MINIMUM_CHANGE_HELP,
166
+ )
158
167
 
159
168
  parser.add_argument(
160
169
  "--ignore-staged", action="store_true", default=None, help=IGNORE_STAGED_HELP
@@ -229,6 +238,7 @@ def parse_coverage_args(argv):
229
238
  "compare_branch": "origin/main",
230
239
  "format": {},
231
240
  "fail_under": 0,
241
+ "minimum_change": 0,
232
242
  "ignore_staged": False,
233
243
  "ignore_unstaged": False,
234
244
  "ignore_untracked": False,
@@ -353,7 +363,7 @@ def generate_coverage_report(
353
363
 
354
364
  # Generate the report
355
365
  reporter.generate_report(output_file)
356
- return reporter.total_percent_covered()
366
+ return reporter.total_percent_covered(), reporter.total_num_lines()
357
367
 
358
368
 
359
369
  def handle_old_format(description, argv):
@@ -418,8 +428,13 @@ def main(argv=None, directory=None):
418
428
  level = logging.ERROR if quiet else logging.WARNING
419
429
  logging.basicConfig(format="%(message)s", level=level)
420
430
 
421
- GitPathTool.set_cwd(directory)
431
+ try:
432
+ GitPathTool.set_cwd(directory)
433
+ except ExecutableNotFoundError as exc:
434
+ LOGGER.error("%s", exc)
435
+ return 1
422
436
  fail_under = arg_dict.get("fail_under")
437
+ minimum_change = arg_dict.get("minimum_change") or 0
423
438
  diff_tool = None
424
439
 
425
440
  if not arg_dict["diff_file"]:
@@ -429,7 +444,7 @@ def main(argv=None, directory=None):
429
444
  else:
430
445
  diff_tool = GitDiffFileTool(arg_dict["diff_file"])
431
446
 
432
- percent_covered = generate_coverage_report(
447
+ percent_covered, num_changed_lines = generate_coverage_report(
433
448
  arg_dict["coverage_files"],
434
449
  arg_dict["compare_branch"],
435
450
  diff_tool,
@@ -449,7 +464,9 @@ def main(argv=None, directory=None):
449
464
  total_percent_float=arg_dict["total_percent_float"],
450
465
  )
451
466
 
452
- if percent_covered >= fail_under:
467
+ if percent_covered >= fail_under or (
468
+ minimum_change and num_changed_lines < minimum_change
469
+ ):
453
470
  return 0
454
471
  LOGGER.error("Failure. Coverage is below %i%%.", fail_under)
455
472
  return 1
@@ -14,6 +14,7 @@ import pluggy
14
14
 
15
15
  import diff_cover
16
16
  from diff_cover import hookspecs
17
+ from diff_cover.command_runner import ExecutableNotFoundError
17
18
  from diff_cover.config_parser import Tool, get_config
18
19
  from diff_cover.diff_cover_tool import (
19
20
  COMPARE_BRANCH_HELP,
@@ -30,6 +31,7 @@ from diff_cover.diff_cover_tool import (
30
31
  INCLUDE_UNTRACKED_HELP,
31
32
  JSON_REPORT_DEFAULT_PATH,
32
33
  MARKDOWN_REPORT_DEFAULT_PATH,
34
+ MINIMUM_CHANGE_HELP,
33
35
  QUIET_HELP,
34
36
  TOTAL_PERCENT_FLOAT_HELP,
35
37
  format_type,
@@ -146,6 +148,12 @@ def parse_quality_args(argv):
146
148
  parser.add_argument(
147
149
  "--fail-under", metavar="SCORE", type=float, help=FAIL_UNDER_HELP
148
150
  )
151
+ parser.add_argument(
152
+ "--minimum-change",
153
+ metavar="LINES",
154
+ type=int,
155
+ help=MINIMUM_CHANGE_HELP,
156
+ )
149
157
 
150
158
  parser.add_argument(
151
159
  "--ignore-staged", action="store_true", default=None, help=IGNORE_STAGED_HELP
@@ -217,6 +225,7 @@ def parse_quality_args(argv):
217
225
  "diff_range_notation": "...",
218
226
  "input_reports": [],
219
227
  "fail_under": 0,
228
+ "minimum_change": 0,
220
229
  "ignore_staged": False,
221
230
  "ignore_unstaged": False,
222
231
  "ignore_untracked": False,
@@ -297,7 +306,7 @@ def generate_quality_report(
297
306
  output_file = io.BytesIO() if quiet else sys.stdout.buffer
298
307
  reporter.generate_report(output_file)
299
308
 
300
- return reporter.total_percent_covered()
309
+ return reporter.total_percent_covered(), reporter.total_num_lines()
301
310
 
302
311
 
303
312
  def _call_reporter_factory(factory_fn, reports, options):
@@ -339,8 +348,13 @@ def main(argv=None, directory=None):
339
348
  level = logging.ERROR if quiet else logging.WARNING
340
349
  logging.basicConfig(format="%(message)s", level=level)
341
350
 
342
- GitPathTool.set_cwd(directory)
351
+ try:
352
+ GitPathTool.set_cwd(directory)
353
+ except ExecutableNotFoundError as exc:
354
+ LOGGER.error("%s", exc)
355
+ return 1
343
356
  fail_under = arg_dict.get("fail_under")
357
+ minimum_change = arg_dict.get("minimum_change") or 0
344
358
  tool = arg_dict["violations"]
345
359
  user_options = arg_dict.get("options")
346
360
  if user_options:
@@ -394,7 +408,7 @@ def main(argv=None, directory=None):
394
408
  reporter_factory_fn, input_reports, user_options
395
409
  )
396
410
 
397
- percent_passing = generate_quality_report(
411
+ percent_passing, num_changed_lines = generate_quality_report(
398
412
  reporter,
399
413
  arg_dict["compare_branch"],
400
414
  GitDiffTool(
@@ -410,7 +424,10 @@ def main(argv=None, directory=None):
410
424
  quiet=quiet,
411
425
  total_percent_float=arg_dict["total_percent_float"],
412
426
  )
413
- if percent_passing >= fail_under:
427
+
428
+ if percent_passing >= fail_under or (
429
+ minimum_change and num_changed_lines < minimum_change
430
+ ):
414
431
  return 0
415
432
 
416
433
  LOGGER.error("Failure. Quality is below %i.", fail_under)
@@ -72,10 +72,12 @@ class GitDiffTool:
72
72
  )[0]
73
73
  except CommandError as e:
74
74
  if "unknown revision" in str(e):
75
- raise ValueError(dedent(f"""
75
+ raise ValueError(
76
+ dedent(f"""
76
77
  Could not find the branch to compare to. Does '{compare_branch}' exist?
77
78
  the `--compare-branch` argument allows you to set a different branch.
78
- """)) from e
79
+ """)
80
+ ) from e
79
81
  raise
80
82
 
81
83
  def diff_unstaged(self):
@@ -115,7 +117,6 @@ class GitDiffTool:
115
117
 
116
118
 
117
119
  class GitDiffFileTool(GitDiffTool):
118
-
119
120
  def __init__(self, diff_file_path):
120
121
 
121
122
  self.diff_file_path = diff_file_path
@@ -0,0 +1,84 @@
1
+ """
2
+ Reading the ``<file>`` elements of a Clover XML coverage report.
3
+ """
4
+
5
+ import itertools
6
+ from collections import defaultdict
7
+
8
+ from diff_cover import util
9
+ from diff_cover.git_path import GitPathTool
10
+
11
+
12
+ class CloverFileIndex:
13
+ """
14
+ The ``<file>`` elements of one Clover report, indexed for lookup by path.
15
+
16
+ Building the index costs one walk of the document. Looking a source path up
17
+ by walking the document instead costs that walk once per source path, which
18
+ is quadratic in a report with many files.
19
+ """
20
+
21
+ def __init__(self, xml_document):
22
+ """
23
+ Index every ``<file>`` element of `xml_document`.
24
+
25
+ A file is matched either by its path relative to the repository root
26
+ or, when the report carries absolute paths, by a suffix test. The
27
+ suffix can only match when the last path segment does, so candidates
28
+ for it are grouped by that segment and the test is applied to the few
29
+ that share it.
30
+
31
+ Both tables store the document position of each element so that
32
+ lookups can return matches in document order, as a direct walk would.
33
+ """
34
+ self._by_relative_path = defaultdict(list)
35
+ self._by_last_segment = defaultdict(list)
36
+ for position, file_tree in enumerate(xml_document.findall(".//file")):
37
+ file_path = file_tree.get("path") or file_tree.get("name")
38
+ if not file_path:
39
+ continue
40
+
41
+ normalized_file_path = util.to_unix_path(file_path)
42
+ relative_file_path = util.to_unix_path(GitPathTool.relative_path(file_path))
43
+ self._by_relative_path[relative_file_path].append((position, file_tree))
44
+ self._by_last_segment[normalized_file_path.rsplit("/", 1)[-1]].append(
45
+ (position, normalized_file_path, file_tree)
46
+ )
47
+
48
+ def _files(self, src_path):
49
+ """
50
+ Return the ``<file>`` elements for `src_path`, in document order.
51
+ """
52
+ normalized_src_path = util.to_unix_path(src_path)
53
+
54
+ matches = {}
55
+ for position, file_tree in self._by_relative_path.get(normalized_src_path, ()):
56
+ matches[position] = file_tree
57
+ suffix = f"/{normalized_src_path}"
58
+ for position, normalized_file_path, file_tree in self._by_last_segment.get(
59
+ normalized_src_path.rsplit("/", 1)[-1], ()
60
+ ):
61
+ if normalized_file_path.endswith(suffix):
62
+ matches[position] = file_tree
63
+
64
+ return [matches[position] for position in sorted(matches)]
65
+
66
+ def line_nodes(self, src_path):
67
+ """
68
+ Return a list of nodes containing line information for `src_path`.
69
+
70
+ If the file is not present in the report, return None
71
+ """
72
+ files = self._files(src_path)
73
+ if not files:
74
+ return None
75
+ lines = []
76
+ for file_tree in files:
77
+ # Clover marks an executable line as one of these three types. PHPUnit's
78
+ # writer emits `method` for the declaration line of every function it
79
+ # measured; leaving it out reported those lines as unmeasured.
80
+ # https://github.com/sebastianbergmann/php-code-coverage/blob/main/src/Report/Clover.php
81
+ lines.append(file_tree.findall('./line[@type="method"]'))
82
+ lines.append(file_tree.findall('./line[@type="stmt"]'))
83
+ lines.append(file_tree.findall('./line[@type="cond"]'))
84
+ return list(itertools.chain(*lines))
@@ -17,6 +17,7 @@ from diff_cover.violationsreporters.base import (
17
17
  RegexBasedDriver,
18
18
  Violation,
19
19
  )
20
+ from diff_cover.violationsreporters.clover import CloverFileIndex
20
21
 
21
22
 
22
23
  class XmlCoverageReporter(BaseViolationReporter):
@@ -51,6 +52,9 @@ class XmlCoverageReporter(BaseViolationReporter):
51
52
  # once per source file in `_cache_file`.
52
53
  self._report_formats = [self._detect_report_format(root) for root in xml_roots]
53
54
 
55
+ # A `CloverFileIndex` for each Clover report, built on first use.
56
+ self._clover_file_index = [None] * len(xml_roots)
57
+
54
58
  self._src_roots = src_roots or [""]
55
59
  self._expand_coverage_report = expand_coverage_report
56
60
  self._branch_coverage = branch_coverage
@@ -125,41 +129,16 @@ class XmlCoverageReporter(BaseViolationReporter):
125
129
  lines = [clazz.findall("./lines/line") for clazz in classes]
126
130
  return list(itertools.chain(*lines))
127
131
 
128
- @staticmethod
129
- def get_src_path_line_nodes_clover(xml_document, src_path):
132
+ def get_src_path_line_nodes_clover(self, index, xml_document, src_path):
130
133
  """
131
134
  Return a list of nodes containing line information for `src_path`
132
135
  in `xml_document`.
133
136
 
134
137
  If file is not present in `xml_document`, return None
135
138
  """
136
-
137
- files = []
138
- normalized_src_path = util.to_unix_path(src_path)
139
- for file_tree in xml_document.findall(".//file"):
140
- file_path = file_tree.get("path") or file_tree.get("name")
141
- if not file_path:
142
- continue
143
-
144
- normalized_file_path = util.to_unix_path(file_path)
145
- relative_file_path = util.to_unix_path(GitPathTool.relative_path(file_path))
146
- if (
147
- relative_file_path == normalized_src_path
148
- or normalized_file_path.endswith(f"/{normalized_src_path}")
149
- ):
150
- files.append(file_tree)
151
- if not files:
152
- return None
153
- lines = []
154
- for file_tree in files:
155
- # Clover marks an executable line as one of these three types. PHPUnit's
156
- # writer emits `method` for the declaration line of every function it
157
- # measured; leaving it out reported those lines as unmeasured.
158
- # https://github.com/sebastianbergmann/php-code-coverage/blob/main/src/Report/Clover.php
159
- lines.append(file_tree.findall('./line[@type="method"]'))
160
- lines.append(file_tree.findall('./line[@type="stmt"]'))
161
- lines.append(file_tree.findall('./line[@type="cond"]'))
162
- return list(itertools.chain(*lines))
139
+ if self._clover_file_index[index] is None:
140
+ self._clover_file_index[index] = CloverFileIndex(xml_document)
141
+ return self._clover_file_index[index].line_nodes(src_path)
163
142
 
164
143
  @staticmethod
165
144
  def _detect_report_format(xml_document):
@@ -246,7 +225,7 @@ class XmlCoverageReporter(BaseViolationReporter):
246
225
  report_format = self._report_formats[i]
247
226
  if report_format == "clover":
248
227
  line_nodes = self.get_src_path_line_nodes_clover(
249
- xml_document, src_path
228
+ i, xml_document, src_path
250
229
  )
251
230
  _number = "num"
252
231
  _hits = "count"
@@ -475,9 +454,9 @@ class LcovCoverageReporter(BaseViolationReporter):
475
454
  if not has_da_directive:
476
455
  # No line execution data, use branch coverage
477
456
  if info["total"] > 0 and info["hit"] < info["total"]:
478
- lcov_report[source_file][
479
- line_no
480
- ] = 0 # Partial branch coverage
457
+ lcov_report[source_file][line_no] = (
458
+ 0 # Partial branch coverage
459
+ )
481
460
  else:
482
461
  lcov_report[source_file][line_no] = info["executions"]
483
462
  continue
@@ -955,7 +934,6 @@ class ClangFormatDriver(QualityDriver):
955
934
  """
956
935
  violations_dict = defaultdict(list)
957
936
  for report in reports:
958
-
959
937
  matches = list(re.finditer(self.clang_expression, report))
960
938
  for match in matches:
961
939
  if match is not None: