source: anuga_core/source/anuga/caching/test_caching.py @ 5856

Last change on this file since 5856 was 5856, checked in by ole, 15 years ago

A bit more fiddling with caching and circular structures not
involving instances.

All relevant tests pass

File size: 22.3 KB
Line 
1
2import unittest
3from Numeric import arange, array
4
5from copy import deepcopy
6       
7from anuga.caching import *
8from anuga.caching.dummy_classes_for_testing import Dummy, Dummy_memorytest
9
10
11# Define a test function to be cached
12#
13def f(a,b,c,N,x=0,y='abcdefg'):
14  """f(a,b,c,N)
15     Do something time consuming and produce a complex result.
16  """
17
18  import string
19
20  B = []
21  for n in range(N):
22    s = str(n+2.0/(n + 4.0))+'.a'*10
23    B.append((a,b,c,s,n,x,y))
24  return(B)
25 
26def f_numeric(A, B):
27  """Operation on Numeric arrays
28  """
29 
30  return 3.1*A + B + 1
31 
32 
33def f_object(A, B):
34  """Operation of objecs of class Dummy
35  """
36 
37  return A.value+B.value, A.another+B.another
38 
39
40def f_generic(A):
41  return A
42 
43def clear_and_create_cache(Dummy, verbose=False):
44
45  a = cache(Dummy, 'clear', verbose=verbose)
46  a = cache(Dummy, args=(9,10),
47            verbose=verbose)
48     
49
50def retrieve_cache(Dummy, verbose=False):
51  if verbose: print 'Check that cache is there'
52 
53  X = cache(Dummy, args=(9,10), test=1,
54            verbose=verbose)     
55           
56  msg = 'Cached value was not found'
57  assert X is not None, msg
58
59     
60
61   
62
63class Test_Caching(unittest.TestCase):
64    def setUp(self):
65        set_option('verbose', 0)  #Why
66
67        pass
68
69    def tearDown(self):
70        pass
71
72    def test_simple(self):
73        """Check set_option (and switch stats off)
74        """
75
76        set_option('savestat', 0)
77        assert options['savestat'] == 0
78        set_option('verbose', 0)
79        assert options['verbose'] == 0       
80       
81
82    def test_basic_caching(self):
83
84        verbose=False
85        # Make some test input arguments
86        #
87        N = 5000  #Make N fairly small here
88
89        a = [1,2]
90        b = ('Thou shalt count the number three',4)
91        c = {'Five is right out': 6, (7,8): 9}
92        x = 3
93        y = 'holy hand granate'
94
95        # Test caching
96        #
97
98        comprange = 2
99
100        for comp in range(comprange):
101 
102            # Evaluate and store
103            #
104            T1 = cache(f, (a,b,c,N), {'x':x, 'y':y}, evaluate=1, \
105                       compression=comp, verbose=verbose)
106
107            # Retrieve
108            #                           
109            T2 = cache(f, (a,b,c,N), {'x':x, 'y':y}, compression=comp) 
110
111            # Reference result
112            #   
113            T3 = f(a,b,c,N,x=x,y=y)  # Compute without caching
114
115
116            assert T1 == T2, 'Cached result does not match computed result'
117            assert T2 == T3, 'Cached result does not match computed result'
118           
119
120           
121    def test_caching_of_numeric_arrays(self):
122        """test_caching_of_numeric_arrays
123       
124        Test that Numeric arrays can be recognised by caching even if their id's are different
125        """
126       
127        verbose = False
128       
129        # Make some test input arguments
130        A0 = arange(5)
131        B0 = array([1.1, 2.2, 0.0, -5, -5])
132       
133        A1 = A0.copy()
134        B1 = B0.copy()
135       
136        # Check that their ids are different
137        assert id(A0) != id(A1)
138        assert id(B0) != id(B1)       
139       
140       
141        # Test caching
142        comprange = 2
143        for comp in range(comprange):
144 
145            # Evaluate and store
146            T1 = cache(f_numeric, (A0, B0), evaluate=1,
147                       compression=comp, verbose=verbose)
148
149            # Retrieve
150            T2 = cache(f_numeric, (A1, B1), 
151                       compression=comp, test=1, verbose=verbose) 
152                       
153            # Check for presence of cached result
154            msg = 'Different array objects with same contents were not recognised'           
155            assert T2 is not None, msg
156
157            # Reference result
158            T3 = f_numeric(A0, B0) # Compute without caching
159
160
161            assert T1 == T2, 'Cached result does not match computed result'
162            assert T2 == T3, 'Cached result does not match computed result'
163           
164
165    def test_caching_of_dictionaries(self):
166        """test_caching_of_dictionaries
167       
168        Real example from ANUGA that caused some
169        hashing problems
170        """
171   
172
173        verbose = False #True
174       
175        D = {'point_attributes': None, 
176             'use_cache': True, 
177             'vertex_coordinates': None, 
178             'verbose': True, 
179             'max_read_lines': 500, 
180             'acceptable_overshoot': 1.01, 
181             'mesh': None, 
182             'data_origin': None, 
183             'alpha': 0.02, 
184             'mesh_origin': None, 
185             'attribute_name': None, 
186             'triangles': None}         
187       
188        DD = deepcopy(D) # Mangles the dictionary ordering
189       
190        assert myhash(DD) == myhash(D)
191
192        # Also test caching now that we are at it
193        comprange = 2
194        for comp in range(comprange):
195 
196            # Evaluate and store using D
197            T1 = cache(f_generic, D, evaluate=1,
198                       compression=comp, verbose=verbose)
199
200            # Retrieve using copy (DD)
201            T2 = cache(f_generic, DD, 
202                       compression=comp, test=1, verbose=verbose) 
203                       
204            # Check for presence of cached result
205            msg = 'Cached object was not found'           
206            assert T2 is not None, msg
207
208            # Reference result
209            T3 = f_generic(D) # Compute without caching
210
211           
212            msg = 'Cached result does not match computed result' 
213           
214            # Compare dictionaries
215            for key in T1:
216                assert T1[key] == T2[key]
217                assert T2[key] == T3[key]               
218               
219           
220           
221           
222
223    def test_caching_of_objects(self):
224        """test_caching_of_objects
225       
226        Test that Objecs can be recognised as input variabelse
227        by caching even if their id's are different
228        """
229   
230
231        verbose = False
232       
233        # Make some test input arguments
234        A0 = Dummy(5, 7)
235        B0 = Dummy(2.2, -5)
236       
237        A0.new_attribute = 'x' 
238        B0.new_attribute = 'x'       
239       
240        A1 = deepcopy(A0)
241        B1 = deepcopy(B0)       
242       
243        # Check that their ids are different
244        assert id(A0) != id(A1)
245        assert id(B0) != id(B1)       
246       
247       
248        # Test caching
249        comprange = 2
250        for comp in range(comprange):
251 
252            # Evaluate and store
253            T1 = cache(f_object, (A0, B0), evaluate=1,
254                       compression=comp, verbose=verbose)
255
256            # Retrieve
257            T2 = cache(f_object, (A1, B1), 
258                       compression=comp, 
259                       test=1, 
260                       verbose=verbose) 
261                       
262            # Check for presence of cached result
263            msg = 'Different objects with same attributes were not recognised'
264            assert T2 is not None, msg
265
266            # Reference result
267            T3 = f_object(A0, B0) # Compute without caching
268
269
270            assert T1 == T2, 'Cached result does not match computed result'
271            assert T2 == T3, 'Cached result does not match computed result'
272           
273
274    def test_caching_of_circular_structures(self):
275        """test_caching_of_circular_structures
276       
277        Test that Caching doesn't recurse infinitely in case of
278        circular or self-referencing structures
279        """
280       
281        verbose = False
282       
283        # Create input argument
284        A = Dummy(5, 7)
285        B = {'x': 10, 'A': A}
286        C = [B, 15]
287        A.value = C # Make it circular
288        A.x = [1,2,C,5,A] # More circular and self referential
289       
290        AA = deepcopy(A)
291
292        # Test caching
293        comprange = 2
294        for comp in range(comprange):
295 
296            # Evaluate and store
297            T1 = cache(f_generic, A, 
298                       evaluate=1,
299                       compression=comp, verbose=verbose)
300
301            # Retrieve
302            T2 = cache(f_generic, AA, 
303                       compression=comp, 
304                       test=1, verbose=verbose) 
305                       
306            # Check for presence of cached result
307            msg = 'Cached object was not found'           
308            assert T2 is not None, msg
309
310            # Reference result
311            T3 = f_generic(A) # Compute without caching
312
313           
314            msg = 'Cached result does not match computed result' 
315            assert str(T1) == str(T2), msg
316            assert str(T2) == str(T3), msg
317                                   
318           
319    def Xtest_caching_of_complex_circular_structure(self):
320        """test_caching_of_complex_circular_structure
321       
322        Test that Caching can handle a realistic
323        complex structure. This one is inspired by
324        ANUGA's Okushiri example, although reduced in size.
325        """
326       
327        pass
328
329       
330    def test_uniqueness_of_hash_values(self):
331        """test_uniqueness_of_hash_values(self):
332       
333        Test that Caching can handle a realistic
334        complex structure by hashing it consistently and
335        uniquely.
336        """
337       
338        verbose = False
339       
340        # Create input argument
341        A = Dummy(5, 7)
342        B = {'x': 10, 'A': A}
343        C = [B, array([1.2, 3, 5, 0.1])]
344        A.value = C # Make it circular
345
346        # Create identical but separate object   
347        AA = Dummy(None, None)
348        BB = {'A': AA, 'x': 10}
349        CC = [BB, array([1.200, 3.000, 5.00, 1.0/10])]
350        AA.value = CC # Make it circular
351        AA.another = 3+4       
352       
353       
354        assert myhash(A) == myhash(AA)     
355           
356           
357       
358        # Also test caching now that we are at it
359        comprange = 2
360        for comp in range(comprange):
361 
362            # Evaluate and store using A
363            T1 = cache(f_generic, A, evaluate=1,
364                       compression=comp, verbose=verbose)
365
366            # Retrieve using copy (AA)
367            T2 = cache(f_generic, AA, 
368                       compression=comp, test=1, verbose=verbose) 
369                       
370            # Check for presence of cached result
371            msg = 'Cached object was not found'           
372            assert T2 is not None, msg
373
374            # Reference result
375            T3 = f_generic(A) # Compute without caching
376
377           
378            msg = 'Cached result does not match computed result' 
379            assert str(T1) == str(T2), msg
380            assert str(T2) == str(T3), msg
381           
382           
383
384    def test_caching_of_simple_circular_dictionaries(self):
385        """test_caching_of_circular_structures
386       
387        Test that Caching doesn't recurse infinitely in case of
388        circular or self-referencing structures
389        """
390       
391        verbose = False #True
392       
393        # Create input argument
394        A = {'x': 10, 'B': None}
395        B = [A, 15]
396        A['B'] = B # Make it circular
397       
398        # Test caching
399        comprange = 2
400        for comp in range(comprange):
401 
402            # Evaluate and store
403            T1 = cache(f_generic, A, evaluate=1,
404                       compression=comp, verbose=verbose)
405                       
406
407            # Retrieve
408            T2 = cache(f_generic, A, 
409                       compression=comp, test=1, verbose=verbose) 
410                       
411            # Check for presence of cached result
412            msg = 'Cached object was not found'           
413            assert T2 is not None, msg
414
415            # Reference result
416            T3 = f_generic(A) # Compute without caching
417
418
419            msg = 'Cached result does not match computed result'
420            assert str(T1) == str(T2), msg
421            assert str(T2) == str(T3), msg
422
423           
424           
425    def test_cachefiles(self):
426        """Test existence of cachefiles
427        """       
428        N = 5000  #Make N fairly small here
429
430        a = [1,2]
431        b = ('Thou shalt count the number three',4)
432        c = {'Five is right out': 6, (7,8): 9}
433        x = 3
434        y = 'holy hand granate'
435
436       
437        FN = cache(f,(a,b,c,N), {'x':x, 'y':y}, verbose=0, \
438                  return_filename = 1)
439
440
441        assert FN[:2] == 'f['
442
443        CD = checkdir(cachedir)
444        compression = 1
445
446        (datafile,compressed0) = myopen(CD+FN+'_'+file_types[0],"rb",compression)
447        (argsfile,compressed1) = myopen(CD+FN+'_'+file_types[1],"rb",compression)
448        (admfile,compressed2) =  myopen(CD+FN+'_'+file_types[2],"rb",compression)
449
450        datafile.close()
451        argsfile.close()
452        admfile.close()
453
454    def test_test(self):       
455        """Test 'test' function when cache is present
456        """
457       
458        verbose = False
459       
460        N = 5 
461
462        a = [1,2]
463        b = ('Thou shalt count the number three',4)
464        c = {'Five is right out': 6, (7,8): 9}
465        x = 3
466        y = 'holy hand granate'
467       
468
469        T1 = cache(f, (a,b,c,N), {'x':x, 'y':y}, 
470                   evaluate=1, 
471                   verbose=verbose)
472       
473        T2 = cache(f, (a,b,c,N), {'x':x, 'y':y}, 
474                   test=1,
475                   verbose=verbose)
476                   
477                   
478        # Check for presence of cached result
479        msg = 'Different objects with same attributes were not recognised'
480        assert T2 is not None, msg                   
481                   
482        assert T1 == T2, "Option 'test' when cache file present failed"     
483
484
485    def test_clear(self):       
486        """Test that 'clear' works
487        """
488
489        N = 5000  #Make N fairly small here
490
491        a = [1,2]
492        b = ('Thou shalt count the number three',4)
493        c = {'Five is right out': 6, (7,8): 9}
494        x = 3
495        y = 'holy hand granate'
496       
497
498        T1 = cache(f,(a,b,c,N), {'x':x, 'y':y}, evaluate = 1)
499       
500       
501        cache(f, (a,b,c,N), {'x':x, 'y':y}, clear = 1)   
502
503 
504        # Test 'test' function when cache is absent
505       
506       
507        T4 = cache(f, (a,b,c,N), {'x':x, 'y':y}, test=1)
508        #print 'T4', T4
509        assert T4 is None, "Option 'test' when cache absent failed"
510
511
512    def test_dependencies(self):
513       
514        # Make a dependency file
515        CD = checkdir(cachedir)       
516
517        DepFN = CD + 'testfile.tmp'
518        DepFN_wildcard = CD + 'test*.tmp'
519        Depfile = open(DepFN,'w')
520        Depfile.write('We are the knights who say NI!')
521        Depfile.close()
522
523       
524        # Test
525        #
526
527        N = 5000  #Make N fairly small here
528
529        a = [1,2]
530        b = ('Thou shalt count the number three',4)
531        c = {'Five is right out': 6, (7,8): 9}
532        x = 3
533        y = 'holy hand granate'
534       
535        T1 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN) 
536        T2 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN)                     
537                       
538        assert T1 == T2, 'Dependencies do not work'
539
540
541        # Test basic wildcard dependency
542        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN_wildcard)                     
543   
544        assert T1 == T3, 'Dependencies with wildcards do not work'
545
546
547        # Test that changed timestamp in dependencies triggers recomputation
548 
549        # Modify dependency file
550        Depfile = open(DepFN,'a')
551        Depfile.write('You must cut down the mightiest tree in the forest with a Herring')
552        Depfile.close()
553 
554        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN, test = 1)
555       
556        assert T3 is None, 'Changed dependencies not recognised'
557 
558        # Test recomputation when dependencies have changed
559        #
560        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN)                       
561        assert T1 == T3, 'Recomputed value with changed dependencies failed'
562
563    #def test_performance(self):       
564    #    """Performance test (with statistics)
565    #    Don't really rely on this as it will depend on specific computer.
566    #    """
567    #
568    #    import time
569    #    set_option('savestat', 1)
570    #
571    #    N = 300000   #Should be large on fast computers...
572    #    a = [1,2]
573    #    b = ('Thou shalt count the number three',4)
574    #    c = {'Five is right out': 6, (7,8): 9}
575    #    x = 3
576    #    y = 'holy hand granate'
577    #     
578    #   
579    #    tt = time.time()
580    #    T1 = cache(f,(a,b,c,N), {'x':x, 'y':y})
581    #    t1 = time.time() - tt
582    #
583    #    tt = time.time()
584    #    T2 = cache(f,(a,b,c,N), {'x':x, 'y':y})
585    #    t2 = time.time() - tt
586     
587    #    assert T1 == T2
588    #     assert t1 > t2, 'WARNING: Performance a bit low - this could be specific to current platform. Try to increase N in this test'
589    #    #test_OK('Performance test: relative time saved = %s pct' \
590    #    #        %str(round((t1-t2)*100/t1,2)))
591
592
593    def test_statsfile(self):                   
594        """Test presence of statistics file
595        """
596        import os, string
597        statsfile  = '.cache_stat'  # Basefilename for cached statistics.
598       
599        CD = checkdir(cachedir)               
600        DIRLIST = os.listdir(CD)
601        SF = []
602        for FN in DIRLIST:
603            if string.find(FN,statsfile) >= 0:
604                try:
605                    fid = open(CD+FN,'r')
606                    fid.close()
607                except:
608                    raise 'Statistics files cannot be opened'         
609 
610         
611
612    # def test_network_cachedir(self):
613
614#         #set_option('cachedir', 'H:\\.python_cache\\')
615#         set_option('cachedir', 'V:\\2\\cit\\.python_cache\\')
616#         set_option('verbose', 1)
617
618       
619#         # Make some test input arguments
620#         #
621#         N = 5000  #Make N fairly small here
622
623#         a = [1,2]
624#         b = ('Thou shalt count the number three',4)
625#         c = {'Five is right out': 6, (7,8): 9}
626#         x = 3
627#         y = 'holy hand granate'
628
629#         # Test caching
630#         #
631
632#         comprange = 2
633
634#         for comp in range(comprange):
635 
636#             # Evaluate and store
637#             #
638#             T1 = cache(f, (a,b,c,N), {'x':x, 'y':y}, evaluate=1, \
639#                        compression=comp)
640
641#             # Retrieve
642#             #                           
643#             T2 = cache(f, (a,b,c,N), {'x':x, 'y':y}, compression=comp)
644
645#             # Reference result
646#             #   
647#             T3 = f(a,b,c,N,x=x,y=y)  # Compute without caching
648
649
650#             assert T1 == T2, 'Cached result does not match computed result'
651#             assert T2 == T3, 'Cached result does not match computed result'
652             
653
654
655    def Will_fail_test_objects(self):
656      """
657      This test shows how instances can't be effectively cached.
658      myhash uses hash which uses id which uses the memory address.
659     
660      This will be a NIL problem once caching can handle instances with different id's and
661      identical content.
662     
663      The test is disabled.
664      """
665     
666
667     
668      verbose = True
669      #verbose = False
670
671      for i in range(2):
672        if verbose: print "clear cache"
673        a = cache(Dummy, 'clear')
674       
675        if verbose: print "cache for first time"
676        a = cache(Dummy, args=(9,10), verbose=verbose)
677        hash_value = myhash(a)
678       
679        #print "hash_value",hash_value
680        if verbose: print "cache for second time"
681        a = cache(Dummy, args=(9,10), verbose=verbose)
682       
683        #print "myhash(a)",myhash(a)
684        assert hash_value == myhash(a)
685
686
687    # This test works in the caching dir and in anuga_core, but not in the
688    # anuga_core/source/anuga dir
689    # This has to do with pickle (see e.g. http://telin.ugent.be/~slippens/drupal/pickleproblem)
690    # The error message is
691    # PicklingError: Can't pickle test_caching.Dummy: it's not the same object as test_caching.Dummy
692    #
693    # This problem was fixed by moving the class into the separate module
694   
695    def test_objects_are_created(self):
696      """
697      This test shows how instances can be created from cache
698      as long as input arguments are unchanged.
699
700      Such instances will have different id's and cannot be currently be used as input
701      arguments in subsequent caches. However, this is still useful.
702
703      Do it for all combinations of compression
704
705      """
706
707      verbose = False
708
709      for compression_store in [False, True]:
710        for compression_retrieve in [False, True]:       
711       
712          if verbose: print 'clear cache'
713          a = cache(Dummy, 'clear')
714       
715          if verbose: print 'cache for first time'
716          a = cache(Dummy, args=(9,10),
717                    compression=compression_store,
718                    verbose=verbose)
719         
720          if verbose: print 'Check that cache is there'
721          assert cache(Dummy, args=(9,10), test=1,
722                       compression=compression_retrieve,
723                       verbose=verbose)
724
725
726
727    # NOTE (Ole): This test has been commented out because,
728    #             although the test will pass (not anymore!)
729    #             inside the caching dir and also at the anuga_core level,
730    #             it won't pass at the anuga_core/source/anuga level.
731    # It may have to do with the comments above.
732    #
733    # But this is a very nice test to run occasionally within the caching
734    # area
735    def Xtest_objects_are_created_memory(self):
736      """
737     
738      This test shows how instances can be created from cache
739      as long as input arguments are unchanged - even if the class
740      lives in different memory locations.
741
742      This is using cache created in the main program below
743      """
744
745      verbose = True #False
746
747      # Redefine class Dummy_memorytest
748      class Dummy_memorytest:
749        def __init__(self, value, another):
750          self.value = value     
751
752      # Make sure that class has been redefined to another address
753      print
754      print 'Initial_addr  ', initial_addr
755      print 'Redefined addr', `Dummy_memorytest`
756      msg = 'Redefined class ended up at same memory location as '
757      msg += 'original class making this test irrelevant. Try to run '
758      msg += 'it again and see if this error goes away.'
759      msg += 'If it persists contact Ole.Nielsen@ga.gov.au'
760      assert initial_addr != `Dummy_memorytest`, msg   
761
762     
763      retrieve_cache(Dummy_memorytest, verbose=verbose)     
764         
765# Cache created for use with 'test_objects_are_created_memory'
766#initial_addr = `Dummy_memorytest`
767#clear_and_create_cache(Dummy_memorytest, verbose=False)
768 
769
770
771#-------------------------------------------------------------
772if __name__ == "__main__":
773    suite = unittest.makeSuite(Test_Caching, 'test')
774    runner = unittest.TextTestRunner()
775    runner.run(suite)
Note: See TracBrowser for help on using the repository browser.