[Python-checkins] r86908 - in python/branches/py3k: Doc/library/unittest.rst Doc/library/warnings.rst Lib/unittest/main.py Lib/unittest/runner.py Lib/unittest/test/_test_warnings.py Lib/unittest/test/test_break.py Lib/unittest/test/test_program.py Lib/unittest/test/test_runner.py

ezio.melotti python-checkins at python.org
Wed Dec 1 01:56:10 CET 2010


Author: ezio.melotti
Date: Wed Dec  1 01:56:10 2010
New Revision: 86908

Log:
#10535: Enable silenced warnings in unittest by default

Added:
   python/branches/py3k/Lib/unittest/test/_test_warnings.py
Modified:
   python/branches/py3k/Doc/library/unittest.rst
   python/branches/py3k/Doc/library/warnings.rst
   python/branches/py3k/Lib/unittest/main.py
   python/branches/py3k/Lib/unittest/runner.py
   python/branches/py3k/Lib/unittest/test/test_break.py
   python/branches/py3k/Lib/unittest/test/test_program.py
   python/branches/py3k/Lib/unittest/test/test_runner.py

Modified: python/branches/py3k/Doc/library/unittest.rst
==============================================================================
--- python/branches/py3k/Doc/library/unittest.rst	(original)
+++ python/branches/py3k/Doc/library/unittest.rst	Wed Dec  1 01:56:10 2010
@@ -1845,12 +1845,21 @@
    instead of repeatedly creating new instances.
 
 
-.. class:: TextTestRunner(stream=sys.stderr, descriptions=True, verbosity=1, runnerclass=None)
+.. class:: TextTestRunner(stream=sys.stderr, descriptions=True, verbosity=1, runnerclass=None, warnings=None)
 
    A basic test runner implementation which prints results on standard error.  It
    has a few configurable parameters, but is essentially very simple.  Graphical
    applications which run test suites should provide alternate implementations.
 
+   By default this runner shows :exc:`DeprecationWarning`,
+   :exc:`PendingDeprecationWarning`, and :exc:`ImportWarning` even if they are
+   :ref:`ignored by default <warning-ignored>`. Deprecation warnings caused by
+   :ref:`deprecated unittest methods <deprecated-aliases>` are also
+   special-cased and, when the warning filters are ``'default'`` or ``'always'``,
+   they will appear only once per-module, in order to avoid too many warning
+   messages.  This behavior can be overridden using the :option`-Wd` or
+   :option:`-Wa` options and leaving *warnings* to ``None``.
+
    .. method:: _makeResult()
 
       This method returns the instance of ``TestResult`` used by :meth:`run`.
@@ -1864,7 +1873,9 @@
 
         stream, descriptions, verbosity
 
-.. function:: main(module='__main__', defaultTest=None, argv=None, testRunner=None, testLoader=unittest.loader.defaultTestLoader, exit=True, verbosity=1, failfast=None, catchbreak=None, buffer=None)
+   .. versionchanged:: 3.2 Added the ``warnings`` argument
+
+.. function:: main(module='__main__', defaultTest=None, argv=None, testRunner=None, testLoader=unittest.loader.defaultTestLoader, exit=True, verbosity=1, failfast=None, catchbreak=None, buffer=None, warnings=None)
 
    A command-line program that runs a set of tests; this is primarily for making
    test modules conveniently executable.  The simplest use for this function is to
@@ -1893,12 +1904,17 @@
    The ``failfast``, ``catchbreak`` and ``buffer`` parameters have the same
    effect as the same-name `command-line options`_.
 
+   The *warning* argument specifies the :ref:`warning filter <warning-filter>`
+   that should be used while running the tests.  If it's not specified, it will
+   remain ``None`` if a :option:`-W` option is passed to :program:`python`,
+   otherwise it will be set to ``'default'``.
+
    Calling ``main`` actually returns an instance of the ``TestProgram`` class.
    This stores the result of the tests run as the ``result`` attribute.
 
    .. versionchanged:: 3.2
-      The ``exit``, ``verbosity``, ``failfast``, ``catchbreak`` and ``buffer``
-      parameters were added.
+      The ``exit``, ``verbosity``, ``failfast``, ``catchbreak``, ``buffer``,
+      and ``warnings`` parameters were added.
 
 
 load_tests Protocol

Modified: python/branches/py3k/Doc/library/warnings.rst
==============================================================================
--- python/branches/py3k/Doc/library/warnings.rst	(original)
+++ python/branches/py3k/Doc/library/warnings.rst	Wed Dec  1 01:56:10 2010
@@ -249,6 +249,8 @@
 entries from the warnings list before each new operation).
 
 
+.. _warning-ignored:
+
 Updating Code For New Versions of Python
 ----------------------------------------
 
@@ -279,6 +281,9 @@
 developer want to be notified that your code is using a deprecated module, to a
 user this information is essentially noise and provides no benefit to them.
 
+The :mod:`unittest` module has been also updated to use the ``'default'``
+filter while running tests.
+
 
 .. _warning-functions:
 

Modified: python/branches/py3k/Lib/unittest/main.py
==============================================================================
--- python/branches/py3k/Lib/unittest/main.py	(original)
+++ python/branches/py3k/Lib/unittest/main.py	Wed Dec  1 01:56:10 2010
@@ -67,12 +67,12 @@
     USAGE = USAGE_FROM_MODULE
 
     # defaults for testing
-    failfast = catchbreak = buffer = progName = None
+    failfast = catchbreak = buffer = progName = warnings = None
 
     def __init__(self, module='__main__', defaultTest=None, argv=None,
                     testRunner=None, testLoader=loader.defaultTestLoader,
                     exit=True, verbosity=1, failfast=None, catchbreak=None,
-                    buffer=None):
+                    buffer=None, warnings=None):
         if isinstance(module, str):
             self.module = __import__(module)
             for part in module.split('.')[1:]:
@@ -87,6 +87,18 @@
         self.catchbreak = catchbreak
         self.verbosity = verbosity
         self.buffer = buffer
+        if warnings is None and not sys.warnoptions:
+            # even if DreprecationWarnings are ignored by default
+            # print them anyway unless other warnings settings are
+            # specified by the warnings arg or the -W python flag
+            self.warnings = 'default'
+        else:
+            # here self.warnings is set either to the value passed
+            # to the warnings args or to None.
+            # If the user didn't pass a value self.warnings will
+            # be None. This means that the behavior is unchanged
+            # and depends on the values passed to -W.
+            self.warnings = warnings
         self.defaultTest = defaultTest
         self.testRunner = testRunner
         self.testLoader = testLoader
@@ -220,7 +232,8 @@
             try:
                 testRunner = self.testRunner(verbosity=self.verbosity,
                                              failfast=self.failfast,
-                                             buffer=self.buffer)
+                                             buffer=self.buffer,
+                                             warnings=self.warnings)
             except TypeError:
                 # didn't accept the verbosity, buffer or failfast arguments
                 testRunner = self.testRunner()

Modified: python/branches/py3k/Lib/unittest/runner.py
==============================================================================
--- python/branches/py3k/Lib/unittest/runner.py	(original)
+++ python/branches/py3k/Lib/unittest/runner.py	Wed Dec  1 01:56:10 2010
@@ -2,6 +2,7 @@
 
 import sys
 import time
+import warnings
 
 from . import result
 from .signals import registerResult
@@ -125,12 +126,13 @@
     resultclass = TextTestResult
 
     def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1,
-                 failfast=False, buffer=False, resultclass=None):
+                 failfast=False, buffer=False, resultclass=None, warnings=None):
         self.stream = _WritelnDecorator(stream)
         self.descriptions = descriptions
         self.verbosity = verbosity
         self.failfast = failfast
         self.buffer = buffer
+        self.warnings = warnings
         if resultclass is not None:
             self.resultclass = resultclass
 
@@ -143,17 +145,30 @@
         registerResult(result)
         result.failfast = self.failfast
         result.buffer = self.buffer
-        startTime = time.time()
-        startTestRun = getattr(result, 'startTestRun', None)
-        if startTestRun is not None:
-            startTestRun()
-        try:
-            test(result)
-        finally:
-            stopTestRun = getattr(result, 'stopTestRun', None)
-            if stopTestRun is not None:
-                stopTestRun()
-        stopTime = time.time()
+        with warnings.catch_warnings():
+            if self.warnings:
+                # if self.warnings is set, use it to filter all the warnings
+                warnings.simplefilter(self.warnings)
+                # if the filter is 'default' or 'always', special-case the
+                # warnings from the deprecated unittest methods to show them
+                # no more than once per module, because they can be fairly
+                # noisy.  The -Wd and -Wa flags can be used to bypass this
+                # only when self.warnings is None.
+                if self.warnings in ['default', 'always']:
+                    warnings.filterwarnings('module',
+                            category=DeprecationWarning,
+                            message='Please use assert\w+ instead.')
+            startTime = time.time()
+            startTestRun = getattr(result, 'startTestRun', None)
+            if startTestRun is not None:
+                startTestRun()
+            try:
+                test(result)
+            finally:
+                stopTestRun = getattr(result, 'stopTestRun', None)
+                if stopTestRun is not None:
+                    stopTestRun()
+            stopTime = time.time()
         timeTaken = stopTime - startTime
         result.printErrors()
         if hasattr(result, 'separator2'):

