예제 #1
0
파일: plugin.py 프로젝트: kiawin/aloe
    def __init__(self):
        """Initialise the helper plugin."""

        # Nose has attrib plugin which works as expected but isn't passed the
        # tests generated from features. Create a local copy which will be used
        # to veto the tests.

        super().__init__()
        self.attrib_plugin = AttributeSelector()
예제 #2
0
    def test_basic_attr(self):
        def f():
            pass
        f.a = 1

        def g():
            pass

        plug = AttributeSelector()
        plug.attribs = [[('a', True)]]
        assert plug.wantFunction(f) is not False
        assert not plug.wantFunction(g)
예제 #3
0
    def test_class_attr(self):
        class TestP:
            foo = True
            def h():
                pass

        def i():
            pass

        plug = AttributeSelector()
        plug.attribs = [[('foo', True)]]
        assert plug.wantMethod(unbound_method(TestP, TestP.h)) is not False
        assert plug.wantFunction(i) is False
    def test_basic_attr(self):
        def f():
            pass

        f.a = 1

        def g():
            pass

        plug = AttributeSelector()
        plug.attribs = [[('a', True)]]
        assert plug.wantFunction(f) is not False
        assert not plug.wantFunction(g)
    def test_class_attr(self):
        class TestP:
            foo = True

            def h():
                pass

        def i():
            pass

        plug = AttributeSelector()
        plug.attribs = [[('foo', True)]]
        assert plug.wantMethod(unbound_method(TestP, TestP.h)) is not False
        assert plug.wantFunction(i) is False
예제 #6
0
    def configure(self, options, noseconfig):
        super(ITE, self).configure(options, noseconfig)

        if not self.enabled:
            return

        self.curdir = os.getcwd()
        noseconfig.testMatch = re.compile('$^')
        self.attr_plugin = AttributeSelector()
        self.attr_plugin.configure(noseconfig.options, noseconfig)

        if os.getuid() == 0:
            os.setegid(self.options.gid)
            os.seteuid(self.options.uid)
예제 #7
0
def run_test():
    nose.main(addplugins=[
        AutotestClientTestRunner(),
        AttributeSelector(),
        Xunit(),
        Coverage()
    ])
    def test_eval_attr(self):
        if not compat_24:
            warn("No support for eval attributes in python versions older"
                 " than 2.4")
            return

        def f():
            pass

        f.monkey = 2

        def g():
            pass

        g.monkey = 6

        def h():
            pass

        h.monkey = 5

        cnf = Config()
        opt = Bucket()
        opt.eval_attr = "monkey > 5"
        plug = AttributeSelector()
        plug.configure(opt, cnf)

        assert not plug.wantFunction(f)
        assert plug.wantFunction(g) is not False
        assert not plug.wantFunction(h)
예제 #9
0
    def __init__(self):
        """Initialise the helper plugin."""

        # Nose has attrib plugin which works as expected but isn't passed the
        # tests generated from features. Create a local copy which will be used
        # to veto the tests.

        super().__init__()
        self.attrib_plugin = AttributeSelector()
예제 #10
0
    def test_eval_attr(self):
        if not compat_24:
            warn("No support for eval attributes in python versions older"
                 " than 2.4")
            return
        def f():
            pass
        f.monkey = 2

        def g():
            pass
        g.monkey = 6

        def h():
            pass
        h.monkey = 5

        cnf = Config()
        opt = Bucket()
        opt.eval_attr = "monkey > 5"
        plug = AttributeSelector()
        plug.configure(opt, cnf)

        assert not plug.wantFunction(f)
        assert plug.wantFunction(g) is not False
        assert not plug.wantFunction(h)
class AttributePluginTester(PluginTester, unittest.TestCase):
    plugins = [AttributeSelector()]
    suitepath = os.path.join(support, 'att')
    # Some cases need -a to activate and others need -A, so
    # let's treat -v as the activate argument and let individual
    # cases specify their -a arguments as part of args
    activate = '-v'

    def runTest(self):
        print('*' * 70)
        print(str(self.output))
        print('*' * 70)
        self.verify()

    def verify(self):
        raise NotImplementedError()
