Hide keyboard shortcuts

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 

2 

3""" 

4Testing for utils.py 

5""" 

6 

7import os 

8from io import BytesIO 

9from tempfile import NamedTemporaryFile 

10import pytest 

11from llama import utils 

12from llama.test import floateql 

13 

14 

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. 

18 

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. 

27 

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 

35 

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 

45 

46 return called_once 

47 

48 

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

59 

60 

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) 

73 

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) 

82 

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] 

92 

93 # clean up the function class after this loop 

94 if 'persist' in memoize_args: 

95 utils.flush_func_cache(func) 

96 

97 

98def test_memoize(): 

99 """Test in-memory ``memoize`` on a function.""" 

100 memoize_func_helper() 

101 

102 

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) 

113 

114 

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) 

127 

128 class CallOnce(object): 

129 """This class has methods that can only be called once.""" 

130 

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) 

140 

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) 

147 

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) 

154 

155 instance = CallOnce() 

156 

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] 

161 

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) 

170 

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] 

176 

177 

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) 

187 

188 

189def test_rotate_angs2vec(): 

190 """Test ``utils.rotate_angs2vec``.""" 

191 

192 

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) 

211 

212 

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)