Commit 2051c909 by David Ormsbee

Test Speedup: Isolate Modulestore Signals

There are a number of Django Signals that are on the modulestore's
SignalHandler class, such as SignalHandler.course_published. These
signals can trigger very expensive processes to occur, such as course
overview or block structures generation. Most of the time, the test
author doesn't care about these side-effects.

This commit does a few things:

* Converts the signals on SignalHandler to be instances of a new
  SwitchedSignal class, that allows signal sending to be disabled.

* Creates a SignalIsolationMixin helper similar in spirit to the
  CacheIsolationMixin, and adds it to the ModuleStoreIsolationMixin
  (and thus to ModuleStoreTestCase and SharedModuleStoreTestCase).

* Converts our various tests to use this new mechanism. In some cases,
  this means adjusting query counts downwards because they no longer
  have to account for publishing listener actions.

Modulestore generated signals are now muted by default during test runs.
Calls to send() them will result in no-ops. You can choose to enable
specific signals for a given subclass of ModuleStoreTestCase or
SharedModuleStoreTestCase by specifying an ENABLED_SIGNALS class
attribute, like the following example:

    from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase

    class MyPublishTestCase(ModuleStoreTestCase):
        ENABLED_SIGNALS = ['course_published', 'pre_publish']

You should take great care when disabling signals outside of a
ModuleStoreTestCase or SharedModuleStoreTestCase, since they can leak
out into other tests. Be sure to always clean up, and never disable
signals outside of testing. Because signals are essentially process
globals, it can have a lot of unpleasant side-effects if we start
mucking around with them during live requests.

Overall, this change has cut the total test execution time for
edx-platform by a bit over a third, though we still spend a lot in
pre-test setup during our test builds.

[PERF-413]
parent 3cfec280
......@@ -13,6 +13,8 @@ class TestHandleItemDeleted(ModuleStoreTestCase, MilestonesTestCaseMixin):
"""
Test case for handle_score_changed django signal handler
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
"""
Initial data setup
......
......@@ -181,13 +181,13 @@ class ContentStoreImportTest(SignalDisconnectTestMixin, ModuleStoreTestCase):
# we try to refresh the inheritance tree for each update_item in the import
with check_exact_number_of_calls(store, 'refresh_cached_metadata_inheritance_tree', 28):
# _get_cached_metadata_inheritance_tree should be called twice (once for import, once on publish)
with check_exact_number_of_calls(store, '_get_cached_metadata_inheritance_tree', 2):
# _get_cached_metadata_inheritance_tree should be called once
with check_exact_number_of_calls(store, '_get_cached_metadata_inheritance_tree', 1):
# with bulk-edit in progress, the inheritance tree should be recomputed only at the end of the import
# NOTE: On Jenkins, with memcache enabled, the number of calls here is only 1.
# Locally, without memcache, the number of calls is actually 2 (once more during the publish step)
with check_number_of_calls(store, '_compute_metadata_inheritance_tree', 2):
# NOTE: On Jenkins, with memcache enabled, the number of calls here is 1.
# Locally, without memcache, the number of calls is 1 (publish no longer counted)
with check_number_of_calls(store, '_compute_metadata_inheritance_tree', 1):
self.load_test_import_course(create_if_not_present=False, module_store=store)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
......
......@@ -100,8 +100,8 @@ class TestOrphan(TestOrphanBase):
self.assertIn(unicode(location), orphans)
@ddt.data(
(ModuleStoreEnum.Type.split, 9, 6),
(ModuleStoreEnum.Type.mongo, 34, 13),
(ModuleStoreEnum.Type.split, 9, 5),
(ModuleStoreEnum.Type.mongo, 34, 12),
)
@ddt.unpack
def test_delete_orphans(self, default_store, max_mongo_calls, min_mongo_calls):
......
......@@ -331,6 +331,8 @@ class TestCourseOutline(CourseTestCase):
"""
Unit tests for the course outline.
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
"""
Set up the for the course outline tests.
......@@ -579,6 +581,8 @@ class TestCourseReIndex(CourseTestCase):
"""
SUCCESSFUL_RESPONSE = _("Course has been successfully reindexed.")
ENABLED_SIGNALS = ['course_published']
def setUp(self):
"""
Set up the for the course outline tests.
......
......@@ -17,6 +17,7 @@ class TestSubsectionGating(CourseTestCase):
Tests for the subsection gating feature
"""
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
ENABLED_SIGNALS = ['item_deleted']
def setUp(self):
"""
......
......@@ -142,6 +142,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
OTHER_EMAIL = "jane@example.com"
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
ENABLED_SIGNALS = ['course_published']
def setUp(self):
""" Create a course and user, then log in. """
......
......@@ -29,6 +29,8 @@ class TestCourseListing(ModuleStoreTestCase, MilestonesTestCaseMixin):
"""
Unit tests for getting the list of courses for a logged in user
"""
ENABLED_SIGNALS = ['course_deleted']
def setUp(self):
"""
Add a student & teacher
......
......@@ -229,6 +229,7 @@ class DashboardTest(ModuleStoreTestCase):
"""
Tests for dashboard utility functions
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(DashboardTest, self).setUp()
......@@ -434,10 +435,11 @@ class DashboardTest(ModuleStoreTestCase):
# If user has a certificate with valid linked-in config then Add Certificate to LinkedIn button
# should be visible. and it has URL value with valid parameters.
self.client.login(username="jack", password="test")
LinkedInAddToProfileConfiguration(
LinkedInAddToProfileConfiguration.objects.create(
company_identifier='0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9',
enabled=True
).save()
)
CourseModeFactory.create(
course_id=self.course.id,
......@@ -474,6 +476,7 @@ class DashboardTest(ModuleStoreTestCase):
u'pfCertificationUrl=www.edx.org&'
u'source=o'
).format(platform=quote(settings.PLATFORM_NAME.encode('utf-8')))
self.assertContains(response, escape(expected_url))
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
......
......@@ -57,6 +57,86 @@ log = logging.getLogger(__name__)
ASSET_IGNORE_REGEX = getattr(settings, "ASSET_IGNORE_REGEX", r"(^\._.*$)|(^\.DS_Store$)|(^.*~$)")
class SwitchedSignal(django.dispatch.Signal):
"""
SwitchedSignal is like a normal Django signal, except that you can turn it
on and off. This is especially useful for tests where we want to be able to
isolate signals and disable expensive operations that are irrelevant to
what's being tested (like everything that triggers off of a course publish).
SwitchedSignals default to being on. You should be very careful if you ever
turn one off -- the only instances of this class are shared class attributes
of `SignalHandler`. You have to make sure that you re-enable the signal when
you're done, or else you may permanently turn that signal off for that
process. I can't think of any reason you'd want to disable signals outside
of running tests.
"""
def __init__(self, name, *args, **kwargs):
"""
The `name` parameter exists only to make debugging more convenient.
All other args are passed to the constructor for django.dispatch.Signal.
"""
super(SwitchedSignal, self).__init__(*args, **kwargs)
self.name = name
self._allow_signals = True
def disable(self):
"""
Turn off signal sending.
All calls to send/send_robust will no-op.
"""
self._allow_signals = False
def enable(self):
"""
Turn on signal sending.
Calls to send/send_robust will behave like normal Django Signals.
"""
self._allow_signals = True
def send(self, *args, **kwargs):
"""
See `django.dispatch.Signal.send()`
This method will no-op and return an empty list if the signal has been
disabled.
"""
log.debug(
"SwitchedSignal %s's send() called with args %s, kwargs %s - %s",
self.name,
args,
kwargs,
"ALLOW" if self._allow_signals else "BLOCK"
)
if self._allow_signals:
return super(SwitchedSignal, self).send(*args, **kwargs)
return []
def send_robust(self, *args, **kwargs):
"""
See `django.dispatch.Signal.send_robust()`
This method will no-op and return an empty list if the signal has been
disabled.
"""
log.debug(
"SwitchedSignal %s's send_robust() called with args %s, kwargs %s - %s",
self.name,
args,
kwargs,
"ALLOW" if self._allow_signals else "BLOCK"
)
if self._allow_signals:
return super(SwitchedSignal, self).send_robust(*args, **kwargs)
return []
def __repr__(self):
return u"SwitchedSignal('{}')".format(self.name)
class SignalHandler(object):
"""
This class is to allow the modulestores to emit signals that can be caught
......@@ -88,23 +168,34 @@ class SignalHandler(object):
almost no work. Its main job is to kick off the celery task that will
do the actual work.
"""
pre_publish = django.dispatch.Signal(providing_args=["course_key"])
course_published = django.dispatch.Signal(providing_args=["course_key"])
course_deleted = django.dispatch.Signal(providing_args=["course_key"])
library_updated = django.dispatch.Signal(providing_args=["library_key"])
item_deleted = django.dispatch.Signal(providing_args=["usage_key", "user_id"])
# If you add a new signal, please don't forget to add it to the _mapping
# as well.
pre_publish = SwitchedSignal("pre_publish", providing_args=["course_key"])
course_published = SwitchedSignal("course_published", providing_args=["course_key"])
course_deleted = SwitchedSignal("course_deleted", providing_args=["course_key"])
library_updated = SwitchedSignal("library_updated", providing_args=["library_key"])
item_deleted = SwitchedSignal("item_deleted", providing_args=["usage_key", "user_id"])
_mapping = {
"pre_publish": pre_publish,
"course_published": course_published,
"course_deleted": course_deleted,
"library_updated": library_updated,
"item_deleted": item_deleted,
signal.name: signal
for signal
in [pre_publish, course_published, course_deleted, library_updated, item_deleted]
}
def __init__(self, modulestore_class):
self.modulestore_class = modulestore_class
@classmethod
def all_signals(cls):
"""Return a list with all our signals in it."""
return cls._mapping.values()
@classmethod
def signal_by_name(cls, signal_name):
"""Given a signal name, return the appropriate signal."""
return cls._mapping[signal_name]
def send(self, signal_name, **kwargs):
"""
Send the signal to the receivers.
......
......@@ -194,7 +194,46 @@ TEST_DATA_SPLIT_MODULESTORE = functools.partial(
)
class ModuleStoreIsolationMixin(CacheIsolationMixin):
class SignalIsolationMixin(object):
"""
Simple utility mixin class to toggle ModuleStore signals on and off. This
class operates on `SwitchedSignal` objects on the modulestore's
`SignalHandler`.
"""
@classmethod
def disable_all_signals(cls):
"""Disable all signals in the modulestore's SignalHandler."""
for signal in SignalHandler.all_signals():
signal.disable()
@classmethod
def enable_all_signals(cls):
"""Enable all signals in the modulestore's SignalHandler."""
for signal in SignalHandler.all_signals():
signal.enable()
@classmethod
def enable_signals_by_name(cls, *signal_names):
"""
Enable specific signals in the modulestore's SignalHandler.
Arguments:
signal_names (list of `str`): Names of signals to enable.
"""
for signal_name in signal_names:
try:
signal = SignalHandler.signal_by_name(signal_name)
except KeyError:
all_signal_names = sorted(s.name for s in SignalHandler.all_signals())
err_msg = (
"You tried to enable signal '{}', but I don't recognize that "
"signal name. Did you mean one of these?: {}"
)
raise ValueError(err_msg.format(signal_name, all_signal_names))
signal.enable()
class ModuleStoreIsolationMixin(CacheIsolationMixin, SignalIsolationMixin):
"""
A mixin to be used by TestCases that want to isolate their use of the
Modulestore.
......@@ -205,6 +244,8 @@ class ModuleStoreIsolationMixin(CacheIsolationMixin):
MODULESTORE = <settings for the modulestore to test>
ENABLED_SIGNALS = ['course_published']
def my_test(self):
self.start_modulestore_isolation()
self.addCleanup(self.end_modulestore_isolation)
......@@ -213,10 +254,21 @@ class ModuleStoreIsolationMixin(CacheIsolationMixin):
...
"""
MODULESTORE = functools.partial(mixed_store_config, mkdtemp_clean(), {})
CONTENTSTORE = functools.partial(contentstore_config)
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
# List of modulestore signals enabled for this test. Defaults to an empty
# list. The list of signals available is found on the SignalHandler class,
# in /common/lib/xmodule/xmodule/modulestore/django.py
#
# You must use the signal itself, and not its name. So for example:
#
# class MyPublishTestCase(ModuleStoreTestCase):
# ENABLED_SIGNALS = ['course_published', 'pre_publish']
#
ENABLED_SIGNALS = []
__settings_overrides = []
__old_modulestores = []
__old_contentstores = []
......@@ -228,6 +280,8 @@ class ModuleStoreIsolationMixin(CacheIsolationMixin):
:py:meth:`end_modulestore_isolation` is called, this modulestore will
be flushed (all content will be deleted).
"""
cls.disable_all_signals()
cls.enable_signals_by_name(*cls.ENABLED_SIGNALS)
cls.start_cache_isolation()
override = override_settings(
MODULESTORE=cls.MODULESTORE(),
......@@ -256,6 +310,7 @@ class ModuleStoreIsolationMixin(CacheIsolationMixin):
assert settings.MODULESTORE == cls.__old_modulestores.pop()
assert settings.CONTENTSTORE == cls.__old_contentstores.pop()
cls.end_cache_isolation()
cls.enable_all_signals()
class SharedModuleStoreTestCase(ModuleStoreIsolationMixin, CacheIsolationTestCase):
......@@ -384,6 +439,14 @@ class ModuleStoreTestCase(ModuleStoreIsolationMixin, TestCase):
# Tell Django to clean out all databases, not just default
multi_db = True
@classmethod
def setUpClass(cls):
super(ModuleStoreTestCase, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(ModuleStoreTestCase, cls).tearDownClass()
def setUp(self):
"""
Creates a test User if `self.CREATE_USER` is True.
......@@ -400,8 +463,6 @@ class ModuleStoreTestCase(ModuleStoreIsolationMixin, TestCase):
super(ModuleStoreTestCase, self).setUp()
SignalHandler.course_published.disconnect(trigger_update_xblocks_cache_task)
self.store = modulestore()
uname = 'testuser'
......
......@@ -117,6 +117,8 @@ class PreRequisiteCourseCatalog(ModuleStoreTestCase, LoginEnrollmentTestCase, Mi
Test to simulate and verify fix for disappearing courses in
course catalog when using pre-requisite courses
"""
ENABLED_SIGNALS = ['course_published']
@patch.dict(settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True})
def test_course_with_prereq(self):
"""
......@@ -160,6 +162,8 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
"""
Test for Index page course cards sorting
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(IndexPageCourseCardsSortingTests, self).setUp()
self.starting_later = CourseFactory.create(
......
......@@ -52,7 +52,6 @@ from student.roles import (
)
from student.tests.factories import AdminFactory
USER_PASSWORD = 'test'
AUTH_ATTRS = ('auth', 'auth_header_oauth2_provider')
......@@ -183,6 +182,8 @@ class CcxListTest(CcxRestApiTest):
"""
Test for the CCX REST APIs
"""
ENABLED_SIGNALS = ['course_published']
@classmethod
def setUpClass(cls):
super(CcxListTest, cls).setUpClass()
......@@ -892,6 +893,8 @@ class CcxDetailTest(CcxRestApiTest):
"""
Test for the CCX REST APIs
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
"""
Set up tests
......
......@@ -25,9 +25,10 @@ from lms.djangoapps.ccx.tasks import send_ccx_course_published
class TestSendCCXCoursePublished(ModuleStoreTestCase):
"""unit tests for the send ccx course published task
"""
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
ENABLED_SIGNALS = ['course_published']
def setUp(self):
"""
Set up tests
......
......@@ -75,6 +75,8 @@ class TestGetCourseChapters(CcxTestCase):
"""
Tests for the `get_course_chapters` util function
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
"""
Set up tests
......
......@@ -88,6 +88,7 @@ class WebCertificateTestMixin(object):
@attr(shard=1)
class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTestCase):
"""Tests for the `certificate_downloadable_status` helper function. """
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(CertificateDownloadableStatusTests, self).setUp()
......@@ -457,6 +458,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
"""Tests for generating certificates for students. """
ERROR_REASON = "Kaboom!"
ENABLED_SIGNALS = ['course_published']
def setUp(self): # pylint: disable=arguments-differ
super(GenerateUserCertificatesTest, self).setUp('certificates.api.tracker')
......
......@@ -69,6 +69,7 @@ class CertificateManagementTest(ModuleStoreTestCase):
@ddt.ddt
class ResubmitErrorCertificatesTest(CertificateManagementTest):
"""Tests for the resubmit_error_certificates management command. """
ENABLED_SIGNALS = ['course_published']
@ddt.data(CourseMode.HONOR, CourseMode.VERIFIED)
def test_resubmit_error_certificate(self, mode):
......
......@@ -105,6 +105,8 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
"""
Tests query counts for the get_blocks function.
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(TestGetBlocksQueryCounts, self).setUp()
......
......@@ -38,6 +38,8 @@ class CourseDetailTestMixin(CourseApiTestMixin):
"""
Common functionality for course_detail tests
"""
ENABLED_SIGNALS = ['course_published']
def _make_api_call(self, requesting_user, target_user, course_key):
"""
Call the `course_detail` api endpoint to get information on the course
......@@ -109,6 +111,7 @@ class TestGetCourseList(CourseListTestMixin, SharedModuleStoreTestCase):
"""
Test the behavior of the `list_courses` api function.
"""
ENABLED_SIGNALS = ['course_published']
@classmethod
def setUpClass(cls):
......@@ -150,6 +153,7 @@ class TestGetCourseListMultipleCourses(CourseListTestMixin, ModuleStoreTestCase)
Test the behavior of the `list_courses` api function (with tests that
modify the courseware).
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(TestGetCourseListMultipleCourses, self).setUp()
......@@ -207,6 +211,8 @@ class TestGetCourseListExtras(CourseListTestMixin, ModuleStoreTestCase):
Tests of course_list api function that require alternative configurations
of created courses.
"""
ENABLED_SIGNALS = ['course_published']
@classmethod
def setUpClass(cls):
super(TestGetCourseListExtras, cls).setUpClass()
......
......@@ -31,6 +31,8 @@ class TestCourseSerializer(CourseApiFactoryMixin, ModuleStoreTestCase):
maxDiff = 5000 # long enough to show mismatched dicts, in case of error
serializer_class = CourseSerializer
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(TestCourseSerializer, self).setUp()
self.staff_user = self.create_user('staff', is_staff=True)
......
......@@ -101,6 +101,7 @@ class CourseListViewTestCaseMultipleCourses(CourseApiTestViewMixin, ModuleStoreT
Test responses returned from CourseListView (with tests that modify the
courseware).
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(CourseListViewTestCaseMultipleCourses, self).setUp()
......
......@@ -38,6 +38,7 @@ class CommandsTestBase(SharedModuleStoreTestCase):
"""
__test__ = False
url_name = '2012_Fall'
ENABLED_SIGNALS = ['course_published']
@classmethod
def setUpClass(cls):
......
......@@ -47,6 +47,7 @@ TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
@ddt.ddt
class CoursesTest(ModuleStoreTestCase):
"""Test methods related to fetching courses."""
ENABLED_SIGNALS = ['course_published']
@override_settings(CMS_BASE=CMS_BASE_TEST)
def test_get_cms_course_block_link(self):
......
......@@ -58,81 +58,82 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase, Milest
'entrance_exam_enabled': True,
}
)
self.chapter = ItemFactory.create(
parent=self.course,
display_name='Overview'
)
self.welcome = ItemFactory.create(
parent=self.chapter,
display_name='Welcome'
)
ItemFactory.create(
parent=self.course,
category='chapter',
display_name="Week 1"
)
self.chapter_subsection = ItemFactory.create(
parent=self.chapter,
category='sequential',
display_name="Lesson 1"
)
chapter_vertical = ItemFactory.create(
parent=self.chapter_subsection,
category='vertical',
display_name='Lesson 1 Vertical - Unit 1'
)
ItemFactory.create(
parent=chapter_vertical,
category="problem",
display_name="Problem - Unit 1 Problem 1"
)
ItemFactory.create(
parent=chapter_vertical,
category="problem",
display_name="Problem - Unit 1 Problem 2"
)
ItemFactory.create(
category="instructor",
parent=self.course,
data="Instructor Tab",
display_name="Instructor"
)
self.entrance_exam = ItemFactory.create(
parent=self.course,
category="chapter",
display_name="Entrance Exam Section - Chapter 1",
is_entrance_exam=True,
in_entrance_exam=True
)
self.exam_1 = ItemFactory.create(
parent=self.entrance_exam,
category='sequential',
display_name="Exam Sequential - Subsection 1",
graded=True,
in_entrance_exam=True
)
subsection = ItemFactory.create(
parent=self.exam_1,
category='vertical',
display_name='Exam Vertical - Unit 1'
)
problem_xml = MultipleChoiceResponseXMLFactory().build_xml(
question_text='The correct answer is Choice 3',
choices=[False, False, True, False],
choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3']
)
self.problem_1 = ItemFactory.create(
parent=subsection,
category="problem",
display_name="Exam Problem - Problem 1",
data=problem_xml
)
self.problem_2 = ItemFactory.create(
parent=subsection,
category="problem",
display_name="Exam Problem - Problem 2"
)
with self.store.bulk_operations(self.course.id):
self.chapter = ItemFactory.create(
parent=self.course,
display_name='Overview'
)
self.welcome = ItemFactory.create(
parent=self.chapter,
display_name='Welcome'
)
ItemFactory.create(
parent=self.course,
category='chapter',
display_name="Week 1"
)
self.chapter_subsection = ItemFactory.create(
parent=self.chapter,
category='sequential',
display_name="Lesson 1"
)
chapter_vertical = ItemFactory.create(
parent=self.chapter_subsection,
category='vertical',
display_name='Lesson 1 Vertical - Unit 1'
)
ItemFactory.create(
parent=chapter_vertical,
category="problem",
display_name="Problem - Unit 1 Problem 1"
)
ItemFactory.create(
parent=chapter_vertical,
category="problem",
display_name="Problem - Unit 1 Problem 2"
)
ItemFactory.create(
category="instructor",
parent=self.course,
data="Instructor Tab",
display_name="Instructor"
)
self.entrance_exam = ItemFactory.create(
parent=self.course,
category="chapter",
display_name="Entrance Exam Section - Chapter 1",
is_entrance_exam=True,
in_entrance_exam=True
)
self.exam_1 = ItemFactory.create(
parent=self.entrance_exam,
category='sequential',
display_name="Exam Sequential - Subsection 1",
graded=True,
in_entrance_exam=True
)
subsection = ItemFactory.create(
parent=self.exam_1,
category='vertical',
display_name='Exam Vertical - Unit 1'
)
problem_xml = MultipleChoiceResponseXMLFactory().build_xml(
question_text='The correct answer is Choice 3',
choices=[False, False, True, False],
choice_names=['choice_0', 'choice_1', 'choice_2', 'choice_3']
)
self.problem_1 = ItemFactory.create(
parent=subsection,
category="problem",
display_name="Exam Problem - Problem 1",
data=problem_xml
)
self.problem_2 = ItemFactory.create(
parent=subsection,
category="problem",
display_name="Exam Problem - Problem 2"
)
add_entrance_exam_milestone(self.course, self.entrance_exam)
......@@ -295,7 +296,7 @@ class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase, Milest
"""
# One query is for getting the list of disabled XBlocks (which is
# then stored in the request).
with self.assertNumQueries(2):
with self.assertNumQueries(1):
exam_score = get_entrance_exam_score(self.request, self.course)
self.assertEqual(exam_score, 0)
......
......@@ -21,6 +21,7 @@ class TestSites(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
"""
STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')]
ENABLED_SIGNALS = ['course_published']
@classmethod
def setUpClass(cls):
......
......@@ -1138,6 +1138,7 @@ class ProgressPageTests(ModuleStoreTestCase):
"""
ENABLED_CACHES = ['default', 'mongo_modulestore_inheritance', 'loc_cache']
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(ProgressPageTests, self).setUp()
......@@ -2032,7 +2033,6 @@ class TestRenderXBlock(RenderXBlockTestMixin, ModuleStoreTestCase):
This class overrides the get_response method, which is used by
the tests defined in RenderXBlockTestMixin.
"""
def setUp(self):
reload_django_url_config()
super(TestRenderXBlock, self).setUp()
......@@ -2060,7 +2060,6 @@ class TestRenderXBlockSelfPaced(TestRenderXBlock):
Test rendering XBlocks for a self-paced course. Relies on the query
count assertions in the tests defined by RenderXBlockMixin.
"""
def setUp(self):
super(TestRenderXBlockSelfPaced, self).setUp()
SelfPacedConfiguration(enabled=True).save()
......
......@@ -145,9 +145,9 @@ class RenderXBlockTestMixin(object):
return response
@ddt.data(
('vertical_block', ModuleStoreEnum.Type.mongo, 11),
('vertical_block', ModuleStoreEnum.Type.mongo, 10),
('vertical_block', ModuleStoreEnum.Type.split, 6),
('html_block', ModuleStoreEnum.Type.mongo, 12),
('html_block', ModuleStoreEnum.Type.mongo, 11),
('html_block', ModuleStoreEnum.Type.split, 6),
)
@ddt.unpack
......
......@@ -356,6 +356,7 @@ class ViewsQueryCountTestCase(
CREATE_USER = False
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
ENABLED_SIGNALS = ['course_published']
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self):
......
......@@ -210,6 +210,8 @@ class CachedDiscussionIdMapTestCase(ModuleStoreTestCase):
"""
Tests that using the cache of discussion id mappings has the same behavior as searching through the course.
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(CachedDiscussionIdMapTestCase, self).setUp()
......
......@@ -22,6 +22,8 @@ class GradesAccessIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreT
"""
Tests integration between grading and block access.
"""
ENABLED_SIGNALS = ['course_published']
@classmethod
def setUpClass(cls):
super(GradesAccessIntegrationTest, cls).setUpClass()
......
......@@ -26,6 +26,8 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe
Tests integration between the eventing in various layers
of the grading infrastructure.
"""
ENABLED_SIGNALS = ['course_published']
@classmethod
def reset_course(cls):
"""
......
......@@ -37,6 +37,8 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
"""
Ensures that the recalculate subsection grade task functions as expected when run.
"""
ENABLED_SIGNALS = ['course_published', 'pre_publish']
def setUp(self):
super(RecalculateSubsectionGradeTest, self).setUp()
self.user = UserFactory()
......
......@@ -29,6 +29,8 @@ class GradesTransformerTestCase(CourseStructureTestCase):
TRANSFORMER_CLASS_TO_TEST = GradesTransformer
ENABLED_SIGNALS = ['course_published']
problem_metadata = {
u'graded': True,
u'weight': 1,
......
......@@ -412,6 +412,6 @@ class TestInstructorDashboardPerformance(ModuleStoreTestCase, LoginEnrollmentTes
# check MongoDB calls count
url = reverse('spoc_gradebook', kwargs={'course_id': self.course.id})
with check_mongo_calls(7):
with check_mongo_calls(9):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
......@@ -87,6 +87,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
NEXT_WEEK = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=7)
LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7)
ADVERTISED_START = "Spring 2016"
ENABLED_SIGNALS = ['course_published']
@patch.dict(settings.FEATURES, {"ENABLE_DISCUSSION_SERVICE": True})
def setUp(self, *args, **kwargs):
......@@ -468,6 +469,8 @@ class TestCourseEnrollmentSerializer(MobileAPITestCase, MilestonesTestCaseMixin)
"""
Test the course enrollment serializer
"""
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(TestCourseEnrollmentSerializer, self).setUp()
self.login_and_enroll()
......
......@@ -2,15 +2,15 @@
from django.core.cache import cache
from django.test.utils import override_settings
from xmodule.modulestore.tests.factories import (check_mongo_calls, CourseFactory)
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from student.models import anonymous_id_for_user
from student.models import UserProfile
from student.roles import (CourseInstructorRole, CourseStaffRole, GlobalStaff,
OrgInstructorRole, OrgStaffRole)
from student.tests.factories import UserFactory, UserProfileFactory
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import (check_mongo_calls, CourseFactory)
# Will also run default tests for IDTokens and UserInfo
......@@ -19,6 +19,7 @@ from edx_oauth2_provider.tests import IDTokenTestCase, UserInfoTestCase
class BaseTestMixin(ModuleStoreTestCase):
profile = None
ENABLED_SIGNALS = ['course_published']
def setUp(self):
super(BaseTestMixin, self).setUp()
......
......@@ -43,7 +43,6 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
"""
These tests cover the parts of the API methods.
"""
def setUp(self):
super(BookmarksAPITests, self).setUp()
......@@ -123,7 +122,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
"""
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
with self.assertNumQueries(10):
with self.assertNumQueries(9):
bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_2.location)
self.assert_bookmark_event_emitted(
......@@ -144,7 +143,7 @@ class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
"""
self.assertEqual(len(api.get_bookmarks(user=self.user, course_key=self.course.id)), 2)
with self.assertNumQueries(10):
with self.assertNumQueries(9):
bookmark_data = api.create_bookmark(user=self.user, usage_key=self.vertical_2.location)
self.assert_bookmark_event_emitted(
......
......@@ -253,10 +253,10 @@ class BookmarkModelTests(BookmarksTestsBase):
@ddt.data(
(ModuleStoreEnum.Type.mongo, 'course', [], 3),
(ModuleStoreEnum.Type.mongo, 'chapter_1', [], 4),
(ModuleStoreEnum.Type.mongo, 'sequential_1', ['chapter_1'], 6),
(ModuleStoreEnum.Type.mongo, 'vertical_1', ['chapter_1', 'sequential_1'], 8),
(ModuleStoreEnum.Type.mongo, 'html_1', ['chapter_1', 'sequential_2', 'vertical_2'], 10),
(ModuleStoreEnum.Type.mongo, 'chapter_1', [], 3),
(ModuleStoreEnum.Type.mongo, 'sequential_1', ['chapter_1'], 4),
(ModuleStoreEnum.Type.mongo, 'vertical_1', ['chapter_1', 'sequential_1'], 6),
(ModuleStoreEnum.Type.mongo, 'html_1', ['chapter_1', 'sequential_2', 'vertical_2'], 7),
(ModuleStoreEnum.Type.split, 'course', [], 3),
(ModuleStoreEnum.Type.split, 'chapter_1', [], 2),
(ModuleStoreEnum.Type.split, 'sequential_1', ['chapter_1'], 2),
......
......@@ -68,7 +68,7 @@ class BookmarksServiceTests(BookmarksTestsBase):
self.bookmark_service.set_bookmarked(usage_key=UsageKey.from_string("i4x://ed/ed/ed/interactive"))
)
with self.assertNumQueries(10):
with self.assertNumQueries(9):
self.assertTrue(self.bookmark_service.set_bookmarked(usage_key=self.vertical_2.location))
def test_unset_bookmarked(self):
......
......@@ -20,6 +20,7 @@ class XBlockCacheTaskTests(BookmarksTestsBase):
"""
Test the XBlockCache model.
"""
def setUp(self):
super(XBlockCacheTaskTests, self).setUp()
......@@ -157,12 +158,6 @@ class XBlockCacheTaskTests(BookmarksTestsBase):
"""
course = getattr(self, course_attr)
if settings.ROOT_URLCONF == 'lms.urls':
# When the tests run under LMS, there is a certificates course_published
# signal handler that runs and causes the number of queries to be one more
# (due to the check for disabled_xblocks in django.py).
expected_sql_queries += 1
with self.assertNumQueries(expected_sql_queries):
_update_xblocks_cache(course.id)
......
......@@ -19,6 +19,8 @@ class CourseBlocksSignalTest(ModuleStoreTestCase):
"""
Tests for the Course Blocks signal
"""
ENABLED_SIGNALS = ['course_deleted', 'course_published']
def setUp(self):
super(CourseBlocksSignalTest, self).setUp()
self.course = CourseFactory.create()
......
......@@ -52,6 +52,8 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
COURSE_OVERVIEW_TABS = {'courseware', 'info', 'textbooks', 'discussion', 'wiki', 'progress'}
ENABLED_SIGNALS = ['course_deleted', 'course_published']
def check_course_overview_against_course(self, course):
"""
Compares a CourseOverview object against its corresponding
......
......@@ -18,6 +18,8 @@ class CourseStructureApiTests(ModuleStoreTestCase):
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
ENABLED_SIGNALS = ['course_published']
def setUp(self):
"""
Test setup
......
......@@ -263,8 +263,8 @@ class TestCohorts(ModuleStoreTestCase):
)
@ddt.data(
(True, 3),
(False, 7),
(True, 2),
(False, 6),
)
@ddt.unpack
def test_get_cohort_sql_queries(self, use_cached, num_sql_queries):
......
......@@ -240,6 +240,8 @@ class WriteOnPublishTest(ModuleStoreTestCase):
"""
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
ENABLED_SIGNALS = ['course_published', 'pre_publish']
@patch.dict(settings.FEATURES, {"ENABLE_COURSEWARE_INDEX": False})
def setUp(self):
super(WriteOnPublishTest, self).setUp()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment