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