Added: python/branches/py3k/Lib/unittest/test/_test_warnings.py
==============================================================================
--- (empty file)
+++ python/branches/py3k/Lib/unittest/test/_test_warnings.py	Wed Dec  1 01:56:10 2010
@@ -0,0 +1,74 @@
+# helper module for test_runner.Test_TextTestRunner.test_warnings
+
+"""
+This module has a number of tests that raise different kinds of warnings.
+When the tests are run, the warnings are caught and their messages are printed
+to stdout.  This module also accepts an arg that is then passed to
+unittest.main to affect the behavior of warnings.
+Test_TextTestRunner.test_warnings executes this script with different
+combinations of warnings args and -W flags and check that the output is correct.
+See #10535.
+"""
+
+import io
+import sys
+import unittest
+import warnings
+
+def warnfun():
+    warnings.warn('rw', RuntimeWarning)
+
+class TestWarnings(unittest.TestCase):
+    # unittest warnings will be printed at most once per type (max one message
+    # for the fail* methods, and one for the assert* methods)
+    def test_assert(self):
+        self.assertEquals(2+2, 4)
+        self.assertEquals(2*2, 4)
+        self.assertEquals(2**2, 4)
+
+    def test_fail(self):
+        self.failUnless(1)
+        self.failUnless(True)
+
+    def test_other_unittest(self):
+        self.assertAlmostEqual(2+2, 4)
+        self.assertNotAlmostEqual(4+4, 2)
+
+    # these warnings are normally silenced, but they are printed in unittest
+    def test_deprecation(self):
+        warnings.warn('dw', DeprecationWarning)
+        warnings.warn('dw', DeprecationWarning)
+        warnings.warn('dw', DeprecationWarning)
+
+    def test_import(self):
+        warnings.warn('iw', ImportWarning)
+        warnings.warn('iw', ImportWarning)
+        warnings.warn('iw', ImportWarning)
+
+    # user warnings should always be printed
+    def test_warning(self):
+        warnings.warn('uw')
+        warnings.warn('uw')
+        warnings.warn('uw')
+
+    # these warnings come from the same place; they will be printed
+    # only once by default or three times if the 'always' filter is used
+    def test_function(self):
+
+        warnfun()
+        warnfun()
+        warnfun()
+
+
+
+if __name__ == '__main__':
+    with warnings.catch_warnings(record=True) as ws:
+        # if an arg is provided pass it to unittest.main as 'warnings'
+        if len(sys.argv) == 2:
+            unittest.main(exit=False, warnings=sys.argv.pop())
+        else:
+            unittest.main(exit=False)
+
+    # print all the warning messages collected
+    for w in ws:
+        print(w.message)

Modified: python/branches/py3k/Lib/unittest/test/test_break.py
==============================================================================
--- python/branches/py3k/Lib/unittest/test/test_break.py	(original)
+++ python/branches/py3k/Lib/unittest/test/test_break.py	Wed Dec  1 01:56:10 2010
@@ -209,7 +209,8 @@
 
         self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,
                                                      'verbosity': verbosity,
-                                                     'failfast': failfast})])
+                                                     'failfast': failfast,
+                                                     'warnings': None})])
         self.assertEqual(FakeRunner.runArgs, [test])
         self.assertEqual(p.result, result)
 
@@ -222,7 +223,8 @@
 
         self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,
                                                      'verbosity': verbosity,
-                                                     'failfast': failfast})])
+                                                     'failfast': failfast,
+                                                     'warnings': None})])
         self.assertEqual(FakeRunner.runArgs, [test])
         self.assertEqual(p.result, result)
 

Modified: python/branches/py3k/Lib/unittest/test/test_program.py
==============================================================================
--- python/branches/py3k/Lib/unittest/test/test_program.py	(original)
+++ python/branches/py3k/Lib/unittest/test/test_program.py	Wed Dec  1 01:56:10 2010
@@ -182,6 +182,27 @@
                 program.parseArgs([None, opt])
                 self.assertEqual(getattr(program, attr), not_none)
 
