161 lines
4.3 KiB
Python
161 lines
4.3 KiB
Python
'''
|
|
Regression tests for the cold package import surface.
|
|
|
|
'''
|
|
import json
|
|
import os
|
|
from statistics import median
|
|
import subprocess
|
|
import sys
|
|
from typing import (
|
|
Any,
|
|
get_type_hints,
|
|
)
|
|
|
|
from tractor.discovery import (
|
|
_addr,
|
|
_multiaddr,
|
|
)
|
|
from tractor.ipc import (
|
|
_tcp,
|
|
_uds,
|
|
)
|
|
|
|
|
|
def run_cold_import(code: str) -> dict[str, object]:
|
|
result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
'-c',
|
|
code,
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_lazy_to_asyncio_package_api():
|
|
'''
|
|
Keep the public lazy submodule discoverable without eagerly
|
|
importing it.
|
|
|
|
Before the lazy conversion, package import side effects exposed
|
|
`to_asyncio` to `dir()` and wildcard imports. Exercise those APIs
|
|
in cold interpreters so this test proves normal `import tractor`
|
|
leaves `asyncio` unloaded, while discovery and wildcard access
|
|
still advertise and resolve the public submodule.
|
|
|
|
'''
|
|
cold = run_cold_import(
|
|
'import json, sys, tractor; '
|
|
'print(json.dumps({'
|
|
'"advertised": "to_asyncio" in dir(tractor), '
|
|
'"asyncio_loaded": "asyncio" in sys.modules}))'
|
|
)
|
|
assert cold == {
|
|
'advertised': True,
|
|
'asyncio_loaded': False,
|
|
}
|
|
|
|
wildcard = run_cold_import(
|
|
'import json; '
|
|
'from tractor import *; '
|
|
'print(json.dumps({'
|
|
'"module": to_asyncio.__name__}))'
|
|
)
|
|
assert wildcard == {
|
|
'module': 'tractor.to_asyncio',
|
|
}
|
|
|
|
|
|
def test_cold_import_budget():
|
|
'''
|
|
Keep cold package import below the pre-optimization regression.
|
|
|
|
The original `inspect.stack()` caller lookup made a fresh
|
|
`import tractor` take about 0.42s and dominate actor startup.
|
|
Run seven independent interpreters and gate their median at a
|
|
deliberately broad 0.35s: over twice the measured ~0.145s
|
|
baseline, but low enough to catch restoration of that hot path.
|
|
|
|
Taking the median absorbs process-start and shared-runner noise.
|
|
The child measures only its import, rather than parent-side
|
|
process creation. `TRACTOR_IMPORT_BUDGET_S` provides an explicit,
|
|
reviewable override for platforms that establish a different
|
|
baseline instead of silently weakening the project default.
|
|
|
|
Each child also reports the modules whose eager loading this PR
|
|
intentionally removes, proving a timing pass cannot hide a
|
|
dependency-import regression.
|
|
|
|
'''
|
|
budget_s = float(
|
|
os.environ.get(
|
|
'TRACTOR_IMPORT_BUDGET_S',
|
|
'0.35',
|
|
)
|
|
)
|
|
optional_mods = (
|
|
'asyncio',
|
|
'bidict',
|
|
'colorlog',
|
|
'multiaddr',
|
|
'wrapt',
|
|
)
|
|
code = (
|
|
'import json, sys, time; '
|
|
'started = time.perf_counter(); '
|
|
'import tractor; '
|
|
'elapsed = time.perf_counter() - started; '
|
|
f'optional = {optional_mods!r}; '
|
|
'print(json.dumps({'
|
|
'"elapsed": elapsed, '
|
|
'"loaded": [name for name in optional '
|
|
'if name in sys.modules]}))'
|
|
)
|
|
samples = [
|
|
run_cold_import(code)
|
|
for _ in range(7)
|
|
]
|
|
elapsed = [
|
|
float(sample['elapsed'])
|
|
for sample in samples
|
|
]
|
|
loaded = {
|
|
name
|
|
for sample in samples
|
|
for name in sample['loaded']
|
|
}
|
|
|
|
assert not loaded
|
|
assert median(elapsed) < budget_s, (
|
|
f'cold import median exceeded {budget_s:.3f}s budget: '
|
|
f'{elapsed!r}'
|
|
)
|
|
|
|
|
|
def test_lazy_annotation_names_resolve():
|
|
'''
|
|
Resolve annotations without importing optional dependencies.
|
|
|
|
Moving annotation-only third-party names under `TYPE_CHECKING`
|
|
left their runtime globals undefined, causing
|
|
`typing.get_type_hints()` to raise `NameError`. Resolve every
|
|
affected API and prove the lazy aliases retain import-free runtime
|
|
introspection.
|
|
|
|
'''
|
|
assert get_type_hints(_multiaddr.mk_maddr)['return'] is Any
|
|
assert get_type_hints(_tcp.MsgpackTCPStream.maddr.fget)[
|
|
'return'
|
|
] is Any
|
|
assert get_type_hints(_uds.MsgpackUDSStream.maddr.fget)[
|
|
'return'
|
|
] == Any|str
|
|
assert get_type_hints(_addr.Address.get_random)[
|
|
'current_actor'
|
|
] is Any
|
|
assert _addr.__annotations__['_address_types'].startswith('dict')
|