Files
wehub-resource-sync ec2b666284
Continuous Integration / Pre-commit Linter (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.10) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.11) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.12) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.10) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.11) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.12) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.14) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Waiting to run
Copybara PR Handler / close-imported-pr (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:25:13 +08:00

219 lines
7.7 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.
"""Unit tests for _BranchPath.
Verifies that _BranchPath correctly parses, serializes, and manipulates
hierarchical dynamic execution branch paths.
"""
from __future__ import annotations
from google.adk.events._branch_path import _BranchPath
import pytest
def test_from_string_with_empty_string_returns_empty_path():
"""Parsing an empty string returns a _BranchPath with no segments."""
path = _BranchPath.from_string("")
assert path.segments == []
assert str(path) == ""
def test_from_string_with_single_segment_returns_path_with_one_segment():
"""Parsing a single name returns a _BranchPath with one segment."""
path = _BranchPath.from_string("agent_0")
assert path.segments == ["agent_0"]
assert str(path) == "agent_0"
def test_from_string_with_multiple_segments_returns_path_with_all_segments():
"""Parsing a dot-separated string returns a _BranchPath with all segments."""
path = _BranchPath.from_string("parent.child.node")
assert path.segments == ["parent", "child", "node"]
assert str(path) == "parent.child.node"
def test_equality_compares_path_segments():
"""Two _BranchPath objects are equal if and only if their segments match."""
path1 = _BranchPath.from_string("parent.child")
path2 = _BranchPath.from_string("parent.child")
path3 = _BranchPath.from_string("parent.other")
assert path1 == path2
assert path1 != path3
assert path1 != "parent.child" # Different type
def test_run_ids_extracts_all_run_ids_from_path():
"""run_ids extracts all run IDs (the part after '@') from all segments."""
# Given paths with various run ID patterns
path_with_ids = _BranchPath.from_string("parent@1.child@2.node")
path_no_ids = _BranchPath.from_string("parent.child")
path_mixed = _BranchPath.from_string("parent@1.child.node@3")
# Then the extracted run IDs match expectations
assert path_with_ids.run_ids == {"1", "2"}
assert path_no_ids.run_ids == set()
assert path_mixed.run_ids == {"1", "3"}
def test_parent_returns_parent_path_or_none_for_root():
"""parent returns a new _BranchPath excluding the leaf segment, or None."""
path = _BranchPath.from_string("parent.child.node")
assert path.parent == _BranchPath.from_string("parent.child")
assert path.parent.parent == _BranchPath.from_string("parent")
assert path.parent.parent.parent is None
def test_is_descendant_of_verifies_path_hierarchy_safely():
"""is_descendant_of returns True if the path is a strict sub-path of ancestor."""
# Given an ancestor and various comparison paths
ancestor = _BranchPath.from_string("parent.child")
descendant = _BranchPath.from_string("parent.child.node.leaf")
not_descendant = _BranchPath.from_string("parent.other")
same = _BranchPath.from_string("parent.child")
# Then descendant checks match expectations
assert descendant.is_descendant_of(ancestor)
assert not ancestor.is_descendant_of(descendant)
assert not not_descendant.is_descendant_of(ancestor)
assert not same.is_descendant_of(ancestor)
def test_is_descendant_of_is_immune_to_partial_name_prefix_match():
"""is_descendant_of compares segments, avoiding partial string prefix bugs."""
# Given an ancestor and a path that has a partial string prefix but different segment
ancestor = _BranchPath.from_string("agent_0")
descendant_with_prefix = _BranchPath.from_string("agent_00.child")
# Then it is not recognized as a descendant because segments don't match
assert not descendant_with_prefix.is_descendant_of(ancestor)
def test_common_prefix_finds_longest_shared_path():
"""common_prefix returns the longest common prefix of a list of paths."""
# Given a list of paths sharing a common prefix
paths = [
_BranchPath.from_string("parent.child.node1"),
_BranchPath.from_string("parent.child.node2.leaf"),
_BranchPath.from_string("parent.child.node3"),
]
# When finding the common prefix
result = _BranchPath.common_prefix(paths)
# Then the result matches the shared parent path
assert result == _BranchPath.from_string("parent.child")
def test_common_prefix_with_no_shared_path_returns_empty():
"""common_prefix returns an empty path if there is no shared prefix."""
paths = [
_BranchPath.from_string("parent.child"),
_BranchPath.from_string("other.child"),
]
result = _BranchPath.common_prefix(paths)
assert result == _BranchPath.from_string("")
def test_common_prefix_with_empty_list_returns_empty():
"""common_prefix returns an empty path if the input list is empty."""
result = _BranchPath.common_prefix([])
assert result == _BranchPath.from_string("")
def test_constructor_copies_segments_list():
"""_BranchPath copies the input segments list to ensure immutability."""
segments = ["parent", "child"]
path = _BranchPath(segments)
# Mutate the original list
segments.append("grandchild")
# The path segments should remain unchanged
assert path.segments == ["parent", "child"]
def test_append_single_segment_returns_new_path():
"""append adds a single segment to an existing path."""
path = _BranchPath.from_string("parent")
new_path = path.append("child")
assert new_path == _BranchPath.from_string("parent.child")
assert path == _BranchPath.from_string("parent") # Immutability check
def test_append_with_run_id_formats_segment():
"""append formats the segment as 'name@run_id' when run_id is provided."""
path = _BranchPath.from_string("parent")
new_path = path.append("child", run_id="call_123")
assert new_path == _BranchPath.from_string("parent.child@call_123")
def test_append_another_branch_path():
"""append combines segments from another _BranchPath instance."""
path1 = _BranchPath.from_string("parent")
path2 = _BranchPath.from_string("child.grandchild")
new_path = path1.append(path2)
assert new_path == _BranchPath.from_string("parent.child.grandchild")
def test_create_sub_branch_formats_string_correctly():
"""create_sub_branch constructs sub-branch strings safely."""
# With base branch and run ID
res1 = _BranchPath.create_sub_branch(
"parent.sub", name="child", run_id="run_1"
)
assert res1 == "parent.sub.child@run_1"
# Without base branch (None or empty)
res2 = _BranchPath.create_sub_branch(None, name="agent", run_id="fc_456")
assert res2 == "agent@fc_456"
# Dot-separated sub-path without run ID
res3 = _BranchPath.create_sub_branch("parent", name="agent.sub_agent")
assert res3 == "parent.agent.sub_agent"
def test_append_with_run_id_and_branch_path_raises_value_error():
"""append raises ValueError when run_id is provided with a _BranchPath."""
path1 = _BranchPath.from_string("parent")
path2 = _BranchPath.from_string("child")
with pytest.raises(ValueError, match="run_id cannot be provided"):
path1.append(path2, run_id="123")
def test_append_with_run_id_and_dot_separated_path_raises_value_error():
"""append raises ValueError when run_id is provided with a dot-separated path."""
path = _BranchPath.from_string("parent")
with pytest.raises(ValueError, match="run_id cannot be provided"):
path.append("child.sub", run_id="123")
def test_run_ids_filters_out_empty_run_ids():
"""run_ids filters out segments with empty run IDs (e.g. ending with '@')."""
path = _BranchPath.from_string("parent@.child@2.node@")
assert path.run_ids == {"2"}