Commit 60901876 authored by Ezio Melotti's avatar Ezio Melotti

#10535: Enable silenced warnings in unittest by default

parent 00f2f97d
...@@ -1845,12 +1845,21 @@ Loading and running tests ...@@ -1845,12 +1845,21 @@ Loading and running tests
instead of repeatedly creating new instances. 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 A basic test runner implementation which prints results on standard error. It
has a few configurable parameters, but is essentially very simple. Graphical has a few configurable parameters, but is essentially very simple. Graphical
applications which run test suites should provide alternate implementations. 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() .. method:: _makeResult()
This method returns the instance of ``TestResult`` used by :meth:`run`. This method returns the instance of ``TestResult`` used by :meth:`run`.
...@@ -1864,7 +1873,9 @@ Loading and running tests ...@@ -1864,7 +1873,9 @@ Loading and running tests
stream, descriptions, verbosity 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 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 test modules conveniently executable. The simplest use for this function is to
...@@ -1893,12 +1904,17 @@ Loading and running tests ...@@ -1893,12 +1904,17 @@ Loading and running tests
The ``failfast``, ``catchbreak`` and ``buffer`` parameters have the same The ``failfast``, ``catchbreak`` and ``buffer`` parameters have the same
effect as the same-name `command-line options`_. 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. Calling ``main`` actually returns an instance of the ``TestProgram`` class.
This stores the result of the tests run as the ``result`` attribute. This stores the result of the tests run as the ``result`` attribute.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
The ``exit``, ``verbosity``, ``failfast``, ``catchbreak`` and ``buffer`` The ``exit``, ``verbosity``, ``failfast``, ``catchbreak``, ``buffer``,
parameters were added. and ``warnings`` parameters were added.
load_tests Protocol load_tests Protocol
......
...@@ -249,6 +249,8 @@ continues to increase after each operation, or else delete the previous ...@@ -249,6 +249,8 @@ continues to increase after each operation, or else delete the previous
entries from the warnings list before each new operation). entries from the warnings list before each new operation).
.. _warning-ignored:
Updating Code For New Versions of Python Updating Code For New Versions of Python
---------------------------------------- ----------------------------------------
...@@ -279,6 +281,9 @@ code that were not there in an older interpreter, e.g. ...@@ -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 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. 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: .. _warning-functions:
......
...@@ -67,12 +67,12 @@ class TestProgram(object): ...@@ -67,12 +67,12 @@ class TestProgram(object):
USAGE = USAGE_FROM_MODULE USAGE = USAGE_FROM_MODULE
# defaults for testing # defaults for testing
failfast = catchbreak = buffer = progName = None failfast = catchbreak = buffer = progName = warnings = None
def __init__(self, module='__main__', defaultTest=None, argv=None, def __init__(self, module='__main__', defaultTest=None, argv=None,
testRunner=None, testLoader=loader.defaultTestLoader, testRunner=None, testLoader=loader.defaultTestLoader,
exit=True, verbosity=1, failfast=None, catchbreak=None, exit=True, verbosity=1, failfast=None, catchbreak=None,
buffer=None): buffer=None, warnings=None):
if isinstance(module, str): if isinstance(module, str):
self.module = __import__(module) self.module = __import__(module)
for part in module.split('.')[1:]: for part in module.split('.')[1:]:
...@@ -87,6 +87,18 @@ class TestProgram(object): ...@@ -87,6 +87,18 @@ class TestProgram(object):
self.catchbreak = catchbreak self.catchbreak = catchbreak
self.verbosity = verbosity self.verbosity = verbosity
self.buffer = buffer 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.defaultTest = defaultTest
self.testRunner = testRunner self.testRunner = testRunner
self.testLoader = testLoader self.testLoader = testLoader
...@@ -220,7 +232,8 @@ class TestProgram(object): ...@@ -220,7 +232,8 @@ class TestProgram(object):
try: try:
testRunner = self.testRunner(verbosity=self.verbosity, testRunner = self.testRunner(verbosity=self.verbosity,
failfast=self.failfast, failfast=self.failfast,
buffer=self.buffer) buffer=self.buffer,
warnings=self.warnings)
except TypeError: except TypeError:
# didn't accept the verbosity, buffer or failfast arguments # didn't accept the verbosity, buffer or failfast arguments
testRunner = self.testRunner() testRunner = self.testRunner()
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import sys import sys
import time import time
import warnings
from . import result from . import result
from .signals import registerResult from .signals import registerResult
...@@ -125,12 +126,13 @@ class TextTestRunner(object): ...@@ -125,12 +126,13 @@ class TextTestRunner(object):
resultclass = TextTestResult resultclass = TextTestResult
def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1, 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.stream = _WritelnDecorator(stream)
self.descriptions = descriptions self.descriptions = descriptions
self.verbosity = verbosity self.verbosity = verbosity
self.failfast = failfast self.failfast = failfast
self.buffer = buffer self.buffer = buffer
self.warnings = warnings
if resultclass is not None: if resultclass is not None:
self.resultclass = resultclass self.resultclass = resultclass
...@@ -143,17 +145,30 @@ class TextTestRunner(object): ...@@ -143,17 +145,30 @@ class TextTestRunner(object):
registerResult(result) registerResult(result)
result.failfast = self.failfast result.failfast = self.failfast
result.buffer = self.buffer result.buffer = self.buffer
startTime = time.time() with warnings.catch_warnings():
startTestRun = getattr(result, 'startTestRun', None) if self.warnings:
if startTestRun is not None: # if self.warnings is set, use it to filter all the warnings
startTestRun() warnings.simplefilter(self.warnings)
try: # if the filter is 'default' or 'always', special-case the
test(result) # warnings from the deprecated unittest methods to show them
finally: # no more than once per module, because they can be fairly
stopTestRun = getattr(result, 'stopTestRun', None) # noisy. The -Wd and -Wa flags can be used to bypass this
if stopTestRun is not None: # only when self.warnings is None.
stopTestRun() if self.warnings in ['default', 'always']:
stopTime = time.time() 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 timeTaken = stopTime - startTime
result.printErrors() result.printErrors()
if hasattr(result, 'separator2'): if hasattr(result, 'separator2'):
......
# 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)
...@@ -209,7 +209,8 @@ class TestBreak(unittest.TestCase): ...@@ -209,7 +209,8 @@ class TestBreak(unittest.TestCase):
self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None, self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,
'verbosity': verbosity, 'verbosity': verbosity,
'failfast': failfast})]) 'failfast': failfast,
'warnings': None})])
self.assertEqual(FakeRunner.runArgs, [test]) self.assertEqual(FakeRunner.runArgs, [test])
self.assertEqual(p.result, result) self.assertEqual(p.result, result)
...@@ -222,7 +223,8 @@ class TestBreak(unittest.TestCase): ...@@ -222,7 +223,8 @@ class TestBreak(unittest.TestCase):
self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None, self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,
'verbosity': verbosity, 'verbosity': verbosity,
'failfast': failfast})]) 'failfast': failfast,
'warnings': None})])
self.assertEqual(FakeRunner.runArgs, [test]) self.assertEqual(FakeRunner.runArgs, [test])
self.assertEqual(p.result, result) self.assertEqual(p.result, result)
......
...@@ -182,6 +182,27 @@ class TestCommandLineArgs(unittest.TestCase): ...@@ -182,6 +182,27 @@ class TestCommandLineArgs(unittest.TestCase):
program.parseArgs([None, opt]) program.parseArgs([None, opt])
self.assertEqual(getattr(program, attr), not_none) 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): def testRunTestsRunnerClass(self):
program = self.program program = self.program
...@@ -189,12 +210,14 @@ class TestCommandLineArgs(unittest.TestCase): ...@@ -189,12 +210,14 @@ class TestCommandLineArgs(unittest.TestCase):
program.verbosity = 'verbosity' program.verbosity = 'verbosity'
program.failfast = 'failfast' program.failfast = 'failfast'
program.buffer = 'buffer' program.buffer = 'buffer'
program.warnings = 'warnings'
program.runTests() program.runTests()
self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity', self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity',
'failfast': 'failfast', 'failfast': 'failfast',
'buffer': 'buffer'}) 'buffer': 'buffer',
'warnings': 'warnings'})
self.assertEqual(FakeRunner.test, 'test') self.assertEqual(FakeRunner.test, 'test')
self.assertIs(program.result, RESULT) self.assertIs(program.result, RESULT)
......
import io import io
import os
import sys
import pickle import pickle
import subprocess
import unittest import unittest
...@@ -144,6 +147,7 @@ class Test_TextTestRunner(unittest.TestCase): ...@@ -144,6 +147,7 @@ class Test_TextTestRunner(unittest.TestCase):
self.assertFalse(runner.failfast) self.assertFalse(runner.failfast)
self.assertFalse(runner.buffer) self.assertFalse(runner.buffer)
self.assertEqual(runner.verbosity, 1) self.assertEqual(runner.verbosity, 1)
self.assertEqual(runner.warnings, None)
self.assertTrue(runner.descriptions) self.assertTrue(runner.descriptions)
self.assertEqual(runner.resultclass, unittest.TextTestResult) self.assertEqual(runner.resultclass, unittest.TextTestResult)
...@@ -244,3 +248,57 @@ class Test_TextTestRunner(unittest.TestCase): ...@@ -244,3 +248,57 @@ class Test_TextTestRunner(unittest.TestCase):
expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY) expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY)
self.assertEqual(runner._makeResult(), expectedresult) 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)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment