示例#1
0
文件: export.py 项目: happz/tmt
def import_nitrate():
    """ Conditionally import the nitrate module """
    # Need to import nitrate only when really needed. Otherwise we get
    # traceback when nitrate not installed or config file not available.
    # And we want to keep the core tmt package with minimal dependencies.
    try:
        global nitrate, DEFAULT_PRODUCT
        import nitrate
        DEFAULT_PRODUCT = nitrate.Product(name='RHEL Tests')
    except ImportError:
        raise ConvertError("Install nitrate to export tests there.")
    except nitrate.NitrateError as error:
        raise ConvertError(error)
示例#2
0
    def plugin_install(self, name):
        """ Install a vagrant plugin if it's not installed yet.
        """
        plugin = f'{self.executable}-{name}'
        command = ['plugin', 'install']
        try:
            # is it already present?
            return self.run(
                f"bash -c \"{self.executable} {command[0]} list | grep '^{plugin}'\""
            )
        except GeneralError:
            pass

        try:
            # try to install it
            return self.run_vagrant(command[0], command[1], plugin)
        except GeneralError as error:
            # Let's work-around the error handling limitation for now
            # by getting the output manually
            command = ' '.join([self.executable] + command + [plugin])

            out, err = self.run(f"bash -c \"{command}; :\"")

            if re.search(r"Conflicting dependency chains:", err) is None:
                raise error

            raise ConvertError(
                'Dependency conflict detected.\n'
                'Please install vagrant plugins from one source only(hint: `dnf remove vagrant-libvirt`).'
            )
示例#3
0
文件: export.py 项目: pkis/tmt
def import_bugzilla():
    """ Conditionally import the bugzilla module """
    try:
        global bugzilla
        import bugzilla
    except ImportError:
        raise ConvertError(
            "Install 'tmt-test-convert' to link test to the bugzilla.")
示例#4
0
文件: convert.py 项目: pvalena/tmt
def write(path, data):
    """ Write gathered metadata in the fmf format """
    # Make sure there is a metadata tree initialized
    try:
        tree = fmf.Tree(path)
    except fmf.utils.RootError:
        raise ConvertError("Initialize metadata tree using 'fmf init'.")
    # Store all metadata into a new main.fmf file
    fmf_path = os.path.join(path, 'main.fmf')
    try:
        with open(fmf_path, 'w', encoding='utf-8') as fmf_file:
            yaml.safe_dump(
                    data, fmf_file,
                    encoding='utf-8', allow_unicode=True,
                    indent=4, default_flow_style=False)
    except IOError:
        raise ConvertError("Unable to write '{0}'".format(fmf_path))
    echo(style(
        "Metadata successfully stored into '{0}'.".format(fmf_path), fg='red'))
示例#5
0
文件: export.py 项目: pkis/tmt
def bz_set_coverage(bz_instance, bug_ids, case_id):
    """ Set coverage in Bugzilla """
    overall_pass = True
    no_email = 1  # Do not send emails about the change
    get_bz_dict = {
        'ids': bug_ids,
        'include_fields': ['id', 'external_bugs', 'flags']
    }
    bugs_data = bz_instance._proxy.Bug.get(get_bz_dict)
    for bug in bugs_data['bugs']:
        # Process flag (might fail for some types)
        bug_id = bug['id']
        if 'qe_test_coverage+' not in set(
            [x['name'] + x['status'] for x in bug['flags']]):
            try:
                bz_instance._proxy.Flag.update({
                    'ids': [bug_id],
                    'nomail':
                    no_email,
                    'updates': [{
                        'name': 'qe_test_coverage',
                        'status': '+'
                    }]
                })
            except xmlrpc.client.Fault as err:
                log.debug(f"Update flag failed: {err}")
                echo(
                    style(
                        f"Failed to set qe_test_coverage+ flag for BZ#{bug_id}",
                        fg='red'))
        # Process external tracker - should succeed
        current = set([
            int(b['ext_bz_bug_id']) for b in bug['external_bugs']
            if b['ext_bz_id'] == EXTERNAL_TRACKER_ID
        ])
        if case_id not in current:
            query = {
                'bug_ids': [bug_id],
                'nomail':
                no_email,
                'external_bugs': [{
                    'ext_type_id': EXTERNAL_TRACKER_ID,
                    'ext_bz_bug_id': case_id,
                    'ext_description': '',
                }]
            }
            try:
                bz_instance._proxy.ExternalBugs.add_external_bug(query)
            except Exception as err:
                log.debug(f"Link case failed: {err}")
                echo(style(f"Failed to link to BZ#{bug_id}", fg='red'))
                overall_pass = False
    if not overall_pass:
        raise ConvertError("Failed to link the case to bugs.")
示例#6
0
def html_to_markdown(html):
    """ Convert html to markdown """
    try:
        import html2text
        md_handler = html2text.HTML2Text()
    except ImportError:
        raise ConvertError("Install tmt-test-convert to import tests.")

    if html is None:
        markdown = ""
    else:
        markdown = md_handler.handle(html).strip()
    return markdown
示例#7
0
文件: vagrant.py 项目: dhodovsk/tmt
    def run_vagrant_success(self, *args):
        """ Run vagrant command and raise an error if it fails
            see run_vagrant() for args
        """
        self.debug()

        cps = self.run_vagrant(*args)
        if cps.returncode != 0:
            raise ConvertError(f'Failed to run vagrant:{self.eol}\
                  command: {self.hr(args)}{self.eol}\
                  stdout:  {self.hr(cps.stdout)}{self.eol}\
                  stderr:  {self.hr(cps.stderr)}{self.eol}\
                ')
        return cps
示例#8
0
文件: convert.py 项目: mruprich/tmt
def write(path, data):
    """ Write gathered metadata in the fmf format """
    # Put keys into a reasonable order
    extra_keys = ['extra-summary', 'extra-task', 'extra-nitrate']
    sorted_data = dict()
    for key in tmt.base.Test._keys + extra_keys:
        try:
            sorted_data[key] = data[key]
        except KeyError:
            pass
    # Store metadata into a fmf file
    try:
        with open(path, 'w', encoding='utf-8') as fmf_file:
            fmf_file.write(tmt.utils.dict_to_yaml(sorted_data))
    except IOError:
        raise ConvertError("Unable to write '{0}'".format(path))
    echo(style(
        "Metadata successfully stored into '{0}'.".format(path), fg='magenta'))
示例#9
0
def adjust_runtest(path):
    """ Adjust runtest.sh content and permission """

    # Nothing to do if there is no runtest.sh
    if not os.path.exists(path):
        return

    # Remove sourcing of rhts-environment.sh and update beakerlib path
    rhts_line = '. /usr/bin/rhts-environment.sh'
    old_beakerlib_path1 = '. /usr/lib/beakerlib/beakerlib.sh'
    old_beakerlib_path2 = '. /usr/share/rhts-library/rhtslib.sh'
    new_beakerlib_path = '. /usr/share/beakerlib/beakerlib.sh || exit 1\n'
    try:
        with open(path, 'r+') as runtest:
            lines = runtest.readlines()
            runtest.seek(0)
            for line in lines:
                if rhts_line in line:
                    echo(
                        style(
                            "Removing sourcing of 'rhts-environment.sh' "
                            "from 'runtest.sh'.",
                            fg='magenta'))
                elif (old_beakerlib_path1 in line
                      or old_beakerlib_path2 in line):
                    runtest.write(new_beakerlib_path)
                    echo(
                        style(
                            "Replacing old beakerlib path with new one "
                            "in 'runtest.sh'.",
                            fg='magenta'))
                else:
                    runtest.write(line)
            runtest.truncate()
    except IOError:
        raise ConvertError("Unable to read/write '{0}'.".format(path))

    # Make sure the script has correct execute permissions
    try:
        os.chmod(path, 0o755)
    except IOError:
        raise tmt.convert.ConvertError(
            "Could not make '{0}' executable.".format(path))
示例#10
0
def write_markdown(path, content):
    """ Write gathered metadata in the markdown format """
    to_print = ""
    if content['setup']:
        to_print += "# Setup\n" + content['setup'] + '\n\n'
    if content['action'] or content['expected']:
        to_print += "# Test\n\n"
        if content['action']:
            to_print += "## Step\n" + content['action'] + '\n\n'
        if content['expected']:
            to_print += "## Expect\n" + content['expected'] + '\n\n'
    if content['cleanup']:
        to_print += "# Cleanup\n" + content['cleanup'] + '\n'

    try:
        with open(path, 'w', encoding='utf-8') as md_file:
            md_file.write(to_print)
            echo(style(
                f"Test case successfully stored into '{path}'.", fg='magenta'))
    except IOError:
        raise ConvertError(f"Unable to write '{path}'.")
示例#11
0
文件: export.py 项目: pkis/tmt
def create_nitrate_case(test):
    """ Create new nitrate case """
    import_nitrate()

    # Get category from Makefile
    try:
        with open('Makefile', encoding='utf-8') as makefile_file:
            makefile = makefile_file.read()
        category = re.search(r'echo\s+"Type:\s*(.*)"', makefile, re.M).group(1)
    # Default to 'Sanity' if Makefile or Type not found
    except (IOError, AttributeError):
        category = 'Sanity'

    # Create the new test case
    remote_dirname = re.sub('.git$', '', os.path.basename(test.fmf_id['url']))
    if not remote_dirname:
        raise ConvertError("Unable to find git remote url.")
    summary = test.node.get('extra-summary', (remote_dirname or "") +
                            (test.name or "") + ' - ' + (test.summary or ""))
    category = nitrate.Category(name=category, product=DEFAULT_PRODUCT)
    testcase = nitrate.TestCase(summary=summary, category=category)
    echo(style(f"Test case '{testcase.identifier}' created.", fg='blue'))
    return testcase
示例#12
0
def read_manual(plan_id, case_id, disabled, with_script):
    """
    Reads metadata of manual test cases from Nitrate
    """
    nitrate = tmt.export.import_nitrate()
    # Turns off nitrate caching
    nitrate.set_cache_level(0)

    try:
        tree = fmf.Tree(os.getcwd())
    except fmf.utils.RootError:
        raise ConvertError("Initialize metadata tree using 'tmt init'.")

    try:
        if plan_id:
            all_cases = nitrate.TestPlan(int(plan_id)).testcases
            case_ids = [case.id for case in all_cases if not case.automated]
        else:
            case_ids = [int(case_id)]
    except ValueError:
        raise ConvertError('Test plan/case identifier must be an integer.')

    # Create directory to store manual tests in
    old_cwd = os.getcwd()
    os.chdir(tree.root)
    try:
        os.mkdir('Manual')
    except FileExistsError:
        pass

    os.chdir('Manual')

    for case_id in case_ids:
        testcase = nitrate.TestCase(case_id)
        if testcase.status.name != 'CONFIRMED' and not disabled:
            log.debug(
                testcase.identifier + ' skipped (testcase is not CONFIRMED).')
            continue
        if testcase.script is not None and not with_script:
            log.debug(testcase.identifier + ' skipped (script is not empty).')
            continue

        # Filename sanitization
        dir_name = testcase.summary.replace(' ', '_')
        dir_name = dir_name.replace('/', '_')
        try:
            os.mkdir(dir_name)
        except FileExistsError:
            pass

        os.chdir(dir_name)
        echo("Importing the '{0}' test case.".format(dir_name))

        # Test case data
        md_content = read_manual_data(testcase)

        # Test case metadata
        data = read_nitrate_case(testcase)
        data['manual'] = True
        data['test'] = 'test.md'

        write_markdown(os.getcwd() + '/test.md', md_content)
        write(os.getcwd() + '/main.fmf', data)
        os.chdir('..')

    os.chdir(old_cwd)
示例#13
0
文件: convert.py 项目: pvalena/tmt
def read(path, makefile, nitrate, purpose):
    """ Read old metadata from various sources """
    echo(style("Checking the '{0}' directory.".format(path), fg='red'))

    data = dict()

    # Makefile (extract summary, component and duration)
    if makefile:
        echo(style('Makefile ', fg='blue'), nl=False)
        makefile_path = os.path.join(path, 'Makefile')
        try:
            with open(makefile_path, encoding='utf-8') as makefile:
                content = makefile.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(makefile_path))
        echo("found in '{0}'.".format(makefile_path))
        # Test
        test = re.search('export TEST=(.*)\n', content).group(1)
        echo(style('test: ', fg='green') + test)
        # Summary
        data['summary'] = re.search(
            r'echo "Description:\s*(.*)"', content).group(1)
        echo(style('description: ', fg='green') + data['summary'])
        # Component
        data['component'] = re.search(
            r'echo "RunFor:\s*(.*)"', content).group(1)
        echo(style('component: ', fg='green') + data['component'])
        # Duration
        data['duration'] = re.search(
            r'echo "TestTime:\s*(.*)"', content).group(1)
        echo(style('duration: ', fg='green') + data['duration'])

    # Purpose (extract everything after the header as a description)
    if purpose:
        echo(style('Purpose ', fg='blue'), nl=False)
        purpose_path = os.path.join(path, 'PURPOSE')
        try:
            with open(purpose_path, encoding='utf-8') as purpose:
                content = purpose.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(purpose_path))
        echo("found in '{0}'.".format(purpose_path))
        for header in ['PURPOSE', 'Description', 'Author']:
            content = re.sub('^{0}.*\n'.format(header), '', content)
        data['description'] = content.lstrip('\n')
        echo(style('description:', fg='green'))
        echo(data['description'].rstrip('\n'))

    # Nitrate (extract contact, environment and relevancy)
    if nitrate:
        echo(style('Nitrate ', fg='blue'), nl=False)
        if test is None:
            raise ConvertError('No test name detected for nitrate search')
        if TestCase is None:
            raise ConvertError('Need nitrate module to import metadata')
        testcases = list(TestCase.search(script=test))
        if not testcases:
            raise ConvertError("No testcase found for '{0}'.".format(test))
        elif len(testcases) > 1:
            log.warn("Multiple test cases found for '{0}', using {1}".format(
                test, testcases[0].identifier))
        testcase = testcases[0]
        echo("test case found '{0}'.".format(testcase.identifier))
        # Contact
        if testcase.tester:
            data['contact'] = '{} <{}>'.format(
                testcase.tester.name, testcase.tester.email)
            echo(style('contact: ', fg='green') + data['contact'])
        # Environment
        if testcase.arguments:
            data['environment'] = tmt.utils.variables_to_dictionary(
                testcase.arguments)
            echo(style('environment:', fg='green'))
            echo(pprint.pformat(data['environment']))
        # Relevancy
        field = tmt.utils.StructuredField(testcase.notes)
        data['relevancy'] = field.get('relevancy')
        echo(style('relevancy:', fg='green'))
        echo(data['relevancy'].rstrip('\n'))

    log.debug('Gathered metadata:\n' + pprint.pformat(data))
    return data
