Commit b1346fc7 by Adam Palay Committed by Nimisha Asthagiri

add tests for structure cache

use default cache for tests
add test for when cache isn't configured
add test for dummy cache
noop if no cache found
parent 39ab0f31
...@@ -369,6 +369,19 @@ class EditInfo(object): ...@@ -369,6 +369,19 @@ class EditInfo(object):
source_version="UNSET" if self.source_version is None else self.source_version, source_version="UNSET" if self.source_version is None else self.source_version,
) # pylint: disable=bad-continuation ) # pylint: disable=bad-continuation
def __eq__(self, edit_info):
"""
Two EditInfo instances are equal iff their storable representations
are equal.
"""
return self.to_storable() == edit_info.to_storable()
def __neq__(self, edit_info):
"""
Two EditInfo instances are not equal if they're not equal.
"""
return not self == edit_info
class BlockData(object): class BlockData(object):
""" """
...@@ -426,6 +439,19 @@ class BlockData(object): ...@@ -426,6 +439,19 @@ class BlockData(object):
classname=self.__class__.__name__, classname=self.__class__.__name__,
) # pylint: disable=bad-continuation ) # pylint: disable=bad-continuation
def __eq__(self, block_data):
"""
Two BlockData objects are equal iff all their attributes are equal.
"""
attrs = ['fields', 'block_type', 'definition', 'defaults', 'edit_info']
return all(getattr(self, attr) == getattr(block_data, attr) for attr in attrs)
def __neq__(self, block_data):
"""
Just define this as not self.__eq__(block_data)
"""
return not self == block_data
new_contract('BlockData', BlockData) new_contract('BlockData', BlockData)
......
...@@ -4,9 +4,7 @@ Segregation of pymongo functions from the data modeling mechanisms for split mod ...@@ -4,9 +4,7 @@ Segregation of pymongo functions from the data modeling mechanisms for split mod
import datetime import datetime
import cPickle as pickle import cPickle as pickle
import math import math
import re
import zlib import zlib
from mongodb_proxy import autoretry_read, MongoProxy
import pymongo import pymongo
import pytz import pytz
import re import re
...@@ -213,15 +211,22 @@ class CourseStructureCache(object): ...@@ -213,15 +211,22 @@ class CourseStructureCache(object):
""" """
Wrapper around django cache object to cache course structure objects. Wrapper around django cache object to cache course structure objects.
The course structures are pickled and compressed when cached. The course structures are pickled and compressed when cached.
If the 'course_structure_cache' doesn't exist, then don't do anything for
for set and get.
""" """
def __init__(self): def __init__(self):
self.no_cache_found = False
try: try:
self.cache = get_cache('course_structure_cache') self.cache = get_cache('course_structure_cache')
except InvalidCacheBackendError: except InvalidCacheBackendError:
self.cache = get_cache('default') self.no_cache_found = True
def get(self, key): def get(self, key):
"""Pull the compressed, pickled struct data from cache and deserialize.""" """Pull the compressed, pickled struct data from cache and deserialize."""
if self.no_cache_found:
return None
compressed_pickled_data = self.cache.get(key) compressed_pickled_data = self.cache.get(key)
if compressed_pickled_data is None: if compressed_pickled_data is None:
return None return None
...@@ -229,6 +234,9 @@ class CourseStructureCache(object): ...@@ -229,6 +234,9 @@ class CourseStructureCache(object):
def set(self, key, structure): def set(self, key, structure):
"""Given a structure, will pickle, compress, and write to cache.""" """Given a structure, will pickle, compress, and write to cache."""
if self.no_cache_found:
return None
pickled_data = pickle.dumps(structure, pickle.HIGHEST_PROTOCOL) pickled_data = pickle.dumps(structure, pickle.HIGHEST_PROTOCOL)
# 1 = Fastest (slightly larger results) # 1 = Fastest (slightly larger results)
compressed_pickled_data = zlib.compress(pickled_data, 1) compressed_pickled_data = zlib.compress(pickled_data, 1)
......
...@@ -794,7 +794,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): ...@@ -794,7 +794,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# find: find parent (definition.children) 2x, find draft item, get inheritance items # find: find parent (definition.children) 2x, find draft item, get inheritance items
# send: one delete query for specific item # send: one delete query for specific item
# Split: # Split:
# find: active_version & structure (cached) # find: active_version & structure
# send: update structure and active_versions # send: update structure and active_versions
@ddt.data(('draft', 4, 1), ('split', 2, 2)) @ddt.data(('draft', 4, 1), ('split', 2, 2))
@ddt.unpack @ddt.unpack
......
...@@ -12,6 +12,7 @@ import uuid ...@@ -12,6 +12,7 @@ import uuid
from contracts import contract from contracts import contract
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from django.core.cache import get_cache, InvalidCacheBackendError
from openedx.core.lib import tempdir from openedx.core.lib import tempdir
from xblock.fields import Reference, ReferenceList, ReferenceValueDict from xblock.fields import Reference, ReferenceList, ReferenceValueDict
...@@ -29,6 +30,7 @@ from xmodule.fields import Date, Timedelta ...@@ -29,6 +30,7 @@ from xmodule.fields import Date, Timedelta
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from xmodule.modulestore.tests.test_modulestore import check_has_course_method from xmodule.modulestore.tests.test_modulestore import check_has_course_method
from xmodule.modulestore.split_mongo import BlockKey from xmodule.modulestore.split_mongo import BlockKey
from xmodule.modulestore.tests.factories import check_mongo_calls
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
from xmodule.modulestore.tests.utils import mock_tab_from_json from xmodule.modulestore.tests.utils import mock_tab_from_json
from xmodule.modulestore.edit_info import EditInfoMixin from xmodule.modulestore.edit_info import EditInfoMixin
...@@ -771,6 +773,79 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -771,6 +773,79 @@ class SplitModuleCourseTests(SplitModuleTest):
self.assertEqual(result.children[0].children[0].locator.version_guid, versions[0]) self.assertEqual(result.children[0].children[0].locator.version_guid, versions[0])
class TestCourseStructureCache(SplitModuleTest):
"""Tests for the CourseStructureCache"""
def setUp(self):
# use the default cache, since the `course_structure_cache`
# is a dummy cache during testing
self.cache = get_cache('default')
# make sure we clear the cache before every test...
self.cache.clear()
# ... and after
self.addCleanup(self.cache.clear)
# make a new course:
self.user = random.getrandbits(32)
self.new_course = modulestore().create_course(
'org', 'course', 'test_run', self.user, BRANCH_NAME_DRAFT,
)
super(TestCourseStructureCache, self).setUp()
@patch('xmodule.modulestore.split_mongo.mongo_connection.get_cache')
def test_course_structure_cache(self, mock_get_cache):
# force get_cache to return the default cache so we can test
# its caching behavior
mock_get_cache.return_value = self.cache
with check_mongo_calls(1):
not_cached_structure = self._get_structure(self.new_course)
# when cache is warmed, we should have one fewer mongo call
with check_mongo_calls(0):
cached_structure = self._get_structure(self.new_course)
# now make sure that you get the same structure
self.assertEqual(cached_structure, not_cached_structure)
@patch('xmodule.modulestore.split_mongo.mongo_connection.get_cache')
def test_course_structure_cache_no_cache_configured(self, mock_get_cache):
mock_get_cache.side_effect = InvalidCacheBackendError
with check_mongo_calls(1):
not_cached_structure = self._get_structure(self.new_course)
# if the cache isn't configured, we expect to have to make
# another mongo call here if we want the same course structure
with check_mongo_calls(1):
cached_structure = self._get_structure(self.new_course)
# now make sure that you get the same structure
self.assertEqual(cached_structure, not_cached_structure)
def test_dummy_cache(self):
with check_mongo_calls(1):
not_cached_structure = self._get_structure(self.new_course)
# Since the test is using the dummy cache, it's not actually caching
# anything
with check_mongo_calls(1):
cached_structure = self._get_structure(self.new_course)
# now make sure that you get the same structure
self.assertEqual(cached_structure, not_cached_structure)
def _get_structure(self, course):
"""
Helper function to get a structure from a course.
"""
return modulestore().db_connection.get_structure(
course.location.as_object_id(course.location.version_guid)
)
class SplitModuleItemTests(SplitModuleTest): class SplitModuleItemTests(SplitModuleTest):
''' '''
Item read tests including inheritance Item read tests including inheritance
......
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