Copybara import of the project:

--
8baeb0b569eaedc638b20e46894178a3b878dbd6 by Amulya Bhatia <amulya.bhatia@t-online.de>:

test: unit tests for built_in_code_executor and unsafe_code_executor

--
cfac73b9271557ead96eb5fb419e05d88c6e8cd4 by Amulya Bhatia <amulya.bhatia@t-online.de>:

test: unit tests for built_in_code_executor and unsafe_code_executor
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/971 from iamulya:code-executor-tests 55290e27b5e58ef3835905aec88639e936318d01
PiperOrigin-RevId: 764976316
This commit is contained in:
Amulya Bhatia 2025-05-29 19:08:23 -07:00 committed by Copybara-Service
parent 04e07b4a14
commit face2e8cf2
2 changed files with 196 additions and 0 deletions

View File

@ -0,0 +1,109 @@
# Copyright 2025 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.
import pytest
from google.genai import types
from google.adk.code_executors.built_in_code_executor import BuiltInCodeExecutor
from google.adk.models.llm_request import LlmRequest
@pytest.fixture
def built_in_executor() -> BuiltInCodeExecutor:
return BuiltInCodeExecutor()
def test_process_llm_request_gemini_2_model_config_none(
built_in_executor: BuiltInCodeExecutor,
):
"""Tests processing when llm_request.config is None for Gemini 2."""
llm_request = LlmRequest(model="gemini-2.0-flash")
built_in_executor.process_llm_request(llm_request)
assert llm_request.config is not None
assert llm_request.config.tools == [
types.Tool(code_execution=types.ToolCodeExecution())
]
def test_process_llm_request_gemini_2_model_tools_none(
built_in_executor: BuiltInCodeExecutor,
):
"""Tests processing when llm_request.config.tools is None for Gemini 2."""
llm_request = LlmRequest(
model="gemini-2.0-pro", config=types.GenerateContentConfig()
)
built_in_executor.process_llm_request(llm_request)
assert llm_request.config.tools == [
types.Tool(code_execution=types.ToolCodeExecution())
]
def test_process_llm_request_gemini_2_model_tools_empty(
built_in_executor: BuiltInCodeExecutor,
):
"""Tests processing when llm_request.config.tools is empty for Gemini 2."""
llm_request = LlmRequest(
model="gemini-2.0-ultra",
config=types.GenerateContentConfig(tools=[]),
)
built_in_executor.process_llm_request(llm_request)
assert llm_request.config.tools == [
types.Tool(code_execution=types.ToolCodeExecution())
]
def test_process_llm_request_gemini_2_model_with_existing_tools(
built_in_executor: BuiltInCodeExecutor,
):
"""Tests processing when llm_request.config.tools already has tools for Gemini 2."""
existing_tool = types.Tool(
function_declarations=[
types.FunctionDeclaration(name="test_func", description="A test func")
]
)
llm_request = LlmRequest(
model="gemini-2.0-flash-001",
config=types.GenerateContentConfig(tools=[existing_tool]),
)
built_in_executor.process_llm_request(llm_request)
assert len(llm_request.config.tools) == 2
assert existing_tool in llm_request.config.tools
assert types.Tool(
code_execution=types.ToolCodeExecution()
) in llm_request.config.tools
def test_process_llm_request_non_gemini_2_model(
built_in_executor: BuiltInCodeExecutor,
):
"""Tests that a ValueError is raised for non-Gemini 2 models."""
llm_request = LlmRequest(model="gemini-1.5-flash")
with pytest.raises(ValueError) as excinfo:
built_in_executor.process_llm_request(llm_request)
assert (
"Gemini code execution tool is not supported for model gemini-1.5-flash"
in str(excinfo.value)
)
def test_process_llm_request_no_model_name(
built_in_executor: BuiltInCodeExecutor,
):
"""Tests that a ValueError is raised if model name is not set."""
llm_request = LlmRequest() # Model name defaults to None
with pytest.raises(ValueError) as excinfo:
built_in_executor.process_llm_request(llm_request)
assert (
"Gemini code execution tool is not supported for model None"
in str(excinfo.value)
)

View File

@ -0,0 +1,87 @@
# Copyright 2025 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.
import pytest
from unittest.mock import MagicMock
from google.adk.code_executors.unsafe_local_code_executor import UnsafeLocalCodeExecutor
from google.adk.code_executors.code_execution_utils import CodeExecutionInput, CodeExecutionResult
from google.adk.agents.invocation_context import InvocationContext
from google.adk.agents.base_agent import BaseAgent
from google.adk.sessions.session import Session
from google.adk.sessions.base_session_service import BaseSessionService
@pytest.fixture
def mock_invocation_context() -> InvocationContext:
"""Provides a mock InvocationContext."""
mock_agent = MagicMock(spec=BaseAgent)
mock_session = MagicMock(spec=Session)
mock_session_service = MagicMock(spec=BaseSessionService)
return InvocationContext(
invocation_id="test_invocation",
agent=mock_agent,
session=mock_session,
session_service=mock_session_service
)
class TestUnsafeLocalCodeExecutor:
def test_init_default(self):
executor = UnsafeLocalCodeExecutor()
assert not executor.stateful
assert not executor.optimize_data_file
def test_init_stateful_raises_error(self):
with pytest.raises(ValueError, match="Cannot set `stateful=True` in UnsafeLocalCodeExecutor."):
UnsafeLocalCodeExecutor(stateful=True)
def test_init_optimize_data_file_raises_error(self):
with pytest.raises(ValueError, match="Cannot set `optimize_data_file=True` in UnsafeLocalCodeExecutor."):
UnsafeLocalCodeExecutor(optimize_data_file=True)
def test_execute_code_simple_print(self, mock_invocation_context: InvocationContext):
executor = UnsafeLocalCodeExecutor()
code_input = CodeExecutionInput(code='print("hello world")')
result = executor.execute_code(mock_invocation_context, code_input)
assert isinstance(result, CodeExecutionResult)
assert result.stdout == "hello world\n"
assert result.stderr == ""
assert result.output_files == []
def test_execute_code_with_error(self, mock_invocation_context: InvocationContext):
executor = UnsafeLocalCodeExecutor()
code_input = CodeExecutionInput(code='raise ValueError("Test error")')
result = executor.execute_code(mock_invocation_context, code_input)
assert isinstance(result, CodeExecutionResult)
assert result.stdout == ""
assert "Test error" in result.stderr
assert result.output_files == []
def test_execute_code_variable_assignment(self, mock_invocation_context: InvocationContext):
executor = UnsafeLocalCodeExecutor()
code_input = CodeExecutionInput(code='x = 10\nprint(x * 2)')
result = executor.execute_code(mock_invocation_context, code_input)
assert result.stdout == "20\n"
assert result.stderr == ""
def test_execute_code_empty(self, mock_invocation_context: InvocationContext):
executor = UnsafeLocalCodeExecutor()
code_input = CodeExecutionInput(code='')
result = executor.execute_code(mock_invocation_context, code_input)
assert result.stdout == ""
assert result.stderr == ""