From 5678919e3b617bbd36281f90b6931c670ea9ab06 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 28 Jun 2025 18:44:16 +0200 Subject: [PATCH 01/34] =?UTF-8?q?=F0=9F=A7=AA=20Instrument=20C-extensions?= =?UTF-8?q?=20to=20collect=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 318229fcc..625519214 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,25 @@ NO_EXTENSIONS = True CFLAGS = ["-O0", "-g3", "-UNDEBUG"] if DEBUG_BUILD else ["-O3", "-DNDEBUG"] +# https://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html +# `-ftest-coverage` -> `.gcno` is in the same place as `.o` -> `{self.build_temp}/multidict` +# `-fprofile-dir` -> change the location of the `.gcda` file +# +# https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html +# `-fkeep-inline-functions` +# `-fkeep-static-functions` +# NOTE: Moving `_multidict.gcno` and `_multidict.o` under `multidict/` before running `pytest`, and `_multidict.gcda` after, worked. Prior to running gcovr. +if DEBUG_BUILD: + CFLAGS.extend(['--coverage', '-coverage', '-fprofile-arcs', '-ftest-coverage', '-fPIC']) + # CFLAGS.extend(['-fprofile-dir=./multidict/']) -if platform.system() != "Windows": +LDFLAGS = ['--coverage', '-coverage', '-lgcov'] if DEBUG_BUILD else [] + +# CFLAGS = ["-O2"] +# CFLAGS.extend(['--coverage', '-g', '-O0']) +# CFLAGS = ['-g'] + +if platform.system() != "Windows" and False: CFLAGS.extend( [ "-std=c11", @@ -25,20 +42,81 @@ ] ) +os.environ['CC'] = 'ccache gcc' +os.environ['CFLAGS'] = ' '.join(CFLAGS) +# os.environ['LDFLAGS'] = ' '.join(LDFLAGS) + extensions = [ Extension( "multidict._multidict", ["multidict/_multidict.c"], extra_compile_args=CFLAGS, + # extra_link_args=LDFLAGS, ), ] +from setuptools.command.build_ext import build_ext + + +class TraceableBinaryExtensionCmd(build_ext): + # def initialize_options(self): # ?? + # def finalize_options(self): # ?? + # super().finalize_options() + # # self._pkg_dir = self.get_finalized_command('build_py').get_package_dir('multidict') + # # self.build_temp = f'{self.build_lib}/{self._pkg_dir}/__debug-symbols__' + # # self.build_temp = self.build_lib + # # breakpoint() + + def run(self): + super().run() + from distutils import log + log.info(f'{self.build_lib=}') + log.info(f'{self.build_temp=}') + # self.copy_file(f'{self.build_temp}/multidict/_multidict.o', f'multidict/_multidict.o', level=self.verbose) # `.o` file seems unnecessary for gcovr to function + + for ext in self.extensions: + fullname = self.get_ext_fullname(ext.name) + # breakpoint() + filename = self.get_ext_filename(fullname) + modpath = fullname.split('.') + package = '.'.join(modpath[:-1]) + build_py = self.get_finalized_command('build_py') # ?? + package_dir = build_py.get_package_dir(package) + inplace_file = os.path.join(package_dir, os.path.basename(filename)) + regular_file = os.path.join(self.build_lib, filename) + tracing_data_file_in_tmp_dir = os.path.join(*modpath) + '.gcno' + # f'{self.build_temp}/{package_dir}/' + tracing_data_in_tmp_dir = f'{self.build_temp}/{package_dir}/' + tracing_data_in_package_dir = f'{package_dir}/__tracing-data__/' + # breakpoint() + + os.makedirs(os.path.join(tracing_data_in_package_dir, os.path.dirname(tracing_data_file_in_tmp_dir)), exist_ok=True) + with open(os.path.join(tracing_data_in_package_dir, '.gitignore'), 'w') as gitignore_fd: + gitignore_fd.writelines(('*', )) + + self.copy_file( + # os.path.join(tracing_data_in_tmp_dir, '_multidict.gcno'), # `.o` file is unnecessary for gcovr to function + os.path.join(self.build_temp, tracing_data_file_in_tmp_dir), # `.o` file is unnecessary for gcovr to function + os.path.join(tracing_data_in_package_dir, tracing_data_file_in_tmp_dir), + level=self.verbose, + ) + # GCOV_PREFIX=multidict/ GCOV_PREFIX_STRIP=4 some-venv/bin/python -Im pytest tests/test_istr.py + # GCOV_PREFIX_STRIP=2 some-venv/bin/python -Im pytest tests/test_istr.py + # GCOV_PREFIX=ext/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest tests/test_istr.py + # GCOV_PREFIX=__tracing-data__/ GCOV_PREFIX_STRIP=2 some-venv/bin/python -Im pytest tests/test_istr.py + # GCOV_PREFIX=multidict/__tracing-data__/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest + # *FINAL*: GCOV_PREFIX=multidict/__tracing-data__/multidict/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest + + if not NO_EXTENSIONS: print("*********************") print("* Accelerated build *") print("*********************") - setup(ext_modules=extensions) + setup( + cmdclass={'build_ext': TraceableBinaryExtensionCmd}, + ext_modules=extensions, + ) else: print("*********************") print("* Pure Python build *") From b27ede9b93ecd2e5aee426ecc5ece4d868f0888c Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sun, 29 Jun 2025 22:53:25 +0200 Subject: [PATCH 02/34] fixup! pathlib + cleanup --- setup.py | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/setup.py b/setup.py index 625519214..c6696b1c8 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import os +import pathlib import platform import sys @@ -23,7 +24,7 @@ CFLAGS.extend(['--coverage', '-coverage', '-fprofile-arcs', '-ftest-coverage', '-fPIC']) # CFLAGS.extend(['-fprofile-dir=./multidict/']) -LDFLAGS = ['--coverage', '-coverage', '-lgcov'] if DEBUG_BUILD else [] +# LDFLAGS = ['--coverage', '-coverage', '-lgcov'] if DEBUG_BUILD else [] # CFLAGS = ["-O2"] # CFLAGS.extend(['--coverage', '-g', '-O0']) @@ -60,45 +61,31 @@ class TraceableBinaryExtensionCmd(build_ext): - # def initialize_options(self): # ?? - # def finalize_options(self): # ?? - # super().finalize_options() - # # self._pkg_dir = self.get_finalized_command('build_py').get_package_dir('multidict') - # # self.build_temp = f'{self.build_lib}/{self._pkg_dir}/__debug-symbols__' - # # self.build_temp = self.build_lib - # # breakpoint() - def run(self): super().run() from distutils import log log.info(f'{self.build_lib=}') log.info(f'{self.build_temp=}') - # self.copy_file(f'{self.build_temp}/multidict/_multidict.o', f'multidict/_multidict.o', level=self.verbose) # `.o` file seems unnecessary for gcovr to function for ext in self.extensions: fullname = self.get_ext_fullname(ext.name) - # breakpoint() - filename = self.get_ext_filename(fullname) modpath = fullname.split('.') package = '.'.join(modpath[:-1]) build_py = self.get_finalized_command('build_py') # ?? - package_dir = build_py.get_package_dir(package) - inplace_file = os.path.join(package_dir, os.path.basename(filename)) - regular_file = os.path.join(self.build_lib, filename) - tracing_data_file_in_tmp_dir = os.path.join(*modpath) + '.gcno' - # f'{self.build_temp}/{package_dir}/' - tracing_data_in_tmp_dir = f'{self.build_temp}/{package_dir}/' - tracing_data_in_package_dir = f'{package_dir}/__tracing-data__/' - # breakpoint() - - os.makedirs(os.path.join(tracing_data_in_package_dir, os.path.dirname(tracing_data_file_in_tmp_dir)), exist_ok=True) - with open(os.path.join(tracing_data_in_package_dir, '.gitignore'), 'w') as gitignore_fd: - gitignore_fd.writelines(('*', )) + package_dir = pathlib.Path(build_py.get_package_dir(package)) + tracing_data_file_in_tmp_dir = pathlib.Path(*modpath).with_suffix('.gcno') # `.o` file is unnecessary for gcovr to function + tracing_data_in_package_dir = package_dir / '__tracing-data__' + breakpoint() + + tracing_data_file_in_tmp_dir_absolute = pathlib.Path(self.build_temp) / tracing_data_file_in_tmp_dir + tracing_data_file_in_package_dir = tracing_data_in_package_dir / tracing_data_file_in_tmp_dir + + tracing_data_file_in_package_dir.parent.mkdir(exist_ok=True, parents=True) + (tracing_data_in_package_dir / '.gitignore').write_text('*\n') self.copy_file( - # os.path.join(tracing_data_in_tmp_dir, '_multidict.gcno'), # `.o` file is unnecessary for gcovr to function - os.path.join(self.build_temp, tracing_data_file_in_tmp_dir), # `.o` file is unnecessary for gcovr to function - os.path.join(tracing_data_in_package_dir, tracing_data_file_in_tmp_dir), + tracing_data_file_in_tmp_dir_absolute, + tracing_data_file_in_package_dir, level=self.verbose, ) # GCOV_PREFIX=multidict/ GCOV_PREFIX_STRIP=4 some-venv/bin/python -Im pytest tests/test_istr.py From 1ce564b232ddae0a45156c6a7128be03af33b405 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Jun 2025 00:04:56 +0200 Subject: [PATCH 03/34] fixup! state integration --- setup.py | 18 ++++++++++++++++-- tests/conftest.py | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c6696b1c8..eaad4d1e9 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +import json import os import pathlib import platform @@ -75,7 +76,6 @@ def run(self): package_dir = pathlib.Path(build_py.get_package_dir(package)) tracing_data_file_in_tmp_dir = pathlib.Path(*modpath).with_suffix('.gcno') # `.o` file is unnecessary for gcovr to function tracing_data_in_package_dir = package_dir / '__tracing-data__' - breakpoint() tracing_data_file_in_tmp_dir_absolute = pathlib.Path(self.build_temp) / tracing_data_file_in_tmp_dir tracing_data_file_in_package_dir = tracing_data_in_package_dir / tracing_data_file_in_tmp_dir @@ -83,6 +83,19 @@ def run(self): tracing_data_file_in_package_dir.parent.mkdir(exist_ok=True, parents=True) (tracing_data_in_package_dir / '.gitignore').write_text('*\n') + log.info(f'GCOV_PREFIX={tracing_data_in_package_dir !s}') + log.info(f'GCOV_PREFIX_STRIP={len(pathlib.Path(self.build_temp).parents) !s}') + build_meta_json_path = (tracing_data_in_package_dir / 'build-metadata.json') + build_meta_json_path.write_text( + json.dumps( + { + 'GCOV_PREFIX': str(tracing_data_in_package_dir), + 'GCOV_PREFIX_STRIP': str(len(pathlib.Path(self.build_temp).parents)), + }, + ), + encoding='utf-8', + ) + breakpoint() self.copy_file( tracing_data_file_in_tmp_dir_absolute, tracing_data_file_in_package_dir, @@ -93,7 +106,8 @@ def run(self): # GCOV_PREFIX=ext/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest tests/test_istr.py # GCOV_PREFIX=__tracing-data__/ GCOV_PREFIX_STRIP=2 some-venv/bin/python -Im pytest tests/test_istr.py # GCOV_PREFIX=multidict/__tracing-data__/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest - # *FINAL*: GCOV_PREFIX=multidict/__tracing-data__/multidict/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest + # GCOV_PREFIX=multidict/__tracing-data__/multidict/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest + # *FINAL*: GCOV_PREFIX=multidict/__tracing-data__/ GCOV_PREFIX_STRIP=2 some-venv/bin/python -Im pytest if not NO_EXTENSIONS: diff --git a/tests/conftest.py b/tests/conftest.py index dfc1a6703..6c1b62611 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,9 @@ from __future__ import annotations import argparse +import importlib.resources +import json +import os import pickle from dataclasses import dataclass from functools import cached_property @@ -68,11 +71,33 @@ def multidict_implementation(request: pytest.FixtureRequest) -> MultidictImpleme return request.param # type: ignore[no-any-return] +@pytest.fixture(scope="session") +def _gcov_configuration() -> None: + """Configure the C-extension gcda write location in debug mode.""" + # NOTE: This is not using `monkeypatch` because it's unavailable with the + # NOTE: session scope. Additionally, we need these environment variables + # NOTE: to survive until the module gets to writing data to disk. + # NOTE: We don't currently run with `pytest-xdist` but might have to + # NOTE: improve this if we start, to avoid data races. Also, should we + # NOTE: integrate `tmp_path` instead of writing to `site-packages/`? + + build_meta_json_path = ( + importlib.resources.files('multidict') # FIXME: read from `pyproject.toml`? + / '__tracing-data__' + / 'build-metadata.json' + ) + gcov_env = json.loads(build_meta_json_path.read_text(encoding='utf-8')) + os.environ.update(gcov_env) + + @pytest.fixture(scope="session") def multidict_module( multidict_implementation: MultidictImplementation, + request: pytest.FixtureRequest, ) -> ModuleType: """Return a pre-imported module containing a multidict variant.""" + if not multidict_implementation.is_pure_python: + request.getfixturevalue('_gcov_configuration') return multidict_implementation.imported_module From 22dc02e7e82755d90d12ad568ad9de953d03a23b Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Jun 2025 00:19:54 +0200 Subject: [PATCH 04/34] Add a gcovr config --- gcovr.cfg | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 gcovr.cfg diff --git a/gcovr.cfg b/gcovr.cfg new file mode 100644 index 000000000..db44a4450 --- /dev/null +++ b/gcovr.cfg @@ -0,0 +1,9 @@ +filter = multidict/ + +html-details = coverage/index.html + +print-summary = yes + +search-path = multidict/__tracing-data__/ + +xml = coverage/coverage.xml From fa496c0c74ae96784d4695708db08316c7a56f06 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Jun 2025 00:24:31 +0200 Subject: [PATCH 05/34] debug! ccache @ many(musl)linux --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cf4a8b354..718dcf300 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,4 +13,4 @@ enable = ["cpython-freethreading"] [tool.cibuildwheel.linux] # Re-enable 32-bit builds (disabled by default in cibuildwheel 3.0) archs = ["auto", "auto32"] -before-all = "yum install -y libffi-devel || apk add --upgrade libffi-dev || apt-get install libffi-dev" +before-all = "yum install -y ccache libffi-devel || apk add --upgrade ccache libffi-dev || apt-get install ccache libffi-dev" From 5488bdaed8f9d6b57f76d5f8037ddb4fbad8dfe5 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Jun 2025 00:29:15 +0200 Subject: [PATCH 06/34] debug! PWD hack note --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eaad4d1e9..c477a210d 100644 --- a/setup.py +++ b/setup.py @@ -95,7 +95,8 @@ def run(self): ), encoding='utf-8', ) - breakpoint() + # TODO: PWD dir hack — https://maskray.me/blog/2023-04-25-compiler-output-files + # breakpoint() self.copy_file( tracing_data_file_in_tmp_dir_absolute, tracing_data_file_in_package_dir, From fc79cc7614a374861ff952a3945308d4570cd11c Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Jun 2025 00:40:58 +0200 Subject: [PATCH 07/34] Skip copyping gcno if not debug --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index c477a210d..8f256e362 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,10 @@ def run(self): log.info(f'{self.build_lib=}') log.info(f'{self.build_temp=}') + if not DEBUG_BUILD: + log.info('Not a debug build. Exiting early...') + return + for ext in self.extensions: fullname = self.get_ext_fullname(ext.name) modpath = fullname.split('.') From 33fd170dfce9ca5c578de0577e902d37ffd3e83e Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Jun 2025 00:41:26 +0200 Subject: [PATCH 08/34] Enable debug builds in cibuildwheel --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 718dcf300..730bd527c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,3 +14,6 @@ enable = ["cpython-freethreading"] # Re-enable 32-bit builds (disabled by default in cibuildwheel 3.0) archs = ["auto", "auto32"] before-all = "yum install -y ccache libffi-devel || apk add --upgrade ccache libffi-dev || apt-get install ccache libffi-dev" + +[tool.cibuildwheel.linux.environment] +MULTIDICT_DEBUG_BUILD = "1" From b0173ddc09ba64ae7701f4252ba89288a698cbb8 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Jun 2025 00:56:02 +0200 Subject: [PATCH 09/34] Copy gcno into the build lib dir --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8f256e362..03106d3d7 100644 --- a/setup.py +++ b/setup.py @@ -76,8 +76,7 @@ def run(self): fullname = self.get_ext_fullname(ext.name) modpath = fullname.split('.') package = '.'.join(modpath[:-1]) - build_py = self.get_finalized_command('build_py') # ?? - package_dir = pathlib.Path(build_py.get_package_dir(package)) + package_dir = pathlib.Path(self.build_lib) tracing_data_file_in_tmp_dir = pathlib.Path(*modpath).with_suffix('.gcno') # `.o` file is unnecessary for gcovr to function tracing_data_in_package_dir = package_dir / '__tracing-data__' From 0551382d0a5cb3eb8b09464206592e38755df038 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 01:52:25 +0200 Subject: [PATCH 10/34] fixup! refactor --- setup.py | 201 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 160 insertions(+), 41 deletions(-) diff --git a/setup.py b/setup.py index 03106d3d7..06db7efd4 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,11 @@ +import functools import json import os import pathlib import platform import sys +from distutils import log from setuptools import Extension, setup NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) @@ -62,56 +64,173 @@ class TraceableBinaryExtensionCmd(build_ext): + def _ext_mod_path_for(self, ext) -> list[str]: + if not DEBUG_BUILD: + raise LookupError + + fullname = self.get_ext_fullname(ext.name) + return fullname.split('.') + + def _ext_tracing_file_for(self, ext) -> Path: + if not DEBUG_BUILD: + raise LookupError + + modpath = self._ext_mod_path_for(ext) + + # NOTE: `.o` file is unnecessary for gcovr to function + return pathlib.Path(*modpath).with_suffix('.gcno') + + def _ext_tracing_data_dir_for(self, ext) -> tuple[str]: + if not DEBUG_BUILD: + raise LookupError + + modpath = self._ext_mod_path_for(ext) + package = '.'.join(modpath[:-1]) + build_py = self.get_finalized_command('build_py') # ?? + package_dir = pathlib.Path(build_py.get_package_dir(package)) + return package_dir / '__tracing-data__' + + @functools.cached_property + def _ext_tracing_data_dir_map(self) -> dict[str, tuple[str]]: + extensions_with_tracing_data = self.extensions if DEBUG_BUILD else () + + return { + ext.name: self._ext_tracing_data_dir_for(ext) + for ext in extensions_with_tracing_data + } + + def _extra_ext_data_files_for(self, ext) -> tuple[str]: + if not DEBUG_BUILD: + return () + + tracing_data_in_package_dir = self._ext_tracing_data_dir_map[ext.name] + tracing_data_file_in_tmp_dir = self._ext_tracing_file_for(ext) + tracing_data_file_in_package_dir = tracing_data_in_package_dir / tracing_data_file_in_tmp_dir + build_meta_json_path = tracing_data_in_package_dir / 'build-metadata.json' + + return (build_meta_json_path, tracing_data_file_in_package_dir) + + @functools.cached_property + def _extra_ext_data_files_map(self) -> dict[str, tuple[str]]: + extensions_with_tracing_data = self.extensions if DEBUG_BUILD else () + + return { + ext.name: self._extra_ext_data_files_for(ext) + for ext in extensions_with_tracing_data + } + + @functools.cached_property + def _extra_wheel_data_files(self) -> tuple[pathlib.Path]: + extensions_with_tracing_data = self.extensions if DEBUG_BUILD else () + + return tuple( + relative_path + for ext in extensions_with_tracing_data + for relative_path in self._extra_ext_data_files_map[ext.name] + ) + + def get_outputs(self) -> list[str]: + """Return absolute file paths to be included in the wheel.""" + base_outputs = super().get_outputs() + + build_dir_path = pathlib.Path(self.build_lib) + tracing_outputs = ( + build_dir_path / relative_path + for relative_path in self._extra_wheel_data_files + ) + + # NOTE: Files returned here end up in wheels and then `site-packages/`. + # NOTE: Editable installs rely on the tracing files to be copied into + # NOTE: the source checkout, which is happening in `run()`. + return [*base_outputs, *tracing_outputs] + + def build_extension(self, ext): + super().build_extension(ext) + + if not DEBUG_BUILD: + log.info('Not a debug build. Skipping tracing data...') + return + + log.info('Copying tracing data into the build directory') + tracing_data_file_in_tmp_dir = self._ext_tracing_file_for(ext) + tracing_data_in_package_dir = self._ext_tracing_data_dir_map[ext.name] + tracing_data_file_in_package_dir = tracing_data_in_package_dir / tracing_data_file_in_tmp_dir + + tracing_data_in_build_dir = pathlib.Path(self.build_lib) / tracing_data_in_package_dir + + tracing_data_file_in_build_dir_absolute = tracing_data_in_build_dir / tracing_data_file_in_tmp_dir + + tracing_data_file_in_build_dir_absolute.parent.mkdir(exist_ok=True, parents=True) + # NOTE: `gcc` writes `.gcno` files next to `.o` which is the temporary + # NOTE: directory for us (`build_temp`). This copies it over into the + # NOTE: regular build directory (`build_lib`) producing the layout we + # NOTE: expect in wheels / site-packages / source checkout. It's later + # NOTE: copied over to those places along with the shared libraries. + self.copy_file( + pathlib.Path(self.build_temp) / tracing_data_file_in_tmp_dir, + tracing_data_file_in_build_dir_absolute, + level=self.verbose, + ) + + # NOTE: `gcc` writes an absolute path to `.gcno` files into the shared + # NOTE: library at build time. It may point to an arbitrary temporary + # NOTE: directory that we don't care for. When pytest imports this + # NOTE: file, the C-extension attempts writing a `.gcda` file in the + # NOTE: same directory. We want it to be written in the source checkout + # NOTE: next to the tracing file. For this, we record information about + # NOTE: the desired location relative to the project root, bundle it + # NOTE: in the wheel and the tests will read from it, and set the + # NOTE: respective environment variables so the coverage data ends up + # NOTE: where we expect it to. `gcovr` will also work better with + # NOTE: predictable data locations. + # NOTE: + # NOTE: The contributors won't have to set the env vars manually + # NOTE: $ GCOV_PREFIX=multidict/__tracing-data__/ \ + # NOTE: GCOV_PREFIX_STRIP=2 \ + # NOTE: python -Im pytest + build_meta_path = tracing_data_in_build_dir / 'build-metadata.json' + tmp_path_length = len(pathlib.Path(self.build_temp).resolve().parents) + build_meta_path.write_text( + json.dumps( + { + 'GCOV_PREFIX': str(tracing_data_in_package_dir), + 'GCOV_PREFIX_STRIP': str(tmp_path_length), + }, + ), + encoding='utf-8', + ) + def run(self): super().run() - from distutils import log - log.info(f'{self.build_lib=}') - log.info(f'{self.build_temp=}') + + if not self.inplace: + log.info('Not an editable install. Skipping tracing data...') if not DEBUG_BUILD: - log.info('Not a debug build. Exiting early...') + log.info('Not a debug build. Skipping tracing data...') return - for ext in self.extensions: - fullname = self.get_ext_fullname(ext.name) - modpath = fullname.split('.') - package = '.'.join(modpath[:-1]) - package_dir = pathlib.Path(self.build_lib) - tracing_data_file_in_tmp_dir = pathlib.Path(*modpath).with_suffix('.gcno') # `.o` file is unnecessary for gcovr to function - tracing_data_in_package_dir = package_dir / '__tracing-data__' - - tracing_data_file_in_tmp_dir_absolute = pathlib.Path(self.build_temp) / tracing_data_file_in_tmp_dir - tracing_data_file_in_package_dir = tracing_data_in_package_dir / tracing_data_file_in_tmp_dir - - tracing_data_file_in_package_dir.parent.mkdir(exist_ok=True, parents=True) - (tracing_data_in_package_dir / '.gitignore').write_text('*\n') - - log.info(f'GCOV_PREFIX={tracing_data_in_package_dir !s}') - log.info(f'GCOV_PREFIX_STRIP={len(pathlib.Path(self.build_temp).parents) !s}') - build_meta_json_path = (tracing_data_in_package_dir / 'build-metadata.json') - build_meta_json_path.write_text( - json.dumps( - { - 'GCOV_PREFIX': str(tracing_data_in_package_dir), - 'GCOV_PREFIX_STRIP': str(len(pathlib.Path(self.build_temp).parents)), - }, - ), - encoding='utf-8', - ) - # TODO: PWD dir hack — https://maskray.me/blog/2023-04-25-compiler-output-files - # breakpoint() + log.info( + 'Editable install in debug mode. Copying tracing data in-tree...', + ) + + # NOTE: Editable installs usually expect data in-tree. This handles + # NOTE: the cases of `python setup.py build_ext --inplace` and + # NOTE: `pip install -e .` + # NOTE: Normal wheel builds include files returned by `get_outputs()`. + + build_dir_path = pathlib.Path(self.build_lib) + for relative_path in self._extra_wheel_data_files: + relative_path.parent.mkdir(exist_ok=True, parents=True) self.copy_file( - tracing_data_file_in_tmp_dir_absolute, - tracing_data_file_in_package_dir, + build_dir_path / relative_path, + relative_path, level=self.verbose, ) - # GCOV_PREFIX=multidict/ GCOV_PREFIX_STRIP=4 some-venv/bin/python -Im pytest tests/test_istr.py - # GCOV_PREFIX_STRIP=2 some-venv/bin/python -Im pytest tests/test_istr.py - # GCOV_PREFIX=ext/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest tests/test_istr.py - # GCOV_PREFIX=__tracing-data__/ GCOV_PREFIX_STRIP=2 some-venv/bin/python -Im pytest tests/test_istr.py - # GCOV_PREFIX=multidict/__tracing-data__/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest - # GCOV_PREFIX=multidict/__tracing-data__/multidict/ GCOV_PREFIX_STRIP=3 some-venv/bin/python -Im pytest - # *FINAL*: GCOV_PREFIX=multidict/__tracing-data__/ GCOV_PREFIX_STRIP=2 some-venv/bin/python -Im pytest + + for ext in self.extensions: + tracing_data_in_pkg_dir = self._ext_tracing_data_dir_map[ext.name] + (tracing_data_in_pkg_dir / '.gitignore').write_text('*\n') if not NO_EXTENSIONS: From 20bf2d1dd85f0c3a5c1763b97bc880abfe65cf53 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 01:52:50 +0200 Subject: [PATCH 11/34] fixup! Drop LDFLAGS --- setup.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.py b/setup.py index 06db7efd4..87a0b3325 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,6 @@ CFLAGS.extend(['--coverage', '-coverage', '-fprofile-arcs', '-ftest-coverage', '-fPIC']) # CFLAGS.extend(['-fprofile-dir=./multidict/']) -# LDFLAGS = ['--coverage', '-coverage', '-lgcov'] if DEBUG_BUILD else [] - # CFLAGS = ["-O2"] # CFLAGS.extend(['--coverage', '-g', '-O0']) # CFLAGS = ['-g'] @@ -48,14 +46,12 @@ os.environ['CC'] = 'ccache gcc' os.environ['CFLAGS'] = ' '.join(CFLAGS) -# os.environ['LDFLAGS'] = ' '.join(LDFLAGS) extensions = [ Extension( "multidict._multidict", ["multidict/_multidict.c"], extra_compile_args=CFLAGS, - # extra_link_args=LDFLAGS, ), ] From 4d5e4a9e39f0b50b5160d374a8ffa3c47d75f6c5 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 01:55:50 +0200 Subject: [PATCH 12/34] fixup! debug first --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 87a0b3325..33bd88632 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ from distutils import log from setuptools import Extension, setup -NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) DEBUG_BUILD = bool(os.environ.get("MULTIDICT_DEBUG_BUILD")) +NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) if sys.implementation.name != "cpython": NO_EXTENSIONS = True From 7b8dccf8ed01b1f5b6fbd6d5e3e1d103d4dd7b61 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 01:56:43 +0200 Subject: [PATCH 13/34] fixup! drop notes --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 33bd88632..6b8e87b88 100644 --- a/setup.py +++ b/setup.py @@ -22,10 +22,8 @@ # https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html # `-fkeep-inline-functions` # `-fkeep-static-functions` -# NOTE: Moving `_multidict.gcno` and `_multidict.o` under `multidict/` before running `pytest`, and `_multidict.gcda` after, worked. Prior to running gcovr. if DEBUG_BUILD: CFLAGS.extend(['--coverage', '-coverage', '-fprofile-arcs', '-ftest-coverage', '-fPIC']) - # CFLAGS.extend(['-fprofile-dir=./multidict/']) # CFLAGS = ["-O2"] # CFLAGS.extend(['--coverage', '-g', '-O0']) From 0ea9dfe9599ee6d84fb756f562c46ae8c7df5034 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 02:01:49 +0200 Subject: [PATCH 14/34] fixup! LDFLAGS --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6b8e87b88..b4988a4c4 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,8 @@ if DEBUG_BUILD: CFLAGS.extend(['--coverage', '-coverage', '-fprofile-arcs', '-ftest-coverage', '-fPIC']) +LDFLAGS = ['--coverage', '-coverage', '-lgcov'] if DEBUG_BUILD else [] + # CFLAGS = ["-O2"] # CFLAGS.extend(['--coverage', '-g', '-O0']) # CFLAGS = ['-g'] @@ -43,13 +45,13 @@ ) os.environ['CC'] = 'ccache gcc' -os.environ['CFLAGS'] = ' '.join(CFLAGS) extensions = [ Extension( "multidict._multidict", ["multidict/_multidict.c"], extra_compile_args=CFLAGS, + extra_link_args=LDFLAGS, ), ] From 7d5aac8e70f8d29e39c10194d60dc6bb55d47587 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 02:17:11 +0200 Subject: [PATCH 15/34] fixup! conditional CC --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b4988a4c4..660231572 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,9 @@ ] ) -os.environ['CC'] = 'ccache gcc' +if DEBUG_BUILD: + # https://gcovr.com/en/stable/cookbook.html#how-to-collect-coverage-for-c-extensions-in-python + os.environ['CC'] = 'ccache gcc' # no distutils/setuptools equivalent extensions = [ Extension( From 28faa0832ce4b4b4198e478021ca147687fe2905 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 02:56:42 +0200 Subject: [PATCH 16/34] fixup! flags --- setup.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 660231572..9f1fa9c02 100644 --- a/setup.py +++ b/setup.py @@ -22,14 +22,22 @@ # https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html # `-fkeep-inline-functions` # `-fkeep-static-functions` +# `-fprofile-prefix-map=old=new` +# `--coverage` is an alias for `-fprofile-arcs -ftest-coverage` when compiling and `-lgcov` when linking; `-coverage` seems to be its synonym. +# `-fprofile-abs-path` makes `gcovr` more robust — https://gcovr.com/en/stable/guide/compiling.html#compiler-options if DEBUG_BUILD: - CFLAGS.extend(['--coverage', '-coverage', '-fprofile-arcs', '-ftest-coverage', '-fPIC']) + CFLAGS.extend([ + '--coverage', + '-fkeep-inline-functions', + '-fkeep-static-functions', + '-fprofile-abs-path', + ]) -LDFLAGS = ['--coverage', '-coverage', '-lgcov'] if DEBUG_BUILD else [] +# LDFLAGS = ['-coverage'] if DEBUG_BUILD else [] +LDFLAGS = ['--coverage'] if DEBUG_BUILD else [] # CFLAGS = ["-O2"] # CFLAGS.extend(['--coverage', '-g', '-O0']) -# CFLAGS = ['-g'] if platform.system() != "Windows" and False: CFLAGS.extend( From 7e647fc472a7b95daba0e9f08b90b9c5a6446ae8 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 02:57:18 +0200 Subject: [PATCH 17/34] fixup! notes cleanup --- setup.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.py b/setup.py index 9f1fa9c02..e0a1f9aea 100644 --- a/setup.py +++ b/setup.py @@ -33,12 +33,8 @@ '-fprofile-abs-path', ]) -# LDFLAGS = ['-coverage'] if DEBUG_BUILD else [] LDFLAGS = ['--coverage'] if DEBUG_BUILD else [] -# CFLAGS = ["-O2"] -# CFLAGS.extend(['--coverage', '-g', '-O0']) - if platform.system() != "Windows" and False: CFLAGS.extend( [ From ccfb2317bdd634d57ffc29f23e5c11744d16f1ed Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 02:58:33 +0200 Subject: [PATCH 18/34] debug! gcovr @ cibuldwheel --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 730bd527c..475055e7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,11 @@ enable = ["cpython-freethreading"] # Re-enable 32-bit builds (disabled by default in cibuildwheel 3.0) archs = ["auto", "auto32"] before-all = "yum install -y ccache libffi-devel || apk add --upgrade ccache libffi-dev || apt-get install ccache libffi-dev" +test-requires = "gcovr -r requirements/pytest.txt" +test-command = [ + 'pytest -m "not leaks" --no-cov {project}/tests', + 'gcovr', +] [tool.cibuildwheel.linux.environment] MULTIDICT_DEBUG_BUILD = "1" From 46fe5588b33e3599580cf3a2b46e8ce51cbfdc20 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 1 Jul 2025 15:51:43 +0200 Subject: [PATCH 19/34] experiment! move gcovr into the test job --- .github/workflows/ci-cd.yml | 7 +++++++ pyproject.toml | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2457650df..19133aa8a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -371,6 +371,13 @@ jobs: --cov-report xml --junitxml=.test-results/pytest/test.xml --${{ matrix.no-extensions == 'Y' && 'no-' || '' }}c-extensions + - name: Experimentally run Gcovr [FIXME] + if: >- + !cancelled() + && runner.os == 'Linux' + run: | + pip install gcovr + gcovr - name: Produce markdown test summary from JUnit if: >- !cancelled() diff --git a/pyproject.toml b/pyproject.toml index 475055e7c..730bd527c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,11 +14,6 @@ enable = ["cpython-freethreading"] # Re-enable 32-bit builds (disabled by default in cibuildwheel 3.0) archs = ["auto", "auto32"] before-all = "yum install -y ccache libffi-devel || apk add --upgrade ccache libffi-dev || apt-get install ccache libffi-dev" -test-requires = "gcovr -r requirements/pytest.txt" -test-command = [ - 'pytest -m "not leaks" --no-cov {project}/tests', - 'gcovr', -] [tool.cibuildwheel.linux.environment] MULTIDICT_DEBUG_BUILD = "1" From 8f80c6af1838e3cd54c69078ec221fb3d6598642 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 00:29:00 +0200 Subject: [PATCH 20/34] experiment! only provision tracing env if ext has data --- tests/conftest.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6c1b62611..aabf24d9b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,11 +81,17 @@ def _gcov_configuration() -> None: # NOTE: improve this if we start, to avoid data races. Also, should we # NOTE: integrate `tmp_path` instead of writing to `site-packages/`? - build_meta_json_path = ( - importlib.resources.files('multidict') # FIXME: read from `pyproject.toml`? + tracing_data_dir_path = ( + # FIXME: read from `pyproject.toml`? + importlib.resources.files('multidict') / '__tracing-data__' - / 'build-metadata.json' ) + if not tracing_data_dir_path.is_dir(): + # NOTE: The C-extention was probably compiled without tracing or + # NOTE: packaging is borked. + return + + build_meta_json_path = tracing_data_dir_path / 'build-metadata.json' gcov_env = json.loads(build_meta_json_path.read_text(encoding='utf-8')) os.environ.update(gcov_env) From 17ef432bd140b048442ee631f4a15a7cd9381d46 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 00:37:58 +0200 Subject: [PATCH 21/34] fixup! change debug build var order back --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e0a1f9aea..1bd373275 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ from distutils import log from setuptools import Extension, setup -DEBUG_BUILD = bool(os.environ.get("MULTIDICT_DEBUG_BUILD")) NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) +DEBUG_BUILD = bool(os.environ.get("MULTIDICT_DEBUG_BUILD")) if sys.implementation.name != "cpython": NO_EXTENSIONS = True From c8b7e763cbe84869d4285a915c7ddeb9edb4fddf Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 00:41:14 +0200 Subject: [PATCH 22/34] fixup! path typing --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1bd373275..ccf29d231 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ def _ext_mod_path_for(self, ext) -> list[str]: fullname = self.get_ext_fullname(ext.name) return fullname.split('.') - def _ext_tracing_file_for(self, ext) -> Path: + def _ext_tracing_file_for(self, ext) -> pathlib.Path: if not DEBUG_BUILD: raise LookupError From 0568bd500e4a0bad079d7cce7b984380c7ea5909 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 01:03:25 +0200 Subject: [PATCH 23/34] debug! tracing files in CI --- .github/workflows/ci-cd.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 19133aa8a..a21cd9d19 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -371,12 +371,29 @@ jobs: --cov-report xml --junitxml=.test-results/pytest/test.xml --${{ matrix.no-extensions == 'Y' && 'no-' || '' }}c-extensions + - name: Experimentally find gcda/gcno [FIXME] + if: >- + !cancelled() + && runner.os == 'Linux' + run: | + find . -name '*.gc*' + - name: Experimentally find gcda/gcno in site-packages [FIXME] + if: >- + !cancelled() + && runner.os == 'Linux' + run: | + find "$(python -Ic 'import importlib.resources; print(str(importlib.resources.files("multidict")))')" -name '*.gc*' + - name: Experimentally install Gcovr [FIXME] + if: >- + !cancelled() + && runner.os == 'Linux' + run: | + gcovr - name: Experimentally run Gcovr [FIXME] if: >- !cancelled() && runner.os == 'Linux' run: | - pip install gcovr gcovr - name: Produce markdown test summary from JUnit if: >- From f5bc5cba3e11d438c556467bcf79e8f094328540 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 01:05:33 +0200 Subject: [PATCH 24/34] debug! show multidict files in CI --- .github/workflows/ci-cd.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a21cd9d19..b08111496 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -365,6 +365,8 @@ jobs: && '.' || steps.wheel-file.outputs.path }}' + - name: Experimental display of multidict files [FIXME] + run: pip show multidict --files - name: Run unittests run: >- python -Im pytest tests -v From b5e647fc10dd35c62a9145b2763016e106383fd5 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 03:05:26 +0200 Subject: [PATCH 25/34] experiment! Copy tracing data in-tree while testing --- tests/conftest.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index aabf24d9b..a00efba7e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,8 @@ from dataclasses import dataclass from functools import cached_property from importlib import import_module +from pathlib import Path +from shutil import copytree from types import ModuleType from typing import Callable, Type, Union @@ -91,8 +93,16 @@ def _gcov_configuration() -> None: # NOTE: packaging is borked. return + project_root = Path(__file__).parent.parent.resolve() + build_meta_json_path = tracing_data_dir_path / 'build-metadata.json' gcov_env = json.loads(build_meta_json_path.read_text(encoding='utf-8')) + + in_project_tracing_data_dir_path = project_root / gcov_env['GCOV_PREFIX'] + gcov_env['GCOV_PREFIX'] = str(in_project_tracing_data_dir_path) + + copytree(tracing_data_dir_path, in_project_tracing_data_dir_path, dirs_exist_ok=True) + os.environ.update(gcov_env) From 5432316fd14dfc19a0b15a42d8c0cf9dbc2dc75f Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 03:19:21 +0200 Subject: [PATCH 26/34] fixup! pip install gcovr --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b08111496..5403418ae 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -390,7 +390,7 @@ jobs: !cancelled() && runner.os == 'Linux' run: | - gcovr + pip install gcovr - name: Experimentally run Gcovr [FIXME] if: >- !cancelled() From 014dcad600f7bf8afb404d04ead469f5cd614cac Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 17:22:29 +0200 Subject: [PATCH 27/34] debug! Install ccache in debug runs --- .github/workflows/ci-cd.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 5403418ae..b739c9835 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -355,6 +355,12 @@ jobs: run: >- make clean shell: bash + - name: Pre-install ccache for gcov [FIXME] + if: steps.build_type.outputs.build_type == 'source' + run: >- + apt update -y + && apt install -y ccache + shell: bash - name: Self-install (from ${{ steps.build_type.outputs.build_type }}) env: MULTIDICT_NO_EXTENSIONS: ${{ matrix.no-extensions }} From cedeae0735decef144c6dc020fdbcf728bc6e4c1 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 17:54:53 +0200 Subject: [PATCH 28/34] fixup! privileges --- .github/workflows/ci-cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b739c9835..f902ff5df 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -358,8 +358,8 @@ jobs: - name: Pre-install ccache for gcov [FIXME] if: steps.build_type.outputs.build_type == 'source' run: >- - apt update -y - && apt install -y ccache + sudo apt update -y + && sudo apt install -y ccache shell: bash - name: Self-install (from ${{ steps.build_type.outputs.build_type }}) env: From 55f932bef047cfdf753a07f2fb29c17410bd8170 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 18:15:40 +0200 Subject: [PATCH 29/34] Include gcovr config into sdist --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 43625782e..62e6ba28d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include .coveragerc +include gcovr.cfg include pyproject.toml include pytest.ini include LICENSE From 46f86bba47e548cf46626330e9a65ce087914f35 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 21:58:08 +0200 Subject: [PATCH 30/34] experiment! coverage dir --- .github/workflows/ci-cd.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index f902ff5df..739f3bd1e 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -397,6 +397,12 @@ jobs: && runner.os == 'Linux' run: | pip install gcovr + - name: Experimentally make a coverage dir [FIXME] + if: >- + !cancelled() + && runner.os == 'Linux' + run: | + mkdir -pv coverage - name: Experimentally run Gcovr [FIXME] if: >- !cancelled() From 60cefdc2e90c08376ae8eb5e647da915cb3444e9 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 23:25:44 +0200 Subject: [PATCH 31/34] experiment! explicitly pass gcda/gcno files to gcovr --- gcovr.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gcovr.cfg b/gcovr.cfg index db44a4450..e46703e34 100644 --- a/gcovr.cfg +++ b/gcovr.cfg @@ -4,6 +4,7 @@ html-details = coverage/index.html print-summary = yes -search-path = multidict/__tracing-data__/ +search-path = multidict/__tracing-data__/multidict/_multidict.gcda +search-path = multidict/__tracing-data__/multidict/_multidict.gcno xml = coverage/coverage.xml From b9c6799ea84170af7089eaac7ede11a3e2ec84f7 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 2 Jul 2025 23:31:28 +0200 Subject: [PATCH 32/34] debug! Make gcovr verbose in CI --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 739f3bd1e..fd803681a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -408,7 +408,7 @@ jobs: !cancelled() && runner.os == 'Linux' run: | - gcovr + gcovr --verbose - name: Produce markdown test summary from JUnit if: >- !cancelled() From bea0a92815c8882e8a4a3090ffc3a221e502b211 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 3 Jul 2025 02:29:58 +0200 Subject: [PATCH 33/34] debug! Log gcovr leftovers --- .github/workflows/ci-cd.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index fd803681a..6609f7b45 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -409,6 +409,12 @@ jobs: && runner.os == 'Linux' run: | gcovr --verbose + - name: See Gcovr leftovers [FIXME] + if: >- + !cancelled() + && runner.os == 'Linux' + run: | + find ~/ -type f -name '*.gcov.json.gz' - name: Produce markdown test summary from JUnit if: >- !cancelled() From 8bea87b3d20fd50e794ca2d12abb18772fb7b838 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 7 Jul 2025 14:39:22 +0200 Subject: [PATCH 34/34] debug! log leftovers around workdir --- .github/workflows/ci-cd.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 6609f7b45..42f2869cd 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -409,12 +409,18 @@ jobs: && runner.os == 'Linux' run: | gcovr --verbose - - name: See Gcovr leftovers [FIXME] + - name: See Gcovr leftovers at home [FIXME] if: >- !cancelled() && runner.os == 'Linux' run: | find ~/ -type f -name '*.gcov.json.gz' + - name: See Gcovr leftovers in workdir [FIXME] + if: >- + !cancelled() + && runner.os == 'Linux' + run: | + find ../.. -type f -name '*.gcov.json.gz' - name: Produce markdown test summary from JUnit if: >- !cancelled()