test: assert the all extra stays the union of the runtime extras

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 963527870
This commit is contained in:
George Weale
2026-08-12 10:30:17 -07:00
committed by Copybara-Service
parent 470d59e4a3
commit b4dc92de57
@@ -27,6 +27,9 @@ regressions documented in the bare-install audit cannot silently re-emerge:
objects while deserializing checkpoint data.
* ``google-genai`` MUST exclude 2.11 and include 2.12.1, whose types module
defers the optional MCP server stack instead of importing it at Agent startup.
* The ``all`` extra MUST stay the union of every extra that unlocks a runtime
feature, so that ``pip install "google-adk[all]"`` cannot silently stop
installing a feature's dependencies.
"""
from __future__ import annotations
@@ -42,6 +45,7 @@ except ImportError:
from packaging.requirements import Requirement
from packaging.specifiers import SpecifierSet
from packaging.utils import canonicalize_name
from packaging.version import InvalidVersion
from packaging.version import Version
import pytest
@@ -52,6 +56,18 @@ _UNSAFE_CHECKPOINT_RELEASES = {
'langgraph-checkpoint': (('2.1.0', '3.0.0', '4.0.0', '4.1.0'), '4.1.1'),
}
# Extras that ``all`` deliberately leaves out, for the reason recorded in the
# comment above ``optional-dependencies.all`` in pyproject.toml. Every other
# extra is part of the ``all`` contract, so a new extra joins that contract
# unless it is also listed here.
_NON_RUNTIME_EXTRAS = frozenset({
'benchmark',
'community',
'dev',
'docs',
'test',
})
def _find_pyproject() -> Path:
"""Locates pyproject.toml by walking up from this file's directory.
@@ -120,6 +136,58 @@ def _requirement_specifier(
return None
def _runtime_extra_requirements(
pyproject: dict,
) -> dict[str, list[tuple[str, Requirement]]]:
"""Returns what the runtime extras require, keyed by distribution name.
Each value lists the extras that ask for the distribution, paired with the
requirement that extra declares, so a failure can name the extra that ``all``
drifted away from.
"""
contributors: dict[str, list[tuple[str, Requirement]]] = {}
for extra, entries in pyproject['project']['optional-dependencies'].items():
if extra == 'all' or extra in _NON_RUNTIME_EXTRAS:
continue
for entry in entries:
requirement = Requirement(entry)
key = canonicalize_name(requirement.name)
contributors.setdefault(key, []).append((extra, requirement))
return contributors
def _all_extra_requirements(pyproject: dict) -> dict[str, Requirement]:
"""Returns the ``all`` extra's requirements, keyed by distribution name."""
entries = pyproject['project']['optional-dependencies']['all']
requirements = (Requirement(entry) for entry in entries)
return {canonicalize_name(req.name): req for req in requirements}
def _specifier_versions(specifier: SpecifierSet) -> set[Version]:
"""Returns the version literals a specifier mentions.
Wildcard clauses such as ``==1.2.*`` name no single version and are skipped.
"""
versions: set[Version] = set()
for clause in specifier:
try:
versions.add(Version(clause.version))
except InvalidVersion:
continue
return versions
def _expected_marker(requirements: list[Requirement]) -> str:
"""Returns the marker text the union of ``requirements`` should carry.
An empty string means the union must be unmarked. Contributors that disagree
about a marker install the distribution between them on every environment any
of them names, so the union goes unmarked rather than under-installing.
"""
markers = {str(req.marker) if req.marker else '' for req in requirements}
return markers.pop() if len(markers) == 1 else ''
def test_main_deps_include_packaging(pyproject: dict) -> None:
"""``packaging`` is imported unguarded by core ADK; it must be a main dep."""
main_deps = _requirement_names(pyproject['project']['dependencies'])
@@ -181,6 +249,90 @@ def test_main_deps_require_lazy_mcp_google_genai_release(
assert Version('2.12.1') in google_genai.specifier
def test_all_extra_covers_every_runtime_extra(pyproject: dict) -> None:
"""``all`` names exactly the distributions the runtime extras name.
This is the guard against the failure that motivated the union: an extra
gains a dependency, nobody mirrors it into ``all``, and users who installed
``google-adk[all]`` hit an ImportError for a feature they believed they had.
"""
contributors = _runtime_extra_requirements(pyproject)
all_extra = _all_extra_requirements(pyproject)
missing = sorted(set(contributors) - set(all_extra))
assert not missing, 'The all extra is missing ' + ', '.join(
f'{name} (required by'
f' {", ".join(sorted(e for e, _ in contributors[name]))})'
for name in missing
)
orphaned = sorted(set(all_extra) - set(contributors))
assert not orphaned, (
f'The all extra requires {", ".join(orphaned)}, which no runtime extra '
'declares. Every entry in all belongs to the extra that owns its '
'feature, so either declare it there or drop it from all.'
)
def test_all_extra_preserves_runtime_extra_constraints(pyproject: dict) -> None:
"""``all`` asks for each distribution on the same terms its extras do.
Installing several extras at once yields the union of their distributions
and the intersection of their version constraints, so ``all`` must request
the union of the sub-extras named, admit a version exactly when every
contributing extra admits it, and carry the environment marker its
contributors agree on.
"""
contributors = _runtime_extra_requirements(pyproject)
all_extra = _all_extra_requirements(pyproject)
problems: list[str] = []
for name, sources in sorted(contributors.items()):
combined = all_extra.get(name)
if combined is None:
continue # Already reported as missing by the coverage test.
extras = sorted(extra for extra, _ in sources)
requirements = [requirement for _, requirement in sources]
wanted_extras = set().union(*(req.extras for req in requirements))
if combined.extras != wanted_extras:
problems.append(
f'{name}: all requests sub-extras {sorted(combined.extras)}, but '
f'{extras} together require {sorted(wanted_extras)}'
)
wanted_marker = _expected_marker(requirements)
actual_marker = str(combined.marker) if combined.marker else ''
if actual_marker != wanted_marker:
problems.append(
f'{name}: all is gated on {actual_marker or "nothing"}, but '
f'{extras} require {wanted_marker or "no marker"}'
)
candidates = _specifier_versions(combined.specifier)
for requirement in requirements:
candidates |= _specifier_versions(requirement.specifier)
for version in sorted(candidates):
admitted_by_all = combined.specifier.contains(version, prereleases=True)
admitted_by_extras = all(
req.specifier.contains(version, prereleases=True)
for req in requirements
)
if admitted_by_all != admitted_by_extras:
problems.append(
f'{name}: all and {extras} disagree about version {version}. all '
f'declares {combined.specifier or "no constraint"}, against '
+ ', '.join(
f'{extra}: {req.specifier or "no constraint"}'
for extra, req in sources
)
)
assert not problems, 'The all extra diverges from its extras:\n' + '\n'.join(
problems
)
def test_environment_simulation_config_imports_validation_error_from_pydantic() -> (
None
):