diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 42156ede40..9699ec0145 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,9 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. +Blades: Handle situation if no response were sent from XQueue to LMS in Matlab +problem after Run Code button press. BLD-994. + Blades: Set initial video quality to large instead of default to avoid automatic switch to HD when iframe resizes. BLD-981. Blades: Add an upload button for authors to provide students with an option to diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index a0631a12db..f66abf9134 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -39,6 +39,7 @@ graded status as'status' # makes sense, but a bunch of problems have markup that assumes block. Bigger TODO: figure out a # general css and layout strategy for capa, document it, then implement it. +import time import json import logging from lxml import etree @@ -53,6 +54,7 @@ from .registry import TagRegistry from chem import chemcalc from calc.preview import latex_preview import xqueue_interface +from xqueue_interface import XQUEUE_TIMEOUT from datetime import datetime from xmodule.stringify import stringify_children @@ -821,6 +823,15 @@ class MatlabInput(CodeInput): self.status = 'queued' self.queue_len = 1 self.msg = self.submitted_msg + # Handle situation if no response from xqueue arrived during specified time. + if ('queuetime' not in self.input_state or + time.time() - self.input_state['queuetime'] > XQUEUE_TIMEOUT): + self.queue_len = 0 + self.status = 'unsubmitted' + self.msg = _( + 'No response from Xqueue within {xqueue_timeout} seconds. Aborted.' + ).format(xqueue_timeout=XQUEUE_TIMEOUT) + def handle_ajax(self, dispatch, data): """ @@ -945,6 +956,7 @@ class MatlabInput(CodeInput): if error == 0: self.input_state['queuekey'] = queuekey self.input_state['queuestate'] = 'queued' + self.input_state['queuetime'] = time.time() return {'success': error == 0, 'message': msg} diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index cba14ff0f2..c1240c73bf 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -28,6 +28,8 @@ from capa import inputtypes from mock import ANY, patch from pyparsing import ParseException +from capa.xqueue_interface import XQUEUE_TIMEOUT + # just a handy shortcut lookup_tag = inputtypes.registry.get_class_for_tag @@ -524,10 +526,11 @@ class MatlabTest(unittest.TestCase): self.assertEqual(context, expected) - def test_rendering_while_queued(self): + @patch('capa.inputtypes.time.time', return_value=10) + def test_rendering_while_queued(self, time): state = {'value': 'print "good evening"', 'status': 'incomplete', - 'input_state': {'queuestate': 'queued'}, + 'input_state': {'queuestate': 'queued', 'queuetime': 5}, } elt = etree.fromstring(self.xml) @@ -576,9 +579,10 @@ class MatlabTest(unittest.TestCase): self.assertTrue('queuekey' not in self.the_input.input_state) self.assertTrue('queuestate' not in self.the_input.input_state) - def test_ungraded_response_success(self): + @patch('capa.inputtypes.time.time', return_value=10) + def test_ungraded_response_success(self, time): queuekey = 'abcd' - input_state = {'queuekey': queuekey, 'queuestate': 'queued'} + input_state = {'queuekey': queuekey, 'queuestate': 'queued', 'queuetime': 5} state = {'value': 'print "good evening"', 'status': 'incomplete', 'input_state': input_state, @@ -594,9 +598,10 @@ class MatlabTest(unittest.TestCase): self.assertTrue(input_state['queuestate'] is None) self.assertEqual(input_state['queue_msg'], inner_msg) - def test_ungraded_response_key_mismatch(self): + @patch('capa.inputtypes.time.time', return_value=10) + def test_ungraded_response_key_mismatch(self, time): queuekey = 'abcd' - input_state = {'queuekey': queuekey, 'queuestate': 'queued'} + input_state = {'queuekey': queuekey, 'queuestate': 'queued', 'queuetime': 5} state = {'value': 'print "good evening"', 'status': 'incomplete', 'input_state': input_state, @@ -612,6 +617,41 @@ class MatlabTest(unittest.TestCase): self.assertEqual(input_state['queuestate'], 'queued') self.assertFalse('queue_msg' in input_state) + @patch('capa.inputtypes.time.time', return_value=20) + def test_matlab_response_timeout_not_exceeded(self, time): + + state = {'input_state': {'queuestate': 'queued', 'queuetime': 5}} + elt = etree.fromstring(self.xml) + + the_input = self.input_class(test_capa_system(), elt, state) + context = the_input._get_render_context() + self.assertEqual(the_input.status, 'queued') + + + @patch('capa.inputtypes.time.time', return_value=45) + def test_matlab_response_timeout_exceeded(self, time): + + state = {'input_state': {'queuestate': 'queued', 'queuetime': 5}} + elt = etree.fromstring(self.xml) + + the_input = self.input_class(test_capa_system(), elt, state) + context = the_input._get_render_context() + self.assertEqual(the_input.status, 'unsubmitted') + self.assertEqual(the_input.msg, 'No response from Xqueue within {} seconds. Aborted.'.format(XQUEUE_TIMEOUT)) + + @patch('capa.inputtypes.time.time', return_value=20) + def test_matlab_response_migration_of_queuetime(self, time): + """ + Test if problem was saved before queuetime was introduced. + """ + state = {'input_state': {'queuestate': 'queued'}} + elt = etree.fromstring(self.xml) + + the_input = self.input_class(test_capa_system(), elt, state) + context = the_input._get_render_context() + self.assertEqual(the_input.status, 'unsubmitted') + + def test_get_html(self): # usual output output = self.the_input.get_html() @@ -651,7 +691,7 @@ class MatlabTest(unittest.TestCase): queue_msg = textwrap.dedent("""
if Conditionally execute statements. The general form of the if statement is - + if expression statements ELSEIF expression @@ -659,11 +699,11 @@ class MatlabTest(unittest.TestCase): ELSE statements END - - The statements are executed if the real part of the expression + + The statements are executed if the real part of the expression has all non-zero elements. The ELSE and ELSEIF parts are optional. Zero or more ELSEIF parts can be used as well as nested if's. - The expression is usually of the form expr rop expr where + The expression is usually of the form expr rop expr where rop is ==, <, >, <=, >=, or ~=. @@ -675,7 +715,7 @@ class MatlabTest(unittest.TestCase): else A(I,J) = 0; end - + See also relop, else, elseif, end, for, while, switch. Reference page in Help browser @@ -693,7 +733,7 @@ class MatlabTest(unittest.TestCase): the_input = self.input_class(test_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=W0212 self.maxDiff = None - expected = u'\n
if Conditionally execute statements.\nThe general form of the if statement is\n\n if expression\n statements\n ELSEIF expression\n statements\n ELSE\n statements\n END\n\nThe statements are executed if the real part of the expression \nhas all non-zero elements. The ELSE and ELSEIF parts are optional.\nZero or more ELSEIF parts can be used as well as nested if\'s.\nThe expression is usually of the form expr rop expr where \nrop is ==, <, >, <=, >=, or ~=.\n\n\nExample\n if I == J\n A(I,J) = 2;\n elseif abs(I-J) == 1\n A(I,J) = -1;\n else\n A(I,J) = 0;\n end\n\nSee also relop, else, elseif, end, for, while, switch.\n\nReference page in Help browser\n doc if\n\n
    \n' + expected = u'\n
    if Conditionally execute statements.\nThe general form of the if statement is\n\n if expression\n statements\n ELSEIF expression\n statements\n ELSE\n statements\n END\n\nThe statements are executed if the real part of the expression\nhas all non-zero elements. The ELSE and ELSEIF parts are optional.\nZero or more ELSEIF parts can be used as well as nested if\'s.\nThe expression is usually of the form expr rop expr where\nrop is ==, <, >, <=, >=, or ~=.\n\n\nExample\n if I == J\n A(I,J) = 2;\n elseif abs(I-J) == 1\n A(I,J) = -1;\n else\n A(I,J) = 0;\n end\n\nSee also relop, else, elseif, end, for, while, switch.\n\nReference page in Help browser\n doc if\n\n
      \n' self.assertEqual(context['queue_msg'], expected) diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py index 10c6394654..0603b65237 100644 --- a/common/lib/capa/capa/xqueue_interface.py +++ b/common/lib/capa/capa/xqueue_interface.py @@ -13,6 +13,9 @@ dateformat = '%Y%m%d%H%M%S' XQUEUE_METRIC_NAME = 'edxapp.xqueue' +# Wait time for response from Xqueue. +XQUEUE_TIMEOUT = 35 # seconds + def make_hashkey(seed): """