[Piglit] [PATCH v2 01/16] framework/exceptions.py: add a new module for unified piglit exceptions

Dylan Baker baker.dylan.c at gmail.com
Mon May 18 10:57:35 PDT 2015


This is the basis of providing clean, unified exception handling to
piglit, this provides a set of Exception classes and a function
decorator to be used on main functions. Together these provide well
formatted error messages that differentiate between expected failures
and unexpected failures and provide clean, readable error messages.

The goal is that this will give us the ability to get clean errors when
they are expected, and the ability to hide stack traces when they're
not.

Stack traces for unexpected errors can be turned back on by setting the
PIGLIT_DEBUG environment variable

Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
 framework/exceptions.py             | 97 +++++++++++++++++++++++++++++++++++++
 framework/programs/run.py           |  5 +-
 framework/programs/summary.py       |  6 ++-
 framework/tests/exceptions_tests.py | 60 +++++++++++++++++++++++
 framework/tests/utils.py            | 19 ++++++++
 5 files changed, 184 insertions(+), 3 deletions(-)
 create mode 100644 framework/exceptions.py
 create mode 100644 framework/tests/exceptions_tests.py

diff --git a/framework/exceptions.py b/framework/exceptions.py
new file mode 100644
index 0000000..1d8cd34
--- /dev/null
+++ b/framework/exceptions.py
@@ -0,0 +1,97 @@
+# 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
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""Exception and error classes for piglit, and exception handlers."""
+
+from __future__ import print_function, absolute_import, division
+import os
+import sys
+import functools
+
+__all__ = [
+    'PiglitInternalError',
+    'PiglitFatalError',
+    'PiglitException',
+    'handler',
+]
+
+_DEBUG = bool(os.environ.get('PIGLIT_DEBUG', False))
+
+
+def handler(func):
+    """Decorator function for handling errors in an entry point.
+
+    This will handle expected errors (PiglitFatalError), and unexpected errors,
+    either PiglitInternalErrors or PiglitExceptions, as well as handling
+    generic Exceptions
+
+    """
+
+    @functools.wraps(func)
+    def _inner(*args, **kwargs):
+        try:
+            func(*args, **kwargs)
+        except PiglitFatalError as e:
+            print('Fatal Error: {}'.format(e.message), file=sys.stderr)
+            sys.exit(1)
+        except (PiglitInternalError, PiglitException) as e:
+            print('Warning: An internal exception that should have '
+                  'been handled was not. This is bug and should be reported.\n'
+                  'BUG: {}'.format(e.message),
+                  file=sys.stderr)
+            if _DEBUG:
+                raise e
+            sys.exit(1)
+        except Exception as e:  # pylint: disable=broad-except
+            print('Warning: A python exception that should have '
+                  'been handled was not. This is bug and should be reported.\n'
+                  'BUG: {}'.format(e.message),
+                  file=sys.stderr)
+            if _DEBUG:
+                raise e
+            sys.exit(1)
+
+    return _inner
+
+
+class PiglitException(Exception):
+    """Class for non-error exceptions.
+
+    These should *always* be caught. If this class (or any subclass) is
+    uncaught that is a bug in piglit.
+
+    """
+
+
+class PiglitInternalError(Exception):
+    """Class for errors in piglit.
+
+    These should always be handled.
+
+    """
+
+
+class PiglitFatalError(Exception):
+    """Class for errors in piglit that cannot be recovered from.
+
+    When this class (or a subclass) is raised it should be raised all the way
+    to the top of the program where it exits.
+
+    """
diff --git a/framework/programs/run.py b/framework/programs/run.py
index 6053074..f3b1963 100644
--- a/framework/programs/run.py
+++ b/framework/programs/run.py
@@ -29,10 +29,9 @@ import time
 import ConfigParser
 import ctypes
 
-import framework.core as core
+from framework import core, backends, exceptions
 import framework.results
 import framework.profile
-import framework.backends as backends
 
 __all__ = ['run',
            'resume']
@@ -232,6 +231,7 @@ def _disable_windows_exception_messages():
         ctypes.windll.kernel32.SetErrorMode(uMode)
 
 
+ at exceptions.handler
 def run(input_):
     """ Function for piglit run command
 
@@ -302,6 +302,7 @@ def run(input_):
           'Results have been written to ' + args.results_path)
 
 
+ at exceptions.handler
 def resume(input_):
     parser = argparse.ArgumentParser()
     parser.add_argument("results_path",
diff --git a/framework/programs/summary.py b/framework/programs/summary.py
index 2d51acb..1ec00bc 100644
--- a/framework/programs/summary.py
+++ b/framework/programs/summary.py
@@ -27,7 +27,7 @@ import os.path as path
 import sys
 import errno
 
-from framework import summary, status, core, backends
+from framework import summary, status, core, backends, exceptions
 
 __all__ = [
     'aggregate',
@@ -37,6 +37,7 @@ __all__ = [
 ]
 
 
+ at exceptions.handler
 def html(input_):
     # Make a copy of the status text list and add all. This is used as the
     # argument list for -e/--exclude
@@ -101,6 +102,7 @@ def html(input_):
     output.generate_html(args.summaryDir, args.exclude_details)
 
 
+ at exceptions.handler
 def console(input_):
     parser = argparse.ArgumentParser()
 
@@ -149,6 +151,7 @@ def console(input_):
     output.generate_text(args.mode or 'all')
 
 
+ at exceptions.handler
 def csv(input_):
     parser = argparse.ArgumentParser()
     parser.add_argument("-o", "--output",
@@ -181,6 +184,7 @@ def csv(input_):
         write_results(sys.stdout)
 
 
+ at exceptions.handler
 def aggregate(input_):
     """Combine files in a tests/ directory into a single results file."""
     parser = argparse.ArgumentParser()
diff --git a/framework/tests/exceptions_tests.py b/framework/tests/exceptions_tests.py
new file mode 100644
index 0000000..66fd8e6
--- /dev/null
+++ b/framework/tests/exceptions_tests.py
@@ -0,0 +1,60 @@
+# 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
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+"""Tests for the exceptions module."""
+
+from __future__ import print_function, absolute_import, division
+
+import nose.tools as nt
+
+from framework.tests import utils
+from framework import exceptions
+
+
+ at nt.raises(SystemExit)
+ at utils.capture_stderr
+ at exceptions.handler
+def test_handle_PiglitFatalError():
+    """exceptions.handler: Handles PiglitFatalError"""
+    raise exceptions.PiglitFatalError
+
+
+ at nt.raises(SystemExit)
+ at utils.capture_stderr
+ at exceptions.handler
+def test_handle_PiglitInternalError():
+    """exceptions.handler: Handles PiglitInternalError"""
+    raise exceptions.PiglitInternalError
+
+
+ at nt.raises(SystemExit)
+ at utils.capture_stderr
+ at exceptions.handler
+def test_handle_PiglitException():
+    """exceptions.handler: Handles PiglitException"""
+    raise exceptions.PiglitException
+
+
+ at nt.raises(SystemExit)
+ at utils.capture_stderr
+ at exceptions.handler
+def test_handle_Exception():
+    """exceptions.handler: Handles Exception"""
+    raise Exception
diff --git a/framework/tests/utils.py b/framework/tests/utils.py
index 9ba412b..abc8ef5 100644
--- a/framework/tests/utils.py
+++ b/framework/tests/utils.py
@@ -341,3 +341,22 @@ def no_error(func):
 
     """
     return not_raises(Exception)(func)
+
+
+def capture_stderr(func):
+    """Redirect stderr to stdout for nose capture.
+
+    It would probably be better to implement a full stderr handler as a
+    plugin...
+
+    """
+    @functools.wraps(func)
+    def _inner(*args, **kwargs):
+        restore = sys.stderr
+        sys.stderr = sys.stdout
+        try:
+            func(*args, **kwargs)
+        finally:
+            sys.stderr = restore
+
+    return _inner
-- 
2.4.0



More information about the Piglit mailing list