QAPI patches patches for 2023-04-26

-----BEGIN PGP SIGNATURE-----
 
 iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmRIvOkSHGFybWJydUBy
 ZWRoYXQuY29tAAoJEDhwtADrkYZTX3MQAIqrKQbOzQ81/cDZ7aeLOroDoGYf1Cs0
 0NiEVlyoblWNzL3HraGgXiNRTP+zaG/TcFKza1nz8qjdkxWxBjdbfF5Lm6mQf5Zo
 tcHUjksmnUlPkLYSOyEjfY9SNvcS6g7djE/NF5lbJtzYGZScZpLarELR7oUvrcXR
 AEiw8N5FZXp+j6cTeWvrLzSqz9qBsFJUCGcGER0T/Mt5MlUwDexE1xe7g8oD5l+b
 s0jeQr1PTZm5k6ehajQtgbHvAkgH8xVTKqbB/U5iz4VhYriH+IPEOtfCFt6/1soz
 pVkYikJpazCvQMjqnWu9dE1onthgSsEIOV29kFU0Kr8ATZuJBQMuLVp4hSsbKANj
 BUVyL2/fUsIp7gd+KikXUOjKYajxek6Q2YLAPpL+1pBCTql/PBQ7td8CECdiv/9e
 Xh50q+BGvyEiyoyf4EEpaLXUZog605WHEaODj9uPtNHJP9x6Rqt93FUsdWUtt/k9
 hJ8RSKy8njr0vxGoJkj89m2XfCwtuX3VQ5IXvv/If4U5Y4+JhcLtiqW+Njh8fAM4
 ZwIrlUYG7inLUKFVcQ3sEGpaj611i5ixIxctUvEiggZX+fPeSFKYUr+Rq8WXM8gv
 suLXz7VF6H4Sw30lCvdQ4LSungbzlYAtQYpmdEQGoM8iasIi4PoDf0cTYBbMYHDX
 +pZvWC50cVtf
 =wLx6
 -----END PGP SIGNATURE-----

Merge tag 'pull-qapi-2023-04-26' of https://repo.or.cz/qemu/armbru into staging

QAPI patches patches for 2023-04-26

# -----BEGIN PGP SIGNATURE-----
#
# iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmRIvOkSHGFybWJydUBy
# ZWRoYXQuY29tAAoJEDhwtADrkYZTX3MQAIqrKQbOzQ81/cDZ7aeLOroDoGYf1Cs0
# 0NiEVlyoblWNzL3HraGgXiNRTP+zaG/TcFKza1nz8qjdkxWxBjdbfF5Lm6mQf5Zo
# tcHUjksmnUlPkLYSOyEjfY9SNvcS6g7djE/NF5lbJtzYGZScZpLarELR7oUvrcXR
# AEiw8N5FZXp+j6cTeWvrLzSqz9qBsFJUCGcGER0T/Mt5MlUwDexE1xe7g8oD5l+b
# s0jeQr1PTZm5k6ehajQtgbHvAkgH8xVTKqbB/U5iz4VhYriH+IPEOtfCFt6/1soz
# pVkYikJpazCvQMjqnWu9dE1onthgSsEIOV29kFU0Kr8ATZuJBQMuLVp4hSsbKANj
# BUVyL2/fUsIp7gd+KikXUOjKYajxek6Q2YLAPpL+1pBCTql/PBQ7td8CECdiv/9e
# Xh50q+BGvyEiyoyf4EEpaLXUZog605WHEaODj9uPtNHJP9x6Rqt93FUsdWUtt/k9
# hJ8RSKy8njr0vxGoJkj89m2XfCwtuX3VQ5IXvv/If4U5Y4+JhcLtiqW+Njh8fAM4
# ZwIrlUYG7inLUKFVcQ3sEGpaj611i5ixIxctUvEiggZX+fPeSFKYUr+Rq8WXM8gv
# suLXz7VF6H4Sw30lCvdQ4LSungbzlYAtQYpmdEQGoM8iasIi4PoDf0cTYBbMYHDX
# +pZvWC50cVtf
# =wLx6
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 26 Apr 2023 06:55:53 AM BST
# gpg:                using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg:                issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [undefined]
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>" [undefined]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867  4E5F 3870 B400 EB91 8653

