ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/pyradlib/lcompare.py
Revision: 1.1
Committed: Mon Mar 28 17:52:11 2016 UTC (9 years, 1 month ago) by schorsch
Content type: text/x-python
Branch: MAIN
Log Message:
Support library for test suite and other Pytho scripts

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