Fix Fixture Loading by Configuring Settings of django in child process that gets spawn #2033
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.