[Piglit] [RFC 06/12] gen_constant_array_size_tests.py: Convert to mako.

Dylan Baker baker.dylan.c at gmail.com
Mon Dec 8 17:11:27 PST 2014


This patch converts from string concatenation to using a mako template
to render the tests.

This allows us to throw 100 lines of code away, and makes the template
much easier to understand.

Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
 generated_tests/gen_constant_array_size_tests.py | 378 +++++++++--------------
 1 file changed, 150 insertions(+), 228 deletions(-)

diff --git a/generated_tests/gen_constant_array_size_tests.py b/generated_tests/gen_constant_array_size_tests.py
index d165fee..0195ebf 100644
--- a/generated_tests/gen_constant_array_size_tests.py
+++ b/generated_tests/gen_constant_array_size_tests.py
@@ -21,262 +21,184 @@
 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-# Generate a pair of glsl parser tests for every overloaded version of
-# every built-in function, which test that the built-in functions are
-# handled properly when applied to constant arguments inside an array
-# size declaration.
-#
-# In each pair of generated tests, one test exercises the built-in
-# function in vertex shaders, and the other exercises it in fragment
-# shaders.
-#
-# This program outputs, to stdout, the name of each file it generates.
-# With the optional argument --names-only, it only outputs the names
-# of the files; it doesn't generate them.
-
-import abc
-import optparse
-import os
-import os.path
-
-import builtin_function
-from builtins import glsl_types, generators
+"""Generate tests for overloaded built-in functions.
 
+Generate a pair of glsl parser tests for every overloaded version of
+every built-in function, which test that the built-in functions are
+handled properly when applied to constant arguments inside an array
+size declaration.
 
-class ParserTest(object):
-    """Class used to build a test of a single built-in.  This is an
-    abstract base class--derived types should override test_suffix(),
-    output_var(), and other functions if necessary.
-    """
-    __metaclass__ = abc.ABCMeta
-
-    def __init__(self, signature, test_vectors):
-        """Prepare to build a test for a single built-in.  signature
-        is the signature of the built-in (a key from the
-        builtin_function.test_suite dict), and test_vectors is the
-        list of test vectors for testing the given builtin (the
-        corresponding value from the builtin_function.test_suite
-        dict).
-        """
-        self.__signature = signature
-        self.__test_vectors = test_vectors
+In each pair of generated tests, one test exercises the built-in
+function in vertex shaders, and the other exercises it in fragment
+shaders.
 
-    def glsl_version(self):
-        if self.__signature.version_introduced < 120:
-            # Before version 1.20, built-in function invocations
-            # weren't allowed in constant expressions.  So even if
-            # this built-in was introduced prior to 1.20, test it in
-            # version 1.20.
-            return 120
-        else:
-            return self.__signature.version_introduced
+This program outputs, to stdout, the name of each file it generates.
+With the optional argument --names-only, it only outputs the names
+of the files; it doesn't generate them.
 
-    def version_directive(self):
-        return '#version {0}\n'.format(self.glsl_version())
+"""
 
-    def additional_declarations(self):
-        """Return a string containing any additional declarations that
-        should be placed after the version directive.  Returns the
-        empty string by default.
-        """
-        return ''
+from __future__ import print_function, division
+import os
 
-    def additional_extensions(self):
-        """Return a list (or other iterable) containing any additional
-        extension requirements that the test has.  Returns the empty
-        list by default.
-        """
-        return []
+from mako.template import Template
 
-    @abc.abstractmethod
-    def test_suffix(self):
-        """Return the suffix that should be used in the test file name
-        to identify the type of shader, e.g. "vert" for a vertex
-        shader test.
-        """
+import builtin_function
+from builtins import glsl_types, generators
 
-    @abc.abstractmethod
-    def output_var(self):
-        """Return the output variable for the test."""
 
