[Piglit] [RFC 6/9] framwork: Add helper functions to abstract the backend better
Dylan Baker
baker.dylan.c at gmail.com
Mon Apr 6 14:30:16 PDT 2015
This allows the backends to be treated abstractly. Uses simply query
some factory functions which will give them an instance to work with,
which provides a standardized interface. This allows backends to be
freely plugged without any concern for what the backend actually is, the
consumer doesn't need to know at all.
Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
framework/backends/__init__.py | 68 +++++++++++++++++
framework/backends/json.py | 7 ++
framework/backends/junit.py | 2 +
framework/backends/register.py | 5 +-
framework/programs/run.py | 3 +-
framework/programs/summary.py | 2 +-
framework/summary.py | 2 +-
framework/tests/backends_tests.py | 140 ++++++++++++++++++++++++++++++++++
framework/tests/json_backend_tests.py | 13 ++++
framework/tests/utils.py | 21 +++++
10 files changed, 259 insertions(+), 4 deletions(-)
diff --git a/framework/backends/__init__.py b/framework/backends/__init__.py
index c1a3a0b..e2f199a 100644
--- a/framework/backends/__init__.py
+++ b/framework/backends/__init__.py
@@ -51,6 +51,8 @@ __all__ = [
'BackendError',
'BackendNotImplementedError',
'get_backend',
+ 'load',
+ 'set_meta',
]
@@ -105,3 +107,69 @@ def get_backend(backend):
'Backend for {} is not implemented'.format(backend))
return inst
+
+
+def load(file_path):
+ """Wrapper for loading runs.
+
+ This function will attempt to determine how to load the file (based on file
+ extension), and then pass the file path into the appropriate loader, and
+ then return the TestrunResult instance.
+
+ """
+ extension = None
+
+ if os.path.isfile(file_path):
+ extension = os.path.splitext(file_path)[1]
+ if not extension:
+ extension = ''
+ else:
+ for file in os.listdir(file_path):
+ if file.startswith('result'):
+ extension = os.path.splitext(file)[1]
+ break
+ elif file == 'main':
+ extension = ''
+ break
+ tests = os.path.join(file_path, 'tests')
+ if extension is None:
+ if os.path.exists(tests):
+ extension = os.path.splitext(os.listdir(tests)[0])[1]
+ else:
+ # At this point we have failed to find any sort of backend, just except
+ # and die
+ raise BackendError("No backend found for any file in {}".format(
+ file_path))
+
+ for backend in BACKENDS.itervalues():
+ if extension in backend.extensions:
+ loader = backend.load
+ break
+ else:
+ raise BackendError(
+ 'No module supports file extensions "{}"'.format(extension))
+
+ if loader is None:
+ raise BackendNotImplementedError(
+ 'Loader for {} is not implemented'.format(extension))
+
+ return loader(file_path)
+
+
+def set_meta(backend, result):
+ """Wrapper around meta that gets the right meta function."""
+ try:
+ BACKENDS[backend].meta(result)
+ except KeyError:
+ raise BackendError('No backend {}'.format(backend))
+ except TypeError as e:
+ # Since we initialize non-implemented backends as None, and None isn't
+ # callable then we'll get a TypeError, and we're looking for NoneType
+ # in the message. If we get that we really want a
+ # BackendNotImplementedError
+ if e.message == "'NoneType' object is not callable":
+ raise BackendNotImplementedError(
+ 'meta function for {} not implemented.'.format(backend))
+ else:
+ # Otherwise re-raise the error
+ raise
diff --git a/framework/backends/json.py b/framework/backends/json.py
index 52bdd08..affd64e 100644
--- a/framework/backends/json.py
+++ b/framework/backends/json.py
@@ -195,6 +195,11 @@ def load_results(filename):
return _update_results(testrun, filepath)
+def set_meta(results):
+ """Set json specific metadata on a TestrunResult."""
+ results.results_version = CURRENT_JSON_VERSION
+
+
def _load(results_file):
"""Load a json results instance and return a TestrunResult.
@@ -521,4 +526,6 @@ def _update_four_to_five(results):
REGISTRY = Registry(
extensions=['', '.json'],
backend=JSONBackend,
+ load=load_results,
+ meta=set_meta,
)
diff --git a/framework/backends/junit.py b/framework/backends/junit.py
index 122944b..5ec4e8e 100644
--- a/framework/backends/junit.py
+++ b/framework/backends/junit.py
@@ -210,4 +210,6 @@ class JUnitBackend(FileBackend):
REGISTRY = Registry(
extensions=['.xml'],
backend=JUnitBackend,
+ load=None,
+ meta=lambda x: x, # The venerable no-op function
)
diff --git a/framework/backends/register.py b/framework/backends/register.py
index 589a0a8..a52083b 100644
--- a/framework/backends/register.py
+++ b/framework/backends/register.py
@@ -22,4 +22,7 @@
import collections
-Registry = collections.namedtuple('Registry', ['extensions', 'backend'])
+Registry = collections.namedtuple(
+ 'Registry',
+ ['extensions', 'backend', 'load', 'meta']
+)
diff --git a/framework/programs/run.py b/framework/programs/run.py
index 989ef45..ca94596 100644
--- a/framework/programs/run.py
+++ b/framework/programs/run.py
@@ -271,6 +271,7 @@ def run(input_):
core.checkDir(args.results_path, False)
results = framework.results.TestrunResult()
+ backends.set_meta(args.backend, results)
# Set results.name
if args.name is not None:
@@ -315,7 +316,7 @@ def resume(input_):
args = parser.parse_args(input_)
_disable_windows_exception_messages()
- results = framework.results.TestrunResult.resume(args.results_path)
+ results = backends.load(args.results_path)
opts = core.Options(concurrent=results.options['concurrent'],
exclude_filter=results.options['exclude_filter'],
include_filter=results.options['filter'],
diff --git a/framework/programs/summary.py b/framework/programs/summary.py
index f098856..fc397f4 100644
--- a/framework/programs/summary.py
+++ b/framework/programs/summary.py
@@ -155,7 +155,7 @@ def csv(input_):
args = parser.parse_args(input_)
try:
- testrun = backends.json.load_results(args.testResults)
+ testrun = backends.load(args.testResults)
except backends.errors.ResultsLoadError as e:
print('Error: {}'.format(e.message), file=sys.stderr)
sys.exit(1)
diff --git a/framework/summary.py b/framework/summary.py
index c027d76..9b30b5e 100644
--- a/framework/summary.py
+++ b/framework/summary.py
@@ -300,7 +300,7 @@ class Summary:
# Create a Result object for each piglit result and append it to the
# results list
try:
- self.results = [backends.json.load_results(i) for i in resultfiles]
+ self.results = [backends.load(i) for i in resultfiles]
except backends.errors.ResultsLoadError as e:
print('Error: {}'.format(e.message), file=sys.stderr)
sys.exit(1)
diff --git a/framework/tests/backends_tests.py b/framework/tests/backends_tests.py
index c15543d..108f114 100644
--- a/framework/tests/backends_tests.py
+++ b/framework/tests/backends_tests.py
@@ -47,6 +47,30 @@ JUNIT_SCHEMA = 'framework/tests/schema/junit-7.xsd'
doc_formatter = utils.DocFormatter({'seperator': grouptools.SEPARATOR})
+# Helpers
+
+def _notimplemented_setup():
+ """Setup function that injects a new test Registry into the BACKENDS
+ variable.
+
+ should be used in conjunction with the _registry_teardown method.
+
+ """
+ backends.BACKENDS['test_backend'] = backends.register.Registry(
+ extensions=['.test_backend'],
+ backend=None,
+ load=None,
+ meta=None,
+ )
+
+
+def _registry_teardown():
+ """Remove the test_backend Register from backends.BACKENDS."""
+ del backends.BACKENDS['test_backend']
+
+
+# Tests
+
@utils.nose_generator
def test_get_backend():
@@ -66,6 +90,122 @@ def test_get_backend():
yield check, name, inst
+ at nt.raises(backends.BackendError)
+def test_get_backend_unknown():
+ """backends.get_backend: An error is raised with an unkown backend."""
+ backends.get_backend('obviously fake backend')
+
+
+ at nt.raises(backends.BackendNotImplementedError)
+ at nt.with_setup(_notimplemented_setup, _registry_teardown)
+def test_get_backend_notimplemented():
+ """backends.get_backend: An error is raised if a backend isn't implemented.
+ """
+ backends.get_backend('test_backend')
+
+
+ at nt.with_setup(teardown=_registry_teardown)
+ at utils.test_in_tempdir
+def test_load():
+ """backends.load(): works as expected.
+
+ This is an interesting function to test, because it is just a wrapper that
+ returns a TestrunResult object. So most of the testing should be happening
+ in the tests for each backend.
+
+ However, we can test this by injecting a fake backend, and ensuring that we
+ get back what we expect. What we do is inject list(), which menas that we
+ should get back [file_path].
+
+ """
+ backends.BACKENDS['test_backend'] = backends.register.Registry(
+ extensions=['.test_extension'],
+ backend=None,
+ load=lambda x: [x],
+ meta=None,
+ )
+
+ file_path = 'foo.test_extension'
+ with open(file_path, 'w') as f:
+ f.write('foo')
+
+ test = backends.load(file_path)
+ nt.assert_list_equal([file_path], test)
+
+
+ at nt.raises(backends.BackendError)
+ at utils.test_in_tempdir
+def test_load_unknown():
+ """backends.load(): An error is raised if no modules supportes `extension`
+ """
+ file_path = 'foo.test_extension'
+
+ with open(file_path, 'w') as f:
+ f.write('foo')
+ backends.load(file_path)
+
+
+ at utils.no_error
+ at nt.with_setup(_notimplemented_setup, _registry_teardown)
+ at utils.test_in_tempdir
+def test_load_resume():
+ """backends.load: works for resuming (no extension known)."""
+ backends.BACKENDS['test_backend'] = backends.register.Registry(
+ extensions=['.test_backend'],
+ backend=None,
+ load=lambda x: x,
+ meta=None,
+ )
+ os.mkdir('tests')
+ name = os.path.join('tests', '0.test_backend')
+ with open(name, 'w') as f:
+ f.write('foo')
+
+ backends.load('.')
+
+
+ at nt.raises(backends.BackendNotImplementedError)
+ at nt.with_setup(_notimplemented_setup, _registry_teardown)
+ at utils.test_in_tempdir
+def test_load_notimplemented():
+ """backends.load(): An error is raised if a loader isn't properly implmented.
+ """
+ file_path = 'foo.test_backend'
+ with open(file_path, 'w') as f:
+ f.write('foo')
+
+ backends.load(file_path)
+
+
+def test_set_meta():
+ """backends.set_meta(): Works as expected."""
+ backends.BACKENDS['test_backend'] = backends.register.Registry(
+ extensions=None,
+ backend=None,
+ load=None,
+ meta=lambda x: x.append('bar'),
+ )
+
+ test = ['foo']
+
+ backends.set_meta('test_backend', test)
+ nt.assert_list_equal(test, ['foo', 'bar'])
+
+
+ at nt.raises(backends.BackendError)
+def test_set_meta_no_backened():
+ """backends.set_meta: raises an error if there is no meta function."""
+ backends.set_meta('foo', {})
+
+
+ at nt.raises(backends.BackendNotImplementedError)
+ at nt.with_setup(_notimplemented_setup, _registry_teardown)
+def test_set_meta_notimplemented():
+ """backends.load(): An error is raised if a set_meta isn't properly implmented.
+ """
+ backends.set_meta('test_backend', {})
+
+
class TestJunitNoTests(utils.StaticDirectory):
@classmethod
def setup_class(cls):
diff --git a/framework/tests/json_backend_tests.py b/framework/tests/json_backend_tests.py
index 54f24dd..45626e8 100644
--- a/framework/tests/json_backend_tests.py
+++ b/framework/tests/json_backend_tests.py
@@ -273,3 +273,16 @@ def test_load_results_file():
""" Test that load_results takes a file """
with utils.resultfile() as tfile:
backends.json.load_results(tfile.name)
+
+
+def test_load_json():
+ """backends.load(): Loads .json files."""
+ with utils.tempdir() as tdir:
+ filename = os.path.join(tdir, 'results.json')
+ with open(filename, 'w') as f:
+ json.dump(utils.JSON_DATA, f)
+
+ result = backends.load(filename)
+
+ nt.assert_is_instance(result, results.TestrunResult)
+ nt.assert_in('sometest', result.tests)
diff --git a/framework/tests/utils.py b/framework/tests/utils.py
index 556f523..f59f6eb 100644
--- a/framework/tests/utils.py
+++ b/framework/tests/utils.py
@@ -340,3 +340,24 @@ class DocFormatter(object):
raise UtilsError(e)
return func
+
+
+def test_in_tempdir(func):
+ """Decorator that moves to a new directory to run a test.
+
+ This decorator ensures that the test moves to a new directory, and then
+ returns to the original directory after the test completes.
+
+ """
+ original_dir = os.getcwd()
+
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ with tempdir() as tdir:
+ try:
+ os.chdir(tdir)
+ func(*args, **kwargs)
+ finally:
+ os.chdir(original_dir)
+
+ return wrapper
--
2.3.5
More information about the Piglit
mailing list