Skip to content

Commit

Permalink
Merge branch 'release/1.8.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
ayalash committed Jul 3, 2019
2 parents 364cc7a + 07521a6 commit 3c0a264
Show file tree
Hide file tree
Showing 30 changed files with 525 additions and 350 deletions.
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ after_success:
after_failure:
- .env/bin/pip freeze

branches:
except:
- trying.tmp
- staging.tmp


deploy:
- provider: pypi
user: vmalloc
Expand Down
5 changes: 5 additions & 0 deletions bors.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
timeout-sec = 14400
delete_merged_branches = true
status = [
"continuous-integration/travis-ci/push"
]
9 changes: 8 additions & 1 deletion doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

* :release:`1.8.0 <03-07-2019>`
* :feature:`945` Drop support for deprecated arguments of ``add_cleanup``
* :feature:`452` Drop support for old-style assertions
* :feature:`952` Added support for getting the currently active scope (``test``, ``module`` or ``session``) through the new ``get_current_scope`` API. ``session.scope_manager.current_scope`` is also available.
* :feature:`925` Support was added for terminals with light backgrounds by changing ``log.console_theme.dark_background`` configuration
* :feature:`950` Slash now emits a log record when handling fatal errors
* :feature:`-` Add tests and suite execution time to xunit plugin
* :feature:`-` Add ``slash.ignored_warnings`` context
* :release:`1.7.10 <30-04-2019>`
* :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
Expand Down
6 changes: 6 additions & 0 deletions doc/warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ For example, you may want to include code in your project's ``.slashrc`` as foll
.. note:: Filter arguments to ignore_warnings are treated as though they are ``and``ed together. This means that a filter for a specific filename and a specific category would only ignore warnings coming from the specified file *and* having the specified category.

For ignoring warnings in specific code-block, one can use the `slash.ignored_warnings` context:
.. code-block:: python
with slash.ignore_warnings(category=DeprecationWarning, filename='/some/bad/file.py'):
...
29 changes: 4 additions & 25 deletions slash/__init__.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,16 @@
# pylint: disable=unused-import
from .__version__ import __version__
from .cleanups import add_cleanup, add_critical_cleanup, add_success_only_cleanup, get_current_cleanup_phase, is_in_cleanup
from .conf import config
from .ctx import context
from .ctx import g, session, test
from .core.scope_manager import get_current_scope
from .core.session import Session
from .core.tagging import tag
# assertions
from . import assertions
should = assertions
from .assertions import (
allowing_exceptions,
assert_almost_equal,
assert_contains,
assert_equal,
assert_equals,
assert_false,
assert_in,
assert_is,
assert_is_none,
assert_empty,
assert_not_empty,
assert_is_not,
assert_is_not_none,
assert_isinstance,
assert_not_contain,
assert_not_contains,
assert_not_equal,
assert_not_equals,
assert_not_in,
assert_not_isinstance,
assert_raises,
assert_true,
)
from .assertions import allowing_exceptions, assert_almost_equal, assert_raises
from .core.test import Test
from .core.test import abstract_test_class
from .core.exclusions import exclude
Expand All @@ -41,7 +20,7 @@
from .core.requirements import requires
from .utils import skip_test, skipped, add_error, add_failure, set_test_detail, repeat, register_skip_exception
from .utils.interactive import start_interactive_shell
from .warnings import ignore_warnings, clear_ignored_warnings
from .warnings import ignore_warnings, ignored_warnings, clear_ignored_warnings
from .runner import run_tests
import logbook
logger = logbook.Logger(__name__)
107 changes: 1 addition & 106 deletions slash/assertions.py
Original file line number Diff line number Diff line change
@@ -1,117 +1,19 @@
import operator
import sys

import logbook
from vintage import deprecated

from . import exception_handling
from ._compat import PY2
from .exceptions import TestFailed, ExpectedExceptionNotCaught
from .utils import operator_information
from .exceptions import ExpectedExceptionNotCaught

sys.modules["slash.should"] = sys.modules[__name__]
_logger = logbook.Logger(__name__)

def _deprecated(func, message=None):
return deprecated(since='0.19.0', what='slash.should.{.__name__}'.format(func),
message=message or 'Use plain assertions instead')(func)


def _binary_assertion(name, operator_func):
op = operator_information.get_operator_by_func(operator_func)

