From 66d4a788e09a357d6fde9aa9eb5c13abf39ff966 Mon Sep 17 00:00:00 2001 From: Kevin Bernal Date: Fri, 14 Feb 2025 14:37:12 +1100 Subject: [PATCH 1/5] fix(coverage): missing files in the coverage report if they have no tests This ensures that un-executed files (i.e. files that aren't tested) are included in the coverage report. The current behavior is that coverage.py excludes them by default. See https://coverage.readthedocs.io/en/7.6.10/source.html#execution --- CHANGELOG.md | 1 + python/private/python_bootstrap_template.txt | 9 +++++++++ python/private/stage2_bootstrap_template.py | 9 +++++++++ 3 files changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eaac3d9cc..8785bb036c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ Unreleased changes template. {obj}`--venvs_use_declare_symlink=no` to have it not create symlinks at build time (they will be created at runtime instead). (Fixes [#2489](https://github.com/bazelbuild/rules_python/issues/2489)) +* (coverage) Fix missing files in the coverage report if they have no tests. {#v1-2-0-added} ### Added diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index e3b39e30cd..1bd63217c2 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -433,6 +433,14 @@ def _RunForCoverage(python_program, main_filename, args, env, relative_files = True ''') PrintVerboseCoverage('Coverage entrypoint:', coverage_entrypoint) + + instrumented_files = [abs_path for abs_path, _ in InstrumentedFilePaths()] + unique_dirs = {os.path.dirname(file) for file in instrumented_files} + source = ",".join(unique_dirs) + + PrintVerboseCoverage("[coveragepy] Instrumented Files:\n" + "\n".join(instrumented_files)) + PrintVerboseCoverage("[coveragepy] Sources:\n" + "\n".join(unique_dirs)) + # First run the target Python file via coveragepy to create a .coverage # database file, from which we can later export lcov. ret_code = subprocess.call( @@ -443,6 +451,7 @@ relative_files = True "--rcfile=" + rcfile_name, "--append", "--branch", + "--source=" + source, main_filename ] + args, env=env, diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index b1f6b031aa..dcc468007b 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -293,6 +293,14 @@ def _maybe_collect_coverage(enable): relative_files = True """ ) + + instrumented_files = [abs_path for abs_path, _ in instrumented_file_paths()] + unique_dirs = {os.path.dirname(file) for file in instrumented_files} + source = list(unique_dirs) + + print_verbose_coverage("[coveragepy] Instrumented Files:\n" + "\n".join(instrumented_files)) + print_verbose_coverage("[coveragepy] Sources:\n" + "\n".join(unique_dirs)) + try: cov = coverage.Coverage( config_file=rcfile_name, @@ -314,6 +322,7 @@ def _maybe_collect_coverage(enable): # see https://github.com/nedbat/coveragepy/blob/bfb0c708fdd8182b2a9f0fc403596693ef65e475/coverage/inorout.py#L153-L164 "*/external/*", ], + source=source, ) cov.start() try: From 088e18235388bd45d1d7eeb4b6fa53e5374fa5fd Mon Sep 17 00:00:00 2001 From: Kevin Bernal Date: Wed, 5 Mar 2025 10:55:28 +1100 Subject: [PATCH 2/5] remove coveragepy prefix --- python/private/stage2_bootstrap_template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index dcc468007b..f3d9ea7f2f 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -298,8 +298,8 @@ def _maybe_collect_coverage(enable): unique_dirs = {os.path.dirname(file) for file in instrumented_files} source = list(unique_dirs) - print_verbose_coverage("[coveragepy] Instrumented Files:\n" + "\n".join(instrumented_files)) - print_verbose_coverage("[coveragepy] Sources:\n" + "\n".join(unique_dirs)) + print_verbose_coverage("Instrumented Files:\n" + "\n".join(instrumented_files)) + print_verbose_coverage("Sources:\n" + "\n".join(unique_dirs)) try: cov = coverage.Coverage( From 76786dcfe758e861bb6178c363eb89dc6e0f1149 Mon Sep 17 00:00:00 2001 From: Kevin Bernal Date: Wed, 5 Mar 2025 12:24:23 +1100 Subject: [PATCH 3/5] move source to .coveragerc file --- python/private/python_bootstrap_template.txt | 19 +++++++++---------- python/private/stage2_bootstrap_template.py | 19 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 1bd63217c2..f34e59d81c 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -425,22 +425,22 @@ def _RunForCoverage(python_program, main_filename, args, env, directory under the runfiles tree, and will recursively delete the runfiles directory if set. """ + instrumented_files = [abs_path for abs_path, _ in InstrumentedFilePaths()] + unique_dirs = {os.path.dirname(file) for file in instrumented_files} + + PrintVerboseCoverage("[coveragepy] Instrumented Files:\n" + "\n".join(instrumented_files)) + PrintVerboseCoverage("[coveragepy] Sources:\n" + "\n".join(unique_dirs)) + # We need for coveragepy to use relative paths. This can only be configured unique_id = uuid.uuid4() rcfile_name = os.path.join(os.environ['COVERAGE_DIR'], ".coveragerc_{}".format(unique_id)) with open(rcfile_name, "w") as rcfile: - rcfile.write('''[run] + rcfile.write(f'''[run] relative_files = True +source = +\t{"\n\t".join(unique_dirs)} ''') PrintVerboseCoverage('Coverage entrypoint:', coverage_entrypoint) - - instrumented_files = [abs_path for abs_path, _ in InstrumentedFilePaths()] - unique_dirs = {os.path.dirname(file) for file in instrumented_files} - source = ",".join(unique_dirs) - - PrintVerboseCoverage("[coveragepy] Instrumented Files:\n" + "\n".join(instrumented_files)) - PrintVerboseCoverage("[coveragepy] Sources:\n" + "\n".join(unique_dirs)) - # First run the target Python file via coveragepy to create a .coverage # database file, from which we can later export lcov. ret_code = subprocess.call( @@ -451,7 +451,6 @@ relative_files = True "--rcfile=" + rcfile_name, "--append", "--branch", - "--source=" + source, main_filename ] + args, env=env, diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index f3d9ea7f2f..cfa6ca4b36 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -276,6 +276,12 @@ def _maybe_collect_coverage(enable): yield return + instrumented_files = [abs_path for abs_path, _ in instrumented_file_paths()] + unique_dirs = {os.path.dirname(file) for file in instrumented_files} + + print_verbose_coverage("Instrumented Files:\n" + "\n".join(instrumented_files)) + print_verbose_coverage("Sources:\n" + "\n".join(unique_dirs)) + import uuid import coverage @@ -289,18 +295,12 @@ def _maybe_collect_coverage(enable): print_verbose_coverage("coveragerc file:", rcfile_name) with open(rcfile_name, "w") as rcfile: rcfile.write( - """[run] + f"""[run] relative_files = True +source = +\t{"\n\t".join(unique_dirs)} """ ) - - instrumented_files = [abs_path for abs_path, _ in instrumented_file_paths()] - unique_dirs = {os.path.dirname(file) for file in instrumented_files} - source = list(unique_dirs) - - print_verbose_coverage("Instrumented Files:\n" + "\n".join(instrumented_files)) - print_verbose_coverage("Sources:\n" + "\n".join(unique_dirs)) - try: cov = coverage.Coverage( config_file=rcfile_name, @@ -322,7 +322,6 @@ def _maybe_collect_coverage(enable): # see https://github.com/nedbat/coveragepy/blob/bfb0c708fdd8182b2a9f0fc403596693ef65e475/coverage/inorout.py#L153-L164 "*/external/*", ], - source=source, ) cov.start() try: From dbba868a203685a3cb26c2b6cb4f68b52934f07e Mon Sep 17 00:00:00 2001 From: Kevin Bernal Date: Wed, 5 Mar 2025 12:46:29 +1100 Subject: [PATCH 4/5] fix backslashes in f-strings --- python/private/python_bootstrap_template.txt | 3 ++- python/private/stage2_bootstrap_template.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index f34e59d81c..9f671ddda5 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -427,6 +427,7 @@ def _RunForCoverage(python_program, main_filename, args, env, """ instrumented_files = [abs_path for abs_path, _ in InstrumentedFilePaths()] unique_dirs = {os.path.dirname(file) for file in instrumented_files} + source = "\n\t".join(unique_dirs) PrintVerboseCoverage("[coveragepy] Instrumented Files:\n" + "\n".join(instrumented_files)) PrintVerboseCoverage("[coveragepy] Sources:\n" + "\n".join(unique_dirs)) @@ -438,7 +439,7 @@ def _RunForCoverage(python_program, main_filename, args, env, rcfile.write(f'''[run] relative_files = True source = -\t{"\n\t".join(unique_dirs)} +\t{source} ''') PrintVerboseCoverage('Coverage entrypoint:', coverage_entrypoint) # First run the target Python file via coveragepy to create a .coverage diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index cfa6ca4b36..4687bc003f 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -278,6 +278,7 @@ def _maybe_collect_coverage(enable): instrumented_files = [abs_path for abs_path, _ in instrumented_file_paths()] unique_dirs = {os.path.dirname(file) for file in instrumented_files} + source = "\n\t".join(unique_dirs) print_verbose_coverage("Instrumented Files:\n" + "\n".join(instrumented_files)) print_verbose_coverage("Sources:\n" + "\n".join(unique_dirs)) @@ -298,7 +299,7 @@ def _maybe_collect_coverage(enable): f"""[run] relative_files = True source = -\t{"\n\t".join(unique_dirs)} +\t{source} """ ) try: From d9429748a745697331a4cff2a5ef83c5f4e1559f Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Fri, 7 Mar 2025 07:05:14 +0000 Subject: [PATCH 5/5] move changelog to the unreleased version --- CHANGELOG.md | 2 +- examples/bzlmod/.python_version | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 examples/bzlmod/.python_version diff --git a/CHANGELOG.md b/CHANGELOG.md index 8785bb036c..49ce84f810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Unreleased changes template. ([#1169](https://github.com/bazelbuild/rules_python/issues/1169)). * (gazelle) Don't collapse depsets to a list or into args when generating the modules mapping file. Support spilling modules mapping args into a params file. +* (coverage) Fix missing files in the coverage report if they have no tests. {#v0-0-0-added} ### Added @@ -115,7 +116,6 @@ Unreleased changes template. {obj}`--venvs_use_declare_symlink=no` to have it not create symlinks at build time (they will be created at runtime instead). (Fixes [#2489](https://github.com/bazelbuild/rules_python/issues/2489)) -* (coverage) Fix missing files in the coverage report if they have no tests. {#v1-2-0-added} ### Added diff --git a/examples/bzlmod/.python_version b/examples/bzlmod/.python_version new file mode 100644 index 0000000000..bd28b9c5c2 --- /dev/null +++ b/examples/bzlmod/.python_version @@ -0,0 +1 @@ +3.9