def test_get_times(self): self.assertEqual(utils.get_times(1), "once") self.assertEqual(utils.get_times(2), "twice") self.assertEqual(utils.get_times(3), "3 times") self.assertEqual(utils.get_times(11), "11 times")
def check_function(name, index=0, missing_msg=None, params_not_matched_msg=None, expand_msg=None, signature=True, typestr="__JINJA__:`{name} function call{} of `{name}()`", state=None): """Check whether a particular function is called. This function is typically followed by ``check_args()`` to check whether the arguments were specified correctly. Args: name (str): the name of the function to be tested. When checking functions in packages, always use the 'full path' of the function. index (int): index of the function call to be checked. Defaults to 0. missing_msg (str): If specified, this overrides an automatically generated feedback message in case the student did not call the function correctly. params_not_matched_msg (str): If specified, this overrides an automatically generated feedback message in case the function parameters were not successfully matched. expand_msg (str): If specified, this overrides any messages that are prepended by previous SCT chains. signature (Signature): Normally, check_function() can figure out what the function signature is, but it might be necessary to use build_sig to manually build a signature and pass this along. typestr (formatted string): If specified, this overrides how the function call is automatically referred to. state (State): State object that is passed from the SCT Chain (don't specify this). :Examples: Student code and solution code:: import numpy as np arr = np.array([1, 2, 3, 4, 5]) np.mean(arr) SCT:: # Verify whether arr was correctly set in np.mean Ex().check_function('numpy.mean').check_args('a').has_equal_value() # Verify whether np.mean(arr) produced the same result Ex().check_function('numpy.mean').has_equal_value() """ append_missing = missing_msg is None append_params_not_matched = params_not_matched_msg is None if missing_msg is None: missing_msg = MISSING_MSG if expand_msg is None: expand_msg = PREPEND_MSG if params_not_matched_msg is None: params_not_matched_msg = SIG_ISSUE_MSG rep = Reporter.active_reporter stu_out = state.student_function_calls sol_out = state.solution_function_calls student_mappings = state.student_mappings fmt_kwargs = {'times': get_times(index+1), 'ord': get_ord(index+1), 'index': index, 'name': get_mapped_name(name, student_mappings)} # Get Parts ---- # Copy, otherwise signature binding overwrites sol_out[name][index]['args'] sol_parts = {**sol_out[name][index]} try: # Copy, otherwise signature binding overwrites stu_out[name][index]['args'] stu_parts = {**stu_out[name][index]} except (KeyError, IndexError): _msg = state.build_message(missing_msg, fmt_kwargs, append=append_missing) rep.do_test(Test(Feedback(_msg, state))) # Signatures ----- if signature: signature = None if isinstance(signature, bool) else signature get_sig = partial(getSignatureInProcess, name=name, signature=signature, manual_sigs = state.get_manual_sigs()) try: sol_sig = get_sig(mapped_name=sol_parts['name'], process=state.solution_process) sol_parts['args'] = bind_args(sol_sig, sol_parts['args']) except: raise ValueError("Something went wrong in matching call index {index} of {name} to its signature. " "You might have to manually specify or correct the signature." .format(index=index, name=name)) try: stu_sig = get_sig(mapped_name=stu_parts['name'], process=state.student_process) stu_parts['args'] = bind_args(stu_sig, stu_parts['args']) except Exception: _msg = state.build_message(params_not_matched_msg, fmt_kwargs, append=append_params_not_matched) rep.do_test(Test(Feedback(_msg, StubState(stu_parts['node'], state.highlighting_disabled)))) # three types of parts: pos_args, keywords, args (e.g. these are bound to sig) append_message = {'msg': expand_msg, 'kwargs': fmt_kwargs} child = part_to_child(stu_parts, sol_parts, append_message, state, node_name='function_calls') return child
def test_get_times(input, output): assert utils.get_times(input) == output
def check_function( state, name, index=0, missing_msg=None, params_not_matched_msg=None, expand_msg=None, signature=True, ): """Check whether a particular function is called. ``check_function()`` is typically followed by: - ``check_args()`` to check whether the arguments were specified. In turn, ``check_args()`` can be followed by ``has_equal_value()`` or ``has_equal_ast()`` to assert that the arguments were correctly specified. - ``has_equal_value()`` to check whether rerunning the function call coded by the student gives the same result as calling the function call as in the solution. Checking function calls is a tricky topic. Please visit the `dedicated article <articles/checking_function_calls.html>`_ for more explanation, edge cases and best practices. Args: name (str): the name of the function to be tested. When checking functions in packages, always use the 'full path' of the function. index (int): index of the function call to be checked. Defaults to 0. missing_msg (str): If specified, this overrides an automatically generated feedback message in case the student did not call the function correctly. params_not_matched_msg (str): If specified, this overrides an automatically generated feedback message in case the function parameters were not successfully matched. expand_msg (str): If specified, this overrides any messages that are prepended by previous SCT chains. signature (Signature): Normally, check_function() can figure out what the function signature is, but it might be necessary to use ``sig_from_params()`` to manually build a signature and pass this along. state (State): State object that is passed from the SCT Chain (don't specify this). :Examples: Student code and solution code:: import numpy as np arr = np.array([1, 2, 3, 4, 5]) np.mean(arr) SCT:: # Verify whether arr was correctly set in np.mean Ex().check_function('numpy.mean').check_args('a').has_equal_value() # Verify whether np.mean(arr) produced the same result Ex().check_function('numpy.mean').has_equal_value() """ append_missing = missing_msg is None append_params_not_matched = params_not_matched_msg is None if missing_msg is None: missing_msg = MISSING_MSG if expand_msg is None: expand_msg = PREPEND_MSG if params_not_matched_msg is None: params_not_matched_msg = SIG_ISSUE_MSG stu_out = state.ast_dispatcher.find("function_calls", state.student_ast) sol_out = state.ast_dispatcher.find("function_calls", state.solution_ast) student_mappings = state.ast_dispatcher.find("mappings", state.student_ast) fmt_kwargs = { "times": get_times(index + 1), "ord": get_ord(index + 1), "index": index, "mapped_name": get_mapped_name(name, student_mappings), } # Get Parts ---- # Copy, otherwise signature binding overwrites sol_out[name][index]['args'] try: sol_parts = {**sol_out[name][index]} except KeyError: raise InstructorError( "`check_function()` couldn't find a call of `%s()` in the solution code. Make sure you get the mapping right!" % name) except IndexError: raise InstructorError( "`check_function()` couldn't find %s calls of `%s()` in your solution code." % (index + 1, name)) try: # Copy, otherwise signature binding overwrites stu_out[name][index]['args'] stu_parts = {**stu_out[name][index]} except (KeyError, IndexError): _msg = state.build_message(missing_msg, fmt_kwargs, append=append_missing) state.report(_msg) # Signatures ----- if signature: signature = None if isinstance(signature, bool) else signature get_sig = partial( getSignatureInProcess, name=name, signature=signature, manual_sigs=state.get_manual_sigs(), ) try: sol_sig = get_sig(mapped_name=sol_parts["name"], process=state.solution_process) sol_parts["args"] = bind_args(sol_sig, sol_parts["args"]) except Exception as e: raise InstructorError( "`check_function()` couldn't match the %s call of `%s` to its signature:\n%s " % (get_ord(index + 1), name, e)) try: stu_sig = get_sig(mapped_name=stu_parts["name"], process=state.student_process) stu_parts["args"] = bind_args(stu_sig, stu_parts["args"]) except Exception: _msg = state.build_message(params_not_matched_msg, fmt_kwargs, append=append_params_not_matched) state.to_child(highlight=stu_parts["node"]).report(_msg) # three types of parts: pos_args, keywords, args (e.g. these are bound to sig) append_message = {"msg": expand_msg, "kwargs": fmt_kwargs} child = part_to_child(stu_parts, sol_parts, append_message, state, node_name="function_calls") return child