* tag 'pull-qapi-2023-04-26' of https://repo.or.cz/qemu/armbru:
  qapi: allow unions to contain further unions
  qapi: Improve specificity of type/member descriptions
  qapi: support updating expected test output via make
  qapi: Require boxed for conditional command and event arguments
  qapi: Fix code generated for optional conditional struct member
  tests/qapi-schema: Cover optional conditional struct member
  tests/qapi-schema: Clean up positive test for conditionals
  tests/qapi-schema: Rename a few conditionals
  tests/qapi-schema: Improve union discriminator coverage
  qapi: Fix to reject 'data': 'mumble' in struct
  qapi: Fix error message when type name or array is expected
  qapi: Simplify code a bit after previous commits
  qapi: Improve error message for unexpected array types
  qapi: Split up check_type()
  qapi: Clean up after removal of simple unions
  qapi/schema: Use super()
  qapi: Fix error message format regression

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2023-04-26 07:23:32 +01:00
commit c3f9aa8e48
39 changed files with 383 additions and 103 deletions

View file

@ -805,9 +805,8 @@ gets its generated code guarded like this::
... generated code ...
#endif /* defined(HAVE_BAR) && defined(CONFIG_FOO) */
Individual members of complex types, commands arguments, and
event-specific data can also be made conditional. This requires the
longhand form of MEMBER.
Individual members of complex types can also be made conditional.
This requires the longhand form of MEMBER.
Example: a struct type with unconditional member 'foo' and conditional
member 'bar' ::

View file

