Testing of C# <-> FFI Interface
(Code) Example Of Cumbersome API
The current FFI testing approach has several pain points:
-
No systematic error path testing - Tests only validate success paths, leaving FFI error codes and error translation
untested.
-
Union marshaling requires manual workarounds - Complex union types like iox2_static_config_t cannot be properly
marshaled, requiring manual memory operations.
-
Handle lifecycle edge cases are untested - No tests for double-disposal, use-after-free detection, or stress testing
handle creation/cleanup cycles.
-
Callback testing is limited and not thread-safe - Service discovery uses thread-local state (_tempServiceList) which is
error-prone.
// Current approach: Only success paths tested, no FFI error validation
[Fact]
public void NativeLibraryLoads()
{
var exception = Record.Exception(() =>
{
Iox2NativeMethods.iox2_set_log_level_from_env_or(
Iox2NativeMethods.iox2_log_level_e.INFO);
});
Assert.Null(exception); // No error code validation
}
// Current workaround for union marshaling limitations
// Note: Due to the simplified constructor (to avoid union marshaling issues),
// PublishSubscribeConfig will be null. We can only verify the service list works.
// Pattern-specific configs will be null until we solve the union marshaling problem.
// Current handle passing pattern - unintuitive and error-prone
var publisherHandle = _handle.DangerousGetHandle();
var result = Native.Iox2NativeMethods.iox2_publisher_loan_slice_uninit(
ref publisherHandle, // Pass by reference for C to modify
IntPtr.Zero,
out var sampleHandle,
(UIntPtr)1);
// No validation of error codes beyond IOX2_OK
Improvement Suggestion
Introduce a comprehensive FFI testing framework that:
- Validates all FFI error codes - Test each error path and ensure proper translation to C# error types.
- Tests handle lifecycle robustly - Include double-disposal detection, use-after-free scenarios, and concurrent handle
operations.
- Provides thread-safe callback testing utilities - Replace thread-local state with proper synchronization mechanisms.
- Documents struct alignment validation - Add compile-time or runtime checks for struct size/alignment mismatches.
// Proposed: Comprehensive FFI error testing
[Theory]
[InlineData(iox2_service_builder_open_error_e.DOES_NOT_EXIST)]
[InlineData(iox2_service_builder_open_error_e.INSUFFICIENT_PERMISSIONS)]
[InlineData(iox2_service_builder_open_error_e.INTERNAL_FAILURE)]
public void FfiErrorCodes_AreProperlyTranslated(iox2_service_builder_open_error_e errorCode)
{
var csharpError = Iox2ErrorTranslator.Translate(errorCode);
Assert.NotEqual(Iox2Error.Unknown, csharpError);
Assert.NotNull(csharpError.Message);
}
// Proposed: Handle lifecycle tests
[Fact]
public void SafeHandleDoubleDisposeDoesNotThrow()
{
var handle = CreateTestHandle();
handle.Dispose();
var exception = Record.Exception(() => handle.Dispose());
Assert.Null(exception);
}
[Fact]
public void SafeHandleUseAfterDisposeThrowsObjectDisposedException()
{
var publisher = CreateTestPublisher();
publisher.Dispose();
Assert.Throws<ObjectDisposedException>(() => publisher.Loan<TestData>());
}
// Proposed: Thread-safe callback testing
[Fact]
public async Task ServiceDiscoveryConcurrentCallsAreThreadSafe()
{
var tasks = Enumerable.Range(0, 10)
.Select(_ => Task.Run(() => ServiceDiscovery.List()))
.ToArray();
var results = await Task.WhenAll(tasks);
Assert.All(results, r => Assert.NotNull(r));
}
// Proposed: Struct alignment validation utility
[Fact]
public void FfiStructsHaveCorrectSize()
{
// Validate C# struct sizes match C FFI expectations
Assert.Equal(
Iox2NativeMethods.iox2_node_builder_storage_t.Size,
FfiStructValidator.GetExpectedSize("iox2_node_builder_storage_t"));
}
Testing of C# <-> FFI Interface
(Code) Example Of Cumbersome API
The current FFI testing approach has several pain points:
No systematic error path testing - Tests only validate success paths, leaving FFI error codes and error translation
untested.
Union marshaling requires manual workarounds - Complex union types like
iox2_static_config_tcannot be properlymarshaled, requiring manual memory operations.
Handle lifecycle edge cases are untested - No tests for double-disposal, use-after-free detection, or stress testing
handle creation/cleanup cycles.
Callback testing is limited and not thread-safe - Service discovery uses thread-local state (
_tempServiceList) which iserror-prone.
Improvement Suggestion
Introduce a comprehensive FFI testing framework that:
operations.