Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions testflo/deprecations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import sys
import os
import site

from io import StringIO

class DeprecationsReport(object):
"""Generates a report of all Deprecation warnings raised during testing."""

def __init__(self, options, stream=sys.stdout):
self.stream = stream
self.options = options
self.startdir = os.getcwd()

def get_iter(self, input_iter):

deprecations = {}

for test in input_iter:
for msg, locs in test.deprecations.items():
deprecations[msg] = deprecations.get(msg, set()) | locs
yield test

report = self.generate_report(deprecations)

if self.options.show_deprecations:
self.stream.write(report)

if self.options.deprecations_report:
with open(self.options.deprecations_report, 'w') as stream:
stream.write(report)

def generate_report(self, deprecations):
report = StringIO()

title = "Deprecations Report"
eqs = "=" * len(title)

write = report.write

write("\n{}\n{}\n\n".format(title, eqs))

count = len(deprecations)

write("{} unique deprecation warnings were captured{}\n\n".format(count, ':' if count else '.'))

if count == 0 and self.options.disallow_deprecations:
write("\nDeprecation warnings have been raised as Exceptions\n"
"due to the use of the --disallow_deprecations option,\n"
"so no deprecation warnings have been captured.")

startdir = self.startdir
try:
sitedirs = site.getsitepackages()
except:
# python older than 3.2
sitedirs = []

def trim_path(path):
# trim the start directory or the site-packages directory from the path
if path.startswith(startdir):
path = path[len(startdir):]
else:
for d in sitedirs:
if path.startswith(d):
path = path[len(d):]
return path

for msg in sorted(deprecations):
write("--\n{}\n\n".format(msg))
for filename, lineno, test_spec in deprecations[msg]:
write(" {}, line {}\n".format(trim_path(filename), lineno))
if test_spec:
write(" [{}]\n\n".format(trim_path(test_spec)))

if count > 0:
write("\n\nFor a stack trace of reported deprecations, run the "
"identified test with the --disallow_deprecations option.")

return report.getvalue()
4 changes: 4 additions & 0 deletions testflo/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def get_iter(self, input_iter)
from testflo.printer import ResultPrinter
from testflo.benchmark import BenchmarkWriter
from testflo.summary import ResultSummary
from testflo.deprecations import DeprecationsReport
from testflo.duration import DurationSummary
from testflo.discover import TestDiscoverer
from testflo.filters import TimeFilter, FailFilter
Expand Down Expand Up @@ -206,6 +207,9 @@ def func_matcher(funcname):

pipeline.append(runner.get_iter)

if options.show_deprecations or options.deprecations_report:
pipeline.append(DeprecationsReport(options).get_iter)

if options.benchmark:
pipeline.append(BenchmarkWriter(stream=bdata).get_iter)

Expand Down
117 changes: 71 additions & 46 deletions testflo/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import os
import sys
import time
import warnings
import traceback
from inspect import isclass
import subprocess
from tempfile import mkstemp
from importlib import import_module
from contextlib import contextmanager
from contextlib import contextmanager, nullcontext

from types import FunctionType, ModuleType
from types import FunctionType
from io import StringIO

from unittest import TestCase, SkipTest
Expand Down Expand Up @@ -101,6 +100,8 @@ def __init__(self, testspec, options):
self._tcase_fixture_first = False
self._tcase_fixture_last = False

self.deprecations = {}

self._get_test_info()

def __iter__(self):
Expand Down Expand Up @@ -290,57 +291,81 @@ def run(self, queue=None):

self.start_time = time.time()

# if there's a module setup, run it
if mod_setup:
status, expected = _try_call(mod_setup)
if status != 'OK':
done = True
mod_teardown = None # don't do teardown if setup failed

# handle @unittest.skip class decorator
if not done and hasattr(parent, '__unittest_skip__') and parent.__unittest_skip__:
sys.stderr.write("%s\n" % parent.__unittest_skip_why__)
status = 'SKIP'
done = True
tcase_setup = None
tcase_teardown = None

if tcase_setup:
status, expected = _try_call(tcase_setup)
if status != 'OK':
catch_deps = self.options.show_deprecations or self.options.deprecations_report
raise_deps = self.options.disallow_deprecations

with warnings.catch_warnings(record=True) if catch_deps else nullcontext() as w:

if raise_deps:
# raise deprecation warnings as exceptions
warnings.filterwarnings("error", category=DeprecationWarning)
warnings.filterwarnings("error", category=PendingDeprecationWarning)
elif catch_deps:
# catch all locations where deprecation warning is triggered
warnings.filterwarnings("always", category=DeprecationWarning)
warnings.filterwarnings("always", category=PendingDeprecationWarning)

# if there's a module setup, run it
if mod_setup:
status, expected = _try_call(mod_setup)
if status != 'OK':
done = True
mod_teardown = None # don't do teardown if setup failed

# handle @unittest.skip class decorator
if not done and hasattr(parent, '__unittest_skip__') and parent.__unittest_skip__:
sys.stderr.write("%s\n" % parent.__unittest_skip_why__)
status = 'SKIP'
done = True
tcase_setup = None
tcase_teardown = None

# if there's a setUp method, run it
if not done and setup:
status, expected = _try_call(setup)
if status != 'OK':
done = True
if tcase_setup:
status, expected = _try_call(tcase_setup)
if status != 'OK':
done = True
tcase_teardown = None

if not done:
status, expected2 = _try_call(getattr(parent, funcname))
# if there's a setUp method, run it
if not done and setup:
status, expected = _try_call(setup)
if status != 'OK':
done = True

if not done and teardown:
tdstatus, expected3 = _try_call(teardown)
if status == 'OK':
status = tdstatus
if not done:
status, expected2 = _try_call(getattr(parent, funcname))

if tcase_teardown:
_try_call(tcase_teardown)
if not done and teardown:
tdstatus, expected3 = _try_call(teardown)
if status == 'OK':
status = tdstatus

if mod_teardown:
_try_call(mod_teardown)
if tcase_teardown:
_try_call(tcase_teardown)

self.end_time = time.time()
self.status = status
self.err_msg = errstream.getvalue()
self.memory_usage = get_memory_usage()
self.expected_fail = expected or expected2 or expected3
if mod_teardown:
_try_call(mod_teardown)

if sys.platform == 'win32':
self.load = (0.0, 0.0, 0.0)
else:
self.load = os.getloadavg()
self.end_time = time.time()
self.status = status
self.err_msg = errstream.getvalue()
self.memory_usage = get_memory_usage()
self.expected_fail = expected or expected2 or expected3

if sys.platform == 'win32':
self.load = (0.0, 0.0, 0.0)
else:
self.load = os.getloadavg()

if catch_deps and w:
deprecations = [wm for wm in w
if wm.category is DeprecationWarning
or wm.category is PendingDeprecationWarning]
for wm in deprecations:
msg = str(wm.message)
dep = self.deprecations.get(msg, set())
dep.add((wm.filename, wm.lineno, self.spec))
self.deprecations[msg] = dep

finally:
stop_coverage()
Expand Down
9 changes: 8 additions & 1 deletion testflo/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ def _get_parser():
parser.add_argument('--disallow_skipped', action='store_true', dest='disallow_skipped',
help="Return exit code 2 if no tests failed but some tests are skipped.")

parser.add_argument('--show_deprecations', action='store_true', dest='show_deprecations',
help="Display a list of all deprecation warnings encountered in testing.")
parser.add_argument('--deprecations_report', action='store', dest='deprecations_report',
metavar='FILE', default=None,
help='Generate a deprecations report with the given file name. Default is None.')
parser.add_argument('--disallow_deprecations', action='store_true', dest='disallow_deprecations',
help="Raise deprecation warnings as Exceptions.")

parser.add_argument('tests', metavar='test', nargs='*',
help='A test method, test case, module, or directory to run. If not '
'supplied, the current working directory is assumed.')
Expand Down Expand Up @@ -479,4 +487,3 @@ def elapsed_str(elapsed):
# in python3, inspect.ismethod doesn't work as you might expect, so...
def ismethod(obj):
return inspect.isfunction(obj) or inspect.ismethod(obj)