diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 676746a56f..b4779d6cf4 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -2057,6 +2057,46 @@ class CustomResponseTest(ResponseTest): # pylint: disable=missing-docstring self.assertEqual(correctness, 'incorrect') self.assertEqual(msg, "Message text") + def test_function_code_with_attempt_number(self): + script = textwrap.dedent("""\ + def gradeit(expect, ans, **kwargs): + attempt = kwargs["attempt"] + message = "This is attempt number {}".format(str(attempt)) + return { + 'input_list': [ + { 'ok': True, 'msg': message}, + ] + } + """) + + problem = self.build_problem( + script=script, + cfn="gradeit", + expect="42", + cfn_extra_args="attempt" + ) + + # first attempt + input_dict = {'1_2_1': '42'} + problem.context['attempt'] = 1 + correct_map = problem.grade_answers(input_dict) + + correctness = correct_map.get_correctness('1_2_1') + msg = correct_map.get_msg('1_2_1') + + self.assertEqual(correctness, 'correct') + self.assertEqual(msg, "This is attempt number 1") + + # second attempt + problem.context['attempt'] = 2 + correct_map = problem.grade_answers(input_dict) + + correctness = correct_map.get_correctness('1_2_1') + msg = correct_map.get_msg('1_2_1') + + self.assertEqual(correctness, 'correct') + self.assertEqual(msg, "This is attempt number 2") + def test_multiple_inputs_return_one_status(self): # When given multiple inputs, the 'answer_given' argument # to the check_func() is a list of inputs diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py index 8afe423472..97abc3b4e9 100644 --- a/common/lib/xmodule/xmodule/capa_base.py +++ b/common/lib/xmodule/xmodule/capa_base.py @@ -1216,6 +1216,7 @@ class CapaMixin(ScorableXBlockMixin, CapaFields): } try: + self.lcp.context['attempt'] = self.attempts + 1 correct_map = self.lcp.grade_answers(answers) self.attempts = self.attempts + 1 self.lcp.done = True @@ -1678,6 +1679,7 @@ class CapaMixin(ScorableXBlockMixin, CapaFields): Operates by creating a new correctness map based on the current state of the LCP, and updating the old correctness map of the LCP. """ + self.lcp.context['attempt'] = self.attempts new_correct_map = self.lcp.get_grade_from_current_answers(None) self.lcp.correct_map.update(new_correct_map) diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 5bb1504a26..15cb781cf7 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -650,6 +650,7 @@ class CapaModuleTest(unittest.TestCase): # Expect that the number of attempts is incremented by 1 self.assertEqual(module.attempts, 2) + self.assertEqual(module.lcp.context['attempt'], 2) def test_submit_problem_incorrect(self): @@ -668,6 +669,7 @@ class CapaModuleTest(unittest.TestCase): # Expect that the number of attempts is incremented by 1 self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 1) def test_submit_problem_closed(self): module = CapaFactory.create(attempts=3) @@ -717,8 +719,9 @@ class CapaModuleTest(unittest.TestCase): self.assertEqual(result['success'], 'correct') - # Expect that number of attempts IS incremented + # Expect that number of attempts IS incremented, still same attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 1) def test_submit_problem_queued(self): module = CapaFactory.create(attempts=1) @@ -852,8 +855,9 @@ class CapaModuleTest(unittest.TestCase): self.assertEqual(expected_msg, result['success']) - # Expect that the number of attempts is NOT incremented + # Expect that the number of attempts is NOT incremented, but it is 2nd attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 2) def test_submit_problem_error_with_codejail_exception(self): @@ -888,8 +892,9 @@ class CapaModuleTest(unittest.TestCase): expected_msg = 'Couldn\'t execute jailed code' self.assertEqual(expected_msg, result['success']) - # Expect that the number of attempts is NOT incremented + # Expect that the number of attempts is NOT incremented, but it is 2nd attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 2) def test_submit_problem_other_errors(self): """ @@ -957,8 +962,9 @@ class CapaModuleTest(unittest.TestCase): self.assertEqual(expected_msg, result['success']) - # Expect that the number of attempts is NOT incremented + # Expect that the number of attempts is NOT incremented, but it is 2nd attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 2) def test_submit_problem_error_with_staff_user(self): @@ -986,8 +992,9 @@ class CapaModuleTest(unittest.TestCase): # We DO include traceback information for staff users self.assertIn('Traceback', result['success']) - # Expect that the number of attempts is NOT incremented + # Expect that the number of attempts is NOT incremented, but it is 2nd attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 2) @ddt.data( ("never", True, None, 'submitted'), @@ -1018,6 +1025,7 @@ class CapaModuleTest(unittest.TestCase): # Expect that the number of attempts is incremented by 1 self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 1) def test_reset_problem(self): module = CapaFactory.create(done=True) @@ -1093,6 +1101,7 @@ class CapaModuleTest(unittest.TestCase): # Expect that the number of attempts is not incremented self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 1) def test_rescore_problem_additional_correct(self): # make sure it also works when new correct answer has been added @@ -1107,8 +1116,9 @@ class CapaModuleTest(unittest.TestCase): self.assertEqual(result['success'], 'incorrect') self.assertEqual(module.get_score(), (0, 1)) self.assertEqual(module.correct_map[answer_id]['correctness'], 'incorrect') - # Expect that the number of attempts is incremented + # Expect that the number of attempts is incremented, still same attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 1) # Simulate that after making an incorrect answer to the correct answer # the new calculated score is (1,1) @@ -1126,8 +1136,9 @@ class CapaModuleTest(unittest.TestCase): # Expect that the problem is marked correct and user earned the score self.assertEqual(module.get_score(), (1, 1)) self.assertEqual(module.correct_map[answer_id]['correctness'], 'correct') - # Expect that the number of attempts is not incremented + # Expect that the number of attempts is not incremented, still same attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 1) def test_rescore_problem_incorrect(self): # make sure it also works when attempts have been reset, @@ -1143,8 +1154,9 @@ class CapaModuleTest(unittest.TestCase): # Expect that the problem is marked incorrect self.assertEqual(module.is_correct(), False) - # Expect that the number of attempts is not incremented + # Expect that the number of attempts is not incremented, still same attempt self.assertEqual(module.attempts, 0) + self.assertEqual(module.lcp.context['attempt'], 0) def test_rescore_problem_not_done(self): # Simulate that the problem is NOT done @@ -1174,8 +1186,9 @@ class CapaModuleTest(unittest.TestCase): with self.assertRaises(exception_class): module.rescore(only_if_higher=False) - # Expect that the number of attempts is NOT incremented + # Expect that the number of attempts is NOT incremented, still same attempt self.assertEqual(module.attempts, 1) + self.assertEqual(module.lcp.context['attempt'], 1) def test_rescore_problem_student_input_error(self): self._rescore_problem_error_helper(StudentInputError)