[RFC,4/9] framework backends: treat backends more like plugins

Submitted by Dylan Baker on April 6, 2015, 9:30 p.m.

Details

Message ID 1428355819-12180-5-git-send-email-baker.dylan.c@gmail.com
State New, archived
Headers show

Not browsing as part of any series.

Commit Message

Dylan Baker April 6, 2015, 9:30 p.m.
This allows backends to register classes and functions using a simple
Registry object, and then uses the existing helper functions to get
those objects for use, without needing to know the details.
---
 framework/backends/__init__.py                  | 98 +++++++++++++++++++++----
 framework/backends/json.py                      | 11 ++-
 framework/backends/junit.py                     |  8 ++
 framework/backends/{__init__.py => register.py} | 22 +-----
 framework/programs/run.py                       |  7 +-
 5 files changed, 107 insertions(+), 39 deletions(-)
 copy framework/backends/{__init__.py => register.py} (66%)

Patch hide | download patch | download mbox

diff --git a/framework/backends/__init__.py b/framework/backends/__init__.py
index eee088e..c1a3a0b 100644
--- a/framework/backends/__init__.py
+++ b/framework/backends/__init__.py
@@ -1,4 +1,4 @@ 
-# Copyright (c) 2014 Intel Corporation
+# Copyright (c) 2014, 2015 Intel Corporation
 
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -18,22 +18,90 @@ 
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
-""" Import public backend classes into one place """
+"""Provides a module like interface for backends.
 
-from . import json, junit
+This package provides an abstract interface for working with backends, which
+implement various functions as provided in the Registry class, and then provide
+a Registry instance as REGISTRY, which maps individual objects to objects that
+piglit expects to use. This module then provides a thin abstraction layer so
+that piglit is never aware of what backend it's using, it just asks for an
+object and receives one.
 
-# A list of available backends
-BACKENDS = ['json', 'junit']
+Most consumers will want to import framework.backends and work directly with
+the helper functions here. For some more detailed uses (test cases expecially)
+the modules themselves may be used.
+
+When this module is loaded it will search through framework/backends for python
+modules (those ending in .py), and attempt to import them. Then it will look
+for an attribute REGISTRY in those modules, and if it as a
+framework.register.Registry instance, it will add the name of that module (sans
+.py) as a key, and the instance as a value to the BACKENDS dictionary. Each of
+the helper functions in this module uses that dictionary to find the function
+that a user actually wants.
+
+"""
+
+import os
+import importlib
+
+from .register import Registry
+
+__all__ = [
+    'BACKENDS',
+    'BackendError',
+    'BackendNotImplementedError',
+    'get_backend',
+]
+
+
+class BackendError(Exception):
+    pass
+
+
+class BackendNotImplementedError(NotImplementedError):
+    pass
+
+
+def _register():
+    """Register backends.
+
+    Walk through the list of backends and register them to a name in a
+    dictionary, so that they can be referenced from helper functions.
+    
+    """
+    registry = {}
+
+    for module in os.listdir(os.path.dirname(os.path.abspath(__file__))):
+        module, extension = os.path.splitext(module)
+        if extension == '.py':
+            mod = importlib.import_module('framework.backends.{}'.format(module))
+            if hasattr(mod, 'REGISTRY') and isinstance(mod.REGISTRY, Registry):
+                registry[module] = mod.REGISTRY
+
+    return registry
+
+
+BACKENDS = _register()
 
 
 def get_backend(backend):
-    """ Returns a BackendInstance based on the string passed """
-    backends = {
-        'json': json.JSONBackend,
-        'junit': junit.JUnitBackend,
-    }
-
-    # Be sure that we're exporting the same list of backends that we actually
-    # have available
-    assert backends.keys() == BACKENDS
-    return backends[backend]
+    """Returns a BackendInstance based on the string passed.
+
+    If the backend isn't a known module, then a BackendError will be raised, it
+    is the responsibility of the caller to handle this error.
+
+    If the backend module exists, but there is not active implementation then a
+    BackendNotImplementedError will be raised, it is also the responsiblity of
+    the caller to handle this error.
+    
+    """
+    try:
+        inst = BACKENDS[backend].backend
+    except KeyError:
+        raise BackendError('Unknown backend: {}'.format(backend))
+
+    if inst is None:
+        raise BackendNotImplementedError(
+            'Backend for {} is not implemented'.format(backend))
+
+    return inst
diff --git a/framework/backends/json.py b/framework/backends/json.py
index 347d3d7..7934ce9 100644
--- a/framework/backends/json.py
+++ b/framework/backends/json.py
@@ -31,14 +31,13 @@  except ImportError:
 
 import framework.status as status
 from .abstract import FileBackend
