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
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.
There was a problem hiding this comment.
Ok, this is reasonable. Maybe you want to clarify the comment above the attribute then?
There was a problem hiding this comment.
Update the comment for attribute.
|
|
||
| // 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.
* 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
e78d045 to
ce9b362
Compare
ce9b362 to
320a521
Compare
* Updated internal field to Arc * Removed Arc from runtime and using clone
320a521 to
d1f2a25
Compare
darkwisebear
left a comment
There was a problem hiding this comment.
We're not there yet. Please consider using a real mock for the bridge that implements all the trait methods and allows for setting call reactions on a per-test basis with defined values that are different from dangling pointers and other stuff that scares me.
| data_backing: Arc<Mutex<Option<BackingEntry>>>, | ||
| //ALLOC_SIZE holds the size of the allocatee type for the next get_allocatee_ptr call, allowing | ||
| //the mock to zero-fill the caller's slot correctly. | ||
| alloc_size: Arc<Mutex<usize>>, | ||
| //SAMPLE_BACKING holds a pointer to the heap-allocated sample data and a drop function to free it. | ||
| sample_backing: Arc<Mutex<Option<BackingEntry>>>, |
There was a problem hiding this comment.
Please merge these three entries by creating a struct that contains the three members and put it behind an Arc<Mutex<MockFFIBridgeState>>.
| unsafe fn get_allocatee_ptr( | ||
| &self, | ||
| event_ptr: *mut SkeletonEventBase, | ||
| allocatee_ptr: *mut std::ffi::c_void, |
There was a problem hiding this comment.
Sorry to ask this so late, but isn't the type of this pointer something known? Why does this have to be c_void?
| if size == 0 { | ||
| return false; | ||
| } | ||
| unsafe { std::ptr::write_bytes(allocatee_ptr as *mut u8, 0, size) }; |
There was a problem hiding this comment.
Why does this line exist?
| return false; | ||
| } | ||
| let size = *self.alloc_size.lock().expect("Failed to lock alloc_size"); | ||
| if size == 0 { |
There was a problem hiding this comment.
If 0 has a special meaning, you should use an Option<NonZeroUsize> instead of using a magic number.
| // `ProxyEventBase` is a ZST opaque type, a dangling non-null pointer is the | ||
| // canonical representation for "valid but empty" in the mock. |
There was a problem hiding this comment.
In which universe is a dangling pointer a "valid but empty representation"..? I really think it's a wording issue but it's just so disturbing. Please consider the mock approach suggested above.
| _allocatee_ptr: *const std::ffi::c_void, | ||
| _type_name: &str, | ||
| ) -> *mut std::ffi::c_void { | ||
| self.data_backing |
There was a problem hiding this comment.
That's brittle. If someone calls get_allocatee_data_ptr and retains the pointer, then a call to set_alloc_backing will invalidate the pointer and we see a use-after-free.
| // LIMITATION: The returned pointer is intentionally dangling and must NOT be | ||
| // dereferenced. It is only valid as a non-null sentinel for null-checks in the | ||
| // mock. Any test that dereferences this pointer will trigger undefined behavior. | ||
| std::ptr::NonNull::<ProxyEventBase>::dangling().as_ptr() |
There was a problem hiding this comment.
That makes me feel uneasy... while I do get the intent, this will lead to something that knowingly returns something bad. I anyway wonder why we cannot use the classic mock approach but we provide a type that intentionally does bad things in a global way. If we had a real mock type (as e.g. made by the mockall crate), we could do such things in a controlled way locally in the test that needs it.
I'd ask you to consider using a mockall mock or give reason why this isn't appropriate.
|
|
||
| impl MockFFIBridge { | ||
| // The handle container is backed by a heap-allocated zeroed stub so the pointer | ||
| // is stable and non-dangling. An extra `Arc` clone is intentionally forgotten |
There was a problem hiding this comment.
Ok, but why? isn't this something the test code may just as well do if it thinks that this is valid? (and under which circumstances is that necessary?). This strikes me again as unclean test code that makes many assumptions on how it's going to be used during testing. While this might work now, this is brittle when refactorings are being made.
| drop_fn, | ||
| }); | ||
| *self.alloc_size.lock().expect("Failed to lock alloc_size") = | ||
| std::mem::size_of::<SampleAllocateePtr<T>>(); |
There was a problem hiding this comment.
I wonder why alloc_size is sized with the SampleAllocateePtr's size, whereas the actual data is of size T. Isn't that a mismatch..?
Uh oh!
There was an error while loading. Please reload this page.