[CI] Stop the config ratchets re-parsing the package on every scan (#36240)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f8f9226cd2
commit
68575b23d0
@@ -13,6 +13,7 @@ one field that is deliberately read before resolution touches it.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
import functools
|
||||||
import pathlib
|
import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -51,6 +52,24 @@ _STALE_FROM_THE_REGISTRIES = frozenset(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=None)
|
||||||
|
def _parsed(path):
|
||||||
|
return ast.parse(path.read_text(encoding="utf-8-sig"))
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=None)
|
||||||
|
def _declared_resolution_fields(path):
|
||||||
|
fields = set()
|
||||||
|
for node in ast.walk(_parsed(path)):
|
||||||
|
if (
|
||||||
|
isinstance(node, ast.Call)
|
||||||
|
and isinstance(node.func, ast.Name)
|
||||||
|
and node.func.id == "declare_resolution"
|
||||||
|
):
|
||||||
|
fields |= {kw.arg for kw in node.keywords if kw.arg}
|
||||||
|
return frozenset(fields)
|
||||||
|
|
||||||
|
|
||||||
def _registry_declared_fields():
|
def _registry_declared_fields():
|
||||||
"""What the live registries and passes declare.
|
"""What the live registries and passes declare.
|
||||||
|
|
||||||
@@ -79,7 +98,7 @@ def _registry_collection_is_after_the_build():
|
|||||||
collection above this handler's own `get_model_config()` call does not move
|
collection above this handler's own `get_model_config()` call does not move
|
||||||
it above the configuration another handler already cached.
|
it above the configuration another handler already cached.
|
||||||
"""
|
"""
|
||||||
tree = ast.parse((_SRT / "server_args.py").read_text(encoding="utf-8-sig"))
|
tree = _parsed(_SRT / "server_args.py")
|
||||||
handler = next(
|
handler = next(
|
||||||
node
|
node
|
||||||
for node in ast.walk(tree)
|
for node in ast.walk(tree)
|
||||||
@@ -129,7 +148,7 @@ def _server_args_names(tree, path):
|
|||||||
def _constructor_reads():
|
def _constructor_reads():
|
||||||
"""Fields `ModelConfig.from_server_args` takes off the record."""
|
"""Fields `ModelConfig.from_server_args` takes off the record."""
|
||||||
path = _SRT / "configs/model_config.py"
|
path = _SRT / "configs/model_config.py"
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = _parsed(path)
|
||||||
constructor = next(
|
constructor = next(
|
||||||
node
|
node
|
||||||
for node in ast.walk(tree)
|
for node in ast.walk(tree)
|
||||||
@@ -177,7 +196,7 @@ def _late_resolution_fields():
|
|||||||
path = _SRT / name
|
path = _SRT / name
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
continue
|
continue
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = _parsed(path)
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
if not isinstance(node, ast.Call):
|
if not isinstance(node, ast.Call):
|
||||||
continue
|
continue
|
||||||
@@ -205,7 +224,7 @@ def _hook_declarations(dispatch, source_module):
|
|||||||
`test_every_opaque_callback_is_still_late`.
|
`test_every_opaque_callback_is_still_late`.
|
||||||
"""
|
"""
|
||||||
imported = {}
|
imported = {}
|
||||||
for node in ast.walk(ast.parse(source_module.read_text(encoding="utf-8-sig"))):
|
for node in ast.walk(_parsed(source_module)):
|
||||||
if isinstance(node, ast.ImportFrom) and node.module:
|
if isinstance(node, ast.ImportFrom) and node.module:
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
imported[alias.asname or alias.name] = node.module
|
imported[alias.asname or alias.name] = node.module
|
||||||
@@ -225,22 +244,14 @@ def _hook_declarations(dispatch, source_module):
|
|||||||
path = _SRT / (module[len("sglang.srt.") :].replace(".", "/") + ".py")
|
path = _SRT / (module[len("sglang.srt.") :].replace(".", "/") + ".py")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
continue
|
continue
|
||||||
for inner in ast.walk(ast.parse(path.read_text(encoding="utf-8-sig"))):
|
for field in _declared_resolution_fields(path):
|
||||||
if (
|
out[field] = max(out.get(field, 0), node.lineno)
|
||||||
isinstance(inner, ast.Call)
|
|
||||||
and isinstance(inner.func, ast.Name)
|
|
||||||
and inner.func.id == "declare_resolution"
|
|
||||||
):
|
|
||||||
for keyword in inner.keywords:
|
|
||||||
if keyword.arg:
|
|
||||||
out[keyword.arg] = max(out.get(keyword.arg, 0), node.lineno)
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _pipeline():
|
def _pipeline():
|
||||||
"""(ordered steps, {step: methods it reaches}) for the resolution dispatch."""
|
"""(ordered steps, {step: methods it reaches}) for the resolution dispatch."""
|
||||||
source = (_SRT / "server_args.py").read_text(encoding="utf-8-sig")
|
tree = _parsed(_SRT / "server_args.py")
|
||||||
tree = ast.parse(source)
|
|
||||||
record = next(
|
record = next(
|
||||||
node
|
node
|
||||||
for node in tree.body
|
for node in tree.body
|
||||||
@@ -305,7 +316,7 @@ def _opaque_callback_positions(dispatch, source_module):
|
|||||||
it.
|
it.
|
||||||
"""
|
"""
|
||||||
imported = {}
|
imported = {}
|
||||||
for node in ast.walk(ast.parse(source_module.read_text(encoding="utf-8-sig"))):
|
for node in ast.walk(_parsed(source_module)):
|
||||||
if isinstance(node, ast.ImportFrom) and node.module:
|
if isinstance(node, ast.ImportFrom) and node.module:
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
imported[alias.asname or alias.name] = node.module
|
imported[alias.asname or alias.name] = node.module
|
||||||
@@ -349,7 +360,7 @@ def _opaque_callback_positions(dispatch, source_module):
|
|||||||
path = _SRT / (module[len("sglang.srt.") :].replace(".", "/") + ".py")
|
path = _SRT / (module[len("sglang.srt.") :].replace(".", "/") + ".py")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
continue
|
continue
|
||||||
for spelling in callbacks_in(ast.parse(path.read_text(encoding="utf-8-sig"))):
|
for spelling in callbacks_in(_parsed(path)):
|
||||||
positions[spelling] = min(positions.get(spelling, 10**9), node.lineno)
|
positions[spelling] = min(positions.get(spelling, 10**9), node.lineno)
|
||||||
return positions
|
return positions
|
||||||
|
|
||||||
@@ -392,7 +403,7 @@ def _declaration_positions():
|
|||||||
|
|
||||||
source_module = _SRT / "server_args.py"
|
source_module = _SRT / "server_args.py"
|
||||||
imported = {}
|
imported = {}
|
||||||
for node in ast.walk(ast.parse(source_module.read_text(encoding="utf-8-sig"))):
|
for node in ast.walk(_parsed(source_module)):
|
||||||
if isinstance(node, ast.ImportFrom) and node.module:
|
if isinstance(node, ast.ImportFrom) and node.module:
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
imported[alias.asname or alias.name] = node.module
|
imported[alias.asname or alias.name] = node.module
|
||||||
@@ -405,15 +416,7 @@ def _declaration_positions():
|
|||||||
path = _SRT / (module[len("sglang.srt.") :].replace(".", "/") + ".py")
|
path = _SRT / (module[len("sglang.srt.") :].replace(".", "/") + ".py")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return frozenset()
|
return frozenset()
|
||||||
fields = set()
|
return _declared_resolution_fields(path)
|
||||||
for node in ast.walk(ast.parse(path.read_text(encoding="utf-8-sig"))):
|
|
||||||
if (
|
|
||||||
isinstance(node, ast.Call)
|
|
||||||
and isinstance(node.func, ast.Name)
|
|
||||||
and node.func.id == "declare_resolution"
|
|
||||||
):
|
|
||||||
fields |= {kw.arg for kw in node.keywords if kw.arg}
|
|
||||||
return frozenset(fields)
|
|
||||||
|
|
||||||
declared_at = {}
|
declared_at = {}
|
||||||
for index, step in enumerate(steps):
|
for index, step in enumerate(steps):
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ It is a ratchet, not a proof.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import pathlib
|
import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
@@ -114,6 +115,7 @@ def _registry_functions():
|
|||||||
return functions
|
return functions
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=None)
|
||||||
def _registered_entries():
|
def _registered_entries():
|
||||||
"""Entries the import map cannot reach: passes and override providers.
|
"""Entries the import map cannot reach: passes and override providers.
|
||||||
|
|
||||||
@@ -138,13 +140,17 @@ def _registered_entries():
|
|||||||
# from the source. The entry carries the *defining* file: `_reaches_a_bag`
|
# from the source. The entry carries the *defining* file: `_reaches_a_bag`
|
||||||
# walks functions in the entry's file, so a call-site key walks nothing.
|
# walks functions in the entry's file, so a call-site key walks nothing.
|
||||||
by_value = set()
|
by_value = set()
|
||||||
trees = {}
|
sources = {
|
||||||
for path in sorted(_SRT.rglob("*.py")):
|
path: path.read_text(encoding="utf-8-sig")
|
||||||
|
for path in sorted(_SRT.rglob("*.py"))
|
||||||
|
}
|
||||||
|
for path, source in sources.items():
|
||||||
|
if "run_post_process_pass" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
trees[path] = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
continue
|
continue
|
||||||
for path, tree in trees.items():
|
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
if (
|
if (
|
||||||
isinstance(node, ast.Call)
|
isinstance(node, ast.Call)
|
||||||
@@ -154,6 +160,14 @@ def _registered_entries():
|
|||||||
and isinstance(node.args[1], ast.Name)
|
and isinstance(node.args[1], ast.Name)
|
||||||
):
|
):
|
||||||
by_value.add(node.args[1].id)
|
by_value.add(node.args[1].id)
|
||||||
|
trees = {}
|
||||||
|
for path, source in sources.items():
|
||||||
|
if not any(name in source for name in by_value):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
trees[path] = ast.parse(source)
|
||||||
|
except SyntaxError:
|
||||||
|
continue
|
||||||
for name in sorted(by_value):
|
for name in sorted(by_value):
|
||||||
defined_in = [
|
defined_in = [
|
||||||
path
|
path
|
||||||
@@ -173,14 +187,19 @@ def _registered_entries():
|
|||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
||||||
def _reaches_a_bag(path, entry):
|
@functools.lru_cache(maxsize=None)
|
||||||
"""Does `entry` in `path` reach a bag accessor, following calls in-module?"""
|
def _functions_in(path):
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
||||||
functions = {
|
return {
|
||||||
node.name: node
|
node.name: node
|
||||||
for node in ast.walk(tree)
|
for node in ast.walk(tree)
|
||||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _reaches_a_bag(path, entry):
|
||||||
|
"""Does `entry` in `path` reach a bag accessor, following calls in-module?"""
|
||||||
|
functions = _functions_in(path)
|
||||||
seen = set()
|
seen = set()
|
||||||
|
|
||||||
def walk(name):
|
def walk(name):
|
||||||
|
|||||||
@@ -47,8 +47,11 @@ def _declared_by_keyword():
|
|||||||
"""Fields named as a keyword at a declaration site."""
|
"""Fields named as a keyword at a declaration site."""
|
||||||
written = set()
|
written = set()
|
||||||
for path in sorted(_SRT.rglob("*.py")):
|
for path in sorted(_SRT.rglob("*.py")):
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if not any(declarer in source for declarer in _DECLARERS):
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
raise AssertionError(f"unparsable module in the census: {path}")
|
raise AssertionError(f"unparsable module in the census: {path}")
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
@@ -293,8 +296,11 @@ def _subtrees_with_their_own_record():
|
|||||||
rel = path.relative_to(_PACKAGE).as_posix()
|
rel = path.relative_to(_PACKAGE).as_posix()
|
||||||
if rel.startswith("srt/") or "/" not in rel:
|
if rel.startswith("srt/") or "/" not in rel:
|
||||||
continue
|
continue
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if "ServerArgs" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
continue
|
continue
|
||||||
if any(
|
if any(
|
||||||
@@ -333,8 +339,11 @@ def _chain_reads(written):
|
|||||||
under_srt = rel[len("srt/") :]
|
under_srt = rel[len("srt/") :]
|
||||||
if path.name in _OWNERS or under_srt.startswith(_OWNERS[-1]):
|
if path.name in _OWNERS or under_srt.startswith(_OWNERS[-1]):
|
||||||
continue
|
continue
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if "server_args" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
raise AssertionError(f"unparsable module in the census: {rel}")
|
raise AssertionError(f"unparsable module in the census: {rel}")
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -498,8 +498,11 @@ def _field_reads():
|
|||||||
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
||||||
if rel.startswith(_SLOT_OWNERS):
|
if rel.startswith(_SLOT_OWNERS):
|
||||||
continue
|
continue
|
||||||
|
source = path.read_text()
|
||||||
|
if "get_server_args" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text())
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
continue
|
continue
|
||||||
module_direct, module_alias = _collect(rel, tree)
|
module_direct, module_alias = _collect(rel, tree)
|
||||||
@@ -551,8 +554,11 @@ class TestConfiguredSizeCallSites(CustomTestCase):
|
|||||||
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
||||||
if rel.startswith(_SLOT_OWNERS):
|
if rel.startswith(_SLOT_OWNERS):
|
||||||
continue
|
continue
|
||||||
|
source = path.read_text()
|
||||||
|
if "configured_" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text())
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
continue
|
continue
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
@@ -588,8 +594,11 @@ class TestNoRenamedAccessorImports(CustomTestCase):
|
|||||||
offenders = []
|
offenders = []
|
||||||
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
||||||
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
||||||
|
source = path.read_text()
|
||||||
|
if "get_server_args" not in source and "configured_" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text())
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
continue
|
continue
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ reaches, because nothing short of booting a server runs the launcher.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
import functools
|
||||||
import pathlib
|
import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ def _multiprocessing_names(tree):
|
|||||||
return modules, constructors
|
return modules, constructors
|
||||||
|
|
||||||
|
|
||||||
|
@functools.lru_cache(maxsize=None)
|
||||||
def _configured_accessors() -> frozenset:
|
def _configured_accessors() -> frozenset:
|
||||||
"""The `configured_*_size()` names `runtime_context` exports.
|
"""The `configured_*_size()` names `runtime_context` exports.
|
||||||
|
|
||||||
@@ -217,11 +219,14 @@ def _launch_paths():
|
|||||||
nothing, which no derivation can reach.
|
nothing, which no derivation can reach.
|
||||||
"""
|
"""
|
||||||
seen = {}
|
seen = {}
|
||||||
|
sizes = frozenset(_LIVE_SHADOWED) | _configured_accessors()
|
||||||
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
||||||
source = path.read_text()
|
source = path.read_text()
|
||||||
# Every spawn shape below names Process, ProcessPoolExecutor or Popen.
|
# Every spawn shape below names Process, ProcessPoolExecutor or Popen.
|
||||||
if not any(name in source for name in ("Process", "Popen", "spawn")):
|
if not any(name in source for name in ("Process", "Popen", "spawn")):
|
||||||
continue
|
continue
|
||||||
|
if not any(name in source for name in sizes):
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(source)
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
|
|||||||
@@ -397,6 +397,9 @@ def _publishing_functions():
|
|||||||
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
||||||
if rel in _PUBLISH_HOMES:
|
if rel in _PUBLISH_HOMES:
|
||||||
continue
|
continue
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if not any(name in source for name in _PUBLISH_NAMES):
|
||||||
|
continue
|
||||||
mod = _module(rel)
|
mod = _module(rel)
|
||||||
if mod is None or not mod.publishers:
|
if mod is None or not mod.publishers:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -81,8 +81,11 @@ class TestServerArgsNamespaces(CustomTestCase):
|
|||||||
for path in sorted(srt.rglob("*.py")):
|
for path in sorted(srt.rglob("*.py")):
|
||||||
if path.name == "runtime_context.py":
|
if path.name == "runtime_context.py":
|
||||||
continue
|
continue
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if "runtime_context" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
self.fail(f"unparsable module in the census: {path}")
|
self.fail(f"unparsable module in the census: {path}")
|
||||||
bindings = collections.defaultdict(set)
|
bindings = collections.defaultdict(set)
|
||||||
@@ -155,8 +158,11 @@ class TestServerArgsNamespaces(CustomTestCase):
|
|||||||
sites = 0
|
sites = 0
|
||||||
disagreements = []
|
disagreements = []
|
||||||
for path in sorted(srt.rglob("*.py")):
|
for path in sorted(srt.rglob("*.py")):
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if not any(name in source for name in accessors):
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
self.fail(f"unparsable module in the census: {path}")
|
self.fail(f"unparsable module in the census: {path}")
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
|
|||||||
@@ -682,8 +682,11 @@ class TestSuppliedInstanceExposure(CustomTestCase):
|
|||||||
root = _PACKAGE_ROOT
|
root = _PACKAGE_ROOT
|
||||||
for path in sorted(root.rglob("*.py")):
|
for path in sorted(root.rglob("*.py")):
|
||||||
rel = path.relative_to(root).as_posix()
|
rel = path.relative_to(root).as_posix()
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if "_late_resolution" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
self.fail(f"unparsable module in the census: {rel}")
|
self.fail(f"unparsable module in the census: {rel}")
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
@@ -734,6 +737,8 @@ class TestSuppliedInstanceExposure(CustomTestCase):
|
|||||||
written |= _expanded_override_keys(rel, tree, node, kw)
|
written |= _expanded_override_keys(rel, tree, node, kw)
|
||||||
return written
|
return written
|
||||||
|
|
||||||
|
_READS_CACHE = None
|
||||||
|
|
||||||
def _supplied_instance_reads(self) -> set:
|
def _supplied_instance_reads(self) -> set:
|
||||||
"""Three spellings of the same read: ``server_args.field`` off the
|
"""Three spellings of the same read: ``server_args.field`` off the
|
||||||
parameter, ``getattr(server_args, "field", default)`` with a literal
|
parameter, ``getattr(server_args, "field", default)`` with a literal
|
||||||
@@ -746,13 +751,18 @@ class TestSuppliedInstanceExposure(CustomTestCase):
|
|||||||
census still does not count -- but those reads are gone for every
|
census still does not count -- but those reads are gone for every
|
||||||
resolution-written field and ``test_chain_read_ratchet.py`` holds them
|
resolution-written field and ``test_chain_read_ratchet.py`` holds them
|
||||||
at zero, so the gap is no longer where the risk is."""
|
at zero, so the gap is no longer where the risk is."""
|
||||||
|
if TestSuppliedInstanceExposure._READS_CACHE is not None:
|
||||||
|
return TestSuppliedInstanceExposure._READS_CACHE
|
||||||
pairs = set()
|
pairs = set()
|
||||||
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
||||||
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
||||||
if rel.startswith(_OWNERS):
|
if rel.startswith(_OWNERS):
|
||||||
continue
|
continue
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if "server_args" not in source:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# A silently dropped module shrinks `found` and reads as
|
# A silently dropped module shrinks `found` and reads as
|
||||||
# intentional surface shrinkage under the bidirectional pin.
|
# intentional surface shrinkage under the bidirectional pin.
|
||||||
@@ -818,6 +828,7 @@ class TestSuppliedInstanceExposure(CustomTestCase):
|
|||||||
and node.value.value.id == "self"
|
and node.value.value.id == "self"
|
||||||
):
|
):
|
||||||
pairs.add((rel, node.attr))
|
pairs.add((rel, node.attr))
|
||||||
|
TestSuppliedInstanceExposure._READS_CACHE = pairs
|
||||||
return pairs
|
return pairs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -826,8 +837,13 @@ class TestSuppliedInstanceExposure(CustomTestCase):
|
|||||||
written = set()
|
written = set()
|
||||||
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
for path in sorted(_PACKAGE_ROOT.rglob("*.py")):
|
||||||
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
rel = path.relative_to(_PACKAGE_ROOT).as_posix()
|
||||||
|
source = path.read_text(encoding="utf-8-sig")
|
||||||
|
if "record_config_updates" not in source and not (
|
||||||
|
"get_context" in source and "override" in source
|
||||||
|
):
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(path.read_text(encoding="utf-8-sig"))
|
tree = ast.parse(source)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
raise AssertionError(f"unparsable module in the census: {rel}")
|
raise AssertionError(f"unparsable module in the census: {rel}")
|
||||||
for node in ast.walk(tree):
|
for node in ast.walk(tree):
|
||||||
|
|||||||
Reference in New Issue
Block a user