The first fix listed modules from the entrypoint's own directory, but Modal runs the deployed entrypoint from outside the repo copy, so no sibling module logger was configured and durable-result timings were still dropped. List modules beside cloud_runtime_logging.py instead, which is always imported from the repo copy, and log the configured count and directory once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
"""Tests for Modal container logging of sibling project modules."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import io
|
|
import logging
|
|
from pathlib import Path
|
|
import sys
|
|
from typing import Any, Iterator
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from conftest import PACKAGE_NAME
|
|
|
|
|
|
@pytest.fixture
|
|
def cloud_runtime_logging_module(extension_package: object) -> Any:
|
|
"""Return the cloud runtime logging helpers."""
|
|
del extension_package
|
|
return importlib.import_module(f"{PACKAGE_NAME}.cloud_runtime_logging")
|
|
|
|
|
|
@pytest.fixture
|
|
def module_logger_names() -> Iterator[list[str]]:
|
|
"""Yield unique logger names and remove their handlers afterwards."""
|
|
names = [f"test_cloud_module_{uuid.uuid4().hex}" for _ in range(2)]
|
|
yield names
|
|
for name in names:
|
|
module_logger = logging.getLogger(name)
|
|
for handler in list(module_logger.handlers):
|
|
module_logger.removeHandler(handler)
|
|
module_logger.setLevel(logging.NOTSET)
|
|
module_logger.propagate = True
|
|
|
|
|
|
def test_cloud_module_loggers_emit_info_to_current_stdout(
|
|
cloud_runtime_logging_module: Any,
|
|
module_logger_names: list[str],
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""INFO from sibling modules must reach the stdout that exists at emit time."""
|
|
cloud_runtime_logging_module.configure_cloud_module_loggers(module_logger_names)
|
|
replaced_stdout = io.StringIO()
|
|
monkeypatch.setattr(sys, "stdout", replaced_stdout)
|
|
|
|
logging.getLogger(module_logger_names[0]).info(
|
|
"Finished remote invocation completion in %.3fs", 1.5
|
|
)
|
|
|
|
output = replaced_stdout.getvalue()
|
|
assert "Finished remote invocation completion in 1.500s" in output
|
|
assert f"INFO [{module_logger_names[0]}]" in output
|
|
|
|
|
|
def test_cloud_module_loggers_are_idempotent_and_isolated(
|
|
cloud_runtime_logging_module: Any,
|
|
module_logger_names: list[str],
|
|
) -> None:
|
|
"""Repeat configuration must not duplicate output or leak to the root logger."""
|
|
module = cloud_runtime_logging_module
|
|
first = module.configure_cloud_module_loggers(module_logger_names)
|
|
second = module.configure_cloud_module_loggers(module_logger_names)
|
|
|
|
assert first == second == tuple(module_logger_names)
|
|
handlers = [logging.getLogger(name).handlers for name in module_logger_names]
|
|
assert all(len(module_handlers) == 1 for module_handlers in handlers)
|
|
assert handlers[0][0] is handlers[1][0]
|
|
for name in module_logger_names:
|
|
module_logger = logging.getLogger(name)
|
|
assert module_logger.level == logging.INFO
|
|
assert module_logger.propagate is False
|
|
|
|
|
|
def test_cloud_module_loggers_skip_the_entrypoint_logger(
|
|
cloud_runtime_logging_module: Any,
|
|
) -> None:
|
|
"""The entrypoint logger keeps its own handler and is never double-configured."""
|
|
module = cloud_runtime_logging_module
|
|
entrypoint_name = module._TARGET_LOGGER.name
|
|
|
|
configured = module.configure_cloud_module_loggers([entrypoint_name])
|
|
|
|
assert configured == ()
|
|
|
|
|
|
def test_cloud_project_module_names_lists_flat_modules(
|
|
cloud_runtime_logging_module: Any,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Every shipped top-level module except the package marker is configured."""
|
|
for file_name in ("cloud_durable_invocation.py", "settings.py", "__init__.py"):
|
|
(tmp_path / file_name).write_text("", encoding="utf-8")
|
|
(tmp_path / "notes.txt").write_text("", encoding="utf-8")
|
|
|
|
names = cloud_runtime_logging_module.cloud_project_module_names(tmp_path)
|
|
|
|
assert names == ("cloud_durable_invocation", "settings")
|
|
|
|
|
|
def test_shipped_cloud_module_names_come_from_the_repo_copy(
|
|
cloud_runtime_logging_module: Any,
|
|
) -> None:
|
|
"""Module discovery must not depend on where Modal places the entrypoint file."""
|
|
names = cloud_runtime_logging_module.shipped_cloud_module_names()
|
|
|
|
assert "cloud_durable_invocation" in names
|
|
assert "cloud_session_bridge" in names
|
|
assert "comfyui_modal_sync_cloud" in names
|
|
assert "__init__" not in names
|