def test_request_cached_with_caches_despite_changing_wrapped_result(self): """ Ensure that after caching a result, we always send it back, even if the underlying result changes. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [1, 2, 3] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) direct_result = mock_wrapper() self.assertEqual(direct_result, 2) self.assertEqual(to_be_wrapped.call_count, 2) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 2) direct_result = mock_wrapper() self.assertEqual(direct_result, 3) self.assertEqual(to_be_wrapped.call_count, 3)
def test_setting_override(self, is_enabled, override_choice, expected_result): RequestCache.clear_request_cache() self.set_waffle_course_override(override_choice, is_enabled) override_value = WaffleFlagCourseOverrideModel.override_value( self.WAFFLE_TEST_NAME, self.TEST_COURSE_KEY ) self.assertEqual(override_value, expected_result)
def test_request_cached_with_caches_despite_changing_wrapped_result(self): """ Ensure that after caching a result, we always send it back, even if the underlying result changes. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [1, 2, 3] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) direct_result = mock_wrapper() self.assertEqual(direct_result, 2) self.assertEqual(to_be_wrapped.call_count, 2) result = wrapped() self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 2) direct_result = mock_wrapper() self.assertEqual(direct_result, 3) self.assertEqual(to_be_wrapped.call_count, 3)
def test_request_cached_mixed_unicode_str_args(self): """ Ensure that request_cached can work with mixed str and Unicode parameters. """ RequestCache.clear_request_cache() def dummy_function(arg1, arg2): """ A dummy function that expects an str and unicode arguments. """ assert isinstance(arg1, str), 'First parameter has to be of type `str`' assert isinstance( arg2, unicode), 'Second parameter has to be of type `unicode`' return True self.assertTrue(dummy_function('Hello', u'World'), 'Should be callable with ASCII chars') self.assertTrue(dummy_function('H∂llå', u'Wørld'), 'Should be callable with non-ASCII chars') wrapped = request_cached(dummy_function) self.assertTrue(wrapped('Hello', u'World'), 'Wrapper should handle ASCII only chars') self.assertTrue(wrapped('H∂llå', u'Wørld'), 'Wrapper should handle non-ASCII chars')
def instrument_course_progress_render( self, course_width, enable_ccx, view_as_ccx, sql_queries, mongo_reads, ): """ Renders the progress page, instrumenting Mongo reads and SQL queries. """ course_key = self.setup_course(course_width, enable_ccx, view_as_ccx) # Switch to published-only mode to simulate the LMS with self.settings(MODULESTORE_BRANCH='published-only'): # Clear all caches before measuring for cache in settings.CACHES: caches[cache].clear() # Refill the metadata inheritance cache get_course_in_cache(course_key) # We clear the request cache to simulate a new request in the LMS. RequestCache.clear_request_cache() # Reset the list of provider classes, so that our django settings changes # can actually take affect. OverrideFieldData.provider_classes = None with self.assertNumQueries(sql_queries, using='default', table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with self.assertNumQueries(0, using='student_module_history'): with self.assertMongoCallCount(mongo_reads): with self.assertXBlockInstantiations(1): self.grade_course(course_key)
def test_get_template_path(self): """ Tests to make sure the get_template_path function works as expected. """ # if the current site has associated SiteTheme then get_template_path should return the argument as is. with patch( "openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme", Mock(return_value=True), ): with patch( "openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite", Mock(return_value=True), ): with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend: mock_microsite_backend.get_template = Mock(return_value="/microsite/about.html") self.assertEqual(theming_helpers.get_template_path("about.html"), "about.html") RequestCache.clear_request_cache() # if the current site does not have associated SiteTheme then get_template_path should return microsite override with patch( "openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme", Mock(return_value=False), ): with patch( "openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite", Mock(return_value=True), ): with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend: mock_microsite_backend.get_template_path = Mock(return_value="/microsite/about.html") self.assertEqual(theming_helpers.get_template_path("about.html"), "/microsite/about.html")
def test_request_context_caching(self): """ Test that the RequestContext is cached in the RequestCache. """ with patch('edxmako.request_context.get_current_request', return_value=None): # requestcontext should be None, because the cache isn't filled self.assertIsNone(get_template_request_context()) with patch('edxmako.request_context.get_current_request', return_value=self.request): # requestcontext should not be None, and should fill the cache self.assertIsNotNone(get_template_request_context()) mock_get_current_request = Mock() with patch('edxmako.request_context.get_current_request', mock_get_current_request): # requestcontext should not be None, because the cache is filled self.assertIsNotNone(get_template_request_context()) mock_get_current_request.assert_not_called() RequestCache.clear_request_cache() with patch('edxmako.request_context.get_current_request', return_value=None): # requestcontext should be None, because the cache isn't filled self.assertIsNone(get_template_request_context())
def test_setting_override_multiple_times(self): RequestCache.clear_request_cache() self.set_waffle_course_override(self.OVERRIDE_CHOICES.on) self.set_waffle_course_override(self.OVERRIDE_CHOICES.off) override_value = WaffleFlagCourseOverrideModel.override_value( self.WAFFLE_TEST_NAME, self.TEST_COURSE_KEY ) self.assertEqual(override_value, self.OVERRIDE_CHOICES.off)
def lti_consumer_fields_editing_flag(course_id, enabled_for_course=False): """ Yields CourseEditLTIFieldsEnabledFlag record for unit tests Arguments: course_id (CourseLocator): course locator to control this feature for. enabled_for_course (bool): whether feature is enabled for 'course_id' """ RequestCache.clear_request_cache() CourseEditLTIFieldsEnabledFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def assert_access_to_gated_content(self, user): """ Verifies access to gated content for the given user is as expected. """ # clear the request cache to flush any cached access results RequestCache.clear_request_cache() # access to gating content (seq1) remains constant self.assertTrue(bool(has_access(user, 'load', self.seq1, self.course.id))) # access to gated content (seq2) remains constant, access is prevented in SeqModule loading self.assertTrue(bool(has_access(user, 'load', self.seq2, self.course.id)))
def assert_access_to_gated_content(self, user): """ Verifies access to gated content for the given user is as expected. """ # clear the request cache to flush any cached access results RequestCache.clear_request_cache() # access to gating content (seq1) remains constant self.assertTrue( bool(has_access(user, 'load', self.seq1, self.course.id))) # access to gated content (seq2) remains constant, access is prevented in SeqModule loading self.assertTrue( bool(has_access(user, 'load', self.seq2, self.course.id)))
def new_assets_page_feature_flags(global_flag, enabled_for_all_courses=False, course_id=None, enabled_for_course=False): """ Most test cases will use a single call to this manager, as they need to set the global setting and the course-specific setting for a single course. """ RequestCache.clear_request_cache() NewAssetsPageFlag.objects.create( enabled=global_flag, enabled_for_all_courses=enabled_for_all_courses) if course_id: CourseNewAssetsPageFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def clear_caches(cls): """ Clear all of the caches defined in settings.CACHES. """ # N.B. As of 2016-04-20, Django won't return any caches # from django.core.cache.caches.all() that haven't been # accessed using caches[name] previously, so we loop # over our list of overridden caches, instead. for cache in settings.CACHES: caches[cache].clear() # The sites framework caches in a module-level dictionary. # Clear that. sites.models.SITE_CACHE.clear() RequestCache.clear_request_cache()
def new_assets_page_feature_flags( global_flag, enabled_for_all_courses=False, course_id=None, enabled_for_course=False ): """ Most test cases will use a single call to this manager, as they need to set the global setting and the course-specific setting for a single course. """ RequestCache.clear_request_cache() NewAssetsPageFlag.objects.create(enabled=global_flag, enabled_for_all_courses=enabled_for_all_courses) if course_id: CourseNewAssetsPageFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def clear_caches(cls): """ Clear all of the caches defined in settings.CACHES. """ # N.B. As of 2016-04-20, Django won't return any caches # from django.core.cache.caches.all() that haven't been # accessed using caches[name] previously, so we loop # over our list of overridden caches, instead. for cache in settings.CACHES: caches[cache].clear() # The sites framework caches in a module-level dictionary. # Clear that. sites.models.SITE_CACHE.clear() RequestCache.clear_request_cache()
def persistent_grades_feature_flags(global_flag, enabled_for_all_courses=False, course_id=None, enabled_for_course=False): """ Most test cases will use a single call to this manager, as they need to set the global setting and the course-specific setting for a single course. """ RequestCache.clear_request_cache() PersistentGradesEnabledFlag.objects.create( enabled=global_flag, enabled_for_all_courses=enabled_for_all_courses) if course_id: CoursePersistentGradesFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def persistent_grades_feature_flags( global_flag, enabled_for_all_courses=False, course_id=None, enabled_for_course=False ): """ Most test cases will use a single call to this manager, as they need to set the global setting and the course-specific setting for a single course. """ RequestCache.clear_request_cache() PersistentGradesEnabledFlag.objects.create(enabled=global_flag, enabled_for_all_courses=enabled_for_all_courses) if course_id: CoursePersistentGradesFlag.objects.create(course_id=course_id, enabled=enabled_for_course) yield
def test_request_cached_with_changing_kwargs(self): """ Ensure that calling a decorated function with different keyword arguments will not use a cached value invoked by a previous call with different arguments. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [1, 2, 3, 4, 5, 6] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) # This will be a miss, and make an underlying call. result = wrapped(1, foo=1) self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) # This will be a miss, and make an underlying call. result = wrapped(2, foo=2) self.assertEqual(result, 2) self.assertEqual(to_be_wrapped.call_count, 2) # This is bypass of the decorator. direct_result = mock_wrapper(3, foo=3) self.assertEqual(direct_result, 3) self.assertEqual(to_be_wrapped.call_count, 3) # These will be hits, and not make an underlying call. result = wrapped(1, foo=1) self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 3) result = wrapped(2, foo=2) self.assertEqual(result, 2) self.assertEqual(to_be_wrapped.call_count, 3) # Since we're changing foo, this will be a miss. result = wrapped(2, foo=5) self.assertEqual(result, 4) self.assertEqual(to_be_wrapped.call_count, 4)
def test_request_cached_with_changing_kwargs(self): """ Ensure that calling a decorated function with different keyword arguments will not use a cached value invoked by a previous call with different arguments. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [1, 2, 3, 4, 5, 6] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) # This will be a miss, and make an underlying call. result = wrapped(1, foo=1) self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 1) # This will be a miss, and make an underlying call. result = wrapped(2, foo=2) self.assertEqual(result, 2) self.assertEqual(to_be_wrapped.call_count, 2) # This is bypass of the decorator. direct_result = mock_wrapper(3, foo=3) self.assertEqual(direct_result, 3) self.assertEqual(to_be_wrapped.call_count, 3) # These will be hits, and not make an underlying call. result = wrapped(1, foo=1) self.assertEqual(result, 1) self.assertEqual(to_be_wrapped.call_count, 3) result = wrapped(2, foo=2) self.assertEqual(result, 2) self.assertEqual(to_be_wrapped.call_count, 3) # Since we're changing foo, this will be a miss. result = wrapped(2, foo=5) self.assertEqual(result, 4) self.assertEqual(to_be_wrapped.call_count, 4)
def dump_courses_to_neo4j(self, credentials, override_cache=False): """ Method that iterates through a list of courses in a modulestore, serializes them, then submits tasks to write them to neo4j. Arguments: credentials (dict): the necessary credentials to connect to neo4j and create a py2neo `Graph` object override_cache: serialize the courses even if they'be been recently serialized Returns: two lists--one of the courses that were successfully written to neo4j and one of courses that were not. """ total_number_of_courses = len(self.course_keys) submitted_courses = [] skipped_courses = [] graph = authenticate_and_create_graph(credentials) for index, course_key in enumerate(self.course_keys): # first, clear the request cache to prevent memory leaks RequestCache.clear_request_cache() log.info( "Now submitting %s for export to neo4j: course %d of %d total courses", course_key, index + 1, total_number_of_courses, ) if not (override_cache or should_dump_course(course_key, graph)): log.info("skipping submitting %s, since it hasn't changed", course_key) skipped_courses.append(six.text_type(course_key)) continue dump_course_to_neo4j.apply_async( args=[six.text_type(course_key), credentials], ) submitted_courses.append(six.text_type(course_key)) return submitted_courses, skipped_courses
def dump_courses_to_neo4j(self, credentials, override_cache=False): """ Method that iterates through a list of courses in a modulestore, serializes them, then submits tasks to write them to neo4j. Arguments: credentials (dict): the necessary credentials to connect to neo4j and create a py2neo `Graph` object override_cache: serialize the courses even if they'be been recently serialized Returns: two lists--one of the courses that were successfully written to neo4j and one of courses that were not. """ total_number_of_courses = len(self.course_keys) submitted_courses = [] skipped_courses = [] graph = authenticate_and_create_graph(credentials) for index, course_key in enumerate(self.course_keys): # first, clear the request cache to prevent memory leaks RequestCache.clear_request_cache() log.info( "Now submitting %s for export to neo4j: course %d of %d total courses", course_key, index + 1, total_number_of_courses, ) if not (override_cache or should_dump_course(course_key, graph)): log.info("skipping submitting %s, since it hasn't changed", course_key) skipped_courses.append(six.text_type(course_key)) continue dump_course_to_neo4j.apply_async( args=[six.text_type(course_key), credentials], ) submitted_courses.append(six.text_type(course_key)) return submitted_courses, skipped_courses
def test_request_cached_with_none_result(self): """ Ensure that calling a decorated function that returns None properly caches the result and doesn't recall the underlying function. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [None, None, None, 1, 1] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) # This will be a miss, and make an underlying call. result = wrapped(1) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 1) # This will be a miss, and make an underlying call. result = wrapped(2) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 2) # This is bypass of the decorator. direct_result = mock_wrapper(3) self.assertEqual(direct_result, None) self.assertEqual(to_be_wrapped.call_count, 3) # These will be hits, and not make an underlying call. result = wrapped(1) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 3) result = wrapped(2) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 3)
def test_request_cached_with_none_result(self): """ Ensure that calling a decorated function that returns None properly caches the result and doesn't recall the underlying function. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.side_effect = [None, None, None, 1, 1] self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) # This will be a miss, and make an underlying call. result = wrapped(1) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 1) # This will be a miss, and make an underlying call. result = wrapped(2) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 2) # This is bypass of the decorator. direct_result = mock_wrapper(3) self.assertEqual(direct_result, None) self.assertEqual(to_be_wrapped.call_count, 3) # These will be hits, and not make an underlying call. result = wrapped(1) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 3) result = wrapped(2) self.assertEqual(result, None) self.assertEqual(to_be_wrapped.call_count, 3)
def test_request_cached_mixed_unicode_str_args(self): """ Ensure that request_cached can work with mixed str and Unicode parameters. """ RequestCache.clear_request_cache() def dummy_function(arg1, arg2): """ A dummy function that expects an str and unicode arguments. """ assert isinstance(arg1, str), 'First parameter has to be of type `str`' assert isinstance(arg2, unicode), 'Second parameter has to be of type `unicode`' return True self.assertTrue(dummy_function('Hello', u'World'), 'Should be callable with ASCII chars') self.assertTrue(dummy_function('H∂llå', u'Wørld'), 'Should be callable with non-ASCII chars') wrapped = request_cached(dummy_function) self.assertTrue(wrapped('Hello', u'World'), 'Wrapper should handle ASCII only chars') self.assertTrue(wrapped('H∂llå', u'Wørld'), 'Wrapper should handle non-ASCII chars')
def test_request_cached_miss_and_then_hit(self): """ Ensure that after a cache miss, we fill the cache and can hit it. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.return_value = 42 self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) result = wrapped() self.assertEqual(result, 42) self.assertEqual(to_be_wrapped.call_count, 1) result = wrapped() self.assertEqual(result, 42) self.assertEqual(to_be_wrapped.call_count, 1)
def test_request_cached_miss_and_then_hit(self): """ Ensure that after a cache miss, we fill the cache and can hit it. """ RequestCache.clear_request_cache() to_be_wrapped = Mock() to_be_wrapped.return_value = 42 self.assertEqual(to_be_wrapped.call_count, 0) def mock_wrapper(*args, **kwargs): """Simple wrapper to let us decorate our mock.""" return to_be_wrapped(*args, **kwargs) wrapped = request_cached(mock_wrapper) result = wrapped() self.assertEqual(result, 42) self.assertEqual(to_be_wrapped.call_count, 1) result = wrapped() self.assertEqual(result, 42) self.assertEqual(to_be_wrapped.call_count, 1)
def test_request_context_caching(self): """ Test that the RequestContext is cached in the RequestCache. """ with patch('edxmako.request_context.get_current_request', return_value=None): # requestcontext should be None, because the cache isn't filled self.assertIsNone(get_template_request_context()) with patch('edxmako.request_context.get_current_request', return_value=self.request): # requestcontext should not be None, and should fill the cache self.assertIsNotNone(get_template_request_context()) mock_get_current_request = Mock() with patch('edxmako.request_context.get_current_request', mock_get_current_request): # requestcontext should not be None, because the cache is filled self.assertIsNotNone(get_template_request_context()) mock_get_current_request.assert_not_called() RequestCache.clear_request_cache() with patch('edxmako.request_context.get_current_request', return_value=None): # requestcontext should be None, because the cache isn't filled self.assertIsNone(get_template_request_context())
def instrument_course_progress_render( self, course_width, enable_ccx, view_as_ccx, sql_queries, mongo_reads, ): """ Renders the progress page, instrumenting Mongo reads and SQL queries. """ course_key = self.setup_course(course_width, enable_ccx, view_as_ccx) # Switch to published-only mode to simulate the LMS with self.settings(MODULESTORE_BRANCH='published-only'): # Clear all caches before measuring for cache in settings.CACHES: caches[cache].clear() # Refill the metadata inheritance cache get_course_in_cache(course_key) # We clear the request cache to simulate a new request in the LMS. RequestCache.clear_request_cache() # Reset the list of provider classes, so that our django settings changes # can actually take affect. OverrideFieldData.provider_classes = None with self.assertNumQueries( sql_queries, using='default', table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with self.assertNumQueries(0, using='student_module_history'): with self.assertMongoCallCount(mongo_reads): with self.assertXBlockInstantiations(1): self.grade_course(course_key)
def invalidate_course_mode_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of course modes. """ RequestCache.clear_request_cache(name=CourseMode.CACHE_NAMESPACE)
def invalidate_course_mode_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of course modes. """ RequestCache.clear_request_cache(name=CourseMode.CACHE_NAMESPACE)
def invalidate_credit_requirement_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of credit requirements. """ RequestCache.clear_request_cache(name=CreditRequirement.CACHE_NAMESPACE)
def invalidate_verified_track_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of VerifiedTrackCohortedCourse. """ RequestCache.clear_request_cache(name=VerifiedTrackCohortedCourse.CACHE_NAMESPACE)
def setUp(self): super(TestCourseWaffleFlag, self).setUp() request = RequestFactory().request() crum.set_current_request(request) RequestCache.clear_request_cache()
def setUp(self): super(TestCourseWaffleFlag, self).setUp() request = RequestFactory().request() crum.set_current_request(request) RequestCache.clear_request_cache()
def setUp(self): super(OverrideWaffleFlagTests, self).setUp() request = RequestFactory().request() self.addCleanup(crum.set_current_request, None) crum.set_current_request(request) RequestCache.clear_request_cache()
def invalidate_verified_track_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of VerifiedTrackCohortedCourse. """ RequestCache.clear_request_cache(name=VerifiedTrackCohortedCourse.CACHE_NAMESPACE)
def invalidate_credit_requirement_cache(sender, **kwargs): # pylint: disable=unused-argument """Invalidate the cache of credit requirements. """ RequestCache.clear_request_cache(name=CreditRequirement.CACHE_NAMESPACE)