Commit c1324ec2 by Will Daly

Modified CodeResponse to use XML factory

parent 6e4c418a
......@@ -43,6 +43,8 @@ class ResponseXMLFactory(object):
*script*: The embedded Python script (a string)
*num_responses*: The number of responses to create [DEFAULT: 1]
*num_inputs*: The number of input elements
to create [DEFAULT: 1]
......@@ -53,6 +55,7 @@ class ResponseXMLFactory(object):
question_text = kwargs.get('question_text', '')
explanation_text = kwargs.get('explanation_text', '')
script = kwargs.get('script', None)
num_responses = kwargs.get('num_responses', 1)
num_inputs = kwargs.get('num_inputs', 1)
# The root is <problem>
......@@ -68,16 +71,19 @@ class ResponseXMLFactory(object):
question = etree.SubElement(root, "p")
question.text = question_text
# Add the response
# Add the response(s)
for i in range(0, int(num_responses)):
response_element = self.create_response_element(**kwargs)
root.append(response_element)
# Add input elements
for i in range(0, int(num_inputs)):
for j in range(0, int(num_inputs)):
input_element = self.create_input_element(**kwargs)
if not (None == input_element):
response_element.append(input_element)
# The problem has an explanation of the solution
if explanation_text:
explanation = etree.SubElement(root, "solution")
explanation_div = etree.SubElement(explanation, "div")
explanation_div.set("class", "detailed-solution")
......@@ -271,13 +277,56 @@ class SchematicResponseXMLFactory(ResponseXMLFactory):
class CodeResponseXMLFactory(ResponseXMLFactory):
""" Factory for creating <coderesponse> XML trees """
def build_xml(self, **kwargs):
# Since we are providing an <answer> tag,
# we should override the default behavior
# of including a <solution> tag as well
kwargs['explanation_text'] = None
return super(CodeResponseXMLFactory, self).build_xml(**kwargs)
def create_response_element(self, **kwargs):
""" Create a <coderesponse> XML element """
raise NotImplemented
""" Create a <coderesponse> XML element:
def create_input_element(self, **kwargs):
raise NotImplemented
Uses **kwargs:
*initial_display*: The code that initially appears in the textbox
[DEFAULT: "Enter code here"]
*answer_display*: The answer to display to the student
[DEFAULT: "This is the correct answer!"]
*grader_payload*: A JSON-encoded string sent to the grader
[DEFAULT: empty dict string]
"""
# Get **kwargs
initial_display = kwargs.get("initial_display", "Enter code here")
answer_display = kwargs.get("answer_display", "This is the correct answer!")
grader_payload = kwargs.get("grader_payload", '{}')
# Create the <coderesponse> element
response_element = etree.Element("coderesponse")
codeparam_element = etree.SubElement(response_element, "codeparam")
# Set the initial display text
initial_element = etree.SubElement(codeparam_element, "initial_display")
initial_element.text = str(initial_display)
# Set the answer display text
answer_element = etree.SubElement(codeparam_element, "answer_display")
answer_element.text = str(answer_display)
# Set the grader payload string
grader_element = etree.SubElement(codeparam_element, "grader_payload")
grader_element.text = str(grader_payload)
# Create the input within the response
input_element = etree.SubElement(response_element, "textbox")
input_element.set("mode", "python")
return response_element
def create_input_element(self, **kwargs):
# Since we create this in create_response_element(),
# return None here
return None
class ChoiceResponseXMLFactory(ResponseXMLFactory):
""" Factory for creating <choiceresponse> XML trees """
......
This file is used to test converting file handles to filenames in the capa utility module
......@@ -399,11 +399,19 @@ class StringResponseTest(ResponseTest):
correct_map = problem.grade_answers(input_dict)
self.assertEquals(correct_map.get_hint('1_2_1'), "")
class CodeResponseTest(unittest.TestCase):
'''
Test CodeResponse
TODO: Add tests for external grader messages
'''
class CodeResponseTest(ResponseTest):
from response_xml_factory import CodeResponseXMLFactory
xml_factory_class = CodeResponseXMLFactory
def setUp(self):
super(CodeResponseTest, self).setUp()
grader_payload = json.dumps({"grader": "ps04/grade_square.py"})
self.problem = self.build_problem(initial_display="def square(x):",
answer_display="answer",
grader_payload=grader_payload,
num_responses=2)
@staticmethod
def make_queuestate(key, time):
timestr = datetime.strftime(time, dateformat)
......@@ -413,39 +421,32 @@ class CodeResponseTest(unittest.TestCase):
"""
Simple test of whether LoncapaProblem knows when it's been queued
"""
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
with open(problem_file) as input_file:
test_lcp = lcp.LoncapaProblem(input_file.read(), '1', system=test_system)
answer_ids = sorted(test_lcp.get_question_answers())
answer_ids = sorted(self.problem.get_question_answers())
# CodeResponse requires internal CorrectMap state. Build it now in the unqueued state
cmap = CorrectMap()
for answer_id in answer_ids:
cmap.update(CorrectMap(answer_id=answer_id, queuestate=None))
test_lcp.correct_map.update(cmap)
self.problem.correct_map.update(cmap)
self.assertEquals(test_lcp.is_queued(), False)
self.assertEquals(self.problem.is_queued(), False)
# Now we queue the LCP
cmap = CorrectMap()
for i, answer_id in enumerate(answer_ids):
queuestate = CodeResponseTest.make_queuestate(i, datetime.now())
cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
test_lcp.correct_map.update(cmap)
self.problem.correct_map.update(cmap)
self.assertEquals(test_lcp.is_queued(), True)
self.assertEquals(self.problem.is_queued(), True)
def test_update_score(self):
'''
Test whether LoncapaProblem.update_score can deliver queued result to the right subproblem
'''
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
with open(problem_file) as input_file:
test_lcp = lcp.LoncapaProblem(input_file.read(), '1', system=test_system)
answer_ids = sorted(test_lcp.get_question_answers())
answer_ids = sorted(self.problem.get_question_answers())
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
old_cmap = CorrectMap()
......@@ -464,53 +465,49 @@ class CodeResponseTest(unittest.TestCase):
# Incorrect queuekey, state should not be updated
for correctness in ['correct', 'incorrect']:
test_lcp.correct_map = CorrectMap()
test_lcp.correct_map.update(old_cmap) # Deep copy
self.problem.correct_map = CorrectMap()
self.problem.correct_map.update(old_cmap) # Deep copy
test_lcp.update_score(xserver_msgs[correctness], queuekey=0)
self.assertEquals(test_lcp.correct_map.get_dict(), old_cmap.get_dict()) # Deep comparison
self.problem.update_score(xserver_msgs[correctness], queuekey=0)
self.assertEquals(self.problem.correct_map.get_dict(), old_cmap.get_dict()) # Deep comparison
for answer_id in answer_ids:
self.assertTrue(test_lcp.correct_map.is_queued(answer_id)) # Should be still queued, since message undelivered
self.assertTrue(self.problem.correct_map.is_queued(answer_id)) # Should be still queued, since message undelivered
# Correct queuekey, state should be updated
for correctness in ['correct', 'incorrect']:
for i, answer_id in enumerate(answer_ids):
test_lcp.correct_map = CorrectMap()
test_lcp.correct_map.update(old_cmap)
self.problem.correct_map = CorrectMap()
self.problem.correct_map.update(old_cmap)
new_cmap = CorrectMap()
new_cmap.update(old_cmap)
npoints = 1 if correctness == 'correct' else 0
new_cmap.set(answer_id=answer_id, npoints=npoints, correctness=correctness, msg=grader_msg, queuestate=None)
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i)
self.assertEquals(test_lcp.correct_map.get_dict(), new_cmap.get_dict())
self.problem.update_score(xserver_msgs[correctness], queuekey=1000 + i)
self.assertEquals(self.problem.correct_map.get_dict(), new_cmap.get_dict())
for j, test_id in enumerate(answer_ids):
if j == i:
self.assertFalse(test_lcp.correct_map.is_queued(test_id)) # Should be dequeued, message delivered
self.assertFalse(self.problem.correct_map.is_queued(test_id)) # Should be dequeued, message delivered
else:
self.assertTrue(test_lcp.correct_map.is_queued(test_id)) # Should be queued, message undelivered
self.assertTrue(self.problem.correct_map.is_queued(test_id)) # Should be queued, message undelivered
def test_recentmost_queuetime(self):
'''
Test whether the LoncapaProblem knows about the time of queue requests
'''
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
with open(problem_file) as input_file:
test_lcp = lcp.LoncapaProblem(input_file.read(), '1', system=test_system)
answer_ids = sorted(test_lcp.get_question_answers())
answer_ids = sorted(self.problem.get_question_answers())
# CodeResponse requires internal CorrectMap state. Build it now in the unqueued state
cmap = CorrectMap()
for answer_id in answer_ids:
cmap.update(CorrectMap(answer_id=answer_id, queuestate=None))
test_lcp.correct_map.update(cmap)
self.problem.correct_map.update(cmap)
self.assertEquals(test_lcp.get_recentmost_queuetime(), None)
self.assertEquals(self.problem.get_recentmost_queuetime(), None)
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
cmap = CorrectMap()
......@@ -519,18 +516,18 @@ class CodeResponseTest(unittest.TestCase):
latest_timestamp = datetime.now()
queuestate = CodeResponseTest.make_queuestate(1000 + i, latest_timestamp)
cmap.update(CorrectMap(answer_id=answer_id, queuestate=queuestate))
test_lcp.correct_map.update(cmap)
self.problem.correct_map.update(cmap)
# Queue state only tracks up to second
latest_timestamp = datetime.strptime(datetime.strftime(latest_timestamp, dateformat), dateformat)
self.assertEquals(test_lcp.get_recentmost_queuetime(), latest_timestamp)
self.assertEquals(self.problem.get_recentmost_queuetime(), latest_timestamp)
def test_convert_files_to_filenames(self):
'''
Test whether file objects are converted to filenames without altering other structures
'''
problem_file = os.path.join(os.path.dirname(__file__), "test_files/coderesponse.xml")
problem_file = os.path.join(os.path.dirname(__file__), "test_files/filename_convert_test.txt")
with open(problem_file) as fp:
answers_with_file = {'1_2_1': 'String-based answer',
'1_3_1': ['answer1', 'answer2', 'answer3'],
......@@ -540,7 +537,6 @@ class CodeResponseTest(unittest.TestCase):
self.assertEquals(answers_converted['1_3_1'], ['answer1', 'answer2', 'answer3'])
self.assertEquals(answers_converted['1_4_1'], [fp.name, fp.name])
class ChoiceResponseTest(ResponseTest):
from response_xml_factory import ChoiceResponseXMLFactory
xml_factory_class = ChoiceResponseXMLFactory
......
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