def _assertion(a, b, msg=None):
if not op(a, b):
msg = _get_message(msg, operator_information.get_operator_by_func(
op.inverse_func).to_expression(a, b))
raise TestFailed(msg)
_assertion.__name__ = name
_assertion.__doc__ = "Asserts **{}**".format(
op.to_expression("ARG1", "ARG2"))
_assertion = _deprecated(_assertion)
return _assertion


def _unary_assertion(name, operator_func):
op = operator_information.get_operator_by_func(operator_func)

def _assertion(a, msg=None):
if not op(a):
msg = _get_message(msg, operator_information.get_operator_by_func(
op.inverse_func).to_expression(a))
raise TestFailed(msg)
_assertion.__name__ = name
_assertion.__doc__ = "Asserts **{}**".format(op.to_expression("ARG"))
_assertion = _deprecated(_assertion)
return _assertion


def _get_message(msg, description):
if msg is None:
return description
return "{} ({})".format(msg, description)

equal = _binary_assertion("equal", operator.eq)
assert_equal = assert_equals = equal = equal

not_equal = _binary_assertion("not_equal", operator.ne)
assert_not_equal = assert_not_equals = not_equals = not_equal

be_a = _binary_assertion("be_a", operator_information.safe_isinstance)
assert_isinstance = be_a

not_be_a = _binary_assertion(
"not_be_a", operator_information.safe_not_isinstance)
assert_not_isinstance = not_be_a

be_none = _unary_assertion("be_none", operator_information.is_none)
assert_is_none = be_none

not_be_none = _unary_assertion("not_be_none", operator_information.is_not_none)
assert_is_not_none = not_be_none

be = _binary_assertion("be", operator.is_)
assert_is = be

not_be = _binary_assertion("not_be", operator.is_not)
assert_is_not = not_be

be_true = _unary_assertion("be_true", operator.truth)
assert_true = be_true

be_false = _unary_assertion("be_false", operator.not_)
assert_false = be_false

be_empty = _unary_assertion("be_empty", operator_information.is_empty)
assert_empty = assert_is_empty = be_empty

not_be_empty = _unary_assertion(
"not_be_empty", operator_information.is_not_empty)
assert_not_empty = assert_is_not_empty = not_be_empty

contain = _binary_assertion("contain", operator.contains)
assert_contains = contains = contain

not_contain = _binary_assertion(
"not_contain", operator_information.not_contains)
assert_not_contains = assert_not_contain = not_contains = not_contain


def be_in(a, b, msg=None):
"""
Asserts **ARG1 in ARG2**
"""
return contain(b, a, msg)
assert_in = be_in


def not_be_in(a, b, msg=None):
"""
Asserts **ARG1 not in ARG2**
"""
return not_contain(b, a, msg)
assert_not_in = not_be_in


class _CaughtContext(object):

Expand Down Expand Up @@ -174,13 +76,6 @@ def allowing_exceptions(exception_class, msg=None):
return _CaughtContext(msg, exception_class, ensure_caught=False)


@deprecated(since='0.19.0', what='slash.should.raise_exception', message='Use slash.assert_raises instead')
def raise_exception(exception_class, msg=None):
return assert_raises(exception_class, msg=msg)

raise_exception.__doc__ = assert_raises.__doc__.replace("assert_raises", "raise_exception")


