diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index c1eb078a20..209c7ddd50 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -63,37 +63,73 @@ log = logging.getLogger(__name__) # main class for this module +class LoncapaSystem(object): + """ + An encapsulation of resources needed from the outside. + + These interfaces are collected here so that a caller of LoncapaProblem + can provide these resources however make sense for their environment, and + this code can remain independent. + + See :class:`ModuleSystem` for documentation of these attributes. + + """ + def __init__( # pylint: disable=invalid-name + self, + ajax_url, + anonymous_student_id, + cache, + can_execute_unsafe_code, + DEBUG, # pylint: disable=invalid-name + filestore, + node_path, + render_template, + seed, # Why do we do this if we have self.seed? + STATIC_URL, # pylint: disable=invalid-name + xqueue, + ): + self.ajax_url = ajax_url + self.anonymous_student_id = anonymous_student_id + self.cache = cache + self.can_execute_unsafe_code = can_execute_unsafe_code + self.DEBUG = DEBUG # pylint: disable=invalid-name + self.filestore = filestore + self.node_path = node_path + self.render_template = render_template + self.seed = seed # Why do we do this if we have self.seed? + self.STATIC_URL = STATIC_URL # pylint: disable=invalid-name + self.xqueue = xqueue + + class LoncapaProblem(object): ''' Main class for capa Problems. ''' - def __init__(self, problem_text, id, state=None, seed=None, system=None): - ''' + def __init__(self, problem_text, id, capa_system, state=None, seed=None): + """ Initializes capa Problem. Arguments: - - problem_text (string): xml defining the problem - - id (string): identifier for this problem; often a filename (no spaces) - - seed (int): random number generator seed (int) - - state (dict): containing the following keys: - - 'seed' - (int) random number generator seed - - 'student_answers' - (dict) maps input id to the stored answer for that input - - 'correct_map' (CorrectMap) a map of each input to their 'correctness' - - 'done' - (bool) indicates whether or not this problem is considered done - - 'input_state' - (dict) maps input_id to a dictionary that holds the state for that input - - system (ModuleSystem): ModuleSystem instance which provides OS, - rendering, and user context + problem_text (string): xml defining the problem. + id (string): identifier for this problem, often a filename (no spaces). + capa_system (LoncapaSystem): LoncapaSystem instance which provides OS, + rendering, user context, and other resources. + state (dict): containing the following keys: + - `seed` (int) random number generator seed + - `student_answers` (dict) maps input id to the stored answer for that input + - `correct_map` (CorrectMap) a map of each input to their 'correctness' + - `done` (bool) indicates whether or not this problem is considered done + - `input_state` (dict) maps input_id to a dictionary that holds the state for that input + seed (int): random number generator seed. - ''' + """ ## Initialize class variables from state self.do_reset() self.problem_id = id - self.system = system - if self.system is None: - raise Exception() + self.capa_system = capa_system state = state or {} @@ -412,8 +448,8 @@ class LoncapaProblem(object): filename = inc.get('file') if filename is not None: try: - # open using ModuleSystem OSFS filestore - ifp = self.system.filestore.open(filename) + # open using LoncapaSystem OSFS filestore + ifp = self.capa_system.filestore.open(filename) except Exception as err: log.warning( 'Error %s in problem xml include: %s' % ( @@ -422,12 +458,12 @@ class LoncapaProblem(object): ) log.warning( 'Cannot find file %s in %s' % ( - filename, self.system.filestore + filename, self.capa_system.filestore ) ) # if debugging, don't fail - just log error # TODO (vshnayder): need real error handling, display to users - if not self.system.get('DEBUG'): + if not self.capa_system.DEBUG: raise else: continue @@ -443,7 +479,7 @@ class LoncapaProblem(object): log.warning('Cannot parse XML in %s' % (filename)) # if debugging, don't fail - just log error # TODO (vshnayder): same as above - if not self.system.get('DEBUG'): + if not self.capa_system.DEBUG: raise else: continue @@ -476,9 +512,9 @@ class LoncapaProblem(object): continue # path is an absolute path or a path relative to the data dir - dir = os.path.join(self.system.filestore.root_path, dir) + dir = os.path.join(self.capa_system.filestore.root_path, dir) # Check that we are within the filestore tree. - reldir = os.path.relpath(dir, self.system.filestore.root_path) + reldir = os.path.relpath(dir, self.capa_system.filestore.root_path) if ".." in reldir: log.warning("Ignoring Python directory outside of course: %r" % dir) continue @@ -527,9 +563,9 @@ class LoncapaProblem(object): context, random_seed=self.seed, python_path=python_path, - cache=self.system.cache, + cache=self.capa_system.cache, slug=self.problem_id, - unsafely=self.system.can_execute_unsafe_code(), + unsafely=self.capa_system.can_execute_unsafe_code(), ) except Exception as err: log.exception("Error while execing script code: " + all_code) @@ -600,7 +636,7 @@ class LoncapaProblem(object): input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag) # save the input type so that we can make ajax calls on it if we need to - self.inputs[input_id] = input_type_cls(self.system, problemtree, state) + self.inputs[input_id] = input_type_cls(self.capa_system, problemtree, state) return self.inputs[input_id].get_html() # let each Response render itself @@ -613,7 +649,7 @@ class LoncapaProblem(object): # let each custom renderer render itself: if problemtree.tag in customrender.registry.registered_tags(): renderer_class = customrender.registry.get_class_for_tag(problemtree.tag) - renderer = renderer_class(self.system, problemtree) + renderer = renderer_class(self.capa_system, problemtree) return renderer.get_html() # otherwise, render children recursively, and copy over attributes @@ -670,7 +706,7 @@ class LoncapaProblem(object): # instantiate capa Response responsetype_cls = responsetypes.registry.get_class_for_tag(response.tag) - responder = responsetype_cls(response, inputfields, self.context, self.system) + responder = responsetype_cls(response, inputfields, self.context, self.capa_system) # save in list in self self.responders[response] = responder diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index e81390724d..af9619296e 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -128,7 +128,7 @@ class InputTypeBase(object): """ Instantiate an InputType class. Arguments: - - system : ModuleSystem instance which provides OS, rendering, and user context. + - system : LoncapaModule instance which provides OS, rendering, and user context. Specifically, must have a render_template function. - xml : Element tree of this Input element - state : a dictionary with optional keys: @@ -146,7 +146,7 @@ class InputTypeBase(object): self.xml = xml self.tag = xml.tag - self.system = system + self.capa_system = system # NOTE: ID should only come from one place. If it comes from multiple, # we use state first, XML second (in case the xml changed, but we have @@ -257,7 +257,7 @@ class InputTypeBase(object): 'value': self.value, 'status': self.status, 'msg': self.msg, - 'STATIC_URL': self.system.STATIC_URL, + 'STATIC_URL': self.capa_system.STATIC_URL, } context.update((a, v) for ( a, v) in self.loaded_attributes.iteritems() if a in self.to_render) @@ -282,7 +282,7 @@ class InputTypeBase(object): context = self._get_render_context() - html = self.system.render_template(self.template, context) + html = self.capa_system.render_template(self.template, context) return etree.XML(html) @@ -505,9 +505,9 @@ class JSInput(InputTypeBase): def _extra_context(self): context = { 'jschannel_loader': '{static_url}js/capa/src/jschannel.js'.format( - static_url=self.system.STATIC_URL), + static_url=self.capa_system.STATIC_URL), 'jsinput_loader': '{static_url}js/capa/src/jsinput.js'.format( - static_url=self.system.STATIC_URL), + static_url=self.capa_system.STATIC_URL), 'saved_state': self.value } @@ -822,18 +822,19 @@ class MatlabInput(CodeInput): - 'message' - message to be rendered in case of error ''' # only send data if xqueue exists - if self.system.xqueue is None: + if self.capa_system.xqueue is None: return {'success': False, 'message': 'Cannot connect to the queue'} # pull relevant info out of get response = data['submission'] # construct xqueue headers - qinterface = self.system.xqueue['interface'] + qinterface = self.capa_system.xqueue['interface'] qtime = datetime.utcnow().strftime(xqueue_interface.dateformat) - callback_url = self.system.xqueue['construct_callback']('ungraded_response') - anonymous_student_id = self.system.anonymous_student_id - queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + + callback_url = self.capa_system.xqueue['construct_callback']('ungraded_response') + anonymous_student_id = self.capa_system.anonymous_student_id + # TODO: Why is this using self.capa_system.seed when we have self.seed??? + queuekey = xqueue_interface.make_hashkey(str(self.capa_system.seed) + qtime + anonymous_student_id + self.input_id) xheader = xqueue_interface.make_xheader( @@ -1006,7 +1007,7 @@ class ChemicalEquationInput(InputTypeBase): """ return { 'previewer': '{static_url}js/capa/chemical_equation_preview.js'.format( - static_url=self.system.STATIC_URL), + static_url=self.capa_system.STATIC_URL), } def handle_ajax(self, dispatch, data): @@ -1091,7 +1092,7 @@ class FormulaEquationInput(InputTypeBase): return { 'previewer': '{static_url}js/capa/src/formula_equation_preview.js'.format( - static_url=self.system.STATIC_URL), + static_url=self.capa_system.STATIC_URL), 'reported_status': reported_status, } @@ -1274,7 +1275,7 @@ class EditAMoleculeInput(InputTypeBase): """ context = { 'applet_loader': '{static_url}js/capa/editamolecule.js'.format( - static_url=self.system.STATIC_URL), + static_url=self.capa_system.STATIC_URL), } return context @@ -1310,7 +1311,7 @@ class DesignProtein2dInput(InputTypeBase): """ context = { 'applet_loader': '{static_url}js/capa/design-protein-2d.js'.format( - static_url=self.system.STATIC_URL), + static_url=self.capa_system.STATIC_URL), } return context @@ -1346,7 +1347,7 @@ class EditAGeneInput(InputTypeBase): """ context = { 'applet_loader': '{static_url}js/capa/edit-a-gene.js'.format( - static_url=self.system.STATIC_URL), + static_url=self.capa_system.STATIC_URL), } return context diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 3bdd154525..5db1f61920 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -129,20 +129,20 @@ class LoncapaResponse(object): allowed_inputfields = [] required_attributes = [] - def __init__(self, xml, inputfields, context, system=None): + def __init__(self, xml, inputfields, context, system): ''' Init is passed the following arguments: - xml : ElementTree of this Response - inputfields : ordered list of ElementTrees for each input entry field in this Response - context : script processor context - - system : ModuleSystem instance which provides OS, rendering, and user context + - system : LoncapaSystem instance which provides OS, rendering, and user context ''' self.xml = xml self.inputfields = inputfields self.context = context - self.system = system + self.capa_system = system self.id = xml.get('id') @@ -298,7 +298,7 @@ class LoncapaResponse(object): python_path=self.context['python_path'], slug=self.id, random_seed=self.context['seed'], - unsafely=self.system.can_execute_unsafe_code(), + unsafely=self.capa_system.can_execute_unsafe_code(), ) except Exception as err: msg = 'Error %s in evaluating hint function %s' % (err, hintfn) @@ -444,7 +444,7 @@ class JavascriptResponse(LoncapaResponse): # manually being compiled to DATA_DIR/js/compiled. # latestTimestamp = 0 - # basepath = self.system.filestore.root_path + '/js/' + # basepath = self.capa_system.filestore.root_path + '/js/' # for filename in (self.display_dependencies + [self.display]): # filepath = basepath + filename # timestamp = os.stat(filepath).st_mtime @@ -467,7 +467,7 @@ class JavascriptResponse(LoncapaResponse): # outfile.close() # TODO this should also be fixed when the above is fixed. - filename = self.system.ajax_url.split('/')[-1] + '.js' + filename = self.capa_system.ajax_url.split('/')[-1] + '.js' self.display_filename = 'compiled/' + filename def parse_xml(self): @@ -510,16 +510,16 @@ class JavascriptResponse(LoncapaResponse): def get_node_env(self): - js_dir = os.path.join(self.system.filestore.root_path, 'js') + js_dir = os.path.join(self.capa_system.filestore.root_path, 'js') tmp_env = os.environ.copy() - node_path = self.system.node_path + ":" + os.path.normpath(js_dir) + node_path = self.capa_system.node_path + ":" + os.path.normpath(js_dir) tmp_env["NODE_PATH"] = node_path return tmp_env def call_node(self, args): - # Node.js code is un-sandboxed. If the XModuleSystem says we aren't + # Node.js code is un-sandboxed. If the LoncapaSystem says we aren't # allowed to run unsafe code, then stop now. - if not self.system.can_execute_unsafe_code(): + if not self.capa_system.can_execute_unsafe_code(): raise LoncapaProblemError("Execution of unsafe Javascript code is not allowed.") subprocess_args = ["node"] @@ -1154,7 +1154,7 @@ class CustomResponse(LoncapaResponse): python_path=self.context['python_path'], slug=self.id, random_seed=self.context['seed'], - unsafely=self.system.can_execute_unsafe_code(), + unsafely=self.capa_system.can_execute_unsafe_code(), ) return globals_dict['cfn_return'] return check_function @@ -1169,8 +1169,8 @@ class CustomResponse(LoncapaResponse): else: answer_src = answer.get('src') if answer_src is not None: - self.code = self.system.filesystem.open( - 'src/' + answer_src).read() + # TODO: this code seems not to be used any more since self.capa_system.filesystem doesn't exist. + self.code = self.capa_system.filesystem.open('src/' + answer_src).read() else: self.code = answer.text @@ -1249,8 +1249,8 @@ class CustomResponse(LoncapaResponse): 'testdat': 'hello world', }) - # pass self.system.debug to cfn - self.context['debug'] = self.system.DEBUG + # Pass DEBUG to the check function. + self.context['debug'] = self.capa_system.DEBUG # Run the check function self.execute_check_function(idset, submission) @@ -1275,10 +1275,10 @@ class CustomResponse(LoncapaResponse): safe_exec.safe_exec( self.code, self.context, - cache=self.system.cache, + cache=self.capa_system.cache, slug=self.id, random_seed=self.context['seed'], - unsafely=self.system.can_execute_unsafe_code(), + unsafely=self.capa_system.can_execute_unsafe_code(), ) except Exception as err: self._handle_exec_exception(err) @@ -1470,18 +1470,21 @@ ScoreMessage = namedtuple('ScoreMessage', ['valid', 'correct', 'points', 'msg']) @registry.register class CodeResponse(LoncapaResponse): """ - Grade student code using an external queueing server, called 'xqueue' + Grade student code using an external queueing server, called 'xqueue'. - Expects 'xqueue' dict in ModuleSystem with the following keys that are needed by CodeResponse: - system.xqueue = { 'interface': XqueueInterface object, - 'construct_callback': Per-StudentModule callback URL - constructor, defaults to using 'score_update' - as the correct dispatch (function), - 'default_queuename': Default queuename to submit request (string) - } + Expects 'xqueue' dict in LoncapaSystem with the following keys that are + needed by CodeResponse:: + + capa_system.xqueue = { + 'interface': XQueueInterface object. + 'construct_callback': Per-StudentModule callback URL constructor, + defaults to using 'score_update' as the correct dispatch (function). + 'default_queuename': Default queue name to submit request (string). + } + + External requests are only submitted for student submission grading, not + for getting reference answers. - External requests are only submitted for student submission grading - (i.e. and not for getting reference answers) """ tags = ['coderesponse'] @@ -1504,8 +1507,8 @@ class CodeResponse(LoncapaResponse): self.url = xml.get('url', None) # We do not support xqueue within Studio. - if self.system.xqueue is not None: - default_queuename = self.system.xqueue['default_queuename'] + if self.capa_system.xqueue is not None: + default_queuename = self.capa_system.xqueue['default_queuename'] else: default_queuename = None self.queue_name = xml.get('queuename', default_queuename) @@ -1548,7 +1551,7 @@ class CodeResponse(LoncapaResponse): raise Exception(err) # We do not support xqueue within Studio. - if self.system.xqueue is None: + if self.capa_system.xqueue is None: cmap = CorrectMap() cmap.set(self.answer_id, queuestate=None, msg='Error checking problem: no external queueing server is configured.') @@ -1557,16 +1560,16 @@ class CodeResponse(LoncapaResponse): # Prepare xqueue request #------------------------------------------------------------ - qinterface = self.system.xqueue['interface'] + qinterface = self.capa_system.xqueue['interface'] qtime = datetime.strftime(datetime.now(UTC), xqueue_interface.dateformat) - anonymous_student_id = self.system.anonymous_student_id + anonymous_student_id = self.capa_system.anonymous_student_id # Generate header queuekey = xqueue_interface.make_hashkey( - str(self.system.seed) + qtime + anonymous_student_id + self.answer_id + str(self.capa_system.seed) + qtime + anonymous_student_id + self.answer_id ) - callback_url = self.system.xqueue['construct_callback']() + callback_url = self.capa_system.xqueue['construct_callback']() xheader = xqueue_interface.make_xheader( lms_callback_url=callback_url, lms_key=queuekey, @@ -1748,8 +1751,8 @@ class ExternalResponse(LoncapaResponse): if answer is not None: answer_src = answer.get('src') if answer_src is not None: - self.code = self.system.filesystem.open( - 'src/' + answer_src).read() + # TODO: this code seems not to be used any more since self.capa_system.filesystem doesn't exist. + self.code = self.capa_system.filesystem.open('src/' + answer_src).read() else: self.code = answer.text else: @@ -1791,7 +1794,7 @@ class ExternalResponse(LoncapaResponse): log.error(msg) raise Exception(msg) - if self.system.DEBUG: + if self.capa_system.DEBUG: log.info('response = %s', req.text) if (not req.text) or (not req.text.strip()): @@ -1830,7 +1833,7 @@ class ExternalResponse(LoncapaResponse): rxml = self.do_external_request('get_score', extra_payload) except Exception as err: # pylint: disable=W0703 log.error('Error %s', err) - if self.system.DEBUG: + if self.capa_system.DEBUG: cmap.set_dict(dict(zip(sorted( self.answer_ids), ['incorrect'] * len(idset)))) cmap.set_property( @@ -1862,7 +1865,7 @@ class ExternalResponse(LoncapaResponse): exans = json.loads(rxml.find('expected').text) except Exception as err: # pylint: disable=W0703 log.error('Error %s', err) - if self.system.DEBUG: + if self.capa_system.DEBUG: msg = '%s' % str( err).replace('<', '<') exans = [''] * len(self.answer_ids) @@ -2100,7 +2103,7 @@ class SchematicResponse(LoncapaResponse): answer_src = answer.get('src') if answer_src is not None: # Untested; never used - self.code = self.system.filestore.open('src/' + answer_src).read() + self.code = self.capa_system.filestore.open('src/' + answer_src).read() else: self.code = answer.text @@ -2114,10 +2117,10 @@ class SchematicResponse(LoncapaResponse): safe_exec.safe_exec( self.code, self.context, - cache=self.system.cache, + cache=self.capa_system.cache, slug=self.id, random_seed=self.context['seed'], - unsafely=self.system.can_execute_unsafe_code(), + unsafely=self.capa_system.can_execute_unsafe_code(), ) except Exception as err: msg = 'Error %s in evaluating SchematicResponse' % err diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py index 7cc8a85a4a..db0af9cc8a 100644 --- a/common/lib/capa/capa/tests/__init__.py +++ b/common/lib/capa/capa/tests/__init__.py @@ -1,9 +1,11 @@ -import fs.osfs +"""Tools for helping with testing capa.""" + import os import os.path -from capa.capa_problem import LoncapaProblem -from xmodule.x_module import ModuleSystem +import fs.osfs + +from capa.capa_problem import LoncapaProblem, LoncapaSystem from mock import Mock, MagicMock import xml.sax.saxutils as saxutils @@ -26,34 +28,28 @@ xqueue_interface = MagicMock() xqueue_interface.send_to_queue.return_value = (0, 'Success!') -def test_system(): +def test_capa_system(): """ - Construct a mock ModuleSystem instance. + Construct a mock LoncapaSystem instance. """ the_system = Mock( - spec=ModuleSystem, + spec=LoncapaSystem, ajax_url='/dummy-ajax-url', - STATIC_URL='/dummy-static/', - DEBUG=True, - track_function=Mock(), - get_module=Mock(), - render_template=tst_render_template, - replace_urls=Mock(), - user=Mock(), - seed=0, - filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")), - debug=True, - hostname="edx.org", - xqueue={'interface': xqueue_interface, 'construct_callback': calledback_url, 'default_queuename': 'testqueue', 'waittime': 10}, - node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), anonymous_student_id='student', cache=None, can_execute_unsafe_code=lambda: False, + DEBUG=True, + filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")), + node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), + render_template=tst_render_template, + seed=0, + STATIC_URL='/dummy-static/', + xqueue={'interface': xqueue_interface, 'construct_callback': calledback_url, 'default_queuename': 'testqueue', 'waittime': 10}, ) return the_system -def new_loncapa_problem(xml, system=None): +def new_loncapa_problem(xml, capa_system=None): """Construct a `LoncapaProblem` suitable for unit tests.""" - return LoncapaProblem(xml, id='1', seed=723, system=system or test_system()) + return LoncapaProblem(xml, id='1', seed=723, capa_system=capa_system or test_capa_system()) diff --git a/common/lib/capa/capa/tests/test_customrender.py b/common/lib/capa/capa/tests/test_customrender.py index 8012804a40..8eeda47333 100644 --- a/common/lib/capa/capa/tests/test_customrender.py +++ b/common/lib/capa/capa/tests/test_customrender.py @@ -2,7 +2,7 @@ from lxml import etree import unittest import xml.sax.saxutils as saxutils -from . import test_system +from . import test_capa_system from capa import customrender # just a handy shortcut @@ -11,7 +11,7 @@ lookup_tag = customrender.registry.get_class_for_tag def extract_context(xml): """ - Given an xml element corresponding to the output of test_system.render_template, get back the + Given an xml element corresponding to the output of test_capa_system.render_template, get back the original context """ return eval(xml.text) @@ -26,7 +26,7 @@ class HelperTest(unittest.TestCase): Make sure that our helper function works! ''' def check(self, d): - xml = etree.XML(test_system().render_template('blah', d)) + xml = etree.XML(test_capa_system().render_template('blah', d)) self.assertEqual(d, extract_context(xml)) def test_extract_context(self): @@ -46,11 +46,11 @@ class SolutionRenderTest(unittest.TestCase): xml_str = """{s}""".format(s=solution) element = etree.fromstring(xml_str) - renderer = lookup_tag('solution')(test_system(), element) + renderer = lookup_tag('solution')(test_capa_system(), element) self.assertEqual(renderer.id, 'solution_12') - # Our test_system "renders" templates to a div with the repr of the context. + # Our test_capa_system "renders" templates to a div with the repr of the context. xml = renderer.get_html() context = extract_context(xml) self.assertEqual(context, {'id': 'solution_12'}) @@ -65,7 +65,7 @@ class MathRenderTest(unittest.TestCase): xml_str = """{tex}""".format(tex=latex_in) element = etree.fromstring(xml_str) - renderer = lookup_tag('math')(test_system(), element) + renderer = lookup_tag('math')(test_capa_system(), element) self.assertEqual(renderer.mathstr, mathjax_out) diff --git a/common/lib/capa/capa/tests/test_html_render.py b/common/lib/capa/capa/tests/test_html_render.py index 08f9b079a9..2803284b40 100644 --- a/common/lib/capa/capa/tests/test_html_render.py +++ b/common/lib/capa/capa/tests/test_html_render.py @@ -6,14 +6,14 @@ import textwrap import mock from .response_xml_factory import StringResponseXMLFactory, CustomResponseXMLFactory -from . import test_system, new_loncapa_problem +from . import test_capa_system, new_loncapa_problem class CapaHtmlRenderTest(unittest.TestCase): def setUp(self): super(CapaHtmlRenderTest, self).setUp() - self.system = test_system() + self.capa_system = test_capa_system() def test_blank_problem(self): """ @@ -44,7 +44,7 @@ class CapaHtmlRenderTest(unittest.TestCase): """) # Create the problem - problem = new_loncapa_problem(xml_str, system=self.system) + problem = new_loncapa_problem(xml_str, capa_system=self.capa_system) # Render the HTML rendered_html = etree.XML(problem.get_html()) @@ -119,12 +119,12 @@ class CapaHtmlRenderTest(unittest.TestCase): xml_str = StringResponseXMLFactory().build_xml(**kwargs) # Mock out the template renderer - the_system = test_system() + the_system = test_capa_system() the_system.render_template = mock.Mock() the_system.render_template.return_value = "
Input Template Render
" # Create the problem and render the HTML - problem = new_loncapa_problem(xml_str, system=the_system) + problem = new_loncapa_problem(xml_str, capa_system=the_system) rendered_html = etree.XML(problem.get_html()) # Expect problem has been turned into a
@@ -253,7 +253,7 @@ class CapaHtmlRenderTest(unittest.TestCase): self.assertRegexpMatches(the_html, r"
\s+
") def _create_test_file(self, path, content_str): - test_fp = self.system.filestore.open(path, "w") + test_fp = self.capa_system.filestore.open(path, "w") test_fp.write(content_str) test_fp.close() diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index b878220edb..85f20cf14a 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -22,7 +22,7 @@ from lxml import etree import unittest import xml.sax.saxutils as saxutils -from . import test_system +from . import test_capa_system from capa import inputtypes from mock import ANY, patch from pyparsing import ParseException @@ -47,7 +47,7 @@ class OptionInputTest(unittest.TestCase): state = {'value': 'Down', 'id': 'sky_input', 'status': 'answered'} - option_input = lookup_tag('optioninput')(test_system(), element, state) + option_input = lookup_tag('optioninput')(test_capa_system(), element, state) context = option_input._get_render_context() # pylint: disable=W0212 @@ -106,7 +106,7 @@ class ChoiceGroupTest(unittest.TestCase): 'id': 'sky_input', 'status': 'answered'} - the_input = lookup_tag(tag)(test_system(), element, state) + the_input = lookup_tag(tag)(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -159,7 +159,7 @@ class JavascriptInputTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': '3', } - the_input = lookup_tag('javascriptinput')(test_system(), element, state) + the_input = lookup_tag('javascriptinput')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -190,7 +190,7 @@ class TextLineTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'BumbleBee', } - the_input = lookup_tag('textline')(test_system(), element, state) + the_input = lookup_tag('textline')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -221,7 +221,7 @@ class TextLineTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'BumbleBee', } - the_input = lookup_tag('textline')(test_system(), element, state) + the_input = lookup_tag('textline')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -264,7 +264,7 @@ class TextLineTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'BumbleBee', } - the_input = lookup_tag('textline')(test_system(), element, state) + the_input = lookup_tag('textline')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -305,7 +305,7 @@ class FileSubmissionTest(unittest.TestCase): 'status': 'incomplete', 'feedback': {'message': '3'}, } input_class = lookup_tag('filesubmission') - the_input = input_class(test_system(), element, state) + the_input = input_class(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -353,7 +353,7 @@ class CodeInputTest(unittest.TestCase): 'feedback': {'message': '3'}, } input_class = lookup_tag('codeinput') - the_input = input_class(test_system(), element, state) + the_input = input_class(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -405,7 +405,7 @@ class MatlabTest(unittest.TestCase): 'feedback': {'message': '3'}, } self.input_class = lookup_tag('matlabinput') - self.the_input = self.input_class(test_system(), elt, state) + self.the_input = self.input_class(test_capa_system(), elt, state) def test_rendering(self): context = self.the_input._get_render_context() # pylint: disable=W0212 @@ -436,7 +436,7 @@ class MatlabTest(unittest.TestCase): 'feedback': {'message': '3'}, } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_system(), elt, state) + the_input = self.input_class(test_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=W0212 expected = { @@ -466,7 +466,7 @@ class MatlabTest(unittest.TestCase): } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_system(), elt, state) + the_input = self.input_class(test_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=W0212 expected = { 'STATIC_URL': '/dummy-static/', @@ -494,7 +494,7 @@ class MatlabTest(unittest.TestCase): } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_system(), elt, state) + the_input = self.input_class(test_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=W0212 expected = { 'STATIC_URL': '/dummy-static/', @@ -519,7 +519,7 @@ class MatlabTest(unittest.TestCase): data = {'submission': 'x = 1234;'} response = self.the_input.handle_ajax("plot", data) - test_system().xqueue['interface'].send_to_queue.assert_called_with(header=ANY, body=ANY) + test_capa_system().xqueue['interface'].send_to_queue.assert_called_with(header=ANY, body=ANY) self.assertTrue(response['success']) self.assertTrue(self.the_input.input_state['queuekey'] is not None) @@ -528,7 +528,7 @@ class MatlabTest(unittest.TestCase): def test_plot_data_failure(self): data = {'submission': 'x = 1234;'} error_message = 'Error message!' - test_system().xqueue['interface'].send_to_queue.return_value = (1, error_message) + test_capa_system().xqueue['interface'].send_to_queue.return_value = (1, error_message) response = self.the_input.handle_ajax("plot", data) self.assertFalse(response['success']) self.assertEqual(response['message'], error_message) @@ -544,7 +544,7 @@ class MatlabTest(unittest.TestCase): 'feedback': {'message': '3'}, } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_system(), elt, state) + the_input = self.input_class(test_capa_system(), elt, state) inner_msg = 'hello!' queue_msg = json.dumps({'msg': inner_msg}) @@ -562,7 +562,7 @@ class MatlabTest(unittest.TestCase): 'feedback': {'message': '3'}, } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_system(), elt, state) + the_input = self.input_class(test_capa_system(), elt, state) inner_msg = 'hello!' queue_msg = json.dumps({'msg': inner_msg}) @@ -601,7 +601,7 @@ class SchematicTest(unittest.TestCase): state = {'value': value, 'status': 'unsubmitted'} - the_input = lookup_tag('schematic')(test_system(), element, state) + the_input = lookup_tag('schematic')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -643,7 +643,7 @@ class ImageInputTest(unittest.TestCase): state = {'value': value, 'status': 'unsubmitted'} - the_input = lookup_tag('imageinput')(test_system(), element, state) + the_input = lookup_tag('imageinput')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -697,7 +697,7 @@ class CrystallographyTest(unittest.TestCase): state = {'value': value, 'status': 'unsubmitted'} - the_input = lookup_tag('crystallography')(test_system(), element, state) + the_input = lookup_tag('crystallography')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -738,7 +738,7 @@ class VseprTest(unittest.TestCase): state = {'value': value, 'status': 'unsubmitted'} - the_input = lookup_tag('vsepr_input')(test_system(), element, state) + the_input = lookup_tag('vsepr_input')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -768,7 +768,7 @@ class ChemicalEquationTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'H2OYeah', } - self.the_input = lookup_tag('chemicalequationinput')(test_system(), element, state) + self.the_input = lookup_tag('chemicalequationinput')(test_capa_system(), element, state) def test_rendering(self): ''' Verify that the render context matches the expected render context''' @@ -853,7 +853,7 @@ class FormulaEquationTest(unittest.TestCase): element = etree.fromstring(xml_str) state = {'value': 'x^2+1/2'} - self.the_input = lookup_tag('formulaequationinput')(test_system(), element, state) + self.the_input = lookup_tag('formulaequationinput')(test_capa_system(), element, state) def test_rendering(self): """ @@ -1005,7 +1005,7 @@ class DragAndDropTest(unittest.TestCase): ] } - the_input = lookup_tag('drag_and_drop_input')(test_system(), element, state) + the_input = lookup_tag('drag_and_drop_input')(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 expected = { @@ -1056,7 +1056,7 @@ class AnnotationInputTest(unittest.TestCase): tag = 'annotationinput' - the_input = lookup_tag(tag)(test_system(), element, state) + the_input = lookup_tag(tag)(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 @@ -1146,7 +1146,7 @@ class TestChoiceText(unittest.TestCase): 'submitted_message': 'Answer received.' } expected.update(state) - the_input = lookup_tag(tag)(test_system(), element, state) + the_input = lookup_tag(tag)(test_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=W0212 self.assertEqual(context, expected) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 87ded3419b..ebf985ceed 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -13,7 +13,7 @@ import textwrap import requests import mock -from . import new_loncapa_problem, test_system +from . import new_loncapa_problem, test_capa_system import calc from capa.responsetypes import LoncapaProblemError, \ @@ -37,9 +37,9 @@ class ResponseTest(unittest.TestCase): if self.xml_factory_class: self.xml_factory = self.xml_factory_class() - def build_problem(self, system=None, **kwargs): + def build_problem(self, capa_system=None, **kwargs): xml = self.xml_factory.build_xml(**kwargs) - return new_loncapa_problem(xml, system=system) + return new_loncapa_problem(xml, capa_system=capa_system) def assert_grade(self, problem, submission, expected_correctness, msg=None): input_dict = {'1_2_1': submission} @@ -1022,10 +1022,10 @@ class JavascriptResponseTest(ResponseTest): coffee_file_path = os.path.dirname(__file__) + "/test_files/js/*.coffee" os.system("node_modules/.bin/coffee -c %s" % (coffee_file_path)) - system = test_system() - system.can_execute_unsafe_code = lambda: True + capa_system = test_capa_system() + capa_system.can_execute_unsafe_code = lambda: True problem = self.build_problem( - system=system, + capa_system=capa_system, generator_src="test_problem_generator.js", grader_src="test_problem_grader.js", display_class="TestProblemDisplay", @@ -1040,12 +1040,12 @@ class JavascriptResponseTest(ResponseTest): def test_cant_execute_javascript(self): # If the system says to disallow unsafe code execution, then making # this problem will raise an exception. - system = test_system() - system.can_execute_unsafe_code = lambda: False + capa_system = test_capa_system() + capa_system.can_execute_unsafe_code = lambda: False with self.assertRaises(LoncapaProblemError): self.build_problem( - system=system, + capa_system=capa_system, generator_src="test_problem_generator.js", grader_src="test_problem_grader.js", display_class="TestProblemDisplay", diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py index bf621fc396..7091d4e45f 100644 --- a/common/lib/xmodule/xmodule/capa_base.py +++ b/common/lib/xmodule/xmodule/capa_base.py @@ -11,7 +11,7 @@ import sys from pkg_resources import resource_string -from capa.capa_problem import LoncapaProblem +from capa.capa_problem import LoncapaProblem, LoncapaSystem from capa.responsetypes import StudentInputError, \ ResponseError, LoncapaProblemError from capa.util import convert_files_to_filenames @@ -260,12 +260,26 @@ class CapaMixin(CapaFields): if text is None: text = self.data + capa_system = LoncapaSystem( + ajax_url=self.system.ajax_url, + anonymous_student_id=self.system.anonymous_student_id, + cache=self.system.cache, + can_execute_unsafe_code=self.system.can_execute_unsafe_code, + DEBUG=self.system.DEBUG, + filestore=self.system.filestore, + node_path=self.system.node_path, + render_template=self.system.render_template, + seed=self.system.seed, # Why do we do this if we have self.seed? + STATIC_URL=self.system.STATIC_URL, + xqueue=self.system.xqueue, + ) + return LoncapaProblem( problem_text=text, id=self.location.html_id(), state=state, seed=self.seed, - system=self.runtime, + capa_system=capa_system, ) def get_state_for_lcp(self):