[Piglit] [PATCH 01/10] grouptools.py: Add a module specifically for working with group strings

Dylan Baker baker.dylan.c at gmail.com
Fri Dec 5 11:24:11 PST 2014


On Friday, December 05, 2014 11:55:41 AM Jose Fonseca wrote:
> On 04/12/14 23:09, Dylan Baker wrote:
> > This module is largely just posixpath (sometimes the functions are
> > renamed), with a couple of unique functions, which are basically
> > wrappers around posixpath functions with some special exceptions. Having
> > and using the module presents the advantage of being able to change the
> > implementation, including how the groups are manipulated. It also is
> > clearer than working with posixpath directly.
> >
> > Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
> > ---
> >   framework/grouptools.py             | 151 +++++++++++++++++++++++++++++++
> >   framework/tests/grouptools_tests.py | 174 ++++++++++++++++++++++++++++++++++++
> >   2 files changed, 325 insertions(+)
> >   create mode 100644 framework/grouptools.py
> >   create mode 100644 framework/tests/grouptools_tests.py
> >
> > diff --git a/framework/grouptools.py b/framework/grouptools.py
> > new file mode 100644
> > index 0000000..64e08b7
> > --- /dev/null
> > +++ b/framework/grouptools.py
> > @@ -0,0 +1,151 @@
> > +# Copyright (c) 2014 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.
> > +
> > +"""Module providing utility functions to work with piglit groups.
> > +
> > +Instead of using posixpath (or the generic os.path) for working with tests this
> > +module should be prefered.
> > +
> > +Piglit groups look much like posix paths, they are '/' delimited with each
> > +element representing a group, and the final element being the test name. Unlike
> > +posix paths they may not start with a leading '/'.
> > +
> > +"""
> > +
> > +import posixpath
> > +
> > +__all__ = [
> > +    'join',
> > +    'commonprefix',
> > +    'relgroup',
> > +    'split',
> > +    'groupname',
> > +    'testname',
> > +    'splitname',
> > +    'from_path'
> > +]
> > +
> > +
> > +def testname(group):
> > +    """Return the last element of a group name.
> > +
> > +    Provided the value 'group1/group2/test1' will provide 'test1', this
> > +    does not enforce any rules that the final element is a test name, and can
> > +    be used to shaved down groups.
> > +
> > +    Analogous to os.path.basename
> > +
> > +    """
> > +    assert '\\' not in group, 'Groups are not paths and cannot contain \\'
> > +    assert not group.startswith('/'), 'Groups cannot start with /'
> > +
> > +    return posixpath.basename(group)
> > +
> > +
> > +def groupname(group):
> > +    """Return all groups except the last.
> > +
> > +    Provided the value 'group1/group2/test1' will provide 'group1/group2', this
> > +    does not enforce any rules that the final element is a test name, and can
> > +    be used to shaved down groups.
> > +
> > +    Analogous to os.path.dirname
> > +
> > +    """
> > +    assert '\\' not in group, 'Groups are not paths and cannot contain \\'
> > +    assert not group.startswith('/'), 'Groups cannot start with /'
> > +
> > +    return posixpath.dirname(group)
> > +
> > +
> > +def splitname(group):
> > +    """Split a group name, Returns tuple "(group, test)"."""
> > +    assert '\\' not in group, 'Groups are not paths and cannot contain \\'
> 
> I think the series is a good cleanup.
> 
> But have you run any tests on Windows? There are so many places that 
> could introduce '\\' in the paths, so I'm concerned this can leave 
> Windows totally broken.
> 
> I can test this myself if you want. Just please push the series to some 
> repos I can pull from.
> 
> Jose

I do not have a windows machine to run piglit on, unfortunately. If you
could test it and let me know if/where there are problems that would be
great.

I pushed it here:
https://github.com/dcbaker/piglit submit/group-tools

