22from pythonwhat .Test import Test , EqualTest
33from pythonwhat .Feedback import Feedback
44from pythonwhat .utils import get_ord
5+ from types import GeneratorType
56from functools import partial
67import copy
78
@@ -136,6 +137,8 @@ def has_equal_part_len(name, insufficient_msg, state=None):
136137 return state
137138
138139
140+ # functions for running multiple sub-tests ------------------------------------
141+
139142def extend (* args , state = None ):
140143 """Run multiple subtests in sequence, each using the output state of the previous."""
141144
@@ -151,30 +154,51 @@ def multi(*args, state=None):
151154 rep = Reporter .active_reporter
152155
153156 # when input is a single list of subtests
154- args = args [0 ] if len (args ) == 1 and isinstance (args [0 ], (list , tuple )) else args
157+ if len (args ) == 1 and isinstance (args [0 ], (list , tuple , GeneratorType )):
158+ args = args [0 ]
155159
156160 for test in args :
157161 # assume test is function needing a state argument
158162 # partial state so reporter can test
159- # TODO: it seems clear the reporter doesn't need to hold state anymore
160163 closure = partial (test , state = state )
161- # message from parent checks
162- #prefix = state.build_message()
163- ## resetting reporter message until it can be refactored
164- #prev_msg = rep.failure_msg
165164 rep .do_test (closure , "" , state .highlight )
166- #rep.failure_msg = prev_msg
167165
168166 # return original state, so can be chained
169167 return state
170168
169+ from pythonwhat .Test import TestFail
170+
171+ def test_not (* args , msg , state = None ):
172+ """Pass if all of the subtests fail"""
173+ rep = Reporter .active_reporter
174+
175+ try : multi (* args , state = state )
176+ except TestFail as e :
177+ rep .failed_test = False # protect against old behavior
178+ return state
179+
180+ _msg = state .build_message (msg )
181+ return rep .do_test (Test (msg ))
182+
183+ # utility functions -----------------------------------------------------------
184+
171185def quiet (n = 0 , state = None ):
172186 """Turn off prepended messages. Defaults to turning all off."""
173187 cpy = copy .copy (state )
174188 hushed = [{** m , 'msg' : "" } for m in cpy .messages ]
175189 cpy .messages = hushed
176190 return cpy
177191
192+ def fail (msg = "" , state = None ):
193+ """Fail test with message"""
194+ rep = Reporter .active_reporter
195+ _msg = state .build_message (msg )
196+ rep .do_test (Test (Feedback (msg , state .highlight )))
197+
198+ return state
199+
200+ # context functions -----------------------------------------------------------
201+
178202from pythonwhat .tasks import setUpNewEnvInProcess , breakDownNewEnvInProcess
179203def with_context (* args , state = None ):
180204 # set up context in processes
@@ -209,18 +233,31 @@ def with_context(*args, state=None):
209233 return state
210234
211235def set_context (* args , state = None , ** kwargs ):
236+ """Update context values for student and solution environments.
237+
238+ Note that excess args and unmatched kwargs will be unused in the student environment.
239+ If an argument is specified both by name and position args, will use named arg.
240+ """
212241 stu_crnt = state .student_context .context
213242 sol_crnt = state .solution_context .context
214- # set args specified by pos ----
215- upd_stu = stu_crnt .update (dict (zip (sol_crnt .keys (), args )))
243+ # set args specified by pos -----------------------------------------------
244+ # stop if too many pos args for solution
245+ if len (args ) > len (sol_crnt ):
246+ raise IndexError ("Too many positional args. There are {} context vals, but tried to set {}"
247+ .format (len (sol_crnt ), len (args )))
248+ # set pos args
216249 upd_sol = sol_crnt .update (dict (zip (stu_crnt .keys (), args )))
250+ upd_stu = stu_crnt .update (dict (zip (sol_crnt .keys (), args )))
217251
218- # set args specified by keyword ----
252+ # set args specified by keyword -------------------------------------------
253+ if set (kwargs ) - set (upd_sol ):
254+ raise KeyError ("Context val names are {}, but tried to set {}"
255+ .format (upd_sol or "none" , kwargs .keys ()))
219256 out_sol = upd_sol .update (kwargs )
220257 # need to match keys in kwargs with corresponding keys in stu context
221258 # in case they used, e.g., different loop variable names
222259 match_keys = dict (zip (sol_crnt .keys (), stu_crnt .keys ()))
223- out_stu = upd_stu .update ({match_keys [k ]: v for k ,v in kwargs .items ()})
260+ out_stu = upd_stu .update ({match_keys [k ]: v for k ,v in kwargs .items () if k in match_keys })
224261
225262 return state .to_child_state (student_subtree = None , solution_subtree = None ,
226263 student_context = out_stu , solution_context = out_sol )
@@ -304,8 +341,8 @@ def call(args,
304341 eval_sol , str_sol = run_call (args , state .solution_parts ['node' ], state .solution_process , get_func , ** kwargs )
305342
306343 if (test == 'error' ) ^ isinstance (str_sol , Exception ):
307- _msg = state .build_message ("FMT:Calling for arguments {args} resulted in an error (or not an error if testing for one). Error message: {str_sol}" ,
308- dict (args = args , str_sol = str_sol ))
344+ _msg = state .build_message ("FMT:Calling for arguments {args} resulted in an error (or not an error if testing for one). Error message: {type_err} { str_sol}" ,
345+ dict (args = args , type_err = type ( str_sol ), str_sol = str_sol ))
309346 raise ValueError (_msg )
310347
311348 if isinstance (eval_sol , ReprFail ):
@@ -367,7 +404,7 @@ def has_expr(incorrect_msg="FMT:Unexpected expression {test}: expected `{sol_eva
367404
368405 if (test == 'error' ) ^ isinstance (str_sol , Exception ):
369406 raise ValueError ("evaluating expression raised error in solution process (or not an error if testing for one). "
370- "Error message : %s" % str_sol )
407+ "Error: %s - %s" % ( type ( str_sol ), str_sol ) )
371408 if isinstance (eval_sol , ReprFail ):
372409 raise ValueError ("Couldn't figure out the value of a default argument: " + eval_sol .info )
373410
0 commit comments