2020import sys
2121from importlib import import_module
2222
23-
2423_TEST_ORDER = {}
2524
2625
@@ -50,6 +49,7 @@ def wrapper(self, *args, **kwargs):
5049 else :
5150 result = func (self , * args , ** kwargs )
5251 return result
52+
5353 return wrapper
5454
5555
@@ -58,6 +58,7 @@ def required():
5858 failed then all of the following methods will fail as well.
5959
6060 """
61+
6162 def decorator (func ):
6263 @wraps (func )
6364 def wrapper (self , * args , ** kwargs ):
@@ -68,7 +69,9 @@ def wrapper(self, *args, **kwargs):
6869 result = None
6970 raise e .__class__ (str (e ) + "\n This test was required. All of the following tests will fail automatically." )
7071 return result
72+
7173 return wrapper
74+
7275 return decorator
7376
7477
@@ -92,33 +95,33 @@ def __new__(cls, name, bases, local):
9295class _JmuTestCase (unittest .TestCase ):
9396 """Additional useful assertions for grading.
9497
95- This is the superclass for JmuTestCase. Users should subclass
98+ This is the superclass for `` JmuTestCase`` . Users should subclass
9699 ``JmuTestCase`` in their test code. """
97100
98101 # counts the number of dynamic modules created
99102 module_count = 0
100103
101- def assertScriptOutputEqual (self , filename , string_in , expected ,
102- variables = None , args = "" , msg = None ,
103- processor = None ):
104- """Assert correct output for the provided Python script.
104+ def getScriptOutput (self , filename , string_in , variables = None , args = "" ,
105+ msg = None , processor = None , only_output = False ):
106+ """Get output for the provided Python script.
105107
106108 Args:
107109 filename (str): The name of the Python file to test
108110 string_in (str): A string that will be fed to stdin for the script
109- expected (str): Expected stdout
110111 variables (dict): A dictionary mapping from variable names to
111112 values. The script will be edited with these
112113 substitutions before it is executed.
113114 args (str): Command line arguments that will be passed to the script.
114115 msg (str): Error message that will be printed if the assertion fails.
115116 processor (func): A function mapping from string to string that will
116- process the script output before it is compared
117- to the expected output .
117+ process the script output before it is returned.
118+ only_output (bool): Return only the stdout (rather than also the stderr) .
118119
119- Raises:
120- AssertionError: If the expected output doesn't match the actual
121- output.
120+ Returns:
121+ dict: keys include 'stdout', and 'msg' as well as 'stderr' if there
122+ there was any output on stderr. 'stdout' and 'stderr' are the script
123+ output and 'msg' is a formatted string describing any execution errors
124+ as well as the inputs to the script.
122125
123126 """
124127 tmpdir = None
@@ -145,29 +148,108 @@ def assertScriptOutputEqual(self, filename, string_in, expected,
145148
146149 if processor :
147150 actual_text = processor (actual_text )
151+ if only_output :
152+ return {"stdout" : actual_text }
148153 stderr_text = stderr .decode ()
149154
150155 if len (stderr ) > 0 :
151- stderr_text = stderr_text .replace (utils .full_source_path () + "/" , '' )
156+ stderr_text = stderr_text .replace (utils .full_source_path () + "/" , '' )
152157 err_msg = "Error during script execution:\n {}" .format (stderr_text )
153158 out_msg = "\n Output before failure:\n {}" .format (actual_text )
154- self . fail ( err_msg + out_msg )
159+ return { "stdout" : actual_text , "stderr" : stderr_text , "msg" : err_msg + out_msg }
155160
156161 show_in = string_in .encode ('unicode_escape' ).decode ()
157162 message = "Input was: '{}'" .format (show_in )
158163 if len (args ) > 0 :
159164 message += "\n Command line arguments: {}" .format (args )
160165 if msg is not None :
161166 message += "\n " + msg
162-
163- self .assertEqual (actual_text , expected , message )
167+ return {"stdout" : actual_text , "msg" : message }
164168 finally :
165169 if tmpdir is not None :
166170 # Restore the original submission in source:
167171 shutil .copy (os .path .join (tmpdir , "__tmp_backup.py" ),
168172 utils .full_source_path (filename ))
169173 shutil .rmtree (tmpdir )
170174
175+ def assertScriptOutputEqual (self , filename , string_in , expected ,
176+ variables = None , args = "" , msg = None ,
177+ processor = None ):
178+ """Deprecated wrapper for :meth:`~jmu_gradescope_utils.jmu_test_case._JmuTestCase.assertOutputEqual`."""
179+ self .assertOutputEqual (filename , string_in , expected , variables , args ,
180+ msg , processor )
181+
182+ def assertOutputEqual (self , filename , string_in , expected ,
183+ variables = None , args = "" , msg = None ,
184+ processor = None ):
185+ """Assert correct output for the provided Python script.
186+
187+ Args:
188+ filename (str): The name of the Python file to test
189+ string_in (str): A string that will be fed to stdin for the script
190+ expected (str): Expected stdout
191+ variables (dict): A dictionary mapping from variable names to
192+ values. The script will be edited with these
193+ substitutions before it is executed.
194+ args (str): Command line arguments that will be passed to the script.
195+ msg (str): Error message that will be printed if the assertion fails.
196+ processor (func): A function mapping from string to string that will
197+ process the script output before it is compared
198+ to the expected output.
199+
200+ Raises:
201+ AssertionError: If the expected output doesn't match the actual
202+ output.
203+
204+ """
205+ result = self .getScriptOutput (filename , string_in , variables = variables ,
206+ args = args , msg = msg , processor = processor )
207+ if "stderr" in result :
208+ self .fail (result ["msg" ])
209+ self .assertEqual (result ["stdout" ], expected , result ["msg" ])
210+
211+ def assertOutputNotEqual (self , filename , string_in , expected ,
212+ variables = None , args = "" , msg = None ,
213+ processor = None ):
214+ """Assert script output is NOT equal to the indicated string.
215+
216+ See :meth:`~jmu_gradescope_utils.jmu_test_case._JmuTestCase.assertOutputEqual` for
217+ description of arguments.
218+ """
219+ result = self .getScriptOutput (filename , string_in , variables = variables ,
220+ args = args , msg = msg , processor = processor )
221+ if "stderr" in result :
222+ self .fail (result ["msg" ])
223+ self .assertNotEqual (result ["stdout" ], expected , result ["msg" ])
224+
225+ def assertInOutput (self , filename , string_in , expected ,
226+ variables = None , args = "" , msg = None ,
227+ processor = None ):
228+ """Assert script output contains the indicated string.
229+
230+ See :meth:`~jmu_gradescope_utils.jmu_test_case._JmuTestCase.assertOutputEqual` for
231+ description of arguments.
232+ """
233+ result = self .getScriptOutput (filename , string_in , variables = variables ,
234+ args = args , msg = msg , processor = processor )
235+ if "stderr" in result :
236+ self .fail (result ["msg" ])
237+ self .assertIn (expected , result ["stdout" ], result ["msg" ])
238+
239+ def assertNotInOutput (self , filename , string_in , expected ,
240+ variables = None , args = "" , msg = None ,
241+ processor = None ):
242+ """Assert script output does not contain the indicated string.
243+
244+ See :meth:`~jmu_gradescope_utils.jmu_test_case._JmuTestCase.assertOutputEqual` for
245+ description of arguments.
246+ """
247+ result = self .getScriptOutput (filename , string_in , variables = variables ,
248+ args = args , msg = msg , processor = processor )
249+ if "stderr" in result :
250+ self .fail (result ["msg" ])
251+ self .assertNotIn (expected , result ["stdout" ], result ["msg" ])
252+
171253 def assertNoLoops (self , filename , msg = None ):
172254 """ Assert that the provided script has no for or while loops.
173255
@@ -312,13 +394,13 @@ def assertRequiredFilesPresent(self, required_files):
312394
313395 def assertOutputCorrect (self , filename , string_in , expected ,
314396 variables = None , processor = None ):
315- """ Wrapper for assertScriptOutputEqual .
397+ """ Wrapper for assertOutputEqual .
316398
317399 I'm not sure why this exists. -NRS
318400
319401 """
320- self .assertScriptOutputEqual (filename , string_in , expected ,
321- variables = variables , processor = processor )
402+ self .assertOutputEqual (filename , string_in , expected ,
403+ variables = variables , processor = processor )
322404 print ('Correct output:\n ' + expected )
323405
324406 def run_with_substitution (self , filename , variables , func ):
@@ -328,7 +410,9 @@ def run_with_substitution(self, filename, variables, func):
328410 if filename [- 3 :] == '.py' :
329411 short_filename = filename [0 :- 3 ]
330412 new_module_name = short_filename + "_" + str (_JmuTestCase .module_count )
331- (tmpdir , new_file_name ) = utils .replace_variables (filename , variables = variables , new_name = new_module_name + ".py" )
413+ (tmpdir , new_file_name ) = utils .replace_variables (filename ,
414+ variables = variables ,
415+ new_name = new_module_name + ".py" )
332416 # insert the new temporary directory into the system module load path
333417 sys .path .insert (1 , tmpdir )
334418 # load the module
@@ -359,12 +443,11 @@ def assertMatchCount(self, filename, regex, num_matches, msg=None):
359443
360444class JmuTestCase (_JmuTestCase , metaclass = OrderAllTestsMeta ):
361445 """Test methods declared within subclasses will be executed in the
362- order they are declared as long as the sortTestMethodUsing attrute
446+ order they are declared as long as the `` sortTestMethodUsing`` attribute
363447 of the defaultTestLoader has been set::
448+ unittest.defaultTestLoader.sortTestMethodsUsing = test_compare
364449
365- unittest.defaultTestLoader.sortTestMethodsUsing = test_compare
366-
367- They will also respect the @required decorator.
450+ They will also respect the ``@required`` decorator.
368451
369452 """
370453 pass
0 commit comments