diff --git a/cms/djangoapps/contentstore/tests/test_gating.py b/cms/djangoapps/contentstore/tests/test_gating.py index beacd7c240..6c46e092d0 100644 --- a/cms/djangoapps/contentstore/tests/test_gating.py +++ b/cms/djangoapps/contentstore/tests/test_gating.py @@ -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 diff --git a/cms/djangoapps/contentstore/tests/test_import.py b/cms/djangoapps/contentstore/tests/test_import.py index f845df6e61..e6c3e152f2 100644 --- a/cms/djangoapps/contentstore/tests/test_import.py +++ b/cms/djangoapps/contentstore/tests/test_import.py @@ -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) diff --git a/cms/djangoapps/contentstore/tests/test_orphan.py b/cms/djangoapps/contentstore/tests/test_orphan.py index 5d089bdd8f..f48d47aa50 100644 --- a/cms/djangoapps/contentstore/tests/test_orphan.py +++ b/cms/djangoapps/contentstore/tests/test_orphan.py @@ -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): diff --git a/cms/djangoapps/contentstore/views/tests/test_course_index.py b/cms/djangoapps/contentstore/views/tests/test_course_index.py index 337335654a..c38daa6020 100644 --- a/cms/djangoapps/contentstore/views/tests/test_course_index.py +++ b/cms/djangoapps/contentstore/views/tests/test_course_index.py @@ -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. diff --git a/cms/djangoapps/contentstore/views/tests/test_gating.py b/cms/djangoapps/contentstore/views/tests/test_gating.py index 5ef6438ba2..88f5974c53 100644 --- a/cms/djangoapps/contentstore/views/tests/test_gating.py +++ b/cms/djangoapps/contentstore/views/tests/test_gating.py @@ -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): """ diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index 88bc30c398..44ea7dcc8f 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -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. """ diff --git a/common/djangoapps/student/tests/test_course_listing.py b/common/djangoapps/student/tests/test_course_listing.py index 1a06f7e039..b9c8782ce2 100644 --- a/common/djangoapps/student/tests/test_course_listing.py +++ b/common/djangoapps/student/tests/test_course_listing.py @@ -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 diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 90da0735b9..b2ff11fcd8 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -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') diff --git a/common/lib/xmodule/xmodule/modulestore/django.py b/common/lib/xmodule/xmodule/modulestore/django.py index e6179f133a..094890c194 100644 --- a/common/lib/xmodule/xmodule/modulestore/django.py +++ b/common/lib/xmodule/xmodule/modulestore/django.py @@ -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. diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index c80317d4d7..870e342c6d 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -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 = + 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' diff --git a/lms/djangoapps/branding/tests/test_page.py b/lms/djangoapps/branding/tests/test_page.py index 89b109c50a..9db971d3b8 100644 --- a/lms/djangoapps/branding/tests/test_page.py +++ b/lms/djangoapps/branding/tests/test_page.py @@ -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( diff --git a/lms/djangoapps/ccx/api/v0/tests/test_views.py b/lms/djangoapps/ccx/api/v0/tests/test_views.py index 47f3da1d31..3060d8eb15 100644 --- a/lms/djangoapps/ccx/api/v0/tests/test_views.py +++ b/lms/djangoapps/ccx/api/v0/tests/test_views.py @@ -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 diff --git a/lms/djangoapps/ccx/tests/test_tasks.py b/lms/djangoapps/ccx/tests/test_tasks.py index b3a45387c0..95e3030873 100644 --- a/lms/djangoapps/ccx/tests/test_tasks.py +++ b/lms/djangoapps/ccx/tests/test_tasks.py @@ -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 diff --git a/lms/djangoapps/ccx/tests/test_utils.py b/lms/djangoapps/ccx/tests/test_utils.py index 50af4df918..21ca542fea 100644 --- a/lms/djangoapps/ccx/tests/test_utils.py +++ b/lms/djangoapps/ccx/tests/test_utils.py @@ -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 diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index 81843cdd82..c1bfb50ac5 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -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') diff --git a/lms/djangoapps/certificates/tests/test_cert_management.py b/lms/djangoapps/certificates/tests/test_cert_management.py index 1a119d75bd..3a8849d1ab 100644 --- a/lms/djangoapps/certificates/tests/test_cert_management.py +++ b/lms/djangoapps/certificates/tests/test_cert_management.py @@ -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): diff --git a/lms/djangoapps/course_api/blocks/tests/test_api.py b/lms/djangoapps/course_api/blocks/tests/test_api.py index 6273edbf9b..c36531e59b 100644 --- a/lms/djangoapps/course_api/blocks/tests/test_api.py +++ b/lms/djangoapps/course_api/blocks/tests/test_api.py @@ -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() diff --git a/lms/djangoapps/course_api/tests/test_api.py b/lms/djangoapps/course_api/tests/test_api.py index d8e375f291..b8b7afd559 100644 --- a/lms/djangoapps/course_api/tests/test_api.py +++ b/lms/djangoapps/course_api/tests/test_api.py @@ -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() diff --git a/lms/djangoapps/course_api/tests/test_serializers.py b/lms/djangoapps/course_api/tests/test_serializers.py index 928a81e5cb..b1d1e76bb6 100644 --- a/lms/djangoapps/course_api/tests/test_serializers.py +++ b/lms/djangoapps/course_api/tests/test_serializers.py @@ -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) diff --git a/lms/djangoapps/course_api/tests/test_views.py b/lms/djangoapps/course_api/tests/test_views.py index bb88688349..6a357194ee 100644 --- a/lms/djangoapps/course_api/tests/test_views.py +++ b/lms/djangoapps/course_api/tests/test_views.py @@ -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() diff --git a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py index 40af376df2..aee2febcec 100644 --- a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py +++ b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py @@ -38,6 +38,7 @@ class CommandsTestBase(SharedModuleStoreTestCase): """ __test__ = False url_name = '2012_Fall' + ENABLED_SIGNALS = ['course_published'] @classmethod def setUpClass(cls): diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index 94c1a96c1d..db961ed503 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -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): diff --git a/lms/djangoapps/courseware/tests/test_entrance_exam.py b/lms/djangoapps/courseware/tests/test_entrance_exam.py index b59c225b1d..8282c37e2f 100644 --- a/lms/djangoapps/courseware/tests/test_entrance_exam.py +++ b/lms/djangoapps/courseware/tests/test_entrance_exam.py @@ -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" - ) + 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" - ) + 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) diff --git a/lms/djangoapps/courseware/tests/test_microsites.py b/lms/djangoapps/courseware/tests/test_microsites.py index 10c353841f..3c49d5da88 100644 --- a/lms/djangoapps/courseware/tests/test_microsites.py +++ b/lms/djangoapps/courseware/tests/test_microsites.py @@ -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): diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index f491be579d..e2d0d60804 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -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() diff --git a/lms/djangoapps/courseware/testutils.py b/lms/djangoapps/courseware/testutils.py index 43fde1eadf..1ef13ad45e 100644 --- a/lms/djangoapps/courseware/testutils.py +++ b/lms/djangoapps/courseware/testutils.py @@ -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 diff --git a/lms/djangoapps/django_comment_client/base/tests.py b/lms/djangoapps/django_comment_client/base/tests.py index 236c2e3b8d..720b249753 100644 --- a/lms/djangoapps/django_comment_client/base/tests.py +++ b/lms/djangoapps/django_comment_client/base/tests.py @@ -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): diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index ad23b133b5..f0abdca7c5 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -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() diff --git a/lms/djangoapps/grades/tests/integration/test_access.py b/lms/djangoapps/grades/tests/integration/test_access.py index cd7c8e2cbb..d73d94fd42 100644 --- a/lms/djangoapps/grades/tests/integration/test_access.py +++ b/lms/djangoapps/grades/tests/integration/test_access.py @@ -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() diff --git a/lms/djangoapps/grades/tests/integration/test_events.py b/lms/djangoapps/grades/tests/integration/test_events.py index b6ce701130..11c4f36a08 100644 --- a/lms/djangoapps/grades/tests/integration/test_events.py +++ b/lms/djangoapps/grades/tests/integration/test_events.py @@ -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): """ diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index 6a3d6a69b0..6d6dc57a6b 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -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() diff --git a/lms/djangoapps/grades/tests/test_transformer.py b/lms/djangoapps/grades/tests/test_transformer.py index 076fc58987..2863a64f13 100644 --- a/lms/djangoapps/grades/tests/test_transformer.py +++ b/lms/djangoapps/grades/tests/test_transformer.py @@ -29,6 +29,8 @@ class GradesTransformerTestCase(CourseStructureTestCase): TRANSFORMER_CLASS_TO_TEST = GradesTransformer + ENABLED_SIGNALS = ['course_published'] + problem_metadata = { u'graded': True, u'weight': 1, diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index 7fdf99fe9b..361fa7eaf4 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -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) diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index b283b5c484..ad58a7839f 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -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() diff --git a/lms/djangoapps/oauth2_handler/tests.py b/lms/djangoapps/oauth2_handler/tests.py index 96a7b3d4c8..878dadcb6a 100644 --- a/lms/djangoapps/oauth2_handler/tests.py +++ b/lms/djangoapps/oauth2_handler/tests.py @@ -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() diff --git a/openedx/core/djangoapps/bookmarks/tests/test_api.py b/openedx/core/djangoapps/bookmarks/tests/test_api.py index bb8705306b..0fb68f91c6 100644 --- a/openedx/core/djangoapps/bookmarks/tests/test_api.py +++ b/openedx/core/djangoapps/bookmarks/tests/test_api.py @@ -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( diff --git a/openedx/core/djangoapps/bookmarks/tests/test_models.py b/openedx/core/djangoapps/bookmarks/tests/test_models.py index cdc9ab3d26..163419b639 100644 --- a/openedx/core/djangoapps/bookmarks/tests/test_models.py +++ b/openedx/core/djangoapps/bookmarks/tests/test_models.py @@ -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), diff --git a/openedx/core/djangoapps/bookmarks/tests/test_services.py b/openedx/core/djangoapps/bookmarks/tests/test_services.py index 341803cbdf..da70d72abe 100644 --- a/openedx/core/djangoapps/bookmarks/tests/test_services.py +++ b/openedx/core/djangoapps/bookmarks/tests/test_services.py @@ -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): diff --git a/openedx/core/djangoapps/bookmarks/tests/test_tasks.py b/openedx/core/djangoapps/bookmarks/tests/test_tasks.py index ac84d56dfe..e757154a16 100644 --- a/openedx/core/djangoapps/bookmarks/tests/test_tasks.py +++ b/openedx/core/djangoapps/bookmarks/tests/test_tasks.py @@ -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) diff --git a/openedx/core/djangoapps/content/block_structure/tests/test_signals.py b/openedx/core/djangoapps/content/block_structure/tests/test_signals.py index ba9871e92b..e2a4843559 100644 --- a/openedx/core/djangoapps/content/block_structure/tests/test_signals.py +++ b/openedx/core/djangoapps/content/block_structure/tests/test_signals.py @@ -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() diff --git a/openedx/core/djangoapps/content/course_overviews/tests.py b/openedx/core/djangoapps/content/course_overviews/tests.py index 99c9918f5b..a8dff4fe9f 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests.py +++ b/openedx/core/djangoapps/content/course_overviews/tests.py @@ -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 diff --git a/openedx/core/djangoapps/content/course_structures/api/v0/tests_api.py b/openedx/core/djangoapps/content/course_structures/api/v0/tests_api.py index ce1093dbea..d61d7b87e1 100644 --- a/openedx/core/djangoapps/content/course_structures/api/v0/tests_api.py +++ b/openedx/core/djangoapps/content/course_structures/api/v0/tests_api.py @@ -18,6 +18,8 @@ class CourseStructureApiTests(ModuleStoreTestCase): ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache'] + ENABLED_SIGNALS = ['course_published'] + def setUp(self): """ Test setup diff --git a/openedx/core/djangoapps/course_groups/tests/test_cohorts.py b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py index 854774e2e9..4c32f45dc6 100644 --- a/openedx/core/djangoapps/course_groups/tests/test_cohorts.py +++ b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py @@ -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): diff --git a/openedx/core/djangoapps/credit/tests/test_verification_access.py b/openedx/core/djangoapps/credit/tests/test_verification_access.py index 4d8779d2c5..763d2fed2d 100644 --- a/openedx/core/djangoapps/credit/tests/test_verification_access.py +++ b/openedx/core/djangoapps/credit/tests/test_verification_access.py @@ -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()