Commit 088d204d by kimth

Added CodeResponse tests

parent 97ab53e7
...@@ -1202,7 +1202,7 @@ class CodeResponse(LoncapaResponse): ...@@ -1202,7 +1202,7 @@ class CodeResponse(LoncapaResponse):
''' '''
Grader reply is a JSON-dump of the following dict Grader reply is a JSON-dump of the following dict
{ 'correct': True/False, { 'correct': True/False,
'score': # TODO -- Partial grading 'score': Numeric value (floating point is okay) to assign to answer
'msg': grader_msg } 'msg': grader_msg }
Returns (valid_score_msg, correct, score, msg): Returns (valid_score_msg, correct, score, msg):
......
...@@ -19,6 +19,7 @@ import capa.calc as calc ...@@ -19,6 +19,7 @@ import capa.calc as calc
import capa.capa_problem as lcp import capa.capa_problem as lcp
from capa.correctmap import CorrectMap from capa.correctmap import CorrectMap
from capa.util import convert_files_to_filenames from capa.util import convert_files_to_filenames
from datetime import datetime
from xmodule import graders, x_module from xmodule import graders, x_module
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from xmodule.graders import Score, aggregate_scores from xmodule.graders import Score, aggregate_scores
...@@ -283,30 +284,61 @@ class CodeResponseTest(unittest.TestCase): ...@@ -283,30 +284,61 @@ class CodeResponseTest(unittest.TestCase):
''' '''
Test CodeResponse Test CodeResponse
''' '''
@staticmethod
def make_queuestate(key, time):
timestr = datetime.strftime(time,'%Y%m%d%H%M%S')
return (key, timestr)
def test_is_queued(self):
'''
Simple test of whether LoncapaProblem knows when it's been queued
'''
problem_file = os.path.dirname(__file__) + "/test_files/coderesponse.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
answer_ids = sorted(test_lcp.get_question_answers().keys())
num_answers = len(answer_ids)
# CodeResponse requires internal CorrectMap state. Build it now in the unqueued state
cmap = CorrectMap()
for i in range(num_answers):
cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=None))
test_lcp.correct_map.update(cmap)
self.assertEquals(test_lcp.is_queued(), False)
# Now we queue the LCP
cmap = CorrectMap()
for i in range(num_answers):
queuestate = CodeResponseTest.make_queuestate(i, datetime.now())
cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
test_lcp.correct_map.update(cmap)
self.assertEquals(test_lcp.is_queued(), True)
def test_update_score(self): def test_update_score(self):
'''
Test whether LoncapaProblem.update_score can deliver queued result to the right subproblem
'''
problem_file = os.path.dirname(__file__) + "/test_files/coderesponse.xml" problem_file = os.path.dirname(__file__) + "/test_files/coderesponse.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs) test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
# CodeResponse requires internal CorrectMap state. Build it now in the 'queued' state
old_cmap = CorrectMap()
answer_ids = sorted(test_lcp.get_question_answers().keys()) answer_ids = sorted(test_lcp.get_question_answers().keys())
numAnswers = len(answer_ids) num_answers = len(answer_ids)
for i in range(numAnswers):
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
old_cmap = CorrectMap()
for i in range(num_answers):
queuekey = 1000 + i queuekey = 1000 + i
queuestate = (queuekey, '') queuestate = CodeResponseTest.make_queuestate(1000+i, datetime.now())
old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate)) old_cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
# TODO: Message format inherited from ExternalResponse # Message format common to external graders
#correct_score_msg = "<edxgrade><awarddetail>EXACT_ANS</awarddetail><message>MESSAGE</message></edxgrade>"
#incorrect_score_msg = "<edxgrade><awarddetail>WRONG_FORMAT</awarddetail><message>MESSAGE</message></edxgrade>"
# New message format common to external graders
correct_score_msg = json.dumps({'correct':True, 'score':1, 'msg':'MESSAGE'}) correct_score_msg = json.dumps({'correct':True, 'score':1, 'msg':'MESSAGE'})
incorrect_score_msg = json.dumps({'correct':False, 'score':0, 'msg':'MESSAGE'}) incorrect_score_msg = json.dumps({'correct':False, 'score':0, 'msg':'MESSAGE'})
xserver_msgs = {'correct': correct_score_msg, xserver_msgs = {'correct': correct_score_msg,
'incorrect': incorrect_score_msg, 'incorrect': incorrect_score_msg,}
}
# Incorrect queuekey, state should not be updated # Incorrect queuekey, state should not be updated
for correctness in ['correct', 'incorrect']: for correctness in ['correct', 'incorrect']:
...@@ -316,12 +348,12 @@ class CodeResponseTest(unittest.TestCase): ...@@ -316,12 +348,12 @@ class CodeResponseTest(unittest.TestCase):
test_lcp.update_score(xserver_msgs[correctness], queuekey=0) test_lcp.update_score(xserver_msgs[correctness], queuekey=0)
self.assertEquals(test_lcp.correct_map.get_dict(), old_cmap.get_dict()) # Deep comparison self.assertEquals(test_lcp.correct_map.get_dict(), old_cmap.get_dict()) # Deep comparison
for i in range(numAnswers): for i in range(num_answers):
self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[i])) # Should be still queued, since message undelivered self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[i])) # Should be still queued, since message undelivered
# Correct queuekey, state should be updated # Correct queuekey, state should be updated
for correctness in ['correct', 'incorrect']: for correctness in ['correct', 'incorrect']:
for i in range(numAnswers): # Target specific answer_id's for i in range(num_answers): # Target specific answer_id's
test_lcp.correct_map = CorrectMap() test_lcp.correct_map = CorrectMap()
test_lcp.correct_map.update(old_cmap) test_lcp.correct_map.update(old_cmap)
...@@ -333,13 +365,51 @@ class CodeResponseTest(unittest.TestCase): ...@@ -333,13 +365,51 @@ class CodeResponseTest(unittest.TestCase):
test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i) test_lcp.update_score(xserver_msgs[correctness], queuekey=1000 + i)
self.assertEquals(test_lcp.correct_map.get_dict(), new_cmap.get_dict()) self.assertEquals(test_lcp.correct_map.get_dict(), new_cmap.get_dict())
for j in range(numAnswers): for j in range(num_answers):
if j == i: if j == i:
self.assertFalse(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be dequeued, message delivered self.assertFalse(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be dequeued, message delivered
else: else:
self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[j])) # Should be queued, message undelivered self.assertTrue(test_lcp.correct_map.is_queued(answer_ids[j])) # 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.dirname(__file__) + "/test_files/coderesponse.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file).read(), '1', system=i4xs)
answer_ids = sorted(test_lcp.get_question_answers().keys())
num_answers = len(answer_ids)
# CodeResponse requires internal CorrectMap state. Build it now in the unqueued state
cmap = CorrectMap()
for i in range(num_answers):
cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=None))
test_lcp.correct_map.update(cmap)
self.assertEquals(test_lcp.get_recentmost_queuetime(), None)
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
cmap = CorrectMap()
answer_ids = sorted(test_lcp.get_question_answers().keys())
num_answers = len(answer_ids)
for i in range(num_answers):
queuekey = 1000 + i
latest_timestamp = datetime.now()
queuestate = CodeResponseTest.make_queuestate(1000+i, latest_timestamp)
cmap.update(CorrectMap(answer_id=answer_ids[i], queuestate=queuestate))
test_lcp.correct_map.update(cmap)
# Queue state only tracks up to second
latest_timestamp = datetime.strptime(datetime.strftime(latest_timestamp,'%Y%m%d%H%M%S'),'%Y%m%d%H%M%S')
self.assertEquals(test_lcp.get_recentmost_queuetime(), latest_timestamp)
def test_convert_files_to_filenames(self): def test_convert_files_to_filenames(self):
'''
Test whether file objects are converted to filenames without altering other structures
'''
problem_file = os.path.dirname(__file__) + "/test_files/coderesponse.xml" problem_file = os.path.dirname(__file__) + "/test_files/coderesponse.xml"
fp = open(problem_file) fp = open(problem_file)
answers_with_file = {'1_2_1': 'String-based answer', answers_with_file = {'1_2_1': 'String-based answer',
......
...@@ -9,91 +9,23 @@ ...@@ -9,91 +9,23 @@
Write a program to compute the square of a number Write a program to compute the square of a number
<coderesponse tests="repeat:2,generate"> <coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/> <textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[ <codeparam>
initial_display = """ <initial_display>def square(x):</initial_display>
def square(n): <answer_display>answer</answer_display>
""" <grader_payload>grader stuff</grader_payload>
</codeparam>
answer = """
def square(n):
return n**2
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testSquare(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: square(%d)'%n
return str(square(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testSquare(0))
elif test == 2: f.write(testSquare(1))
else: f.write(testSquare())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse> </coderesponse>
</text> </text>
<text> <text>
Write a program to compute the cube of a number Write a program to compute the square of a number
<coderesponse tests="repeat:2,generate"> <coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/> <textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[ <codeparam>
initial_display = """ <initial_display>def square(x):</initial_display>
def cube(n): <answer_display>answer</answer_display>
""" <grader_payload>grader stuff</grader_payload>
</codeparam>
answer = """
def cube(n):
return n**3
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testCube(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: cube(%d)'%n
return str(cube(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testCube(0))
elif test == 2: f.write(testCube(1))
else: f.write(testCube())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse> </coderesponse>
</text> </text>
......
<problem>
<text>
<h2>Code response</h2>
<p>
</p>
<text>
Write a program to compute the square of a number
<coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[
initial_display = """
def square(n):
"""
answer = """
def square(n):
return n**2
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testSquare(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: square(%d)'%n
return str(square(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testSquare(0))
elif test == 2: f.write(testSquare(1))
else: f.write(testSquare())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
<text>
Write a program to compute the cube of a number
<coderesponse tests="repeat:2,generate">
<textbox rows="10" cols="70" mode="python"/>
<answer><![CDATA[
initial_display = """
def cube(n):
"""
answer = """
def cube(n):
return n**3
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testCube(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: cube(%d)'%n
return str(cube(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testCube(0))
elif test == 2: f.write(testCube(1))
else: f.write(testCube())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
</text>
</problem>
...@@ -58,7 +58,7 @@ XQUEUE_INTERFACE = { ...@@ -58,7 +58,7 @@ XQUEUE_INTERFACE = {
}, },
"basic_auth": ('anant', 'agarwal'), "basic_auth": ('anant', 'agarwal'),
} }
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
# TODO (cpennington): We need to figure out how envs/test.py can inject things # TODO (cpennington): We need to figure out how envs/test.py can inject things
# into common.py so that we don't have to repeat this sort of thing # into common.py so that we don't have to repeat this sort of thing
......
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