"""Test generic functions""" from unittest import TestCase, makeSuite, TestSuite import operator, string from types import ClassType, InstanceType import dispatch,protocols from dispatch import * from dispatch.predicates import *; from dispatch.functions import * from protocols import Interface,advise,declareImplementation from protocols.advice import getMRO from dispatch import strategy, functions from dispatch.strategy import * class Vehicle(object): pass class LandVehicle(Vehicle): pass class WaterVehicle(Vehicle): pass class Wheeled(Interface): pass class FourWheeled(Wheeled): pass class TwoWheeled(Wheeled): pass class GasPowered: pass class HumanPowered: pass class Bicycle(HumanPowered,LandVehicle): advise(instancesProvide=[TwoWheeled]) class Hummer(GasPowered,LandVehicle): advise(instancesProvide=[FourWheeled]) class Speedboat(GasPowered,WaterVehicle): pass class PaddleBoat(HumanPowered,WaterVehicle): pass class RiverBoat(WaterVehicle): advise(instancesProvide=[TwoWheeled]) class TestGraph(TestCase): def testItems(self): g = strategy.TGraph() self.assertEqual(g.items(),[]) g.add(2,3) self.assertEqual(g.items(),[(2,3)]) g.add(1,2) items = g.items(); items.sort() self.assertEqual(items,[(1,2),(1,3),(2,3)]) g = strategy.TGraph() self.assertEqual(g.items(),[]) g.add(1,2) self.assertEqual(g.items(),[(1,2)]) g.add(2,3) items = g.items(); items.sort() self.assertEqual(items,[(1,2),(1,3),(2,3)]) g.add(1,5); g.add(2,6) items = g.items(); items.sort() self.assertEqual(items,[(1,2),(1,3),(1,5),(1,6),(2,3),(2,6)]) def testSuccessors(self): g = strategy.TGraph() g.add(1,2) self.assertEqual(g.successors([1]),{2:1}) self.assertEqual(g.successors([2]),{}) self.assertEqual(g.successors([3]),{}) g.add(2,3) self.assertEqual(g.successors([1]),{2:1, 3:1}) self.assertEqual(g.successors([2]),{3:1}) self.assertEqual(g.successors([1,2]),{2:1, 3:1}) self.assertEqual(g.successors([3]),{}) g.add(3,1) self.assertEqual(g.successors([1,2,3]),{}) items = g.items(); items.sort() self.assertEqual(items, [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)]) class CriteriaTests(TestCase): def testClassCriteriaMembership(self): hp = ISeededCriterion(HumanPowered) self.failUnless(PaddleBoat in hp) self.failUnless(Bicycle in hp) self.failIf(Vehicle in hp) self.failIf(Speedboat in hp) self.failIf(Hummer in hp) self.failIf(object in hp) it = ISeededCriterion(InstanceType) ob = ISeededCriterion(object) for klass in (GasPowered,HumanPowered): self.failUnless(klass in it) self.failUnless(klass in ob) for klass in (Vehicle,LandVehicle,WaterVehicle,Bicycle,Hummer, Speedboat,PaddleBoat ): self.failIf(klass in it) self.failUnless(klass in ob) def testTestImplication(self): self.failUnless(ICriterion(Bicycle).implies(Wheeled)) self.failUnless(ICriterion(PaddleBoat).implies(HumanPowered)) self.failUnless(ICriterion(Hummer).implies(FourWheeled)) self.failUnless(ICriterion(Hummer).implies(LandVehicle)) self.failUnless(ICriterion(Speedboat).implies(Vehicle)) self.failUnless(ICriterion(Wheeled).implies(object)) self.failUnless(ICriterion(GasPowered).implies(InstanceType)) self.failUnless(ICriterion(Wheeled).implies(Vehicle)) self.failIf(ICriterion(object).implies(Speedboat)) def testNullCriterion(self): # Null test has no seeds self.failIf(list(NullCriterion.seeds())) # and it matches anything self.failUnless(object in NullCriterion) self.failUnless(Speedboat in NullCriterion) # is implied by everything self.failUnless(ICriterion(Vehicle).implies(NullCriterion)) # and implies nothing self.failIf(NullCriterion.implies(object)) def testClassCriteriaSeedsAndDispatchFunctions(self): for klass in (Vehicle,LandVehicle,WaterVehicle,HumanPowered,GasPowered): seeds = list(ISeededCriterion(klass).seeds()) self.failUnless(klass in seeds) self.failUnless(object in seeds) self.failIf(len(seeds)<>2) validateCriterion(klass, strategy.make_node_type(strategy.dispatch_by_mro), parents=[ICriterion(cls) for cls in getMRO(klass,True)]) def testCriterionAdaptation(self): self.failUnless(Hummer in ISeededCriterion(Wheeled)) self.failIf(ICriterion(Hummer).implies(Speedboat)) self.failUnless(ICriterion(Speedboat).implies(WaterVehicle)) self.failUnless(object in list(ISeededCriterion(InstanceType).seeds())) def testProtocolCriterion(self): self.failUnless(Bicycle in ISeededCriterion(Wheeled)) seeds = list(ISeededCriterion(Wheeled).seeds()) self.failUnless(Hummer in seeds) self.failUnless(Bicycle in seeds) self.failUnless(object in seeds) self.failUnless(len(seeds)==4) class BrokenBike(Bicycle): advise(instancesDoNotProvide=[Wheeled]) self.failIf(BrokenBike in ISeededCriterion(Wheeled)) def testSignatures(self): a0 = Argument(0); a1 = Argument(1) d1 = {a0:ICriterion(LandVehicle), a1:ICriterion(WaterVehicle)} d2 = {a0:ICriterion(Hummer), a1:ICriterion(Speedboat)} d3 = {a0:ICriterion(WaterVehicle), a1:ICriterion(LandVehicle)} d4 = {a0:ICriterion(LandVehicle), a1:ICriterion(LandVehicle)} for d in d1,d2,d3,d4: self.assertEqual( dict(Signature(d.items()).items()), dict([((k,v.node_type),v) for k,v in d.items()]) ) s1 = Signature(d1.items()) s2 = Signature(d2.items()) s3 = Signature(d3.items()) s4 = Signature(d4.items()) s5 = PositionalSignature( (ICriterion(LandVehicle),ICriterion(WaterVehicle), ICriterion(object)) ) self.failUnless(s2.implies(s1)); self.failIf(s1.implies(s2)) self.failUnless(s5.implies(s1)); self.failIf(s1.implies(s3)) self.failIf(s1.implies(s4)); self.failIf(s2.implies(s3)) self.failIf(s2.implies(s4)); self.failIf(s1.implies(s5)) all_sigs = [(s1,0),(s2,0),(s3,0),(s4,0),(s5,0)] self.assertEqual( most_specific_signatures(all_sigs), [(s2,0),(s3,0),(s4,0),(s5,0)] ) self.assertEqual( most_specific_signatures([(s1,0),(s2,0)]), [(s2,0)] ) self.assertEqual( list(ordered_signatures(all_sigs)), [[(s2,0),(s3,0),(s4,0),(s5,0)],[(s1,0)]] ) self.assertEqual( list(ordered_signatures([(s1,0),(s2,0)])), [[(s2,0)],[(s1,0)]] ) def testMinMax(self): self.failUnless(Min < Max) self.failUnless(Max > Min) self.failUnless(Max == Max) self.failUnless(Min == Min) self.failIf(Min==Max or Max==Min) self.failUnless(Max > "xyz") self.failUnless(Min < "xyz") self.failUnless(Max > 999999) self.failUnless(Min < -999999) data = [(27,Max),(Min,99),(53,Max),(Min,27),(53,56)] data.sort() self.assertEqual(data, [(Min,27),(Min,99),(27,Max),(53,56),(53,Max)] ) class X: """Ensure rich comparisons work correctly with classic classes""" x = X() for v1,v2 in [(Min,x),(x,Max)]: self.failUnless(v1 < v2) self.failUnless(v1 <= v2) self.failIf(v1 == v2) self.failUnless(v1 != v2) self.failUnless(v2 > v1) self.failUnless(v2 >= v2) def testIdentityDispatch(self): ob1, ob2, ob3 = object(),object(),object() id1, id2, id3 = map(id, [ob1,ob2,ob3]) table = {id1:1,id2:2,None:3} self.assertEqual(strategy.dispatch_by_identity(table, Vehicle), 3) self.assertEqual(strategy.dispatch_by_identity(table, ob1), 1) self.assertEqual(strategy.dispatch_by_identity(table, ob2), 2) self.assertEqual(strategy.dispatch_by_identity(table, ob3), 3) def testTruth(self): for t in True,False: validateCriterion(TruthCriterion(t), strategy.make_node_type(strategy.dispatch_by_truth)) def testPointers(self): anOb = object() ptr = Pointer(anOb) self.assertEqual(id(anOb),ptr) self.assertEqual(hash(id(anOb)),hash(ptr)) class X: pass anOb = X() ptr = Pointer(anOb) oid = id(anOb) self.assertEqual(oid,ptr) self.assertEqual(hash(oid),hash(ptr)) del anOb self.assertNotEqual(oid,ptr) self.assertEqual(ptr,ptr) self.assertEqual(hash(oid),hash(ptr)) def testIdentityCriterion(self): ob = object() i = Pointer(ob) validateCriterion(i, strategy.make_node_type(strategy.dispatch_by_identity)) i = ISeededCriterion(i) self.assertEqual(list(i.seeds()),[None,id(ob)]) def testSeededIndex(self): i = SeededIndex(None) # XXX i[SubclassCriterion(int)] = 42 self.assertEqual(i.casemap_for([42]), {int:[42],None:[]}) i.addSeed(int) # make sure seed isn't duplicated self.assertEqual(i.casemap_for([42]), {int:[42],None:[]}) i = SeededIndex(None) # XXX def testSubclassCriterion(self): s = SubclassCriterion(Vehicle) validateCriterion(s, strategy.make_node_type(strategy.dispatch_by_subclass), parents=[SubclassCriterion(c) for c in getMRO(Vehicle,True)] ) # seeds() self.assertEqual( s.seeds(), [Vehicle,None]) # __contains__ for klass in Vehicle,LandVehicle,WaterVehicle: self.failUnless(klass in s) for klass in None,GasPowered,object,Wheeled: self.failUnless(klass not in s) # implies() self.failUnless( s.implies(SubclassCriterion(object)) ) self.failUnless( SubclassCriterion(LandVehicle).implies(s) ) self.failIf( s.implies(SubclassCriterion(LandVehicle)) ) self.failIf( SubclassCriterion(object).implies(s) ) # eq/ne/invert self.assertEqual( s, SubclassCriterion(Vehicle)) self.assertNotEqual( s, SubclassCriterion(LandVehicle)) # matches() table = {LandVehicle:1,object:2,None:3} items = list(s.matches(table)) self.assertEqual(items,[LandVehicle]) # dispatch table = {Vehicle:1,object:2,None:3} self.assertEqual(strategy.dispatch_by_subclass(table, Vehicle), 1) self.assertEqual(strategy.dispatch_by_subclass(table, LandVehicle), 1) self.assertEqual(strategy.dispatch_by_subclass(table, object), 2) self.assertEqual(strategy.dispatch_by_subclass(table, None), 3) self.assertRaises(AttributeError, strategy.dispatch_by_subclass, table, Bicycle) def testInequalities(self): self.assertRaises(ValueError, Inequality, '', 1) self.assertRaises(ValueError, Inequality, 'xyz', 2) t1 = Inequality('>',55); t2 = Inequality('>=',100) self.failIf( (55,55) in t1 ) self.failIf( (55,55) in t2 ) self.failUnless( (100,100) in t2 ) self.failUnless( (100,100) in t1 ) self.failUnless( (101,101) in t2 ) self.failUnless( (110,Max) in t2 ) self.failUnless(t2.implies(t1)) self.failIf(t1.implies(t2)) t3 = Inequality('<',99) self.failIf(t1.implies(t3) or t2.implies(t3)) self.failIf(t3.implies(t1) or t3.implies(t2)) t4 = Inequality('<',"abc") self.failUnless(("a","a") in t4); self.failIf(("b","b") in t4) t5 = Inequality('==',99) i = strategy.InequalityIndex() for t in t1,t2,t3,t4,t5: validateCriterion(t,Inequality.node_type,seeded=False) i[t] = repr(t) ct, size = i.count_for(map(repr,[t1,t2,t3,t4,t5])) # Min, ..., 55, ..., 99, ..., 100, ..., 'abc', ..., Max self.assertEqual(ct, 11) # >55: 8, >=100: 5, <99: 4, <'abc': 8, ==99: 1 ---> 26 self.assertEqual(size,26) # Test intersection of overlapping open intervals t6=Inequality('>=',6); t8=Inequality('<=',8); t68 = t6&t8 self.assertEqual(t6.ranges, ((6,6),(6,Max))) self.assertEqual(t8.ranges, ((Min,8),(8,8))) self.assertEqual(t68.ranges, ((6,6),(6,8),(8,8))) def testInequalitySeeds(self): self.assertEqual( strategy.concatenate_ranges( {(Min,27):[], (27,27):[], (27,Max):[], (Min,19):[], (19,19):[], (19,27): [], } ), [(Min,19),(19,27),(27,Max)], ) self.assertEqual( strategy.concatenate_ranges( {(Min,19):[], (27,27):[], (19,Max):[], (19,27):[], (19,19):[], (27,Max):[], } ), [(Min,19),(19,27),(27,Max)], ) def testClasslessDispatch(self): class Classic: pass # Classic classes have no __class_ attribute strategy.dispatch_by_mro({},Classic) def testInequalityDispatch(self): classify = GenericFunction(lambda age:None) classify[(Inequality('<',2),)] = lambda age:"infant" classify[(Inequality('<',13),)] = lambda age:"preteen" classify[(Inequality('<',5),)] = lambda age:"preschooler" classify[(Inequality('<',20),)] = lambda age:"teenager" classify[(Inequality('>=',20),)] = lambda age:"adult" classify[(Inequality('>=',55),)] = lambda age:"senior" classify[(Inequality('=',16),)] = lambda age:"sweet sixteen" self.assertEqual(classify(25),"adult") self.assertEqual(classify(17),"teenager") self.assertEqual(classify(13),"teenager") self.assertEqual(classify(12.99),"preteen") self.assertEqual(classify(0),"infant") self.assertEqual(classify(4),"preschooler") self.assertEqual(classify(55),"senior") self.assertEqual(classify(54.9),"adult") self.assertEqual(classify(14.5),"teenager") self.assertEqual(classify(16),"sweet sixteen") self.assertEqual(classify(16.5),"teenager") self.assertEqual(classify(99),"senior") self.assertEqual(classify(Min),"infant") self.assertEqual(classify(Max),"senior") def testTruth(self): self.assertEqual(TruthCriterion(27), TruthCriterion("abc")) self.assertNotEqual(TruthCriterion(1), TruthCriterion(False)) self.failUnless(True in TruthCriterion(1)) self.failUnless(False not in TruthCriterion(1)) self.failUnless(True not in TruthCriterion(0)) self.failUnless(False in TruthCriterion(0)) self.failIf(TruthCriterion(1).implies(TruthCriterion(0))) self.failIf(TruthCriterion(0).implies(TruthCriterion(1))) self.failUnless(TruthCriterion(0).implies(TruthCriterion(0))) self.failUnless(TruthCriterion(1).implies(TruthCriterion(1))) self.assertEqual(TruthCriterion(42).seeds(), (True,False)) self.assertEqual(TruthCriterion(None).seeds(), (True,False)) def testAndOr(self): equals_two = Inequality('==',2) less_than_four = Inequality('<',4) lo_primes = Inequality('>',1) & less_than_four #2, 3 odd_primes = (~equals_two) & lo_primes self.failIf((4,4) in lo_primes) self.failUnless((3,3) in lo_primes) even_primes = equals_two & less_than_four self.failUnless((3,3) in less_than_four) self.failIf((4,4) in less_than_four) self.failIf((3,3) in even_primes) self.failUnless((2,2) in even_primes) self.failUnless(even_primes.implies(equals_two)) self.failUnless(even_primes.implies(less_than_four)) self.failUnless(even_primes.implies(NullCriterion)) self.assertRaises(protocols.AdaptationFailure, lambda: TruthCriterion(1) & Inequality('==',1) ) def testRangeIntersection(self): ten_to_twenty = Inequality('>=',10)&Inequality('<=',20) fifteen_to_nineteen = ( Inequality('>=',15) & Inequality('<=',19)) self.failUnless( (5,5) not in ten_to_twenty ) self.failUnless( (5,5) not in fifteen_to_nineteen ) self.failUnless( (15,15) in ten_to_twenty ) self.failUnless( (15,15) in fifteen_to_nineteen ) self.failUnless( (10,10) in ten_to_twenty ) self.failUnless( (16,17) in fifteen_to_nineteen) self.failUnless( fifteen_to_nineteen.implies(ten_to_twenty) ) self.failIf(ten_to_twenty.implies(fifteen_to_nineteen)) self.failUnless( (~ten_to_twenty).implies(~fifteen_to_nineteen) ) self.failIf( (~fifteen_to_nineteen).implies(~ten_to_twenty) ) for item in fifteen_to_nineteen, ten_to_twenty: self.failUnless( item.implies(NullCriterion) ) self.failUnless( (~item).implies(NullCriterion) ) def testClassIntersections(self): self.failUnless( Hummer in AndCriterion(LandVehicle,GasPowered) ) self.failUnless( Speedboat in NotCriterion(LandVehicle) ) self.failUnless( AndCriterion(LandVehicle,GasPowered).implies( GasPowered) ) # This implication doesn't hold true because RiverBoat is a Wheeled # non-LandVehicle; if Riverboat didn't exist the implication would hold self.failIf( NotCriterion(LandVehicle).implies(NotCriterion(Wheeled)) ) def testSimplifications(self): self.assertEqual((~TruthCriterion(1)), TruthCriterion(0)) self.assertEqual((~(~TruthCriterion(1))), TruthCriterion(27)) self.assertEqual( Inequality('>=',10) & Inequality('<=',20) & Inequality('==',15), Inequality('..', [(15,15)]) ) def testInequalityInverses(self): self.assertEqual(~Inequality(">=",27), Inequality("<",27)) for op,rev in strategy.rev_ops.items(): self.assertEqual(Inequality(op,27), ~Inequality(rev,27)) def testTruthDispatch(self): x_gt_y = Call(operator.gt, Argument(name='x'), Argument(name='y')) greater = GenericFunction(lambda x,y:None) greater[Signature([(x_gt_y, TruthCriterion(False))])]= lambda x,y:False greater[Signature([(x_gt_y, TruthCriterion(True))])] = lambda x,y:True self.failIf(greater(1,10)) self.failIf(greater(1,1)) self.failUnless(greater(2,1)) def testSignatureArithmetic(self): x_gt_10 = Signature(x=Inequality('>',10)) x_lt_20 = Signature(x=Inequality('<',20)) y_in_LandVehicle = Signature(y=LandVehicle) empty = Signature() self.assertEqual((x_gt_10 & x_lt_20), Signature(x=Inequality('>',10) & Inequality('<',20)) ) self.assertEqual((x_gt_10 & y_in_LandVehicle), Signature(x=Inequality('>',10),y=LandVehicle) ) self.assertEqual((x_gt_10 & x_gt_10), x_gt_10) self.assertEqual((x_gt_10 & empty), x_gt_10) self.assertEqual((empty & x_gt_10), x_gt_10) self.assertEqual((x_gt_10 | empty), empty) self.assertEqual((empty | x_gt_10), empty) self.assertEqual((x_gt_10 | x_lt_20), Predicate([x_gt_10, x_lt_20])) self.assertEqual((x_gt_10 | y_in_LandVehicle), Predicate([x_gt_10,y_in_LandVehicle]) ) # sig | pred self.assertEqual((x_gt_10 | Predicate([y_in_LandVehicle])), Predicate([x_gt_10,y_in_LandVehicle]) ) # sig & pred self.assertEqual((x_gt_10 & Predicate([y_in_LandVehicle])), Predicate([x_gt_10 & y_in_LandVehicle])) # pred & sig self.assertEqual((Predicate([y_in_LandVehicle]) & x_gt_10), Predicate([x_gt_10 & y_in_LandVehicle])) # pred | pred self.assertEqual((Predicate([x_gt_10]) | Predicate([y_in_LandVehicle])), Predicate([x_gt_10, y_in_LandVehicle]) ) # pred & pred self.assertEqual((Predicate([x_gt_10]) & Predicate([y_in_LandVehicle])), Predicate([x_gt_10 & y_in_LandVehicle]) ) # Ensure ordering preserved among conditions within a signature self.assertNotEqual( (x_gt_10 & y_in_LandVehicle).items(), (y_in_LandVehicle & x_gt_10).items()) self.assertNotEqual( (x_gt_10 & y_in_LandVehicle & x_lt_20).items(), (y_in_LandVehicle & x_gt_10 & x_lt_20).items()) def testSignatureOrdering(self): gt_10 = Argument(0),Inequality('>',10) lt_20 = Argument(1),Inequality('<',20) for data in [gt_10,lt_20], [lt_20,gt_10]: # Verify both the raw signature, and an 'and'-ed version for s in Signature(data), Signature(data[:1])&Signature(data[1:]): self.assertEqual(s.items(), [((k,v.node_type),v) for k,v in data] ) def testIndexEnumerables(self): i = SeededIndex(strategy.dispatch_by_mro) i[ClassCriterion(LandVehicle)] = 1 self.assertEqual(i.casemap_for([1]), {LandVehicle:[1], object:[]}) i[ClassCriterion(Bicycle)] = 2 self.assertEqual(i.casemap_for([2]), {Bicycle:[2], object:[], LandVehicle:[]}) i[ClassCriterion(HumanPowered)] = 3 self.assertEqual(i.casemap_for([3]), {Bicycle:[3], object:[], LandVehicle:[], HumanPowered:[3]}) def testIndexAnd(self): i = SeededIndex(strategy.dispatch_by_mro) i[ClassCriterion(LandVehicle) & ClassCriterion(HumanPowered)] = 1 self.assertEqual(i.casemap_for([1]), {HumanPowered:[], object:[], LandVehicle:[]}) i[ClassCriterion(Bicycle)] = 2 self.assertEqual(i.casemap_for([1,2]), {HumanPowered:[], object:[], LandVehicle:[], Bicycle:[1,2]}) i[ClassCriterion(LandVehicle)] = 3 self.assertEqual(i.casemap_for([1,2,3]), {HumanPowered:[], object:[], LandVehicle:[3], Bicycle:[1,2,3]}) def testIndexNot(self): i = SeededIndex(strategy.dispatch_by_mro) i[ClassCriterion(LandVehicle)] = 1 i[~ClassCriterion(Bicycle)] = 2 self.assertEqual(i.casemap_for([1,2]), {object:[2], LandVehicle:[1,2], Bicycle:[1]}) i[ClassCriterion(Bicycle)] = 3 self.assertEqual(i.casemap_for([1,2,3]), {object:[2], LandVehicle:[1,2], Bicycle:[1,3]}) class ExpressionTests(TestCase): def testArgumentBasics(self): self.assertRaises(ValueError, Argument) # must specify name or posn self.failUnless(Argument(0) == Argument(0)) self.failIf( Argument(0) == Argument(1)) self.failUnless(Argument(name="x") == Argument(name="x")) self.failIf( Argument(name="x") == Argument(name="y")) self.failIf( Argument(name="x") == Argument(1,"x")) self.failIf( Argument(1,"x") == Argument(name="x")) self.failIf( Argument(1) == Argument(1,"x")) self.failIf( Argument(1,"x") == Argument(1)) self.failUnless(Argument(0,"x") == Argument(0,"x")) self.failIf( Argument(0,"x") == Argument(0,"y")) self.failIf( Argument(0,"x") == Argument(1,"x")) self.failIf( Argument(0,"x") == Argument(1,"y")) a1 = Argument(0,"x"); a2 = Argument(0,"x") self.assertEqual(hash(a1), hash(a2)) a1 = Argument(1); a2 = Argument(1) self.assertEqual(hash(a1), hash(a2)) a1 = Argument(name="x"); a2 = Argument(name="x") self.assertEqual(hash(a1), hash(a2)) def testArgumentCanonicalization(self): f = GenericFunction(lambda v1,v2:None) self.assertEqual( f.getExpressionId(Argument(name='v1')), f.getExpressionId(Argument(0)) ) self.assertEqual( f.getExpressionId(Argument(name='v2')), f.getExpressionId(Argument(1)) ) def testCalls(self): self.assertEqual(Call(operator.add,1,2), Call(operator.add,1,2)) self.assertNotEqual(Call(operator.sub,1,2), Call(operator.add,1,2)) self.assertNotEqual(Call(operator.add,2,1), Call(operator.add,1,2)) c1 = Call(operator.add, Argument(name='x'), Argument(name='y')) c2 = Call(operator.add, Argument(name='x'), Argument(name='y')) self.assertEqual(hash(c1), hash(c2)) c3 = Call(operator.sub, Argument(name='x'), Argument(name='y')) self.assertNotEqual(hash(c1), hash(c3)) f = GenericFunction(lambda x,y:None) self.assertEqual(f.getExpressionId(c1), f.getExpressionId(c2)) self.assertNotEqual(f.getExpressionId(c1), f.getExpressionId(c3)) self.assertEqual( f.getExpressionId(c3), f.getExpressionId( Call(operator.sub, Argument(name='x'), Argument(name='y')) ) ) # Make the function handle 'x+y > 100' f[Signature([(c1,Inequality('>',100))])] = lambda x,y: "yes" f[Signature([])] = lambda x,y: "no" self.assertEqual(f(51,49), "no") self.assertEqual(f(99,10), "yes") self.assertEqual(f(27,89), "yes") def testConsts(self): f = GenericFunction(lambda x:None) x_plus_two = Call(operator.add,Argument(name='x'),Const(2)) f[Signature([(x_plus_two,Inequality('>',10))])] = lambda x: True f[Signature([])] = lambda x: False self.failUnless(f(9)) self.failIf(f(8)) foo, bar, fourA, fourB = Const("foo"),Const("bar"),Const(4),Const(4) self.assertEqual(fourA,fourB) self.assertEqual(hash(fourA),hash(fourB)) self.assertNotEqual(bar,foo) self.assertNotEqual(hash(bar),hash(foo)) def testGetattr(self): vehicle_mpg = Getattr(Argument(name='v'),'mpg') test_mpg = lambda test,val: (vehicle_mpg,Inequality(test,val)) fuel_efficient = GenericFunction(lambda v:None) fuel_efficient[Signature([test_mpg('==','N/A')])] = lambda v: True fuel_efficient[Signature([test_mpg('>',35)])] = lambda v: True fuel_efficient[Signature([])] = lambda v: False b=Bicycle(); b.mpg = 'N/A'; h=Hummer(); h.mpg = 10 self.failUnless(fuel_efficient(b)) self.failIf(fuel_efficient(h)) vm2 = Getattr(Argument(name='v'),'mpg') xm = Getattr(Argument(name='x'),'mpg') vg = Getattr(Argument(name='v'),'gpm') self.assertEqual(vehicle_mpg, vm2) self.assertEqual(hash(vehicle_mpg), hash(vm2)) for item in xm,vg: self.assertNotEqual(vehicle_mpg, item) self.assertNotEqual(hash(vehicle_mpg), hash(item)) def testTuple(self): xy = Tuple(tuple,Argument(name='x'),Argument(name='y')) xy_is_one_two = GenericFunction(lambda x,y:None) xy_is_one_two[Signature([(xy,Inequality('==',(1,2)))])]=lambda x,y:True xy_is_one_two[Signature([])] = lambda x,y: False self.failUnless(xy_is_one_two(1,2)) self.failIf(xy_is_one_two(1,3)) self.failIf(xy_is_one_two(2,1)) xy2 = Tuple(tuple,Argument(name='x'),Argument(name='y')) yx = Tuple(tuple,Argument(name='y'),Argument(name='x')) lx = Tuple(list,Argument(name='x'),Argument(name='y')) zz = Tuple(tuple,Argument(name='z'),Argument(name='z')) self.assertEqual(xy, xy2) self.assertEqual(hash(xy), hash(xy2)) for item in yx,lx,zz: self.assertNotEqual(xy, item) self.assertNotEqual(hash(xy), hash(item)) def testOrExpr(self): x, y = Argument(name='x'), Argument(name='y') z = Call(operator.div,Argument(name='y'),Argument(name='z')) xyz = OrExpr(x,y,z) or_ = GenericFunction(lambda x,y,z:None) or_[Signature([(xyz,TruthCriterion())])] = lambda x,y,z:True or_[Signature([])] = lambda x,y,z: False self.failUnless(or_(1,0,1)) self.failIf(or_(0,0,1)) self.assertRaises(ZeroDivisionError,or_,0,0,0) zyx = OrExpr(z,y,x) xyz2 = OrExpr(x,y,z) xy = OrExpr(x,y) self.assertEqual(xyz, xyz2) self.assertEqual(hash(xyz), hash(xyz2)) for item in xy,zyx: self.assertNotEqual(xyz, item) self.assertNotEqual(hash(xyz), hash(item)) or_eq_23 = GenericFunction(lambda x,y:None) or_eq_23[Signature([(xy,Inequality('==',23))])] = lambda x,y:True or_eq_23[Signature([])] = lambda x,y: False self.failUnless(or_eq_23(23,0)) self.failUnless(or_eq_23(0,23)) self.failIf(or_eq_23(0,0)) self.failIf(or_eq_23(15,15)) or_eq_None = GenericFunction(lambda x,y:None) or_eq_None[Signature([(xy,Inequality('==',None))])] = lambda x,y:True or_eq_None[Signature([])] = lambda x,y: False self.failUnless(or_eq_None(None,None)) self.failUnless(or_eq_None(0,None)) self.failIf(or_eq_None(1,None)) self.failIf(or_eq_None(None,1)) def testAndExpr(self): x, y = Argument(name='x'), Argument(name='y') z = Call(operator.div,Argument(name='y'),Argument(name='z')) xyz = AndExpr(x,y,z) and_ = GenericFunction(lambda x,y,z:None) and_[Signature([(xyz,TruthCriterion())])] = lambda x,y,z:True and_[Signature([])] = lambda x,y,z: False self.failUnless(and_(True,True,True)) self.failIf(and_(False,True,True)) self.failIf(and_(False,27,0)) self.assertRaises(ZeroDivisionError,and_,15,27,0) zyx = AndExpr(z,y,x) xyz2 = AndExpr(x,y,z) xy = AndExpr(x,y) self.assertEqual(xyz, xyz2) self.assertEqual(hash(xyz), hash(xyz2)) for item in xy,zyx: self.assertNotEqual(xyz, item) self.assertNotEqual(hash(xyz), hash(item)) and_eq_23 = GenericFunction(lambda x,y:None) and_eq_23[Signature([(xy,Inequality('==',23))])] = lambda x,y:True and_eq_23[Signature([])] = lambda x,y: False self.failUnless(and_eq_23(3,23)) self.failUnless(and_eq_23(23,23)) self.failIf(and_eq_23(23,15)) self.failIf(and_eq_23(23,0)) and_eq_None = GenericFunction(lambda x,y:None) and_eq_None[Signature([(xy,Inequality('==',None))])] = lambda x,y:True and_eq_None[Signature([])] = lambda x,y: False self.failUnless(and_eq_None(None,None)) self.failUnless(and_eq_None(1,None)) self.failIf(and_eq_None(0,1)) self.failIf(and_eq_None(1,0)) class SimpleGenerics(TestCase): def testTrivialities(self): [dispatch.on('x')] def f1(x,*y,**z): "foo bar" [dispatch.on('x')] def f2(x,*y,**z): "baz spam" for f,doc in (f1,"foo bar"),(f2,"baz spam"): self.assertEqual(f.__doc__, doc) # Empty generic should raise NoApplicableMethods self.assertRaises(dispatch.NoApplicableMethods, f, 1, 2, 3) self.assertRaises(dispatch.NoApplicableMethods, f, "x", y="z") # Must have at least one argument to do dispatching self.assertRaises(TypeError, f) self.assertRaises(TypeError, f, foo="bar") def testSimpleDefinitions(self): [dispatch.on('xx')] def g(xx,*y,**z): pass class Classic: pass class NewStyle(object): pass class IFoo(protocols.Interface): pass class Impl: protocols.advise(instancesProvide=[IFoo]) c=Classic() n=NewStyle() i=Impl() for item in c,n,i,1,"blue",TestCase: self.assertRaises(dispatch.NoApplicableMethods, g, item) g.addMethod(Classic,lambda *args,**kw: ("classic!",args,kw)) g.addMethod(NewStyle,lambda *args,**kw: ("new!",args,kw)) g.addMethod(IFoo,lambda *args,**kw: ("foo!",args,kw)) self.assertEqual(g(c,"foo"), ("classic!",(c,"foo",),{})) self.assertEqual(g(n,foo="bar"), ("new!",(n,),{'foo':'bar'})) self.assertEqual(g(i,"foo",x="y"), ("foo!",(i,"foo",),{"x":"y"})) for item in 1,"blue",TestCase: self.assertRaises(dispatch.NoApplicableMethods, g, item) def testMultiDefinition(self): class Classic: pass class NewStyle(object): pass class IFoo(protocols.Interface): pass class Impl: protocols.advise(instancesProvide=[IFoo]) c=Classic() n=NewStyle() i=Impl() [dispatch.on('xx')] def g(xx,q=27,*y,**z): pass [g.when([Classic,NewStyle,IFoo])] def g(*args,**kw): return ("yes!",args,kw) self.assertEqual(g(c,"foo"), ("yes!",(c,"foo",),{})) self.assertEqual(g(n,foo="bar"), ("yes!",(n,27),{'foo':'bar'})) self.assertEqual(g(i,"foo",x="y"), ("yes!",(i,"foo",),{"x":"y"})) for item in 1,"blue",TestCase: self.assertRaises(dispatch.NoApplicableMethods, g, item) def testAdaptedDefinition(self): class Classic: pass class IFoo(protocols.Interface): pass class A(protocols.Adapter): protocols.advise( instancesProvide=[IFoo],asAdapterForTypes=[Classic] ) [dispatch.on('xx')] def g(xx,*y,**z): pass [g.when(IFoo)] def g(thing, *args,**kw): return thing c=Classic(); it = g(c) self.assertNotEqual(it, c) self.failUnless(IFoo(it) is it) def testWhenMethods(self): m = GenericFunction(lambda v:None) m.when(Signature(v=LandVehicle)) def foo(v): return "land" import types self.failUnless(isinstance(m,GenericFunction)) self.failUnless(isinstance(foo,types.FunctionType)) m.when(Signature(v=WaterVehicle)) def m(v): return "water" self.failUnless(isinstance(m,types.FunctionType)) self.assertEqual(m(LandVehicle()),"land") self.assertEqual(m(WaterVehicle()),"water") [dispatch.on('v')] def s(v): """Blah""" [s.when(LandVehicle)] def bar(v): return "land" self.failUnless(hasattr(s,'when')) self.failUnless(isinstance(bar,types.FunctionType)) [s.when(WaterVehicle)] def s(v): return "water" self.failUnless(hasattr(s,'when')) self.assertEqual(s(LandVehicle()),"land") self.assertEqual(s(WaterVehicle()),"water") def testAltArg(self): [dispatch.on('v')] def s(x,v): """X""" [s.when(LandVehicle)] def bar(x,v): return "land" [s.when(WaterVehicle)] def s(x,v): return "water" self.assertEqual(s(None,LandVehicle()),"land") self.assertEqual(s(None,v=WaterVehicle()),"water") def testInstanceMethod(self): class X: [dispatch.on('v')] def s(x,v): """X""" [s.when(LandVehicle)] def bar(x,v): return "land" [s.when(WaterVehicle)] def s(x,v): return "water" class Y: s = X.s.clone() [s.when(WaterVehicle)] def s(x,v): return "splash!" self.assertEqual(X().s(v=LandVehicle()),"land") self.assertEqual(X().s(WaterVehicle()),"water") self.assertEqual(Y().s(WaterVehicle()),"splash!") class GenericTests(TestCase): def testBasicSingleDispatch(self): m = GenericFunction(lambda v:None) m[(LandVehicle,)] = lambda v: "land" m[(WaterVehicle,)] = lambda v: "water" self.assertEquals(m(Hummer()), "land") self.assertEquals(m(Speedboat()), "water") self.assertRaises(NoApplicableMethods, m, GasPowered()) def testSimpleDoubleDispatchAndNamedArgs(self): faster = GenericFunction(lambda v1,v2:None) faster[Signature(v1=GasPowered,v2=HumanPowered)] = lambda v1,v2: True faster[Signature(v1=Hummer,v2=Speedboat)] = lambda v1,v2: True faster[(object,object)] = lambda v1,v2: "dunno" faster[Signature(v1=HumanPowered,v2=GasPowered)] = lambda v1,v2: False faster[Signature(v2=Hummer,v1=Speedboat)] = lambda v1,v2: False self.assertEqual(faster(Hummer(),Bicycle()), True) def testAmbiguity(self): add = GenericFunction(lambda addend,augend:None) add[(object, int)] = operator.add add[(int, object)] = operator.sub self.assertRaises(AmbiguousMethod, add, 1, 2) def testDynamic(self): roll = GenericFunction(lambda vehicle:None) class Tricycle(HumanPowered,LandVehicle): pass roll[Signature(vehicle=Wheeled)] = lambda ob: "We're rolling" self.assertRaises(NoApplicableMethods, roll, Tricycle()) declareImplementation(Tricycle,[Wheeled]) self.assertEqual(roll(Tricycle()),"We're rolling") def testMRO(self): t = GenericFunction(lambda vehicle,num:None) t[Signature(vehicle=HumanPowered,num=Inequality('<',10))]=lambda v,n:False t[Signature(vehicle=WaterVehicle,num=Inequality('<',5))]=lambda v,n:True self.assertRaises(AmbiguousMethod, t, PaddleBoat(), 4) def testSimpleChaining(self): def both_vehicles(ob1,ob2): return "They're both vehicles." def both_land(next_method,ob1,ob2): return next_method(ob1,ob2)+" They are both land vehicles." def both_sea(next_method,ob1,ob2): return next_method(ob1,ob2)+" They are both sea vehicles." def mixed_vehicles(next_method,ob1,ob2): return next_method(ob1,ob2)+ \ " One vehicle is a land vehicle, the other is a sea vehicle." [dispatch.generic()] def compare(v1,v2): pass compare.addMethod([(Vehicle, Vehicle)], both_vehicles) compare.addMethod([(LandVehicle, LandVehicle)],both_land) compare.addMethod([(WaterVehicle, WaterVehicle)],both_sea) compare.addMethod( [(LandVehicle, WaterVehicle),(WaterVehicle, LandVehicle)], mixed_vehicles ) land = Bicycle() sea = Speedboat() self.assertEqual( compare(land, land), "They're both vehicles. They are both land vehicles.") self.assertEqual( compare(sea, sea), "They're both vehicles. They are both sea vehicles.") self.assertEqual( compare(land, sea), "They're both vehicles. \ One vehicle is a land vehicle, the other is a sea vehicle.") self.assertEqual( compare(sea, land), "They're both vehicles. \ One vehicle is a land vehicle, the other is a sea vehicle.") def testSubexpressionOrderingConstraints(self): g = GenericFunction(lambda x,y:None) self.assertEqual(g.constraints.items(),[]) df = Inequality.node_type yx = Call(operator.div, Argument(name='y'), Argument(name='x')) yxid = g.getExpressionId(yx), df xid = g.getExpressionId(Argument(name='x')), df yid = g.getExpressionId(Argument(name='y')), df [g.when('x==2 and y>10 and x<27')] def x(x,y): return "foo" self.assertEqual(g.constraints.items(),[]) [g.when('x>0 and y/x>10')] def x(x,y): return "bar" self.assertEqual(g.constraints.items(),[(xid,yxid)]) [g.when('x==1 and y>0 and y/x>10')] def x(x,y): return "bar" items = g.constraints.items(); items.sort() expected = [(xid,yxid),(yid,yxid)]; expected.sort() self.assertEqual(items,expected) self.assertEqual(g.constraints.successors([yid,yxid]),{yxid:1}) self.assertEqual(g.constraints.successors([xid,yxid]),{yxid:1}) best_id, remaining_ids = g._best_split(range(len(g.cases)), [yxid,yid]) self.assertEqual(best_id, yid) best_id, remaining_ids = g._best_split(range(len(g.cases)), [yxid,xid]) self.assertEqual(best_id, xid) def testSimpleMultiDispatch(self): class A: pass class B(A): pass class C: pass class D(A,C): pass def m1(*x): return "m1" def m2(*x): return "m2" def m3(*x): return "m3" def m4(*x): return "m4" def m5(*x): return "m5" class T: pass class F: pass tf = [F(),T()] g = GenericFunction( lambda f1,f1_x,f2,f1x_at_not_B, f1_y_eq_f2_y: None) # f1, f1.x, f2, f1.x@!B, f1.y=f2.y g.addMethod([(A,A,NullCriterion,T,T)], m1) g.addMethod([(B,B),(C,B,A)], m2) g.addMethod([(C,NullCriterion,C)], m3) g.addMethod([(C,)], m4) g.addMethod([(T,)], m5) def w(f1,f1x,f2,ymatches=F()): return g(f1,f1x,f2,tf[not isinstance(f1x,B)],ymatches) for time in 1,2: self.assertEqual( w(A(),A(),C(),T()), "m1") self.assertEqual( w(B(),B(),C()), "m2") self.assertEqual( w(C(),B(),B()), "m2") self.assertEqual( w(C(),C(),C()), "m3") self.assertEqual( w(C(),A(),A()), "m4") self.assertEqual( g(T(),None,None,None,None), "m5") g.criterionChanged() def testInstanceMethods(self): class X: [dispatch.generic()] def s(self,v): """X""" [s.when("v in LandVehicle")] def bar(self,v): return "land" [s.when("v in WaterVehicle")] def s(self,v): return "water" class Y(X): s = X.s [s.when("v in WaterVehicle")] def s(self,v): return "splash!" self.assertEqual(Y().s(WaterVehicle()),"splash!") self.assertEqual(X().s(v=LandVehicle()),"land") self.assertEqual(X().s(WaterVehicle()),"water") def testSubclassDispatch(self): [dispatch.generic()] def gm (t) : pass [gm.when(default)] def gm (t) : return 'default' [gm.when('issubclass(t,int)')] def gm2 (t) : return 'int' self.assertEqual(gm(int),"int") self.assertEqual(gm(object),"default") self.assertEqual(gm(float),"default") def testArgNormalization(self): from dispatch.functions import _mkNormalizer class dispatcher: def __getitem__(self,argtuple): return lambda *_,**__: argtuple f,a = _mkNormalizer(lambda foo: None, dispatcher()) self.assertEqual(a,['foo']) self.assertEqual(f("bar"), ("bar",)) self.assertEqual(f(foo="bar"), ("bar",)) f,a = _mkNormalizer(lambda foo=42: None, dispatcher()) self.assertEqual(a,['foo']) self.assertEqual(f("bar"), ("bar",)) self.assertEqual(f(foo="bar"), ("bar",)) self.assertEqual(f(), (42,)) f,a = _mkNormalizer(lambda foo,*z: None, dispatcher()) self.assertEqual(a,['foo','z']) self.assertEqual(f("bar","baz"), ("bar",("baz",))) self.assertEqual(f(foo="bar"), ("bar",())) f,a = _mkNormalizer(lambda foo="shoo",blue=42,*z: None, dispatcher()) self.assertEqual(a,['foo','blue','z']) self.assertEqual(f("bar","baz"), ("bar","baz",())) self.assertEqual(f("bar","baz","spam"), ("bar","baz",("spam",))) self.assertEqual(f(foo="bar"), ("bar",42,())) self.assertEqual(f(blue="two"), ("shoo","two",())) self.assertEqual(f(1,2,3,4), (1,2,(3,4))) self.assertEqual(f(), ("shoo",42,())) f,a = _mkNormalizer(lambda (x,y): None, dispatcher()) self.assertEqual(a,['x','y']) self.assertEqual(f((1,2)), (1,2)) f,a = _mkNormalizer(lambda foo,(x,y),*z,**zz: None, dispatcher()) self.assertEqual(a,['foo','x','y','z','zz']) self.assertEqual( f("foo",(1,2),fizz="fuzz"), ("foo",1,2,(),{'fizz':'fuzz'}) ) def testKwArgHandling(self): [dispatch.generic()] def f(**fiz): """Test of kw handling""" [f.when("'x' in fiz")] def f(**fiz): return "x" [f.when("'y' in fiz")] def f(**fiz): return "y" self.assertEqual(f(x=1),"x") self.assertEqual(f(y=1),"y") self.assertRaises(AmbiguousMethod, f, x=1, y=1) def testVarArgHandling(self): [dispatch.generic()] def f(*fiz): """Test of vararg handling""" [f.when("'x' in fiz")] def f(*fiz): return "x" [f.when("'y' in fiz")] def f(*fiz): return "y" self.assertEqual(f("foo","x"),"x") self.assertEqual(f("bar","q","y"),"y") self.assertEqual(f("bar","q","y"),"y") self.assertEqual(f("y","q",),"y") self.assertRaises(AmbiguousMethod, f, "x","y") TestClasses = ( TestGraph, CriteriaTests, ExpressionTests, SimpleGenerics, GenericTests, ) def test_combiners(): from protocols.tests import doctest return doctest.DocFileSuite( 'combiners.txt', optionflags=doctest.ELLIPSIS, package='dispatch', ) def test_assembler(): from protocols.tests import doctest return doctest.DocFileSuite( 'assembler.txt', package='dispatch', optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE, ) def test_suite(): return TestSuite( [test_combiners(), test_assembler()] + [makeSuite(t,'test') for t in TestClasses] )