# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for ReplaySequenceBarrier.""" from __future__ import annotations import asyncio from google.adk.workflow.utils._replay_sequence_barrier import ReplaySequenceBarrier import pytest @pytest.mark.asyncio async def test_barrier_initialization(): """Verifies that barrier initializes sequence index and sets the first event.""" # Given a chronological sequence of completions sequence = ['NodeA@1', 'NodeB@1'] # When barrier is created barrier = ReplaySequenceBarrier(sequence) # Then state is correctly set assert barrier.sequence == sequence assert barrier.current_index == 0 assert len(barrier.events) == 2 assert barrier.events['NodeA@1'].is_set() assert not barrier.events['NodeB@1'].is_set() @pytest.mark.asyncio async def test_barrier_wait_blocks_and_unblocks(): """Verifies that wait blocks on subsequent keys and is unblocked by advance.""" sequence = ['NodeA@1', 'NodeB@1'] barrier = ReplaySequenceBarrier(sequence) # When first key waits, it completes instantly await barrier.wait('NodeA@1') # When second key waits, it blocks b_completed = False async def wait_b(): nonlocal b_completed await barrier.wait('NodeB@1') b_completed = True task = asyncio.create_task(wait_b()) await asyncio.sleep(0.05) assert not b_completed # Still blocked # When first key advances the sequence barrier.check_and_advance('NodeA@1') # Then index progresses and second event is released await task assert b_completed assert barrier.current_index == 1 assert barrier.events['NodeB@1'].is_set() def test_barrier_advance_out_of_order_ignored(): """Verifies that out-of-order advance calls are ignored and do not progress index.""" sequence = ['NodeA@1', 'NodeB@1'] barrier = ReplaySequenceBarrier(sequence) # When second key tries to advance out of order barrier.check_and_advance('NodeB@1') # Then state remains unchanged assert barrier.current_index == 0 assert not barrier.events['NodeB@1'].is_set() @pytest.mark.asyncio async def test_barrier_wait_non_existent_key(): """Verifies that waiting on a key not in sequence does not block.""" sequence = ['NodeA@1'] barrier = ReplaySequenceBarrier(sequence) # When a key not in sequence waits, it passes instantly await barrier.wait('NonExistent@1') # No blocks, successfully completes! assert True @pytest.mark.asyncio async def test_barrier_wait_timeout_on_divergence(): """Verifies that waiting on a blocked key raises RuntimeError due to timeout.""" # We use a short sequence where NodeB is never unblocked sequence = ['NodeA@1', 'NodeB@1'] # Use a fast timeout to keep the test rapid without mocking standard library functions barrier = ReplaySequenceBarrier(sequence, timeout_sec=0.01) with pytest.raises(RuntimeError, match='Replay divergence detected'): await barrier.wait('NodeB@1')