def full(): """ More complex metadata structure with inheritance """ data = """ duration: 5m enabled: true require: [one] adjust: - enabled: false when: distro = centos because: the feature is not there yet continue: false - duration: 1m when: arch = ppc64 because: they are super fast continue: true - require+: [two] when: distro = fedora /inherit: summary: This test just inherits all rules. /define: summary: This test defines its own rules. adjust: recommend: custom-package when: distro = fedora /extend: summary: This test extends parent rules. adjust+: - require+: [three] when: distro = fedora """ return fmf.Tree(yaml.safe_load(data))
def init(path, mini, full, force): """ Initialize the tree root. """ # Initialize the FMF metadata tree root try: tree = fmf.Tree(path) echo("Tree root '{}' already exists.".format(tree.root)) except fmf.utils.RootError: try: root = fmf.Tree.init(path) except fmf.utils.GeneralError as error: raise tmt.utils.GeneralError( "Failed to initialize tree root in '{}': {}".format( path, error)) echo("Tree root '{}' initialized.".format(root)) # Populate the tree with example objects if requested if mini: tmt.Test.create('/tests/example', 'shell', force) tmt.Plan.create('/plans/example', 'mini', force) tmt.Story.create('/stories/example', 'mini', force) if full: tmt.Test.create('/tests/example', 'shell', force) tmt.Plan.create('/plans/example', 'full', force) tmt.Story.create('/stories/example', 'full', force) return 'init'
def __init__(self, id_=None, tree=None, context=None): """ Initialize tree, workdir and plans """ # Use the last run id if requested self.config = tmt.utils.Config() if context.params.get('last'): id_ = self.config.last_run() if id_ is None: raise tmt.utils.GeneralError( "No last run id found. Have you executed any run?") super().__init__(workdir=id_ or True, context=context) # Store workdir as the last run id self.config.last_run(self.workdir) default_plan = tmt.utils.yaml_to_dict(tmt.templates.DEFAULT_PLAN) # Save the tree try: self.tree = tree if tree else tmt.Tree('.') self.debug(f"Using tree '{self.tree.root}'.") # Insert default plan if no plan detected if not list(self.tree.tree.prune(keys=['execute'])): self.tree.tree.update(default_plan) self.debug(f"No plan found, adding the default plan.") # Create an empty default plan if no fmf metadata found except tmt.utils.MetadataError: # The default discover method for this case is 'shell' default_plan['/plans/default']['discover']['how'] = 'shell' self.tree = tmt.Tree(tree=fmf.Tree(default_plan)) self.debug(f"No metadata found, using the default plan.") self._plans = None self._environment = dict() self.remove = self.opt('remove')
def show(self, brief=False): """ Show metadata for each path given """ output = [] for path in self.options.paths or ["."]: if self.options.verbose: utils.info("Checking {0} for metadata.".format(path)) tree = fmf.Tree(path) for node in tree.prune(self.options.whole, self.options.keys, self.options.names, self.options.filters, self.options.conditions): if brief: show = node.show(brief=True) else: show = node.show(brief=False, formatting=self.options.formatting, values=self.options.values) # List source files when in debug mode if self.options.debug: for source in node.sources: show += utils.color("{0}\n".format(source), "blue") if show is not None: output.append(show) # Print output and summary if brief or self.options.formatting: joined = "".join(output) else: joined = "\n".join(output) print(joined, end="") if self.options.verbose: utils.info("Found {0}.".format(utils.listed(len(output), "object"))) self.output = joined
def main(cmdline=None): """ Parse options, gather metadata, print requested data """ # Parse command line arguments options, arguments = Options().parse(cmdline) if not arguments: arguments = ["."] output = "" # Enable debugging output if options.debug: utils.log.setLevel(utils.LOG_DEBUG) # Show metadata for each path given counter = 0 for path in arguments: if options.verbose: utils.info("Checking {0} for metadata.".format(path)) tree = fmf.Tree(path) for node in tree.prune(options.whole, options.keys, options.names, options.filters): show = node.show(options.brief, options.formatting, options.values) if show is not None: print(show, end="") output += show counter += 1 # Print summary if options.verbose: utils.info("Found {0}.".format(utils.listed(counter, "object"))) return output
def mini(): """ Simple tree node with minimum set of attributes """ data = """ enabled: true adjust: enabled: false when: distro = centos """ return fmf.Tree(yaml.safe_load(data))
def tree(self): """ Initialize tree only when accessed """ if self._tree is None: try: self._tree = fmf.Tree(self._path) except fmf.utils.RootError: raise tmt.utils.GeneralError( "No metadata found in the '{0}' directory.".format( self._path)) return self._tree
def tree(self): """ Initialize tree only when accessed """ if self._tree is None: try: self._tree = fmf.Tree(self._path) except fmf.utils.RootError: raise tmt.utils.MetadataError( f"No metadata found in the '{self._path}' directory. " f"Use 'tmt init' to get started.") except fmf.utils.FileError as error: raise tmt.utils.GeneralError(f"Invalid yaml syntax: {error}") return self._tree
def fetch(self): """ Fetch the library (unless already fetched) """ # Initialize library cache (indexed by the repository name) if not hasattr(self.parent, '_library_cache'): self.parent._library_cache = dict() # Check if the library was already fetched try: library = self.parent._library_cache[self.repo] if library.url != self.url: raise tmt.utils.GeneralError( f"Library '{self.repo}' with url '{self.url}' conflicts " f"with already fetched library from '{library.url}'.") if library.ref != self.ref: raise tmt.utils.GeneralError( f"Library '{self.repo}' using ref '{self.ref}' conflicts " f"with already fetched library using ref '{library.ref}'.") self.parent.debug(f"Library '{self}' already fetched.", level=3) # Reuse the existing metadata tree self.tree = library.tree # Fetch the library and add it to the index except KeyError: self.parent.debug(f"Fetch library '{self}'.", level=3) # Prepare path, clone the repository, checkout ref directory = os.path.join( self.parent.workdir, self.dest, self.repo) # Clone repo with disabled prompt to ignore missing/private repos try: self.parent.run( ['git', 'clone', self.url, directory], shell=False, env={"GIT_TERMINAL_PROMPT": "0"}) except tmt.utils.RunError as error: # Fallback to install during the prepare step if in rpm format if self.format == 'rpm': raise LibraryError self.parent.info( f"Failed to fetch library '{self}' from '{self.url}'.", color='red') raise self.parent.run( ['git', 'checkout', self.ref], shell=False, cwd=directory) # Initialize metadata tree, add self into the library index self.tree = fmf.Tree(directory) self.parent._library_cache[self.repo] = self # Get the library node, check require and recommend library = self.tree.find(self.name) if not library: raise tmt.utils.GeneralError( f"Library '{self.name}' not found in '{self.repo}'.") self.require = tmt.utils.listify(library.get('require', [])) self.recommend = tmt.utils.listify(library.get('recommend', []))
def test_convert(): """ Convert old metadata """ tmp = tempfile.mkdtemp() os.system('cp -a {} {}'.format(CONVERT, tmp)) path = os.path.join(tmp, 'convert') command = 'test convert --no-nitrate {}'.format(path) result = runner.invoke(tmt.cli.main, command.split()) assert result.exit_code == 0 assert 'Metadata successfully stored' in result.output assert 'This is really that simple' in result.output node = fmf.Tree(path).find('/') assert node.get('summary') == 'Simple smoke test' assert node.get('component') == ['tmt'] assert 'This is really that simple.' in node.get('description') shutil.rmtree(tmp)
def _save_tree(self, tree): """ Save metadata tree, handle the default plan """ default_plan = tmt.utils.yaml_to_dict(tmt.templates.DEFAULT_PLAN) try: self.tree = tree if tree else tmt.Tree('.') self.debug(f"Using tree '{self.tree.root}'.") # Insert default plan if no plan detected if not list(self.tree.tree.prune(keys=['execute'])): self.tree.tree.update(default_plan) self.debug(f"No plan found, adding the default plan.") # Create an empty default plan if no fmf metadata found except tmt.utils.MetadataError: # The default discover method for this case is 'shell' default_plan['/plans/default']['discover']['how'] = 'shell' self.tree = tmt.Tree(tree=fmf.Tree(default_plan)) self.debug(f"No metadata found, using the default plan.")
def __init__(self, data, name=None): """ Initialize test data from an fmf node or a dictionary The following two methods are supported: Test(node) Test(dictionary, name) Test name is required when initializing from a dictionary. """ # Create a simple test node if dictionary given if isinstance(data, dict): if name is None: raise tmt.utils.GeneralError( 'Name required to initialize test.') elif not name.startswith('/'): raise tmt.utils.SpecificationError( "Test name should start with a '/'.") node = fmf.Tree(data) node.name = name else: node = data super().__init__(node) # Set all supported attributes for key in self._keys: setattr(self, key, self.node.get(key)) # Path defaults to the node name self._check('path', expected=str, default=self.name) # Check that lists are lists or strings, listify if needed for key in ['component', 'contact', 'require', 'recommend', 'tag']: self._check(key, expected=(list, str), default=[], listify=True) # Check that environment is a dictionary self._check('environment', expected=dict, default={}) self.environment = dict([(key, str(value)) for key, value in self.environment.items()]) # Default duration, manual, enabled and result self._check('duration', expected=str, default=DEFAULT_TEST_DURATION) self._check('manual', expected=bool, default=False) self._check('enabled', expected=bool, default=True) self._check('result', expected=str, default='respect')
def go(self): """ Discover available tests """ super(DiscoverShell, self).go() tests = fmf.Tree(dict(summary='tests')) # Check and process each defined shell test for data in self.data['tests']: # Create data copy (we want to keep original data for save() data = copy.deepcopy(data) # Extract name, make sure it is present try: name = data.pop('name') except KeyError: raise tmt.utils.SpecificationError( f"Missing test name in '{self.step.plan.name}'.") # Make sure that the test script is defined if 'test' not in data: raise tmt.utils.SpecificationError( f"Missing test script in '{self.step.plan.name}'.") # Prepare path to the test working directory (tree root by default) try: data['path'] = f"/tests{data['path']}" except KeyError: data['path'] = f"/tests" # Apply default test duration unless provided if 'duration' not in data: data['duration'] = tmt.base.DEFAULT_TEST_DURATION_L2 # Create a simple fmf node, adjust its name tests.child(name, data) # Copy directory tree (if defined) to the workdir directory = self.step.plan.my_run.tree.root testdir = os.path.join(self.workdir, 'tests') if directory: self.info('directory', directory, 'green') self.debug("Copy '{}' to '{}'.".format(directory, testdir)) shutil.copytree(directory, testdir, symlinks=True) else: os.makedirs(testdir) # Use a tmt.Tree to apply possible command line filters tests = tmt.Tree(tree=tests).tests(conditions=["manual is False"]) self._tests = tests
def main(cmdline=None): """ Parse options, gather metadata, print requested data """ # Parse command line arguments options, arguments = Options().parse(cmdline) if not arguments: arguments = ["."] output = "" # Enable debugging output if options.debug: utils.log.setLevel(utils.LOG_DEBUG) # Show metadata for each path given counter = 0 for path in arguments: if options.verbose: utils.info("Checking {0} for metadata.".format(path)) tree = fmf.Tree(path) for node in tree.climb(options.whole): # Select only nodes with key content if not all([key in node.data for key in options.keys]): continue # Select nodes with name matching regular expression if options.names and not any( [re.search(name, node.name) for name in options.names]): continue # Apply advanced filters if given try: if not all([utils.filter(filter, node.data) for filter in options.filters]): continue # Handle missing attribute as if filter failed except utils.FilterError: continue show = node.show(brief=options.brief) print(show) output += show + "\n" counter += 1 # Print summary if options.verbose: utils.info("Found {0}.".format(utils.listed(counter, "object"))) return output
def go(self): """ Discover available tests """ super(DiscoverShell, self).go() tests = fmf.Tree(dict(summary='tests')) # Check and process each defined shell test for data in self.data['tests']: # Create data copy (we want to keep original data for save() data = copy.deepcopy(data) # Extract name, make sure it is present try: name = data.pop('name') except KeyError: raise tmt.utils.SpecificationError( f"Missing test name in '{self.step.plan.name}'.") # Make sure that the test script is defined if 'test' not in data: raise tmt.utils.SpecificationError( f"Missing test script in '{self.step.plan.name}'.") # Prepare path to the test working directory (tree root by default) try: data['path'] = f"/tests{data['path']}" except KeyError: data['path'] = f"/tests" # Apply default test duration unless provided if 'duration' not in data: data['duration'] = tmt.base.DEFAULT_TEST_DURATION_L2 # Create a simple fmf node, adjust its name tests.child(name, data) # Symlink tests directory to the plan work tree testdir = os.path.join(self.workdir, "tests") relative_path = os.path.relpath(self.step.plan.worktree, self.workdir) os.symlink(relative_path, testdir) # Use a tmt.Tree to apply possible command line filters tests = tmt.Tree(tree=tests).tests(conditions=["manual is False"]) self._tests = tests
def go(self): """ Discover available tests """ super(DiscoverShell, self).go() tests = fmf.Tree(dict(summary='tests')) path = False for data in self.data['tests']: # Extract name, make sure it is present try: name = data.pop('name') except KeyError: raise tmt.utils.SpecificationError( f"Missing test name in '{self.step.plan.name}'.") # Make sure that the test script is defined if 'test' not in data: raise tmt.utils.SpecificationError( f"Missing test script in '{self.step.plan.name}'.") # Adjust path if necessary (defaults to '.') try: data['path'] = f"/{self.name}/tests{data['path']}" path = True except KeyError: data['path'] = '.' # Create a simple fmf node, adjust its name tests.child(name, data) # Copy current directory to workdir only if any path specified if path: directory = self.step.plan.run.tree.root testdir = os.path.join(self.workdir, 'tests') self.info('directory', directory, 'green') self.debug("Copy '{}' to '{}'.".format(directory, testdir)) shutil.copytree(directory, testdir) # Use a tmt.Tree to apply possible command line filters tests = tmt.Tree(tree=tests).tests() # Summary of selected tests, test list in verbose mode summary = fmf.utils.listed(len(tests), 'test') + ' selected' self.info('tests', summary, 'green') for test in tests: self.verbose(test.name, color='red', shift=1) self._tests = tests
def import_(context, paths, makefile, nitrate, purpose, disabled, **kwargs): """ Import old test metadata into the new fmf format. Accepts one or more directories where old metadata are stored. By default all available sources and current directory are used. The following test metadata are converted for each source: \b makefile ..... summary, component, duration, require purpose ...... description nitrate ...... contact, component, tag, environment, relevancy, enabled """ tmt.Test._save_context(context) if not paths: paths = ['.'] for path in paths: # Make sure we've got a real directory path = os.path.realpath(path) if not os.path.isdir(path): raise tmt.utils.GeneralError( "Path '{0}' is not a directory.".format(path)) # Gather old metadata and store them as fmf common, individual = tmt.convert.read(path, makefile, nitrate, purpose, disabled) # Add path to common metadata if there are virtual test cases if individual: root = fmf.Tree(path).root common['path'] = os.path.join('/', os.path.relpath(path, root)) # Store common metadata common_path = os.path.join(path, 'main.fmf') tmt.convert.write(common_path, common) # Store individual data (as virtual tests) for testcase in individual: testcase_path = os.path.join( path, str(testcase['extra-nitrate']) + '.fmf') tmt.convert.write(testcase_path, testcase)
#!/usr/bin/python import fmf # Browse directory with wget example metadata for node in fmf.Tree("../wget").climb(): try: # List nodes with "test" attribute defined and "Tier2" in tags if "test" in node.data and "Tier2" in node.data["tags"]: print(node.show()) except KeyError: pass
def __init__(self, data, name=None): """ Initialize test data from an fmf node or a dictionary The following two methods are supported: Test(node) Test(dictionary, name) Test name is required when initializing from a dictionary. """ # Create a simple test node if dictionary given if isinstance(data, dict): if name is None: raise tmt.utils.GeneralError( 'Name required to initialize test.') elif not name.startswith('/'): raise tmt.utils.SpecificationError( "Test name should start with a '/'.") node = fmf.Tree(data) node.name = name else: node = data super().__init__(node) # Set all supported attributes for key in self._keys: setattr(self, key, self.node.get(key)) # Path defaults to the directory where metadata are stored or to # the root '/' if fmf metadata were not stored on the filesystem try: directory = os.path.dirname(self.node.sources[-1]) relative_path = os.path.relpath(directory, self.node.root) if relative_path == '.': default_path = '/' else: default_path = os.path.join('/', relative_path) except (AttributeError, IndexError): default_path = '/' self._check('path', expected=str, default=default_path) # Check that lists are lists or strings, listify if needed for key in ['component', 'contact', 'require', 'recommend', 'tag']: self._check(key, expected=(list, str), default=[], listify=True) # FIXME Framework should default to 'shell' in the future. For # backward-compatibility with the old execute methods we need to be # able to detect if the test has explicitly set the framework. self._check('framework', expected=str, default=None) if self.framework == 'beakerlib': self.require.append('beakerlib') # Check that environment is a dictionary self._check('environment', expected=dict, default={}) self.environment = dict([(key, str(value)) for key, value in self.environment.items()]) # Default duration, manual, enabled and result self._check('duration', expected=str, default=DEFAULT_TEST_DURATION_L1) self._check('manual', expected=bool, default=False) self._check('enabled', expected=bool, default=True) self._check('result', expected=str, default='respect') # Store original metadata with applied defaults and including # keys which are not defined in the L1 metadata specification self._metadata = self.node.data.copy() self._metadata.update(self.export(format_='dict')) self._metadata['name'] = self.name
def __init__(self, path='.'): """ Initialize testsets for given directory path """ self.tree = fmf.Tree(path) self.testsets = [Testset(testset) for testset in self.tree.climb()]
def go(self): """ Discover available tests """ super(DiscoverFmf, self).go() # Check url and path, prepare test directory url = self.get('url') path = self.get('path') testdir = os.path.join(self.workdir, 'tests') dist_git_source = self.get('dist-git-source', False) # Raise an exception if --fmf-id uses w/o url and git root # doesn't exist for discovered plan if self.opt('fmf_id'): def assert_git_url(plan_name=None): try: subprocess.run('git rev-parse --show-toplevel'.split(), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True) except subprocess.CalledProcessError: raise tmt.utils.DiscoverError( f"`tmt run discover --fmf-id` without `url` option in " f"plan `{plan_name}` can be used only within" f" git repo.") # It covers only one case, when there is: # 1) no --url on CLI # 2) plan w/o url exists in test run if not self.opt('url'): try: fmf_tree = fmf.Tree(os.getcwd()) except fmf.utils.RootError: raise tmt.utils.DiscoverError( f"No metadata found in the current directory. " f"Use 'tmt init' to get started.") for i, attr in enumerate(fmf_tree.climb()): try: plan_url = attr.data.get('discover').get('url') plan_name = attr.name if not plan_url: assert_git_url(plan_name) except AttributeError: pass # All other cases are covered by this condition if not url: assert_git_url(self.step.plan.name) # Clone provided git repository (if url given) with disabled # prompt to ignore possibly missing or private repositories if url: self.info('url', url, 'green') self.debug(f"Clone '{url}' to '{testdir}'.") self.run(['git', 'clone', url, testdir], env={"GIT_ASKPASS": "******"}) # Copy git repository root to workdir else: # Path for distgit sources cannot be checked until the # tarball is extracted if path and not os.path.isdir(path) and not dist_git_source: raise tmt.utils.DiscoverError( f"Provided path '{path}' is not a directory.") if dist_git_source: git_root = self.step.plan.my_run.tree.root else: fmf_root = path or self.step.plan.my_run.tree.root if fmf_root is None: raise tmt.utils.DiscoverError( f"No metadata found in the current directory.") # Check git repository root (use fmf root if not found) try: output = self.run(["git", "rev-parse", "--show-toplevel"], cwd=fmf_root, dry=True) git_root = output[0].strip('\n') except tmt.utils.RunError: self.debug(f"Git root not found, using '{fmf_root}.'") git_root = fmf_root # Set path to relative path from the git root to fmf root path = os.path.relpath(fmf_root, git_root) self.info('directory', git_root, 'green') self.debug(f"Copy '{git_root}' to '{testdir}'.") if not self.opt('dry'): shutil.copytree(git_root, testdir, symlinks=True) # Checkout revision if requested ref = self.get('ref') if ref: self.info('ref', ref, 'green') self.debug(f"Checkout ref '{ref}'.") self.run(['git', 'checkout', '-f', str(ref)], cwd=testdir) # Show current commit hash if inside a git repository try: hash_, _ = self.run(["git", "rev-parse", "--short", "HEAD"], cwd=testdir) self.verbose('hash', hash_.strip(), 'green') except (tmt.utils.RunError, AttributeError): pass # Fetch and extract distgit sources if dist_git_source: try: self.extract_distgit_source(testdir, testdir, self.get('dist-git-type')) except Exception as error: raise tmt.utils.DiscoverError( f"Failed to process 'dist-git-source'.", original=error) # Adjust path and optionally show if path is None or path == '.': path = '' else: self.info('path', path, 'green') # Prepare the whole tree path and test path prefix tree_path = os.path.join(testdir, path.lstrip('/')) if not os.path.isdir(tree_path) and not self.opt('dry'): raise tmt.utils.DiscoverError( f"Metadata tree path '{path}' not found.") prefix_path = os.path.join('/tests', path.lstrip('/')) # Show filters and test names if provided # Check the 'test --filter' option first, then from discover filters = list(tmt.base.Test._opt('filters') or self.get('filter', [])) for filter_ in filters: self.info('filter', filter_, 'green') # Names of tests selected by --test option names = self.get('test', []) if names: self.info('tests', fmf.utils.listed(names), 'green') # Check the 'test --link' option first, then from discover links = list(tmt.base.Test._opt('link') or self.get('link', [])) for link_ in links: self.info('link', link_, 'green') excludes = list( tmt.base.Test._opt('exclude') or self.get('exclude', [])) # Filter only modified tests if requested modified_only = self.get('modified-only') modified_url = self.get('modified-url') if modified_url: self.info('modified-url', modified_url, 'green') self.debug(f"Fetch also '{modified_url}' as 'reference'.") self.run(['git', 'remote', 'add', 'reference', modified_url], cwd=testdir) self.run(['git', 'fetch', 'reference'], cwd=testdir) if modified_only: modified_ref = self.get('modified-ref', tmt.utils.default_branch(testdir)) self.info('modified-ref', modified_ref, 'green') output = self.run([ 'git', 'log', '--format=', '--stat', '--name-only', f"{modified_ref}..HEAD" ], cwd=testdir)[0] modified = set(f"^/{re.escape(name)}" for name in map(os.path.dirname, output.split('\n')) if name) self.debug(f"Limit to modified test dirs: {modified}", level=3) names.extend(modified) # Initialize the metadata tree, search for available tests self.debug(f"Check metadata tree in '{tree_path}'.") if self.opt('dry'): self._tests = [] return tree = tmt.Tree(path=tree_path, context=self.step.plan._fmf_context()) self._tests = tree.tests(filters=filters, names=names, conditions=["manual is False"], unique=False, links=links, excludes=excludes) # Prefix tests and handle library requires for test in self._tests: # Prefix test path with 'tests' and possible 'path' prefix test.path = os.path.join(prefix_path, test.path.lstrip('/')) # Check for possible required beakerlib libraries if test.require or test.recommend: test.require, test.recommend, _ = tmt.beakerlib.dependencies( test.require, test.recommend, parent=self)
#!/usr/bin/python import fmf from pathlib import Path import subprocess tree_root = Path.cwd().absolute() node = fmf.Tree(tree_root).find("/plans") with node as data: data["discover"]["url"] = "https://github.com/packit/packit.git" data["discover"]["ref"] = (subprocess.check_output( ["git", "rev-parse", "HEAD"]).decode().strip())
def __init__(self, args, unknown): # choose between TESTS and ADDITIONAL ENVIRONMENT from options if args.linter: self.tests += glob.glob("{MTF_TOOLS}/{GENERIC_TEST}/*.py".format( MTF_TOOLS=metadata_common.MetadataLoaderMTF.MTF_LINTER_PATH, GENERIC_TEST=common.conf["generic"]["generic_tests"])) self.tests += glob.glob("{MTF_TOOLS}/{STATIC_LINTERS}/*.py".format( MTF_TOOLS=metadata_common.MetadataLoaderMTF.MTF_LINTER_PATH, STATIC_LINTERS=common.conf["generic"]["static_tests"])) self.args = args # parse unknow options and try to find what parameter is test while unknown: if unknown[0] in self.A_KNOWN_PARAMS_SIMPLE: self.additionalAvocadoArg.append(unknown[0]) unknown = unknown[1:] elif unknown[0].startswith("-"): if "=" in unknown[0] or len(unknown) < 2: self.additionalAvocadoArg.append(unknown[0]) unknown = unknown[1:] else: self.additionalAvocadoArg += unknown[0:2] unknown = unknown[2:] elif glob.glob(unknown[0]): # dereference filename via globs testlist = glob.glob(unknown[0]) self.tests += testlist unknown = unknown[1:] else: self.tests.append(unknown[0]) unknown = unknown[1:] if self.args.metadata: core.print_info("Using Metadata loader for tests and filtering") metadata_tests = filtertests(backend="mtf", location=os.getcwd(), linters=False, tests=[], tags=[], relevancy="") tests_dict = [x[metadata_common.SOURCE] for x in metadata_tests] self.tests += tests_dict core.print_debug("Loaded tests via metadata file: %s" % tests_dict) core.print_debug("tests = {0}".format(self.tests)) core.print_debug("additionalAvocadoArg = {0}".format( self.additionalAvocadoArg)) # Advanced filtering and testcases adding based on FMF metadata, see help if self.args.fmffilter or self.args.fmfpath: # if fmf path is empty, use actual directory if not self.args.fmfpath: self.args.fmfpath = ["."] try: import fmf except ImportError: mtfexceptions.ModuleFrameworkException( "FMF metadata format not installed on your system," " see fmf.readthedocs.io/en/latest/" "for more info (how to install)") core.print_debug( "Using FMF metadata: path - {} and filters - {}".format( self.args.fmfpath, common.conf["fmf"]["filters"] + self.args.fmffilter)) for onepath in self.args.fmfpath: tree = fmf.Tree(onepath) for node in tree.prune( False, common.conf["fmf"]["keys"], common.conf["fmf"]["names"], common.conf["fmf"]["filters"] + self.args.fmffilter): testcase = node.show(False, common.conf["fmf"]["format"], common.conf["fmf"]["values"]) core.print_debug("added test by FMF: {}".format(testcase)) self.tests.append(testcase)
return 0 if not paths: paths = ['.'] for path in paths: # Make sure we've got a real directory path = os.path.realpath(path) if not os.path.isdir(path): raise tmt.utils.GeneralError( "Path '{0}' is not a directory.".format(path)) # Gather old metadata and store them as fmf common, individual = tmt.convert.read(path, makefile, nitrate, purpose, disabled) # Add path to common metadata if there are virtual test cases if individual: root = fmf.Tree(path).root common['path'] = os.path.join('/', os.path.relpath(path, root)) # Store common metadata common_path = os.path.join(path, 'main.fmf') tmt.convert.write(common_path, common) # Store individual data (as virtual tests) for testcase in individual: testcase_path = os.path.join( path, str(testcase['extra-nitrate']) + '.fmf') tmt.convert.write(testcase_path, testcase) # Adjust runtest.sh content and permission if needed tmt.convert.adjust_runtest(os.path.join(path, 'runtest.sh')) @tests.command()
import pytest import fmf import tmt import tmt.plugins # Load all plugins tmt.plugins.explore() # Ignore loading/saving from/to workdir tmt.steps.execute.Execute.load = lambda self: None tmt.steps.execute.Execute.save = lambda self: None # Smoke plan smoke = tmt.Plan(fmf.Tree({'execute': {'script': 'tmt --help'}})) smoke.execute.wake() def test_smoke_method(): assert smoke.execute.data[0]['how'] == 'shell' def test_smoke_plugin(): assert isinstance( smoke.execute.plugins()[0], tmt.steps.execute.ExecutePlugin) def test_requires(): assert smoke.execute.requires() == [] # Basic plan basic = tmt.Plan(fmf.Tree({'execute': {'how': 'beakerlib'}}))
def fetch(self): """ Fetch the library (unless already fetched) """ # Initialize library cache (indexed by the repository name) if not hasattr(self.parent, '_library_cache'): self.parent._library_cache = dict() # Check if the library was already fetched try: library = self.parent._library_cache[self.repo] # The url must be identical if library.url != self.url: raise tmt.utils.GeneralError( f"Library '{self}' with url '{self.url}' conflicts " f"with already fetched library from '{library.url}'.") # Use the default branch if no ref provided if self.ref is None: self.ref = library.default_branch # The same ref has to be used if library.ref != self.ref: raise tmt.utils.GeneralError( f"Library '{self}' using ref '{self.ref}' conflicts " f"with already fetched library '{library}' " f"using ref '{library.ref}'.") self.parent.debug(f"Library '{self}' already fetched.", level=3) # Reuse the existing metadata tree self.tree = library.tree # Fetch the library and add it to the index except KeyError: self.parent.debug(f"Fetch library '{self}'.", level=3) # Prepare path, clone the repository, checkout ref directory = os.path.join(self.parent.workdir, self.dest, self.repo) # Clone repo with disabled prompt to ignore missing/private repos try: if self.url: self.parent.run(['git', 'clone', self.url, directory], env={"GIT_ASKPASS": "******"}) else: self.parent.debug( f"Copy local library '{self.path}' to '{directory}'.", level=3) shutil.copytree(self.path, directory, symlinks=True) # Detect the default branch from the origin try: self.default_branch = tmt.utils.default_branch(directory) except OSError as error: raise tmt.utils.GeneralError( f"Unable to detect default branch for '{directory}'. " f"Is the git repository '{self.url}' empty?") # Use the default branch if no ref provided if self.ref is None: self.ref = self.default_branch except tmt.utils.RunError as error: # Fallback to install during the prepare step if in rpm format if self.format == 'rpm': self.parent.debug(f"Repository '{self.url}' not found.") raise LibraryError self.parent.fail( f"Failed to fetch library '{self}' from '{self.url}'.") raise # Check out the requested branch try: self.parent.run(['git', 'checkout', self.ref], cwd=directory) except tmt.utils.RunError as error: # Fallback to install during the prepare step if in rpm format if self.format == 'rpm': self.parent.debug(f"Invalid reference '{self.ref}'.") raise LibraryError self.parent.fail( f"Reference '{self.ref}' for library '{self}' not found.") raise # Initialize metadata tree, add self into the library index self.tree = fmf.Tree(directory) self.parent._library_cache[self.repo] = self # Get the library node, check require and recommend library = self.tree.find(self.name) if not library: # Fallback to install during the prepare step if in rpm format if self.format == 'rpm': self.parent.debug( f"Library '{self.name.lstrip('/')}' not found " f"in the '{self.url}' repo.") raise LibraryError raise tmt.utils.GeneralError( f"Library '{self.name}' not found in '{self.repo}'.") self.require = tmt.utils.listify(library.get('require', [])) self.recommend = tmt.utils.listify(library.get('recommend', [])) # Create a symlink if the library is deep in the structure # FIXME: hot fix for https://github.com/beakerlib/beakerlib/pull/72 # Covers also cases when library is stored more than 2 levels deep if os.path.dirname(self.name).lstrip('/'): link = self.name.lstrip('/') path = os.path.join(self.tree.root, os.path.basename(self.name)) self.parent.debug( f"Create a '{link}' symlink as the library is stored " f"deep in the directory structure.") try: os.symlink(link, path) except OSError as error: self.parent.warn(f"Unable to create a '{link}' symlink " f"for a deep library ({error}).")
def __init__(self, fmf_tree_path: str = '.'): self._cur_path = os.path.abspath(fmf_tree_path) self._tree = fmf.Tree(self._cur_path)