Skip to content

Commit 18c1728

Browse files
test: add unit tests for _eval_set_results_manager_utils
Merge #6205 PiperOrigin-RevId: 954797056
1 parent d4d2f6e commit 18c1728

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
import json
18+
19+
from google.adk.evaluation._eval_set_results_manager_utils import _sanitize_eval_set_result_name
20+
from google.adk.evaluation._eval_set_results_manager_utils import create_eval_set_result
21+
from google.adk.evaluation._eval_set_results_manager_utils import parse_eval_set_result_json
22+
from google.adk.evaluation.eval_metrics import EvalStatus
23+
from google.adk.evaluation.eval_result import EvalCaseResult
24+
from google.adk.evaluation.eval_result import EvalSetResult
25+
import pytest
26+
27+
28+
def _build_eval_case_result(
29+
eval_id: str = "eval_1", session_id: str = "session_1"
30+
) -> EvalCaseResult:
31+
"""Builds a minimal but valid EvalCaseResult for testing."""
32+
return EvalCaseResult(
33+
eval_set_id="eval_set_1",
34+
eval_id=eval_id,
35+
final_eval_status=EvalStatus.PASSED,
36+
overall_eval_metric_results=[],
37+
eval_metric_result_per_invocation=[],
38+
session_id=session_id,
39+
)
40+
41+
42+
class TestSanitizeEvalSetResultName:
43+
44+
def test_replaces_forward_slash_with_underscore(self):
45+
assert _sanitize_eval_set_result_name("app/eval_set") == "app_eval_set"
46+
47+
def test_replaces_all_forward_slashes(self):
48+
assert _sanitize_eval_set_result_name("a/b/c/d") == "a_b_c_d"
49+
50+
def test_name_without_slash_is_unchanged(self):
51+
assert _sanitize_eval_set_result_name("app_eval_set_123") == (
52+
"app_eval_set_123"
53+
)
54+
55+
def test_empty_name_is_unchanged(self):
56+
assert _sanitize_eval_set_result_name("") == ""
57+
58+
59+
class TestCreateEvalSetResult:
60+
61+
def test_creates_eval_set_result_with_expected_fields(self):
62+
eval_case_results = [_build_eval_case_result()]
63+
64+
result = create_eval_set_result(
65+
app_name="my_app",
66+
eval_set_id="my_eval_set",
67+
eval_case_results=eval_case_results,
68+
)
69+
70+
assert isinstance(result, EvalSetResult)
71+
assert result.eval_set_id == "my_eval_set"
72+
assert result.eval_case_results == eval_case_results
73+
74+
def test_result_id_encodes_app_eval_set_and_timestamp(self):
75+
result = create_eval_set_result(
76+
app_name="my_app",
77+
eval_set_id="my_eval_set",
78+
eval_case_results=[],
79+
)
80+
81+
# The id is "{app_name}_{eval_set_id}_{timestamp}" and the timestamp is
82+
# stored verbatim as the creation_timestamp.
83+
assert result.eval_set_result_id == (
84+
f"my_app_my_eval_set_{result.creation_timestamp}"
85+
)
86+
87+
def test_result_name_is_sanitized(self):
88+
# A "/" in the id (here via the app name) must not survive into the name,
89+
# since the name is used to derive a filesystem-safe identifier.
90+
result = create_eval_set_result(
91+
app_name="my/app",
92+
eval_set_id="my_eval_set",
93+
eval_case_results=[],
94+
)
95+
96+
assert "/" not in result.eval_set_result_name
97+
assert result.eval_set_result_name == (
98+
result.eval_set_result_id.replace("/", "_")
99+
)
100+
101+
def test_creates_result_with_empty_eval_case_results(self):
102+
result = create_eval_set_result(
103+
app_name="my_app",
104+
eval_set_id="my_eval_set",
105+
eval_case_results=[],
106+
)
107+
108+
assert result.eval_case_results == []
109+
110+
111+
class TestParseEvalSetResultJson:
112+
113+
def _build_eval_set_result(self) -> EvalSetResult:
114+
return EvalSetResult(
115+
eval_set_result_id="my_app_my_eval_set_123.0",
116+
eval_set_result_name="my_app_my_eval_set_123.0",
117+
eval_set_id="my_eval_set",
118+
eval_case_results=[_build_eval_case_result()],
119+
creation_timestamp=123.0,
120+
)
121+
122+
def test_parses_standard_json_string(self):
123+
original = self._build_eval_set_result()
124+
125+
parsed = parse_eval_set_result_json(original.model_dump_json())
126+
127+
assert parsed == original
128+
129+
def test_parses_json_bytes(self):
130+
original = self._build_eval_set_result()
131+
132+
parsed = parse_eval_set_result_json(original.model_dump_json().encode())
133+
134+
assert parsed == original
135+
136+
def test_parses_camel_case_aliased_json(self):
137+
original = self._build_eval_set_result()
138+
139+
parsed = parse_eval_set_result_json(original.model_dump_json(by_alias=True))
140+
141+
assert parsed == original
142+
143+
def test_parses_legacy_double_encoded_json(self):
144+
# Legacy result files stored the object as a JSON-encoded string, i.e. the
145+
# outer JSON is a string whose value is itself the inner JSON object.
146+
original = self._build_eval_set_result()
147+
double_encoded = json.dumps(original.model_dump_json())
148+
149+
parsed = parse_eval_set_result_json(double_encoded)
150+
151+
assert parsed == original
152+
153+
def test_raises_on_json_object_missing_required_fields(self):
154+
with pytest.raises(Exception):
155+
parse_eval_set_result_json('{"unexpected_field": "value"}')
156+
157+
def test_raises_on_non_json_input(self):
158+
with pytest.raises(Exception):
159+
parse_eval_set_result_json("not valid json at all {")

0 commit comments

Comments
 (0)