Exemplo n.º 1
0
 def testProjectPart(self):
     project = Project()
     XmlProjectParser.load_project(self.project_path, project)
     # Check project part
     self.assertEqual(
         self.project_part,
         project.get_fpga_part()
     )
Exemplo n.º 2
0
 def test_project_part(self):
     project = Project()
     project.load_project(self.project_path)
     # Check project part
     self.assertEqual(
         self.project_part,
         project.get_fpga_part()
     )
Exemplo n.º 3
0
 def testProjectPart(self):
     project = Project()
     XmlProjectParser.load_project(self.project_path, project)
     # Check project part
     self.assertEqual(self.project_part, project.get_fpga_part())
Exemplo n.º 4
0
class CommandLine(cmd.Cmd):
    def __init__(self, project=None):
        super(CommandLine, self).__init__()
        prj = ''
        if project is None:
            try:
                self.project = Project()
                projects = self.locateProjects()
                if len(projects) > 0:
                    prj = (
                        'Type ' + term.colourise(
                            '\'load_project <path>\'', fg='yellow'
                        ) +
                        ' to load a project.\n'
                    )
                    prj += (
                        'The current directory contains ' +
                        'the following projects:\n'
                    )
                    prj += term.colourise(
                        '\n'.join('\t{0}: {1}'.format(
                            idx+1,
                            path
                        ) for idx, path in enumerate(projects)),
                        fg='yellow'
                    )
                    self.intro = INTRO_TEMPL % dict(
                        version=_version.__version__,
                        projects=prj,
                    )
            except exceptions.ProjectFileException:
                log.error(
                    'The project file contains errors, ' +
                    'fix them and then type \'reload\''
                )
            except:
                log.error(
                    'The software has to terminate due to the following error:'
                )
                log.error(traceback.format_exc())
                sys.exit(1)
        else:
            self.project = project
            prj = (
                'Project contains ' + term.colourise(
                    str(len(self.project.get_files())),
                    fg='yellow'
                ) + ' file(s) and ' + term.colourise(
                    str(len(self.project.get_tests())),
                    fg='yellow'
                ) + ' test(s).'
            )
            self.intro = INTRO_TEMPL % dict(
                version=_version.__version__,
                projects=prj,
            )

        self.test_set = set()

    def locateProjects(self):
        """
        Return a list of projects found in the current path.
        """
        projects = []
        logging.getLogger("chiptools").setLevel(
            logging.CRITICAL
        )
        for filePath in os.listdir(os.getcwd()):
            if filePath.endswith('.xml'):
                try:
                    tempProject = Project()
                    # Load the XML file
                    XmlProjectParser.load_project(filePath, tempProject)
                    # If the project contains files we can add it to our list
                    files = tempProject.get_files()
                    files = files if files is not None else []
                    if len(files) != 0:
                        projects.append(filePath)
                except:
                    pass
        logging.getLogger("chiptools").setLevel(logging.INFO)
        return projects

    @wraps_do_commands
    def do_load_project(self, path):
        """Load the given project XML file: load_project <path_to_project>"""
        path = os.path.abspath(path)
        if os.path.exists(path) and os.path.isfile(path):
            try:
                log.info(
                    'Loading {0} in current working directory: {1}'.format(
                        path,
                        os.getcwd()
                    )
                )
                XmlProjectParser.load_project(path, self.project)
            except:
                log.error('The project could not be loaded due to an error:')
                log.error(traceback.format_exc())
        else:
            log.error('File not found: {0}'.format(path))

    @wraps_do_commands
    def do_exit(self, command):
        return True

    @wraps_do_commands
    def do_compile(self, command):
        self.project.compile()

    @wraps_do_commands
    def do_show_synthesis_fileset(self, command):
        """Print out the synthesis file set"""
        items = self.project.get_synthesis_fileset().items()
        if len(items) == 0:
            log.info('There are no synthesisable files loaded.')
            return
        for libName, fileList in items:
            log.info('Library: ' + libName)
            for file in fileList:
                log.info('\t\t' + file.path)

    @wraps_do_commands
    def do_synthesise(self, command):
        """Synthesise the design using the chosen synthesis tool and report any
        errors Example: (Cmd) simulate my_library.my_entity"""
        command_elems = command.split(' ')
        fpga_part = None
        if len(command_elems) == 3:
            target, tool_name, fpga_part = command_elems
        elif len(command_elems) == 2:
            target, tool_name = command_elems
        else:
            target = command_elems[0]
            tool_name = None

        try:
            library, entity = target.split('.')
        except ValueError:
            log.error('Command \"' + command + '\" not understood.')
            log.error(
                "Please specify a library and entity.\n" +
                "Example: (Cmd) synthesise my_library.my_entity [tool_name]"
            )
            return

        self.project.synthesise(
            library,
            entity,
            tool_name=tool_name,
            fpga_part=fpga_part
        )

    @wraps_do_commands
    def do_run_preprocessors(self, command):
        """For each project file in the design run any associated
        preprocessors. You can use this command to test that a preprocessor is
        running on your file correctly. Preprocessors are called automatically
        when you run synthesis"""
        log.info('Running preprocessors...')
        self.project.run_preprocessors()
        log.info('...done')

    @wraps_do_commands
    def do_simulate(self, command):
        """Compile the given entity in the given library using the chosen
        simulator and invoke the simulator GUI.
        Example: (Cmd) simulate my_library.my_entity"""
        command_elems = command.split(' ')
        if len(command_elems) == 2:
            target, tool_name = command_elems
        else:
            target = command_elems[0]
            tool_name = None

        try:
            library, entity = target.split('.')
        except ValueError:
            log.error('Command \"' + command + '\" not understood.')
            log.error(
                "Please specify a library and entity.\n" +
                "Example: (Cmd) simulate my_library.my_entity [tool_name]"
            )
            return
        self.project.simulate(library, entity, gui=True, tool_name=tool_name)

    @wraps_do_commands
    def do_clean(self, command):
        """Clear the file cache"""
        # Delete libraries from the simulation area
        simpath = self.project.get_simulation_directory()
        if simpath is None:
            log.error('No simulation path is set, aborting operation.')
            return
        log.info('Cleaning simulation folder: ' + simpath)
        for tool_name in self.project.cache.get_tool_names():
            for libname in self.project.cache.get_libraries(tool_name):
                path = os.path.join(simpath, libname)
                if os.path.exists(path):
                    log.info('Removing ' + path)
                    shutil.rmtree(path)
        log.info('...done')
        self.project.cache.initialise_cache()

    @wraps_do_commands
    def do_pwd(self, command):
        print(
            term.colourise('Working directory: ', fg='yellow') +
            term.colourise('{0}', fg='green').format(os.getcwd())
        )

    @wraps_do_commands
    def do_plugins(self, command):
        simulators = self.project.get_available_simulators()
        synthesisers = self.project.get_available_synthesisers()
        for i, plugin_registry in enumerate([simulators, synthesisers]):
            print(term.yellow(['Simulator Plugins:', 'Synthesis Plugins:'][i]))
            for name, inst in plugin_registry.items():
                plugin_path = sys.modules[inst.__module__].__file__
                plugin_file = os.path.basename(plugin_path)
                print(
                    SEP * 1 + term.darkgray(plugin_file) + '\n' +
                    SEP * 2 + '{:<15}: {:<35}'.format(
                        'Plugin Path',
                        term.green(plugin_path)
                    ) + '\n' +
                    SEP * 2 + '{:<15}: {:<35}'.format(
                        'Name',
                        term.green(str(name))
                    ) + '\n' +
                    SEP * 2 + '{:<15}: {:<35}'.format(
                        'Tool Path',
                        [
                            term.red('(not found) ' + str(inst.path)),
                            term.green(str(inst.path))
                        ][inst.installed]
                    )
                )

    @wraps_do_commands
    def do_show_config(self, command):
        """Print out the project settings"""
        available_simulator_string = ''
        for name, inst in self.project.get_available_simulators().items():
            available_simulator_string += (
                SEP * 2 + '{:<15}: ' +
                [
                    '(not found) ' + term.red('{:<35}'),
                    term.green('{:<35}')
                ][inst.installed] +
                '\n'
            ).format(name, inst.path)

        available_synthesiser_string = ''
        for name, inst in self.project.get_available_synthesisers().items():
            available_synthesiser_string += (
                SEP * 2 + '{:<15}: ' +
                [
                    '(not found) ' + term.red('{:<35}'),
                    term.green('{:<35}')
                ][inst.installed] +
                '\n'
            ).format(name, inst.path)

        msg = (
            '\n' +
            term.yellow(term.bold('System Configuration: ')) +
            term.green('%(options)s') + '\n' +
            term.darkgray(SEP + 'Working directory:\n') +
            SEP * 2 + term.green('%(working_directory)s') + '\n' +
            term.darkgray(SEP + 'Available simulators:\n') +
            available_simulator_string +
            term.darkgray(SEP + 'Available synthesisers:\n') +
            available_synthesiser_string +
            term.darkgray(SEP + 'Simulation libraries:\n') +
            ''.join(
                (SEP * 2 + '{:<15}: ' + term.green('{:<35}') + '\n').format(
                    k,
                    v
                ) for k, v in (
                    self.project.get_simulator_library_dependencies().items()
                )
            ) +
            '\n' +
            term.yellow(term.bold('Project Configuration: ')) +
            term.green('%(project)s') + '\n' +
            term.darkgray(SEP + 'Simulation directory set to:\n') +
            SEP * 2 + term.green('%(simulation_directory)s') + '\n' +
            term.darkgray(SEP + 'Using the simulation tool:\n') +
            SEP * 2 + term.green('%(simulation_tool_name)s') + '\n' +
            term.darkgray(SEP + 'Synthesis directory set to:\n') +
            SEP * 2 + term.green('%(synthesis_directory)s') + '\n' +
            term.darkgray(SEP + 'Using the synthesis tool:\n') +
            SEP * 2 + term.green('%(synthesis_tool_name)s') + '\n' +
            term.darkgray(SEP + 'Targeting FPGA part:\n') +
            SEP * 2 + term.green('%(fpga_part)s') + '\n' +
            term.darkgray(SEP + 'Using synthesis generic binding:\n') +
            SEP * 2 + term.green('%(synthesis_generics)s') + '\n' +
            term.darkgray(SEP + 'Modelsim Specific Arguments:\n') +
            SEP * 2 + 'vsim: ' + term.green('%(modelsim_vsim_args)s') + '\n'
        )

        print(msg % dict(
            working_directory=os.getcwd(),
            options=self.project.get_system_config_path(),
            simulation_directory=self.project.get_simulation_directory(),
            simulation_tool_name=self.project.get_simulation_tool_name(),
            synthesis_directory=self.project.get_synthesis_directory(),
            synthesis_tool_name=self.project.get_synthesis_tool_name(),
            fpga_part=self.project.get_fpga_part(),
            modelsim_vsim_args=self.project.get_tool_arguments(
                'modelsim',
                'simulate'
            ),
            project='',
            synthesis_generics=''.join(
                str(k) + ':' + str(v) + ', ' for k, v in
                self.project.get_generics().items()
            ))
        )

    @wraps_do_commands
    def do_show_tests(self, command):
        """
        Show the tests available in the current project.
        """
        tests = self.project.get_tests()

        if len(tests) == 0:
            log.info('There are no tests available.')
            return

        testUniqueId = 0
        for file_object in tests:
            file_name = os.path.basename(file_object.path)
            print(term.yellow(file_name))
            for test_group in file_object.testsuite:
                for testId, test in enumerate(test_group):
                    if testId == 0:
                        print(
                            SEP + term.green(str(test.__class__.__name__))
                        )
                    doc = test.shortDescription()
                    if doc is None:
                        doc = term.darkred('No description')
                    if testUniqueId in self.test_set:
                        msg = SEP * 2 + '[' + term.blue('ID ' + str(
                            testUniqueId) + ' ' + test.id().split('.')[-1]
                        ) + ']'
                    else:
                        msg = SEP * 2 + term.lightgray('ID ' + str(
                            testUniqueId) + ' ' + test.id().split('.')[-1]
                        )
                    print(msg)
                    print(term.darkgray(textwrap.fill(
                        doc,
                        width=80,
                        initial_indent=SEP * 2,
                        subsequent_indent=SEP * 2,
                    )))
                    testUniqueId += 1

    def show_test_selection(self):
        """
        Show the currently selected tests in the current project.
        Add or remove tests using the
        add_tests and remove_tests commands respectively.
        """
        ids_list = list(self.test_set)
        tests = []
        for file_object in self.project.get_tests():
            file_name = os.path.basename(file_object.path)
            print(term.yellow(file_name))
            for test_group in file_object.testsuite:
                for testId, test in enumerate(test_group):
                    if testId == 0:
                        groupName = str(test.__class__.__name__)
                    testName = test.id().split('.')[-1]
                    tests.append((file_name, groupName, testName, test))

        # Filter out any invalid indices
        ids_list = list(filter(lambda x: x < len(tests), ids_list))
        for idx, (fileName, groupName, testName, test) in enumerate(
            [tests[idx] for idx in ids_list]
        ):
            print(
                '{:<5}'.format(str(ids_list[idx]) + ':') +
                '[' + term.yellow(fileName) + ']' +
                '[' + term.green(groupName) + ']' +
                '[' + term.blue(testName) + ']'
            )

    @wraps_do_commands
    def do_add_tests(self, command):
        """
        Add the given tests to the test suite. Tests should be supplied as
        comma separated numbers or numeric ranges, for example:

        add_tests 1-3, 5, 6, 7

        Would add tests 1, 2, 3, 5, 6, 7 to the test suite.

        You can check which tests are available by issuing the show_tests
        command or remove tests that have been added to the suite by issuing
        the remove_tests command.
        """
        try:
            ids = utils.parseRange(command)
        except ValueError:
            log.error('Invalid test range specified: ' + command)
            return
        for testId in ids:
            self.test_set.add(testId)
        self.show_test_selection()

    @wraps_do_commands
    def do_remove_tests(self, command):
        """
        Remove the given tests from the test suite. Tests should be supplied
        as comma separated numbers or numeric ranges, for example:

        remove_tests 1-3, 5, 6, 7

        Would remove tests 1, 2, 3, 5, 6, 7 from the test suite.

        You can check which tests are available by issuing the show_tests
        command or add tests by issuing the add_tests command.
        """
        try:
            ids = utils.parseRange(command)
        except ValueError:
            log.error('Invalid test range specified: ' + command)
            return
        for testId in ids:
            try:
                self.test_set.remove(testId)
            except KeyError:
                pass
        self.show_test_selection()

    @wraps_do_commands
    def do_run_tests(self, command):
        """
        Run the tests that were selected via the add_tests command and report
        the results.
        """
        if len(command) > 0:
            tool_name = command
        else:
            tool_name = None
        self.show_test_selection()
        self.project.run_tests(self.test_set, tool_name=tool_name)
