From e77fa7afc53e5035765751cf894dfb5d452cb53b Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Mon, 1 May 2017 16:41:43 +1000 Subject: [PATCH 01/13] Add test cases for single-line test case docstring. --- Lib/unittest/test/test_functiontestcase.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Lib/unittest/test/test_functiontestcase.py b/Lib/unittest/test/test_functiontestcase.py index c5f2bcbe741b61..3dfe017cad1c27 100644 --- a/Lib/unittest/test/test_functiontestcase.py +++ b/Lib/unittest/test/test_functiontestcase.py @@ -126,23 +126,27 @@ def test_id(self): self.assertIsInstance(test.id(), str) - # "Returns a one-line description of the test, or None if no description - # has been provided. The default implementation of this method returns - # the first line of the test method's docstring, if available, or None." - def test_shortDescription__no_docstring(self): + def test_shortDescription__no_description_no_docstring(self): + """ Should return None by default for shortDescription. """ test = unittest.FunctionTestCase(lambda: None) self.assertEqual(test.shortDescription(), None) - # "Returns a one-line description of the test, or None if no description - # has been provided. The default implementation of this method returns - # the first line of the test method's docstring, if available, or None." - def test_shortDescription__singleline_docstring(self): + def test_shortDescription__singleline_description(self): + """ Should use the specified description for shortDescription. """ desc = "this tests foo" test = unittest.FunctionTestCase(lambda: None, description=desc) self.assertEqual(test.shortDescription(), "this tests foo") + def test_shortDescription__no_description_singleline_docstring(self): + """ Should use the function docstring for the shortDescription. """ + test_function = (lambda: None) + test_function.__doc__ = """Should use the function docstring.""" + test = unittest.FunctionTestCase(test_function) + expected_description = "Should use the function docstring." + self.assertEqual(test.shortDescription(), expected_description) + if __name__ == "__main__": unittest.main() From 538ab1907f27f9728cd80c7fa947cd7eebb90512 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Mon, 1 May 2017 16:01:13 +1000 Subject: [PATCH 02/13] Add test cases for stripping surrounding space for shortDescription. --- Lib/unittest/test/test_case.py | 10 ++++++++++ Lib/unittest/test/test_functiontestcase.py | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index b84959150542bd..045c06464c5062 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -596,6 +596,16 @@ def testShortDescriptionWithMultiLineDocstring(self): 'Tests shortDescription() for a method with a longer ' 'docstring.') + @unittest.skipIf( + sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testShortDescriptionWithSurroundingSpaceOneLineDocstring(self): + """ Surrounding space should be stripped to get the shortDescription. """ + expected_description = ( + "Surrounding space should be stripped" + " to get the shortDescription.") + self.assertEqual(self.shortDescription(), expected_description) + def testAddTypeEqualityFunc(self): class SadSnake(object): """Dummy class for test_addTypeEqualityFunc.""" diff --git a/Lib/unittest/test/test_functiontestcase.py b/Lib/unittest/test/test_functiontestcase.py index 3dfe017cad1c27..4bd591209f8e4d 100644 --- a/Lib/unittest/test/test_functiontestcase.py +++ b/Lib/unittest/test/test_functiontestcase.py @@ -147,6 +147,14 @@ def test_shortDescription__no_description_singleline_docstring(self): expected_description = "Should use the function docstring." self.assertEqual(test.shortDescription(), expected_description) + def test_shortDescription__singleline_docstring_space_surrounded(self): + """ Surrounding space should be stripped to get the shortDescription. """ + test_function = (lambda: None) + test_function.__doc__ = """ Surrounding space should be stripped. """ + test = unittest.FunctionTestCase(test_function) + expected_description = "Surrounding space should be stripped." + self.assertEqual(test.shortDescription(), expected_description) + if __name__ == "__main__": unittest.main() From cec53713b87eaeec9d75b7e3aba7a1e95e1d3c85 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Mon, 1 May 2017 16:01:49 +1000 Subject: [PATCH 03/13] Add test cases for test case docstring with surrounding newlines. PEP 257 specifies that tools which parse docstrings should strip surrounding blank lines *before* determining what is the first line . --- Lib/unittest/test/test_case.py | 32 ++++++++++++++++++++++ Lib/unittest/test/test_functiontestcase.py | 30 ++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index 045c06464c5062..e01ac142087f95 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -606,6 +606,38 @@ def testShortDescriptionWithSurroundingSpaceOneLineDocstring(self): " to get the shortDescription.") self.assertEqual(self.shortDescription(), expected_description) + @unittest.skipIf( + sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testShortDescriptionWithSurroundingNewlineOneLineDocstring(self): + """ + Surrounding newlines should be stripped to get the shortDescription. + """ + expected_description = ( + "Surrounding newlines should be stripped" + " to get the shortDescription.") + self.assertEqual(self.shortDescription(), expected_description) + + @unittest.skipIf( + sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testShortDescriptionWithSurroundingNewlineMultiLineDocstring(self): + """ + Surrounding newlines should be stripped to get the shortDescription. + + The specification of how docstring space should be parsed is at + https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation + which requires that “Blank lines should be removed from the + beginning and end of the docstring.” + + The PEP 257 algorithm is implemented by `pydoc.splitdoc`. + + """ + expected_description = ( + "Surrounding newlines should be stripped" + " to get the shortDescription.") + self.assertEqual(self.shortDescription(), expected_description) + def testAddTypeEqualityFunc(self): class SadSnake(object): """Dummy class for test_addTypeEqualityFunc.""" diff --git a/Lib/unittest/test/test_functiontestcase.py b/Lib/unittest/test/test_functiontestcase.py index 4bd591209f8e4d..d1ec44254ce188 100644 --- a/Lib/unittest/test/test_functiontestcase.py +++ b/Lib/unittest/test/test_functiontestcase.py @@ -155,6 +155,36 @@ def test_shortDescription__singleline_docstring_space_surrounded(self): expected_description = "Surrounding space should be stripped." self.assertEqual(test.shortDescription(), expected_description) + def test_shortDescription__singleline_docstring_newline_surrounded(self): + """ + Surrounding newlines should be stripped to get the shortDescription. + """ + test_function = (lambda: None) + test_function.__doc__ = """ + Surrounding newlines should be stripped. + """ + test = unittest.FunctionTestCase(test_function) + expected_description = "Surrounding newlines should be stripped." + self.assertEqual(test.shortDescription(), expected_description) + + def test_shortDescription__multiline_docstring_newline_surrounded(self): + """ + Surrounding newlines should be stripped to get the shortDescription. + """ + test_function = (lambda: None) + test_function.__doc__ = """ + Surrounding newlines should be stripped. + + The specification of how docstring space should be parsed is at + https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation + which requires that “Blank lines should be removed from the + beginning and end of the docstring.” + + """ + test = unittest.FunctionTestCase(test_function) + expected_description = "Surrounding newlines should be stripped." + self.assertEqual(test.shortDescription(), expected_description) + if __name__ == "__main__": unittest.main() From d1315d476f1e7caefcb2a7b4c8e3e0a645d27614 Mon Sep 17 00:00:00 2001 From: Ben Finney Date: Mon, 1 May 2017 16:44:40 +1000 Subject: [PATCH 04/13] Parse the test case docstring using `pydoc.splitdoc`. --- Lib/unittest/case.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 993aaec1940748..3db4a59332de64 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -5,6 +5,7 @@ import difflib import logging import pprint +import pydoc import re import warnings import collections @@ -338,6 +339,25 @@ def __exit__(self, exc_type, exc_value, tb): .format(logging.getLevelName(self.level), self.logger.name)) +def short_description_from_docstring(text): + """ + Return the test case short description from a docstring. + + Ths docstring is parsed by the standard `pydoc.splitdoc` function + into (`synopsis`, `long_description`). + + If there is no `synopsis`, return None. + """ + if text: + (synopsis, long_description) = pydoc.splitdoc(text) + synopsis = synopsis.strip() + else: + # The text is either an empty string, or some other false value. + synopsis = None + + return synopsis + + class TestCase(object): """A class whose instances are single test cases. @@ -463,15 +483,8 @@ def defaultTestResult(self): return result.TestResult() def shortDescription(self): - """Returns a one-line description of the test, or None if no - description has been provided. - - The default implementation of this method returns the first line of - the specified test method's docstring. - """ - doc = self._testMethodDoc - return doc and doc.split("\n")[0].strip() or None - + """ Return a one-line description of the test, if any; otherwise None. """ + return short_description_from_docstring(self._testMethodDoc) def id(self): return "%s.%s" % (strclass(self.__class__), self._testMethodName) @@ -1395,8 +1408,7 @@ def __repr__(self): def shortDescription(self): if self._description is not None: return self._description - doc = self._testFunc.__doc__ - return doc and doc.split("\n")[0].strip() or None + return short_description_from_docstring(self._testFunc.__doc__) class _SubTest(TestCase): From 5c71fd0871b2b5c47909e75417b30101bb428ec3 Mon Sep 17 00:00:00 2001 From: Brian May Date: Sun, 21 May 2017 11:26:01 +1000 Subject: [PATCH 05/13] Change docstring into comment for internal function --- Lib/unittest/case.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 3db4a59332de64..d4567902629234 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -340,14 +340,8 @@ def __exit__(self, exc_type, exc_value, tb): def short_description_from_docstring(text): - """ - Return the test case short description from a docstring. - - Ths docstring is parsed by the standard `pydoc.splitdoc` function - into (`synopsis`, `long_description`). - - If there is no `synopsis`, return None. - """ + # Return the summary line from a docstring. + # If there is no summary line, return None. if text: (synopsis, long_description) = pydoc.splitdoc(text) synopsis = synopsis.strip() From 0094fdcb3e84d277fd146d1a07029f20fc93119e Mon Sep 17 00:00:00 2001 From: Brian May Date: Sun, 21 May 2017 11:30:07 +1000 Subject: [PATCH 06/13] Merge surrounding space test with existing tests --- Lib/unittest/test/test_case.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index e01ac142087f95..dc0484f72de400 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -577,7 +577,7 @@ def testShortDescriptionWithoutDocstring(self): @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def testShortDescriptionWithOneLineDocstring(self): - """Tests shortDescription() for a method with a docstring.""" + """ Tests shortDescription() for a method with a docstring.""" self.assertEqual( self.shortDescription(), 'Tests shortDescription() for a method with a docstring.') @@ -585,7 +585,7 @@ def testShortDescriptionWithOneLineDocstring(self): @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def testShortDescriptionWithMultiLineDocstring(self): - """Tests shortDescription() for a method with a longer docstring. + """ Tests shortDescription() for a method with a longer docstring. This method ensures that only the first line of a docstring is returned used in the short description, no matter how long the @@ -596,16 +596,6 @@ def testShortDescriptionWithMultiLineDocstring(self): 'Tests shortDescription() for a method with a longer ' 'docstring.') - @unittest.skipIf( - sys.flags.optimize >= 2, - "Docstrings are omitted with -O2 and above") - def testShortDescriptionWithSurroundingSpaceOneLineDocstring(self): - """ Surrounding space should be stripped to get the shortDescription. """ - expected_description = ( - "Surrounding space should be stripped" - " to get the shortDescription.") - self.assertEqual(self.shortDescription(), expected_description) - @unittest.skipIf( sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") From b4fe11337b004e77484a3f6dc025cefd1d0c73f2 Mon Sep 17 00:00:00 2001 From: Brian May Date: Sun, 21 May 2017 11:31:30 +1000 Subject: [PATCH 07/13] Prefix internal function with _ --- Lib/unittest/case.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index d4567902629234..4ab8a4ba97843e 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -339,7 +339,7 @@ def __exit__(self, exc_type, exc_value, tb): .format(logging.getLevelName(self.level), self.logger.name)) -def short_description_from_docstring(text): +def _short_description_from_docstring(text): # Return the summary line from a docstring. # If there is no summary line, return None. if text: @@ -478,7 +478,7 @@ def defaultTestResult(self): def shortDescription(self): """ Return a one-line description of the test, if any; otherwise None. """ - return short_description_from_docstring(self._testMethodDoc) + return _short_description_from_docstring(self._testMethodDoc) def id(self): return "%s.%s" % (strclass(self.__class__), self._testMethodName) @@ -1402,7 +1402,7 @@ def __repr__(self): def shortDescription(self): if self._description is not None: return self._description - return short_description_from_docstring(self._testFunc.__doc__) + return _short_description_from_docstring(self._testFunc.__doc__) class _SubTest(TestCase): From e3dbe3e53337531f9a485319ca88c54ae9fe91a4 Mon Sep 17 00:00:00 2001 From: Brian May Date: Sun, 21 May 2017 11:41:44 +1000 Subject: [PATCH 08/13] Fix broken docstrings These docstrings were not PEP 257, and the tests failed with the new PEP 257 compliant docstring parser. --- Lib/unittest/test/test_result.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/unittest/test/test_result.py b/Lib/unittest/test/test_result.py index 0a615535637385..7778e9a8d5f92c 100644 --- a/Lib/unittest/test/test_result.py +++ b/Lib/unittest/test/test_result.py @@ -369,6 +369,7 @@ def testGetSubTestDescriptionWithOneLineDocstring(self): "Docstrings are omitted with -O2 and above") def testGetDescriptionWithMultiLineDocstring(self): """Tests getDescription() for a method with a longer docstring. + The second line of the docstring. """ result = unittest.TextTestResult(None, True, 1) @@ -383,6 +384,7 @@ def testGetDescriptionWithMultiLineDocstring(self): "Docstrings are omitted with -O2 and above") def testGetSubTestDescriptionWithMultiLineDocstring(self): """Tests getDescription() for a method with a longer docstring. + The second line of the docstring. """ result = unittest.TextTestResult(None, True, 1) From 669b46de40d94b2db6fa95d38392adedcecf85f1 Mon Sep 17 00:00:00 2001 From: Brian May Date: Sun, 21 May 2017 11:51:50 +1000 Subject: [PATCH 09/13] Fix docstring for shortDescription This docstring was truncated in error. --- Lib/unittest/case.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 4ab8a4ba97843e..bd3361ebddb0fe 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -477,7 +477,12 @@ def defaultTestResult(self): return result.TestResult() def shortDescription(self): - """ Return a one-line description of the test, if any; otherwise None. """ + """Returns a one-line description of the test, or None if no + description has been provided. + + This method returns the summary line of + the specified test method's docstring, as per PEP-257. + """ return _short_description_from_docstring(self._testMethodDoc) def id(self): From 0caec7e29236e7b5d6bb0423e952a671f964c510 Mon Sep 17 00:00:00 2001 From: Brian May Date: Wed, 24 May 2017 17:17:15 +1000 Subject: [PATCH 10/13] Rename internal function --- Lib/unittest/case.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index bd3361ebddb0fe..ad28eaccb8958c 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -339,7 +339,7 @@ def __exit__(self, exc_type, exc_value, tb): .format(logging.getLevelName(self.level), self.logger.name)) -def _short_description_from_docstring(text): +def _get_short_description(text): # Return the summary line from a docstring. # If there is no summary line, return None. if text: @@ -483,7 +483,7 @@ def shortDescription(self): This method returns the summary line of the specified test method's docstring, as per PEP-257. """ - return _short_description_from_docstring(self._testMethodDoc) + return _get_short_description(self._testMethodDoc) def id(self): return "%s.%s" % (strclass(self.__class__), self._testMethodName) @@ -1407,7 +1407,7 @@ def __repr__(self): def shortDescription(self): if self._description is not None: return self._description - return _short_description_from_docstring(self._testFunc.__doc__) + return _get_short_description(self._testFunc.__doc__) class _SubTest(TestCase): From 17201143e69a66ee77ae33721ff5169a398cb1cb Mon Sep 17 00:00:00 2001 From: Brian May Date: Wed, 24 May 2017 17:18:53 +1000 Subject: [PATCH 11/13] Rename text parameter to doc --- Lib/unittest/case.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index ad28eaccb8958c..dc9c4c0765a437 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -339,11 +339,11 @@ def __exit__(self, exc_type, exc_value, tb): .format(logging.getLevelName(self.level), self.logger.name)) -def _get_short_description(text): +def _get_short_description(doc): # Return the summary line from a docstring. # If there is no summary line, return None. - if text: - (synopsis, long_description) = pydoc.splitdoc(text) + if doc: + (synopsis, long_description) = pydoc.splitdoc(doc) synopsis = synopsis.strip() else: # The text is either an empty string, or some other false value. From 30cec86e437da3f6af5501f4fadb64bec03e3a84 Mon Sep 17 00:00:00 2001 From: Brian May Date: Wed, 24 May 2017 17:20:23 +1000 Subject: [PATCH 12/13] Use guard clause for internal function --- Lib/unittest/case.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index dc9c4c0765a437..4a87d52cce8abd 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -342,13 +342,11 @@ def __exit__(self, exc_type, exc_value, tb): def _get_short_description(doc): # Return the summary line from a docstring. # If there is no summary line, return None. - if doc: - (synopsis, long_description) = pydoc.splitdoc(doc) - synopsis = synopsis.strip() - else: - # The text is either an empty string, or some other false value. - synopsis = None + if not doc: + return None + (synopsis, long_description) = pydoc.splitdoc(doc) + synopsis = synopsis.strip() return synopsis From ea60b7be5201dbc10388fee46a3258d9aa6301d0 Mon Sep 17 00:00:00 2001 From: Brian May Date: Wed, 24 May 2017 17:22:05 +1000 Subject: [PATCH 13/13] Replace comment with docstring --- Lib/unittest/case.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 4a87d52cce8abd..9dde7249c44454 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -340,8 +340,11 @@ def __exit__(self, exc_type, exc_value, tb): def _get_short_description(doc): - # Return the summary line from a docstring. - # If there is no summary line, return None. + """ + Return the summary line from a docstring. + + If there is no summary line, return None. + """ if not doc: return None