The ZeroBuffer Protocol Tests provide a standardized way to test the ZeroBuffer IPC library across different programming languages and execution modes. The architecture uses:
- SpecFlow for test orchestration with natural language scenarios
- Generic JSON-RPC protocol over stdin/stdout for language-agnostic communication
- Configuration-driven target selection for different language implementations
A completely generic test orchestrator that knows nothing about ZeroBuffer. It uses 12 total methods:
- 3 context-setting methods (Given/When/Then)
- 9 generic step methods (0, 1, or 2 parameters for each of Given/When/Then)
[Binding]
public class GenericJsonRpcSteps
{
// Context setters - detect configured targets
[Given(@"(.*) '(.*)'")]
public async Task Given1(string step, string param1)
{
if (IsConfiguredTarget(param1) && step.EndsWith(" is"))
{
_context["currentGivenTarget"] = GetOrStartProcess(param1);
return;
}
// Normal step execution...
}
// Generic steps with 0-2 parameters
[When(@"(.*)")]
[When(@"(.*) '(.*)'")]
[When(@"(.*) '(.*)' and '(.*)'")]
// etc...
}Each language implementation exposes a test service that communicates via JSON-RPC over stdin/stdout:
// C# implementation
var rpc = new JsonRpc(Console.OpenStandardOutput(), Console.OpenStandardInput());
rpc.AddLocalRpcTarget(new TestService());
rpc.StartListening();{
"targets": {
"csharp": {
"executable": "dotnet",
"arguments": "run --project ZeroBuffer.ProtocolTests -- serve"
},
"python": {
"executable": "python",
"arguments": "protocol_tests.py serve"
},
"cpp": {
"executable": "./zerobuffer_tests",
"arguments": "serve"
}
}
}The generic test service exposes a single method that interprets natural language steps:
Request:
{
"jsonrpc": "2.0",
"method": "executeStep",
"params": {
"stepType": "given",
"step": "create buffer 'test-101' with size '10240'",
"parameters": ["test-101", "10240"],
"context": {
"scenarioId": "abc123",
"previousResults": {}
}
},
"id": 1
}Response (Success):
{
"jsonrpc": "2.0",
"result": {
"success": true,
"data": {
"bufferId": "test-101",
"created": true
},
"context": {
"bufferId": "test-101",
"role": "reader"
}
},
"id": 1
}Response (Error):
{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "Buffer already exists",
"data": {
"bufferId": "test-101"
}
},
"id": 1
}The test service maintains its own state and interprets the step strings to determine what actions to take.
The test scenarios have been organized into feature files:
BasicCommunication.feature- Fundamental read/write operationsProcessLifecycle.feature- Process crash detection and recoveryPerformance.feature- Edge cases and performance testsDuplexChannel.feature- Bidirectional communicationErrorHandling.feature- Error conditions and recoverySynchronization.feature- Concurrent operationsEdgeCases.feature- Boundary conditionsPlatformSpecific.feature- Platform-specific behaviorBenchmarks.feature- Performance benchmarksStressTests.feature- Long-running stress testsInitialization.feature- Resource managementDuplexAdvanced.feature- Advanced duplex scenarios
Example from BasicCommunication.feature:
Feature: Basic Communication Tests
Tests for fundamental ZeroBuffer communication patterns
Background:
Given the test mode is configured
Scenario: Test 1.1 - Simple Write-Read Cycle
Given the reader is 'csharp'
And create buffer 'test-basic' with metadata size '1024' and payload size '10240'
When the writer is 'python'
And connect to buffer 'test-basic'
And write metadata with size '100'
And write frame with size '1024' and sequence '1'
Then the reader is 'csharp'
And read frame should have sequence '1' and size '1024'
And frame data should be valid
And signal space availableAll language implementations must support these standard test steps:
createBuffer(metadataSize, payloadSize)- Create a new shared memory bufferconnectToBuffer(bufferName)- Connect to an existing buffercloseBuffer()- Close buffer connection
writeMetadata(data)- Write metadata to bufferwriteFrame(data, sequence)- Write a frame with sequence numberwriteFrameZeroCopy(size, sequence, fillPattern)- Write using zero-copy API
readMetadata()- Read metadata from bufferreadFrame(timeoutMs)- Read next framereadFrameZeroCopy(timeoutMs)- Read using zero-copy API
verifyMetadata(expected)- Verify metadata matches expectedverifyFrame(frame, expectedData, expectedSequence)- Verify frame contentsverifyBufferState(expectedState)- Verify buffer state
isWriterConnected()- Check if writer is connectedisReaderConnected()- Check if reader is connectedgetBufferStats()- Get buffer statistics
simulateCrash()- Simulate process crashwaitForEvent(eventName, timeoutMs)- Wait for specific event
Tests are organized by category with specific numbering:
-
1xx: Basic Communication Tests
- 101: Simple Write-Read Cycle
- 102: Multiple Frames Sequential
- 103: Buffer Full Handling
-
2xx: Process Lifecycle Tests
- 201: Writer Crash Detection
- 202: Reader Crash Detection
- 203: Reader Replacement After Crash
-
14xx: Duplex Channel Tests
- 1401: Basic Request-Response
- 1402: Sequence Number Correlation
- 1403: Concurrent Client Operations
1. SpecFlow reads feature file
2. Detects target from step (e.g., "the reader is 'csharp'")
3. Starts/reuses process from configuration
4. Sends step via JSON-RPC: executeStep("given", "create buffer 'test-101' with size '10240'")
5. Target process interprets and executes step
6. Returns result via JSON-RPC
7. SpecFlow continues with next step
- Generic Test Runner: The SpecFlow test runner is completely generic and has no knowledge of ZeroBuffer
- Configuration-Driven: New language implementations are added via configuration only
- Natural Language: Tests are written in Gherkin and read like specifications
- Context Switching: The phrases "the reader is", "the writer is", etc. automatically switch execution context
- Single Process Per Language: Each language runs one process that can handle all roles (reader/writer/server/client)
- Create implementation that exposes JSON-RPC on stdin/stdout
- Implement the
executeStepmethod that interprets step strings - Add configuration entry:
"rust": { "executable": "./zerobuffer_tests_rust", "arguments": "serve" }
- Use in feature files:
Given the reader is 'rust'
- Create feature file with scenarios
- Use natural language that maps to test steps
- Switch contexts with "the [role] is '[target]'"
- No code changes needed in test runner
Each language implementation must parse step strings like:
"create buffer 'test-101' with size '10240'"→ Create a Reader with buffer"connect to buffer 'test-101'"→ Create a Writer"write frame with data 'Hello'"→ Use Writer to send data"read frame should return 'Hello'"→ Use Reader and assert
- Zero Domain Knowledge in Runner: Test runner doesn't know about ZeroBuffer
- Configuration-Only Extensions: Add languages without code changes
- Natural Language Tests: Non-developers can read and understand tests
- Automatic Context Management: Language switching is handled automatically
- Reusable Framework: Can test any system that implements the protocol
All test scenarios from TEST_SCENARIOS.md have been translated into Gherkin feature files. The original document remains as the reference specification, while the feature files provide executable test definitions.
The feature files use the same test numbering scheme:
- Test 1.1 = Simple Write-Read Cycle
- Test 2.1 = Writer Crash Detection
- Test 14.1 = Basic Request-Response
- etc.
See the feature files in the Features/ directory for the complete set of translated test scenarios.