Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

root/branches/crazycache/dejavu/test/zoo_fixture.py

Revision 577 (checked in by fumanchu, 4 years ago)

Crazycache: Test and fix for some indexing errors.

  • Property svn:eol-style set to native
Line 
1 """Test fixture for Storage Managers."""
2
3 import datetime
4 import math
5
6 try:
7     import pythoncom
8 except ImportError:
9     pythoncom = None
10
11 import random
12
13 try:
14     set
15 except NameError:
16     from sets import Set as set
17
18 import sys
19 import threading
20 import time
21 import traceback
22 import unittest
23 import warnings
24
25 try:
26     # Builtin in Python 2.5?
27     decimal
28 except NameError:
29     try:
30         # Module in Python 2.3, 2.4
31         import decimal
32     except ImportError:
33         decimal = None
34
35 try:
36     import fixedpoint
37 except ImportError:
38     fixedpoint = None
39
40 __all__ = ['Animal', 'Exhibit', 'Lecture', 'Vet', 'Visit', 'Zoo',
41            # Don't export the ZooTests class--it will break e.g. test_dejavu.
42            'root', 'run', 'setup', 'teardown']
43
44
45 import dejavu
46 from dejavu import errors, storage
47 from dejavu import Unit, UnitProperty, ToOne, ToMany, UnitSequencerInteger, UnitAssociation
48 from dejavu.test import tools
49 from dejavu import engines
50 from geniusql import logic, logicfuncs
51 logicfuncs.init()
52
53
54 class EscapeProperty(UnitProperty):
55     def __set__(self, unit, value):
56         UnitProperty.__set__(self, unit, value)
57         # Zoo is a ToOne association, so it will return a unit or None.
58         z = unit.Zoo()
59         if z:
60             z.LastEscape = unit.LastEscape
61
62
63 class Animal(Unit):
64     Class = UnitProperty(index=True)
65     Species = UnitProperty(hints={'bytes': 100})
66     ZooID = UnitProperty(int, index=True)
67     Legs = UnitProperty(int, default=4)
68     PreviousZoos = UnitProperty(list, hints={'bytes': 8000})
69     LastEscape = EscapeProperty(datetime.datetime)
70     Lifespan = UnitProperty(float, hints={'precision': 4})
71     Age = UnitProperty(float, hints={'precision': 4}, default=1)
72     MotherID = UnitProperty(int)
73     PreferredFoodID = UnitProperty(int)
74     AlternateFoodID = UnitProperty(int)
75
76 Animal.many_to_one('ID', Animal, 'MotherID')
77
78
79 class Zoo(Unit):
80     Name = UnitProperty()
81     Founded = UnitProperty(datetime.date)
82     Opens = UnitProperty(datetime.time)
83     LastEscape = UnitProperty(datetime.datetime)
84    
85     if fixedpoint:
86         # Explicitly set precision and scale so test_storemsaccess
87         # can test CURRENCY type
88         Admission = UnitProperty(fixedpoint.FixedPoint,
89                                  hints={'precision': 4, 'scale': 2})
90     else:
91         Admission = UnitProperty(float)
92
93 Zoo.one_to_many('ID', Animal, 'ZooID')
94
95 class AlternateFoodAssociation(UnitAssociation):
96     to_many = False
97     register = False
98    
99     def related(self, unit, expr=None):
100         food = unit.sandbox.unit(Food, ID=unit.AlternateFoodID)
101         return food
102
103 class Food(Unit):
104     """A food item."""
105     Name = UnitProperty()
106     NutritionValue = UnitProperty(int)
107
108 Food.one_to_many('ID', Animal, 'PreferredFoodID')
109
110 descriptor = AlternateFoodAssociation('AlternateFoodID', Food, 'ID')
111 descriptor.nearClass = Animal
112 Animal._associations['Alternate Food'] = descriptor
113 Animal.AlternateFood = descriptor
114 del descriptor
115
116 class Vet(Unit):
117     """A Veterinarian."""
118     Name = UnitProperty()
119     ZooID = UnitProperty(int, index=True)
120     FavoriteColor = UnitProperty()
121     sequencer = UnitSequencerInteger(initial=200)
122
123 Vet.many_to_one('ZooID', Zoo, 'ID')
124
125
126 class Visit(Unit):
127     """Work done by a Veterinarian on an Animal."""
128     VetID = UnitProperty(int, index=True)
129     ZooID = UnitProperty(int, index=True)
130     AnimalID = UnitProperty(int, index=True)
131     Date = UnitProperty(datetime.date)
132
133 Vet.one_to_many('ID', Visit, 'VetID')
134 Animal.one_to_many('ID', Visit, 'AnimalID')
135
136
137 class Lecture(Visit):
138     """A Visit by a Vet to train staff (rather than visit an Animal)."""
139     AnimalID = None
140     Topic = UnitProperty()
141
142
143 class Exhibit(Unit):
144     # Make this a string to help test vs unicode.
145     Name = UnitProperty(str)
146     ZooID = UnitProperty(int)
147     Animals = UnitProperty(list)
148     PettingAllowed = UnitProperty(bool)
149     Creators = UnitProperty(tuple)
150    
151     if decimal:
152         Acreage = UnitProperty(decimal.Decimal)
153     else:
154         Acreage = UnitProperty(float)
155    
156     # Remove the ID property (inherited from Unit) from the Exhibit class.
157     ID = None
158     sequencer = dejavu.UnitSequencer()
159     identifiers = ("ZooID", Name)
160
161 Zoo.one_to_many('ID', Exhibit, 'ZooID')
162
163
164 class GateAccessLog(Unit):
165     Timestamp = UnitProperty(datetime.datetime)
166     CardID = UnitProperty(int)
167     # This unit has no primary key
168     ID = None
169     identifiers = ()
170
171 May_16_2007 = datetime.datetime(2007, 5, 16)
172 logtimes = [(cardid, May_16_2007 + datetime.timedelta(0, random.randint(0, 86399)))
173             for cardid in xrange(5) for x in xrange(5)]
174 logtimes.sort()
175 del cardid, x
176
177
178 class Ticket(Unit):
179     """A sold admission ticket to the Zoo."""
180     ZooID = UnitProperty(int, index=True)
181     Price = UnitProperty(float)
182     Date = UnitProperty(datetime.date)
183
184
185 class NothingToDoWithZoos(Unit):
186     ALong = UnitProperty(long, hints={'precision': 1})
187     AFloat = UnitProperty(float, hints={'precision': 1})
188     if decimal:
189         ADecimal = UnitProperty(decimal.Decimal,
190                                 hints={'precision': 1, 'scale': 1})
191     if fixedpoint:
192         AFixed = UnitProperty(fixedpoint.FixedPoint,
193                               hints={'precision': 1, 'scale': 1})
194
195
196 Jan_1_2001 = datetime.date(2001, 1, 1)
197 every13days = [Jan_1_2001 + datetime.timedelta(x * 13) for x in range(20)]
198 every17days = [Jan_1_2001 + datetime.timedelta(x * 17) for x in range(20)]
199 del x
200
201 class ZooTests(unittest.TestCase):
202    
203     def test_1_model(self):
204         self.assertEqual(Zoo.Animal.__class__, dejavu.ToMany)
205         self.assertEqual(Zoo.Animal.nearClass, Zoo)
206         self.assertEqual(Zoo.Animal.nearKey, 'ID')
207         self.assertEqual(Zoo.Animal.farClass, Animal)
208         self.assertEqual(Zoo.Animal.farKey, 'ZooID')
209        
210         self.assertEqual(Animal.Zoo.__class__, dejavu.ToOne)
211         self.assertEqual(Animal.Zoo.nearClass, Animal)
212         self.assertEqual(Animal.Zoo.nearKey, 'ZooID')
213         self.assertEqual(Animal.Zoo.farClass, Zoo)
214         self.assertEqual(Animal.Zoo.farKey, 'ID')
215    
216     def test_2_populate(self):
217         box = root.new_sandbox()
218         try:
219            
220             # Notice this also tests that: a Unit which is only
221             # dirtied via __init__ is still saved.
222             WAP = Zoo(Name = 'Wild Animal Park',
223                       Founded = datetime.date(2000, 1, 1),
224                       # 59 can give rounding errors with divmod, which
225                       # ADO adapters needs to correct.
226                       Opens = datetime.time(8, 15, 59),
227                       LastEscape = datetime.datetime(2004, 7, 29, 5, 6, 7),
228                       Admission = "4.95",
229                       )
230             box.memorize(WAP)
231             # The object should get an ID automatically.
232             self.assertNotEqual(WAP.ID, None)
233            
234             SDZ = Zoo(Name = 'San Diego Zoo',
235                       # This early date should play havoc with a number
236                       # of implementations.
237                       Founded = datetime.date(1835, 9, 13),
238                       Opens = datetime.time(9, 0, 0),
239                       Admission = "0",
240                       )
241             box.memorize(SDZ)
242             # The object should get an ID automatically.
243             self.assertNotEqual(SDZ.ID, None)
244            
245             Biodome = Zoo(Name = u'Montr\xe9al Biod\xf4me',
246                           Founded = datetime.date(1992, 6, 19),
247                           Opens = datetime.time(9, 0, 0),
248                           Admission = "11.75",
249                           )
250             box.memorize(Biodome)
251            
252             seaworld = Zoo(Name = 'Sea_World', Admission = "60")
253             box.memorize(seaworld)
254     ##       
255     ##        mostly_empty = Zoo(Name = 'The Mostly Empty Zoo' + (" " * 255))
256     ##        box.memorize(mostly_empty)
257            
258             # Animals
259             leopard = Animal(Class='Mammalia', Species='Leopard', Lifespan=73.5)
260             self.assertEqual(leopard.PreviousZoos, None)
261             box.memorize(leopard)
262             self.assertEqual(leopard.ID, 1)
263            
264             leopard.add(WAP)
265             leopard.LastEscape = datetime.datetime(2004, 12, 21, 8, 15, 0, 999907)
266            
267             lion = Animal(Class='Mammalia', Species='Lion', ZooID=WAP.ID,
268                           LastEscape = datetime.datetime(2007, 9, 24,
269                                                          16, 18, 42))
270             box.memorize(lion)
271            
272             box.memorize(Animal(Class='Gastropoda', Species='Slug',
273                                 Legs=1, Lifespan=.75,
274                                 # Test our 8000-byte limit
275                                 PreviousZoos=["f" * (8000 - 14)]))
276            
277             tiger = Animal(Class='Mammalia', Species='Tiger',
278                            PreviousZoos=['animal\\universe'])
279             box.memorize(tiger)
280            
281             # Override Legs.default with itself just to make sure it works.
282             box.memorize(Animal(Class='Mammalia', Species='Bear', Legs=4))
283             # Notice that ostrich.PreviousZoos is [], whereas leopard is None.
284             box.memorize(Animal(Class='Aves', Species='Ostrich',
285                                 Legs=2, PreviousZoos=[],
286                                 Lifespan=103.2))
287             box.memorize(Animal(Class='Chilopoda', Species='Centipede', Legs=100))
288            
289             emp = Animal(Class='Aves', Species='Emperor Penguin', Legs=2)
290             box.memorize(emp)
291             adelie = Animal(Class='Aves', Species='Adelie Penguin', Legs=2,
292                             LastEscape = datetime.datetime(2007, 9, 20,
293                                                            19, 10, 14))
294             box.memorize(adelie)
295            
296             seaworld.add(emp, adelie)
297            
298             millipede = Animal(Class='Diplopoda', Species='Millipede', Legs=1000000)
299             millipede.PreviousZoos = [WAP.Name]
300             box.memorize(millipede)
301            
302             SDZ.add(tiger, millipede)
303            
304             # Add a mother and child to test relationships
305             bai_yun = Animal(Class='Mammalia', Species='Ape', Legs=2)
306             box.memorize(bai_yun)   # ID = 11
307             self.assertEqual(bai_yun.ID, 11)
308             hua_mei = Animal(Class='Mammalia', Species='Ape', Legs=2,
309                              MotherID=bai_yun.ID)
310             box.memorize(hua_mei)   # ID = 12
311             self.assertEqual(hua_mei.ID, 12)
312            
313             # Exhibits
314             pe = Exhibit(Name = 'The Penguin Encounter',
315                          ZooID = seaworld.ID,
316                          Animals = [emp.ID, adelie.ID],
317                          PettingAllowed = True,
318                          Acreage = "3.1",
319                          # See ticket #45
320                          Creators = (u'Richard F\xfcrst', u'Sonja Martin'),
321                          )
322             box.memorize(pe)
323            
324             tr = Exhibit(Name = 'Tiger River',
325                          ZooID = SDZ.ID,
326                          Animals = [tiger.ID],
327                          PettingAllowed = False,
328                          Acreage = "4",
329                          )
330             box.memorize(tr)
331            
332             # Vets
333             cs = Vet(Name = 'Charles Schroeder', ZooID = SDZ.ID,
334                      FavoriteColor = 'Red')
335             box.memorize(cs)
336             self.assertEqual(cs.ID, Vet.sequencer.initial)
337            
338             jm = Vet(Name = 'Jim McBain', ZooID = seaworld.ID,
339                      FavoriteColor = 'Red')
340             box.memorize(jm)
341            
342             # Visits
343             for d in every13days:
344                 box.memorize(Visit(VetID=cs.ID, AnimalID=tiger.ID, Date=d))
345             for d in every17days:
346                 box.memorize(Visit(VetID=jm.ID, AnimalID=emp.ID, Date=d))
347            
348             # Foods
349             dead_fish = Food(Name="Dead Fish", Nutrition=5)
350             live_fish = Food(Name="Live Fish", Nutrition=10)
351             bunnies = Food(Name="Live Bunny Wabbit", Nutrition=10)
352             steak = Food(Name="T-Bone", Nutrition=7)
353             for food in [dead_fish, live_fish, bunnies, steak]:
354                 box.memorize(food)
355            
356             # Foods --> add preferred foods
357             lion.add(steak)
358             tiger.add(bunnies)
359             emp.add(live_fish)
360             adelie.add(live_fish)
361            
362             # Foods --> add alternate foods
363             lion.AlternateFoodID = bunnies.ID
364             tiger.AlternateFoodID = steak.ID
365             emp.AlternateFoodID = dead_fish.ID
366             adelie.AlternateFoodID = dead_fish.ID
367            
368             # GateAccessLog (no identity!)
369             for cardid, t in logtimes:
370                 box.memorize(GateAccessLog(Timestamp=t, CardID=cardid))
371         finally:
372             box.flush_all()
373    
374     def test_3_Properties(self):
375         box = root.new_sandbox()
376         try:
377             # Zoos
378             WAP = box.unit(Zoo, Name='Wild Animal Park')
379             self.assertNotEqual(WAP, None)
380             self.assertEqual(WAP.Founded, datetime.date(2000, 1, 1))
381             self.assertEqual(WAP.Opens, datetime.time(8, 15, 59))
382             # This should have been updated when leopard.LastEscape was set.
383     ##        self.assertEqual(WAP.LastEscape,
384     ##                         datetime.datetime(2004, 12, 21, 8, 15, 0, 999907))
385             self.assertEqual(WAP.Admission, Zoo.Admission.coerce(WAP, "4.95"))
386            
387             SDZ = box.unit(Zoo, Founded=datetime.date(1835, 9, 13))
388             self.assertNotEqual(SDZ, None)
389             self.assertEqual(SDZ.Founded, datetime.date(1835, 9, 13))
390             self.assertEqual(SDZ.Opens, datetime.time(9, 0, 0))
391             self.assertEqual(SDZ.LastEscape, None)
392             self.assertEqual(float(SDZ.Admission), 0)
393            
394             # Try a magic Sandbox recaller method
395             Biodome = box.Zoo(Name = u'Montr\xe9al Biod\xf4me')
396             self.assertNotEqual(Biodome, None)
397             self.assertEqual(Biodome.Name, u'Montr\xe9al Biod\xf4me')
398             self.assertEqual(Biodome.Founded, datetime.date(1992, 6, 19))
399             self.assertEqual(Biodome.Opens, datetime.time(9, 0, 0))
400             self.assertEqual(Biodome.LastEscape, None)
401             self.assertEqual(float(Biodome.Admission), 11.75)
402            
403             if fixedpoint:
404                 seaworld = box.unit(Zoo, Admission = fixedpoint.FixedPoint(60))
405             else:
406                 seaworld = box.unit(Zoo, Admission = float(60))
407             self.assertNotEqual(seaworld, None)
408             self.assertEqual(seaworld.Name, u'Sea_World')
409            
410             # Animals
411             leopard = box.unit(Animal, Species='Leopard')
412             self.assertEqual(leopard.Species, 'Leopard')
413             self.assertEqual(leopard.Legs, 4)
414             self.assertEqual(leopard.Lifespan, 73.5)
415             self.assertEqual(leopard.ZooID, WAP.ID)
416             self.assertEqual(leopard.PreviousZoos, None)
417     ##        self.assertEqual(leopard.LastEscape,
418     ##                         datetime.datetime(2004, 12, 21, 8, 15, 0, 999907))
419            
420             ostrich = box.unit(Animal, Species='Ostrich')
421             self.assertEqual(ostrich.Species, 'Ostrich')
422             self.assertEqual(ostrich.Legs, 2)
423             self.assertEqual(ostrich.ZooID, None)
424             self.assertEqual(ostrich.PreviousZoos, [])
425             self.assertEqual(ostrich.LastEscape, None)
426            
427             millipede = box.unit(Animal, Legs=1000000)
428             self.assertEqual(millipede.Species, 'Millipede')
429             self.assertEqual(millipede.Legs, 1000000)
430             self.assertEqual(millipede.ZooID, SDZ.ID)
431             self.assertEqual(millipede.PreviousZoos, [WAP.Name])
432             self.assertEqual(millipede.LastEscape, None)
433            
434             # Test that strings in a list get decoded correctly.
435             # See http://projects.amor.org/dejavu/ticket/50
436             tiger = box.unit(Animal, Species='Tiger')
437             self.assertEqual(tiger.PreviousZoos, ["animal\\universe"])
438            
439             # Test our 8000-byte limit.
440             # len(pickle.dumps(["f" * (8000 - 14)]) == 8000
441             slug = box.unit(Animal, Species='Slug')
442             self.assertEqual(len(slug.PreviousZoos[0]), 8000 - 14)
443            
444             # Exhibits
445             exes = box.recall(Exhibit)
446             self.assertEqual(len(exes), 2)
447             if exes[0].Name == 'The Penguin Encounter':
448                 pe = exes[0]
449                 tr = exes[1]
450             else:
451                 pe = exes[1]
452                 tr = exes[0]
453             self.assertEqual(pe.ZooID, seaworld.ID)
454             self.assertEqual(len(pe.Animals), 2)
455             self.assertEqual(float(pe.Acreage), 3.1)
456             self.assertEqual(pe.PettingAllowed, True)
457             self.assertEqual(pe.Creators, (u'Richard F\xfcrst', u'Sonja Martin'))
458            
459             self.assertEqual(tr.ZooID, SDZ.ID)
460             self.assertEqual(len(tr.Animals), 1)
461             self.assertEqual(float(tr.Acreage), 4)
462             self.assertEqual(tr.PettingAllowed, False)
463            
464         finally:
465             box.flush_all()
466    
467     def test_4_Expressions(self):
468         box = root.new_sandbox()
469         try:
470             def matches(lam, cls=Animal):
471                 # We flush_all to ensure a DB hit each time.
472                 box.flush_all()
473                 return len(box.recall(cls, lam))
474            
475             zoos = box.recall(Zoo)
476             self.assertEqual(zoos[0].dirty(), False)
477             self.assertEqual(len(zoos), 4)
478             self.assertEqual(matches(lambda x: True), 12)
479             self.assertEqual(matches(lambda x: x.Legs == 4), 4)
480             self.assertEqual(matches(lambda x: x.Legs == 2), 5)
481             self.assertEqual(matches(lambda x: x.Legs >= 2 and x.Legs < 20), 9)
482             self.assertEqual(matches(lambda x: x.Legs > 10), 2)
483             self.assertEqual(matches(lambda x: x.Lifespan > 70), 2)
484             self.assertEqual(matches(lambda x: x.Species.startswith('L')), 2)
485             self.assertEqual(matches(lambda x: x.Species.endswith('pede')), 2)
486             self.assertEqual(matches(lambda x: x.LastEscape != None), 3)
487             self.assertEqual(matches(lambda x: x.LastEscape is not None), 3)
488             self.assertEqual(matches(lambda x: None == x.LastEscape), 9)
489            
490             # In operator (containedby)
491             self.assertEqual(matches(lambda x: 'pede' in x.Species), 2)
492             self.assertEqual(matches(lambda x: x.Species in ('Lion', 'Tiger', 'Bear')), 3)
493            
494             # Try In with cell references
495             class thing(object): pass
496             pet, pet2 = thing(), thing()
497             pet.Name, pet2.Name = 'Slug', 'Ostrich'
498             self.assertEqual(matches(lambda x: x.Species in (pet.Name, pet2.Name)), 2)
499            
500             # logic and other functions
501             self.assertEqual(matches(lambda x: ieq(x.Species, 'slug')), 1)
502             self.assertEqual(matches(lambda x: icontains(x.Species, 'PEDE')), 2)
503             self.assertEqual(matches(lambda x: icontains(('Lion', 'Banana'), x.Species)), 1)
504             f = lambda x: icontainedby(x.Species, ('Lion', 'Bear', 'Leopard'))
505             self.assertEqual(matches(f), 3)
506             name = 'Lion'
507             self.assertEqual(matches(lambda x: len(x.Species) == len(name)), 3)
508            
509             # This broke sometime in 2004. Rev 32 seems to have fixed it.
510             self.assertEqual(matches(lambda x: 'i' in x.Species), 7)
511            
512             # Test now(), today(), year(), month(), day()
513             self.assertEqual(matches(lambda x: x.Founded != None
514                                      and x.Founded < today(), Zoo), 3)
515             self.assertEqual(matches(lambda x: x.LastEscape == now()), 0)
516             self.assertEqual(matches(lambda x: year(x.LastEscape) == 2004), 1)
517             self.assertEqual(matches(lambda x: month(x.LastEscape) == 12), 1)
518             self.assertEqual(matches(lambda x: day(x.LastEscape) == 21), 1)
519
520            
521             # Test AND, OR with cannot_represent.
522             # Notice that we reference a method ('count') which no
523             # known SM handles, so it will default back to Expr.eval().
524             self.assertEqual(matches(lambda x: 'p' in x.Species
525                                      and x.Species.count('e') > 1), 3)
526            
527             # This broke in MSAccess (storeado) in April 2005, due to a bug in
528             # db.SQLDecompiler.visit_CALL_FUNCTION (append TOS, not replace!).
529             box.flush_all()
530             e = logic.Expression(lambda x, **kw: x.LastEscape != None
531                                  and x.LastEscape >= datetime.datetime(kw['Year'], 12, 1)
532                                  and x.LastEscape < datetime.datetime(kw['Year'], 12, 31)
533                                  )
534             e.bind_args(Year=2004)
535             units = box.recall(Animal, e)
536             self.assertEqual(len(units), 1)
537            
538             # Test wildcards in LIKE. This fails with SQLite <= 3.0.8,
539             # so make sure it's always at the end of this method so
540             # it doesn't preclude running the other tests.
541             box.flush_all()
542             units = box.recall(Zoo, lambda x: "_" in x.Name)
543             self.assertEqual(len(units), 1)
544         finally:
545             box.flush_all()
546    
547     def test_4a_Indexing(self):
548         # Different subsets of an index leaf node must return correct data.
549         for legs, species in [(2, set(['Ape', 'Ape'])),
550                               (4, set(['Leopard', 'Lion', 'Tiger', 'Bear']))]:
551             animals = root.recall(Animal, dict(Class='Mammalia', Legs=legs))
552             self.assertEqual(set([a.Species for a in animals]), species)
553    
554     def test_5_Aggregates(self):
555         box = root.new_sandbox()
556         try:
557             # views
558             legs = [x[0] for x in box.view((Animal, ['Legs']))]
559             legs.sort()
560             self.assertEqual(legs, [1, 2, 2, 2, 2, 2, 4, 4, 4, 4, 100, 1000000])
561            
562             expected = {'Leopard': 73.5,
563                         'Slug': .75,
564                         'Tiger': None,
565                         'Lion': None,
566                         'Bear': None,
567                         'Ostrich': 103.2,
568                         'Centipede': None,
569                         'Emperor Penguin': None,
570                         'Adelie Penguin': None,
571                         'Millipede': None,
572                         'Ape': None,
573                         }
574             for species, lifespan in box.view((Animal, ['Species', 'Lifespan'])):
575                 if expected[species] is None:
576                     self.assertEqual(lifespan, None)
577                 else:
578                     self.assertAlmostEqual(expected[species], lifespan, places=5)
579            
580             expected = [u'Montr\xe9al Biod\xf4me', 'Wild Animal Park']
581             e = (lambda x: x.Founded != None
582                  and x.Founded <= today()
583                  and x.Founded >= datetime.date(1990, 1, 1))
584             values =  [val[0] for val in box.view((Zoo, ['Name'], e))]
585             for name in expected:
586                 self.assert_(name in values)
587            
588             # distinct
589             legs = box.view((Animal, ['Legs']), distinct=True)
590             legs.sort()
591             self.assertEqual(legs, [(1, ), (2, ), (4, ), (100, ), (1000000, )])
592            
593             # This may raise a warning on some DB's.
594             f = (lambda x: x.Species == 'Lion')
595             escapees = box.view((Animal, ['Legs'], f), distinct=True)
596             self.assertEqual(escapees, [(4, ), ])
597            
598             # count should use an aggregate function when using DB's
599             fourlegged = root.count(Animal, lambda x: x.Legs == 4)
600             self.assertEqual(fourlegged, 4)
601             topics = root.count(Exhibit)
602             self.assertEqual(topics, 2)
603            
604             # range should return a sorted list
605             legs = box.range(Animal, 'Legs', lambda x: x.Legs <= 100)
606             self.assertEqual(legs, range(1, 101))
607             topics = box.range(Exhibit, 'Name')
608             self.assertEqual(topics, ['The Penguin Encounter', 'Tiger River'])
609             vets = box.range(Vet, 'Name')
610             self.assertEqual(vets, ['Charles Schroeder', 'Jim McBain'])
611            
612             # Test view() with a Unit that has no identifiers (primary keys).
613             access = box.view((GateAccessLog, ['CardID', 'Timestamp']))
614             access.sort()
615             self.assertEqual(len(access), len(logtimes))
616             self.assertEqual(access, logtimes)
617         finally:
618             box.flush_all()
619    
620     def test_5a_order_and_limit(self):
621         all_animals = [u'Adelie Penguin', u'Ape', u'Ape', u'Bear',
622                        u'Centipede', u'Emperor Penguin', u'Leopard',
623                        u'Lion', u'Millipede', u'Ostrich', u'Slug', u'Tiger']
624         len_animals = len(all_animals)
625        
626         # Test various limit points against a sandbox.
627         box = root.new_sandbox()
628         try:
629             animals = box.recall(Animal, order=['Species'])
630             self.assertEqual([a.Species for a in animals], all_animals)
631             for lim in range(len_animals + 1):
632                 animals = box.recall(Animal, order=['Species'], limit=lim)
633                 self.assertEqual([a.Species for a in animals], all_animals[:lim])
634         finally:
635             box.flush_all()
636        
637         # Test various limit points against root.
638         animals = root.recall(Animal, order=['Species'])
639         self.assertEqual([a.Species for a in animals], all_animals)
640         for lim in range(len_animals + 1):
641             animals = root.recall(Animal, order=['Species'], limit=lim)
642             self.assertEqual([a.Species for a in animals], all_animals[:lim])
643        
644         animals = root.recall(Animal, order=['Species'], limit=5, offset=2)
645         self.assertEqual([a.Species for a in animals],
646                          [u'Ape', u'Bear', u'Centipede',
647                           u'Emperor Penguin', u'Leopard'])
648        
649         animals = root.view((Animal, ['Legs', 'Species'], None),
650                             order=['Species'], limit=5, offset=2)
651         self.assertEqual(animals, [(2, u'Ape'), (4, u'Bear'),
652                                    (100, u'Centipede'),
653                                    (2, u'Emperor Penguin'),
654                                    (4, u'Leopard')])
655        
656         # Test reversed()
657         zoos = root.recall(Zoo, lambda z: z.Founded != None,
658                            order=lambda z: [reversed(z.Founded)],
659                            limit=2,
660                            )
661         self.assertEqual([z.Founded for z in zoos],
662                          [datetime.date(2000, 1, 1),
663                           datetime.date(1992, 6, 19),
664                           ])
665        
666         # Test limit, reversed() with a join.
667         data = root.recall(Animal << Zoo, lambda a, z: a.LastEscape != None,
668                            order=lambda a, z: [reversed(a.LastEscape)],
669                            limit=1)
670         # If we didn't limit, we should have received:
671         # [datetime.datetime(2007, 9, 24, 16, 18, 42),
672         #  datetime.datetime(2007, 9, 20, 19, 10, 14),
673         #  datetime.datetime(2004, 12, 21, 8, 15),
674         #  ]
675         self.assertEqual([a.LastEscape for a, z in data],
676                          [datetime.datetime(2007, 9, 24, 16, 18, 42)])
677        
678         # Try ordering using the 'related units' method.
679         box = root.new_sandbox()
680         try:
681             wap = box.unit(Zoo, Name='Wild Animal Park')
682             data = wap.Animal(lambda a: a.LastEscape != None,
683                               order=lambda a: [reversed(a.LastEscape)],
684                               limit=2)
685             self.assertEqual([a.LastEscape for a in data],
686                              [datetime.datetime(2007, 9, 24, 16, 18, 42),
687                               datetime.datetime(2004, 12, 21, 8, 15),
688                               ])
689         finally:
690             box.flush_all()
691        
692         # Test that offset with no order raises an error.
693         try:
694             root.recall(Animal, offset=3)
695         except:
696             pass
697         else:
698             self.fail("offset with no order did not raise an error.")
699         try:
700             root.new_sandbox().recall(Animal, offset=3)
701         except:
702             pass
703         else:
704             self.fail("offset with no order did not raise an error.")
705    
706     def test_6_Editing(self):
707         # Edit
708         box = root.new_sandbox()
709         try:
710             SDZ = box.unit(Zoo, Name='San Diego Zoo')
711             SDZ.Name = 'The San Diego Zoo'
712             SDZ.Founded = datetime.date(1900, 1, 1)
713             SDZ.Opens = datetime.time(7, 30, 0)
714             SDZ.Admission = "35.00"
715            
716             # Test that unit/recall get the sandboxed values (not storage).
717             SDZ = box.unit(Zoo, Name='The San Diego Zoo')
718             self.assertEqual(SDZ.Name, 'The San Diego Zoo')
719             self.assertEqual(SDZ.Founded, datetime.date(1900, 1, 1))
720             self.assertEqual(SDZ.Opens, datetime.time(7, 30, 0))
721            
722             SDZ = box.recall(Zoo, lambda z: z.Name == 'The San Diego Zoo')[0]
723             self.assertEqual(SDZ.Name, 'The San Diego Zoo')
724             self.assertEqual(SDZ.Founded, datetime.date(1900, 1, 1))
725             self.assertEqual(SDZ.Opens, datetime.time(7, 30, 0))
726         finally:
727             box.flush_all()
728        
729         # Test edits
730         box = root.new_sandbox()
731         try:
732             SDZ = box.unit(Zoo, Name='The San Diego Zoo')
733             self.assertEqual(SDZ.Name, 'The San Diego Zoo')
734             self.assertEqual(SDZ.Founded, datetime.date(1900, 1, 1))
735             self.assertEqual(SDZ.Opens, datetime.time(7, 30, 0))
736             if fixedpoint:
737                 self.assertEqual(SDZ.Admission, fixedpoint.FixedPoint(35, 2))
738             else:
739                 self.assertEqual(SDZ.Admission, 35.0)
740         finally:
741             box.flush_all()
742        
743         # Change it back
744         box = root.new_sandbox()
745         try:
746             SDZ = box.unit(Zoo, Name='The San Diego Zoo')
747             SDZ.Name = 'San Diego Zoo'
748             SDZ.Founded = datetime.date(1835, 9, 13)
749             SDZ.Opens = datetime.time(9, 0, 0)
750             SDZ.Admission = "0"
751         finally:
752             box.flush_all()
753        
754         # Test re-edits
755         box = root.new_sandbox()
756         try:
757             SDZ = box.unit(Zoo, Name='San Diego Zoo')
758             self.assertEqual(SDZ.Name, 'San Diego Zoo')
759             self.assertEqual(SDZ.Founded, datetime.date(1835, 9, 13))
760             self.assertEqual(SDZ.Opens, datetime.time(9, 0, 0))
761             if fixedpoint:
762                 self.assertEqual(SDZ.Admission, fixedpoint.FixedPoint(0, 2))
763             else:
764                 self.assertEqual(SDZ.Admission, 0.0)
765         finally:
766             box.flush_all()
767    
768     def test_7_Multirecall(self):
769         box = root.new_sandbox()
770         try:
771             f = (lambda z, a: z.Name == 'San Diego Zoo')
772             zooed_animals = box.recall(Zoo & Animal, f)
773             self.assertEqual(len(zooed_animals), 2)
774            
775             SDZ = box.unit(Zoo, Name='San Diego Zoo')
776             aid = 0
777             for z, a in zooed_animals:
778                 self.assertEqual(id(z), id(SDZ))
779                 self.assertNotEqual(id(a), aid)
780                 aid = id(a)
781            
782             # Assert that multirecalls with no matching related units returns
783             # no matches for the initial class, since all joins are INNER.
784             # We're also going to test that you can combine a one-arg expr
785             # with a two-arg expr.
786             sdexpr = logic.filter(Name='San Diego Zoo')
787             leo = lambda z, a: a.Species == 'Leopard'
788             zooed_animals = box.recall(Zoo & Animal, sdexpr + leo)
789             self.assertEqual(len(zooed_animals), 0)
790            
791             # Now try the same expr with INNER, LEFT, and RIGHT JOINs.
792             zooed_animals = box.recall(Zoo & Animal)
793             self.assertEqual(len(zooed_animals), 6)
794             self.assertEqual(set([(z.Name, a.Species) for z, a in zooed_animals]),
795                              set([("Wild Animal Park", "Leopard"),
796                                   ("Wild Animal Park", "Lion"),
797                                   ("San Diego Zoo", "Tiger"),
798                                   ("San Diego Zoo", "Millipede"),
799                                   ("Sea_World", "Emperor Penguin"),
800                                   ("Sea_World", "Adelie Penguin")]))
801            
802             zooed_animals = box.recall(Zoo >> Animal)
803             self.assertEqual(len(zooed_animals), 12)
804             self.assertEqual(set([(z.Name, a.Species) for z, a in zooed_animals]),
805                              set([("Wild Animal Park", "Leopard"),
806                                   ("Wild Animal Park", "Lion"),
807                                   ("San Diego Zoo", "Tiger"),
808                                   ("San Diego Zoo", "Millipede"),
809                                   ("Sea_World", "Emperor Penguin"),
810                                   ("Sea_World", "Adelie Penguin"),
811                                   (None, "Slug"),
812                                   (None, "Bear"),
813                                   (None, "Ostrich"),
814                                   (None, "Centipede"),
815                                   (None, "Ape"),
816                                   (None, "Ape"),
817                                   ]))
818            
819             zooed_animals = box.recall(Zoo << Animal)
820             self.assertEqual(len(zooed_animals), 7)
821             self.assertEqual(set([(z.Name, a.Species) for z, a in zooed_animals]),
822                              set([("Wild Animal Park", "Leopard"),
823                                   ("Wild Animal Park", "Lion"),
824                                   ("San Diego Zoo", "Tiger"),
825                                   ("San Diego Zoo", "Millipede"),
826                                   ("Sea_World", "Emperor Penguin"),
827                                   ("Sea_World", "Adelie Penguin"),
828                                   (u'Montr\xe9al Biod\xf4me', None),
829                                   ]))
830            
831             # Try a multiple-arg expression
832             f = (lambda a, z: a.Legs >= 4 and z.Admission < 10)
833             animal_zoos = box.recall(Animal & Zoo, f)
834             self.assertEqual(len(animal_zoos), 4)
835             names = [a.Species for a, z in animal_zoos]
836             names.sort()
837             self.assertEqual(names, ['Leopard', 'Lion', 'Millipede', 'Tiger'])
838            
839             # Let's try three joined classes just for the sadistic fun of it.
840             tree = (Animal >> Zoo) >> Vet
841             f = (lambda a, z, v: z.Name == 'Sea_World')
842             self.assertEqual(len(box.recall(tree, f)), 2)
843            
844             # MSAccess can't handle an INNER JOIN nested in an OUTER JOIN.
845             # Test that this fails for MSAccess, but works for other SM's.
846             trees = []
847             def make_tree():
848                 trees.append( (Animal & Zoo) >> Vet )
849             warnings.filterwarnings("ignore", category=errors.StorageWarning)
850             try:
851                 make_tree()
852             finally:
853                 warnings.filters.pop(0)
854            
855             azv = []
856             def set_azv():
857                 f = (lambda a, z, v: z.Name == 'Sea_World')
858                 azv.append(box.recall(trees[0], f))
859            
860             try:
861                 set_azv()
862             except pythoncom.com_error:
863                 # MSAccess should raise com_error
864                 warnings.warn("illegal INNER JOIN nested in OUTER JOIN.")
865             else:
866                 self.assertEqual(len(azv[0]), 2)
867            
868             # Try mentioning the same class twice.
869             tree = (Animal << Animal)
870             f = (lambda anim, mother: mother.ID != None)
871             animals = [mother.ID for anim, mother in box.recall(tree, f)]
872             self.assertEqual(animals, [11])
873         finally:
874             box.flush_all()
875    
876     def test_8_CustomAssociations(self):
877         box = root.new_sandbox()
878         try:
879             # Try different association paths
880             std_expected = ['Live Bunny Wabbit', 'Live Fish', 'Live Fish', 'T-Bone']
881             cus_expected = ['Dead Fish', 'Dead Fish', 'Live Bunny Wabbit', 'T-Bone']
882             uj = Animal & Food
883             for path, expected in [# standard path
884                                    (None, std_expected),
885                                    # custom path
886                                    ('Alternate Food', cus_expected)]:
887                
888                 uj.path = path
889                 foods = [food for animal, food in box.recall(uj)]
890                 foods.sort(dejavu.sort('Name'))
891                 self.assertEqual([f.Name for f in foods], expected)
892
893             # Test the magic association methods
894             tiger = box.unit(Animal, Species='Tiger')
895             self.assertEqual(tiger.Food().Name, 'Live Bunny Wabbit')
896             self.assertEqual(tiger.AlternateFood().Name, 'T-Bone')
897            
898         finally:
899             box.flush_all()
900    
901     def test_9_store_views(self):
902         # Test a simple view
903         legs = [x[0] for x in root.view((Animal, ['Legs']))]
904         legs.sort()
905         self.assertEqual(legs, [1, 2, 2, 2, 2, 2, 4, 4, 4, 4, 100, 1000000])
906        
907         # Test multiple columns
908         expected = {'Leopard': 73.5,
909                     'Slug': .75,
910                     'Tiger': None,
911                     'Lion': None,
912                     'Bear': None,
913                     'Ostrich': 103.2,
914                     'Centipede': None,
915                     'Emperor Penguin': None,
916                     'Adelie Penguin': None,
917                     'Millipede': None,
918                     'Ape': None,
919                     }
920         for species, lifespan in root.view((Animal, ['Species', 'Lifespan'])):
921             if expected[species] is None:
922                 self.assertEqual(lifespan, None)
923             else:
924                 self.assertAlmostEqual(expected[species], lifespan, places=5)
925        
926         # Try a restriction Expression.
927         expected = [u'Montr\xe9al Biod\xf4me', 'Wild Animal Park']
928         e = (lambda x: x.Founded != None
929              and x.Founded <= today()
930              and x.Founded >= datetime.date(1990, 1, 1))
931         values =  [val[0] for val in root.view((Zoo, ['Name'], e))]
932         for name in expected:
933             self.assert_(name in values)
934        
935         # distinct
936         distinct_legs = [(1, ), (2, ), (4, ), (100, ), (1000000, )]
937         legs = root.view((Animal, ['Legs']), distinct=True)
938         legs.sort()
939         self.assertEqual(legs, distinct_legs)
940         legs = root.view((Animal, lambda a: (a.Legs,)), distinct=True)
941         legs.sort()
942         self.assertEqual(legs, distinct_legs)
943        
944         # This may raise a warning on some DB's.
945         f = (lambda x: x.Species == 'Lion')
946         escapees = root.view((Animal, ['Legs'], f), distinct=True)
947         self.assertEqual(escapees, [(4, )])
948        
949         # range should return a sorted list
950         legs = root.range(Animal, 'Legs', lambda x: x.Legs <= 100)
951         self.assertEqual(legs, range(1, 101))
952         topics = root.range(Exhibit, 'Name')
953         self.assertEqual(topics, ['The Penguin Encounter', 'Tiger River'])
954         vets = root.range(Vet, 'Name')
955         self.assertEqual(vets, ['Charles Schroeder', 'Jim McBain'])
956        
957         # Now try views on a join.
958         all_legs_and_names = [(1, None), (2, None), (2, None), (2, None),
959                               (2, u'Sea_World'), (2, u'Sea_World'),
960                               (4, None), (4, u'San Diego Zoo'),
961                               (4, u'Wild Animal Park'),
962                               (4, u'Wild Animal Park'),
963                               (100, None), (1000000, u'San Diego Zoo'),
964                               ]
965         legs = root.view((Animal << Zoo, lambda a, z: (a.Legs, z.Name)))
966         legs.sort()
967         self.assertEqual(legs, all_legs_and_names)
968         legs = root.view((Animal << Zoo, [("Legs",), ("Name",)]))
969         legs.sort()
970         self.assertEqual(legs, all_legs_and_names)
971        
972         # Altogether now: distinct view with restriction on a join.
973         legs = root.view((Animal << Zoo,
974                            lambda a, z: (a.Legs, z.Name),
975                            lambda a, z: z.Name is not None and "o" in z.Name),
976                           distinct=True)
977         legs.sort()
978         self.assertEqual(legs, [(2, u'Sea_World'),
979                                 (4, u'San Diego Zoo'),
980                                 (1000000, u'San Diego Zoo'),
981                                 ])
982    
983     def test_Sandbox_Sync(self):
984         # This should expose all sorts of problems where data in the
985         # Sandbox has changed (but hasn't been flushed).
986         box = root.new_sandbox()
987         try:
988             # Restriction expression data out of sync.
989             # This should work just fine for a single unit class.
990             reds = box.recall(Vet, lambda v: v.FavoriteColor == 'Red')
991             self.assertEqual(len(reds), 2)
992             # Change one and try again
993             aVet = reds[0]
994             aVet.FavoriteColor = 'Blue'
995             reds = box.recall(Vet, lambda v: v.FavoriteColor == 'Red')
996             self.assertEqual(len(reds), 1)
997             # Change it back and try again
998             aVet.FavoriteColor = 'Red'
999             reds = box.recall(Vet, lambda v: v.FavoriteColor == 'Red')
1000             self.assertEqual(len(reds), 2)
1001            
1002             # Restriction expression data out of sync.
1003             # This currently doesn't work for multiple classes.
1004             reds = box.recall(Zoo << Vet,
1005                               lambda z, v: (z.Name == 'San Diego Zoo' and
1006                                             v.FavoriteColor == 'Red'))
1007             self.assertEqual(len(reds), 1)
1008             # Change the color and try again
1009             aVet = reds[0][0]
1010             aVet.FavoriteColor = 'Blue'
1011             blues = box.recall(Zoo << Vet,
1012                                lambda z, v: (z.Name == 'San Diego Zoo' and
1013                                              v.FavoriteColor == 'Blue'))
1014             self.assertEqual(len(blues), 1)
1015             # Change it back and try again
1016             aVet.FavoriteColor = 'Red'
1017             reds = box.recall(Zoo << Vet,
1018                               lambda z, v: (z.Name == 'San Diego Zoo' and
1019                                             v.FavoriteColor == 'Red'))
1020             self.assertEqual(len(reds), 1)
1021            
1022             # Join keys out of sync.
1023             swpengs = box.recall(Zoo << Animal,
1024                                  lambda z, a: 'Penguin' in a.Species)
1025             self.assertEqual(len(swpengs), 2)    # emp, adelie
1026             # Move a penguin to another Zoo and try again.
1027             swpengs[0][1].ZooID = None
1028             swpengs = box.recall(Zoo << Animal,
1029                                  lambda z, a: 'Penguin' in a.Species)
1030             self.assertEqual(len(swpengs), 1)
1031         finally:
1032             box.flush_all()
1033    
1034     def test_Iteration(self):
1035         box = root.new_sandbox()
1036         try:
1037             # Test box.unit inside of xrecall
1038             for visit in box.xrecall(Visit, dict(VetID=1)):
1039                 firstvisit = box.unit(Visit, VetID=1, Date=Jan_1_2001)
1040                 self.assertEqual(firstvisit.VetID, 1)
1041                 self.assertEqual(visit.VetID, 1)
1042            
1043             # Test recall inside of xrecall
1044             for visit in box.xrecall(Visit, dict(VetID=1)):
1045                 f = (lambda x: x.VetID == 1 and x.ID != visit.ID)
1046                 othervisits = box.recall(Visit, f)
1047                 self.assertEqual(len(othervisits), len(every13days) - 1)
1048            
1049             # Test far associations inside of xrecall
1050             for visit in box.xrecall(Visit, dict(VetID=1)):
1051                 # visit.Vet is a ToOne association, so will return a unit or None.
1052                 vet = visit.Vet()
1053                 self.assertEqual(vet.ID, 1)
1054         finally:
1055             box.flush_all()
1056    
1057     def test_Engines(self):
1058         box = root.new_sandbox()
1059         try:
1060             quadrupeds = box.recall(Animal, dict(Legs=4))
1061             self.assertEqual(len(quadrupeds), 4)
1062            
1063             eng = engines.UnitEngine()
1064             box.memorize(eng)
1065             eng.add_rule('CREATE', 1, "Animal")
1066             eng.add_rule('FILTER', 1, logic.filter(Legs=4))
1067             self.assertEqual(eng.FinalClassName, "Animal")
1068            
1069             qcoll = eng.take_snapshot()
1070             self.assertEqual(len(qcoll), 4)
1071             self.assertEqual(qcoll.EngineID, eng.ID)
1072            
1073             eng.add_rule('TRANSFORM', 1, "Zoo")
1074             self.assertEqual(eng.FinalClassName, "Zoo")
1075            
1076             # Sleep for a second so the Timestamps are different.
1077             time.sleep(1)
1078             qcoll = eng.take_snapshot()
1079             self.assertEqual(len(qcoll), 2)
1080             zoos = qcoll.units()
1081             zoos.sort(dejavu.sort('Name'))
1082            
1083             SDZ = box.unit(Zoo, Name='San Diego Zoo')
1084             WAP = box.unit(Zoo, Name='Wild Animal Park')
1085             self.assertEqual(zoos, [SDZ, WAP])
1086            
1087             # Flush and start over
1088             box.flush_all()
1089             box = root.new_sandbox()
1090            
1091             # Use the Sandbox magic recaller method
1092             eng = box.UnitEngine(1)
1093             self.assertEqual(len(eng.rules()), 3)
1094             snaps = eng.snapshots()
1095             self.assertEqual(len(snaps), 2)
1096            
1097             self.assertEqual(snaps[0].Type, "Animal")
1098             self.assertEqual(len(snaps[0]), 4)
1099            
1100             self.assertEqual(snaps[1].Type, "Zoo")
1101             self.assertEqual(len(snaps[1]), 2)
1102             self.assertEqual(eng.last_snapshot(), snaps[1])
1103            
1104             # Remove the last TRANSFORM rule to see if finalclass reverts.
1105             self.assertEqual(eng.FinalClassName, "Zoo")
1106             eng.rules()[-1].forget()
1107             self.assertEqual(eng.FinalClassName, "Animal")
1108         finally:
1109             box.flush_all()
1110    
1111     def test_zzz_Schema_Upgrade(self):
1112         # Must run last.
1113         zs = ZooSchema(root)
1114        
1115         # In this first upgrade, we simulate the case where the code was
1116         # upgraded, and the database schema upgrade performed afterward.
1117         # The Schema.latest property is set, and upgrade() is called with
1118         # no argument (which should upgrade us to "latest").
1119         Animal.set_property("ExhibitID")
1120         # Test numeric default (see hack in storeado for MS Access).
1121         prop = Animal.set_property("Stomachs", int)
1122         prop.default = 1
1123         zs.latest = 2
1124         zs.upgrade()
1125        
1126         # In this example, we simulate the developer who wants to put
1127         # model changes inline with database changes (see upgrade_to_3).
1128         # We do not set latest, but instead supply an arg to upgrade().
1129         zs.upgrade(3)
1130        
1131         # Test that Animals have a new "Family" property, and an ExhibitID.
1132         box = root.new_sandbox()
1133         try:
1134             emp = box.unit(Animal, Family='Emperor Penguin')
1135             self.assertEqual(emp.ExhibitID, 'The Penguin Encounter')
1136         finally:
1137             box.flush_all()
1138
1139
1140 class KeyStoreTests(unittest.TestCase):
1141     """Timed tests for key/value storage styles common in HA environments."""
1142    
1143     numobjects = 500
1144    
1145     def test_01_Storage(self):
1146         today = datetime.date.today()
1147        
1148         start = datetime.datetime.now()
1149         for id in xrange(self.numobjects):
1150             root.reserve(Ticket(ID=id+1, Price=25.00, Date=today))
1151         if root.commit:
1152             root.commit()
1153         print ("%r objects stored in %s" %
1154                (self.numobjects, datetime.datetime.now() - start)),
1155    
1156     def test_02_Retrieval(self):
1157         start = datetime.datetime.now()
1158         for id in xrange(self.numobjects):
1159             ticket = root.unit(Ticket, ID=id+1)
1160             if ticket is None:
1161                 self.fail("No such ticket %r" % id)
1162         print ("%r objects retrieved in %s" %
1163                (self.numobjects, datetime.datetime.now() - start)),
1164
1165
1166 class ConcurrencyTests(unittest.TestCase):
1167    
1168     def test_Multithreading(self):
1169 ##        print "skipped ",
1170 ##        return
1171        
1172         # Test threads overlapping on separate sandboxes
1173         f = (lambda x: x.Legs == 4)
1174         def box_per_thread():
1175             # Notice that, although we write changes in each thread,
1176             # we only assert the unchanged data, since the order of
1177             # thread execution can not be guaranteed.
1178             box = root.new_sandbox()
1179             try:
1180                 quadrupeds = box.recall(Animal, f)
1181                 self.assertEqual(len(quadrupeds), 4)
1182                 quadrupeds[0].Age += 1.0
1183             finally:
1184                 box.flush_all()
1185         ts = []
1186         # PostgreSQL, for example, has a default max_connections of 100.
1187         for x in range(99):
1188             t = threading.Thread(target=box_per_thread)
1189             t.start()
1190             ts.append(t)
1191         for t in ts:
1192             t.join()
1193    
1194     def test_ContextManagement(self):
1195         # Test context management using Python 2.5 'with ... as'
1196         try:
1197             from dejavu.test import test_context
1198         except SyntaxError:
1199             print "'with ... as' not supported (skipped) ",
1200         else:
1201             test_context.test_with_context(root)
1202
1203
1204 class NumericTests(unittest.TestCase):
1205    
1206     def test_long(self):
1207 ##        print "skipped ",
1208 ##        return
1209        
1210         box = root.new_sandbox()
1211         try:
1212             print "precision:",
1213             # PostgreSQL should be able to go up to 1000 decimal digits (~= 2 ** 10),
1214             # but SQL constants don't actually overflow until 2 ** 15. Meh.
1215             db = getattr(leaf_store(), "db", None)
1216             if db:
1217                 maxprec = db.typeset.numeric_max_precision()
1218                 if maxprec == 0:
1219                     # SQLite, for example, must always use TEXT.
1220                     # So we might as well try... oh... how about 3?
1221                     overflow_prec = 3
1222                 else:
1223                     overflow_prec = int(math.log(maxprec, 2)) + 1
1224             else:
1225                 overflow_prec = 8
1226            
1227             for prec in xrange(overflow_prec + 1):
1228                 p = 2 ** prec
1229                 print p,
1230                
1231                 # We don't need to test <type long> at different 'scales'.
1232                 root.drop_property(NothingToDoWithZoos, 'ALong')
1233                 NothingToDoWithZoos.ALong.hints['bytes'] = p
1234                 root.add_property(NothingToDoWithZoos, 'ALong')
1235                
1236                 for neg in (False, True):
1237                     # Create an instance and set the specified precision
1238                     # Assume all numeric dbtypes are signed; test both
1239                     # positive and negative, and cut the min/max in half.
1240                     # Divide by 2 for signed type.
1241                     Lval = ((16 ** p) / 2) - 1
1242                     if neg:
1243                         Lval = 0 - Lval
1244                     box.memorize(NothingToDoWithZoos(ALong=Lval))
1245                    
1246                     # Flush and retrieve the object. Use comparisons to test
1247                     # decompilation of imperfect_type when using large numbers.
1248                     box.flush_all()
1249                     nothing = box.unit(NothingToDoWithZoos, ALong=Lval)
1250                     if nothing is None:
1251                         self.fail("Unit not found by long property. prec=%s" % p)
1252                    
1253                     # Test retrieved values.
1254                     if nothing.ALong != Lval:
1255                         self.fail("%r != %r prec=%s" % (nothing.ALong, Lval, p))
1256                     nothing.forget()
1257         finally:
1258             box.flush_all()
1259    
1260     def test_float(self):
1261 ##        print "skipped ",
1262 ##        return
1263        
1264         float_prec = 53
1265         box = root.new_sandbox()
1266         try:
1267             print "precision:",
1268             # PostgreSQL should be able to go up to 1000 decimal digits (~= 2 ** 10),
1269             # but SQL constants don't actually overflow until 2 ** 15. Meh.
1270             db = getattr(leaf_store(), "db", None)
1271             if db:
1272                 maxprec = db.typeset.numeric_max_precision()
1273                 if maxprec == 0:
1274                     # SQLite, for example, must always use TEXT.
1275                     # So we might as well try... oh... how about 3?
1276                     overflow_prec = 3
1277                 else:
1278                     overflow_prec = int(math.log(maxprec, 2)) + 1
1279             else:
1280                 overflow_prec = 8
1281            
1282             for prec in xrange(overflow_prec + 1):
1283                 p = 2 ** prec
1284                 print p,
1285                
1286                 # Test scales at both extremes and the median
1287                 for s in (0, int(prec/2), max(prec-1, 0)):
1288                     s = 2 ** s
1289                    
1290                     # Modify the model and storage
1291 ##                    if p <= float_prec:
1292                     root.drop_property(NothingToDoWithZoos, 'AFloat')
1293                     NothingToDoWithZoos.AFloat.hints['precision'] = p
1294                     root.add_property(NothingToDoWithZoos, 'AFloat')
1295                    
1296                     for neg in (False, True):
1297                         # Create an instance and set the specified precision
1298                         # and scale for all fields. Assume all numeric
1299                         # dbtypes are signed; test both positive and negative.
1300 ##                        if p <= float_prec:
1301                         fmax = (2 ** p) - 1
1302                         fval = float(fmax / (2 ** s))
1303                         if neg:
1304                             fval = 0 - fval
1305                         box.memorize(NothingToDoWithZoos(AFloat=fval))
1306                        
1307                         # Flush and retrieve the object. Use comparisons to test
1308                         # decompilation of imperfect_type when using large numbers.
1309 ##                        if p <= float_prec:
1310                         box.flush_all()
1311                         nothing = box.unit(NothingToDoWithZoos, AFloat=fval)
1312                         if nothing is None:
1313                             self.fail("Unit not found by float property. "
1314                                       "prec=%s scale=%s" % (p, s))
1315                        
1316                         # Test retrieved values.
1317 ##                        if p <= float_prec:
1318                         if nothing.AFloat != fval:
1319                             self.fail("%s != %s prec=%s scale=%s" %
1320                                       (`nothing.AFloat`, `fval`, p, s))
1321                         nothing.forget()
1322         finally:
1323             box.flush_all()
1324    
1325     def test_decimal_and_fixed(self):
1326         if not (decimal or fixedpoint):
1327             print "skipped (no decimal or fixedpoint libraries available)"
1328             return
1329        
1330         box = root.new_sandbox()
1331         try:
1332             print "precision:",
1333             # PostgreSQL should be able to go up to 1000 decimal digits (~= 2 ** 10),
1334             # but SQL constants don't actually overflow until 2 ** 15. Meh.
1335             db = getattr(leaf_store(), "db", None)
1336             if db:
1337                 maxprec = db.typeset.numeric_max_precision()
1338                 if maxprec == 0:
1339                     # SQLite, for example, must always use TEXT.
1340                     # So we might as well try... oh... how about 3?
1341                     overflow_prec = 3
1342                 else:
1343                     overflow_prec = int(math.log(maxprec, 2)) + 1
1344             else:
1345                 overflow_prec = 8
1346            
1347             dc = decimal.getcontext()
1348            
1349             for prec in xrange(overflow_prec + 1):
1350                 p = 2 ** prec
1351                 print p,
1352                 if p > dc.prec:
1353                     dc.prec = p
1354                
1355                 # Test scales at both extremes and the median
1356                 for s in (0, int(prec/2), max(prec-1, 0)):
1357                     s = 2 ** s
1358                    
1359                     # Modify the model and storage
1360                     if decimal:
1361                         root.drop_property(NothingToDoWithZoos, 'ADecimal')
1362                         NothingToDoWithZoos.ADecimal.hints['precision'] = p
1363                         NothingToDoWithZoos.ADecimal.hints['scale'] = s
1364                         root.add_property(NothingToDoWithZoos, 'ADecimal')
1365                     if fixedpoint:
1366                         root.drop_property(NothingToDoWithZoos, 'AFixed')
1367                         NothingToDoWithZoos.AFixed.hints['precision'] = p
1368                         NothingToDoWithZoos.AFixed.hints['scale'] = s
1369                         root.add_property(NothingToDoWithZoos, 'AFixed')
1370                    
1371                     for neg in (False, True):
1372                         # Create an instance and set the specified precision
1373                         # and scale for all fields. Assume all numeric
1374                         # dbtypes are signed; test both positive and
1375                         # negative, and cut the min/max in half for long
1376                         # (floats and numerics don't need to be cut in half,
1377                         # because they have implicit sign bits).
1378                         nothing = NothingToDoWithZoos()
1379                         nval = "1" * p
1380                         nval = nval[:-s] + "." + nval[-s:]
1381                         if decimal:
1382                             dval = decimal.Decimal(nval)
1383                             if neg:
1384                                 dval = 0 - dval
1385                             setattr(nothing, 'ADecimal', dval)
1386                         if fixedpoint:
1387                             # fixedpoint uses "precision" where we use "scale";
1388                             # that is, number of digits after the decimal point.
1389                             fpval = fixedpoint.FixedPoint(nval, s)
1390                             if neg:
1391                                 fpval = 0 - fpval
1392                             setattr(nothing, 'AFixed', fpval)
1393                         box.memorize(nothing)
1394                        
1395                         # Flush and retrieve the object. Use comparisons to test
1396                         # decompilation of imperfect_type when using large numbers.
1397                         if decimal:
1398                             box.flush_all()
1399                             nothing = box.unit(NothingToDoWithZoos, ADecimal=dval)
1400                             if nothing is None:
1401                                 self.fail("Unit not found by decimal property. "
1402                                           "prec=%s scale=%s" % (p, s))
1403                         if fixedpoint:
1404                             box.flush_all()
1405                             nothing = box.unit(NothingToDoWithZoos, AFixed=fpval)
1406                             if nothing is None:
1407                                 self.fail("Unit not found by fixedpoint property. "
1408                                           "prec=%s scale=%s" % (p, s))
1409                        
1410                         # Test retrieved values.
1411                         if decimal:
1412                             if nothing.ADecimal != dval:
1413                                 self.fail("%s != %s prec=%s scale=%s" %
1414                                           (`nothing.ADecimal`, `dval`, p, s))
1415                         if fixedpoint:
1416                             if nothing.AFixed != fpval:
1417                                 self.fail("%s != %s prec=%s scale=%s" %
1418                                           (`nothing.AFixed`, `fpval`, p, s))
1419                         nothing.forget()
1420         finally:
1421             box.flush_all()
1422
1423
1424 class DiscoveryTests(unittest.TestCase):
1425    
1426     def assertIn(self, first, second, msg=None):
1427         """Fail if 'second not in first'."""
1428         if not second.lower() in first.lower():
1429             raise self.failureException, (msg or '%r not in %r' % (second, first))
1430    
1431     def setUp(self):
1432         self.modeler = None
1433        
1434         s = leaf_store()
1435         if not hasattr(s, "db"):
1436             return
1437        
1438         # Clear out all mappings and re-discover
1439         dict.clear(s.schema)
1440         s.schema.discover_all()
1441        
1442         from dejavu.storage import db
1443         self.modeler = db.Modeler(s.schema)
1444    
1445     def test_make_classes(self):
1446         if not self.modeler:
1447             print "not a db (skipped) ",
1448             return
1449        
1450         for cls in (Zoo, Animal):
1451             tkey = self.modeler.schema.table_name(cls.__name__)
1452            
1453             uc = self.modeler.make_class(tkey, cls.__name__)
1454             self.assert_(not issubclass(uc, cls))
1455             self.assertEqual(uc.__name__, cls.__name__)
1456            
1457             # Both Zoo and Animal should have autoincrementing ID's
1458             # (but MySQL uses all lowercase identifiers).
1459             self.assertEqual(set([x.lower() for x in uc.identifiers]),
1460                              set([x.lower() for x in cls.identifiers]))
1461             self.assert_(isinstance(uc.sequencer, UnitSequencerInteger),
1462                          "%r sequencer is of type %r (expected %r)"
1463                          % (cls, type(uc.sequencer), UnitSequencerInteger))
1464            
1465             for pname in cls.properties:
1466                 cname = self.modeler.schema.column_name(tkey, pname)
1467                 copy = getattr(uc, cname)
1468                 orig = getattr(cls, pname)
1469                 self.assertEqual(copy.key, cname)
1470                 # self.assertEqual(copy.type, orig.type)
1471                 self.assertEqual(copy.default, orig.default,
1472                                  "%s.%s default %s != copy %s"
1473                                  % (cls.__name__, pname,
1474                                     `orig.default`, `copy.default`))
1475                
1476                 for k, v in orig.hints.iteritems():
1477                     if isinstance(v, (int, long)):
1478                         v2 = copy.hints.get(k)
1479                         if v2 != 0 and v2 < v:
1480                             self.fail("%s.%s hints[%s]: %s not >= %s" %
1481                                       (cls.__name__, pname, k, v2, v))
1482                     else:
1483                         self.assertEqual(copy.hints[k], v)
1484    
1485     def test_make_source(self):
1486         if not self.modeler:
1487             print "not a db (skipped) ",
1488             return
1489        
1490         tkey = self.modeler.schema.table_name('Exhibit')
1491         source = self.modeler.make_source(tkey, 'Exhibit')
1492        
1493         classline = "class Exhibit(Unit):"
1494         if not source.lower().startswith(classline.lower()):
1495             self.fail("%r does not start with %r" % (source, classline))
1496        
1497         clsname = self.modeler.schema.__class__.__name__
1498         if "SQLite" in clsname:
1499             # SQLite's internal types are teh suck.
1500             self.assertIn(source, "    Name = UnitProperty(")
1501             self.assertIn(source, "    ZooID = UnitProperty(")
1502             self.assertIn(source, "    PettingAllowed = UnitProperty(")
1503             self.assertIn(source, "    Acreage = UnitProperty(")
1504             self.assertIn(source, "    sequencer = UnitSequencer")
1505         else:
1506             try:
1507                 self.assertIn(source, "    Name = UnitProperty(unicode")
1508             except AssertionError:
1509                 self.assertIn(source, "    Name = UnitProperty(str")
1510            
1511             self.assertIn(source, "    ZooID = UnitProperty(int")
1512             if "Firebird" in clsname:
1513                 # Firebird doesn't have a bool datatype
1514                 self.assertIn(source, "    PettingAllowed = UnitProperty(int")
1515             else:
1516                 self.assertIn(source, "    PettingAllowed = UnitProperty(bool")
1517             if decimal:
1518                 self.assertIn(source, "    Acreage = UnitProperty(decimal.Decimal")
1519             else:
1520                 self.assertIn(source, "    Acreage = UnitProperty(float")
1521            
1522             self.assertIn(source, "    sequencer = UnitSequencer()")
1523        
1524        
1525         if "    ID = UnitProperty" in source:
1526             self.fail("Exhibit incorrectly possesses an ID property.")
1527        
1528         # ID = None should remove the existing ID property
1529         self.assertIn(source, "    ID = None")
1530        
1531         for items in ["'zooid', 'name'", "'name', 'zooid'",
1532                       "u'zooid', u'name'", "u'name', u'zooid'"]:
1533             if ("    identifiers = (%s)" % items) in source.lower():
1534                 break
1535         else:
1536             self.fail("%r not found in %r" %
1537                       ("    identifiers = ('ZooID', 'Name')", source))
1538
1539
1540 root = None
1541
1542 def leaf_store():
1543     try:
1544         child = root.stores.values()[0]
1545     except AttributeError:
1546         # Not a mediated store
1547         child = root
1548     return child
1549
1550
1551 class ZooSchema(dejavu.Schema):
1552    
1553     # We set "latest" to 1 so we can test upgrading manually.
1554     latest = 1
1555    
1556     def upgrade_to_2(self):
1557         self.store.add_property(Animal, "Stomachs")
1558         self.store.add_property(Animal, "ExhibitID")
1559         box = self.store.new_sandbox()
1560         for exhibit in box.recall(Exhibit):
1561             for animalID in exhibit.Animals:
1562                 # Use the Sandbox magic recaller method.
1563                 a = box.Animal(animalID)
1564                 if a:
1565                     # Exhibits are identified by ZooID and Name
1566                     a.ZooID = exhibit.ZooID
1567                     a.ExhibitID = exhibit.Name
1568         box.flush_all()
1569    
1570     def upgrade_to_3(self):
1571         Animal.remove_property("Species")
1572         Animal.set_property("Family")
1573        
1574         # Note that we drop this column in a separate step from step 2.
1575         # If we had mixed model properties and SM properties in step 2,
1576         # we could have done this all in one step. But this is a better
1577         # demonstration of the possibilities. ;)
1578         Exhibit.remove_property("Animals")
1579         self.store.drop_property(Exhibit, "Animals")
1580        
1581         self.store.rename_property(Animal, "Species", "Family")
1582
1583
1584 def setup(store, mediated=False):
1585     """Set up storage for Zoo classes."""
1586    
1587     global root
1588     store.register_all(globals())
1589     engines.register_classes(store)
1590     dejavu.DeployedVersion.register(store)
1591     if hasattr(store, "cache"):
1592         store.cache.register_all(globals())
1593         engines.register_classes(store.cache)
1594         dejavu.DeployedVersion.register(store.cache)
1595     if hasattr(store, "nextstore"):
1596         store.nextstore.register_all(globals())
1597         engines.register_classes(store.nextstore)
1598         dejavu.DeployedVersion.register(store.nextstore)
1599    
1600     if mediated:
1601         from dejavu.storage import partitions
1602         root = partitions.VerticalPartitioner()
1603         root.add_store('testSM', store)
1604     else:
1605         root = store
1606    
1607     root.create_database()
1608     zs = ZooSchema(root)
1609     zs.upgrade()
1610     zs.assert_storage()
1611
1612
1613 def teardown():
1614     """Tear down storage for Zoo classes."""
1615     # Manually drop each table just to test that code.
1616     # Call map_all first in case our discovery tests screwed up the keys.
1617     root.map_all(conflicts='ignore')
1618    
1619     for cls in root.classes:
1620         try:
1621             root.drop_storage(cls, conflicts='warn')
1622         except KeyError:
1623             pass
1624    
1625     root.drop_database(conflicts='warn')
1626     root.shutdown(conflicts='warn')
1627
1628 def run(store, mediated=False):
1629     """Run the zoo fixture."""
1630     try:
1631         try:
1632             setup(store, mediated)
1633             loader = unittest.TestLoader().loadTestsFromTestCase
1634            
1635             # Run the ZooTests and time it.
1636             zoocase = loader(ZooTests)
1637             startTime = datetime.datetime.now()
1638             tools.djvTestRunner.run(zoocase)
1639             print "Ran zoo cases in:", datetime.datetime.now() - startTime
1640            
1641             # Run the other cases.
1642             tools.djvTestRunner.run(loader(KeyStoreTests))
1643             tools.djvTestRunner.run(loader(NumericTests))
1644 ##            tools.djvTestRunner.run(loader(ConcurrencyTests))
1645             tools.djvTestRunner.run(loader(DiscoveryTests))
1646         except:
1647             traceback.print_exc()
1648     finally:
1649         teardown()
Note: See TracBrowser for help on using the browser.