Example #1
0
    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = io.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = io.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture
Example #2
0
    def setup_capture(self, context):
        assert context is not None
        if self.config.stdout_capture:
            self.stdout_capture = StringIO()
            context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO()
            context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            context.log_capture = self.log_capture
    def test_get_value_returns_all_log_records(self):
        class FakeConfig(object):
            logging_filter = None
            logging_format = None
            logging_datefmt = None
            logging_level = None

        fake_records = [object() for x in range(0, 10)]

        handler = LoggingCapture(FakeConfig())
        handler.buffer = fake_records

        with patch.object(handler.formatter, 'format') as format:
            format.return_value = 'foo'
            expected = '\n'.join(['foo'] * len(fake_records))

            eq_(handler.getvalue(), expected)

            calls = [args[0][0] for args in format.call_args_list]
            eq_(calls, fake_records)
Example #4
0
    def test_get_value_returns_all_log_records(self):
        class FakeConfig(object):
            logging_filter = None
            logging_format = None
            logging_datefmt = None
            logging_level = None

        fake_records = [object() for x in range(0, 10)]

        handler = LoggingCapture(FakeConfig())
        handler.buffer = fake_records

        with patch.object(handler.formatter, 'format') as format:
            format.return_value = 'foo'
            expected = '\n'.join(['foo'] * len(fake_records))

            eq_(handler.getvalue(), expected)

            calls = [args[0][0] for args in format.call_args_list]
            eq_(calls, fake_records)
Example #5
0
def step_I_capture_logrecords(context):
    """

    .. code-block: gherkin
        Given I capture log records
        When I capture log records

    :param context:
    """
    raise NotImplementedError()
    logcapture = getattr(context, "logcapture", None)
    if not logcapture:
        context.logcapture = LoggingCapture()
Example #6
0
    def test_get_value_returns_all_log_records(self):
        __pychecker__ = "no-shadowbuiltin unusednames=x"
        class FakeConfig(object):
            logging_filter = None
            logging_format = None
            logging_datefmt = None
            logging_level = None

        fake_records = [object() for x in range(0, 10)]

        handler = LoggingCapture(FakeConfig())
        handler.buffer = fake_records

        # pylint: disable=W0622
        #   W0622   Redefining built-in format
        with patch.object(handler.formatter, 'format') as format:
            format.return_value = 'foo'
            expected = '\n'.join(['foo'] * len(fake_records))

            eq_(handler.getvalue(), expected)

            calls = [args[0][0] for args in format.call_args_list]
            eq_(calls, fake_records)
Example #7
0
    def setup_capture(self, context):
        assert context is not None
        if self.config.stdout_capture:
            self.stdout_capture = StringIO()
            context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO()
            context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            context.log_capture = self.log_capture
Example #8
0
    def setup_capture(self):
        # pylint: disable=W0201
        #   W0201   Attribute ... defined outside __init__
        #           => stdout_capture, log_capture (BUT WRONG)
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture
Example #9
0
    def setup_capture(self):
        if not self.context:
            self.context = Context(self)

        if self.config.stdout_capture:
            self.stdout_capture = six.moves.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = six.moves.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture
Example #10
0
class ModelRunner(object):
    """
    Test runner for a behave model (features).
    Provides the core functionality of a test runner and
    the functional API needed by model elements.

    .. attribute:: aborted

          This is set to true when the user aborts a test run
          (:exc:`KeyboardInterrupt` exception). Initially: False.
          Stored as derived attribute in :attr:`Context.aborted`.
    """

    def __init__(self, config, features=None):
        self.config = config
        self.features = features or []
        self.hooks = {}
        self.formatters = []
        self.undefined_steps = []

        self.context = None
        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

    # @property
    def _get_aborted(self):
        value = False
        if self.context:
            value = self.context.aborted
        return value

    # @aborted.setter
    def _set_aborted(self, value):
        assert self.context
        self.context._set_root_attribute('aborted', bool(value))

    aborted = property(_get_aborted, _set_aborted,
                       doc="Indicates that test run is aborted by the user.")

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            # try:
            with context.user_mode():
                self.hooks[name](context, *args)
            # except KeyboardInterrupt:
            #     self.aborted = True
            #     if name not in ("before_all", "after_all"):
            #         raise

    def setup_capture(self):
        if not self.context:
            self.context = Context(self)

        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stdout:
                self.old_stdout = sys.stdout
                sys.stdout = self.stdout_capture
            assert sys.stdout is self.stdout_capture

        if self.config.stderr_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stderr:
                self.old_stderr = sys.stderr
                sys.stderr = self.stderr_capture
            assert sys.stderr is self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stdout:
                sys.stdout = self.old_stdout
                self.old_stdout = None
            assert sys.stdout is not self.stdout_capture

        if self.config.stderr_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stderr:
                sys.stderr = self.old_stderr
                self.old_stderr = None
            assert sys.stderr is not self.stderr_capture

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()

    def run_model(self, features=None):
        if not self.context:
            self.context = Context(self)
        if features is None:
            features = self.features

        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        context = self.context
        self.setup_capture()
        self.run_hook('before_all', context)

        run_feature = not self.aborted
        failed_count = 0
        undefined_steps_initial_size = len(self.undefined_steps)
        for feature in features:
            if run_feature:
                try:
                    self.feature = feature
                    for formatter in self.formatters:
                        formatter.uri(feature.filename)

                    failed = feature.run(self)
                    if failed:
                        failed_count += 1
                        if self.config.stop or self.aborted:
                            # -- FAIL-EARLY: After first failure.
                            run_feature = False
                except KeyboardInterrupt:
                    self.aborted = True
                    failed_count += 1
                    run_feature = False

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        if self.aborted:
            print("\nABORTED: By user.")
        for formatter in self.formatters:
            formatter.close()
        self.run_hook('after_all', self.context)
        for reporter in self.config.reporters:
            reporter.end()
        # if self.aborted:
        #     print("\nABORTED: By user.")
        failed = ((failed_count > 0) or self.aborted or
                  (len(self.undefined_steps) > undefined_steps_initial_size))
        return failed

    def run(self):
        """
        Implements the run method by running the model.
        """
        self.context = Context(self)
        return self.run_model()
Example #11
0
class ModelRunner(object):
    """
    Test runner for a behave model (features).
    Provides the core functionality of a test runner and
    the functional API needed by model elements.

    .. attribute:: aborted

          This is set to true when the user aborts a test run
          (:exc:`KeyboardInterrupt` exception). Initially: False.
          Stored as derived attribute in :attr:`Context.aborted`.
    """

    # pylint: disable=too-many-instance-attributes

    def __init__(self, config, features=None, step_registry=None):
        self.config = config
        self.features = features or []
        self.hooks = {}
        self.formatters = []
        self.undefined_steps = []
        self.step_registry = step_registry

        self.context = None
        self.feature = None
        self.hook_failures = 0

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

    # @property
    def _get_aborted(self):
        value = False
        if self.context:
            value = self.context.aborted
        return value

    # @aborted.setter
    def _set_aborted(self, value):
        # pylint: disable=protected-access
        assert self.context, "REQUIRE: context, but context=%r" % self.context
        self.context._set_root_attribute("aborted", bool(value))

    aborted = property(_get_aborted,
                       _set_aborted,
                       doc="Indicates that test run is aborted by the user.")

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            try:
                with context.user_mode():
                    self.hooks[name](context, *args)
            # except KeyboardInterrupt:
            #     self.aborted = True
            #     if name not in ("before_all", "after_all"):
            #         raise
            except Exception as e:  # pylint: disable=broad-except
                # -- HANDLE HOOK ERRORS:
                use_traceback = False
                if self.config.verbose:
                    use_traceback = True
                    ExceptionUtil.set_traceback(e)
                extra = u""
                if "tag" in name:
                    extra = "(tag=%s)" % args[0]

                error_text = ExceptionUtil.describe(e, use_traceback).rstrip()
                error_message = u"HOOK-ERROR in %s%s: %s" % (name, extra,
                                                             error_text)
                print(error_message)
                self.hook_failures += 1
                if "tag" in name:
                    # -- SCENARIO or FEATURE
                    statement = getattr(context, "scenario", context.feature)
                elif "all" in name:
                    # -- ABORT EXECUTION: For before_all/after_all
                    self.aborted = True
                    statement = None
                else:
                    # -- CASE: feature, scenario, step
                    statement = args[0]

                if statement:
                    # -- CASE: feature, scenario, step
                    statement.hook_failed = True
                    statement.store_exception_context(e)
                    statement.error_message = error_message

    def setup_capture(self):
        if not self.context:
            self.context = Context(self)

        if self.config.stdout_capture:
            self.stdout_capture = StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stdout:
                self.old_stdout = sys.stdout
                sys.stdout = self.stdout_capture
            assert sys.stdout is self.stdout_capture

        if self.config.stderr_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stderr:
                self.old_stderr = sys.stderr
                sys.stderr = self.stderr_capture
            assert sys.stderr is self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stdout:
                sys.stdout = self.old_stdout
                self.old_stdout = None
            assert sys.stdout is not self.stdout_capture

        if self.config.stderr_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stderr:
                sys.stderr = self.old_stderr
                self.old_stderr = None
            assert sys.stderr is not self.stderr_capture

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()

    def run_model(self, features=None):
        # pylint: disable=too-many-branches
        if not self.context:
            self.context = Context(self)
        if self.step_registry is None:
            self.step_registry = the_step_registry
        if features is None:
            features = self.features

        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        context = self.context
        self.hook_failures = 0
        self.setup_capture()
        self.run_hook("before_all", context)

        run_feature = not self.aborted
        failed_count = 0
        undefined_steps_initial_size = len(self.undefined_steps)
        for feature in features:
            if run_feature:
                try:
                    self.feature = feature
                    for formatter in self.formatters:
                        formatter.uri(feature.filename)

                    failed = feature.run(self)
                    if failed:
                        failed_count += 1
                        if self.config.stop or self.aborted:
                            # -- FAIL-EARLY: After first failure.
                            run_feature = False
                except KeyboardInterrupt:
                    self.aborted = True
                    failed_count += 1
                    run_feature = False

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        if self.aborted:
            print("\nABORTED: By user.")
        for formatter in self.formatters:
            formatter.close()
        self.run_hook("after_all", self.context)
        for reporter in self.config.reporters:
            reporter.end()

        failed = ((failed_count > 0) or self.aborted
                  or (self.hook_failures > 0) or
                  (len(self.undefined_steps) > undefined_steps_initial_size))
        return failed

    def run(self):
        """
        Implements the run method by running the model.
        """
        self.context = Context(self)
        return self.run_model()
Example #12
0
class Runner(object):
    def __init__(self, config):
        self.config = config

        self.hooks = {}

        self.features = []
        self.passed = []
        self.failed = []
        self.undefined = []
        self.skipped = []

        self.path_manager = PathManager()

        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.out_stdout = None

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print 'Supplied path:', ', '.join('"%s"' % path for path
                                                  in self.config.paths)
            base_dir = os.path.abspath(self.config.paths[0])

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print 'Primary path is to a file so using its directory'
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print 'Using default path "./features"'
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = os.path.split(base_dir)[0]

        new_base_dir = base_dir

        while True:
            if self.config.verbose:
                print 'Trying base directory:', new_base_dir

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment.py')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find "steps" directory. Please '\
                        'specify where to find your features.'
                else:
                    print 'ERROR: Could not find "steps" directory in your '\
                        'specified path "%s"' % base_dir
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find any "<name>.feature" files. '\
                        'Please specify where to find your features.'
                else:
                    print 'ERROR: Could not find any "<name>.feature" files '\
                        'in your specified path "%s"' % base_dir
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def load_hooks(self, filename='environment.py'):
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

    def load_step_definitions(self, extra_step_paths=[]):
        steps_dir = os.path.join(self.base_dir, 'steps')

        # allow steps to import other stuff from the steps dir
        sys.path.insert(0, steps_dir)

        step_globals = {
            'step_matcher': matchers.step_matcher,
        }

        for step_type in ('given', 'when', 'then', 'step'):
            decorator = getattr(step_registry, step_type)
            step_globals[step_type] = decorator
            step_globals[step_type.title()] = decorator

        for path in [steps_dir] + list(extra_step_paths):
            for name in os.listdir(path):
                if name.endswith('.py'):
                    exec_file(os.path.join(path, name), step_globals)

        # clean up the path
        sys.path.pop(0)

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            with context.user_mode():
                self.hooks[name](context, *args)

    def feature_files(self):
        files = []
        for path in self.config.paths:
            if os.path.isdir(path):
                for dirpath, dirnames, filenames in os.walk(path):
                    for filename in filenames:
                        if filename.endswith('.feature'):
                            files.append(os.path.join(dirpath, filename))
            elif path.startswith('@'):
                files.extend([filename.strip() for filename in open(path)])
            elif os.path.exists(path):
                files.append(path)
            else:
                raise Exception("Can't find path: " + path)
        return files

    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()

    def run_with_paths(self):
        self.load_hooks()
        self.load_step_definitions()

        context = self.context = Context(self)
        stream = self.config.output
        failed = False

        self.run_hook('before_all', context)

        for filename in self.feature_files():
            if self.config.exclude(filename):
                continue

            feature = parser.parse_file(os.path.abspath(filename),
                                        language=self.config.lang)

            self.features.append(feature)
            self.feature = feature

            self.formatter = formatters.get_formatter(self.config, stream)
            self.formatter.uri(filename)

            failed |= feature.run(self)

            self.formatter.close()
            stream.write('\n')

            [reporter.feature(feature) for reporter in self.config.reporters]

            if failed and self.config.stop:
                break

        self.run_hook('after_all', context)

        [reporter.end() for reporter in self.config.reporters]

        return failed

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            self.old_stdout = sys.stdout
            sys.stdout = self.stdout_capture

        if self.config.stderr_capture:
            self.old_stderr = sys.stderr
            sys.stderr = self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            sys.stdout = self.old_stdout

        if self.config.stderr_capture:
            sys.stderr = self.old_stderr

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()
Example #13
0
class Runner(object):
    def __init__(self, config):
        self.config = config
        self.hooks = {}
        self.features = []
        self.undefined = []
        # -- XXX-JE-UNUSED:
        # self.passed = []
        # self.failed = []
        # self.skipped = []

        self.path_manager = PathManager()
        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None

        self.base_dir = None
        self.context = None
        self.formatter = None

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print 'Supplied path:', \
                      ', '.join('"%s"' % path for path in self.config.paths)
            base_dir = self.config.paths[0]
            if base_dir.startswith('@'):
                # -- USE: behave @features.txt
                base_dir = base_dir[1:]
                files = self.feature_files()
                if files:
                    base_dir = os.path.dirname(files[0])
            base_dir = os.path.abspath(base_dir)

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print 'Primary path is to a file so using its directory'
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print 'Using default path "./features"'
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = path_getrootdir(base_dir)
        new_base_dir = base_dir

        while True:
            if self.config.verbose:
                print 'Trying base directory:', new_base_dir

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment.py')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find "steps" directory. Please '\
                        'specify where to find your features.'
                else:
                    print 'ERROR: Could not find "steps" directory in your '\
                        'specified path "%s"' % base_dir
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find any "<name>.feature" files. '\
                        'Please specify where to find your features.'
                else:
                    print 'ERROR: Could not find any "<name>.feature" files '\
                        'in your specified path "%s"' % base_dir
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def load_hooks(self, filename='environment.py'):
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

    def load_step_definitions(self, extra_step_paths=[]):
        steps_dir = os.path.join(self.base_dir, 'steps')

        # allow steps to import other stuff from the steps dir
        sys.path.insert(0, steps_dir)

        step_globals = {
            'step_matcher': matchers.step_matcher,
        }
        # -- Default matcher can be overridden in "environment.py" hook.
        default_matcher = matchers.current_matcher

        for step_type in ('given', 'when', 'then', 'step'):
            decorator = getattr(step_registry, step_type)
            step_globals[step_type] = decorator
            step_globals[step_type.title()] = decorator

        for path in [steps_dir] + list(extra_step_paths):
            for name in os.listdir(path):
                if name.endswith('.py'):
                    # -- LOAD STEP DEFINITION:
                    # Reset to default matcher after each step-definition.
                    # A step-definition may change the matcher 0..N times.
                    # ENSURE: Each step definition has clean globals.
                    step_module_globals = dict(step_globals)
                    exec_file(os.path.join(path, name), step_module_globals)
                    matchers.current_matcher = default_matcher

        # -- CLEANUP: Clean up the path.
        sys.path.pop(0)

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            with context.user_mode():
                self.hooks[name](context, *args)

    @staticmethod
    def parse_features_configfile(features_configfile):
        """
        Read textual file, ala '@features.txt'. This file contains:

          * a feature filename in each line
          * empty lines (skipped)
          * comment lines (skipped)

        Relative path names are evaluated relative to the configfile directory.
        A leading '@' (AT) character is removed from the configfile name.

        :param features_configfile:  Name of features configfile.
        :return: List of feature filenames.
        """
        if features_configfile.startswith('@'):
            features_configfile = features_configfile[1:]
        here = os.path.dirname(features_configfile) or "."
        files = []
        for line in open(features_configfile).readlines():
            line = line.strip()
            if not line:
                continue  # SKIP: Over empty line(s).
            elif line.startswith('#'):
                continue  # SKIP: Over comment line(s).
            files.append(os.path.normpath(os.path.join(here, line)))
        return files

    def feature_files(self):
        files = []
        for path in self.config.paths:
            if os.path.isdir(path):
                for dirpath, dirnames, filenames in os.walk(path):
                    dirnames.sort()
                    for filename in sorted(filenames):
                        if filename.endswith('.feature'):
                            files.append(os.path.join(dirpath, filename))
            elif path.startswith('@'):
                # -- USE: behave @list_of_features.txt
                files.extend(self.parse_features_configfile(path[1:]))
            elif os.path.exists(path):
                files.append(path)
            else:
                raise Exception("Can't find path: " + path)
        return files

    def parse_features(self, feature_files):
        """
        Parse feature files and return list of Feature model objects.

        :param feature_files: List of feature files to parse.
        :return: List of feature objects.
        """
        features = []
        for filename in feature_files:
            if self.config.exclude(filename):
                continue

            filename2 = os.path.abspath(filename)
            feature = parser.parse_file(filename2, language=self.config.lang)
            if not feature:
                # -- CORNER-CASE: Feature file without any feature(s).
                continue
            features.append(feature)
        return features

    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()

    def run_with_paths(self):
        self.load_hooks()
        self.load_step_definitions()

        context = self.context = Context(self)
        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        self.setup_capture()
        stream = self.config.output
        failed_count = 0

        self.run_hook('before_all', context)

        # -- STEP: Parse all feature files.
        features = self.parse_features(self.feature_files())
        self.features.extend(features)

        # -- STEP: Run all features.
        undefined_steps_initial_size = len(self.undefined)
        run_feature = True
        for feature in features:
            if run_feature:
                self.feature = feature
                self.formatter = formatters.get_formatter(self.config, stream)
                self.formatter.uri(feature.filename)

                failed = feature.run(self)
                if failed:
                    failed_count += 1
                    if self.config.stop:
                        # -- FAIL-EARLY: After first failure.
                        run_feature = False

                self.formatter.close()

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        self.run_hook('after_all', context)
        for reporter in self.config.reporters:
            reporter.end()

        failed = ((failed_count > 0)
                  or (len(self.undefined) > undefined_steps_initial_size))
        return failed

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            self.old_stdout = sys.stdout
            sys.stdout = self.stdout_capture

        if self.config.stderr_capture:
            self.old_stderr = sys.stderr
            sys.stderr = self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            sys.stdout = self.old_stdout

        if self.config.stderr_capture:
            sys.stderr = self.old_stderr

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()
Example #14
0
class CaptureController(object):
    """Simplifies the lifecycle to capture output from various sources."""
    def __init__(self, config):
        self.config = config
        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

    @property
    def captured(self):
        """Provides access of the captured output data.

        :return: Object that stores the captured output parts (as Captured).
        """
        stdout = None
        stderr = None
        log_out = None
        if self.config.stdout_capture and self.stdout_capture:
            stdout = _text(self.stdout_capture.getvalue())
        if self.config.stderr_capture and self.stderr_capture:
            stderr = _text(self.stderr_capture.getvalue())
        if self.config.log_capture and self.log_capture:
            log_out = _text(self.log_capture.getvalue())
        return Captured(stdout, stderr, log_out)

    def setup_capture(self, context):
        assert context is not None
        if self.config.stdout_capture:
            self.stdout_capture = StringIO()
            context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO()
            context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stdout:
                self.old_stdout = sys.stdout
                sys.stdout = self.stdout_capture
            assert sys.stdout is self.stdout_capture

        if self.config.stderr_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stderr:
                self.old_stderr = sys.stderr
                sys.stderr = self.stderr_capture
            assert sys.stderr is self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stdout:
                sys.stdout = self.old_stdout
                self.old_stdout = None
            assert sys.stdout is not self.stdout_capture

        if self.config.stderr_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stderr:
                sys.stderr = self.old_stderr
                self.old_stderr = None
            assert sys.stderr is not self.stderr_capture

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()

    def make_capture_report(self):
        """Combine collected output and return as string."""
        return self.captured.make_report()
Example #15
0
class Runner(object):
    def __init__(self, config):
        self.config = config
        self.hooks = {}
        self.features = []
        self.undefined = []
        # -- XXX-JE-UNUSED:
        # self.passed = []
        # self.failed = []
        # self.skipped = []

        self.path_manager = PathManager()
        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

        self.base_dir = None
        self.context = None
        self.formatters = None

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print 'Supplied path:', \
                      ', '.join('"%s"' % path for path in self.config.paths)
            base_dir = self.config.paths[0]
            if base_dir.startswith('@'):
                # -- USE: behave @features.txt
                base_dir = base_dir[1:]
                files = self.feature_files()
                if files:
                    base_dir = os.path.dirname(files[0])
            base_dir = os.path.abspath(base_dir)

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print 'Primary path is to a file so using its directory'
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print 'Using default path "./features"'
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = path_getrootdir(base_dir)
        new_base_dir = base_dir

        while True:
            if self.config.verbose:
                print 'Trying base directory:', new_base_dir

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment.py')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find "steps" directory. Please '\
                        'specify where to find your features.'
                else:
                    print 'ERROR: Could not find "steps" directory in your '\
                        'specified path "%s"' % base_dir
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find any "<name>.feature" files. '\
                        'Please specify where to find your features.'
                else:
                    print 'ERROR: Could not find any "<name>.feature" files '\
                        'in your specified path "%s"' % base_dir
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def load_hooks(self, filename='environment.py'):
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

    def load_step_definitions(self, extra_step_paths=[]):
        steps_dir = os.path.join(self.base_dir, 'steps')

        # allow steps to import other stuff from the steps dir
        sys.path.insert(0, steps_dir)

        step_globals = {
            'step_matcher': matchers.step_matcher,
        }
        # -- Default matcher can be overridden in "environment.py" hook.
        default_matcher = matchers.current_matcher

        for step_type in ('given', 'when', 'then', 'step'):
            decorator = getattr(step_registry, step_type)
            step_globals[step_type] = decorator
            step_globals[step_type.title()] = decorator

        for path in [steps_dir] + list(extra_step_paths):
            for name in os.listdir(path):
                if name.endswith('.py'):
                    # -- LOAD STEP DEFINITION:
                    # Reset to default matcher after each step-definition.
                    # A step-definition may change the matcher 0..N times.
                    # ENSURE: Each step definition has clean globals.
                    step_module_globals = dict(step_globals)
                    exec_file(os.path.join(path, name), step_module_globals)
                    matchers.current_matcher = default_matcher

        # -- CLEANUP: Clean up the path.
        sys.path.pop(0)

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            with context.user_mode():
                self.hooks[name](context, *args)

    def feature_files(self):
        return collect_feature_files(self.config.paths)


    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()

    def run_with_paths(self):
        self.load_hooks()
        self.load_step_definitions()

        context = self.context = Context(self)
        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        self.setup_capture()
        stream_openers = self.config.outputs
        failed_count = 0

        self.run_hook('before_all', context)

        # -- STEP: Parse all feature files.
        feature_files = [ filename for filename in self.feature_files()
                                    if not self.config.exclude(filename) ]
        features = parse_features(feature_files, language=self.config.lang)
        self.features.extend(features)

        # -- STEP: Run all features.
        self.formatters = formatters.get_formatter(self.config, stream_openers)
        undefined_steps_initial_size = len(self.undefined)
        run_feature = True
        for feature in features:
            if run_feature:
                self.feature = feature
                for formatter in self.formatters:
                    formatter.uri(feature.filename)

                failed = feature.run(self)
                if failed:
                    failed_count += 1
                    if self.config.stop:
                        # -- FAIL-EARLY: After first failure.
                        run_feature = False

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        for formatter in self.formatters:
            formatter.close()
        self.run_hook('after_all', context)
        for reporter in self.config.reporters:
            reporter.end()

        failed = ((failed_count > 0) or
                  (len(self.undefined) > undefined_steps_initial_size))
        return failed

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            self.old_stdout = sys.stdout
            sys.stdout = self.stdout_capture

        if self.config.stderr_capture:
            self.old_stderr = sys.stderr
            sys.stderr = self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            sys.stdout = self.old_stdout

        if self.config.stderr_capture:
            sys.stderr = self.old_stderr

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()
Example #16
0
class Runner(object):
    '''
    Test runner for behave.

    .. attribute:: aborted

      This is set to true when the user aborts a test run
      (:exc:`KeyboardInterrupt` exception). Initially: False.
      Stored as derived attribute in :attr:`Context.aborted`.
    '''
    def __init__(self, config):
        self.config = config
        self.hooks = {}
        self.features = []
        self.undefined = []
        # -- XXX-JE-UNUSED:
        # self.passed = []
        # self.failed = []
        # self.skipped = []

        self.path_manager = PathManager()
        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

        self.base_dir = None
        self.context = None
        self.formatters = None

    # @property
    def _get_aborted(self):
        """
        Indicates that a test run was aborted by the user
        (:exc:`KeyboardInterrupt` exception).
        Stored in :attr:`Context.aborted` attribute (as root attribute).

        :return: Current aborted state, initially false.
        :rtype: bool
        """
        value = False
        if self.context:
            value = self.context.aborted
        return value

    # @aborted.setter
    def _set_aborted(self, value):
        """
        Set the aborted value.

        :param value: New aborted value (as bool).
        """
        assert self.context
        self.context._set_root_attribute('aborted', bool(value))

    aborted = property(_get_aborted, _set_aborted,
                       doc="Indicates that test run is aborted by the user.")

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print 'Supplied path:', \
                      ', '.join('"%s"' % path for path in self.config.paths)
            first_path = self.config.paths[0]
            if hasattr(first_path, "filename"):
                # -- BETTER: isinstance(first_path, FileLocation):
                first_path = first_path.filename
            base_dir = first_path
            if base_dir.startswith('@'):
                # -- USE: behave @features.txt
                base_dir = base_dir[1:]
                file_locations = self.feature_locations()
                if file_locations:
                    base_dir = os.path.dirname(file_locations[0].filename)
            base_dir = os.path.abspath(base_dir)

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print 'Primary path is to a file so using its directory'
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print 'Using default path "./features"'
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = path_getrootdir(base_dir)
        new_base_dir = base_dir

        while True:
            if self.config.verbose:
                print 'Trying base directory:', new_base_dir

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment.py')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find "steps" directory. Please '\
                        'specify where to find your features.'
                else:
                    print 'ERROR: Could not find "steps" directory in your '\
                        'specified path "%s"' % base_dir
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir
        self.config.base_dir = base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find any "<name>.feature" files. '\
                        'Please specify where to find your features.'
                else:
                    print 'ERROR: Could not find any "<name>.feature" files '\
                        'in your specified path "%s"' % base_dir
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def load_hooks(self, filename='environment.py'):
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

    def load_step_definitions(self, extra_step_paths=[]):
        step_globals = {
            'step_matcher': matchers.step_matcher,
        }
        setup_step_decorators(step_globals)

        # -- Allow steps to import other stuff from the steps dir
        # NOTE: Default matcher can be overridden in "environment.py" hook.
        steps_dir = os.path.join(self.base_dir, 'steps')
        paths = [steps_dir] + list(extra_step_paths)
        with PathManager(paths):
            default_matcher = matchers.current_matcher
            for path in paths:
                for name in sorted(os.listdir(path)):
                    if name.endswith('.py'):
                        # -- LOAD STEP DEFINITION:
                        # Reset to default matcher after each step-definition.
                        # A step-definition may change the matcher 0..N times.
                        # ENSURE: Each step definition has clean globals.
                        step_module_globals = step_globals.copy()
                        exec_file(os.path.join(path, name), step_module_globals)
                        matchers.current_matcher = default_matcher

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            # try:
            with context.user_mode():
                self.hooks[name](context, *args)
            # except KeyboardInterrupt:
            #     self.aborted = True
            #     if name not in ("before_all", "after_all"):
            #         raise

    def feature_locations(self):
        return collect_feature_locations(self.config.paths)


    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()

    def run_with_paths(self):
        self.load_hooks()
        self.load_step_definitions()
        context = self.context = Context(self)
        assert not self.aborted
        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        self.setup_capture()
        stream_openers = self.config.outputs
        failed_count = 0

        self.run_hook('before_all', context)

        # -- STEP: Parse all feature files (by using their file location).
        feature_locations = [ filename for filename in self.feature_locations()
                                    if not self.config.exclude(filename) ]
        features = parse_features(feature_locations, language=self.config.lang)
        self.features.extend(features)

        # -- STEP: Run all features.
        self.formatters = formatters.get_formatter(self.config, stream_openers)
        undefined_steps_initial_size = len(self.undefined)
        run_feature = True
        for feature in features:
            if run_feature:
                try:
                    self.feature = feature
                    for formatter in self.formatters:
                        formatter.uri(feature.filename)

                    failed = feature.run(self)
                    if failed:
                        failed_count += 1
                        if self.config.stop or self.aborted:
                            # -- FAIL-EARLY: After first failure.
                            run_feature = False
                except KeyboardInterrupt:
                    self.aborted = True
                    failed_count += 1
                    run_feature = False
        
            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        if self.aborted:
            print "\nABORTED: By user."
        for formatter in self.formatters:
            formatter.close()
        self.run_hook('after_all', context)
        for reporter in self.config.reporters:
            reporter.end()
        # if self.aborted:
        #     print "\nABORTED: By user."

        failed = ((failed_count > 0) or self.aborted or
                  (len(self.undefined) > undefined_steps_initial_size))
        return failed

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            self.old_stdout = sys.stdout
            sys.stdout = self.stdout_capture

        if self.config.stderr_capture:
            self.old_stderr = sys.stderr
            sys.stderr = self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            sys.stdout = self.old_stdout

        if self.config.stderr_capture:
            sys.stderr = self.old_stderr

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()
Example #17
0
class Runner(object):
    def __init__(self, config):
        self.config = config

        self.hooks = {}

        self.features = []
        self.passed = []
        self.failed = []
        self.undefined = []
        self.skipped = []

        self.path_manager = PathManager()

        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None

        self.base_dir   = None
        self.context    = None
        self.formatter  = None

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print 'Supplied path:', ', '.join('"%s"' % path 
                       for path in self.config.paths)
            base_dir = self.config.paths[0]
            if base_dir.startswith('@'):
                # -- USE: behave @features.txt
                base_dir = base_dir[1:]
                files = self.feature_files()
                if files:
                    base_dir = os.path.dirname(files[0])
            base_dir = os.path.abspath(base_dir)

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print 'Primary path is to a file so using its directory'
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print 'Using default path "./features"'
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = path_getrootdir(base_dir)
        new_base_dir = base_dir

        while True:
            if self.config.verbose:
                print 'Trying base directory:', new_base_dir

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment.py')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find "steps" directory. Please '\
                        'specify where to find your features.'
                else:
                    print 'ERROR: Could not find "steps" directory in your '\
                        'specified path "%s"' % base_dir
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find any "<name>.feature" files. '\
                        'Please specify where to find your features.'
                else:
                    print 'ERROR: Could not find any "<name>.feature" files '\
                        'in your specified path "%s"' % base_dir
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def load_hooks(self, filename='environment.py'):
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

    def load_step_definitions(self, extra_step_paths=[]):
        steps_dir = os.path.join(self.base_dir, 'steps')

        # allow steps to import other stuff from the steps dir
        sys.path.insert(0, steps_dir)

        step_globals = {
            'step_matcher': matchers.step_matcher,
        }
        # -- Default matcher can be overridden in "environment.py" hook.
        default_matcher = matchers.current_matcher

        for step_type in ('given', 'when', 'then', 'step'):
            decorator = getattr(step_registry, step_type)
            step_globals[step_type] = decorator
            step_globals[step_type.title()] = decorator

        for path in [steps_dir] + list(extra_step_paths):
            for name in os.listdir(path):
                if name.endswith('.py'):
                    # -- LOAD STEP DEFINITION:
                    # Reset to default matcher after each step-definition.
                    # A step-definition may change the matcher 0..N times.
                    exec_file(os.path.join(path, name), step_globals)
                    matchers.current_matcher = default_matcher

        # -- CLEANUP: Clean up the path.
        sys.path.pop(0)

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            with context.user_mode():
                self.hooks[name](context, *args)

    @staticmethod
    def parse_features_file(features_filename):
        """
        Read textual file, ala '@features.txt'
        :param features_filename:  Name of features file.
        :return: List of feature names.
        """
        if features_filename.startswith('@'):
            features_filename = features_filename[1:]
        here  = os.path.dirname(features_filename) or "."
        files = []
        for line in open(features_filename).readlines():
            line = line.strip()
            if not line:
                continue    # SKIP: Over empty line(s).
            elif line.startswith('#'):
                continue    # SKIP: Over comment line(s).
            files.append(os.path.normpath(os.path.join(here, line)))
        return files

    def feature_files(self):
        files = []
        for path in self.config.paths:
            if os.path.isdir(path):
                for dirpath, dirnames, filenames in os.walk(path):
                    for filename in filenames:
                        if filename.endswith('.feature'):
                            files.append(os.path.join(dirpath, filename))
            elif path.startswith('@'):
                # -- USE: behave @list_of_features.txt
                files.extend(self.parse_features_file(path[1:]))
            elif os.path.exists(path):
                files.append(path)
            else:
                raise Exception("Can't find path: " + path)
        return files

    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()

    def run_with_paths(self):
        self.load_hooks()
        self.load_step_definitions()

        context = self.context = Context(self)
        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        self.setup_capture()
        stream = self.config.output
        failed = False
        failed_count = 0

        self.run_hook('before_all', context)

        for filename in self.feature_files():
            if self.config.exclude(filename):
                continue

            feature = parser.parse_file(os.path.abspath(filename),
                                        language=self.config.lang)

            self.features.append(feature)
            self.feature = feature

            self.formatter = formatters.get_formatter(self.config, stream)
            self.formatter.uri(filename)

            failed = feature.run(self)
            if failed:
                failed_count += 1

            self.formatter.close()
            for reporter in self.config.reporters:
                reporter.feature(feature)

            if failed and self.config.stop:
                break

        self.run_hook('after_all', context)
        for reporter in self.config.reporters:
            reporter.end()

        failed = (failed_count > 0)
        return failed

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            self.old_stdout = sys.stdout
            sys.stdout = self.stdout_capture

        if self.config.stderr_capture:
            self.old_stderr = sys.stderr
            sys.stderr = self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            sys.stdout = self.old_stdout

        if self.config.stderr_capture:
            sys.stderr = self.old_stderr

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()
Example #18
0
class Runner(object):
    """
    Standard test runner for behave:

      * setup paths
      * loads environment hooks
      * loads step definitions
      * select feature files, parses them and creates model (elements)
    """
    def __init__(self, config):
        self.config = config
        self.hooks = {}
        self.features= []
        self.undefined_steps = []


        self.path_manager = PathManager()
        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

        self.base_dir = None
        self.context = None
        self.formatters = None

    # @aborted.setter

    def _get_aborted(self):
        """
        Indicates that a test run was aborted by the user
        (:exc:`KeyboardInterrupt` exception).
        Stored in :attr:`Context.aborted` attribute (as root attribute).
        :return: Current aborted state, initially false.
        :rtype: bool
        """
        value = False
        if self.context:
            value = self.context.aborted
        return value

    def _set_aborted(self, value):
        """
        Set the aborted value.
        :param value: New aborted value (as bool).
        """
        assert self.context
        self.context._set_root_attribute('aborted', bool(value))

    aborted = property(_get_aborted, _set_aborted,
                       doc="Indicates that test run is aborted by the user.")

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print(('Supplied path:', ', '.join('"%s"' % path for path in self.config.paths)))
            first_path = self.config.paths[0]
            if hasattr(first_path, "filename"):
                # -- BETTER: isinstance(first_path, FileLocation):
                first_path = first_path.filename
            base_dir = first_path
            if base_dir.startswith('@'):
                # -- USE: behave @features.txt
                base_dir = base_dir[1:]
                file_locations = self.feature_locations()
                if file_locations:
                    base_dir = os.path.dirname(file_locations[0].filename)
            base_dir = os.path.abspath(base_dir)

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print('Primary path is to a file so using its directory')
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print('Using default path "./features"')
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = path_getrootdir(base_dir)
        new_base_dir = base_dir


        while True:
            if self.config.verbose:
                print(('Trying base directory:', new_base_dir))

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print('ERROR: Could not find "steps" directory. Please specify where to find your features.')
                else:
                    print(('ERROR: Could not find "steps" directory in your specified path "%s"' % base_dir))
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir
        self.config.base_dir = base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print('ERROR: Could not find any "<name>.feature" files. Please specify where to find your features.')
                else:
                    print(('ERROR: Could not find any "<name>.feature" files in your specified path "%s"' % base_dir))
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def before_all_default_hook(self, context):
        """
        Default implementation for :func:`before_all()` hook.
        Setup the logging subsystem based on the configuration data.
        """
        context.config.setup_logging()

    def load_hooks(self, filename=None):
        filename = filename or self.config.environment_file
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

        if 'before_all' not in self.hooks:
            self.hooks['before_all'] = self.before_all_default_hook

    def load_step_definitions(self, extra_step_paths=[]):
        step_globals = {
            'step_matcher':     matchers.step_matcher,
        }
        setup_step_decorators(step_globals)

        # -- Allow steps to import other stuff from the steps dir
        # NOTE: Default matcher can be overridden in "environment.py" hook.
        steps_dir = os.path.join(self.base_dir, 'steps')
        paths = [steps_dir] + list(extra_step_paths)
        with PathManager(paths):
            default_matcher = matchers.current_matcher
            for path in paths:
                for name in sorted(os.listdir(path)):
                    if name.endswith('.py'):
                        # -- LOAD STEP DEFINITION:
                        # Reset to default matcher after each step-definition.
                        # A step-definition may change the matcher 0..N times.
                        # ENSURE: Each step definition has clean globals.
                        # try:
                        step_module_globals = step_globals.copy()
                        exec_file(os.path.join(path, name), step_module_globals)
                        matchers.current_matcher = default_matcher
                        # except Exception as e:
                        #     e_text = _text(e)
                        #     print("Exception %s: %s" % (e.__class__.__name__, e_text))
                        #     raise

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            # try:
            with context.user_mode():
                self.hooks[name](context, *args)
            # except KeyboardInterrupt:
            #     self.aborted = True
            #     if name not in ("before_all", "after_all"):
            #         raise

    def feature_locations(self):
        return collect_feature_locations(self.config.paths)


    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()


    def run_with_paths(self):
        context = self.context = Context(self)
        self.load_hooks()
        self.load_step_definitions()
        assert not self.aborted
        stream_openers = self.config.outputs
        failed_count = 0

        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        self.setup_capture()
        self.run_hook('before_all', context)

        # -- STEP: Parse all feature files (by using their file location).
        feature_locations = [ filename for filename in self.feature_locations()
                                    if not self.config.exclude(filename) ]
        features = parse_features(feature_locations, language=self.config.lang)
        self.features.extend(features)

        # -- STEP: Multi-processing!
        if getattr(self.config, 'proc_count'):
            return self.run_multiproc()

        # -- STEP: Run all features.
        self.formatters = make_formatters(self.config, stream_openers)
        undefined_steps_initial_size = len(self.undefined_steps)
        run_feature = True
        for feature in features:
            if run_feature:
                try:
                    self.feature = feature
                    for formatter in self.formatters:
                        formatter.uri(feature.filename)

                    failed = feature.run(self)
                    if failed:
                        failed_count += 1
                        if self.config.stop or self.aborted:
                            # -- FAIL-EARLY: After first failure.
                            run_feature = False
                except KeyboardInterrupt:
                    self.aborted = True
                    failed_count += 1
                    run_feature = False

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        if self.aborted:
            print("\nABORTED: By user.")
        for formatter in self.formatters:
            formatter.close()
        self.run_hook('after_all', context)
        for reporter in self.config.reporters:
            reporter.end()
        # if self.aborted:
        #     print "\nABORTED: By user."

        failed = ((failed_count > 0) or self.aborted or
                  (len(self.undefined_steps) > undefined_steps_initial_size))
        return failed


    def run_multiproc(self):

        if not multiprocessing:
            print ("ERROR: Cannot import multiprocessing module."
            " If you're on python2.5, go get the backport")
            return 1
        self.config.format = ['plain']
        self.parallel_element = getattr(self.config, 'parallel_element')

        if not self.parallel_element:
            self.parallel_element = 'scenario'
            print("INFO: Without giving --parallel-element, defaulting to 'scenario'...")
        else:
            if self.parallel_element != 'feature' and \
                self.parallel_element != 'scenario':
                    print(("ERROR: When using --processes, --parallel-element"
                    " option must be set to 'feature' or 'scenario'. You gave '"+
                    str(self.parallel_element)+"', which isn't valid."))
                    return 1

        # -- Prevent context warnings.
        def do_nothing(obj2, obj3):
            pass
        self.context._emit_warning = do_nothing


        self.joblist_index_queue = multiprocessing.Manager().JoinableQueue()
        self.resultsqueue = multiprocessing.Manager().JoinableQueue()

        self.joblist = []
        scenario_count = 0
        feature_count = 0
        for feature in self.features:
            if self.parallel_element == 'feature' or 'serial' in feature.tags:
                self.joblist.append(feature)
                self.joblist_index_queue.put(feature_count + scenario_count)
                feature_count += 1
                continue
            for scenario in feature.scenarios:
                if scenario.type == 'scenario':
                    self.joblist.append(scenario)
                    self.joblist_index_queue.put(
                        feature_count + scenario_count)
                    scenario_count += 1
                else:
                    for subscenario in scenario.scenarios:
                        self.joblist.append(subscenario)
                        self.joblist_index_queue.put(
                            feature_count + scenario_count)
                        scenario_count += 1

        proc_count = int(getattr(self.config, 'proc_count'))
        if feature_count <= proc_count:
            proc_count = feature_count
        print(("INFO: {0} scenario(s) and {1} feature(s) queued for"
                " consideration by {2} workers. Some may be skipped if the"
                " -t option was given..."
               .format(scenario_count, feature_count, proc_count)))
        time.sleep(0.5)
        # processes = {}
        # n = 0
        # procs = []
        pool = multiprocessing.Pool(proc_count)
        test_runs = pool.map(self.worker, range(feature_count))
        pool.close()
        pool.join()
        # for i in range(proc_count):
        #     p = multiprocessing.Process(target=self.worker, args=(i, ))
        #     procs.append(p)
        #     processes[n] = p
        #     n += 1
        #
        # print(procs)
        # print(processes)
        #
        # for p in procs:
        #     p.start()
        #     # while True:
        #     #     if p.is_alive():
        #     #         break

        # while len(processes) > 0:
        #     for n in processes.keys():
        #         p = processes[n]
        #         print(p)
        #         time.sleep(0.5)
        #         if p.exitcode is None and p.is_alive():
        #             pass
        #         elif int(p.exitcode) is None and not p.is_alive():
        #             print('2nd if Processes restarted')
        #             p.terminate()
        #             p = multiprocessing.Process(target=self.worker, args=(i,))
        #             procs.append(p)
        #             processes[n] = p
        #             n += 1
        #             p.start()
        #         elif int(p.exitcode) < 0:
        #             print('3rd if Processes restarted')
        #             p.terminate()
        #             p = multiprocessing.Process(target=self.worker, args=(i,))
        #             procs.append(p)
        #             processes[n] = p
        #             n += 1
        #             p.start()
        #         elif p.exitcode == 0:
        #             p.join()
        #             del processes[n]
        # print('Processes finished')
        self.run_hook('after_all', self.context)
        return self.multiproc_fullreport()

    def worker(self, proc_number):
        while 1:
            try:
                joblist_index = self.joblist_index_queue.get_nowait()
            except Exception as e:
                break
            current_job = self.joblist[joblist_index]
            writebuf = io.StringIO()
            self.setfeature(current_job)
            self.config.outputs = []
            self.config.outputs.append(StreamOpener(stream=writebuf))

            stream_openers = self.config.outputs

            self.formatters = make_formatters(self.config, stream_openers)

            for formatter in self.formatters:
                formatter.uri(current_job.filename)

            start_time = time.strftime("%Y-%m-%d %H:%M:%S")
            current_job.run(self)
            end_time = time.strftime("%Y-%m-%d %H:%M:%S")

            sys.stderr.write(current_job.status[0]+"\n")

            if current_job.type == 'feature':
                for reporter in self.config.reporters:
                    reporter.feature(current_job)

            # self.clean_buffer(writebuf)
            job_report_text = self.generatereport(
                proc_number, current_job,
                start_time, end_time, writebuf)

            if job_report_text:
                results = dict()
                results['steps_passed'] = 0
                results['steps_failed'] = 0
                results['steps_skipped'] = 0
                results['steps_undefined'] = 0
                results['steps_untested'] = 0
                results['jobtype'] = current_job.type
                results['reportinginfo'] = job_report_text
                results['status'] = current_job.status
                if current_job.type != 'feature':
                    results['uniquekey'] = current_job.filename + current_job.feature.name
                else:
                    results['scenarios_passed'] = 0
                    results['scenarios_failed'] = 0
                    results['scenarios_skipped'] = 0
                    self.countscenariostatus(current_job, results)
                self.countstepstatus(current_job, results)
                if current_job.type != 'feature' and getattr(self.config, 'junit'):
                        results['junit_report'] = self.generate_junit_report(current_job, writebuf)
                self.resultsqueue.put(results)

    def setfeature(self, current_job):
        if current_job.type == 'feature':
            self.feature = current_job
        else:
            self.feature = current_job.feature

    def generatereport(self, proc_number, current_job, start_time, end_time, writebuf):
        if not writebuf.tell():
            return ""

        reportheader = start_time + "|WORKER" + str(proc_number) + " START|" + \
        "status:" + current_job.status + "|" + current_job.filename + "\n"

        reportfooter = end_time + "|WORKER" + str(proc_number) + " END|" + \
        "status:" + current_job.status + "|" + current_job.filename + \
        "|Duration:" + str(current_job.duration)

        if self.config.format[0] == 'plain' and len(current_job.tags):
            tags = "@"
            for tag in current_job.tags:
                tags += tag + " "
            reportheader += "\n" + tags + "\n"

        if current_job.status == 'failed':
            self.getskippedsteps(current_job, writebuf)

        try:
            writebuf.seek(0)
        except UnicodeDecodeError as e:
            print(("SEEK: %s" % e))
            return ""

        header_unicode = self.to_unicode(reportheader)
        footer_unicode = self.to_unicode(reportfooter)
        try:
            result = header_unicode + writebuf.read() + "\n" + footer_unicode
        except UnicodeError as err:
            print(("HEADER ERROR: %s" % err))
            result = header_unicode + str(writebuf.read(), errors='replace') + "\n" + footer_unicode
            #result = err.object[0:err.start]+err.object[err.end:len(err.object)-1]

        return result

    def getskippedsteps(self, current_job, writebuf):
        if current_job.type != 'scenario':
            [self.getskippedsteps(s, writebuf) for s in current_job.scenarios]
        else:
            for step in current_job.all_steps:
                if step.status == 'skipped':
                    writebuf.write("Skipped step because of previous error - Scenario:{0}|step:{1}\n"
                                   .format(current_job.name, step.name))

    def countscenariostatus(self, current_job, results):
        if current_job.type != 'scenario':
            [self.countscenariostatus(s, results) for s in current_job.scenarios]
        else:
            results['scenarios_' + current_job.status] += 1

    def countstepstatus(self, current_job, results):
        if current_job.type != 'scenario':
            [self.countstepstatus(s, results) for s in current_job.scenarios]
        else:
            for step in current_job.all_steps:
                results['steps_' + step.status] += 1

    def multiproc_fullreport(self):
        metrics = collections.defaultdict(int)
        combined_features_from_scenarios_results = collections.defaultdict(lambda: '')
        junit_report_objs = []
        while not self.resultsqueue.empty():
            print(("\n" * 3))
            print(("_" * 75))
            jobresult = self.resultsqueue.get()

            try:
                print((self.to_unicode(jobresult['reportinginfo'])))
            except Exception as e:
                logging.info(e)

            if 'junit_report' in jobresult:
                junit_report_objs.append(jobresult['junit_report'])
            if jobresult['jobtype'] != 'feature':
                combined_features_from_scenarios_results[
                    jobresult['uniquekey']] += '|' + jobresult['status']
                metrics['scenarios_' + jobresult['status']] += 1
            else:
                metrics['features_' + jobresult['status']] += 1

            metrics['steps_passed'] += jobresult['steps_passed']
            metrics['steps_failed'] += jobresult['steps_failed']
            metrics['steps_skipped'] += jobresult['steps_skipped']
            metrics['steps_undefined'] += jobresult['steps_undefined']

            if jobresult['jobtype'] == 'feature':
                metrics['scenarios_passed'] += jobresult['scenarios_passed']
                metrics['scenarios_failed'] += jobresult['scenarios_failed']
                metrics['scenarios_skipped'] += jobresult['scenarios_skipped']

        for uniquekey in combined_features_from_scenarios_results:
            if 'failed' in combined_features_from_scenarios_results[uniquekey]:
                metrics['features_failed'] += 1
            elif 'passed' in combined_features_from_scenarios_results[uniquekey]:
                metrics['features_passed'] += 1
            else:
                metrics['features_skipped'] += 1

        print(("\n" * 3))
        print(("_" * 75))
        print((("{0} features passed, {1} features failed, {2} features skipped\n"
               "{3} scenarios passed, {4} scenarios failed, {5} scenarios skipped\n"
               "{6} steps passed, {7} steps failed, {8} steps skipped, {9} steps undefined\n")\
                .format(
                metrics['features_passed'], metrics['features_failed'], metrics['features_skipped'],
                metrics['scenarios_passed'], metrics['scenarios_failed'], metrics['scenarios_skipped'],
                metrics['steps_passed'], metrics['steps_failed'], metrics['steps_skipped'], metrics['steps_undefined'])))
        if getattr(self.config,'junit'):
            self.write_paralleltestresults_to_junitfile(junit_report_objs)
        return metrics['features_failed']

    def generate_junit_report(self, cj, writebuf):
        report_obj = {}
        report_string = ""
        report_obj['filebasename'] = cj.location.basename()[:-8]
        report_obj['feature_name'] = cj.feature.name
        report_obj['status'] = cj.status
        report_obj['duration'] = round(cj.duration,4)
        report_string += '<testcase classname="'
        report_string += report_obj['filebasename']+'.'
        report_string += report_obj['feature_name']+'" '
        report_string += 'name="'+cj.name+'" '
        report_string += 'status="'+cj.status+'" '
        report_string += 'time="'+str(round(cj.duration,4))+'">'
        if cj.status == 'failed':
            report_string += self.get_junit_error(cj, writebuf)
        report_string += "<system-out>\n<![CDATA[\n"
        report_string += "@scenario.begin\n"
        writebuf.seek(0)
        loglines = writebuf.readlines()
        report_string += loglines[1]
        for step in cj.all_steps:
            report_string += " "*4
            report_string += step.keyword + " "
            report_string += step.name + " ... "
            report_string += step.status + " in "
            report_string += str(round(step.duration,4)) + "s\n"
        report_string += "\[email protected]\n"
        report_string += "-"*80
        report_string += "\n"

        report_string += self.get_junit_stdoutstderr(cj,loglines)
        report_string += "</testcase>"
        report_obj['report_string'] = report_string
        report_obj = convert(report_obj)
        return report_obj

    def get_junit_stdoutstderr(self, cj, loglines):
        substring = ""
        if cj.status == 'passed':
            substring += "\nCaptured stdout:\n"
            substring += cj.stdout
            substring += "\n]]>\n</system-out>"
            if cj.stderr:
                substring += "<system-err>\n<![CDATA[\n"
                substring += "Captured stderr:\n"
                substring += cj.stderr
                substring += "\n]]>\n</system-err>"
            return substring

        q = 0
        while q < len(loglines):
            if loglines[q] == "Captured stdout:\n":
                while q < len(loglines) and \
                        loglines[q] != "Captured stderr:\n":
                    substring += loglines[q]
                    q += 1
                break
            q += 1

        substring += "]]>\n</system-out>"

        if q < len(loglines):
            substring += "<system-err>\n<![CDATA[\n"
            while q < len(loglines):
                substring += loglines[q]
                q = q + 1
            substring += "]]>\n</system-err>"

        return substring


    def get_junit_error(self, cj, writebuf):
        failed_step = None
        error_string = ""
        error_string += '<error message="'
        for step in cj.steps:
            if step.status == 'failed':
                failed_step = step
                break

        if not failed_step:
            error_string += "Unknown Error" '" type="AttributeError">\n'
            error_string += "Failing step: Unknown\n"
            error_string += "<![CDATA[\n"
            error_string += "]]>\n"
            error_string += "</error>"
            return error_string

        try:
            error_string += failed_step.exception[0]+'" '
        except Exception:
            error_string += 'No Exception" '

        error_string += 'type="'
        error_string += re.sub(".*?\.(.*?)\'.*","\\1",\
        str(type(failed_step.exception)))+'">\n'
        error_string += "Failing step: "
        error_string += failed_step.name + " ... failed in "
        error_string += str(round(failed_step.duration,4))+"s\n"
        error_string += "Location: " + str(failed_step.location)
        error_string += "<![CDATA[\n"
        error_string += failed_step.error_message
        error_string += "]]>\n</error>"
        return error_string

    def write_paralleltestresults_to_junitfile(self,junit_report_objs):
        feature_reports = {}
        for jro in junit_report_objs:
            #NOTE: There's an edge-case where this key would not be unique
            #Where a feature has the same filename and feature name but
            #different directory.
            uniquekey = jro['filebasename']+"."+jro['feature_name']
            if uniquekey not in feature_reports:
                newfeature = {}
                newfeature['duration'] = float(jro['duration'])
                newfeature['statuses'] = jro['status']
                newfeature['filebasename'] = jro['filebasename']
                newfeature['total_scenarios'] = 1
                newfeature['data'] = jro['report_string']
                feature_reports[uniquekey] = newfeature
            else:
                feature_reports[uniquekey]['duration'] += float(jro['duration'])
                feature_reports[uniquekey]['statuses'] += jro['status']
                feature_reports[uniquekey]['total_scenarios'] += 1
                feature_reports[uniquekey]['data'] += jro['report_string']

        for uniquekey in list(feature_reports.keys()):
            filedata = "<?xml version='1.0' encoding='UTF-8'?>\n"
            filedata += '<testsuite errors="'
            filedata += str(len(re.findall\
            ("failed",feature_reports[uniquekey]['statuses'])))
            filedata += '" failures="0" name="'
            filedata += uniquekey+'" '
            filedata += 'skipped="'
            filedata += str(len(re.findall\
            ("skipped",feature_reports[uniquekey]['statuses'])))
            filedata += '" tests="'
            filedata += str(feature_reports[uniquekey]['total_scenarios'])
            filedata += '" time="'
            filedata += str(round(feature_reports[uniquekey]['duration'],4))
            filedata += '">'
            filedata += "\n\n"
            filedata += feature_reports[uniquekey]['data']
            filedata += "</testsuite>"
            outputdir = "reports"
            custdir = getattr(self.config,'junit_directory')
            if custdir:
                outputdir = custdir
            if not os.path.exists(outputdir):
                os.makedirs(outputdir)
            filename = outputdir+"/"+"TESTS-"
            filename += feature_reports[uniquekey]['filebasename']
            filename += ".xml"
            fd = open(filename,"wb")
            fd.write(filedata.encode('utf8'))
            fd.close()

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = io.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = io.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            self.old_stdout = sys.stdout
            sys.stdout = self.stdout_capture

        if self.config.stderr_capture:
            self.old_stderr = sys.stderr
            sys.stderr = self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            sys.stdout = self.old_stdout

        if self.config.stderr_capture:
            sys.stderr = self.old_stderr

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()

    # def clean_buffer(self, buf):
    #     for i in range(len(buf.buflist)):
    #         buf.buflist[i] = self.to_unicode(buf.buflist[i])


    @staticmethod
    def to_unicode(var):
        string = str(var) if isinstance(var, int) else var
        return str(string) if isinstance(string, str) else string
Example #19
0
class ModelRunner(object):
    """
    Test runner for a behave model (features).
    Provides the core functionality of a test runner and
    the functional API needed by model elements.

    .. attribute:: aborted

          This is set to true when the user aborts a test run
          (:exc:`KeyboardInterrupt` exception). Initially: False.
          Stored as derived attribute in :attr:`Context.aborted`.
    """
    # pylint: disable=too-many-instance-attributes

    def __init__(self, config, features=None, step_registry=None):
        self.config = config
        self.features = features or []
        self.hooks = {}
        self.formatters = []
        self.undefined_steps = []
        self.step_registry = step_registry

        self.context = None
        self.feature = None
        self.hook_failures = 0

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

    # @property
    def _get_aborted(self):
        value = False
        if self.context:
            value = self.context.aborted
        return value

    # @aborted.setter
    def _set_aborted(self, value):
        # pylint: disable=protected-access
        assert self.context
        self.context._set_root_attribute("aborted", bool(value))

    aborted = property(_get_aborted, _set_aborted,
                       doc="Indicates that test run is aborted by the user.")

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            try:
                with context.user_mode():
                    self.hooks[name](context, *args)
            # except KeyboardInterrupt:
            #     self.aborted = True
            #     if name not in ("before_all", "after_all"):
            #         raise
            except Exception as e:  # pylint: disable=broad-except
                # -- HANDLE HOOK ERRORS:
                use_traceback = False
                if self.config.verbose:
                    use_traceback = True
                    ExceptionUtil.set_traceback(e)
                extra = u""
                if "tag" in name:
                    extra = "(tag=%s)" % args[0]

                error_text = ExceptionUtil.describe(e, use_traceback)
                print(u"HOOK-ERROR in %s%s: %s" % (name, extra, error_text))
                self.hook_failures += 1
                if "step" in name:
                    step = args[0]
                    step.hook_failed = True
                elif "tag" in name:
                    # -- FEATURE or SCENARIO => Use Feature as collector.
                    context.feature.hook_failed = True
                elif "scenario" in name:
                    scenario = args[0]
                    scenario.hook_failed = True
                elif "feature" in name:
                    feature = args[0]
                    feature.hook_failed = True
                elif "all" in name:
                    # -- ABORT EXECUTION: For before_all/after_all
                    self.aborted = True

    def setup_capture(self):
        if not self.context:
            self.context = Context(self)

        if self.config.stdout_capture:
            self.stdout_capture = StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stdout:
                self.old_stdout = sys.stdout
                sys.stdout = self.stdout_capture
            assert sys.stdout is self.stdout_capture

        if self.config.stderr_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stderr:
                self.old_stderr = sys.stderr
                sys.stderr = self.stderr_capture
            assert sys.stderr is self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stdout:
                sys.stdout = self.old_stdout
                self.old_stdout = None
            assert sys.stdout is not self.stdout_capture

        if self.config.stderr_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stderr:
                sys.stderr = self.old_stderr
                self.old_stderr = None
            assert sys.stderr is not self.stderr_capture

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()

    def run_model(self, features=None):
        # pylint: disable=too-many-branches
        if not self.context:
            self.context = Context(self)
        if self.step_registry is None:
            self.step_registry = the_step_registry
        if features is None:
            features = self.features

        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        context = self.context
        self.hook_failures = 0
        self.setup_capture()
        self.run_hook("before_all", context)

        run_feature = not self.aborted
        failed_count = 0
        undefined_steps_initial_size = len(self.undefined_steps)
        for feature in features:
            if run_feature:
                try:
                    self.feature = feature
                    for formatter in self.formatters:
                        formatter.uri(feature.filename)

                    failed = feature.run(self)
                    if failed:
                        failed_count += 1
                        if self.config.stop or self.aborted:
                            # -- FAIL-EARLY: After first failure.
                            run_feature = False
                except KeyboardInterrupt:
                    self.aborted = True
                    failed_count += 1
                    run_feature = False

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        if self.aborted:
            print("\nABORTED: By user.")
        for formatter in self.formatters:
            formatter.close()
        self.run_hook("after_all", self.context)
        for reporter in self.config.reporters:
            reporter.end()

        failed = ((failed_count > 0) or self.aborted or (self.hook_failures > 0)
                  or (len(self.undefined_steps) > undefined_steps_initial_size))
        return failed

    def run(self):
        """
        Implements the run method by running the model.
        """
        self.context = Context(self)
        return self.run_model()
Example #20
0
class Runner(object):
    '''
    Test runner for behave.

    .. attribute:: aborted

      This is set to true when the user aborts a test run
      (:exc:`KeyboardInterrupt` exception). Initially: False.
      Stored as derived attribute in :attr:`Context.aborted`.
    '''
    def __init__(self, config):
        self.config = config
        self.hooks = {}
        self.features = []
        self.undefined = []
        # -- XXX-JE-UNUSED:
        # self.passed = []
        # self.failed = []
        # self.skipped = []

        self.path_manager = PathManager()
        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

        self.base_dir = None
        self.context = None
        self.formatters = None

    # @property
    def _get_aborted(self):
        """
        Indicates that a test run was aborted by the user
        (:exc:`KeyboardInterrupt` exception).
        Stored in :attr:`Context.aborted` attribute (as root attribute).

        :return: Current aborted state, initially false.
        :rtype: bool
        """
        value = False
        if self.context:
            value = self.context.aborted
        return value

    # @aborted.setter
    def _set_aborted(self, value):
        """
        Set the aborted value.

        :param value: New aborted value (as bool).
        """
        assert self.context
        self.context._set_root_attribute('aborted', bool(value))

    aborted = property(_get_aborted, _set_aborted,
                       doc="Indicates that test run is aborted by the user.")

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print 'Supplied path:', \
                      ', '.join('"%s"' % path for path in self.config.paths)
            first_path = self.config.paths[0]
            if hasattr(first_path, "filename"):
                # -- BETTER: isinstance(first_path, FileLocation):
                first_path = first_path.filename
            base_dir = first_path
            if base_dir.startswith('@'):
                # -- USE: behave @features.txt
                base_dir = base_dir[1:]
                file_locations = self.feature_locations()
                if file_locations:
                    base_dir = os.path.dirname(file_locations[0].filename)
            base_dir = os.path.abspath(base_dir)

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print 'Primary path is to a file so using its directory'
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print 'Using default path "./features"'
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = path_getrootdir(base_dir)
        new_base_dir = base_dir

        while True:
            if self.config.verbose:
                print 'Trying base directory:', new_base_dir

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment.py')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find "steps" directory. Please '\
                        'specify where to find your features.'
                else:
                    print 'ERROR: Could not find "steps" directory in your '\
                        'specified path "%s"' % base_dir
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir
        self.config.base_dir = base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find any "<name>.feature" files. '\
                        'Please specify where to find your features.'
                else:
                    print 'ERROR: Could not find any "<name>.feature" files '\
                        'in your specified path "%s"' % base_dir
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def before_all_default_hook(self, context):
        """
        Default implementation for :func:`before_all()` hook.
        Setup the logging subsystem based on the configuration data.
        """
        context.config.setup_logging()

    def load_hooks(self, filename='environment.py'):
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

        if 'before_all' not in self.hooks:
            self.hooks['before_all'] = self.before_all_default_hook

    def load_step_definitions(self, extra_step_paths=[]):
        step_globals = {
            'step_matcher': matchers.step_matcher,
        }
        setup_step_decorators(step_globals)

        # -- Allow steps to import other stuff from the steps dir
        # NOTE: Default matcher can be overridden in "environment.py" hook.
        steps_dir = os.path.join(self.base_dir, 'steps')
        paths = [steps_dir] + list(extra_step_paths)
        with PathManager(paths):
            default_matcher = matchers.current_matcher
            for path in paths:
                for name in sorted(os.listdir(path)):
                    if name.endswith('.py'):
                        # -- LOAD STEP DEFINITION:
                        # Reset to default matcher after each step-definition.
                        # A step-definition may change the matcher 0..N times.
                        # ENSURE: Each step definition has clean globals.
                        step_module_globals = step_globals.copy()
                        exec_file(os.path.join(path, name), step_module_globals)
                        matchers.current_matcher = default_matcher

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            # try:
            with context.user_mode():
                self.hooks[name](context, *args)
            # except KeyboardInterrupt:
            #     self.aborted = True
            #     if name not in ("before_all", "after_all"):
            #         raise

    def feature_locations(self):
        return collect_feature_locations(self.config.paths)


    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()

    def run_with_paths(self):
        context = self.context = Context(self)
        self.load_hooks()
        self.load_step_definitions()
        assert not self.aborted
        stream_openers = self.config.outputs
        failed_count = 0

        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        self.setup_capture()
        self.run_hook('before_all', context)

        # -- STEP: Parse all feature files (by using their file location).
        feature_locations = [ filename for filename in self.feature_locations()
                                    if not self.config.exclude(filename) ]
        features = parse_features(feature_locations, language=self.config.lang)
        self.features.extend(features)

        # -- STEP: Run all features.
        self.formatters = formatters.get_formatter(self.config, stream_openers)
        undefined_steps_initial_size = len(self.undefined)
        run_feature = True
        for feature in features:
            if run_feature:
                try:
                    self.feature = feature
                    for formatter in self.formatters:
                        formatter.uri(feature.filename)

                    failed = feature.run(self)
                    if failed:
                        failed_count += 1
                        if self.config.stop or self.aborted:
                            # -- FAIL-EARLY: After first failure.
                            run_feature = False
                except KeyboardInterrupt:
                    self.aborted = True
                    failed_count += 1
                    run_feature = False

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        if self.aborted:
            print "\nABORTED: By user."
        for formatter in self.formatters:
            formatter.close()
        self.run_hook('after_all', context)
        for reporter in self.config.reporters:
            reporter.end()
        # if self.aborted:
        #     print "\nABORTED: By user."

        failed = ((failed_count > 0) or self.aborted or
                  (len(self.undefined) > undefined_steps_initial_size))
        return failed

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stdout:
                self.old_stdout = sys.stdout
                sys.stdout = self.stdout_capture
            assert sys.stdout is self.stdout_capture

        if self.config.stderr_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stderr:
                self.old_stderr = sys.stderr
                sys.stderr = self.stderr_capture
            assert sys.stderr is self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stdout:
                sys.stdout = self.old_stdout
                self.old_stdout = None
            assert sys.stdout is not self.stdout_capture

        if self.config.stderr_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stderr:
                sys.stderr = self.old_stderr
                self.old_stderr = None
            assert sys.stderr is not self.stderr_capture

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()
Example #21
0
class Runner(object):
    def __init__(self, config):
        self.config = config

        self.hooks = {}

        self.features = []
        self.passed = []
        self.failed = []
        self.undefined = []
        self.skipped = []

        self.path_manager = PathManager()

        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.out_stdout = None

    def setup_paths(self):
        if self.config.paths:
            if self.config.verbose:
                print 'Supplied path:', ', '.join(
                    '"%s"' % path for path in self.config.paths)
            base_dir = os.path.abspath(self.config.paths[0])

            # supplied path might be to a feature file
            if os.path.isfile(base_dir):
                if self.config.verbose:
                    print 'Primary path is to a file so using its directory'
                base_dir = os.path.dirname(base_dir)
        else:
            if self.config.verbose:
                print 'Using default path "./features"'
            base_dir = os.path.abspath('features')

        # Get the root. This is not guaranteed to be '/' because Windows.
        root_dir = os.path.split(base_dir)[0]

        new_base_dir = base_dir

        while True:
            if self.config.verbose:
                print 'Trying base directory:', new_base_dir

            if os.path.isdir(os.path.join(new_base_dir, 'steps')):
                break
            if os.path.isfile(os.path.join(new_base_dir, 'environment.py')):
                break
            if new_base_dir == root_dir:
                break

            new_base_dir = os.path.dirname(new_base_dir)

        if new_base_dir == root_dir:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find "steps" directory. Please '\
                        'specify where to find your features.'
                else:
                    print 'ERROR: Could not find "steps" directory in your '\
                        'specified path "%s"' % base_dir
            raise ConfigError('No steps directory in "%s"' % base_dir)

        base_dir = new_base_dir

        for dirpath, dirnames, filenames in os.walk(base_dir):
            if [fn for fn in filenames if fn.endswith('.feature')]:
                break
        else:
            if self.config.verbose:
                if not self.config.paths:
                    print 'ERROR: Could not find any "<name>.feature" files. '\
                        'Please specify where to find your features.'
                else:
                    print 'ERROR: Could not find any "<name>.feature" files '\
                        'in your specified path "%s"' % base_dir
            raise ConfigError('No feature files in "%s"' % base_dir)

        self.base_dir = base_dir
        self.path_manager.add(base_dir)
        if not self.config.paths:
            self.config.paths = [base_dir]

        if base_dir != os.getcwd():
            self.path_manager.add(os.getcwd())

    def load_hooks(self, filename='environment.py'):
        hooks_path = os.path.join(self.base_dir, filename)
        if os.path.exists(hooks_path):
            exec_file(hooks_path, self.hooks)

    def load_step_definitions(self, extra_step_paths=[]):
        steps_dir = os.path.join(self.base_dir, 'steps')

        # allow steps to import other stuff from the steps dir
        sys.path.insert(0, steps_dir)

        step_globals = {
            'step_matcher': matchers.step_matcher,
        }

        for step_type in ('given', 'when', 'then', 'step'):
            decorator = getattr(step_registry, step_type)
            step_globals[step_type] = decorator
            step_globals[step_type.title()] = decorator

        for path in [steps_dir] + list(extra_step_paths):
            for name in os.listdir(path):
                if name.endswith('.py'):
                    exec_file(os.path.join(path, name), step_globals)

        # clean up the path
        sys.path.pop(0)

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            with context.user_mode():
                self.hooks[name](context, *args)

    def feature_files(self):
        files = []
        for path in self.config.paths:
            if os.path.isdir(path):
                for dirpath, dirnames, filenames in os.walk(path):
                    for filename in filenames:
                        if filename.endswith('.feature'):
                            files.append(os.path.join(dirpath, filename))
            elif path.startswith('@'):
                files.extend([filename.strip() for filename in open(path)])
            elif os.path.exists(path):
                files.append(path)
            else:
                raise Exception("Can't find path: " + path)
        return files

    def run(self):
        with self.path_manager:
            self.setup_paths()
            return self.run_with_paths()

    def run_with_paths(self):
        self.load_hooks()
        self.load_step_definitions()

        context = self.context = Context(self)
        stream = self.config.output
        failed = False

        self.run_hook('before_all', context)

        for filename in self.feature_files():
            if self.config.exclude(filename):
                continue

            feature = parser.parse_file(os.path.abspath(filename),
                                        language=self.config.lang)

            self.features.append(feature)
            self.feature = feature

            self.formatter = formatters.get_formatter(self.config, stream)
            self.formatter.uri(filename)

            failed = feature.run(self)

            self.formatter.close()
            stream.write('\n')

            [reporter.feature(feature) for reporter in self.config.reporters]

            if failed and self.config.stop:
                break

        self.run_hook('after_all', context)

        [reporter.end() for reporter in self.config.reporters]

        return failed

    def setup_capture(self):
        if self.config.stdout_capture:
            self.stdout_capture = StringIO.StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO.StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            self.old_stdout = sys.stdout
            sys.stdout = self.stdout_capture

        if self.config.stderr_capture:
            self.old_stderr = sys.stderr
            sys.stderr = self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            sys.stdout = self.old_stdout

        if self.config.stderr_capture:
            sys.stderr = self.old_stderr

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()
Example #22
0
class ModelRunner(object):
    """
    Test runner for a behave model (features).
    Provides the core functionality of a test runner and
    the functional API needed by model elements.

    .. attribute:: aborted

          This is set to true when the user aborts a test run
          (:exc:`KeyboardInterrupt` exception). Initially: False.
          Stored as derived attribute in :attr:`Context.aborted`.
    """

    def __init__(self, config, features=None):
        self.config = config
        self.features = features or []
        self.hooks = {}
        self.formatters = []
        self.undefined_steps = []

        self.context = None
        self.feature = None

        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

    # @property
    def _get_aborted(self):
        value = False
        if self.context:
            value = self.context.aborted
        return value

    # @aborted.setter
    def _set_aborted(self, value):
        assert self.context
        self.context._set_root_attribute('aborted', bool(value))

    aborted = property(_get_aborted, _set_aborted,
                       doc="Indicates that test run is aborted by the user.")

    def run_hook(self, name, context, *args):
        if not self.config.dry_run and (name in self.hooks):
            # try:
            with context.user_mode():
                self.hooks[name](context, *args)
            # except KeyboardInterrupt:
            #     self.aborted = True
            #     if name not in ("before_all", "after_all"):
            #         raise

    def setup_capture(self):
        if not self.context:
            self.context = Context(self)

        if self.config.stdout_capture:
            self.stdout_capture = StringIO()
            self.context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO()
            self.context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            self.context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stdout:
                self.old_stdout = sys.stdout
                sys.stdout = self.stdout_capture
            assert sys.stdout is self.stdout_capture

        if self.config.stderr_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stderr:
                self.old_stderr = sys.stderr
                sys.stderr = self.stderr_capture
            assert sys.stderr is self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stdout:
                sys.stdout = self.old_stdout
                self.old_stdout = None
            assert sys.stdout is not self.stdout_capture

        if self.config.stderr_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stderr:
                sys.stderr = self.old_stderr
                self.old_stderr = None
            assert sys.stderr is not self.stderr_capture

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()

    def run_model(self, features=None):
        if not self.context:
            self.context = Context(self)
        if features is None:
            features = self.features

        # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...)
        context = self.context
        self.setup_capture()
        self.run_hook('before_all', context)

        run_feature = not self.aborted
        failed_count = 0
        undefined_steps_initial_size = len(self.undefined_steps)
        for feature in features:
            if run_feature:
                try:
                    self.feature = feature
                    for formatter in self.formatters:
                        formatter.uri(feature.filename)
                    tries = int(self.config.retry_count)
                    failed = True
                    while failed and tries > 0:
                        failed = feature.run(self)
                        tries -= 1
                    if failed:
                        failed_count += 1
                        if self.config.stop or self.aborted:
                            # -- FAIL-EARLY: After first failure.
                            run_feature = False
                except KeyboardInterrupt:
                    self.aborted = True
                    failed_count += 1
                    run_feature = False

            # -- ALWAYS: Report run/not-run feature to reporters.
            # REQUIRED-FOR: Summary to keep track of untested features.
            for reporter in self.config.reporters:
                reporter.feature(feature)

        # -- AFTER-ALL:
        if self.aborted:
            print("\nABORTED: By user.")
        for formatter in self.formatters:
            formatter.close()
        self.run_hook('after_all', self.context)
        for reporter in self.config.reporters:
            reporter.end()
        # if self.aborted:
        #     print("\nABORTED: By user.")
        failed = ((failed_count > 0) or self.aborted or
                  (len(self.undefined_steps) > undefined_steps_initial_size))
        return failed

    def run(self):
        """
        Implements the run method by running the model.
        """
        self.context = Context(self)
        return self.run_model()
