Skip to content

Commit

Permalink
Merge branch 'release/1.7.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
vmalloc committed Apr 30, 2019
2 parents 2974d69 + 2a75b8e commit 0210aff
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 120 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Slash
| | |
|-----------------------|------------------------------------------------------------------------------------|
| Build Status | ![Build Status](https://secure.travis-ci.org/getslash/slash.png?branch=master,dev) |
| Supported Versions | ![Supported Versions](https://img.shields.io/badge/python-2.7%2C3.3%2C3.4%2C3.5%2C3.6-green.svg) |
| Supported Versions | ![Supported Versions](https://img.shields.io/badge/python-2.7%2C3.5%2C3.6%2C3.7-green.svg) |
| Latest Version | ![Latest Version](https://img.shields.io/pypi/v/slash.svg) |
| Test Coverage | ![Coverage Status](https://img.shields.io/coveralls/getslash/slash/develop.svg) |

Expand Down
3 changes: 3 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Changelog
=========

* :bug:`930` Restore behavior of exceptions propagating out of the test_start or test_end hooks. Correct behavior is for those to fail the test (thanks @pierreluctg)
* :bug:`934` Parallel sessions now honor fatal exceptions encountered in worker sessions
* :bug:`928` Fixed a bug causing requirements to leak across sibling test classes
* :release:`1.7.9 <09-03-2019>`
* :bug:`-` Revert console coloring change, as it does not behave consistently across different terminals
* :release:`1.7.8 <04-03-2019>`
Expand Down
13 changes: 6 additions & 7 deletions slash/core/fixtures/fixture_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,13 @@ def call_with_fixtures(self, test_func, namespace, trigger_test_start=False, tri
if trigger_test_start:
for fixture in self.iter_active_fixtures():
fixture.call_test_start()

return test_func(**kwargs)
finally:
try:
return test_func(**kwargs) # pylint: disable=lost-exception
finally:
if trigger_test_end:
for fixture in self.iter_active_fixtures():
with handling_exceptions(swallow=True):
fixture.call_test_end()
if trigger_test_end:
for fixture in self.iter_active_fixtures():
with handling_exceptions(swallow=True):
fixture.call_test_end()

def get_required_fixture_names(self, test_func):
"""Returns a list of fixture names needed by test_func.
Expand Down
32 changes: 25 additions & 7 deletions slash/core/requirements.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from ..utils.python import resolve_underlying_function

_SLASH_REQUIRES_KEY_NAME = '__slash_requirements__'


Expand All @@ -12,18 +14,34 @@ def requires(req, message=None):
else:
assert message is None, 'Cannot specify message when passing Requirement objects to slash.requires'

def decorator(func):
reqs = getattr(func, _SLASH_REQUIRES_KEY_NAME, None)
if reqs is None:
reqs = []
setattr(func, _SLASH_REQUIRES_KEY_NAME, reqs)
def decorator(func_or_class):
reqs = _get_requirements_list(func_or_class)
reqs.append(req)
return func
return func_or_class
return decorator

def _get_requirements_list(thing, create=True):

thing = resolve_underlying_function(thing)
existing = getattr(thing, _SLASH_REQUIRES_KEY_NAME, None)

key = id(thing)


if existing is None or key != existing[0]:
new_reqs = (key, [] if existing is None else existing[1][:])
if create:
setattr(thing, _SLASH_REQUIRES_KEY_NAME, new_reqs)
assert thing.__slash_requirements__ is new_reqs
returned = new_reqs[1]
else:
returned = existing[1]

return returned


def get_requirements(test):
return list(getattr(test, _SLASH_REQUIRES_KEY_NAME, []))
return list(_get_requirements_list(test, create=False))


class Requirement(object):
Expand Down
2 changes: 1 addition & 1 deletion slash/core/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def run(self): # pylint: disable=E0202
method = self.get_test_function()
with bound_parametrizations_context(self._variation, self._fixture_store, self._fixture_namespace):
_call_with_fixtures = functools.partial(self._fixture_store.call_with_fixtures, namespace=self._fixture_namespace)
_call_with_fixtures(self.before, trigger_test_start=True)
_call_with_fixtures(self.before)
try:
with handling_exceptions():
result = _call_with_fixtures(method, trigger_test_start=True)
Expand Down
5 changes: 3 additions & 2 deletions slash/parallel/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@ def finished_test(self, client_id, result_dict):
with _get_test_context(self.tests[test_index], logging=False) as (result, _):
result.deserialize(result_dict)
context.session.reporter.report_test_end(self.tests[test_index], result)
if not result.is_success(allow_skips=True) and config.root.run.stop_on_error:
_logger.debug("Stopping (run.stop_on_error==True)")
if result.has_fatal_exception() or (not result.is_success(allow_skips=True) and config.root.run.stop_on_error):
_logger.debug("Server stops serving tests, run.stop_on_error: {}, result.has_fatal_exception: {}",
config.root.run.stop_on_error, result.has_fatal_exception())
self.state = ServerStates.STOP_TESTS_SERVING
self._mark_unrun_tests()
else:
Expand Down
12 changes: 12 additions & 0 deletions slash/utils/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,15 @@ def call_all_raise_first(_funcs, *args, **kwargs):
exc_info = sys.exc_info()
if exc_info is not None:
reraise(*exc_info)


def resolve_underlying_function(thing):
"""Gets the underlying (real) function for functions, wrapped functions, methods, etc.
Returns the same object for things that are not functions
"""
while True:
wrapped = getattr(thing, "__func__", None) or getattr(thing, "__wrapped__", None) or getattr(thing, "__wraps__", None)
if wrapped is None:
break
thing = wrapped
return thing
48 changes: 10 additions & 38 deletions tests/test_fixture_start_end_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,12 @@ def test_start(*_, **__):

return param

@slash.fixture(scope='module')
def fixture_failing(this):
@this.test_start
def test_start(*_, **__):
raise AssertionError()

return None

class SomeTest(slash.Test):
@slash.parametrize("param", [10, 20, 30])
def test_something(self, param, fixture, fixture_failing): # pylint: disable=unused-argument
slash.context.result.data["value"] = param + fixture

suite_builder.build().run().assert_all(6).exception(ZeroDivisionError).with_data(
[
{"value": 11}, {"value": 12},
{"value": 21}, {"value": 22},
{"value": 31}, {"value": 32}
]
)
def test_something(self, param, fixture): # pylint: disable=unused-argument
slash.add_error("Not supposed to reach here").mark_fatal()

suite_builder.build().run().assert_all(6).exception(ZeroDivisionError)


def test_fixture_start_test_raises_exception_w_before(suite_builder):
Expand All @@ -105,26 +91,12 @@ def test_start(*_, **__):

return param

@slash.fixture(scope='module')
def fixture_failing(this):
@this.test_start
def test_start(*_, **__):
raise AssertionError()

return None

class SomeTest(slash.Test):
def before(self, fixture, fixture_failing): # pylint: disable=unused-argument,arguments-differ
self.data = fixture
def before(self, fixture): # pylint: disable=unused-argument,arguments-differ
self.data = 1

@slash.parametrize("param", [10, 20, 30])
def test_something(self, param):
slash.context.result.data["value"] = param + self.data

suite_builder.build().run().assert_all(6).exception(ZeroDivisionError).with_data(
[
{"value": 11}, {"value": 12},
{"value": 21}, {"value": 22},
{"value": 31}, {"value": 32}
]
)
def test_something(self, param): # pylint: disable=unused-argument
slash.add_error("Not supposed to reach here").mark_fatal()

suite_builder.build().run().assert_all(6).exception(ZeroDivisionError)
9 changes: 9 additions & 0 deletions tests/test_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ def test_child_errors_in_cleanup_are_session_errors(parallel_suite):
assert not session_results.results.is_success()
assert session_results.parallel_manager.server.worker_error_reported

def test_child_fatal_error_terminates_session(parallel_suite):
parallel_suite[0].expect_failure()
parallel_suite[0].append_line("import slash")
parallel_suite[0].append_line("slash.add_error('bla').mark_fatal()")
session_results = parallel_suite.run(num_workers=1, verify=False).session
first_result = session_results.results[0]
assert first_result.is_error() and first_result.has_fatal_errors()
assert session_results.results.get_num_not_run() == len(parallel_suite) - 1

def test_traceback_vars(parallel_suite):
#code to be inserted:
# def test_traceback_frames():
Expand Down
59 changes: 58 additions & 1 deletion tests/test_python_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# pylint: disable=redefined-outer-name
import pytest
from slash._compat import PY2

from slash.utils.python import call_all_raise_first
if PY2:
from slash.utils.python import wraps
else:
from functools import wraps

from slash.utils.python import call_all_raise_first, resolve_underlying_function


def test_call_all_raise_first(funcs):
Expand Down Expand Up @@ -35,3 +41,54 @@ class CustomException(Exception):
return self.exc_type

return [Func() for _ in range(10)]


@pytest.mark.parametrize('class_method', [True, False])
def test_resolve_underlying_function_method(class_method):
if class_method:
decorator = classmethod
else:
decorator = lambda f: f

class Blap(object):

@decorator
def method(self):
pass

resolved = resolve_underlying_function(Blap.method)
assert resolved is resolve_underlying_function(Blap.method) # stable
assert not hasattr(resolved, '__func__')
assert resolved.__name__ == 'method'


@pytest.mark.parametrize('thing', [object(), object, None, 2, "string"])
def test_resolve_underlying_function_method_no_op(thing):
assert resolve_underlying_function(thing) is thing


def _example_decorator(func):
@wraps(func)
def new_func():
pass

return new_func

def test_resolve_underlying_decorator_regular_func():

def orig():
pass
decorated = _example_decorator(orig)
assert resolve_underlying_function(decorated) is orig

def test_resolve_underlying_decorator_method():

class Blap(object):

def orig(self):
pass

decorated = _example_decorator(orig)

assert resolve_underlying_function(Blap.decorated) is resolve_underlying_function(Blap.orig)
assert resolve_underlying_function(Blap.decorated).__name__ == 'orig'
Loading

0 comments on commit 0210aff

Please sign in to comment.