Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Fixture Loading by Configuring Settings of django in child process that gets spawn #2033

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ashtonrobinson
Copy link

@ashtonrobinson ashtonrobinson commented Jul 19, 2023

This fix refers to here

Currently, when a test class inherits from ChannelsLiveServerTestCase, all database changes that happen will be written to the default main database, not the test database that is created by the parent class--TransactionTestCase. This is the reason that fixtures are not currently referenced

When ChannelsLiveServerTestCase initiates a DaphneProcess, by calling this following line
self._server_process = self.ProtocolServerProcess(self.host, get_application)
This instantiates a DaphneProcess object which is a multiprocessing.Process object which takes care of creating a new process. This uses the SpawnProcess of multiprocessing class on Windows and macOS. Linux uses the Fork method, but Fork causes problems. Spawn should be used only.

Here is what is said about spawn.
The parent process starts a fresh python interpreter process. The child process will only inherit those resources necessary to run the process objects run() method. In particular, unnecessary file descriptors and handles from the parent process will not be inherited. Starting a process using this method is rather slow compared to using fork or forkserver. [Available on Unix and Windows. The default on Windows and macOS.]

This means that django setup() is called and the settings file it reloaded. This means that `setting.DATABASES['default'] in the process that runs the Daphne server will point to the default database. This is incorrect, and will cause a leakage of all changes made by selenium or another testing framework.

Because ChannelsLiveServerTestCase inherits from TransactionTestCase, it it necessary that all functionality is retained. Therefore, we must point the django module at the test database in the the new process that is running Daphne so that we can correctly use the fixtures, and have the test database removed after testing is finished. Also, so that changes do not leak into the default database.

This is why, we reassign the default main database NAME to the NAME of the TEST attribute(see the codes difference).
This is fine because the documentation already describes that you need to have this structure of your settings file here

To reproduce the issue, create a test file in some django project that has authentication, a login page, and that uses an asgi and selenium or another web driver framework.

from channels.testing import ChannelsLiveServerTestCase
from django.contrib.auth.models import User

class LiveTest(ChannelsLiveServerTestCase):
serve_static = True
fixtures = [] # any fixtures you want to be loaded

@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.driver = webdriver.Chrome()

@classmethod
def tearDownClass(cls) -> None:
super().tearDownClass()
cls.driver.quit()

def setUp(self):
user = User.objects.create(username='temp')
user.set_password('temp')

def login(self, username:str, password:str):
time.sleep(2)
self.driver.get(self.live_server_url + '/')
time.sleep(2)
self.driver.get(self.live_server_url + '/login/')
# print(settings.DATABASES['default'])
user_input_field = #find user input field
password_input_field = #find password input field
user_input_field.send_keys(username)
password_input_field.send_keys(password)
time.sleep(3)
login_button = #find login button
login_button.click()
time.sleep(3)

def test_click(self):
self.login('temp', 'temp')

Even though we have created a temp user in the test, Django will not let us log in. This because the Daphne server is pointed at the wrong database in the child process.

When we examine settings.DATABASES['default'] in the test file, we see that django returns this.
{'ENGINE': 'django.db.backends.sqlite3', 'NAME': '/Users/ashtonrobinson/cNode/testdatabase.sqlite3', 'USER': 'user', 'PASSWORD': 'password', 'HOST': 'localhost', 'PORT': '5432', 'TEST': {'NAME': '/Users/ashtonrobinson/cNode/testdatabase.sqlite3', 'CHARSET': None, 'COLLATION': None, 'MIGRATE': True, 'MIRROR': None}, 'ATOMIC_REQUESTS': False, 'AUTOCOMMIT': True, 'CONN_MAX_AGE': 0, 'OPTIONS': {}, 'TIME_ZONE': None}

We see that the NAME field is correctly the test database, but this needs to be set when django is reloaded in the child process. This is what is being done by the updates to the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant