[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