Commit dbbc64ff by Eric Fischer

CohortMembership Race Condition Test

By using the before_after library, we can force a race condition to reliably
occur in the CohortMembership.save() method. This unit test will do just that,
and ensure that our race-condition-handling code functions as it should.
parent 08ed3b54
......@@ -4,6 +4,7 @@ Tests for cohorts
# pylint: disable=no-member
import ddt
from mock import call, patch
import before_after
from django.contrib.auth.models import User
from django.db import IntegrityError
......@@ -635,6 +636,45 @@ class TestCohorts(ModuleStoreTestCase):
lambda: cohorts.add_user_to_cohort(first_cohort, "non_existent_username")
)
@patch("openedx.core.djangoapps.course_groups.cohorts.tracker")
def add_user_to_cohorts_race_condition(self, mock_tracker):
"""
Makes use of before_after to force a race condition, in order to
confirm handling of such conditions is done correctly.
"""
course_user = UserFactory(username="Username", email="a@b.com")
course = modulestore().get_course(self.toy_course_key)
CourseEnrollment.enroll(course_user, self.toy_course_key)
first_cohort = CohortFactory(course_id=course.id, name="FirstCohort")
second_cohort = CohortFactory(course_id=course.id, name="SecondCohort")
# This before_after contextmanager allows for reliable reproduction of a race condition.
# It will break before the first save() call creates an entry, and then run add_user_to_cohort again.
# Because this second call will write before control is returned, the first call will be writing stale data.
# This test confirms that the first add_user_to_cohort call can handle this stale read condition properly.
# Proper handling is defined as treating calls as sequential, with write time deciding the order.
with before_after.before_after(
'django.db.models.Model.save',
after_ftn=cohorts.add_user_to_cohort(second_cohort, course_user.username),
autospec=True
):
# This method will read, then break, then try to write stale data.
# It should fail at that, then retry with refreshed data
cohorts.add_user_to_cohort(first_cohort, course_user.username)
mock_tracker.emit.assert_any_call(
"edx.cohort.user_add_requested",
{
"user_id": course_user.id,
"cohort_id": first_cohort.id,
"cohort_name": first_cohort.name,
"previous_cohort_id": second_cohort.id,
"previous_cohort_name": second_cohort.name,
}
)
# Note that the following get() will fail with MultipleObjectsReturned if race condition is not handled.
self.assertEqual(first_cohort.users.get(), course_user)
def test_get_course_cohort_settings(self):
"""
Test that cohorts.get_course_cohort_settings is working as expected.
......
......@@ -120,6 +120,7 @@ django_debug_toolbar==1.3.2
# Used for testing
astroid==1.3.8
before_after==0.1.3
bok-choy==0.4.7
chrono==1.0.2
coverage==4.0
......
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