示例#14
0
文件: export.py 项目: pkis/tmt
def export_to_nitrate(test):
    """ Export fmf metadata to nitrate test cases """
    import_nitrate()
    new_test_created = False

    # Check command line options
    create = test.opt('create')
    general = test.opt('general')
    duplicate = test.opt('duplicate')
    link_bugzilla = test.opt('bugzilla')

    if link_bugzilla:
        import_bugzilla()
        try:
            bz_instance = bugzilla.Bugzilla(url=BUGZILLA_XMLRPC_URL)
        except Exception as exc:
            log.debug(traceback.format_exc())
            raise ConvertError("Couldn't initialize the Bugzilla client.",
                               original=exc)
        if not bz_instance.logged_in:
            raise ConvertError(
                "Not logged to Bugzilla, check `man bugzilla` section "
                "'AUTHENTICATION CACHE AND API KEYS'.")

    # Check nitrate test case
    try:
        nitrate_id = test.node.get('extra-nitrate')[3:]
        nitrate_case = nitrate.TestCase(int(nitrate_id))
        nitrate_case.summary  # Make sure we connect to the server now
        echo(style(f"Test case '{nitrate_case.identifier}' found.", fg='blue'))
    except TypeError:
        # Create a new nitrate test case
        if create:
            nitrate_case = None
            # Check for existing Nitrate tests with the same fmf id
            if not duplicate:
                testcases = _nitrate_find_fmf_testcases(test)
                try:
                    # Select the first found testcase if any
                    nitrate_case = next(testcases)
                except StopIteration:
                    pass
            if not nitrate_case:
                nitrate_case = create_nitrate_case(test)
            new_test_created = True
            # Newly created tmt tests have special format summary
            test._metadata['extra-summary'] = nitrate_case.summary
        else:
            raise ConvertError(f"Nitrate test case id not found for {test}"
                               " (You can use --create option to enforce"
                               " creating testcases)")
    except (nitrate.NitrateError, gssapi.raw.misc.GSSError) as error:
        raise ConvertError(error)

    # Summary
    summary = (test._metadata.get('extra-summary')
               or test._metadata.get('extra-task') or test.summary
               or test.name)
    if summary:
        nitrate_case.summary = summary
        echo(style('summary: ', fg='green') + summary)
    else:
        raise ConvertError("Nitrate case summary could not be determined.")

    # Script
    if test.node.get('extra-task'):
        nitrate_case.script = test.node.get('extra-task')
        echo(style('script: ', fg='green') + test.node.get('extra-task'))

    # Components
    # First remove any components that are already there
    nitrate_case.components.clear()
    if general:
        # Remove also all general plans linked to testcase
        for nitrate_plan in [plan for plan in nitrate_case.testplans]:
            if nitrate_plan.type.name == "General":
                nitrate_case.testplans.remove(nitrate_plan)
    # Then add fmf ones
    if test.component:
        echo(style('components: ', fg='green') + ' '.join(test.component))
        for component in test.component:
            try:
                nitrate_case.components.add(
                    nitrate.Component(name=component,
                                      product=DEFAULT_PRODUCT.id))
            except nitrate.xmlrpc_driver.NitrateError as error:
                log.debug(error)
                echo(style(f"Failed to add component '{component}'.",
                           fg='red'))
            if general:
                try:
                    general_plan = find_general_plan(component)
                    nitrate_case.testplans.add(general_plan)
                except nitrate.NitrateError as error:
                    log.debug(error)
                    echo(
                        style(
                            f"Failed to link general test plan for '{component}'.",
                            fg='red'))

    # Tags
    nitrate_case.tags.clear()
    # Convert 'tier' attribute into a Tier tag
    if test.tier is not None:
        test.tag.append(f"Tier{test.tier}")
    # Add special fmf-export tag
    test.tag.append('fmf-export')
    nitrate_case.tags.add([nitrate.Tag(tag) for tag in test.tag])
    echo(style('tags: ', fg='green') + ' '.join(set(test.tag)))

    # Default tester
    if test.contact:
        # Need to pick one value, so picking the first contact
        email_address = email.utils.parseaddr(test.contact[0])[1]
        # TODO handle nitrate user not existing and other possible exceptions
        nitrate_case.tester = nitrate.User(email_address)
        echo(style('default tester: ', fg='green') + email_address)

    # Duration
    nitrate_case.time = test.duration
    echo(style('estimated time: ', fg='green') + test.duration)

    # Manual
    nitrate_case.automated = not test.manual
    echo(style('automated: ', fg='green') + ['auto', 'manual'][test.manual])

    # Status
    current_status = nitrate_case.status
    # Enable enabled tests
    if test.enabled:
        nitrate_case.status = nitrate.CaseStatus('CONFIRMED')
        echo(style('status: ', fg='green') + 'CONFIRMED')
    # Disable disabled tests which are CONFIRMED
    elif current_status == nitrate.CaseStatus('CONFIRMED'):
        nitrate_case.status = nitrate.CaseStatus('DISABLED')
        echo(style('status: ', fg='green') + 'DISABLED')
    # Keep disabled tests in their states
    else:
        echo(style('status: ', fg='green') + str(current_status))

    # Environment
    if test.environment:
        environment = ' '.join(tmt.utils.shell_variables(test.environment))
        nitrate_case.arguments = environment
        echo(style('arguments: ', fg='green') + environment)
    else:
        # FIXME unable clear to set empty arguments
        # (possibly error in xmlrpc, BZ#1805687)
        nitrate_case.arguments = ' '
        echo(style('arguments: ', fg='green') + "' '")

    # Structured Field
    struct_field = tmt.utils.StructuredField(nitrate_case.notes)
    echo(style('Structured Field: ', fg='green'))

    # Mapping of structured field sections to fmf case attributes
    section_to_attr = {
        'description': test.summary,
        'purpose-file': test.description,
        'hardware': test.node.get('extra-hardware'),
        'pepa': test.node.get('extra-pepa'),
    }
    for section, attribute in section_to_attr.items():
        if attribute is None:
            try:
                struct_field.remove(section)
            except tmt.utils.StructuredFieldError:
                pass
        else:
            struct_field.set(section, attribute)
            echo(style(section + ': ', fg='green') + attribute.strip())

    # fmf identifer
    fmf_id = tmt.utils.dict_to_yaml(test.fmf_id)
    struct_field.set('fmf', fmf_id)
    echo(style('fmf id:\n', fg='green') + fmf_id.strip())

    # Warning
    if WARNING not in struct_field.header():
        struct_field.header(WARNING + struct_field.header())
        echo(style('Add migration warning to the test case notes.',
                   fg='green'))

    # Saving case.notes with edited StructField
    nitrate_case.notes = struct_field.save()

    # Export manual test instructions from *.md file to nitrate as html
    md_path = return_markdown_file()
    if os.path.exists(md_path):
        step, expect, setup, cleanup = convert_manual_to_nitrate(md_path)
        nitrate.User()._server.TestCase.store_text(nitrate_case.id, step,
                                                   expect, setup, cleanup)
        echo(style(f"manual steps:", fg='green') + f" found in {md_path}")

    # Append id of newly created nitrate case to its file
    if new_test_created:
        echo(style(f"Append the nitrate test case id.", fg='green'))
        try:
            with test.node as data:
                data["extra-nitrate"] = nitrate_case.identifier
        except AttributeError:
            # FIXME: Remove this deprecated code after fmf support
            # for storing modified data is released long enough
            file_path = test.node.sources[-1]
            try:
                with open(file_path, encoding='utf-8', mode='a+') as file:
                    file.write(f"extra-nitrate: {nitrate_case.identifier}\n")
            except IOError:
                raise ConvertError("Unable to open '{0}'.".format(file_path))

    # List of bugs test verifies
    verifies_bug_ids = []
    for link in test.link:
        try:
            verifies_bug_ids.append(
                int(re.search(RE_BUGZILLA_URL, link['verifies']).group(1)))
        except Exception as err:
            log.debug(err)

    # Add bugs to the Nitrate case
    for bug_id in verifies_bug_ids:
        nitrate_case.bugs.add(nitrate.Bug(bug=int(bug_id)))

    # Update nitrate test case
    nitrate_case.update()
    echo(
        style("Test case '{0}' successfully exported to nitrate.".format(
            nitrate_case.identifier),
              fg='magenta'))

    # Optionally link Bugzilla to Nitrate case
    if link_bugzilla and verifies_bug_ids:
        try:
            bz_set_coverage(bz_instance, verifies_bug_ids,
                            int(nitrate_case.id))
            echo(
                style("Linked to Bugzilla: {}.".format(" ".join(
                    [f"BZ#{bz_id}" for bz_id in verifies_bug_ids])),
                      fg='magenta'))
        except Exception as err:
            raise ConvertError("Couldn't update bugs", original=err)
示例#15
0
def read(path, makefile, nitrate, purpose):
    """
    Read old metadata from various sources

    Returns tuple (common_data, individual_data) where 'common_data' are
    metadata which belong to main.fmf and 'individual_data' contains
    data for individual testcases (if multiple nitrate testcases found).
    """

    data = dict()
    echo("Checking the '{0}' directory.".format(path))

    # Makefile (extract summary, test, duration and requires)
    if makefile:
        echo(style('Makefile ', fg='blue'), nl=False)
        makefile_path = os.path.join(path, 'Makefile')
        try:
            with open(makefile_path, encoding='utf-8') as makefile:
                content = makefile.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(makefile_path))
        echo("found in '{0}'.".format(makefile_path))
        # Beaker task name
        try:
            beaker_task = re.search('export TEST=(.*)\n', content).group(1)
            echo(style('task: ', fg='green') + beaker_task)
            data['extra-task'] = beaker_task
        except AttributeError:
            raise ConvertError("Unable to parse 'TEST' from the Makefile.")
        # Summary
        data['summary'] = re.search(r'echo "Description:\s*(.*)"',
                                    content).group(1)
        echo(style('summary: ', fg='green') + data['summary'])
        # Test script
        data['test'] = re.search('^run:.*\n\t(.*)$', content, re.M).group(1)
        echo(style('test: ', fg='green') + data['test'])
        # Component
        data['component'] = re.search(r'echo "RunFor:\s*(.*)"',
                                      content).group(1).split()
        echo(style('component: ', fg='green') + ' '.join(data['component']))
        # Duration
        try:
            data['duration'] = re.search(r'echo "TestTime:\s*(.*)"',
                                         content).group(1)
            echo(style('duration: ', fg='green') + data['duration'])
        except AttributeError:
            pass
        # Requires and RhtsRequires (optional)
        requires = re.findall(r'echo "(?:Rhts)?Requires:\s*(.*)"', content)
        if requires:
            data['require'] = [
                require for line in requires for require in line.split()
            ]
            echo(style('require: ', fg='green') + ' '.join(data['require']))

    # Purpose (extract everything after the header as a description)
    if purpose:
        echo(style('Purpose ', fg='blue'), nl=False)
        purpose_path = os.path.join(path, 'PURPOSE')
        try:
            with open(purpose_path, encoding='utf-8') as purpose:
                content = purpose.read()
            echo("found in '{0}'.".format(purpose_path))
            for header in ['PURPOSE', 'Description', 'Author']:
                content = re.sub('^{0}.*\n'.format(header), '', content)
            data['description'] = content.lstrip('\n')
            echo(style('description:', fg='green'))
            echo(data['description'].rstrip('\n'))
        except IOError:
            echo("not found.")

    # Nitrate (extract contact, environment and relevancy)
    if nitrate:
        common_data, individual_data = read_nitrate(beaker_task, data)
    else:
        common_data = data
        individual_data = []

    log.debug('Common metadata:\n' + pprint.pformat(common_data))
    log.debug('Individual metadata:\n' + pprint.pformat(individual_data))
    return common_data, individual_data