Example #23
0
class CaptureController(object):
    """Simplifies the lifecycle to capture output from various sources."""
    def __init__(self, config):
        self.config = config
        self.stdout_capture = None
        self.stderr_capture = None
        self.log_capture = None
        self.old_stdout = None
        self.old_stderr = None

    @property
    def captured(self):
        """Provides access of the captured output data.

        :return: Object that stores the captured output parts (as Captured).
        """
        stdout = None
        stderr = None
        log_out = None
        if self.config.stdout_capture and self.stdout_capture:
            stdout = _text(self.stdout_capture.getvalue())
        if self.config.stderr_capture and self.stderr_capture:
            stderr = _text(self.stderr_capture.getvalue())
        if self.config.log_capture and self.log_capture:
            log_out = _text(self.log_capture.getvalue())
        return Captured(stdout, stderr, log_out)

    def setup_capture(self, context):
        assert context is not None
        if self.config.stdout_capture:
            self.stdout_capture = StringIO()
            context.stdout_capture = self.stdout_capture

        if self.config.stderr_capture:
            self.stderr_capture = StringIO()
            context.stderr_capture = self.stderr_capture

        if self.config.log_capture:
            self.log_capture = LoggingCapture(self.config)
            self.log_capture.inveigle()
            context.log_capture = self.log_capture

    def start_capture(self):
        if self.config.stdout_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stdout:
                self.old_stdout = sys.stdout
                sys.stdout = self.stdout_capture
            assert sys.stdout is self.stdout_capture

        if self.config.stderr_capture:
            # -- REPLACE ONLY: In non-capturing mode.
            if not self.old_stderr:
                self.old_stderr = sys.stderr
                sys.stderr = self.stderr_capture
            assert sys.stderr is self.stderr_capture

    def stop_capture(self):
        if self.config.stdout_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stdout:
                sys.stdout = self.old_stdout
                self.old_stdout = None
            assert sys.stdout is not self.stdout_capture

        if self.config.stderr_capture:
            # -- RESTORE ONLY: In capturing mode.
            if self.old_stderr:
                sys.stderr = self.old_stderr
                self.old_stderr = None
            assert sys.stderr is not self.stderr_capture

    def teardown_capture(self):
        if self.config.log_capture:
            self.log_capture.abandon()

    def make_capture_report(self):
        """Combine collected output and return as string."""
        return self.captured.make_report()