Files
gx afc2dd7fff
build / cmake build (CUDA 12.4, Ubuntu 22.04) (pull_request) Failing after 1m50s
build / ffmpeg filter patch (out-of-tree) (pull_request) Has been skipped
python: DLPack + health stats + CUDA stream + docs (tasks #199-#202)
#199 DLPack export:
- frame.dlpack_y() / .dlpack_uv() — explicit multi-plane access для NV12
- frame.__dlpack__() / __dlpack_device__() — protocol для torch/cupy
- Capsule deleter правильно держит refcount на frame_keep_alive,
  releases shape/strides arrays. CUDA pointer принадлежит frame.

#200 Health/stats counters:
- frames_received, timeouts, errors — per-call counters
- last_seq, gap_count — proxy для drop count (NEWEST_ONLY mode)
- last_frame_pts_ns
- stats() — snapshot dict для MQTT health publish
- counted в pybind layer т.к. C API не expose'ит ring_occupancy

#201 Per-subscriber CUDA stream + thread-safety:
- consumer_stream kwarg в subscribe() — int (cudaStream_t pointer)
- subscriber.consumer_stream property
- Thread-safety contract в docstring CuframesSubscriber
- next_frame() передаёт consumer_stream_ в cuframes_subscriber_next

#202 Smoke test + docs:
- 10/10 pytest passed (расширен +2 теста на consumer_stream)
- docs/python.md (~250 строк): quick start, API reference, integration
  с PyTorch/CuPy, reconnect-loop pattern, per-stream usage,
  pitch alignment, thread-safety, error taxonomy, backpressure,
  Phase 0 limitations

Verify build + tests:
  cmake -B build-python -DBUILD_PYTHON_BINDINGS=ON
  cmake --build build-python -j
  pytest python/tests/ -v   # 10/10

Закрывает Phase 0 issue gx/cuframes#6.
Разблокирует goldix-smart-home/yolo-world-detector Phase 1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 21:33:21 +01:00

113 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Smoke tests для cuframes Python bindings.
В Phase 0 (skeleton) проверяем что:
- модуль импортируется
- версия читается
- error классы существуют и являются нормальной иерархией
Subscriber / DLPack тесты появятся в следующих фазах
(см. issue gx/cuframes#6, tasks #198+).
"""
import cuframes
def test_version_format():
v = cuframes.version_string()
assert isinstance(v, str)
parts = v.split(".")
assert len(parts) >= 3
assert all(p.isdigit() for p in parts[:3])
def test_protocol_version_is_uint():
pv = cuframes.protocol_version()
assert isinstance(pv, int)
assert pv >= 0
def test_pixel_format_enum_members():
assert cuframes.PixelFormat.NV12.value == 0
assert cuframes.PixelFormat.YUV420P.value == 1
def test_subscriber_mode_enum_members():
assert cuframes.SubscriberMode.NEWEST_ONLY.value == 0
assert cuframes.SubscriberMode.STRICT_ORDER.value == 1
def test_error_hierarchy():
"""Все subtype'ы наследуются от CuframesError."""
for sub in [
cuframes.CuframesPublisherGone,
cuframes.CuframesFrameTimeout,
cuframes.CuframesDeviceLost,
cuframes.CuframesShmError,
cuframes.CuframesProtocolMismatch,
cuframes.CuframesInvalidArgument,
cuframes.CuframesOutOfMemory,
cuframes.CuframesInternal,
]:
assert issubclass(sub, cuframes.CuframesError)
def test_subscriber_class_exposed():
"""CuframesSubscriber/CuframesFrame exposed как public classes."""
assert hasattr(cuframes, "CuframesSubscriber")
assert hasattr(cuframes, "CuframesFrame")
assert hasattr(cuframes, "subscribe")
def test_subscribe_to_missing_publisher_raises():
"""Subscribe к несуществующему publisher → CuframesError (subclass)
после connect_timeout_ms.
Этот тест работает на любом хосте (без живого cuframes-pub) — мы
верифицируем что error path работает и маппит CUFRAMES_ERR_*
в правильный Python exception.
"""
import pytest
with pytest.raises(cuframes.CuframesError):
cuframes.subscribe(
"definitely-not-existing-publisher-xyz",
connect_timeout_ms=100,
)
def test_subscriber_repr_when_unable_to_connect():
"""Лёгкий тест что repr не падает и close idempotent."""
import pytest
try:
sub = cuframes.subscribe("nope-xyz", connect_timeout_ms=100)
except cuframes.CuframesError:
return # ожидаемо
pytest.fail("subscribe должно было выкинуть exception")
def test_subscribe_accepts_consumer_stream_param():
"""consumer_stream — uintptr (cudaStream_t).
Проверяем что параметр accepted; реальное использование требует
cuda-python / torch.cuda.Stream — это в integration тестах
yolo-world-detector'а.
"""
import pytest
with pytest.raises(cuframes.CuframesError):
cuframes.subscribe(
"nope-xyz",
connect_timeout_ms=100,
consumer_stream=0, # 0 = default stream
)
def test_subscribe_kwargs_signature():
"""Проверяем что у subscribe правильный набор kwargs."""
import inspect
# Pybind11-обёртки не дают inspect.signature, но help_doc отражает их.
doc = cuframes.subscribe.__doc__
assert "consumer_name" in doc
assert "mode" in doc
assert "cuda_device" in doc
assert "connect_timeout_ms" in doc
assert "consumer_stream" in doc