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

# 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     # 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