# 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.
import base64
import multiprocessing
import time
import traceback
from google.adk.code_executors import code_execution_utils
from google.genai import types
# The extraction itself must finish promptly. The join budget is far looser
# because it also covers spawning the child and importing this module there.
_REDOS_DEADLINE_SECONDS = 2.0
_CHILD_JOIN_TIMEOUT_SECONDS = 120.0
def _exercise_redos_candidate(result_conn) -> None:
"""Runs the ReDoS regression payload in an independently stoppable process."""
failure = None
try:
ticks = "`" * 3
long_invalid_payload = (
ticks + "python\n" + "x = 1\n" * 5000 + "not_matching"
)
content = types.Content(
role="model",
parts=[types.Part(text=long_invalid_payload)],
)
delimiters = [(ticks + "python\n", "\n" + ticks)]
started = time.perf_counter()
code = code_execution_utils.CodeExecutionUtils.extract_code_and_truncate_content(
content, delimiters
)
elapsed = time.perf_counter() - started
if code is not None:
failure = f"expected no code to be extracted, got {code!r}"
elif elapsed > _REDOS_DEADLINE_SECONDS:
failure = (
f"extraction took {elapsed:.3f}s, over the"
f" {_REDOS_DEADLINE_SECONDS}s deadline (possible ReDoS regression)"
)
except BaseException: # pylint: disable=broad-except
# Without this the parent only sees a bare exit code and has to dig the
# traceback out of the child's captured stderr.
failure = f"extraction raised in the child:\n{traceback.format_exc()}"
result_conn.send(failure)
result_conn.close()
def test_extract_code_and_truncate_content_basic():
"""Tests basic code extraction and content truncation."""
content = types.Content(
role="model",
parts=[
types.Part(
text=(
"Here is some code:\n```python\nx = 1\n```\nAnd some text"
" after."
)
)
],
)
delimiters = [("```python\n", "\n```")]
code = (
code_execution_utils.CodeExecutionUtils.extract_code_and_truncate_content(
content, delimiters
)
)
assert code == "x = 1"
assert len(content.parts) == 2
assert content.parts[0].text == "Here is some code:\n"
assert content.parts[1].executable_code.code == "x = 1"
def test_extract_code_and_truncate_content_multiple_blocks():
"""Tests that the first code block is extracted when multiple exist."""
content = types.Content(
role="model",
parts=[
types.Part(
text=(
"First:\n"
"```python\n"
"x = 1\n"
"```\n"
"Second:\n"
"```python\n"
"y = 2\n"
"```"
)
)
],
)
delimiters = [("```python\n", "\n```")]
code = (
code_execution_utils.CodeExecutionUtils.extract_code_and_truncate_content(
content, delimiters
)
)
assert code == "x = 1"
assert len(content.parts) == 2
assert content.parts[0].text == "First:\n"
assert content.parts[1].executable_code.code == "x = 1"
def test_extract_code_and_truncate_content_no_delimiter():
"""Tests when no delimiters are found in the content."""
content = types.Content(
role="model",
parts=[types.Part(text="Just plain text without code.")],
)
delimiters = [("```python\n", "\n```")]
code = (
code_execution_utils.CodeExecutionUtils.extract_code_and_truncate_content(
content, delimiters
)
)
assert code is None
# Content should be unmodified.
assert len(content.parts) == 1
assert content.parts[0].text == "Just plain text without code."
def test_extract_code_and_truncate_content_redos_vulnerability():
"""Tests that a string that would cause ReDoS behaves reasonably."""
context = multiprocessing.get_context("spawn")
receiver, sender = context.Pipe(duplex=False)
process = context.Process(target=_exercise_redos_candidate, args=(sender,))
process.start()
sender.close()
process.join(timeout=_CHILD_JOIN_TIMEOUT_SECONDS)
hung = process.is_alive()
if hung:
process.kill()
process.join()
exitcode = process.exitcode
try:
# poll() is also true at EOF, so recv() has to carry the "child died
# without reporting" case rather than a poll() guard.
failure = receiver.recv()
except EOFError:
failure = "child exited without reporting a result"
finally:
receiver.close()
process.close()
assert not hung, "extraction never returned (possible ReDoS regression)"
assert failure is None, failure
assert exitcode == 0, f"extraction process exited with {exitcode}"
def test_extract_code_and_truncate_content_multiple_delimiter_pairs():
"""Tests code extraction when multiple different delimiter pairs are provided."""
ticks = "`" * 3
# Case 1: First delimiter pair matches first
content = types.Content(
role="model",
parts=[
types.Part(
text="Here is tool code:\n"
+ ticks
+ "tool_code\nx = 1\n"
+ ticks
+ "\nAnd python code:\n"
+ ticks
+ "python\ny = 2\n"
+ ticks
)
],
)
delimiters = [
(ticks + "tool_code\n", "\n" + ticks),
(ticks + "python\n", "\n" + ticks),
]
code = (
code_execution_utils.CodeExecutionUtils.extract_code_and_truncate_content(
content, delimiters
)
)
assert code == "x = 1"
assert len(content.parts) == 2
assert content.parts[0].text == "Here is tool code:\n"
assert content.parts[1].executable_code.code == "x = 1"
# Case 2: Second delimiter pair matches first
content = types.Content(
role="model",
parts=[
types.Part(
text="Here is python code:\n"
+ ticks
+ "python\ny = 2\n"
+ ticks
+ "\nAnd tool code:\n"
+ ticks
+ "tool_code\nx = 1\n"
+ ticks
)
],
)
code = (
code_execution_utils.CodeExecutionUtils.extract_code_and_truncate_content(
content, delimiters
)
)
assert code == "y = 2"
assert len(content.parts) == 2
assert content.parts[0].text == "Here is python code:\n"
assert content.parts[1].executable_code.code == "y = 2"
def test_get_encoded_file_content_encodes_raw_bytes():
"""Raw binary must come back base64-encoded, not verbatim."""
encoded = code_execution_utils.CodeExecutionUtils.get_encoded_file_content(
b"\x00\x01\x02"
)
# base64 of the three bytes 00 01 02 is "AAEC" (no padding needed).
assert encoded == b"AAEC"
def test_get_encoded_file_content_encodes_payload_with_invalid_padding():
"""A payload that is not decodable base64 is encoded, not passed through."""
encoded = code_execution_utils.CodeExecutionUtils.get_encoded_file_content(
b"hello"
)
assert encoded == b"aGVsbG8="
def test_get_encoded_file_content_leaves_already_encoded_bytes_unchanged():
"""Double-encoding would corrupt the file for the executor that decodes it."""
already_encoded = base64.b64encode(b"file,contents\n1,2\n")
encoded = code_execution_utils.CodeExecutionUtils.get_encoded_file_content(
already_encoded
)
assert encoded == already_encoded
assert base64.b64decode(encoded) == b"file,contents\n1,2\n"
def test_get_encoded_file_content_is_idempotent():
once = code_execution_utils.CodeExecutionUtils.get_encoded_file_content(
b"\x00\x01\x02"
)
twice = code_execution_utils.CodeExecutionUtils.get_encoded_file_content(once)
assert twice == once
def test_build_executable_code_part_carries_code_and_python_language():
part = code_execution_utils.CodeExecutionUtils.build_executable_code_part(
"print(1)"
)
assert part.executable_code.code == "print(1)"
assert part.executable_code.language == types.Language.PYTHON
def test_build_code_execution_result_part_stderr_reports_failure():
"""stderr wins over stdout: a run that wrote to stderr did not succeed."""
result = code_execution_utils.CodeExecutionResult(
stdout="partial output", stderr="Traceback: boom"
)
part = (
code_execution_utils.CodeExecutionUtils.build_code_execution_result_part(
result
)
)
assert part.code_execution_result.outcome == types.Outcome.OUTCOME_FAILED
# The failure text is the stderr verbatim, so the model sees the real error.
assert part.code_execution_result.output == "Traceback: boom"
def test_build_code_execution_result_part_stdout_only():
result = code_execution_utils.CodeExecutionResult(stdout="42")
part = (
code_execution_utils.CodeExecutionUtils.build_code_execution_result_part(
result
)
)
assert part.code_execution_result.outcome == types.Outcome.OUTCOME_OK
assert part.code_execution_result.output == "Code execution result:\n42\n"
def test_build_code_execution_result_part_empty_run_still_reports_result():
"""A silent successful run still gets a result header, not an empty string."""
result = code_execution_utils.CodeExecutionResult()
part = (
code_execution_utils.CodeExecutionUtils.build_code_execution_result_part(
result
)
)
assert part.code_execution_result.outcome == types.Outcome.OUTCOME_OK
assert part.code_execution_result.output == "Code execution result:\n\n"
def test_build_code_execution_result_part_files_only_omits_result_header():
"""With no stdout but saved files, only the artifact list is reported."""
result = code_execution_utils.CodeExecutionResult(
output_files=[
code_execution_utils.File(name="a.csv", content=""),
code_execution_utils.File(name="b.png", content=""),
]
)
part = (
code_execution_utils.CodeExecutionUtils.build_code_execution_result_part(
result
)
)
assert part.code_execution_result.outcome == types.Outcome.OUTCOME_OK
assert (
part.code_execution_result.output == "Saved artifacts:\n`a.csv`,`b.png`"
)
def test_build_code_execution_result_part_stdout_and_files():
result = code_execution_utils.CodeExecutionResult(
stdout="done",
output_files=[code_execution_utils.File(name="a.csv", content="")],
)
part = (
code_execution_utils.CodeExecutionUtils.build_code_execution_result_part(
result
)
)
assert part.code_execution_result.output == (
"Code execution result:\ndone\n\n\nSaved artifacts:\n`a.csv`"
)
def test_convert_code_execution_parts_rewrites_trailing_executable_code():
content = types.Content(
role="model",
parts=[
types.Part(text="here goes:"),
code_execution_utils.CodeExecutionUtils.build_executable_code_part(
"x = 1"
),
],
)
code_execution_utils.CodeExecutionUtils.convert_code_execution_parts(
content, ("", ""), ("", "")
)
# The leading text part is left alone; only the trailing code part becomes
# text, wrapped in the code delimiters.
assert content.parts[0].text == "here goes:"
assert content.parts[1].text == "x = 1"
assert content.parts[1].executable_code is None
assert content.role == "model"
def test_convert_code_execution_parts_rewrites_lone_execution_result_as_user():
content = types.Content(
role="model",
parts=[
types.Part.from_code_execution_result(
outcome="OUTCOME_OK", output="42"
)
],
)
code_execution_utils.CodeExecutionUtils.convert_code_execution_parts(
content, ("", ""), ("", "")
)
assert content.parts[0].text == "42"
# The execution result was produced by the executor, not the model, so the
# rewritten turn is attributed to the user.
assert content.role == "user"
def test_convert_code_execution_parts_keeps_multipart_execution_result():
"""A multi-part content came from the model, so its result is left as-is."""
content = types.Content(
role="model",
parts=[
types.Part(text="the answer is"),
types.Part.from_code_execution_result(
outcome="OUTCOME_OK", output="42"
),
],
)
code_execution_utils.CodeExecutionUtils.convert_code_execution_parts(
content, ("", ""), ("", "")
)
assert content.parts[1].text is None
assert content.parts[1].code_execution_result.output == "42"
assert content.role == "model"
def test_convert_code_execution_parts_execution_result_without_output():
content = types.Content(
role="model",
parts=[
types.Part(
code_execution_result=types.CodeExecutionResult(
outcome="OUTCOME_OK"
)
)
],
)
code_execution_utils.CodeExecutionUtils.convert_code_execution_parts(
content, ("", ""), ("", "")
)
# No output means no delimiters either - an empty text part, not "".
assert content.parts[0].text == ""
assert content.role == "user"
def test_convert_code_execution_parts_empty_parts_is_a_noop():
content = types.Content(role="model", parts=[])
code_execution_utils.CodeExecutionUtils.convert_code_execution_parts(
content, ("", ""), ("", "")
)
assert content.parts == []
assert content.role == "model"