qemu/tests/qapi-schema/qapi-schema-test.json
Markus Armbruster c0644771eb qapi: Reject alternates that can't work with keyval_parse()
Alternates are sum types like unions, but use the JSON type on the
wire / QType in QObject instead of an explicit tag.  That's why we
require alternate members to have distinct QTypes.

The recently introduced keyval_parse() (commit d454dbe) can only
produce string scalars.  The qobject_input_visitor_new_keyval() input
visitor mostly hides the difference, so code using a QObject input
visitor doesn't have to care whether its input was parsed from JSON or
KEY=VALUE,...  The difference leaks for alternates, as noted in commit
0ee9ae7: a non-string, non-enum scalar alternate value can't currently
be expressed.

In part, this is just our insufficiently sophisticated implementation.
Consider alternate type 'GuestFileWhence'.  It has an integer member
and a 'QGASeek' member.  The latter is an enumeration with values
'set', 'cur', 'end'.  The meaning of b=set, b=cur, b=end, b=0, b=1 and
so forth is perfectly obvious.  However, our current implementation
falls apart at run time for b=0, b=1, and so forth.  Fixable, but not
today; add a test case and a TODO comment.

Now consider an alternate type with a string and an integer member.
What's the meaning of a=42?  Is it the string "42" or the integer 42?
Whichever meaning you pick makes the other inexpressible.  This isn't
just an implementation problem, it's fundamental.  Our current
implementation will pick string.

So far, we haven't needed such alternates.  To make sure we stop and
think before we add one that cannot sanely work with keyval_parse(),
let's require alternate members to have sufficiently distinct
representation in KEY=VALUE,... syntax:

* A string member clashes with any other scalar member

* An enumeration member clashes with bool members when it has value
  'on' or 'off'.

* An enumeration member clashes with numeric members when it has a
  value that starts with '-', '+', or a decimal digit.  This is a
  rather lazy approximation of the actual number syntax accepted by
  the visitor.

  Note that enumeration values starting with '-' and '+' are rejected
  elsewhere already, but better safe than sorry.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <1495471335-23707-5-git-send-email-armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
2017-05-31 16:04:09 +02:00

192 lines
6.9 KiB
Python

