29
29
import yaml
30
30
import tempfile
31
31
from contextlib import contextmanager
32
- import random
33
32
34
- from beets .util import py3_path
33
+ from beets .util import py3_path , bluelet
35
34
from beetsplug import bpd
36
35
import confuse
37
36
@@ -231,11 +230,6 @@ def readline(self, terminator=b'\n', bufsize=1024):
231
230
return line
232
231
233
232
234
- def start_beets (* args ):
235
- import beets .ui
236
- beets .ui .main (list (args ))
237
-
238
-
239
233
def implements (commands , expectedFailure = False ): # noqa: N803
240
234
def _test (self ):
241
235
with self .run_bpd () as client :
@@ -246,6 +240,27 @@ def _test(self):
246
240
return unittest .expectedFailure (_test ) if expectedFailure else _test
247
241
248
242
243
+ bluelet_listener = bluelet .Listener
244
+ @mock .patch ("beets.util.bluelet.Listener" )
245
+ def start_server (args , assigned_port , listener_patch ):
246
+ """Start the bpd server, writing the port to `assigned_port`.
247
+ """
248
+ def listener_wrap (host , port ):
249
+ """Wrap `bluelet.Listener`, writing the port to `assigend_port`.
250
+ """
251
+ # `bluelet.Listener` has previously been saved to
252
+ # `bluelet_listener` as this function will replace it at its
253
+ # original location.
254
+ listener = bluelet_listener (host , port )
255
+ # read port assigned by OS
256
+ assigned_port .put_nowait (listener .sock .getsockname ()[1 ])
257
+ return listener
258
+ listener_patch .side_effect = listener_wrap
259
+
260
+ import beets .ui
261
+ beets .ui .main (args )
262
+
263
+
249
264
class BPDTestHelper (unittest .TestCase , TestHelper ):
250
265
def setUp (self ):
251
266
self .setup_beets (disk = True )
@@ -263,22 +278,18 @@ def tearDown(self):
263
278
self .unload_plugins ()
264
279
265
280
@contextmanager
266
- def run_bpd (self , host = 'localhost' , port = None , password = None ,
267
- do_hello = True , second_client = False ):
281
+ def run_bpd (self , host = 'localhost' , password = None , do_hello = True ,
282
+ second_client = False ):
268
283
""" Runs BPD in another process, configured with the same library
269
284
database as we created in the setUp method. Exposes a client that is
270
285
connected to the server, and kills the server at the end.
271
286
"""
272
- # Choose a port (randomly) to avoid conflicts between parallel
273
- # tests.
274
- if not port :
275
- port = 9876 + random .randint (0 , 10000 )
276
-
277
287
# Create a config file:
278
288
config = {
279
289
'pluginpath' : [py3_path (self .temp_dir )],
280
290
'plugins' : 'bpd' ,
281
- 'bpd' : {'host' : host , 'port' : port , 'control_port' : port + 1 },
291
+ # use port 0 to let the OS choose a free port
292
+ 'bpd' : {'host' : host , 'port' : 0 , 'control_port' : 0 },
282
293
}
283
294
if password :
284
295
config ['bpd' ]['password' ] = password
@@ -290,38 +301,39 @@ def run_bpd(self, host='localhost', port=None, password=None,
290
301
config_file .close ()
291
302
292
303
# Fork and launch BPD in the new process:
293
- args = (
304
+ assigned_port = mp .Queue (2 ) # 2 slots, `control_port` and `port`
305
+ server = mp .Process (target = start_server , args = ([
294
306
'--library' , self .config ['library' ].as_filename (),
295
307
'--directory' , py3_path (self .libdir ),
296
308
'--config' , py3_path (config_file .name ),
297
309
'bpd'
298
- )
299
- server = mp .Process (target = start_beets , args = args )
310
+ ], assigned_port ))
300
311
server .start ()
301
312
302
- # Wait until the socket is connected:
303
- sock , sock2 = None , None
304
- for _ in range (20 ):
313
+ try :
314
+ assigned_port .get (timeout = 1 ) # skip control_port
315
+ port = assigned_port .get (timeout = 0.5 ) # read port
316
+
305
317
sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
306
- if sock .connect_ex ((host , port )) == 0 :
307
- break
308
- else :
309
- sock .close ()
310
- time .sleep (0.01 )
311
- else :
312
- raise RuntimeError ('Timed out waiting for the BPD server' )
318
+ try :
319
+ sock .connect ((host , port ))
320
+
321
+ if second_client :
322
+ sock2 = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
323
+ try :
324
+ sock2 .connect ((host , port ))
325
+ yield (
326
+ MPCClient (sock , do_hello ),
327
+ MPCClient (sock2 , do_hello ),
328
+ )
329
+ finally :
330
+ sock2 .close ()
313
331
314
- try :
315
- if second_client :
316
- sock2 = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
317
- sock2 .connect ((host , port ))
318
- yield MPCClient (sock , do_hello ), MPCClient (sock2 , do_hello )
319
- else :
320
- yield MPCClient (sock , do_hello )
332
+ else :
333
+ yield MPCClient (sock , do_hello )
334
+ finally :
335
+ sock .close ()
321
336
finally :
322
- sock .close ()
323
- if sock2 :
324
- sock2 .close ()
325
337
server .terminate ()
326
338
server .join (timeout = 0.2 )
327
339
0 commit comments