-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Create task to process a batch of grade computations. #14752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,11 @@ | |
| import itertools | ||
| from mock import patch, MagicMock | ||
| import pytz | ||
| import six | ||
| from util.date_utils import to_timestamp | ||
|
|
||
| from openedx.core.djangoapps.content.block_structure.exceptions import BlockStructureNotFound | ||
| from student.models import anonymous_id_for_user | ||
| from student.models import CourseEnrollment, anonymous_id_for_user | ||
| from student.tests.factories import UserFactory | ||
| from track.event_transaction_utils import ( | ||
| create_new_event_transaction_id, | ||
|
|
@@ -29,22 +30,17 @@ | |
| from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum | ||
| from lms.djangoapps.grades.models import PersistentCourseGrade, PersistentSubsectionGrade | ||
| from lms.djangoapps.grades.signals.signals import PROBLEM_WEIGHTED_SCORE_CHANGED | ||
| from lms.djangoapps.grades.tasks import recalculate_subsection_grade_v3, RECALCULATE_GRADE_DELAY | ||
| from lms.djangoapps.grades.tasks import ( | ||
| compute_grades_for_course, | ||
| recalculate_subsection_grade_v3, | ||
| RECALCULATE_GRADE_DELAY | ||
| ) | ||
|
|
||
|
|
||
| @patch.dict(settings.FEATURES, {'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS': False}) | ||
| @ddt.ddt | ||
| class RecalculateSubsectionGradeTest(ModuleStoreTestCase): | ||
| class HasCourseWithProblemsMixin(object): | ||
| """ | ||
| Ensures that the recalculate subsection grade task functions as expected when run. | ||
| Mixin to provide tests with a sample course with graded subsections | ||
| """ | ||
| ENABLED_SIGNALS = ['course_published', 'pre_publish'] | ||
|
|
||
| def setUp(self): | ||
| super(RecalculateSubsectionGradeTest, self).setUp() | ||
| self.user = UserFactory() | ||
| PersistentGradesEnabledFlag.objects.create(enabled_for_all_courses=True, enabled=True) | ||
|
|
||
| def set_up_course(self, enable_persistent_grades=True, create_multiple_subsections=False): | ||
| """ | ||
| Configures the course for this test. | ||
|
|
@@ -100,6 +96,20 @@ def set_up_course(self, enable_persistent_grades=True, create_multiple_subsectio | |
| _ = anonymous_id_for_user(self.user, self.course.id) | ||
| # pylint: enable=attribute-defined-outside-init,no-member | ||
|
|
||
|
|
||
| @patch.dict(settings.FEATURES, {'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS': False}) | ||
| @ddt.ddt | ||
| class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTestCase): | ||
| """ | ||
| Ensures that the recalculate subsection grade task functions as expected when run. | ||
| """ | ||
| ENABLED_SIGNALS = ['course_published', 'pre_publish'] | ||
|
|
||
| def setUp(self): | ||
| super(RecalculateSubsectionGradeTest, self).setUp() | ||
| self.user = UserFactory() | ||
| PersistentGradesEnabledFlag.objects.create(enabled_for_all_courses=True, enabled=True) | ||
|
|
||
| @contextmanager | ||
| def mock_get_score(self, score=MagicMock(grade=1.0, max_grade=2.0)): | ||
| """ | ||
|
|
@@ -363,3 +373,47 @@ def _assert_retry_not_called(self, mock_retry): | |
| Verifies the task was not retried. | ||
| """ | ||
| self.assertFalse(mock_retry.called) | ||
|
|
||
|
|
||
| @ddt.ddt | ||
| class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase): | ||
| """ | ||
| Test compute_grades_for_course task. | ||
| """ | ||
|
|
||
| ENABLED_SIGNALS = ['course_published', 'pre_publish'] | ||
|
|
||
| def setUp(self): | ||
| super(ComputeGradesForCourseTest, self).setUp() | ||
| self.users = [UserFactory.create() for _ in xrange(12)] | ||
| self.set_up_course() | ||
| for user in self.users: | ||
| CourseEnrollment.enroll(user, self.course.id) | ||
|
|
||
| @ddt.data(*xrange(0, 12, 3)) | ||
| def test_behavior(self, batch_size): | ||
| result = compute_grades_for_course.delay( | ||
| course_key=six.text_type(self.course.id), | ||
| batch_size=batch_size, | ||
| offset=4 | ||
| ) | ||
| self.assertTrue(result.successful) | ||
| self.assertEqual( | ||
| PersistentCourseGrade.objects.filter(course_id=self.course.id).count(), | ||
| min(batch_size, 8) # No more than 8 due to offset | ||
| ) | ||
| self.assertEqual( | ||
| PersistentSubsectionGrade.objects.filter(course_id=self.course.id).count(), | ||
| min(batch_size, 8) # No more than 8 due to offset | ||
| ) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question for @edx/educator-neem devs: It looks like PersistentCourseGrade objects aren't getting created. Is there still a toggle for that which needs to get turned on for the test?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to suggest the write-if-engaged work might be at fault here, but that hasn't merged yet. I'd suggest manually stepping through the code in this test.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's working fine now. I think I must have changed something since I updated the test. Not sure what it was.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I reintroduced testing the PersistentCourseGrade count. |
||
|
|
||
| @ddt.data(*xrange(1, 12, 3)) | ||
| def test_database_calls(self, batch_size): | ||
| per_user_queries = 16 * min(batch_size, 6) # No more than 6 due to offset | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "per_user_queries" is not referenced. I think you meant to reference it on line 413, so I'll make that change in #14731 because I need to up the number of query calls there. |
||
| with self.assertNumQueries(3 + 16 * min(batch_size, 6)): | ||
| with check_mongo_calls(1): | ||
| compute_grades_for_course.delay( | ||
| course_key=six.text_type(self.course.id), | ||
| batch_size=batch_size, | ||
| offset=6, | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you update this function signature to take
kwargsinstead? Passingkwargsshould now be the best practice for all tasks we create in the future. Otherwise, we always run into backward compatibility issues as @efischer19 and I have encountered. (I was thinking of adding a parameter to this task, related to my PR, but ran into this issue.)Also, I believe the task isn't retried unless it's encased in a
try-exceptblock with a call toself.retry. Please consult @efischer19 on this tomorrow.