# *-*- Mode: Python -*-*
# This file is a stress test of supported qapi constructs that must
# parse and compile correctly.
# Whitelists to permit QAPI rule violations
{ 'pragma': {
# Commands allowed to return a non-dictionary:
'returns-whitelist': [
'guest-get-time',
'guest-sync' ] } }
{ 'struct': 'TestStruct',
'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } }
# for testing enums
{ 'struct': 'NestedEnumsOne',
'data': { 'enum1': 'EnumOne', # Intentional forward reference
'*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } }
# An empty enum, although unusual, is currently acceptable
{ 'enum': 'MyEnum', 'data': [ ] }
# Likewise for an empty struct, including an empty base
{ 'struct': 'Empty1', 'data': { } }
{ 'struct': 'Empty2', 'base': 'Empty1', 'data': { } }
{ 'command': 'user_def_cmd0', 'data': 'Empty2', 'returns': 'Empty2' }
# for testing override of default naming heuristic
{ 'enum': 'QEnumTwo',
'prefix': 'QENUM_TWO',
'data': [ 'value1', 'value2' ] }
# for testing nested structs
{ 'struct': 'UserDefOne',
'base': 'UserDefZero', # intentional forward reference
'data': { 'string': 'str',
'*enum1': 'EnumOne' } } # intentional forward reference
{ 'enum': 'EnumOne',
'data': [ 'value1', 'value2', 'value3' ] }
{ 'struct': 'UserDefZero',
'data': { 'integer': 'int' } }
{ 'struct': 'UserDefTwoDictDict',
'data': { 'userdef': 'UserDefOne', 'string': 'str' } }
{ 'struct': 'UserDefTwoDict',
'data': { 'string1': 'str',
'dict2': 'UserDefTwoDictDict',
'*dict3': 'UserDefTwoDictDict' } }
{ 'struct': 'UserDefTwo',
'data': { 'string0': 'str',
'dict1': 'UserDefTwoDict' } }
# dummy struct to force generation of array types not otherwise mentioned
{ 'struct': 'ForceArrays',
'data': { 'unused1':['UserDefOne'], 'unused2':['UserDefTwo'],
'unused3':['TestStruct'] } }
# for testing unions
# Among other things, test that a name collision between branches does
# not cause any problems (since only one branch can be in use at a time),
# by intentionally using two branches that both have a C member 'a_b'
{ 'struct': 'UserDefA',
'data': { 'boolean': 'bool', '*a_b': 'int' } }
{ 'struct': 'UserDefB',
'data': { 'intb': 'int', '*a-b': 'bool' } }
{ 'union': 'UserDefFlatUnion',
'base': 'UserDefUnionBase', # intentional forward reference
'discriminator': 'enum1',
'data': { 'value1' : 'UserDefA',
'value2' : 'UserDefB',
'value3' : 'UserDefB' } }
{ 'struct': 'UserDefUnionBase',
'base': 'UserDefZero',
'data': { 'string': 'str', 'enum1': 'EnumOne' } }
# this variant of UserDefFlatUnion defaults to a union that uses members with
# allocated types to test corner cases in the cleanup/dealloc visitor
{ 'union': 'UserDefFlatUnion2',
'base': { '*integer': 'int', 'string': 'str', 'enum1': 'QEnumTwo' },
'discriminator': 'enum1',
'data': { 'value1' : 'UserDefC', # intentional forward reference
'value2' : 'UserDefB' } }
{ 'struct': 'WrapAlternate',
'data': { 'alt': 'UserDefAlternate' } }
{ 'alternate': 'UserDefAlternate',
'data': { 'udfu': 'UserDefFlatUnion', 'e': 'EnumOne', 'i': 'int' } }
{ 'struct': 'UserDefC',
'data': { 'string1': 'str', 'string2': 'str' } }
# for testing use of 'number' within alternates
{ 'alternate': 'AltEnumBool', 'data': { 'e': 'EnumOne', 'b': 'bool' } }
{ 'alternate': 'AltEnumNum', 'data': { 'e': 'EnumOne', 'n': 'number' } }
{ 'alternate': 'AltNumEnum', 'data': { 'n': 'number', 'e': 'EnumOne' } }
{ 'alternate': 'AltEnumInt', 'data': { 'e': 'EnumOne', 'i': 'int' } }
{ 'alternate': 'AltIntNum', 'data': { 'i': 'int', 'n': 'number' } }
{ 'alternate': 'AltNumInt', 'data': { 'n': 'number', 'i': 'int' } }
# for testing use of 'str' within alternates
{ 'alternate': 'AltStrObj', 'data': { 's': 'str', 'o': 'TestStruct' } }
# for testing native lists
{ 'union': 'UserDefNativeListUnion',
'data': { 'integer': ['int'],
's8': ['int8'],
's16': ['int16'],
's32': ['int32'],
's64': ['int64'],
'u8': ['uint8'],
'u16': ['uint16'],
'u32': ['uint32'],
'u64': ['uint64'],
'number': ['number'],
'boolean': ['bool'],
'string': ['str'],
'sizes': ['size'],
'any': ['any'] } }
# testing commands
{ 'command': 'user_def_cmd', 'data': {} }
{ 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} }
{ 'command': 'user_def_cmd2',
'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'},
'returns': 'UserDefTwo' }
# Returning a non-dictionary requires a name from the whitelist
{ 'command': 'guest-get-time', 'data': {'a': 'int', '*b': 'int' },
'returns': 'int' }
{ 'command': 'guest-sync', 'data': { 'arg': 'any' }, 'returns': 'any' }
{ 'command': 'boxed-struct', 'boxed': true, 'data': 'UserDefZero' }
{ 'command': 'boxed-union', 'data': 'UserDefNativeListUnion', 'boxed': true }
# For testing integer range flattening in opts-visitor. The following schema
# corresponds to the option format:
#
# -userdef i64=3-6,i64=-5--1,u64=2,u16=1,u16=7-12
#
# For simplicity, this example doesn't use [type=]discriminator nor optargs
# specific to discriminator values.
{ 'struct': 'UserDefOptions',
'data': {
'*i64' : [ 'int' ],
'*u64' : [ 'uint64' ],
'*u16' : [ 'uint16' ],
'*i64x': 'int' ,
'*u64x': 'uint64' } }
# testing event
{ 'struct': 'EventStructOne',
'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }
{ 'event': 'EVENT_A' }
{ 'event': 'EVENT_B',
'data': { } }
{ 'event': 'EVENT_C',
'data': { '*a': 'int', '*b': 'UserDefOne', 'c': 'str' } }
{ 'event': 'EVENT_D',
'data': { 'a' : 'EventStructOne', 'b' : 'str', '*c': 'str', '*enum3': 'EnumOne' } }
{ 'event': 'EVENT_E', 'boxed': true, 'data': 'UserDefZero' }
{ 'event': 'EVENT_F', 'boxed': true, 'data': 'UserDefAlternate' }
# test that we correctly compile downstream extensions, as well as munge
# ticklish names
{ 'enum': '__org.qemu_x-Enum', 'data': [ '__org.qemu_x-value' ] }
{ 'struct': '__org.qemu_x-Base',
'data': { '__org.qemu_x-member1': '__org.qemu_x-Enum' } }
{ 'struct': '__org.qemu_x-Struct', 'base': '__org.qemu_x-Base',
'data': { '__org.qemu_x-member2': 'str', '*wchar-t': 'int' } }
{ 'union': '__org.qemu_x-Union1', 'data': { '__org.qemu_x-branch': 'str' } }
{ 'struct': '__org.qemu_x-Struct2',
'data': { 'array': ['__org.qemu_x-Union1'] } }
{ 'union': '__org.qemu_x-Union2', 'base': '__org.qemu_x-Base',
'discriminator': '__org.qemu_x-member1',
'data': { '__org.qemu_x-value': '__org.qemu_x-Struct2' } }
{ 'alternate': '__org.qemu_x-Alt',
'data': { '__org.qemu_x-branch': 'str', 'b': '__org.qemu_x-Base' } }
{ 'event': '__ORG.QEMU_X-EVENT', 'data': '__org.qemu_x-Struct' }
{ 'command': '__org.qemu_x-command',
'data': { 'a': ['__org.qemu_x-Enum'], 'b': ['__org.qemu_x-Struct'],
'c': '__org.qemu_x-Union2', 'd': '__org.qemu_x-Alt' },
'returns': '__org.qemu_x-Union1' }