test(eventhubs): add live tests for the event processor - #5093
Draft
Johnathan W (j7nw4r) wants to merge 2 commits into
Draft
test(eventhubs): add live tests for the event processor#5093Johnathan W (j7nw4r) wants to merge 2 commits into
Johnathan W (j7nw4r) wants to merge 2 commits into
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 3 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
The event processor had no live coverage for checkpoint resume, for shutdown, for load balancing across two instances, or for the blob checkpoint store. Eleven new tests cover those paths. Every new test is live gated with the recorded::test(live) attribute, so the offline suite reports each one as ignored. The live run is still open, and no test in this change has run against a real namespace yet. The test hub is shared and holds four partitions, so each test tags its events with a per-run marker and reads only its own events. A named tokio timeout bounds every stream read and every wait for a partition client, so a miss looks like a failure and never hangs. The tokio dev-dependency gains the sync feature. The tests in eventhubs_processor.rs drive the same hub, so each one takes a tokio::sync::Mutex on its first line.
A live run against a real namespace found three wrong assumptions. A partition client does not attach its receiver when the processor hands it out. `add_partition_client` only awaits `open_receiver_on_partition`, which does no network I/O, so the attach happens on the first poll of the stream and `Latest` resolves there. Two tests sent their events before that first poll, so the broker resolved `Latest` past those events and the read timed out. Poll each stream once before the send. An unknown consumer group never reaches `run()` for the same reason. `run()` stayed pending past 60 seconds. Assert the failure on the first poll of the partition client's stream, which is where the broker rejects the attach. Two Balanced processors that share a store can claim one partition in the same cycle. The loser gets an ETag mismatch, and `run()` ends with that error instead of treating a lost claim as a normal outcome. Tolerate it in that one test and name it, so the split and the delivery assertions still hold. 13 of the 14 tests in the file now pass live. The remaining failure, `second_processor_displaces_first_with_consumer_disconnected`, fails the same way on an unmodified origin/main worktree, so it is not from this change.
Johnathan W (j7nw4r)
force-pushed
the
j7nw4r/eventhubs-processor-live-tests
branch
from
August 25, 2026 18:13
4cd5440 to
350dd07
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This change adds 11 live tests for the Event Hubs event processor. The tests cover checkpoint resume across processor instances, graceful shutdown and restart, ownership records in the checkpoint store, the balanced load balancing strategy across two processors, invalid configuration errors, and an end to end run on
BlobCheckpointStoreagainst live Event Hubs and live Storage.Motivation
Checkpoint resume is the core promise of the processor. A processor that restarts and reprocesses from the start, or skips events, breaks the checkpoint contract, and no existing test detects it. The processor tests before this change never call
shutdown(), never share a checkpoint store between two processors, never readlist_ownershipsorlist_checkpoints, and never assert an error. Load balancing and graceful shutdown are the other two behaviors that a caller depends on in production.Changes
sdk/eventhubs/azure_messaging_eventhubs/tests/eventhubs_processor.rsfor checkpoint resume, shutdown, restart, ownership records, the balanced strategy, an unknown event hub name, and an unknown consumer group.sdk/eventhubs/azure_messaging_eventhubs/tests/eventhubs_processor_blob.rswith 2 live tests that run the processor onBlobCheckpointStore, one for a missing storage container and one for a checkpoint round trip.eventhubs_processor.rsand applies it to the new tests and to the five existing tests. Every processor receiver attaches at owner level 0, so two processor tests that run at the same time displace each other.syncfeature to the tokio dev dependency, which the serial mutex needs. This is the only change outside a test file, and no file undersrc/changes.defaultGroupconsumer group. Cargo runs test binaries in parallel and a file mutex cannot reach across binaries, so the separate consumer group is what keeps the two binaries apart.Three behaviors of the current code shape these tests, and each one is a way a weaker test would pass while proving nothing.
PartitionClient::update_checkpointreturnsOk(())and writes nothing when the received event carries no message annotations. Both checkpoint tests therefore assert that the checkpoint reached the store before they assert anything about resume. The resume test readslist_checkpointsand matches the sequence number. The blob test seeds a known prior value and asserts the stored value changed.EventProcessor::get_start_positionclones the configured default start position and overrides only the location, so theinclusiveflag is inherited from the default. Every new test setsinclusive: false. Withinclusive: truea resumed reader re-reads the checkpointed event and the resume assertion breaks silently.EventProcessor::shutdownclears a flag and closes no receiver, so a partition client that the processor already handed out keeps streaming. The shutdown tests assert thatrun()resolves toOk(())and that the processor issues no new partition client. They do not assert that an already issued partition client stops, because that assertion would fail against correct source. This gap is tracked in [Event Hubs] EventProcessor::shutdown() does not stop event delivery or release ownership #5096.The .NET tests detect every invalid configuration at start time. The Rust processor surfaces the same conditions at three different call sites, which the issue permits. An unknown event hub name fails at
build(), and a missing storage container fails atrun()on the first dispatch. An unknown consumer group reaches neither: a live run heldrun()pending past 60 seconds, because the receiver attaches on the first poll of the partition client's stream, so that test asserts there. The lazy attach is tracked in #5094. Each test pins the call that returns the error rather than the error text, because an unknown event hub and an unknown consumer group both map toamqp:not-foundand the crate error type has no not found variant. The missing container test is the exception and asserts the structured 404 with theContainerNotFounderror code.The bad namespace case adds no new test.
consumer_new_with_errorinsdk/eventhubs/azure_messaging_eventhubs/tests/eventhubs_consumer.rsalready opens a consumer against an invalid host and asserts the full error chain. A processor flavored copy would assert strictly less.Test plan
Run the live tests with this exact command, after deploying the test resources with
eng/common/TestResources/New-TestResources.ps1 -ServiceDirectory eventhubsand settingEVENTHUBS_HOST,EVENTHUB_NAME,AZURE_STORAGE_BLOB_ENDPOINT, andAZURE_STORAGE_BLOB_CONTAINER.The offline gates below all pass on this branch.
cargo fmt --all -- --checkexits 0.RUSTFLAGS=-Dwarnings cargo test --no-run --package azure_messaging_eventhubs --all-featuresexits 0.RUSTFLAGS=-Dwarnings cargo clippy -p azure_messaging_eventhubs --all-features --all-targets --no-depsexits 0.RUSTDOCFLAGS=-Dwarnings cargo doc -p azure_messaging_eventhubs --all-features --no-depsexits 0.cargo test --package azure_messaging_eventhubs --all-features --no-fail-fast -- --test-threads=1exits 0. The suite reports 144 unit tests passed, 6 checkpoint store tests passed, 45 doc tests passed, and no failures. The new tests report as ignored, 14 ineventhubs_processorand 2 ineventhubs_processor_blob.Two limits are known and recorded rather than fixed.
processor_shutdown_stops_new_partition_clientsasserts a negative, so it first asserts that the processor has partitions left to claim. A processor that already holds every partition also issues no new client, and the test would prove nothing. The precondition fails loudly instead of passing hollow.azure_storage_blobexposes no container create, so two concurrent runs ofprocessor_checkpoints_to_blob_storewould race on one checkpoint blob. CI runs the suite once, so this is a hazard for parallel developers, not for CI.Closes #4893
Live validation
Every test here ran against a live Event Hubs namespace on 2026-08-20: 13 of 14 in
eventhubs_processor.rsand 2 of 2 ineventhubs_processor_blob.rs.Command:
AZURE_TEST_MODE=live cargo test --package azure_messaging_eventhubs --test eventhubs_processor --test eventhubs_processor_blob -- --test-threads=1.Three tests were corrected after that run, and the commit message records each one. The remaining failure,
second_processor_displaces_first_with_consumer_disconnected, is not from this change: it fails the same way on a detached worktree of an unmodifiedorigin/main.Two further findings from the live run have their own issues: a lost ownership claim ending
run()under the balanced strategy is #5095, andupdate_checkpointsucceeding without writing when an event carries no annotations is #5097.