def has_printout(state, index, not_printed_msg=None, pre_code=None, name=None, copy=False): """Check if the right printouts happened. ``has_printout()`` will look for the printout in the solution code that you specified with ``index`` (0 in this case), rerun the ``print()`` call in the solution process, capture its output, and verify whether the output is present in the output of the student. This is more robust as ``Ex().check_function('print')`` initiated chains as students can use as many printouts as they want, as long as they do the correct one somewhere. Args: index (int): index of the ``print()`` call in the solution whose output you want to search for in the student output. not_printed_msg (str): if specified, this overrides the default message that is generated when the output is not found in the student output. pre_code (str): Python code as a string that is executed before running the targeted student call. This is the ideal place to set a random seed, for example. copy (bool): whether to try to deep copy objects in the environment, such as lists, that could accidentally be mutated. Disabled by default, which speeds up SCTs. state (State): state as passed by the SCT chain. Don't specify this explicitly. :Example: Suppose you want somebody to print out 4: :: print(1, 2, 3, 4) The following SCT would check that: :: Ex().has_printout(0) All of the following SCTs would pass: :: print(1, 2, 3, 4) print('1 2 3 4') print(1, 2, '3 4') print("random"); print(1, 2, 3, 4) :Example: Watch out: ``has_printout()`` will effectively **rerun** the ``print()`` call in the solution process after the entire solution script was executed. If your solution script updates the value of `x` after executing it, ``has_printout()`` will not work. Suppose you have the following solution: :: x = 4 print(x) x = 6 The following SCT will not work: :: Ex().has_printout(0) Why? When the ``print(x)`` call is executed, the value of ``x`` will be 6, and pythonwhat will look for the output `'6`' in the output the student generated. In cases like these, ``has_printout()`` cannot be used. :Example: Inside a for loop ``has_printout()`` Suppose you have the following solution: :: for i in range(5): print(i) The following SCT will not work: :: Ex().check_for_loop().check_body().has_printout(0) The reason is that ``has_printout()`` can only be called from the root state. ``Ex()``. If you want to check printouts done in e.g. a for loop, you have to use a `check_function('print')` chain instead: :: Ex().check_for_loop().check_body().\\ set_context(0).check_function('print').\\ check_args(0).has_equal_value() """ extra_msg = "If you want to check printouts done in e.g. a for loop, you have to use a `check_function('print')` chain instead." state.assert_execution_root("has_printout", extra_msg=extra_msg) if not_printed_msg is None: not_printed_msg = ( "Have you used `{{sol_call}}` to do the appropriate printouts?") try: sol_call_ast = state.ast_dispatcher.find( "function_calls", state.solution_ast)["print"][index]["node"] except (KeyError, IndexError): raise InstructorError.from_message( "`has_printout({})` couldn't find the {} print call in your solution." .format(index, get_ord(index + 1))) out_sol, str_sol = getOutputInProcess( tree=sol_call_ast, process=state.solution_process, context=state.solution_context, env=state.solution_env, pre_code=pre_code, copy=copy, ) sol_call_str = state.solution_ast_tokens.get_text(sol_call_ast) if isinstance(str_sol, Exception): with debugger(state): state.report( "Evaluating the solution expression {} raised error in solution process." "Error: {} - {}".format(sol_call_str, type(out_sol), str_sol)) has_output( state, out_sol.strip(), pattern=False, no_output_msg=FeedbackComponent(not_printed_msg, {"sol_call": sol_call_str}), ) return state
def has_printout(index, not_printed_msg=None, pre_code=None, name=None, copy=False, state=None): """Check if the output of print() statement in the solution is in the output the student generated. This is more robust as ``Ex().check_function('print')`` initiated chains as students can use as many printouts as they want, as long as they do the correct one somewhere. .. note:: When zooming in on parts of the student submission (with e.g. ``check_for_loop()``), we are not zooming in on the piece of the student output that is related to that piece of the student code. In other words, ``has_printout()`` always considers the entire student output. Args: index (int): index of the ``print()`` call in the solution whose output you want to search for in the student output. not_printed_msg (str): if specified, this overrides the default message that is generated when the output is not found in the student output. pre_code (str): Python code as a string that is executed before running the targeted student call. This is the ideal place to set a random seed, for example. copy (bool): whether to try to deep copy objects in the environment, such as lists, that could accidentally be mutated. Disabled by default, which speeds up SCTs. state (State): state as passed by the SCT chain. Don't specify this explicitly. :Example: Solution:: print(1, 2, 3, 4) SCT:: Ex().has_printout(0) Each of these submissions will pass:: print(1, 2, 3, 4) print('1 2 3 4') print(1, 2, '3 4') print("random"); print(1, 2, 3, 4) """ if not_printed_msg is None: not_printed_msg = "__JINJA__:Have you used `{{sol_call}}` to do the appropriate printouts?" try: sol_call_ast = state.solution_function_calls['print'][index]['node'] except (KeyError, IndexError): raise ValueError( "Using has_printout() with index {} expects that there is/are at least {} print() call(s) in your solution." "Is that the case?".format(index, index + 1)) out_sol, str_sol = getOutputInProcess(tree=sol_call_ast, process=state.solution_process, context=state.solution_context, env=state.solution_env, pre_code=pre_code, copy=copy) sol_call_str = state.solution_tree_tokens.get_text(sol_call_ast) if isinstance(str_sol, Exception): raise ValueError( "Evaluating the solution expression {} raised error in solution process." "Error: {} - {}".format(sol_call_str, type(out_sol), str_sol)) _msg = state.build_message(not_printed_msg, {'sol_call': sol_call_str}) has_output(out_sol.strip(), pattern=False, no_output_msg=_msg, state=state) return state