示例#16
0
def read_nitrate(beaker_task, common_data):
    """ Read old metadata from nitrate test cases """

    # Check test case, make sure nitrate is available
    echo(style('Nitrate ', fg='blue'), nl=False)
    if beaker_task is None:
        raise ConvertError('No test name detected for nitrate search')
    if TestCase is None:
        raise ConvertError('Need nitrate module to import metadata')

    # Find testcases that have CONFIRMED status
    testcases = list(TestCase.search(script=beaker_task, case_status=2))
    if not testcases:
        echo("No testcase found for '{0}'.".format(beaker_task))
        return common_data, []
    elif len(testcases) > 1:
        echo("Multiple test cases found for '{0}'.".format(beaker_task))

    # Process individual test cases
    individual_data = list()
    for testcase in testcases:
        data = dict()
        echo("test case found '{0}'.".format(testcase.identifier))
        # Test identifier
        data['extra-nitrate'] = testcase.identifier
        # Beaker task name (taken from summary)
        if testcase.summary:
            data['extra-summary'] = testcase.summary
            echo(style('extra-summary: ', fg='green') + data['extra-summary'])
        # Contact
        if testcase.tester:
            data['contact'] = '{} <{}>'.format(testcase.tester.name,
                                               testcase.tester.email)
            echo(style('contact: ', fg='green') + data['contact'])
        # Environment
        if testcase.arguments:
            data['environment'] = tmt.utils.variables_to_dictionary(
                testcase.arguments)
            echo(style('environment:', fg='green'))
            echo(pprint.pformat(data['environment']))
        # Tags
        if testcase.tags:
            data['tag'] = sorted([
                tag.name for tag in testcase.tags if tag.name != 'fmf-export'
            ])
            echo(style('tag: ', fg='green') + str(data['tag']))
        # Component
        data['component'] = [comp.name for comp in testcase.components]
        echo(style('component: ', fg='green') + ' '.join(data['component']))
        # Status
        data['enabled'] = testcase.status.name == "CONFIRMED"
        echo(style('enabled: ', fg='green') + str(data['enabled']))
        # Relevancy
        field = tmt.utils.StructuredField(testcase.notes)
        try:
            relevancy = field.get('relevancy')
            if relevancy:
                data['relevancy'] = relevancy
                echo(style('relevancy:', fg='green'))
                echo(data['relevancy'].rstrip('\n'))
        except tmt.utils.StructuredFieldError:
            pass
        # Extras: [pepa] and [hardware]
        try:
            extra_pepa = field.get('pepa')
            if extra_pepa:
                data['extra-pepa'] = extra_pepa
                echo(style('extra-pepa:', fg='green'))
                echo(data['extra-pepa'].rstrip('\n'))
        except tmt.utils.StructuredFieldError:
            pass
        try:
            extra_hardware = field.get('hardware')
            if extra_hardware:
                data['extra-hardware'] = extra_hardware
                echo(style('extra-hardware:', fg='green'))
                echo(data['extra-hardware'].rstrip('\n'))
        except tmt.utils.StructuredFieldError:
            pass
        individual_data.append(data)

    # Find common data from individual test cases
    common_candidates = dict()
    histogram = dict()
    for testcase in individual_data:
        if individual_data.index(testcase) == 0:
            common_candidates = copy.copy(testcase)
            for key in testcase:
                histogram[key] = 1
        else:
            for key, value in testcase.items():
                if key in common_candidates:
                    if value != common_candidates[key]:
                        common_candidates.pop(key)
                if key in histogram:
                    histogram[key] += 1

    for key in histogram:
        if key in common_candidates and histogram[key] < len(individual_data):
            common_candidates.pop(key)

    # Add common data to main.fmf
    for key, value in common_candidates.items():
        common_data[key] = value

    # If there is only single testcase found there is no need to continue
    if len(individual_data) <= 1:
        return common_data, []

    # Remove common data from individual fmfs
    for common_key in common_candidates:
        for testcase in individual_data:
            if common_key in testcase:
                testcase.pop(common_key)

    return common_data, individual_data
示例#17
0
文件: convert.py 项目: psss/tmt
def read(path, makefile, restraint, nitrate, purpose, disabled, types):
    """
    Read old metadata from various sources

    Returns tuple (common_data, individual_data) where 'common_data' are
    metadata which belong to main.fmf and 'individual_data' contains
    data for individual testcases (if multiple nitrate testcases found).
    """

    echo("Checking the '{0}' directory.".format(path))

    # Make sure there is a metadata tree initialized
    try:
        tree = fmf.Tree(path)
    except fmf.utils.RootError:
        raise ConvertError("Initialize metadata tree using 'tmt init'.")

    # Ascertain if datafile is of type Makefile or metadata
    makefile_file = None
    restraint_file = None
    filename = None

    files = \
        [f for f in os.listdir(path)
         if os.path.isfile(os.path.join(path, f))]

    # Ascertain which file to use based on cmd arg.
    # If both are false raise an assertion.
    # If both are true then default to using
    # the restraint metadata file.
    # Raise an assertion if the file is not found.
    if not makefile and not restraint:
        raise ConvertError("Please specify either a "
                           "Makefile or Restraint file.")
    elif makefile and restraint:
        if 'metadata' in files:
            filename = 'metadata'
            restraint_file = True
            echo(style('Restraint file ', fg='blue'), nl=False)
        elif 'Makefile' in files:
            filename = 'Makefile'
            makefile_file = True
            echo(style('Makefile ', fg='blue'), nl=False)
        else:
            raise ConvertError("Unable to find any metadata file.")
    elif makefile:
        if 'Makefile' not in files:
            raise ConvertError("Unable to find Makefile")
        else:
            filename = 'Makefile'
            makefile_file = True
            echo(style('Makefile ', fg='blue'), nl=False)
    elif restraint:
        if 'metadata' not in files:
            raise ConvertError("Unable to find restraint metadata file")
        else:
            filename = 'metadata'
            restraint_file = True
            echo(style('Restraint ', fg='blue'), nl=False)

    # Open the datafile
    if restraint_file or makefile_file:
        datafile_path = os.path.join(path, filename)
        try:
            with open(datafile_path, encoding='utf-8') as datafile_file:
                datafile = datafile_file.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(datafile_path))
        echo("found in '{0}'.".format(datafile_path))

    # If testinfo.desc exists read it to preserve content and remove it
    testinfo_path = os.path.join(path, 'testinfo.desc')
    if os.path.isfile(testinfo_path):
        try:
            with open(testinfo_path, encoding='utf-8') as testinfo:
                old_testinfo = testinfo.read()
                os.remove(testinfo_path)
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(testinfo_path))
    else:
        old_testinfo = None

    # Make Makefile 'makeable' without extra dependecies
    # (replace targets, make include optional and remove rhts-lint)
    if makefile_file:
        datafile = datafile.replace('$(METADATA)', 'testinfo.desc')
        datafile = re.sub(r'^include /usr/share/rhts/lib/rhts-make.include',
                          '-include /usr/share/rhts/lib/rhts-make.include',
                          datafile,
                          flags=re.MULTILINE)
        datafile = re.sub('.*rhts-lint.*', '', datafile)
        # Create testinfo.desc file with resolved variables
        try:
            subprocess.run(["make", "testinfo.desc", "-C", path, "-f", "-"],
                           input=datafile,
                           check=True,
                           encoding='utf-8',
                           stdout=subprocess.DEVNULL)
        except FileNotFoundError:
            raise ConvertError("Install tmt-test-convert to "
                               "convert metadata from {0}.".format(filename))
        except subprocess.CalledProcessError:
            raise ConvertError(
                "Failed to convert metadata using 'make testinfo.desc'.")

        # Read testinfo.desc
        try:
            with open(testinfo_path, encoding='utf-8') as testinfo_file:
                testinfo = testinfo_file.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(testinfo_path))

    # restraint
    if restraint_file:
        beaker_task, data = \
            read_datafile(path, filename, datafile, types)

    # Makefile (extract summary, test, duration and requires)
    else:
        beaker_task, data = \
            read_datafile(path, filename, datafile, types, testinfo)

        # Warn if makefile has extra lines in run and build targets
        def target_content(target):
            """ Extract lines from the target content """
            regexp = rf"^{target}:.*\n((?:\t[^\n]*\n?)*)"
            target = re.search(regexp, datafile, re.M).group(1)
            return [line.strip('\t') for line in target.splitlines()]

        run_target_list = target_content("run")
        run_target_list.remove(data["test"])
        if run_target_list:
            echo(
                style(f"warn: Extra lines detected in the 'run' target:",
                      fg="yellow"))
            for line in run_target_list:
                echo(f"    {line}")

        build_target_list = target_content("build")
        if len(build_target_list) > 1:
            echo(
                style(f"warn: Multiple lines detected in the 'build' target:",
                      fg="yellow"))
            for line in build_target_list:
                echo(f"    {line}")

        # Restore the original testinfo.desc content (if existed)
        if old_testinfo:
            try:
                with open(testinfo_path, 'w', encoding='utf-8') as testinfo:
                    testinfo.write(old_testinfo)
            except IOError:
                raise ConvertError(
                    "Unable to write '{0}'.".format(testinfo_path))
        # Remove created testinfo.desc otherwise
        else:
            os.remove(testinfo_path)

    # Purpose (extract everything after the header as a description)
    if purpose:
        echo(style('Purpose ', fg='blue'), nl=False)
        purpose_path = os.path.join(path, 'PURPOSE')
        try:
            with open(purpose_path, encoding='utf-8') as purpose:
                content = purpose.read()
            echo("found in '{0}'.".format(purpose_path))
            for header in ['PURPOSE', 'Description', 'Author']:
                content = re.sub('^{0}.*\n'.format(header), '', content)
            data['description'] = content.lstrip('\n')
            echo(style('description:', fg='green'))
            echo(data['description'].rstrip('\n'))
        except IOError:
            echo("not found.")

    # Nitrate (extract contact, environment and relevancy)
    if nitrate:
        common_data, individual_data = read_nitrate(beaker_task, data,
                                                    disabled)
    else:
        common_data = data
        individual_data = []

    # Remove keys which are inherited from parent
    parent_path = os.path.dirname(path.rstrip('/'))
    parent_name = '/' + os.path.relpath(parent_path, tree.root)
    parent = tree.find(parent_name)
    if parent:
        for test in [common_data] + individual_data:
            for key in list(test):
                if parent.get(key) == test[key]:
                    test.pop(key)

    log.debug('Common metadata:\n' + pprint.pformat(common_data))
    log.debug('Individual metadata:\n' + pprint.pformat(individual_data))
    return common_data, individual_data
