diff --git a/tmt/base.py b/tmt/base.py index 33a7b257b3..163d3f55a7 100644 --- a/tmt/base.py +++ b/tmt/base.py @@ -205,7 +205,8 @@ def lint(self): elif len(self.summary) > 50: echo(verdict(2, 'summary should not exceed 50 characters')) - def export(self, format_='yaml', keys=None, environment=None): + def export( + self, format_='yaml', keys=None, environment=None, create=False): """ Export test data into requested format @@ -235,7 +236,7 @@ def export(self, format_='yaml', keys=None, environment=None): # Export to Nitrate test case management system elif format_ == 'nitrate': - return tmt.export.export_to_nitrate(self) + tmt.export.export_to_nitrate(self, create) # Common node export otherwise else: diff --git a/tmt/cli.py b/tmt/cli.py index 7d7f0c9691..3cefb725e9 100644 --- a/tmt/cli.py +++ b/tmt/cli.py @@ -515,13 +515,16 @@ def import_(context, paths, makefile, nitrate, purpose, disabled, **kwargs): @click.option( '--nitrate', is_flag=True, help='Export test metadata to Nitrate.') +@click.option( + '--create', is_flag=True, + help="Create test cases in nitrate if they don't exist.") @click.option( '--format', 'format_', default='yaml', show_default=True, metavar='FORMAT', help='Output format.') @click.option( '-d', '--debug', is_flag=True, help='Provide as much debugging details as possible.') -def export(context, format_, nitrate, **kwargs): +def export(context, format_, nitrate, create, **kwargs): """ Export test data into the desired format @@ -531,7 +534,7 @@ def export(context, format_, nitrate, **kwargs): tmt.Test._save_context(context) for test in context.obj.tree.tests(): if nitrate: - test.export(format_='nitrate') + test.export(format_='nitrate', create=create) else: echo(test.export(format_=format_)) diff --git a/tmt/export.py b/tmt/export.py index 9f178e1749..f4f55c905a 100644 --- a/tmt/export.py +++ b/tmt/export.py @@ -10,6 +10,9 @@ import yaml import fmf import os +import re + +from tmt.utils import ConvertError log = fmf.utils.Logging('tmt').logger @@ -19,25 +22,38 @@ """.lstrip() -def export_to_nitrate(test): - """ Export fmf metadata to nitrate test cases """ +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 tmt.utils.ConvertError("Install nitrate to export tests there.") + raise ConvertError("Install nitrate to export tests there.") except nitrate.NitrateError as error: - raise tmt.utils.ConvertError(error) + raise ConvertError(error) + +def export_to_nitrate(test, create): + """ 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:] - except KeyError: - return 0 - nitrate_case = nitrate.TestCase(int(nitrate_id)) - echo(style(f"Test case '{nitrate_case.identifier}' found.", fg='blue')) + 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.") # Components # First remove any components that are already there @@ -137,7 +153,17 @@ def export_to_nitrate(test): echo(style("Test case '{0}' successfully exported to nitrate.".format( nitrate_case.identifier), fg='magenta')) - return 0 + # 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) def create_fmf_id(test): @@ -166,3 +192,25 @@ def run(command): fmf_id['path'] = os.path.join('/', os.path.relpath(fmf_root, git_root)) return fmf_id + + +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 + summary = test.node.get('extra-summary', test.summary) + 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