-    def make_condition(self, test_vector):
-        """Generate a GLSL constant expression that should evaluate to
-        true if the GLSL compiler's constant evaluation produces the
-        correct result for the given test vector, and false if not.
-        """
-        invocation = self.__signature.template.format(
-            *[generators.glsl_constant(x) for x in test_vector.arguments])
-        if self.__signature.rettype.base_type == glsl_types.GLSL_FLOAT:
-            # Test floating-point values within tolerance
-            if self.__signature.name == 'distance':
-                # Don't use the distance() function to test itself.
-                return '{0} <= {1} && {1} <= {2}'.format(
-                    test_vector.result - test_vector.tolerance,
-                    invocation,
-                    test_vector.result + test_vector.tolerance)
-            elif self.__signature.rettype.is_matrix:
-                # We can't apply distance() to matrices.  So apply it
-                # to each column and root-sum-square the results.  It
-                # is safe to use pow() here because its behavior is
-                # verified in the pow() tests.
-                terms = []
-                for col in xrange(self.__signature.rettype.num_cols):
-                    terms.append('pow(distance({0}[{1}], {2}), 2)'.format(
-                        invocation, col,
-                        generators.glsl_constant(test_vector.result[:, col])))
-                rss_distance = ' + '.join(terms)
-                sq_tolerance = test_vector.tolerance * test_vector.tolerance
-                return '{0} <= {1}'.format(
-                    rss_distance, generators.glsl_constant(sq_tolerance))
-            else:
-                return 'distance({0}, {1}) <= {2}'.format(
-                    invocation,
-                    generators.glsl_constant(test_vector.result),
-                    generators.glsl_constant(test_vector.tolerance))
+TEMPLATE = Template("""\
+/* [config]
+ * expect_result: pass
+ * glsl_version: ${glsl_version}
+ * [end config]
+ *
+ * Check that the following test vectors are constantfolded correctly:
+% for template, result in comments:
+ * ${template} => ${result}
+% endfor
+ */
+#version ${int(float(glsl_version) * 100)}
+% if extension:
+#extension GL_${extension} : require
+% endif
+
+void main()
+{
+% for i, vector in enumerate(vectors):
+  float[${vector} ? 1 : -1] array${i};
+% else:
+  ## This is a clever (but maybe not good) use of python's for/else syntax.
+  ## vectors is a generator, which means that we cannot get a value again
+  ## after we've iterated passed it. This trick allows us to bypass that
+  ## limitation and output an additional value on the last iteration without
+  ## storing the value of i because the else is still part of the for loop,
+  ## and all of the variables from the last iteration of the  for loop are
+  ## still in scope.
+  ${output_var} = vec4(${' + '.join('array{}.length()'.format(x) for x in xrange(i + 1))});
+% endfor
+}
+""")
+
+
+def make_glsl_version(signature, stage):
+    """Get the minimum glsl_version from the signature."""
+    if stage == 'geom':
+        version = max(150, signature.version_introduced)
+    else:
+        version = max(120, signature.version_introduced)
+    return '{0:1.2f}'.format(float(version) / 100)
+
+
+def make_condition(signature, test_vector):
+    """Generate a GLSL constant expression that should evaluate to true if the
+    GLSL compiler's constant evaluation produces the correct result for the
+    given test vector, and false if not.
+    """
+    invocation = signature.template.format(
+        *[generators.glsl_constant(x) for x in test_vector.arguments])
+    if signature.rettype.base_type == glsl_types.GLSL_FLOAT:
+        # Test floating-point values within tolerance
+        if signature.name == 'distance':
+            # Don't use the distance() function to test itself.
+            return '{0} <= {1} && {1} <= {2}'.format(
+                test_vector.result - test_vector.tolerance,
+                invocation,
+                test_vector.result + test_vector.tolerance)
+        elif signature.rettype.is_matrix:
+            # We can't apply distance() to matrices.  So apply it
+            # to each column and root-sum-square the results.  It
+            # is safe to use pow() here because its behavior is
+            # verified in the pow() tests.
+            terms = []
+            for col in xrange(signature.rettype.num_cols):
+                terms.append('pow(distance({0}[{1}], {2}), 2)'.format(
+                    invocation, col,
+                    generators.glsl_constant(test_vector.result[:, col])))
+            rss_distance = ' + '.join(terms)
+            sq_tolerance = test_vector.tolerance * test_vector.tolerance
+            return '{0} <= {1}'.format(
+                rss_distance, generators.glsl_constant(sq_tolerance))
         else:
-            # Test non-floating point values exactly
-            assert not self.__signature.rettype.is_matrix
-            if self.__signature.name == 'equal':
-                # Don't use the equal() function to test itself.
-                assert self.__signature.rettype.is_vector
-                terms = []
-                for row in xrange(self.__signature.rettype.num_rows):
-                    terms.append('{0}[{1}] == {2}'.format(
-                        invocation, row,
-                        generators.glsl_constant(test_vector.result[row])))
-                return ' && '.join(terms)
-            elif self.__signature.rettype.is_vector:
-                return 'all(equal({0}, {1}))'.format(
-                    invocation,
-                    generators.glsl_constant(test_vector.result))
-            else:
-                return '{0} == {1}'.format(
-                    invocation,
-                    generators.glsl_constant(test_vector.result))
-
-    def make_shader(self):
-        """Generate the shader code necessary to test the built-in."""
-        shader = self.version_directive()
-        if self.__signature.extension:
-            shader += '#extension GL_{0} : require\n'.format(self.__signature.extension)
-        shader += self.additional_declarations()
-        shader += '\n'
-        shader += 'void main()\n'
-        shader += '{\n'
-        lengths = []
-        for i, test_vector in enumerate(self.__test_vectors):
-            shader += '  float[{0} ? 1 : -1] array{1};\n'.format(
-                self.make_condition(test_vector), i)
-            lengths.append('array{0}.length()'.format(i))
-        shader += '  {0} = vec4({1});\n'.format(
-            self.output_var(), ' + '.join(lengths))
-        shader += '}\n'
-        return shader
-
-    def filename(self):
-        argtype_names = '-'.join(
-            str(argtype) for argtype in self.__signature.argtypes)
-        if self.__signature.extension:
-            subdir = self.__signature.extension.lower()
+            return 'distance({0}, {1}) <= {2}'.format(
+                invocation,
+                generators.glsl_constant(test_vector.result),
+                generators.glsl_constant(test_vector.tolerance))
+    else:
+        # Test non-floating point values exactly
+        assert not signature.rettype.is_matrix
+        if signature.name == 'equal':
+            # Don't use the equal() function to test itself.
+            assert signature.rettype.is_vector
+            terms = []
+            for row in xrange(signature.rettype.num_rows):
+                terms.append('{0}[{1}] == {2}'.format(
+                    invocation, row,
+                    generators.glsl_constant(test_vector.result[row])))
+            return ' && '.join(terms)
+        elif signature.rettype.is_vector:
+            return 'all(equal({0}, {1}))'.format(
+                invocation,
+                generators.glsl_constant(test_vector.result))
         else:
-            subdir = 'glsl-{0:1.2f}'.format(float(self.glsl_version()) / 100)
-        return os.path.join(
-            'spec', subdir, 'compiler', 'built-in-functions',
-            '{0}-{1}.{2}'.format(
-                self.__signature.name, argtype_names, self.test_suffix()))
-
-    def generate_parser_test(self):
-        """Generate the test and write it to the output file."""
-        parser_test = '/* [config]\n'
-        parser_test += ' * expect_result: pass\n'
-        parser_test += ' * glsl_version: {0:1.2f}\n'.format(
-            float(self.glsl_version()) / 100)
-        req_extensions = list(self.additional_extensions())
-        if req_extensions:
-            parser_test += ' * require_extensions: {0}\n'.format(
-                ' '.join(req_extensions))
-        parser_test += ' * [end config]\n'
-        parser_test += ' *\n'
-        parser_test += ' * Check that the following test vectors are constant'\
-                       'folded correctly:\n'
-        for test_vector in self.__test_vectors:
-            parser_test += ' * {0} => {1}\n'.format(
-                self.__signature.template.format(
-                    *[generators.glsl_constant(arg) for
-                      arg in test_vector.arguments]),
+            return '{0} == {1}'.format(
+                invocation,
                 generators.glsl_constant(test_vector.result))
-        parser_test += ' */\n'
-        parser_test += self.make_shader()
-        filename = self.filename()
-        dirname = os.path.dirname(filename)
-        if not os.path.exists(dirname):
-            os.makedirs(dirname)
-        with open(filename, 'w') as f:
-            f.write(parser_test)
 
 
-class VertexParserTest(ParserTest):
-    """Derived class for tests that exercise the built-in in a vertex
-    shader.
-    """
-    def test_suffix(self):
-        return 'vert'
-
-    def output_var(self):
-        return 'gl_Position'
+def get_filename(signature, stage):
+    """Construct a filename from the given args."""
+    argtype_names = '-'.join(str(argtype) for argtype in signature.argtypes)
+    if signature.extension:
+        subdir = signature.extension.lower()
+    else:
+        subdir = 'glsl-{0}'.format(make_glsl_version(signature, stage))
 
+    return os.path.join('spec', subdir, 'compiler', 'built-in-functions',
+                        '{0}-{1}.{2}'.format(signature.name, argtype_names,
+                                             stage))
 
-class GeometryParserTest(ParserTest):
-    """Derived class for tests that exercise the built-in in a geometry
-    shader.
-    """
-    def glsl_version(self):
-        return max(150, ParserTest.glsl_version(self))
-
-    def test_suffix(self):
-        return 'geom'
-
-    def output_var(self):
-        return 'gl_Position'
 
+def make_output_var(stage):
+    """Set the output variable by stage."""
+    if stage == "frag":
+        return 'gl_FragColor'
+    return 'gl_Position'
 
-class FragmentParserTest(ParserTest):
-    """Derived class for tests that exercise the built-in in a fagment
-    shader.
-    """
-    def test_suffix(self):
-        return 'frag'
 
-    def output_var(self):
-        return 'gl_FragColor'
+def comment_generator(signature, vectors):
+    """Generate comment arguments."""
+    for vector in vectors:
+        template = signature.template.format(
+            *[generators.glsl_constant(a) for a in vector.arguments])
+        result = generators.glsl_constant(vector.result)
+        yield template, result
 
 
 def all_tests():
-    for signature, test_vectors in sorted(builtin_function.test_suite.items()):
+    for signature, vectors in sorted(builtin_function.test_suite.iteritems()):
         # Assignment operators other than = cannot be used in the constant
         # array size tests
         if not signature.name.startswith('op-assign'):
-            yield VertexParserTest(signature, test_vectors)
-            yield GeometryParserTest(signature, test_vectors)
-            yield FragmentParserTest(signature, test_vectors)
+            yield signature, vectors
 
 
 def main():
-    desc = 'Generate shader tests that test built-in functions using constant'\
-           'array sizes'
-    usage = 'usage: %prog [-h] [--names-only]'
-    parser = optparse.OptionParser(description=desc, usage=usage)
-    parser.add_option('--names-only',
-                      dest='names_only',
-                      action='store_true',
-                      help="Don't output files, just generate a list of"
-                           "filenames to stdout")
-    options, args = parser.parse_args()
-
-    for test in all_tests():
-        if not options.names_only:
-            test.generate_parser_test()
-        print test.filename()
+    for signature, vectors in all_tests():
+        for stage in ['frag', 'vert', 'geom']:
+            filename = get_filename(signature, stage)
+
+            if not os.path.exists(os.path.dirname(filename)):
+                os.makedirs(os.path.dirname(filename))
+
+            with open(filename, 'w') as f:
+                f.write(TEMPLATE.render(
+                    glsl_version=make_glsl_version(signature, stage),
+                    vectors=(make_condition(signature, v) for v in vectors),
+                    comments=comment_generator(signature, vectors),
+                    output_var=make_output_var(stage),
+                    extension=signature.extension))
+            print(filename)
 
 
 if __name__ == '__main__':
-- 
2.2.0



More information about the Piglit mailing list