Exemplo n.º 5
0
class CommandLine(cmd.Cmd):
    def __init__(self, project=None):
        super(CommandLine, self).__init__()
        prj = ''
        if project is None:
            try:
                self.project = Project()
                projects = self.locateProjects()
                if len(projects) > 0:
                    prj = ('Type ' + term.colourise('\'load_project <path>\'',
                                                    fg='yellow') +
                           ' to load a project.\n')
                    prj += ('The current directory contains ' +
                            'the following projects:\n')
                    prj += term.colourise('\n'.join(
                        '\t{0}: {1}'.format(idx + 1, path)
                        for idx, path in enumerate(projects)),
                                          fg='yellow')
                    self.intro = INTRO_TEMPL % dict(
                        version=_version.__version__,
                        projects=prj,
                    )
            except exceptions.ProjectFileException:
                log.error('The project file contains errors, ' +
                          'fix them and then type \'reload\'')
            except:
                log.error(
                    'The software has to terminate due to the following error:'
                )
                log.error(traceback.format_exc())
                sys.exit(1)
        else:
            self.project = project
            prj = ('Project contains ' +
                   term.colourise(str(len(self.project.get_files())),
                                  fg='yellow') + ' file(s) and ' +
                   term.colourise(str(len(self.project.get_tests())),
                                  fg='yellow') + ' test(s).')
            self.intro = INTRO_TEMPL % dict(
                version=_version.__version__,
                projects=prj,
            )

        self.test_set = set()

    def locateProjects(self):
        """
        Return a list of projects found in the current path.
        """
        projects = []
        logging.getLogger("chiptools").setLevel(logging.CRITICAL)
        for filePath in os.listdir(os.getcwd()):
            if filePath.endswith('.xml'):
                try:
                    tempProject = Project()
                    # Load the XML file
                    XmlProjectParser.load_project(filePath, tempProject)
                    # If the project contains files we can add it to our list
                    files = tempProject.get_files()
                    files = files if files is not None else []
                    if len(files) != 0:
                        projects.append(filePath)
                except:
                    pass
        logging.getLogger("chiptools").setLevel(logging.INFO)
        return projects

    @wraps_do_commands
    def do_load_project(self, path):
        """Load the given project XML file: load_project <path_to_project>"""
        path = os.path.abspath(path)
        if os.path.exists(path) and os.path.isfile(path):
            try:
                log.info(
                    'Loading {0} in current working directory: {1}'.format(
                        path, os.getcwd()))
                XmlProjectParser.load_project(path, self.project)
            except:
                log.error('The project could not be loaded due to an error:')
                log.error(traceback.format_exc())
        else:
            log.error('File not found: {0}'.format(path))

    @wraps_do_commands
    def do_exit(self, command):
        return True

    @wraps_do_commands
    def do_compile(self, command):
        self.project.compile()

    @wraps_do_commands
    def do_show_synthesis_fileset(self, command):
        """Print out the synthesis file set"""
        items = self.project.get_synthesis_fileset().items()
        if len(items) == 0:
            log.info('There are no synthesisable files loaded.')
            return
        for libName, fileList in items:
            log.info('Library: ' + libName)
            for file in fileList:
                log.info('\t\t' + file.path)

    @wraps_do_commands
    def do_synthesise(self, command):
        """Synthesise the design using the chosen synthesis tool and report any
        errors Example: (Cmd) simulate my_library.my_entity"""
        command_elems = command.split(' ')
        fpga_part = None
        if len(command_elems) == 3:
            target, tool_name, fpga_part = command_elems
        elif len(command_elems) == 2:
            target, tool_name = command_elems
        else:
            target = command_elems[0]
            tool_name = None

        try:
            library, entity = target.split('.')
        except ValueError:
            log.error('Command \"' + command + '\" not understood.')
            log.error(
                "Please specify a library and entity.\n" +
                "Example: (Cmd) synthesise my_library.my_entity [tool_name]")
            return

        self.project.synthesise(library,
                                entity,
                                tool_name=tool_name,
                                fpga_part=fpga_part)

    @wraps_do_commands
    def do_run_preprocessors(self, command):
        """For each project file in the design run any associated
        preprocessors. You can use this command to test that a preprocessor is
        running on your file correctly. Preprocessors are called automatically
        when you run synthesis"""
        log.info('Running preprocessors...')
        self.project.run_preprocessors()
        log.info('...done')

    @wraps_do_commands
    def do_simulate(self, command):
        """Compile the given entity in the given library using the chosen
        simulator and invoke the simulator GUI.
        Example: (Cmd) simulate my_library.my_entity"""
        command_elems = command.split(' ')
        if len(command_elems) == 2:
            target, tool_name = command_elems
        else:
            target = command_elems[0]
            tool_name = None

        try:
            library, entity = target.split('.')
        except ValueError:
            log.error('Command \"' + command + '\" not understood.')
            log.error(
                "Please specify a library and entity.\n" +
                "Example: (Cmd) simulate my_library.my_entity [tool_name]")
            return
        self.project.simulate(library, entity, gui=True, tool_name=tool_name)

    @wraps_do_commands
    def do_clean(self, command):
        """Clear the file cache"""
        # Delete libraries from the simulation area
        simpath = self.project.get_simulation_directory()
        if simpath is None:
            log.error('No simulation path is set, aborting operation.')
            return
        log.info('Cleaning simulation folder: ' + simpath)
        for tool_name in self.project.cache.get_tool_names():
            for libname in self.project.cache.get_libraries(tool_name):
                path = os.path.join(simpath, libname)
                if os.path.exists(path):
                    log.info('Removing ' + path)
                    shutil.rmtree(path)
        log.info('...done')
        self.project.cache.initialise_cache()

    @wraps_do_commands
    def do_pwd(self, command):
        print(
            term.colourise('Working directory: ', fg='yellow') +
            term.colourise('{0}', fg='green').format(os.getcwd()))

    @wraps_do_commands
    def do_plugins(self, command):
        simulators = self.project.get_available_simulators()
        synthesisers = self.project.get_available_synthesisers()
        for i, plugin_registry in enumerate([simulators, synthesisers]):
            print(term.yellow(['Simulator Plugins:', 'Synthesis Plugins:'][i]))
            for name, inst in plugin_registry.items():
                plugin_path = sys.modules[inst.__module__].__file__
                plugin_file = os.path.basename(plugin_path)
                print(SEP * 1 + term.darkgray(plugin_file) + '\n' + SEP * 2 +
                      '{:<15}: {:<35}'.format('Plugin Path',
                                              term.green(plugin_path)) + '\n' +
                      SEP * 2 +
                      '{:<15}: {:<35}'.format('Name', term.green(str(name))) +
                      '\n' + SEP * 2 +
                      '{:<15}: {:<35}'.format('Tool Path', [
                          term.red('(not found) ' + str(inst.path)),
                          term.green(str(inst.path))
                      ][inst.installed]))

    @wraps_do_commands
    def do_show_config(self, command):
        """Print out the project settings"""
        available_simulator_string = ''
        for name, inst in self.project.get_available_simulators().items():
            available_simulator_string += (
                SEP * 2 + '{:<15}: ' +
                ['(not found) ' + term.red('{:<35}'),
                 term.green('{:<35}')][inst.installed] + '\n').format(
                     name, inst.path)

        available_synthesiser_string = ''
        for name, inst in self.project.get_available_synthesisers().items():
            available_synthesiser_string += (
                SEP * 2 + '{:<15}: ' +
                ['(not found) ' + term.red('{:<35}'),
                 term.green('{:<35}')][inst.installed] + '\n').format(
                     name, inst.path)

        msg = (
            '\n' + term.yellow(term.bold('System Configuration: ')) +
            term.green('%(options)s') + '\n' +
            term.darkgray(SEP + 'Working directory:\n') + SEP * 2 +
            term.green('%(working_directory)s') + '\n' +
            term.darkgray(SEP + 'Available simulators:\n') +
            available_simulator_string +
            term.darkgray(SEP + 'Available synthesisers:\n') +
            available_synthesiser_string +
            term.darkgray(SEP + 'Simulation libraries:\n') + ''.join(
                (SEP * 2 + '{:<15}: ' + term.green('{:<35}') +
                 '\n').format(k, v) for k, v in
                (self.project.get_simulator_library_dependencies().items())) +
            '\n' + term.yellow(term.bold('Project Configuration: ')) +
            term.green('%(project)s') + '\n' +
            term.darkgray(SEP + 'Simulation directory set to:\n') + SEP * 2 +
            term.green('%(simulation_directory)s') + '\n' +
            term.darkgray(SEP + 'Using the simulation tool:\n') + SEP * 2 +
            term.green('%(simulation_tool_name)s') + '\n' +
            term.darkgray(SEP + 'Synthesis directory set to:\n') + SEP * 2 +
            term.green('%(synthesis_directory)s') + '\n' +
            term.darkgray(SEP + 'Using the synthesis tool:\n') + SEP * 2 +
            term.green('%(synthesis_tool_name)s') + '\n' +
            term.darkgray(SEP + 'Targeting FPGA part:\n') + SEP * 2 +
            term.green('%(fpga_part)s') + '\n' +
            term.darkgray(SEP + 'Using synthesis generic binding:\n') +
            SEP * 2 + term.green('%(synthesis_generics)s') + '\n' +
            term.darkgray(SEP + 'Modelsim Specific Arguments:\n') + SEP * 2 +
            'vsim: ' + term.green('%(modelsim_vsim_args)s') + '\n')

        print(
            msg %
            dict(working_directory=os.getcwd(),
                 options=self.project.get_system_config_path(),
                 simulation_directory=self.project.get_simulation_directory(),
                 simulation_tool_name=self.project.get_simulation_tool_name(),
                 synthesis_directory=self.project.get_synthesis_directory(),
                 synthesis_tool_name=self.project.get_synthesis_tool_name(),
                 fpga_part=self.project.get_fpga_part(),
                 modelsim_vsim_args=self.project.get_tool_arguments(
                     'modelsim', 'simulate'),
                 project='',
                 synthesis_generics=''.join(
                     str(k) + ':' + str(v) + ', '
                     for k, v in self.project.get_generics().items())))

    @wraps_do_commands
    def do_show_tests(self, command):
        """
        Show the tests available in the current project.
        """
        tests = self.project.get_tests()

        if len(tests) == 0:
            log.info('There are no tests available.')
            return

        testUniqueId = 0
        for file_object in tests:
            file_name = os.path.basename(file_object.path)
            print(term.yellow(file_name))
            for test_group in file_object.testsuite:
                for testId, test in enumerate(test_group):
                    if testId == 0:
                        print(SEP + term.green(str(test.__class__.__name__)))
                    doc = test.shortDescription()
                    if doc is None:
                        doc = term.darkred('No description')
                    if testUniqueId in self.test_set:
                        msg = SEP * 2 + '[' + term.blue(
                            'ID ' + str(testUniqueId) + ' ' +
                            test.id().split('.')[-1]) + ']'
                    else:
                        msg = SEP * 2 + term.lightgray('ID ' + str(
                            testUniqueId) + ' ' + test.id().split('.')[-1])
                    print(msg)
                    print(
                        term.darkgray(
                            textwrap.fill(
                                doc,
                                width=80,
                                initial_indent=SEP * 2,
                                subsequent_indent=SEP * 2,
                            )))
                    testUniqueId += 1

    def show_test_selection(self):
        """
        Show the currently selected tests in the current project.
        Add or remove tests using the
        add_tests and remove_tests commands respectively.
        """
        ids_list = list(self.test_set)
        tests = []
        for file_object in self.project.get_tests():
            file_name = os.path.basename(file_object.path)
            print(term.yellow(file_name))
            for test_group in file_object.testsuite:
                for testId, test in enumerate(test_group):
                    if testId == 0:
                        groupName = str(test.__class__.__name__)
                    testName = test.id().split('.')[-1]
                    tests.append((file_name, groupName, testName, test))

        # Filter out any invalid indices
        ids_list = list(filter(lambda x: x < len(tests), ids_list))
        for idx, (fileName, groupName, testName,
                  test) in enumerate([tests[idx] for idx in ids_list]):
            print('{:<5}'.format(str(ids_list[idx]) + ':') + '[' +
                  term.yellow(fileName) + ']' + '[' + term.green(groupName) +
                  ']' + '[' + term.blue(testName) + ']')

    @wraps_do_commands
    def do_add_tests(self, command):
        """
        Add the given tests to the test suite. Tests should be supplied as
        comma separated numbers or numeric ranges, for example:

        add_tests 1-3, 5, 6, 7

        Would add tests 1, 2, 3, 5, 6, 7 to the test suite.

        You can check which tests are available by issuing the show_tests
        command or remove tests that have been added to the suite by issuing
        the remove_tests command.
        """
        try:
            ids = utils.parseRange(command)
        except ValueError:
            log.error('Invalid test range specified: ' + command)
            return
        for testId in ids:
            self.test_set.add(testId)
        self.show_test_selection()

    @wraps_do_commands
    def do_remove_tests(self, command):
        """
        Remove the given tests from the test suite. Tests should be supplied
        as comma separated numbers or numeric ranges, for example:

        remove_tests 1-3, 5, 6, 7

        Would remove tests 1, 2, 3, 5, 6, 7 from the test suite.

        You can check which tests are available by issuing the show_tests
        command or add tests by issuing the add_tests command.
        """
        try:
            ids = utils.parseRange(command)
        except ValueError:
            log.error('Invalid test range specified: ' + command)
            return
        for testId in ids:
            try:
                self.test_set.remove(testId)
            except KeyError:
                pass
        self.show_test_selection()

    @wraps_do_commands
    def do_run_tests(self, command):
        """
        Run the tests that were selected via the add_tests command and report
        the results.
        """
        if len(command) > 0:
            tool_name = command
        else:
            tool_name = None
        self.show_test_selection()
        self.project.run_tests(self.test_set, tool_name=tool_name)