import string # We'd like to use itertools.chain but it's 2.6 or higher. def chain(*iterables): # chain('ABC', 'DEF') --> A B C D E F for it in iterables: for element in it: yield element # We'd like to use itertools.combinations but it's 2.6 or higher. def combinations(iterable, r): # combinations('ABCD', 2) --> AB AC AD BC BD CD # combinations(range(4), 3) --> 012 013 023 123 pool = tuple(iterable) n = len(pool) if r > n: return indices = list(range(r)) yield tuple(pool[i] for i in indices) while True: for i in reversed(range(r)): if indices[i] != i + n - r: break else: return indices[i] += 1 for j in range(i + 1, r): indices[j] = indices[j - 1] + 1 yield tuple(pool[i] for i in indices) # We'd like to use itertools.combinations_with_replacement but it's 2.7 or # higher. def combinations_with_replacement(iterable, r): # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC pool = tuple(iterable) n = len(pool) if not n and r: return indices = [0] * r yield tuple(pool[i] for i in indices) while True: for i in reversed(range(r)): if indices[i] != n - 1: break else: return indices[i:] = [indices[i] + 1] * (r - i) yield tuple(pool[i] for i in indices) def WebIDLTest(parser, harness): types = [ "float", "double", "short", "unsigned short", "long", "unsigned long", "long long", "unsigned long long", "boolean", "byte", "octet", "DOMString", "ByteString", "USVString", # "sequence", "object", "ArrayBuffer", # "Date", "TestInterface1", "TestInterface2", ] testPre = """ interface TestInterface1 { }; interface TestInterface2 { }; """ interface = ( testPre + """ interface PrepareForTest { """ ) for (i, type) in enumerate(types): interface += string.Template( """ readonly attribute ${type} attr${i}; """ ).substitute(i=i, type=type) interface += """ }; """ parser.parse(interface) results = parser.finish() iface = results[2] parser = parser.reset() def typesAreDistinguishable(t): return all(u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2)) def typesAreNotDistinguishable(t): return any(not u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2)) def unionTypeName(t): if len(t) > 2: t[0:2] = [unionTypeName(t[0:2])] return "(" + " or ".join(t) + ")" # typeCombinations is an iterable of tuples containing the name of the type # as a string and the parsed IDL type. def unionTypes(typeCombinations, predicate): for c in typeCombinations: if predicate(t[1] for t in c): yield unionTypeName([t[0] for t in c]) # We limit invalid union types with a union member type to the subset of 3 # types with one invalid combination. # typeCombinations is an iterable of tuples containing the name of the type # as a string and the parsed IDL type. def invalidUnionWithUnion(typeCombinations): for c in typeCombinations: if ( typesAreNotDistinguishable((c[0][1], c[1][1])) and typesAreDistinguishable((c[1][1], c[2][1])) and typesAreDistinguishable((c[0][1], c[2][1])) ): yield unionTypeName([t[0] for t in c]) # Create a list of tuples containing the name of the type as a string and # the parsed IDL type. types = zip(types, (a.type for a in iface.members)) validUnionTypes = chain( unionTypes(combinations(types, 2), typesAreDistinguishable), unionTypes(combinations(types, 3), typesAreDistinguishable), ) invalidUnionTypes = chain( unionTypes(combinations_with_replacement(types, 2), typesAreNotDistinguishable), invalidUnionWithUnion(combinations(types, 3)), ) interface = ( testPre + """ interface TestUnion { """ ) for (i, type) in enumerate(validUnionTypes): interface += string.Template( """ undefined method${i}(${type} arg); ${type} returnMethod${i}(); attribute ${type} attr${i}; undefined optionalMethod${i}(${type}? arg); """ ).substitute(i=i, type=type) interface += """ }; """ parser.parse(interface) results = parser.finish() parser = parser.reset() for invalid in invalidUnionTypes: interface = ( testPre + string.Template( """ interface TestUnion { undefined method(${type} arg); }; """ ).substitute(type=invalid) ) threw = False try: parser.parse(interface) results = parser.finish() except Exception: threw = True harness.ok(threw, "Should have thrown.") parser = parser.reset()