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 the collection paths resolved by pytest's argument parser."""
23+ targets = []
24+ for arg in config .args :
25+ raw_path = arg .split ("::" , 1 )[0 ]
26+ path = Path (raw_path )
27+ if not path .is_absolute ():
28+ normalized = raw_path .removeprefix ("./" )
29+ if normalized .split ("/" , 1 )[0 ] not in {"tests" , "examples" , "omniload" }:
30+ continue
31+ path = Path (config .rootpath ) / normalized
32+ targets .append (path .resolve ())
33+ return targets
34+
35+
36+ def _only_main_filesystem_tests (config ) -> bool :
37+ """Return whether every explicit pytest target is in main/filesystem."""
38+ filesystem_root = (Path (config .rootpath ) / "tests/main/filesystem" ).resolve ()
39+ targets = _explicit_test_targets (config )
40+ return bool (targets ) and all (
41+ target == filesystem_root or filesystem_root in target .parents
42+ for target in targets
43+ )
44+
45+
46+ def _remote_filesystem_tests_selected (config ) -> bool :
47+ """Return whether the invocation can collect the remote emulator test module."""
48+ if hasattr (config , "workerinput" ):
49+ return config .workerinput ["remote_filesystem_tests_selected" ]
50+ if _integration_deselected (config ):
51+ return False
52+ remote_tests = (
53+ Path (config .rootpath ) / "tests/main/filesystem/test_remote_integration.py"
54+ ).resolve ()
55+ targets = _explicit_test_targets (config )
56+ if not targets :
57+ return True
58+ return any (
59+ target == remote_tests or target in remote_tests .parents for target in targets
60+ )
61+
62+
63+ def _all_managed_containers (config ):
64+ containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
65+ if _remote_filesystem_tests_selected (config ):
66+ containers |= set (get_remote_filesystem_services ().values ())
67+ return containers
68+
69+
70+ def _managed_containers (config ):
71+ containers = set ()
72+ if not _only_main_filesystem_tests (config ):
73+ containers |= set (SOURCES .values ()) | set (DESTINATIONS .values ())
74+ if _remote_filesystem_tests_selected (config ):
75+ containers |= set (get_remote_filesystem_services ().values ())
76+ return containers
77+
78+
2079def pytest_configure (config ):
2180 logging .getLogger ("urllib3.connectionpool" ).setLevel (logging .INFO )
2281 logging .getLogger ("testcontainers.core.waiting_utils" ).setLevel (logging .WARNING )
@@ -28,6 +87,9 @@ def pytest_configure(config):
2887def pytest_configure_node (node ):
2988 """xdist hook"""
3089 node .workerinput ["shared_directory" ] = node .config .shared_directory
90+ node .workerinput ["remote_filesystem_tests_selected" ] = (
91+ _remote_filesystem_tests_selected (node .config )
92+ )
3193
3294
3395def pytest_sessionstart (session ):
@@ -68,6 +130,18 @@ def _integration_deselected(config):
68130 return (config .option .markexpr or "" ).strip () == "not integration"
69131
70132
133+ def pytest_ignore_collect (collection_path : Path , config ) -> bool | None :
134+ """Avoid importing emulator-only tests in the Docker-free fast lane."""
135+ if not _integration_deselected (config ):
136+ return None
137+ remote_tests = (
138+ Path (config .rootpath ) / "tests/main/filesystem/test_remote_integration.py"
139+ ).resolve ()
140+ if collection_path .resolve () == remote_tests :
141+ return True
142+ return None
143+
144+
71145def _skip_containers (config ):
72146 return _integration_deselected (config ) or not _docker_available ()
73147
@@ -100,8 +174,10 @@ def testdata_path() -> Path:
100174
101175@pytest .fixture (scope = "session" , autouse = True )
102176def manage_containers (request , shared_directory ):
103- unique_containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
104- for container in unique_containers :
177+ # Set every lock directory on both controller and workers. Container boot is
178+ # still filtered below, but fixture polling must not depend on both processes
179+ # deriving exactly the same target set from their invocation arguments.
180+ for container in _all_managed_containers (request .config ):
105181 container .lock_dir = shared_directory # ty: ignore[invalid-assignment, unresolved-attribute, unused-ignore-comment]
106182
107183
@@ -111,8 +187,7 @@ def start_containers(config):
111187 if is_worker (config ):
112188 return
113189
114- unique_containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
115- unique_containers = [x for x in unique_containers if x is not None ]
190+ unique_containers = [x for x in _managed_containers (config ) if x is not None ]
116191
117192 for container in unique_containers :
118193 container .lock_dir = config .shared_directory # ty: ignore[invalid-assignment, unresolved-attribute, unused-ignore-comment]
@@ -164,8 +239,7 @@ def stop_containers(config):
164239 if not should_manage_containers :
165240 return
166241
167- unique_containers = set (SOURCES .values ()) | set (DESTINATIONS .values ())
168- unique_containers = [x for x in unique_containers if x is not None ]
242+ unique_containers = [x for x in _managed_containers (config ) if x is not None ]
169243
170244 for container in unique_containers :
171245 try :
0 commit comments