''' Regression tests for the cold package import surface. ''' import json import os from statistics import median import subprocess import sys 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 introspection 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', 'multibase', 'pyroute2', 'tractor.discovery._multiaddr', 'tractor.net', 'tractor.net._bindspace', 'tractor.net._tunnel', '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_net_package_api(): ''' Keep the public network package cold until symbol access. The old discovery re-exports imported bindspace, tunnel, multiaddr and optional dependencies while initializing a package. Import `tractor.net` in a clean interpreter, inspect its public surface, and prove no implementation or optional dependency was loaded. Then resolve one symbol from each backing module and prove the facade caches each value while preserving boundaries. ''' modules: tuple[str, ...] = ( 'tractor.net._bindspace', 'tractor.net._tunnel', 'tractor.discovery._multiaddr', 'multiaddr', 'multibase', 'pyroute2', ) cold: dict[str, object] = run_cold_import( 'import json, sys; import tractor.net as net; ' f'names = {modules!r}; ' 'print(json.dumps({' '"public": all(name in dir(net) for name in net.__all__), ' '"loaded": [name for name in names if name in sys.modules]' '}))' ) assert cold == { 'public': True, 'loaded': [], } resolved: dict[str, object] = run_cold_import( 'import json, sys; import tractor.net as net; ' 'bindspace = net.BindspaceSpec; ' 'bindspace_cached = net.BindspaceSpec is bindspace; ' 'maddr = net.mk_maddr; ' 'maddr_cached = net.mk_maddr is maddr; ' 'tunnel = net.WGTunnelSpec; ' 'tunnel_cached = net.WGTunnelSpec is tunnel; ' 'print(json.dumps({' '"bindspace_cached": bindspace_cached, ' '"maddr_cached": maddr_cached, ' '"tunnel_cached": tunnel_cached, ' '"bindspace_module": bindspace.__module__, ' '"maddr_module": maddr.__module__, ' '"tunnel_module": tunnel.__module__, ' '"multiaddr_loaded": "multiaddr" in sys.modules, ' '"pyroute2_loaded": "pyroute2" in sys.modules' '}))' ) assert resolved == { 'bindspace_cached': True, 'maddr_cached': True, 'tunnel_cached': True, 'bindspace_module': 'tractor.net._bindspace', 'maddr_module': 'tractor.discovery._multiaddr', 'tunnel_module': 'tractor.net._tunnel', 'multiaddr_loaded': False, 'pyroute2_loaded': False, } def test_net_root_export_and_old_discovery_surface(): ''' Publish networking only from its approved namespace. Before extraction, unshipped network names and implementation modules lived under `tractor.discovery`. Exercise root attribute and wildcard access in clean interpreters, proving `tractor.net` is discoverable and cached without loading implementations. Also prove the old exports are absent and their modules no longer resolve, preventing accidental compatibility aliases. ''' root: dict[str, object] = run_cold_import( 'import json, sys, tractor; ' 'advertised = "net" in dir(tractor); ' 'net = tractor.net; ' 'print(json.dumps({' '"advertised": advertised, ' '"cached": tractor.net is net, ' '"module": net.__name__, ' '"bindspace_loaded": ' '"tractor.net._bindspace" in sys.modules, ' '"tunnel_loaded": "tractor.net._tunnel" in sys.modules' '}))' ) assert root == { 'advertised': True, 'cached': True, 'module': 'tractor.net', 'bindspace_loaded': False, 'tunnel_loaded': False, } old: dict[str, object] = run_cold_import( 'import importlib.util, json; ' 'import tractor.discovery as discovery; ' 'old_names = ("Bindspace", "TunnelledAddress", ' '"mk_maddr", "parse_maddr", "parse_endpoints"); ' 'old_modules = ("tractor.discovery._bindspace", ' '"tractor.discovery._tunnel"); ' 'print(json.dumps({' '"exports": [name for name in old_names ' 'if hasattr(discovery, name)], ' '"modules": [name for name in old_modules ' 'if importlib.util.find_spec(name) is not None]' '}))' ) assert old == { 'exports': [], 'modules': [], } def test_discovery_types_are_dependency_neutral(): ''' Keep canonical address types independent of runtime implementations. Annotation-only imports previously rebound unavailable model names to `Any` so `get_type_hints()` could resolve them. That test only verified its own workaround and hid the `_addr`/transport import cycle. Import the canonical declarations in a cold interpreter and prove they do not pull in optional networking modules. Then prove each implementation imports the same real declarations instead of substituting `Any` under the model names. ''' cold = run_cold_import( 'import json, sys; ' 'from tractor.discovery import types; ' 'blocked = (' '"multiaddr", "multibase", "pyroute2", ' '"tractor.discovery._multiaddr", "tractor.net", ' '"tractor.net._bindspace", "tractor.net._tunnel"); ' 'print(json.dumps({' '"address_module": types.Address.__module__, ' '"blocked": [name for name in blocked ' 'if name in sys.modules]}))' ) assert cold == { 'address_module': 'tractor.discovery.types', 'blocked': [], } from tractor.discovery import ( _addr, _multiaddr, types, ) from tractor.ipc import ( _chan, _server, _tcp, _transport, _uds, ) from tractor.net import _tunnel assert _addr.Address is types.Address assert _addr.UnwrappedAddress is types.UnwrappedAddress assert _multiaddr.AddressDeclaration is types.AddressDeclaration assert _tcp.TaggedTCPAddress is types.TaggedTCPAddress assert _uds.TaggedUnixAddress is types.TaggedUnixAddress assert _transport.Address is types.Address assert _chan.AddressDeclaration is types.AddressDeclaration assert _server.AddressDeclaration is types.AddressDeclaration assert _tunnel.AddressDeclaration is types.AddressDeclaration