示例#18
0
文件: export.py 项目: happz/tmt
def export_to_nitrate(test, create, general):
    """ Export fmf metadata to nitrate test cases """
    import_nitrate()

    new_test_created = False
    # Check nitrate test case
    try:
        nitrate_id = test.node.get('extra-nitrate')[3:]
        nitrate_case = nitrate.TestCase(int(nitrate_id))
        echo(style(f"Test case '{nitrate_case.identifier}' found.", fg='blue'))
    except TypeError:
        # Create a new nitrate test case
        if create:
            nitrate_case = create_nitrate_case(test)
            new_test_created = True
        else:
            raise ConvertError("Nitrate test case id not found.")

    # Summary
    summary = test.node.get('extra-summary',
                            test.node.get('extra-task', test.summary))
    if summary:
        nitrate_case.summary = summary
        echo(style('summary: ', fg='green') + summary)
    else:
        raise ConvertError("Nitrate case summary could not be determined.")

    # Script
    if test.node.get('extra-task'):
        nitrate_case.script = test.node.get('extra-task')
        echo(style('script: ', fg='green') + test.node.get('extra-task'))

    # Components
    # First remove any components that are already there
    nitrate_case.components.clear()
    # Then add fmf ones
    if test.component:
        echo(style('components: ', fg='green') + ' '.join(test.component))
        for component in test.component:
            try:
                nitrate_case.components.add(
                    nitrate.Component(name=component,
                                      product=DEFAULT_PRODUCT.id))
            except nitrate.xmlrpc_driver.NitrateError as error:
                log.debug(error)
                echo(style(f"Failed to add component '{component}'.",
                           fg='red'))
            if general:
                try:
                    general_plan = find_general_plan(component)
                    nitrate_case.testplans.add(general_plan)
                except nitrate.NitrateError as error:
                    log.debug(error)
                    echo(
                        style(
                            f"Failed to link general test plan for '{component}'.",
                            fg='red'))

    # Tags
    nitrate_case.tags.clear()
    # Convert 'tier' attribute into a Tier tag
    if test.tier is not None:
        test.tag.append(f"Tier{test.tier}")
    # Add special fmf-export tag
    test.tag.append('fmf-export')
    nitrate_case.tags.add([nitrate.Tag(tag) for tag in test.tag])
    echo(style('tags: ', fg='green') + ' '.join(set(test.tag)))

    # Default tester
    if test.contact:
        # Need to pick one value, so picking the first contact
        email_address = email.utils.parseaddr(test.contact[0])[1]
        # TODO handle nitrate user not existing and other possible exceptions
        nitrate_case.tester = nitrate.User(email_address)
        echo(style('default tester: ', fg='green') + email_address)

    # Duration
    nitrate_case.time = test.duration
    echo(style('estimated time: ', fg='green') + test.duration)

    # Status
    current_status = nitrate_case.status
    # Enable enabled tests
    if test.enabled:
        nitrate_case.status = nitrate.CaseStatus('CONFIRMED')
        echo(style('status: ', fg='green') + 'CONFIRMED')
    # Disable disabled tests which are CONFIRMED
    elif current_status == nitrate.CaseStatus('CONFIRMED'):
        nitrate_case.status = nitrate.CaseStatus('DISABLED')
        echo(style('status: ', fg='green') + 'DISABLED')
    # Keep disabled tests in their states
    else:
        echo(style('status: ', fg='green') + str(current_status))

    # Environment
    if test.environment:
        environment = ' '.join(tmt.utils.shell_variables(test.environment))
        nitrate_case.arguments = environment
        echo(style('arguments: ', fg='green') + environment)
    else:
        # FIXME unable clear to set empty arguments
        # (possibly error in xmlrpc, BZ#1805687)
        nitrate_case.arguments = ' '
        echo(style('arguments: ', fg='green') + "' '")

    # Structured Field
    struct_field = tmt.utils.StructuredField(nitrate_case.notes)
    echo(style('Structured Field: ', fg='green'))

    # Mapping of structured field sections to fmf case attributes
    section_to_attr = {
        'relevancy': test.relevancy,
        'description': test.summary,
        'purpose-file': test.description,
        'hardware': test.node.get('extra-hardware'),
        'pepa': test.node.get('extra-pepa'),
    }
    for section, attribute in section_to_attr.items():
        if attribute is None:
            try:
                struct_field.remove(section)
            except tmt.utils.StructuredFieldError:
                pass
        else:
            struct_field.set(section, attribute)
            echo(style(section + ': ', fg='green') + attribute.strip())

    # fmf identifer
    fmf_id = test.fmf_id
    struct_field.set('fmf', yaml.dump(fmf_id))
    echo(style('fmf id:\n', fg='green') + yaml.dump(fmf_id).strip())

    # Warning
    if WARNING not in struct_field.header():
        struct_field.header(WARNING + struct_field.header())
        echo(style('Added warning about porting to case notes.', fg='green'))

    # Saving case.notes with edited StructField
    nitrate_case.notes = struct_field.save()

    # Update nitrate test case
    nitrate_case.update()
    echo(
        style("Test case '{0}' successfully exported to nitrate.".format(
            nitrate_case.identifier),
              fg='magenta'))

    # Write id of newly created nitrate case to its file
    if new_test_created:
        fmf_file_path = test.node.sources[-1]
        try:
            with open(fmf_file_path, encoding='utf-8') as fmf_file:
                content = yaml.safe_load(fmf_file)
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(fmf_file_path))

        content['extra-nitrate'] = nitrate_case.identifier
        tmt.convert.write(fmf_file_path, content)
