Skip to content

Commit 6a59637

Browse files
Fixed #35402 -- Fixed crash in DatabaseFeatures.django_test_skips when running a subset of tests.
Thanks Tim Graham for the report and the review.
1 parent 040bb3e commit 6a59637

2 files changed

Lines changed: 35 additions & 18 deletions

File tree

django/db/backends/base/creation.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -358,27 +358,39 @@ def mark_expected_failures_and_skips(self):
358358
Mark tests in Django's test suite which are expected failures on this
359359
database and test which should be skipped on this database.
360360
"""
361-
# Only load unittest if we're actually testing.
362-
from unittest import expectedFailure, skip
363-
364361
for test_name in self.connection.features.django_test_expected_failures:
365-
test_case_name, _, test_method_name = test_name.rpartition(".")
366-
test_app = test_name.split(".")[0]
367-
# Importing a test app that isn't installed raises RuntimeError.
368-
if test_app in settings.INSTALLED_APPS:
369-
test_case = import_string(test_case_name)
370-
test_method = getattr(test_case, test_method_name)
371-
setattr(test_case, test_method_name, expectedFailure(test_method))
362+
self._mark_test(test_name)
372363
for reason, tests in self.connection.features.django_test_skips.items():
373364
for test_name in tests:
374-
test_case_name, _, test_method_name = test_name.rpartition(".")
375-
test_app = test_name.split(".")[0]
376-
# Importing a test app that isn't installed raises
377-
# RuntimeError.
378-
if test_app in settings.INSTALLED_APPS:
379-
test_case = import_string(test_case_name)
380-
test_method = getattr(test_case, test_method_name)
381-
setattr(test_case, test_method_name, skip(reason)(test_method))
365+
self._mark_test(test_name, reason)
366+
367+
def _mark_test(self, test_name, skip_reason=None):
368+
# Only load unittest during testing.
369+
from unittest import expectedFailure, skip
370+
371+
module_or_class_name, _, name_to_mark = test_name.rpartition(".")
372+
test_app = test_name.split(".")[0]
373+
# Importing a test app that isn't installed raises RuntimeError.
374+
if test_app in settings.INSTALLED_APPS:
375+
try:
376+
test_frame = import_string(module_or_class_name)
377+
except ImportError:
378+
# import_string() can raise ImportError if a submodule's parent
379+
# module hasn't already been imported during test discovery.
380+
# This can happen in at least two cases:
381+
# 1. When running a subset of tests in a module, the test
382+
# runner won't import tests in that module's other
383+
# submodules.
384+
# 2. When the parallel test runner spawns workers with an empty
385+
# import cache.
386+
test_to_mark = import_string(test_name)
387+
test_frame = sys.modules.get(test_to_mark.__module__)
388+
else:
389+
test_to_mark = getattr(test_frame, name_to_mark)
390+
if skip_reason:
391+
setattr(test_frame, name_to_mark, skip(skip_reason)(test_to_mark))
392+
else:
393+
setattr(test_frame, name_to_mark, expectedFailure(test_to_mark))
382394

383395
def sql_table_creation_suffix(self):
384396
"""

tests/backends/base/test_creation.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import copy
22
import datetime
33
import os
4+
import sys
45
from unittest import mock
56

67
from django.db import DEFAULT_DB_ALIAS, connection, connections
@@ -333,6 +334,10 @@ def test_mark_expected_failures_and_skips(self):
333334
"backends.base.test_creation.skip_test_function",
334335
},
335336
}
337+
# Emulate the scenario where the parent module for
338+
# backends.base.test_creation has not been imported yet.
339+
popped_module = sys.modules.pop("backends.base")
340+
self.addCleanup(sys.modules.__setitem__, "backends.base", popped_module)
336341
creation.mark_expected_failures_and_skips()
337342
self.assertIs(
338343
expected_failure_test_function.__unittest_expecting_failure__,

0 commit comments

Comments
 (0)