blob: 010b7230af7300e6eb9d22c87aa8e08582960fdf [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Shared pytest configuration and fixtures for all tests.
This module provides:
- Custom pytest markers (gpu, slow)
- Auto-skip logic for QDP tests when the native extension is not built
- Shared fixtures for QDP availability checking
QDP tests are automatically skipped if the _qdp extension is not available,
allowing contributors without CUDA to run the qumat test suite.
"""
import pytest
# Check if QDP extension is available at module load time
_QDP_AVAILABLE = False
_QDP_IMPORT_ERROR: str | None = "No module named '_qdp'"
try:
import _qdp
_QDP_AVAILABLE = True
_QDP_IMPORT_ERROR = None
except ImportError as e:
_QDP_IMPORT_ERROR = str(e)
def _gpu_available() -> bool:
"""Return True if a CUDA device is actually usable at runtime.
The ``_qdp`` extension can now be built and imported without the CUDA
toolkit -- it links stub CUDA Runtime symbols (see qdp-core ``build.rs`` /
``cuda_ffi.rs``). So importing ``_qdp`` no longer implies a working GPU,
and ``@pytest.mark.gpu`` tests must additionally check for a real device.
Prefers the native engine's own signal (``qumat_qdp.is_cuda_available()``),
which is correct even for a stub build: a host with a GPU + PyTorch but no
CUDA toolkit builds ``_qdp`` against stubs, where ``torch.cuda.is_available()``
would wrongly report True. Falls back to torch when the helper is absent.
"""
try:
from qumat_qdp import is_cuda_available
return bool(is_cuda_available())
except Exception:
pass
try:
import torch
return bool(torch.cuda.is_available())
except Exception:
return False
_GPU_AVAILABLE = _gpu_available()
def pytest_configure(config):
"""Register custom pytest markers."""
config.addinivalue_line(
"markers", "gpu: marks tests as requiring GPU and _qdp extension"
)
config.addinivalue_line("markers", "slow: marks tests as slow running")
def pytest_collection_modifyitems(config, items):
"""Auto-skip QDP tests when the extension is missing, and GPU tests when
no CUDA device is available.
``_qdp`` can now build/import without a GPU (stub CUDA symbols), so
extension availability alone no longer implies a usable device. GPU tests
are therefore skipped whenever ``torch.cuda.is_available()`` is False, even
when the extension imports -- otherwise they execute against the stub
runtime and abort the worker process.
"""
skip_no_qdp = pytest.mark.skip(
reason=f"QDP extension not available: {_QDP_IMPORT_ERROR}. "
"Build with: cd qdp/qdp-python && maturin develop"
)
skip_no_gpu = pytest.mark.skip(
reason="GPU required: no CUDA device available. The _qdp extension is "
"importable but linked against CUDA stubs, or no GPU is present."
)
# Tests that work without _qdp (PyTorch reference backend tests).
_NO_QDP_OK = {
"test_torch_ref.py",
"test_fallback.py",
"test_benchmark_utils.py",
"test_benchmark_cli_validation.py",
}
for item in items:
is_gpu = "gpu" in item.keywords
fspath_str = str(item.fspath)
needs_qdp = (
"testing/qdp" in fspath_str or "testing\\qdp" in fspath_str
) and not any(name in fspath_str for name in _NO_QDP_OK)
if not _QDP_AVAILABLE:
# No extension at all: skip GPU tests and everything needing _qdp.
if is_gpu or needs_qdp:
item.add_marker(skip_no_qdp)
elif is_gpu and not _GPU_AVAILABLE:
# Extension built (possibly with CUDA stubs) but no usable device.
item.add_marker(skip_no_gpu)
@pytest.fixture
def qdp_available():
"""
Fixture that skips the test if QDP extension is not available.
Usage:
def test_something_with_qdp(qdp_available):
from _qdp import QdpEngine
engine = QdpEngine(0)
...
"""
if not _QDP_AVAILABLE:
pytest.skip(f"QDP extension not available: {_QDP_IMPORT_ERROR}")
return True
@pytest.fixture
def qdp_engine(qdp_available):
"""
Fixture that provides a QDP engine instance.
Automatically skips if QDP is not available.
Usage:
def test_encoding(qdp_engine):
qtensor = qdp_engine.encode([1.0, 2.0], num_qubits=1, encoding_method="amplitude")
...
"""
from _qdp import QdpEngine
return QdpEngine(0)