Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
231b8c6a
Commit
231b8c6a
authored
12 years ago
by
David Ormsbee
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #548 from MITx/kimth/limit-queue-dos
Kimth/limit queue dos
parents
18127cf9
377b09e1
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
329 additions
and
172 deletions
+329
-172
common/lib/capa/capa/capa_problem.py
+21
-5
common/lib/capa/capa/correctmap.py
+27
-11
common/lib/capa/capa/inputtypes.py
+2
-2
common/lib/capa/capa/responsetypes.py
+14
-7
common/lib/capa/capa/templates/filesubmission.html
+1
-1
common/lib/capa/capa/templates/textbox.html
+1
-1
common/lib/capa/capa/xqueue_interface.py
+1
-1
common/lib/xmodule/xmodule/capa_module.py
+9
-0
common/lib/xmodule/xmodule/tests/__init__.py
+134
-63
common/lib/xmodule/xmodule/tests/test_files/coderesponse.xml
+11
-79
common/lib/xmodule/xmodule/tests/test_files/coderesponse_externalresponseformat.xml
+101
-0
lms/djangoapps/courseware/module_render.py
+3
-1
lms/envs/common.py
+3
-0
lms/envs/test.py
+1
-1
No files found.
common/lib/capa/capa/capa_problem.py
View file @
231b8c6a
...
@@ -14,6 +14,8 @@ This is used by capa_module.
...
@@ -14,6 +14,8 @@ This is used by capa_module.
from
__future__
import
division
from
__future__
import
division
from
datetime
import
datetime
import
json
import
logging
import
logging
import
math
import
math
import
numpy
import
numpy
...
@@ -32,6 +34,7 @@ from correctmap import CorrectMap
...
@@ -32,6 +34,7 @@ from correctmap import CorrectMap
import
eia
import
eia
import
inputtypes
import
inputtypes
from
util
import
contextualize_text
,
convert_files_to_filenames
from
util
import
contextualize_text
,
convert_files_to_filenames
import
xqueue_interface
# to be replaced with auto-registering
# to be replaced with auto-registering
import
responsetypes
import
responsetypes
...
@@ -202,11 +205,24 @@ class LoncapaProblem(object):
...
@@ -202,11 +205,24 @@ class LoncapaProblem(object):
'''
'''
Returns True if any part of the problem has been submitted to an external queue
Returns True if any part of the problem has been submitted to an external queue
'''
'''
queued
=
False
return
any
(
self
.
correct_map
.
is_queued
(
answer_id
)
for
answer_id
in
self
.
correct_map
)
for
answer_id
in
self
.
correct_map
:
if
self
.
correct_map
.
is_queued
(
answer_id
):
queued
=
True
def
get_recentmost_queuetime
(
self
):
return
queued
'''
Returns a DateTime object that represents the timestamp of the most recent queueing request, or None if not queued
'''
if
not
self
.
is_queued
():
return
None
# Get a list of timestamps of all queueing requests, then convert it to a DateTime object
queuetime_strs
=
[
self
.
correct_map
.
get_queuetime_str
(
answer_id
)
for
answer_id
in
self
.
correct_map
if
self
.
correct_map
.
is_queued
(
answer_id
)]
queuetimes
=
[
datetime
.
strptime
(
qt_str
,
xqueue_interface
.
dateformat
)
for
qt_str
in
queuetime_strs
]
return
max
(
queuetimes
)
def
grade_answers
(
self
,
answers
):
def
grade_answers
(
self
,
answers
):
'''
'''
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/correctmap.py
View file @
231b8c6a
...
@@ -15,7 +15,8 @@ class CorrectMap(object):
...
@@ -15,7 +15,8 @@ class CorrectMap(object):
- msg : string (may have HTML) giving extra message response (displayed below textline or textbox)
- msg : string (may have HTML) giving extra message response (displayed below textline or textbox)
- hint : string (may have HTML) giving optional hint (displayed below textline or textbox, above msg)
- hint : string (may have HTML) giving optional hint (displayed below textline or textbox, above msg)
- hintmode : one of (None,'on_request','always') criteria for displaying hint
- hintmode : one of (None,'on_request','always') criteria for displaying hint
- queuekey : a random integer for xqueue_callback verification
- queuestate : Dict {key:'', time:''} where key is a secret string, and time is a string dump
of a DateTime object in the format '
%
Y
%
m
%
d
%
H
%
M
%
S'. Is None when not queued
Behaves as a dict.
Behaves as a dict.
'''
'''
...
@@ -31,14 +32,15 @@ class CorrectMap(object):
...
@@ -31,14 +32,15 @@ class CorrectMap(object):
def
__iter__
(
self
):
def
__iter__
(
self
):
return
self
.
cmap
.
__iter__
()
return
self
.
cmap
.
__iter__
()
def
set
(
self
,
answer_id
=
None
,
correctness
=
None
,
npoints
=
None
,
msg
=
''
,
hint
=
''
,
hintmode
=
None
,
queuekey
=
None
):
# See the documentation for 'set_dict' for the use of kwargs
def
set
(
self
,
answer_id
=
None
,
correctness
=
None
,
npoints
=
None
,
msg
=
''
,
hint
=
''
,
hintmode
=
None
,
queuestate
=
None
,
**
kwargs
):
if
answer_id
is
not
None
:
if
answer_id
is
not
None
:
self
.
cmap
[
answer_id
]
=
{
'correctness'
:
correctness
,
self
.
cmap
[
answer_id
]
=
{
'correctness'
:
correctness
,
'npoints'
:
npoints
,
'npoints'
:
npoints
,
'msg'
:
msg
,
'msg'
:
msg
,
'hint'
:
hint
,
'hint'
:
hint
,
'hintmode'
:
hintmode
,
'hintmode'
:
hintmode
,
'queue
key'
:
queuekey
,
'queue
state'
:
queuestate
,
}
}
def
__repr__
(
self
):
def
__repr__
(
self
):
...
@@ -52,25 +54,39 @@ class CorrectMap(object):
...
@@ -52,25 +54,39 @@ class CorrectMap(object):
def
set_dict
(
self
,
correct_map
):
def
set_dict
(
self
,
correct_map
):
'''
'''
set internal dict to provided correct_map dict
Set internal dict of CorrectMap to provided correct_map dict
for graceful migration, if correct_map is a one-level dict, then convert it to the new
dict of dicts format.
correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This means that
when the definition of CorrectMap (e.g. its properties) are altered, existing correct_map dict
not coincide with the newest CorrectMap format as defined by self.set.
For graceful migration, feed the contents of each correct map to self.set, rather than
making a direct copy of the given correct_map dict. This way, the common keys between
the incoming correct_map dict and the new CorrectMap instance will be written, while
mismatched keys will be gracefully ignored.
Special migration case:
If correct_map is a one-level dict, then convert it to the new dict of dicts format.
'''
'''
if
correct_map
and
not
(
type
(
correct_map
[
correct_map
.
keys
()[
0
]])
==
dict
):
if
correct_map
and
not
(
type
(
correct_map
[
correct_map
.
keys
()[
0
]])
==
dict
):
self
.
__init__
()
# empty current dict
self
.
__init__
()
# empty current dict
for
k
in
correct_map
:
self
.
set
(
k
,
correct_map
[
k
])
# create new dict entries
for
k
in
correct_map
:
self
.
set
(
k
,
correct_map
[
k
])
# create new dict entries
else
:
else
:
self
.
cmap
=
correct_map
self
.
__init__
()
for
k
in
correct_map
:
self
.
set
(
k
,
**
correct_map
[
k
])
def
is_correct
(
self
,
answer_id
):
def
is_correct
(
self
,
answer_id
):
if
answer_id
in
self
.
cmap
:
return
self
.
cmap
[
answer_id
][
'correctness'
]
==
'correct'
if
answer_id
in
self
.
cmap
:
return
self
.
cmap
[
answer_id
][
'correctness'
]
==
'correct'
return
None
return
None
def
is_queued
(
self
,
answer_id
):
def
is_queued
(
self
,
answer_id
):
return
answer_id
in
self
.
cmap
and
self
.
cmap
[
answer_id
][
'queue
key
'
]
is
not
None
return
answer_id
in
self
.
cmap
and
self
.
cmap
[
answer_id
][
'queue
state
'
]
is
not
None
def
is_right_queuekey
(
self
,
answer_id
,
test_key
):
def
is_right_queuekey
(
self
,
answer_id
,
test_key
):
return
answer_id
in
self
.
cmap
and
self
.
cmap
[
answer_id
][
'queuekey'
]
==
test_key
return
self
.
is_queued
(
answer_id
)
and
self
.
cmap
[
answer_id
][
'queuestate'
][
'key'
]
==
test_key
def
get_queuetime_str
(
self
,
answer_id
):
return
self
.
cmap
[
answer_id
][
'queuestate'
][
'time'
]
def
get_npoints
(
self
,
answer_id
):
def
get_npoints
(
self
,
answer_id
):
npoints
=
self
.
get_property
(
answer_id
,
'npoints'
)
npoints
=
self
.
get_property
(
answer_id
,
'npoints'
)
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/inputtypes.py
View file @
231b8c6a
...
@@ -351,7 +351,7 @@ def filesubmission(element, value, status, render_template, msg=''):
...
@@ -351,7 +351,7 @@ def filesubmission(element, value, status, render_template, msg=''):
if
status
==
'incomplete'
:
# Flag indicating that the problem has been queued, 'msg' is length of queue
if
status
==
'incomplete'
:
# Flag indicating that the problem has been queued, 'msg' is length of queue
status
=
'queued'
status
=
'queued'
queue_len
=
msg
queue_len
=
msg
msg
=
'Submitted to grader.
(Queue length:
%
s)'
%
queue_len
msg
=
'Submitted to grader.
'
context
=
{
'id'
:
eid
,
'state'
:
status
,
'msg'
:
msg
,
'value'
:
value
,
context
=
{
'id'
:
eid
,
'state'
:
status
,
'msg'
:
msg
,
'value'
:
value
,
'queue_len'
:
queue_len
,
'allowed_files'
:
allowed_files
,
'queue_len'
:
queue_len
,
'allowed_files'
:
allowed_files
,
...
@@ -384,7 +384,7 @@ def textbox(element, value, status, render_template, msg=''):
...
@@ -384,7 +384,7 @@ def textbox(element, value, status, render_template, msg=''):
if
status
==
'incomplete'
:
# Flag indicating that the problem has been queued, 'msg' is length of queue
if
status
==
'incomplete'
:
# Flag indicating that the problem has been queued, 'msg' is length of queue
status
=
'queued'
status
=
'queued'
queue_len
=
msg
queue_len
=
msg
msg
=
'Submitted to grader.
(Queue length:
%
s)'
%
queue_len
msg
=
'Submitted to grader.
'
# For CodeMirror
# For CodeMirror
mode
=
element
.
get
(
'mode'
,
'python'
)
mode
=
element
.
get
(
'mode'
,
'python'
)
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/responsetypes.py
View file @
231b8c6a
...
@@ -26,6 +26,7 @@ import xml.sax.saxutils as saxutils
...
@@ -26,6 +26,7 @@ import xml.sax.saxutils as saxutils
# specific library imports
# specific library imports
from
calc
import
evaluator
,
UndefinedVariable
from
calc
import
evaluator
,
UndefinedVariable
from
correctmap
import
CorrectMap
from
correctmap
import
CorrectMap
from
datetime
import
datetime
from
util
import
*
from
util
import
*
from
lxml
import
etree
from
lxml
import
etree
from
lxml.html.soupparser
import
fromstring
as
fromstring_bs
# uses Beautiful Soup!!! FIXME?
from
lxml.html.soupparser
import
fromstring
as
fromstring_bs
# uses Beautiful Soup!!! FIXME?
...
@@ -1026,7 +1027,7 @@ class CodeResponse(LoncapaResponse):
...
@@ -1026,7 +1027,7 @@ class CodeResponse(LoncapaResponse):
TODO: Determines whether in synchronous or asynchronous (queued) mode
TODO: Determines whether in synchronous or asynchronous (queued) mode
'''
'''
xml
=
self
.
xml
xml
=
self
.
xml
self
.
url
=
xml
.
get
(
'url'
,
None
)
# XML can override external resource (grader/queue) URL
self
.
url
=
xml
.
get
(
'url'
,
None
)
#
TODO:
XML can override external resource (grader/queue) URL
self
.
queue_name
=
xml
.
get
(
'queuename'
,
self
.
system
.
xqueue
[
'default_queuename'
])
self
.
queue_name
=
xml
.
get
(
'queuename'
,
self
.
system
.
xqueue
[
'default_queuename'
])
# VS[compat]:
# VS[compat]:
...
@@ -1128,7 +1129,7 @@ class CodeResponse(LoncapaResponse):
...
@@ -1128,7 +1129,7 @@ class CodeResponse(LoncapaResponse):
xheader
=
xqueue_interface
.
make_xheader
(
lms_callback_url
=
self
.
system
.
xqueue
[
'callback_url'
],
xheader
=
xqueue_interface
.
make_xheader
(
lms_callback_url
=
self
.
system
.
xqueue
[
'callback_url'
],
lms_key
=
queuekey
,
lms_key
=
queuekey
,
queue_name
=
self
.
queue_name
)
queue_name
=
self
.
queue_name
)
# Generate body
# Generate body
if
is_list_of_files
(
submission
):
if
is_list_of_files
(
submission
):
self
.
context
.
update
({
'submission'
:
queuekey
})
# For tracking. TODO: May want to record something else here
self
.
context
.
update
({
'submission'
:
queuekey
})
# For tracking. TODO: May want to record something else here
...
@@ -1148,16 +1149,22 @@ class CodeResponse(LoncapaResponse):
...
@@ -1148,16 +1149,22 @@ class CodeResponse(LoncapaResponse):
(
error
,
msg
)
=
qinterface
.
send_to_queue
(
header
=
xheader
,
(
error
,
msg
)
=
qinterface
.
send_to_queue
(
header
=
xheader
,
body
=
json
.
dumps
(
contents
))
body
=
json
.
dumps
(
contents
))
# State associated with the queueing request
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
queuestate
=
{
'key'
:
queuekey
,
'time'
:
qtime
,
}
cmap
=
CorrectMap
()
cmap
=
CorrectMap
()
if
error
:
if
error
:
cmap
.
set
(
self
.
answer_id
,
queue
key
=
None
,
cmap
.
set
(
self
.
answer_id
,
queue
state
=
None
,
msg
=
'Unable to deliver your submission to grader. (Reason:
%
s.) Please try again later.'
%
msg
)
msg
=
'Unable to deliver your submission to grader. (Reason:
%
s.) Please try again later.'
%
msg
)
else
:
else
:
# Queueing mechanism flags:
# Queueing mechanism flags:
# 1) Backend: Non-null CorrectMap['queue
key
'] indicates that the problem has been queued
# 1) Backend: Non-null CorrectMap['queue
state
'] indicates that the problem has been queued
# 2) Frontend: correctness='incomplete' eventually trickles down through inputtypes.textbox
# 2) Frontend: correctness='incomplete' eventually trickles down through inputtypes.textbox
# and .filesubmission to inform the browser to poll the LMS
# and .filesubmission to inform the browser to poll the LMS
cmap
.
set
(
self
.
answer_id
,
queue
key
=
queuekey
,
correctness
=
'incomplete'
,
msg
=
msg
)
cmap
.
set
(
self
.
answer_id
,
queue
state
=
queuestate
,
correctness
=
'incomplete'
,
msg
=
msg
)
return
cmap
return
cmap
...
@@ -1180,7 +1187,7 @@ class CodeResponse(LoncapaResponse):
...
@@ -1180,7 +1187,7 @@ class CodeResponse(LoncapaResponse):
points
=
0
points
=
0
elif
points
>
self
.
maxpoints
[
self
.
answer_id
]:
elif
points
>
self
.
maxpoints
[
self
.
answer_id
]:
points
=
self
.
maxpoints
[
self
.
answer_id
]
points
=
self
.
maxpoints
[
self
.
answer_id
]
oldcmap
.
set
(
self
.
answer_id
,
npoints
=
points
,
correctness
=
correctness
,
msg
=
msg
.
replace
(
' '
,
' '
),
queue
key
=
None
)
# Queuekey
is consumed
oldcmap
.
set
(
self
.
answer_id
,
npoints
=
points
,
correctness
=
correctness
,
msg
=
msg
.
replace
(
' '
,
' '
),
queue
state
=
None
)
# Queuestate
is consumed
else
:
else
:
log
.
debug
(
'CodeResponse: queuekey
%
s does not match for answer_id=
%
s.'
%
(
queuekey
,
self
.
answer_id
))
log
.
debug
(
'CodeResponse: queuekey
%
s does not match for answer_id=
%
s.'
%
(
queuekey
,
self
.
answer_id
))
...
@@ -1197,7 +1204,7 @@ class CodeResponse(LoncapaResponse):
...
@@ -1197,7 +1204,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):
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/templates/filesubmission.html
View file @
231b8c6a
...
@@ -10,7 +10,7 @@
...
@@ -10,7 +10,7 @@
<span
class=
"processing"
id=
"status_${id}"
></span>
<span
class=
"processing"
id=
"status_${id}"
></span>
<span
style=
"display:none;"
class=
"xqueue"
id=
"${id}"
>
${queue_len}
</span>
<span
style=
"display:none;"
class=
"xqueue"
id=
"${id}"
>
${queue_len}
</span>
% endif
% endif
<span
class=
"debug"
>
(${state})
</span>
<span
style=
"display:none;"
class=
"debug"
>
(${state})
</span>
<br/>
<br/>
<span
class=
"message"
>
${msg|n}
</span>
<span
class=
"message"
>
${msg|n}
</span>
<br/>
<br/>
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/templates/textbox.html
View file @
231b8c6a
...
@@ -21,7 +21,7 @@
...
@@ -21,7 +21,7 @@
<div
style=
"display:none;"
name=
"${hidden}"
inputid=
"input_${id}"
/>
<div
style=
"display:none;"
name=
"${hidden}"
inputid=
"input_${id}"
/>
% endif
% endif
<br/>
<br/>
<span
class=
"debug"
>
(${state})
</span>
<span
style=
"display:none;"
class=
"debug"
>
(${state})
</span>
<br/>
<br/>
<span
class=
"message"
>
${msg|n}
</span>
<span
class=
"message"
>
${msg|n}
</span>
<br/>
<br/>
...
...
This diff is collapsed.
Click to expand it.
common/lib/capa/capa/xqueue_interface.py
View file @
231b8c6a
...
@@ -9,7 +9,7 @@ import time
...
@@ -9,7 +9,7 @@ import time
log
=
logging
.
getLogger
(
'mitx.'
+
__name__
)
log
=
logging
.
getLogger
(
'mitx.'
+
__name__
)
dateformat
=
'
%
Y
%
m
%
d
%
H
%
M
%
S'
def
make_hashkey
(
seed
=
None
):
def
make_hashkey
(
seed
=
None
):
'''
'''
...
...
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/capa_module.py
View file @
231b8c6a
...
@@ -462,6 +462,15 @@ class CapaModule(XModule):
...
@@ -462,6 +462,15 @@ class CapaModule(XModule):
self
.
system
.
track_function
(
'save_problem_check_fail'
,
event_info
)
self
.
system
.
track_function
(
'save_problem_check_fail'
,
event_info
)
raise
NotFoundError
(
'Problem must be reset before it can be checked again'
)
raise
NotFoundError
(
'Problem must be reset before it can be checked again'
)
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if
self
.
lcp
.
is_queued
():
current_time
=
datetime
.
datetime
.
now
()
prev_submit_time
=
self
.
lcp
.
get_recentmost_queuetime
()
waittime_between_requests
=
self
.
system
.
xqueue
[
'waittime'
]
if
(
current_time
-
prev_submit_time
)
.
total_seconds
()
<
waittime_between_requests
:
msg
=
'You must wait at least
%
d seconds between submissions'
%
waittime_between_requests
return
{
'success'
:
msg
,
'html'
:
''
}
# Prompts a modal dialog in ajax callback
try
:
try
:
old_state
=
self
.
lcp
.
get_state
()
old_state
=
self
.
lcp
.
get_state
()
lcp_id
=
self
.
lcp
.
problem_id
lcp_id
=
self
.
lcp
.
problem_id
...
...
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/tests/__init__.py
View file @
231b8c6a
...
@@ -19,6 +19,8 @@ import capa.calc as calc
...
@@ -19,6 +19,8 @@ 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
capa.xqueue_interface
import
dateformat
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
...
@@ -35,7 +37,7 @@ i4xs = ModuleSystem(
...
@@ -35,7 +37,7 @@ i4xs = ModuleSystem(
user
=
Mock
(),
user
=
Mock
(),
filestore
=
fs
.
osfs
.
OSFS
(
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
))
+
"/test_files"
),
filestore
=
fs
.
osfs
.
OSFS
(
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
))
+
"/test_files"
),
debug
=
True
,
debug
=
True
,
xqueue
=
{
'interface'
:
None
,
'callback_url'
:
'/'
,
'default_queuename'
:
'testqueue'
},
xqueue
=
{
'interface'
:
None
,
'callback_url'
:
'/'
,
'default_queuename'
:
'testqueue'
,
'waittime'
:
10
},
node_path
=
os
.
environ
.
get
(
"NODE_PATH"
,
"/usr/local/lib/node_modules"
)
node_path
=
os
.
environ
.
get
(
"NODE_PATH"
,
"/usr/local/lib/node_modules"
)
)
)
...
@@ -283,70 +285,139 @@ class CodeResponseTest(unittest.TestCase):
...
@@ -283,70 +285,139 @@ class CodeResponseTest(unittest.TestCase):
'''
'''
Test CodeResponse
Test CodeResponse
'''
'''
def
test_update_score
(
self
):
@staticmethod
problem_file
=
os
.
path
.
dirname
(
__file__
)
+
"/test_files/coderesponse.xml"
def
make_queuestate
(
key
,
time
):
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
problem_file
)
.
read
(),
'1'
,
system
=
i4xs
)
timestr
=
datetime
.
strftime
(
time
,
dateformat
)
return
{
'key'
:
key
,
'time'
:
timestr
}
# CodeResponse requires internal CorrectMap state. Build it now in the 'queued' state
old_cmap
=
CorrectMap
()
def
test_is_queued
(
self
):
answer_ids
=
sorted
(
test_lcp
.
get_question_answers
()
.
keys
())
'''
numAnswers
=
len
(
answer_ids
)
Simple test of whether LoncapaProblem knows when it's been queued
for
i
in
range
(
numAnswers
):
'''
old_cmap
.
update
(
CorrectMap
(
answer_id
=
answer_ids
[
i
],
queuekey
=
1000
+
i
))
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
=
i4xs
)
answer_ids
=
sorted
(
test_lcp
.
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
.
assertEquals
(
test_lcp
.
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
.
assertEquals
(
test_lcp
.
is_queued
(),
True
)
# TODO: Message format inherited from ExternalResponse
def
test_update_score
(
self
):
#correct_score_msg = "<edxgrade><awarddetail>EXACT_ANS</awarddetail><message>MESSAGE</message></edxgrade>"
'''
#incorrect_score_msg = "<edxgrade><awarddetail>WRONG_FORMAT</awarddetail><message>MESSAGE</message></edxgrade>"
Test whether LoncapaProblem.update_score can deliver queued result to the right subproblem
'''
# New message format common to external graders
problem_file
=
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
"test_files/coderesponse.xml"
)
correct_score_msg
=
json
.
dumps
({
'correct'
:
True
,
'score'
:
1
,
'msg'
:
'MESSAGE'
})
with
open
(
problem_file
)
as
input_file
:
incorrect_score_msg
=
json
.
dumps
({
'correct'
:
False
,
'score'
:
0
,
'msg'
:
'MESSAGE'
}
)
test_lcp
=
lcp
.
LoncapaProblem
(
input_file
.
read
(),
'1'
,
system
=
i4xs
)
xserver_msgs
=
{
'correct'
:
correct_score_msg
,
answer_ids
=
sorted
(
test_lcp
.
get_question_answers
())
'incorrect'
:
incorrect_score_msg
,
}
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
old_cmap
=
CorrectMap
()
# Incorrect queuekey, state should not be updated
for
i
,
answer_id
in
enumerate
(
answer_ids
):
for
correctness
in
[
'correct'
,
'incorrect'
]:
queuekey
=
1000
+
i
test_lcp
.
correct_map
=
CorrectMap
(
)
queuestate
=
CodeResponseTest
.
make_queuestate
(
1000
+
i
,
datetime
.
now
()
)
test_lcp
.
correct_map
.
update
(
old_cmap
)
# Deep copy
old_cmap
.
update
(
CorrectMap
(
answer_id
=
answer_ids
[
i
],
queuestate
=
queuestate
))
test_lcp
.
update_score
(
xserver_msgs
[
correctness
],
queuekey
=
0
)
# Message format common to external graders
self
.
assertEquals
(
test_lcp
.
correct_map
.
get_dict
(),
old_cmap
.
get_dict
())
# Deep comparison
correct_score_msg
=
json
.
dumps
({
'correct'
:
True
,
'score'
:
1
,
'msg'
:
'MESSAGE'
})
incorrect_score_msg
=
json
.
dumps
({
'correct'
:
False
,
'score'
:
0
,
'msg'
:
'MESSAGE'
})
for
i
in
range
(
numAnswers
):
self
.
assertTrue
(
test_lcp
.
correct_map
.
is_queued
(
answer_ids
[
i
]))
# Should be still queued, since message undelivered
xserver_msgs
=
{
'correct'
:
correct_score_msg
,
'incorrect'
:
incorrect_score_msg
,}
# Correct queuekey, state should be updated
for
correctness
in
[
'correct'
,
'incorrect'
]:
# Incorrect queuekey, state should not be updated
for
i
in
range
(
numAnswers
):
# Target specific answer_id's
for
correctness
in
[
'correct'
,
'incorrect'
]:
test_lcp
.
correct_map
=
CorrectMap
()
test_lcp
.
correct_map
=
CorrectMap
()
test_lcp
.
correct_map
.
update
(
old_cmap
)
test_lcp
.
correct_map
.
update
(
old_cmap
)
# Deep copy
new_cmap
=
CorrectMap
()
test_lcp
.
update_score
(
xserver_msgs
[
correctness
],
queuekey
=
0
)
new_cmap
.
update
(
old_cmap
)
self
.
assertEquals
(
test_lcp
.
correct_map
.
get_dict
(),
old_cmap
.
get_dict
())
# Deep comparison
npoints
=
1
if
correctness
==
'correct'
else
0
new_cmap
.
set
(
answer_id
=
answer_ids
[
i
],
npoints
=
npoints
,
correctness
=
correctness
,
msg
=
'MESSAGE'
,
queuekey
=
None
)
for
answer_id
in
answer_ids
:
self
.
assertTrue
(
test_lcp
.
correct_map
.
is_queued
(
answer_id
))
# Should be still queued, since message undelivered
test_lcp
.
update_score
(
xserver_msgs
[
correctness
],
queuekey
=
1000
+
i
)
self
.
assertEquals
(
test_lcp
.
correct_map
.
get_dict
(),
new_cmap
.
get_dict
())
# Correct queuekey, state should be updated
for
correctness
in
[
'correct'
,
'incorrect'
]:
for
j
in
range
(
numAnswers
):
for
i
,
answer_id
in
enumerate
(
answer_ids
):
if
j
==
i
:
test_lcp
.
correct_map
=
CorrectMap
()
self
.
assertFalse
(
test_lcp
.
correct_map
.
is_queued
(
answer_ids
[
j
]))
# Should be dequeued, message delivered
test_lcp
.
correct_map
.
update
(
old_cmap
)
else
:
self
.
assertTrue
(
test_lcp
.
correct_map
.
is_queued
(
answer_ids
[
j
]))
# Should be queued, message undelivered
new_cmap
=
CorrectMap
()
new_cmap
.
update
(
old_cmap
)
def
test_convert_files_to_filenames
(
self
):
npoints
=
1
if
correctness
==
'correct'
else
0
problem_file
=
os
.
path
.
dirname
(
__file__
)
+
"/test_files/coderesponse.xml"
new_cmap
.
set
(
answer_id
=
answer_id
,
npoints
=
npoints
,
correctness
=
correctness
,
msg
=
'MESSAGE'
,
queuestate
=
None
)
fp
=
open
(
problem_file
)
answers_with_file
=
{
'1_2_1'
:
'String-based answer'
,
test_lcp
.
update_score
(
xserver_msgs
[
correctness
],
queuekey
=
1000
+
i
)
'1_3_1'
:
[
'answer1'
,
'answer2'
,
'answer3'
],
self
.
assertEquals
(
test_lcp
.
correct_map
.
get_dict
(),
new_cmap
.
get_dict
())
'1_4_1'
:
[
fp
,
fp
]}
answers_converted
=
convert_files_to_filenames
(
answers_with_file
)
for
j
,
test_id
in
enumerate
(
answer_ids
):
self
.
assertEquals
(
answers_converted
[
'1_2_1'
],
'String-based answer'
)
if
j
==
i
:
self
.
assertEquals
(
answers_converted
[
'1_3_1'
],
[
'answer1'
,
'answer2'
,
'answer3'
])
self
.
assertFalse
(
test_lcp
.
correct_map
.
is_queued
(
test_id
))
# Should be dequeued, message delivered
self
.
assertEquals
(
answers_converted
[
'1_4_1'
],
[
fp
.
name
,
fp
.
name
])
else
:
self
.
assertTrue
(
test_lcp
.
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
=
i4xs
)
answer_ids
=
sorted
(
test_lcp
.
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
.
assertEquals
(
test_lcp
.
get_recentmost_queuetime
(),
None
)
# CodeResponse requires internal CorrectMap state. Build it now in the queued state
cmap
=
CorrectMap
()
for
i
,
answer_id
in
enumerate
(
answer_ids
):
queuekey
=
1000
+
i
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
)
# 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
)
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"
)
with
open
(
problem_file
)
as
fp
:
answers_with_file
=
{
'1_2_1'
:
'String-based answer'
,
'1_3_1'
:
[
'answer1'
,
'answer2'
,
'answer3'
],
'1_4_1'
:
[
fp
,
fp
]}
answers_converted
=
convert_files_to_filenames
(
answers_with_file
)
self
.
assertEquals
(
answers_converted
[
'1_2_1'
],
'String-based answer'
)
self
.
assertEquals
(
answers_converted
[
'1_3_1'
],
[
'answer1'
,
'answer2'
,
'answer3'
])
self
.
assertEquals
(
answers_converted
[
'1_4_1'
],
[
fp
.
name
,
fp
.
name
])
class
ChoiceResponseTest
(
unittest
.
TestCase
):
class
ChoiceResponseTest
(
unittest
.
TestCase
):
...
...
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/tests/test_files/coderesponse.xml
View file @
231b8c6a
...
@@ -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
cub
e of a number
Write a program to compute the
squar
e 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>
...
...
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/tests/test_files/coderesponse_externalresponseformat.xml
0 → 100644
View file @
231b8c6a
<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>
This diff is collapsed.
Click to expand it.
lms/djangoapps/courseware/module_render.py
View file @
231b8c6a
...
@@ -220,7 +220,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
...
@@ -220,7 +220,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
xqueue
=
{
'interface'
:
xqueue_interface
,
xqueue
=
{
'interface'
:
xqueue_interface
,
'callback_url'
:
xqueue_callback_url
,
'callback_url'
:
xqueue_callback_url
,
'default_queuename'
:
xqueue_default_queuename
.
replace
(
' '
,
'_'
)}
'default_queuename'
:
xqueue_default_queuename
.
replace
(
' '
,
'_'
),
'waittime'
:
settings
.
XQUEUE_WAITTIME_BETWEEN_REQUESTS
}
def
inner_get_module
(
location
):
def
inner_get_module
(
location
):
"""
"""
...
...
This diff is collapsed.
Click to expand it.
lms/envs/common.py
View file @
231b8c6a
...
@@ -86,6 +86,9 @@ DEFAULT_GROUPS = []
...
@@ -86,6 +86,9 @@ DEFAULT_GROUPS = []
# If this is true, random scores will be generated for the purpose of debugging the profile graphs
# If this is true, random scores will be generated for the purpose of debugging the profile graphs
GENERATE_PROFILE_SCORES
=
False
GENERATE_PROFILE_SCORES
=
False
# Used with XQueue
XQUEUE_WAITTIME_BETWEEN_REQUESTS
=
5
# seconds
############################# SET PATH INFORMATION #############################
############################# SET PATH INFORMATION #############################
PROJECT_ROOT
=
path
(
__file__
)
.
abspath
()
.
dirname
()
.
dirname
()
# /mitx/lms
PROJECT_ROOT
=
path
(
__file__
)
.
abspath
()
.
dirname
()
.
dirname
()
# /mitx/lms
REPO_ROOT
=
PROJECT_ROOT
.
dirname
()
REPO_ROOT
=
PROJECT_ROOT
.
dirname
()
...
...
This diff is collapsed.
Click to expand it.
lms/envs/test.py
View file @
231b8c6a
...
@@ -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
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment