Bug 741125: Update WebIDL parser.

This commit is contained in:
Kyle Huey 2012-04-04 12:07:28 -07:00
parent ab4a917101
commit 4a035a99ee
11 changed files with 409 additions and 140 deletions

View File

@ -7,6 +7,9 @@ NSS_DIRS = (('dbm', 'mozilla/dbm'),
('security/dbm', 'mozilla/security/dbm'))
NSSCKBI_DIRS = (('security/nss/lib/ckfw/builtins', 'mozilla/security/nss/lib/ckfw/builtins'),)
LIBFFI_DIRS = (('js/ctypes/libffi', 'libffi'),)
WEBIDLPARSER_DIR = 'dom/bindings/parser'
WEBIDLPARSER_REPO = 'https://hg.mozilla.org/users/khuey_mozilla.com/webidl-parser'
WEBIDLPARSER_EXCLUSIONS = ['.hgignore', '.gitignore', '.hg', 'ply']
CVSROOT_MOZILLA = ':pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot'
CVSROOT_LIBFFI = ':pserver:anoncvs@sources.redhat.com:/cvs/libffi'
@ -15,6 +18,7 @@ import os
import sys
import datetime
import shutil
import glob
from optparse import OptionParser
from subprocess import check_call
@ -30,7 +34,6 @@ def do_hg_pull(dir, repository, hg):
fulldir = os.path.join(topsrcdir, dir)
# clone if the dir doesn't exist, pull if it does
if not os.path.exists(fulldir):
fulldir = os.path.join(topsrcdir, dir)
check_call_noisy([hg, 'clone', repository, fulldir])
else:
cmd = [hg, 'pull', '-u', '-R', fulldir]
@ -40,6 +43,25 @@ def do_hg_pull(dir, repository, hg):
check_call([hg, 'parent', '-R', fulldir,
'--template=Updated to revision {node}.\n'])
def do_hg_replace(dir, repository, tag, exclusions, hg):
"""
Replace the contents of dir with the contents of repository, except for
files matching exclusions.
"""
fulldir = os.path.join(topsrcdir, dir)
if os.path.exists(fulldir):
shutil.rmtree(fulldir)
assert not os.path.exists(fulldir)
check_call_noisy([hg, 'clone', '-u', tag, repository, fulldir])
for thing in exclusions:
for excluded in glob.iglob(os.path.join(fulldir, thing)):
if os.path.isdir(excluded):
shutil.rmtree(excluded)
else:
os.remove(excluded)
def do_cvs_export(modules, tag, cvsroot, cvs):
"""Check out a CVS directory without CVS metadata, using "export"
modules is a list of directories to check out and the corresponding
@ -60,7 +82,7 @@ def do_cvs_export(modules, tag, cvsroot, cvs):
cwd=os.path.join(topsrcdir, parent))
print "CVS export end: " + datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
o = OptionParser(usage="client.py [options] update_nspr tagname | update_nss tagname | update_libffi tagname")
o = OptionParser(usage="client.py [options] update_nspr tagname | update_nss tagname | update_libffi tagname | update_webidlparser tagname")
o.add_option("--skip-mozilla", dest="skip_mozilla",
action="store_true", default=False,
help="Obsolete")
@ -69,6 +91,8 @@ o.add_option("--cvs", dest="cvs", default=os.environ.get('CVS', 'cvs'),
help="The location of the cvs binary")
o.add_option("--cvsroot", dest="cvsroot",
help="The CVSROOT (default for mozilla checkouts: %s)" % CVSROOT_MOZILLA)
o.add_option("--hg", dest="hg", default=os.environ.get('HG', 'hg'),
help="The location of the hg binary")
try:
options, args = o.parse_args()
@ -104,6 +128,9 @@ elif action in ('update_libffi'):
if not options.cvsroot:
options.cvsroot = CVSROOT_LIBFFI
do_cvs_export(LIBFFI_DIRS, tag, options.cvsroot, options.cvs)
elif action in ('update_webidlparser'):
tag, = args[1:]
do_hg_replace(WEBIDLPARSER_DIR, WEBIDLPARSER_REPO, tag, WEBIDLPARSER_EXCLUSIONS, options.hg)
else:
o.print_help()
sys.exit(2)

View File

@ -69,14 +69,16 @@ def parseInt(literal):
# Magic for creating enums
def M_add_class_attribs(attribs):
def foo(name, bases, dict_):
for v, k in attribs:
for v, k in enumerate(attribs):
dict_[k] = v
assert 'length' not in dict_
dict_['length'] = len(attribs)
return type(name, bases, dict_)
return foo
def enum(*names):
class Foo(object):
__metaclass__ = M_add_class_attribs(enumerate(names))
__metaclass__ = M_add_class_attribs(names)
def __setattr__(self, name, value): # this makes it read-only
raise NotImplementedError
return Foo()
@ -93,9 +95,8 @@ class WebIDLError(Exception):
self.location)
class Location(object):
_line = None
def __init__(self, lexer, lineno, lexpos, filename):
self._line = None
self._lineno = lineno
self._lexpos = lexpos
self._lexdata = lexer.lexdata
@ -105,36 +106,47 @@ class Location(object):
return self._lexpos == other._lexpos and \
self._file == other._file
def filename(self):
return self.filename
def resolve(self):
if self._line:
return
startofline = self._lexdata.rfind('\n', 0, self._lexpos) + 1
endofline = self._lexdata.find('\n', self._lexpos, self._lexpos + 80)
self._line = self._lexdata[startofline:endofline]
if endofline != -1:
self._line = self._lexdata[startofline:endofline]
else:
self._line = self._lexdata[startofline:]
self._colno = self._lexpos - startofline
def pointerline(self):
def i():
for i in xrange(0, self._colno):
yield " "
yield "^"
return "".join(i())
def get(self):
self.resolve()
return "%s line %s:%s" % (self._file, self._lineno, self._colno)
def _pointerline(self):
return " " * self._colno + "^"
def __str__(self):
self.resolve()
return "%s line %s:%s\n%s\n%s" % (self._file, self._lineno, self._colno,
self._line, self.pointerline())
self._line, self._pointerline())
class BuiltinLocation(object):
def __init__(self, text):
self.msg = text
def __eq__(self, other):
return isinstance(other, BuiltinLocation) and \
self.msg == other.msg
def filename(self):
return '<builtin>'
def resolve(self):
pass
def get(self):
return self.msg
@ -150,7 +162,7 @@ class IDLObject(object):
self.userData = dict()
def filename(self):
return self.location._file
return self.location.filename()
def isInterface(self):
return False
@ -198,6 +210,11 @@ class IDLScope(IDLObject):
return "::"
def ensureUnique(self, identifier, object):
"""
Ensure that there is at most one 'identifier' in scope ('self').
Note that object can be None. This occurs if we end up here for an
interface type we haven't seen yet.
"""
assert isinstance(identifier, IDLUnresolvedIdentifier)
assert not object or isinstance(object, IDLObjectWithIdentifier)
assert not object or object.identifier == identifier
@ -300,6 +317,9 @@ class IDLUnresolvedIdentifier(IDLObject):
object.identifier = identifier
return identifier
def finish(self):
assert False # Should replace with a resolved identifier first.
class IDLObjectWithIdentifier(IDLObject):
def __init__(self, location, parentScope, identifier):
IDLObject.__init__(self, location)
@ -368,9 +388,8 @@ class IDLInterface(IDLObjectWithScope):
self.parent = parent
self._callback = False
self._finished = False
self.members = list(members) # clone the list
assert iter(self.members) # Assert it's iterable
IDLObjectWithScope.__init__(self, location, parentScope, name)
@ -404,7 +423,7 @@ class IDLInterface(IDLObjectWithScope):
return retval
def finish(self, scope):
if hasattr(self, "_finished"):
if self._finished:
return
self._finished = True
@ -416,7 +435,6 @@ class IDLInterface(IDLObjectWithScope):
self.parent = parent
assert iter(self.members)
members = None
if self.parent:
self.parent.finish(scope)
@ -427,19 +445,6 @@ class IDLInterface(IDLObjectWithScope):
else:
members = list(self.members)
SpecialType = enum(
'NamedGetter',
'NamedSetter',
'NamedCreator',
'NamedDeleter',
'IndexedGetter',
'IndexedSetter',
'IndexedCreator',
'IndexedDeleter'
)
specialMembersSeen = [False for i in range(8)]
def memberNotOnParentChain(member, iface):
assert iface
@ -451,59 +456,39 @@ class IDLInterface(IDLObjectWithScope):
return False
return memberNotOnParentChain(member, iface.parent)
# Ensure that there's at most one of each {named,indexed}
# {getter,setter,creator,deleter}.
specialMembersSeen = set()
for member in members:
if memberNotOnParentChain(member, self):
member.resolve(self)
if member.tag == IDLInterfaceMember.Tags.Method:
if member.isGetter():
if member.isNamed():
if specialMembersSeen[SpecialType.NamedGetter]:
raise WebIDLError("Multiple named getters on %s" % (self),
self.location)
specialMembersSeen[SpecialType.NamedGetter] = True
else:
assert member.isIndexed()
if specialMembersSeen[SpecialType.IndexedGetter]:
raise WebIDLError("Multiple indexed getters on %s" % (self),
self.location)
specialMembersSeen[SpecialType.IndexedGetter] = True
if member.isSetter():
if member.isNamed():
if specialMembersSeen[SpecialType.NamedSetter]:
raise WebIDLError("Multiple named setters on %s" % (self),
self.location)
specialMembersSeen[SpecialType.NamedSetter] = True
else:
assert member.isIndexed()
if specialMembersSeen[SpecialType.IndexedSetter]:
raise WebIDLError("Multiple indexed setters on %s" % (self),
self.location)
specialMembersSeen[SpecialType.IndexedSetter] = True
if member.isCreator():
if member.isNamed():
if specialMembersSeen[SpecialType.NamedCreator]:
raise WebIDLError("Multiple named creators on %s" % (self),
self.location)
specialMembersSeen[SpecialType.NamedCreator] = True
else:
assert member.isIndexed()
if specialMembersSeen[SpecialType.IndexedCreator]:
raise WebIDLError("Multiple indexed creators on %s" % (self),
self.location)
specialMembersSeen[SpecialType.IndexedCreator] = True
if member.isDeleter():
if member.isNamed():
if specialMembersSeen[SpecialType.NamedDeleter]:
raise WebIDLError("Multiple named deleters on %s" % (self),
self.location)
specialMembersSeen[SpecialType.NamedDeleter] = True
else:
assert member.isIndexed()
if specialMembersSeen[SpecialType.IndexedDeleter]:
raise WebIDLError("Multiple indexed Deleters on %s" % (self),
self.location)
specialMembersSeen[SpecialType.IndexedDeleter] = True
if member.tag != IDLInterfaceMember.Tags.Method:
continue
if member.isGetter():
memberType = "getters"
elif member.isSetter():
memberType = "setters"
elif member.isCreator():
memberType = "creators"
elif member.isDeleter():
memberType = "deleters"
else:
continue
if member.isNamed():
memberType = "named " + memberType
elif member.isIndexed():
memberType = "indexed " + memberType
else:
continue
if memberType in specialMembersSeen:
raise WebIDLError("Multiple " + memberType + " on %s" % (self),
self.location)
specialMembersSeen.add(memberType)
for member in self.members:
member.finish(scope)
@ -529,7 +514,7 @@ class IDLInterface(IDLObjectWithScope):
return depth
def hasConstants(self):
return reduce(lambda b, m: b or m.isConst(), self.members, False)
return any(m.isConst() for m in self.members)
def hasInterfaceObject(self):
if self.isCallback():
@ -567,10 +552,7 @@ class IDLInterface(IDLObjectWithScope):
identifier = IDLUnresolvedIdentifier(self.location, "constructor",
allowForbidden=True)
method = IDLMethod(self.location, identifier, retType, args,
False, False, False, False, False, False,
False, False)
method = IDLMethod(self.location, identifier, retType, args)
method.resolve(self)
self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
@ -763,6 +745,12 @@ class IDLNullableType(IDLType):
def isString(self):
return self.inner.isString()
def isFloat(self):
return self.inner.isFloat()
def isInteger(self):
return self.inner.isInteger()
def isVoid(self):
return False
@ -772,6 +760,9 @@ class IDLNullableType(IDLType):
def isArray(self):
return self.inner.isArray()
def isArrayBuffer(self):
return self.inner.isArrayBuffer()
def isDictionary(self):
return self.inner.isDictionary()
@ -797,7 +788,7 @@ class IDLNullableType(IDLType):
return self
def unroll(self):
return self.inner
return self.inner.unroll()
def isDistinguishableFrom(self, other):
if other.nullable():
@ -835,18 +826,19 @@ class IDLSequenceType(IDLType):
return True
def isArray(self):
return self.inner.isArray()
return False
def isDictionary(self):
return self.inner.isDictionary()
return False
def isInterface(self):
return self.inner.isInterface()
return False
def isEnum(self):
return self.inner.isEnum();
return False
def tag(self):
# XXXkhuey this is probably wrong.
return self.inner.tag()
def resolveType(self, parentScope):
@ -858,10 +850,11 @@ class IDLSequenceType(IDLType):
def complete(self, scope):
self.inner = self.inner.complete(scope)
self.name = self.inner.name
return self
def unroll(self):
return self.inner
return self.inner.unroll()
def isDistinguishableFrom(self, other):
return (other.isPrimitive() or other.isString() or other.isEnum() or
@ -895,32 +888,33 @@ class IDLArrayType(IDLType):
return False
def isPrimitive(self):
return self.inner.isPrimitive()
return False
def isString(self):
return self.inner.isString()
return False
def isVoid(self):
return False
def isSequence(self):
assert not self.inner.isSequence()
return self.inner.isSequence()
return False
def isArray(self):
return True
def isDictionary(self):
assert not self.inner.isDictionary()
return self.inner.isDictionary()
return False
def isInterface(self):
return self.inner.isInterface()
return False
def isEnum(self):
return self.inner.isEnum()
return False
def tag(self):
# XXXkhuey this is probably wrong.
return self.inner.tag()
def resolveType(self, parentScope):
@ -932,10 +926,11 @@ class IDLArrayType(IDLType):
def complete(self, scope):
self.inner = self.inner.complete(scope)
self.name = self.inner.name
return self
def unroll(self):
return self.inner
return self.inner.unroll()
def isDistinguishableFrom(self, other):
return (other.isPrimitive() or other.isString() or other.isEnum() or
@ -995,7 +990,7 @@ class IDLTypedefType(IDLType, IDLObjectWithIdentifier):
return self.inner.tag()
def unroll(self):
return self.inner
return self.inner.unroll()
def isDistinguishableFrom(self, other):
return self.inner.isDistinguishableFrom(other)
@ -1041,6 +1036,10 @@ class IDLWrapperType(IDLType):
def isEnum(self):
return isinstance(self.inner, IDLEnum)
def resolveType(self, parentScope):
assert isinstance(parentScope, IDLScope)
self.inner.resolve(parentScope)
def isComplete(self):
return True
@ -1122,32 +1121,32 @@ class IDLBuiltinType(IDLType):
def __init__(self, location, name, type):
IDLType.__init__(self, location, name)
self.builtin = True
self.type = type
self._typeTag = type
def isPrimitive(self):
return self.type <= IDLBuiltinType.Types.double
return self._typeTag <= IDLBuiltinType.Types.double
def isString(self):
return self.type == IDLBuiltinType.Types.domstring
return self._typeTag == IDLBuiltinType.Types.domstring
def isInteger(self):
return self.type <= IDLBuiltinType.Types.unsigned_long_long
return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long
def isArrayBuffer(self):
return self.type == IDLBuiltinType.Types.ArrayBuffer
return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
def isInterface(self):
# ArrayBuffers are interface types per the TypedArray spec,
# but we handle them as builtins because SpiderMonkey implements
# ArrayBuffers.
return self.type == IDLBuiltinType.Types.ArrayBuffer
return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
def isFloat(self):
return self.type == IDLBuiltinType.Types.float or \
self.type == IDLBuiltinType.Types.double
return self._typeTag == IDLBuiltinType.Types.float or \
self._typeTag == IDLBuiltinType.Types.double
def tag(self):
return IDLBuiltinType.TagLookup[self.type]
return IDLBuiltinType.TagLookup[self._typeTag]
def isDistinguishableFrom(self, other):
if self.isPrimitive() or self.isString():
@ -1280,7 +1279,7 @@ class IDLValue(IDLObject):
# We're both integer types. See if we fit.
(min, max) = integerTypeSizes[type.type]
(min, max) = integerTypeSizes[type._typeTag]
if self.value <= max and self.value >= min:
# Promote
return IDLValue(self.location, type, self.value)
@ -1492,8 +1491,10 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
)
def __init__(self, location, identifier, returnType, arguments,
static, getter, setter, creator, deleter, specialType, legacycaller,
stringifier):
static=False, getter=False, setter=False, creator=False,
deleter=False, specialType=NamedOrIndexed.Neither,
legacycaller=False, stringifier=False):
# REVIEW: specialType is NamedOrIndexed -- wow, this is messed up.
IDLInterfaceMember.__init__(self, location, identifier,
IDLInterfaceMember.Tags.Method)
@ -1678,6 +1679,7 @@ class Tokenizer(object):
def t_INTEGER(self, t):
r'-?(0([0-7]+|[Xx][0-9A-Fa-f]+)?|[1-9][0-9]*)'
try:
# Can't use int(), because that doesn't handle octal properly.
t.value = parseInt(t.value)
except:
raise WebIDLError("Invalid integer literal",
@ -2261,8 +2263,9 @@ class Parser(Tokenizer):
"legacycaller" if legacycaller else ""), allowDoubleUnderscore=True)
method = IDLMethod(self.getLocation(p, 2), identifier, returnType, arguments,
static, getter, setter, creator, deleter, specialType,
legacycaller, False)
static=static, getter=getter, setter=setter, creator=creator,
deleter=deleter, specialType=specialType,
legacycaller=legacycaller, stringifier=False)
p[0] = method
def p_QualifiersStatic(self, p):
@ -2861,7 +2864,14 @@ class Parser(Tokenizer):
for production in self._productions:
production.finish(self.globalScope())
return set(self._productions)
# De-duplicate self._productions, without modifying its order.
seen = set()
result = []
for p in self._productions:
if p not in seen:
seen.add(p)
result.append(p)
return result
def reset(self):
return Parser()

View File

@ -1 +0,0 @@
__all__ = ['WebIDL']

View File

@ -37,36 +37,76 @@
import os, sys
import glob
import optparse
import traceback
import WebIDL
class TestHarness(object):
def __init__(self, test, verbose):
self.test = test
self.verbose = verbose
self.printed_intro = False
def start(self):
if self.verbose:
self.maybe_print_intro()
def finish(self):
if self.verbose or self.printed_intro:
print "Finished test %s" % self.test
def maybe_print_intro(self):
if not self.printed_intro:
print "Starting test %s" % self.test
self.printed_intro = True
def test_pass(self, msg):
if self.verbose:
print "TEST-PASS | %s" % msg
def test_fail(self, msg):
self.maybe_print_intro()
print "TEST-UNEXPECTED-FAIL | %s" % msg
def ok(self, condition, msg):
if condition:
print "TEST-PASS | %s" % msg
self.test_pass(msg)
else:
print "TEST-UNEXPECTED-FAIL | %s" % msg
self.test_fail(msg)
def check(self, a, b, msg):
if a == b:
print "TEST-PASS | %s" % msg
self.test_pass(msg)
else:
print "TEST-UNEXPECTED-FAIL | %s" % msg
self.test_fail(msg)
print "\tGot %s expected %s" % (a, b)
def run_tests():
harness = TestHarness()
def run_tests(tests, verbose):
testdir = os.path.join(os.path.dirname(__file__), 'tests')
if not tests:
tests = glob.iglob(os.path.join(testdir, "*.py"))
sys.path.append(testdir)
tests = glob.iglob("tests/*.py")
sys.path.append("./tests")
for test in tests:
(testpath, ext) = os.path.splitext(os.path.basename(test))
_test = __import__(testpath, globals(), locals(), ['WebIDLTest'])
#try:
_test.WebIDLTest.__call__(WebIDL.Parser(), harness)
#except:
# print "TEST-UNEXPECTED-FAIL | Unhandled exception in Test %s" % testpath
# print sys.exc_info()[0]
print "Test %s Complete\n" % testpath
harness = TestHarness(test, verbose)
harness.start()
try:
_test.WebIDLTest.__call__(WebIDL.Parser(), harness)
except:
print "TEST-UNEXPECTED-FAIL | Unhandled exception in test %s" % testpath
traceback.print_exc()
finally:
harness.finish()
if __name__ == '__main__':
run_tests()
usage = """%prog [OPTIONS] [TESTS]
Where TESTS are relative to the tests directory."""
parser = optparse.OptionParser(usage=usage)
parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
help="Don't print passing tests.")
options, tests = parser.parse_args()
run_tests(tests, verbose=options.verbose)

View File

@ -0,0 +1,13 @@
import WebIDL
def WebIDLTest(parser, harness):
parser.parse("""
interface A {
attribute long a;
};
interface B {
attribute A[] b;
};
""");
parser.finish()

View File

@ -0,0 +1,11 @@
import WebIDL
def WebIDLTest(parser, harness):
parser.parse("""
interface Test {
attribute long b;
};
""");
attr = parser.finish()[0].members[0]
harness.check(attr.type.filename(), '<builtin>', 'Filename on builtin type')

View File

@ -62,14 +62,14 @@ def WebIDLTest(parser, harness):
"Should be an IDLInterface")
checkMethod(results[0].ctor(), "::TestConstructorNoArgs::constructor",
"constructor", [("TestConstructorNoArgs", [])])
"constructor", [("TestConstructorNoArgs (Wrapper)", [])])
checkMethod(results[1].ctor(), "::TestConstructorWithArgs::constructor",
"constructor",
[("TestConstructorWithArgs",
[("TestConstructorWithArgs (Wrapper)",
[("::TestConstructorWithArgs::constructor::name", "name", "String", False, False)])])
checkMethod(results[2].ctor(), "::TestConstructorOverloads::constructor",
"constructor",
[("TestConstructorOverloads",
[("TestConstructorOverloads (Wrapper)",
[("::TestConstructorOverloads::constructor::foo", "foo", "Object", False, False)]),
("TestConstructorOverloads",
("TestConstructorOverloads (Wrapper)",
[("::TestConstructorOverloads::constructor::bar", "bar", "Boolean", False, False)])])

View File

@ -0,0 +1,15 @@
import WebIDL
def WebIDLTest(parser, harness):
parser.parse("""
interface Foo;
interface Bar;
interface Foo;
""");
results = parser.finish()
# There should be no duplicate interfaces in the result.
expectedNames = sorted(['Foo', 'Bar'])
actualNames = sorted(map(lambda iface: iface.identifier.name, results))
harness.check(actualNames, expectedNames, "Parser shouldn't output duplicate names.")

View File

@ -47,7 +47,7 @@ def WebIDLTest(parser, harness):
harness.check(len(signatures), 1, "Expect one signature")
(returnType, arguments) = signatures[0]
harness.check(str(returnType), "TestEnum", "Method type is the correct name")
harness.check(str(returnType), "TestEnum (Wrapper)", "Method type is the correct name")
harness.check(len(arguments), 1, "Method has the right number of arguments")
arg = arguments[0]
harness.ok(isinstance(arg, WebIDL.IDLArgument), "Should be an IDLArgument")
@ -58,4 +58,4 @@ def WebIDLTest(parser, harness):
"Attr has correct QName")
harness.check(attr.identifier.name, "foo", "Attr has correct name")
harness.check(str(attr.type), "TestEnum", "Attr type is the correct name")
harness.check(str(attr.type), "TestEnum (Wrapper)", "Attr type is the correct name")

View File

@ -0,0 +1,20 @@
import WebIDL
def WebIDLTest(parser, harness):
# Check that error messages put the '^' in the right place.
threw = False
input = 'interface ?'
try:
parser.parse(input)
results = parser.finish()
except WebIDL.WebIDLError as e:
threw = True
lines = str(e).split('\n')
harness.check(len(lines), 3, 'Expected number of lines in error message')
harness.check(lines[1], input, 'Second line shows error')
harness.check(lines[2], ' ' * (len(input) - 1) + '^',
'Correct column pointer in error message')
harness.ok(threw, "Should have thrown.")

View File

@ -0,0 +1,134 @@
import WebIDL
def WebIDLTest(parser, harness):
parser.parse("""
interface TestNullableEquivalency1 {
attribute long a;
attribute long? b;
};
interface TestNullableEquivalency2 {
attribute ArrayBuffer a;
attribute ArrayBuffer? b;
};
/* Not implemented */
/*dictionary TestNullableEquivalency3Dict {
long foo = 42;
};
interface TestNullableEquivalency3 {
attribute Test3Dict a;
attribute Test3Dict? b;
};*/
enum TestNullableEquivalency4Enum {
"Foo",
"Bar"
};
interface TestNullableEquivalency4 {
attribute TestNullableEquivalency4Enum a;
attribute TestNullableEquivalency4Enum? b;
};
interface TestNullableEquivalency5 {
attribute TestNullableEquivalency4 a;
attribute TestNullableEquivalency4? b;
};
interface TestNullableEquivalency6 {
attribute boolean a;
attribute boolean? b;
};
interface TestNullableEquivalency7 {
attribute DOMString a;
attribute DOMString? b;
};
/* Not implemented. */
/*interface TestNullableEquivalency8 {
attribute float a;
attribute float? b;
};*/
interface TestNullableEquivalency8 {
attribute double a;
attribute double? b;
};
interface TestNullableEquivalency9 {
attribute object a;
attribute object? b;
};
interface TestNullableEquivalency10 {
attribute double[] a;
attribute double[]? b;
};
interface TestNullableEquivalency11 {
attribute TestNullableEquivalency9[] a;
attribute TestNullableEquivalency9[]? b;
};
""")
for decl in parser.finish():
if decl.isInterface():
checkEquivalent(decl, harness)
def checkEquivalent(iface, harness):
type1 = iface.members[0].type
type2 = iface.members[1].type
harness.check(type1.nullable(), False, 'attr1 should not be nullable')
harness.check(type2.nullable(), True, 'attr2 should be nullable')
# We don't know about type1, but type2, the nullable type, definitely
# shouldn't be builtin.
harness.check(type2.builtin, False, 'attr2 should not be builtin')
# Ensure that all attributes of type2 match those in type1, except for:
# - names on an ignore list,
# - names beginning with '_',
# - functions which throw when called with no args, and
# - class-level non-callables ("static variables").
#
# Yes, this is an ugly, fragile hack. But it finds bugs...
for attr in dir(type1):
if attr.startswith('_') or \
attr in ['nullable', 'builtin', 'filename', 'location',
'inner', 'QName'] or \
(hasattr(type(type1), attr) and not callable(getattr(type1, attr))):
continue
a1 = getattr(type1, attr)
if callable(a1):
try:
v1 = a1()
except:
# Can't call a1 with no args, so skip this attriute.
continue
try:
a2 = getattr(type2, attr)
except:
harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface))
continue
if not callable(a2):
harness.ok(False, "%s attribute on type %s in %s wasn't callable" % (attr, type2, iface))
continue
v2 = a2()
harness.check(v2, v1, '%s method return value' % attr)
else:
try:
a2 = getattr(type2, attr)
except:
harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface))
continue
harness.check(a2, a1, '%s attribute should match' % attr)