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

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

Thought of testing hash-collision and found issue with comparison
This is now fixed and tests verified.

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