1212
1313from omniload .target .clickhouse import ClickhouseDestination
1414from tests .util .common import get_testdata_path
15+ from tests .warehouse .manager import get_remote_filesystem_services
1516from tests .warehouse .settings import DESTINATIONS , SOURCES
1617
1718logger = logging .getLogger (__name__ )
1819
1920
21+ def _explicit_test_targets (config ) -> list [Path ]:
22+ """Return explicit repo test paths from pytest's original invocation."""
23+ targets = []
24+ for arg in config .invocation_params .args :
25+ raw_path = arg .split ("::" , 1 )[0 ]
26+ if raw_path .startswith ("-" ):
27+ continue
28+ path = Path (raw_path )
29+ if not path .is_absolute ():
30+ normalized = raw_path .removeprefix ("./" )
31+ if normalized .split ("/" , 1 )[0 ] not in {"tests" , "examples" , "omniload" }:
32+ continue
33+ path = Path (config .rootpath ) / normalized
34+ targets .append (path .resolve ())
35+ return targets
36+
37+
38+ def _only_main_filesystem_tests (config ) -> bool :
39+ """Return whether every explicit pytest target is in main/filesystem."""
40+ filesystem_root = (Path (config .rootpath ) / "tests/main/filesystem" ).resolve ()
41+ targets = _explicit_test_targets (config )
42+ return bool (targets ) and all (
43+ target == filesystem_root or filesystem_root in target .parents
44+ for target in targets
45+ )
46+
47+
48+ def _remote_filesystem_tests_selected (config ) -> bool :
49+ """Return whether the invocation can collect the remote emulator test module."""
50+ if hasattr (config , "workerinput" ):
51+ return config .workerinput ["remote_filesystem_tests_selected" ]
52+ if _integration_deselected (config ):
53+ return False
54+ remote_tests = (
55+ Path (config .rootpath ) / "tests/main/filesystem/test_remote_integration.py"
56+ ).resolve ()
57+ targets = _explicit_test_targets (config )
58+ if not targets :
59+ return True
60+ return any (
61+ target == remote_tests or target in remote_tests .parents for target in targets
62+ )
63+
64+
65+ def _all_managed_containers (config ):
66+ containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
67+ if _remote_filesystem_tests_selected (config ):
68+ containers |= set (get_remote_filesystem_services ().values ())
69+ return containers
70+
71+
72+ def _managed_containers (config ):
73+ containers = set ()
74+ if not _only_main_filesystem_tests (config ):
75+ containers |= set (SOURCES .values ()) | set (DESTINATIONS .values ())
76+ if _remote_filesystem_tests_selected (config ):
77+ containers |= set (get_remote_filesystem_services ().values ())
78+ return containers
79+
80+
2081def pytest_configure (config ):
2182 logging .getLogger ("urllib3.connectionpool" ).setLevel (logging .INFO )
2283 logging .getLogger ("testcontainers.core.waiting_utils" ).setLevel (logging .WARNING )
@@ -28,6 +89,9 @@ def pytest_configure(config):
2889def pytest_configure_node (node ):
2990 """xdist hook"""
3091 node .workerinput ["shared_directory" ] = node .config .shared_directory
92+ node .workerinput ["remote_filesystem_tests_selected" ] = (
93+ _remote_filesystem_tests_selected (node .config )
94+ )
3195
3296
3397def pytest_sessionstart (session ):
@@ -68,6 +132,18 @@ def _integration_deselected(config):
68132 return (config .option .markexpr or "" ).strip () == "not integration"
69133
70134
135+ def pytest_ignore_collect (collection_path : Path , config ) -> bool | None :
136+ """Avoid importing emulator-only tests in the Docker-free fast lane."""
137+ if not _integration_deselected (config ):
138+ return None
139+ remote_tests = (
140+ Path (config .rootpath ) / "tests/main/filesystem/test_remote_integration.py"
141+ ).resolve ()
142+ if collection_path .resolve () == remote_tests :
143+ return True
144+ return None
145+
146+
71147def _skip_containers (config ):
72148 return _integration_deselected (config ) or not _docker_available ()
73149
@@ -100,8 +176,10 @@ def testdata_path() -> Path:
100176
101177@pytest .fixture (scope = "session" , autouse = True )
102178def manage_containers (request , shared_directory ):
103- unique_containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
104- for container in unique_containers :
179+ # Set every lock directory on both controller and workers. Container boot is
180+ # still filtered below, but fixture polling must not depend on both processes
181+ # deriving exactly the same target set from their invocation arguments.
182+ for container in _all_managed_containers (request .config ):
105183 container .lock_dir = shared_directory # ty: ignore[invalid-assignment, unresolved-attribute, unused-ignore-comment]
106184
107185
@@ -111,8 +189,7 @@ def start_containers(config):
111189 if is_worker (config ):
112190 return
113191
114- unique_containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
115- unique_containers = [x for x in unique_containers if x is not None ]
192+ unique_containers = [x for x in _managed_containers (config ) if x is not None ]
116193
117194 for container in unique_containers :
118195 container .lock_dir = config .shared_directory # ty: ignore[invalid-assignment, unresolved-attribute, unused-ignore-comment]
@@ -164,8 +241,7 @@ def stop_containers(config):
164241 if not should_manage_containers :
165242 return
166243
167- unique_containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
168- unique_containers = [x for x in unique_containers if x is not None ]
244+ unique_containers = [x for x in _managed_containers (config ) if x is not None ]
169245
170246 for container in unique_containers :
171247 try :
0 commit comments