Add a TestCase mixin for enabling caches in tests

By default, disable all caching in tests, to preserve test independence.
In order to enable caching, inherit from CacheSetupMixin, and specify
which cache configuration is needed.

[EV-32]
This commit is contained in:
Calen Pennington
2016-04-14 16:21:54 -04:00
parent 18e1610043
commit 853bfe7a36
47 changed files with 624 additions and 272 deletions

View File

@@ -40,6 +40,8 @@ class BookmarksTestsBase(ModuleStoreTestCase):
STORE_TYPE = ModuleStoreEnum.Type.mongo
TEST_PASSWORD = 'test'
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
def setUp(self):
super(BookmarksTestsBase, self).setUp()

View File

@@ -16,6 +16,8 @@ class CourseStructureApiTests(ModuleStoreTestCase):
"""
MOCK_CACHE = "openedx.core.djangoapps.content.course_structures.api.v0.api.cache"
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
def setUp(self):
"""
Test setup

View File

@@ -16,16 +16,20 @@ from openedx.core.djangoapps.credentials.utils import (
)
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from student.tests.factories import UserFactory
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@attr('shard_2')
class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin, CredentialsDataMixin,
ProgramsDataMixin, TestCase):
ProgramsDataMixin, CacheIsolationTestCase):
""" Tests covering the retrieval of user credentials from the Credentials
service.
"""
ENABLED_CACHES = ['default']
def setUp(self):
super(TestCredentialsRetrieval, self).setUp()

View File

@@ -55,6 +55,8 @@ class CreditApiTestBase(ModuleStoreTestCase):
Base class for test cases of the credit API.
"""
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
PROVIDER_ID = "hogwarts"
PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
PROVIDER_URL = "https://credit.example.com/request"

View File

@@ -31,6 +31,7 @@ class ReverificationPartitionTest(ModuleStoreTestCase):
SUBMITTED = "submitted"
APPROVED = "approved"
DENIED = "denied"
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
def setUp(self):
super(ReverificationPartitionTest, self).setUp()

View File

@@ -17,6 +17,7 @@ from openedx.core.djangoapps.programs import utils
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.tests import factories
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from student.tests.factories import UserFactory, CourseEnrollmentFactory
@@ -26,8 +27,11 @@ UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@attr('shard_2')
class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
CredentialsApiConfigMixin, TestCase):
CredentialsApiConfigMixin, CacheIsolationTestCase):
"""Tests covering the retrieval of programs from the Programs service."""
ENABLED_CACHES = ['default']
def setUp(self):
super(TestProgramRetrieval, self).setUp()

View File

@@ -25,6 +25,7 @@ from student.tests.factories import UserFactory
from student.models import UserProfile, LanguageProficiency, PendingEmailChange
from openedx.core.djangoapps.user_api.accounts import ACCOUNT_VISIBILITY_PREF_KEY
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from .. import PRIVATE_VISIBILITY, ALL_USERS_VISIBILITY
TEST_PROFILE_IMAGE_UPLOADED_AT = datetime.datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
@@ -121,10 +122,13 @@ class UserAPITestCase(APITestCase):
clear=True
)
@attr('shard_2')
class TestAccountAPI(UserAPITestCase):
class TestAccountAPI(CacheIsolationTestCase, UserAPITestCase):
"""
Unit tests for the Account API.
"""
ENABLED_CACHES = ['default']
def setUp(self):
super(TestAccountAPI, self).setUp()
@@ -244,7 +248,7 @@ class TestAccountAPI(UserAPITestCase):
"""
self.different_client.login(username=self.different_user.username, password=self.test_password)
self.create_mock_profile(self.user)
with self.assertNumQueries(11):
with self.assertNumQueries(14):
response = self.send_get(self.different_client)
self._verify_full_shareable_account_response(response, account_privacy=ALL_USERS_VISIBILITY)
@@ -259,7 +263,7 @@ class TestAccountAPI(UserAPITestCase):
"""
self.different_client.login(username=self.different_user.username, password=self.test_password)
self.create_mock_profile(self.user)
with self.assertNumQueries(11):
with self.assertNumQueries(14):
response = self.send_get(self.different_client)
self._verify_private_account_response(response, account_privacy=PRIVATE_VISIBILITY)
@@ -307,11 +311,11 @@ class TestAccountAPI(UserAPITestCase):
Test that a client (logged in) can get her own account information (using default legacy profile information,
as created by the test UserFactory).
"""
def verify_get_own_information():
def verify_get_own_information(queries):
"""
Internal helper to perform the actual assertions
"""
with self.assertNumQueries(9):
with self.assertNumQueries(queries):
response = self.send_get(self.client)
data = response.data
self.assertEqual(17, len(data))
@@ -333,12 +337,12 @@ class TestAccountAPI(UserAPITestCase):
self.assertEqual(False, data["accomplishments_shared"])
self.client.login(username=self.user.username, password=self.test_password)
verify_get_own_information()
verify_get_own_information(12)
# Now make sure that the user can get the same information, even if not active
self.user.is_active = False
self.user.save()
verify_get_own_information()
verify_get_own_information(9)
def test_get_account_empty_string(self):
"""
@@ -352,7 +356,7 @@ class TestAccountAPI(UserAPITestCase):
legacy_profile.save()
self.client.login(username=self.user.username, password=self.test_password)
with self.assertNumQueries(9):
with self.assertNumQueries(12):
response = self.send_get(self.client)
for empty_field in ("level_of_education", "gender", "country", "bio"):
self.assertIsNone(response.data[empty_field])

View File

@@ -20,6 +20,7 @@ from social.apps.django_app.default.models import UserSocialAuth
from django_comment_common import models
from openedx.core.lib.api.test_utils import ApiTestCase, TEST_API_KEY
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from student.tests.factories import UserFactory
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
from third_party_auth.tests.utils import (
@@ -334,12 +335,14 @@ class UserViewSetTest(UserApiTestCase):
)
class UserPreferenceViewSetTest(UserApiTestCase):
class UserPreferenceViewSetTest(CacheIsolationTestCase, UserApiTestCase):
"""
Test cases covering the User Preference DRF view class and its various behaviors
"""
LIST_URI = USER_PREFERENCE_LIST_URI
ENABLED_CACHES = ['default']
def setUp(self):
super(UserPreferenceViewSetTest, self).setUp()
self.detail_uri = self.get_uri_for_pref(self.prefs[0])
@@ -1725,12 +1728,16 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
@httpretty.activate
@ddt.ddt
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin):
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTestCase):
"""
Tests for the User API registration endpoint with 3rd party authentication.
"""
CREATE_USER = False
ENABLED_CACHES = ['default']
__test__ = False
def setUp(self):
super(ThirdPartyRegistrationTestMixin, self).setUp()
self.url = reverse('user_api_registration')
@@ -1847,6 +1854,8 @@ class TestFacebookRegistrationView(
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase
):
"""Tests the User API registration endpoint with Facebook authentication."""
__test__ = True
def test_social_auth_exception(self):
"""
According to the do_auth method in social.backends.facebook.py,
@@ -1863,6 +1872,8 @@ class TestGoogleRegistrationView(
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase
):
"""Tests the User API registration endpoint with Google authentication."""
__test__ = True
pass