@ -66,6 +66,7 @@ def gen_call(name: str,
elif arg_type:
assert not arg_type.variants
for memb in arg_type.members:
assert not memb.ifcond.is_present()
if memb.need_has():
argstr += 'arg.has_%s, ' % c_name(memb.name)
argstr += 'arg.%s, ' % c_name(memb.name)

View file

@ -333,62 +333,56 @@ def normalize_members(members: object) -> None:
members[key] = {'type': arg}
def check_type(value: Optional[object],
info: QAPISourceInfo,
source: str,
allow_array: bool = False,
allow_dict: Union[bool, str] = False) -> None:
"""
Normalize and validate the QAPI type of ``value``.
def check_type_name(value: Optional[object],
info: QAPISourceInfo, source: str) -> None:
if value is not None and not isinstance(value, str):
raise QAPISemError(info, "%s should be a type name" % source)
Python types of ``str`` or ``None`` are always allowed.
def check_type_name_or_array(value: Optional[object],
info: QAPISourceInfo, source: str) -> None:
if value is None or isinstance(value, str):
return
if not isinstance(value, list):
raise QAPISemError(info,
"%s should be a type name or array" % source)
if len(value) != 1 or not isinstance(value[0], str):
raise QAPISemError(info,
"%s: array type must contain single type name" %
source)
def check_type_implicit(value: Optional[object],
info: QAPISourceInfo, source: str,
parent_name: Optional[str]) -> None:
"""
Normalize and validate an optional implicit struct type.
Accept ``None`` or a ``dict`` defining an implicit struct type.
The latter is normalized in place.
:param value: The value to check.
:param info: QAPI schema source file information.
:param source: Error string describing this ``value``.
:param allow_array:
Allow a ``List[str]`` of length 1, which indicates an array of
the type named by the list element.
:param allow_dict:
Allow a dict. Its members can be struct type members or union
branches. When the value of ``allow_dict`` is in pragma
``member-name-exceptions``, the dict's keys may violate the
member naming rules. The dict members are normalized in place.
:param parent_name:
When the value of ``parent_name`` is in pragma
``member-name-exceptions``, an implicit struct type may
violate the member naming rules.
:raise QAPISemError: When ``value`` fails validation.
:return: None, ``value`` is normalized in-place as needed.
:return: None
"""
if value is None:
return
# Type name
if isinstance(value, str):
return
# Array type
if isinstance(value, list):
if not allow_array:
raise QAPISemError(info, "%s cannot be an array" % source)
if len(value) != 1 or not isinstance(value[0], str):
raise QAPISemError(info,
"%s: array type must contain single type name" %
source)
return
# Anonymous type
if not allow_dict:
raise QAPISemError(info, "%s should be a type name" % source)
if not isinstance(value, dict):
raise QAPISemError(info,
"%s should be an object or type name" % source)
permissive = False
if isinstance(allow_dict, str):
permissive = allow_dict in info.pragma.member_name_exceptions
permissive = parent_name in info.pragma.member_name_exceptions
# value is a dictionary, check that each member is okay
for (key, arg) in value.items():
key_source = "%s member '%s'" % (source, key)
if key.startswith('*'):
@ -401,7 +395,16 @@ def check_type(value: Optional[object],
check_keys(arg, info, key_source, ['type'], ['if', 'features'])
check_if(arg, info, key_source)
check_features(arg.get('features'), info)
check_type(arg['type'], info, key_source, allow_array=True)
check_type_name_or_array(arg['type'], info, key_source)
def check_type_name_or_implicit(value: Optional[object],
info: QAPISourceInfo, source: str,
parent_name: Optional[str]) -> None:
if value is None or isinstance(value, str):
return
check_type_implicit(value, info, source, parent_name)
def check_features(features: Optional[object],
@ -489,8 +492,8 @@ def check_struct(expr: QAPIExpression) -> None:
name = cast(str, expr['struct']) # Checked in check_exprs
members = expr['data']
check_type(members, expr.info, "'data'", allow_dict=name)
check_type(expr.get('base'), expr.info, "'base'")
check_type_implicit(members, expr.info, "'data'", name)
check_type_name(expr.get('base'), expr.info, "'base'")
def check_union(expr: QAPIExpression) -> None:
@ -508,7 +511,7 @@ def check_union(expr: QAPIExpression) -> None:
members = expr['data']
info = expr.info
check_type(base, info, "'base'", allow_dict=name)
check_type_name_or_implicit(base, info, "'base'", name)
check_name_is_str(discriminator, info, "'discriminator'")
if not isinstance(members, dict):
@ -518,7 +521,7 @@ def check_union(expr: QAPIExpression) -> None:
source = "'data' member '%s'" % key
check_keys(value, info, source, ['type'], ['if'])
check_if(value, info, source)
check_type(value['type'], info, source, allow_array=not base)
check_type_name(value['type'], info, source)
def check_alternate(expr: QAPIExpression) -> None:
@ -544,7 +547,7 @@ def check_alternate(expr: QAPIExpression) -> None:
check_name_lower(key, info, source)
check_keys(value, info, source, ['type'], ['if'])
check_if(value, info, source)
check_type(value['type'], info, source, allow_array=True)
check_type_name_or_array(value['type'], info, source)
def check_command(expr: QAPIExpression) -> None:
@ -560,10 +563,13 @@ def check_command(expr: QAPIExpression) -> None:
rets = expr.get('returns')
boxed = expr.get('boxed', False)
if boxed and args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type(args, expr.info, "'data'", allow_dict=not boxed)
check_type(rets, expr.info, "'returns'", allow_array=True)
if boxed:
if args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type_name(args, expr.info, "'data'")
else:
check_type_name_or_implicit(args, expr.info, "'data'", None)
check_type_name_or_array(rets, expr.info, "'returns'")
def check_event(expr: QAPIExpression) -> None:
@ -578,9 +584,12 @@ def check_event(expr: QAPIExpression) -> None:
args = expr.get('data')
boxed = expr.get('boxed', False)
if boxed and args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type(args, expr.info, "'data'", allow_dict=not boxed)
if boxed:
if args is None:
raise QAPISemError(expr.info, "'boxed': true requires 'data'")
check_type_name(args, expr.info, "'data'")
else:
check_type_name_or_implicit(args, expr.info, "'data'", None)
def check_exprs(exprs: List[QAPIExpression]) -> List[QAPIExpression]:

View file

@ -119,6 +119,7 @@ def build_params(arg_type: Optional[QAPISchemaObjectType],
elif arg_type:
assert not arg_type.variants
for memb in arg_type.members:
assert not memb.ifcond.is_present()
ret += sep
sep = ', '
if memb.need_has():

View file

@ -98,6 +98,6 @@ def main() -> int:
builtins=args.builtins,
gen_tracing=not args.suppress_tracing)
except QAPIError as err:
print(f"{sys.argv[0]}: {str(err)}", file=sys.stderr)
print(err, file=sys.stderr)
return 1
return 0

View file

@ -259,7 +259,7 @@ def need_has_if_optional(self):
return not self.c_type().endswith(POINTER_SUFFIX)
def check(self, schema):
QAPISchemaEntity.check(self, schema)
super().check(schema)
for feat in self.features:
if feat.is_special():
raise QAPISemError(
@ -465,9 +465,10 @@ def check(self, schema):
# on behalf of info, which is not necessarily self.info
def check_clash(self, info, seen):
assert self._checked
assert not self.variants # not implemented
for m in self.members:
m.check_clash(info, seen)
if self.variants:
self.variants.check_clash(info, seen)
def connect_doc(self, doc=None):
super().connect_doc(doc)
@ -486,6 +487,10 @@ def is_empty(self):
assert self.members is not None
return not self.members and not self.variants
def has_conditional_members(self):
assert self.members is not None
return any(m.ifcond.is_present() for m in self.members)
def c_name(self):
assert self.name != 'q_empty'
return super().c_name()
@ -652,8 +657,7 @@ def check(self, schema, seen):
self.info,
"branch '%s' is not a value of %s"
% (v.name, self.tag_member.type.describe()))
if (not isinstance(v.type, QAPISchemaObjectType)
or v.type.variants):
if not isinstance(v.type, QAPISchemaObjectType):
raise QAPISemError(
self.info,
"%s cannot use %s"
@ -697,6 +701,7 @@ def connect_doc(self, doc):
def describe(self, info):
role = self.role
meta = 'type'
defined_in = self.defined_in
assert defined_in
@ -708,13 +713,17 @@ def describe(self, info):
# Implicit type created for a command's dict 'data'
assert role == 'member'
role = 'parameter'
meta = 'command'
defined_in = defined_in[:-4]
elif defined_in.endswith('-base'):
# Implicit type created for a union's dict 'base'
role = 'base ' + role
defined_in = defined_in[:-5]
else:
assert False
elif defined_in != info.defn_name:
return "%s '%s' of type '%s'" % (role, self.name, defined_in)
if defined_in != info.defn_name:
return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in)
return "%s '%s'" % (role, self.name)
@ -817,6 +826,11 @@ def check(self, schema):
self.info,
"command's 'data' can take %s only with 'boxed': true"
% self.arg_type.describe())
self.arg_type.check(schema)
if self.arg_type.has_conditional_members() and not self.boxed:
raise QAPISemError(
self.info,
"conditional command arguments require 'boxed': true")
if self._ret_type_name:
self.ret_type = schema.resolve_type(
self._ret_type_name, self.info, "command's 'returns'")
@ -872,6 +886,11 @@ def check(self, schema):
self.info,
"event's 'data' can take %s only with 'boxed': true"
% self.arg_type.describe())
self.arg_type.check(schema)
if self.arg_type.has_conditional_members() and not self.boxed:
raise QAPISemError(
self.info,
"conditional event arguments require 'boxed': true")
def connect_doc(self, doc=None):
super().connect_doc(doc)

View file

@ -74,11 +74,13 @@ def gen_visit_object_members(name: str,
sep = ''
for memb in members:
if memb.optional and not memb.need_has():
ret += memb.ifcond.gen_if()
ret += mcgen('''
bool has_%(c_name)s = !!obj->%(c_name)s;
''',
c_name=c_name(memb.name))
sep = '\n'
ret += memb.ifcond.gen_endif()
ret += sep
if base:

View file

@ -0,0 +1,2 @@
args-if-implicit.json: In command 'test-if-cmd':
args-if-implicit.json:1: conditional command arguments require 'boxed': true

View file

@ -0,0 +1,4 @@
{ 'command': 'test-if-cmd',
'data': {
'foo': 'int',
'bar': { 'type': 'str', 'if': 'TEST_IF_CMD_ARG' } } }

View file

View file

@ -0,0 +1,2 @@
args-if-unboxed.json: In command 'test-if-cmd':
args-if-unboxed.json:5: conditional command arguments require 'boxed': true

View file

@ -0,0 +1,6 @@
{ 'struct': 'TestIfCmdArgs',
'data': {
'foo': 'int',
'bar': { 'type': 'str', 'if': 'TEST_IF_CMD_ARG' } } }
{ 'command': 'test-if-cmd',
'data': 'TestIfCmdArgs' }

View file

View file

@ -1,2 +1,2 @@
bad-data.json: In command 'oops':
bad-data.json:2: 'data' cannot be an array
bad-data.json:2: 'data' should be an object or type name

View file

@ -0,0 +1,2 @@
tests/qapi-schema/event-args-if-unboxed.json: In event 'TEST_IF_EVENT':
tests/qapi-schema/event-args-if-unboxed.json:1: event's 'data' members may have 'if' conditions only with 'boxed': true

View file

@ -0,0 +1,4 @@
{ 'event': 'TEST_IF_EVENT',
'data': {
'foo': 'int',
'bar': { 'type': 'str', 'if': 'TEST_IF_CMD_ARG' } } }

View file

@ -1,2 +1,2 @@
event-nest-struct.json: In event 'EVENT_A':
event-nest-struct.json:1: 'data' member 'a' should be a type name
event-nest-struct.json:1: 'data' member 'a' should be a type name or array

View file

@ -27,6 +27,8 @@ schemas = [
'args-bad-boxed.json',
'args-boxed-anon.json',
'args-boxed-string.json',
'args-if-implicit.json',
'args-if-unboxed.json',
'args-int.json',
'args-invalid.json',
'args-member-array-bad.json',
@ -164,6 +166,7 @@ schemas = [
'struct-base-clash-deep.json',
'struct-base-clash.json',
'struct-data-invalid.json',
'struct-data-typename.json',
'struct-member-if-invalid.json',
'struct-member-invalid-dict.json',
'struct-member-invalid.json',
@ -194,6 +197,8 @@ schemas = [
'union-invalid-data.json',
'union-invalid-discriminator.json',
'union-invalid-if-discriminator.json',
'union-invalid-union-subfield.json',
'union-invalid-union-subtype.json',
'union-no-base.json',
'union-optional-discriminator.json',
'union-string-discriminator.json',

View file

@ -1,2 +1,2 @@
nested-struct-data.json: In command 'foo':
nested-struct-data.json:2: 'data' member 'a' should be a type name
nested-struct-data.json:2: 'data' member 'a' should be a type name or array

View file

@ -114,6 +114,38 @@
{ 'struct': 'UserDefC',
'data': { 'string1': 'str', 'string2': 'str' } }
# this tests that unions can contain other unions in their branches
{ 'enum': 'TestUnionEnum',
'data': [ 'value-a', 'value-b' ] }
{ 'enum': 'TestUnionEnumA',
'data': [ 'value-a1', 'value-a2' ] }
{ 'struct': 'TestUnionTypeA1',
'data': { 'integer': 'int',
'name': 'str'} }
{ 'struct': 'TestUnionTypeA2',
'data': { 'integer': 'int',
'size': 'int' } }
{ 'union': 'TestUnionTypeA',
'base': { 'type-a': 'TestUnionEnumA' },
'discriminator': 'type-a',
'data': { 'value-a1': 'TestUnionTypeA1',
'value-a2': 'TestUnionTypeA2' } }
{ 'struct': 'TestUnionTypeB',
'data': { 'integer': 'int',
'onoff': 'bool' } }
{ 'union': 'TestUnionInUnion',
'base': { 'type': 'TestUnionEnum' },
'discriminator': 'type',
'data': { 'value-a': 'TestUnionTypeA',
'value-b': 'TestUnionTypeB' } }
# for testing use of 'number' within alternates
{ 'alternate': 'AltEnumBool', 'data': { 'e': 'EnumOne', 'b': 'bool' } }
{ 'alternate': 'AltEnumNum', 'data': { 'e': 'EnumOne', 'n': 'number' } }
@ -220,18 +252,19 @@
{ 'struct': 'TestIfStruct',
'data': { 'foo': 'int',
'bar': { 'type': 'int', 'if': 'TEST_IF_STRUCT_BAR'} },
'bar': { 'type': 'int', 'if': 'TEST_IF_STRUCT_MEMBER'},
'*baz': { 'type': 'str', 'if': 'TEST_IF_STRUCT_MEMBER'} },
'if': 'TEST_IF_STRUCT' }
{ 'enum': 'TestIfEnum',
'data': [ 'foo', { 'name' : 'bar', 'if': 'TEST_IF_ENUM_BAR' } ],
'if': 'TEST_IF_ENUM' }
'data': [ 'foo', { 'name' : 'bar', 'if': 'TEST_IF_ENUM_MEMBER' } ],
'if': 'TEST_IF_UNION' }
{ 'union': 'TestIfUnion',
'base': { 'type': 'TestIfEnum' },
'discriminator': 'type',
'data': { 'foo': 'TestStruct',
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ENUM_BAR'} },
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ENUM_MEMBER'} },
'if': { 'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT'] } }
{ 'command': 'test-if-union-cmd',
@ -240,7 +273,7 @@
{ 'alternate': 'TestIfAlternate',
'data': { 'foo': 'int',
'bar': { 'type': 'TestStruct', 'if': 'TEST_IF_ALT_BAR'} },
'bar': { 'type': 'TestStruct', 'if': 'TEST_IF_ALT_MEMBER'} },
'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }
{ 'command': 'test-if-alternate-cmd',
@ -248,17 +281,16 @@
'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }
{ 'command': 'test-if-cmd',
'data': {
'foo': 'TestIfStruct',
'bar': { 'type': 'TestIfEnum', 'if': 'TEST_IF_CMD_BAR' } },
'boxed': true,
'data': 'TestIfStruct',
'returns': 'UserDefThree',
'if': { 'all': ['TEST_IF_CMD', 'TEST_IF_STRUCT'] } }
{ 'command': 'test-cmd-return-def-three', 'returns': 'UserDefThree' }
{ 'event': 'TEST_IF_EVENT',
'data': { 'foo': 'TestIfStruct',
'bar': { 'type': ['TestIfEnum'], 'if': 'TEST_IF_EVT_BAR' } },
'boxed': true,
'data': 'TestIfStruct',
'if': { 'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT'] } }
{ 'event': 'TEST_IF_EVENT2', 'data': {},

View file

@ -105,6 +105,35 @@ alternate UserDefAlternate
object UserDefC
member string1: str optional=False
member string2: str optional=False
enum TestUnionEnum
member value-a
member value-b
enum TestUnionEnumA
member value-a1
member value-a2
object TestUnionTypeA1
member integer: int optional=False
member name: str optional=False
object TestUnionTypeA2
member integer: int optional=False
member size: int optional=False
object q_obj_TestUnionTypeA-base
member type-a: TestUnionEnumA optional=False
object TestUnionTypeA
base q_obj_TestUnionTypeA-base
tag type-a
case value-a1: TestUnionTypeA1
case value-a2: TestUnionTypeA2
object TestUnionTypeB
member integer: int optional=False
member onoff: bool optional=False
object q_obj_TestUnionInUnion-base
member type: TestUnionEnum optional=False
object TestUnionInUnion
base q_obj_TestUnionInUnion-base
tag type
case value-a: TestUnionTypeA
case value-b: TestUnionTypeB
alternate AltEnumBool
tag type
case e: EnumOne
@ -246,13 +275,15 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> None
object TestIfStruct
member foo: int optional=False
member bar: int optional=False
if TEST_IF_STRUCT_BAR
if TEST_IF_STRUCT_MEMBER
member baz: str optional=True
if TEST_IF_STRUCT_MEMBER
if TEST_IF_STRUCT
enum TestIfEnum
member foo
member bar
if TEST_IF_ENUM_BAR
if TEST_IF_ENUM
if TEST_IF_ENUM_MEMBER
if TEST_IF_UNION
object q_obj_TestIfUnion-base
member type: TestIfEnum optional=False
if {'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT']}
@ -261,7 +292,7 @@ object TestIfUnion
tag type
case foo: TestStruct
case bar: UserDefZero
if TEST_IF_ENUM_BAR
if TEST_IF_ENUM_MEMBER
if {'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT']}
object q_obj_test-if-union-cmd-arg
member union-cmd-arg: TestIfUnion optional=False
@ -273,7 +304,7 @@ alternate TestIfAlternate
tag type
case foo: int
case bar: TestStruct
if TEST_IF_ALT_BAR
if TEST_IF_ALT_MEMBER
if {'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT']}
object q_obj_test-if-alternate-cmd-arg
member alt-cmd-arg: TestIfAlternate optional=False
@ -281,25 +312,13 @@ object q_obj_test-if-alternate-cmd-arg
command test-if-alternate-cmd q_obj_test-if-alternate-cmd-arg -> None
gen=True success_response=True boxed=False oob=False preconfig=False
if {'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT']}
object q_obj_test-if-cmd-arg
member foo: TestIfStruct optional=False
member bar: TestIfEnum optional=False
if TEST_IF_CMD_BAR
if {'all': ['TEST_IF_CMD', 'TEST_IF_STRUCT']}
command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
gen=True success_response=True boxed=False oob=False preconfig=False
command test-if-cmd TestIfStruct -> UserDefThree
gen=True success_response=True boxed=True oob=False preconfig=False
if {'all': ['TEST_IF_CMD', 'TEST_IF_STRUCT']}
command test-cmd-return-def-three None -> UserDefThree
gen=True success_response=True boxed=False oob=False preconfig=False
array TestIfEnumList TestIfEnum
if TEST_IF_ENUM
object q_obj_TEST_IF_EVENT-arg
member foo: TestIfStruct optional=False
member bar: TestIfEnumList optional=False
if TEST_IF_EVT_BAR
if {'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT']}
event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
boxed=False
event TEST_IF_EVENT TestIfStruct
boxed=True
if {'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT']}
event TEST_IF_EVENT2 None
boxed=False

View file

@ -1,2 +1,2 @@
returns-dict.json: In command 'oops':
returns-dict.json:2: 'returns' should be a type name
returns-dict.json:2: 'returns' should be a type name or array

View file

@ -0,0 +1,2 @@
struct-data-typename.json: In struct 'Stru2':
struct-data-typename.json:2: 'data' should be an object or type name

View file

@ -0,0 +1,2 @@
{ 'struct': 'Stru1', 'data': {} }
{ 'struct': 'Stru2', 'data': 'Stru1' }

View file

@ -1,2 +1,2 @@
struct-member-invalid.json: In struct 'Foo':
struct-member-invalid.json:1: 'data' member 'a' should be a type name
struct-member-invalid.json:1: 'data' member 'a' should be a type name or array

View file

@ -206,6 +206,7 @@ def main(argv):
parser.add_argument('-d', '--dir', action='store', default='',
help="directory containing tests")
parser.add_argument('-u', '--update', action='store_true',
default='QAPI_TEST_UPDATE' in os.environ,
help="update expected test results")
parser.add_argument('tests', nargs='*', metavar='TEST', action='store')
args = parser.parse_args()

View file

@ -1,2 +1,2 @@
union-array-branch.json: In union 'TestUnion':
union-array-branch.json:8: 'data' member 'value1' cannot be an array
union-array-branch.json:8: 'data' member 'value1' should be a type name

View file

@ -1,2 +1,2 @@
union-invalid-discriminator.json: In union 'TestUnion':
union-invalid-discriminator.json:10: discriminator 'enum_wrong' is not a member of 'base'
union-invalid-discriminator.json:10: discriminator 'type_tag' is not a member of 'base'

View file

@ -8,7 +8,7 @@
'data': { 'integer': 'int' } }
{ 'union': 'TestUnion',
'base': { 'enum1': 'TestEnum' },
'discriminator': 'enum_wrong',
'base': { 'type-tag': 'TestEnum' },
'discriminator': 'type_tag',
'data': { 'value1': 'TestTypeA',
'value2': 'TestTypeB' } }

View file

@ -0,0 +1,2 @@
union-invalid-union-subfield.json: In union 'TestUnion':
union-invalid-union-subfield.json:25: member 'teeth' of type 'TestTypeFish' collides with base member 'teeth'

View file

@ -0,0 +1,30 @@
# Clash between common member and union variant's variant member
# Base's member 'teeth' clashes with TestTypeFish's
{ 'enum': 'TestEnum',
'data': [ 'animals', 'plants' ] }
{ 'enum': 'TestAnimals',
'data': [ 'fish', 'birds'] }
{ 'struct': 'TestTypeFish',
'data': { 'scales': 'int', 'teeth': 'int' } }
{ 'struct': 'TestTypeBirds',
'data': { 'feathers': 'int' } }
{ 'union': 'TestTypeAnimals',
'base': { 'atype': 'TestAnimals' },
'discriminator': 'atype',
'data': { 'fish': 'TestTypeFish',
'birds': 'TestTypeBirds' } }
{ 'struct': 'TestTypePlants',
'data': { 'integer': 'int' } }
{ 'union': 'TestUnion',
'base': { 'type': 'TestEnum',
'teeth': 'int' },
'discriminator': 'type',
'data': { 'animals': 'TestTypeAnimals',
'plants': 'TestTypePlants' } }

View file

@ -0,0 +1,2 @@
union-invalid-union-subtype.json: In union 'TestUnion':
union-invalid-union-subtype.json:25: base member 'type' of type 'TestTypeA' collides with base member 'type'

View file

@ -0,0 +1,29 @@
# Clash between common member and union variant's common member
# Base's member 'type' clashes with TestTypeA's
{ 'enum': 'TestEnum',
'data': [ 'value-a', 'value-b' ] }
{ 'enum': 'TestEnumA',
'data': [ 'value-a1', 'value-a2' ] }
{ 'struct': 'TestTypeA1',
'data': { 'integer': 'int' } }
{ 'struct': 'TestTypeA2',
'data': { 'integer': 'int' } }
{ 'union': 'TestTypeA',
'base': { 'type': 'TestEnumA' },
'discriminator': 'type',
'data': { 'value-a1': 'TestTypeA1',
'value-a2': 'TestTypeA2' } }
{ 'struct': 'TestTypeB',
'data': { 'integer': 'int' } }
{ 'union': 'TestUnion',
'base': { 'type': 'TestEnum' },
'discriminator': 'type',
'data': { 'value-a': 'TestTypeA',
'value-b': 'TestTypeB' } }

View file

@ -706,6 +706,51 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data,
g_assert(&base->enum1 == &tmp->enum1);
}
static void test_visitor_in_union_in_union(TestInputVisitorData *data,
const void *unused)
{
Visitor *v;
g_autoptr(TestUnionInUnion) tmp = NULL;
v = visitor_input_test_init(data,
"{ 'type': 'value-a', "
" 'type-a': 'value-a1', "
" 'integer': 2, "
" 'name': 'fish' }");
visit_type_TestUnionInUnion(v, NULL, &tmp, &error_abort);
g_assert_cmpint(tmp->type, ==, TEST_UNION_ENUM_VALUE_A);
g_assert_cmpint(tmp->u.value_a.type_a, ==, TEST_UNION_ENUMA_VALUE_A1);
g_assert_cmpint(tmp->u.value_a.u.value_a1.integer, ==, 2);
g_assert_cmpint(strcmp(tmp->u.value_a.u.value_a1.name, "fish"), ==, 0);
qapi_free_TestUnionInUnion(tmp);
v = visitor_input_test_init(data,
"{ 'type': 'value-a', "
" 'type-a': 'value-a2', "
" 'integer': 1729, "
" 'size': 87539319 }");
visit_type_TestUnionInUnion(v, NULL, &tmp, &error_abort);
g_assert_cmpint(tmp->type, ==, TEST_UNION_ENUM_VALUE_A);
g_assert_cmpint(tmp->u.value_a.type_a, ==, TEST_UNION_ENUMA_VALUE_A2);
g_assert_cmpint(tmp->u.value_a.u.value_a2.integer, ==, 1729);
g_assert_cmpint(tmp->u.value_a.u.value_a2.size, ==, 87539319);
qapi_free_TestUnionInUnion(tmp);
v = visitor_input_test_init(data,
"{ 'type': 'value-b', "
" 'integer': 1729, "
" 'onoff': true }");
visit_type_TestUnionInUnion(v, NULL, &tmp, &error_abort);
g_assert_cmpint(tmp->type, ==, TEST_UNION_ENUM_VALUE_B);
g_assert_cmpint(tmp->u.value_b.integer, ==, 1729);
g_assert_cmpint(tmp->u.value_b.onoff, ==, true);
}
static void test_visitor_in_alternate(TestInputVisitorData *data,
const void *unused)
{
@ -1216,6 +1261,8 @@ int main(int argc, char **argv)
NULL, test_visitor_in_null);
input_visitor_test_add("/visitor/input/union-flat",
NULL, test_visitor_in_union_flat);
input_visitor_test_add("/visitor/input/union-in-union",
NULL, test_visitor_in_union_in_union);
input_visitor_test_add("/visitor/input/alternate",
NULL, test_visitor_in_alternate);
input_visitor_test_add("/visitor/input/errors",

View file

@ -352,6 +352,62 @@ static void test_visitor_out_union_flat(TestOutputVisitorData *data,
qapi_free_UserDefFlatUnion(tmp);
}
static void test_visitor_out_union_in_union(TestOutputVisitorData *data,
const void *unused)
{
QDict *qdict;
TestUnionInUnion *tmp = g_new0(TestUnionInUnion, 1);
tmp->type = TEST_UNION_ENUM_VALUE_A;
tmp->u.value_a.type_a = TEST_UNION_ENUMA_VALUE_A1;
tmp->u.value_a.u.value_a1.integer = 42;
tmp->u.value_a.u.value_a1.name = g_strdup("fish");
visit_type_TestUnionInUnion(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "value-a");
g_assert_cmpstr(qdict_get_str(qdict, "type-a"), ==, "value-a1");
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 42);
g_assert_cmpstr(qdict_get_str(qdict, "name"), ==, "fish");
qapi_free_TestUnionInUnion(tmp);
visitor_reset(data);
tmp = g_new0(TestUnionInUnion, 1);
tmp->type = TEST_UNION_ENUM_VALUE_A;
tmp->u.value_a.type_a = TEST_UNION_ENUMA_VALUE_A2;
tmp->u.value_a.u.value_a2.integer = 1729;
tmp->u.value_a.u.value_a2.size = 87539319;
visit_type_TestUnionInUnion(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "value-a");
g_assert_cmpstr(qdict_get_str(qdict, "type-a"), ==, "value-a2");
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 1729);
g_assert_cmpint(qdict_get_int(qdict, "size"), ==, 87539319);
qapi_free_TestUnionInUnion(tmp);
visitor_reset(data);
tmp = g_new0(TestUnionInUnion, 1);
tmp->type = TEST_UNION_ENUM_VALUE_B;
tmp->u.value_b.integer = 1729;
tmp->u.value_b.onoff = true;
visit_type_TestUnionInUnion(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "value-b");
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 1729);
g_assert_cmpint(qdict_get_bool(qdict, "onoff"), ==, true);
qapi_free_TestUnionInUnion(tmp);
}
static void test_visitor_out_alternate(TestOutputVisitorData *data,
const void *unused)
{
@ -586,6 +642,8 @@ int main(int argc, char **argv)
&out_visitor_data, test_visitor_out_list_qapi_free);
output_visitor_test_add("/visitor/output/union-flat",
&out_visitor_data, test_visitor_out_union_flat);
output_visitor_test_add("/visitor/output/union-in-union",
&out_visitor_data, test_visitor_out_union_in_union);
output_visitor_test_add("/visitor/output/alternate",
&out_visitor_data, test_visitor_out_alternate);
output_visitor_test_add("/visitor/output/null",