Rust::com FFI mock implementation for unit test#388
Conversation
3ddde38 to
c33d943
Compare
85c633c to
94efa87
Compare
darkwisebear
left a comment
There was a problem hiding this comment.
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.
| /// # 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( |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I just asked myself why these methods are there in the first place... Why not let HandleContainer do that job?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
Removed as not required.
There was a problem hiding this comment.
Please enable Rust 2024 edition for all targets.
There was a problem hiding this comment.
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)] |
There was a problem hiding this comment.
Why hide the mock backend?
There was a problem hiding this comment.
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)] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
I prefer a dedicated struct here. This way, you can name the parts and it's clear what part does what.
There was a problem hiding this comment.
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! { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Isn't that redundant? Why is this necessary?
There was a problem hiding this comment.
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>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
79c4959 to
d15ac90
Compare
* Updated rust edition to 2024 in bazel com-api related rust target
d15ac90 to
2495ad2
Compare
53c4061 to
e014b35
Compare
* 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
e014b35 to
e5aee85
Compare
@darkwisebear, I have updated the mock backend, Instead of using |
c73650c to
ae47262
Compare
* Added centralized ARC in producer and consumer info
ae47262 to
5be1bf8
Compare
Uh oh!
There was an error while loading. Please reload this page.