[Python-checkins] bpo-35798: Add test.support.check_syntax_warning(). (#11895)

Serhiy Storchaka webhook-mailer at python.org
Tue Feb 19 01:30:20 EST 2019


https://github.com/python/cpython/commit/e7a4bb554edb72fc6619d23241d59162d06f249a
commit: e7a4bb554edb72fc6619d23241d59162d06f249a
branch: master
author: Serhiy Storchaka <storchaka at gmail.com>
committer: GitHub <noreply at github.com>
date: 2019-02-19T08:30:15+02:00
summary:

bpo-35798: Add test.support.check_syntax_warning(). (#11895)

It checks that a SyntaxWarning is raised when compile specified
statement, that it is raised only once, that it is converted to
a SyntaxError when raised as exception, and that both warning and
exception objects have corresponding attributes.

files:
A Misc/NEWS.d/next/Tests/2019-02-16-15-19-31.bpo-35798.JF16MP.rst
M Doc/library/test.rst
M Lib/test/support/__init__.py
M Lib/test/test_grammar.py
M Lib/test/test_string_literals.py

diff --git a/Doc/library/test.rst b/Doc/library/test.rst
index 6a10babe259c..b5057c0ace56 100644
--- a/Doc/library/test.rst
+++ b/Doc/library/test.rst
@@ -936,9 +936,24 @@ The :mod:`test.support` module defines the following functions:
 
    Test for syntax errors in *statement* by attempting to compile *statement*.
    *testcase* is the :mod:`unittest` instance for the test.  *errtext* is the
-   text of the error raised by :exc:`SyntaxError`.  If *lineno* is not None,
-   compares to the line of the :exc:`SyntaxError`.  If *offset* is not None,
-   compares to the offset of the :exc:`SyntaxError`.
+   regular expression which should match the string representation of the
+   raised :exc:`SyntaxError`.  If *lineno* is not ``None``, compares to
+   the line of the exception.  If *offset* is not ``None``, compares to
+   the offset of the exception.
+
+
+.. function:: check_syntax_warning(testcase, statement, errtext='', *, lineno=1, offset=None)
+
+   Test for syntax warning in *statement* by attempting to compile *statement*.
+   Test also that the :exc:`SyntaxWarning` is emitted only once, and that it
+   will be converted to a :exc:`SyntaxError` when turned into error.
+   *testcase* is the :mod:`unittest` instance for the test.  *errtext* is the
+   regular expression which should match the string representation of the
+   emitted :exc:`SyntaxWarning` and raised :exc:`SyntaxError`.  If *lineno*
+   is not ``None``, compares to the line of the warning and exception.
+   If *offset* is not ``None``, compares to the offset of the exception.
+
+   .. versionadded:: 3.8
 
 
 .. function:: open_urlresource(url, *args, **kw)
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 436f648a979c..9b75a211bea0 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -86,6 +86,7 @@
     # unittest
     "is_resource_enabled", "requires", "requires_freebsd_version",
     "requires_linux_version", "requires_mac_ver", "check_syntax_error",
+    "check_syntax_warning",
     "TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset",
     "transient_internet", "BasicTestRunner", "run_unittest", "run_doctest",
     "skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
@@ -1113,6 +1114,7 @@ def make_bad_fd():
         file.close()
         unlink(TESTFN)
 
+
 def check_syntax_error(testcase, statement, errtext='', *, lineno=None, offset=None):
     with testcase.assertRaisesRegex(SyntaxError, errtext) as cm:
         compile(statement, '<test string>', 'exec')
@@ -1124,6 +1126,33 @@ def check_syntax_error(testcase, statement, errtext='', *, lineno=None, offset=N
     if offset is not None:
         testcase.assertEqual(err.offset, offset)
 
+def check_syntax_warning(testcase, statement, errtext='', *, lineno=1, offset=None):
+    # Test also that a warning is emitted only once.
+    with warnings.catch_warnings(record=True) as warns:
+        warnings.simplefilter('always', SyntaxWarning)
+        compile(statement, '<testcase>', 'exec')
+    testcase.assertEqual(len(warns), 1, warns)
+
+    warn, = warns
+    testcase.assertTrue(issubclass(warn.category, SyntaxWarning), warn.category)
+    if errtext:
+        testcase.assertRegex(str(warn.message), errtext)
+    testcase.assertEqual(warn.filename, '<testcase>')
+    testcase.assertIsNotNone(warn.lineno)
+    if lineno is not None:
+        testcase.assertEqual(warn.lineno, lineno)
+
+    # SyntaxWarning should be converted to SyntaxError when raised,
+    # since the latter contains more information and provides better
+    # error report.
+    with warnings.catch_warnings(record=True) as warns:
+        warnings.simplefilter('error', SyntaxWarning)
+        check_syntax_error(testcase, statement, errtext,
+                           lineno=lineno, offset=offset)
+    # No warnings are leaked when a SyntaxError is raised.
+    testcase.assertEqual(warns, [])
+
+
 def open_urlresource(url, *args, **kw):
     import urllib.request, urllib.parse
 
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index 2ee38f028443..6d7d5544ed9c 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -1,7 +1,7 @@
 # Python test set -- part 1, grammar.
 # This just tests whether the parser accepts them all.
 
-from test.support import check_syntax_error
+from test.support import check_syntax_error, check_syntax_warning
 import inspect
 import unittest
 import sys
@@ -101,7 +101,7 @@
 
 class TokenTests(unittest.TestCase):
 
-    check_syntax_error = check_syntax_error
+    from test.support import check_syntax_error
 
     def test_backslash(self):
         # Backslash means line continuation:
@@ -276,7 +276,7 @@ def __getitem__(self, item):
 
 class GrammarTests(unittest.TestCase):
 
-    check_syntax_error = check_syntax_error
+    from test.support import check_syntax_error, check_syntax_warning
 
     # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
     # XXX can't test in a script -- this rule is only used when interactive
@@ -1109,12 +1109,10 @@ def testAssert2(self):
         else:
             self.fail("AssertionError not raised by 'assert False'")
 
-        with self.assertWarnsRegex(SyntaxWarning, 'assertion is always true'):
-            compile('assert(x, "msg")', '<testcase>', 'exec')
+        self.check_syntax_warning('assert(x, "msg")',
+                                  'assertion is always true')
         with warnings.catch_warnings():
-            warnings.filterwarnings('error', category=SyntaxWarning)
-            with self.assertRaisesRegex(SyntaxError, 'assertion is always true'):
-                compile('assert(x, "msg")', '<testcase>', 'exec')
+            warnings.simplefilter('error', SyntaxWarning)
             compile('assert x, "msg"', '<testcase>', 'exec')
 
 
@@ -1243,12 +1241,7 @@ def test_comparison(self):
 
     def test_comparison_is_literal(self):
         def check(test, msg='"is" with a literal'):
-            with self.assertWarnsRegex(SyntaxWarning, msg):
-                compile(test, '<testcase>', 'exec')
-            with warnings.catch_warnings():
-                warnings.filterwarnings('error', category=SyntaxWarning)
-                with self.assertRaisesRegex(SyntaxError, msg):
-                    compile(test, '<testcase>', 'exec')
+            self.check_syntax_warning(test, msg)
 
         check('x is 1')
         check('x is "thing"')
@@ -1257,7 +1250,7 @@ def check(test, msg='"is" with a literal'):
         check('x is not 1', '"is not" with a literal')
 
         with warnings.catch_warnings():
-            warnings.filterwarnings('error', category=SyntaxWarning)
+            warnings.simplefilter('error', SyntaxWarning)
             compile('x is None', '<testcase>', 'exec')
             compile('x is False', '<testcase>', 'exec')
             compile('x is True', '<testcase>', 'exec')
@@ -1265,12 +1258,7 @@ def check(test, msg='"is" with a literal'):
 
     def test_warn_missed_comma(self):
         def check(test):
-            with self.assertWarnsRegex(SyntaxWarning, msg):
-                compile(test, '<testcase>', 'exec')
-            with warnings.catch_warnings():
-                warnings.filterwarnings('error', category=SyntaxWarning)
-                with self.assertRaisesRegex(SyntaxError, msg):
-                    compile(test, '<testcase>', 'exec')
+            self.check_syntax_warning(test, msg)
 
         msg=r'is not callable; perhaps you missed a comma\?'
         check('[(1, 2) (3, 4)]')
@@ -1342,7 +1330,7 @@ def check(test):
         check('[[1, 2] [...]]')
 
         with warnings.catch_warnings():
-            warnings.filterwarnings('error', category=SyntaxWarning)
+            warnings.simplefilter('error', SyntaxWarning)
             compile('[(lambda x, y: x) (3, 4)]', '<testcase>', 'exec')
             compile('[[1, 2] [i]]', '<testcase>', 'exec')
             compile('[[1, 2] [0]]', '<testcase>', 'exec')
diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py
index 635ba57e6c95..048f40d90a4b 100644
--- a/Lib/test/test_string_literals.py
+++ b/Lib/test/test_string_literals.py
@@ -63,6 +63,8 @@ def byte(i):
 
 class TestLiterals(unittest.TestCase):
 
+    from test.support import check_syntax_warning
+
     def setUp(self):
         self.save_path = sys.path[:]
         self.tmpdir = tempfile.mkdtemp()
@@ -112,21 +114,7 @@ def test_eval_str_invalid_escape(self):
             with self.assertWarns(SyntaxWarning):
                 self.assertEqual(eval(r"'\%c'" % b), '\\' + chr(b))
 
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('always', category=SyntaxWarning)
-            eval("'''\n\\z'''")
-        self.assertEqual(len(w), 1)
-        self.assertEqual(w[0].filename, '<string>')
-        self.assertEqual(w[0].lineno, 1)
-
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('error', category=SyntaxWarning)
-            with self.assertRaises(SyntaxError) as cm:
-                eval("'''\n\\z'''")
-            exc = cm.exception
-        self.assertEqual(w, [])
-        self.assertEqual(exc.filename, '<string>')
-        self.assertEqual(exc.lineno, 1)
+        self.check_syntax_warning("'''\n\\z'''")
 
     def test_eval_str_raw(self):
         self.assertEqual(eval(""" r'x' """), 'x')
@@ -161,21 +149,7 @@ def test_eval_bytes_invalid_escape(self):
             with self.assertWarns(SyntaxWarning):
                 self.assertEqual(eval(r"b'\%c'" % b), b'\\' + bytes([b]))
 
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('always', category=SyntaxWarning)
-            eval("b'''\n\\z'''")
-        self.assertEqual(len(w), 1)
-        self.assertEqual(w[0].filename, '<string>')
-        self.assertEqual(w[0].lineno, 1)
-
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('error', category=SyntaxWarning)
-            with self.assertRaises(SyntaxError) as cm:
-                eval("b'''\n\\z'''")
-            exc = cm.exception
-        self.assertEqual(w, [])
-        self.assertEqual(exc.filename, '<string>')
-        self.assertEqual(exc.lineno, 1)
+        self.check_syntax_warning("b'''\n\\z'''")
 
     def test_eval_bytes_raw(self):
         self.assertEqual(eval(""" br'x' """), b'x')
diff --git a/Misc/NEWS.d/next/Tests/2019-02-16-15-19-31.bpo-35798.JF16MP.rst b/Misc/NEWS.d/next/Tests/2019-02-16-15-19-31.bpo-35798.JF16MP.rst
new file mode 100644
index 000000000000..e3204666cba5
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2019-02-16-15-19-31.bpo-35798.JF16MP.rst
@@ -0,0 +1 @@
+Added :func:`test.support.check_syntax_warning`.



More information about the Python-checkins mailing list