11from pythonwhat .Reporter import Reporter
2- from pythonwhat .Test import Test
2+ from pythonwhat .Test import Test , EqualTest
33from pythonwhat .Feedback import Feedback
44from pythonwhat .utils import get_ord
55from functools import partial
@@ -96,7 +96,10 @@ def has_part(name, msg, state=None, fmt_kwargs=None):
9696 ** fmt_kwargs
9797 }
9898
99- if not d ['stu_part' ][name ] is not None :
99+ try :
100+ part = state .student_parts [name ]
101+ if part is None : raise KeyError
102+ except (KeyError , IndexError ):
100103 _msg = state .build_message (msg , d )
101104 rep .do_test (Test (Feedback (_msg , state .highlight )))
102105
@@ -127,30 +130,6 @@ def has_equal_part_len(name, insufficient_msg, state=None):
127130
128131 return state
129132
130- def has_equal_value (msg , state = None ):
131- from pythonwhat .tasks import getResultInProcess , ReprFail
132- from pythonwhat .Test import EqualTest
133- rep = Reporter .active_reporter
134- eval_solution , str_solution = getResultInProcess (tree = state .solution_tree ,
135- context = state .solution_context ,
136- process = state .solution_process )
137- if str_solution is None :
138- raise ValueError ("Evaluating a default argument in the solution environment raised an error" )
139- if isinstance (eval_solution , ReprFail ):
140- raise ValueError ("Couldn't figure out the value of a default argument: " + eval_solution .info )
141-
142- eval_student , str_student = getResultInProcess (tree = state .student_tree ,
143- context = state .student_context ,
144- process = state .student_process )
145-
146- _msg = state .build_message (msg , {'stu_part' : state .student_parts , 'sol_part' : state .solution_parts })
147- feedback = Feedback (_msg , state .highlight )
148- if str_student is None :
149- rep .do_test (Test (feedback ))
150- else :
151- rep .do_test (EqualTest (eval_student , eval_solution , feedback ))
152-
153- return state
154133
155134def extend (* args , state = None ):
156135 """Run multiple subtests in sequence, each using the output state of the previous."""
@@ -175,11 +154,11 @@ def multi(*args, state=None):
175154 # TODO: it seems clear the reporter doesn't need to hold state anymore
176155 closure = partial (test , state = state )
177156 # message from parent checks
178- prefix = state .build_message ()
179- # resetting reporter message until it can be refactored
180- prev_msg = rep .failure_msg
181- rep .do_test (closure , prefix , state .highlight )
182- rep .failure_msg = prev_msg
157+ # prefix = state.build_message()
158+ ## resetting reporter message until it can be refactored
159+ # prev_msg = rep.failure_msg
160+ rep .do_test (closure , "" , state .highlight )
161+ # rep.failure_msg = prev_msg
183162
184163 # return original state, so can be chained
185164 return state
@@ -241,3 +220,176 @@ def set_context(*args, state=None, **kwargs):
241220 return state .to_child_state (student_subtree = None , solution_subtree = None ,
242221 student_context = out_stu , solution_context = out_sol )
243222
223+
224+ def check_arg (name , missing_msg = 'check the argument `{part}`, ' , state = None ):
225+ if name in ['*args' , '**kwargs' ]:
226+ return check_part (name , name , state = state , missing_msg = missing_msg )
227+ else :
228+ return check_part_index ('args' , name , name , state = state , missing_msg = missing_msg )
229+
230+
231+ # CALL CHECK ==================================================================
232+
233+ from pythonwhat .tasks import getResultInProcess , getOutputInProcess , getErrorInProcess , ReprFail
234+ import ast
235+
236+ evalCalls = {'value' : getResultInProcess ,
237+ 'output' : getOutputInProcess ,
238+ 'error' : getErrorInProcess }
239+
240+ call_warnings = {
241+ 'value' : 'in the solution process resulted in an error' ,
242+ 'error' : 'did not generate an error in the solution environment' ,
243+ 'output' : 'in the solution process resulted in an error'
244+ }
245+
246+ def fix_format (arguments ):
247+ if isinstance (arguments , str ):
248+ arguments = (arguments , )
249+ if isinstance (arguments , tuple ):
250+ arguments = list (arguments )
251+
252+ if isinstance (arguments , list ):
253+ arguments = {'args' : arguments , 'kwargs' : {}}
254+
255+ if not isinstance (arguments , dict ) or 'args' not in arguments or 'kwargs' not in arguments :
256+ raise ValueError ("Wrong format of arguments in 'results', 'outputs' or 'errors'; either a list, or a dictionary with names args (a list) and kwargs (a dict)" )
257+
258+ return (arguments )
259+
260+ # TODO: test string syntax with check_function_def
261+ # test argument syntax with check_lambda
262+ # implement for error and output
263+ def run_call (args , node , process , get_func , ** kwargs ):
264+ # Get function expression
265+ if isinstance (node , ast .FunctionDef ): # function name
266+ func_expr = ast .Name (id = node .name , ctx = ast .Load ())
267+ elif isinstance (node , ast .Lambda ): # lambda body expr
268+ func_expr = node
269+ else : raise TypeError ("Only function definition or lambda may be called" )
270+
271+ # args is a call string or argument list/dict
272+ if isinstance (args , str ):
273+ parsed = ast .parse (args ).body [0 ].value
274+ parsed .func = func_expr
275+ ast .fix_missing_locations (parsed )
276+ return get_func (process = process , tree = parsed , ** kwargs )
277+ else :
278+ # e.g. list -> {args: [...], kwargs: {}}
279+ fmt_args = fix_format (args )
280+ ast .fix_missing_locations (func_expr )
281+ return get_func (process = process , tree = func_expr , call = fmt_args , ** kwargs )
282+
283+
284+ MSG_CALL_INCORRECT = "Calling it should result in {str_sol}, instead got {str_sol}"
285+ MSG_CALL_ERROR = "Calling it should result in {str_sol}, instead got an error"
286+ def call (args ,
287+ test = 'value' ,
288+ incorrect_msg = MSG_CALL_INCORRECT ,
289+ error_msg = MSG_CALL_ERROR ,
290+ # TODO hardcoded lambda description for now
291+ argstr = 'the Xth lambda function' ,
292+ state = None , ** kwargs ):
293+ rep = Reporter .active_reporter
294+ test_type = ('value' , 'output' , 'error' )
295+
296+ get_func = evalCalls [test ]
297+
298+ # Run for Solution --------------------------------------------------------
299+ eval_sol , str_sol = run_call (args , state .solution_parts ['node' ], state .solution_process , get_func , ** kwargs )
300+
301+ if (test == 'error' ) ^ isinstance (str_sol , Exception ):
302+ _msg_prefix = "Calling %s for arguments %s " % (argstr , args )
303+ raise ValueError (_msg_prefix + call_warnings [test ])
304+
305+ if isinstance (eval_sol , ReprFail ):
306+ raise ValueError ("Can't get the result of calling %s for arguments %s: %s" % (argstr , args , eval_sol .info ))
307+
308+ # Run for Submission ------------------------------------------------------
309+ eval_stu , str_stu = run_call (args , state .student_parts ['node' ], state .student_process , get_func , ** kwargs )
310+ fmt_kwargs = {'part' : argstr , 'argstr' : argstr , 'str_sol' : str_sol , 'str_stu' : str_stu }
311+
312+ # either error test and no error, or vice-versa
313+ stu_node = state .student_parts ['node' ]
314+ if (test == 'error' ) ^ isinstance (str_stu , Exception ):
315+ _msg = state .build_message (error_msg , fmt_kwargs )
316+ rep .do_test (Test (Feedback (_msg , stu_node )))
317+
318+ # incorrect result
319+ _msg = state .build_message (incorrect_msg , fmt_kwargs )
320+ rep .do_test (EqualTest (eval_sol , eval_stu , Feedback (_msg , stu_node )))
321+
322+ return state
323+
324+ # Expression tests ------------------------------------------------------------
325+ from pythonwhat .tasks import ReprFail , UndefinedValue
326+ from pythonwhat .Test import EqualTest
327+ from pythonwhat import utils
328+ def has_expr (incorrect_msg ,
329+ error_msg = "Running an expression in the student process caused an issue" ,
330+ undefined_msg = "Have you defined `{name}` without errors?" ,
331+ extra_env = None ,
332+ context_vals = None ,
333+ expr_code = None ,
334+ pre_code = None ,
335+ keep_objs_in_env = None ,
336+ name = None ,
337+ highlight = None ,
338+ state = None ,
339+ test = None ):
340+ rep = Reporter .active_reporter
341+
342+ # run function to highlight a block of code
343+ if callable (highlight ):
344+ try : highlight = highlight (state = state ).student_tree
345+ except : pass
346+ highlight = highlight or state .highlight
347+
348+ get_func = partial (evalCalls [test ],
349+ extra_env = extra_env ,
350+ context_vals = context_vals ,
351+ pre_code = pre_code ,
352+ expr_code = expr_code ,
353+ keep_objs_in_env = keep_objs_in_env ,
354+ name = name ,
355+ do_exec = True if test == 'output' else False )
356+
357+ eval_sol , str_sol = get_func (tree = state .solution_tree ,
358+ process = state .solution_process ,
359+ context = state .solution_context )
360+
361+ if (test == 'error' ) ^ isinstance (str_sol , Exception ):
362+ raise ValueError ("evaluating expression raised error in solution process" )
363+ if isinstance (eval_sol , ReprFail ):
364+ raise ValueError ("Couldn't figure out the value of a default argument: " + eval_sol .info )
365+
366+ eval_stu , str_stu = get_func (tree = state .student_tree ,
367+ process = state .student_process ,
368+ context = state .student_context )
369+
370+ # kwargs ---
371+ fmt_kwargs = {'stu_part' : state .student_parts , 'sol_part' : state .solution_parts , 'name' : name }
372+ fmt_kwargs ['stu_eval' ] = utils .shorten_str (str (eval_stu ))
373+ fmt_kwargs ['sol_eval' ] = utils .shorten_str (str (eval_sol ))
374+
375+ # tests ---
376+ # error in process
377+ if (test == 'error' ) ^ isinstance (str_stu , Exception ):
378+ _msg = state .build_message (error_msg , fmt_kwargs )
379+ feedback = Feedback (_msg , highlight )
380+ rep .do_test (Test (feedback ))
381+
382+ # name is undefined after running expression
383+ if isinstance (str_stu , UndefinedValue ):
384+ _msg = state .build_message (undefined_msg , fmt_kwargs )
385+ rep .do_test (Test (Feedback (_msg , highlight )))
386+
387+ # test equality of results
388+ _msg = state .build_message (incorrect_msg , fmt_kwargs )
389+ rep .do_test (EqualTest (eval_stu , eval_sol , Feedback (_msg , highlight )))
390+
391+ return state
392+
393+ has_equal_value = partial (has_expr , test = 'value' )
394+ has_equal_output = partial (has_expr , test = 'output' )
395+ has_equal_error = partial (has_expr , test = 'error' )
0 commit comments