Skip to content

Commit 4b87247

Browse files
committed
pytest plugin metadata loader
1 parent c08e904 commit 4b87247

File tree

7 files changed

+141
-12
lines changed

7 files changed

+141
-12
lines changed

fmf/base.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,10 @@
1616
import fmf.utils as utils
1717
from io import open
1818
from fmf.utils import log, dict_to_yaml
19+
from fmf.constants import SUFFIX, IGNORED_DIRECTORIES, MAIN
20+
from fmf.plugin_loader import get_suffixes, get_plugin_for_file
1921
from pprint import pformat as pretty
2022

21-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
22-
# Constants
23-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24-
25-
SUFFIX = ".fmf"
26-
MAIN = "main" + SUFFIX
27-
IGNORED_DIRECTORIES = ['/dev', '/proc', '/sys']
28-
2923
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3024
# YAML
3125
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -95,7 +89,6 @@ def __init__(self, data, name=None, parent=None):
9589
# Track whether the data dictionary has been updated
9690
# (needed to prevent removing nodes with an empty dict).
9791
self._updated = False
98-
9992
# Special handling for top parent
10093
if self.parent is None:
10194
self.name = "/"
@@ -453,7 +446,7 @@ def grow(self, path):
453446
return
454447
# Investigate main.fmf as the first file (for correct inheritance)
455448
filenames = sorted(
456-
[filename for filename in filenames if filename.endswith(SUFFIX)])
449+
[filename for filename in filenames if any(filter(filename.endswith, get_suffixes()))])
457450
try:
458451
filenames.insert(0, filenames.pop(filenames.index(MAIN)))
459452
except ValueError:
@@ -465,8 +458,12 @@ def grow(self, path):
465458
fullpath = os.path.abspath(os.path.join(dirpath, filename))
466459
log.info("Checking file {0}".format(fullpath))
467460
try:
468-
with open(fullpath, encoding='utf-8') as datafile:
469-
data = yaml.load(datafile, Loader=YamlLoader)
461+
if fullpath.endswith(SUFFIX):
462+
with open(fullpath, encoding='utf-8') as datafile:
463+
data = yaml.load(datafile, Loader=YamlLoader)
464+
else:
465+
plugin = get_plugin_for_file(fullpath)
466+
data = plugin().get_data(fullpath)
470467
except yaml.error.YAMLError as error:
471468
raise(utils.FileError("Failed to parse '{0}'.\n{1}".format(
472469
fullpath, error)))

fmf/constants.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2+
# Constants
3+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4+
5+
SUFFIX = ".fmf"
6+
MAIN = "main" + SUFFIX
7+
IGNORED_DIRECTORIES = ['/dev', '/proc', '/sys']
8+
# comma separated list for plugin env var
9+
PLUGIN_ENV = "PLUGINS"

fmf/plugin_loader.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from functools import lru_cache
2+
import inspect
3+
from fmf.constants import PLUGIN_ENV, SUFFIX
4+
from fmf.utils import log
5+
import importlib
6+
import os
7+
8+
9+
10+
class Plugin:
11+
"""
12+
Main abstact class for FMF plugins
13+
"""
14+
# you have to define extension list as class attribute e.g. [".py"]
15+
extensions = list()
16+
17+
def get_data(self, filename):
18+
raise NotImplemented("Define own impementation")
19+
20+
21+
@lru_cache(maxsize=1)
22+
def enabled_plugins():
23+
plugins = os.getenv(PLUGIN_ENV).split(",") if os.getenv(PLUGIN_ENV) else []
24+
plugin_list = list()
25+
for item in plugins:
26+
loader = importlib.machinery.SourceFileLoader(os.path.basename(item), item)
27+
module = importlib.util.module_from_spec(
28+
importlib.util.spec_from_loader(loader.name, loader)
29+
)
30+
loader.exec_module(module)
31+
for name, item in inspect.getmembers(module):
32+
if inspect.isclass(item) and issubclass(item, Plugin):
33+
plugin_list.append(item)
34+
log.info("Loaded plugin {}".format(item))
35+
return plugin_list
36+
37+
38+
def get_suffixes():
39+
output = [SUFFIX]
40+
for item in enabled_plugins():
41+
output += item.extensions
42+
return output
43+
44+
45+
def get_plugin_for_file(filename):
46+
extension = "." + filename.rsplit(".", 1)[1]
47+
for item in enabled_plugins():
48+
if extension in item.extensions:
49+
log.debug("File {} parsed by by plugin {}".format(filename, item))
50+
return item

fmf/plugins/pytest.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from fmf.plugin_loader import Plugin
2+
from fmf.utils import log
3+
from fmf_metadata.pytest_collector import collect
4+
from fmf_metadata.constants import PYTEST_DEFAULT_CONF
5+
from fmf_metadata.base import _Test, _TestCls, define_undefined
6+
from fmf_metadata.base import FMF
7+
import re
8+
import os
9+
10+
_ = FMF
11+
12+
def update_data(store_dict, func, config):
13+
keys = []
14+
filename = os.path.basename(func.fspath)
15+
if func.cls:
16+
cls = _TestCls(func.cls, filename)
17+
keys.append(cls.name)
18+
else:
19+
cls = _TestCls(None, filename)
20+
test = _Test(func)
21+
# normalise test name to pytest identifier
22+
test.name = re.search(
23+
f".*({os.path.basename(func.function.__name__)}.*)", func.name
24+
).group(1)
25+
# TODO: removed str_normalise(...) will see what happen
26+
keys.append(test.name)
27+
define_undefined(store_dict, keys, config, filename, cls, test)
28+
return store_dict
29+
30+
31+
class Pytest(Plugin):
32+
extensions = [".py"]
33+
34+
def get_data(self, file_name):
35+
out = dict()
36+
for item in collect([file_name]):
37+
update_data(store_dict=out, func=item, config=PYTEST_DEFAULT_CONF)
38+
log.info("Processing Item: {}".format(item))
39+
return out

tests/tests_plugin/.fmf/version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1

tests/tests_plugin/main.fmf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
author: Jan Scotka <jscotka@redhat.com>
2+
3+
/pure_fmf:
4+
test: ./runtest.sh
5+
summary: Pure FMF test case

tests/tests_plugin/test_basic.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
import unittest
3+
from fmf_metadata import FMF
4+
5+
6+
@FMF.tag("Tier1")
7+
@FMF.summary("This is basic testcase")
8+
def test_pass():
9+
assert True
10+
11+
12+
def test_fail():
13+
assert False
14+
15+
16+
@pytest.mark.skip
17+
def test_skip():
18+
assert True
19+
20+
21+
@pytest.mark.parametrize("test_input", ["a", "b", "c"])
22+
def test_parametrize(test_input):
23+
assert bool(test_input)
24+
25+
26+
class TestCls(unittest.TestCase):
27+
def test(self):
28+
self.assertTrue(True)

0 commit comments

Comments
 (0)