From 9dd745fa70cbf68b3e8bacdc7e26fe3f10c3ea18 Mon Sep 17 00:00:00 2001 From: Johann Cohen-Tanugi Date: Mon, 26 Oct 2026 14:48:52 +0100 Subject: [PATCH 1/3] Make pyccl importable when isitgr is installed but unloadable An installed but broken isitgr (e.g. an invalid isitgrlib.so, as seen on the macOS arm64 CI runners) raises OSError from dlopen, not ImportError. The guarded import in pyccl/boltzmann.py only caught ModuleNotFoundError, so `import pyccl` and the whole test suite (conftest) failed. - boltzmann.py: catch (ImportError, OSError) on the early isitgr import. - tests/conftest.py: skip tests parametrized with boltzmann_isitgr when isitgr cannot be imported (previously they failed). - Add a regression test for the broken-isitgr import. This does not fix the underlying isitgr build on macOS arm64. --- pyccl/boltzmann.py | 2 +- pyccl/tests/conftest.py | 32 +++++++++++++++++++ pyccl/tests/test_power_mu_sigma_sigma8norm.py | 12 +++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/pyccl/boltzmann.py b/pyccl/boltzmann.py index 7f0e76407..bb337ff75 100644 --- a/pyccl/boltzmann.py +++ b/pyccl/boltzmann.py @@ -4,7 +4,7 @@ try: import isitgr # noqa: F401 -except ModuleNotFoundError: +except (ImportError, OSError): pass # prevent nans from isitgr from . import CCLError, Pk2D, check, lib, sigma8 diff --git a/pyccl/tests/conftest.py b/pyccl/tests/conftest.py index 7e68afe4f..6cdbc2562 100644 --- a/pyccl/tests/conftest.py +++ b/pyccl/tests/conftest.py @@ -1,6 +1,8 @@ """Config file for pytest. Allow caching for faster test completion. `$ pytest tests/ --use-cache` """ +import pytest + from .test_cclobject import all_subclasses, init_decorator import pyccl @@ -15,6 +17,36 @@ def pytest_generate_tests(metafunc): pyccl.Caching.enable() +def _isitgr_unusable_reason(): + """Return why isitgr cannot be used (None if it can). An installed + but unloadable isitgr (e.g. an invalid shared library) raises OSError, + not ImportError.""" + try: + import isitgr # noqa: F401 + except (ImportError, OSError) as exc: + return f"isitgr is not usable: {exc!r}" + return None + + +_ISITGR_REASON = _isitgr_unusable_reason() + + +def pytest_collection_modifyitems(config, items): + # Skip the tests that need a working isitgr when it is unavailable. + if _ISITGR_REASON is None: + return + skip = pytest.mark.skip(reason=_ISITGR_REASON) + for item in items: + callspec = getattr(item, "callspec", None) + if callspec is None: + continue + # Only compare string parameters: other parameters may be arrays, + # for which `==` would not return a single bool. + if any(isinstance(val, str) and val == "boltzmann_isitgr" + for val in callspec.params.values()): + item.add_marker(skip) + + # For testing, we want to make sure that all instances of the subclasses # of `CCLAutoRepr` contain all attributes listed in `__repr_attrs__`. # We run some things post-init for these subclasses, which are triggered diff --git a/pyccl/tests/test_power_mu_sigma_sigma8norm.py b/pyccl/tests/test_power_mu_sigma_sigma8norm.py index 55fb1732d..c73ce211c 100644 --- a/pyccl/tests/test_power_mu_sigma_sigma8norm.py +++ b/pyccl/tests/test_power_mu_sigma_sigma8norm.py @@ -94,3 +94,15 @@ def test_nonlin_camb_MG_error(): # Check that non-linear power spectrum isn't being used with sigma8 with pytest.raises(ValueError): ccl.nonlin_matter_power(ccl_cosmo, k, 1.0) + + +def test_import_survives_broken_isitgr(tmp_path, monkeypatch): + """An installed but unloadable isitgr (e.g. an invalid shared library + raising OSError at import) must not prevent importing pyccl.""" + pkg = tmp_path / "isitgr" + pkg.mkdir() + (pkg / "__init__.py").write_text("raise OSError('invalid shared lib')\n") + monkeypatch.syspath_prepend(str(tmp_path)) + with mock.patch.dict(sys.modules): + sys.modules.pop("isitgr", None) + reload(ccl.boltzmann) From 293cfcb3b0cfc16b0768206c1fbb5667622dfce2 Mon Sep 17 00:00:00 2001 From: Johann Cohen-Tanugi Date: Mon, 26 Oct 2026 15:11:06 +0100 Subject: [PATCH 2/3] CI: use the active Xcode SDK for CFLAGS/LDFLAGS on macOS CFLAGS/LDFLAGS pointed at /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk (a workaround from the macos-10.15/11 images, see #1025), while the compiler uses the SDK of the active Xcode (xcrun, see SDKROOT). On the current macos-latest image the Command Line Tools SDK is newer than the Xcode linker can parse, so the CMake C++ compiler check fails at link time ("ld: tapi error: malformed file ... libSystem.B.tbd ... unknown architecture"). Use $SDKROOT for both, consistent with CONDA_BUILD_SYSROOT. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3064bc20b..394e10047 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,8 +79,8 @@ jobs: echo "SDKROOT: ${SDKROOT}" echo "SDKROOT=${SDKROOT}" >> $GITHUB_ENV echo "CONDA_BUILD_SYSROOT=${SDKROOT}" >> $GITHUB_ENV - echo "CFLAGS=-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include" >> $GITHUB_ENV - echo "LDFLAGS=-L/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib" >> $GITHUB_ENV + echo "CFLAGS=-I${SDKROOT}/usr/include" >> $GITHUB_ENV + echo "LDFLAGS=-L${SDKROOT}/usr/lib" >> $GITHUB_ENV - name: Set current date as environment variable run: echo "TODAY=$(date +'%Y.%m.%d')" >> $GITHUB_ENV From c73ee08162cc9f864a8d94ad41846a0cfa3145b4 Mon Sep 17 00:00:00 2001 From: Johann Cohen-Tanugi Date: Mon, 26 Oct 2026 15:42:15 +0100 Subject: [PATCH 3/3] Skip isitgr benchmarks when isitgr is not importable --- benchmarks/conftest.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/benchmarks/conftest.py b/benchmarks/conftest.py index 311b28d29..b5bb3c9fc 100644 --- a/benchmarks/conftest.py +++ b/benchmarks/conftest.py @@ -1,6 +1,7 @@ """Config file for pytest. Allow caching for faster test completion. `$ pytest tests/ --use-cache` """ +import pytest def pytest_addoption(parser): @@ -11,3 +12,37 @@ def pytest_generate_tests(metafunc): if metafunc.config.getoption("use_cache"): import pyccl pyccl.Caching.enable() + + +def _isitgr_unusable_reason(): + """Return why isitgr cannot be used (None if it can). An installed + but unloadable isitgr (e.g. an invalid shared library) raises OSError, + not ImportError.""" + try: + import isitgr # noqa: F401 + except (ImportError, OSError) as exc: + return f"isitgr is not usable: {exc!r}" + return None + + +_ISITGR_REASON = _isitgr_unusable_reason() +_USES_ISITGR = {} + + +def _module_uses_isitgr(path): + # The isitgr transfer function is requested inside the tests, not through + # a pytest parameter, so look at the source of the test module. + if path not in _USES_ISITGR: + _USES_ISITGR[path] = "boltzmann_isitgr" in path.read_text( + encoding="utf-8") + return _USES_ISITGR[path] + + +def pytest_collection_modifyitems(config, items): + # Skip the benchmarks that need a working isitgr when it is unavailable. + if _ISITGR_REASON is None: + return + skip = pytest.mark.skip(reason=_ISITGR_REASON) + for item in items: + if _module_uses_isitgr(item.path): + item.add_marker(skip)