Skip to content

Rust::com FFI mock implementation for unit test#388

Open
bharatGoswami8 wants to merge 14 commits into
eclipse-score:mainfrom
bharatGoswami8:ffi_mock_impl_for_test
Open

Rust::com FFI mock implementation for unit test#388
bharatGoswami8 wants to merge 14 commits into
eclipse-score:mainfrom
bharatGoswami8:ffi_mock_impl_for_test

Conversation

@bharatGoswami8
Copy link
Copy Markdown
Contributor

@bharatGoswami8 bharatGoswami8 commented May 6, 2026

  • Added a native FFI bridge and a mock FFI bridge for deterministic local testing.
  • Runtime, consumer, and producer APIs are now pluggable via a bridge abstraction to support interchangeable backends.
  • Added an explicit error for requests that exceed the maximum allowed sample count.
  • Added mock-backed tests

@bharatGoswami8 bharatGoswami8 force-pushed the ffi_mock_impl_for_test branch from 85c633c to 94efa87 Compare May 6, 2026 06:44
Copy link
Copy Markdown

@rpreddyhv rpreddyhv left a comment

Choose a reason for hiding this comment

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

Looks good to me.

Copy link
Copy Markdown
Contributor

@darkwisebear darkwisebear left a comment

Choose a reason for hiding this comment

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

I think the idea is fine. However, the approach with the thread locals bothers me: Why not just instantiate the bridge type and hold test data inside the bridge type if necessary? The extra self parameter needed for the methods are ok imo.

