From 6cd7e08c1b78e143416ec9ea426b9d7f4d6097b9 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 25 Jul 2014 13:21:31 -0400 Subject: [PATCH] Assert course equality in bulk, so that all differences are displayed --- common/lib/xmodule/xmodule/tests/__init__.py | 168 ++++++++++++------- 1 file changed, 112 insertions(+), 56 deletions(-) diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index eec7aa7265..b79494eda3 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -12,6 +12,7 @@ import os import pprint import unittest +from contextlib import contextmanager from mock import Mock from path import path @@ -174,12 +175,64 @@ def map_references(value, field, actual_course_key): return value -class CourseComparisonTest(unittest.TestCase): +class BulkAssertionManager(object): + """ + This provides a facility for making a large number of assertions, and seeing all of + the failures at once, rather than only seeing single failures. + """ + def __init__(self, test_case): + self._equal_expected = [] + self._equal_actual = [] + self._test_case = test_case + + def assertEqual(self, expected, actual, description=None): + if description is None: + description = "{!r} does not equal {!r}".format(expected, actual) + if expected != actual: + self._equal_expected.append((description, expected)) + self._equal_actual.append((description, actual)) + + def run_assertions(self): + self._test_case.assertEqual(self._equal_expected, self._equal_actual) + + +class BulkAssertionTest(unittest.TestCase): + """ + This context manager provides a BulkAssertionManager to assert with, + and then calls `run_assertions` at the end of the block to validate all + of the assertions. + """ + + def setUp(self, *args, **kwargs): + super(BulkAssertionTest, self).setUp(*args, **kwargs) + self._manager = None + + @contextmanager + def bulk_assertions(self): + if self._manager: + yield + else: + try: + self._manager = BulkAssertionManager(self) + yield + finally: + self._manager.run_assertions() + self._manager = None + + def assertEqual(self, expected, actual, message=None): + if self._manager is not None: + self._manager.assertEqual(expected, actual, message) + else: + super(BulkAssertionTest, self).assertEqual(expected, actual, message) + + +class CourseComparisonTest(BulkAssertionTest): """ Mixin that has methods for comparing courses for equality. """ def setUp(self): + super(CourseComparisonTest, self).setUp() self.field_exclusions = set() self.ignored_asset_keys = set() @@ -235,68 +288,69 @@ class CourseComparisonTest(unittest.TestCase): self._assertCoursesEqual(expected_items, actual_items, actual_course_key, expect_drafts=True) def _assertCoursesEqual(self, expected_items, actual_items, actual_course_key, expect_drafts=False): - self.assertEqual(len(expected_items), len(actual_items)) + with self.bulk_assertions(): + self.assertEqual(len(expected_items), len(actual_items)) - actual_item_map = { - item.location.block_id: item - for item in actual_items - } + actual_item_map = { + item.location.block_id: item + for item in actual_items + } - for expected_item in expected_items: - actual_item_location = actual_course_key.make_usage_key(expected_item.category, expected_item.location.block_id) - # split and old mongo use different names for the course root but we don't know which - # modulestore actual's come from here; so, assume old mongo and if that fails, assume split - 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.block_id) - # must be split - if actual_item is None and expected_item.location.category == 'course': - actual_item_location = actual_item_location.replace(name='course') + for expected_item in expected_items: + actual_item_location = actual_course_key.make_usage_key(expected_item.category, expected_item.location.block_id) + # split and old mongo use different names for the course root but we don't know which + # modulestore actual's come from here; so, assume old mongo and if that fails, assume split + 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.block_id) - self.assertIsNotNone(actual_item, u'cannot find {} in {}'.format(actual_item_location, actual_item_map)) + # must be split + if actual_item is None and expected_item.location.category == 'course': + actual_item_location = actual_item_location.replace(name='course') + actual_item = actual_item_map.get(actual_item_location.block_id) + self.assertIsNotNone(actual_item, u'cannot find {} in {}'.format(actual_item_location, actual_item_map)) - # compare fields - self.assertEqual(expected_item.fields, actual_item.fields) + # compare fields + self.assertEqual(expected_item.fields, actual_item.fields) - for field_name, field in expected_item.fields.iteritems(): - if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions: - continue + for field_name, field in expected_item.fields.iteritems(): + if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions: + continue - if (None, 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 + # Children are handled specially + if field_name == 'children': + continue - exp_value = map_references(field.read_from(expected_item), field, actual_course_key) - actual_value = field.read_from(actual_item) - self.assertEqual( - exp_value, - actual_value, - "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, + exp_value = map_references(field.read_from(expected_item), field, actual_course_key) + actual_value = field.read_from(actual_item) + self.assertEqual( exp_value, actual_value, + "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, + exp_value, + actual_value, + ) ) - ) - # compare children - self.assertEqual(expected_item.has_children, actual_item.has_children) - if expected_item.has_children: - expected_children = [ - (expected_item_child.location.block_type, expected_item_child.location.block_id) - # get_children() rather than children to strip privates from public parents - for expected_item_child in expected_item.get_children() - ] - actual_children = [ - (item_child.location.block_type, item_child.location.block_id) - # get_children() rather than children to strip privates from public parents - for item_child in actual_item.get_children() - ] - self.assertEqual(expected_children, actual_children) + # compare children + self.assertEqual(expected_item.has_children, actual_item.has_children) + if expected_item.has_children: + expected_children = [ + (expected_item_child.location.block_type, expected_item_child.location.block_id) + # get_children() rather than children to strip privates from public parents + for expected_item_child in expected_item.get_children() + ] + actual_children = [ + (item_child.location.block_type, item_child.location.block_id) + # get_children() rather than children to strip privates from public parents + for item_child in actual_item.get_children() + ] + self.assertEqual(expected_children, actual_children) def assertAssetEqual(self, expected_course_key, expected_asset, actual_course_key, actual_asset): """ @@ -339,10 +393,12 @@ class CourseComparisonTest(unittest.TestCase): 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) + with self.bulk_assertions(): - 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.assertEqual(expected_count, actual_count) + self._assertAssetsEqual(expected_course_key, expected_content, actual_course_key, actual_content) - self._assertAssetsEqual(expected_course_key, expected_thumbs, actual_course_key, actual_thumbs) + 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)