+    def testWarning(self):
+        """Test the warnings argument"""
+        # see #10535
+        class FakeTP(unittest.TestProgram):
+            def parseArgs(self, *args, **kw): pass
+            def runTests(self, *args, **kw): pass
+        warnoptions = sys.warnoptions
+        try:
+            sys.warnoptions[:] = []
+            # no warn options, no arg -> default
+            self.assertEqual(FakeTP().warnings, 'default')
+            # no warn options, w/ arg -> arg value
+            self.assertEqual(FakeTP(warnings='ignore').warnings, 'ignore')
+            sys.warnoptions[:] = ['somevalue']
+            # warn options, no arg -> None
+            # warn options, w/ arg -> arg value
+            self.assertEqual(FakeTP().warnings, None)
+            self.assertEqual(FakeTP(warnings='ignore').warnings, 'ignore')
+        finally:
+            sys.warnoptions[:] = warnoptions
+
     def testRunTestsRunnerClass(self):
         program = self.program
 
@@ -189,12 +210,14 @@
         program.verbosity = 'verbosity'
         program.failfast = 'failfast'
         program.buffer = 'buffer'
+        program.warnings = 'warnings'
 
         program.runTests()
 
         self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity',
                                                 'failfast': 'failfast',
-                                                'buffer': 'buffer'})
+                                                'buffer': 'buffer',
+                                                'warnings': 'warnings'})
         self.assertEqual(FakeRunner.test, 'test')
         self.assertIs(program.result, RESULT)
 

Modified: python/branches/py3k/Lib/unittest/test/test_runner.py
==============================================================================
--- python/branches/py3k/Lib/unittest/test/test_runner.py	(original)
+++ python/branches/py3k/Lib/unittest/test/test_runner.py	Wed Dec  1 01:56:10 2010
@@ -1,5 +1,8 @@
 import io
+import os
+import sys
 import pickle
+import subprocess
 
 import unittest
 
@@ -144,6 +147,7 @@
         self.assertFalse(runner.failfast)
         self.assertFalse(runner.buffer)
         self.assertEqual(runner.verbosity, 1)
+        self.assertEqual(runner.warnings, None)
         self.assertTrue(runner.descriptions)
         self.assertEqual(runner.resultclass, unittest.TextTestResult)
 
@@ -244,3 +248,57 @@
 
         expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY)
         self.assertEqual(runner._makeResult(), expectedresult)
+
+    def test_warnings(self):
+        """
+        Check that warnings argument of TextTestRunner correctly affects the
+        behavior of the warnings.
+        """
+        # see #10535 and the _test_warnings file for more information
+
+        def get_parse_out_err(p):
+            return [b.splitlines() for b in p.communicate()]
+        opts = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                    cwd=os.path.dirname(__file__))
+        ae_msg = b'Please use assertEqual instead.'
+        at_msg = b'Please use assertTrue instead.'
+
+        # no args -> all the warnings are printed, unittest warnings only once
+        p = subprocess.Popen([sys.executable, '_test_warnings.py'], **opts)
+        out, err = get_parse_out_err(p)
+        self.assertEqual(err[-1], b'OK')
+        # check that the total number of warnings in the output is correct
+        self.assertEqual(len(out), 12)
+        # check that the numbers of the different kind of warnings is correct
+        for msg in [b'dw', b'iw', b'uw']:
+            self.assertEqual(out.count(msg), 3)
+        for msg in [ae_msg, at_msg, b'rw']:
+            self.assertEqual(out.count(msg), 1)
+
+        args_list = (
+            # passing 'ignore' as warnings arg -> no warnings
+            [sys.executable, '_test_warnings.py', 'ignore'],
+            # -W doesn't affect the result if the arg is passed
+            [sys.executable, '-Wa', '_test_warnings.py', 'ignore'],
+            # -W affects the result if the arg is not passed
+            [sys.executable, '-Wi', '_test_warnings.py']
+        )
+        # in all these cases no warnings are printed
+        for args in args_list:
+            p = subprocess.Popen(args, **opts)
+            out, err = get_parse_out_err(p)
+            self.assertEqual(err[-1], b'OK')
+            self.assertEqual(len(out), 0)
+
+
+        # passing 'always' as warnings arg -> all the warnings printed,
+        #                                     unittest warnings only once
+        p = subprocess.Popen([sys.executable, '_test_warnings.py', 'always'],
+                             **opts)
+        out, err = get_parse_out_err(p)
+        self.assertEqual(err[-1], b'OK')
+        self.assertEqual(len(out), 14)
+        for msg in [b'dw', b'iw', b'uw', b'rw']:
+            self.assertEqual(out.count(msg), 3)
+        for msg in [ae_msg, at_msg]:
+            self.assertEqual(out.count(msg), 1)


More information about the Python-checkins mailing list