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 (9 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

# User Rev Content
1 schorsch 1.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 schorsch 1.2 import re
14     import shlex
15 schorsch 1.1 # 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 schorsch 1.2 _strtypes = (type(b''), type(u''))
24    
25 schorsch 1.1 # internal functions
26     def _icompare(itest, iref):
27     '''compare ints (not public)'''
28 schorsch 1.2 if isinstance(itest, _strtypes):
29 schorsch 1.1 iftest = int(itest)
30     else: iftest = itest
31 schorsch 1.2 if iftest == iref: return True
32     return False
33 schorsch 1.1
34     def _fcompare(ftest, fref):
35     '''compare floats (not public)'''
36     FUZZ = 0.0000001 # XXX heuristically determined (quite low?)
37 schorsch 1.2 if isinstance(ftest, _strtypes):
38 schorsch 1.1 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 schorsch 1.2 if isinstance(tref, _strtypes) and tref != ttest:
69 schorsch 1.1 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 schorsch 1.2 if lref and not isinstance(lref, _strtypes):
98    
99 schorsch 1.1 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 schorsch 1.2 if isinstance(rfirst, _strtypes):
106 schorsch 1.1 rfirst = None
107     if hasattr(rfirst, '__iter__') or isinstance(rfirst, (list, tuple)):
108 schorsch 1.2 llcompare(ltest, lref,
109 schorsch 1.1 _recurse=_recurse + [i], ignore_empty=ignore_empty)
110     try: lcompare(ltest, lref)
111 schorsch 1.2 # except TypeError:
112     # print(ltest, lref)
113     # raise
114 schorsch 1.1 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 schorsch 1.2 _HLPATS = '(\s*)(?:([^=\s]*)\s*=\s*(.*)\s*|(.*)\s*)'
120     _hlpat = re.compile(_HLPATS)
121 schorsch 1.1 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 schorsch 1.2 ll = s.split('\n')
125 schorsch 1.1 nll = []
126     for l in ll:
127 schorsch 1.2 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 schorsch 1.1 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