Skip to content

Migrate LibCI dds tests to pytest - #15104

Merged
OhadMeir merged 3 commits into
realsenseai:developmentfrom
AviaAv:dds-pytest-migration
Jun 25, 2026
Merged

OhadMeir merged 3 commits into
realsenseai:developmentfrom
AviaAv:dds-pytest-migration

Conversation

@AviaAv

@AviaAv AviaAv commented May 26, 2026

Copy link
Copy Markdown
Contributor

Tracked on: [RSDSO-21466]

Comment thread unit-tests/dds/adapter/pytest-depth.py Resolved
Comment thread unit-tests/dds/pytest-dds-metadata.py Resolved
Comment thread unit-tests/dds/pytest-options.py Resolved

@Nir-Az Nir-Az left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-generated review by rs-agentic bot

Overall this is a solid migration of the DDS test suite from the legacy test-*.py runner to pytest. The fixture patterns, pytestmark usage, pytest_check soft-assertions, and pytest.raises replacements are well done. A few issues are flagged inline below.

Comment thread unit-tests/dds/pytest-device-init.py Resolved
Comment thread unit-tests/dds/pytest-librs-context.py Resolved
Comment thread unit-tests/dds/pytest-device-init.py Resolved
Comment thread unit-tests/dds/pytest-control-reply.py Resolved
Comment thread unit-tests/dds/pytest-dds-metadata.py Resolved
Comment thread unit-tests/conftest.py Resolved
@AviaAv
AviaAv requested a review from Nir-Az May 26, 2026 16:13
Comment thread unit-tests/dds/pytest-streaming.py Outdated
remote.wait_until_ready()

with test.closure( '1W:1R, 1 message' ):
remote.run( 'topic = flexible.writer( participant, "1w1r" )' )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote.run is given a dynamically constructed code string (concatenating JSON). Avoid executing constructed code strings; use explicit RPC/callable interfaces instead.

Details

✨ AI Reasoning
​The change constructs Python code as strings and sends them to be executed remotely. Dynamically built code strings can hide behavior, making it harder to review and potentially enabling execution of unintended or malicious payloads. Even in tests, explicit function calls or structured RPC would be clearer and safer than composing code snippets. This concern arises from the added use of remote.run with concatenated strings that embed JSON payloads, which increases opacity of executed actions and could mask side effects.

🔧 How do I fix it?
Ensure code is transparent and not intentionally obfuscated. Avoid hiding functionality from code review. Focus on intent and deception, not specific patterns.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment thread unit-tests/dds/pytest-streaming.py Outdated
topic = flexible.reader( participant, "1w1r" )
topic.wait_for_writers()
remote.run( 'topic.wait_for_readers()' )
remote.run( 'topic.write( """' + sample1_json + '""" )' )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote.run executes a dynamically constructed string including sample1_json. Prefer explicit call interfaces to avoid hidden behavior.

Details

✨ AI Reasoning
​The added code builds code snippets by concatenating JSON into triple-quoted Python source strings and executes them remotely. This dynamic execution pattern obscures the exact operations performed and is commonly used to hide malicious behavior, making review and auditing harder.

🔧 How do I fix it?
Ensure code is transparent and not intentionally obfuscated. Avoid hiding functionality from code review. Focus on intent and deception, not specific patterns.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment thread unit-tests/dds/pytest-streaming.py Outdated
topic.wait_for_writers()
remote.run( 'topic.wait_for_readers()' )
remote.run( 'topic.write( """' + sample1_json + '""" )' )
remote.run( 'topic.write( """' + sample2_json + '""" )' )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote.run executes constructed code containing sample2_json; prefer structured remote invocation instead of executing assembled source.

Details

✨ AI Reasoning
​Another remote.run call is created by concatenating sample2_json into a string to be executed remotely. Dynamically executing such constructed source increases opacity and may hide unintended actions.

🔧 How do I fix it?
Ensure code is transparent and not intentionally obfuscated. Avoid hiding functionality from code review. Focus on intent and deception, not specific patterns.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment thread unit-tests/dds/pytest-streaming.py Outdated
r1.wait_for_writers()
r2.wait_for_writers()
remote.run( 'topic.wait_for_readers( 2 )' )
remote.run( 'topic.write( """' + sample1_json + '""" )' )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote.run executes constructed code containing sample1_json for write; pass payloads as data to a remote API instead of embedding them into source strings.

Details

✨ AI Reasoning
​The remote.run call composes a topic.write call with sample1_json embedded into the executed string. Embedding data into executable source increases the risk of hiding operations and makes intention less explicit during review.

🔧 How do I fix it?
Ensure code is transparent and not intentionally obfuscated. Avoid hiding functionality from code review. Focus on intent and deception, not specific patterns.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

@AviaAv
AviaAv force-pushed the dds-pytest-migration branch from 21e8fa4 to 44d657f Compare June 3, 2026 10:59
with test.remote.fork( script=__file__, nested_indent=' S' ) as remote:
yield remote

