1+ """JMUTestCase class and related code.
2+
3+ The ``JMUTestCase`` is a subclass of ``unittest.TestCase`` with the following
4+ features:
5+
6+ * Some useful assertions for autograding.
7+ * Guaranteed test method execution order based on definition order.
8+ * ``@required`` annotation that can be used to make a particular test a requirement for all subsequent tests.
9+
10+ """
111import types
212import unittest
313import tempfile
@@ -80,14 +90,37 @@ def __new__(cls, name, bases, local):
8090
8191
8292class _JmuTestCase (unittest .TestCase ):
83- """ Additional useful assertions for grading. """
93+ """Additional useful assertions for grading.
94+
95+ This is the superclass for JmuTestCase. Users should subclass
96+ ``JmuTestCase`` in their test code. """
8497
8598 # counts the number of dynamic modules created
8699 module_count = 0
87100
88101 def assertScriptOutputEqual (self , filename , string_in , expected ,
89102 variables = None , args = "" , msg = None ,
90103 processor = None ):
104+ """Assert correct output for the provided Python script.
105+
106+ Args:
107+ filename (str): The name of the Python file to test
108+ string_in (str): A string that will be fed to stdin for the script
109+ expected (str): Expected stdout
110+ variables (dict): A dictionary mapping from variable names to
111+ values. The script will be edited with these
112+ substitutions before it is executed.
113+ args (str): Command line arguments that will be passed to the script.
114+ msg (str): Error message that will be printed if the assertion fails.
115+ 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.
118+
119+ Raises:
120+ AssertionError: If the expected output doesn't match the actual
121+ output.
122+
123+ """
91124 tmpdir = None
92125 try :
93126 tmpdir , new_file_name = utils .replace_variables (filename ,
@@ -136,6 +169,18 @@ def assertScriptOutputEqual(self, filename, string_in, expected,
136169 shutil .rmtree (tmpdir )
137170
138171 def assertNoLoops (self , filename , msg = None ):
172+ """ Assert that the provided script has no for or while loops.
173+
174+ Comments will be ignored.
175+
176+ Args:
177+ filename (str): The name of the Python file to test
178+ msg (str): Error message that will be printed if the assertion fails.
179+
180+ Raises:
181+ AssertionError: If the file contains a loop.
182+
183+ """
139184 loop_regex = "(^|(\r \n ?|\n ))\s*(for|while).*:\s*(#.*)*($|(\r \n ?|\n ))"
140185 count = utils .count_regex_matches (loop_regex , filename )
141186 message = f"It looks like the file { filename } contains at least one loop."
@@ -145,6 +190,18 @@ def assertNoLoops(self, filename, msg=None):
145190 self .fail (message )
146191
147192 def assertNoForLoops (self , filename , msg = None ):
193+ """ Assert that the provided script has no for loops.
194+
195+ Comments will be ignored.
196+
197+ Args:
198+ filename (str): The name of the Python file to test
199+ msg (str): Error message that will be printed if the assertion fails.
200+
201+ Raises:
202+ AssertionError: If the file contains a for loop.
203+
204+ """
148205 loop_regex = "(^|(\r \n ?|\n ))\s*(for).*:\s*(#.*)*($|(\r \n ?|\n ))"
149206 count = utils .count_regex_matches (loop_regex , filename )
150207 message = f"It looks like the file { filename } contains at least one for loop."
@@ -154,6 +211,18 @@ def assertNoForLoops(self, filename, msg=None):
154211 self .fail (message )
155212
156213 def assertNoWhileLoops (self , filename , msg = None ):
214+ """ Assert that the provided script has no while loops.
215+
216+ Comments will be ignored.
217+
218+ Args:
219+ filename (str): The name of the Python file to test
220+ msg (str): Error message that will be printed if the assertion fails.
221+
222+ Raises:
223+ AssertionError: If the file contains a while loop.
224+
225+ """
157226 loop_regex = "(^|(\r \n ?|\n ))\s*(while).*:\s*(#.*)*($|(\r \n ?|\n ))"
158227 count = utils .count_regex_matches (loop_regex , filename )
159228 message = f"It looks like the file { filename } contains at least one while loop."
@@ -163,6 +232,18 @@ def assertNoWhileLoops(self, filename, msg=None):
163232 self .fail (message )
164233
165234 def assertNoConditionals (self , filename , msg = None ):
235+ """ Assert that the provided script has no conditional statements.
236+
237+ Comments will be ignored. ``if __name__ == "__main__":`` will be ignored.
238+
239+ Args:
240+ filename (str): The name of the Python file to test
241+ msg (str): Error message that will be printed if the assertion fails.
242+
243+ Raises:
244+ AssertionError: If the file contains an if.
245+
246+ """
166247 if_regex = "(^|(\r \n ?|\n ))\s*if.*:\s*(#.*)*($|(\r \n ?|\n ))"
167248 main_regex = "(^|(\r \n ?|\n ))\s*if\s*__name__.*:\s*(#.*)*($|(\r \n ?|\n ))"
168249 count = utils .count_regex_matches (if_regex , filename )
@@ -174,18 +255,55 @@ def assertNoConditionals(self, filename, msg=None):
174255 self .fail (message )
175256
176257 def assertPassesPep8 (self , filename ):
258+ """Assert that there are no formatting errors as discovered by flake8.
259+
260+ This will use the config file flake8.cfg included in the autograder
261+ folder.
262+
263+ Args:
264+ filename (str): The name of the Python file to test
265+
266+ Raises:
267+ AssertionError: If flake8 produces any output.
268+
269+ """
177270 output = utils .run_flake8 (filename )
178271 if len (output ) != 0 :
179272 self .fail ("Submission does not pass pep8 checks:\n " + output )
180273 print ('Submission passes all formatting checks!' )
181274
182275 def assertDocstringsCorrect (self , filename ):
276+ """Assert that there are no formatting errors as discovered by flake8.
277+
278+ This will use the config file docstring.cfg included in the autograder
279+ folder.
280+
281+ Args:
282+ filename (str): The name of the Python file to test
283+
284+ Raises:
285+ AssertionError: If flake8 produces any output.
286+
287+ """
183288 output = utils .run_flake8_docstring (filename )
184289 if len (output ) != 0 :
185290 self .fail ("Submission does not pass docstring checks:\n " + output )
186291 print ('Submission passes all docstring checks!' )
187292
188293 def assertRequiredFilesPresent (self , required_files ):
294+ """Assert that all files in the provided list were submitted.
295+
296+ Note that this assertion won't get a chance to run if the test file
297+ attempts to import a missing file. One workaround is to do the
298+ imports inside the test methods.
299+
300+ Args:
301+ required_files (list): A list of Python file names.
302+
303+ Raises:
304+ AssertionError: If any of the indicated files are missing.
305+
306+ """
189307 missing_files = utils .check_submitted_files (required_files )
190308 for path in missing_files :
191309 print ('Missing {0}' .format (path ))
@@ -194,6 +312,11 @@ def assertRequiredFilesPresent(self, required_files):
194312
195313 def assertOutputCorrect (self , filename , string_in , expected ,
196314 variables = None , processor = None ):
315+ """ Wrapper for assertScriptOutputEqual.
316+
317+ I'm not sure why this exists. -NRS
318+
319+ """
197320 self .assertScriptOutputEqual (filename , string_in , expected ,
198321 variables = variables , processor = processor )
199322 print ('Correct output:\n ' + expected )
@@ -213,6 +336,23 @@ def run_with_substitution(self, filename, variables, func):
213336 func (dynamic_module )
214337
215338 def assertMatchCount (self , filename , regex , num_matches , msg = None ):
339+ """Assert that the regex matches exactly the correct number of times.
340+
341+ Ignores comments and docstrings.
342+
343+ Could be used if the problem instructions say something like:
344+ "Your program must use exactly one while loop."
345+
346+ Args:
347+ filename (str): The name of the Python file to test
348+ regex (str): A Python regular expression.
349+ num_matches (str): The expected number of matches.
350+ msg (str): Error message that will be printed if the assertion fails.
351+
352+ Raises:
353+ AssertionError: If the count doesn't match.
354+
355+ """
216356 count = utils .count_regex_matches (regex , filename )
217357 self .assertEqual (num_matches , count , msg = msg )
218358
0 commit comments