<commit_before># -*- coding: utf-8 -*-
# pylint: disable=too-many-lines
"""
This module provides the model element class that represent a behave model:

* :class:`Feature`
* :class:`Scenario`
* :class:`ScenarioOutline`
* :class:`Step`
* ...
"""

from __future__ import absolute_import, with_statement
import copy
import difflib
import logging
import itertools
import time
import six
from six.moves import zip       # pylint: disable=redefined-builtin
from behave.model_core import \
        Status, BasicStatement, TagAndStatusStatement, TagStatement, Replayable
from behave.matchers import NoMatch
from behave.textutil import text as _text
if six.PY2:
    # -- USE PYTHON3 BACKPORT: With unicode traceback support.
    import traceback2 as traceback
else:
    import traceback


class Feature(TagAndStatusStatement, Replayable):
    """A `feature`_ parsed from a *feature file*.

    The attributes are:

    .. attribute:: keyword

       This is the keyword as seen in the *feature file*. In English this will
       be "Feature".

    .. attribute:: name

       The name of the feature (the text after "Feature".)

    .. attribute:: description

       The description of the feature as seen in the *feature file*. This is
       stored as a list of text lines.

    .. attribute:: background

       The :class:`~behave.model.Background` for this feature, if any.

    .. attribute:: scenarios

       A list of :class:`~behave.model.Scenario` making up this feature.

    .. attribute:: tags

       A list of @tags (as :class:`~behave.model.Tag` which are basically
       glorified strings) attached to the feature.
       See :ref:`controlling things with tags`.

    .. attribute:: status

       Read-Only. A summary status of the feature's run. If read before the
       feature is fully tested it will return "untested" otherwise it will
       return one of:

       Status.untested
         The feature was has not been completely tested yet.
       Status.skipped
         One or more steps of this feature was passed over during testing.
       Status.passed
         The feature was tested successfully.
       Status.failed
         One or more steps of this feature failed.

       .. versionchanged:: 1.2.6
            Use Status enum class (was: string).

    .. attribute:: hook_failed

        Indicates if a hook failure occured while running this feature.

        .. versionadded:: 1.2.6

    .. attribute:: duration

       The time, in seconds, that it took to test this feature. If read before
       the feature is tested it will return 0.0.

    .. attribute:: filename

       The file name (or "<string>") of the *feature file* where the feature
       was found.

    .. attribute:: line

       The line number of the *feature file* where the feature was found.

    .. attribute:: language

       Indicates which spoken language (English, French, German, ..) was used
       for parsing the feature file and its keywords. The I18N language code
       indicates which language is used. This corresponds to the language tag
       at the beginning of the feature file.

       .. versionadded:: 1.2.6

    .. _`feature`: gherkin.html#features
    """

    type = "feature"

    def __init__(self, filename, line, keyword, name, tags=None,
                 description=None, scenarios=None, background=None,
                 language=None):
        tags = tags or []
        super(Feature, self).__init__(filename, line, keyword, name, tags)
        self.description = description or []
        self.scenarios = []
        self.background = background
        self.language = language
        self.parser = None
        self.hook_failed = False
        if scenarios:
            for scenario in scenarios:
                self.add_scenario(scenario)

    def reset(self):
        """Reset to clean state before a test run."""
        super(Feature, self).reset()
        self.hook_failed = False
        for scenario in self.scenarios:
            scenario.reset()

    def __repr__(self):
        return '<Feature "%s": %d scenario(s)>' % \
            (self.name, len(self.scenarios))

    def __iter__(self):
        return iter(self.scenarios)

    def add_scenario(self, scenario):
        scenario.feature = self
        scenario.background = self.background
        self.scenarios.append(scenario)

    def compute_status(self):
        """Compute the status of this feature based on its:
          * scenarios
          * scenario outlines
          * hook failures

        :return: Computed status (as string-enum).
        """
        if self.hook_failed:
            return Status.failed

        skipped = True
        passed_count = 0
        for scenario in self.scenarios:
            scenario_status = scenario.status
            if scenario_status == Status.failed:
                return Status.failed
            elif scenario_status == Status.untested:
                if passed_count > 0:
                    return Status.failed  # ABORTED: Some passed, now untested.
                return Status.untested
            if scenario_status != Status.skipped:
                skipped = False
            if scenario_status == Status.passed:
                passed_count += 1

        if skipped:
            return Status.skipped
        else:
            return Status.passed


    @property
    def duration(self):
        # -- NEW: Background is executed N times, now part of scenarios.
        feature_duration = 0.0
        for scenario in self.scenarios:
            feature_duration += scenario.duration
        return feature_duration

    def walk_scenarios(self, with_outlines=False):
        """
        Provides a flat list of all scenarios of this feature.
        A ScenarioOutline element adds its scenarios to this list.
        But the ScenarioOutline element itself is only added when specified.

        A flat scenario list is useful when all scenarios of a features
        should be processed.

        :param with_outlines: If ScenarioOutline items should be added, too.
        :return: List of all scenarios of this feature.
        """
        all_scenarios = []
        for scenario in self.scenarios:
            if isinstance(scenario, ScenarioOutline):
                scenario_outline = scenario
                if with_outlines:
                    all_scenarios.append(scenario_outline)
                all_scenarios.extend(scenario_outline.scenarios)
            else:
                all_scenarios.append(scenario)
        return all_scenarios

    def should_run(self, config=None):
        """
        Determines if this Feature (and its scenarios) should run.
        Implements the run decision logic for a feature.
        The decision depends on:

          * if the Feature is marked as skipped
          * if the config.tags (tag expression) enable/disable this feature

        :param config:  Runner configuration to use (optional).
        :return: True, if scenario should run. False, otherwise.
        """
        answer = not self.should_skip
        if answer and config:
            answer = self.should_run_with_tags(config.tags)
        return answer

    def should_run_with_tags(self, tag_expression):
        """Determines if this feature should run when the tag expression is used.
        A feature should run if:
          * it should run according to its tags
          * any of its scenarios should run according to its tags

        :param tag_expression:  Runner/config environment tags to use.
        :return: True, if feature should run. False, otherwise (skip it).
        """
        run_feature = tag_expression.check(self.tags)
        if not run_feature:
            for scenario in self:
                if scenario.should_run_with_tags(tag_expression):
                    run_feature = True
                    break
        return run_feature

    def mark_skipped(self):
        """Marks this feature (and all its scenarios and steps) as skipped.
        Note this function may be called before the feature is executed.
        """
        self.skip(require_not_executed=True)
        assert self.status == Status.skipped or self.hook_failed

    def skip(self, reason=None, require_not_executed=False):
        """Skip executing this feature or the remaining parts of it.
        Note that this feature may be already partly executed
        when this function is called.

        :param reason:  Optional reason why feature should be skipped (as string).
        :param require_not_executed: Optional, requires that feature is not
                        executed yet (default: false).
        """
        if reason:
            logger = logging.getLogger("behave")
            logger.warning(u"SKIP FEATURE %s: %s", self.name, reason)

        self.clear_status()
        self.should_skip = True
        self.skip_reason = reason
        for scenario in self.scenarios:
            scenario.skip(reason, require_not_executed)
        if not self.scenarios:
            # -- SPECIAL CASE: Feature without scenarios
            self.set_status(Status.skipped)
        assert self.status in self.final_status #< skipped, failed or passed.

    def run(self, runner):
        # pylint: disable=too-many-branches
        # MAYBE: self.reset()
        self.clear_status()
        self.hook_failed = False

        runner.context._push()      # pylint: disable=protected-access
        runner.context.feature = self
        runner.context.tags = set(self.tags)

        skip_feature_untested = runner.aborted
        run_feature = self.should_run(runner.config)
        failed_count = 0
        hooks_called = False
        if not runner.config.dry_run and run_feature:
            hooks_called = True
            for tag in self.tags:
                runner.run_hook("before_tag", runner.context, tag)
            runner.run_hook("before_feature", runner.context, self)
            if self.hook_failed:
                failed_count += 1

            # -- RE-EVALUATE SHOULD-RUN STATE:
            # Hook may call feature.mark_skipped() to exclude it.
            skip_feature_untested = self.hook_failed or runner.aborted
            run_feature = self.should_run()

        # run this feature if the tags say so or any one of its scenarios
        if run_feature or runner.config.show_skipped:
            for formatter in runner.formatters:
                formatter.feature(self)
            if self.background:
                for formatter in runner.formatters:
                    formatter.background(self.background)

        if not skip_feature_untested:
            for scenario in self.scenarios:
                # -- OPTIONAL: Select scenario by name (regular expressions).
                if (runner.config.name and
                        not scenario.should_run_with_name_select(runner.config)):
                    scenario.mark_skipped()
                    continue

                failed = scenario.run(runner)
                if failed:
                    failed_count += 1
                    if runner.config.stop or runner.aborted:
                        # -- FAIL-EARLY: Stop after first failure.
                        break

        self.clear_status()  # -- ENFORCE: compute_status() after run.
        if not self.scenarios and not run_feature:
            # -- SPECIAL CASE: Feature without scenarios
            self.set_status("skipped")

        if hooks_called:
            runner.run_hook("after_feature", runner.context, self)
            for tag in self.tags:
                runner.run_hook("after_tag", runner.context, tag)
            if self.hook_failed:
                failed_count += 1
                self.set_status("failed")

        runner.context._pop()       # pylint: disable=protected-access

        if run_feature or runner.config.show_skipped:
            for formatter in runner.formatters:
                formatter.eof()

        failed = (failed_count > 0)
        return failed