예제 #12
0
class TestAttributeArrayAnd(PluginTester, unittest.TestCase):
    activate = "-a d=1,d=2"
    args = ['-v']
    plugins = [AttributeSelector()]
    suitepath = os.path.join(support, 'att')

    def runTest(self):
        print '*' * 70
        print str(self.output)
        print '*' * 70

        assert 'test_attr.test_one ... ok' in self.output
        assert 'test_attr.test_two ... ok' not in self.output
        assert 'test_attr.test_three ... ok' not in self.output
        assert 'TestClass.test_class_one ... ok' not in self.output
        assert 'TestClass.test_class_two ... ok' not in self.output
        assert 'TestClass.test_class_three ... ok' not in self.output
        assert 'test_case_two' not in self.output
        assert 'test_case_one' not in self.output
        assert 'test_case_three' not in self.output
예제 #13
0
    def test_class_and_method_str_attr(self):
        class TestP(object):
            foo = 'a'

            def h(self):
                pass
            h.foo = 'b'

        def i():
            pass
        i.foo = 'a'

        plug = AttributeSelector()
        plug.attribs = [[('foo', 'a'), ('foo', 'b')]]
        assert plug.wantMethod(unbound_method(TestP, TestP.h)) is not False
        assert plug.wantFunction(i) is False

        plug.attribs = [[('foo', 'b')]]
        assert plug.wantMethod(unbound_method(TestP, TestP.h)) is not False
        assert plug.wantFunction(i) is False
예제 #14
0
    def test_attr_a_b(self):
        def f1():
            pass
        f1.tags = ['a', 'b']

        def f2():
            pass
        f2.tags = ['a', 'c']

        def f3():
            pass
        f3.tags = ['b', 'c']

        def f4():
            pass
        f4.tags = ['c', 'd']

        cnf = Config()
        parser = OptionParser()
        plug = AttributeSelector()

        plug.add_options(parser)

        # OR
        opt, args = parser.parse_args(['test', '-a', 'tags=a',
                                       '-a', 'tags=b'])
        print opt
        plug.configure(opt, cnf)

        assert plug.wantFunction(f1) is None
        assert plug.wantFunction(f2) is None
        assert plug.wantFunction(f3) is None
        assert not plug.wantFunction(f4)

        # AND
        opt, args = parser.parse_args(['test', '-a', 'tags=a,tags=b'])
        print opt
        plug.configure(opt, cnf)

        assert plug.wantFunction(f1) is None
        assert not plug.wantFunction(f2)
        assert not plug.wantFunction(f3)
        assert not plug.wantFunction(f4)
예제 #15
0
 def selector(self):
     if self._selector is None:
         self._selector = AttributeSelector()
     return self._selector
예제 #16
0
class ITE(ExtendedPlugin):
    """
    Log when the session has started (after all plugins are configured).
    """
    score = 1000

    def options(self, parser, env):
        """Register command line options."""
        parser.add_option('--with-ite',
                          action='store_true',
                          dest='with_ite',
                          default=False,
                          help="Enable ITE mockup. (default: no)")

    def configure(self, options, noseconfig):
        super(ITE, self).configure(options, noseconfig)

        if not self.enabled:
            return

        self.curdir = os.getcwd()
        noseconfig.testMatch = re.compile('$^')
        self.attr_plugin = AttributeSelector()
        self.attr_plugin.configure(noseconfig.options, noseconfig)

        if os.getuid() == 0:
            os.setegid(self.options.gid)
            os.seteuid(self.options.uid)

    def begin(self):
        STDOUT.info('ITE emulation plugin starting...')
        for path in self.options.paths:

            if path.startswith('/'):
                p = path
            else:
                p = os.path.join(os.path.dirname(__file__), path)

            # Pull any existing paths to make sure our mocks come first.
            if p in sys.path:
                sys.path.remove(p)
            sys.path.append(p)

    def mdparse(self, source):
        metadata_regex = re.compile(
            r"""
                                    ^\#\s?@         # Metadata marker
                                    (\w+):       # Name of metadata key
                                    \s*?(.*?)\n  # The value of the metadata
                                    """, re.VERBOSE | re.MULTILINE)
        tokens = [(key.strip(), val.strip())
                  for key, val in metadata_regex.findall(source)]
        return tokens

    def beforeTest(self, test):
        if hasattr(test.test.descriptor, ITE_METADATA):
            os.chdir(os.path.dirname(test.test.descriptor.__file__))

    def loadTestsFromModule(self, module, path=None):
        if not os.path.isfile(path):
            return
        with open(path) as f:
            source = f.read()
            tokens = dict(self.mdparse(source))

        if tokens:

            def test_eval():
                g = globals()
                g['__name__'] = '__main__'
                g['__file__'] = path
                g['__path__'] = [path]
                need_root = to_bool(tokens.get('Sudo', False))
                try:
                    #exec(source, g)
                    if need_root and os.getuid() == 0:
                        os.setegid(0)
                        os.seteuid(0)
                    else:
                        STDOUT.warning('This test requires root privileges.')
                    exec(compile(source, path, 'exec'), g)
                except SystemExit as e:
                    from tcutils.base import PASS
                    assert e.code in (None, PASS), e.code
                finally:
                    if need_root and os.getuid() == 0:
                        os.setegid(self.options.gid)
                        os.seteuid(self.options.uid)

            module.__module__ = module.__name__
            module.compat_func_name = 'main'
            f = FunctionTestCase(test_eval, descriptor=module)
            dummy = Dummy()
            for k, v in tokens.items():
                setattr(dummy, k, v)
            setattr(module, ITE_METADATA, dummy)
            if not self.attr_plugin.enabled:
                return [f]
            if self.attr_plugin.validateAttrib(dummy) is None:
                return [f]
예제 #17
0
    def test_add_options(self):
        plug = AttributeSelector()
        parser = MockOptParser()
        plug.add_options(parser)

        expect = [(('-a', '--attr'),
                   {'dest': 'attr', 'action': 'append', 'default': None,
                    'metavar': 'ATTR',
                    'help': 'Run only tests that have attributes '
                    'specified by ATTR [NOSE_ATTR]'})]

        if compat_24:
            expect.append(
                (('-A', '--eval-attr'),
                 {'dest': 'eval_attr', 'action': 'append',
                  'default': None, 'metavar': 'EXPR',
                  'help': 'Run only tests for whose attributes the '
                  'Python expression EXPR evaluates to True '
                  '[NOSE_EVAL_ATTR]'}))
        self.assertEqual(parser.opts, expect)

        opt = Bucket()
        opt.attr = ['!slow']
        plug.configure(opt, Config())
        assert plug.enabled
        self.assertEqual(plug.attribs, [[('slow', False)]])

        opt.attr = ['fast,quick', 'weird=66']
        plug.configure(opt, Config())
        self.assertEqual(plug.attribs, [[('fast', True),
                                         ('quick', True)],
                                        [('weird', '66')]])

        # don't die on trailing ,
        opt.attr = [ 'something,' ]
        plug.configure(opt, Config())
        self.assertEqual(plug.attribs, [[('something', True)]] )

        if compat_24:
            opt.attr = None
            opt.eval_attr = [ 'weird >= 66' ]
            plug.configure(opt, Config())
            self.assertEqual(plug.attribs[0][0][0], 'weird >= 66')
            assert callable(plug.attribs[0][0][1])
예제 #18
0
    def test_attr_a_b(self):
        def f1():
            pass

        f1.tags = ["a", "b"]

        def f2():
            pass

        f2.tags = ["a", "c"]

        def f3():
            pass

        f3.tags = ["b", "c"]

        def f4():
            pass

        f4.tags = ["c", "d"]

        cnf = Config()
        parser = OptionParser()
        plug = AttributeSelector()

        plug.add_options(parser)

        # OR
        opt, args = parser.parse_args(["test", "-a", "tags=a", "-a", "tags=b"])
        print opt
        plug.configure(opt, cnf)

        assert plug.wantFunction(f1) is None
        assert plug.wantFunction(f2) is None
        assert plug.wantFunction(f3) is None
        assert not plug.wantFunction(f4)

        # AND
        opt, args = parser.parse_args(["test", "-a", "tags=a,tags=b"])
        print opt
        plug.configure(opt, cnf)

        assert plug.wantFunction(f1) is None
        assert not plug.wantFunction(f2)
        assert not plug.wantFunction(f3)
        assert not plug.wantFunction(f4)
