Source code for slash.runner

from contextlib import contextmanager, ExitStack

import logbook

from . import hooks
from . import log
from .conf import config
from .ctx import context
from .exception_handling import handling_exceptions
from .exceptions import NoActiveSession, SlashInternalError
from .core.function_test import FunctionTest
from .core.metadata import ensure_test_metadata
from .core.exclusions import is_excluded
from .core import requirements
from .utils.iteration import PeekableIterator


_logger = logbook.Logger(__name__)
log.set_log_color(_logger.name, logbook.NOTICE, 'blue')


[docs]def run_tests(iterable, stop_on_error=None): """ Runs tests from an iterable using the current session """ # pylint: disable=maybe-no-member def should_stop_on_error(): if stop_on_error is None: return config.root.run.stop_on_error return stop_on_error if context.session is None or not context.session.started: raise NoActiveSession("A session is not currently started") test_iterator = PeekableIterator(iterable) last_filename = None complete = False try: for test in test_iterator: if config.root.run.dump_variation: _dump_variation(test) _set_test_metadata(test) test_filename = test.__slash__.file_path if last_filename != test_filename: context.session.reporter.report_file_start(test_filename) last_filename = test_filename context.session.reporter.report_test_start(test) _logger.notice( "#{}: {}", test.__slash__.test_index1, test.__slash__.get_address( raw_params=config.root.log.show_raw_param_values ), extra={'highlight': True, 'filter_bypass': True}) _run_single_test(test, test_iterator) result = context.session.results[test] context.session.reporter.report_test_end(test, result) if not test_iterator.has_next() or ensure_test_metadata(test_iterator.peek()).file_path != last_filename: context.session.reporter.report_file_end(last_filename) if result.has_fatal_exception(): _logger.debug("Stopping on fatal exception") break if should_stop_on_error() and not context.session.results.is_success(allow_skips=True): _logger.debug("Stopping (run.stop_on_error==True)") break else: complete = True finally: if config.root.parallel.worker_id is None: context.session.initiate_cleanup() if config.root.parallel.worker_id is None: _mark_unrun_tests(test_iterator) if complete: context.session.mark_complete() elif last_filename is not None: context.session.reporter.report_file_end(last_filename) _logger.trace('Session finished. is_success={0} has_skips={1}', context.session.results.is_success(allow_skips=True), bool(context.session.results.get_num_skipped()))
def _dump_variation(test): _logger.trace('Variation information:\n{}', '\n'.join('\t{}: {!r}'.format(k, v) for k, v in sorted(test.get_variation().id.items()))) def _run_single_test(test, test_iterator): next_test = test_iterator.peek_or_none() with ExitStack() as exit_stack: # sets the current result, test id etc. result, prev_result = exit_stack.enter_context(_get_test_context(test)) with handling_exceptions(swallow=True): should_run = _process_requirements_and_exclusions(test) if not should_run: return result.mark_started() with TestStartEndController(result, prev_result) as controller: try: try: with handling_exceptions(swallow=True): context.session.scope_manager.begin_test(test) try: controller.start() with handling_exceptions(swallow=True): test.run() finally: context.session.scope_manager.end_test(test) except context.session.get_skip_exception_types(): pass _fire_test_summary_hooks(test, result) if next_test is None and config.root.parallel.worker_id is None: controller.end() with handling_exceptions(swallow=True): context.session.initiate_cleanup() except context.session.get_skip_exception_types(): pass def _process_requirements_and_exclusions(test): """Returns whether or not a test should run based on requirements and exclusions, also triggers skips and relevant hooks """ unmet_reqs = test.get_unmet_requirements() if not unmet_reqs: return _process_exclusions(test) messages = set() for req, message in unmet_reqs: if isinstance(req, requirements.Skip): context.result.add_skip(req.reason) msg = 'Skipped' if not req.reason else req.reason else: msg = 'Unmet requirement: {}'.format(message or req) context.result.add_skip(msg) messages.add(msg) hooks.test_avoided(reason=', '.join(messages)) # pylint: disable=no-member return False def _process_exclusions(test): if is_excluded(test): reason = 'Excluded due to parameter combination exclusion rules' context.result.add_skip(reason) hooks.test_avoided(reason=reason) # pylint: disable=no-member return False return True class TestStartEndController(object): def __init__(self, result, prev_result): self._result = result self._prev_result = prev_result self._started = False def __enter__(self): return self def start(self): if not self._started: self._started = True self._result.mark_started() hooks.test_start() # pylint: disable=no-member def end(self): if self._started: self._started = False try: with context.session.cleanups.forbid_implicit_scoping_context(): hooks.test_end() # pylint: disable=no-member self._result.mark_finished() finally: context.result = self._prev_result def __exit__(self, *args): self.end() def _fire_test_summary_hooks(test, result): # pylint: disable=unused-argument with handling_exceptions(): if result.is_just_failure(): hooks.test_failure() # pylint: disable=no-member elif result.is_skip(): hooks.test_skip(reason=result.get_skips()[0]) # pylint: disable=no-member elif result.is_success(): hooks.test_success() # pylint: disable=no-member else: _logger.debug('Firing test_error hook for {0} (result: {1})', test, result) hooks.test_error() # pylint: disable=no-member def _set_test_metadata(test): ensure_test_metadata(test) if test.__slash__.test_index0 is not None: raise SlashInternalError('Test index of {} should be None when setting test metadata but is {}' .format(test, test.__slash__.test_index0)) test.__slash__.test_index0 = next(context.session.test_index_counter) if config.root.parallel.worker_id is None \ else context.session.current_parallel_test_index def _mark_unrun_tests(test_iterator): remaining = list(test_iterator) for test in remaining: with _get_test_context(test, logging=False): pass @contextmanager def _get_test_context(test, logging=True): ensure_test_metadata(test) with _set_current_test_context(test): result = context.session.results.create_result(test) prev_result = context.result context.result = result try: # pylint: disable=superfluous-parens with (context.session.logging.get_test_logging_context(result) if logging else ExitStack()): _logger.debug("Started test #{0.__slash__.test_index1}: {0}", test) yield result, prev_result finally: context.result = prev_result @contextmanager def _set_current_test_context(test): prev_test = context.test prev_test_id = context.test_id context.test = test context.test_id = test.__slash__.id if isinstance(test, FunctionTest): context.test_classname = None context.test_methodname = test.__slash__.factory_name else: context.test_classname = test.__slash__.factory_name # this includes a dot (.), so it has to be truncated context.test_methodname = test.__slash__.address_in_factory[1:] try: yield finally: context.test = prev_test context.test_id = prev_test_id