TEST: skip when macos+uv and adding uv venv tests (#6387)

## Why are these changes needed?

> The pytest tests test_local_executor_with_custom_venv and
test_local_executor_with_custom_venv_in_local_relative_path located in
packages/autogen-ext/tests/code_executors/test_commandline_code_executor.py
fail when run on macOS (aarch64) using a Python interpreter managed by
uv (following the project's recommended development setup).
> 
> The failure occurs during the creation of a nested virtual environment
using Python's standard venv.EnvBuilder. Specifically, the attempt to
run ensurepip inside the newly created venv fails immediately with a
SIGABRT signal. The root cause appears to be a dynamic library loading
error (dyld error) where the Python executable inside the newly created
venv cannot find its required libpythonX.Y.dylib shared library.

So, when MacOS + uv case, skipping that test.
And, adding uv-venv case

## Related issue number

Closes #6341

## Checks

- [ ] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.

Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
This commit is contained in:
EeS 2025-04-25 06:34:38 +09:00 committed by GitHub
parent cbd8745f2b
commit 0c9fd64d6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -5,8 +5,10 @@ import asyncio
import os import os
import platform import platform
import shutil import shutil
import subprocess
import sys import sys
import tempfile import tempfile
import types
import venv import venv
from pathlib import Path from pathlib import Path
from typing import AsyncGenerator, TypeAlias from typing import AsyncGenerator, TypeAlias
@ -21,6 +23,85 @@ from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
HAS_POWERSHELL: bool = platform.system() == "Windows" and ( HAS_POWERSHELL: bool = platform.system() == "Windows" and (
shutil.which("powershell") is not None or shutil.which("pwsh") is not None shutil.which("powershell") is not None or shutil.which("pwsh") is not None
) )
IS_MACOS: bool = platform.system() == "Darwin"
IS_UV_VENV: bool = (
lambda: (
(
lambda venv_path: (
False
if not venv_path
else (
False
if not os.path.isfile(os.path.join(venv_path, "pyvenv.cfg"))
else (
subprocess.run(
["grep", "-q", "^uv = ", os.path.join(venv_path, "pyvenv.cfg")],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
).returncode
== 0
)
)
)
)(os.environ.get("VIRTUAL_ENV"))
)
)()
HAS_UV: bool = shutil.which("uv") is not None
def create_venv_with_uv(env_dir: str) -> types.SimpleNamespace:
try:
subprocess.run(
["uv", "venv", env_dir],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError as e:
error_message = f"uv virtual env creation failed with error code {e.returncode}:\n"
error_message += f" cmd:\n{e.stdout.decode()}\n"
error_message += f" stderr:\n{e.stderr}\n"
error_message += f" stdout:\n{e.stdout}"
raise RuntimeError(error_message) from e
except Exception as e:
raise RuntimeError(f"Failed to create uv virtual env: {e}") from e
# create a venv.EnvBuilder context
if platform.system() == "Windows":
bin_name = "Scripts"
exe_suffix = ".exe"
else:
bin_name = "bin"
exe_suffix = ""
bin_path = os.path.join(env_dir, bin_name)
python_executable = os.path.join(bin_path, f"python{exe_suffix}")
py_version_short = f"{sys.version_info.major}.{sys.version_info.minor}"
lib_path = os.path.join(env_dir, "lib", f"python{py_version_short}", "site-packages")
if not os.path.exists(lib_path):
lib_path_fallback = os.path.join(env_dir, "lib")
if os.path.exists(lib_path_fallback):
lib_path = lib_path_fallback
else:
raise RuntimeError(f"Failed to find site-packages in {lib_path} or {lib_path_fallback}")
context = types.SimpleNamespace(
env_dir=env_dir,
env_name=os.path.basename(env_dir),
prompt=f"({os.path.basename(env_dir)}) ",
executable=python_executable,
python_dir=os.path.dirname(python_executable),
python_exe=os.path.basename(python_executable),
inc_path=os.path.join(env_dir, "include"),
lib_path=lib_path, # site-packages
bin_path=bin_path, # bin or Scripts
bin_name=bin_name, # bin or Scripts
env_exe=python_executable,
env_exec_cmd=python_executable,
)
return context
@pytest_asyncio.fixture(scope="function") # type: ignore @pytest_asyncio.fixture(scope="function") # type: ignore
@ -169,6 +250,10 @@ print("hello world")
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.skipif(
IS_MACOS and IS_UV_VENV,
reason="uv-venv is not supported on macOS.",
)
async def test_local_executor_with_custom_venv() -> None: async def test_local_executor_with_custom_venv() -> None:
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
env_builder = venv.EnvBuilder(with_pip=True) env_builder = venv.EnvBuilder(with_pip=True)
@ -190,6 +275,10 @@ async def test_local_executor_with_custom_venv() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.skipif(
IS_MACOS and IS_UV_VENV,
reason="uv-venv is not supported on macOS.",
)
async def test_local_executor_with_custom_venv_in_local_relative_path() -> None: async def test_local_executor_with_custom_venv_in_local_relative_path() -> None:
relative_folder_path = "tmp_dir" relative_folder_path = "tmp_dir"
try: try:
@ -220,6 +309,62 @@ async def test_local_executor_with_custom_venv_in_local_relative_path() -> None:
shutil.rmtree(relative_folder_path) shutil.rmtree(relative_folder_path)
@pytest.mark.asyncio
@pytest.mark.skipif(
not HAS_UV,
reason="uv is not installed.",
)
async def test_local_executor_with_custom_uv_venv() -> None:
with tempfile.TemporaryDirectory() as temp_dir:
env_builder_context = create_venv_with_uv(temp_dir)
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir, virtual_env_context=env_builder_context)
await executor.start()
code_blocks = [
# https://stackoverflow.com/questions/1871549/how-to-determine-if-python-is-running-inside-a-virtualenv
CodeBlock(code="import sys; print(sys.prefix != sys.base_prefix)", language="python"),
]
cancellation_token = CancellationToken()
result = await executor.execute_code_blocks(code_blocks, cancellation_token=cancellation_token)
assert result.exit_code == 0
assert result.output.strip() == "True"
@pytest.mark.asyncio
@pytest.mark.skipif(
not HAS_UV,
reason="uv is not installed.",
)
async def test_local_executor_with_custom_uv_venv_in_local_relative_path() -> None:
relative_folder_path = "tmp_dir"
try:
if not os.path.isdir(relative_folder_path):
os.mkdir(relative_folder_path)
env_path = os.path.join(relative_folder_path, ".venv")
env_builder_context = create_venv_with_uv(env_path)
executor = LocalCommandLineCodeExecutor(work_dir=relative_folder_path, virtual_env_context=env_builder_context)
await executor.start()
code_blocks = [
CodeBlock(code="import sys; print(sys.executable)", language="python"),
]
cancellation_token = CancellationToken()
result = await executor.execute_code_blocks(code_blocks, cancellation_token=cancellation_token)
assert result.exit_code == 0
# Check if the expected venv has been used
bin_path = os.path.abspath(env_builder_context.bin_path)
assert Path(result.output.strip()).parent.samefile(bin_path)
finally:
if os.path.isdir(relative_folder_path):
shutil.rmtree(relative_folder_path)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_serialize_deserialize() -> None: async def test_serialize_deserialize() -> None:
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir: