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

Last change on this file since 5855 was 5855, checked in by ole, 16 years ago

Much more work on caching and fitting.
Caching of set_quantity as per Okushiri example now works again.

Moreover, caching can now handle instances and ever circular references.

This should address ticket:244 and ticket:302

File size: 22.5 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 XXtest_caching_of_simple_circular_structures(self):
385   
386        # FIXME (Ole): This one recurses infinitly on
387        # arg strings.
388       
389        """test_caching_of_circular_structures
390       
391        Test that Caching doesn't recurse infinitely in case of
392        circular or self-referencing structures
393        """
394       
395        verbose = True
396       
397        # Create input argument
398        A = {'x': 10, 'B': None}
399        B = [A, 15]
400        A['B'] = B # Make it circular
401       
402        print A
403
404        # Test caching
405        comprange = 2
406        for comp in range(comprange):
407 
408            # Evaluate and store
409            T1 = cache(f_generic, A, evaluate=1,
410                       compression=comp, verbose=verbose)
411                       
412            import sys; sys.exit() 
413                       
414
415            # Retrieve
416            T2 = cache(f_generic, A, 
417                       compression=comp, test=1, verbose=verbose) 
418                       
419            # Check for presence of cached result
420            msg = 'Cached object was not found'           
421            assert T2 is not None, msg
422
423            # Reference result
424            T3 = f_generic(A) # Compute without caching
425
426
427            assert T1 == T2, 'Cached result does not match computed result'
428            assert T2 == T3, 'Cached result does not match computed result'
429           
430                                   
431           
432           
433    def test_cachefiles(self):
434        """Test existence of cachefiles
435        """       
436        N = 5000  #Make N fairly small here
437
438        a = [1,2]
439        b = ('Thou shalt count the number three',4)
440        c = {'Five is right out': 6, (7,8): 9}
441        x = 3
442        y = 'holy hand granate'
443
444       
445        FN = cache(f,(a,b,c,N), {'x':x, 'y':y}, verbose=0, \
446                  return_filename = 1)
447
448
449        assert FN[:2] == 'f['
450
451        CD = checkdir(cachedir)
452        compression = 1
453
454        (datafile,compressed0) = myopen(CD+FN+'_'+file_types[0],"rb",compression)
455        (argsfile,compressed1) = myopen(CD+FN+'_'+file_types[1],"rb",compression)
456        (admfile,compressed2) =  myopen(CD+FN+'_'+file_types[2],"rb",compression)
457
458        datafile.close()
459        argsfile.close()
460        admfile.close()
461
462    def test_test(self):       
463        """Test 'test' function when cache is present
464        """
465       
466        verbose = False
467       
468        N = 5 
469
470        a = [1,2]
471        b = ('Thou shalt count the number three',4)
472        c = {'Five is right out': 6, (7,8): 9}
473        x = 3
474        y = 'holy hand granate'
475       
476
477        T1 = cache(f, (a,b,c,N), {'x':x, 'y':y}, 
478                   evaluate=1, 
479                   verbose=verbose)
480       
481        T2 = cache(f, (a,b,c,N), {'x':x, 'y':y}, 
482                   test=1,
483                   verbose=verbose)
484                   
485                   
486        # Check for presence of cached result
487        msg = 'Different objects with same attributes were not recognised'
488        assert T2 is not None, msg                   
489                   
490        assert T1 == T2, "Option 'test' when cache file present failed"     
491
492
493    def test_clear(self):       
494        """Test that 'clear' works
495        """
496
497        N = 5000  #Make N fairly small here
498
499        a = [1,2]
500        b = ('Thou shalt count the number three',4)
501        c = {'Five is right out': 6, (7,8): 9}
502        x = 3
503        y = 'holy hand granate'
504       
505
506        T1 = cache(f,(a,b,c,N), {'x':x, 'y':y}, evaluate = 1)
507       
508       
509        cache(f, (a,b,c,N), {'x':x, 'y':y}, clear = 1)   
510
511 
512        # Test 'test' function when cache is absent
513       
514       
515        T4 = cache(f, (a,b,c,N), {'x':x, 'y':y}, test=1)
516        #print 'T4', T4
517        assert T4 is None, "Option 'test' when cache absent failed"
518
519
520    def test_dependencies(self):
521       
522        # Make a dependency file
523        CD = checkdir(cachedir)       
524
525        DepFN = CD + 'testfile.tmp'
526        DepFN_wildcard = CD + 'test*.tmp'
527        Depfile = open(DepFN,'w')
528        Depfile.write('We are the knights who say NI!')
529        Depfile.close()
530
531       
532        # Test
533        #
534
535        N = 5000  #Make N fairly small here
536
537        a = [1,2]
538        b = ('Thou shalt count the number three',4)
539        c = {'Five is right out': 6, (7,8): 9}
540        x = 3
541        y = 'holy hand granate'
542       
543        T1 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN) 
544        T2 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN)                     
545                       
546        assert T1 == T2, 'Dependencies do not work'
547
548
549        # Test basic wildcard dependency
550        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN_wildcard)                     
551   
552        assert T1 == T3, 'Dependencies with wildcards do not work'
553
554
555        # Test that changed timestamp in dependencies triggers recomputation
556 
557        # Modify dependency file
558        Depfile = open(DepFN,'a')
559        Depfile.write('You must cut down the mightiest tree in the forest with a Herring')
560        Depfile.close()
561 
562        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN, test = 1)
563       
564        assert T3 is None, 'Changed dependencies not recognised'
565 
566        # Test recomputation when dependencies have changed
567        #
568        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN)                       
569        assert T1 == T3, 'Recomputed value with changed dependencies failed'
570
571    #def test_performance(self):       
572    #    """Performance test (with statistics)
573    #    Don't really rely on this as it will depend on specific computer.
574    #    """
575    #
576    #    import time
577    #    set_option('savestat', 1)
578    #
579    #    N = 300000   #Should be large on fast computers...
580    #    a = [1,2]
581    #    b = ('Thou shalt count the number three',4)
582    #    c = {'Five is right out': 6, (7,8): 9}
583    #    x = 3
584    #    y = 'holy hand granate'
585    #     
586    #   
587    #    tt = time.time()
588    #    T1 = cache(f,(a,b,c,N), {'x':x, 'y':y})
589    #    t1 = time.time() - tt
590    #
591    #    tt = time.time()
592    #    T2 = cache(f,(a,b,c,N), {'x':x, 'y':y})
593    #    t2 = time.time() - tt
594     
595    #    assert T1 == T2
596    #     assert t1 > t2, 'WARNING: Performance a bit low - this could be specific to current platform. Try to increase N in this test'
597    #    #test_OK('Performance test: relative time saved = %s pct' \
598    #    #        %str(round((t1-t2)*100/t1,2)))
599
600
601    def test_statsfile(self):                   
602        """Test presence of statistics file
603        """
604        import os, string
605        statsfile  = '.cache_stat'  # Basefilename for cached statistics.
606       
607        CD = checkdir(cachedir)               
608        DIRLIST = os.listdir(CD)
609        SF = []
610        for FN in DIRLIST:
611            if string.find(FN,statsfile) >= 0:
612                try:
613                    fid = open(CD+FN,'r')
614                    fid.close()
615                except:
616                    raise 'Statistics files cannot be opened'         
617 
618         
619
620    # def test_network_cachedir(self):
621
622#         #set_option('cachedir', 'H:\\.python_cache\\')
623#         set_option('cachedir', 'V:\\2\\cit\\.python_cache\\')
624#         set_option('verbose', 1)
625
626       
627#         # Make some test input arguments
628#         #
629#         N = 5000  #Make N fairly small here
630
631#         a = [1,2]
632#         b = ('Thou shalt count the number three',4)
633#         c = {'Five is right out': 6, (7,8): 9}
634#         x = 3
635#         y = 'holy hand granate'
636
637#         # Test caching
638#         #
639
640#         comprange = 2
641
642#         for comp in range(comprange):
643 
644#             # Evaluate and store
645#             #
646#             T1 = cache(f, (a,b,c,N), {'x':x, 'y':y}, evaluate=1, \
647#                        compression=comp)
648
649#             # Retrieve
650#             #                           
651#             T2 = cache(f, (a,b,c,N), {'x':x, 'y':y}, compression=comp)
652
653#             # Reference result
654#             #   
655#             T3 = f(a,b,c,N,x=x,y=y)  # Compute without caching
656
657
658#             assert T1 == T2, 'Cached result does not match computed result'
659#             assert T2 == T3, 'Cached result does not match computed result'
660             
661
662
663    def Will_fail_test_objects(self):
664      """
665      This test shows how instances can't be effectively cached.
666      myhash uses hash which uses id which uses the memory address.
667     
668      This will be a NIL problem once caching can handle instances with different id's and
669      identical content.
670     
671      The test is disabled.
672      """
673     
674
675     
676      verbose = True
677      #verbose = False
678
679      for i in range(2):
680        if verbose: print "clear cache"
681        a = cache(Dummy, 'clear')
682       
683        if verbose: print "cache for first time"
684        a = cache(Dummy, args=(9,10), verbose=verbose)
685        hash_value = myhash(a)
686       
687        #print "hash_value",hash_value
688        if verbose: print "cache for second time"
689        a = cache(Dummy, args=(9,10), verbose=verbose)
690       
691        #print "myhash(a)",myhash(a)
692        assert hash_value == myhash(a)
693
694
695    # This test works in the caching dir and in anuga_core, but not in the
696    # anuga_core/source/anuga dir
697    # This has to do with pickle (see e.g. http://telin.ugent.be/~slippens/drupal/pickleproblem)
698    # The error message is
699    # PicklingError: Can't pickle test_caching.Dummy: it's not the same object as test_caching.Dummy
700    #
701    # This problem was fixed by moving the class into the separate module
702   
703    def test_objects_are_created(self):
704      """
705      This test shows how instances can be created from cache
706      as long as input arguments are unchanged.
707
708      Such instances will have different id's and cannot be currently be used as input
709      arguments in subsequent caches. However, this is still useful.
710
711      Do it for all combinations of compression
712
713      """
714
715      verbose = False
716
717      for compression_store in [False, True]:
718        for compression_retrieve in [False, True]:       
719       
720          if verbose: print 'clear cache'
721          a = cache(Dummy, 'clear')
722       
723          if verbose: print 'cache for first time'
724          a = cache(Dummy, args=(9,10),
725                    compression=compression_store,
726                    verbose=verbose)
727         
728          if verbose: print 'Check that cache is there'
729          assert cache(Dummy, args=(9,10), test=1,
730                       compression=compression_retrieve,
731                       verbose=verbose)
732
733
734
735    # NOTE (Ole): This test has been commented out because,
736    #             although the test will pass (not anymore!)
737    #             inside the caching dir and also at the anuga_core level,
738    #             it won't pass at the anuga_core/source/anuga level.
739    # It may have to do with the comments above.
740    #
741    # But this is a very nice test to run occasionally within the caching
742    # area
743    def Xtest_objects_are_created_memory(self):
744      """
745     
746      This test shows how instances can be created from cache
747      as long as input arguments are unchanged - even if the class
748      lives in different memory locations.
749
750      This is using cache created in the main program below
751      """
752
753      verbose = True #False
754
755      # Redefine class Dummy_memorytest
756      class Dummy_memorytest:
757        def __init__(self, value, another):
758          self.value = value     
759
760      # Make sure that class has been redefined to another address
761      print
762      print 'Initial_addr  ', initial_addr
763      print 'Redefined addr', `Dummy_memorytest`
764      msg = 'Redefined class ended up at same memory location as '
765      msg += 'original class making this test irrelevant. Try to run '
766      msg += 'it again and see if this error goes away.'
767      msg += 'If it persists contact Ole.Nielsen@ga.gov.au'
768      assert initial_addr != `Dummy_memorytest`, msg   
769
770     
771      retrieve_cache(Dummy_memorytest, verbose=verbose)     
772         
773# Cache created for use with 'test_objects_are_created_memory'
774#initial_addr = `Dummy_memorytest`
775#clear_and_create_cache(Dummy_memorytest, verbose=False)
776 
777
778
779#-------------------------------------------------------------
780if __name__ == "__main__":
781    suite = unittest.makeSuite(Test_Caching, 'test')
782    runner = unittest.TextTestRunner()
783    runner.run(suite)
Note: See TracBrowser for help on using the repository browser.