ec2b666284
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
109 lines
3.4 KiB
Python
109 lines
3.4 KiB
Python
# 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')
|