示例#19
0
def read_nitrate(beaker_task, common_data, disabled):
    """ Read old metadata from nitrate test cases """

    # Need to import nitrate only when really needed. Otherwise we get
    # traceback when nitrate is not installed or config file not available.
    try:
        import nitrate
        import gssapi
    except ImportError:
        raise ConvertError('Install tmt-test-convert to import metadata.')

    # Check test case
    echo(style('Nitrate ', fg='blue'), nl=False)
    if beaker_task is None:
        raise ConvertError('No test name detected for nitrate search')

    # Find all testcases
    try:
        if disabled:
            testcases = list(nitrate.TestCase.search(script=beaker_task))
        # Find testcases that do not have 'DISABLED' status
        else:
            testcases = list(nitrate.TestCase.search(
                script=beaker_task, case_status__in=[1, 2, 4]))
    except (nitrate.NitrateError, gssapi.raw.misc.GSSError) as error:
        raise ConvertError(error)
    if not testcases:
        echo("No {0}testcase found for '{1}'.".format(
            '' if disabled else 'non-disabled ', beaker_task))
        return common_data, []
    elif len(testcases) > 1:
        echo("Multiple test cases found for '{0}'.".format(beaker_task))

    # Process individual test cases
    individual_data = list()
    md_content = dict()
    for testcase in testcases:
        # Testcase data must be fetched due to
        # https://github.com/psss/python-nitrate/issues/24
        testcase._fetch()
        data = read_nitrate_case(testcase, common_data)
        individual_data.append(data)
        # Check testcase for manual data
        md_content_tmp = read_manual_data(testcase)
        if any(md_content_tmp.values()):
            md_content = md_content_tmp

    # Write md file if there is something to write
    # or try to remove if there isn't.
    md_path = os.getcwd() + '/test.md'
    if md_content:
        write_markdown(md_path, md_content)
    else:
        try:
            os.remove(md_path)
            echo(style(f"Test case file '{md_path}' "
                       "successfully removed.", fg='magenta'))
        except FileNotFoundError:
            pass
        except IOError:
            raise ConvertError(
                "Unable to remove '{0}'.".format(md_path))

    # Merge environment from Makefile and Nitrate
    if 'environment' in common_data:
        for case in individual_data:
            if 'environment' in case:
                case_environment = case['environment']
                case['environment'] = common_data['environment'].copy()
                case['environment'].update(case_environment)

    # Merge description from PURPOSE with header/footer from Nitrate notes
    for testcase in individual_data:
        if 'description' in common_data:
            testcase['description'] = common_data['description'] + \
                testcase['description']

    if 'description' in common_data:
        common_data.pop('description')

    # Find common data from individual test cases
    common_candidates = dict()
    histogram = dict()
    for testcase in individual_data:
        if individual_data.index(testcase) == 0:
            common_candidates = copy.copy(testcase)
            for key in testcase:
                histogram[key] = 1
        else:
            for key, value in testcase.items():
                if key in common_candidates:
                    if value != common_candidates[key]:
                        common_candidates.pop(key)
                if key in histogram:
                    histogram[key] += 1

    for key in histogram:
        if key in common_candidates and histogram[key] < len(individual_data):
            common_candidates.pop(key)

    # Add common data to main.fmf
    for key, value in common_candidates.items():
        common_data[key] = value

    # If there is only single testcase found there is no need to continue
    if len(individual_data) <= 1:
        return common_data, []

    # Remove common data from individual fmfs
    for common_key in common_candidates:
        for testcase in individual_data:
            if common_key in testcase:
                testcase.pop(common_key)

    return common_data, individual_data
示例#20
0
def read(path, makefile, nitrate, purpose, disabled):
    """
    Read old metadata from various sources

    Returns tuple (common_data, individual_data) where 'common_data' are
    metadata which belong to main.fmf and 'individual_data' contains
    data for individual testcases (if multiple nitrate testcases found).
    """

    data = dict(framework='beakerlib')
    echo("Checking the '{0}' directory.".format(path))

    # Make sure there is a metadata tree initialized
    try:
        tree = fmf.Tree(path)
    except fmf.utils.RootError:
        raise ConvertError("Initialize metadata tree using 'tmt init'.")

    # Makefile (extract summary, test, duration and requires)
    if makefile:
        echo(style('Makefile ', fg='blue'), nl=False)
        makefile_path = os.path.join(path, 'Makefile')
        try:
            with open(makefile_path, encoding='utf-8') as makefile_file:
                makefile = makefile_file.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(makefile_path))
        echo("found in '{0}'.".format(makefile_path))

        # If testinfo.desc exists read it to preserve content and remove it
        testinfo_path = os.path.join(path, 'testinfo.desc')
        if os.path.isfile(testinfo_path):
            try:
                with open(testinfo_path, encoding='utf-8') as testinfo:
                    old_testinfo = testinfo.read()
                    os.remove(testinfo_path)
            except IOError:
                raise ConvertError(
                    "Unable to open '{0}'.".format(testinfo_path))
        else:
            old_testinfo = None

        # Make Makefile 'makeable' without extra dependecies
        # (replace targets, make include optional and remove rhts-lint)
        makefile = makefile.replace('$(METADATA)', 'testinfo.desc')
        makefile = re.sub(
            r'^include /usr/share/rhts/lib/rhts-make.include',
            '-include /usr/share/rhts/lib/rhts-make.include',
            makefile, flags=re.MULTILINE)
        makefile = re.sub('.*rhts-lint.*', '', makefile)

        # Create testinfo.desc file with resolved variables
        try:
            process = subprocess.run(
                ["make", "testinfo.desc", "-C", path, "-f", "-"],
                input=makefile, check=True, encoding='utf-8',
                stdout=subprocess.DEVNULL)
        except FileNotFoundError:
            raise ConvertError(
                "Install tmt-test-convert to convert metadata from Makefile.")
        except subprocess.CalledProcessError:
            raise ConvertError(
                "Failed to convert metadata using 'make testinfo.desc'.")

        # Read testinfo.desc
        try:
            with open(testinfo_path, encoding='utf-8') as testinfo_file:
                testinfo = testinfo_file.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(testinfo_path))

        # Beaker task name
        try:
            beaker_task = re.search(r'Name:\s*(.*)\n', testinfo).group(1)
            echo(style('task: ', fg='green') + beaker_task)
            data['extra-task'] = beaker_task
            data['extra-summary'] = beaker_task
        except AttributeError:
            raise ConvertError("Unable to parse 'Name' from testinfo.desc.")
        # Summary
        try:
            data['summary'] = re.search(
                r'^Description:\s*(.*)\n', testinfo, re.M).group(1)
            echo(style('summary: ', fg='green') + data['summary'])
        except AttributeError:
            pass
        # Test script
        try:
            data['test'] = re.search(
                r'^run:.*\n\t(.*)$', makefile, re.M).group(1)
            echo(style('test: ', fg='green') + data['test'])
        except AttributeError:
            raise ConvertError("Makefile is missing the 'run' target.")
        # Contact
        try:
            data['contact'] = re.search(
                r'^Owner:\s*(.*)', testinfo, re.M).group(1)
            echo(style('contact: ', fg='green') + data['contact'])
        except AttributeError:
            pass
        # Component
        try:
            data['component'] = re.search(
                r'^RunFor:\s*(.*)', testinfo, re.M).group(1).split()
            echo(style('component: ', fg='green') +
                 ' '.join(data['component']))
        except AttributeError:
            pass
        # Duration
        try:
            data['duration'] = re.search(
                r'^TestTime:\s*(.*)', testinfo, re.M).group(1)
            echo(style('duration: ', fg='green') + data['duration'])
        except AttributeError:
            pass
        # Environment
        variables = re.findall(r'^Environment:\s*(.*)', testinfo, re.M)
        if variables:
            data['environment'] = {}
            for variable in variables:
                key, value = variable.split('=', maxsplit=1)
                data['environment'][key] = value
            echo(style('environment:', fg='green'))
            echo(pprint.pformat(data['environment']))
        # RhtsRequires (optional) goes to require
        requires = re.findall(r'^RhtsRequires:\s*(.*)', testinfo, re.M)
        if requires:
            data['require'] = [
                require for line in requires for require in line.split()]
            echo(style('require: ', fg='green') + ' '.join(data['require']))

        # Requires (optional) goes to recommend
        recommends = re.findall(r'^Requires:\s*(.*)', testinfo, re.M)
        if recommends:
            data['recommend'] = [
                recommend for line in recommends for recommend in line.split()]
            echo(
                style('recommend: ', fg='green') + ' '.join(data['recommend']))

        # Multihost (from Type) -> Add tag for now
        try:
            mkfile_type = re.search(r'^Type:\s*(.*)', testinfo, re.M).group(1)
            if "Multihost" in mkfile_type:
                data['tag'] = ['multihost']
                echo(
                    style('multihost: ', fg='green')
                    + 'Marked with the "multihost" tag')
        except AttributeError:
            pass
        # Add relevant bugs to the 'link' attribute
        for bug in re.findall(r'^Bug:\s*([0-9]+)', testinfo, re.M):
            add_bug(bug, data)

        # Restore the original testinfo.desc content (if existed)
        if old_testinfo:
            try:
                with open(testinfo_path, 'w', encoding='utf-8') as testinfo:
                    testinfo.write(old_testinfo)
            except IOError:
                raise ConvertError(
                    "Unable to write '{0}'.".format(testinfo_path))
        # Remove created testinfo.desc otherwise
        else:
            os.remove(testinfo_path)

    # Purpose (extract everything after the header as a description)
    if purpose:
        echo(style('Purpose ', fg='blue'), nl=False)
        purpose_path = os.path.join(path, 'PURPOSE')
        try:
            with open(purpose_path, encoding='utf-8') as purpose:
                content = purpose.read()
            echo("found in '{0}'.".format(purpose_path))
            for header in ['PURPOSE', 'Description', 'Author']:
                content = re.sub('^{0}.*\n'.format(header), '', content)
            data['description'] = content.lstrip('\n')
            echo(style('description:', fg='green'))
            echo(data['description'].rstrip('\n'))
        except IOError:
            echo("not found.")

    # Nitrate (extract contact, environment and relevancy)
    if nitrate:
        common_data, individual_data = read_nitrate(
            beaker_task, data, disabled)
    else:
        common_data = data
        individual_data = []

    # Remove keys which are inherited from parent
    parent_path = os.path.dirname(path.rstrip('/'))
    parent_name = '/' + os.path.relpath(parent_path, tree.root)
    parent = tree.find(parent_name)
    if parent:
        for test in [common_data] + individual_data:
            for key in list(test):
                if parent.get(key) == test[key]:
                    test.pop(key)

    log.debug('Common metadata:\n' + pprint.pformat(common_data))
    log.debug('Individual metadata:\n' + pprint.pformat(individual_data))
    return common_data, individual_data
示例#21
0
文件: convert.py 项目: mruprich/tmt
def read_nitrate(beaker_task, common_data, disabled):
    """ Read old metadata from nitrate test cases """

    # Need to import nitrate only when really needed. Otherwise we get
    # traceback when nitrate not installed or config file not available.
    try:
        import nitrate
    except ImportError:
        raise ConvertError('Install nitrate module to import metadata.')

    # Check test case
    echo(style('Nitrate ', fg='blue'), nl=False)
    if beaker_task is None:
        raise ConvertError('No test name detected for nitrate search')

    # Find all testcases
    try:
        if disabled:
            testcases = list(nitrate.TestCase.search(script=beaker_task))
        # Find testcases that do not have 'DISABLED' status
        else:
            testcases = list(nitrate.TestCase.search(
                script=beaker_task, case_status__in=[1, 2, 4]))
    except nitrate.NitrateError as error:
        raise ConvertError(error)
    if not testcases:
        echo("No {0}testcase found for '{1}'.".format(
            '' if disabled else 'non-disabled ', beaker_task))
        return common_data, []
    elif len(testcases) > 1:
        echo("Multiple test cases found for '{0}'.".format(beaker_task))

    # Process individual test cases
    individual_data = list()
    for testcase in testcases:
        data = dict()
        echo("test case found '{0}'.".format(testcase.identifier))
        # Test identifier
        data['extra-nitrate'] = testcase.identifier
        # Beaker task name (taken from summary)
        if testcase.summary:
            data['extra-summary'] = testcase.summary
            echo(style('extra-summary: ', fg='green') + data['extra-summary'])
        # Contact
        if testcase.tester:
            data['contact'] = '{} <{}>'.format(
                testcase.tester.name, testcase.tester.email)
            echo(style('contact: ', fg='green') + data['contact'])
        # Environment
        if testcase.arguments:
            data['environment'] = tmt.utils.variables_to_dictionary(
                testcase.arguments)
            if not data['environment']:
                data.pop('environment')
            else:
                echo(style('environment:', fg='green'))
                echo(pprint.pformat(data['environment']))
        # Tags
        if testcase.tags:
            tags = []
            for tag in testcase.tags:
                if tag.name == 'fmf-export':
                    continue
                tags.append(tag.name)
                # Add the tier attribute, if there are multiple TierX tags,
                # pick the one with the lowest index.
                tier_match = re.match(r'^Tier ?(?P<num>\d+)$', tag.name, re.I)
                if tier_match:
                    num = tier_match.group('num')
                    if 'tier' in data:
                        log.warning('Multiple Tier tags found, using the one '
                                    'with a lower index')
                        if int(num) < int(data['tier']):
                            data['tier'] = num
                    else:
                        data['tier'] = num

            data['tag'] = sorted(tags)
            echo(style('tag: ', fg='green') + str(data['tag']))
        # Tier
        try:
            echo(style('tier: ', fg='green') + data['tier'])
        except KeyError:
            pass
        # Component
        data['component'] = [comp.name for comp in testcase.components]
        echo(style('component: ', fg='green') + ' '.join(data['component']))
        # Status
        data['enabled'] = testcase.status.name == "CONFIRMED"
        echo(style('enabled: ', fg='green') + str(data['enabled']))
        # Relevancy
        field = tmt.utils.StructuredField(testcase.notes)
        try:
            relevancy = field.get('relevancy')
            if relevancy:
                data['relevancy'] = relevancy
                echo(style('relevancy:', fg='green'))
                echo(data['relevancy'].rstrip('\n'))
        except tmt.utils.StructuredFieldError:
            pass
        # Extras: [pepa] and [hardware]
        try:
            extra_pepa = field.get('pepa')
            if extra_pepa:
                data['extra-pepa'] = extra_pepa
                echo(style('extra-pepa:', fg='green'))
                echo(data['extra-pepa'].rstrip('\n'))
        except tmt.utils.StructuredFieldError:
            pass
        try:
            extra_hardware = field.get('hardware')
            if extra_hardware:
                data['extra-hardware'] = extra_hardware
                echo(style('extra-hardware:', fg='green'))
                echo(data['extra-hardware'].rstrip('\n'))
        except tmt.utils.StructuredFieldError:
            pass
        individual_data.append(data)

    # Find common data from individual test cases
    common_candidates = dict()
    histogram = dict()
    for testcase in individual_data:
        if individual_data.index(testcase) == 0:
            common_candidates = copy.copy(testcase)
            for key in testcase:
                histogram[key] = 1
        else:
            for key, value in testcase.items():
                if key in common_candidates:
                    if value != common_candidates[key]:
                        common_candidates.pop(key)
                if key in histogram:
                    histogram[key] += 1

    for key in histogram:
        if key in common_candidates and histogram[key] < len(individual_data):
            common_candidates.pop(key)

    # Add common data to main.fmf
    for key, value in common_candidates.items():
        common_data[key] = value

    # If there is only single testcase found there is no need to continue
    if len(individual_data) <= 1:
        return common_data, []

    # Remove common data from individual fmfs
    for common_key in common_candidates:
        for testcase in individual_data:
            if common_key in testcase:
                testcase.pop(common_key)

    return common_data, individual_data
示例#22
0
 def vagrant_status(self):
     """ Get vagrant's status """
     raise ConvertError('NYI: cannot currently return status.')
