Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

root/trunk/test/zoo_fixture.py

Revision 230 (checked in by fumanchu, 7 years ago)

Introspection fixes for ADO and SQLite (which now declares datatypes in CREATE TABLE).

  • Property svn:eol-style set to native
Line 
1 """Test fixture for Storage Managers."""
2
3 import datetime
4 import os
5 try:
6     import pythoncom
7 except ImportError:
8     pythoncom = None
9
10 try:
11     set
12 except NameError:
13     from sets import Set as set
14
15 import threading
16 import time
17 import unittest
18 import warnings
19
20 try:
21     # Builtin in Python 2.5?
22     decimal
23 except NameError:
24     try:
25         # Module in Python 2.3, 2.4
26         import decimal
27     except ImportError:
28         decimal = None
29
30 try:
31     import fixedpoint
32 except ImportError:
33     fixedpoint = None
34
35 __all__ = ['Animal', 'Exhibit', 'Lecture', 'Vet', 'Visit', 'Zoo',
36            # Don't export the ZooTests class--it will break e.g. test_dejavu.
37            'arena', 'init', 'run', 'setup', 'teardown']
38
39 import dejavu
40 from dejavu import logic
41 from dejavu import Unit, UnitProperty, ToOne, ToMany, UnitSequencerInteger
42 from dejavu.test import tools
43 from dejavu import engines
44
45
46 class EscapeProperty(UnitProperty):
47     def __set__(self, unit, value):
48         UnitProperty.__set__(self, unit, value)
49         # Zoo is a ToOne association, so it will return a unit or None.
50         z = unit.Zoo()
51         if z:
52             z.LastEscape = unit.LastEscape
53
54
55 class Animal(Unit):
56     Species = UnitProperty(hints={'bytes': 100})
57     ZooID = UnitProperty(int, index=True)
58     Legs = UnitProperty(int, default=4)
59     PreviousZoos = UnitProperty(list, hints={'bytes': 8000})
60     LastEscape = EscapeProperty(datetime.datetime)
61     Lifespan = UnitProperty(float, hints={'precision': 4})
62     Age = UnitProperty(float, hints={'precision': 4}, default=1)
63     MotherID = UnitProperty(int)
64
65 Animal.many_to_one('ID', Animal, 'MotherID')
66
67
68 class Zoo(Unit):
69     Name = UnitProperty()
70     Founded = UnitProperty(datetime.date)
71     Opens = UnitProperty(datetime.time)
72     LastEscape = UnitProperty(datetime.datetime)
73    
74     if fixedpoint:
75         Admission = UnitProperty(fixedpoint.FixedPoint)
76     else:
77         Admission = UnitProperty(float)
78
79 Zoo.one_to_many('ID', Animal, 'ZooID')
80
81
82 class Vet(Unit):
83     """A Veterinarian."""
84     Name = UnitProperty()
85     ZooID = UnitProperty(int, index=True)
86     sequencer = UnitSequencerInteger(initial=200)
87
88 Vet.many_to_one('ZooID', Zoo, 'ID')
89
90
91 class Visit(Unit):
92     """Work done by a Veterinarian on an Animal."""
93     VetID = UnitProperty(int, index=True)
94     ZooID = UnitProperty(int, index=True)
95     AnimalID = UnitProperty(int, index=True)
96     Date = UnitProperty(datetime.date)
97
98 Vet.one_to_many('ID', Visit, 'VetID')
99 Animal.one_to_many('ID', Visit, 'AnimalID')
100
101
102 class Lecture(Visit):
103     """A Visit by a Vet to train staff (rather than visit an Animal)."""
104     AnimalID = None
105     Topic = UnitProperty()
106
107
108 class Exhibit(Unit):
109     # Make this a string to help test vs unicode.
110     Name = UnitProperty(str)
111     ZooID = UnitProperty(int)
112     Animals = UnitProperty(list)
113     PettingAllowed = UnitProperty(bool)
114     Creators = UnitProperty(tuple)
115    
116     if decimal:
117         Acreage = UnitProperty(decimal.Decimal)
118     else:
119         Acreage = UnitProperty(float)
120    
121     # Remove the ID property (inherited from Unit) from the Exhibit class.
122     ID = None
123     sequencer = dejavu.UnitSequencerNull()
124     identifiers = ("ZooID", Name)
125
126 Zoo.one_to_many('ID', Exhibit, 'ZooID')
127
128
129 # Create decimal fields of precision 1, 2, 4, 8, ..., (2 ** NUM_PREC_LOG2)
130 # PostgreSQL should be able to go up to 10, but using 4 here
131 # (= 2 ** 4 = 16 decimal digits) makes the entire test suite cleaner. ;)
132 NUM_PREC_LOG2 = 4
133
134 class NothingToDoWithZoos(Unit):
135     pass
136 for prec in xrange(NUM_PREC_LOG2 + 1):
137     p = 2 ** prec
138     for s in xrange(prec):
139         s = 2 ** s
140         d = NothingToDoWithZoos.set_property("Float%s_%s" % (p, s), float)
141         d.hints['precision'] = p
142         if decimal:
143             d = NothingToDoWithZoos.set_property("Decimal%s_%s" % (p, s),
144                                                  decimal.Decimal)
145             d.hints['precision'] = p
146             d.hints['scale'] = s
147
148
149 Jan_1_2001 = datetime.date(2001, 1, 1)
150 every13days = [Jan_1_2001 + datetime.timedelta(x * 13) for x in range(20)]
151 every17days = [Jan_1_2001 + datetime.timedelta(x * 17) for x in range(20)]
152 del x
153
154 class ZooTests(unittest.TestCase):
155    
156     def test_1_model(self):
157         self.assertEqual(Zoo.Animal.__class__, dejavu.ToMany)
158         self.assertEqual(Zoo.Animal.nearClass, Zoo)
159         self.assertEqual(Zoo.Animal.nearKey, 'ID')
160         self.assertEqual(Zoo.Animal.farClass, Animal)
161         self.assertEqual(Zoo.Animal.farKey, 'ZooID')
162        
163         self.assertEqual(Animal.Zoo.__class__, dejavu.ToOne)
164         self.assertEqual(Animal.Zoo.nearClass, Animal)
165         self.assertEqual(Animal.Zoo.nearKey, 'ZooID')
166         self.assertEqual(Animal.Zoo.farClass, Zoo)
167         self.assertEqual(Animal.Zoo.farKey, 'ID')
168    
169     def test_2_populate(self):
170         box = arena.new_sandbox()
171        
172         # Notice this also tests that: a Unit which is only
173         # dirtied via __init__ is still saved.
174         WAP = Zoo(Name = 'Wild Animal Park',
175                   Founded = datetime.date(2000, 1, 1),
176                   # 59 can give rounding errors with divmod, which
177                   # AdapterFromADO needs to correct.
178                   Opens = datetime.time(8, 15, 59),
179                   LastEscape = datetime.datetime(2004, 7, 29, 5, 6, 7),
180                   Admission = "4.95",
181                   )
182         box.memorize(WAP)
183         # The object should get an ID automatically.
184         self.assertNotEqual(WAP.ID, None)
185        
186         SDZ = Zoo(Name = 'San Diego Zoo',
187                   # This early date should play havoc with a number
188                   # of implementations.
189                   Founded = datetime.date(1835, 9, 13),
190                   Opens = datetime.time(9, 0, 0),
191                   Admission = "0",
192                   )
193         box.memorize(SDZ)
194         # The object should get an ID automatically.
195         self.assertNotEqual(SDZ.ID, None)
196        
197         Biodome = Zoo(Name = u'Montr\xe9al Biod\xf4me',
198                       Founded = datetime.date(1992, 6, 19),
199                       Opens = datetime.time(9, 0, 0),
200                       Admission = "11.75",
201                       )
202         box.memorize(Biodome)
203        
204         seaworld = Zoo(Name = 'Sea_World', Admission = "60")
205         box.memorize(seaworld)
206        
207         # Animals
208         leopard = Animal(Species='Leopard', Lifespan=73.5)
209         self.assertEqual(leopard.PreviousZoos, None)
210         box.memorize(leopard)
211         leopard.add(WAP)
212         leopard.LastEscape = datetime.datetime(2004, 12, 21, 8, 15, 0)
213        
214         lion = Animal(Species='Lion', ZooID=WAP.ID)
215         box.memorize(lion)
216        
217         box.memorize(Animal(Species='Slug', Legs=1, Lifespan=.75,
218                             # Test our 8000-byte limit
219                             PreviousZoos=["f" * (8000 - 14)]))
220        
221         tiger = Animal(Species='Tiger', PreviousZoos=['animal\\universe'])
222         box.memorize(tiger)
223        
224         # Override Legs.default with itself just to make sure it works.
225         box.memorize(Animal(Species='Bear', Legs=4))
226         # Notice that ostrich.PreviousZoos is [], whereas leopard is None.
227         box.memorize(Animal(Species='Ostrich', Legs=2, PreviousZoos=[],
228                             Lifespan=103.2))
229         box.memorize(Animal(Species='Centipede', Legs=100))
230        
231         emp = Animal(Species='Emperor Penguin', Legs=2)
232         box.memorize(emp)
233         adelie = Animal(Species='Adelie Penguin', Legs=2)
234         box.memorize(adelie)
235        
236         seaworld.add(emp, adelie)
237        
238         millipede = Animal(Species='Millipede', Legs=1000000)
239         millipede.PreviousZoos = [WAP.Name]
240         box.memorize(millipede)
241        
242         SDZ.add(tiger, millipede)
243        
244         # Add a mother and child to test relationships
245         bai_yun = Animal(Species='Ape', Legs=2)
246         box.memorize(bai_yun)   # ID = 11
247         self.assertEqual(bai_yun.ID, 11)
248         hua_mei = Animal(Species='Ape', Legs=2, MotherID=bai_yun.ID)
249         box.memorize(hua_mei)   # ID = 12
250         self.assertEqual(hua_mei.ID, 12)
251        
252         # Exhibits
253         pe = Exhibit(Name = 'The Penguin Encounter',
254                      ZooID = seaworld.ID,
255                      Animals = [emp.ID, adelie.ID],
256                      PettingAllowed = True,
257                      Acreage = "3.1",
258                      # See ticket #45
259                      Creators = (u'Richard F\xfcrst', u'Sonja Martin'),
260                      )
261         box.memorize(pe)
262        
263         tr = Exhibit(Name = 'Tiger River',
264                      ZooID = SDZ.ID,
265                      Animals = [tiger.ID],
266                      PettingAllowed = False,
267                      Acreage = "4",
268                      )
269         box.memorize(tr)
270        
271         # Vets
272         cs = Vet(Name = 'Charles Schroeder', ZooID = SDZ.ID)
273         box.memorize(cs)
274         self.assertEqual(cs.ID, Vet.sequencer.initial)
275        
276         jm = Vet(Name = 'Jim McBain', ZooID = seaworld.ID)
277         box.memorize(jm)
278        
279         # Visits
280         for d in every13days:
281             box.memorize(Visit(VetID=cs.ID, AnimalID=tiger.ID, Date=d))
282         for d in every17days:
283             box.memorize(Visit(VetID=jm.ID, AnimalID=emp.ID, Date=d))
284        
285         box.flush_all()
286    
287     def test_3_Properties(self):
288         box = arena.new_sandbox()
289        
290         # Zoos
291         WAP = box.unit(Zoo, Name='Wild Animal Park')
292         self.assertNotEqual(WAP, None)
293         self.assertEqual(WAP.Founded, datetime.date(2000, 1, 1))
294         self.assertEqual(WAP.Opens, datetime.time(8, 15, 59))
295         # This should have been updated when leopard.LastEscape was set.
296         self.assertEqual(WAP.LastEscape,
297                          datetime.datetime(2004, 12, 21, 8, 15, 0))
298         self.assertEqual(str(WAP.Admission), "4.95")
299        
300         SDZ = box.unit(Zoo, Founded=datetime.date(1835, 9, 13))
301         self.assertNotEqual(SDZ, None)
302         self.assertEqual(SDZ.Founded, datetime.date(1835, 9, 13))
303         self.assertEqual(SDZ.Opens, datetime.time(9, 0, 0))
304         self.assertEqual(SDZ.LastEscape, None)
305         self.assertEqual(float(SDZ.Admission), 0)
306        
307         # Try a magic Sandbox recaller method
308         Biodome = box.Zoo(Name = u'Montr\xe9al Biod\xf4me')
309         self.assertNotEqual(Biodome, None)
310         self.assertEqual(Biodome.Name, u'Montr\xe9al Biod\xf4me')
311         self.assertEqual(Biodome.Founded, datetime.date(1992, 6, 19))
312         self.assertEqual(Biodome.Opens, datetime.time(9, 0, 0))
313         self.assertEqual(Biodome.LastEscape, None)
314         self.assertEqual(float(Biodome.Admission), 11.75)
315        
316         if fixedpoint:
317             seaworld = box.unit(Zoo, Admission = fixedpoint.FixedPoint(60))
318         else:
319             seaworld = box.unit(Zoo, Admission = float(60))
320         self.assertNotEqual(seaworld, None)
321         self.assertEqual(seaworld.Name, u'Sea_World')
322        
323         # Animals
324         leopard = box.unit(Animal, Species='Leopard')
325         self.assertEqual(leopard.Species, 'Leopard')
326         self.assertEqual(leopard.Legs, 4)
327         self.assertEqual(leopard.Lifespan, 73.5)
328         self.assertEqual(leopard.ZooID, WAP.ID)
329         self.assertEqual(leopard.PreviousZoos, None)
330         self.assertEqual(leopard.LastEscape,
331                          datetime.datetime(2004, 12, 21, 8, 15, 0))
332        
333         ostrich = box.unit(Animal, Species='Ostrich')
334         self.assertEqual(ostrich.Species, 'Ostrich')
335         self.assertEqual(ostrich.Legs, 2)
336         self.assertEqual(ostrich.ZooID, None)
337         self.assertEqual(ostrich.PreviousZoos, [])
338         self.assertEqual(ostrich.LastEscape, None)
339        
340         millipede = box.unit(Animal, Legs=1000000)
341         self.assertEqual(millipede.Species, 'Millipede')
342         self.assertEqual(millipede.Legs, 1000000)
343         self.assertEqual(millipede.ZooID, SDZ.ID)
344         self.assertEqual(millipede.PreviousZoos, [WAP.Name])
345         self.assertEqual(millipede.LastEscape, None)
346        
347         # Test that strings in a list get decoded correctly.
348         # See http://projects.amor.org/dejavu/ticket/50
349         tiger = box.unit(Animal, Species='Tiger')
350         self.assertEqual(tiger.PreviousZoos, ["animal\\universe"])
351        
352         # Test our 8000-byte limit.
353         # len(pickle.dumps(["f" * (8000 - 14)]) == 8000
354         slug = box.unit(Animal, Species='Slug')
355         self.assertEqual(len(slug.PreviousZoos[0]), 8000 - 14)
356        
357         # Exhibits
358         exes = box.recall(Exhibit)
359         self.assertEqual(len(exes), 2)
360         if exes[0].Name == 'The Penguin Encounter':
361             pe = exes[0]
362             tr = exes[1]
363         else:
364             pe = exes[1]
365             tr = exes[0]
366         self.assertEqual(pe.ZooID, seaworld.ID)
367         self.assertEqual(len(pe.Animals), 2)
368         self.assertEqual(float(pe.Acreage), 3.1)
369         self.assertEqual(pe.PettingAllowed, True)
370        
371         self.assertEqual(tr.ZooID, SDZ.ID)
372         self.assertEqual(len(tr.Animals), 1)
373         self.assertEqual(float(tr.Acreage), 4)
374         self.assertEqual(tr.PettingAllowed, False)
375        
376         box.flush_all()
377    
378     def test_Expressions(self):
379         box = arena.new_sandbox()
380        
381         def matches(lam, cls=Animal):
382             # We flush_all to ensure a DB hit each time.
383             box.flush_all()
384             return len(box.recall(cls, logic.Expression(lam)))
385        
386         zoos = box.recall(Zoo)
387         self.assertEqual(zoos[0].dirty(), False)
388         self.assertEqual(len(zoos), 4)
389         self.assertEqual(matches(lambda x: True), 12)
390         self.assertEqual(matches(lambda x: x.Legs == 4), 4)
391         self.assertEqual(matches(lambda x: x.Legs == 2), 5)
392         self.assertEqual(matches(lambda x: x.Legs >= 2 and x.Legs < 20), 9)
393         self.assertEqual(matches(lambda x: x.Legs > 10), 2)
394         self.assertEqual(matches(lambda x: x.Lifespan > 70), 2)
395         self.assertEqual(matches(lambda x: x.Species.startswith('L')), 2)
396         self.assertEqual(matches(lambda x: x.Species.endswith('pede')), 2)
397         self.assertEqual(matches(lambda x: x.LastEscape != None), 1)
398         self.assertEqual(matches(lambda x: x.LastEscape is not None), 1)
399         self.assertEqual(matches(lambda x: None == x.LastEscape), 11)
400        
401         # In operator (containedby)
402         self.assertEqual(matches(lambda x: 'pede' in x.Species), 2)
403         self.assertEqual(matches(lambda x: x.Species in ('Lion', 'Tiger', 'Bear')), 3)
404        
405         # Try In with cell references
406         class thing(object): pass
407         pet, pet2 = thing(), thing()
408         pet.Name, pet2.Name = 'Slug', 'Ostrich'
409         self.assertEqual(matches(lambda x: x.Species in (pet.Name, pet2.Name)), 2)
410        
411         # logic and other functions
412         self.assertEqual(matches(lambda x: dejavu.ieq(x.Species, 'slug')), 1)
413         self.assertEqual(matches(lambda x: dejavu.icontains(x.Species, 'PEDE')), 2)
414         self.assertEqual(matches(lambda x: dejavu.icontains(('Lion', 'Banana'), x.Species)), 1)
415         f = lambda x: dejavu.icontainedby(x.Species, ('Lion', 'Bear', 'Leopard'))
416         self.assertEqual(matches(f), 3)
417         name = 'Lion'
418         self.assertEqual(matches(lambda x: len(x.Species) == len(name)), 3)
419        
420         # This broke sometime in 2004. Rev 32 seems to have fixed it.
421         self.assertEqual(matches(lambda x: 'i' in x.Species), 7)
422        
423         # Test now(), today(), year(), month()
424         self.assertEqual(matches(lambda x: x.Founded != None
425                                  and x.Founded < dejavu.today(), Zoo), 3)
426         self.assertEqual(matches(lambda x: x.LastEscape == dejavu.now()), 0)
427         self.assertEqual(matches(lambda x: dejavu.year(x.LastEscape) == 2004), 1)
428         self.assertEqual(matches(lambda x: dejavu.month(x.LastEscape) == 12), 1)
429
430        
431         # Test AND, OR with cannot_represent.
432         # Notice that we reference a method ('count') which no
433         # known SM handles, so it will default back to Expr.eval().
434         self.assertEqual(matches(lambda x: 'p' in x.Species
435                                  and x.Species.count('e') > 1), 3)
436        
437         # This broke in MSAccess (storeado) in April 2005, due to a bug in
438         # db.SQLDecompiler.visit_CALL_FUNCTION (append TOS, not replace!).
439         box.flush_all()
440         e = logic.Expression(lambda x, **kw: x.LastEscape != None
441                              and x.LastEscape >= datetime.datetime(kw['Year'], 12, 1)
442                              and x.LastEscape < datetime.datetime(kw['Year'], 12, 31)
443                              )
444         e.bind_args(Year=2004)
445         units = box.recall(Animal, e)
446         self.assertEqual(len(units), 1)
447        
448         # Test wildcards in LIKE. This fails with SQLite <= 3.0.8,
449         # so make sure it's always at the end of this method so
450         # it doesn't preclude running the other tests.
451         box.flush_all()
452         units = box.recall(Zoo, logic.Expression(lambda x: "_" in x.Name))
453         self.assertEqual(len(units), 1)
454    
455     def test_Aggregates(self):
456         box = arena.new_sandbox()
457        
458         # views
459         legs = [x[0] for x in box.view(Animal, ['Legs'])]
460         legs.sort()
461         self.assertEqual(legs, [1, 2, 2, 2, 2, 2, 4, 4, 4, 4, 100, 1000000])
462        
463         expected = {'Leopard': 73.5,
464                     'Slug': .75,
465                     'Tiger': None,
466                     'Lion': None,
467                     'Bear': None,
468                     'Ostrich': 103.2,
469                     'Centipede': None,
470                     'Emperor Penguin': None,
471                     'Adelie Penguin': None,
472                     'Millipede': None,
473                     'Ape': None,
474                     }
475         for species, lifespan in box.view(Animal, ['Species', 'Lifespan']):
476             if expected[species] is None:
477                 self.assertEqual(lifespan, None)
478             else:
479                 self.assertAlmostEqual(expected[species], lifespan, places=5)
480        
481         # distinct
482         legs = box.distinct(Animal, ['Legs'])
483         legs.sort()
484         self.assertEqual(legs, [1, 2, 4, 100, 1000000])
485        
486         # This may raise a warning on some DB's.
487         f = logic.Expression(lambda x: x.Species == 'Lion')
488         escapees = box.distinct(Animal, ['Legs'], f)
489         self.assertEqual(escapees, [4])
490    
491     def test_Multirecall(self):
492         # Multirecall isn't designed with caching proxies in mind.
493         # If we use any, sweep out all their units before proceeding.
494         for store in arena.stores.itervalues():
495             if hasattr(store, "sweep_all"):
496                 store.sweep_all()
497        
498         box = arena.new_sandbox()
499        
500         f = logic.Expression(lambda z, a: z.Name == 'San Diego Zoo')
501         zooed_animals = box.recall(Zoo & Animal, f)
502         self.assertEqual(len(zooed_animals), 2)
503        
504         SDZ = box.unit(Zoo, Name='San Diego Zoo')
505         aid = 0
506         for z, a in zooed_animals:
507             self.assertEqual(id(z), id(SDZ))
508             self.assertNotEqual(id(a), aid)
509             aid = id(a)
510        
511         # Assert that multirecalls with no matching related units returns
512         # no matches for the initial class, since all joins are INNER.
513         # We're also going to test that you can combine a one-arg expr
514         # with a two-arg expr.
515         sdexpr = logic.filter(Name='San Diego Zoo')
516         leo = logic.Expression(lambda z, a: a.Species == 'Leopard')
517         zooed_animals = box.recall(Zoo & Animal, sdexpr + leo)
518         self.assertEqual(len(zooed_animals), 0)
519        
520         # Now try the same expr with INNER, LEFT, and RIGHT JOINs.
521         zooed_animals = box.recall(Zoo & Animal)
522         self.assertEqual(len(zooed_animals), 6)
523         self.assertEqual(set([(z.Name, a.Species) for z, a in zooed_animals]),
524                          set([("Wild Animal Park", "Leopard"),
525                               ("Wild Animal Park", "Lion"),
526                               ("San Diego Zoo", "Tiger"),
527                               ("San Diego Zoo", "Millipede"),
528                               ("Sea_World", "Emperor Penguin"),
529                               ("Sea_World", "Adelie Penguin")]))
530        
531         zooed_animals = box.recall(Zoo >> Animal)
532         self.assertEqual(len(zooed_animals), 12)
533         self.assertEqual(set([(z.Name, a.Species) for z, a in zooed_animals]),
534                          set([("Wild Animal Park", "Leopard"),
535                               ("Wild Animal Park", "Lion"),
536                               ("San Diego Zoo", "Tiger"),
537                               ("San Diego Zoo", "Millipede"),
538                               ("Sea_World", "Emperor Penguin"),
539                               ("Sea_World", "Adelie Penguin"),
540                               (None, "Slug"),
541                               (None, "Bear"),
542                               (None, "Ostrich"),
543                               (None, "Centipede"),
544                               (None, "Ape"),
545                               (None, "Ape"),
546                               ]))
547        
548         zooed_animals = box.recall(Zoo << Animal)
549         self.assertEqual(len(zooed_animals), 7)
550         self.assertEqual(set([(z.Name, a.Species) for z, a in zooed_animals]),
551                          set([("Wild Animal Park", "Leopard"),
552                               ("Wild Animal Park", "Lion"),
553                               ("San Diego Zoo", "Tiger"),
554                               ("San Diego Zoo", "Millipede"),
555                               ("Sea_World", "Emperor Penguin"),
556                               ("Sea_World", "Adelie Penguin"),
557                               (u'Montr\xe9al Biod\xf4me', None),
558                               ]))
559        
560         # Try a multiple-arg expression
561         f = logic.Expression(lambda a, z: a.Legs >= 4 and z.Admission < 10)
562         animal_zoos = box.recall(Animal & Zoo, f)
563         self.assertEqual(len(animal_zoos), 4)
564         names = [a.Species for a, z in animal_zoos]
565         names.sort()
566         self.assertEqual(names, ['Leopard', 'Lion', 'Millipede', 'Tiger'])
567        
568         # Let's try three joined classes just for the sadistic fun of it.
569         tree = (Animal >> Zoo) >> Vet
570         f = logic.Expression(lambda a, z, v: z.Name == 'Sea_World')
571         self.assertEqual(len(box.recall(tree, f)), 2)
572        
573         # MSAccess can't handle an INNER JOIN nested in an OUTER JOIN.
574         # Test that this fails for MSAccess, but works for other SM's.
575         trees = []
576         def make_tree():
577             trees.append( (Animal & Zoo) >> Vet )
578         warnings.filterwarnings("ignore", category=dejavu.StorageWarning)
579         try:
580             make_tree()
581         finally:
582             warnings.filters.pop(0)
583        
584         azv = []
585         def set_azv():
586             f = logic.Expression(lambda a, z, v: z.Name == 'Sea_World')
587             azv.append(box.recall(trees[0], f))
588        
589         smname = arena.stores['testSM'].__class__.__name__
590         if smname in ("StorageManagerADO_MSAccess",):
591             self.assertRaises(pythoncom.com_error, set_azv)
592         else:
593             set_azv()
594             self.assertEqual(len(azv[0]), 2)
595        
596         # Try mentioning the same class twice.
597         tree = (Animal << Animal)
598         f = logic.Expression(lambda anim, mother: mother.ID != None)
599         animals = [mother.ID for anim, mother in box.recall(tree, f)]
600         self.assertEqual(animals, [11])
601    
602     def test_Editing(self):
603         # Edit
604         box = arena.new_sandbox()
605         SDZ = box.unit(Zoo, Name='San Diego Zoo')
606         SDZ.Name = 'The San Diego Zoo'
607         SDZ.Founded = datetime.date(1900, 1, 1)
608         SDZ.Opens = datetime.time(7, 30, 0)
609         SDZ.Admission = "35.00"
610         box.flush_all()
611        
612         # Test edits
613         box = arena.new_sandbox()
614         SDZ = box.unit(Zoo, Name='The San Diego Zoo')
615         self.assertEqual(SDZ.Name, 'The San Diego Zoo')
616         self.assertEqual(SDZ.Founded, datetime.date(1900, 1, 1))
617         self.assertEqual(SDZ.Opens, datetime.time(7, 30, 0))
618         if fixedpoint:
619             self.assertEqual(SDZ.Admission, fixedpoint.FixedPoint(35, 2))
620         else:
621             self.assertEqual(SDZ.Admission, 35.0)
622         box.flush_all()
623        
624         # Change it back
625         box = arena.new_sandbox()
626         SDZ = box.unit(Zoo, Name='The San Diego Zoo')
627         SDZ.Name = 'San Diego Zoo'
628         SDZ.Founded = datetime.date(1835, 9, 13)
629         SDZ.Opens = datetime.time(9, 0, 0)
630         SDZ.Admission = "0"
631         box.flush_all()
632        
633         # Test re-edits
634         box = arena.new_sandbox()
635         SDZ = box.unit(Zoo, Name='San Diego Zoo')
636         self.assertEqual(SDZ.Name, 'San Diego Zoo')
637         self.assertEqual(SDZ.Founded, datetime.date(1835, 9, 13))
638         self.assertEqual(SDZ.Opens, datetime.time(9, 0, 0))
639         if fixedpoint:
640             self.assertEqual(SDZ.Admission, fixedpoint.FixedPoint(0, 2))
641         else:
642             self.assertEqual(SDZ.Admission, 0.0)
643    
644     def test_Multithreading(self):
645 ##        print "skipped ",
646 ##        return
647    
648         # Test threads overlapping on separate sandboxes
649         f = logic.Expression(lambda x: x.Legs == 4)
650         def box_per_thread():
651             # Notice that, although we write changes in each thread,
652             # we only assert the unchanged data, since the order of
653             # thread execution can not be guaranteed.
654             box = arena.new_sandbox()
655             quadrupeds = box.recall(Animal, f)
656             self.assertEqual(len(quadrupeds), 4)
657             quadrupeds[0].Age += 1.0
658             box.flush_all()
659        
660         ts = []
661         # PostgreSQL, for example, has a default max_connections of 100.
662         for x in range(99):
663             t = threading.Thread(target=box_per_thread)
664             t.start()
665             ts.append(t)
666         for t in ts:
667             t.join()
668        
669         # Test threads overlapping on the same sandbox
670         f = logic.Expression(lambda x: x.Legs == 4)
671         box = arena.new_sandbox()
672         def threaded_recall():
673             # Notice that, although we write changes in each thread,
674             # we only assert the unchanged data, since the order of
675             # thread execution can not be guaranteed.
676             quadrupeds = box.recall(Animal, f)
677             self.assertEqual(len(quadrupeds), 4)
678             quadrupeds[0].Age += 1.0
679        
680         ts = []
681         # PostgreSQL, for example, has a default max_connections of 100.
682         for x in range(99):
683             t = threading.Thread(target=threaded_recall)
684             t.start()
685             ts.append(t)
686         for t in ts:
687             t.join()
688         box.flush_all()
689    
690     def test_Iteration(self):
691         box = arena.new_sandbox()
692        
693         # Test box.unit inside of xrecall
694         for visit in box.xrecall(Visit, logic.filter(VetID=1)):
695             firstvisit = box.unit(Visit, VetID=1, Date=Jan_1_2001)
696             self.assertEqual(firstvisit.VetID, 1)
697             self.assertEqual(visit.VetID, 1)
698        
699         # Test recall inside of xrecall
700         for visit in box.xrecall(Visit, logic.filter(VetID=1)):
701             f = logic.Expression(lambda x: x.VetID == 1 and x.ID != visit.ID)
702             othervisits = box.recall(Visit, f)
703             self.assertEqual(len(othervisits), len(every13days) - 1)
704        
705         # Test far associations inside of xrecall
706         for visit in box.xrecall(Visit, logic.filter(VetID=1)):
707             # visit.Vet is a ToOne association, so will return a unit or None.
708             vet = visit.Vet()
709             self.assertEqual(vet.ID, 1)
710    
711     def test_Engines(self):
712         box = arena.new_sandbox()
713        
714         quadrupeds = box.recall(Animal, logic.filter(Legs=4))
715         self.assertEqual(len(quadrupeds), 4)
716        
717         eng = engines.UnitEngine()
718         box.memorize(eng)
719         eng.add_rule('CREATE', 1, "Animal")
720         eng.add_rule('FILTER', 1, logic.filter(Legs=4))
721         self.assertEqual(eng.FinalClassName, "Animal")
722        
723         qcoll = eng.take_snapshot()
724         self.assertEqual(len(qcoll), 4)
725         self.assertEqual(qcoll.EngineID, eng.ID)
726        
727         eng.add_rule('TRANSFORM', 1, "Zoo")
728         self.assertEqual(eng.FinalClassName, "Zoo")
729        
730         # Sleep for a second so the Timestamps are different.
731         time.sleep(1)
732         qcoll = eng.take_snapshot()
733         self.assertEqual(len(qcoll), 2)
734         zoos = qcoll.units()
735         zoos.sort(dejavu.sort('Name'))
736        
737         SDZ = box.unit(Zoo, Name='San Diego Zoo')
738         WAP = box.unit(Zoo, Name='Wild Animal Park')
739         self.assertEqual(zoos, [SDZ, WAP])
740        
741         # Flush and start over
742         box.flush_all()
743         box = arena.new_sandbox()
744        
745         # Use the Sandbox magic recaller method
746         eng = box.UnitEngine(1)
747         self.assertEqual(len(eng.rules()), 3)
748         snaps = eng.snapshots()
749         self.assertEqual(len(snaps), 2)
750        
751         self.assertEqual(snaps[0].Type, "Animal")
752         self.assertEqual(len(snaps[0]), 4)
753        
754         self.assertEqual(snaps[1].Type, "Zoo")
755         self.assertEqual(len(snaps[1]), 2)
756         self.assertEqual(eng.last_snapshot(), snaps[1])
757        
758         # Remove the last TRANSFORM rule to see if finalclass reverts.
759         self.assertEqual(eng.FinalClassName, "Zoo")
760         eng.rules()[-1].forget()
761         self.assertEqual(eng.FinalClassName, "Animal")
762    
763     def test_Subclassing(self):
764         box = arena.new_sandbox()
765         box.memorize(Visit(VetID=21, ZooID=1, AnimalID=1))
766         box.memorize(Visit(VetID=21, ZooID=1, AnimalID=2))
767         box.memorize(Visit(VetID=32, ZooID=1, AnimalID=3))
768         box.memorize(Lecture(VetID=21, ZooID=1, Topic='Cage Cleaning'))
769         box.memorize(Lecture(VetID=21, ZooID=1, Topic='Ape Mating Habits'))
770         box.memorize(Lecture(VetID=32, ZooID=3, Topic='Your Tiger and Steroids'))
771        
772         visits = box.recall(Visit, logic.filter(ZooID=1))
773         self.assertEqual(len(visits), 5)
774        
775         box.flush_all()
776        
777         box = arena.new_sandbox()
778         visits = box.recall(Visit, logic.filter(VetID=21))
779         self.assertEqual(len(visits), 4)
780         cc = [x for x in visits
781               if getattr(x, "Topic", None) == "Cage Cleaning"]
782         self.assertEqual(len(cc), 1)
783        
784         # Checking for non-existent attributes in/from subclasses
785         # isn't supported yet.
786 ##        f = logic.filter(AnimalID=2)
787 ##        self.assertEqual(len(box.recall(Visit, f)), 1)
788 ##        self.assertEqual(len(box.recall(Lecture, f)), 0)
789    
790 ##    def test_Transactions(self):
791 ##        box = arena.new_sandbox()
792 ##        box.mark("rollback point name")
793 ##        lion = box.unit(Animal, Species=Lion)
794 ##        lion.LastEscape = datetime.datetime(2005, 12, 25, 8, 14)
795 ##        box.commit_since("rollback point name")
796    
797     def test_DB_Introspection(self):
798         s = arena.stores.values()[0]
799         if not hasattr(s, "db"):
800             return
801        
802         zootable = s.db['Zoo']
803         cols = zootable.columns
804         self.assertEqual(len(cols), 6)
805         idcol = cols['ID']
806         self.assertEqual(s.db.python_type(idcol.dbtype), int)
807        
808         # Test the automatic construction of Unit classes.
809         def test_autoclass():
810             for cls in (Zoo, Animal):
811                 t = s.db[cls.__name__]
812                 uc = s.autoclass(t, cls.__name__)
813                 self.assert_(not issubclass(uc, cls))
814                 self.assertEqual(uc.__name__, cls.__name__)
815                 for pname in uc.properties:
816                     copy = getattr(uc, pname)
817                     orig = getattr(cls, pname)
818                     self.assertEqual(copy.key, orig.key)
819                     # self.assertEqual(copy.type, orig.type)
820                     self.assertEqual(copy.default, orig.default)
821                    
822 ##                    if "sqlite" not in s.db.__class__.__name__.lower():
823 ##                        # SQLite doesn't use byte or precision limits
824                     for k, v in orig.hints.iteritems():
825                         if isinstance(v, (int, long)):
826                             self.assert_(copy.hints[k] >= v,
827                                          "%s not >= %s" % (copy.hints[k], v))
828                         else:
829                             self.assertEqual(copy.hints[k], v)
830         test_autoclass()
831         print "sync ",
832         s.sync()
833         test_autoclass()
834    
835     def testzzzz_Schema_Upgrade(self):
836         # Must run last.
837         zs = ZooSchema(arena)
838        
839         # In this first upgrade, we simulate the case where the code was
840         # upgraded, and the database schema upgrade performed afterward.
841         # The Schema.latest property is set, and upgrade() is called with
842         # no argument (which should upgrade us to "latest").
843         Animal.set_property("ExhibitID")
844         zs.latest = 2
845         zs.upgrade()
846        
847         # In this example, we simulate the developer who wants to put
848         # model changes inline with database changes (see upgrade_to_3).
849         # We do not set latest, but instead supply an arg to upgrade().
850         zs.upgrade(3)
851        
852         # Test that Animals have a new "Family" property, and an ExhibitID.
853         box = arena.new_sandbox()
854         emp = box.unit(Animal, Family='Emperor Penguin')
855         self.assertEqual(emp.ExhibitID, 'The Penguin Encounter')
856    
857     def test_numbers(self):
858         import math
859         sm = arena.stores['testSM']
860         if hasattr(sm, 'typeAdapter'):
861             num_prec_log2 = int(math.log(sm.typeAdapter.numeric_max_precision, 2))
862             float_prec = sm.typeAdapter.float_max_precision
863         else:
864             num_prec_log2 = 9
865             float_prec = 2 ** 10
866         if num_prec_log2 > NUM_PREC_LOG2:
867             num_prec_log2 = NUM_PREC_LOG2
868         box = arena.new_sandbox()
869        
870         nothing = NothingToDoWithZoos()
871         for prec in xrange(num_prec_log2 + 1):
872             p = 2 ** prec
873             for s in xrange(prec):
874                 s = 2 ** s
875                 if p <= float_prec:
876                     # Store a number of the specified binary precision/scale
877                     val = ((2 ** p) - 1) / (2 ** s)
878                     setattr(nothing, "Float%s_%s" % (p, s), float(val))
879                 if decimal:
880                     # Store a number of the specified decimal precision/scale
881                     val = "1" * p
882                     val = val[:-s] + "." + val[-s:]
883                     setattr(nothing, "Decimal%s_%s" % (p, s), decimal.Decimal(val))
884         box.memorize(nothing)
885        
886         box.flush_all()
887         nothing = box.unit(NothingToDoWithZoos)
888         for prec in xrange(num_prec_log2 + 1):
889             p = 2 ** prec
890             for s in xrange(prec):
891                 s = 2 ** s
892                 if p <= float_prec:
893                     val = ((2 ** p) - 1) / (2 ** s)
894                     actual = getattr(nothing, "Float%s_%s" % (p, s))
895                     self.assertEqual(actual, float(val),
896                                      "%s != %s prec=%s scale=%s" %
897                                      (actual, float(val), p, s))
898                 if decimal:
899                     val = "1" * p
900                     val = val[:-s] + "." + val[-s:]
901                     actual = getattr(nothing, "Decimal%s_%s" % (p, s))
902                     self.assertEqual(actual, decimal.Decimal(val),
903                                      "%s != %s prec=%s scale=%s" %
904                                      (actual, decimal.Decimal(val), p, s))
905
906
907 arena = dejavu.Arena()
908
909 def _djvlog(message, flag):
910     """Dejavu logger (writes to error.log)."""
911     if flag & arena.logflags:
912         if isinstance(message, unicode):
913             message = message.encode('utf8')
914         s = "%s %s" % (datetime.datetime.now().isoformat(), message)
915         fname = os.path.join(os.path.dirname(__file__), "djvtest.log")
916         f = open(fname, 'ab')
917         f.write(s + '\n')
918         f.close()
919
920 def init():
921     global arena
922     arena = dejavu.Arena()
923     arena.log = _djvlog
924     arena.logflags = dejavu.LOGSQL + dejavu.LOGIO
925
926
927 class ZooSchema(dejavu.Schema):
928    
929     # We set "latest" to 1 so we can test upgrading.
930     latest = 1
931    
932     def upgrade_to_2(self):
933         self.arena.add_property(Animal, "ExhibitID")
934         box = self.arena.new_sandbox()
935         for exhibit in box.recall(Exhibit):
936             for animalID in exhibit.Animals:
937                 # Use the Sandbox magic recaller method.
938                 a = box.Animal(animalID)
939                 if a:
940                     # Exhibits are identified by ZooID and Name
941                     a.ZooID = exhibit.ZooID
942                     a.ExhibitID = exhibit.Name
943         box.flush_all()
944    
945     def upgrade_to_3(self):
946         Animal.remove_property("Species")
947         Animal.set_property("Family")
948        
949         # Note that we drop this column in a separate step from step 2.
950         # If we had mixed model properties and SM properties in step 2,
951         # we could have done this all in one step. But this is a better
952         # demonstration of the possibilities. ;)
953         Exhibit.remove_property("Animals")
954         self.arena.drop_property(Exhibit, "Animals")
955        
956         self.arena.rename_property(Animal, "Species", "Family")
957
958
959 def setup(SM_class, opts):
960     """setup(SM_class, opts). Set up storage for Zoo classes."""
961     global arena
962     arena.add_store('testSM', SM_class, opts)
963     v = getattr(arena.stores['testSM'], "version", None)
964     if v:
965         print v()
966     arena.stores['testSM'].create_database()
967    
968     arena.register_all(globals())
969     engines.register_classes(arena)
970    
971     if hasattr(arena.stores['testSM'], "db"):
972         arena.stores['testSM'].sync()
973    
974     zs = ZooSchema(arena)
975     zs.upgrade()
976     zs.assert_storage()
977
978
979 def teardown():
980     """Tear down storage for Zoo classes."""
981     global arena
982     arena.shutdown()
983     for store in arena.stores.values():
984         try:
985             store.drop_database()
986         except (AttributeError, NotImplementedError):
987             pass
988     arena.stores = {}
989     arena.defaultStore = None
990
991 def run(SM_class, opts):
992     """Run the zoo fixture."""
993     try:
994         try:
995             setup(SM_class, opts)
996             suite = unittest.TestLoader().loadTestsFromTestCase(ZooTests)
997             startTime = datetime.datetime.now()
998             tools.djvTestRunner.run(suite)
999             print "Ran zoo suite in:", (datetime.datetime.now() - startTime)
1000         except:
1001             import traceback
1002             traceback.print_exc()
1003     finally:
1004         teardown()
1005
Note: See TracBrowser for help on using the browser.