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 <[email protected]> --- 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 _______________________________________________ Piglit mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/piglit
