blob: fdb598a5d2aa15e06103b56c0c3db12119085281 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import unittest
import web_idl_schema
from web_idl_schema import SchemaCompilerError
# Helper functions for fetching specific parts of a processed API schema
# dictionary.
def getFunction(schema: dict, name: str) -> dict:
"""Gets the function dictionary with the specified name from the schema.
Args:
schema: The processed API schema dictionary to look for the function in.
name: The name of the function to look for.
Returns:
The dictionary for the function with the specified name.
Raises:
KeyError: If the given function name was not found in the list of functions.
"""
for item in schema['functions']:
if item['name'] == name:
return item
raise KeyError('Could not find "function" with name "%s" in schema' % name)
def getType(schema: dict, name: str) -> dict:
"""Gets the custom type dictionary with the specified name from the schema.
Args:
schema: The processed API schema dictionary to look for the type in.
name: The name of the custom type to look for.
Returns:
The dictionary for the custom type with the specified name.
Raises:
KeyError: If the given type name was not found in the list of types.
"""
for item in schema['types']:
if item['id'] == name:
return item
raise KeyError('Could not find "type" with id "%s" in schema' % name)
def getFunctionReturn(schema: dict, name: str) -> dict:
"""Gets the return dictionary for the function with the specified name.
Args:
schema: The processed API schema dictionary to look for the function in.
name: The name of the function to get the return value from.
Returns:
The dictionary representing the return for the specified function name if it
has a return, otherwise None if it does not.
"""
function = getFunction(schema, name)
return function.get('returns', None)
def getFunctionAsyncReturn(schema: dict, name: str) -> dict:
"""Gets the async return dictionary for the function with the specified name.
Args:
schema: The processed API schema dictionary to look for the function in.
name: The name of the function to get the async return value from.
Returns:
The dictionary representing the async return for the function with the
specified name if it has one, otherwise None if it does not.
"""
function = getFunction(schema, name)
return function.get('returns_async', None)
def getFunctionParameters(schema: dict, name: str) -> dict:
"""Gets the list of parameters for the function with the specified name.
Args:
schema: The processed API schema dictionary to look for the function in.
name: The name of the function to get the parameters list from.
Returns:
The list of dictionaries representing the function parameters for the
function with the specified name if it has any, otherwise None if it does
not.
"""
# TODO(crbug.com/340297705): All functions should have the 'parameters' key,
# so we shouldn't have a None fallback and just raise a KeyError if it isn't
# present.
function = getFunction(schema, name)
return function.get('parameters', None)
class WebIdlSchemaTest(unittest.TestCase):
def setUp(self):
loaded = web_idl_schema.Load('test/web_idl/basics.idl')
self.assertEqual(1, len(loaded))
self.assertEqual('testWebIdl', loaded[0]['namespace'])
self.idl_basics = loaded[0]
def testFunctionReturnTypes(self):
schema = self.idl_basics
# Test basic types.
self.assertEqual(
None,
getFunctionReturn(schema, 'returnsVoid'),
)
self.assertEqual(
{
'name': 'returnsBoolean',
'type': 'boolean'
},
getFunctionReturn(schema, 'returnsBoolean'),
)
self.assertEqual(
{
'name': 'returnsDouble',
'type': 'number'
},
getFunctionReturn(schema, 'returnsDouble'),
)
self.assertEqual(
{
'name': 'returnsLong',
'type': 'integer'
},
getFunctionReturn(schema, 'returnsLong'),
)
self.assertEqual(
{
'name': 'returnsDOMString',
'type': 'string'
},
getFunctionReturn(schema, 'returnsDOMString'),
)
self.assertEqual({
'name': 'returnsCustomType',
'$ref': 'ExampleType'
}, getFunctionReturn(schema, 'returnsCustomType'))
def testPromiseBasedReturn(self):
schema = self.idl_basics
self.assertEqual(
{
'name': 'callback',
'parameters': [{
'type': 'string'
}],
'type': 'promise'
}, getFunctionAsyncReturn(schema, 'stringPromiseReturn'))
self.assertEqual(
{
'name': 'callback',
'parameters': [{
'optional': True,
'type': 'string'
}],
'type': 'promise'
}, getFunctionAsyncReturn(schema, 'nullablePromiseReturn'))
self.assertEqual(
{
'name': 'callback',
'parameters': [{
'$ref': 'ExampleType'
}],
'type': 'promise'
}, getFunctionAsyncReturn(schema, 'customTypePromiseReturn'))
self.assertEqual({
'name': 'callback',
'parameters': [],
'type': 'promise'
}, getFunctionAsyncReturn(schema, 'undefinedPromiseReturn'))
# Tests function parameters are processed as expected.
def testFunctionParameters(self):
schema = self.idl_basics
# A function with no arguments has an empty array on the "parameters" key.
self.assertEqual([], getFunctionParameters(schema, 'takesNoArguments'))
self.assertEqual([{
'name': 'stringArgument',
'type': 'string'
}], getFunctionParameters(schema, 'takesDOMString'))
self.assertEqual([{
'name': 'optionalBoolean',
'optional': True,
'type': 'boolean'
}], getFunctionParameters(schema, 'takesOptionalBoolean'))
self.assertEqual([{
'name': 'argument1',
'type': 'string'
}, {
'name': 'argument2',
'optional': True,
'type': 'number'
}], getFunctionParameters(schema, 'takesMultipleArguments'))
self.assertEqual([{
'name': 'first',
'type': 'string'
}, {
'name': 'optionalInner',
'optional': True,
'type': 'string'
}, {
'name': 'last',
'type': 'string'
}], getFunctionParameters(schema, 'takesOptionalInnerArgument'))
self.assertEqual([{
'name': 'customTypeArgument',
'$ref': 'ExampleType'
}], getFunctionParameters(schema, 'takesCustomType'))
self.assertEqual([{
'name': 'optionalCustomTypeArgument',
'optional': True,
'$ref': 'ExampleType'
}], getFunctionParameters(schema, 'takesOptionalCustomType'))
# Tests that Dictionaries defined on the top level of the IDL file are
# processed into types on the resulting namespace.
def testApiTypesOnNamespace(self):
schema = self.idl_basics
self.assertEqual(
{
'id': 'ExampleType',
'properties': {
'someString': {
'name':
'someString',
'type':
'string',
'description':
('Attribute comment attached to ExampleType.someString.'),
},
'someNumber': {
'name':
'someNumber',
'type':
'number',
'description':
('Comment where <var>someNumber</var> has some markup.'),
},
# TODO(crbug.com/379052294): using HTML comments like this is a
# bit of a hack to allow us to add comments in IDL files (e.g.
# for TODOs) and to not have them end up on the documentation
# site. We should probably just filter them out during
# compilation.
'optionalBoolean': {
'name':
'optionalBoolean',
'type':
'boolean',
'optional':
True,
'description':
('Comment with HTML comment. <!-- Which should get'
' through -->'),
},
},
'type': 'object',
},
getType(schema, 'ExampleType'),
)
# Tests that a top level API comment is processed into a description
# attribute, with HTML paragraph nodes added due to the blank commented line.
def testApiDescriptionComment(self):
schema = self.idl_basics
expected_description = (
'<p>This comment is an example of a top level API description, which'
' will be extracted and added to the processed python dictionary as a'
' description.</p><p>Note: All comment lines preceding the thing they'
' are attached to will be part of the description, until a blank new'
' line or non-comment is reached.</p>')
self.assertEqual(expected_description, schema['description'])
# Tests that if the nodoc extended attribute is not specified on the API
# interface the related attribute is set to false after processing.
def testNodocUnspecifiedOnNamespace(self):
self.assertFalse(self.idl_basics['nodoc'])
# TODO(crbug.com/340297705): This will eventually be relaxed when adding
# support for shared types to the new parser.
def testMissingBrowserInterface(self):
expected_error_regex = (
'.* File\(test\/web_idl\/missing_browser_interface.idl\): Required'
' partial Browser interface not found in schema\.')
self.assertRaisesRegex(
SchemaCompilerError,
expected_error_regex,
web_idl_schema.Load,
'test/web_idl/missing_browser_interface.idl',
)
# Tests that having a Browser interface on an API definition with no attribute
# throws an error.
def testMissingAttributeOnBrowser(self):
expected_error_regex = (
'.* Interface\(Browser\): The partial Browser interface should have'
' exactly one attribute for the name the API will be exposed under\.')
self.assertRaisesRegex(
Exception,
expected_error_regex,
web_idl_schema.Load,
'test/web_idl/missing_attribute_on_browser.idl',
)
# Tests that using a valid basic WebIDL type with a "name" the schema compiler
# doesn't support yet throws an error.
def testUnsupportedBasicType(self):
expected_error_regex = (
'.* PrimitiveType\(float\): Unsupported basic type found when'
' processing type\.')
self.assertRaisesRegex(
SchemaCompilerError,
expected_error_regex,
web_idl_schema.Load,
'test/web_idl/unsupported_basic_type.idl',
)
# Tests that using a valid WebIDL type with a node "class" the schema compiler
# doesn't support yet throws an error.
def testUnsupportedTypeClass(self):
expected_error_regex = (
'.* Any\(\): Unsupported type class when processing type\.')
self.assertRaisesRegex(
SchemaCompilerError,
expected_error_regex,
web_idl_schema.Load,
'test/web_idl/unsupported_type_class.idl',
)
# Tests that if description parsing from file comments reaches the top of the
# file, a schema compiler error is thrown (as the top of the file should
# always be copyright lines and not part of the description).
def testDocumentationCommentReachedTopOfFile(self):
expected_error_regex = (
'.* Reached top of file when trying to parse description from file'
' comment. Make sure there is a blank line before the comment.')
self.assertRaisesRegex(
SchemaCompilerError,
expected_error_regex,
web_idl_schema.Load,
'test/web_idl/documentation_comment_top_of_file.idl',
)
# Tests that an API interface that uses the nodoc extended attribute has the
# related nodoc attribute set to true after processing.
def testNoDocOnNamespace(self):
idl = web_idl_schema.Load('test/web_idl/nodoc_on_namespace.idl')
self.assertEqual(1, len(idl))
schema = idl[0]
self.assertEqual('nodocAPI', schema['namespace'])
self.assertTrue(schema['nodoc'])
# Also ensure the description comes through correctly on the node with
# 'nodoc' as an extended attribute.
self.assertEqual(
'The nodoc API. This exists to demonstrate nodoc on the main interface'
' itself.',
schema['description'],
)
# Tests that extended attributes being listed on the the line previous to a
# node come through correctly and don't throw off and associated descriptions.
# TODO(crbug.com/340297705): Add checks for functions here once support for
# processing their descriptions is complete.
def testPreviousLineExtendedAttributes(self):
idl = web_idl_schema.Load('test/web_idl/preceding_extended_attributes.idl')
self.assertEqual(1, len(idl))
schema = idl[0]
self.assertEqual('precedingExtendedAttributes', schema['namespace'])
self.assertTrue(schema['nodoc'])
self.assertEqual(
'Comment on a schema that has extended attributes on a previous line.',
schema['description'],
)
if __name__ == '__main__':
unittest.main()