class Background(BasicStatement, Replayable):
    """A `background`_ parsed from a *feature file*.

    The attributes are:

    .. attribute:: keyword<commit_msg>Correct: Replace string-based status assignment with Enum-class value (in 2 places).<commit_after># -*- coding: utf-8 -*-
# pylint: disable=too-many-lines
"""
This module provides the model element class that represent a behave model:

* :class:`Feature`
* :class:`Scenario`
* :class:`ScenarioOutline`
* :class:`Step`
* ...
"""

from __future__ import absolute_import, with_statement
import copy
import difflib
import logging
import itertools
import time
import six
from six.moves import zip       # pylint: disable=redefined-builtin
from behave.model_core import \
        Status, BasicStatement, TagAndStatusStatement, TagStatement, Replayable
from behave.matchers import NoMatch
from behave.textutil import text as _text
if six.PY2:
    # -- USE PYTHON3 BACKPORT: With unicode traceback support.
    import traceback2 as traceback
else:
    import traceback


class Feature(TagAndStatusStatement, Replayable):
    """A `feature`_ parsed from a *feature file*.

    The attributes are:

    .. attribute:: keyword

       This is the keyword as seen in the *feature file*. In English this will
       be "Feature".

    .. attribute:: name

       The name of the feature (the text after "Feature".)

    .. attribute:: description

       The description of the feature as seen in the *feature file*. This is
       stored as a list of text lines.

    .. attribute:: background

       The :class:`~behave.model.Background` for this feature, if any.

    .. attribute:: scenarios

       A list of :class:`~behave.model.Scenario` making up this feature.

    .. attribute:: tags

       A list of @tags (as :class:`~behave.model.Tag` which are basically
       glorified strings) attached to the feature.
       See :ref:`controlling things with tags`.

    .. attribute:: status

       Read-Only. A summary status of the feature's run. If read before the
       feature is fully tested it will return "untested" otherwise it will
       return one of:

       Status.untested
         The feature was has not been completely tested yet.
       Status.skipped
         One or more steps of this feature was passed over during testing.
       Status.passed
         The feature was tested successfully.
       Status.failed
         One or more steps of this feature failed.

       .. versionchanged:: 1.2.6
            Use Status enum class (was: string).

    .. attribute:: hook_failed

        Indicates if a hook failure occured while running this feature.

        .. versionadded:: 1.2.6

    .. attribute:: duration

       The time, in seconds, that it took to test this feature. If read before
       the feature is tested it will return 0.0.

    .. attribute:: filename

       The file name (or "<string>") of the *feature file* where the feature
       was found.

    .. attribute:: line

       The line number of the *feature file* where the feature was found.

    .. attribute:: language

       Indicates which spoken language (English, French, German, ..) was used
       for parsing the feature file and its keywords. The I18N language code
       indicates which language is used. This corresponds to the language tag
       at the beginning of the feature file.

       .. versionadded:: 1.2.6

    .. _`feature`: gherkin.html#features
    """

    type = "feature"

    def __init__(self, filename, line, keyword, name, tags=None,
                 description=None, scenarios=None, background=None,
                 language=None):
        tags = tags or []
        super(Feature, self).__init__(filename, line, keyword, name, tags)
        self.description = description or []
        self.scenarios = []
        self.background = background
        self.language = language
        self.parser = None
        self.hook_failed = False
        if scenarios:
            for scenario in scenarios:
                self.add_scenario(scenario)

    def reset(self):
        """Reset to clean state before a test run."""
        super(Feature, self).reset()
        self.hook_failed = False
        for scenario in self.scenarios:
            scenario.reset()

    def __repr__(self):
        return '<Feature "%s": %d scenario(s)>' % \
            (self.name, len(self.scenarios))

    def __iter__(self):
        return iter(self.scenarios)

    def add_scenario(self, scenario):
        scenario.feature = self
        scenario.background = self.background
        self.scenarios.append(scenario)

    def compute_status(self):
        """Compute the status of this feature based on its:
          * scenarios
          * scenario outlines
          * hook failures

        :return: Computed status (as string-enum).
        """
        if self.hook_failed:
            return Status.failed

        skipped = True
        passed_count = 0
        for scenario in self.scenarios:
            scenario_status = scenario.status
            if scenario_status == Status.failed:
                return Status.failed
            elif scenario_status == Status.untested:
                if passed_count > 0:
                    return Status.failed  # ABORTED: Some passed, now untested.
                return Status.untested
            if scenario_status != Status.skipped:
                skipped = False
            if scenario_status == Status.passed:
                passed_count += 1

        if skipped:
            return Status.skipped
        else:
            return Status.passed


    @property
    def duration(self):
        # -- NEW: Background is executed N times, now part of scenarios.
        feature_duration = 0.0
        for scenario in self.scenarios:
            feature_duration += scenario.duration
        return feature_duration

    def walk_scenarios(self, with_outlines=False):
        """
        Provides a flat list of all scenarios of this feature.
        A ScenarioOutline element adds its scenarios to this list.
        But the ScenarioOutline element itself is only added when specified.

        A flat scenario list is useful when all scenarios of a features
        should be processed.

        :param with_outlines: If ScenarioOutline items should be added, too.
        :return: List of all scenarios of this feature.
        """
        all_scenarios = []
        for scenario in self.scenarios:
            if isinstance(scenario, ScenarioOutline):
                scenario_outline = scenario
                if with_outlines:
                    all_scenarios.append(scenario_outline)
                all_scenarios.extend(scenario_outline.scenarios)
            else:
                all_scenarios.append(scenario)
        return all_scenarios

    def should_run(self, config=None):
        """
        Determines if this Feature (and its scenarios) should run.
        Implements the run decision logic for a feature.
        The decision depends on:

          * if the Feature is marked as skipped
          * if the config.tags (tag expression) enable/disable this feature

        :param config:  Runner configuration to use (optional).
        :return: True, if scenario should run. False, otherwise.
        """
        answer = not self.should_skip
        if answer and config:
            answer = self.should_run_with_tags(config.tags)
        return answer

    def should_run_with_tags(self, tag_expression):
        """Determines if this feature should run when the tag expression is used.
        A feature should run if:
          * it should run according to its tags
          * any of its scenarios should run according to its tags

        :param tag_expression:  Runner/config environment tags to use.
        :return: True, if feature should run. False, otherwise (skip it).
        """
        run_feature = tag_expression.check(self.tags)
        if not run_feature:
            for scenario in self:
                if scenario.should_run_with_tags(tag_expression):
                    run_feature = True
                    break
        return run_feature

    def mark_skipped(self):
        """Marks this feature (and all its scenarios and steps) as skipped.
        Note this function may be called before the feature is executed.
        """
        self.skip(require_not_executed=True)
        assert self.status == Status.skipped or self.hook_failed

    def skip(self, reason=None, require_not_executed=False):
        """Skip executing this feature or the remaining parts of it.
        Note that this feature may be already partly executed
        when this function is called.

        :param reason:  Optional reason why feature should be skipped (as string).
        :param require_not_executed: Optional, requires that feature is not
                        executed yet (default: false).
        """
        if reason:
            logger = logging.getLogger("behave")
            logger.warning(u"SKIP FEATURE %s: %s", self.name, reason)

        self.clear_status()
        self.should_skip = True
        self.skip_reason = reason
        for scenario in self.scenarios:
            scenario.skip(reason, require_not_executed)
        if not self.scenarios:
            # -- SPECIAL CASE: Feature without scenarios
            self.set_status(Status.skipped)
        assert self.status in self.final_status #< skipped, failed or passed.

    def run(self, runner):
        # pylint: disable=too-many-branches
        # MAYBE: self.reset()
        self.clear_status()
        self.hook_failed = False

        runner.context._push()      # pylint: disable=protected-access
        runner.context.feature = self
        runner.context.tags = set(self.tags)

        skip_feature_untested = runner.aborted
        run_feature = self.should_run(runner.config)
        failed_count = 0
        hooks_called = False
        if not runner.config.dry_run and run_feature:
            hooks_called = True
            for tag in self.tags:
                runner.run_hook("before_tag", runner.context, tag)
            runner.run_hook("before_feature", runner.context, self)
            if self.hook_failed:
                failed_count += 1

            # -- RE-EVALUATE SHOULD-RUN STATE:
            # Hook may call feature.mark_skipped() to exclude it.
            skip_feature_untested = self.hook_failed or runner.aborted
            run_feature = self.should_run()

        # run this feature if the tags say so or any one of its scenarios
        if run_feature or runner.config.show_skipped:
            for formatter in runner.formatters:
                formatter.feature(self)
            if self.background:
                for formatter in runner.formatters:
                    formatter.background(self.background)

        if not skip_feature_untested:
            for scenario in self.scenarios:
                # -- OPTIONAL: Select scenario by name (regular expressions).
                if (runner.config.name and
                        not scenario.should_run_with_name_select(runner.config)):
                    scenario.mark_skipped()
                    continue

                failed = scenario.run(runner)
                if failed:
                    failed_count += 1
                    if runner.config.stop or runner.aborted:
                        # -- FAIL-EARLY: Stop after first failure.
                        break

        self.clear_status()  # -- ENFORCE: compute_status() after run.
        if not self.scenarios and not run_feature:
            # -- SPECIAL CASE: Feature without scenarios
            self.set_status(Status.skipped)

        if hooks_called:
            runner.run_hook("after_feature", runner.context, self)
            for tag in self.tags:
                runner.run_hook("after_tag", runner.context, tag)
            if self.hook_failed:
                failed_count += 1
                self.set_status(Status.failed)

        runner.context._pop()       # pylint: disable=protected-access

        if run_feature or runner.config.show_skipped:
            for formatter in runner.formatters:
                formatter.eof()

        failed = (failed_count > 0)
        return failed


class Background(BasicStatement, Replayable):
    """A `background`_ parsed from a *feature file*.

    The attributes are:

    .. attribute:: keyword
{'fraction_non_alphanumeric': 0.07406495052756908, 'fraction_numerical': 0.002258077759986862, 'mean_word_length': 2.034130543099153, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4637002', 'n_tokens_mistral': 6367, 'n_tokens_neox': 6071, 'n_words': 2163}