1

My code creates two instances of SimpleFTPServer, which are then called with multiprocessing.Process. The test case used both servers, which will sometimes get replaced with each other. This causes the test to be flaky. Here is the test and fixtures code:

# fixtures.py
import multiprocessing
import random
import time

import pytest

from pytest_localftpserver.servers import ProcessFTPServer, SimpleFTPServer


class ProcessFTPServer:

    def __init__(self, username, password, ftp_home, ftp_port, use_TLS=False):
        self._server = SimpleFTPServer(username, password, ftp_port=ftp_port,
                                       ftp_home=ftp_home, use_TLS=use_TLS)
        print(self._server)
        self.process = multiprocessing.Process(target=self._server.serve_forever)
        # This is a must in order to clear used sockets
        self.process.daemon = True
        # time.sleep(0.5) # and 200 out 200 runs pass ...?
        self.process.start()

    def stop(self):
        self.process.terminate()

   # adding this will cause the tests to fail less often
   #def __repr__(self):
   #    return f"{self._server.username}:{self._server.password}"

    
@pytest.fixture(scope="function")
def servers(request):
    port1, port2 = random.randint(1024, 2**16-1), random.randint(1024, 2**16-1)
    while port1 == port2:
        port2 = random.randint(1024, 2**16-1)

    server1 = ProcessFTPServer(username="benz", password="erni1",
                              ftp_home="/home/oznt/Music", ftp_port=port1) # uses explicit parameters
    request.addfinalizer(server1.stop)

    server2 = ProcessFTPServer(username="fakeusername", password="qweqwe",
                              ftp_home="/home/oznt/", ftp_port=port2) # uses explicit parameters
    request.addfinalizer(server2.stop)
    assert id(server1) != id(server2)
    return [server1, server2]

And:

# tests.py
from ftplib import FTP
import ftplib

from fixtures import servers

def test_ftp(servers):
    ftpserver_from, ftpserver_to = servers
    try:
        ftp1 = FTP()
        ftp1.connect('localhost', ftpserver_from._server._ftp_port)
        ftp1.login(ftpserver_from._server.username, ftpserver_from._server.password)
        ftp2 = FTP()

        ftp2.connect('localhost', ftpserver_to._server._ftp_port)
        ftp2.login(ftpserver_to._server.username, ftpserver_to._server.password)
    except ftplib.error_perm:
        import pdb; pdb.set_trace()

Here is an example when the code fails to run (the exception is triggered):

$ for i in `seq 1 10`; do pytest -s . ; done
=============================================== test session starts ================================================
platform linux -- Python 3.9.4, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /home/oznt/Software/demo-pytest-ftpserver, configfile: pytest.ini
plugins: localftpserver-1.1.2, env-0.6.2
collected 1 item                                                                                                   

tests/test_two_servers.py <pytest_localftpserver.servers.SimpleFTPServer at 0x7f654a1714c0>
<pytest_localftpserver.servers.SimpleFTPServer at 0x7f654a171220>
{'servers': (<fixtures.ProcessFTPServer object at 0x7f654a171820>, <fixtures.ProcessFTPServer object at 0x7f654a171520>)}

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB set_trace >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
--Return--
> /home/oznt/Software/demo-pytest-ftpserver/tests/test_two_servers.py(19)test_ftp()->None
-> import pdb; pdb.set_trace()
(Pdb)

Obviously, the instances of ProcessFTPServer are 'attached' to fixtures in some order I can't control. The order of fixutres might be related. However, I do not why (I have actually just 1 fixture). Oddly enough, when I add a __repr__ method to ProcessFTPServer the tests fail less often. If I add a small time.sleep(0.5) to purposely slow the tests, 200 runs out of 200 pass. Can someone explain this behavior?

oz123
  • 27,559
  • 27
  • 125
  • 187

0 Answers0