[Piglit] [Patch v2 04/11] framework/shader_test.py: simplify ShaderTest

Dylan Baker baker.dylan.c at gmail.com
Thu Mar 27 15:33:57 PDT 2014


This patch almost completely reworks ShaderTest. It was originally much
more complicated than was necessary, this code should be much easier to
work with, and removes some quirks. One of those was that ShaderTest
inherited from PlainExecTest, but used ExecTest's __init__ method, which
has implications for changing either of those classes.

v2: - remove shader_test_tests. This only had an initializer test, and
      that no longer applies since ShaderTest is now a function
v3: - Don't remove shader_test_tests
    - Keep ShaderTest as a class rather than function
    - make looping over test files more efficient
    - make errors more specific
    - make tests more precise

Signed-off-by: Dylan Baker <baker.dylan.c at gmail.com>
---
 framework/shader_test.py             | 244 ++++++++++-------------------------
 framework/tests/dmesg_tests.py       |   3 +-
 framework/tests/shader_test_tests.py |  15 ++-
 3 files changed, 80 insertions(+), 182 deletions(-)

diff --git a/framework/shader_test.py b/framework/shader_test.py
index 81fef09..f257b56 100644
--- a/framework/shader_test.py
+++ b/framework/shader_test.py
@@ -27,12 +27,80 @@ import os
 import os.path as path
 import re
 
-from .core import testBinDir, Group, Test, TestResult
+from .core import testBinDir, Group
 from .exectest import PlainExecTest
 
+__all__ = ['add_shader_test', 'add_shader_test_dir']
+
+
+class ShaderTest(PlainExecTest):
+    """ Parse a shader test file and return a PlainExecTest instance
+
+    This function parses a shader test to determine if it's a GL, GLES2 or
+    GLES3 test, and then returns a PlainExecTest setup properly.
+
+    """
+    def __init__(self, arguments):
+        is_gl = re.compile(r'GL (<|<=|=|>=|>) \d\.\d')
+        # Iterate over the lines in shader file looking for the config section.
+        # By using a generator this can be split into two for loops at minimal
+        # cost. The first one looks for the start of the config block or raises
+        # an exception. The second looks for the GL version or raises an
+        # exception
+        with open(arguments, 'r') as shader_file:
+            lines = (l for l in shader_file)
+
+            # Find the config section
+            for line in lines:
+                # We need to find the first line of the configuration file, as
+                # soon as we do then we can move on to geting the
+                # configuration. The first line needs to be parsed by the next
+                # block.
+                if line.lstrip().startswith('[require]'):
+                    break
+            else:
+                raise ShaderTestParserException("Config block not found")
+
+            # Find the OpenGL API to use
+            for line in lines:
+                line = line.strip()
+                if line.startswith('GL ES'):
+                    if line.endswith('3.0'):
+                        prog = path.join(testBinDir, 'shader_runner_gles3')
+                    elif line.endswith('2.0'):
+                        prog = path.join(testBinDir, 'shader_runner_gles2')
+                    # If we don't set gles2 or gles3 continue the loop,
+                    # probably htting the exception in the for/else
+                    else:
+                        raise ShaderTestParserException("No GL ES version set")
+                    break
+                elif line.startswith('[') or is_gl.match(line):
+                    # In the event that we reach the end of the config black
+                    # and an API hasn't been found, it's an old test and uses
+                    # "GL"
+                    prog = path.join(testBinDir, 'shader_runner')
+                    break
+            else:
+                raise ShaderTestParserException("No GL version set")
+
+        super(ShaderTest, self).__init__([prog, arguments, '-auto'])
+
+
+class ShaderTestParserException(Exception):
+    """ An excpetion to be raised for errors in the ShaderTest parser """
+    pass
+
 
 def add_shader_test(group, testname, filepath):
-    group[testname] = ShaderTest([filepath, '-auto'])
+    """ Add a shader test to a group
+
+    Arguments:
+    group -- a dictionary-like object to add the test to
+    testname -- the key to use in the group
+    filepath -- the argument to pass to ShaderTest
+
+    """
+    group[testname] = ShaderTest(filepath)
 
 
 def add_shader_test_dir(group, dirpath, recursive=False):
@@ -51,175 +119,3 @@ def add_shader_test_dir(group, dirpath, recursive=False):
                 continue
             testname = filename[0:-(len(ext) + 1)]  # +1 for '.'
             add_shader_test(group, testname, filepath)
-
-
-class ShaderTest(PlainExecTest):
-    API_ERROR = 0
-    API_GL = 1
-    API_GLES2 = 2
-    API_GLES3 = 3
-
-    __has_compiled_regexes = False
-    __re_require_header = None
-    __re_gl = None
-    __re_gles2 = None
-    __re_gles3 = None
-    __re_gl_unknown = None
-
-    @classmethod
-    def __compile_regexes(cls):
-        """Compile the regular expressions needed to parse shader tests.
-
-        Hundreds, maybe thousands, of ShaderTests may be instantiated.  This
-        function compiles the regular expressions only once, at class scope,
-        and uses them for all instances.
-
-        This function is idempotent."""
-
-        if cls.__has_compiled_regexes:
-            return
-
-        common = {
-            'cmp': r'(<|<=|=|>=|>)',
-            'gl_version': r'(\d.\d)',
-            'gles2_version': r'(2.\d\s)',
-            'gles3_version': r'(3.\d\s)',
-            'comment': r'(#.*)'
-        }
-
-        cls.__re_require_header = re.compile(r'^\s*\[require\]'
-                                             '\s*{comment}?$'.format(**common))
-        cls.__re_end_require_block = re.compile(r'^\s*\['.format(*common))
-        cls.__re_gl = re.compile(r'^\s*GL\s*{cmp}\s*{gl_version}\s*{comment}'
-                                 '?$'.format(**common))
-        cls.__re_gles2 = re.compile(r'^\s*GL ES\s*{cmp}\s*{gles2_version}'
-                                    '\s*{comment}?$'.format(**common))
-        cls.__re_gles3 = re.compile(r'^\s*GL ES\s*{cmp}\s*{gles3_version}'
-                                    '\s*{comment}?$'.format(**common))
-        cls.__re_gl_unknown = re.compile(r'^\s*GL\s*{cmp}'.format(**common))
-
-    def __init__(self, shader_runner_args):
-        Test.__init__(self, runConcurrent=True)
-
-        assert(isinstance(shader_runner_args, list))
-        assert(isinstance(shader_runner_args[0], str) or
-               isinstance(shader_runner_args[0], unicode))
-
-        self.__shader_runner_args = shader_runner_args
-        self.__test_filepath = shader_runner_args[0]
-        self.__result = None
-        self.__command = None
-        self.__gl_api = None
-
-        self.env = {}
-
-    def __report_failure(self, message):
-        assert(self.__result is None)
-        self.__result = TestResult()
-        self.__result["result"] = "fail"
-        self.__result["errors"] = [message]
-
-    def __parse_test_file(self):
-        self.__set_gl_api()
-
-    def __set_gl_api(self):
-        """Set self.__gl_api by parsing the test's requirement block.
-
-        This function is idempotent."""
-
-        if self.__gl_api is not None:
-            return
-
-        cls = self.__class__
-        cls.__compile_regexes()
-
-        PARSE_FIND_REQUIRE_HEADER = 0
-        PARSE_FIND_GL_REQUIREMENT = 1
-
-        parse_state = PARSE_FIND_REQUIRE_HEADER
-
-        try:
-            with open(self.__test_filepath) as f:
-                for line in f:
-                    if parse_state == PARSE_FIND_REQUIRE_HEADER:
-                        if cls.__re_require_header.match(line) is not None:
-                            parse_state = PARSE_FIND_GL_REQUIREMENT
-                        else:
-                            continue
-                    elif parse_state == PARSE_FIND_GL_REQUIREMENT:
-                        if cls.__re_gl.match(line) is not None:
-                            self.__gl_api = ShaderTest.API_GL
-                            return
-                        elif cls.__re_gles2.match(line) is not None:
-                            self.__gl_api = ShaderTest.API_GLES2
-                            return
-                        elif cls.__re_gles3.match(line) is not None:
-                            self.__gl_api = ShaderTest.API_GLES3
-                            return
-                        elif cls.__re_gl_unknown.match(line) is not None:
-                            self.__report_failure("Failed to parse GL "
-                                                  "requirement: " + line)
-                            self.__gl_api = ShaderTest.API_ERROR
-                            return
-                        elif cls.__re_end_require_block.match(line):
-                            # Default to GL if no API is given.
-                            self.__gl_api = ShaderTest.API_GL
-                            return
-                        else:
-                            continue
-                    else:
-                        assert(False)
-
-                if parse_state == PARSE_FIND_REQUIRE_HEADER or \
-                   parse_state == PARSE_FIND_GL_REQUIREMENT:
-                    # If no requirements are found, then assume the required
-                    # API is GL. This matches the behavior of the
-                    # shader_runner executable, whose default requirements are
-                    # GL >= 1.0 and GLSL >= 1.10.
-                    self.__gl_api = ShaderTest.API_GL
-                else:
-                    assert(False)
-
-        except IOError:
-            self.__report_failure("Failed to read test file "
-                                  "{0!r}".format(self.__test_filepath))
-            return
-
-    @property
-    def command(self):
-        if self.__command is not None:
-            return self.__command
-
-        self.__set_gl_api()
-
-        if self.__result is not None:
-            assert(self.__result["result"] == "fail")
-            return ["/bin/false"]
-
-        if self.__gl_api == ShaderTest.API_GL:
-            runner = "shader_runner"
-        elif self.__gl_api == ShaderTest.API_GLES2:
-            runner = "shader_runner_gles2"
-        elif self.__gl_api == ShaderTest.API_GLES3:
-            runner = "shader_runner_gles3"
-        else:
-            assert(False)
-
-        runner = os.path.join(testBinDir, runner)
-        self.__command = [runner] + self.__shader_runner_args
-        return self.__command
-
-    def run(self, env):
-        """ Parse the test file's [require] block to determine which
-        executable is needed to run the test. Then run the executable on the
-        test file."""
-
-        # Parse the test file to discover any errors.
-        self.__parse_test_file()
-
-        if self.__result is not None:
-            # We've already decided the test result, most likely because
-            # parsing the test file discovered an error.
-            return self.__result
-
-        return PlainExecTest.run(self, env)
diff --git a/framework/tests/dmesg_tests.py b/framework/tests/dmesg_tests.py
index 63d9051..d7c596a 100644
--- a/framework/tests/dmesg_tests.py
+++ b/framework/tests/dmesg_tests.py
@@ -233,8 +233,7 @@ def test_testclasses_dmesg():
 
     lists = [(PlainExecTest, ['attribs', '-auto', '-fbo'], 'PlainExecTest'),
              (GleanTest, 'basic', "GleanTest"),
-             (ShaderTest,
-              ['tests/shaders/loopfunc.shader_test', '-auto', '-fbo'],
+             (ShaderTest, 'tests/shaders/loopfunc.shader_test',
               'ShaderTest'),
              (GLSLParserTest, 'tests/glslparsertest/shaders/main1.vert',
               'GLSLParserTest')]
diff --git a/framework/tests/shader_test_tests.py b/framework/tests/shader_test_tests.py
index b254e89..6600dee 100644
--- a/framework/tests/shader_test_tests.py
+++ b/framework/tests/shader_test_tests.py
@@ -28,17 +28,20 @@ import framework.tests.utils as utils
 
 def test_initialize_shader_test():
     """ Test that ShaderTest initializes """
-    test = shader_test.ShaderTest(['loopfunc.shader_test'])
-    assert test
+    shader_test.ShaderTest('tests/spec/glsl-es-1.00/execution/sanity.shader_test')
 
 
- at nt.raises(AssertionError)
 def test_parse_gl_test_no_decimal():
     """ The GL Parser raises an exception if GL version lacks decimal """
     data = ('[require]\n'
             'GL = 2\n')
     with utils.with_tempfile(data) as temp:
-        test = shader_test.ShaderTest([temp])
+        with nt.assert_raises(shader_test.ShaderTestParserException) as exc:
+            shader_test.ShaderTest(temp)
+            nt.assert_equal(exc.exception, "No GL version set",
+                            msg="A GL version was passed without a decimal, "
+                                "which should have raised an exception, but "
+                                "did not")
 
 
 def test_parse_gles2_test():
@@ -47,7 +50,7 @@ def test_parse_gles2_test():
             'GL ES >= 2.0\n'
             'GLSL ES >= 1.00\n')
     with utils.with_tempfile(data) as temp:
-        test = shader_test.ShaderTest([temp])
+        test = shader_test.ShaderTest(temp)
 
     nt.assert_equal(
         os.path.basename(test.command[0]), "shader_runner_gles2",
@@ -61,7 +64,7 @@ def test_parse_gles3_test():
             'GL ES >= 3.0\n'
             'GLSL ES >= 3.00\n')
     with utils.with_tempfile(data) as temp:
-        test = shader_test.ShaderTest([temp])
+        test = shader_test.ShaderTest(temp)
 
     nt.assert_equal(
         os.path.basename(test.command[0]), "shader_runner_gles3",
-- 
1.9.1



More information about the Piglit mailing list