示例#23
0
文件: convert.py 项目: mruprich/tmt
def read(path, makefile, nitrate, purpose, disabled):
    """
    Read old metadata from various sources

    Returns tuple (common_data, individual_data) where 'common_data' are
    metadata which belong to main.fmf and 'individual_data' contains
    data for individual testcases (if multiple nitrate testcases found).
    """

    data = dict()
    echo("Checking the '{0}' directory.".format(path))

    # Make sure there is a metadata tree initialized
    try:
        tree = fmf.Tree(path)
    except fmf.utils.RootError:
        raise ConvertError("Initialize metadata tree using 'tmt init'.")

    # Makefile (extract summary, test, duration and requires)
    if makefile:
        echo(style('Makefile ', fg='blue'), nl=False)
        makefile_path = os.path.join(path, 'Makefile')
        try:
            with open(makefile_path, encoding='utf-8') as makefile_file:
                makefile = makefile_file.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(makefile_path))
        echo("found in '{0}'.".format(makefile_path))

        # If testinfo.desc exists read it to preserve content and remove it
        testinfo_path = os.path.join(path, 'testinfo.desc')
        if os.path.isfile(testinfo_path):
            try:
                with open(testinfo_path, encoding='utf-8') as testinfo:
                    old_testinfo = testinfo.read()
                    os.remove(testinfo_path)
            except IOError:
                raise ConvertError(
                    "Unable to open '{0}'.".format(testinfo_path))
        else:
            old_testinfo = None

        # Make Makefile 'makeable' without extra dependecies
        # (replace targets, make include optional and remove rhts-lint)
        makefile = makefile.replace('$(METADATA)', 'testinfo.desc')
        makefile = makefile.replace(
            'include /usr/share/rhts/lib/rhts-make.include',
            '-include /usr/share/rhts/lib/rhts-make.include')
        makefile = makefile.replace('rhts-lint testinfo.desc', '')

        # Create testinfo.desc file with resolved variables
        try:
            process = subprocess.run(
                ["make", "testinfo.desc", "-C", path, "-f", "-"],
                input=makefile, check=True, encoding='utf-8',
                stdout=subprocess.DEVNULL)
        except FileNotFoundError:
            raise ConvertError(
                "Install 'make' to convert metadata from Makefile.")
        except subprocess.CalledProcessError:
            raise ConvertError(
                "Failed to convert metadata using 'make testinfo.desc'.")

        # Read testinfo.desc
        try:
            with open(testinfo_path, encoding='utf-8') as testinfo_file:
                testinfo = testinfo_file.read()
        except IOError:
            raise ConvertError("Unable to open '{0}'.".format(testinfo_path))

        # Beaker task name
        try:
            beaker_task = re.search(r'Name:\s*(.*)\n', testinfo).group(1)
            echo(style('task: ', fg='green') + beaker_task)
            data['extra-task'] = beaker_task
        except AttributeError:
            raise ConvertError("Unable to parse 'Name' from testinfo.desc.")
        # Summary
        try:
            data['summary'] = re.search(
                r'^Description:\s*(.*)\n', testinfo, re.M).group(1)
            echo(style('summary: ', fg='green') + data['summary'])
        except AttributeError:
            pass
        # Test script
        try:
            data['test'] = re.search(
                r'^run:.*\n\t(.*)$', makefile, re.M).group(1)
            echo(style('test: ', fg='green') + data['test'])
        except AttributeError:
            raise ConvertError("Makefile is missing the 'run' target.")
        # Component
        try:
            data['component'] = re.search(
                r'^RunFor:\s*(.*)', testinfo, re.M).group(1).split()
            echo(style('component: ', fg='green') +
                 ' '.join(data['component']))
        except AttributeError:
            pass
        # Duration
        try:
            data['duration'] = re.search(
                r'^TestTime:\s*(.*)', testinfo, re.M).group(1)
            echo(style('duration: ', fg='green') + data['duration'])
        except AttributeError:
            pass
        # Requires and RhtsRequires (optional)
        requires = re.findall(r'^(?:Rhts)?Requires:\s*(.*)', testinfo, re.M)
        if requires:
            data['require'] = [
                require for line in requires for require in line.split()]
            echo(style('require: ', fg='green') + ' '.join(data['require']))

        # Restore the original testinfo.desc content (if existed)
        if old_testinfo:
            try:
                with open(testinfo_path, 'w', encoding='utf-8') as testinfo:
                    testinfo.write(old_testinfo)
            except IOError:
                raise ConvertError(
                    "Unable to write '{0}'.".format(testinfo_path))
        # Remove created testinfo.desc otherwise
        else:
            os.remove(testinfo_path)

    # Purpose (extract everything after the header as a description)
    if purpose:
        echo(style('Purpose ', fg='blue'), nl=False)
        purpose_path = os.path.join(path, 'PURPOSE')
        try:
            with open(purpose_path, encoding='utf-8') as purpose:
                content = purpose.read()
            echo("found in '{0}'.".format(purpose_path))
            for header in ['PURPOSE', 'Description', 'Author']:
                content = re.sub('^{0}.*\n'.format(header), '', content)
            data['description'] = content.lstrip('\n')
            echo(style('description:', fg='green'))
            echo(data['description'].rstrip('\n'))
        except IOError:
            echo("not found.")

    # Nitrate (extract contact, environment and relevancy)
    if nitrate:
        common_data, individual_data = read_nitrate(
            beaker_task, data, disabled)
    else:
        common_data = data
        individual_data = []

    log.debug('Common metadata:\n' + pprint.pformat(common_data))
    log.debug('Individual metadata:\n' + pprint.pformat(individual_data))
    return common_data, individual_data
示例#24
0
文件: convert.py 项目: psss/tmt
def read_datafile(path, filename, datafile, types, testinfo=None):
    """
    Read data values from supplied Makefile or metadata file.
    Returns task name and a dictionary of the collected values.
    """

    data = dict()
    if filename == 'Makefile':
        regex_task = r'Name:\s*(.*)\n'
        regex_summary = r'^Description:\s*(.*)\n'
        regex_test = r'^run:.*\n\t(.*)$'
        regex_contact = r'^Owner:\s*(.*)'
        regex_duration = r'^TestTime:\s*(.*)'
        regex_recommend = r'^Requires:\s*(.*)'
        regex_require = r'^RhtsRequires:\s*(.*)'
        rec_separator = None
    else:
        regex_task = r'name=\s*(.*)\n'
        regex_summary = r'description=\s*(.*)\n'
        regex_test = r'entry_point=\s*(.*)$'
        regex_contact = r'owner=\s*(.*)'
        regex_duration = r'max_time=\s*(.*)'
        regex_require = r'dependencies=\s*(.*)'
        regex_recommend = r'softDependencies=\s*(.*)'
        rec_separator = ';'

    if testinfo is None:
        testinfo = datafile
    # Beaker task name
    try:
        beaker_task = re.search(regex_task, testinfo).group(1)
        echo(style('task: ', fg='green') + beaker_task)
        data['extra-task'] = beaker_task
        data['extra-summary'] = beaker_task
    except AttributeError:
        raise ConvertError("Unable to parse 'Name' from testinfo.desc.")
    # Summary
    try:
        data['summary'] = re.search(regex_summary, testinfo, re.M).group(1)
        echo(style('summary: ', fg='green') + data['summary'])
    except AttributeError:
        pass
    # Test script
    try:
        data['test'] = re.search(regex_test, datafile, re.M).group(1)
        if filename == 'metadata':
            data['test'] = data['test'].split()[-1]
        echo(style('test: ', fg='green') + data['test'])
    except AttributeError:
        raise ConvertError("Makefile is missing the 'run' target.")
    # Detect framework
    try:
        test_path = os.path.join(path, data["test"])
        with open(test_path, encoding="utf-8") as test_file:
            if re.search("beakerlib", test_file.read()):
                data["framework"] = "beakerlib"
            else:
                data["framework"] = "shell"
        echo(style("framework: ", fg="green") + data["framework"])
    except IOError:
        raise ConvertError("Unable to open '{0}'.".format(test_path))
    # Contact
    try:
        data['contact'] = re.search(regex_contact, testinfo, re.M).group(1)
        echo(style('contact: ', fg='green') + data['contact'])
    except AttributeError:
        pass

    if filename == 'Makefile':
        # Component
        try:
            data['component'] = re.search(r'^RunFor:\s*(.*)', testinfo,
                                          re.M).group(1).split()
            echo(
                style('component: ', fg='green') + ' '.join(data['component']))
        except AttributeError:
            pass

    # Duration
    try:
        data['duration'] = re.search(regex_duration, testinfo, re.M).group(1)
        echo(style('duration: ', fg='green') + data['duration'])
    except AttributeError:
        pass

    if filename == 'Makefile':
        # Environment
        variables = re.findall(r'^Environment:\s*(.*)', testinfo, re.M)
        if variables:
            data['environment'] = {}
            for variable in variables:
                key, value = variable.split('=', maxsplit=1)
                data['environment'][key] = value
            echo(style('environment:', fg='green'))
            echo(pprint.pformat(data['environment']))

    # RhtsRequires or repoRequires (optional) goes to require
    requires = re.findall(regex_require, testinfo, re.M)
    if requires:
        data['require'] = [
            require for line in requires
            for require in line.split(rec_separator)
        ]
        echo(style('require: ', fg='green') + ' '.join(data['require']))

    # Requires or softDependencies (optional) goes to recommend
    recommends = re.findall(regex_recommend, testinfo, re.M)
    if recommends:
        data['recommend'] = [
            recommend for line in recommends
            for recommend in line.split(rec_separator)
        ]
        echo(style('recommend: ', fg='green') + ' '.join(data['recommend']))

    if filename == 'Makefile':
        # Convert Type into tags
        try:
            makefile_type = re.search(r'^Type:\s*(.*)', testinfo,
                                      re.M).group(1)
            if 'all' in [type_.lower() for type_ in types]:
                tags = makefile_type.split()
            else:
                tags = [
                    type_ for type_ in types
                    if type_.lower() in makefile_type.lower().split()
                ]
            if tags:
                echo(style("tag: ", fg="green") + " ".join(tags))
                data["tag"] = tags
        except AttributeError:
            pass
        # Add relevant bugs to the 'link' attribute
        for bug_line in re.findall(r'^Bug:\s*([0-9\s]+)', testinfo, re.M):
            for bug in re.findall(r'(\d+)', bug_line):
                add_bug(bug, data)

    return beaker_task, data