qapi: New QMP command query-qmp-schema for QMP introspection
qapi/introspect.json defines the introspection schema. It's designed for QMP introspection, but should do for similar uses, such as QGA. The introspection schema does not reflect all the rules and restrictions that apply to QAPI schemata. A valid QAPI schema has an introspection value conforming to the introspection schema, but the converse is not true. Introspection lowers away a number of schema details, and makes implicit things explicit: * The built-in types are declared with their JSON type. All integer types are mapped to 'int', because how many bits we use internally is an implementation detail. It could be pressed into external interface service as very approximate range information, but that's a bad idea. If we need range information, we better do it properly. * Implicit type definitions are made explicit, and given auto-generated names: - Array types, named by appending "List" to the name of their element type, like in generated C. - The enumeration types implicitly defined by simple union types, named by appending "Kind" to the name of their simple union type, like in generated C. - Types that don't occur in generated C. Their names start with ':' so they don't clash with the user's names. * All type references are by name. * The struct and union types are generalized into an object type. * Base types are flattened. * Commands take a single argument and return a single result. Dictionary argument or list result is an implicit type definition. The empty object type is used when a command takes no arguments or produces no results. The argument is always of object type, but the introspection schema doesn't reflect that. The 'gen': false directive is omitted as implementation detail. The 'success-response' directive is omitted as well for now, even though it's not an implementation detail, because it's not used by QMP. * Events carry a single data value. Implicit type definition and empty object type use, just like for commands. The value is of object type, but the introspection schema doesn't reflect that. * Types not used by commands or events are omitted. Indirect use counts as use. * Optional members have a default, which can only be null right now Instead of a mandatory "optional" flag, we have an optional default. No default means mandatory, default null means optional without default value. Non-null is available for optional with default (possible future extension). * Clients should *not* look up types by name, because type names are not ABI. Look up the command or event you're interested in, then follow the references. TODO Should we hide the type names to eliminate the temptation? New generator scripts/qapi-introspect.py computes an introspection value for its input, and generates a C variable holding it. It can generate awfully long lines. Marked TODO. A new test-qmp-input-visitor test case feeds its result for both tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a QmpInputVisitor to verify it actually conforms to the schema. New QMP command query-qmp-schema takes its return value from that variable. Its reply is some 85KiBytes for me right now. If this turns out to be too much, we have a couple of options: * We can use shorter names in the JSON. Not the QMP style. * Optionally return the sub-schema for commands and events given as arguments. Right now qmp_query_schema() sends the string literal computed by qmp-introspect.py. To compute sub-schema at run time, we'd have to duplicate parts of qapi-introspect.py in C. Unattractive. * Let clients cache the output of query-qmp-schema. It changes only on QEMU upgrades, i.e. rarely. Provide a command query-qmp-schema-hash. Clients can have a cache indexed by hash, and re-query the schema only when they don't have it cached. Even simpler: put the hash in the QMP greeting. Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
parent
2d21291ae6
commit
39a1815816
1
.gitignore
vendored
1
.gitignore
vendored
@ -34,6 +34,7 @@
|
||||
/qapi-visit.[ch]
|
||||
/qapi-event.[ch]
|
||||
/qmp-commands.h
|
||||
/qmp-introspect.[ch]
|
||||
/qmp-marshal.c
|
||||
/qemu-doc.html
|
||||
/qemu-tech.html
|
||||
|
9
Makefile
9
Makefile
@ -52,6 +52,8 @@ endif
|
||||
GENERATED_HEADERS = config-host.h qemu-options.def
|
||||
GENERATED_HEADERS += qmp-commands.h qapi-types.h qapi-visit.h qapi-event.h
|
||||
GENERATED_SOURCES += qmp-marshal.c qapi-types.c qapi-visit.c qapi-event.c
|
||||
GENERATED_HEADERS += qmp-introspect.h
|
||||
GENERATED_SOURCES += qmp-introspect.c
|
||||
|
||||
GENERATED_HEADERS += trace/generated-events.h
|
||||
GENERATED_SOURCES += trace/generated-events.c
|
||||
@ -269,7 +271,7 @@ $(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
||||
|
||||
qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
|
||||
$(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
|
||||
$(SRC_PATH)/qapi/event.json
|
||||
$(SRC_PATH)/qapi/event.json $(SRC_PATH)/qapi/introspect.json
|
||||
|
||||
qapi-types.c qapi-types.h :\
|
||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
||||
@ -291,6 +293,11 @@ $(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
|
||||
$(gen-out-type) -o "." -m $<, \
|
||||
" GEN $@")
|
||||
qmp-introspect.h qmp-introspect.c :\
|
||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
|
||||
$(gen-out-type) -o "." $<, \
|
||||
" GEN $@")
|
||||
|
||||
QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
|
||||
$(qga-obj-y) qemu-ga.o: $(QGALIB_GEN)
|
||||
|
@ -1,7 +1,8 @@
|
||||
#######################################################################
|
||||
# Common libraries for tools and emulators
|
||||
stub-obj-y = stubs/
|
||||
util-obj-y = util/ qobject/ qapi/ qapi-types.o qapi-visit.o qapi-event.o
|
||||
util-obj-y = util/ qobject/ qapi/
|
||||
util-obj-y += qmp-introspect.o qapi-types.o qapi-visit.o qapi-event.o
|
||||
|
||||
#######################################################################
|
||||
# block-obj-y is code used by both qemu system emulation and qemu-img
|
||||
@ -92,6 +93,7 @@ common-obj-$(CONFIG_FDT) += device_tree.o
|
||||
# qapi
|
||||
|
||||
common-obj-y += qmp-marshal.o
|
||||
common-obj-y += qmp-introspect.o
|
||||
common-obj-y += qmp.o hmp.o
|
||||
endif
|
||||
|
||||
|
@ -502,13 +502,206 @@ Resulting in this JSON object:
|
||||
"timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
|
||||
|
||||
|
||||
== Client JSON Protocol introspection ==
|
||||
|
||||
Clients of a Client JSON Protocol commonly need to figure out what
|
||||
exactly the server (QEMU) supports.
|
||||
|
||||
For this purpose, QMP provides introspection via command
|
||||
query-qmp-schema. QGA currently doesn't support introspection.
|
||||
|
||||
query-qmp-schema returns a JSON array of SchemaInfo objects. These
|
||||
objects together describe the wire ABI, as defined in the QAPI schema.
|
||||
|
||||
However, the SchemaInfo can't reflect all the rules and restrictions
|
||||
that apply to QMP. It's interface introspection (figuring out what's
|
||||
there), not interface specification. The specification is in the QAPI
|
||||
schema. To understand how QMP is to be used, you need to study the
|
||||
QAPI schema.
|
||||
|
||||
Like any other command, query-qmp-schema is itself defined in the QAPI
|
||||
schema, along with the SchemaInfo type. This text attempts to give an
|
||||
overview how things work. For details you need to consult the QAPI
|
||||
schema.
|
||||
|
||||
SchemaInfo objects have common members "name" and "meta-type", and
|
||||
additional variant members depending on the value of meta-type.
|
||||
|
||||
Each SchemaInfo object describes a wire ABI entity of a certain
|
||||
meta-type: a command, event or one of several kinds of type.
|
||||
|
||||
SchemaInfo for entities defined in the QAPI schema have the same name
|
||||
as in the schema. This is the case for all commands and events, and
|
||||
most types.
|
||||
|
||||
Command and event names are part of the wire ABI, but type names are
|
||||
not. Therefore, looking up a type by its name in the QAPI schema is
|
||||
wrong. Look up the command or event, then follow references by name.
|
||||
|
||||
QAPI schema definitions not reachable that way are omitted.
|
||||
|
||||
The SchemaInfo for a command has meta-type "command", and variant
|
||||
members "arg-type" and "ret-type". On the wire, the "arguments"
|
||||
member of a client's "execute" command must conform to the object type
|
||||
named by "arg-type". The "return" member that the server passes in a
|
||||
success response conforms to the type named by "ret-type".
|
||||
|
||||
If the command takes no arguments, "arg-type" names an object type
|
||||
without members. Likewise, if the command returns nothing, "ret-type"
|
||||
names an object type without members.
|
||||
|
||||
Example: the SchemaInfo for command query-qmp-schema
|
||||
|
||||
{ "name": "query-qmp-schema", "meta-type": "command",
|
||||
"arg-type": ":empty", "ret-type": "SchemaInfoList" }
|
||||
|
||||
Type ":empty" is an object type without members, and type
|
||||
"SchemaInfoList" is the array of SchemaInfo type.
|
||||
|
||||
The SchemaInfo for an event has meta-type "event", and variant member
|
||||
"arg-type". On the wire, a "data" member that the server passes in an
|
||||
event conforms to the object type named by "arg-type".
|
||||
|
||||
If the event carries no additional information, "arg-type" names an
|
||||
object type without members. The event may not have a data member on
|
||||
the wire then.
|
||||
|
||||
Each command or event defined with dictionary-valued 'data' in the
|
||||
QAPI schema implicitly defines an object type called ":obj-NAME-arg",
|
||||
where NAME is the command or event's name.
|
||||
|
||||
Example: the SchemaInfo for EVENT_C from section Events
|
||||
|
||||
{ "name": "EVENT_C", "meta-type": "event",
|
||||
"arg-type": ":obj-EVENT_C-arg" }
|
||||
|
||||
Type ":obj-EVENT_C-arg" is an implicitly defined object type with
|
||||
the two members from the event's definition.
|
||||
|
||||
The SchemaInfo for struct and union types has meta-type "object".
|
||||
|
||||
The SchemaInfo for a struct type has variant member "members".
|
||||
|
||||
The SchemaInfo for a union type additionally has variant members "tag"
|
||||
and "variants".
|
||||
|
||||
"members" is a JSON array describing the object's common members, if
|
||||
any. Each element is a JSON object with members "name" (the member's
|
||||
name), "type" (the name of its type), and optionally "default". The
|
||||
member is optional if "default" is present. Currently, "default" can
|
||||
only have value null. Other values are reserved for future
|
||||
extensions.
|
||||
|
||||
Example: the SchemaInfo for MyType from section Struct types
|
||||
|
||||
{ "name": "MyType", "meta-type": "object",
|
||||
"members": [
|
||||
{ "name": "member1", "type": "str" },
|
||||
{ "name": "member2", "type": "int" },
|
||||
{ "name": "member3", "type": "str", "default": null } ] }
|
||||
|
||||
"tag" is the name of the common member serving as type tag.
|
||||
"variants" is a JSON array describing the object's variant members.
|
||||
Each element is a JSON object with members "case" (the value of type
|
||||
tag this element applies to) and "type" (the name of an object type
|
||||
that provides the variant members for this type tag value).
|
||||
|
||||
Example: the SchemaInfo for flat union BlockdevOptions from section
|
||||
Union types
|
||||
|
||||
{ "name": "BlockdevOptions", "meta-type": "object",
|
||||
"members": [
|
||||
{ "name": "driver", "type": "BlockdevDriver" },
|
||||
{ "name": "readonly", "type": "bool"} ],
|
||||
"tag": "driver",
|
||||
"variants": [
|
||||
{ "case": "file", "type": "FileOptions" },
|
||||
{ "case": "qcow2", "type": "Qcow2Options" } ] }
|
||||
|
||||
Note that base types are "flattened": its members are included in the
|
||||
"members" array.
|
||||
|
||||
A simple union implicitly defines an enumeration type for its implicit
|
||||
discriminator (called "type" on the wire, see section Union types).
|
||||
Such a type's name is made by appending "Kind" to the simple union's
|
||||
name.
|
||||
|
||||
A simple union implicitly defines an object type for each of its
|
||||
variants. The type's name is ":obj-NAME-wrapper", where NAME is the
|
||||
name of the name of the variant's type.
|
||||
|
||||
Example: the SchemaInfo for simple union BlockdevOptions from section
|
||||
Union types
|
||||
|
||||
{ "name": "BlockdevOptions", "meta-type": "object",
|
||||
"members": [
|
||||
{ "name": "kind", "type": "BlockdevOptionsKind" } ],
|
||||
"tag": "type",
|
||||
"variants": [
|
||||
{ "case": "file", "type": ":obj-FileOptions-wrapper" },
|
||||
{ "case": "qcow2", "type": ":obj-Qcow2Options-wrapper" } ] }
|
||||
|
||||
Enumeration type "BlockdevOptionsKind" and the object types
|
||||
":obj-FileOptions-wrapper", ":obj-Qcow2Options-wrapper" are
|
||||
implicitly defined.
|
||||
|
||||
The SchemaInfo for an alternate type has meta-type "alternate", and
|
||||
variant member "members". "members" is a JSON array. Each element is
|
||||
a JSON object with member "type", which names a type. Values of the
|
||||
alternate type conform to exactly one of its member types.
|
||||
|
||||
Example: the SchemaInfo for BlockRef from section Alternate types
|
||||
|
||||
{ "name": "BlockRef", "meta-type": "alternate",
|
||||
"members": [
|
||||
{ "type": "BlockdevOptions" },
|
||||
{ "type": "str" } ] }
|
||||
|
||||
The SchemaInfo for an array type has meta-type "array", and variant
|
||||
member "element-type", which names the array's element type. Array
|
||||
types are implicitly defined. An array type's name is made by
|
||||
appending "List" to its element type's name.
|
||||
|
||||
Example: the SchemaInfo for ['str']
|
||||
|
||||
{ "name": "strList", "meta-type": "array",
|
||||
"element-type": "str" }
|
||||
|
||||
The SchemaInfo for an enumeration type has meta-type "enum" and
|
||||
variant member "values".
|
||||
|
||||
Example: the SchemaInfo for MyEnum from section Enumeration types
|
||||
|
||||
{ "name": "MyEnum", "meta-type": "enum",
|
||||
"values": [ "value1", "value2", "value3" ] }
|
||||
|
||||
The SchemaInfo for a built-in type has the same name as the type in
|
||||
the QAPI schema (see section Built-in Types), with one exception
|
||||
detailed below. It has variant member "json-type" that shows how
|
||||
values of this type are encoded on the wire.
|
||||
|
||||
Example: the SchemaInfo for str
|
||||
|
||||
{ "name": "str", "meta-type": "builtin", "json-type": "string" }
|
||||
|
||||
The QAPI schema supports a number of integer types that only differ in
|
||||
how they map to C. They are identical as far as SchemaInfo is
|
||||
concerned. Therefore, they get all mapped to a single type "int" in
|
||||
SchemaInfo.
|
||||
|
||||
As explained above, type names are not part of the wire ABI. Not even
|
||||
the names of built-in types. Clients should examine member
|
||||
"json-type" instead of hard-coding names of built-in types.
|
||||
|
||||
|
||||
== Code generation ==
|
||||
|
||||
Schemas are fed into 3 scripts to generate all the code/files that, paired
|
||||
with the core QAPI libraries, comprise everything required to take JSON
|
||||
commands read in by a Client JSON Protocol server, unmarshal the arguments into
|
||||
the underlying C types, call into the corresponding C function, and map the
|
||||
response back to a Client JSON Protocol response to be returned to the user.
|
||||
Schemas are fed into four scripts to generate all the code/files that,
|
||||
paired with the core QAPI libraries, comprise everything required to
|
||||
take JSON commands read in by a Client JSON Protocol server, unmarshal
|
||||
the arguments into the underlying C types, call into the corresponding
|
||||
C function, and map the response back to a Client JSON Protocol
|
||||
response to be returned to the user.
|
||||
|
||||
As an example, we'll use the following schema, which describes a single
|
||||
complex user-defined type (which will produce a C struct, along with a list
|
||||
@ -856,3 +1049,37 @@ Example:
|
||||
extern const char *const example_QAPIEvent_lookup[];
|
||||
|
||||
#endif
|
||||
|
||||
=== scripts/qapi-introspect.py ===
|
||||
|
||||
Used to generate the introspection C code for a schema. The following
|
||||
files are created:
|
||||
|
||||
$(prefix)qmp-introspect.c - Defines a string holding a JSON
|
||||
description of the schema.
|
||||
$(prefix)qmp-introspect.h - Declares the above string.
|
||||
|
||||
Example:
|
||||
|
||||
$ python scripts/qapi-introspect.py --output-dir="qapi-generated"
|
||||
--prefix="example-" example-schema.json
|
||||
$ cat qapi-generated/example-qmp-introspect.c
|
||||
[Uninteresting stuff omitted...]
|
||||
|
||||
const char example_qmp_schema_json[] = "["
|
||||
"{\"arg-type\": \":empty\", \"meta-type\": \"event\", \"name\": \"MY_EVENT\"}, "
|
||||
"{\"json-type\": \"int\", \"meta-type\": \"builtin\", \"name\": \"int\"}, "
|
||||
"{\"json-type\": \"string\", \"meta-type\": \"builtin\", \"name\": \"str\"}, "
|
||||
"{\"members\": [], \"meta-type\": \"object\", \"name\": \":empty\"}, "
|
||||
"{\"members\": [{\"name\": \"arg1\", \"type\": \"UserDefOne\"}], \"meta-type\": \"object\", \"name\": \":obj-my-command-arg\"}, "
|
||||
"{\"members\": [{\"name\": \"integer\", \"type\": \"int\"}, {\"name\": \"string\", \"type\": \"str\"}], \"meta-type\": \"object\", \"name\": \"UserDefOne\"}, "
|
||||
"{\"arg-type\": \":obj-my-command-arg\", \"meta-type\": \"command\", \"name\": \"my-command\", \"ret-type\": \"UserDefOne\"}]";
|
||||
$ cat qapi-generated/example-qmp-introspect.h
|
||||
[Uninteresting stuff omitted...]
|
||||
|
||||
#ifndef EXAMPLE_QMP_INTROSPECT_H
|
||||
#define EXAMPLE_QMP_INTROSPECT_H
|
||||
|
||||
extern const char example_qmp_schema_json[];
|
||||
|
||||
#endif
|
||||
|
16
monitor.c
16
monitor.c
@ -74,6 +74,7 @@
|
||||
#include "block/qapi.h"
|
||||
#include "qapi/qmp-event.h"
|
||||
#include "qapi-event.h"
|
||||
#include "qmp-introspect.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
|
||||
/* for hmp_info_irq/pic */
|
||||
@ -928,6 +929,21 @@ EventInfoList *qmp_query_events(Error **errp)
|
||||
return ev_list;
|
||||
}
|
||||
|
||||
/*
|
||||
* Minor hack: generated marshalling suppressed for this command
|
||||
* ('gen': false in the schema) so we can parse the JSON string
|
||||
* directly into QObject instead of first parsing it with
|
||||
* visit_type_SchemaInfoList() into a SchemaInfoList, then marshal it
|
||||
* to QObject with generated output marshallers, every time. Instead,
|
||||
* we do it in test-qmp-input-visitor.c, just to make sure
|
||||
* qapi-introspect.py's output actually conforms to the schema.
|
||||
*/
|
||||
static void qmp_query_qmp_schema(QDict *qdict, QObject **ret_data,
|
||||
Error **errp)
|
||||
{
|
||||
*ret_data = qobject_from_json(qmp_schema_json);
|
||||
}
|
||||
|
||||
/* set the current CPU defined by the user */
|
||||
int monitor_set_cpu(int cpu_index)
|
||||
{
|
||||
|
@ -17,6 +17,9 @@
|
||||
# Tracing commands
|
||||
{ 'include': 'qapi/trace.json' }
|
||||
|
||||
# QAPI introspection
|
||||
{ 'include': 'qapi/introspect.json' }
|
||||
|
||||
##
|
||||
# @LostTickPolicy:
|
||||
#
|
||||
|
277
qapi/introspect.json
Normal file
277
qapi/introspect.json
Normal file
@ -0,0 +1,277 @@
|
||||
# -*- Mode: Python -*-
|
||||
#
|
||||
# QAPI/QMP introspection
|
||||
#
|
||||
# Copyright (C) 2015 Red Hat, Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Markus Armbruster <armbru@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
##
|
||||
# @query-qmp-schema
|
||||
#
|
||||
# Command query-qmp-schema exposes the QMP wire ABI as an array of
|
||||
# SchemaInfo. This lets QMP clients figure out what commands and
|
||||
# events are available in this QEMU, and their parameters and results.
|
||||
#
|
||||
# However, the SchemaInfo can't reflect all the rules and restrictions
|
||||
# that apply to QMP. It's interface introspection (figuring out
|
||||
# what's there), not interface specification. The specification is in
|
||||
# the QAPI schema.
|
||||
#
|
||||
# Returns: array of @SchemaInfo, where each element describes an
|
||||
# entity in the ABI: command, event, type, ...
|
||||
#
|
||||
# Note: the QAPI schema is also used to help define *internal*
|
||||
# interfaces, by defining QAPI types. These are not part of the QMP
|
||||
# wire ABI, and therefore not returned by this command.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'command': 'query-qmp-schema',
|
||||
'returns': [ 'SchemaInfo' ],
|
||||
'gen': false } # just to simplify qmp_query_json()
|
||||
|
||||
##
|
||||
# @SchemaMetaType
|
||||
#
|
||||
# This is a @SchemaInfo's meta type, i.e. the kind of entity it
|
||||
# describes.
|
||||
#
|
||||
# @builtin: a predefined type such as 'int' or 'bool'.
|
||||
#
|
||||
# @enum: an enumeration type
|
||||
#
|
||||
# @array: an array type
|
||||
#
|
||||
# @object: an object type (struct or union)
|
||||
#
|
||||
# @alternate: an alternate type
|
||||
#
|
||||
# @command: a QMP command
|
||||
#
|
||||
# @event: a QMP event
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'enum': 'SchemaMetaType',
|
||||
'data': [ 'builtin', 'enum', 'array', 'object', 'alternate',
|
||||
'command', 'event' ] }
|
||||
|
||||
##
|
||||
# @SchemaInfoBase
|
||||
#
|
||||
# Members common to any @SchemaInfo.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoBase',
|
||||
'data': { 'name': 'str', 'meta-type': 'SchemaMetaType' } }
|
||||
|
||||
##
|
||||
# @SchemaInfo
|
||||
#
|
||||
# @name: the entity's name, inherited from @base.
|
||||
# Entities defined in the QAPI schema have the name defined in
|
||||
# the schema. Implicitly defined entities have generated
|
||||
# names. See docs/qapi-code-gen.txt section "Client JSON
|
||||
# Protocol introspection" for details.
|
||||
#
|
||||
# All references to other SchemaInfo are by name.
|
||||
#
|
||||
# Command and event names are part of the wire ABI, but type names are
|
||||
# not. Therefore, looking up a type by "well-known" name is wrong.
|
||||
# Look up the command or event, then follow the references.
|
||||
#
|
||||
# @meta-type: the entity's meta type, inherited from @base.
|
||||
#
|
||||
# Additional members depend on the value of @meta-type.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'union': 'SchemaInfo',
|
||||
'base': 'SchemaInfoBase',
|
||||
'discriminator': 'meta-type',
|
||||
'data': {
|
||||
'builtin': 'SchemaInfoBuiltin',
|
||||
'enum': 'SchemaInfoEnum',
|
||||
'array': 'SchemaInfoArray',
|
||||
'object': 'SchemaInfoObject',
|
||||
'alternate': 'SchemaInfoAlternate',
|
||||
'command': 'SchemaInfoCommand',
|
||||
'event': 'SchemaInfoEvent' } }
|
||||
|
||||
##
|
||||
# @SchemaInfoBuiltin
|
||||
#
|
||||
# Additional SchemaInfo members for meta-type 'builtin'.
|
||||
#
|
||||
# @json-type: the JSON type used for this type on the wire.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoBuiltin',
|
||||
'data': { 'json-type': 'JSONType' } }
|
||||
|
||||
##
|
||||
# @JSONType
|
||||
#
|
||||
# The four primitive and two structured types according to RFC 7159
|
||||
# section 1, plus 'int' (split off 'number'), plus the obvious top
|
||||
# type 'value'.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'enum': 'JSONType',
|
||||
'data': [ 'string', 'number', 'int', 'boolean', 'null',
|
||||
'object', 'array', 'value' ] }
|
||||
|
||||
##
|
||||
# @SchemaInfoEnum
|
||||
#
|
||||
# Additional SchemaInfo members for meta-type 'enum'.
|
||||
#
|
||||
# @values: the enumeration type's values.
|
||||
#
|
||||
# Values of this type are JSON string on the wire.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoEnum',
|
||||
'data': { 'values': ['str'] } }
|
||||
|
||||
##
|
||||
# @SchemaInfoArray
|
||||
#
|
||||
# Additional SchemaInfo members for meta-type 'array'.
|
||||
#
|
||||
# @element-type: the array type's element type.
|
||||
#
|
||||
# Values of this type are JSON array on the wire.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoArray',
|
||||
'data': { 'element-type': 'str' } }
|
||||
|
||||
##
|
||||
# @SchemaInfoObject
|
||||
#
|
||||
# Additional SchemaInfo members for meta-type 'object'.
|
||||
#
|
||||
# @members: the object type's (non-variant) members.
|
||||
#
|
||||
# @tag: #optional the name of the member serving as type tag.
|
||||
# An element of @members with this name must exist.
|
||||
#
|
||||
# @variants: #optional variant members, i.e. additional members that
|
||||
# depend on the type tag's value. Present exactly when
|
||||
# @tag is present.
|
||||
#
|
||||
# Values of this type are JSON object on the wire.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoObject',
|
||||
'data': { 'members': [ 'SchemaInfoObjectMember' ],
|
||||
'*tag': 'str',
|
||||
'*variants': [ 'SchemaInfoObjectVariant' ] } }
|
||||
|
||||
##
|
||||
# @SchemaInfoObjectMember
|
||||
#
|
||||
# An object member.
|
||||
#
|
||||
# @name: the member's name, as defined in the QAPI schema.
|
||||
#
|
||||
# @type: the name of the member's type.
|
||||
#
|
||||
# @default: #optional default when used as command parameter.
|
||||
# If absent, the parameter is mandatory.
|
||||
# If present, the value must be null. The parameter is
|
||||
# optional, and behavior when it's missing is not specified
|
||||
# here.
|
||||
# Future extension: if present and non-null, the parameter
|
||||
# is optional, and defaults to this value.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoObjectMember',
|
||||
'data': { 'name': 'str', 'type': 'str', '*default': 'any' } }
|
||||
# @default's type must be null or match @type
|
||||
|
||||
##
|
||||
# @SchemaInfoObjectVariant
|
||||
#
|
||||
# The variant members for a value of the type tag.
|
||||
#
|
||||
# @case: a value of the type tag.
|
||||
#
|
||||
# @type: the name of the object type that provides the variant members
|
||||
# when the type tag has value @case.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoObjectVariant',
|
||||
'data': { 'case': 'str', 'type': 'str' } }
|
||||
|
||||
##
|
||||
# @SchemaInfoAlternate
|
||||
#
|
||||
# Additional SchemaInfo members for meta-type 'alternate'.
|
||||
#
|
||||
# @members: the alternate type's members.
|
||||
# The members' wire encoding is distinct, see
|
||||
# docs/qapi-code-gen.txt section Alternate types.
|
||||
#
|
||||
# On the wire, this can be any of the members.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoAlternate',
|
||||
'data': { 'members': [ 'SchemaInfoAlternateMember' ] } }
|
||||
|
||||
##
|
||||
# @SchemaInfoAlternateMember
|
||||
#
|
||||
# An alternate member.
|
||||
#
|
||||
# @type: the name of the member's type.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoAlternateMember',
|
||||
'data': { 'type': 'str' } }
|
||||
|
||||
##
|
||||
# @SchemaInfoCommand
|
||||
#
|
||||
# Additional SchemaInfo members for meta-type 'command'.
|
||||
#
|
||||
# @arg-type: the name of the object type that provides the command's
|
||||
# parameters.
|
||||
#
|
||||
# @ret-type: the name of the command's result type.
|
||||
#
|
||||
# TODO @success-response (currently irrelevant, because it's QGA, not QMP)
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoCommand',
|
||||
'data': { 'arg-type': 'str', 'ret-type': 'str' } }
|
||||
|
||||
##
|
||||
# @SchemaInfoEvent
|
||||
#
|
||||
# Additional SchemaInfo members for meta-type 'event'.
|
||||
#
|
||||
# @arg-type: the name of the object type that provides the event's
|
||||
# parameters.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'SchemaInfoEvent',
|
||||
'data': { 'arg-type': 'str' } }
|
@ -2192,6 +2192,23 @@ EQMP
|
||||
.mhandler.cmd_new = qmp_marshal_query_events,
|
||||
},
|
||||
|
||||
SQMP
|
||||
query-qmp-schema
|
||||
----------------
|
||||
|
||||
Return the QMP wire schema. The returned value is a json-array of
|
||||
named schema entities. Entities are commands, events and various
|
||||
types. See docs/qapi-code-gen.txt for information on their structure
|
||||
and intended use.
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
.name = "query-qmp-schema",
|
||||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_query_qmp_schema,
|
||||
},
|
||||
|
||||
SQMP
|
||||
query-chardev
|
||||
-------------
|
||||
|
184
scripts/qapi-introspect.py
Normal file
184
scripts/qapi-introspect.py
Normal file
@ -0,0 +1,184 @@
|
||||
#
|
||||
# QAPI introspection generator
|
||||
#
|
||||
# Copyright (C) 2015 Red Hat, Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Markus Armbruster <armbru@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2.
|
||||
# See the COPYING file in the top-level directory.
|
||||
|
||||
from qapi import *
|
||||
|
||||
|
||||
# Caveman's json.dumps() replacement (we're stuck at Python 2.4)
|
||||
# TODO try to use json.dumps() once we get unstuck
|
||||
def to_json(obj, level=0):
|
||||
if obj is None:
|
||||
ret = 'null'
|
||||
elif isinstance(obj, str):
|
||||
ret = '"' + obj.replace('"', r'\"') + '"'
|
||||
elif isinstance(obj, list):
|
||||
elts = [to_json(elt, level + 1)
|
||||
for elt in obj]
|
||||
ret = '[' + ', '.join(elts) + ']'
|
||||
elif isinstance(obj, dict):
|
||||
elts = ['"%s": %s' % (key.replace('"', r'\"'),
|
||||
to_json(obj[key], level + 1))
|
||||
for key in sorted(obj.keys())]
|
||||
ret = '{' + ', '.join(elts) + '}'
|
||||
else:
|
||||
assert False # not implemented
|
||||
if level == 1:
|
||||
ret = '\n' + ret
|
||||
return ret
|
||||
|
||||
|
||||
def to_c_string(string):
|
||||
return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"'
|
||||
|
||||
|
||||
class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor):
|
||||
def __init__(self):
|
||||
self.defn = None
|
||||
self.decl = None
|
||||
self._schema = None
|
||||
self._jsons = None
|
||||
self._used_types = None
|
||||
|
||||
def visit_begin(self, schema):
|
||||
self._schema = schema
|
||||
self._jsons = []
|
||||
self._used_types = []
|
||||
return QAPISchemaType # don't visit types for now
|
||||
|
||||
def visit_end(self):
|
||||
# visit the types that are actually used
|
||||
for typ in self._used_types:
|
||||
typ.visit(self)
|
||||
self._jsons.sort()
|
||||
# generate C
|
||||
# TODO can generate awfully long lines
|
||||
name = prefix + 'qmp_schema_json'
|
||||
self.decl = mcgen('''
|
||||
extern const char %(c_name)s[];
|
||||
''',
|
||||
c_name=c_name(name))
|
||||
lines = to_json(self._jsons).split('\n')
|
||||
c_string = '\n '.join([to_c_string(line) for line in lines])
|
||||
self.defn = mcgen('''
|
||||
const char %(c_name)s[] = %(c_string)s;
|
||||
''',
|
||||
c_name=c_name(name),
|
||||
c_string=c_string)
|
||||
self._schema = None
|
||||
self._jsons = None
|
||||
self._used_types = None
|
||||
|
||||
def _use_type(self, typ):
|
||||
# Map the various integer types to plain int
|
||||
if typ.json_type() == 'int':
|
||||
typ = self._schema.lookup_type('int')
|
||||
elif (isinstance(typ, QAPISchemaArrayType) and
|
||||
typ.element_type.json_type() == 'int'):
|
||||
typ = self._schema.lookup_type('intList')
|
||||
# Add type to work queue if new
|
||||
if typ not in self._used_types:
|
||||
self._used_types.append(typ)
|
||||
return typ.name
|
||||
|
||||
def _gen_json(self, name, mtype, obj):
|
||||
obj['name'] = name
|
||||
obj['meta-type'] = mtype
|
||||
self._jsons.append(obj)
|
||||
|
||||
def _gen_member(self, member):
|
||||
ret = {'name': member.name, 'type': self._use_type(member.type)}
|
||||
if member.optional:
|
||||
ret['default'] = None
|
||||
return ret
|
||||
|
||||
def _gen_variants(self, tag_name, variants):
|
||||
return {'tag': tag_name,
|
||||
'variants': [self._gen_variant(v) for v in variants]}
|
||||
|
||||
def _gen_variant(self, variant):
|
||||
return {'case': variant.name, 'type': self._use_type(variant.type)}
|
||||
|
||||
def visit_builtin_type(self, name, info, json_type):
|
||||
self._gen_json(name, 'builtin', {'json-type': json_type})
|
||||
|
||||
def visit_enum_type(self, name, info, values, prefix):
|
||||
self._gen_json(name, 'enum', {'values': values})
|
||||
|
||||
def visit_array_type(self, name, info, element_type):
|
||||
self._gen_json(name, 'array',
|
||||
{'element-type': self._use_type(element_type)})
|
||||
|
||||
def visit_object_type_flat(self, name, info, members, variants):
|
||||
obj = {'members': [self._gen_member(m) for m in members]}
|
||||
if variants:
|
||||
obj.update(self._gen_variants(variants.tag_member.name,
|
||||
variants.variants))
|
||||
self._gen_json(name, 'object', obj)
|
||||
|
||||
def visit_alternate_type(self, name, info, variants):
|
||||
self._gen_json(name, 'alternate',
|
||||
{'members': [{'type': self._use_type(m.type)}
|
||||
for m in variants.variants]})
|
||||
|
||||
def visit_command(self, name, info, arg_type, ret_type,
|
||||
gen, success_response):
|
||||
arg_type = arg_type or self._schema.the_empty_object_type
|
||||
ret_type = ret_type or self._schema.the_empty_object_type
|
||||
self._gen_json(name, 'command',
|
||||
{'arg-type': self._use_type(arg_type),
|
||||
'ret-type': self._use_type(ret_type)})
|
||||
|
||||
def visit_event(self, name, info, arg_type):
|
||||
arg_type = arg_type or self._schema.the_empty_object_type
|
||||
self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
|
||||
|
||||
(input_file, output_dir, do_c, do_h, prefix, dummy) = parse_command_line()
|
||||
|
||||
c_comment = '''
|
||||
/*
|
||||
* QAPI/QMP schema introspection
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
'''
|
||||
h_comment = '''
|
||||
/*
|
||||
* QAPI/QMP schema introspection
|
||||
*
|
||||
* Copyright (C) 2015 Red Hat, Inc.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
||||
* See the COPYING.LIB file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
'''
|
||||
|
||||
(fdef, fdecl) = open_output(output_dir, do_c, do_h, prefix,
|
||||
'qmp-introspect.c', 'qmp-introspect.h',
|
||||
c_comment, h_comment)
|
||||
|
||||
fdef.write(mcgen('''
|
||||
#include "%(prefix)sqmp-introspect.h"
|
||||
|
||||
''',
|
||||
prefix=prefix))
|
||||
|
||||
schema = QAPISchema(input_file)
|
||||
gen = QAPISchemaGenIntrospectVisitor()
|
||||
schema.visit(gen)
|
||||
fdef.write(gen.defn)
|
||||
fdecl.write(gen.decl)
|
||||
|
||||
close_output(fdef, fdecl)
|
@ -788,6 +788,9 @@ class QAPISchemaVisitor(object):
|
||||
def visit_object_type(self, name, info, base, members, variants):
|
||||
pass
|
||||
|
||||
def visit_object_type_flat(self, name, info, members, variants):
|
||||
pass
|
||||
|
||||
def visit_alternate_type(self, name, info, variants):
|
||||
pass
|
||||
|
||||
@ -943,6 +946,8 @@ class QAPISchemaObjectType(QAPISchemaType):
|
||||
def visit(self, visitor):
|
||||
visitor.visit_object_type(self.name, self.info,
|
||||
self.base, self.local_members, self.variants)
|
||||
visitor.visit_object_type_flat(self.name, self.info,
|
||||
self.members, self.variants)
|
||||
|
||||
|
||||
class QAPISchemaObjectTypeMember(object):
|
||||
@ -1113,6 +1118,9 @@ class QAPISchema(object):
|
||||
('bool', 'boolean', 'bool', 'false'),
|
||||
('any', 'value', 'QObject' + pointer_suffix, 'NULL')]:
|
||||
self._def_builtin_type(*t)
|
||||
self.the_empty_object_type = QAPISchemaObjectType(':empty', None, None,
|
||||
[], None)
|
||||
self._def_entity(self.the_empty_object_type)
|
||||
|
||||
def _make_implicit_enum_type(self, name, values):
|
||||
name = name + 'Kind'
|
||||
@ -1260,9 +1268,10 @@ class QAPISchema(object):
|
||||
ent.check(self)
|
||||
|
||||
def visit(self, visitor):
|
||||
visitor.visit_begin(self)
|
||||
ignore = visitor.visit_begin(self)
|
||||
for name in sorted(self._entity_dict.keys()):
|
||||
self._entity_dict[name].visit(visitor)
|
||||
if not ignore or not isinstance(self._entity_dict[name], ignore):
|
||||
self._entity_dict[name].visit(visitor)
|
||||
visitor.visit_end()
|
||||
|
||||
|
||||
|
1
tests/.gitignore
vendored
1
tests/.gitignore
vendored
@ -35,6 +35,7 @@ test-qmp-commands.h
|
||||
test-qmp-event
|
||||
test-qmp-input-strict
|
||||
test-qmp-input-visitor
|
||||
test-qmp-introspect.[ch]
|
||||
test-qmp-marshal.c
|
||||
test-qmp-output-visitor
|
||||
test-rcu-list
|
||||
|
@ -269,7 +269,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
|
||||
struct-base-clash.json struct-base-clash-deep.json )
|
||||
|
||||
GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
|
||||
tests/test-qmp-commands.h tests/test-qapi-event.h
|
||||
tests/test-qmp-commands.h tests/test-qapi-event.h \
|
||||
tests/test-qmp-introspect.h
|
||||
|
||||
test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \
|
||||
tests/check-qlist.o tests/check-qfloat.o tests/check-qjson.o \
|
||||
@ -289,7 +290,7 @@ QEMU_CFLAGS += -I$(SRC_PATH)/tests
|
||||
test-util-obj-y = libqemuutil.a libqemustub.a
|
||||
test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y)
|
||||
test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
|
||||
tests/test-qapi-event.o \
|
||||
tests/test-qapi-event.o tests/test-qmp-introspect.o \
|
||||
$(test-qom-obj-y)
|
||||
test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
|
||||
test-block-obj-y = $(block-obj-y) $(test-crypto-obj-y)
|
||||
@ -346,6 +347,11 @@ $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-eve
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
|
||||
$(gen-out-type) -o tests -p "test-" $<, \
|
||||
" GEN $@")
|
||||
tests/test-qmp-introspect.c tests/test-qmp-introspect.h :\
|
||||
$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
|
||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
|
||||
$(gen-out-type) -o tests -p "test-" $<, \
|
||||
" GEN $@")
|
||||
|
||||
tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o $(test-qapi-obj-y)
|
||||
tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o $(test-qapi-obj-y)
|
||||
|
@ -1,3 +1,4 @@
|
||||
object :empty
|
||||
alternate Alt
|
||||
case value: int
|
||||
case string: Enum
|
||||
|
@ -1,3 +1,4 @@
|
||||
object :empty
|
||||
object :obj-okay-arg
|
||||
member member1: intList optional=False
|
||||
member member2: defList optional=False
|
||||
|
@ -1 +1,2 @@
|
||||
object :empty
|
||||
enum Status ['good', 'bad', 'ugly']
|
||||
|
@ -0,0 +1 @@
|
||||
object :empty
|
@ -1 +1,2 @@
|
||||
object :empty
|
||||
enum MyEnum []
|
||||
|
@ -1 +1,2 @@
|
||||
object :empty
|
||||
event oops None
|
||||
|
@ -1,3 +1,4 @@
|
||||
object :empty
|
||||
object TestBase
|
||||
member enum1: TestEnum optional=False
|
||||
enum TestEnum ['value1', 'value2']
|
||||
|
@ -1,3 +1,4 @@
|
||||
object :empty
|
||||
object :obj-fooA-arg
|
||||
member bar1: str optional=False
|
||||
command fooA :obj-fooA-arg -> None
|
||||
|
@ -1 +1,2 @@
|
||||
object :empty
|
||||
enum Status ['good', 'bad', 'ugly']
|
||||
|
@ -1 +1,2 @@
|
||||
object :empty
|
||||
enum Status ['good', 'bad', 'ugly']
|
||||
|
@ -1 +1,2 @@
|
||||
object :empty
|
||||
enum Status ['good', 'bad', 'ugly']
|
||||
|
@ -1,3 +1,4 @@
|
||||
object :empty
|
||||
command eins None -> None
|
||||
gen=True success_response=True
|
||||
command zwei None -> None
|
||||
|
@ -1,3 +1,4 @@
|
||||
object :empty
|
||||
object :obj-EVENT_C-arg
|
||||
member a: int optional=True
|
||||
member b: UserDefOne optional=True
|
||||
|
@ -1,2 +1,3 @@
|
||||
object :empty
|
||||
command guest-get-time None -> int
|
||||
gen=True success_response=True
|
||||
|
@ -19,6 +19,9 @@
|
||||
#include "test-qapi-types.h"
|
||||
#include "test-qapi-visit.h"
|
||||
#include "qapi/qmp/types.h"
|
||||
#include "test-qmp-introspect.h"
|
||||
#include "qmp-introspect.h"
|
||||
#include "qapi-visit.h"
|
||||
|
||||
typedef struct TestInputVisitorData {
|
||||
QObject *obj;
|
||||
@ -62,6 +65,30 @@ Visitor *validate_test_init(TestInputVisitorData *data,
|
||||
return v;
|
||||
}
|
||||
|
||||
/* similar to validate_test_init(), but does not expect a string
|
||||
* literal/format json_string argument and so can be used for
|
||||
* programatically generated strings (and we can't pass in programatically
|
||||
* generated strings via %s format parameters since qobject_from_jsonv()
|
||||
* will wrap those in double-quotes and treat the entire object as a
|
||||
* string)
|
||||
*/
|
||||
static Visitor *validate_test_init_raw(TestInputVisitorData *data,
|
||||
const char *json_string)
|
||||
{
|
||||
Visitor *v;
|
||||
|
||||
data->obj = qobject_from_json(json_string);
|
||||
g_assert(data->obj != NULL);
|
||||
|
||||
data->qiv = qmp_input_visitor_new_strict(data->obj);
|
||||
g_assert(data->qiv != NULL);
|
||||
|
||||
v = qmp_input_get_visitor(data->qiv);
|
||||
g_assert(v != NULL);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
typedef struct TestStruct
|
||||
{
|
||||
int64_t integer;
|
||||
@ -293,6 +320,32 @@ static void test_validate_fail_alternate(TestInputVisitorData *data,
|
||||
qapi_free_UserDefAlternate(tmp);
|
||||
}
|
||||
|
||||
static void do_test_validate_qmp_introspect(TestInputVisitorData *data,
|
||||
const char *schema_json)
|
||||
{
|
||||
SchemaInfoList *schema = NULL;
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = validate_test_init_raw(data, schema_json);
|
||||
|
||||
visit_type_SchemaInfoList(v, &schema, NULL, &err);
|
||||
if (err) {
|
||||
fprintf(stderr, "%s", error_get_pretty(err));
|
||||
}
|
||||
g_assert(!err);
|
||||
g_assert(schema);
|
||||
|
||||
qapi_free_SchemaInfoList(schema);
|
||||
}
|
||||
|
||||
static void test_validate_qmp_introspect(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
do_test_validate_qmp_introspect(data, test_qmp_schema_json);
|
||||
do_test_validate_qmp_introspect(data, qmp_schema_json);
|
||||
}
|
||||
|
||||
static void validate_test_add(const char *testpath,
|
||||
TestInputVisitorData *data,
|
||||
void (*test_func)(TestInputVisitorData *data, const void *user_data))
|
||||
@ -333,6 +386,8 @@ int main(int argc, char **argv)
|
||||
&testdata, test_validate_fail_alternate);
|
||||
validate_test_add("/visitor/input-strict/fail/union-native-list",
|
||||
&testdata, test_validate_fail_union_native_list);
|
||||
validate_test_add("/visitor/input-strict/pass/qmp-introspect",
|
||||
&testdata, test_validate_qmp_introspect);
|
||||
|
||||
g_test_run();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user