chromium/tools/json_schema_compiler/idl_schema_test.py

#!/usr/bin/env python3
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import idl_schema
import unittest

from json_parse import OrderedDict


def getFunction(schema, name):
  for item in schema['functions']:
    if item['name'] == name:
      return item
  raise KeyError('Missing function %s' % name)


def getParams(schema, name):
  function = getFunction(schema, name)
  return function['parameters']


def getReturnsAsync(schema, name):
  function = getFunction(schema, name)
  return function.get('returns_async', False)


def getReturns(schema, name):
  function = getFunction(schema, name)
  return function['returns']


def getType(schema, id):
  for item in schema['types']:
    if item['id'] == id:
      return item


class IdlSchemaTest(unittest.TestCase):

  def setUp(self):
    loaded = idl_schema.Load('test/idl_basics.idl')
    self.assertEqual(1, len(loaded))
    self.assertEqual('idl_basics', loaded[0]['namespace'])
    self.idl_basics = loaded[0]
    self.maxDiff = None

  def testSimpleCallbacks(self):
    schema = self.idl_basics
    expected = {'name': 'cb', 'parameters': []}
    self.assertEqual(expected, getReturnsAsync(schema, 'function4'))

    expected = {'name': 'cb', 'parameters': [{'name': 'x', 'type': 'integer'}]}
    self.assertEqual(expected, getReturnsAsync(schema, 'function5'))

    expected = {
        'name': 'cb',
        'parameters': [{
            'name': 'arg',
            '$ref': 'MyType1'
        }]
    }
    self.assertEqual(expected, getReturnsAsync(schema, 'function6'))

  def testCallbackWithArrayArgument(self):
    schema = self.idl_basics
    expected = {
        'name':
        'cb',
        'parameters': [{
            'name': 'arg',
            'type': 'array',
            'items': {
                '$ref': 'MyType2'
            }
        }]
    }
    self.assertEqual(expected, getReturnsAsync(schema, 'function12'))

  def testArrayOfCallbacks(self):
    schema = idl_schema.Load('test/idl_function_types.idl')[0]
    expected = [{
        'type': 'array',
        'name': 'callbacks',
        'items': {
            'type': 'function',
            'name': 'MyCallback',
            'parameters': [{
                'type': 'integer',
                'name': 'x'
            }]
        }
    }]
    self.assertEqual(expected, getParams(schema, 'whatever'))

  def testProperties(self):
    self.assertEqual(
        {
            'x': {
                'name': 'x',
                'type': 'integer',
                'description': 'This comment tests "double-quotes".'
            },
            'y': {
                'name': 'y',
                'type': 'string'
            },
            'z': {
                'name': 'z',
                'type': 'string'
            },
            'a': {
                'name': 'a',
                'type': 'string'
            },
            'b': {
                'name': 'b',
                'type': 'string'
            },
            'c': {
                'name': 'c',
                'type': 'string'
            }
        },
        getType(self.idl_basics, 'MyType1')['properties'])

  def testMemberOrdering(self):
    self.assertEqual(['x', 'y', 'z', 'a', 'b', 'c'],
                     list(
                         getType(self.idl_basics,
                                 'MyType1')['properties'].keys()))

  def testEnum(self):
    schema = self.idl_basics
    expected = {
        'enum': [{
            'name': 'name1',
            'description': 'comment1'
        }, {
            'name': 'name2'
        }],
        'description': 'Enum description',
        'type': 'string',
        'id': 'EnumType'
    }
    self.assertEqual(expected, getType(schema, expected['id']))

    expected_params = [{'name': 'type', '$ref': 'EnumType'}]
    expected_returns_async = {
        'name': 'cb',
        'parameters': [{
            'name': 'type',
            '$ref': 'EnumType'
        }]
    }
    self.assertEqual(expected_params, getParams(schema, 'function13'))
    self.assertEqual(expected_returns_async,
                     getReturnsAsync(schema, 'function13'))

    expected = [{
        'items': {
            '$ref': 'EnumType'
        },
        'name': 'types',
        'type': 'array'
    }]
    self.assertEqual(expected, getParams(schema, 'function14'))

  def testScopedArguments(self):
    schema = self.idl_basics
    expected = [{'name': 'value', '$ref': 'idl_other_namespace.SomeType'}]
    self.assertEqual(expected, getParams(schema, 'function20'))

    expected = [{
        'items': {
            '$ref': 'idl_other_namespace.SomeType'
        },
        'name': 'values',
        'type': 'array'
    }]
    self.assertEqual(expected, getParams(schema, 'function21'))

    expected = [{
        'name': 'value',
        '$ref': 'idl_other_namespace.sub_namespace.AnotherType'
    }]
    self.assertEqual(expected, getParams(schema, 'function22'))

    expected = [{
        'items': {
            '$ref': 'idl_other_namespace.sub_namespace.'
            'AnotherType'
        },
        'name': 'values',
        'type': 'array'
    }]
    self.assertEqual(expected, getParams(schema, 'function23'))

  def testNoCompile(self):
    schema = self.idl_basics
    func = getFunction(schema, 'function15')
    self.assertTrue(func is not None)
    self.assertTrue(func['nocompile'])

  def testNoDocOnEnum(self):
    schema = self.idl_basics
    enum_with_nodoc = getType(schema, 'EnumTypeWithNoDoc')
    self.assertTrue(enum_with_nodoc is not None)
    self.assertTrue(enum_with_nodoc['nodoc'])

  def testNoDocOnEnumValue(self):
    schema = self.idl_basics
    expected = {
        'enum': [{
            'name': 'name1'
        }, {
            'name': 'name2',
            'nodoc': True,
            'description': 'comment2'
        }, {
            'name': 'name3',
            'description': 'comment3'
        }],
        'type':
        'string',
        'id':
        'EnumTypeWithNoDocValue',
        'description':
        ''
    }
    self.assertEqual(expected, getType(schema, expected['id']))

  def testReturnTypes(self):
    schema = self.idl_basics
    self.assertEqual({
        'name': 'function24',
        'type': 'integer'
    }, getReturns(schema, 'function24'))
    self.assertEqual({
        'name': 'function25',
        '$ref': 'MyType1',
        'optional': True
    }, getReturns(schema, 'function25'))
    self.assertEqual(
        {
            'name': 'function26',
            'type': 'array',
            'items': {
                '$ref': 'MyType1'
            }
        }, getReturns(schema, 'function26'))
    self.assertEqual(
        {
            'name': 'function27',
            '$ref': 'EnumType',
            'optional': True
        }, getReturns(schema, 'function27'))
    self.assertEqual(
        {
            'name': 'function28',
            'type': 'array',
            'items': {
                '$ref': 'EnumType'
            }
        }, getReturns(schema, 'function28'))
    self.assertEqual(
        {
            'name': 'function29',
            '$ref': 'idl_other_namespace.SomeType',
            'optional': True
        }, getReturns(schema, 'function29'))
    self.assertEqual(
        {
            'name': 'function30',
            'type': 'array',
            'items': {
                '$ref': 'idl_other_namespace.SomeType'
            }
        }, getReturns(schema, 'function30'))

  def testIgnoresAdditionalPropertiesOnType(self):
    self.assertTrue(
        getType(self.idl_basics,
                'IgnoreAdditionalPropertiesType')['ignoreAdditionalProperties'])

  def testChromeOSPlatformsNamespace(self):
    schema = idl_schema.Load('test/idl_namespace_chromeos.idl')[0]
    self.assertEqual('idl_namespace_chromeos', schema['namespace'])
    expected = ['chromeos']
    self.assertEqual(expected, schema['platforms'])

  def testAllPlatformsNamespace(self):
    schema = idl_schema.Load('test/idl_namespace_all_platforms.idl')[0]
    self.assertEqual('idl_namespace_all_platforms', schema['namespace'])
    expected = ['chromeos', 'fuchsia', 'linux', 'mac', 'win']
    self.assertEqual(expected, schema['platforms'])

  def testNonSpecificPlatformsNamespace(self):
    schema = idl_schema.Load('test/idl_namespace_non_specific_platforms.idl')[0]
    self.assertEqual('idl_namespace_non_specific_platforms',
                     schema['namespace'])
    expected = None
    self.assertEqual(expected, schema['platforms'])

  def testGenerateErrorMessages(self):
    schema = idl_schema.Load('test/idl_generate_error_messages.idl')[0]
    self.assertEqual('idl_generate_error_messages', schema['namespace'])
    self.assertTrue(schema['compiler_options'].get('generate_error_messages',
                                                   False))

    schema = idl_schema.Load('test/idl_basics.idl')[0]
    self.assertEqual('idl_basics', schema['namespace'])
    self.assertFalse(schema['compiler_options'].get('generate_error_messages',
                                                    False))

  def testSpecificImplementNamespace(self):
    schema = idl_schema.Load('test/idl_namespace_specific_implement.idl')[0]
    self.assertEqual('idl_namespace_specific_implement', schema['namespace'])
    expected = 'idl_namespace_specific_implement.idl'
    self.assertEqual(expected, schema['compiler_options']['implemented_in'])

  def testSpecificImplementOnChromeOSNamespace(self):
    schema = idl_schema.Load(
        'test/idl_namespace_specific_implement_chromeos.idl')[0]
    self.assertEqual('idl_namespace_specific_implement_chromeos',
                     schema['namespace'])
    expected_implemented_path = 'idl_namespace_specific_implement_chromeos.idl'
    expected_platform = ['chromeos']
    self.assertEqual(expected_implemented_path,
                     schema['compiler_options']['implemented_in'])
    self.assertEqual(expected_platform, schema['platforms'])

  def testCallbackComment(self):
    schema = self.idl_basics
    self.assertEqual('A comment on a callback.',
                     getReturnsAsync(schema, 'function16')['description'])
    self.assertEqual(
        'A parameter.',
        getReturnsAsync(schema, 'function16')['parameters'][0]['description'])
    self.assertEqual(
        'Just a parameter comment, with no comment on the callback.',
        getReturnsAsync(schema, 'function17')['parameters'][0]['description'])
    self.assertEqual('Override callback comment.',
                     getReturnsAsync(schema, 'function18')['description'])

  def testFunctionComment(self):
    schema = self.idl_basics
    func = getFunction(schema, 'function3')
    self.assertEqual(('This comment should appear in the documentation, '
                      'despite occupying multiple lines.'), func['description'])
    self.assertEqual([{
        'description': ('So should this comment about the argument. '
                        '<em>HTML</em> is fine too.'),
        'name':
        'arg',
        '$ref':
        'MyType1'
    }], func['parameters'])
    func = getFunction(schema, 'function4')
    self.assertEqual(
        '<p>This tests if "double-quotes" are escaped correctly.</p>'
        '<p>It also tests a comment with two newlines.</p>',
        func['description'])

  def testReservedWords(self):
    schema = idl_schema.Load('test/idl_reserved_words.idl')[0]

    foo_type = getType(schema, 'Foo')
    self.assertEqual([{
        'name': 'float'
    }, {
        'name': 'DOMString'
    }], foo_type['enum'])

    enum_type = getType(schema, 'enum')
    self.assertEqual([{
        'name': 'callback'
    }, {
        'name': 'namespace'
    }], enum_type['enum'])

    dictionary = getType(schema, 'dictionary')
    self.assertEqual('integer', dictionary['properties']['long']['type'])

    mytype = getType(schema, 'MyType')
    self.assertEqual('string', mytype['properties']['interface']['type'])

    params = getParams(schema, 'static')
    self.assertEqual('Foo', params[0]['$ref'])
    self.assertEqual('enum', params[1]['$ref'])

  def testObjectTypes(self):
    schema = idl_schema.Load('test/idl_object_types.idl')[0]

    foo_type = getType(schema, 'FooType')
    self.assertEqual('object', foo_type['type'])
    self.assertEqual('integer', foo_type['properties']['x']['type'])
    self.assertEqual('object', foo_type['properties']['y']['type'])
    self.assertEqual(
        'any', foo_type['properties']['y']['additionalProperties']['type'])
    self.assertEqual('object', foo_type['properties']['z']['type'])
    self.assertEqual(
        'any', foo_type['properties']['z']['additionalProperties']['type'])
    self.assertEqual('Window', foo_type['properties']['z']['isInstanceOf'])

    bar_type = getType(schema, 'BarType')
    self.assertEqual('object', bar_type['type'])
    self.assertEqual('any', bar_type['properties']['x']['type'])

  def testObjectTypesInFunctions(self):
    schema = idl_schema.Load('test/idl_object_types.idl')[0]

    params = getParams(schema, 'objectFunction1')
    self.assertEqual('object', params[0]['type'])
    self.assertEqual('any', params[0]['additionalProperties']['type'])
    self.assertEqual('ImageData', params[0]['isInstanceOf'])

    params = getParams(schema, 'objectFunction2')
    self.assertEqual('any', params[0]['type'])

  def testObjectTypesWithOptionalFields(self):
    schema = idl_schema.Load('test/idl_object_types.idl')[0]

    baz_type = getType(schema, 'BazType')
    self.assertEqual(True, baz_type['properties']['x']['optional'])
    self.assertEqual('integer', baz_type['properties']['x']['type'])
    self.assertEqual(True, baz_type['properties']['foo']['optional'])
    self.assertEqual('FooType', baz_type['properties']['foo']['$ref'])

  def testObjectTypesWithUnions(self):
    schema = idl_schema.Load('test/idl_object_types.idl')[0]

    union_type = getType(schema, 'UnionType')
    expected = {
        'type': 'object',
        'id': 'UnionType',
        'properties': {
            'x': {
                'name': 'x',
                'optional': True,
                'choices': [
                    {
                        'type': 'integer'
                    },
                    {
                        '$ref': 'FooType'
                    },
                ]
            },
            'y': {
                'name':
                'y',
                'choices': [{
                    'type': 'string'
                }, {
                    'type': 'object',
                    'additionalProperties': {
                        'type': 'any'
                    }
                }]
            },
            'z': {
                'name':
                'z',
                'choices': [{
                    'type': 'object',
                    'isInstanceOf': 'ImageData',
                    'additionalProperties': {
                        'type': 'any'
                    }
                }, {
                    'type': 'integer'
                }]
            }
        },
    }

    self.assertEqual(expected, union_type)

  def testUnionsWithModifiers(self):
    schema = idl_schema.Load('test/idl_object_types.idl')[0]

    union_type = getType(schema, 'ModifiedUnionType')
    expected = {
        'type': 'object',
        'id': 'ModifiedUnionType',
        'properties': {
            'x': {
                'name': 'x',
                'nodoc': True,
                'choices': [{
                    'type': 'integer'
                }, {
                    'type': 'string'
                }]
            }
        }
    }

    self.assertEqual(expected, union_type)

  def testSerializableFunctionType(self):
    schema = idl_schema.Load('test/idl_object_types.idl')[0]
    object_type = getType(schema, 'SerializableFunctionObject')
    expected = {
        'type': 'object',
        'id': 'SerializableFunctionObject',
        'properties': {
            'func': {
                'name': 'func',
                'serializableFunction': True,
                'type': 'function',
                'parameters': []
            }
        }
    }
    self.assertEqual(expected, object_type)

  def testUnionsWithFunctions(self):
    schema = idl_schema.Load('test/idl_function_types.idl')[0]

    union_params = getParams(schema, 'union_params')
    expected = [{
        'name': 'x',
        'choices': [{
            'type': 'integer'
        }, {
            'type': 'string'
        }]
    }]

    self.assertEqual(expected, union_params)

  def testUnionsWithCallbacks(self):
    schema = idl_schema.Load('test/idl_function_types.idl')[0]

    blah_params = getReturnsAsync(schema, 'blah')
    expected = {
        'name':
        'callback',
        'parameters': [{
            'name': 'x',
            'choices': [{
                'type': 'integer'
            }, {
                'type': 'string'
            }]
        }]
    }

    badabish_params = getReturnsAsync(schema, 'badabish')
    expected = {
        'name':
        'callback',
        'parameters': [{
            'name': 'x',
            'optional': True,
            'choices': [{
                'type': 'integer'
            }, {
                'type': 'string'
            }]
        }]
    }

    self.assertEqual(expected, badabish_params)

  def testFunctionWithoutPromiseSupport(self):
    schema = idl_schema.Load('test/idl_function_types.idl')[0]

    expected_params = []
    expected_returns_async = {
        'name': 'callback',
        'parameters': [{
            'name': 'x',
            'type': 'integer'
        }],
        'does_not_support_promises': 'Test'
    }
    params = getParams(schema, 'non_promise_supporting')
    returns_async = getReturnsAsync(schema, 'non_promise_supporting')

    self.assertEqual(expected_params, params)
    self.assertEqual(expected_returns_async, returns_async)

  def testFunctionWithoutPromiseSupportAndParams(self):
    schema = idl_schema.Load('test/idl_function_types.idl')[0]

    expected_params = [{
        'name': 'z',
        'type': 'integer'
    }, {
        'name': 'y',
        'choices': [{
            'type': 'integer'
        }, {
            'type': 'string'
        }]
    }]
    expected_returns_async = {
        'name': 'callback',
        'parameters': [{
            'name': 'x',
            'type': 'integer'
        }],
        'does_not_support_promises': 'Test'
    }
    params = getParams(schema, 'non_promise_supporting_with_params')
    returns_async = getReturnsAsync(schema,
                                    'non_promise_supporting_with_params')

    self.assertEqual(expected_params, params)
    self.assertEqual(expected_returns_async, returns_async)

  def testProperties(self):
    schema = idl_schema.Load('test/idl_properties.idl')[0]
    self.assertEqual(
        OrderedDict([
            ('first',
             OrderedDict([
                 ('description', 'Integer property.'),
                 ('type', 'integer'),
                 ('value', 42),
             ])),
            ('second',
             OrderedDict([
                 ('description', 'Double property.'),
                 ('type', 'number'),
                 ('value', 42.1),
             ])),
            ('third',
             OrderedDict([
                 ('description', 'String property.'),
                 ('type', 'string'),
                 ('value', 'hello world'),
             ])),
            ('fourth',
             OrderedDict([
                 ('description', 'Unvalued property.'),
                 ('type', 'integer'),
             ])),
        ]), schema.get('properties'))

  def testManifestKeys(self):
    schema = self.idl_basics
    # Test a smattering of the manifest key generation. We don't make this
    # exhaustive so we don't have to update it each time we add a new key in the
    # test file.
    manifest_keys = schema.get('manifest_keys')
    self.assertEqual(
        manifest_keys['key_str'],
        OrderedDict([('description', 'String manifest key.'),
                     ('name', 'key_str'), ('type', 'string')]))
    self.assertEqual(manifest_keys['key_ref'],
                     OrderedDict([('name', 'key_ref'), ('$ref', 'MyType2')])),
    self.assertEqual(
        manifest_keys['choice_with_arrays'],
        OrderedDict([('name', 'choice_with_arrays'),
                     ('$ref', 'ChoiceWithArraysType')])),
    self.assertEqual(
        manifest_keys['choice_with_optional'],
        OrderedDict([('name', 'choice_with_optional'),
                     ('$ref', 'ChoiceWithOptionalType')]))

  def testNoManifestKeys(self):
    schema = idl_schema.Load('test/idl_properties.idl')[0]
    self.assertIsNone(schema.get('manifest_keys'))


if __name__ == '__main__':
  unittest.main()