def test_device_discovery(remote_server):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_device_discovery is very long and mixes many responsibilities; split into smaller tests/fixtures to improve readability and reduce cognitive load.

Details

✨ AI Reasoning
​1. Identify the code element: a single test function was added that contains many different responsibilities: setup of DDS topic reader, callback handlers, watcher setup, numerous broadcast/add/remove scenarios, and multiple inline helper classes and functions.
2. Does it harm maintainability? Yes — the function is long and mixes setup, orchestration, multiple test scenarios and detailed checks, increasing cognitive burden.
3. Is it appropriate here? Test functions benefit from clarity and small focused scopes; this one bundles many flows.
4. Would fixing meaningfully improve codebase? Yes — splitting into smaller fixtures/helpers and separate tests would improve readability and reduce fragility.
5. Is it a common pattern? Large tests exist but this exceeds a reasonable test size threshold.
6. Fix scope: can be addressed within PR by extracting helpers/fixtures and breaking into smaller tests.
Confidence rationale: computed per provided rubric (base 0.5, +0.4 length, +0.4 complexity), resulting confidence 1.0.

🔧 How do I fix it?
Break down long functions into smaller helper functions. Aim for functions under 60 lines with fewer than 10 local variables.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AikidoSec ignore: out of scope for migration PR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Based on your feedback, we ignored this issue because of the following reason:

out of scope for migration PR

Comment thread unit-tests/py/rspy/devices.py Outdated
# underlying rs.context can be torn down (lets DDS tests have a clean
# FastDDS state). If the cache is gone, re-create a transient context
# and look up by serial number on first access.
if self._dev is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Device.handle re-acquire duplicates transient-context device-enumeration logic; consolidate into a shared helper to avoid duplicated enumeration loops.

Details

✨ AI Reasoning
​A new lazy re-acquire block was added to Device.handle that constructs an rs.context and iterates ctx.query_devices() to find a device by serial number. That repeats the same device-enumeration logic used elsewhere (transient context + device loop) to locate devices. Duplication increases maintenance burden because future changes to enumeration (filters, settings, error handling) would need parallel updates in both places. The new block is narrowly scoped and not trivial logging, so consolidation into a shared helper would be beneficial.

🔧 How do I fix it?
Delete extra code. Extract repeated code sequences into reusable functions or methods. Use loops or data structures to eliminate repetitive patterns.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

// Held by shared_ptr so the watcher's device-added/removed callbacks (which run on detached
// threads) keep the signal alive for their duration: a callback may still be in flight when this
// singleton is destroyed, and dereferencing it through a raw `this` would be a use-after-free.
std::shared_ptr< signal > const _callbacks = std::make_shared< signal >();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this part?
Real bug fixed? how discovered?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a fix for a 'use-after-free' - the watcher's device-added/removed callbacks ran on a detached thread that captured this, if one is running when the watcher died, it touched freed memory and crashed

Real bug, discovered when running tests as part of this PR

