By convention, names starting with "x-" are experimental. The parts of external interfaces so named may be withdrawn or changed incompatibly in future releases. The naming convention makes unstable interfaces easy to recognize. Promoting something from experimental to stable involves a name change. Client code needs to be updated. Occasionally bothersome. Worse, the convention is not universally observed: * QOM type "input-barrier" has properties "x-origin", "y-origin". Looks accidental, but it's ABI since 4.2. * QOM types "memory-backend-file", "memory-backend-memfd", "memory-backend-ram", and "memory-backend-epc" have a property "x-use-canonical-path-for-ramblock-id" that is documented to be stable despite its name. We could document these exceptions, but documentation helps only humans. We want to recognize "unstable" in code, like "deprecated". So support recognizing it the same way: introduce new special feature flag "unstable". It will be treated specially by the QAPI generator, like the existing feature flag "deprecated", and unlike regular feature flags. This commit updates documentation and prepares tests. The next commit updates the QAPI schema. The remaining patches update the QAPI generator and wire up -compat policy checking. Management applications can then use query-qmp-schema and -compat to manage or guard against use of unstable interfaces the same way as for deprecated interfaces. docs/devel/qapi-code-gen.txt no longer mandates the naming convention. Using it anyway might help writers of programs that aren't full-fledged management applications. Not using it can save us bothersome renames. We'll see how that shakes out. Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Juan Quintela <quintela@redhat.com> Reviewed-by: John Snow <jsnow@redhat.com> Message-Id: <20211028102520.747396-2-armbru@redhat.com>
354 lines
12 KiB
Python
354 lines
12 KiB
Python
# *-*- Mode: Python -*-*
|
|
# vim: filetype=python
|
|
|
|
# This file is a stress test of supported qapi constructs that must
|
|
# parse and compile correctly.
|
|
|
|
# Whitelists to permit QAPI rule violations
|
|
{ 'pragma': {
|
|
# Types whose member names may use '_'
|
|
'member-name-exceptions': [
|
|
'UserDefA'
|
|
],
|
|
# Commands allowed to return a non-dictionary:
|
|
'command-returns-exceptions': [
|
|
'guest-get-time',
|
|
'guest-sync' ] } }
|
|
|
|
{ 'struct': 'TestStruct',
|
|
'data': { 'integer': {'type': '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': { } }
|
|
|
|
# Likewise for an empty union
|
|
{ 'union': 'Union',
|
|
'base': { 'type': 'EnumOne' }, 'discriminator': 'type',
|
|
'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', 'value4' ] }
|
|
|
|
{ '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' } }
|
|
|
|
{ 'struct': 'UserDefThree',
|
|
'data': { 'string0': 'str' } }
|
|
|
|
# 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' : {'type': 'UserDefA'},
|
|
'value2' : 'UserDefB',
|
|
'value3' : 'UserDefB'
|
|
# 'value4' defaults to empty
|
|
} }
|
|
|
|
{ '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': {'type': 'UserDefFlatUnion'}, 'e': 'EnumOne', 'i': 'int',
|
|
'n': 'null' } }
|
|
|
|
{ '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' } }
|
|
|
|
# for testing use of 'str' within alternates
|
|
{ 'alternate': 'AltStrObj', 'data': { 's': 'str', 'o': 'TestStruct' } }
|
|
|
|
{ 'struct': 'ArrayStruct',
|
|
'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'],
|
|
'*sz': ['size'],
|
|
'*any': ['any'],
|
|
'*user': ['Status'] } } # intentional forward ref. to sub-module
|
|
|
|
# for testing sub-modules
|
|
{ 'include': 'include/sub-module.json' }
|
|
|
|
# testing commands
|
|
{ 'command': 'user-def-cmd', 'data': {} }
|
|
{ 'command': 'user-def-cmd1', 'data': {'ud1a': 'UserDefOne'} }
|
|
{ 'command': 'user-def-cmd2',
|
|
'data': {'ud1a': {'type': 'UserDefOne'}, '*ud1b': 'UserDefOne'},
|
|
'returns': 'UserDefTwo' }
|
|
|
|
{ 'command': 'cmd-success-response', 'data': {}, 'success-response': false }
|
|
{ 'command': 'coroutine-cmd', 'data': {}, 'coroutine': true }
|
|
|
|
# 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': 'UserDefFlatUnion', 'boxed': true }
|
|
{ 'command': 'boxed-empty', 'boxed': true, 'data': 'Empty1' }
|
|
|
|
# Smoke test on out-of-band and allow-preconfig-test
|
|
{ 'command': 'test-flags-command', 'allow-oob': true, 'allow-preconfig': 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': {'type': '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': 'UserDefFlatUnion' }
|
|
{ 'event': 'EVENT_G', 'boxed': true, 'data': 'Empty1' }
|
|
|
|
# test that we correctly compile downstream extensions, as well as munge
|
|
# ticklish names
|
|
# also test union and alternate with just one branch
|
|
{ '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' } }
|
|
{ 'alternate': '__org.qemu_x-Alt1', 'data': { '__org.qemu_x-branch': 'str' } }
|
|
{ 'struct': '__org.qemu_x-Struct2',
|
|
'data': { 'array': ['__org.qemu_x-Union'] } }
|
|
{ 'union': '__org.qemu_x-Union', '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': '__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-Union', 'd': '__org.qemu_x-Alt' } }
|
|
|
|
# test 'if' condition handling
|
|
|
|
{ 'struct': 'TestIfStruct',
|
|
'data': { 'foo': 'int',
|
|
'bar': { 'type': 'int', 'if': 'TEST_IF_STRUCT_BAR'} },
|
|
'if': 'TEST_IF_STRUCT' }
|
|
|
|
{ 'enum': 'TestIfEnum',
|
|
'data': [ 'foo', { 'name' : 'bar', 'if': 'TEST_IF_ENUM_BAR' } ],
|
|
'if': 'TEST_IF_ENUM' }
|
|
|
|
{ 'union': 'TestIfUnion',
|
|
'base': { 'type': 'TestIfEnum' },
|
|
'discriminator': 'type',
|
|
'data': { 'foo': 'TestStruct',
|
|
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ENUM_BAR'} },
|
|
'if': { 'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT'] } }
|
|
|
|
{ 'command': 'test-if-union-cmd',
|
|
'data': { 'union-cmd-arg': 'TestIfUnion' },
|
|
'if': { 'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT'] } }
|
|
|
|
{ 'alternate': 'TestIfAlternate',
|
|
'data': { 'foo': 'int',
|
|
'bar': { 'type': 'TestStruct', 'if': 'TEST_IF_ALT_BAR'} },
|
|
'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }
|
|
|
|
{ 'command': 'test-if-alternate-cmd',
|
|
'data': { 'alt-cmd-arg': 'TestIfAlternate' },
|
|
'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }
|
|
|
|
{ 'command': 'test-if-cmd',
|
|
'data': {
|
|
'foo': 'TestIfStruct',
|
|
'bar': { 'type': 'TestIfEnum', 'if': 'TEST_IF_CMD_BAR' } },
|
|
'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' } },
|
|
'if': { 'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT'] } }
|
|
|
|
{ 'event': 'TEST_IF_EVENT2', 'data': {},
|
|
'if': { 'not': { 'any': [ { 'not': 'TEST_IF_EVT' },
|
|
{ 'not': 'TEST_IF_STRUCT' } ] } } }
|
|
|
|
# test 'features'
|
|
|
|
{ 'struct': 'FeatureStruct0',
|
|
'data': { 'foo': 'int' },
|
|
'features': [] }
|
|
{ 'struct': 'FeatureStruct1',
|
|
'data': { 'foo': { 'type': 'int', 'features': [ 'deprecated' ] } },
|
|
'features': [ 'feature1' ] }
|
|
{ 'struct': 'FeatureStruct2',
|
|
'data': { 'foo': { 'type': 'int', 'features': [ 'unstable' ] } },
|
|
'features': [ { 'name': 'feature1' } ] }
|
|
{ 'struct': 'FeatureStruct3',
|
|
'data': { 'foo': 'int' },
|
|
'features': [ 'feature1', 'feature2' ] }
|
|
{ 'struct': 'FeatureStruct4',
|
|
'data': { 'namespace-test': 'int' },
|
|
'features': [ 'namespace-test', 'int', 'name', 'if' ] }
|
|
|
|
{ 'struct': 'CondFeatureStruct1',
|
|
'data': { 'foo': 'int' },
|
|
'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'} ] }
|
|
{ 'struct': 'CondFeatureStruct2',
|
|
'data': { 'foo': 'int' },
|
|
'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'},
|
|
{ 'name': 'feature2', 'if': 'TEST_IF_FEATURE_2'} ] }
|
|
{ 'struct': 'CondFeatureStruct3',
|
|
'data': { 'foo': 'int' },
|
|
'features': [ { 'name': 'feature1',
|
|
'if': { 'all': [ 'TEST_IF_COND_1',
|
|
'TEST_IF_COND_2'] } } ] }
|
|
{ 'struct': 'CondFeatureStruct4',
|
|
'data': { 'foo': 'int' },
|
|
'features': [ { 'name': 'feature1',
|
|
'if': {'any': ['TEST_IF_COND_1',
|
|
'TEST_IF_COND_2'] } } ] }
|
|
|
|
{ 'enum': 'FeatureEnum1',
|
|
'data': [ 'eins', 'zwei',
|
|
{ 'name': 'drei', 'features': [ 'deprecated' ] } ],
|
|
'features': [ 'feature1' ] }
|
|
|
|
{ 'union': 'FeatureUnion1',
|
|
'base': { 'tag': 'FeatureEnum1' },
|
|
'discriminator': 'tag',
|
|
'data': { 'eins': 'FeatureStruct1' },
|
|
'features': [ 'feature1' ] }
|
|
|
|
{ 'alternate': 'FeatureAlternate1',
|
|
'data': { 'eins': 'FeatureStruct1' },
|
|
'features': [ 'feature1' ] }
|
|
|
|
{ 'command': 'test-features0',
|
|
'data': { '*fs0': 'FeatureStruct0',
|
|
'*fs1': 'FeatureStruct1',
|
|
'*fs2': 'FeatureStruct2',
|
|
'*fs3': 'FeatureStruct3',
|
|
'*fs4': 'FeatureStruct4',
|
|
'*cfs1': 'CondFeatureStruct1',
|
|
'*cfs2': 'CondFeatureStruct2',
|
|
'*cfs3': 'CondFeatureStruct3',
|
|
'*cfs4': 'CondFeatureStruct4' },
|
|
'returns': 'FeatureStruct1',
|
|
'features': [] }
|
|
|
|
{ 'command': 'test-command-features1',
|
|
'features': [ 'deprecated' ] }
|
|
{ 'command': 'test-command-features3',
|
|
'features': [ 'unstable', 'feature1', 'feature2' ] }
|
|
|
|
{ 'command': 'test-command-cond-features1',
|
|
'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'} ] }
|
|
{ 'command': 'test-command-cond-features2',
|
|
'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'},
|
|
{ 'name': 'feature2', 'if': 'TEST_IF_FEATURE_2'} ] }
|
|
{ 'command': 'test-command-cond-features3',
|
|
'features': [ { 'name': 'feature1',
|
|
'if': { 'all': [ 'TEST_IF_COND_1',
|
|
'TEST_IF_COND_2'] } } ] }
|
|
|
|
{ 'event': 'TEST_EVENT_FEATURES0',
|
|
'data': 'FeatureStruct1' }
|
|
|
|
{ 'event': 'TEST_EVENT_FEATURES1',
|
|
'features': [ 'deprecated' ] }
|
|
|
|
{ 'event': 'TEST_EVENT_FEATURES2',
|
|
'features': [ 'unstable' ] }
|