+from .register import Registry
 
 __all__ = [
-    'CURRENT_JSON_VERSION',
+    'REGISTRY',
     'JSONBackend',
-    'piglit_encoder',
 ]
 
-
 # The current version of the JSON results
 CURRENT_JSON_VERSION = 5
 
@@ -145,3 +144,9 @@  class JSONBackend(FileBackend):
         with open(t, 'w') as f:
             json.dump({name: data}, f, default=piglit_encoder)
             self._fsync(f)
+
+
+REGISTRY = Registry(
+    extensions=['', '.json'],
+    backend=JSONBackend,
+)
diff --git a/framework/backends/junit.py b/framework/backends/junit.py
index 3d32387..122944b 100644
--- a/framework/backends/junit.py
+++ b/framework/backends/junit.py
@@ -32,8 +32,10 @@  except ImportError:
 import framework.grouptools as grouptools
 from framework.core import PIGLIT_CONFIG
 from .abstract import FileBackend
+from .register import Registry
 
 __all__ = [
+    'REGISTRY',
     'JUnitBackend',
 ]
 
@@ -203,3 +205,9 @@  class JUnitBackend(FileBackend):
         with open(t, 'w') as f:
             f.write(etree.tostring(element))
             self._fsync(f)
+
+
+REGISTRY = Registry(
+    extensions=['.xml'],
+    backend=JUnitBackend,
+)
diff --git a/framework/backends/__init__.py b/framework/backends/register.py
similarity index 66%
copy from framework/backends/__init__.py
copy to framework/backends/register.py
index eee088e..589a0a8 100644
--- a/framework/backends/__init__.py
+++ b/framework/backends/register.py
@@ -1,4 +1,4 @@ 
-# Copyright (c) 2014 Intel Corporation
+# Copyright (c) 2015 Intel Corporation
 
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -18,22 +18,8 @@ 
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
-""" Import public backend classes into one place """
+"""An object for registering backends."""
 
-from . import json, junit
+import collections
 
-# A list of available backends
-BACKENDS = ['json', 'junit']
-
-
-def get_backend(backend):
-    """ Returns a BackendInstance based on the string passed """
-    backends = {
-        'json': json.JSONBackend,
-        'junit': junit.JUnitBackend,
-    }
-
-    # Be sure that we're exporting the same list of backends that we actually
-    # have available
-    assert backends.keys() == BACKENDS
-    return backends[backend]
+Registry = collections.namedtuple('Registry', ['extensions', 'backend'])
diff --git a/framework/programs/run.py b/framework/programs/run.py
index 8be6439..989ef45 100644
--- a/framework/programs/run.py
+++ b/framework/programs/run.py
@@ -78,9 +78,10 @@  def _default_backend():
     """
     try:
         backend = core.PIGLIT_CONFIG.get('core', 'backend')
-        if backend not in backends.BACKENDS:
+        if backend not in backends.BACKENDS.keys():
             print('Backend is not valid\n',
-                  'valid backends are: {}'.format(' '.join(backends.BACKENDS)),
+                  'valid backends are: {}'.format(
+                      ' '.join(backends.BACKENDS.keys())),
                   file=sys.stderr)
             sys.exit(1)
         return backend
@@ -127,7 +128,7 @@  def _run_parser(input_):
                              "(can be used more than once)")
     parser.add_argument('-b', '--backend',
                         default=_default_backend(),
-                        choices=backends.BACKENDS,
+                        choices=backends.BACKENDS.keys(),
                         help='select a results backend to use')
     conc_parser = parser.add_mutually_exclusive_group()
     conc_parser.add_argument('-c', '--all-concurrent',