11import sys
22import os
3+ import re
34import time
45import uuid
56import shlex
2829logger = logging .getLogger (__name__ )
2930
3031
32+ def _worker_port_base ():
33+ """Return a port base offset for the current pytest-xdist worker.
34+
35+ Each worker (gw0, gw1, ...) gets a unique 100-port window so parallel
36+ test runs don't collide. Outside of xdist the offset is 0.
37+ """
38+ worker = os .environ .get ("PYTEST_XDIST_WORKER" , "gw0" )
39+ match = re .match (r"gw(\d+)" , worker )
40+ return int (match .group (1 )) * 100 if match else 0
41+
42+
3143def import_app (app_file , application_name = "app" ):
3244 """Import a dash application from a module. The import path is in dot
3345 notation to the module. The variable named app will be returned.
@@ -59,12 +71,12 @@ def import_app(app_file, application_name="app"):
5971class BaseDashRunner :
6072 """Base context manager class for running applications."""
6173
62- _next_port = 58050
74+ _next_port = 58050 + _worker_port_base ()
6375
6476 def __init__ (self , keep_open , stop_timeout , scheme = "http" , host = "localhost" ):
6577 self .scheme = scheme
6678 self .host = host
67- self .port = 8050
79+ self .port = BaseDashRunner . _next_port
6880 self .started = None
6981 self .keep_open = keep_open
7082 self .stop_timeout = stop_timeout
@@ -220,7 +232,11 @@ def __init__(self, keep_open=False, stop_timeout=3):
220232
221233 # pylint: disable=arguments-differ
222234 def start (self , app , start_timeout = 3 , ** kwargs ):
223- self .port = kwargs .get ("port" , 8050 )
235+ if "port" not in kwargs :
236+ kwargs ["port" ] = self .port = BaseDashRunner ._next_port
237+ BaseDashRunner ._next_port += 1
238+ else :
239+ self .port = kwargs ["port" ]
224240
225241 def target ():
226242 app .scripts .config .serve_locally = True
@@ -279,7 +295,7 @@ def start(
279295 app_module = None ,
280296 application_name = "app" ,
281297 raw_command = None ,
282- port = 8050 ,
298+ port = None ,
283299 start_timeout = 3 ,
284300 ):
285301 """Start the server with waitress-serve in process flavor."""
@@ -288,6 +304,9 @@ def start(
288304 "the process runner needs to start with at least one valid command"
289305 )
290306 return
307+ if port is None :
308+ port = BaseDashRunner ._next_port
309+ BaseDashRunner ._next_port += 1
291310 self .port = port
292311 args = shlex .split (
293312 raw_command
0 commit comments