Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions common/lib/capa/capa/tests/test_responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,46 @@ def check_func(expect, answer_given, options, dynamath):
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
Expand Down
8 changes: 8 additions & 0 deletions common/lib/xmodule/xmodule/capa_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,12 @@ def submit_problem(self, data, override_time=False):
}

try:
# expose the attempt number to a potential python custom grader
# self.lcp.context['attempt'] refers to the attempt number (1-based)
self.lcp.context['attempt'] = self.attempts + 1
correct_map = self.lcp.grade_answers(answers)
# self.attempts refers to the number of attempts that did not
# raise an error (0-based)
self.attempts = self.attempts + 1
self.lcp.done = True
self.set_state_from_lcp()
Expand Down Expand Up @@ -1678,6 +1683,9 @@ def update_correctness(self):
Operates by creating a new correctness map based on the current
state of the LCP, and updating the old correctness map of the LCP.
"""
# Make sure that the attempt number is always at least 1 for grading purposes,
# even if the number of attempts have been reset and this problem is regraded.
self.lcp.context['attempt'] = max(self.attempts, 1)
new_correct_map = self.lcp.get_grade_from_current_answers(None)
self.lcp.correct_map.update(new_correct_map)

Expand Down
28 changes: 26 additions & 2 deletions common/lib/xmodule/xmodule/tests/test_capa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,8 @@ def test_submit_problem_correct(self):

# Expect that the number of attempts is incremented by 1
self.assertEqual(module.attempts, 2)
# and that this was considered attempt number 2 for grading purposes
self.assertEqual(module.lcp.context['attempt'], 2)

def test_submit_problem_incorrect(self):

Expand All @@ -668,6 +670,8 @@ def test_submit_problem_incorrect(self):

# Expect that the number of attempts is incremented by 1
self.assertEqual(module.attempts, 1)
# and that this is considered the first attempt
self.assertEqual(module.lcp.context['attempt'], 1)

def test_submit_problem_closed(self):
module = CapaFactory.create(attempts=3)
Expand Down Expand Up @@ -717,8 +721,9 @@ def test_submit_problem_resubmitted_no_randomize(self, rerandomize):

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)
Expand Down Expand Up @@ -854,6 +859,8 @@ def test_submit_problem_error(self):

# Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1)
# but that this was considered attempt number 2 for grading purposes
self.assertEqual(module.lcp.context['attempt'], 2)

def test_submit_problem_error_with_codejail_exception(self):

Expand Down Expand Up @@ -890,6 +897,8 @@ def test_submit_problem_error_with_codejail_exception(self):

# Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1)
# but that this was considered the second attempt for grading purposes
self.assertEqual(module.lcp.context['attempt'], 2)

def test_submit_problem_other_errors(self):
"""
Expand Down Expand Up @@ -959,6 +968,8 @@ def test_submit_problem_error_nonascii(self):

# Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1)
# but that this was considered the second attempt for grading purposes
self.assertEqual(module.lcp.context['attempt'], 2)

def test_submit_problem_error_with_staff_user(self):

Expand Down Expand Up @@ -988,6 +999,8 @@ def test_submit_problem_error_with_staff_user(self):

# Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1)
# but that it was considered the second attempt for grading purposes
self.assertEqual(module.lcp.context['attempt'], 2)

@ddt.data(
("never", True, None, 'submitted'),
Expand Down Expand Up @@ -1018,6 +1031,7 @@ def test_handle_ajax_show_correctness(self, show_correctness, is_correct, expect

# 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)
Expand Down Expand Up @@ -1093,6 +1107,8 @@ def test_rescore_problem_correct(self):

# Expect that the number of attempts is not incremented
self.assertEqual(module.attempts, 1)
# and that this was considered attempt number 1 for grading purposes
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
Expand All @@ -1107,8 +1123,10 @@ def test_rescore_problem_additional_correct(self):
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 has incremented to 1
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)
Expand All @@ -1128,6 +1146,8 @@ def test_rescore_problem_additional_correct(self):
self.assertEqual(module.correct_map[answer_id]['correctness'], 'correct')
# Expect that the number of attempts is not incremented
self.assertEqual(module.attempts, 1)
# and hence that this was still considered the first attempt for grading purposes
self.assertEqual(module.lcp.context['attempt'], 1)

def test_rescore_problem_incorrect(self):
# make sure it also works when attempts have been reset,
Expand All @@ -1145,6 +1165,8 @@ def test_rescore_problem_incorrect(self):

# Expect that the number of attempts is not incremented
self.assertEqual(module.attempts, 0)
# and that this is treated as the first attempt for grading purposes
self.assertEqual(module.lcp.context['attempt'], 1)

def test_rescore_problem_not_done(self):
# Simulate that the problem is NOT done
Expand Down Expand Up @@ -1176,6 +1198,8 @@ def _rescore_problem_error_helper(self, exception_class):

# Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1)
# and that this was considered the first attempt for grading purposes
self.assertEqual(module.lcp.context['attempt'], 1)

def test_rescore_problem_student_input_error(self):
self._rescore_problem_error_helper(StudentInputError)
Expand Down