> 
> > +    assert not group.startswith('/'), 'Groups cannot start with /'
> > +
> > +    return posixpath.split(group)
> > +
> > +
> > +def commonprefix(args):
> > +    """Given a list of groups, returns the longest common leading component."""
> > +    for group in args:
> > +        assert '\\' not in group, 'Groups are not paths and cannot contain \\'
> > +        assert not group.startswith('/'), 'Groups cannot start with /'
> > +
> > +    return posixpath.commonprefix(args)
> > +
> > +
> > +def join(*args):
> > +    """Join multiple groups together with some sanity checking.
> > +
> > +    Prevents groups from having '/' as the leading character or from having
> > +    '\\' in them, as these are groups not paths.
> > +
> > +    """
> > +    for group in args:
> > +        assert '\\' not in group, 'Groups are not paths and cannot contain \\'
> > +    assert not args[0].startswith('/'), 'Groups cannot start with /'
> > +
> > +    return posixpath.join(*args)
> > +
> > +
> > +def relgroup(large, small):
> > +    """Find the relationship between two groups.
> > +
> > +    This allows the comparison of two groups, and returns a string. If start
> > +    start is longer than the group then '' is returned.
> > +
> > +    """
> > +    for element in {large, small}:
> > +        assert '\\' not in element, 'Groups are not paths and cannot contain \\'
> > +        assert not element.startswith('/'), 'Groups cannot start with /'
> > +
> > +    if len(small) > len(large):
> > +        return ''
> > +    elif small == '' and large == '':
> > +        return ''
> > +    else:
> > +        return posixpath.relpath(large, small)
> > +
> > +
> > +def split(group):
> > +    """Split the group into a list of elements.
> > +
> > +    If input is '' return an empty list
> > +
> > +    """
> > +    assert '\\' not in group, 'Groups are not paths and cannot contain \\'
> > +    assert not group.startswith('/'), 'Groups cannot start with /'
> > +    if group == '':
> > +        return []
> > +    return group.split('/')
> > +
> > +
> > +def from_path(path):
> > +    """Create a group from a path.
> > +
> > +    This function takes a path, and creates a group out of it.
> > +
> > +    This safely handles both Windows and Unix style paths.
> > +
> > +    """
> > +    if '\\' in path:
> > +        return path.replace('\\', '/')
> > +    return path
> > diff --git a/framework/tests/grouptools_tests.py b/framework/tests/grouptools_tests.py
> > new file mode 100644
> > index 0000000..f65ad10
> > --- /dev/null
> > +++ b/framework/tests/grouptools_tests.py
> > @@ -0,0 +1,174 @@
> > +# Copyright (c) 2014 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.
> > +
> > +"""Module with tests for grouptools."""
> > +
> > +from __future__ import print_function
> > +import inspect
> > +
> > +import nose.tools as nt
> > +
> > +import framework.grouptools as grouptools
> > +import framework.tests.utils as utils
> > +
> > +
> > +def description(desc):
> > +    """Set description on a bound method.
> > +
> > +    This is an ugly little mother that does something awful, it sets the
> > +    description attribute on a bound method. This allows a bound method to be
> > +    passed from a test generator with a description.
> > +
> > +    The string will be formated passing self.__dict__ to str.vformat, so any
> > +    values that should be filled in should have the same names as the
> > +    attributes of the class
> > +
> > +    """
> > +    def wrapper(func):
> > +        func.description = desc
> > +        return func
> > +    return wrapper
> > +
> > +
> > +class _SharedFunctionTest(object):  # pylint: disable=too-few-public-methods
> > +    """Magic test class."""
> > +    def __init__(self, name, function, input_, expected, explode=False):
> > +        # pylint: disable=too-many-arguments
> > +        self.input = input_
> > +        self.expected = expected
> > +        self.name = name
> > +        self.function = function
> > +        self.explode = explode
> > +
> > +    def __iter__(self):
> > +        """Iterate over the test methods of this class.
> > +
> > +        Like the description wrapper this is ugly, it iterates over the bound
> > +        methods of the class looking for those who's names start with test_,
> > +        then it uses getatter to get the coresponding method, then formats that
> > +        description string of that method with vformat, and finally yields the
> > +        test.
> > +
> > +        """
> > +        # Not exactly sure why x() needs to be called, but it does.
> > +        wrapper = lambda x: x()
> > +
> > +        for name, test in inspect.getmembers(self, inspect.ismethod):
> > +            if name.startswith('test_'):
> > +                wrapper.description = test.description.format(**self.__dict__)
> > +                yield wrapper, test
> > +
> > +
> > +class _GroupToolsTest(_SharedFunctionTest):
> > +    """Not so simple class for running the same tests on multiple callables.
> > +
> > +    Provides a set of test methods that rely on instance attributes to do the
> > +    testing, meaning setting a few attributes creates a complete test.
> > +
> > +    Arguments:
> > +    input --    data to pass to the function
> > +    expected -- the result that is expected
> > +    name --     the name of the function being tested
> > +    function -- the function to test
> > +    explode --  if the function takes multiple arguments they must be passed as
> > +                container, if explode is set to True then the container will be
> > +                exploded when passed in
> > +
> > +    """
> > +    @description("grouptools.{name}: works")
> > +    def test_functionality(self):
> > +        """Test that the functionality of the function."""
> > +        if not self.explode:
> > +            nt.assert_equal(self.function(self.input), self.expected)
> > +        else:
> > +            nt.assert_equal(self.function(*self.input), self.expected)
> > +
> > +    @description("grouptools.{name}: doesn't accept a leading /")
> > +    @nt.raises(AssertionError)
> > +    def test_assertion_slash(self):
> > +        """Test that a leading / is an error."""
> > +        if isinstance(self.input, (str, unicode)):
> > +            self.function('/' + self.input)
> > +        elif not self.explode:
> > +            self.function(['/' + i for i in self.input])
> > +        else:
> > +            self.function(*['/' + i for i in self.input])
> > +
> > +    @description("grouptools.{name}: doesn't accept \\ in groups")
> > +    @nt.raises(AssertionError)
> > +    def test_assertion_backslash(self):
> > +        """Test that \\ in a group is an error."""
> > +        if isinstance(self.input, (str, unicode)):
> > +            self.function(self.input.replace('/', '\\'))
> > +        elif not self.explode:
> > +            self.function(i.replace('/', '\\') for i in self.input)
> > +        else:
> > +            self.function(*[i.replace('/', '\\') for i in self.input])
> > +
> > +
> > + at utils.nose_generator
> > +def generate_tests():
> > +    """Generate tests for the groups tools module.
> > +
> > +    This cannot test all corners of the more complicated members.
> > +
> > +    """
> > +    # pylint: disable=line-too-long
> > +    tests = [
> > +        ('testname', grouptools.testname, 'g1/g2/t1', 't1'),
> > +        ('groupname', grouptools.groupname, 'g1/g2/t1', 'g1/g2'),
> > +        ('splitname', grouptools.splitname, 'g1/g2/t1', ('g1/g2', 't1')),
> > +        ('commonprefix', grouptools.commonprefix, ['g1/g2/1', 'g1/g2/2'], 'g1/g2/'),
> > +        ('join', grouptools.join, ['g1/g2', 't1'], 'g1/g2/t1', True),
> > +        ('split', grouptools.split, 'g1/g2/t1', ['g1', 'g2', 't1']),
> > +        ('relgroup', grouptools.relgroup, ['g1/g2/g3/t1', 'g1/g2'], 'g3/t1', True)
> > +    ]
> > +    # pylint: enable=line-too-long
> > +
> > +    for args in tests:
> > +        test_class = _GroupToolsTest(*args)
> > +        # In python3 we could use 'yield from'
> > +        for wrapper, test in test_class:
> > +            yield wrapper, test
> > +
> > +
> > +def test_relgroup_small_gt_large():
> > +    """grouptools.relgroup: if small > large return ''."""
> > +    nt.assert_equal(grouptools.relgroup('foo', 'foobar'), '')
> > +
> > +
> > +def test_relgroup_both_empty():
> > +    """grouptools.relgroup: if small == '' and large == '' return ''."""
> > +    nt.assert_equal(grouptools.relgroup('', ''), '')
> > +
> > +
> > +def test_split_input_empty():
> > +    """grouptools.split: an empty input returns []."""
> > +    nt.assert_equal(grouptools.split(''), [])
> > +
> > +
> > +def test_from_path_posix():
> > +    """grouptools.from_path: doesn't change posixpaths."""
> > +    nt.assert_equal(grouptools.from_path('foo/bar'), 'foo/bar')
> > +
> > +
> > +def test_from_path_nt():
> > +    """grouptools.from_path: converts \\ to / in nt paths."""
> > +    nt.assert_equal(grouptools.from_path('foo\\bar'), 'foo/bar')
> >
> 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: This is a digitally signed message part.
URL: <http://lists.freedesktop.org/archives/piglit/attachments/20141205/8f671551/attachment-0001.sig>


More information about the Piglit mailing list