ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/pyradlib/lcompare.py
Revision: 1.2
Committed: Wed Mar 30 18:09:00 2016 UTC (8 years, 1 month ago) by schorsch
Content type: text/x-python
Branch: MAIN
CVS Tags: rad5R4, rad5R2, rad5R3, HEAD
Changes since 1.1: +29 -20 lines
Log Message:
The test utilities need testing (and some fixing) as well.

File Contents

# Content
1 # -*- coding: utf-8 -*-
2 ''' Text comparison functions for Radiance unit testing.
3
4 The comparison allows for differences in whitespace, so we need to
5 split the text into tokens first. Tokens are then converted into an
6 appropriate data type, so that we can do an "almost equal" comparison
7 for floating point values, ignoring binary rounding errors.
8 '''
9 from __future__ import division, print_function, unicode_literals
10 __all__ = ['error', 'lcompare', 'llcompare',
11 'split_headers', 'split_rad', 'split_radfile']
12
13 import re
14 import shlex
15 # Py2.7/3.x compatibility
16 try: from itertools import (izip_longest as zip_longest, chain,
17 ifilter as filter, izip as zip)
18 except ImportError:
19 from itertools import zip_longest, chain
20
21 class error(Exception): pass
22
23 _strtypes = (type(b''), type(u''))
24
25 # internal functions
26 def _icompare(itest, iref):
27 '''compare ints (not public)'''
28 if isinstance(itest, _strtypes):
29 iftest = int(itest)
30 else: iftest = itest
31 if iftest == iref: return True
32 return False
33
34 def _fcompare(ftest, fref):
35 '''compare floats (not public)'''
36 FUZZ = 0.0000001 # XXX heuristically determined (quite low?)
37 if isinstance(ftest, _strtypes):
38 fftest = float(ftest)
39 else: fftest = ftest
40 if (fftest < (fref + FUZZ)) and (fftest > (fref - FUZZ)):
41 return True
42 return False
43
44 def _typify_token(t):
45 '''return the token as int resp. float if possible (not public)'''
46 try: return int(t)
47 except ValueError: pass
48 try: return float(t)
49 except ValueError: pass
50 return t
51
52
53 # public comparison functions
54
55 def lcompare(ltest, lref):
56 '''Compare a list/iterator of tokens.
57 Raise an error if there are intolerable differences.
58 The reference tokens in lref should already be of the correct type.
59 '''
60 i = 0
61 for ttest, tref in zip_longest(ltest, lref, fillvalue=False):
62 if ttest is False:
63 raise error('List comparision failed: Fewer tokens than expected'
64 ' (%d != >= %d)' % (i, i+1))
65 if tref is False:
66 raise error('List comparision failed: More tokens than expected'
67 ' (>= %d != %d)' % (i+1, i))
68 if isinstance(tref, _strtypes) and tref != ttest:
69 raise error('String token comparison failed: "%s" != "%s"'
70 % (ttest, tref))
71 elif type(tref) == int and not _icompare(ttest, tref):
72 raise error('Int token comparison failed: %s != %s' % (ttest, tref))
73 elif type(tref) == float and not _fcompare(ttest, tref):
74 raise error('Float token comparison failed: %s != %s'
75 % (ttest, tref))
76 i += 1
77
78 def llcompare(lltest, llref, ignore_empty=False, _recurse=[]):
79 '''Compare a list/iterator of lists/iterators of tokens recursively.
80 Raise an error if there are intolerable differences.
81 The reference tokens in lref should already be of the correct type.
82 If ignore_empty is true, empty lines are not included in the comparison.
83 The _recurse argument is only used internally.
84 '''
85 if ignore_empty:
86 lltest = filter(None, lltest)
87 llref = filter(None, llref)
88 i = 0
89 for ltest, lref in zip_longest(lltest, llref, fillvalue=False):
90 i += 1
91 if ltest is False:
92 raise error('List comparision failed: Fewer entries than expected'
93 ' (%d != >= %d)' % (i, i+1))
94 if lref is False:
95 raise error('List comparision failed: More entries than expected'
96 ' (>= %d != %d)' % (i+1, i))
97 if lref and not isinstance(lref, _strtypes):
98
99 if hasattr(lref, '__getitem__'):
100 rfirst = lref[0]
101 elif hasattr(lref, 'next') or hasattr(lref, '__next__'):
102 rfirst = next(lref) # "peek" at first
103 lref = chain([rfirst], lref) # "push" back
104 else: rfirst = None
105 if isinstance(rfirst, _strtypes):
106 rfirst = None
107 if hasattr(rfirst, '__iter__') or isinstance(rfirst, (list, tuple)):
108 llcompare(ltest, lref,
109 _recurse=_recurse + [i], ignore_empty=ignore_empty)
110 try: lcompare(ltest, lref)
111 # except TypeError:
112 # print(ltest, lref)
113 # raise
114 except error as e:
115 if _recurse:
116 raise error('%s (line %s)' % (str(e), _recurse + [i + 1]))
117 else: raise error('%s (line %d)' % (str(e), i + 1))
118
119 _HLPATS = '(\s*)(?:([^=\s]*)\s*=\s*(.*)\s*|(.*)\s*)'
120 _hlpat = re.compile(_HLPATS)
121 def split_headers(s):
122 '''split Radiance file headers (eg. the output of getinfo).
123 Return a list of lists of tokens suitable for llcompare().'''
124 ll = s.split('\n')
125 nll = []
126 for l in ll:
127 m = _hlpat.match(l)
128 groups = m.groups()
129 indent = groups[0]
130 if groups[1]:
131 left = groups[1]
132 right = [_typify_token(s) for s in shlex.split(groups[2])]
133 nll.append([indent, left, '='] + [right])
134 else:
135 full = [_typify_token(s) for s in shlex.split(groups[3])]
136 nll.append([indent] + [full])
137 return nll
138
139 def split_rad(s):
140 '''Split the contents of a scene description string.
141 Return a list of list of tokens suitable for llcompare().'''
142 ll = [ss.strip() for ss in s.split('\n')]
143 return [[_typify_token(s) for s in l.split()] for l in ll]
144
145 def split_radfile(fn):
146 '''Split the contents of a file containing a scene description.
147 Return a list of list of tokens suitable for llcompare().'''
148 with open(fn, 'r') as f:
149 return split_rad(f.read())
150