Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# (c) Stefan Countryman 2016-2018
3"""
4Testing for utils.py
5"""
7import os
8from io import BytesIO
9from tempfile import NamedTemporaryFile
10import pytest
11from llama import utils
12from llama.test import floateql
15def memoize_helper(*returnvals):
16 """Creates a function that can only be called once to make sure that
17 ``memoize`` doesn't unnecessarily get called many times.
19 Parameters
20 ----------
21 returnvals : iterable
22 The values that ``called_once`` should return (regardless of input
23 args). If only one argument is provided, then that value will be
24 returned. If multiple arguments are provided, they will be returned as
25 a tuple. If no arguments are provided, then there will be no return
26 value, i.e. ``None`` will be returned.
28 Returns
29 -------
30 called_once : function
31 A function that can only be called once (or else it raises an
32 ``AssertionError``.
33 """
34 has_not_been_called = [True] # need a list to use this as a pointer
36 def called_once(*args, **kwargs): # pylint: disable=unused-argument
37 """This function can only be called once, or else it fails."""
38 assert has_not_been_called[0]
39 has_not_been_called[0] = False
40 if returnvals:
41 if len(returnvals) == 1:
42 return returnvals[0]
43 return tuple(returnvals)
44 return None
46 return called_once
49def test_memoize_helper():
50 """Test the ``memoize_helper``. Returned function should raise an
51 ``AssertionError`` when called more than once."""
52 args = [(), (3,), (3, 4)]
53 results = (None, 3, (3, 4))
54 for i, arg in enumerate(args):
55 once_callable = memoize_helper(*arg)
56 assert results[i] == once_callable()
57 with pytest.raises(AssertionError):
58 once_callable()
61def memoize_func_helper(**memoize_args):
62 """Test ``utils.memoize`` for functions on both persistent and in-memory
63 memoization (persistent memoization is available to functions only, not
64 methods). Takes arguments to be passed to ``utils.memoize``."""
65 result_collections = [('foo',), (2, 4), ()]
66 results = ['foo', (2, 4), None]
67 # pick a few different possible (hashable!) inputs
68 arg_collections = [('bar',), ('age',), (2, 'name', ('x', tuple))]
69 memoizer = utils.memoize(**memoize_args)
70 for i, returnvals in enumerate(result_collections):
71 for arguments in arg_collections:
72 bare_func = memoize_helper(*returnvals)
74 @memoizer
75 def func(*args):
76 """make a memoized version of bare_func"""
77 # we are immediately using this function within this loop,
78 # so we don't need to worry about ``instance_methods`` (or
79 # the others) referring to a changing variable. For
80 # details, see: stackoverflow.com/questions/25314547
81 return bare_func(*args)
83 # we should be able to call the function once to cache it...
84 assert func(*arguments) == results[i]
85 # then we can assert that the function wrapped by memoize will
86 # raise an exception if called again...
87 with pytest.raises(AssertionError):
88 bare_func(*arguments)
89 # but due to caching, we should still get the same return
90 # values as before when using the @memoize methods.
91 assert func(*arguments) == results[i]
93 # clean up the function class after this loop
94 if 'persist' in memoize_args:
95 utils.flush_func_cache(func)
98def test_memoize():
99 """Test in-memory ``memoize`` on a function."""
100 memoize_func_helper()
103# def test_memoize_persistent():
104# """Test persistent on-disk ``memoize`` on a function."""
105# # clean up the persistent cache if it exists
106# def func():
107# pass
108# try:
109# utils.flush_func_cache(func)
110# except (OSError, FileNotFoundError):
111# pass
112# memoize_func_helper(persist=True)
115def test_memoize_class():
116 """Test ``utils.memoize`` for classes."""
117 # pick a few different return values
118 result_collections = [(), (3,), (3, 4)]
119 results = [None, 3, (3, 4)]
120 # pick a few different possible (hashable!) inputs
121 arg_collections = [(), ('age',), (4, 'foo', ('a', 'tuple'))]
122 for i, returnvals in enumerate(result_collections):
123 for arguments in arg_collections:
124 instance_method = memoize_helper(*returnvals)
125 class_method = memoize_helper(*returnvals)
126 property_method = memoize_helper(*returnvals)
128 class CallOnce(object):
129 """This class has methods that can only be called once."""
131 @utils.memoize(method=True)
132 def meth(self, *args):
133 """Test whether instance methods are memoizeable."""
134 # we are immediately using this class within this loop, so
135 # we don't need to worry about ``instance_methods`` (or the
136 # others) referring to a changing variable. For details,
137 # see: stackoverflow.com/questions/25314547
138 # pylint: disable=cell-var-from-loop
139 return instance_method(self, *args)
141 @classmethod
142 @utils.memoize(method=True)
143 def clas(cls, *args):
144 """Test whether classmethods are memoizeable."""
145 # pylint: disable=cell-var-from-loop
146 return class_method(cls, *args)
148 @property
149 @utils.memoize(method=True)
150 def prop(self):
151 """Test whether properties are memoizeable."""
152 # pylint: disable=cell-var-from-loop
153 return property_method(self)
155 instance = CallOnce()
157 # we should be able to call the below methods once to cache them...
158 assert instance.meth(*arguments) == results[i]
159 assert instance.clas(*arguments) == results[i]
160 assert instance.prop == results[i]
162 # then we can assert that the functions they rely on will raise
163 # exceptions if called again...
164 with pytest.raises(AssertionError):
165 instance_method(instance, *arguments)
166 with pytest.raises(AssertionError):
167 class_method(CallOnce, *arguments)
168 with pytest.raises(AssertionError):
169 property_method(instance)
171 # but due to caching, we should still get the same return values as
172 # before when using the @memoize methods.
173 assert instance.meth(*arguments) == results[i]
174 assert instance.clas(*arguments) == results[i]
175 assert instance.prop == results[i]
178def test_write_gzip():
179 """Test ``utils.write_gzip``."""
180 with NamedTemporaryFile(suffix='.gz', delete=False) as tmp:
181 outfilename = tmp.name
182 infile = BytesIO('this is obviously not a valid gzip file'.encode())
183 assert not utils.write_gzip(infile, outfilename)
184 with open(outfilename, 'rb') as actuallyzippedfile:
185 assert utils.write_gzip(actuallyzippedfile, outfilename)
186 os.remove(outfilename)
189def test_rotate_angs2vec():
190 """Test ``utils.rotate_angs2vec``."""
193def test_get_grid_make_grid():
194 """Test ``utils.get_grid``. Simply see if we can make a grid."""
195 import numpy as np
196 expectedras = np.array([-1e-7, -5e-8, 0, 5e-8, 1e-7, -1e-7, -5e-8, 0, 5e-8,
197 1e-7, -1e-7, -5e-8, 0, 5e-8, 1e-7, -1e-7, -5e-8, 0,
198 5e-8, 1e-7, -1e-7, -5e-8, 0, 5e-8, 1e-7])
199 expecteddecs = np.array([-1e-7, -1e-7, -1e-7, -1e-7, -1e-7, -5e-8, -5e-8,
200 -5e-8, -5e-8, -5e-8, 0, 0, 0, 0, 0, 5e-8, 5e-8,
201 5e-8, 5e-8, 5e-8, 1e-7, 1e-7, 1e-7, 1e-7, 1e-7])
202 expectedarea = 4e-16
203 ras, decs, area = utils.get_grid(0, 0, 1e-7, 5)
204 assert floateql(ras, expectedras, prec=1e-6)
205 assert floateql(decs, expecteddecs, prec=1e-6)
206 assert floateql(area, expectedarea)
207 ras, decs, area = utils.get_grid(0, 0, 1e-7, 5, degrees=False)
208 assert floateql(ras, expectedras, prec=1e-6)
209 assert floateql(decs, expecteddecs, prec=1e-6)
210 assert floateql(area, expectedarea)
213def test_get_grid_north_pole():
214 """Test ``utils.get_grid``. See if we can place a point at the north
215 pole."""
216 import numpy as np
217 # pylint: disable=unused-variable
218 ras, decs, area = utils.get_grid(0, 90, 0, 1)
219 assert floateql(decs, 90, prec=1e-6)
220 assert floateql(area, 0, prec=1e-6)
221 ras, decs, area = utils.get_grid(0, np.pi/2, 0, 1)
222 assert floateql(decs, np.pi/2, prec=1e-6)
223 assert floateql(area, 0, prec=1e-6)