@@ -1181,10 +1181,11 @@ PYBIND11_MODULE(NAME, m) {
py::class_< dds_stream, std::shared_ptr< dds_stream > > stream_client_base( m, "stream", stream_base );
stream_client_base //
.def( "open", &dds_stream::open )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need a GIL release?
why only close?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when close runs it needs to join the callback thread, but if the callback thread is running, it needs GIL to finish the python callback - so it's stuck because close is holding it

this would deadlock as:
close holding GIL and waiting for reader thread
reader thread won't exit and wait for GIL

open does not wait for a thread join so not needed there

// GIL release needed: close/stop_streaming join the reader thread and hang if it's inside a callback
.def( "close", &dds_stream::close, py::call_guard< py::gil_scoped_release >() )
.def( "is_open", &dds_stream::is_open )
.def( "start_streaming", &dds_stream::start_streaming )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed, and not really needed on close_streaming too - just added it for consistency with close

@AviaAv
AviaAv requested a review from Nir-Az June 10, 2026 12:51
Nir-Az
Nir-Az previously approved these changes Jun 10, 2026

@Nir-Az Nir-Az left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work

@Nir-Az
Nir-Az self-requested a review June 10, 2026 19:03
@Nir-Az

Nir-Az commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Sorry wrong pr
Did not review yet

@AviaAv
AviaAv dismissed Nir-Az’s stale review June 11, 2026 07:28

Dismissing the review as accidently approved, please review when possible

@AviaAv
AviaAv requested a review from OhadMeir June 11, 2026 07:46
Comment thread .github/workflows/buildsCI.yaml Resolved
Comment thread unit-tests/dds/adapter/pytest-depth.py Resolved
Comment thread unit-tests/dds/adapter/pytest-depth.py Resolved
Comment thread unit-tests/dds/pytest-control-reply.py Resolved
Comment thread unit-tests/dds/pytest-md-syncer.py Resolved
check.is_true( len(dropped_metadata) > 0 )

with test.closure( 'Two threads, slow callback on image thread -> metadata builds up with eventual drops' ):
def test_two_threads_slow_callback_image_thread_metadata_buildup():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function names shorten the old names, but perhaps too short. Add comments explaining the expected logic, at least for multithreaded tests, as it is not always trivial

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some docstrings

def test_color_and_od_streams_have_distinct_types(remote_and_streams):
_, od_stream, color_stream, _, _ = remote_and_streams
# Verify the color stream is not mistaken for an object_detection stream
check.is_true( color_stream.type_string() != od_stream.type_string(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this will be tested in the fixture it can simplify the return values

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also would diff less than original :-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to test in fixture, test_color_and_od_streams_have_distinct_types removed

Comment thread unit-tests/dds/pytest-query-option.py Outdated
device = dds.device( participant, device_info )
device.wait_until_ready()
try:
yield remote, device

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote not used

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@Nir-Az
Nir-Az removed their request for review June 23, 2026 09:24
@AviaAv
AviaAv force-pushed the dds-pytest-migration branch from 28f10e3 to afdb6a2 Compare June 25, 2026 09:49

#############################################################################################
#
def test_d435i_extrinsics(remote_and_context):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_d435i_extrinsics is large and mixes discovery, profile selection, extrinsics calculation and many assertions; split responsibilities into smaller helper functions to reduce complexity.

Details

✨ AI Reasoning
​1. The function's goal is to discover a broadcast device, gather sensor stream profiles, compute extrinsics between several profile pairs, and assert many numerical expectations. 2. It mixes device discovery, profile selection loops, extrinsics computation, and multiple detailed numeric assertions, increasing cognitive load. 3. This is test code but still a single test function with many responsibilities and branches that make it harder to scan and maintain. 4. Splitting responsibilities (discovery, profile-selection, and per-extrinsics assertions) into smaller helpers would improve readability and reduce the chance of errors when tests are extended.

🔧 How do I fix it?
Break down long functions into smaller helper functions. Aim for functions under 60 lines with fewer than 10 local variables.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AikidoSec ignore: Out of scope for migration PR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Based on your feedback, we ignored this issue because of the following reason:

Out of scope for migration PR

@sysrsbuild-gh-agentic sysrsbuild-gh-agentic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-generated review by rs-agentic bot

Reviewed the squashed/rebased commits on afdb6a28c4956551ad2203b492f0f48c05027e1f. The sentinel-nonce improvement in rspy/test.py, the use-after-free fix in rsdds-device-factory.cpp, the GIL-release fix in pyrealdds.cpp, and the --not-live guard on devices.query are all solid changes.

However, the squash/rebase has introduced a copyright-year regression and also revealed that many new files (listed below) still carry their legacy years. Per .github/copilot-instructions.md, every new source file must use Copyright(c) <current year> (2026).

Files still needing Copyright(c) 2026:

File Current year
unit-tests/dds/adapter/pytest-depth.py 2024
unit-tests/dds/pytest-control-reply.py 2023-4 ← regression (was fixed in a pre-squash commit)
unit-tests/dds/pytest-guid.py 2023
unit-tests/dds/pytest-librs-connections.py 2022-4
unit-tests/dds/pytest-librs-device-properties.py 2022-4
unit-tests/dds/pytest-librs-extrinsics.py 2022-4
unit-tests/dds/pytest-librs-formats-conversion.py 2023-4
unit-tests/dds/pytest-librs-intrinsics.py 2022-4
unit-tests/dds/pytest-librs-options.py 2024
unit-tests/dds/pytest-no-metadata.py 2023
unit-tests/dds/pytest-option-value.py 2024
unit-tests/dds/pytest-options.py 2024
unit-tests/dds/pytest-participant.py 2022-4
unit-tests/dds/pytest-query-filter.py 2025
unit-tests/dds/pytest-query-option.py 2024
unit-tests/dds/pytest-streaming.py 2022
unit-tests/dds/pytest-time.py 2022

Note: renamed files (pytest-librs-context.py, pytest-md-syncer.py, pytest-stream-sensor-bridge.py) keep their original year — that is correct.

Change approved (from previous rs-agentic review): The TimeoutError fix in pytest-device-discovery.py (devices_added/devices_removed in the message) is present and correct. Copyright years for pytest-device-init.py, pytest-dds-metadata.py, and pytest-device-discovery.py were updated to 2026 and retained — change approved for those.

Comment thread unit-tests/dds/pytest-control-reply.py Resolved
Comment thread unit-tests/dds/adapter/pytest-depth.py Resolved
Comment thread unit-tests/conftest.py Resolved

@OhadMeir OhadMeir left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, lots of tests that needed to be migrated.

@OhadMeir
OhadMeir merged commit 9ba1751 into realsenseai:development Jun 25, 2026
30 checks passed
@AviaAv
AviaAv deleted the dds-pytest-migration branch June 25, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants