Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions test/assertions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,7 @@ def three_warnings_caught(warnings):
class DocTest(DocTestCase):
module = assertions


if __name__ == '__main__':
run()
# vim:et:sts=4:sw=4:
9 changes: 6 additions & 3 deletions test/discovery_failure_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def test_discover_test_with_broken_import(self):
non-existent module is discovered."""

stdout, stderr = cmd_output(
'python', '-m', 'testify.test_program', 'discovery_error', cwd='examples',
'python', '-W', 'ignore::RuntimeWarning:runpy:',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious: what's the RuntimeWarning that's happening here -- can we fix the code instead of silencing the warning?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's RuntimeWarning: 'testify.test_program' found in sys.modules after import of package 'testify', but prior to execution of 'testify.test_program'; this may result in unpredic table behaviour and I wasn't quite sure how to restructure testify itself to get around this issue

'-m', 'testify.test_program', 'discovery_error',
cwd='examples',
)

T.assert_equal(
Expand All @@ -37,7 +39,7 @@ def test_discover_test_with_broken_import(self):
r' submod = __import__\(module_name, fromlist=\[str\(\'__trash\'\)\]\)\n'
r' File "[^"]+", line \d+, in <module>\n'
r' import non_existent_module\n'
r' ImportError: No module named \'?non_existent_module\'?\n'
r' (ImportError|ModuleNotFoundError): No module named \'?non_existent_module\'?\n'
),
)

Expand All @@ -49,7 +51,7 @@ def test_discover_test_with_broken_import(self):
r" submod = __import__\(module_name, fromlist=\[str\(\'__trash\'\)\]\)\n"
r' File .+, line \d+, in <module>\n'
r' import non_existent_module\n'
r"ImportError: No module named '?non_existent_module'?\n"
r"(ImportError|ModuleNotFoundError): No module named '?non_existent_module'?\n"
),
)

Expand All @@ -65,6 +67,7 @@ def test_discover_test_with_unknown_import_error(self):
T.assert_in('AttributeError: aaaaa!', stderr)
T.assert_in('AttributeError: aaaaa!', stdout)


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we split the lint / test fixes into a separate PR?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do this once we agree that this change should actually be made to Testify

if __name__ == '__main__':
T.run()

Expand Down
1 change: 1 addition & 0 deletions test/plugins/test_case_time_log_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def mock_conf_files():
with mock.patch.object(six.moves.builtins, 'open', _mock_conf_file_open):
yield


start_time = datetime.datetime(2014, 12, 17, 12, 38, 37, 0)
end_time = datetime.datetime(2014, 12, 17, 15, 38, 37, 0)
output_str = (
Expand Down
2 changes: 2 additions & 0 deletions testify/deprecated_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ def failIfAlmostEqual(self, first, second, places=7, msg=None):

# Synonyms for assertion methods


# stop using these
assertEqual = assertEquals = failUnlessEqual

# stop using these
assertNotEqual = assertNotEquals = failIfEqual

Expand Down
1 change: 1 addition & 0 deletions testify/plugins/json_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def test_complete(self, result):

self.log_file.write(json.dumps(result))
self.log_file.write("\n")
self.log_file.flush()

self._reset_logging()

Expand Down
1 change: 1 addition & 0 deletions testify/plugins/test_case_time_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def _reset_logging(self):
def test_case_complete(self, result):
self.log_file.write(json.dumps(result))
self.log_file.write("\n")
self.log_file.flush()

self._reset_logging()

Expand Down
53 changes: 33 additions & 20 deletions testify/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from collections import defaultdict
import functools
import itertools
import inspect
import types
import unittest
Expand Down Expand Up @@ -133,7 +134,7 @@ def __init__(self, *args, **kwargs):

self.__suites_exclude = kwargs.get('suites_exclude', set())
self.__suites_require = kwargs.get('suites_require', set())
self.__name_overrides = kwargs.get('name_overrides', None)
self.__name_overrides = kwargs.get('name_overrides', itertools.repeat(None)) or itertools.repeat(None)

TestResult.debug = kwargs.get('debugger') # sorry :(

Expand Down Expand Up @@ -168,25 +169,37 @@ def runnable_test_methods(self):
any of our exclude_suites. If there are any require_suites, it will then further
limit itself to test methods in those suites.
"""
for member_name in dir(self):
if not member_name.startswith("test"):
continue
member = getattr(self, member_name)
if not inspect.ismethod(member):
continue

