Best Python code snippet using avocado_python
loader.py
Source:loader.py  
...81            mapping = plugin.get_type_label_mapping()82            # Using __func__ to avoid problem with different term_supp instances83            healthy_func = getattr(output.TERM_SUPPORT.healthy_str, '__func__')84            types = [mapping[_[0]]85                     for _ in plugin.get_decorator_mapping().items()86                     if _[1].__func__ is healthy_func]87            return [name + '.' + _ for _ in types]88        def _str_loaders():89            """90            :return: string of sorted loaders and types91            """92            out = ""93            for plugin in self.registered_plugins:94                out += "  %s: %s\n" % (plugin.name,95                                       ", ".join(_good_test_types(plugin)))96            return out.rstrip('\n')97        # When running from the JobAPI there is no subcommand98        subcommand = config.get('subcommand') or 'run'99        self.register_plugin(TapLoader)100        # Register external runner when --external-runner is used101        external_runner = config.get("{}.external_runner".format(subcommand))102        if external_runner:103            self.register_plugin(ExternalLoader)104            key = "{}.loaders".format(subcommand)105            default_loaders = {'file', '@DEFAULT'}106            loaders = config.get(key) or default_loaders107            if set(loaders) != default_loaders:108                warnings.warn("The loaders and external-runner are incompatible."109                              "The values in loaders will be ignored.",110                              RuntimeWarning)111            config[key] = ["external:{}".format(external_runner)]112        else:113            # Add (default) file loader if not already registered114            if FileLoader not in self.registered_plugins:115                self.register_plugin(FileLoader)116            if ExternalLoader not in self.registered_plugins:117                self.register_plugin(ExternalLoader)118        supported_loaders = [_.name for _ in self.registered_plugins]119        supported_types = []120        for plugin in self.registered_plugins:121            supported_types.extend(_good_test_types(plugin))122        # Here is one of the few exceptions that has a hardcoded default123        loaders = config.get("{}.loaders".format(subcommand)) or ['file',124                                                                '@DEFAULT']125        if "@DEFAULT" in loaders:  # Replace @DEFAULT with unused loaders126            idx = loaders.index("@DEFAULT")127            loaders = (loaders[:idx] + [plugin for plugin in supported_loaders128                                        if plugin not in loaders] +129                       loaders[idx + 1:])130            # Remove duplicate @DEFAULT entries131            loaders = [item for item in loaders if item != "@DEFAULT"]132        loaders = [_.split(':', 1) for _ in loaders]133        priority = [_[0] for _ in loaders]134        for i, name in enumerate(priority):135            extra_params = {}136            if name in supported_types:137                name, extra_params['allowed_test_types'] = name.split('.', 1)138            elif name not in supported_loaders:139                raise InvalidLoaderPlugin("Unknown loader '%s'. Available "140                                          "plugins are:\n%s"141                                          % (name, _str_loaders()))142            if len(loaders[i]) == 2:143                extra_params['loader_options'] = loaders[i][1]144            plugin = self.registered_plugins[supported_loaders.index(name)]145            self._initialized_plugins.append(plugin(config, extra_params))146    def _update_mappings(self):147        """148        Update the mappings according the current initialized plugins149        """150        # Plugins are initialized, let's update mappings151        self._label_mapping = {MissingTest: "MISSING"}152        for plugin in self._initialized_plugins:153            self._label_mapping.update(plugin.get_full_type_label_mapping())154        self._decorator_mapping = {MissingTest:155                                   output.TERM_SUPPORT.fail_header_str}156        for plugin in self._initialized_plugins:157            self._decorator_mapping.update(plugin.get_full_decorator_mapping())158    def get_extra_listing(self):159        for loader_plugin in self._initialized_plugins:160            loader_plugin.get_extra_listing()161    def get_base_keywords(self):162        base_path = []163        for loader_plugin in self._initialized_plugins:164            base_path += loader_plugin.get_base_keywords()165        return base_path166    def get_type_label_mapping(self):167        if self._label_mapping is None:168            raise RuntimeError("LoaderProxy.discover has to be called before "169                               "LoaderProxy.get_type_label_mapping")170        return self._label_mapping171    def get_decorator_mapping(self):172        if self._label_mapping is None:173            raise RuntimeError("LoaderProxy.discover has to be called before "174                               "LoaderProxy.get_decorator_mapping")175        return self._decorator_mapping176    def discover(self, references, which_tests=DiscoverMode.DEFAULT, force=None):177        """178        Discover (possible) tests from test references.179        :param references: a list of tests references; if [] use plugin defaults180        :type references: builtin.list181        :param which_tests: Limit tests to be displayed182        :type which_tests: :class:`DiscoverMode`183        :param force: don't raise an exception when some test references184                      are not resolved to tests.185        :return: A list of test factories (tuples (TestClass, test_params))186        """187        def handle_exception(plugin, details):188            # FIXME: Introduce avocado.exceptions logger and use here189            stacktrace.log_message("Test discovery plugin %s failed: "190                                   "%s" % (plugin, details),191                                   LOG_UI.getChild("exceptions"))192            # FIXME: Introduce avocado.traceback logger and use here193            stacktrace.log_exc_info(sys.exc_info(), LOG_UI.getChild("debug"))194        tests = []195        unhandled_references = []196        if not references:197            for loader_plugin in self._initialized_plugins:198                try:199                    tests.extend(loader_plugin.discover(None, which_tests))200                except Exception as details:  # pylint: disable=W0703201                    handle_exception(loader_plugin, details)202        else:203            for reference in references:204                handled = False205                for loader_plugin in self._initialized_plugins:206                    try:207                        _test = loader_plugin.discover(reference, which_tests)208                        if _test:209                            tests.extend(_test)210                            handled = True211                            if which_tests != DiscoverMode.ALL:212                                break  # Don't process other plugins213                    except Exception as details:  # pylint: disable=W0703214                        handle_exception(loader_plugin, details)215                if not handled:216                    unhandled_references.append(reference)217        if unhandled_references:218            if which_tests == DiscoverMode.ALL:219                tests.extend([(MissingTest, {'name': reference})220                              for reference in unhandled_references])221            else:222                # This is a workaround to avoid changing the method signature223                if force is True or force == 'on':224                    LOG_UI.error(LoaderUnhandledReferenceError(unhandled_references,225                                                               self._initialized_plugins))226                else:227                    raise LoaderUnhandledReferenceError(unhandled_references,228                                                        self._initialized_plugins)229        self._update_mappings()230        return tests231    def load_test(self, test_factory):232        """233        Load test from the test factory.234        :param test_factory: a pair of test class and parameters.235        :type test_factory: tuple236        :return: an instance of :class:`avocado.core.test.Test`.237        """238        test_class, test_parameters = test_factory239        if 'modulePath' in test_parameters:240            test_path = test_parameters.pop('modulePath')241        else:242            test_path = None243        if isinstance(test_class, str):244            module_name = os.path.basename(test_path).split('.')[0]245            test_module_dir = os.path.abspath(os.path.dirname(test_path))246            # Tests with local dir imports need this247            try:248                sys.path.insert(0, test_module_dir)249                f, p, d = imp.find_module(module_name, [test_module_dir])250                test_module = imp.load_module(module_name, f, p, d)251            except:  # pylint: disable=W0702252                # On load_module exception we fake the test class and pass253                # the exc_info as parameter to be logged.254                test_parameters['methodName'] = 'test'255                exception = stacktrace.prepare_exc_info(sys.exc_info())256                test_parameters['exception'] = exception257                return test.TestError(**test_parameters)258            finally:259                if test_module_dir in sys.path:260                    sys.path.remove(test_module_dir)261            for _, obj in inspect.getmembers(test_module):262                if (inspect.isclass(obj) and obj.__name__ == test_class and263                        inspect.getmodule(obj) == test_module):264                    if issubclass(obj, test.Test):265                        test_class = obj266                        break267        if test_class is test.DryRunTest:268            test_parameters['modulePath'] = test_path269        if 'run.results_dir' in test_parameters:270            test_parameters['base_logdir'] = test_parameters.pop('run.results_dir')271        test_instance = test_class(**test_parameters)272        return test_instance273    def clear_plugins(self):274        self.registered_plugins = []275class TestLoader:276    """277    Base for test loader classes278    """279    name = None     # Human friendly name of the loader280    def __init__(self, config, extra_params):  # pylint: disable=W0613281        if "allowed_test_types" in extra_params:282            mapping = self.get_type_label_mapping()283            types = extra_params.pop("allowed_test_types")284            if len(mapping) != 1:285                msg = ("Loader '%s' supports multiple test types but does not "286                       "handle the 'allowed_test_types'. Either don't use "287                       "'%s' instead of '%s.%s' or take care of the "288                       "'allowed_test_types' in the plugin."289                       % (self.name, self.name, self.name, types))290                raise LoaderError(msg)291            elif next(mapping.itervalues()) != types:292                raise LoaderError("Loader '%s' doesn't support test type '%s',"293                                  " it supports only '%s'"294                                  % (self.name, types,295                                     next(mapping.itervalues())))296        if "loader_options" in extra_params:297            raise LoaderError("Loader '%s' doesn't support 'loader_options', "298                              "please don't use --loader %s:%s"299                              % (self.name, self.name,300                                 extra_params.get("loader_options")))301        if extra_params:302            raise LoaderError("Loader '%s' doesn't handle extra params %s, "303                              "please adjust your plugin to take care of them."304                              % (self.name, extra_params))305        self.config = config306    def get_extra_listing(self):307        pass308    @staticmethod309    def get_type_label_mapping():310        """311        Get label mapping for display in test listing.312        :return: Dict {TestClass: 'TEST_LABEL_STRING'}313        """314        raise NotImplementedError315    def get_full_type_label_mapping(self):     # pylint: disable=R0201316        """317        Allows extending the type-label-mapping after the object is initialized318        """319        return self.get_type_label_mapping()320    @staticmethod321    def get_decorator_mapping():322        """323        Get label mapping for display in test listing.324        :return: Dict {TestClass: decorator function}325        """326        raise NotImplementedError327    def get_full_decorator_mapping(self):      # pylint: disable=R0201328        """329        Allows extending the decorator-mapping after the object is initialized330        """331        return self.get_decorator_mapping()332    def discover(self, reference, which_tests=DiscoverMode.DEFAULT):333        """334        Discover (possible) tests from an reference.335        :param reference: the reference to be inspected.336        :type reference: str337        :param which_tests: Limit tests to be displayed338        :type which_tests: :class:`DiscoverMode`339        :return: a list of test matching the reference as params.340        """341        raise NotImplementedError342class BrokenSymlink:343    """ Dummy object to represent reference pointing to a BrokenSymlink path """344class AccessDeniedPath:345    """ Dummy object to represent reference pointing to a inaccessible path """346def add_loader_options(parser, section='run'):347    arggrp = parser.add_argument_group('loader options')348    help_msg = ("Overrides the priority of the test loaders. You can specify "349                "either @loader_name or TEST_TYPE. By default it tries all "350                "available loaders according to priority set in "351                "settings->plugins.loaders.")352    settings.register_option(section=section,353                             key='loaders',354                             nargs='*',355                             key_type=list,356                             default=['file', '@DEFAULT'],357                             help_msg=help_msg,358                             parser=arggrp,359                             long_arg='--loaders')360    help_msg = ("Path to an specific test runner that allows the use of its "361                "own tests. This should be used for running tests that do not "362                "conform to Avocado\'s SIMPLE test interface and can not run "363                "standalone. Note: the use of --external-runner overwrites "364                "the --loaders to 'external_runner'")365    settings.register_option(section=section,366                             key='external_runner',367                             default=None,368                             help_msg=help_msg,369                             parser=arggrp,370                             long_arg='--external-runner')371    help_msg = ("Change directory before executing tests. This option may be "372                "necessary because of requirements and/or limitations of the "373                "external test runner. If the external runner requires to be "374                "run from its own base directory, use 'runner' here. If the "375                "external runner runs tests based  on files and requires to "376                "be run from the directory where those files are located, "377                "use 'test' here and specify the test directory with the "378                "option '--external-runner-testdir'.")379    settings.register_option(section=section,380                             key='external_runner_chdir',381                             help_msg=help_msg,382                             default=None,383                             parser=arggrp,384                             choices=('runner', 'test'),385                             long_arg='--external-runner-chdir')386    help_msg = ("Where test files understood by the external test runner "387                "are located in the filesystem. Obviously this assumes and "388                "only applies to external test runners that run tests from "389                "files")390    settings.register_option(section=section,391                             key='external_runner_testdir',392                             metavar='DIRECTORY',393                             default=None,394                             help_msg=help_msg,395                             parser=arggrp,396                             long_arg='--external-runner-testdir')397class NotATest:398    """399    Class representing something that is not a test400    """401class SimpleFileLoader(TestLoader):402    """403    Test loader class.404    """405    name = 'file'406    NOT_TEST_STR = ("Not a supported test")407    def __init__(self, config, extra_params):408        test_type = extra_params.pop('allowed_test_types', None)409        super(SimpleFileLoader, self).__init__(config, extra_params)410        self.test_type = test_type411    @staticmethod412    def get_type_label_mapping():413        return {NotATest: 'NOT_A_TEST',414                MissingTest: 'MISSING',415                BrokenSymlink: 'BROKEN_SYMLINK',416                AccessDeniedPath: 'ACCESS_DENIED'}417    @staticmethod418    def get_decorator_mapping():419        return {NotATest: output.TERM_SUPPORT.warn_header_str,420                MissingTest: output.TERM_SUPPORT.fail_header_str,421                BrokenSymlink: output.TERM_SUPPORT.fail_header_str,422                AccessDeniedPath: output.TERM_SUPPORT.fail_header_str}423    @staticmethod424    def _is_matching_test_class(tst, test_class):425        return issubclass(tst, test_class)426    def discover(self, reference, which_tests=DiscoverMode.DEFAULT):427        """428        Discover (possible) tests from a directory.429        Recursively walk in a directory and find tests params.430        The tests are returned in alphabetic order.431        Afterwards when "allowed_test_types" is supplied it verifies if all432        found tests are of the allowed type. If not return None (even on433        partial match).434        :param reference: the directory path to inspect.435        :param which_tests: Limit tests to be displayed436        :type which_tests: :class:`DiscoverMode`437        :return: list of matching tests438        """439        tests = self._discover(reference, which_tests)440        if self.test_type:441            mapping = self.get_type_label_mapping()442            test_class = next(key for key, value in mapping.items()443                              if value == self.test_type)444            for tst in tests:445                if not self._is_matching_test_class(tst[0], test_class):446                    return None447        return tests448    def _discover(self, reference, which_tests=DiscoverMode.DEFAULT):449        """450        Recursively walk in a directory and find tests params.451        The tests are returned in alphabetic order.452        :param reference: the directory path to inspect.453        :param which_tests: Limit tests to be displayed454        :type which_tests: :class:`DiscoverMode`455        :return: list of matching tests456        """457        if reference is None:458            if which_tests == DiscoverMode.DEFAULT:459                return []  # Return empty set when not listing details460            else:461                reference = data_dir.get_test_dir()462        ignore_suffix = ('.data', '.pyc', '.pyo', '__init__.py',463                         '__main__.py')464        # Look for filename:test_method pattern465        reference, subtests_filter = reference_split(reference)466        if subtests_filter is not None:467            subtests_filter = re.compile(subtests_filter)468        if not os.path.isdir(reference):  # Single file469            return self._make_tests(reference, which_tests == DiscoverMode.ALL,470                                    subtests_filter)471        tests = []472        def add_test_from_exception(exception):473            """ If the exc.filename is valid test it's added to tests """474            tests.extend(self._make_tests(exception.filename,475                                          which_tests == DiscoverMode.ALL))476        def skip_non_test(exception):  # pylint: disable=W0613477            """ Always return None """478            return None479        if which_tests == DiscoverMode.ALL:480            onerror = add_test_from_exception481        else:  # DEFAULT, AVAILABLE => skip missing tests482            onerror = skip_non_test483        for dirpath, dirs, filenames in os.walk(reference, onerror=onerror):484            dirs.sort()485            for file_name in sorted(filenames):486                if file_name.startswith('.') or file_name.endswith(ignore_suffix):487                    continue488                pth = os.path.join(dirpath, file_name)489                tests.extend(self._make_tests(pth,490                                              which_tests == DiscoverMode.ALL,491                                              subtests_filter))492        return tests493    def _make_simple_test(self, test_path, subtests_filter):494        return self._make_test(test.SimpleTest, test_path,495                               subtests_filter=subtests_filter,496                               executable=test_path)497    def _make_simple_or_broken_test(self, test_path, subtests_filter, make_broken):498        if os.access(test_path, os.X_OK):499            return self._make_simple_test(test_path, subtests_filter)500        else:501            return make_broken(NotATest, test_path,502                               self.NOT_TEST_STR)503    def _make_existing_file_tests(self, test_path, make_broken,504                                  subtests_filter):505        return self._make_simple_or_broken_test(test_path, subtests_filter, make_broken)506    def _make_nonexisting_file_tests(self, test_path, make_broken,507                                     subtests_filter, test_name):  # pylint: disable=W0613508        return make_broken(NotATest, test_name, "File not found "509                           "('%s'; '%s')" % (test_name, test_path))510    @staticmethod511    def _make_test(klass, uid, description=None, subtests_filter=None,512                   **test_arguments):513        """514        Create test template515        :param klass: test class516        :param uid: test uid (by default used as id and name)517        :param description: Description appended to "uid" (for listing purpose)518        :param subtests_filter: optional filter of methods for avocado tests519        :param test_arguments: arguments to be passed to the klass(test_arguments)520        """521        if subtests_filter and not subtests_filter.search(uid):522            return []523        if description:524            uid = "%s: %s" % (uid, description)525        test_arguments["name"] = uid526        return [(klass, test_arguments)]527    def _make_tests(self, test_path, list_non_tests, subtests_filter=None):528        """529        Create test templates from given path530        :param test_path: File system path531        :param list_non_tests: include bad tests (NotATest, BrokenSymlink,...)532        :param subtests_filter: optional filter of methods for avocado tests533        """534        def ignore_broken(klass, uid, description=None):  # pylint: disable=W0613535            """ Always return empty list """536            return []537        if list_non_tests:  # return broken test with params538            make_broken = self._make_test539        else:  # return empty set instead540            make_broken = ignore_broken541        test_name = test_path542        if os.path.exists(test_path):543            if os.access(test_path, os.R_OK) is False:544                return make_broken(AccessDeniedPath, test_path, "Is not "545                                   "readable")546            return self._make_existing_file_tests(test_path, make_broken,547                                                  subtests_filter)548        else:549            if os.path.islink(test_path):550                try:551                    if not os.path.isfile(os.readlink(test_path)):552                        return make_broken(BrokenSymlink, test_path, "Is a "553                                           "broken symlink")554                except OSError:555                    return make_broken(AccessDeniedPath, test_path, "Is not "556                                       "accessible.")557            return self._make_nonexisting_file_tests(test_path, make_broken,558                                                     subtests_filter,559                                                     test_name)560class FileLoader(SimpleFileLoader):561    """562    Test loader class.563    """564    name = 'file'565    NOT_TEST_STR = ("Not an INSTRUMENTED (avocado.Test based), PyUNITTEST ("566                    "unittest.TestCase based) or SIMPLE (executable) test")567    @staticmethod568    def get_type_label_mapping():569        mapping = SimpleFileLoader.get_type_label_mapping()570        mapping.update(571            {test.SimpleTest: 'SIMPLE',572             test.Test: 'INSTRUMENTED',573             test.PythonUnittest: 'PyUNITTEST'})574        return mapping575    @staticmethod576    def get_decorator_mapping():577        mapping = SimpleFileLoader.get_decorator_mapping()578        mapping.update(579            {test.SimpleTest: output.TERM_SUPPORT.healthy_str,580             test.Test: output.TERM_SUPPORT.healthy_str,581             test.PythonUnittest: output.TERM_SUPPORT.healthy_str})582        return mapping583    @staticmethod584    def _is_matching_test_class(tst, test_class):585        if test_class is test.Test:586            # Instrumented tests are defined as string and loaded at the587            # execution time.588            return isinstance(tst, str)589        else:590            return not isinstance(tst, str) and issubclass(tst, test_class)591    def _find_python_unittests(self, test_path, disabled, subtests_filter):592        result = []593        class_methods = safeloader.find_python_unittests(test_path)594        for klass, methods in class_methods.items():595            if klass in disabled:596                continue597            if test_path.endswith(".py"):598                test_path = test_path[:-3]599            test_module_name = os.path.relpath(test_path)600            test_module_name = test_module_name.replace(os.path.sep, ".")601            candidates = [("%s.%s.%s" % (test_module_name, klass, method),602                           tags) for (method, tags, _) in methods]603            if subtests_filter:604                result += [_ for _ in candidates if subtests_filter.search(_)]605            else:606                result += candidates607        return result608    def _make_simple_test(self, test_path, subtests_filter):609        return self._make_test(test.SimpleTest, test_path,610                               subtests_filter=subtests_filter,611                               executable=test_path)612    def _make_simple_or_broken_test(self, test_path, subtests_filter, make_broken):613        if os.access(test_path, os.X_OK):614            # Module does not have an avocado test class inside but615            # it's executable, let's execute it.616            return self._make_simple_test(test_path, subtests_filter)617        else:618            # Module does not have an avocado test class inside, and619            # it's not executable. Not a Test.620            return make_broken(NotATest, test_path,621                               self.NOT_TEST_STR)622    def _make_python_file_tests(self, test_path, make_broken,623                                subtests_filter, test_name=None):624        if test_name is None:625            test_name = test_path626        try:627            # Avocado tests628            avocado_tests, disabled = safeloader.find_avocado_tests(test_path)629            if avocado_tests:630                test_factories = []631                for test_class, info in avocado_tests.items():632                    if isinstance(test_class, str):633                        for test_method, tags, _ in info:634                            name = test_name + \635                                ':%s.%s' % (test_class, test_method)636                            if (subtests_filter and637                                    not subtests_filter.search(name)):638                                continue639                            tst = (test_class, {'name': name,640                                                'modulePath': test_path,641                                                'methodName': test_method,642                                                'tags': tags})643                            test_factories.append(tst)644                return test_factories645            # Python unittests646            old_dir = os.getcwd()647            try:648                py_test_dir = os.path.abspath(os.path.dirname(test_path))649                py_test_name = os.path.basename(test_path)650                os.chdir(py_test_dir)651                python_unittests = self._find_python_unittests(py_test_name,652                                                               disabled,653                                                               subtests_filter)654            finally:655                os.chdir(old_dir)656            if python_unittests:657                return [(test.PythonUnittest, {"name": name,658                                               "test_dir": py_test_dir,659                                               "tags": tags})660                        for (name, tags) in python_unittests]661            else:662                # Module does not have an avocado test or pyunittest class inside,663                # but maybe it's a Python executable.664                return self._make_simple_or_broken_test(test_path,665                                                        subtests_filter,666                                                        make_broken)667        # Since a lot of things can happen here, the broad exception is668        # justified. The user will get it unadulterated anyway, and avocado669        # will not crash. Ugly python files can raise any exception670        except BaseException as details:  # pylint: disable=W0703671            if isinstance(details, KeyboardInterrupt):672                raise  # Don't ignore ctrl+c673            else:674                return self._make_simple_or_broken_test(test_path,675                                                        subtests_filter,676                                                        make_broken)677    def _make_existing_file_tests(self, test_path, make_broken,678                                  subtests_filter):679        if test_path.endswith('.py'):680            return self._make_python_file_tests(test_path, make_broken,681                                                subtests_filter)682        else:683            return self._make_simple_or_broken_test(test_path,684                                                    subtests_filter,685                                                    make_broken)686    def _make_nonexisting_file_tests(self, test_path, make_broken,687                                     subtests_filter, test_name):688        # Try to resolve test ID (keep compatibility)689        test_path = os.path.join(data_dir.get_test_dir(), test_name)690        if os.path.exists(test_path):691            return self._make_python_file_tests(test_path, make_broken,692                                                subtests_filter,693                                                test_name)694        else:695            if not subtests_filter and ':' in test_name:696                test_name, subtests_filter = test_name.split(':', 1)697                test_path = os.path.join(data_dir.get_test_dir(),698                                         test_name)699                if os.path.exists(test_path):700                    subtests_filter = re.compile(subtests_filter)701                    return self._make_python_file_tests(test_path,702                                                        make_broken,703                                                        subtests_filter,704                                                        test_name)705            return make_broken(NotATest, test_name, "File not found "706                               "('%s'; '%s')" % (test_name, test_path))707class ExternalLoader(TestLoader):708    """709    External-runner loader class710    """711    name = 'external'712    def __init__(self, config, extra_params):713        loader_options = extra_params.pop('loader_options', None)714        super(ExternalLoader, self).__init__(config, extra_params)715        if loader_options == '?':716            raise LoaderError("File loader accepts an option to set the "717                              "external-runner executable.")718        self._external_runner = self._process_external_runner(719            config, loader_options)720    @staticmethod721    def _process_external_runner(config, runner):722        """ Enables the external_runner when asked for """723        subcommand = config.get('subcommand')724        chdir = config.get("{}.external_runner_chdir".format(subcommand))725        test_dir = config.get("{}.external_runner_testdir".format(subcommand))726        if runner:727            external_runner_and_args = shlex.split(runner)728            executable = os.path.abspath(external_runner_and_args[0])729            runner = " ".join([executable] + external_runner_and_args[1:])730            if not os.path.exists(executable):731                msg = ('Could not find the external runner executable "%s"'732                       % executable)733                raise LoaderError(msg)734            if chdir == 'test':735                if not test_dir:736                    msg = ('Option "--external-runner-chdir=test" requires '737                           '"--external-runner-testdir" to be set.')738                    raise LoaderError(msg)739            elif test_dir:740                msg = ('Option "--external-runner-testdir" requires '741                       '"--external-runner-chdir=test".')742                raise LoaderError(msg)743            return test.ExternalRunnerSpec(runner, chdir, test_dir)744        elif chdir:745            msg = ('Option "--external-runner-chdir" requires '746                   '"--external-runner" to be set.')747            raise LoaderError(msg)748        elif test_dir:749            msg = ('Option "--external-runner-testdir" requires '750                   '"--external-runner" to be set.')751            raise LoaderError(msg)752        return None  # Skip external runner753    def discover(self, reference, which_tests=DiscoverMode.DEFAULT):754        """755        :param reference: arguments passed to the external_runner756        :param which_tests: Limit tests to be displayed757        :type which_tests: :class:`DiscoverMode`758        :return: list of matching tests759        """760        if (not self._external_runner) or (reference is None):761            return []762        return [(test.ExternalRunnerTest, {'name': reference, 'external_runner':763                                           self._external_runner,764                                           'external_runner_argument':765                                           reference})]766    @staticmethod767    def get_type_label_mapping():768        return {test.ExternalRunnerTest: 'EXTERNAL'}769    @staticmethod770    def get_decorator_mapping():771        return {test.ExternalRunnerTest: output.TERM_SUPPORT.healthy_str}772class TapLoader(SimpleFileLoader):773    """774    Test Anything Protocol loader class775    """776    name = "tap"777    @staticmethod778    def get_type_label_mapping():779        mapping = SimpleFileLoader.get_type_label_mapping()780        mapping.update(781            {test.TapTest: 'TAP'})782        return mapping783    @staticmethod784    def get_decorator_mapping():785        mapping = SimpleFileLoader.get_decorator_mapping()786        mapping.update(787            {test.TapTest: output.TERM_SUPPORT.healthy_str})788        return mapping789    def _make_simple_test(self, test_path, subtests_filter):790        return self._make_test(test.TapTest, test_path,791                               subtests_filter=subtests_filter,792                               executable=test_path)...Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!