View File

@@ -0,0 +1,122 @@
from django.core.cache import caches
from django.test import TestCase, override_settings
from django.conf import settings
from django.contrib import sites
from request_cache.middleware import RequestCache
class CacheIsolationMixin(object):
"""
This class can be used to enable specific django caches for
specific the TestCase that it's mixed into.
Usage:
Use the ENABLED_CACHES to list the names of caches that should
be enabled in the context of this TestCase. These caches will
use a loc_mem_cache with the default settings.
Set the class variable CACHES to explicitly specify the cache settings
that should be overridden. This class will insert those values into
django.conf.settings, and will reset all named caches before each
test.
If both CACHES and ENABLED_CACHES are not None, raises an error.
"""
CACHES = None
ENABLED_CACHES = None
__settings_override = None
@classmethod
def start_cache_isolation(cls):
"""
Start cache isolation by overriding the settings.CACHES and
flushing the cache.
"""
cache_settings = None
if cls.CACHES is not None and cls.ENABLED_CACHES is not None:
raise Exception(
"Use either CACHES or ENABLED_CACHES, but not both"
)
if cls.CACHES is not None:
cache_settings = cls.CACHES
elif cls.ENABLED_CACHES is not None:
cache_settings = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
cache_settings.update({
cache_name: {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': cache_name,
'KEY_FUNCTION': 'util.memcache.safe_key',
} for cache_name in cls.ENABLED_CACHES
})
if cache_settings is None:
return
cls.__settings_override = override_settings(CACHES=cache_settings)
cls.__settings_override.__enter__()
# Start with empty caches
cls.clear_caches()
@classmethod
def end_cache_isolation(cls):
"""
End cache isolation by flushing the cache and then returning
settings.CACHES to its original state.
"""
# Make sure that cache contents don't leak out after the isolation is ended
cls.clear_caches()
if cls.__settings_override is not None:
cls.__settings_override.__exit__(None, None, None)
cls.__settings_override = None
@classmethod
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()
class CacheIsolationTestCase(CacheIsolationMixin, TestCase):
"""
A TestCase that isolates caches (as described in
:py:class:`CacheIsolationMixin`) at class setup, and flushes the cache
between every test.
"""
@classmethod
def setUpClass(cls):
super(CacheIsolationTestCase, cls).setUpClass()
cls.start_cache_isolation()
@classmethod
def tearDownClass(cls):
cls.end_cache_isolation()
super(CacheIsolationTestCase, cls).tearDownClass()
def setUp(self):
super(CacheIsolationTestCase, self).setUp()
self.clear_caches()
self.addCleanup(self.clear_caches)

View File

@@ -15,6 +15,7 @@ from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from openedx.core.lib.edx_api_utils import get_edx_api_data
from student.tests.factories import UserFactory
@@ -24,8 +25,11 @@ LOGGER_NAME = 'openedx.core.lib.edx_api_utils'
@attr('shard_2')
class TestApiDataRetrieval(CredentialsApiConfigMixin, CredentialsDataMixin, ProgramsApiConfigMixin, ProgramsDataMixin,
TestCase):
CacheIsolationTestCase):
"""Test utility for API data retrieval."""
ENABLED_CACHES = ['default']
def setUp(self):
super(TestApiDataRetrieval, self).setUp()
ClientFactory(name=CredentialsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)