[GRASS-SVN] r60698 - in sandbox/wenzeslaus/gunittest: . testsuite

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Jun 3 21:54:33 PDT 2014


Author: wenzeslaus
Date: 2014-06-03 21:54:33 -0700 (Tue, 03 Jun 2014)
New Revision: 60698

Added:
   sandbox/wenzeslaus/gunittest/testsuite/test_checkers.py
Modified:
   sandbox/wenzeslaus/gunittest/checkers.py
Log:
gunittest: text key-value functions

Modified: sandbox/wenzeslaus/gunittest/checkers.py
===================================================================
--- sandbox/wenzeslaus/gunittest/checkers.py	2014-06-04 03:27:34 UTC (rev 60697)
+++ sandbox/wenzeslaus/gunittest/checkers.py	2014-06-04 04:54:33 UTC (rev 60698)
@@ -3,10 +3,280 @@
 import sys
 import re
 import doctest
+import grass.script.core as gcore
 
 # alternative term to check(er(s)) would be compare
 
 
+# TODO: these functions returns dictionary but they also modify it
+def unify_projection(dic):
+    """Unifies names of projections.
+
+    Some projections has possibily duplicate names like
+    'Universal Transverse Mercator' and 'Universe Transverse Mercator'.
+    This function replaces synonyms by a unified name.
+
+    >>> unify_projection({'name': ['Universe Transverse Mercator']})
+    {'name': ['Universal Transverse Mercator']}
+
+    @param dic The dictionary containing information about projection
+
+    @return The dictionary with the new values if needed
+    """
+    # the lookup variable is a list of list, each list contains all the
+    # possible name for a projection system
+    lookup = [['Universal Transverse Mercator', 'Universe Transverse Mercator']]
+    dic = dict(dic)
+    for lo in lookup:
+        for n in range(len(dic['name'])):
+            if dic['name'][n] in lo:
+                dic['name'][n] = lo[0]
+    return dic
+
+
+def unify_units(dic):
+    """Unifies names of units.
+
+    Some units have different spelling although they are the same units.
+    This functions replaces different spelling options by unified one.
+
+    >>> unify_units({'units': ['metres'], 'unit': ['metre']})
+    {'units': ['meters'], 'unit': ['meter']}
+
+    @param dic The dictionary containing information about units
+
+    @return The dictionary with the new values if needed
+    """
+    # the lookup variable is a list of list, each list contains all the
+    # possible name for a units
+    lookup = [['meter', 'metre'], ['meters', 'metres'], ['kilometer',
+              'kilometre'], ['kilometers', 'kilometres']]
+    dic = dict(dic)
+    for l in lookup:
+        for n in range(len(dic['unit'])):
+            if dic['unit'][n] in l:
+                dic['unit'][n] = l[0]
+        for n in range(len(dic['units'])):
+            if dic['units'][n] in l:
+                dic['units'][n] = l[0]
+    return dic
+
+
+def text_to_keyvalue(text, sep=":", val_sep=",", functions=None,
+                     skip_invalid=False, skip_empty=False):
+    """
+    Converts a key-value text file, where entries are separated
+    by newlines and the key and value are separated by `sep`,
+    into a key-value dictionary and discover/use the correct
+    data types (float, int or string) for values.
+
+    And empty string is a valid input because empty dictionary is a valid
+    dictionary.
+
+    @param filename The name or name and path of the text file to convert
+    @param sep The character that separates the keys and values, default is ":"
+    @param val_sep The character that separates the values of a single key, default is ","
+    @param functions list of functions to apply on the resulting dictionary
+    @param skip_invalid skips all lines which does not contain separator (including empty lines)
+
+    @return The dictionary
+
+    A text file with this content:
+    Will be represented as this dictionary:
+
+    >>> sorted(text_to_keyvalue('''a: Hello
+    ... b: 1.0
+    ... c: 1,2,3,4,5
+    ... d : hello,8,0.1''').items())  # sorted items from the dictionary
+    [('a', 'Hello'), ('b', 1.0), ('c', [1, 2, 3, 4, 5]), ('d', ['hello', 8, 0.1])]
+    """
+    def value_from_string(value):
+        """
+        >>> convert_from_string('1')
+        1
+        >>> convert_from_string('5.6')
+        5.6
+        >>> convert_from_string('  5.6\t  ')
+        5.6
+        >>> convert_from_string('hello')
+        'hello'
+        """
+        # note that above code is not invoked by doctest
+        not_float = False
+        not_int = False
+        # Convert values into correct types
+        # We first try integer then float because
+        # int('1.0') is ValueError (although int(1.0) is not)
+        # while float('1') is not
+        try:
+            value_converted = int(value)
+        except ValueError:
+            not_int = True
+        if not_int:
+            try:
+                value_converted = float(value)
+            except ValueError:
+                not_float = True
+        # strip strings from whitespace (expecting spaces and tabs)
+        if not_int and not_float:
+            value_converted = value.strip()
+        return value_converted
+
+    # split according to universal newlines approach
+    # TODO: add also general split with vsep
+    text = text.splitlines()
+    kvdict = gcore.KeyValue()
+    functions = [] if functions is None else functions
+
+    for line in text:
+        if line.find(sep) >= 0:
+            key, value = line.split(sep, 1)
+            key = key.strip()
+            value = value.strip()
+            # this strip may not be necessary, we strip each item in list
+            # and also if there is only one value
+        else:
+            # lines with no separator (empty or invalid)
+            if not line:
+                if not skip_empty:
+                    # TODO: here should go _ for translation
+                    # TODO: the error message is not really informative
+                    # in case of skipping lines we may get here with no key
+                    msg = ("Empty line in the parsed text.")
+                    if kvdict:
+                        # key is the one from previous line
+                        msg = ("Empty line in the parsed text."
+                               " Previous line's key is <%s>") % key
+                    raise ValueError(msg)
+            else:
+                # line contains something but not separator
+                if not skip_invalid:
+                    # TODO: here should go _ for translation
+                    raise ValueError(("Line <{l}> does not contain"
+                                      " separator <{s}>.").format(l=line, s=sep))
+            # if we get here we are silently ignoring the line
+            # because it is invalid (does not contain key-value separator) or
+            # because it is empty
+            continue
+        if value.find(val_sep) >= 0:
+            # lists
+            values = value.split(val_sep)
+            value_list = []
+            for value in values:
+                value_converted = value_from_string(value)
+                value_list.append(value_converted)
+            kvdict[key] = value_list
+        else:
+            # single values
+            kvdict[key] = value_from_string(value)
+    for function in functions:
+        kvdict = function(kvdict)
+    return kvdict
+
+
+def values_equal(value_a, value_b, precision=0.000001):
+    """
+    >>> values_equal(1.022, 1.02, precision=0.01)
+    True
+    >>> values_equal([1.2, 5.3, 6.8], [1.1, 5.2, 6.9], precision=0.2)
+    True
+    >>> values_equal(7, 5, precision=2)
+    True
+    >>> values_equal(1, 5.9, precision=10)
+    True
+    >>> values_equal('Hello', 'hello')
+    False
+    """
+    # each if need to handle only not equal state
+    if isinstance(value_a, float) and isinstance(value_b, float):
+        # both values are float
+        if abs(value_a - value_b) > precision:
+            return False
+    elif (isinstance(value_a, float) and isinstance(value_b, int)) or \
+            (isinstance(value_b, float) and isinstance(value_a, int)):
+        # on is float the other is int
+        # we will apply precision to int-float comparison
+        # rather than converting both to integer
+        if abs(value_a - value_b) > precision:
+            return False
+    elif isinstance(value_a, int) and isinstance(value_b, int) and \
+            int(precision) > 0:
+        # both int but precision applies for them
+        if abs(value_a - value_b) > precision:
+            return False
+    elif isinstance(value_a, list) and isinstance(value_b, list):
+        if len(value_a) != len(value_b):
+            return False
+        for i in range(len(value_a)):
+            # apply this function for comparison of items in the list
+            if not values_equal(value_a[i], value_b[i], precision):
+                return False
+    else:
+        if value_a != value_b:
+            return False
+    return True
+
+
+def compare_keyvalue_texts(text_a, text_b, sep=":",
+                           val_sep=",", precision=0.000001,
+                           def_equal=values_equal,
+                           key_equal=None,
+                           polish_functions=None):
+    """
+    !Compare two key-value text files
+
+    This method will print a warning in case keys that are present in the first
+    file are not present in the second one.
+    The comparison method tries to convert the values into their native format
+    (float, int or string) to allow correct comparison.
+
+    An example key-value text file may have this content:
+
+    
+    >>> compare_keyvalue_texts('''a: Hello
+    ... b: 1.0
+    ... c: 1,2,3,4,5
+    ... d: hello,8,0.1''',
+    ... '''a: Hello
+    ... b: 1.1
+    ... c: 1,22,3,4,5
+    ... d: hello,8,0.1''', precision=0.1)
+    False
+
+    @param filename_a name of the first key-value text file
+    @param filenmae_b name of the second key-value text file
+    @param sep character that separates the keys and values, default is ":"
+    @param val_sep character that separates the values of a single key, default is ","
+    @param precision precision with which the floating point values are compared
+    @param proj True if it has to check some information about projection system
+    @param units True if it has to check some information about units
+
+    @return True if full or almost identical, False if different
+    """
+    dict_a = text_to_keyvalue(text_a, sep, val_sep, functions=polish_functions)
+    dict_b = text_to_keyvalue(text_b, sep, val_sep, functions=polish_functions)
+    key_equal = {} if key_equal is None else key_equal
+
+    if sorted(dict_a.keys()) != sorted(dict_b.keys()):
+        return False
+
+    # We compare matching keys
+    for key in dict_a.keys():
+        equal_fun = key_equal.get(key, def_equal)
+        if not equal_fun(dict_a[key], dict_b[key], precision):
+            return False
+        # bahavior change comparing to the original proj fun
+        # elif isinstance(dict_a[key], float) or isinstance(dict_b[key], float):
+            #warning(_("Mixing value types. Will try to compare after "
+            #          "integer conversion"))
+            #return int(dict_a[key]) == int(dict_b[key])
+        #elif key == "+towgs84":
+        #    # We compare the sum of the entries
+        #    if abs(sum(dict_a[key]) - sum(dict_b[key])) > precision:
+        #        return False
+    return True
+
+
 # alternative names: looks like, correspond with/to
 def check_text_ellipsis(reference, actual):
     """

Added: sandbox/wenzeslaus/gunittest/testsuite/test_checkers.py
===================================================================
--- sandbox/wenzeslaus/gunittest/testsuite/test_checkers.py	                        (rev 0)
+++ sandbox/wenzeslaus/gunittest/testsuite/test_checkers.py	2014-06-04 04:54:33 UTC (rev 60698)
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+
+"""
+Tests assertion methods.
+"""
+
+
+import sys
+import os
+
+# import gunittest as a package so that relative imports there works
+sys.path.insert(0, os.path.split(os.path.split((os.path.dirname(__file__)))[0])[0])
+from gunittest.case import GrassTestCase
+import gunittest.checkers as checkers
+
+
+class TestValuesEqual(GrassTestCase):
+
+    def test_floats(self):
+        self.assertTrue(checkers.values_equal(5.0, 5.0))
+        self.assertTrue(checkers.values_equal(5.1, 5.19, precision=0.1))
+        self.assertTrue(checkers.values_equal(5.00005, 5.000059, precision=0.00001))
+        self.assertFalse(checkers.values_equal(5.125, 5.280))
+        self.assertFalse(checkers.values_equal(5.00005, 5.00006, precision=0.00001))
+        self.assertFalse(checkers.values_equal(2.5, 15.5, precision=5))
+
+    def test_ints(self):
+        self.assertTrue(checkers.values_equal(5, 5, precision=0.01))
+        self.assertFalse(checkers.values_equal(5, 6, precision=0.01))
+        self.assertTrue(checkers.values_equal(5, 8, precision=3))
+        self.assertFalse(checkers.values_equal(3600, 3623, precision=20))
+        self.assertTrue(checkers.values_equal(5, 5))
+        self.assertFalse(checkers.values_equal(5, 6))
+
+    def test_floats_and_ints(self):
+        self.assertTrue(checkers.values_equal(5.1, 5, precision=0.2))
+        self.assertFalse(checkers.values_equal(5.1, 5, precision=0.01))
+
+    def test_strings(self):
+        self.assertTrue(checkers.values_equal('hello', 'hello'))
+        self.assertFalse(checkers.values_equal('Hello', 'hello'))
+
+    def test_lists(self):
+        self.assertTrue(checkers.values_equal([1, 2, 3], [1, 2, 3]))
+        self.assertTrue(checkers.values_equal([1.1, 2.0, 3.9],
+                                              [1.1, 1.95, 4.0],
+                                              precision=0.2))
+        self.assertFalse(checkers.values_equal([1, 2, 3, 4, 5],
+                                               [1, 22, 3, 4, 5],
+                                               precision=1))
+
+    def test_mixed_lists(self):
+        self.assertTrue(checkers.values_equal([1, 'abc', 8], [1, 'abc', 8.2],
+                                              precision=0.5))
+
+    def test_recursive_lists(self):
+        self.assertTrue(checkers.values_equal([1, 'abc', [5, 9.6, 9.0]],
+                                              [1, 'abc', [4.9, 9.2, 9.3]],
+                                              precision=0.5))
+
+KEYVAL_TEXT = '''s: Hello
+str: Hello world!
+f: 1.0
+l: 1,2,3,4,5
+mixed: hello,8,-25,world!,4-1,5:2,0.1,-9.6
+'''
+
+
+class TestTextToKeyValue(GrassTestCase):
+    def test_conversion(self):
+        keyvals = checkers.text_to_keyvalue(KEYVAL_TEXT, sep=':', val_sep=',')
+        expected = {'s': 'Hello',
+                    'str': 'Hello world!',
+                    'f': 1.0,
+                    'l': [1, 2, 3, 4, 5],
+                    'mixed': ['hello', 8, -25, 'world!',
+                              '4-1', '5:2', 0.1, -9.6]}
+        self.assertDictEqual(expected, keyvals)
+
+    def test_single_values(self):
+        keyvals = checkers.text_to_keyvalue("a: 1.5", sep=':')
+        self.assertDictEqual({'a': 1.5}, keyvals)
+        keyvals = checkers.text_to_keyvalue("abc=1", sep='=')
+        self.assertDictEqual({'abc': 1}, keyvals)
+        keyvals = checkers.text_to_keyvalue("abc=hello", sep='=')
+        self.assertDictEqual({'abc': 'hello'}, keyvals)
+
+    def test_strip(self):
+        keyvals = checkers.text_to_keyvalue("a:   2.8  ", sep=':')
+        self.assertDictEqual({'a': 2.8}, keyvals)
+        keyvals = checkers.text_to_keyvalue("a:  2  ; 2.8 ; ab cd ", sep=':', val_sep=';')
+        self.assertDictEqual({'a': [2, 2.8, 'ab cd']}, keyvals)
+        keyvals = checkers.text_to_keyvalue("a  :  2  ; 2.8", sep=':', val_sep=';')
+        self.assertDictEqual({'a': [2, 2.8]}, keyvals)
+        keyvals = checkers.text_to_keyvalue("a  : \t 2  ;\t2.8", sep=':', val_sep=';')
+        self.assertDictEqual({'a': [2, 2.8]}, keyvals)
+
+    def test_empty_list_item(self):
+        keyvals = checkers.text_to_keyvalue("a: 1, ,5,,", sep=':', val_sep=',')
+        self.assertDictEqual({'a': [1, '', 5, '', '']}, keyvals)
+
+    def test_empty_value(self):
+        keyvals = checkers.text_to_keyvalue("a: ", sep=':')
+        self.assertDictEqual({'a': ''}, keyvals)
+        keyvals = checkers.text_to_keyvalue("a:", sep=':')
+        self.assertDictEqual({'a': ''}, keyvals)
+
+    def test_wrong_lines(self):
+        # we consider no key-value separator as invalid line
+        # and we silently ignore these
+        keyvals = checkers.text_to_keyvalue("a", sep=':',
+                                            skip_invalid=True, skip_empty=False)
+        self.assertDictEqual({}, keyvals)
+
+        self.assertRaises(ValueError, checkers.text_to_keyvalue, "a", sep=':',
+                                      skip_invalid=False, skip_empty=False)
+
+        # text_to_keyvalue considers the empty string as valid input
+        keyvals = checkers.text_to_keyvalue("", sep=':',
+                                            skip_invalid=False, skip_empty=False)
+        self.assertDictEqual({}, keyvals)
+
+        self.assertRaises(ValueError, checkers.text_to_keyvalue, "\n", sep=':',
+                                      skip_invalid=True, skip_empty=False)
+
+        keyvals = checkers.text_to_keyvalue("a\n\n", sep=':',
+                                            skip_invalid=True, skip_empty=True)
+        self.assertDictEqual({}, keyvals)
+
+    def test_separators(self):
+        keyvals = checkers.text_to_keyvalue("a=a;b;c", sep='=', val_sep=';')
+        self.assertDictEqual({'a': ['a', 'b', 'c']}, keyvals)
+        keyvals = checkers.text_to_keyvalue("a 1;2;3", sep=' ', val_sep=';')
+        self.assertDictEqual({'a': [1, 2, 3]}, keyvals)
+        # spaces as key-value separator and values separators
+        # this should work (e.g. because of : in DMS),
+        # although it does not support stripping (we don't merge separators)
+        keyvals = checkers.text_to_keyvalue("a 1 2 3", sep=' ', val_sep=' ')
+        self.assertDictEqual({'a': [1, 2, 3]}, keyvals)


Property changes on: sandbox/wenzeslaus/gunittest/testsuite/test_checkers.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native



More information about the grass-commit mailing list