Commit 437a3bf9 by Don Mitchell

Merge pull request #4542 from edx/split/perf_test

Split/perf test
parents 36d47dd0 02e21d8a
...@@ -8,8 +8,6 @@ from datetime import datetime ...@@ -8,8 +8,6 @@ from datetime import datetime
import dateutil.parser import dateutil.parser
from lazy import lazy from lazy import lazy
from opaque_keys.edx.locations import Location
from opaque_keys.edx.locator import UsageKey
from xmodule.seq_module import SequenceDescriptor, SequenceModule from xmodule.seq_module import SequenceDescriptor, SequenceModule
from xmodule.graders import grader_from_conf from xmodule.graders import grader_from_conf
from xmodule.tabs import CourseTabList from xmodule.tabs import CourseTabList
...@@ -17,7 +15,6 @@ import json ...@@ -17,7 +15,6 @@ import json
from xblock.fields import Scope, List, String, Dict, Boolean, Integer from xblock.fields import Scope, List, String, Dict, Boolean, Integer
from .fields import Date from .fields import Date
from opaque_keys.edx.locator import CourseLocator
from django.utils.timezone import UTC from django.utils.timezone import UTC
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
...@@ -568,10 +568,10 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite): ...@@ -568,10 +568,10 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
:param category: the xblock category :param category: the xblock category
:param fields: the dictionary of {fieldname: value} :param fields: the dictionary of {fieldname: value}
""" """
result = collections.defaultdict(dict)
if fields is None: if fields is None:
return {} return result
cls = self.mixologist.mix(XBlock.load_class(category, select=prefer_xmodules)) cls = self.mixologist.mix(XBlock.load_class(category, select=prefer_xmodules))
result = collections.defaultdict(dict)
for field_name, value in fields.iteritems(): for field_name, value in fields.iteritems():
field = getattr(cls, field_name) field = getattr(cls, field_name)
result[field.scope][field_name] = value result[field.scope][field_name] = value
......
...@@ -85,3 +85,6 @@ class InvalidBranchSetting(Exception): ...@@ -85,3 +85,6 @@ class InvalidBranchSetting(Exception):
super(InvalidBranchSetting, self).__init__() super(InvalidBranchSetting, self).__init__()
self.expected_setting = expected_setting self.expected_setting = expected_setting
self.actual_setting = actual_setting self.actual_setting = actual_setting
def __unicode__(self, *args, **kwargs):
return u"Invalid branch: expected {} but got {}".format(self.expected_setting, self.actual_setting)
...@@ -95,7 +95,7 @@ def path_to_location(modulestore, usage_key): ...@@ -95,7 +95,7 @@ def path_to_location(modulestore, usage_key):
category = path[path_index].block_type category = path[path_index].block_type
if category == 'sequential' or category == 'videosequence': if category == 'sequential' or category == 'videosequence':
section_desc = modulestore.get_item(path[path_index]) section_desc = modulestore.get_item(path[path_index])
child_locs = [c.location for c in section_desc.get_children()] child_locs = [c.location.version_agnostic() for c in section_desc.get_children()]
# positions are 1-indexed, and should be strings to be consistent with # positions are 1-indexed, and should be strings to be consistent with
# url parsing. # url parsing.
position_list.append(str(child_locs.index(path[path_index + 1]) + 1)) position_list.append(str(child_locs.index(path[path_index + 1]) + 1))
......
...@@ -53,7 +53,7 @@ class SplitMigrator(object): ...@@ -53,7 +53,7 @@ class SplitMigrator(object):
new_run = source_course_key.run new_run = source_course_key.run
new_course_key = CourseLocator(new_org, new_course, new_run, branch=ModuleStoreEnum.BranchName.published) new_course_key = CourseLocator(new_org, new_course, new_run, branch=ModuleStoreEnum.BranchName.published)
new_fields = self._get_json_fields_translate_references(original_course, new_course_key, None) new_fields = self._get_fields_translate_references(original_course, new_course_key, None)
if fields: if fields:
new_fields.update(fields) new_fields.update(fields)
new_course = self.split_modulestore.create_course( new_course = self.split_modulestore.create_course(
...@@ -92,7 +92,7 @@ class SplitMigrator(object): ...@@ -92,7 +92,7 @@ class SplitMigrator(object):
course_version_locator, course_version_locator,
module.location.block_type, module.location.block_type,
block_id=module.location.block_id, block_id=module.location.block_id,
fields=self._get_json_fields_translate_references( fields=self._get_fields_translate_references(
module, course_version_locator, new_course.location.block_id module, course_version_locator, new_course.location.block_id
), ),
continue_version=True continue_version=True
...@@ -127,7 +127,7 @@ class SplitMigrator(object): ...@@ -127,7 +127,7 @@ class SplitMigrator(object):
if field.is_set_on(split_module) and not module.fields[name].is_set_on(module): if field.is_set_on(split_module) and not module.fields[name].is_set_on(module):
field.delete_from(split_module) field.delete_from(split_module)
for field, value in self._get_fields_translate_references( for field, value in self._get_fields_translate_references(
module, new_draft_course_loc, published_course_usage_key.block_id module, new_draft_course_loc, published_course_usage_key.block_id, field_names=False
).iteritems(): ).iteritems():
field.write_to(split_module, value) field.write_to(split_module, value)
...@@ -138,7 +138,7 @@ class SplitMigrator(object): ...@@ -138,7 +138,7 @@ class SplitMigrator(object):
user_id, new_draft_course_loc, user_id, new_draft_course_loc,
new_locator.block_type, new_locator.block_type,
block_id=new_locator.block_id, block_id=new_locator.block_id,
fields=self._get_json_fields_translate_references( fields=self._get_fields_translate_references(
module, new_draft_course_loc, published_course_usage_key.block_id module, new_draft_course_loc, published_course_usage_key.block_id
) )
) )
...@@ -173,43 +173,13 @@ class SplitMigrator(object): ...@@ -173,43 +173,13 @@ class SplitMigrator(object):
new_parent.children.insert(new_parent_cursor, new_locator) new_parent.children.insert(new_parent_cursor, new_locator)
new_parent = self.split_modulestore.update_item(new_parent, user_id) new_parent = self.split_modulestore.update_item(new_parent, user_id)
def _get_json_fields_translate_references(self, xblock, new_course_key, course_block_id): def _get_fields_translate_references(self, xblock, new_course_key, course_block_id, field_names=True):
"""
Return the json repr for explicitly set fields but convert all references to their Locators
"""
def get_translation(location):
"""
Convert the location
"""
return new_course_key.make_usage_key(
location.category,
location.block_id if location.category != 'course' else course_block_id
)
result = {}
for field_name, field in xblock.fields.iteritems():
if field.is_set_on(xblock):
field_value = getattr(xblock, field_name)
if isinstance(field, Reference) and field_value is not None:
result[field_name] = get_translation(field_value)
elif isinstance(field, ReferenceList):
result[field_name] = [
get_translation(ele) for ele in field_value
]
elif isinstance(field, ReferenceValueDict):
result[field_name] = {
key: get_translation(subvalue)
for key, subvalue in field_value.iteritems()
}
else:
result[field_name] = field.read_json(xblock)
return result
def _get_fields_translate_references(self, xblock, new_course_key, course_block_id):
""" """
Return a dictionary of field: value pairs for explicitly set fields Return a dictionary of field: value pairs for explicitly set fields
but convert all references to their BlockUsageLocators but convert all references to their BlockUsageLocators
Args:
field_names: if Truthy, the dictionary keys are the field names. If falsey, the keys are the
field objects.
""" """
def get_translation(location): def get_translation(location):
""" """
...@@ -223,19 +193,20 @@ class SplitMigrator(object): ...@@ -223,19 +193,20 @@ class SplitMigrator(object):
result = {} result = {}
for field_name, field in xblock.fields.iteritems(): for field_name, field in xblock.fields.iteritems():
if field.is_set_on(xblock): if field.is_set_on(xblock):
field_value = getattr(xblock, field_name) field_value = field.read_from(xblock)
field_key = field_name if field_names else field
if isinstance(field, Reference) and field_value is not None: if isinstance(field, Reference) and field_value is not None:
result[field] = get_translation(field_value) result[field_key] = get_translation(field_value)
elif isinstance(field, ReferenceList): elif isinstance(field, ReferenceList):
result[field] = [ result[field_key] = [
get_translation(ele) for ele in field_value get_translation(ele) for ele in field_value
] ]
elif isinstance(field, ReferenceValueDict): elif isinstance(field, ReferenceValueDict):
result[field] = { result[field_key] = {
key: get_translation(subvalue) key: get_translation(subvalue)
for key, subvalue in field_value.iteritems() for key, subvalue in field_value.iteritems()
} }
else: else:
result[field] = field_value result[field_key] = field_value
return result return result
...@@ -77,6 +77,8 @@ from .caching_descriptor_system import CachingDescriptorSystem ...@@ -77,6 +77,8 @@ from .caching_descriptor_system import CachingDescriptorSystem
from xmodule.modulestore.split_mongo.mongo_connection import MongoConnection from xmodule.modulestore.split_mongo.mongo_connection import MongoConnection
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore.split_mongo import encode_key_for_mongo, decode_key_from_mongo from xmodule.modulestore.split_mongo import encode_key_for_mongo, decode_key_from_mongo
import types
from _collections import defaultdict
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -1006,7 +1008,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1006,7 +1008,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
raise DuplicateCourseError(locator, index) raise DuplicateCourseError(locator, index)
partitioned_fields = self.partition_fields_by_scope(root_category, fields) partitioned_fields = self.partition_fields_by_scope(root_category, fields)
block_fields = partitioned_fields.setdefault(Scope.settings, {}) block_fields = partitioned_fields[Scope.settings]
if Scope.children in partitioned_fields: if Scope.children in partitioned_fields:
block_fields.update(partitioned_fields[Scope.children]) block_fields.update(partitioned_fields[Scope.children])
definition_fields = self._serialize_fields(root_category, partitioned_fields.get(Scope.content, {})) definition_fields = self._serialize_fields(root_category, partitioned_fields.get(Scope.content, {}))
...@@ -1106,14 +1108,15 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1106,14 +1108,15 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
original_structure = self._lookup_course(descriptor.location)['structure'] original_structure = self._lookup_course(descriptor.location)['structure']
index_entry = self._get_index_if_valid(descriptor.location, force) index_entry = self._get_index_if_valid(descriptor.location, force)
definition_fields = descriptor.get_explicitly_set_fields_by_scope(Scope.content) partitioned_fields = self.partition_xblock_fields_by_scope(descriptor)
definition_fields = partitioned_fields[Scope.content]
descriptor.definition_locator, is_updated = self.update_definition_from_data( descriptor.definition_locator, is_updated = self.update_definition_from_data(
descriptor.definition_locator, definition_fields, user_id descriptor.definition_locator, definition_fields, user_id
) )
original_entry = self._get_block_from_structure(original_structure, descriptor.location.block_id) original_entry = self._get_block_from_structure(original_structure, descriptor.location.block_id)
# check metadata # check metadata
settings = descriptor.get_explicitly_set_fields_by_scope(Scope.settings) settings = partitioned_fields[Scope.settings]
settings = self._serialize_fields(descriptor.category, settings) settings = self._serialize_fields(descriptor.category, settings)
if not is_updated: if not is_updated:
is_updated = self._compare_settings(settings, original_entry['fields']) is_updated = self._compare_settings(settings, original_entry['fields'])
...@@ -1235,7 +1238,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1235,7 +1238,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
def _persist_subdag(self, xblock, user_id, structure_blocks, new_id): def _persist_subdag(self, xblock, user_id, structure_blocks, new_id):
# persist the definition if persisted != passed # persist the definition if persisted != passed
new_def_data = self._serialize_fields(xblock.category, xblock.get_explicitly_set_fields_by_scope(Scope.content)) partitioned_fields = self.partition_xblock_fields_by_scope(xblock)
new_def_data = self._serialize_fields(xblock.category, partitioned_fields[Scope.content])
is_updated = False is_updated = False
if xblock.definition_locator is None or isinstance(xblock.definition_locator.definition_id, LocalId): if xblock.definition_locator is None or isinstance(xblock.definition_locator.definition_id, LocalId):
xblock.definition_locator = self.create_definition_from_data( xblock.definition_locator = self.create_definition_from_data(
...@@ -1270,7 +1274,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1270,7 +1274,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
children.append(child.block_id) children.append(child.block_id)
is_updated = is_updated or structure_blocks[encoded_block_id]['fields']['children'] != children is_updated = is_updated or structure_blocks[encoded_block_id]['fields']['children'] != children
block_fields = xblock.get_explicitly_set_fields_by_scope(Scope.settings) block_fields = partitioned_fields[Scope.settings]
block_fields = self._serialize_fields(xblock.category, block_fields) block_fields = self._serialize_fields(xblock.category, block_fields)
if not is_new and not is_updated: if not is_new and not is_updated:
is_updated = self._compare_settings(block_fields, structure_blocks[encoded_block_id]['fields']) is_updated = self._compare_settings(block_fields, structure_blocks[encoded_block_id]['fields'])
...@@ -1697,6 +1701,18 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1697,6 +1701,18 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
index_entry['versions'][branch] = new_id index_entry['versions'][branch] = new_id
self.db_connection.update_course_index(index_entry) self.db_connection.update_course_index(index_entry)
def partition_xblock_fields_by_scope(self, xblock):
"""
Return a dictionary of scopes mapped to this xblock's explicitly set fields w/o any conversions
"""
# explicitly_set_fields_by_scope converts to json; so, avoiding it
# the existing partition_fields_by_scope works on a dict not an xblock
result = defaultdict(dict)
for field in xblock.fields.itervalues():
if field.is_set_on(xblock):
result[field.scope][field.name] = field.read_from(xblock)
return result
def _serialize_fields(self, category, fields): def _serialize_fields(self, category, fields):
""" """
Convert any references to their serialized form. Convert any references to their serialized form.
...@@ -1708,6 +1724,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1708,6 +1724,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
assert isinstance(fields, dict) assert isinstance(fields, dict)
xblock_class = XBlock.load_class(category, self.default_class) xblock_class = XBlock.load_class(category, self.default_class)
xblock_class = self.mixologist.mix(xblock_class) xblock_class = self.mixologist.mix(xblock_class)
for field_name, value in fields.iteritems(): for field_name, value in fields.iteritems():
if value: if value:
if isinstance(xblock_class.fields[field_name], Reference): if isinstance(xblock_class.fields[field_name], Reference):
...@@ -1719,6 +1736,9 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1719,6 +1736,9 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
elif isinstance(xblock_class.fields[field_name], ReferenceValueDict): elif isinstance(xblock_class.fields[field_name], ReferenceValueDict):
for key, subvalue in value.iteritems(): for key, subvalue in value.iteritems():
value[key] = subvalue.block_id value[key] = subvalue.block_id
# should this recurse down dicts and lists just in case they contain datetime?
elif not isinstance(value, datetime.datetime): # don't convert datetimes!
fields[field_name] = xblock_class.fields[field_name].to_json(value)
# I think these are obsolete conditions; so, I want to confirm that. Thus the warnings # I think these are obsolete conditions; so, I want to confirm that. Thus the warnings
if 'location' in fields: if 'location' in fields:
......
...@@ -9,7 +9,7 @@ from xblock.core import XBlock ...@@ -9,7 +9,7 @@ from xblock.core import XBlock
from xmodule.tabs import StaticTab from xmodule.tabs import StaticTab
from decorator import contextmanager from decorator import contextmanager
from mock import Mock, patch from mock import Mock, patch
from nose.tools import assert_less_equal, assert_greater_equal from nose.tools import assert_less_equal, assert_greater_equal, assert_equal
class Dummy(object): class Dummy(object):
...@@ -259,18 +259,49 @@ def check_mongo_calls(mongo_store, num_finds=0, num_sends=None): ...@@ -259,18 +259,49 @@ def check_mongo_calls(mongo_store, num_finds=0, num_sends=None):
of calls to send_message which is for insert, update, and remove (if you provide num_sends). At the of calls to send_message which is for insert, update, and remove (if you provide num_sends). At the
end of the with statement, it compares the counts to the num_finds and num_sends. end of the with statement, it compares the counts to the num_finds and num_sends.
:param mongo_store: the MongoModulestore or subclass to watch :param mongo_store: the MongoModulestore or subclass to watch or a SplitMongoModuleStore
:param num_finds: the exact number of find calls expected :param num_finds: the exact number of find calls expected
:param num_sends: If none, don't instrument the send calls. If non-none, count and compare to :param num_sends: If none, don't instrument the send calls. If non-none, count and compare to
the given int value. the given int value.
""" """
with check_exact_number_of_calls(mongo_store.collection, mongo_store.collection.find, num_finds): if mongo_store.get_modulestore_type() == ModuleStoreEnum.Type.mongo:
if num_sends: with check_exact_number_of_calls(mongo_store.collection, mongo_store.collection.find, num_finds):
with check_exact_number_of_calls( if num_sends is not None:
mongo_store.database.connection, with check_exact_number_of_calls(
mongo_store.database.connection._send_message, # pylint: disable=protected-access mongo_store.database.connection,
num_sends, mongo_store.database.connection._send_message, # pylint: disable=protected-access
): num_sends,
):
yield
else:
yield yield
else: elif mongo_store.get_modulestore_type() == ModuleStoreEnum.Type.split:
yield collections = [
mongo_store.db_connection.course_index,
mongo_store.db_connection.structures,
mongo_store.db_connection.definitions,
]
# could add else clause which raises exception or just rely on the below to suss that out
try:
find_wraps = []
wrap_patches = []
for collection in collections:
find_wrap = Mock(wraps=collection.find)
find_wraps.append(find_wrap)
wrap_patch = patch.object(collection, 'find', find_wrap)
wrap_patches.append(wrap_patch)
wrap_patch.start()
if num_sends is not None:
connection = mongo_store.db_connection.database.connection
with check_exact_number_of_calls(
connection,
connection._send_message, # pylint: disable=protected-access
num_sends,
):
yield
else:
yield
finally:
map(lambda wrap_patch: wrap_patch.stop(), wrap_patches)
call_count = sum([find_wrap.call_count for find_wrap in find_wraps])
assert_equal(call_count, num_finds)
# encoding: utf-8
"""
The data type and use of it for declaratively creating test courses.
"""
# used to create course subtrees in ModuleStoreTestCase.create_test_course
# adds to self properties w/ the given block_id which hold the UsageKey for easy retrieval.
# fields is a dictionary of keys and values. sub_tree is a collection of BlockInfo
from collections import namedtuple
import datetime
BlockInfo = namedtuple('BlockInfo', 'block_id, category, fields, sub_tree')
default_block_info_tree = [
BlockInfo(
'chapter_x', 'chapter', {}, [
BlockInfo(
'sequential_x1', 'sequential', {}, [
BlockInfo(
'vertical_x1a', 'vertical', {}, [
BlockInfo('problem_x1a_1', 'problem', {}, []),
BlockInfo('problem_x1a_2', 'problem', {}, []),
BlockInfo('problem_x1a_3', 'problem', {}, []),
BlockInfo('html_x1a_1', 'html', {}, []),
]
)
]
)
]
),
BlockInfo(
'chapter_y', 'chapter', {}, [
BlockInfo(
'sequential_y1', 'sequential', {}, [
BlockInfo(
'vertical_y1a', 'vertical', {}, [
BlockInfo('problem_y1a_1', 'problem', {}, []),
BlockInfo('problem_y1a_2', 'problem', {}, []),
BlockInfo('problem_y1a_3', 'problem', {}, []),
]
)
]
)
]
)
]
# equivalent to toy course in xml
TOY_BLOCK_INFO_TREE = [
BlockInfo(
'Overview', "chapter", {"display_name" : "Overview"}, [
BlockInfo(
"Toy_Videos", "videosequence", {
"xml_attributes": {"filename": ["", None]}, "display_name": "Toy Videos", "format": "Lecture Sequence"
}, [
BlockInfo(
"secret:toylab", "html", {
"data": "<b>Lab 2A: Superposition Experiment</b>\n\n<<<<<<< Updated upstream\n<p>Isn't the toy course great?</p>\n\n<p>Let's add some markup that uses non-ascii characters.\nFor example, we should be able to write words like encyclop&aelig;dia, or foreign words like fran&ccedil;ais.\nLooking beyond latin-1, we should handle math symbols: &pi;r&sup2 &le; &#8734.\nAnd it shouldn't matter if we use entities or numeric codes &mdash; &Omega; &ne; &pi; &equiv; &#937; &#8800; &#960;.\n</p>\n=======\n<p>Isn't the toy course great? — &le;</p>\n>>>>>>> Stashed changes\n",
"xml_attributes": { "filename" : [ "html/secret/toylab.xml", "html/secret/toylab.xml" ] },
"display_name" : "Toy lab"
}, []
),
BlockInfo(
"toyjumpto", "html", {
"data" : "<a href=\"/jump_to_id/vertical_test\">This is a link to another page and some Chinese 四節比分和七年前</a> <p>Some more Chinese 四節比分和七年前</p>\n",
"xml_attributes": { "filename" : [ "html/toyjumpto.xml", "html/toyjumpto.xml" ] }
}, []),
BlockInfo(
"toyhtml", "html", {
"data" : "<a href='/static/handouts/sample_handout.txt'>Sample</a>",
"xml_attributes" : { "filename" : [ "html/toyhtml.xml", "html/toyhtml.xml" ] }
}, []),
BlockInfo(
"nonportable", "html", {
"data": "<a href=\"/static/foo.jpg\">link</a>\n",
"xml_attributes" : { "filename" : [ "html/nonportable.xml", "html/nonportable.xml" ] }
}, []),
BlockInfo(
"nonportable_link", "html", {
"data": "<a href=\"/jump_to_id/nonportable_link\">link</a>\n\n",
"xml_attributes": {"filename": ["html/nonportable_link.xml", "html/nonportable_link.xml"]}
}, []),
BlockInfo(
"badlink", "html", {
"data": "<img src=\"/static//file.jpg\" />\n",
"xml_attributes" : { "filename" : [ "html/badlink.xml", "html/badlink.xml" ] }
}, []),
BlockInfo(
"with_styling", "html", {
"data": "<p style=\"font:italic bold 72px/30px Georgia, serif; color: red; \">Red text here</p>",
"xml_attributes": {"filename": ["html/with_styling.xml", "html/with_styling.xml"]}
}, []),
BlockInfo(
"just_img", "html", {
"data": "<img src=\"/static/foo_bar.jpg\" />",
"xml_attributes": {"filename": [ "html/just_img.xml", "html/just_img.xml" ] }
}, []),
BlockInfo(
"Video_Resources", "video", {
"youtube_id_1_0" : "1bK-WdDi6Qw", "display_name" : "Video Resources"
}, []),
]),
BlockInfo(
"Welcome", "video", {"data": "", "youtube_id_1_0": "p2Q6BrNhdh8", "display_name": "Welcome"}, []
),
BlockInfo(
"video_123456789012", "video", {"data": "", "youtube_id_1_0": "p2Q6BrNhdh8", "display_name": "Test Video"}, []
),
BlockInfo(
"video_4f66f493ac8f", "video", {"youtube_id_1_0": "p2Q6BrNhdh8"}, []
)
]
),
BlockInfo(
"secret:magic", "chapter", {
"xml_attributes": {"filename": [ "chapter/secret/magic.xml", "chapter/secret/magic.xml"]}
}, [
BlockInfo(
"toyvideo", "video", {"youtube_id_1_0": "OEoXaMPEzfMA", "display_name": "toyvideo"}, []
)
]
),
BlockInfo(
"poll_test", "chapter", {}, [
BlockInfo(
"T1_changemind_poll_foo", "poll_question", {
"question": "<p>Have you changed your mind? ’</p>",
"answers": [{"text": "Yes", "id": "yes"}, {"text": "No", "id": "no"}],
"xml_attributes": {"reset": "false", "filename": ["", None]},
"display_name": "Change your answer"
}, []) ]
),
BlockInfo(
"vertical_container", "chapter", {
"xml_attributes": {"filename": ["chapter/vertical_container.xml", "chapter/vertical_container.xml"]}
}, [
BlockInfo("vertical_sequential", "sequential", {}, [
BlockInfo("vertical_test", "vertical", {
"xml_attributes": {"filename": ["vertical/vertical_test.xml", "vertical_test"]}
}, [
BlockInfo(
"sample_video", "video", {
"youtube_id_1_25": "AKqURZnYqpk",
"youtube_id_0_75": "JMD_ifUUfsU",
"youtube_id_1_0": "OEoXaMPEzfM",
"display_name": "default",
"youtube_id_1_5": "DYpADpL7jAY"
}, []),
BlockInfo(
"separate_file_video", "video", {
"youtube_id_1_25": "AKqURZnYqpk",
"youtube_id_0_75": "JMD_ifUUfsU",
"youtube_id_1_0": "OEoXaMPEzfM",
"display_name": "default",
"youtube_id_1_5": "DYpADpL7jAY"
}, []),
BlockInfo(
"video_with_end_time", "video", {
"youtube_id_1_25": "AKqURZnYqpk",
"display_name": "default",
"youtube_id_1_0": "OEoXaMPEzfM",
"end_time": datetime.timedelta(seconds=10),
"youtube_id_1_5": "DYpADpL7jAY",
"youtube_id_0_75": "JMD_ifUUfsU"
}, []),
BlockInfo(
"T1_changemind_poll_foo_2", "poll_question", {
"question": "<p>Have you changed your mind?</p>",
"answers": [{"text": "Yes", "id": "yes"}, {"text": "No", "id": "no"}],
"xml_attributes": {"reset": "false", "filename": [ "", None]},
"display_name": "Change your answer"
}, []),
]),
BlockInfo("unicode", "html", {
"data": "…", "xml_attributes": {"filename": ["", None]}
}, [])
]),
]
),
BlockInfo(
"handout_container", "chapter", {
"xml_attributes" : {"filename" : ["chapter/handout_container.xml", "chapter/handout_container.xml"]}
}, [
BlockInfo(
"html_7e5578f25f79", "html", {
"data": "<a href=\"/static/handouts/sample_handout.txt\"> handouts</a>",
"xml_attributes": {"filename": ["", None]}
}, []
),
]
)
]
from nose.tools import assert_equals, assert_raises, assert_true, assert_false # pylint: disable=E0611 from nose.tools import assert_equals, assert_true, assert_false # pylint: disable=E0611
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.search import path_to_location
from opaque_keys.edx.locations import SlashSeparatedCourseKey
def check_path_to_location(modulestore):
"""
Make sure that path_to_location works: should be passed a modulestore
with the toy and simple courses loaded.
"""
course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
should_work = (
(course_id.make_usage_key('video', 'Welcome'),
(course_id, "Overview", "Welcome", None)),
(course_id.make_usage_key('chapter', 'Overview'),
(course_id, "Overview", None, None)),
)
for location, expected in should_work:
assert_equals(path_to_location(modulestore, location), expected)
not_found = (
course_id.make_usage_key('video', 'WelcomeX'),
course_id.make_usage_key('course', 'NotHome'),
)
for location in not_found:
with assert_raises(ItemNotFoundError):
path_to_location(modulestore, location)
def check_has_course_method(modulestore, locator, locator_key_fields): def check_has_course_method(modulestore, locator, locator_key_fields):
......
...@@ -31,13 +31,11 @@ from xmodule.modulestore.xml_exporter import export_to_xml ...@@ -31,13 +31,11 @@ from xmodule.modulestore.xml_exporter import export_to_xml
from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint
from xmodule.contentstore.mongo import MongoContentStore from xmodule.contentstore.mongo import MongoContentStore
from xmodule.modulestore.tests.test_modulestore import check_path_to_location
from nose.tools import assert_in from nose.tools import assert_in
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
from git.test.lib.asserts import assert_not_none from git.test.lib.asserts import assert_not_none
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
from xmodule.modulestore.mongo.base import as_draft from xmodule.modulestore.mongo.base import as_draft
from xmodule.modulestore.tests.factories import check_mongo_calls
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -245,11 +243,6 @@ class TestMongoModuleStore(unittest.TestCase): ...@@ -245,11 +243,6 @@ class TestMongoModuleStore(unittest.TestCase):
self.draft_store._find_one(Location('edX', 'toy', '2012_Fall', 'video', 'Welcome')), self.draft_store._find_one(Location('edX', 'toy', '2012_Fall', 'video', 'Welcome')),
) )
def test_path_to_location(self):
'''Make sure that path_to_location works'''
with check_mongo_calls(self.draft_store, 9):
check_path_to_location(self.draft_store)
def test_xlinter(self): def test_xlinter(self):
''' '''
Run through the xlinter, we know the 'toy' course has violations, but the Run through the xlinter, we know the 'toy' course has violations, but the
......
...@@ -1464,19 +1464,11 @@ class TestCourseCreation(SplitModuleTest): ...@@ -1464,19 +1464,11 @@ class TestCourseCreation(SplitModuleTest):
original_locator = CourseLocator(org='guestx', course='contender', run="run", branch=BRANCH_NAME_DRAFT) original_locator = CourseLocator(org='guestx', course='contender', run="run", branch=BRANCH_NAME_DRAFT)
original = modulestore().get_course(original_locator) original = modulestore().get_course(original_locator)
original_index = modulestore().get_course_index_info(original_locator) original_index = modulestore().get_course_index_info(original_locator)
fields = {} fields = {
for field in original.fields.values(): 'grading_policy': original.grading_policy,
value = getattr(original, field.name) 'display_name': 'Derivative',
if not isinstance(value, datetime.datetime): }
json_value = field.to_json(value)
else:
json_value = value
if field.scope == Scope.content and field.name != 'location':
fields[field.name] = json_value
elif field.scope == Scope.settings:
fields[field.name] = json_value
fields['grading_policy']['GRADE_CUTOFFS'] = {'A': .9, 'B': .8, 'C': .65} fields['grading_policy']['GRADE_CUTOFFS'] = {'A': .9, 'B': .8, 'C': .65}
fields['display_name'] = 'Derivative'
new_draft = modulestore().create_course( new_draft = modulestore().create_course(
'counter', 'leech', 'leech_run', 'leech_master', BRANCH_NAME_DRAFT, 'counter', 'leech', 'leech_run', 'leech_master', BRANCH_NAME_DRAFT,
versions_dict={BRANCH_NAME_DRAFT: original_index['versions'][BRANCH_NAME_DRAFT]}, versions_dict={BRANCH_NAME_DRAFT: original_index['versions'][BRANCH_NAME_DRAFT]},
......
...@@ -8,10 +8,8 @@ from glob import glob ...@@ -8,10 +8,8 @@ from glob import glob
from mock import patch from mock import patch
from xmodule.modulestore.xml import XMLModuleStore from xmodule.modulestore.xml import XMLModuleStore
from opaque_keys.edx.locations import Location
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from .test_modulestore import check_path_to_location
from xmodule.tests import DATA_DIR from xmodule.tests import DATA_DIR
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.tests.test_modulestore import check_has_course_method from xmodule.modulestore.tests.test_modulestore import check_has_course_method
...@@ -32,15 +30,6 @@ class TestXMLModuleStore(unittest.TestCase): ...@@ -32,15 +30,6 @@ class TestXMLModuleStore(unittest.TestCase):
""" """
Test around the XML modulestore Test around the XML modulestore
""" """
def test_path_to_location(self):
"""Make sure that path_to_location works properly"""
print "Starting import"
modulestore = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'simple'])
print "finished import"
check_path_to_location(modulestore)
def test_xml_modulestore_type(self): def test_xml_modulestore_type(self):
store = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'simple']) store = XMLModuleStore(DATA_DIR, course_dirs=['toy', 'simple'])
self.assertEqual(store.get_modulestore_type(), ModuleStoreEnum.Type.xml) self.assertEqual(store.get_modulestore_type(), ModuleStoreEnum.Type.xml)
......
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