예제 #19
0
    def test_add_options(self):
        plug = AttributeSelector()
        parser = MockOptParser()
        plug.add_options(parser)

        expect = [
            (
                ("-a", "--attr"),
                {
                    "dest": "attr",
                    "action": "append",
                    "default": None,
                    "metavar": "ATTR",
                    "help": "Run only tests that have attributes " "specified by ATTR [NOSE_ATTR]",
                },
            )
        ]

        if compat_24:
            expect.append(
                (
                    ("-A", "--eval-attr"),
                    {
                        "dest": "eval_attr",
                        "action": "append",
                        "default": None,
                        "metavar": "EXPR",
                        "help": "Run only tests for whose attributes the "
                        "Python expression EXPR evaluates to True "
                        "[NOSE_EVAL_ATTR]",
                    },
                )
            )
        self.assertEqual(parser.opts, expect)

        opt = Bucket()
        opt.attr = ["!slow"]
        plug.configure(opt, Config())
        assert plug.enabled
        self.assertEqual(plug.attribs, [[("slow", False)]])

        opt.attr = ["fast,quick", "weird=66"]
        plug.configure(opt, Config())
        self.assertEqual(plug.attribs, [[("fast", True), ("quick", True)], [("weird", "66")]])

        # don't die on trailing ,
        opt.attr = ["something,"]
        plug.configure(opt, Config())
        self.assertEqual(plug.attribs, [[("something", True)]])

        if compat_24:
            opt.attr = None
            opt.eval_attr = ["weird >= 66"]
            plug.configure(opt, Config())
            self.assertEqual(plug.attribs[0][0][0], "weird >= 66")
            assert callable(plug.attribs[0][0][1])
    def test_attr_a_b(self):
        def f1():
            pass

        f1.tags = ['a', 'b']

        def f2():
            pass

        f2.tags = ['a', 'c']

        def f3():
            pass

        f3.tags = ['b', 'c']

        def f4():
            pass

        f4.tags = ['c', 'd']

        cnf = Config()
        parser = OptionParser()
        plug = AttributeSelector()

        plug.add_options(parser)

        # OR
        opt, args = parser.parse_args(['test', '-a', 'tags=a', '-a', 'tags=b'])
        print(opt)
        plug.configure(opt, cnf)

        assert plug.wantFunction(f1) is None
        assert plug.wantFunction(f2) is None
        assert plug.wantFunction(f3) is None
        assert not plug.wantFunction(f4)

        # AND
        opt, args = parser.parse_args(['test', '-a', 'tags=a,tags=b'])
        print(opt)
        plug.configure(opt, cnf)

        assert plug.wantFunction(f1) is None
        assert not plug.wantFunction(f2)
        assert not plug.wantFunction(f3)
        assert not plug.wantFunction(f4)
예제 #21
0
파일: plugin.py 프로젝트: kiawin/aloe
class GherkinPlugin(Plugin):
    """
    Collect Gherkin tests.
    """

    # Nose interface has non-Pythonic names
    # pylint:disable=invalid-name,unused-argument

    name = 'gherkin'
    enableOpt = 'gherkin'

    TEST_CLASS = TestCase

    def __init__(self):
        """Initialise the helper plugin."""

        # Nose has attrib plugin which works as expected but isn't passed the
        # tests generated from features. Create a local copy which will be used
        # to veto the tests.

        super().__init__()
        self.attrib_plugin = AttributeSelector()

    def begin(self):
        """
        Start the test suite, loading all the step definitions.
        """

        if self.conf.options.version or self.conf.options.showPlugins:
            # Don't try to load anything if only called for information
            return

        self.feature_dirs = [
            dir_ for dir_ in FeatureLoader.find_feature_directories('.')
        ]
        for feature_dir in self.feature_dirs:
            FeatureLoader.find_and_load_step_definitions(feature_dir)

    def options(self, parser, env=None):
        """
        Register the plugin options.
        """

        if env is None:
            env = os.environ

        super().options(parser, env)

        test_class_name = \
            '{c.__module__}.{c.__name__}'.format(c=self.TEST_CLASS)
        parser.add_option(
            '--test-class',
            action='store',
            dest='test_class_name',
            default=env.get('NOSE_GHERKIN_CLASS', test_class_name),
            metavar='TEST_CLASS',
            help='Base class to use for the generated tests',
        )
        parser.add_option(
            '--no-ignore-python',
            action='store_false',
            dest='ignore_python',
            default=True,
            help='Run Python and Gherkin tests together',
        )
        parser.add_option(
            '-n',
            '--scenario-indices',
            action='store',
            dest='scenario_indices',
            default='',
            help='Only run scenarios with these indices (comma-separated)',
        )
        parser.add_option(
            '--color',
            action='store_true',
            dest='force_color',
            default=False,
            help='Force colored output',
        )

        # Options for attribute plugin will be registered by its main instance

    def configure(self, options, conf):
        """
        Configure the plugin.
        """

        super().configure(options, conf)

        module_name, class_name = options.test_class_name.rsplit('.', 1)
        module = import_module(module_name)
        self.test_class = getattr(module, class_name)
        self.ignore_python = options.ignore_python

        conf.force_color = options.force_color

        if options.scenario_indices:
            self.scenario_indices = tuple(
                int(index) for index in options.scenario_indices.split(','))
        else:
            self.scenario_indices = None

        self.attrib_plugin.configure(options, conf)

    def wantDirectory(self, directory):
        """
        Collect features from 'features' directories.
        """

        directory = os.path.abspath(directory)
        for feature_dir in self.feature_dirs:
            feature_dir = os.path.abspath(feature_dir)
            if feature_dir.startswith(directory) or \
                    directory.startswith(feature_dir):
                return True

    def wantFile(self, file_):
        """
        Load features from feature files.
        """

        if os.path.basename(file_).endswith('.feature'):
            return True

    def wantPython(self, _):
        """
        Ignore Python tests if required.
        """

        if self.ignore_python:
            return False

    wantClass = wantFunction = wantMethod = wantModule = wantPython

    def scenario_matches(self, feature, scenario_index, scenario_name):
        """
        Whether a given scenario is selected by the command-line options.

        @feature The feature class
        @scenario_index The scenario index
        @scenario_name The scenario name
        """

        if self.scenario_indices:
            if scenario_index not in self.scenario_indices:
                return False

        if self.attrib_plugin.enabled:
            scenario = getattr(feature, scenario_name)
            # False means "no", None means "don't care" for Nose plugins
            if self.attrib_plugin.validateAttrib(scenario, feature) is False:
                return False

        return True

    def loadTestsFromFile(self, file_):
        """
        Load a feature from the feature file.
        """

        test = self.test_class.from_file(file_)

        # About to run a feature - ensure "before all" callbacks have run
        self.ensure_before_callbacks()

        has_tests = False

        # Filter the scenarios, if asked
        for idx, scenario_name in test.scenarios():
            if self.scenario_matches(test, idx, scenario_name):
                has_tests = True
                yield test(scenario_name)

        # Feature OK but no tests filtered
        if not has_tests:
            yield False

    def ensure_before_callbacks(self):
        """
        Before the first test, run the "before all" callbacks.
        """

        if not hasattr(self, 'after_hook'):
            before_all, after_all = CALLBACK_REGISTRY.before_after('all')
            before_all()
            self.after_hook = after_all

    def finalize(self, result):
        """
        After the last test, run the "after all" callbacks.
        """

        # TODO: Is there a better method to do something _after_ all the tests
        # have been run?

        if hasattr(self, 'after_hook'):
            self.after_hook()
            delattr(self, 'after_hook')

    def prepareTestRunner(self, runner):
        """
        Monkeypatch in our TestResult class.

        In unittest we could just set runner.resultclass, but Nose
        doesn't use this.
        """
        def _makeResult():
            """Build our result"""
            return AloeTestResult(runner.stream, runner.descriptions,
                                  runner.verbosity, runner.config)

        runner._makeResult = _makeResult  # pylint:disable=protected-access

        return runner
    def test_add_options(self):
        plug = AttributeSelector()
        parser = MockOptParser()
        plug.add_options(parser)

        expect = [(('-a', '--attr'), {
            'dest':
            'attr',
            'action':
            'append',
            'default':
            None,
            'metavar':
            'ATTR',
            'help':
            'Run only tests that have attributes '
            'specified by ATTR [NOSE_ATTR]'
        })]

        if compat_24:
            expect.append((('-A', '--eval-attr'), {
                'dest':
                'eval_attr',
                'action':
                'append',
                'default':
                None,
                'metavar':
                'EXPR',
                'help':
                'Run only tests for whose attributes the '
                'Python expression EXPR evaluates to True '
                '[NOSE_EVAL_ATTR]'
            }))
        self.assertEqual(parser.opts, expect)

        opt = Bucket()
        opt.attr = ['!slow']
        plug.configure(opt, Config())
        assert plug.enabled
        self.assertEqual(plug.attribs, [[('slow', False)]])

        opt.attr = ['fast,quick', 'weird=66']
        plug.configure(opt, Config())
        self.assertEqual(plug.attribs, [[('fast', True),
                                         ('quick', True)], [('weird', '66')]])

        # don't die on trailing ,
        opt.attr = ['something,']
        plug.configure(opt, Config())
        self.assertEqual(plug.attribs, [[('something', True)]])

        if compat_24:
            opt.attr = None
            opt.eval_attr = ['weird >= 66']
            plug.configure(opt, Config())
            self.assertEqual(plug.attribs[0][0][0], 'weird >= 66')
            assert isinstance(plug.attribs[0][0][1], collections.Callable)
예제 #23
0
class GherkinPlugin(Plugin):
    """
    Collect Gherkin tests.
    """

    # Nose interface has non-Pythonic names
    # pylint:disable=invalid-name,unused-argument

    name = 'gherkin'
    enableOpt = 'gherkin'

    TEST_CLASS = TestCase

    def __init__(self):
        """Initialise the helper plugin."""

        # Nose has attrib plugin which works as expected but isn't passed the
        # tests generated from features. Create a local copy which will be used
        # to veto the tests.

        super().__init__()
        self.attrib_plugin = AttributeSelector()

    def begin(self):
        """
        Start the test suite, loading all the step definitions.
        """

        if self.conf.options.version or self.conf.options.showPlugins:
            # Don't try to load anything if only called for information
            return

        self.feature_dirs = [
            dir_
            for dir_ in FeatureLoader.find_feature_directories('.')
        ]
        for feature_dir in self.feature_dirs:
            FeatureLoader.find_and_load_step_definitions(feature_dir)

    def options(self, parser, env=None):
        """
        Register the plugin options.
        """

        if env is None:
            env = os.environ

        super().options(parser, env)

        test_class_name = \
            '{c.__module__}.{c.__name__}'.format(c=self.TEST_CLASS)
        parser.add_option(
            '--test-class', action='store',
            dest='test_class_name',
            default=env.get('NOSE_GHERKIN_CLASS', test_class_name),
            metavar='TEST_CLASS',
            help='Base class to use for the generated tests',
        )
        parser.add_option(
            '--no-ignore-python', action='store_false',
            dest='ignore_python',
            default=True,
            help='Run Python and Gherkin tests together',
        )
        parser.add_option(
            '-n', '--scenario-indices', action='store',
            dest='scenario_indices',
            default='',
            help='Only run scenarios with these indices (comma-separated)',
        )
        parser.add_option(
            '--color', action='store_true',
            dest='force_color',
            default=False,
            help='Force colored output',
        )

        # Options for attribute plugin will be registered by its main instance

    def configure(self, options, conf):
        """
        Configure the plugin.
        """

        super().configure(options, conf)

        module_name, class_name = options.test_class_name.rsplit('.', 1)
        module = import_module(module_name)
        self.test_class = getattr(module, class_name)
        self.ignore_python = options.ignore_python

        conf.force_color = options.force_color

        if options.scenario_indices:
            self.scenario_indices = tuple(
                int(index)
                for index in options.scenario_indices.split(',')
            )
        else:
            self.scenario_indices = None

        self.attrib_plugin.configure(options, conf)

    def wantDirectory(self, directory):
        """
        Collect features from 'features' directories.

        This returns true for any directory either _above_ or _below_ any of
        the features directories; above to ensure the search continues inside,
        below to collect features from all the subdirectories.
        """

        directory = os.path.abspath(directory)
        for feature_dir in self.feature_dirs:
            feature_dir = os.path.abspath(feature_dir)
            if feature_dir.startswith(directory) or \
                    directory.startswith(feature_dir):
                return True

    def wantFile(self, file_):
        """
        Load features from feature files.
        """

        # Check that the feature is in one of the features directories
        file_dir = os.path.abspath(os.path.dirname(file_))
        if any(
                file_dir.startswith(os.path.abspath(feature_dir))
                for feature_dir in self.feature_dirs
        ):
            # Check the file extension
            # Convert to str (not bytes) since Nose passes in both depending on
            # whether the feature is in a Python module dir or not
            if isinstance(file_, bytes):
                file_ = file_.decode(sys.getfilesystemencoding())
            if os.path.basename(file_).endswith('.feature'):
                return True

    def wantPython(self, _):
        """
        Ignore Python tests if required.
        """

        if self.ignore_python:
            return False

    wantClass = wantFunction = wantMethod = wantModule = wantPython

    def scenario_matches(self, feature, scenario_index, scenario_name):
        """
        Whether a given scenario is selected by the command-line options.

        @feature The feature class
        @scenario_index The scenario index
        @scenario_name The scenario name
        """

        if self.scenario_indices:
            if scenario_index not in self.scenario_indices:
                return False

        if self.attrib_plugin.enabled:
            scenario = getattr(feature, scenario_name)
            # False means "no", None means "don't care" for Nose plugins
            if self.attrib_plugin.validateAttrib(scenario, feature) is False:
                return False

        return True

    def loadTestsFromFile(self, file_):
        """
        Load a feature from the feature file.
        """

        test = self.test_class.from_file(file_)

        # About to run a feature - ensure "before all" callbacks have run
        self.ensure_before_callbacks()

        has_tests = False

        # Filter the scenarios, if asked
        for idx, scenario_name in test.scenarios():
            if self.scenario_matches(test, idx, scenario_name):
                has_tests = True
                yield test(scenario_name)

        # Feature OK but no tests filtered
        if not has_tests:
            yield False

    def ensure_before_callbacks(self):
        """
        Before the first test, run the "before all" callbacks.
        """

        if not hasattr(self, 'after_hook'):
            before_all, after_all = CALLBACK_REGISTRY.before_after('all')
            before_all()
            self.after_hook = after_all

    def finalize(self, result):
        """
        After the last test, run the "after all" callbacks.
        """

        # TODO: Is there a better method to do something _after_ all the tests
        # have been run?

        if hasattr(self, 'after_hook'):
            self.after_hook()
            delattr(self, 'after_hook')

    def prepareTestRunner(self, runner):
        """
        Monkeypatch in our TestResult class.

        In unittest we could just set runner.resultclass, but Nose
        doesn't use this.
        """
        def _makeResult():
            """Build our result"""
            return AloeTestResult(runner.stream,
                                  runner.descriptions,
                                  runner.verbosity,
                                  runner.config)

        runner._makeResult = _makeResult  # pylint:disable=protected-access

        return runner