[Piglit] [PATCH v3 2/4] generators: Add helper classes for GLSL version numbers

Dylan Baker baker.dylan.c at gmail.com
Wed Apr 13 00:20:05 UTC 2016


This adds a new module in the generated_tests/modules directory, which
contains three classes, GLSLVersion, GLSLESVersion, and Version. Version
is a factory that caches other versions and makes GLSLVersion and
GLSLESVersion instances on demand.

The goal of these classes is to provide a simple, unified method for
dealing with GLSL version numbers, which is something that a lot of
generators need to do. To that end it provides rich comparisons against
each other and against ints and floats. The hope is that other generator
writers could leverage this work in their generators to simplify things.

Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
 generated_tests/modules/__init__.py                |   8 +
 generated_tests/modules/glsl.py                    | 211 +++++++++++++++
 tox.ini                                            |   4 +-
 .../modules => unittests/generators}/__init__.py   |   0
 unittests/generators/test_glsl.py                  | 299 +++++++++++++++++++++
 5 files changed, 520 insertions(+), 2 deletions(-)
 create mode 100644 generated_tests/modules/glsl.py
 copy {generated_tests/modules => unittests/generators}/__init__.py (100%)
 create mode 100644 unittests/generators/test_glsl.py

diff --git a/generated_tests/modules/__init__.py b/generated_tests/modules/__init__.py
index e69de29..11e926b 100644
--- a/generated_tests/modules/__init__.py
+++ b/generated_tests/modules/__init__.py
@@ -0,0 +1,8 @@
+import importlib
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', 'framework')))
+
+importlib.import_module('compat')
diff --git a/generated_tests/modules/glsl.py b/generated_tests/modules/glsl.py
new file mode 100644
index 0000000..85de920
--- /dev/null
+++ b/generated_tests/modules/glsl.py
@@ -0,0 +1,211 @@
+# encoding=utf-8
+# Copyright © 2016 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.
+
+"""Provides helper classes for representing glsl data."""
+
+from __future__ import (
+    absolute_import, division, print_function, unicode_literals
+)
+import functools
+
+import six
+
+import compat
+
+__all__ = [
+    'GLSLESVersion',
+    'GLSLVersion',
+    'Version',
+]
+
+
+class _Version(object):
+    """Factory class for glsl versions.
+
+    provides an object cache to reduce duplication of objects. This object
+    should not be initialized more than once, to avoid that use the Version
+    constant provided by the module.
+
+    It would provide either a GLSLVersion or a GLSLESVersion.
+
+    """
+    _es_versions = [
+        '100',
+        '300 es',
+        '310 es',
+        '320 es',
+    ]
+
+    _versions = [
+        '110',
+        '120',
+        '130',
+        '140',
+        '150',
+        '330',
+        '400',
+        '410',
+        '420',
+        '430',
+        '440',
+        '450',
+    ]
+
+    def __init__(self):
+        self.__cache = {}
+
+    def __call__(self, ver):
+        """Make a Version object, or provide one from the cache."""
+        assert isinstance(ver, six.text_type)
+
+        # Try to get an object from the cache, if that fails create a new one
+        # and add it to the cache before returning it.
+        try:
+            return self.__cache[ver]
+        except KeyError:
+            if ver in self._es_versions:
+                obj = GLSLESVersion(ver)
+            elif ver in self._versions:
+                obj = GLSLVersion(ver)
+            else:
+                raise Exception('Undefined version {}'.format(ver))
+
+            self.__cache[ver] = obj
+            return obj
+
+
+Version = _Version()  # pylint: disable=invalid-name
+
+
+ at compat.python_2_unicode_compatible  # pylint: disable=no-member
+ at functools.total_ordering
+class GLSLVersion(object):
+    """A Representation of an OpenGL Shading Language version.
+
+    This object provides a bunch of the niceties that one would want. It's
+    orderable (can be sorted, and can be compared with the standard ==, !=, <,
+    etc), can be called with str() (which provides the integer version, like
+    120), and a method to print the decimal version (1.20).
+
+    Do not initialize this directly.
+
+    """
+    def __init__(self, ver):
+        assert ver in ['110', '120', '130', '140', '150', '330', '400', '410',
+                       '420', '430', '440', '450']
+        self._internal = ver
+        self.is_es = False
+
+    def __str__(self):
+        return self._internal
+
+    def __int__(self):
+        return int(self._internal)
+
+    def __float__(self):
+        return float(int(self) / 100)
+
+    def __eq__(self, other):
+        # If the other version is ES then we know they're not equal
+        if isinstance(other, GLSLESVersion):
+            return False
+        elif isinstance(other, (GLSLVersion, int)):
+            return int(self) == int(other)
+        elif isinstance(other, float):
+            return float(self) == float(other)
+        else:
+            return NotImplemented
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __lt__(self, other):
+        if isinstance(other, GLSLESVersion):
+            raise TypeError('Unorderable types GLSLVersion and GLSLESVersion')
+        elif isinstance(other, (GLSLVersion, int)):
+            return int(self) < int(other)
+        elif isinstance(other, float):
+            return float(self) < other
+        else:
+            return NotImplemented
+
+    def print_float(self):
+        """A version suitable to print as a decimal with two trailing digits."""
+        return '{:.2f}'.format(float(self))
+
+
+ at compat.python_2_unicode_compatible  # pylint: disable=no-member
+ at functools.total_ordering
+class GLSLESVersion(object):
+    """Represents a GLSL ES version.
+
+    Do not initialize this directly.
+
+    """
+    def __init__(self, ver):
+        if ver == '100':
+            self._internal = ver
+        else:
+            self._internal = ver[:-3]  # drop " es"
+        self.is_es = True
+
+    def __str__(self):
+        if self._internal == '100':
+            return self._internal
+        else:
+            return self._internal + " es"
+
+    def __int__(self):
+        return int(self._internal)
+
+    def __float__(self):
+        return float(int(self) / 100)
+
+    def __eq__(self, other):
+        # If the other version is ES then we know they're not equal
+        if isinstance(other, GLSLVersion):
+            return False
+        elif isinstance(other, (GLSLESVersion, int)):
+            return int(self) == int(other)
+        elif isinstance(other, float):
+            return float(self) == float(other)
+        else:
+            return NotImplemented
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __lt__(self, other):
+        if isinstance(other, GLSLVersion):
+            raise TypeError('Unorderable types GLSLESVersion and GLSLVersion')
+        elif isinstance(other, (GLSLESVersion, int)):
+            return int(self) < int(other)
+        elif isinstance(other, float):
+            return float(self) < other
+        else:
+            return NotImplemented
+
+    def print_float(self):
+        """A version suitable to print as a decimal with two trailing digits."""
+        if self._internal == '100':
+            return '{:.2f}'.format(float(self))
+        else:
+            return '{:.2f} es'.format(float(self))
diff --git a/tox.ini b/tox.ini
index a1556fa..fbb88c0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,5 +19,5 @@ deps =
     py35: mako==1.0.2
     generator: numpy==1.7.0
 commands = 
-    {accel,noaccel}: nosetests unittests []
-    generator: nosetests generated_tests/test_generators.py []
+    {accel,noaccel}: nosetests unittests -e generators []
+    generator: nosetests generated_tests/test_generators.py unittests/generators []
diff --git a/generated_tests/modules/__init__.py b/unittests/generators/__init__.py
similarity index 100%
copy from generated_tests/modules/__init__.py
copy to unittests/generators/__init__.py
diff --git a/unittests/generators/test_glsl.py b/unittests/generators/test_glsl.py
new file mode 100644
index 0000000..eabb600
--- /dev/null
+++ b/unittests/generators/test_glsl.py
@@ -0,0 +1,299 @@
+# encoding=utf-8
+# Copyright © 2016 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 generated_tests/modules/glsl.py."""
+
+from __future__ import (
+    absolute_import, division, print_function, unicode_literals
+)
+import itertools
+import operator
+import os
+import sys
+
+import nose.tools as nt
+import six
+
+# Add <piglit root>/generated_tests to the module path, this allows it to be
+# imported for testing.
+sys.path.insert(0, os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', 'generated_tests')))
+
+# pylint can't figure out the sys.path manipulation.
+from modules import glsl  # pylint: disable=import-error
+from .. import utils
+
+# pylint: disable=no-self-use,invalid-name
+
+_GLSL = [
+    '110',
+    '120',
+    '130',
+    '140',
+    '150',
+    '330',
+    '400',
+    '410',
+    '420',
+    '430',
+    '440',
+    '450',
+]
+
+_GLSLES = [
+    '100',
+    '300 es',
+    '310 es',
+    '320 es',
+]
+
+
+def test_factory_glsl():
+    """generated_tests.modules.glsl.Version: provides a GLSLVersion."""
+    test = glsl.Version('110')
+    nt.assert_is_instance(test, glsl.GLSLVersion)
+
+
+def test_factory_glsles():
+    """generated_tests.modules.glsl.Version: provides a GLSLESVersion."""
+    test = glsl.Version('100')
+    nt.assert_is_instance(test, glsl.GLSLESVersion)
+
+
+def test_factory_cache():
+    """generated_tests.modules.glsl.Version: caches objects."""
+    test1 = glsl.Version('100')
+    test2 = glsl.Version('100')
+    nt.assert_is(test1, test2)
+
+
+class TestCompare(object):
+    """Comparison tests for GLSLESVersion and GLSLVersion."""
+    _operators = [
+        ('<', operator.lt),
+        ('<=', operator.le),
+        ('==', operator.eq),
+        ('!=', operator.ne),
+        ('>=', operator.ge),
+        ('>', operator.gt),
+    ]
+
+    @utils.nose_generator
+    def test_glsl_glsl(self):
+        """Test GLSLVersion <cmp> GLSLVersion."""
+        def expected(first, second, op):
+            return op(int(first), int(second))
+
+        def test(first, second, op):
+            nt.eq_(op(glsl.Version(first), glsl.Version(second)),
+                   expected(first, second, op))
+
+        desc = 'generated_tests.modules.glsl.GLSLVersion: {} {} {}'
+
+        for ver1, ver2 in itertools.combinations(_GLSL, 2):
+            for name, op in self._operators:
+                test.description = desc.format(ver1, name, ver2)
+                yield test, ver1, ver2, op
+
+    @utils.nose_generator
+    def test_glsles_glsles(self):
+        """Test GLSLESVersion <cmp> GLSLESVersion."""
+        def expected(first, second, op):
+            # use the slice to drop " es" if it exists
+            return op(int(first[:3]), int(second[:3]))
+
+        def test(first, second, op):
+            nt.eq_(op(glsl.Version(first), glsl.Version(second)),
+                   expected(first, second, op))
+
+        desc = 'generated_tests.modules.glsl.GLSLESVersion: {} {} {}'
+
+        for ver1, ver2 in itertools.combinations(_GLSLES, 2):
+            for name, op in self._operators:
+                test.description = desc.format(ver1, name, ver2)
+                yield test, ver1, ver2, op
+
+    @nt.raises(TypeError)
+    def test_glsl_glsles(self):
+        """generated_tests.modules.glsl: GLSLVersion <cmp> GLSLESVersion."""
+        return glsl.Version('110') < glsl.Version('100')
+
+    @nt.raises(TypeError)
+    def test_glsles_glsl(self):
+        """generated_tests.modules.glsl: GLSLESVersion <cmp> GLSLVersion."""
+        return glsl.Version('100') < glsl.Version('110')
+
+    @utils.nose_generator
+    def test_glsl_int(self):
+        """Test GLSLVersion <cmp> GLSLVersion."""
+        def expected(first, second, op):
+            return op(int(first), int(second))
+
+        def test(first, second, op, expect):
+            nt.eq_(op(first, second), expect)
+
+        desc = 'generated_tests.modules.glsl.GLSLVersion: {} {} {}'
+
+        for ver1, ver2 in itertools.combinations(_GLSL, 2):
+            for name, op in self._operators:
+                test.description = desc.format(
+                    'GLSLVersion({})'.format(ver1),
+                    name,
+                    'int({})'.format(ver2))
+                yield (test, glsl.Version(ver1), int(ver2), op,
+                       expected(ver1, ver2, op))
+
+                test.description = desc.format(
+                    'int({})'.format(ver1),
+                    name,
+                    'GLSLVersion({})'.format(ver2))
+                yield (test, int(ver1), glsl.Version(ver2), op,
+                       expected(ver1, ver2, op))
+
+    @utils.nose_generator
+    def test_glsl_float(self):
+        """Test GLSLVersion <cmp> GLSLVersion."""
+        def expected(first, second, op):
+            return op(float(first) / 100, float(second) / 100)
+
+        def test(first, second, op, expect):
+            nt.eq_(op(first, second), expect)
+
+        desc = 'generated_tests.modules.glsl.GLSLVersion: {} {} {}'
+
+        for ver1, ver2 in itertools.combinations(_GLSL, 2):
+            for name, op in self._operators:
+                test.description = desc.format(
+                    'GLSLVersion({})'.format(ver1),
+                    name,
+                    'float({})'.format(ver2))
+                yield (test, glsl.Version(ver1), float(ver2) / 100, op,
+                       expected(ver1, ver2, op))
+
+                test.description = desc.format(
+                    'float({})'.format(ver1),
+                    name,
+                    'GLSLVersion({})'.format(ver2))
+                yield (test, float(ver1) / 100, glsl.Version(ver2), op,
+                       expected(ver1, ver2, op))
+
+    @utils.nose_generator
+    def test_glsles_int(self):
+        """Test GLSLESVersion <cmp> GLSLESVersion."""
+        def expected(first, second, op):
+            return op(int(first[:3]), int(second[:3]))
+
+        def test(first, second, op, expect):
+            nt.eq_(op(first, second), expect)
+
+        desc = 'generated_tests.modules.glsl.GLSLESVersion: {} {} {}'
+
+        for ver1, ver2 in itertools.combinations(_GLSLES, 2):
+            for name, op in self._operators:
+                test.description = desc.format(
+                    'GLSLESVersion({})'.format(ver1),
+                    name,
+                    'int({})'.format(ver2))
+                # Slice to avoid calling int on '300 es'
+                yield (test, glsl.Version(ver1), int(ver2[:3]), op,
+                       expected(ver1, ver2, op))
+
+                test.description = desc.format(
+                    'int({})'.format(ver1),
+                    name,
+                    'GLSLESVersion({})'.format(ver2))
+                # Slice to avoid calling int on '300 es'
+                yield (test, int(ver1[:3]), glsl.Version(ver2), op,
+                       expected(ver1, ver2, op))
+
+    @utils.nose_generator
+    def test_glsles_float(self):
+        """Test GLSLESVersion <cmp> GLSLESVersion."""
+        def expected(first, second, op):
+            return op(float(first[:3]) / 100, float(second[:3]) / 100)
+
+        def test(first, second, op, expect):
+            nt.eq_(op(first, second), expect)
+
+        desc = 'generated_tests.modules.glsl.GLSLESVersion: {} {} {}'
+
+        for ver1, ver2 in itertools.combinations(_GLSLES, 2):
+            for name, op in self._operators:
+                test.description = desc.format(
+                    'GLSLESVersion({})'.format(ver1),
+                    name,
+                    'float({})'.format(ver2))
+                # Slice to avoid calling float on '300 es'
+                yield (test, glsl.Version(ver1), float(ver2[:3]) / 100, op,
+                       expected(ver1, ver2, op))
+
+                test.description = desc.format(
+                    'float({})'.format(ver1),
+                    name,
+                    'GLSLESVersion({})'.format(ver2))
+                # Slice to avoid calling float on '300 es'
+                yield (test, float(ver1[:3]) / 100, glsl.Version(ver2), op,
+                       expected(ver1, ver2, op))
+
+
+def test_GLSLVersion_str():
+    """generated_tests.modules.glsl.GLSLVersion: str()"""
+    nt.eq_(six.text_type(glsl.Version('110')), '110')
+
+
+def test_GLSLESVersion_str():
+    """generated_tests.modules.glsl.GLSLESVersion: str()"""
+    nt.eq_(six.text_type(glsl.Version('100')), '100')
+
+
+def test_GLSLVersion_int():
+    """generated_tests.modules.glsl.GLSLVersion: int()"""
+    nt.eq_(int(glsl.Version('110')), 110)
+
+
+def test_GLSLESVersion_int():
+    """generated_tests.modules.glsl.GLSLESVersion: int()"""
+    nt.eq_(int(glsl.Version('100')), 100)
+
+
+def test_GLSLVersion_float():
+    """generated_tests.modules.glsl.GLSLVersion: float()"""
+    nt.eq_(float(glsl.Version('110')), 1.10)
+
+
+def test_GLSLESVersion_float():
+    """generated_tests.modules.glsl.GLSLESVersion: float()"""
+    nt.eq_(float(glsl.Version('100')), 1.00)
+
+
+def test_GLSLVersion_print_float():
+    """generated_tests.modules.glsl.GLSLVersion: print_float()"""
+    nt.eq_(glsl.Version('110').print_float(), '1.10')
+
+
+def test_GLSLESVersion_print_float():
+    """generated_tests.modules.glsl.GLSLESVersion: print_float()"""
+    nt.eq_(glsl.Version('100').print_float(), '1.00')
+
+
+def test_GLSLESVersion_print_float_es():
+    """generated_tests.modules.glsl.GLSLESVersion: print_float() (es version)"""
+    nt.eq_(glsl.Version('300 es').print_float(), '3.00 es')
-- 
2.8.0



More information about the Piglit mailing list