diff options
author | Ezio Melotti <ezio.melotti@gmail.com> | 2010-12-01 00:56:10 (GMT) |
---|---|---|
committer | Ezio Melotti <ezio.melotti@gmail.com> | 2010-12-01 00:56:10 (GMT) |
commit | 60901876561a4ef665555e38fae1bdfe7bda78ba (patch) | |
tree | e0f1998dfc9a106dbcef71a614e905bd475dd7db | |
parent | 00f2f97dbd3db898e5d6c2368edc780a81e2e5fe (diff) | |
download | cpython-60901876561a4ef665555e38fae1bdfe7bda78ba.zip cpython-60901876561a4ef665555e38fae1bdfe7bda78ba.tar.gz cpython-60901876561a4ef665555e38fae1bdfe7bda78ba.tar.bz2 |
#10535: Enable silenced warnings in unittest by default
-rw-r--r-- | Doc/library/unittest.rst | 24 | ||||
-rw-r--r-- | Doc/library/warnings.rst | 5 | ||||
-rw-r--r-- | Lib/unittest/main.py | 19 | ||||
-rw-r--r-- | Lib/unittest/runner.py | 39 | ||||
-rw-r--r-- | Lib/unittest/test/_test_warnings.py | 74 | ||||
-rw-r--r-- | Lib/unittest/test/test_break.py | 6 | ||||
-rw-r--r-- | Lib/unittest/test/test_program.py | 25 | ||||
-rw-r--r-- | Lib/unittest/test/test_runner.py | 58 |
8 files changed, 228 insertions, 22 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 6bfdd8d..1308eb4 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -1845,12 +1845,21 @@ Loading and running tests 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 @@ Loading and running tests 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 @@ Loading and running tests 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 diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index 17e9856..03140aa 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -249,6 +249,8 @@ continues to increase after each operation, or else delete the previous entries from the warnings list before each new operation). +.. _warning-ignored: + Updating Code For New Versions of Python ---------------------------------------- @@ -279,6 +281,9 @@ code that were not there in an older interpreter, e.g. 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: diff --git a/Lib/unittest/main.py b/Lib/unittest/main.py index 8473a33..bd5f2a4 100644 --- a/Lib/unittest/main.py +++ b/Lib/unittest/main.py @@ -67,12 +67,12 @@ class TestProgram(object): 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 @@ class TestProgram(object): 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 @@ class TestProgram(object): 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() diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py index cd9526d..c5e4258 100644 --- a/Lib/unittest/runner.py +++ b/Lib/unittest/runner.py @@ -2,6 +2,7 @@ import sys import time +import warnings from . import result from .signals import registerResult @@ -125,12 +126,13 @@ class TextTestRunner(object): 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 @@ class TextTestRunner(object): 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'): diff --git a/Lib/unittest/test/_test_warnings.py b/Lib/unittest/test/_test_warnings.py new file mode 100644 index 0000000..d0be18d --- /dev/null +++ b/Lib/unittest/test/_test_warnings.py @@ -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) diff --git a/Lib/unittest/test/test_break.py b/Lib/unittest/test/test_break.py index 0e09dfb..77ce201 100644 --- a/Lib/unittest/test/test_break.py +++ b/Lib/unittest/test/test_break.py @@ -209,7 +209,8 @@ class TestBreak(unittest.TestCase): 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 @@ class TestBreak(unittest.TestCase): self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None, 'verbosity': verbosity, - 'failfast': failfast})]) + 'failfast': failfast, + 'warnings': None})]) self.assertEqual(FakeRunner.runArgs, [test]) self.assertEqual(p.result, result) diff --git a/Lib/unittest/test/test_program.py b/Lib/unittest/test/test_program.py index f8e2804..2eb0be3 100644 --- a/Lib/unittest/test/test_program.py +++ b/Lib/unittest/test/test_program.py @@ -182,6 +182,27 @@ class TestCommandLineArgs(unittest.TestCase): 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 @@ class TestCommandLineArgs(unittest.TestCase): 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) diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py index 25f7541..efd4962 100644 --- a/Lib/unittest/test/test_runner.py +++ b/Lib/unittest/test/test_runner.py @@ -1,5 +1,8 @@ import io +import os +import sys import pickle +import subprocess import unittest @@ -144,6 +147,7 @@ class Test_TextTestRunner(unittest.TestCase): 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 @@ class Test_TextTestRunner(unittest.TestCase): 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) |