member_suites = self.suites(member)

# if there are any exclude suites, exclude methods under them
if self.__suites_exclude and self.__suites_exclude & member_suites:
continue
# if there are any require suites, only run methods in *all* of those suites
if self.__suites_require and not ((self.__suites_require & member_suites) == self.__suites_require):
continue

# if there are any name overrides, only run the named methods
if self.__name_overrides is None or member.__name__ in self.__name_overrides:
yield member
already_inspected = set()
name_overrides, self.__name_overrides = itertools.tee(self.__name_overrides)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm -1 on this, I don't believe it makes it any more lazy than the left hand side and at a great cost to readability


for name_override in name_overrides:
for member_name in dir(self):
# No overrides were passed in, stop if we've seen all members already
if not name_override:
if member_name in already_inspected:
return
already_inspected.add(member_name)
elif name_override == "__testify_abort_enumeration":
return

if not member_name.startswith("test"):
continue
member = getattr(self, member_name)
if not inspect.ismethod(member):
continue

member_suites = self.suites(member)

# if there are any exclude suites, exclude methods under them
if self.__suites_exclude and self.__suites_exclude & member_suites:
continue
# if there are any require suites, only run methods in *all* of those suites
if self.__suites_require and not ((self.__suites_require & member_suites) == self.__suites_require):
continue

# if there are any name overrides, only run the named methods
if name_override is None or member.__name__ == name_override:
yield member

def run(self):
"""Delegator method encapsulating the flow for executing a TestCase instance.
Expand Down
34 changes: 25 additions & 9 deletions testify/test_rerunner.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import absolute_import
from itertools import chain
from itertools import groupby
from json import loads
import sys
Expand All @@ -8,6 +9,15 @@
from .test_runner import TestRunner


def readlines(fileobj):
while True:
line = fileobj.readline().rstrip()
if line:
yield line
else:
return


class Test(namedtuple('TestTuple', ('module', 'cls', 'method'))):
@classmethod
def from_json(cls, json):
Expand All @@ -32,23 +42,29 @@ def discover(self):
else:
tests = open(self.filename)

with tests as tests:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now this file doesn't get closed in a timely manner -- is this the RuntimeWarning that you silenced? (seems incorrect to me)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not, but I can close this file.

tests = tests.read()
test = tests.readline().rstrip()

if not test:
return

if tests.startswith('{'):
if test.startswith('{'):
constructor = Test.from_json
else:
constructor = Test.from_name

tests = [
constructor(test)
for test in tests.splitlines()
if test # Skip blank lines
]
# Put back the test name we peeked at to choose a constructor
tests = chain(
[constructor(test)],
(
constructor(test.rstrip())
for test in readlines(tests)
if test # Skip blank lines
),
)

for class_path, tests in groupby(tests, lambda test: test[:2]):
methods = [test.method for test in tests]
module, cls = class_path
methods = (test.method for test in tests)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given it's already gone through groupby making this into a generator is likely to increase the memory (as it now needs to keep the closure scope alive), and slower because generators


cls = test_discovery.import_test_class(module, cls)
yield self._construct_test(cls, name_overrides=methods)
2 changes: 1 addition & 1 deletion testify/test_runner_json_replay.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def loadlines(self):
continue
try:
results.append(json.loads(line.strip()))
except:
except Exception:
sys.exit("Invalid JSON line: %r" % line.strip())

if lines[-1].strip() != "RUN COMPLETE":
Expand Down