Comment on lines +235 to +239
/// # Safety
/// `callback` must be a valid `FatPtr` referencing a callable compatible with the
/// find-service callback signature. `instance_spec` must be a valid `InstanceSpecifier`.
/// The returned handle must eventually be passed to `stop_find_service`.
unsafe fn start_find_service(
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.

I think this unsafety is misplaced. Afaiu, the main point is that FatPtr is essentially a type-erased Rust fat ptr. However, we could turn this method into something safe if we wrap the FatPtr into a FindServiceCallable and embed the FatPtr inside this type. The invariant of this type would then be the guarantee that it points to the appropriate callback. If the Rust compiler cannot verify that, then the unsafety is pushed to the point where this new type is instantiated. And this is imo the better place, since it is directly at the point where the original type gets "encoded into the Rust type system".

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.

I have created an issue/ task for this, will investigate and optimize the unsafe call in rust FFI.
#431

/// `container` must be a valid reference to a `HandleContainer` that was produced by the
/// bridge (e.g. from `start_find_service` callback). Calling this on a sentinel mock pointer
/// is only safe when the mock implementation ignores the inner pointer.
unsafe fn handle_container_size(container: &HandleContainer) -> usize;
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.

Same. If it is possible to safely construct a HandleContainer, the method's safety should not rely on the content being correct. If there is a precondition for the HandleContainer to be correct, this shall be pushed to construction time.

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.

I just asked myself why these methods are there in the first place... Why not let HandleContainer do that job?

Copy link
Copy Markdown
Contributor Author

@bharatGoswami8 bharatGoswami8 May 14, 2026

Choose a reason for hiding this comment

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

HandleContainer is still part of the proxy_bridge(old API implementation) and we don't have generic or mock support, anyways we are going to clean-up the old API implementation and required part will move in bridge folder.
(#432)
For bridge as we have enabled the mock support so rather than calling direct from proxy_bridge, we are using from this file.

Unsafe removed from method signature.

/// # Safety
/// Same as `handle_container_size`. `index` need not be in-bounds; the method must return
/// `None` when `index >= handle_container_size(container)`.
unsafe fn handle_container_get_at(
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.

unsafe also unclear.

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 as not required.

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.

Please enable Rust 2024 edition for all targets.

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 ffi BUILD as well as another COM-API lib related target also.

// As of now, it just provide basic mock implementation and returning values based on
// input parameters. In future, we will enhance this mock implementation to verify
// all the test cases and scenarios.
#![doc(hidden)]
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.

Why hide the mock backend?

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.

It was included because, as it is only needed for internal unit testing, so I didn’t see the need to expose it in the crate-level documentation.

// input parameters. In future, we will enhance this mock implementation to verify
// all the test cases and scenarios.
#![doc(hidden)]
#![allow(clippy::missing_safety_doc)]
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.

Yes, it's test code, but I don't see how this justifies skipping the safety comments. I'd rather say we can afford things that allocate more and are less performant in exchange to using less unsafe code instead.

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 the clippy lint and added safety comment on required places.

use std::cell::RefCell;

// Type alias for the heap-pointer + drop-glue pair stored in backing thread-locals.
type BackingEntry = (*mut std::ffi::c_void, fn(*mut std::ffi::c_void));
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.

I prefer a dedicated struct here. This way, you can name the parts and it's clear what part does what.

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.

changed to struct type

type BackingEntry = (*mut std::ffi::c_void, fn(*mut std::ffi::c_void));

// The thread-local storage is used to hold the backing data for the mock FFI bridge.
thread_local! {
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.

Why thread_local!? Because each test is run in a separate thread..? I think it would make more sense to generalize all the instance pointers in the trait and use them to store mock data. Or you just add self parameters and use the usual mocking methods instead of relying on static data. Imo that's almost always a smell, also in test code.

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 it was because of test running in multithread environment but as you suggested i have optimized the MockFFI and Runtime implementation to use the instance of bridge and &self in ffi method signature.


// Initialise the Lola runtime.
let mut runtime_builder = LolaRuntimeBuilderImpl::new();
let mut runtime_builder: LolaRuntimeBuilderImpl = LolaRuntimeBuilderImpl::new();
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.

Isn't that redundant? Why is this necessary?

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.

Compiler is not able to resolve the FFIBridge even though there is default and complaining about it

note: cannot satisfy `_: bridge_ffi_rs::FFIBridge`
help: the trait `bridge_ffi_rs::FFIBridge` is implemented for `bridge_ffi_lola::LolaFFIBridge`

explicit type annotation gives the compiler enough information to resolve the generic parameter.

type ProviderInfo = LolaProviderInfo;
type ConsumerInfo = LolaConsumerInfo;
pub struct LolaRuntimeImpl<B: FFIBridge = LolaFFIBridge> {
_marker: PhantomData<B>,
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.

Why not just B?

Copy link
Copy Markdown
Contributor Author

@bharatGoswami8 bharatGoswami8 May 14, 2026

Choose a reason for hiding this comment

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

we do not need a field with type with B , if we keep B then we need to construct instance and we don't need FFIBridge instance in LolaRuntimeImpl or runtime trait impl.

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.

But only because you use static data to keep the mock data. If you had an instance here, you might keep the data there. Much cleaner imo.

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.

understood, optimized the MockFFI and Runtime implementation to use the instance of bridge and &self in method signature

* FFI mock api implemented for runtime unit test
* FFIBridge as generic parameter so that can be change between mock and Lola FFI
* Added unit test using FFI mock
* Moved Lola specific FFI implementation in seperate file
* Updated HandleContainer method for Lola and Mock
* Added global variable cleanup
* Updated validation check on test
* remove unsafe from handleContainer support function from FFI
* Updated FFI type alias to struct type
@bharatGoswami8 bharatGoswami8 force-pushed the ffi_mock_impl_for_test branch 3 times, most recently from 79c4959 to d15ac90 Compare May 14, 2026 11:23
* Updated rust edition to 2024 in bazel com-api related rust target
@bharatGoswami8 bharatGoswami8 force-pushed the ffi_mock_impl_for_test branch from d15ac90 to 2495ad2 Compare May 14, 2026 11:36
@bharatGoswami8 bharatGoswami8 force-pushed the ffi_mock_impl_for_test branch 2 times, most recently from 53c4061 to e014b35 Compare May 15, 2026 08:38
* Taking FFI bridge as intance on runtime impl
* Removed static init in mock ffi
* Added paramter in mock type
* Calling to FFI function in runtime updated
@bharatGoswami8 bharatGoswami8 force-pushed the ffi_mock_impl_for_test branch from e014b35 to e5aee85 Compare May 15, 2026 09:23
@bharatGoswami8
Copy link
Copy Markdown
Contributor Author

I think the idea is fine. However, the approach with the thread locals bothers me: Why not just instantiate the bridge type and hold test data inside the bridge type if necessary? The extra self parameter needed for the methods are ok imo.

@darkwisebear, I have updated the mock backend, Instead of using thread_local!, I have adopted the approach you suggested - instantiating the bridge type and storing the test data within it.

@bharatGoswami8 bharatGoswami8 force-pushed the ffi_mock_impl_for_test branch 2 times, most recently from c73650c to ae47262 Compare May 15, 2026 11:16
* Added centralized ARC in producer and consumer info
@bharatGoswami8 bharatGoswami8 force-pushed the ffi_mock_impl_for_test branch from ae47262 to 5be1bf8 Compare May 15, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants