chore: import upstream snapshot with attribution
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:44:17 +08:00
commit 85742ab165
588 changed files with 320176 additions and 0 deletions
+422
View File
@@ -0,0 +1,422 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import io
import logging
import multiprocessing as mp
from multiprocessing.queues import Queue
from pathlib import Path
from typing import Any, Dict, List
import pytest
from agentlightning.logging import _to_level_value # pyright: ignore[reportPrivateUsage]
from agentlightning.logging import (
DATE_FORMAT,
DEFAULT_FORMAT,
)
pytestmark = pytest.mark.utils
def _logging_worker(case: str, queue: Queue[Dict[str, Any]]) -> None:
"""
Runs in a separate process using spawn. It performs a specific logging
configuration scenario and returns a summary dict via the queue.
"""
import logging
import warnings
# Re-import inside the subprocess so everything is picklable & isolated
from agentlightning.logging import (
setup,
setup_module,
)
if case == "setup_module_plain_console":
logger = setup_module(
level="DEBUG",
name="agentlightning.test",
console=True,
color=False,
propagate=False,
)
handlers = logger.handlers
handler = handlers[0] if handlers else None
fmt = handler.formatter._fmt if handler and handler.formatter else None
datefmt = handler.formatter.datefmt if handler and handler.formatter else None
queue.put(
{
"logger_name": logger.name,
"logger_level": logger.level,
"num_handlers": len(handlers),
"handler_class": handler.__class__.__name__ if handler else None,
"handler_level": handler.level if handler else None,
"fmt": fmt,
"datefmt": datefmt,
}
)
elif case == "setup_module_color_rich":
# Rich variant: color=True uses RichHandler
logger = setup_module(
level="INFO",
name="agentlightning.rich",
console=True,
color=True,
propagate=False,
)
handlers = logger.handlers
handler = handlers[0] if handlers else None
queue.put(
{
"logger_name": logger.name,
"logger_level": logger.level,
"num_handlers": len(handlers),
"handler_class": handler.__class__.__name__ if handler else None,
"handler_has_formatter": handler.formatter is not None if handler else None,
}
)
elif case == "setup_with_submodules_apply_to_capture_warnings":
# Extra handler to attach via extra_handlers
stream = io.StringIO()
stream_handler = logging.StreamHandler(stream)
setup(
level="INFO",
console=False,
color=False,
propagate=False,
disable_existing_loggers=False,
capture_warnings=True,
submodule_levels={"agentlightning.io": "DEBUG"},
extra_handlers=[stream_handler],
apply_to=["external"],
)
base = logging.getLogger("agentlightning")
sub = logging.getLogger("agentlightning.io")
ext = logging.getLogger("external")
# Capture warnings via logging after capture_warnings=True
class ListHandler(logging.Handler):
def __init__(self) -> None:
super().__init__()
self.records: List[logging.LogRecord] = []
def emit(self, record: logging.LogRecord) -> None:
self.records.append(record)
lh = ListHandler()
wlog = logging.getLogger("py.warnings")
wlog.handlers.clear()
wlog.addHandler(lh)
wlog.setLevel(logging.WARNING)
wlog.propagate = False
warnings.warn("from warnings", UserWarning)
queue.put(
{
"base_level": base.level,
"base_num_handlers": len(base.handlers),
"extra_in_base": stream_handler in base.handlers,
"sub_level": sub.level,
"ext_level": ext.level,
"ext_handlers_same": base.handlers == ext.handlers,
"ext_propagate": ext.propagate,
"warnings_logged": len(lh.records),
}
)
elif case == "setup_with_console_and_extra_handler":
# Console + extra handler combination to test handler attachment
stream = io.StringIO()
extra_handler = logging.StreamHandler(stream)
setup(
level="WARNING",
console=True,
color=False,
propagate=False,
extra_handlers=[extra_handler],
)
base = logging.getLogger("agentlightning")
handler_classes = [h.__class__.__name__ for h in base.handlers]
has_extra = extra_handler in base.handlers
queue.put(
{
"base_level": base.level,
"num_handlers": len(base.handlers),
"handler_classes": handler_classes,
"has_extra": has_extra,
}
)
else:
queue.put({})
def _logging_worker_files_string(queue: Queue[Dict[str, Any]], base_dir: str) -> None:
"""
Runs in a separate spawned process and configures logging with a single
files=str path. Returns information about the attached FileHandler.
"""
import logging
import os
from agentlightning.logging import setup
log_path = os.path.join(base_dir, "logs", "agent.log")
setup(
level="INFO",
console=False,
color=False,
propagate=False,
files=log_path,
)
base = logging.getLogger("agentlightning")
file_handlers = [h for h in base.handlers if isinstance(h, logging.FileHandler)]
fh = file_handlers[0] if file_handlers else None
fmt = fh.formatter._fmt if fh and fh.formatter else None
datefmt = fh.formatter.datefmt if fh and fh.formatter else None
queue.put(
{
"logger_level": base.level,
"num_handlers": len(base.handlers),
"num_file_handlers": len(file_handlers),
"file_base": fh.baseFilename if fh else None,
"file_level": fh.level if fh else None,
"fmt": fmt,
"datefmt": datefmt,
}
)
def _logging_worker_files_mapping(queue: Queue[Dict[str, Any]], base_dir: str) -> None:
"""
Runs in a separate spawned process and configures logging with a files=dict
mapping, then calls setup twice to verify idempotent FileHandler attachment.
"""
import logging
import os
from agentlightning.logging import setup
base_log = os.path.join(base_dir, "agent.log")
external_log = os.path.join(base_dir, "external.log")
files_mapping: Dict[str, str] = {
"agentlightning": base_log,
"external": external_log,
}
def file_handlers(logger: logging.Logger) -> list[logging.FileHandler]:
return [h for h in logger.handlers if isinstance(h, logging.FileHandler)]
# First setup call
setup(
level="DEBUG",
console=False,
color=False,
propagate=False,
files=files_mapping,
)
base_logger = logging.getLogger("agentlightning")
ext_logger = logging.getLogger("external")
base_fh_first = file_handlers(base_logger)
ext_fh_first = file_handlers(ext_logger)
# Second setup call with the same mapping should not add duplicate FileHandlers
setup(
level="DEBUG",
console=False,
color=False,
propagate=False,
files=files_mapping,
)
base_fh_second = file_handlers(base_logger)
ext_fh_second = file_handlers(ext_logger)
queue.put(
{
"base_level": base_logger.level,
"ext_level": ext_logger.getEffectiveLevel(),
"base_first_count": len(base_fh_first),
"ext_first_count": len(ext_fh_first),
"base_second_count": len(base_fh_second),
"ext_second_count": len(ext_fh_second),
"base_file_first": base_fh_first[0].baseFilename if base_fh_first else None,
"ext_file_first": ext_fh_first[0].baseFilename if ext_fh_first else None,
"base_file_second": base_fh_second[0].baseFilename if base_fh_second else None,
"ext_file_second": ext_fh_second[0].baseFilename if ext_fh_second else None,
# For sanity: capture handler levels as well
"base_handler_level": base_fh_first[0].level if base_fh_first else None,
"ext_handler_level": ext_fh_first[0].level if ext_fh_first else None,
}
)
def _run_case(case: str) -> Dict[str, Any]:
"""Helper to run a scenario in a spawned process and fetch the result."""
ctx = mp.get_context("spawn")
q: Queue[Dict[str, Any]] = ctx.Queue()
p = ctx.Process(target=_logging_worker, args=(case, q))
p.start()
result = q.get(timeout=10)
p.join(timeout=10)
assert p.exitcode == 0
return result
def test_to_level_value_int_and_str() -> None:
# direct, no multiprocessing needed
assert _to_level_value(logging.DEBUG) == logging.DEBUG
assert _to_level_value("info") == logging.INFO
assert _to_level_value("WARNING") == logging.WARNING
with pytest.raises(ValueError):
_to_level_value("not-a-level")
def test_setup_module_plain_console_spawn() -> None:
result = _run_case("setup_module_plain_console")
assert result["logger_name"] == "agentlightning.test"
assert result["logger_level"] == logging.DEBUG
# Console handler with plain formatter configured
assert result["num_handlers"] == 1
assert result["handler_class"].endswith("StreamHandler")
assert result["handler_level"] == logging.DEBUG
assert result["fmt"] == DEFAULT_FORMAT
assert result["datefmt"] == DATE_FORMAT
def test_setup_module_color_rich_spawn() -> None:
# Only run this test if rich is installed
pytest.importorskip("rich")
result = _run_case("setup_module_color_rich")
assert result["logger_name"] == "agentlightning.rich"
assert result["logger_level"] == logging.INFO
assert result["num_handlers"] == 1
# We cant rely on full module path, just the class name
assert result["handler_class"].endswith("RichHandler")
def test_setup_with_submodules_apply_to_and_capture_warnings_spawn() -> None:
result = _run_case("setup_with_submodules_apply_to_capture_warnings")
# Base logger level and handler attachment
assert result["base_level"] == logging.INFO
assert result["base_num_handlers"] >= 1
assert result["extra_in_base"] is True
# Submodule level overridden
assert result["sub_level"] == logging.DEBUG
# apply_to logger mirrors base handlers & level, propagation disabled
assert result["ext_level"] == logging.INFO
assert result["ext_handlers_same"] is True
assert result["ext_propagate"] is False
# capture_warnings=True causes warnings.warn to go through logging
assert result["warnings_logged"] >= 1
def test_setup_with_console_and_extra_handler_spawn() -> None:
result = _run_case("setup_with_console_and_extra_handler")
# Level propagated to base logger
assert result["base_level"] == logging.WARNING
# Both console handler and extra handler should be attached
assert result["num_handlers"] >= 2
assert any(cls.endswith("StreamHandler") for cls in result["handler_classes"])
assert result["has_extra"] is True
def test_setup_files_string_spawn(tmp_path: Path) -> None:
"""
Verifies that passing files as a string attaches a single FileHandler with
the expected level and default formatter in a spawned process.
"""
ctx = mp.get_context("spawn")
q: Queue[Dict[str, Any]] = ctx.Queue()
p = ctx.Process(target=_logging_worker_files_string, args=(q, str(tmp_path)))
p.start()
result = q.get(timeout=10)
p.join(timeout=10)
assert p.exitcode == 0
assert result["logger_level"] == logging.INFO
# We expect at least one handler and exactly one FileHandler
assert result["num_handlers"] >= 1
assert result["num_file_handlers"] == 1
# Filename should be inside the tmp_path tree
assert str(tmp_path) in result["file_base"]
# FileHandler uses the base logger level
assert result["file_level"] == logging.INFO
# Default formatter applied by _ensure_file_handler
assert result["fmt"] == DEFAULT_FORMAT
assert result["datefmt"] == DATE_FORMAT
def test_setup_files_mapping_spawn(tmp_path: Path) -> None:
"""
Verifies that passing files as a mapping attaches FileHandlers to each
logger and that calling setup twice does not create duplicate handlers.
"""
ctx = mp.get_context("spawn")
q: Queue[Dict[str, Any]] = ctx.Queue()
p = ctx.Process(target=_logging_worker_files_mapping, args=(q, str(tmp_path)))
p.start()
result = q.get(timeout=10)
p.join(timeout=10)
assert p.exitcode == 0
# Base logger level is DEBUG
assert result["base_level"] == logging.DEBUG
# External's effective level is WARNING (inherited from root)
assert result["ext_level"] == logging.WARNING
# First setup: one FileHandler per logger
assert result["base_first_count"] == 1
assert result["ext_first_count"] == 1
# Second setup: still one FileHandler per logger (idempotence)
assert result["base_second_count"] == 1
assert result["ext_second_count"] == 1
# File paths are stable across calls
assert result["base_file_first"] == result["base_file_second"]
assert result["ext_file_first"] == result["ext_file_second"]
# Paths should live under tmp_path
assert str(tmp_path) in result["base_file_first"]
assert str(tmp_path) in result["ext_file_first"]
# Handler levels:
# - base handler uses the base logger level (DEBUG)
# - external handler uses external's effective level at creation (WARNING)
assert result["base_handler_level"] == logging.DEBUG
assert result["ext_handler_level"] == logging.WARNING