def assert_almost_equal(a, b, delta=0.00000001):
"""Asserts that abs(a - b) <= delta
"""
Expand Down
3 changes: 3 additions & 0 deletions slash/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"log": {
"colorize": False // Doc("Emit log colors to files"),
"console_theme": {
'dark_background': True,
'inline-file-end-fail': 'red',
'inline-file-end-skip': 'yellow',
'inline-file-end-success': 'green',
Expand All @@ -26,8 +27,10 @@
'error-cause-marker': 'white/bold',
'fancy-message': 'yellow/bold',
'frame-local-varname': 'yellow/bold',
'num-collected': 'white/bold',
'session-summary-success': 'green/bold',
'session-summary-failure': 'red/bold',
'session-start': 'white/bold',
'error-separator-dash': 'red',
'tb-error-message': 'red/bold',
'tb-error': 'red/bold',
Expand Down
8 changes: 2 additions & 6 deletions slash/core/cleanup_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import logbook
from sentinels import Sentinel
from vintage import warn_deprecation

from .. import hooks
from ..ctx import context
Expand Down Expand Up @@ -63,11 +62,8 @@ def add_cleanup(self, _func, *args, **kwargs):

new_kwargs = kwargs.pop('kwargs', {}).copy()
new_args = list(kwargs.pop('args', ()))
if args or kwargs:
warn_deprecation('Passing *args/**kwargs to slash.add_cleanup is deprecated. '
'Use args=(...) and/or kwargs={...} instead', frame_correction=+2)
new_args.extend(args)
new_kwargs.update(kwargs)
assert (not args) and (not kwargs), \
'Passing *args/**kwargs to slash.add_cleanup is not supported. Use args=(...) and/or kwargs={...} instead'

added = _Cleanup(_func, new_args, new_kwargs, critical=critical, success_only=success_only)

Expand Down
1 change: 1 addition & 0 deletions slash/core/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def __init__(self, factory, test):
self.tags = test.get_tags()
self._sort_key = next(_sort_key_generator)
self.repeat_all_index = 0
self.parallel_index = None
if factory is not None:
#: The path to the file from which this test was loaded
self.module_name = factory.get_module_name()
Expand Down
15 changes: 14 additions & 1 deletion slash/core/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import pickle
import sys
from numbers import Number

import gossip
import logbook
from datetime import datetime, timedelta
from vintage import deprecated

from .. import hooks
Expand Down Expand Up @@ -35,6 +35,8 @@ def __init__(self, test_metadata=None):
self.test_metadata = test_metadata
#: dictionary to be use by tests and plugins to store result-related information for later analysis
self.data = {}
self._start_time = None
self._end_time = None
self._errors = []
self._failures = []
self._skips = []
Expand Down Expand Up @@ -155,6 +157,7 @@ def is_not_run(self):
return not self.is_started() and not self.has_errors_or_failures()

def mark_started(self):
self._start_time = datetime.now()
self._started = True

def is_error(self):
Expand Down Expand Up @@ -191,6 +194,7 @@ def is_finished(self):
return self._finished

def mark_finished(self):
self._end_time = datetime.now()
self._finished = True

def mark_interrupted(self):
Expand Down Expand Up @@ -246,6 +250,15 @@ def add_skip(self, reason, append=True):
self._skips.append(reason)
context.reporter.report_test_skip_added(context.test, reason)

def get_duration(self):
"""Returns the test duration time as timedelta object
:return: timedelta
"""
if self._end_time is None or self._start_time is None:
return timedelta()
return self._end_time - self._start_time

def get_errors(self):
"""Returns the list of errors recorded for this result
Expand Down
15 changes: 14 additions & 1 deletion slash/core/scope_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logbook

from ..ctx import context
from ..exceptions import SlashInternalError
from ..utils.python import call_all_raise_first

Expand All @@ -15,6 +16,12 @@ def __init__(self, session):
self._scopes = []
self._last_module = self._last_test = None

@property
def current_scope(self):
if not self._scopes:
return None
return self._scopes[-1]

def begin_test(self, test):
test_module = test.__slash__.module_name
if not test_module:
Expand All @@ -28,7 +35,7 @@ def begin_test(self, test):
if self._last_module is not None:
_logger.trace('Module scope has changed. Popping previous module scope')
self._pop_scope('module')
assert self._scopes[-1] != 'module'
assert self.current_scope != 'module'
self._push_scope('module')
self._last_module = test_module
self._push_scope('test')
Expand Down Expand Up @@ -66,3 +73,9 @@ def flush_remaining_scopes(self):
_logger.trace('Flushing remaining scopes: {}', self._scopes)
call_all_raise_first([functools.partial(self._pop_scope, s)
for s in self._scopes[::-1]])


def get_current_scope():
if context.session is None or context.session.scope_manager is None:
return None
return context.session.scope_manager.current_scope
5 changes: 4 additions & 1 deletion slash/exception_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,15 @@ def handle_exception(exc_info, context=None):
It also adds the exception to its correct place in the current result, be it a failure, an error or a skip
"""
already_handled = is_exception_handled(exc_info[1])
exc_value = exc_info[1]
already_handled = is_exception_handled(exc_value)
msg = "Handling exception"
if context is not None:
msg += " (Context: {0})"
if already_handled:
msg += " (already handled)"
if is_exception_fatal(exc_value):
msg += " FATAL"
_logger.debug(msg, context, exc_info=exc_info if not already_handled else None)

if not already_handled:
Expand Down
Loading

0 comments on commit 3c0a264

Please sign in to comment.