From 7a55f1207d7f03f7ce594c8482f5848a5026653f Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Mon, 7 Jul 2014 15:32:21 -0400
Subject: [PATCH 1/7] Add tests of cross-modulestore import/export
[LMS-2945]
---
.../lib/xmodule/xmodule/modulestore/django.py | 1 +
.../lib/xmodule/xmodule/modulestore/mixed.py | 6 +-
.../xmodule/modulestore/mongo/draft.py | 3 +-
.../test_cross_modulestore_import_export.py | 303 ++++++++++++++++++
.../tests/test_mixed_modulestore.py | 8 +-
.../xmodule/modulestore/xml_importer.py | 1 -
common/lib/xmodule/xmodule/tests/__init__.py | 143 ++++++++-
7 files changed, 453 insertions(+), 12 deletions(-)
create mode 100644 common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
diff --git a/common/lib/xmodule/xmodule/modulestore/django.py b/common/lib/xmodule/xmodule/modulestore/django.py
index 973833f4f8..fbcc491ac7 100644
--- a/common/lib/xmodule/xmodule/modulestore/django.py
+++ b/common/lib/xmodule/xmodule/modulestore/django.py
@@ -71,6 +71,7 @@ def create_modulestore_instance(engine, content_store, doc_store_config, options
doc_store_config=doc_store_config,
i18n_service=i18n_service or ModuleI18nService(),
branch_setting_func=_get_modulestore_branch_setting,
+ create_modulestore_instance=create_modulestore_instance,
**_options
)
diff --git a/common/lib/xmodule/xmodule/modulestore/mixed.py b/common/lib/xmodule/xmodule/modulestore/mixed.py
index 4811163429..34a19dd2c0 100644
--- a/common/lib/xmodule/xmodule/modulestore/mixed.py
+++ b/common/lib/xmodule/xmodule/modulestore/mixed.py
@@ -12,7 +12,6 @@ from opaque_keys import InvalidKeyError
from . import ModuleStoreWriteBase
from xmodule.modulestore import ModuleStoreEnum
-from xmodule.modulestore.django import create_modulestore_instance
from opaque_keys.edx.locator import CourseLocator, BlockUsageLocator
from xmodule.modulestore.exceptions import ItemNotFoundError
from opaque_keys.edx.keys import CourseKey, UsageKey
@@ -30,13 +29,16 @@ class MixedModuleStore(ModuleStoreWriteBase):
"""
ModuleStore knows how to route requests to the right persistence ms
"""
- def __init__(self, contentstore, mappings, stores, i18n_service=None, **kwargs):
+ def __init__(self, contentstore, mappings, stores, i18n_service=None, create_modulestore_instance=None, **kwargs):
"""
Initialize a MixedModuleStore. Here we look into our passed in kwargs which should be a
collection of other modulestore configuration information
"""
super(MixedModuleStore, self).__init__(contentstore, **kwargs)
+ if create_modulestore_instance is None:
+ raise ValueError('MixedModuleStore constructor must be passed a create_modulestore_instance function')
+
self.modulestores = []
self.mappings = {}
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py
index 9e26c211dc..98cdc9fad7 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py
@@ -50,7 +50,8 @@ class DraftModuleStore(MongoModuleStore):
def __init__(self, *args, **kwargs):
"""
Args:
- branch_setting_func: a function that returns the branch setting to use for this store's operations
+ branch_setting_func: a function that returns the branch setting to use for this store's operations.
+ This should be an attribute from ModuleStoreEnum.Branch
"""
super(DraftModuleStore, self).__init__(*args, **kwargs)
self.branch_setting_func = kwargs.pop('branch_setting_func', lambda: ModuleStoreEnum.Branch.published_only)
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
new file mode 100644
index 0000000000..6763838fde
--- /dev/null
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
@@ -0,0 +1,303 @@
+"""
+This suite of tests verifies that courses exported from one modulestore can be imported into
+another modulestore and the result will be identical (ignoring changes to identifiers that are
+the result of being imported into a course with a different course id).
+
+It does this by providing facilities for creating and cleaning up each of the modulestore types,
+and then for each combination of modulestores, performing the sequence:
+ 1) use xml_importer to read a course from xml from disk into the first modulestore (called the source)
+ 2) use xml_exporter to dump the course from the source modulestore to disk
+ 3) use xml_importer to read the dumped course into a second modulestore (called the destination)
+ 4) Compare all modules in the source and destination modulestores to make sure that they line up
+
+"""
+
+import ddt
+import itertools
+import random
+from contextlib import contextmanager, nested
+from functools import partial
+from unittest import TestCase
+from shutil import rmtree
+from tempfile import mkdtemp
+from opaque_keys.edx.locations import SlashSeparatedCourseKey
+
+from xmodule.tests import CourseComparisonTest
+
+from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
+from xmodule.modulestore.mongo.base import ModuleStoreEnum
+from xmodule.modulestore.mongo.draft import DraftModuleStore
+from xmodule.modulestore.mixed import MixedModuleStore
+from xmodule.contentstore.mongo import MongoContentStore
+from xmodule.modulestore.xml_importer import import_from_xml
+from xmodule.modulestore.xml_exporter import export_to_xml
+
+COMMON_DOCSTORE_CONFIG = {
+ 'host': 'localhost'
+}
+
+
+class MemoryCache(object):
+ """
+ This fits the metadata_inheritance_cache_subsystem interface used by
+ the modulestore, and stores the data in a dictionary in memory.
+ """
+ def __init__(self):
+ self._data = {}
+
+ def get(self, key, default=None):
+ return self._data.get(key, default)
+
+ def set(self, key, value):
+ self._data[key] = value
+
+
+class MongoModulestoreBuilder(object):
+ """
+ A builder class for a DraftModuleStore.
+ """
+ @contextmanager
+ def build(self, contentstore):
+ """
+ A contextmanager that returns an isolated mongo modulestore, and then deletes
+ all of its data at the end of the context.
+
+ Args:
+ contentstore: The contentstore that this modulestore should use to store
+ all of its assets.
+ """
+ doc_store_config = dict(
+ db='modulestore{}'.format(random.randint(0, 10000)),
+ collection='xmodule',
+ **COMMON_DOCSTORE_CONFIG
+ )
+
+ # Set up a temp directory for storing filesystem content created during import
+ fs_root = mkdtemp()
+
+ modulestore = DraftModuleStore(
+ contentstore,
+ doc_store_config,
+ fs_root,
+ render_template=repr,
+ branch_setting_func=lambda: ModuleStoreEnum.Branch.draft_preferred,
+ metadata_inheritance_cache_subsystem=MemoryCache(),
+ )
+
+ try:
+ yield modulestore
+ finally:
+ # Delete the created database
+ db = modulestore.database
+ db.connection.drop_database(db)
+ db.connection.close()
+
+ # Delete the created directory on the filesystem
+ rmtree(fs_root)
+
+ def __repr__(self):
+ return 'MongoModulestoreBuilder()'
+
+
+class VersioningModulestoreBuilder(object):
+ """
+ A builder class for a VersioningModuleStore.
+ """
+ @contextmanager
+ def build(self, contentstore):
+ """
+ A contextmanager that returns an isolated versioning modulestore, and then deletes
+ all of its data at the end of the context.
+
+ Args:
+ contentstore: The contentstore that this modulestore should use to store
+ all of its assets.
+ """
+ doc_store_config = dict(
+ db='modulestore{}'.format(random.randint(0, 10000)),
+ collection='split_module',
+ **COMMON_DOCSTORE_CONFIG
+ )
+ # Set up a temp directory for storing filesystem content created during import
+ fs_root = mkdtemp()
+
+ modulestore = SplitMongoModuleStore(
+ contentstore,
+ doc_store_config,
+ fs_root,
+ render_template=repr,
+ )
+
+ try:
+ yield modulestore
+ finally:
+ # Delete the created database
+ db = modulestore.db
+ db.connection.drop_database(db)
+ db.connection.close()
+
+ # Delete the created directory on the filesystem
+ rmtree(fs_root)
+
+ def __repr__(self):
+ return 'SplitModulestoreBuilder()'
+
+class MixedModulestoreBuilder(object):
+ """
+ A builder class for a MixedModuleStore.
+ """
+ def __init__(self, store_builders, mappings=None):
+ """
+ Args:
+ store_builders: A list of modulestore builder objects. These will be instantiated, in order,
+ as the backing stores for the MixedModuleStore.
+ mappings: Any course mappings to pass to the MixedModuleStore on instantiation.
+ """
+ self.store_builders = store_builders
+ self.mappings = mappings or {}
+
+ @contextmanager
+ def build(self, contentstore):
+ """
+ A contextmanager that returns a mixed modulestore built on top of modulestores
+ generated by other builder classes.
+
+ Args:
+ contentstore: The contentstore that this modulestore should use to store
+ all of its assets.
+ """
+ names, generators = zip(*self.store_builders)
+
+ with nested(*(gen.build(contentstore) for gen in generators)) as modulestores:
+ # Make the modulestore creation function just return the already-created modulestores
+ store_iterator = iter(modulestores)
+ create_modulestore_instance = lambda *args, **kwargs: store_iterator.next()
+
+ # Generate a fake list of stores to give the already generated stores appropriate names
+ stores = [{'NAME': name, 'ENGINE': 'This space deliberately left blank'} for name in names]
+
+ modulestore = MixedModuleStore(contentstore, self.mappings, stores, create_modulestore_instance=create_modulestore_instance)
+
+ yield modulestore
+
+ def __repr__(self):
+ return 'MixedModulestoreBuilder({!r}, {!r})'.format(self.store_builders, self.mappings)
+
+
+class MongoContentstoreBuilder(object):
+ """
+ A builder class for a MongoContentStore.
+ """
+ @contextmanager
+ def build(self):
+ """
+ A contextmanager that returns a MongoContentStore, and deletes its contents
+ when the context closes.
+ """
+ contentstore = MongoContentStore(
+ db='contentstore{}'.format(random.randint(0, 10000)),
+ collection='content',
+ **COMMON_DOCSTORE_CONFIG
+ )
+
+ try:
+ yield contentstore
+ finally:
+ # Delete the created database
+ db = contentstore.fs_files.database
+ db.connection.drop_database(db)
+ db.connection.close()
+
+ def __repr__(self):
+ return 'MongoContentstoreBuilder()'
+
+
+MODULESTORE_SETUPS = (
+ MongoModulestoreBuilder(),
+ VersioningModulestoreBuilder(),
+ MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())]),
+ MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]),
+)
+CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),)
+COURSE_DATA_NAMES = ('toy', 'test_unicode')
+
+@ddt.ddt
+class CrossStoreXMLRoundtrip(CourseComparisonTest):
+ """
+ This class exists to test XML import and export between different modulestore
+ classes.
+ """
+
+ def setUp(self):
+ super(CrossStoreXMLRoundtrip, self).setUp()
+ self.export_dir = mkdtemp()
+ self.addCleanup(rmtree, self.export_dir)
+
+ @ddt.data(*itertools.product(
+ MODULESTORE_SETUPS,
+ MODULESTORE_SETUPS,
+ CONTENTSTORE_SETUPS,
+ CONTENTSTORE_SETUPS,
+ COURSE_DATA_NAMES,
+ ))
+ @ddt.unpack
+ def test_round_trip(self, source_builder, dest_builder, source_content_builder, dest_content_builder, course_data_name):
+ self.maxDiff = None
+ source_course_key = SlashSeparatedCourseKey('source', 'course', 'key')
+ dest_course_key = SlashSeparatedCourseKey('dest', 'course', 'key')
+
+ # Construct the contentstore for storing the first import
+ with source_content_builder.build() as source_content:
+ # Construct the modulestore for storing the first import (using the previously created contentstore)
+ with source_builder.build(source_content) as source_store:
+ # Construct the contentstore for storing the second import
+ with dest_content_builder.build() as dest_content:
+ # Construct the modulestore for storing the second import (using the second contentstore)
+ with dest_builder.build(dest_content) as dest_store:
+ import_from_xml(
+ source_store,
+ 'test_user',
+ 'common/test/data',
+ course_dirs=[course_data_name],
+ static_content_store=source_content,
+ target_course_id=source_course_key,
+ create_new_course_if_not_present=True,
+ )
+
+ export_to_xml(
+ source_store,
+ source_content,
+ source_course_key,
+ self.export_dir,
+ 'exported_course',
+ )
+
+ import_from_xml(
+ dest_store,
+ 'test_user',
+ self.export_dir,
+ static_content_store=dest_content,
+ target_course_id=dest_course_key,
+ create_new_course_if_not_present=True,
+ )
+
+ self.exclude_field(source_course_key.make_usage_key('course', 'key'), 'wiki_slug')
+ self.exclude_field(None, 'xml_attributes')
+ self.ignore_asset_key('_id')
+ self.ignore_asset_key('uploadDate')
+ self.ignore_asset_key('content_son')
+ self.ignore_asset_key('thumbnail_location')
+
+ self.assertCoursesEqual(
+ source_store,
+ source_course_key,
+ dest_store,
+ dest_course_key,
+ )
+
+ self.assertAssetsEqual(
+ source_content,
+ source_course_key,
+ dest_content,
+ dest_course_key,
+ )
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
index 7dc251b4e1..1c4f94d102 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
@@ -104,12 +104,6 @@ class TestMixedModuleStore(unittest.TestCase):
self.addCleanup(self.connection.close)
super(TestMixedModuleStore, self).setUp()
- patcher = patch.multiple(
- 'xmodule.modulestore.mixed',
- create_modulestore_instance=create_modulestore_instance,
- )
- patcher.start()
- self.addCleanup(patcher.stop)
self.addTypeEqualityFunc(BlockUsageLocator, '_compareIgnoreVersion')
self.addTypeEqualityFunc(CourseLocator, '_compareIgnoreVersion')
# define attrs which get set in initdb to quell pylint
@@ -207,7 +201,7 @@ class TestMixedModuleStore(unittest.TestCase):
if index > 0:
store_configs[index], store_configs[0] = store_configs[0], store_configs[index]
break
- self.store = MixedModuleStore(None, **self.options)
+ self.store = MixedModuleStore(None, create_modulestore_instance=create_modulestore_instance, **self.options)
self.addCleanup(self.store.close_all_connections)
# convert to CourseKeys
diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
index 2de6a9b01a..158c9001d8 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py
@@ -151,7 +151,6 @@ def import_from_xml(
# If we're going to remap the course_id, then we can only do that with
# a single course
-
if target_course_id:
assert(len(xml_module_store.modules) == 1)
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index d79bd4366c..e7c8e06912 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -19,7 +19,7 @@ from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
-from xmodule.modulestore.inheritance import InheritanceMixin
+from xmodule.modulestore.inheritance import InheritanceMixin, own_metadata
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.mako_module import MakoDescriptorSystem
from xmodule.error_module import ErrorDescriptor
@@ -154,3 +154,144 @@ class LogicTest(unittest.TestCase):
def ajax_request(self, dispatch, data):
"""Call Xmodule.handle_ajax."""
return json.loads(self.xmodule.handle_ajax(dispatch, data))
+
+
+class CourseComparisonTest(unittest.TestCase):
+ """
+ Mixin that has methods for comparing courses for equality.
+ """
+
+ def setUp(self):
+ self.field_exclusions = set()
+ self.ignored_asset_keys = set()
+
+ def exclude_field(self, usage_id, field_name):
+ """
+ Mark field ``field_name`` of expected block usage ``usage_id`` as ignored
+
+ Args:
+ usage_id (:class:`opaque_keys.edx.UsageKey` or ``None``). If ``None``, skip, this field in all blocks
+ field_name (string): The name of the field to skip
+ """
+ self.field_exclusions.add((usage_id, field_name))
+
+ def ignore_asset_key(self, key_name):
+ self.ignored_asset_keys.add(key_name)
+
+ def assertCoursesEqual(self, expected_store, expected_course_key, actual_store, actual_course_key):
+ """
+ Assert that the courses identified by ``expected_course_key`` in ``expected_store`` and
+ ``actual_course_key`` in ``actual_store`` are identical (ignore differences related
+ owing to the course_keys being different).
+
+ Any field value mentioned in ``self.field_exclusions`` by the key (usage_id, field_name)
+ will be ignored for the purpose of equality checking.
+ """
+ expected_items = expected_store.get_items(expected_course_key)
+ actual_items = actual_store.get_items(actual_course_key)
+ self.assertGreater(len(expected_items), 0)
+ self.assertEqual(len(expected_items), len(actual_items))
+
+ actual_item_map = {item.location: item for item in actual_items}
+
+ for expected_item in expected_items:
+ actual_item_location = expected_item.location.map_into_course(actual_course_key)
+ if expected_item.location.category == 'course':
+ actual_item_location = actual_item_location.replace(name=actual_item_location.run)
+ actual_item = actual_item_map.get(actual_item_location)
+
+ # compare published state
+ exp_pub_state = expected_store.compute_publish_state(expected_item)
+ act_pub_state = actual_store.compute_publish_state(actual_item)
+ self.assertEqual(
+ exp_pub_state,
+ act_pub_state,
+ 'Published states for usages {} and {} differ: {!r} != {!r}'.format(
+ expected_item.location,
+ actual_item.location,
+ exp_pub_state,
+ act_pub_state
+ )
+ )
+
+ # compare fields
+ self.assertEqual(expected_item.fields, actual_item.fields)
+
+ for field_name in expected_item.fields:
+ if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions:
+ continue
+
+ if (None, field_name) in self.field_exclusions:
+ continue
+
+ # Children are handled specially
+ if field_name == 'children':
+ continue
+
+ exp_value = getattr(expected_item, field_name)
+ actual_value = getattr(actual_item, field_name)
+ self.assertEqual(
+ exp_value,
+ actual_value,
+ "Field {} doesn't match between usages {} and {}: {!r} != {!r}".format(
+ field_name,
+ expected_item.scope_ids.usage_id,
+ actual_item.scope_ids.usage_id,
+ exp_value,
+ actual_value,
+ )
+ )
+
+ # compare children
+ self.assertEqual(expected_item.has_children, actual_item.has_children)
+ if expected_item.has_children:
+ expected_children = []
+ for course1_item_child in expected_item.children:
+ expected_children.append(
+ course1_item_child.map_into_course(actual_course_key)
+ )
+ self.assertEqual(expected_children, actual_item.children)
+
+ def assertAssetEqual(self, expected_course_key, expected_asset, actual_course_key, actual_asset):
+ for key in self.ignored_asset_keys:
+ if key in expected_asset:
+ del expected_asset[key]
+ if key in actual_asset:
+ del actual_asset[key]
+
+ expected_key = expected_asset.pop('asset_key')
+ actual_key = actual_asset.pop('asset_key')
+ self.assertEqual(expected_key.map_into_course(actual_course_key), actual_key)
+ self.assertEqual(expected_key, actual_key.map_into_course(expected_course_key))
+
+ expected_filename = expected_asset.pop('filename')
+ actual_filename = actual_asset.pop('filename')
+ self.assertEqual(expected_key.to_deprecated_string(), expected_filename)
+ self.assertEqual(actual_key.to_deprecated_string(), actual_filename)
+ self.assertEqual(expected_asset, actual_asset)
+
+ def _assertAssetsEqual(self, expected_course_key, expected_assets, actual_course_key, actual_assets):
+ self.assertEqual(len(expected_assets), len(actual_assets))
+
+ actual_assets_map = {asset['asset_key']: asset for asset in actual_assets}
+ for expected_item in expected_assets:
+ actual_item = actual_assets_map[expected_item['asset_key'].map_into_course(actual_course_key)]
+ self.assertAssetEqual(expected_course_key, expected_item, actual_course_key, actual_item)
+
+ def assertAssetsEqual(self, expected_store, expected_course_key, actual_store, actual_course_key):
+ """
+ Assert that the course assets identified by ``expected_course_key`` in ``expected_store`` and
+ ``actual_course_key`` in ``actual_store`` are identical, allowing for differences related
+ to their being from different course keys.
+ """
+
+ expected_content, expected_count = expected_store.get_all_content_for_course(expected_course_key)
+ actual_content, actual_count = actual_store.get_all_content_for_course(actual_course_key)
+
+ self.assertEqual(expected_count, actual_count)
+ self._assertAssetsEqual(expected_course_key, expected_content, actual_course_key, actual_content)
+
+ expected_thumbs = expected_store.get_all_content_thumbnails_for_course(expected_course_key)
+ actual_thumbs = actual_store.get_all_content_thumbnails_for_course(actual_course_key)
+
+ self._assertAssetsEqual(expected_course_key, expected_thumbs, actual_course_key, actual_thumbs)
From 8e0189bcf7f90ea4dd1798c2a8e39c8798ced8c7 Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Wed, 16 Jul 2014 08:53:58 -0400
Subject: [PATCH 2/7] Skip DraftVersioningModuleStore tests
---
.../test_cross_modulestore_import_export.py | 22 ++++++++++++++++---
common/lib/xmodule/xmodule/tests/__init__.py | 14 +++++++++++-
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
index 6763838fde..23a8c21f6b 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
@@ -16,8 +16,7 @@ import ddt
import itertools
import random
from contextlib import contextmanager, nested
-from functools import partial
-from unittest import TestCase
+from unittest import SkipTest
from shutil import rmtree
from tempfile import mkdtemp
from opaque_keys.edx.locations import SlashSeparatedCourseKey
@@ -46,9 +45,23 @@ class MemoryCache(object):
self._data = {}
def get(self, key, default=None):
+ """
+ Get a key from the cache.
+
+ Args:
+ key: The key to update.
+ default: The value to return if the key hasn't been set previously.
+ """
return self._data.get(key, default)
def set(self, key, value):
+ """
+ Set a key in the cache.
+
+ Args:
+ key: The key to update.
+ value: The value change the key to.
+ """
self._data[key] = value
@@ -113,6 +126,8 @@ class VersioningModulestoreBuilder(object):
contentstore: The contentstore that this modulestore should use to store
all of its assets.
"""
+ # pylint: disable=unreachable
+ raise SkipTest("DraftVersioningModuleStore doesn't yet support the same interface as the rest of the modulestores")
doc_store_config = dict(
db='modulestore{}'.format(random.randint(0, 10000)),
collection='split_module',
@@ -142,6 +157,7 @@ class VersioningModulestoreBuilder(object):
def __repr__(self):
return 'SplitModulestoreBuilder()'
+
class MixedModulestoreBuilder(object):
"""
A builder class for a MixedModuleStore.
@@ -221,6 +237,7 @@ MODULESTORE_SETUPS = (
CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),)
COURSE_DATA_NAMES = ('toy', 'test_unicode')
+
@ddt.ddt
class CrossStoreXMLRoundtrip(CourseComparisonTest):
"""
@@ -242,7 +259,6 @@ class CrossStoreXMLRoundtrip(CourseComparisonTest):
))
@ddt.unpack
def test_round_trip(self, source_builder, dest_builder, source_content_builder, dest_content_builder, course_data_name):
- self.maxDiff = None
source_course_key = SlashSeparatedCourseKey('source', 'course', 'key')
dest_course_key = SlashSeparatedCourseKey('dest', 'course', 'key')
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index e7c8e06912..eaedb54da7 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -176,6 +176,12 @@ class CourseComparisonTest(unittest.TestCase):
self.field_exclusions.add((usage_id, field_name))
def ignore_asset_key(self, key_name):
+ """
+ Add an asset key to the list of keys to be ignored when comparing assets.
+
+ Args:
+ key_name: The name of the key to ignore.
+ """
self.ignored_asset_keys.add(key_name)
def assertCoursesEqual(self, expected_store, expected_course_key, actual_store, actual_course_key):
@@ -253,6 +259,9 @@ class CourseComparisonTest(unittest.TestCase):
self.assertEqual(expected_children, actual_item.children)
def assertAssetEqual(self, expected_course_key, expected_asset, actual_course_key, actual_asset):
+ """
+ Assert that two assets are equal, allowing for differences related to their being from different courses.
+ """
for key in self.ignored_asset_keys:
if key in expected_asset:
del expected_asset[key]
@@ -270,7 +279,10 @@ class CourseComparisonTest(unittest.TestCase):
self.assertEqual(actual_key.to_deprecated_string(), actual_filename)
self.assertEqual(expected_asset, actual_asset)
- def _assertAssetsEqual(self, expected_course_key, expected_assets, actual_course_key, actual_assets):
+ def _assertAssetsEqual(self, expected_course_key, expected_assets, actual_course_key, actual_assets): # pylint: disable=invalid-name
+ """
+ Private helper method for assertAssetsEqual
+ """
self.assertEqual(len(expected_assets), len(actual_assets))
actual_assets_map = {asset['asset_key']: asset for asset in actual_assets}
From 4e67c2a9b393cf921b1da8127d4c953c3a65917e Mon Sep 17 00:00:00 2001
From: Calen Pennington
Date: Wed, 16 Jul 2014 09:08:13 -0400
Subject: [PATCH 3/7] Add the manual testing course for testing xml
import/export across modulestores
---
.../test_cross_modulestore_import_export.py | 2 +-
common/lib/xmodule/xmodule/tests/__init__.py | 2 +-
.../about/overview.html | 47 +++++
.../about/short_description.html | 0
.../0edc5e96bacc449eaae9fe8b5be75994.xml | 15 ++
.../0f9fc83830694ffc8fead4fcf7aa8d39.xml | 15 ++
.../473ab1a4c5064c919ae483ecc86a8df7.xml | 5 +
.../5b18cea952a84fb1a34db345a476175a.xml | 20 +++
.../90e352a0316a4dc68baaca0308f002e3.xml | 26 +++
.../c0a3b20598dc4ebba704a51a309251db.xml | 15 ++
.../e13a327cc54f4e56bda64c5e9627edd1.xml | 15 ++
.../2df1fe87253549199f30cabb19e14b7c.xml | 3 +
.../3d216a50442f4cd5a1d4171c68f13f58.xml | 3 +
.../5bb7a5ab824f460580a756a4f347377c.xml | 3 +
.../60989ac1589241ed9dbca0f2070276fd.xml | 40 +++++
.../a0178ff300514e24829e239604dce12c.xml | 3 +
.../a64a6f63f75d430aa71e6ce113c5b4d2.xml | 8 +
.../ab97a6dbfafd48868c36bed4c8c5391d.xml | 3 +
.../be8a64868f2f460ea490e001a25e588d.xml | 3 +
.../ce2fd991d84b4a5ca75350eb8e350627.xml | 3 +
.../d68c2861c10a4c9d92a679b4cfc0f924.xml | 7 +
.../3b04d935c8d945c3900708279fb24892.xml | 93 ++++++++++
.../b3aa2db471a9412fbc96302f2e5ea983.xml | 99 +++++++++++
.../ecfe4fa774ff48d089ae84daa1f6cc75.xml | 93 ++++++++++
.../data/manual-testing-complete/course.xml | 1 +
.../manual-testing-complete/course/2014.xml | 13 ++
.../ecfe4fa774ff48d089ae84daa1f6cc75.xml | 93 ++++++++++
.../fd120d9503334db5a2ce53c2e0162128.html | 1 +
.../html/fd120d9503334db5a2ce53c2e0162128.xml | 1 +
.../29fe8cca3a99412c801c5b9ce53780c5.xml | 20 +++
.../370cfd42f00843578f50e32545356ef1.xml | 22 +++
.../5904f30aba7a41d3ab1609e58bb5a6c2.xml | 3 +
.../5c49dcff565848b8a4834ee8b3beeebc.xml | 21 +++
.../7e39859879f24e8689861024d4f7cb1e.xml | 33 ++++
.../006fa27802794e20a0e04e146a7e4e66.xml | 3 +
.../084d74e8d1494817ac5661ae5893fd61.xml | 3 +
.../0b73083f132c4ecb8ea24a363efcbc68.xml | 3 +
.../11b926ee2cde42259b5165b918de0493.xml | 1 +
.../14d2052331234fd68fdbd984a2ec8396.xml | 3 +
.../1f17ac7c38b2421b8c6027c3652366c6.xml | 3 +
.../32b9f88c4c164dd69ae87a0754516267.xml | 1 +
.../38105f0357534b4999bbc1dcf82c9148.xml | 1 +
.../4755fc0ee7ff4ed19a720b61810064d1.xml | 1 +
.../47735fdcfbf1444dbabbd5a09df1a790.xml | 1 +
.../5887a034ad17480393c5ebca4b8fd1d4.xml | 3 +
.../5bac2ffd65ea4f988bcf474fe93257e6.xml | 1 +
.../7897479a19e24871bbbdc02457aa3c0b.xml | 1 +
.../90c15baa6c004d3f90b43af405968de3.xml | 1 +
.../9a63361c946c4b6486e233d21b4947be.xml | 3 +
.../a4bfead3b0204b15a988d77a5d7f58f2.xml | 1 +
.../ccc9fb7ada404ba0986b3fca4056b831.xml | 1 +
.../82e9d374eeb944489d5decdb6b8fbd76.html | 33 ++++
.../html/82e9d374eeb944489d5decdb6b8fbd76.xml | 1 +
.../info/handouts.html | 1 +
.../manual-testing-complete/info/updates.html | 1 +
.../info/updates.items.json | 1 +
.../lti/b05c0297100f41e888ddadf82b3ce1b2.xml | 1 +
.../policies/2014/grading_policy.json | 1 +
.../policies/2014/policy.json | 1 +
.../policies/assets.json | 1 +
.../009f9b6b79404206a2c316117099fed5.xml | 13 ++
.../11bbd906f12d4cbdbca3050d68cea79f.xml | 32 ++++
.../29fe8cca3a99412c801c5b9ce53780c5.xml | 20 +++
.../2f7da8477d8748f0aad0f7fc2134b84f.xml | 32 ++++
.../35501134637b4c2b8cfe07ba5e6492bb.xml | 33 ++++
.../39bb74b14f674ea1ab25f52bac9eb83b.xml | 92 ++++++++++
.../3a1e2c95f8a54bc98b65e9373dafc86e.xml | 26 +++
.../427a1515100a4d08b959ba5852a1630d.xml | 6 +
.../440e298f5b804377bc31d06a3f783a5c.xml | 21 +++
.../45c30a061c3f473499a32d7e14f4a750.xml | 21 +++
.../45c317cb93d447f293ce982a2eccd77d.xml | 31 ++++
.../5c49dcff565848b8a4834ee8b3beeebc.xml | 21 +++
.../5e3b300353974a42875052a21823e39b.xml | 20 +++
.../70257670b9dd45c8afd5abd3c9fe606f.xml | 32 ++++
.../7eff0366d53045c1b423ee988d344a13.xml | 92 ++++++++++
.../8a5a7653bf804a968c17d398cb91aa4e.xml | 17 ++
.../9497fab9cc6f4187ba17d23731b614bf.xml | 14 ++
.../9992192a14ce47fcb71f77d7e77c821c.xml | 22 +++
.../a473cecce312487a8339995bde24be53.xml | 11 ++
.../a5ca9b0f09cc4798bb53e5840e625301.xml | 10 ++
.../b6b80c6b383f4c3c80efc32b968368dc.xml | 37 ++++
.../c2ee6e8917fe4c97ac99d7b616ff0b89.xml | 162 ++++++++++++++++++
.../d0bdd7c3487d4f94b7bcd207f110c20a.xml | 31 ++++
.../d30b3812201a46af88d30bd4c7c009e6.xml | 15 ++
.../dcada38f8944442799ac2fed42201641.xml | 11 ++
.../eb248c5260254ce094c157ffb8352d16.xml | 15 ++
.../01a66b857fad4221b01f742ec0e86c49.xml | 1 +
.../0aa765632f4d4b84ad8d96f41cec5825.xml | 3 +
.../0cd40e13b4a045aba07d4f626c8b32de.xml | 3 +
.../0d0e69d08e95436fbbf1be4c6dfec48a.xml | 1 +
.../0e86943b2cb54a56a1a14c13da3f388d.xml | 3 +
.../158963ff751747e3945f3834dd30e5fb.xml | 3 +
.../17d39634673a4151b6f337a5c216eb52.xml | 3 +
.../1dd8f4178f2a4b9cb09f37c5d6230f9d.xml | 1 +
.../21b1a19dbe264a98b06ce9567a3e4171.xml | 3 +
.../2db8efb4573842c8854648a3ac9e52a4.xml | 3 +
.../2e23db09e8b5472cbae9624208e3e1d7.xml | 3 +
.../30e61af5a8d74833bb66e19ccea1e5d8.xml | 1 +
.../313d1bb9031c4d529c7018248d6fff52.xml | 1 +
.../345d618ca88944668d86586f83bff338.xml | 1 +
.../363603b2c7d34d26a3baa9de29be2211.xml | 3 +
.../3aa3759deca94e1783b7dc9c148cd483.xml | 3 +
.../3b115f75b12b42699a3148ea0ffee76b.xml | 3 +
.../4026dba735fe450faf81f766c63bef8b.xml | 1 +
.../47b7432f998c45abad9f79effeda60bf.xml | 3 +
.../4cd0b5b3dd3343b5937fea80ec5005cc.xml | 3 +
.../4eadf76912cd436b9d698c8759784d8d.xml | 3 +
.../508e5fa820b643b1a329e2ab5dd59393.xml | 3 +
.../589cf38cfb22450a901818b48d4b7ff5.xml | 3 +
.../5c33f2c2b3aa45f5bfbf7bf7f9bcb2ff.xml | 3 +
.../613404c6df1445ed81f12883dde30a41.xml | 3 +
.../67c9c6cd94fe498fb1dc965c35eca3d3.xml | 3 +
.../6c4c10b89cc449fcbde0006c47e3ee26.xml | 3 +
.../7942fe1a06aa439092aabe3615d91b15.xml | 3 +
.../7a598df4cc4345138332c1d19ecd963d.xml | 3 +
.../7fe9686bb8fe4edba75867ddd1d7b1c5.xml | 3 +
.../8ad25eec767f40ae81bcc7555778c91e.xml | 3 +
.../948737f132254c2aa65f6024edee7e68.xml | 3 +
.../aa338c6acd1e498ca8d4ccf6ded72f9b.xml | 3 +
.../ac37154b78454cecaad080221cf1dbd5.xml | 1 +
.../b7ebe0f048e9466e9ef32e7815fb5a93.xml | 3 +
.../b857bc7d37c74e38b51741340da91dcd.xml | 3 +
.../bb9bc5a7d0d945cea2a77e3d85f28c41.xml | 3 +
.../bba59b360c344a1db0eb3239e2381484.xml | 1 +
.../bd0b30ef8bea48ff86559be1cbe2fa49.xml | 1 +
.../c18b834531e14a3fb070dabf16380541.xml | 3 +
.../c59bd9a5a7ec4b31a95515c14bb9f552.xml | 3 +
.../c681c865017d4c0ea931f7345aa79277.xml | 3 +
.../c76b46a765e24c1f9f51de2b49131be0.xml | 1 +
.../ca6fc483ef064fa7b275f9e712f041f6.xml | 1 +
.../d0e7879a63a4429fb1223a22443681b9.xml | 3 +
.../d49ee0959c564c8d8b55660ca5fa9bcd.xml | 1 +
.../d6d7e96bf6a441a4b4e7c7eac0d0e573.xml | 3 +
.../d7d631967807476485aa26ba0c39a992.xml | 3 +
.../d912a92ed03d4f818661a1636b8a6f9b.xml | 3 +
.../dc2b88afd57241f9bcd2dcbd03c6c2f3.xml | 1 +
.../f09502cf408742c2aa3c92705ab1dce7.xml | 3 +
.../f0e52b8eec5647ffb6013aef62b3d309.xml | 1 +
.../f585fca58e5c4fe8ab231a5f62701de3.xml | 3 +
.../f58fd90cbd794cad881692d3b6e5cdbf.xml | 1 +
.../f9372e3b199a4986a46c8d18e094b931.xml | 3 +
.../fbbe37e2980c4f0d96b0b8ac45c0378b.xml | 3 +
.../data/manual-testing-complete/static/1.pdf | Bin 0 -> 194007 bytes
.../Screen Shot 2013-04-16 at 1.43.36 PM.png | Bin 0 -> 8733 bytes
.../static/subs_OEoXaMPEzfM.srt.sjson | 143 ++++++++++++++++
.../8e4cce2b4aaf4ba28b1220804619e41f.html | 1 +
.../0a1602f0f191422ba5ed727f903627b2.xml | 3 +
.../0b693c2547674645a8afd0be4c57b8b7.xml | 1 +
.../0b73083f132c4ecb8ea24a363efcbc68.xml | 3 +
.../18b50987e2d84bcca2cfc74ef6b25275.xml | 3 +
.../193fbc9bb9184ba685d01430cb2025d3.xml | 3 +
.../1f17ac7c38b2421b8c6027c3652366c6.xml | 3 +
.../224cff129b784243a79f08dd7203151c.xml | 3 +
.../3a7305c59c254ce9814093f98b913e8a.xml | 3 +
.../4502126328484ed58c87e7ba3b0fa21d.xml | 3 +
.../4aeec80b652c4cc2bf38c248d6440c9f.xml | 4 +
.../50c89b9bf3bc40c2bb723fc7d1c756d1.xml | 3 +
.../53142c50e5284f9798e8e0cab8471528.xml | 3 +
.../536b3582ce354e9da79f157077172bb3.xml | 3 +
.../572f898249da49bc9ee426804302aa24.xml | 3 +
.../5887a034ad17480393c5ebca4b8fd1d4.xml | 3 +
.../6b1a4d545905465ca491f0115b6e759b.xml | 3 +
.../6b54ae89dc554f77b4dd94e1d9911df6.xml | 3 +
.../6cb018f4cc884e98b9df621b4cc54a29.xml | 3 +
.../81323bb6c1cd4b83b8cd1e9b8fb119a7.xml | 3 +
.../856a9709ac974c92b539710bb64fb4c5.xml | 3 +
.../93113ad02cac43659c0e1833880408f3.xml | 3 +
.../999c7e94329144cdb844aec9671fc8ca.xml | 3 +
.../99ab6b79e2b6426f9bc7e07e8540a655.xml | 3 +
.../a99ab4f608de41e9927d281dcfbd4343.xml | 3 +
.../b34ef5e4257b4a34a60c2679aeeb5455.xml | 3 +
.../bcb995ca53934a7b8aee27f81d96d189.xml | 3 +
.../be87d2061b8f41be87293fcc643514c7.xml | 3 +
.../c641d8fe821440bea1781287d05fdc82.xml | 3 +
.../c7c39a80da5f4ac4935ab83790528063.xml | 3 +
.../c87ff358615846b08b85f66ebce94fb3.xml | 3 +
.../cd6b719c0f9c46e7abd2d44c37d49cd8.xml | 3 +
.../da4f07aceaf542f5b1807c779a719067.xml | 3 +
.../ddd289f35194456eaa470a3fd0d08ca2.xml | 3 +
.../de8fb6b7e0904de3a39db9d4bfaab6a2.xml | 3 +
.../e34798bf546a4178ab76afe3a5f729af.xml | 3 +
.../e81c7ddcf5434387a2a6163ca973520c.xml | 3 +
.../e8a308821e204375905bedc227fa769f.xml | 3 +
.../e9c213fb60134b93a3b1b4ba882cb1a7.xml | 3 +
.../ec70919ea9c24ed192e37e7d022d4133.xml | 3 +
.../f3387cc263aa4e2e9070b6954a9c1b90.xml | 3 +
.../37ec61cf011c429db8cb5f49b47681f7.xml | 1 +
.../5d607a74f7214eaa92079c3195e0badb.xml | 1 +
188 files changed, 2039 insertions(+), 2 deletions(-)
create mode 100644 common/test/data/manual-testing-complete/about/overview.html
create mode 100644 common/test/data/manual-testing-complete/about/short_description.html
create mode 100644 common/test/data/manual-testing-complete/annotatable/0edc5e96bacc449eaae9fe8b5be75994.xml
create mode 100644 common/test/data/manual-testing-complete/annotatable/0f9fc83830694ffc8fead4fcf7aa8d39.xml
create mode 100644 common/test/data/manual-testing-complete/annotatable/473ab1a4c5064c919ae483ecc86a8df7.xml
create mode 100644 common/test/data/manual-testing-complete/annotatable/5b18cea952a84fb1a34db345a476175a.xml
create mode 100644 common/test/data/manual-testing-complete/annotatable/90e352a0316a4dc68baaca0308f002e3.xml
create mode 100644 common/test/data/manual-testing-complete/annotatable/c0a3b20598dc4ebba704a51a309251db.xml
create mode 100644 common/test/data/manual-testing-complete/annotatable/e13a327cc54f4e56bda64c5e9627edd1.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/2df1fe87253549199f30cabb19e14b7c.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/3d216a50442f4cd5a1d4171c68f13f58.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/5bb7a5ab824f460580a756a4f347377c.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/60989ac1589241ed9dbca0f2070276fd.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/a0178ff300514e24829e239604dce12c.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/a64a6f63f75d430aa71e6ce113c5b4d2.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/ab97a6dbfafd48868c36bed4c8c5391d.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/be8a64868f2f460ea490e001a25e588d.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/ce2fd991d84b4a5ca75350eb8e350627.xml
create mode 100644 common/test/data/manual-testing-complete/chapter/d68c2861c10a4c9d92a679b4cfc0f924.xml
create mode 100644 common/test/data/manual-testing-complete/combinedopenended/3b04d935c8d945c3900708279fb24892.xml
create mode 100644 common/test/data/manual-testing-complete/combinedopenended/b3aa2db471a9412fbc96302f2e5ea983.xml
create mode 100644 common/test/data/manual-testing-complete/combinedopenended/ecfe4fa774ff48d089ae84daa1f6cc75.xml
create mode 100644 common/test/data/manual-testing-complete/course.xml
create mode 100644 common/test/data/manual-testing-complete/course/2014.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/combinedopenended/ecfe4fa774ff48d089ae84daa1f6cc75.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/html/fd120d9503334db5a2ce53c2e0162128.html
create mode 100644 common/test/data/manual-testing-complete/drafts/html/fd120d9503334db5a2ce53c2e0162128.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/problem/29fe8cca3a99412c801c5b9ce53780c5.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/problem/370cfd42f00843578f50e32545356ef1.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/problem/5904f30aba7a41d3ab1609e58bb5a6c2.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/problem/5c49dcff565848b8a4834ee8b3beeebc.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/problem/7e39859879f24e8689861024d4f7cb1e.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/006fa27802794e20a0e04e146a7e4e66.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/084d74e8d1494817ac5661ae5893fd61.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/0b73083f132c4ecb8ea24a363efcbc68.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/11b926ee2cde42259b5165b918de0493.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/14d2052331234fd68fdbd984a2ec8396.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/1f17ac7c38b2421b8c6027c3652366c6.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/32b9f88c4c164dd69ae87a0754516267.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/38105f0357534b4999bbc1dcf82c9148.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/4755fc0ee7ff4ed19a720b61810064d1.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/47735fdcfbf1444dbabbd5a09df1a790.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/5887a034ad17480393c5ebca4b8fd1d4.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/5bac2ffd65ea4f988bcf474fe93257e6.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/7897479a19e24871bbbdc02457aa3c0b.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/90c15baa6c004d3f90b43af405968de3.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/9a63361c946c4b6486e233d21b4947be.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/a4bfead3b0204b15a988d77a5d7f58f2.xml
create mode 100644 common/test/data/manual-testing-complete/drafts/vertical/ccc9fb7ada404ba0986b3fca4056b831.xml
create mode 100644 common/test/data/manual-testing-complete/html/82e9d374eeb944489d5decdb6b8fbd76.html
create mode 100644 common/test/data/manual-testing-complete/html/82e9d374eeb944489d5decdb6b8fbd76.xml
create mode 100644 common/test/data/manual-testing-complete/info/handouts.html
create mode 100644 common/test/data/manual-testing-complete/info/updates.html
create mode 100644 common/test/data/manual-testing-complete/info/updates.items.json
create mode 100644 common/test/data/manual-testing-complete/lti/b05c0297100f41e888ddadf82b3ce1b2.xml
create mode 100644 common/test/data/manual-testing-complete/policies/2014/grading_policy.json
create mode 100644 common/test/data/manual-testing-complete/policies/2014/policy.json
create mode 100644 common/test/data/manual-testing-complete/policies/assets.json
create mode 100644 common/test/data/manual-testing-complete/problem/009f9b6b79404206a2c316117099fed5.xml
create mode 100644 common/test/data/manual-testing-complete/problem/11bbd906f12d4cbdbca3050d68cea79f.xml
create mode 100644 common/test/data/manual-testing-complete/problem/29fe8cca3a99412c801c5b9ce53780c5.xml
create mode 100644 common/test/data/manual-testing-complete/problem/2f7da8477d8748f0aad0f7fc2134b84f.xml
create mode 100644 common/test/data/manual-testing-complete/problem/35501134637b4c2b8cfe07ba5e6492bb.xml
create mode 100644 common/test/data/manual-testing-complete/problem/39bb74b14f674ea1ab25f52bac9eb83b.xml
create mode 100644 common/test/data/manual-testing-complete/problem/3a1e2c95f8a54bc98b65e9373dafc86e.xml
create mode 100644 common/test/data/manual-testing-complete/problem/427a1515100a4d08b959ba5852a1630d.xml
create mode 100644 common/test/data/manual-testing-complete/problem/440e298f5b804377bc31d06a3f783a5c.xml
create mode 100644 common/test/data/manual-testing-complete/problem/45c30a061c3f473499a32d7e14f4a750.xml
create mode 100644 common/test/data/manual-testing-complete/problem/45c317cb93d447f293ce982a2eccd77d.xml
create mode 100644 common/test/data/manual-testing-complete/problem/5c49dcff565848b8a4834ee8b3beeebc.xml
create mode 100644 common/test/data/manual-testing-complete/problem/5e3b300353974a42875052a21823e39b.xml
create mode 100644 common/test/data/manual-testing-complete/problem/70257670b9dd45c8afd5abd3c9fe606f.xml
create mode 100644 common/test/data/manual-testing-complete/problem/7eff0366d53045c1b423ee988d344a13.xml
create mode 100644 common/test/data/manual-testing-complete/problem/8a5a7653bf804a968c17d398cb91aa4e.xml
create mode 100644 common/test/data/manual-testing-complete/problem/9497fab9cc6f4187ba17d23731b614bf.xml
create mode 100644 common/test/data/manual-testing-complete/problem/9992192a14ce47fcb71f77d7e77c821c.xml
create mode 100644 common/test/data/manual-testing-complete/problem/a473cecce312487a8339995bde24be53.xml
create mode 100644 common/test/data/manual-testing-complete/problem/a5ca9b0f09cc4798bb53e5840e625301.xml
create mode 100644 common/test/data/manual-testing-complete/problem/b6b80c6b383f4c3c80efc32b968368dc.xml
create mode 100644 common/test/data/manual-testing-complete/problem/c2ee6e8917fe4c97ac99d7b616ff0b89.xml
create mode 100644 common/test/data/manual-testing-complete/problem/d0bdd7c3487d4f94b7bcd207f110c20a.xml
create mode 100644 common/test/data/manual-testing-complete/problem/d30b3812201a46af88d30bd4c7c009e6.xml
create mode 100644 common/test/data/manual-testing-complete/problem/dcada38f8944442799ac2fed42201641.xml
create mode 100644 common/test/data/manual-testing-complete/problem/eb248c5260254ce094c157ffb8352d16.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/01a66b857fad4221b01f742ec0e86c49.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/0aa765632f4d4b84ad8d96f41cec5825.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/0cd40e13b4a045aba07d4f626c8b32de.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/0d0e69d08e95436fbbf1be4c6dfec48a.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/0e86943b2cb54a56a1a14c13da3f388d.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/158963ff751747e3945f3834dd30e5fb.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/17d39634673a4151b6f337a5c216eb52.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/1dd8f4178f2a4b9cb09f37c5d6230f9d.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/21b1a19dbe264a98b06ce9567a3e4171.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/2db8efb4573842c8854648a3ac9e52a4.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/2e23db09e8b5472cbae9624208e3e1d7.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/30e61af5a8d74833bb66e19ccea1e5d8.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/313d1bb9031c4d529c7018248d6fff52.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/345d618ca88944668d86586f83bff338.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/363603b2c7d34d26a3baa9de29be2211.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/3aa3759deca94e1783b7dc9c148cd483.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/3b115f75b12b42699a3148ea0ffee76b.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/4026dba735fe450faf81f766c63bef8b.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/47b7432f998c45abad9f79effeda60bf.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/4cd0b5b3dd3343b5937fea80ec5005cc.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/4eadf76912cd436b9d698c8759784d8d.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/508e5fa820b643b1a329e2ab5dd59393.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/589cf38cfb22450a901818b48d4b7ff5.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/5c33f2c2b3aa45f5bfbf7bf7f9bcb2ff.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/613404c6df1445ed81f12883dde30a41.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/67c9c6cd94fe498fb1dc965c35eca3d3.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/6c4c10b89cc449fcbde0006c47e3ee26.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/7942fe1a06aa439092aabe3615d91b15.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/7a598df4cc4345138332c1d19ecd963d.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/7fe9686bb8fe4edba75867ddd1d7b1c5.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/8ad25eec767f40ae81bcc7555778c91e.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/948737f132254c2aa65f6024edee7e68.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/aa338c6acd1e498ca8d4ccf6ded72f9b.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/ac37154b78454cecaad080221cf1dbd5.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/b7ebe0f048e9466e9ef32e7815fb5a93.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/b857bc7d37c74e38b51741340da91dcd.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/bb9bc5a7d0d945cea2a77e3d85f28c41.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/bba59b360c344a1db0eb3239e2381484.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/bd0b30ef8bea48ff86559be1cbe2fa49.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/c18b834531e14a3fb070dabf16380541.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/c59bd9a5a7ec4b31a95515c14bb9f552.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/c681c865017d4c0ea931f7345aa79277.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/c76b46a765e24c1f9f51de2b49131be0.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/ca6fc483ef064fa7b275f9e712f041f6.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/d0e7879a63a4429fb1223a22443681b9.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/d49ee0959c564c8d8b55660ca5fa9bcd.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/d6d7e96bf6a441a4b4e7c7eac0d0e573.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/d7d631967807476485aa26ba0c39a992.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/d912a92ed03d4f818661a1636b8a6f9b.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/dc2b88afd57241f9bcd2dcbd03c6c2f3.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/f09502cf408742c2aa3c92705ab1dce7.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/f0e52b8eec5647ffb6013aef62b3d309.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/f585fca58e5c4fe8ab231a5f62701de3.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/f58fd90cbd794cad881692d3b6e5cdbf.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/f9372e3b199a4986a46c8d18e094b931.xml
create mode 100644 common/test/data/manual-testing-complete/sequential/fbbe37e2980c4f0d96b0b8ac45c0378b.xml
create mode 100644 common/test/data/manual-testing-complete/static/1.pdf
create mode 100644 common/test/data/manual-testing-complete/static/Screen Shot 2013-04-16 at 1.43.36 PM.png
create mode 100644 common/test/data/manual-testing-complete/static/subs_OEoXaMPEzfM.srt.sjson
create mode 100644 common/test/data/manual-testing-complete/tabs/8e4cce2b4aaf4ba28b1220804619e41f.html
create mode 100644 common/test/data/manual-testing-complete/vertical/0a1602f0f191422ba5ed727f903627b2.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/0b693c2547674645a8afd0be4c57b8b7.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/0b73083f132c4ecb8ea24a363efcbc68.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/18b50987e2d84bcca2cfc74ef6b25275.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/193fbc9bb9184ba685d01430cb2025d3.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/1f17ac7c38b2421b8c6027c3652366c6.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/224cff129b784243a79f08dd7203151c.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/3a7305c59c254ce9814093f98b913e8a.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/4502126328484ed58c87e7ba3b0fa21d.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/4aeec80b652c4cc2bf38c248d6440c9f.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/50c89b9bf3bc40c2bb723fc7d1c756d1.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/53142c50e5284f9798e8e0cab8471528.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/536b3582ce354e9da79f157077172bb3.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/572f898249da49bc9ee426804302aa24.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/5887a034ad17480393c5ebca4b8fd1d4.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/6b1a4d545905465ca491f0115b6e759b.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/6b54ae89dc554f77b4dd94e1d9911df6.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/6cb018f4cc884e98b9df621b4cc54a29.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/81323bb6c1cd4b83b8cd1e9b8fb119a7.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/856a9709ac974c92b539710bb64fb4c5.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/93113ad02cac43659c0e1833880408f3.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/999c7e94329144cdb844aec9671fc8ca.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/99ab6b79e2b6426f9bc7e07e8540a655.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/a99ab4f608de41e9927d281dcfbd4343.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/b34ef5e4257b4a34a60c2679aeeb5455.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/bcb995ca53934a7b8aee27f81d96d189.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/be87d2061b8f41be87293fcc643514c7.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/c641d8fe821440bea1781287d05fdc82.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/c7c39a80da5f4ac4935ab83790528063.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/c87ff358615846b08b85f66ebce94fb3.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/cd6b719c0f9c46e7abd2d44c37d49cd8.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/da4f07aceaf542f5b1807c779a719067.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/ddd289f35194456eaa470a3fd0d08ca2.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/de8fb6b7e0904de3a39db9d4bfaab6a2.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/e34798bf546a4178ab76afe3a5f729af.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/e81c7ddcf5434387a2a6163ca973520c.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/e8a308821e204375905bedc227fa769f.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/e9c213fb60134b93a3b1b4ba882cb1a7.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/ec70919ea9c24ed192e37e7d022d4133.xml
create mode 100644 common/test/data/manual-testing-complete/vertical/f3387cc263aa4e2e9070b6954a9c1b90.xml
create mode 100644 common/test/data/manual-testing-complete/video/37ec61cf011c429db8cb5f49b47681f7.xml
create mode 100644 common/test/data/manual-testing-complete/word_cloud/5d607a74f7214eaa92079c3195e0badb.xml
diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
index 23a8c21f6b..0ea2fae369 100644
--- a/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
+++ b/common/lib/xmodule/xmodule/modulestore/tests/test_cross_modulestore_import_export.py
@@ -235,7 +235,7 @@ MODULESTORE_SETUPS = (
MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]),
)
CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),)
-COURSE_DATA_NAMES = ('toy', 'test_unicode')
+COURSE_DATA_NAMES = ('toy', 'manual-testing-complete')
@ddt.ddt
diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py
index eaedb54da7..824652fcf1 100644
--- a/common/lib/xmodule/xmodule/tests/__init__.py
+++ b/common/lib/xmodule/xmodule/tests/__init__.py
@@ -239,7 +239,7 @@ class CourseComparisonTest(unittest.TestCase):
self.assertEqual(
exp_value,
actual_value,
- "Field {} doesn't match between usages {} and {}: {!r} != {!r}".format(
+ "Field {!r} doesn't match between usages {} and {}: {!r} != {!r}".format(
field_name,
expected_item.scope_ids.usage_id,
actual_item.scope_ids.usage_id,
diff --git a/common/test/data/manual-testing-complete/about/overview.html b/common/test/data/manual-testing-complete/about/overview.html
new file mode 100644
index 0000000000..33911ae1ee
--- /dev/null
+++ b/common/test/data/manual-testing-complete/about/overview.html
@@ -0,0 +1,47 @@
+
+
About This Course
+
Include your long course description here. The long course description should contain 150-400 words.
+
+
This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.
+
+
+
+
Prerequisites
+
Add information about course prerequisites here.
+
+
+
+
Course Staff
+
+
+
+
+
+
Staff Member #1
+
Biography of instructor/staff member #1
+
+
+
+
+
+
+
+
Staff Member #2
+
Biography of instructor/staff member #2
+
+
+
+
+
+
Frequently Asked Questions
+
+
Do I need to buy a textbook?
+
No, a free online version of Chemistry: Principles, Patterns, and Applications, First Edition by Bruce Averill and Patricia Eldredge will be available, though you can purchase a printed version (published by FlatWorld Knowledge) if you’d like.
+
+
+
+
Question #2
+
Your answer would be displayed here.
+
+
+
diff --git a/common/test/data/manual-testing-complete/about/short_description.html b/common/test/data/manual-testing-complete/about/short_description.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/test/data/manual-testing-complete/annotatable/0edc5e96bacc449eaae9fe8b5be75994.xml b/common/test/data/manual-testing-complete/annotatable/0edc5e96bacc449eaae9fe8b5be75994.xml
new file mode 100644
index 0000000000..2984d94bd4
--- /dev/null
+++ b/common/test/data/manual-testing-complete/annotatable/0edc5e96bacc449eaae9fe8b5be75994.xml
@@ -0,0 +1,15 @@
+
+
+
Enter your (optional) instructions for the exercise in HTML format.
+
Annotations are specified by an <annotation> tag which may may have the following attributes:
+
+
title (optional). Title of the annotation. Defaults to Commentary if omitted.
+
body (required). Text of the annotation.
+
problem (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have problem="0".
+
highlight (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.
+
+
+
Add your HTML with annotation spans here.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sodales laoreet est, egestas gravida felis egestas nec. Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.
+
Nulla facilisi. Pellentesque id vestibulum libero. Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.
Enter your (optional) instructions for the exercise in HTML format.
+
Annotations are specified by an <annotation> tag which may may have the following attributes:
+
+
title (optional). Title of the annotation. Defaults to Commentary if omitted.
+
body (required). Text of the annotation.
+
problem (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have problem="0".
+
highlight (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.
+
+
+
Add your HTML with annotation spans here.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sodales laoreet est, egestas gravida felis egestas nec. Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.
+
Nulla facilisi. Pellentesque id vestibulum libero. Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.
This page is required to post a comment These are one of the tags. Thanks
+
Check this. This is something else. Specialized problems are advanced problems such as annotations, open response assessments, and word clouds. These problems are available through the Advanced component
Enter your (optional) instructions for the exercise in HTML format.
+
Annotations are specified by an <annotation> tag which may may have the following attributes:
+
+
title (optional). Title of the annotation. Defaults to Commentary if omitted.
+
body (required). Text of the annotation.
+
problem (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have problem="0".
+
highlight (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sodales laoreet est, egestas gravida felis egestas nec. Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.
+
Nulla facilisi. Pellentesque id vestibulum libero. Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.
+ Hour 1 has four separate (two-part) annotation questions. To complete an Annotation Exercise in this course, hover your mouse over each highlighted portion of the focus text. When the Instructor prompt appears, read the question and click "Reply to Annotation." This will take you to a two-part task: an open-ended response question, and a question that will be answered by choosing one of multiple semantic tags.
+
+
Each of these exercises, one per hour, is meant to improve your understanding of the given text (in this case, Hour 1 Text C) by helping you analyze the context after you finish your slow reading of the text. But these exercises of annotation after slow reading do much more than that. They will build up your ability to understand not only the context but also [[1]] all other texts that contain similar contexts and [[2]] all the texts you will be reading in general. I can make this big promise because the texts that you are analyzing are part of a system, and the systematic thinking that went into the original texts can be decoded by analyzing the building blocks of the system. The way you analyze these building blocks is by trying to figure out how they are connected to the other building blocks in the system. That is what these annotation exercises are all about: they help you figure out the connections. And the more things you figure out, the more mental connections you can make on your own. But the exercise of making such connections through your annotations is a gradual process, and you need to be patient with yourself. You will get better and better at it, I am sure. The more connections you are able to make, the more powerful will be your reading ability. That is the beauty of analyzing something that was created by way of systematic thinking in an ancient tradition that took many centuries to develop (I estimate at least ten centuries). The tradition actually helps you think about the tradition. It will help you figure things out.
+
By now you have seen that I really like the expression figure out: it puts the emphasis on reading out of the text, not into the text. When you read into the text, you lose sight of the system that had built that text.
+
In the next exercise, I will switch metaphors in describing the system that had built the text. In the present exercise, I have been using the metaphor of building a structure with building blocks. In the next exercise I will start using the metaphor of weaving a fabric.
+
(Here is a working definition of metaphor: it is an expression of meaning where one thing is substituted for another thing. For example, when we use the metaphor “thread of thought,” the idea of a thread, as used by someone who is weaving or sewing, is substituted for the idea of a way of thinking. For another example: when Nietzsche speaks about reading with delicate fingers, the idea of a goldsmith touching gold is substituted for the idea of reading a text with the eyes.)
+
The main goal of this first exercise is to start practicing the art of slow reading. The best way to make a start, I think, is to read a story within a story. The inner story in this exercise is the story of the hero Hēraklēs, as retold by Agamemnon and as quoted by “Homer” in lines 78-138 of Iliad XIX. The outer story is the story of the Iliad itself, as retold by “Homer.”
+
So the story within a story is 60 lines long, while the story of the Iliad itself is over 15,000 lines long. Your task in this exercise is to do a close reading of the short story of 60 lines, which is embedded in the long story of the Iliad.
+
You can perform this task even if you haven’t started reading the Iliad, since I can summarize for you all 15,000+ lines of this huge epic in just three sentences here:
+
+
+
Achilles experiences a cosmic kind of anger, mēnis, after being insulted in a quarrel with Agamemnon, who is superior to Achilles socially but is inferior to him as a hero.
+
+
As Achilles sits out the war, staying in his shelter, the Achaeans (= Danaans = Argives) are in danger of becoming the losers while the Trojans become the winners.
+
+
The Achaeans are demoralized, and many of their leaders are wounded, including Agamemnon, who finally decides to settle his quarrel with Achilles.
+
+
+
Now that you have the “big picture” of the Iliad as the outer story, you can have a very interesting experience as you do your slow reading of the inner story, contained in the 60 lines spoken by Agamemnon and telling the story of Hēraklēs.
+
Here is something to keep in mind. The research of the great literary critic I. A. Richards, who was once a professor at Harvard University (he retired in 1963 and died in 1979), shows that the relationship of any kind of an outer story to any kind of an inner story is not necessarily a “one-way street,” as it were, leading from the outer into the inner story: there can also be a “two-way street,” with the inner story communicating with the outer story as well as the other way around. Where I use the expressions “outer story” and “inner story,” Richards used the terms “tenor” and “vehicle.” I have always found those terms rather forbidding, but, as you see, they stand for things that are fairly easy to explain.
+
+
|76 Then Agamemnon, the king of men, spoke up at their meeting, |77 right there from the place where he was sitting, not even standing up in the middle of the assembly. |78 “Near and dear ones,” said he, “Danaan [= Achaean] heroes, attendants [therapontes] of Arēs! |79 It is a good thing to listen when a man stands up to speak, and it is not seemly |80 to speak in relay after him. It would be hard for someone to do that, even if he is a practiced speaker. |81 For how could any man in an assembly either hear anything when there is an uproar |82 or say anything? Even a public speaker who speaks clearly will be disconcerted by it. |83 What I will do is to make a declaration addressed to [Achilles] the son of Peleus. As for the rest of you |84 Argives [= Achaeans], you should understand and know well, each one of you, the words [mūthos] that I say for the record. |85 By now the Achaeans have been saying these words [mūthos] to me many times, |86 and they have been blaming me. But I am not responsible [aitios]. |87 No, those who are really responsible are Zeus and Fate [Moira] and the Fury [Erinys] who roams in the mist. |88 They are the ones who, at the public assembly, had put savage derangement [atē] into my thinking [phrenes] |89 on that day when I myself deprived Achilles of his honorific portion [geras]. |90 But what could I do? The god is the one who brings everything to its fulfillment [teleutân]. |91 That goddess atē, senior daughter of Zeus - she makes everyone veer off-course [aâsthai], |92 that disastrous one [oulomenē], the one who has delicate steps. She never makes contact with the ground of the threshold, |93 never even going near it, but instead she hovers over the heads of men, bringing harm to mortals. |94 In her harmfulness, she has incapacitated others as well [besides me], and I have in mind one person in particular. |95 Yes, once upon a time even Zeus veered off-course [aâsthai], who is said to be the best |96 among men and gods. Even he |97 was deceived; Hērā did it, with her devious ways of thinking, female that she is. |98 It happened on the day when the mighty Hērakleēs |99 was about to be born of Alkmene in Thebes, the city garlanded by good walls. |100 He [= Zeus], making a formal declaration [eukhesthai], spoke up at a meeting of all the gods and said: |101 “hear me, all gods and all goddesses, |102 and let me say to you what the heart [thūmos] in my chest tells me to say. |103 Today the goddess who presides over the pains of childbirth, Eileithuia, will help bring forth a man into the light, |104 revealing him, and he will be king over all the people who live around him. |105 He comes from an ancestral line of men who are descended from blood that comes from me.” |106 Thinking devious thoughts, the goddess Hērā addressed him [= Zeus]: |107 “You will be mistaken, and you will not be able to make a fulfillment [telos] of the words [mūthos] that you have spoken for the record. |108 But come, Olympian god, swear for me a binding oath: |109 swear that he will really be king over all the people who live around him, |110 I mean, the one who on this day shall fall to the ground between the legs of a woman |111 who is descended from men who come from your line of ancestry, from blood that comes from you.” |112 So she spoke. And Zeus did not at all notice [noeîn] her devious thinking, |113 but he swore a great oath. And right then and there, he veered off-course [aâsthai] in a big way. |114 Meanwhile, Hērā sped off, leaving the ridges of Olympus behind, |115 and swiftly she reached Achaean Argos. She knew that she would find there |116 the strong wife of Sthenelos son of Perseus. |117 She was pregnant with a dear son, and she was in her eighth month. |118 And she brought him forth into the light, even though he was still one month short. |119 Meanwhile she put a pause on the time of delivery for Alkmene, holding back the divine powers of labor, the Eileithuiai. |120 And then she herself went to tell the news to Zeus the son of Kronos, saying: |121 “Zeus the father, you with the gleaming thunderbolt, I will put a word into your thoughts: |122 there has just been born a man, a noble one, who will be king over the Argives. |123 He is Eurystheus son of Sthenelos son of Perseus. |124 He is from your line of ancestry, and it is not unseemly for him to be king over the Argives.” |125 So she spoke, and he was struck in his mind [phrēn] with a sharp sorrow [akhos]. |126 And right away she grabbed the goddess atē by the head - that head covered with luxuriant curls - |127 since he was angry in his thinking [phrenes], and he swore a binding oath |128 that never will she come to Olympus and to the starry sky |129 never again will she come back, that goddess atē, who makes everyone veer off-course [aâsthai]. |130 And so saying he threw her down from the starry sky, |131 having whirled her around in his hand. And then she [= atē] came to the fields where mortals live and work. |132 He [= Zeus] always mourned the fact that she ever existed, every time he saw how his own dear son |133 was having one of his degrading Labors [āthloi] to work on. |134 So also I [= Agamemnon], while the great Hector, the one with the gleaming helmet, |135 was destroying the Argives [= Achaeans] at the sterns of the beached ships, |136 was not able to keep out of my mind the veering [atē] I experienced once I veered off-course [aâsthai]. |137 But since I did veer off-course [aâsthai] and since Zeus took away from me my thinking, |138 I now want to make amends, and to give untold amounts of compensation.
Enter your (optional) instructions for the exercise in HTML format.
+
Annotations are specified by an <annotation> tag which may may have the following attributes:
+
+
title (optional). Title of the annotation. Defaults to Commentary if omitted.
+
body (required). Text of the annotation.
+
problem (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have problem="0".
+
highlight (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.
+
+
+
Add your HTML with annotation spans here.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sodales laoreet est, egestas gravida felis egestas nec. Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.
+
Nulla facilisi. Pellentesque id vestibulum libero. Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.
Enter your (optional) instructions for the exercise in HTML format.
+
Annotations are specified by an <annotation> tag which may may have the following attributes:
+
+
title (optional). Title of the annotation. Defaults to Commentary if omitted.
+
body (required). Text of the annotation.
+
problem (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have problem="0".
+
highlight (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.
+
+
+
Add your HTML with annotation spans here.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sodales laoreet est, egestas gravida felis egestas nec. Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.
+
Nulla facilisi. Pellentesque id vestibulum libero. Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.
'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author
+
+
+ Write a persuasive essay to a newspaper reflecting your views on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.
+
'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author
+
+
+ Write a persuasive essay to a newspaper reflecting your views on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.
+
'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author
+
+
+ Write a persuasive essay to a newspaper reflecting your views on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.
+
'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author
+
+
+ Write a persuasive essay to a newspaper reflecting your views on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.
+
What is edX? An organization established by MIT and Harvard University that will develop an open-source technology platform to deliver online courses. EdX will support Harvard and MIT faculty in conducting research on teaching and learning on campus through tools that enrich classroom and laboratory experiences. At the same time, edX will also reach learners around the world through online course materials. The edX website will begin by hosting MITx and Harvardx content, with the goal of adding content from other universities interested in joining the platform. edX will also support the Harvard and MIT faculty in conducting research on teaching and learning.
\ No newline at end of file
diff --git a/common/test/data/manual-testing-complete/drafts/html/fd120d9503334db5a2ce53c2e0162128.xml b/common/test/data/manual-testing-complete/drafts/html/fd120d9503334db5a2ce53c2e0162128.xml
new file mode 100644
index 0000000000..36d335e266
--- /dev/null
+++ b/common/test/data/manual-testing-complete/drafts/html/fd120d9503334db5a2ce53c2e0162128.xml
@@ -0,0 +1 @@
+