From 6fb55451728e6dc74ae4e67e4f5ab557468f084e Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:04:58 -0600 Subject: [PATCH 01/40] qapi: Add copyright declaration on docs While our top-level COPYING with its GPLv2+ license applies to any documentation file that omits explicit instructions, these days it's better to be a good example of calling out our intentions. Correct use of GPL requires the use of a copyright statement, so I'm adding notice to two QAPI documents, by attributing these files to the initial authors and major contributors. I used: $ git blame --line-porcelain $file \ | sed -n 's/^author //p' | sort | uniq -c | sort -rn to determine authorship of these two files. qmp-spec.txt blames entirely to Red Hat (easy, since my contribution falls in that category); while qapi-code-gen.txt has multiple contributors representing multiple entities. But since it was originally supplied by Michael Roth, the notice I added there copies the notice he has used in other files. As there is no intended change in license from the implicit one previously present from the top level, I have not bothered to CC other contributors; if we want to weaken things to something looser (such as LGPL) so that there is no question that someone re-implementing the spec is not forced to use GPL, that would be a different commit. CC: Michael Roth Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/qapi-code-gen.txt | 8 ++++++++ docs/qmp/qmp-spec.txt | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index 8313ba6af8..e8bbaf8a07 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -1,5 +1,13 @@ = How to use the QAPI code generator = +Copyright IBM Corp. 2011 +Copyright (C) 2012-2015 Red Hat, Inc. + +This work is licensed under the terms of the GNU GPL, version 2 or +later. See the COPYING file in the top-level directory. + +== Introduction == + QAPI is a native C API within QEMU which provides management-level functionality to internal/external users. For external users/processes, this interface is made available by a JSON-based diff --git a/docs/qmp/qmp-spec.txt b/docs/qmp/qmp-spec.txt index 22568c644e..cb1600a983 100644 --- a/docs/qmp/qmp-spec.txt +++ b/docs/qmp/qmp-spec.txt @@ -1,5 +1,13 @@ QEMU Machine Protocol Specification +0. About This Document +====================== + +Copyright (C) 2009-2015 Red Hat, Inc. + +This work is licensed under the terms of the GNU GPL, version 2 or +later. See the COPYING file in the top-level directory. + 1. Introduction =============== From e790e666518e68134ca0570b6b4a707169ea3cb1 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:04:59 -0600 Subject: [PATCH 02/40] qapi: Document type-safety considerations Go into more details about the various types of valid expressions in a qapi schema, including tweaks to document fixes being done later in the current patch series. Also fix some stale and missing documentation in the QMP specification. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/qapi-code-gen.txt | 423 ++++++++++++++++++++++++++++++++--------- docs/qmp/qmp-spec.txt | 107 ++++++++--- 2 files changed, 411 insertions(+), 119 deletions(-) diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index e8bbaf8a07..6404a2d734 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -9,61 +9,179 @@ later. See the COPYING file in the top-level directory. == Introduction == QAPI is a native C API within QEMU which provides management-level -functionality to internal/external users. For external -users/processes, this interface is made available by a JSON-based -QEMU Monitor protocol that is provided by the QMP server. +functionality to internal and external users. For external +users/processes, this interface is made available by a JSON-based wire +format for the QEMU Monitor Protocol (QMP) for controlling qemu, as +well as the QEMU Guest Agent (QGA) for communicating with the guest. -To map QMP-defined interfaces to the native C QAPI implementations, -a JSON-based schema is used to define types and function -signatures, and a set of scripts is used to generate types/signatures, -and marshaling/dispatch code. The QEMU Guest Agent also uses these -scripts, paired with a separate schema, to generate -marshaling/dispatch code for the guest agent server running in the -guest. - -This document will describe how the schemas, scripts, and resulting -code are used. +To map QMP and QGA interfaces to the native C QAPI implementations, a +JSON-based schema is used to define types and function signatures, and +a set of scripts is used to generate types, signatures, and +marshaling/dispatch code. This document will describe how the schemas, +scripts, and resulting code are used. == QMP/Guest agent schema == -This file defines the types, commands, and events used by QMP. It should -fully describe the interface used by QMP. +A QAPI schema file is designed to be loosely based on JSON +(http://www.ietf.org/rfc/rfc7159.txt) with changes for quoting style +and the use of comments; a QAPI schema file is then parsed by a python +code generation program. A valid QAPI schema consists of a series of +top-level expressions, with no commas between them. Where +dictionaries (JSON objects) are used, they are parsed as python +OrderedDicts so that ordering is preserved (for predictable layout of +generated C structs and parameter lists). Ordering doesn't matter +between top-level expressions or the keys within an expression, but +does matter within dictionary values for 'data' and 'returns' members +of a single expression. QAPI schema input is written using 'single +quotes' instead of JSON's "double quotes" (in contrast, QMP uses no +comments, and while input accepts 'single quotes' as an extension, +output is strict JSON using only "double quotes"). As in JSON, +trailing commas are not permitted in arrays or dictionaries. Input +must be ASCII (although QMP supports full Unicode strings, the QAPI +parser does not). At present, there is no place where a QAPI schema +requires the use of JSON numbers or null. -This file is designed to be loosely based on JSON although it's technically -executable Python. While dictionaries are used, they are parsed as -OrderedDicts so that ordering is preserved. +Comments are allowed; anything between an unquoted # and the following +newline is ignored. Although there is not yet a documentation +generator, a form of stylized comments has developed for consistently +documenting details about an expression and when it was added to the +schema. The documentation is delimited between two lines of ##, then +the first line names the expression, an optional overview is provided, +then individual documentation about each member of 'data' is provided, +and finally, a 'Since: x.y.z' tag lists the release that introduced +the expression. Optional fields are tagged with the phrase +'#optional', often with their default value; and extensions added +after the expression was first released are also given a '(since +x.y.z)' comment. For example: -There are two basic syntaxes used, type definitions and command definitions. + ## + # @BlockStats: + # + # Statistics of a virtual block device or a block backing device. + # + # @device: #optional If the stats are for a virtual block device, the name + # corresponding to the virtual block device. + # + # @stats: A @BlockDeviceStats for the device. + # + # @parent: #optional This describes the file block device if it has one. + # + # @backing: #optional This describes the backing block device if it has one. + # (Since 2.0) + # + # Since: 0.14.0 + ## + { 'type': 'BlockStats', + 'data': {'*device': 'str', 'stats': 'BlockDeviceStats', + '*parent': 'BlockStats', + '*backing': 'BlockStats'} } -The first syntax defines a type and is represented by a dictionary. There are -three kinds of user-defined types that are supported: complex types, -enumeration types and union types. +The schema sets up a series of types, as well as commands and events +that will use those types. Forward references are allowed: the parser +scans in two passes, where the first pass learns all type names, and +the second validates the schema and generates the code. This allows +the definition of complex structs that can have mutually recursive +types, and allows for indefinite nesting of QMP that satisfies the +schema. A type name should not be defined more than once. -Generally speaking, types definitions should always use CamelCase for the type -names. Command names should be all lower case with words separated by a hyphen. +There are six top-level expressions recognized by the parser: +'include', 'command', 'type', 'enum', 'union', and 'event'. There are +several built-in types, such as 'int' and 'str'; additionally, the +top-level expressions can define complex types, enumeration types, and +several flavors of union types. The 'command' and 'event' expressions +can refer to existing types by name, or list an anonymous type as a +dictionary. Listing a type name inside an array refers to a +single-dimension array of that type; multi-dimension arrays are not +directly supported (although an array of a complex struct that +contains an array member is possible). + +Types, commands, and events share a common namespace. Therefore, +generally speaking, type definitions should always use CamelCase for +user-defined type names, while built-in types are lowercase. Type +definitions should not end in 'Kind', as this namespace is used for +creating implicit C enums for visiting union types. Command names, +and field names within a type, should be all lower case with words +separated by a hyphen. However, some existing older commands and +complex types use underscore; when extending such expressions, +consistency is preferred over blindly avoiding underscore. Event +names should be ALL_CAPS with words separated by underscore. The +special string '**' appears for some commands that manually perform +their own type checking rather than relying on the type-safe code +produced by the qapi code generators. + +Any name (command, event, type, field, or enum value) beginning with +"x-" is marked experimental, and may be withdrawn or changed +incompatibly in a future release. Downstream vendors may add +extensions; such extensions should begin with a prefix matching +"__RFQDN_" (for the reverse-fully-qualified-domain-name of the +vendor), even if the rest of the name uses dash (example: +__com.redhat_drive-mirror). Other than downstream extensions (with +leading underscore and the use of dots), all names should begin with a +letter, and contain only ASCII letters, digits, dash, and underscore. +It is okay to reuse names that match C keywords; the generator will +rename a field named "default" in the QAPI to "q_default" in the +generated C code. + +In the rest of this document, usage lines are given for each +expression type, with literal strings written in lower case and +placeholders written in capitals. If a literal string includes a +prefix of '*', that key/value pair can be omitted from the expression. +For example, a usage statement that includes '*base':COMPLEX-TYPE-NAME +means that an expression has an optional key 'base', which if present +must have a value that forms a complex type name. + + +=== Built-in Types === + +The following types are built-in to the parser: + 'str' - arbitrary UTF-8 string + 'int' - 64-bit signed integer (although the C code may place further + restrictions on acceptable range) + 'number' - floating point number + 'bool' - JSON value of true or false + 'int8', 'int16', 'int32', 'int64' - like 'int', but enforce maximum + bit size + 'uint8', 'uint16', 'uint32', 'uint64' - unsigned counterparts + 'size' - like 'uint64', but allows scaled suffix from command line + visitor === Includes === +Usage: { 'include': STRING } + The QAPI schema definitions can be modularized using the 'include' directive: - { 'include': 'path/to/file.json'} + { 'include': 'path/to/file.json' } The directive is evaluated recursively, and include paths are relative to the -file using the directive. Multiple includes of the same file are safe. +file using the directive. Multiple includes of the same file are +safe. No other keys should appear in the expression, and the include +value should be a string. + +As a matter of style, it is a good idea to have all files be +self-contained, but at the moment, nothing prevents an included file +from making a forward reference to a type that is only introduced by +an outer file. The parser may be made stricter in the future to +prevent incomplete include files. === Complex types === -A complex type is a dictionary containing a single key whose value is a -dictionary. This corresponds to a struct in C or an Object in JSON. An -example of a complex type is: +Usage: { 'type': STRING, 'data': DICT, '*base': COMPLEX-TYPE-NAME } + +A complex type is a dictionary containing a single 'data' key whose +value is a dictionary. This corresponds to a struct in C or an Object +in JSON. Each value of the 'data' dictionary must be the name of a +type, or a one-element array containing a type name. An example of a +complex type is: { 'type': 'MyType', 'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } } -The use of '*' as a prefix to the name means the member is optional. +The use of '*' as a prefix to the name means the member is optional in +the corresponding QMP usage. The default initialization value of an optional argument should not be changed between versions of QEMU unless the new default maintains backward @@ -108,22 +226,52 @@ both fields like this: { "file": "/some/place/my-image", "backing": "/some/place/my-backing-file" } + === Enumeration types === -An enumeration type is a dictionary containing a single key whose value is a -list of strings. An example enumeration is: +Usage: { 'enum': STRING, 'data': ARRAY-OF-STRING } + +An enumeration type is a dictionary containing a single 'data' key +whose value is a list of strings. An example enumeration is: { 'enum': 'MyEnum', 'data': [ 'value1', 'value2', 'value3' ] } +Nothing prevents an empty enumeration, although it is probably not +useful. The list of strings should be lower case; if an enum name +represents multiple words, use '-' between words. The string 'max' is +not allowed as an enum value, and values should not be repeated. + +The enumeration values are passed as strings over the QMP protocol, +but are encoded as C enum integral values in generated code. While +the C code starts numbering at 0, it is better to use explicit +comparisons to enum values than implicit comparisons to 0; the C code +will also include a generated enum member ending in _MAX for tracking +the size of the enum, useful when using common functions for +converting between strings and enum values. Since the wire format +always passes by name, it is acceptable to reorder or add new +enumeration members in any location without breaking QMP clients; +however, removing enum values would break compatibility. For any +complex type that has a field that will only contain a finite set of +string values, using an enum type for that field is better than +open-coding the field to be type 'str'. + + === Union types === -Union types are used to let the user choose between several different data -types. A union type is defined using a dictionary as explained in the +Usage: { 'union': STRING, 'data': DICT } +or: { 'union': STRING, 'data': DICT, 'base': COMPLEX-TYPE-NAME, + 'discriminator': ENUM-MEMBER-OF-BASE } +or: { 'union': STRING, 'data': DICT, 'discriminator': {} } + +Union types are used to let the user choose between several different +variants for an object. There are three flavors: simple (no +discriminator or base), flat (both base and discriminator are +strings), and anonymous (discriminator is an empty dictionary). A +union type is defined using a data dictionary as explained in the following paragraphs. - -A simple union type defines a mapping from discriminator values to data types -like in this example: +A simple union type defines a mapping from automatic discriminator +values to data types like in this example: { 'type': 'FileOptions', 'data': { 'filename': 'str' } } { 'type': 'Qcow2Options', @@ -133,36 +281,34 @@ like in this example: 'data': { 'file': 'FileOptions', 'qcow2': 'Qcow2Options' } } -In the QMP wire format, a simple union is represented by a dictionary that -contains the 'type' field as a discriminator, and a 'data' field that is of the -specified data type corresponding to the discriminator value: +In the QMP wire format, a simple union is represented by a dictionary +that contains the 'type' field as a discriminator, and a 'data' field +that is of the specified data type corresponding to the discriminator +value, as in these examples: + { "type": "file", "data" : { "filename": "/some/place/my-image" } } { "type": "qcow2", "data" : { "backing-file": "/some/place/my-image", "lazy-refcounts": true } } - -A union definition can specify a complex type as its base. In this case, the -fields of the complex type are included as top-level fields of the union -dictionary in the QMP wire format. An example definition is: - - { 'type': 'BlockdevCommonOptions', 'data': { 'readonly': 'bool' } } - { 'union': 'BlockdevOptions', - 'base': 'BlockdevCommonOptions', - 'data': { 'raw': 'RawOptions', - 'qcow2': 'Qcow2Options' } } - -And it looks like this on the wire: - - { "type": "qcow2", - "readonly": false, - "data" : { "backing-file": "/some/place/my-image", - "lazy-refcounts": true } } +The generated C code uses a struct containing a union. Additionally, +an implicit C enum 'NameKind' is created, corresponding to the union +'Name', for accessing the various branches of the union. No branch of +the union can be named 'max', as this would collide with the implicit +enum. The value for each branch can be of any type. -Flat union types avoid the nesting on the wire. They are used whenever a -specific field of the base type is declared as the discriminator ('type' is -then no longer generated). The discriminator must be of enumeration type. -The above example can then be modified as follows: +A flat union definition specifies a complex type as its base, and +avoids nesting on the wire. All branches of the union must be +complex types, and the top-level fields of the union dictionary on +the wire will be combination of fields from both the base type and the +appropriate branch type (when merging two dictionaries, there must be +no keys in common). The 'discriminator' field must be the name of an +enum-typed member of the base type. + +The following example enhances the above simple union example by +adding a common field 'readonly', renaming the discriminator to +something more applicable, and reducing the number of {} required on +the wire: { 'enum': 'BlockdevDriver', 'data': [ 'raw', 'qcow2' ] } { 'type': 'BlockdevCommonOptions', @@ -170,28 +316,47 @@ The above example can then be modified as follows: { 'union': 'BlockdevOptions', 'base': 'BlockdevCommonOptions', 'discriminator': 'driver', - 'data': { 'raw': 'RawOptions', + 'data': { 'file': 'FileOptions', 'qcow2': 'Qcow2Options' } } -Resulting in this JSON object: +Resulting in these JSON objects: - { "driver": "qcow2", - "readonly": false, - "backing-file": "/some/place/my-image", - "lazy-refcounts": true } + { "driver": "file", "readonly": true, + "filename": "/some/place/my-image" } + { "driver": "qcow2", "readonly": false, + "backing-file": "/some/place/my-image", "lazy-refcounts": true } + +Notice that in a flat union, the discriminator name is controlled by +the user, but because it must map to a base member with enum type, the +code generator can ensure that branches exist for all values of the +enum (although the order of the keys need not match the declaration of +the enum). In the resulting generated C data types, a flat union is +represented as a struct with the base member fields included directly, +and then a union of structures for each branch of the struct. + +A simple union can always be re-written as a flat union where the base +class has a single member named 'type', and where each branch of the +union has a complex type with a single member named 'data'. That is, + + { 'union': 'Simple', 'data': { 'one': 'str', 'two': 'int' } } + +is identical on the wire to: + + { 'enum': 'Enum', 'data': ['one', 'two'] } + { 'type': 'Base', 'data': { 'type': 'Enum' } } + { 'type': 'Branch1', 'data': { 'data': 'str' } } + { 'type': 'Branch2', 'data': { 'data': 'int' } } + { 'union': 'Flat': 'base': 'Base', 'discriminator': 'type', + 'data': { 'one': 'Branch1', 'two': 'Branch2' } } -A special type of unions are anonymous unions. They don't form a dictionary in -the wire format but allow the direct use of different types in their place. As -they aren't structured, they don't have any explicit discriminator but use -the (QObject) data type of their value as an implicit discriminator. This means -that they are restricted to using only one discriminator value per QObject -type. For example, you cannot have two different complex types in an anonymous -union, or two different integer types. - -Anonymous unions are declared using an empty dictionary as their discriminator. -The discriminator values never appear on the wire, they are only used in the -generated C code. Anonymous unions cannot have a base type. +The final flavor of unions is an anonymous union. While the other two +union types are always passed as a JSON object in the wire format, an +anonymous union instead allows the direct use of different types in +its place. Anonymous unions are declared using an empty dictionary as +their discriminator. The discriminator values never appear on the +wire, they are only used in the generated C code. Anonymous unions +cannot have a base type. { 'union': 'BlockRef', 'discriminator': {}, @@ -208,23 +373,95 @@ This example allows using both of the following example objects: === Commands === -Commands are defined by using a list containing three members. The first -member is the command name, the second member is a dictionary containing -arguments, and the third member is the return type. +Usage: { 'command': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT, + '*returns': TYPE-NAME-OR-DICT, + '*gen': false, '*success-response': false } -An example command is: +Commands are defined by using a dictionary containing several members, +where three members are most common. The 'command' member is a +mandatory string, and determines the "execute" value passed in a QMP +command exchange. + +The 'data' argument maps to the "arguments" dictionary passed in as +part of a QMP command. The 'data' member is optional and defaults to +{} (an empty dictionary). If present, it must be the string name of a +complex type, a one-element array containing the name of a complex +type, or a dictionary that declares an anonymous type with the same +semantics as a 'type' expression, with one exception noted below when +'gen' is used. + +The 'returns' member describes what will appear in the "return" field +of a QMP reply on successful completion of a command. The member is +optional from the command declaration; if absent, the "return" field +will be an empty dictionary. If 'returns' is present, it must be the +string name of a complex or built-in type, a one-element array +containing the name of a complex or built-in type, or a dictionary +that declares an anonymous type with the same semantics as a 'type' +expression, with one exception noted below when 'gen' is used. +Although it is permitted to have the 'returns' member name a built-in +type or an array of built-in types, any command that does this cannot +be extended to return additional information in the future; thus, new +commands should strongly consider returning a dictionary-based type or +an array of dictionaries, even if the dictionary only contains one +field at the present. + +All commands use a dictionary to report failure, with no way to +specify that in QAPI. Where the error return is different than the +usual GenericError class in order to help the client react differently +to certain error conditions, it is worth documenting this in the +comments before the command declaration. + +Some example commands: + + { 'command': 'my-first-command', + 'data': { 'arg1': 'str', '*arg2': 'str' } } + { 'type': 'MyType', 'data': { '*value': 'str' } } + { 'command': 'my-second-command', + 'returns': [ 'MyType' ] } + +which would validate this QMP transaction: + + => { "execute": "my-first-command", + "arguments": { "arg1": "hello" } } + <= { "return": { } } + => { "execute": "my-second-command" } + <= { "return": [ { "value": "one" }, { } ] } + +In rare cases, QAPI cannot express a type-safe representation of a +corresponding QMP command. In these cases, if the command expression +includes the key 'gen' with boolean value false, then the 'data' or +'returns' member that intends to bypass generated type-safety and do +its own manual validation should use an inline dictionary definition, +with a value of '**' rather than a valid type name for the keys that +the generated code will not validate. Please try to avoid adding new +commands that rely on this, and instead use type-safe unions. For an +example of bypass usage: + + { 'command': 'netdev_add', + 'data': {'type': 'str', 'id': 'str', '*props': '**'}, + 'gen': false } + +Normally, the QAPI schema is used to describe synchronous exchanges, +where a response is expected. But in some cases, the action of a +command is expected to change state in a way that a successful +response is not possible (although the command will still return a +normal dictionary error on failure). When a successful reply is not +possible, the command expression should include the optional key +'success-response' with boolean value false. So far, only QGA makes +use of this field. - { 'command': 'my-command', - 'data': { 'arg1': 'str', '*arg2': 'str' }, - 'returns': 'str' } === Events === -Events are defined with the keyword 'event'. When 'data' is also specified, -additional info will be included in the event. Finally there will be C API -generated in qapi-event.h; when called by QEMU code, a message with timestamp -will be emitted on the wire. If timestamp is -1, it means failure to retrieve -host time. +Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT } + +Events are defined with the keyword 'event'. It is not allowed to +name an event 'MAX', since the generator also produces a C enumeration +of all event names with a generated _MAX value at the end. When +'data' is also specified, additional info will be included in the +event, with similar semantics to a 'type' expression. Finally there +will be C API generated in qapi-event.h; when called by QEMU code, a +message with timestamp will be emitted on the wire. An example event is: @@ -319,7 +556,7 @@ Example: #ifndef EXAMPLE_QAPI_TYPES_H #define EXAMPLE_QAPI_TYPES_H -[Builtin types omitted...] +[Built-in types omitted...] typedef struct UserDefOne UserDefOne; @@ -332,7 +569,7 @@ Example: struct UserDefOneList *next; } UserDefOneList; -[Functions on builtin types omitted...] +[Functions on built-in types omitted...] struct UserDefOne { @@ -431,7 +668,7 @@ Example: #ifndef EXAMPLE_QAPI_VISIT_H #define EXAMPLE_QAPI_VISIT_H -[Visitors for builtin types omitted...] +[Visitors for built-in types omitted...] void visit_type_UserDefOne(Visitor *m, UserDefOne **obj, const char *name, Error **errp); void visit_type_UserDefOneList(Visitor *m, UserDefOneList **obj, const char *name, Error **errp); diff --git a/docs/qmp/qmp-spec.txt b/docs/qmp/qmp-spec.txt index cb1600a983..4c28cd9438 100644 --- a/docs/qmp/qmp-spec.txt +++ b/docs/qmp/qmp-spec.txt @@ -11,8 +11,11 @@ later. See the COPYING file in the top-level directory. 1. Introduction =============== -This document specifies the QEMU Machine Protocol (QMP), a JSON-based protocol -which is available for applications to operate QEMU at the machine-level. +This document specifies the QEMU Machine Protocol (QMP), a JSON-based +protocol which is available for applications to operate QEMU at the +machine-level. It is also in use by the QEMU Guest Agent (QGA), which +is available for host applications to interact with the guest +operating system. 2. Protocol Specification ========================= @@ -26,14 +29,27 @@ following format: json-DATA-STRUCTURE-NAME -Where DATA-STRUCTURE-NAME is any valid JSON data structure, as defined by -the JSON standard: +Where DATA-STRUCTURE-NAME is any valid JSON data structure, as defined +by the JSON standard: -http://www.ietf.org/rfc/rfc4627.txt +http://www.ietf.org/rfc/rfc7159.txt -For convenience, json-object members and json-array elements mentioned in -this document will be in a certain order. However, in real protocol usage -they can be in ANY order, thus no particular order should be assumed. +The protocol is always encoded in UTF-8 except for synchronization +bytes (documented below); although thanks to json-string escape +sequences, the server will reply using only the strict ASCII subset. + +For convenience, json-object members mentioned in this document will +be in a certain order. However, in real protocol usage they can be in +ANY order, thus no particular order should be assumed. On the other +hand, use of json-array elements presumes that preserving order is +important unless specifically documented otherwise. Repeating a key +within a json-object gives unpredictable results. + +Also for convenience, the server will accept an extension of +'single-quoted' strings in place of the usual "double-quoted" +json-string, and both input forms of strings understand an additional +escape sequence of "\'" for a single quote. The server will only use +double quoting on output. 2.1 General Definitions ----------------------- @@ -60,7 +76,16 @@ The greeting message format is: - The "version" member contains the Server's version information (the format is the same of the query-version command) - The "capabilities" member specify the availability of features beyond the - baseline specification + baseline specification; the order of elements in this array has no + particular significance, so a client must search the entire array + when looking for a particular capability + +2.2.1 Capabilities +------------------ + +As of the date this document was last revised, no server or client +capability strings have been defined. + 2.3 Issuing Commands -------------------- @@ -73,10 +98,14 @@ The format for command execution is: - The "execute" member identifies the command to be executed by the Server - The "arguments" member is used to pass any arguments required for the - execution of the command, it is optional when no arguments are required + execution of the command, it is optional when no arguments are + required. Each command documents what contents will be considered + valid when handling the json-argument - The "id" member is a transaction identification associated with the command execution, it is optional and will be part of the response if - provided + provided. The "id" member can be any json-value, although most + clients merely use a json-number incremented for each successive + command 2.4 Commands Responses ---------------------- @@ -89,13 +118,15 @@ of a command execution: success or error. The format of a success response is: -{ "return": json-object, "id": json-value } +{ "return": json-value, "id": json-value } Where, -- The "return" member contains the command returned data, which is defined - in a per-command basis or an empty json-object if the command does not - return data +- The "return" member contains the data returned by the command, which + is defined on a per-command basis (usually a json-object or + json-array of json-objects, but sometimes a json-number, json-string, + or json-array of json-strings); it is an empty json-object if the + command does not return data - The "id" member contains the transaction identification associated with the command execution if issued by the Client @@ -122,7 +153,8 @@ if provided by the client. ----------------------- As a result of state changes, the Server may send messages unilaterally -to the Client at any time. They are called "asynchronous events". +to the Client at any time, when not in the middle of any other +response. They are called "asynchronous events". The format of asynchronous events is: @@ -134,13 +166,27 @@ The format of asynchronous events is: - The "event" member contains the event's name - The "data" member contains event specific data, which is defined in a per-event basis, it is optional -- The "timestamp" member contains the exact time of when the event occurred - in the Server. It is a fixed json-object with time in seconds and - microseconds +- The "timestamp" member contains the exact time of when the event + occurred in the Server. It is a fixed json-object with time in + seconds and microseconds relative to the Unix Epoch (1 Jan 1970); if + there is a failure to retrieve host time, both members of the + timestamp will be set to -1. For a listing of supported asynchronous events, please, refer to the qmp-events.txt file. +2.5 QGA Synchronization +----------------------- + +When using QGA, an additional synchronization feature is built into +the protocol. If the Client sends a raw 0xFF sentinel byte (not valid +JSON), then the Server will reset its state and discard all pending +data prior to the sentinel. Conversely, if the Client makes use of +the 'guest-sync-delimited' command, the Server will send a raw 0xFF +sentinel byte prior to its response, to aid the Client in discarding +any data prior to the sentinel. + + 3. QMP Examples =============== @@ -153,32 +199,37 @@ This section provides some examples of real QMP usage, in all of them S: { "QMP": { "version": { "qemu": { "micro": 50, "minor": 6, "major": 1 }, "package": ""}, "capabilities": []}} -3.2 Simple 'stop' execution +3.2 Client QMP negotiation +-------------------------- +C: { "execute": "qmp_capabilities" } +S: { "return": {}} + +3.3 Simple 'stop' execution --------------------------- C: { "execute": "stop" } S: { "return": {} } -3.3 KVM information +3.4 KVM information ------------------- C: { "execute": "query-kvm", "id": "example" } S: { "return": { "enabled": true, "present": true }, "id": "example"} -3.4 Parsing error +3.5 Parsing error ------------------ C: { "execute": } S: { "error": { "class": "GenericError", "desc": "Invalid JSON syntax" } } -3.5 Powerdown event +3.6 Powerdown event ------------------- S: { "timestamp": { "seconds": 1258551470, "microseconds": 802384 }, "event": "POWERDOWN" } 4. Capabilities Negotiation ----------------------------- +=========================== When a Client successfully establishes a connection, the Server is in Capabilities Negotiation mode. @@ -197,7 +248,7 @@ effect, all commands (except qmp_capabilities) are allowed and asynchronous messages are delivered. 5 Compatibility Considerations ------------------------------- +============================== All protocol changes or new features which modify the protocol format in an incompatible way are disabled by default and will be advertised by the @@ -221,12 +272,16 @@ However, Clients must not assume any particular: - Amount of errors generated by a command, that is, new errors can be added to any existing command in newer versions of the Server +Any command or field name beginning with "x-" is deemed experimental, +and may be withdrawn or changed in an incompatible manner in a future +release. + Of course, the Server does guarantee to send valid JSON. But apart from this, a Client should be "conservative in what they send, and liberal in what they accept". 6. Downstream extension of QMP ------------------------------- +============================== We recommend that downstream consumers of QEMU do *not* modify QMP. Management tools should be able to support both upstream and downstream From b52c4b9cf0bbafdf8cede4ea1f62770d86815718 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:00 -0600 Subject: [PATCH 03/40] qapi: Simplify builtin type handling There was some redundancy between builtin_types[] and builtin_type_qtypes{}. Merge them into one. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi-types.py | 10 +++++----- scripts/qapi-visit.py | 6 +++--- scripts/qapi.py | 8 +------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index db872180c6..e400b03479 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -182,8 +182,8 @@ const int %(name)s_qtypes[QTYPE_MAX] = { for key in members: qapi_type = members[key] - if builtin_type_qtypes.has_key(qapi_type): - qtype = builtin_type_qtypes[qapi_type] + if builtin_types.has_key(qapi_type): + qtype = builtin_types[qapi_type] elif find_struct(qapi_type): qtype = "QTYPE_QDICT" elif find_union(qapi_type): @@ -398,7 +398,7 @@ exprs = parse_schema(input_file) exprs = filter(lambda expr: not expr.has_key('gen'), exprs) fdecl.write(guardstart("QAPI_TYPES_BUILTIN_STRUCT_DECL")) -for typename in builtin_types: +for typename in builtin_types.keys(): fdecl.write(generate_fwd_struct(typename, None, builtin_type=True)) fdecl.write(guardend("QAPI_TYPES_BUILTIN_STRUCT_DECL")) @@ -426,7 +426,7 @@ for expr in exprs: # to avoid header dependency hell, we always generate declarations # for built-in types in our header files and simply guard them fdecl.write(guardstart("QAPI_TYPES_BUILTIN_CLEANUP_DECL")) -for typename in builtin_types: +for typename in builtin_types.keys(): fdecl.write(generate_type_cleanup_decl(typename + "List")) fdecl.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DECL")) @@ -435,7 +435,7 @@ fdecl.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DECL")) # over these cases if do_builtins: fdef.write(guardstart("QAPI_TYPES_BUILTIN_CLEANUP_DEF")) - for typename in builtin_types: + for typename in builtin_types.keys(): fdef.write(generate_type_cleanup(typename + "List")) fdef.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DEF")) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 1be4d67d8a..41596bb95b 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -261,7 +261,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e disc_type = '%sKind' % (name) for key in members: - assert (members[key] in builtin_types + assert (members[key] in builtin_types.keys() or find_struct(members[key]) or find_union(members[key]) or find_enum(members[key])), "Invalid anonymous union member" @@ -538,7 +538,7 @@ exprs = parse_schema(input_file) # to avoid header dependency hell, we always generate declarations # for built-in types in our header files and simply guard them fdecl.write(guardstart("QAPI_VISIT_BUILTIN_VISITOR_DECL")) -for typename in builtin_types: +for typename in builtin_types.keys(): fdecl.write(generate_declaration(typename, None, builtin_type=True)) fdecl.write(guardend("QAPI_VISIT_BUILTIN_VISITOR_DECL")) @@ -546,7 +546,7 @@ fdecl.write(guardend("QAPI_VISIT_BUILTIN_VISITOR_DECL")) # have the functions defined, so we use -b option to provide control # over these cases if do_builtins: - for typename in builtin_types: + for typename in builtin_types.keys(): fdef.write(generate_visit_list(typename, None)) for expr in exprs: diff --git a/scripts/qapi.py b/scripts/qapi.py index 77d46aa995..2b5775d2af 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -16,13 +16,7 @@ from ordereddict import OrderedDict import os import sys -builtin_types = [ - 'str', 'int', 'number', 'bool', - 'int8', 'int16', 'int32', 'int64', - 'uint8', 'uint16', 'uint32', 'uint64' -] - -builtin_type_qtypes = { +builtin_types = { 'str': 'QTYPE_QSTRING', 'int': 'QTYPE_QINT', 'number': 'QTYPE_QFLOAT', From cb17f79eef0d161e81ac457e4c1f124405be2a18 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:01 -0600 Subject: [PATCH 04/40] qapi: Fix generation of 'size' builtin type We were missing the 'size' builtin type (which means that QAPI using [ 'size' ] would fail to compile). Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 1 + tests/qapi-schema/qapi-schema-test.json | 3 ++- tests/qapi-schema/qapi-schema-test.out | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 2b5775d2af..d47034760d 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -29,6 +29,7 @@ builtin_types = { 'uint16': 'QTYPE_QINT', 'uint32': 'QTYPE_QINT', 'uint64': 'QTYPE_QINT', + 'size': 'QTYPE_QINT', } def error_path(parent): diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index d43b5fd2e9..84f0f07637 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -74,7 +74,8 @@ 'u64': ['uint64'], 'number': ['number'], 'boolean': ['bool'], - 'string': ['str'] } } + 'string': ['str'], + 'sizes': ['size'] } } # testing commands { 'command': 'user_def_cmd', 'data': {} } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 08d7304dfa..915a61bcd1 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -12,7 +12,7 @@ OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]), OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]), OrderedDict([('union', 'UserDefAnonUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), - OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('integer', ['int']), ('s8', ['int8']), ('s16', ['int16']), ('s32', ['int32']), ('s64', ['int64']), ('u8', ['uint8']), ('u16', ['uint16']), ('u32', ['uint32']), ('u64', ['uint64']), ('number', ['number']), ('boolean', ['bool']), ('string', ['str'])]))]), + OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('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'])]))]), OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]), OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]), OrderedDict([('command', 'user_def_cmd2'), ('data', OrderedDict([('ud1a', 'UserDefOne'), ('*ud1b', 'UserDefOne')])), ('returns', 'UserDefTwo')]), From fe2a9303c9e511462f662a415c2e9d2defe9b7ca Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:02 -0600 Subject: [PATCH 05/40] qapi: Require ASCII in schema Python 2 and Python 3 have a wild history of whether strings default to ascii or unicode, where Python 3 requires checking isinstance(foo, basestr) to cover all strings, but where that code is not portable to Python 2. It's simpler to just state that we don't care about Unicode strings, and to just always use the simpler isinstance(foo, str) everywhere. I'm no python expert, so I'm basing it on this conversation: https://lists.gnu.org/archive/html/qemu-devel/2014-09/msg05278.html Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index d47034760d..20ee505430 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -2,7 +2,7 @@ # QAPI helper library # # Copyright IBM, Corp. 2011 -# Copyright (c) 2013 Red Hat Inc. +# Copyright (c) 2013-2015 Red Hat Inc. # # Authors: # Anthony Liguori @@ -354,7 +354,7 @@ def parse_schema(input_file): return exprs def parse_args(typeinfo): - if isinstance(typeinfo, basestring): + if isinstance(typeinfo, str): struct = find_struct(typeinfo) assert struct != None typeinfo = struct['data'] From ad11dbb93752ffd4bd1d5f31da7e2d9c40a68e8a Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:03 -0600 Subject: [PATCH 06/40] qapi: Add some enum tests Demonstrate that the qapi generator doesn't deal well with enums that aren't up to par. Later patches will update the expected results as the generator is made stricter. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/Makefile | 6 +++++- tests/qapi-schema/enum-bad-name.err | 0 tests/qapi-schema/enum-bad-name.exit | 1 + tests/qapi-schema/enum-bad-name.json | 2 ++ tests/qapi-schema/enum-bad-name.out | 3 +++ tests/qapi-schema/enum-clash-member.err | 0 tests/qapi-schema/enum-clash-member.exit | 1 + tests/qapi-schema/enum-clash-member.json | 2 ++ tests/qapi-schema/enum-clash-member.out | 3 +++ tests/qapi-schema/enum-dict-member.err | 0 tests/qapi-schema/enum-dict-member.exit | 1 + tests/qapi-schema/enum-dict-member.json | 2 ++ tests/qapi-schema/enum-dict-member.out | 3 +++ tests/qapi-schema/enum-empty.err | 0 tests/qapi-schema/enum-empty.exit | 1 + tests/qapi-schema/enum-empty.json | 2 ++ tests/qapi-schema/enum-empty.out | 3 +++ tests/qapi-schema/enum-int-member.err | 1 + tests/qapi-schema/enum-int-member.exit | 1 + tests/qapi-schema/enum-int-member.json | 3 +++ tests/qapi-schema/enum-int-member.out | 0 tests/qapi-schema/enum-max-member.err | 0 tests/qapi-schema/enum-max-member.exit | 1 + tests/qapi-schema/enum-max-member.json | 3 +++ tests/qapi-schema/enum-max-member.out | 3 +++ tests/qapi-schema/enum-missing-data.err | 6 ++++++ tests/qapi-schema/enum-missing-data.exit | 1 + tests/qapi-schema/enum-missing-data.json | 2 ++ tests/qapi-schema/enum-missing-data.out | 0 tests/qapi-schema/enum-union-clash.err | 0 tests/qapi-schema/enum-union-clash.exit | 1 + tests/qapi-schema/enum-union-clash.json | 4 ++++ tests/qapi-schema/enum-union-clash.out | 5 +++++ tests/qapi-schema/enum-wrong-data.err | 0 tests/qapi-schema/enum-wrong-data.exit | 1 + tests/qapi-schema/enum-wrong-data.json | 2 ++ tests/qapi-schema/enum-wrong-data.out | 3 +++ 37 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/qapi-schema/enum-bad-name.err create mode 100644 tests/qapi-schema/enum-bad-name.exit create mode 100644 tests/qapi-schema/enum-bad-name.json create mode 100644 tests/qapi-schema/enum-bad-name.out create mode 100644 tests/qapi-schema/enum-clash-member.err create mode 100644 tests/qapi-schema/enum-clash-member.exit create mode 100644 tests/qapi-schema/enum-clash-member.json create mode 100644 tests/qapi-schema/enum-clash-member.out create mode 100644 tests/qapi-schema/enum-dict-member.err create mode 100644 tests/qapi-schema/enum-dict-member.exit create mode 100644 tests/qapi-schema/enum-dict-member.json create mode 100644 tests/qapi-schema/enum-dict-member.out create mode 100644 tests/qapi-schema/enum-empty.err create mode 100644 tests/qapi-schema/enum-empty.exit create mode 100644 tests/qapi-schema/enum-empty.json create mode 100644 tests/qapi-schema/enum-empty.out create mode 100644 tests/qapi-schema/enum-int-member.err create mode 100644 tests/qapi-schema/enum-int-member.exit create mode 100644 tests/qapi-schema/enum-int-member.json create mode 100644 tests/qapi-schema/enum-int-member.out create mode 100644 tests/qapi-schema/enum-max-member.err create mode 100644 tests/qapi-schema/enum-max-member.exit create mode 100644 tests/qapi-schema/enum-max-member.json create mode 100644 tests/qapi-schema/enum-max-member.out create mode 100644 tests/qapi-schema/enum-missing-data.err create mode 100644 tests/qapi-schema/enum-missing-data.exit create mode 100644 tests/qapi-schema/enum-missing-data.json create mode 100644 tests/qapi-schema/enum-missing-data.out create mode 100644 tests/qapi-schema/enum-union-clash.err create mode 100644 tests/qapi-schema/enum-union-clash.exit create mode 100644 tests/qapi-schema/enum-union-clash.json create mode 100644 tests/qapi-schema/enum-union-clash.out create mode 100644 tests/qapi-schema/enum-wrong-data.err create mode 100644 tests/qapi-schema/enum-wrong-data.exit create mode 100644 tests/qapi-schema/enum-wrong-data.json create mode 100644 tests/qapi-schema/enum-wrong-data.out diff --git a/tests/Makefile b/tests/Makefile index 309e8697fd..3865a611e7 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -207,7 +207,11 @@ $(foreach target,$(SYSEMU_TARGET_LIST), \ $(eval check-qtest-$(target)-y += tests/qom-test$(EXESUF)))) check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ - comments.json empty.json funny-char.json indented-expr.json \ + comments.json empty.json enum-empty.json enum-missing-data.json \ + enum-wrong-data.json enum-int-member.json enum-dict-member.json \ + enum-clash-member.json enum-max-member.json enum-union-clash.json \ + enum-bad-name.json \ + funny-char.json indented-expr.json \ missing-colon.json missing-comma-list.json \ missing-comma-object.json non-objects.json \ qapi-schema-test.json quoted-structural-chars.json \ diff --git a/tests/qapi-schema/enum-bad-name.err b/tests/qapi-schema/enum-bad-name.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-bad-name.exit b/tests/qapi-schema/enum-bad-name.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/enum-bad-name.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/enum-bad-name.json b/tests/qapi-schema/enum-bad-name.json new file mode 100644 index 0000000000..0c32448a3e --- /dev/null +++ b/tests/qapi-schema/enum-bad-name.json @@ -0,0 +1,2 @@ +# FIXME: we should ensure all enum names can map to C +{ 'enum': 'MyEnum', 'data': [ 'not^possible' ] } diff --git a/tests/qapi-schema/enum-bad-name.out b/tests/qapi-schema/enum-bad-name.out new file mode 100644 index 0000000000..d24ea49a0f --- /dev/null +++ b/tests/qapi-schema/enum-bad-name.out @@ -0,0 +1,3 @@ +[OrderedDict([('enum', 'MyEnum'), ('data', ['not^possible'])])] +[{'enum_name': 'MyEnum', 'enum_values': ['not^possible']}] +[] diff --git a/tests/qapi-schema/enum-clash-member.err b/tests/qapi-schema/enum-clash-member.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-clash-member.exit b/tests/qapi-schema/enum-clash-member.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/enum-clash-member.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/enum-clash-member.json b/tests/qapi-schema/enum-clash-member.json new file mode 100644 index 0000000000..99d442a978 --- /dev/null +++ b/tests/qapi-schema/enum-clash-member.json @@ -0,0 +1,2 @@ +# FIXME: we should reject enums where members will clash when mapped to C enum +{ 'enum': 'MyEnum', 'data': [ 'one', 'ONE' ] } diff --git a/tests/qapi-schema/enum-clash-member.out b/tests/qapi-schema/enum-clash-member.out new file mode 100644 index 0000000000..0814459721 --- /dev/null +++ b/tests/qapi-schema/enum-clash-member.out @@ -0,0 +1,3 @@ +[OrderedDict([('enum', 'MyEnum'), ('data', ['one', 'ONE'])])] +[{'enum_name': 'MyEnum', 'enum_values': ['one', 'ONE']}] +[] diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-dict-member.exit b/tests/qapi-schema/enum-dict-member.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/enum-dict-member.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/enum-dict-member.json b/tests/qapi-schema/enum-dict-member.json new file mode 100644 index 0000000000..de4d6bf777 --- /dev/null +++ b/tests/qapi-schema/enum-dict-member.json @@ -0,0 +1,2 @@ +# FIXME: we should reject any enum member that is not a string +{ 'enum': 'MyEnum', 'data': [ { 'value': 'str' } ] } diff --git a/tests/qapi-schema/enum-dict-member.out b/tests/qapi-schema/enum-dict-member.out new file mode 100644 index 0000000000..8b293f85c5 --- /dev/null +++ b/tests/qapi-schema/enum-dict-member.out @@ -0,0 +1,3 @@ +[OrderedDict([('enum', 'MyEnum'), ('data', [OrderedDict([('value', 'str')])])])] +[{'enum_name': 'MyEnum', 'enum_values': [OrderedDict([('value', 'str')])]}] +[] diff --git a/tests/qapi-schema/enum-empty.err b/tests/qapi-schema/enum-empty.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-empty.exit b/tests/qapi-schema/enum-empty.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/enum-empty.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/enum-empty.json b/tests/qapi-schema/enum-empty.json new file mode 100644 index 0000000000..40d4e85a2f --- /dev/null +++ b/tests/qapi-schema/enum-empty.json @@ -0,0 +1,2 @@ +# An empty enum, although unusual, is currently acceptable +{ 'enum': 'MyEnum', 'data': [ ] } diff --git a/tests/qapi-schema/enum-empty.out b/tests/qapi-schema/enum-empty.out new file mode 100644 index 0000000000..3b75c1613c --- /dev/null +++ b/tests/qapi-schema/enum-empty.out @@ -0,0 +1,3 @@ +[OrderedDict([('enum', 'MyEnum'), ('data', [])])] +[{'enum_name': 'MyEnum', 'enum_values': []}] +[] diff --git a/tests/qapi-schema/enum-int-member.err b/tests/qapi-schema/enum-int-member.err new file mode 100644 index 0000000000..071c5213d8 --- /dev/null +++ b/tests/qapi-schema/enum-int-member.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-int-member.json:3:31: Stray "1" diff --git a/tests/qapi-schema/enum-int-member.exit b/tests/qapi-schema/enum-int-member.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/enum-int-member.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/enum-int-member.json b/tests/qapi-schema/enum-int-member.json new file mode 100644 index 0000000000..6c9c32e149 --- /dev/null +++ b/tests/qapi-schema/enum-int-member.json @@ -0,0 +1,3 @@ +# we reject any enum member that is not a string +# FIXME: once the parser understands integer inputs, improve the error message +{ 'enum': 'MyEnum', 'data': [ 1 ] } diff --git a/tests/qapi-schema/enum-int-member.out b/tests/qapi-schema/enum-int-member.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-max-member.err b/tests/qapi-schema/enum-max-member.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-max-member.exit b/tests/qapi-schema/enum-max-member.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/enum-max-member.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/enum-max-member.json b/tests/qapi-schema/enum-max-member.json new file mode 100644 index 0000000000..1519541344 --- /dev/null +++ b/tests/qapi-schema/enum-max-member.json @@ -0,0 +1,3 @@ +# FIXME: we should reject user-supplied 'max' for clashing with implicit enum end +# TODO: should we instead munge the implicit value to avoid the clash? +{ 'enum': 'MyEnum', 'data': [ 'max' ] } diff --git a/tests/qapi-schema/enum-max-member.out b/tests/qapi-schema/enum-max-member.out new file mode 100644 index 0000000000..c933044c69 --- /dev/null +++ b/tests/qapi-schema/enum-max-member.out @@ -0,0 +1,3 @@ +[OrderedDict([('enum', 'MyEnum'), ('data', ['max'])])] +[{'enum_name': 'MyEnum', 'enum_values': ['max']}] +[] diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err new file mode 100644 index 0000000000..814ab2613d --- /dev/null +++ b/tests/qapi-schema/enum-missing-data.err @@ -0,0 +1,6 @@ +Traceback (most recent call last): + File "tests/qapi-schema/test-qapi.py", line 19, in + exprs = parse_schema(sys.argv[1]) + File "scripts/qapi.py", line 334, in parse_schema + add_enum(expr['enum'], expr['data']) +KeyError: 'data' diff --git a/tests/qapi-schema/enum-missing-data.exit b/tests/qapi-schema/enum-missing-data.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/enum-missing-data.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/enum-missing-data.json b/tests/qapi-schema/enum-missing-data.json new file mode 100644 index 0000000000..01f3f32e70 --- /dev/null +++ b/tests/qapi-schema/enum-missing-data.json @@ -0,0 +1,2 @@ +# FIXME: we should require that all QAPI enums have a data array +{ 'enum': 'MyEnum' } diff --git a/tests/qapi-schema/enum-missing-data.out b/tests/qapi-schema/enum-missing-data.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-union-clash.err b/tests/qapi-schema/enum-union-clash.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-union-clash.exit b/tests/qapi-schema/enum-union-clash.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/enum-union-clash.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/enum-union-clash.json b/tests/qapi-schema/enum-union-clash.json new file mode 100644 index 0000000000..714ff6d556 --- /dev/null +++ b/tests/qapi-schema/enum-union-clash.json @@ -0,0 +1,4 @@ +# FIXME: we should reject types that would conflict with implicit union enum +{ 'enum': 'UnionKind', 'data': [ 'oops' ] } +{ 'union': 'Union', + 'data': { 'a': 'int' } } diff --git a/tests/qapi-schema/enum-union-clash.out b/tests/qapi-schema/enum-union-clash.out new file mode 100644 index 0000000000..d45f5e8a69 --- /dev/null +++ b/tests/qapi-schema/enum-union-clash.out @@ -0,0 +1,5 @@ +[OrderedDict([('enum', 'UnionKind'), ('data', ['oops'])]), + OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int')]))])] +[{'enum_name': 'UnionKind', 'enum_values': ['oops']}, + {'enum_name': 'UnionKind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/enum-wrong-data.err b/tests/qapi-schema/enum-wrong-data.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-wrong-data.exit b/tests/qapi-schema/enum-wrong-data.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/enum-wrong-data.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/enum-wrong-data.json b/tests/qapi-schema/enum-wrong-data.json new file mode 100644 index 0000000000..61d25ec427 --- /dev/null +++ b/tests/qapi-schema/enum-wrong-data.json @@ -0,0 +1,2 @@ +# FIXME: we should require that all qapi enums have an array for data +{ 'enum': 'MyEnum', 'data': { 'value': 'str' } } diff --git a/tests/qapi-schema/enum-wrong-data.out b/tests/qapi-schema/enum-wrong-data.out new file mode 100644 index 0000000000..28d22116d0 --- /dev/null +++ b/tests/qapi-schema/enum-wrong-data.out @@ -0,0 +1,3 @@ +[OrderedDict([('enum', 'MyEnum'), ('data', OrderedDict([('value', 'str')]))])] +[{'enum_name': 'MyEnum', 'enum_values': OrderedDict([('value', 'str')])}] +[] From cf3935907b5df16f667d54ad6761c7e937dcf425 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:04 -0600 Subject: [PATCH 07/40] qapi: Better error messages for bad enums The previous commit demonstrated that the generator had several flaws with less-than-perfect enums: - an enum that listed the same string twice (or two variant strings that map to the same C enumerator) ended up generating an invalid C enum - because the generator adds a _MAX terminator to each enum, the use of an enum member 'max' can also cause this clash - if an enum omits 'data', the generator left a python stack trace rather than a graceful message - an enum that used a non-array 'data' was silently accepted by the parser - an enum that used non-string members in the 'data' member was silently accepted by the parser Add check_enum to cover these situations, and update testcases to match. While valid .json files won't trigger any of these cases, we might as well be nicer to developers that make a typo while trying to add new QAPI code. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 34 ++++++++++++++++++++---- tests/qapi-schema/enum-clash-member.err | 1 + tests/qapi-schema/enum-clash-member.exit | 2 +- tests/qapi-schema/enum-clash-member.json | 2 +- tests/qapi-schema/enum-clash-member.out | 3 --- tests/qapi-schema/enum-dict-member.err | 1 + tests/qapi-schema/enum-dict-member.exit | 2 +- tests/qapi-schema/enum-dict-member.json | 2 +- tests/qapi-schema/enum-dict-member.out | 3 --- tests/qapi-schema/enum-max-member.err | 1 + tests/qapi-schema/enum-max-member.exit | 2 +- tests/qapi-schema/enum-max-member.json | 2 +- tests/qapi-schema/enum-max-member.out | 3 --- tests/qapi-schema/enum-missing-data.err | 7 +---- tests/qapi-schema/enum-missing-data.json | 2 +- tests/qapi-schema/enum-wrong-data.err | 1 + tests/qapi-schema/enum-wrong-data.exit | 2 +- tests/qapi-schema/enum-wrong-data.json | 2 +- tests/qapi-schema/enum-wrong-data.out | 3 --- 19 files changed, 43 insertions(+), 32 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 20ee505430..3ce8c3321b 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -311,13 +311,37 @@ def check_union(expr, expr_info): # Todo: add checking for values. Key is checked as above, value can be # also checked here, but we need more functions to handle array case. +def check_enum(expr, expr_info): + name = expr['enum'] + members = expr.get('data') + values = { 'MAX': '(automatic)' } + + if not isinstance(members, list): + raise QAPIExprError(expr_info, + "Enum '%s' requires an array for 'data'" % name) + for member in members: + if not isinstance(member, str): + raise QAPIExprError(expr_info, + "Enum '%s' member '%s' is not a string" + % (name, member)) + key = _generate_enum_string(member) + if key in values: + raise QAPIExprError(expr_info, + "Enum '%s' member '%s' clashes with '%s'" + % (name, member, values[key])) + values[key] = member + def check_exprs(schema): for expr_elem in schema.exprs: expr = expr_elem['expr'] - if expr.has_key('union'): - check_union(expr, expr_elem['info']) - if expr.has_key('event'): - check_event(expr, expr_elem['info']) + info = expr_elem['info'] + + if expr.has_key('enum'): + check_enum(expr, info) + elif expr.has_key('union'): + check_union(expr, info) + elif expr.has_key('event'): + check_event(expr, info) def parse_schema(input_file): try: @@ -331,7 +355,7 @@ def parse_schema(input_file): for expr_elem in schema.exprs: expr = expr_elem['expr'] if expr.has_key('enum'): - add_enum(expr['enum'], expr['data']) + add_enum(expr['enum'], expr.get('data')) elif expr.has_key('union'): add_union(expr) elif expr.has_key('type'): diff --git a/tests/qapi-schema/enum-clash-member.err b/tests/qapi-schema/enum-clash-member.err index e69de29bb2..48bd1360e7 100644 --- a/tests/qapi-schema/enum-clash-member.err +++ b/tests/qapi-schema/enum-clash-member.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-clash-member.json:2: Enum 'MyEnum' member 'ONE' clashes with 'one' diff --git a/tests/qapi-schema/enum-clash-member.exit b/tests/qapi-schema/enum-clash-member.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/enum-clash-member.exit +++ b/tests/qapi-schema/enum-clash-member.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/enum-clash-member.json b/tests/qapi-schema/enum-clash-member.json index 99d442a978..b7dc02a28d 100644 --- a/tests/qapi-schema/enum-clash-member.json +++ b/tests/qapi-schema/enum-clash-member.json @@ -1,2 +1,2 @@ -# FIXME: we should reject enums where members will clash when mapped to C enum +# we reject enums where members will clash when mapped to C enum { 'enum': 'MyEnum', 'data': [ 'one', 'ONE' ] } diff --git a/tests/qapi-schema/enum-clash-member.out b/tests/qapi-schema/enum-clash-member.out index 0814459721..e69de29bb2 100644 --- a/tests/qapi-schema/enum-clash-member.out +++ b/tests/qapi-schema/enum-clash-member.out @@ -1,3 +0,0 @@ -[OrderedDict([('enum', 'MyEnum'), ('data', ['one', 'ONE'])])] -[{'enum_name': 'MyEnum', 'enum_values': ['one', 'ONE']}] -[] diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err index e69de29bb2..7e966a8aae 100644 --- a/tests/qapi-schema/enum-dict-member.err +++ b/tests/qapi-schema/enum-dict-member.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-dict-member.json:2: Enum 'MyEnum' member 'OrderedDict([('value', 'str')])' is not a string diff --git a/tests/qapi-schema/enum-dict-member.exit b/tests/qapi-schema/enum-dict-member.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/enum-dict-member.exit +++ b/tests/qapi-schema/enum-dict-member.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/enum-dict-member.json b/tests/qapi-schema/enum-dict-member.json index de4d6bf777..79672e0f09 100644 --- a/tests/qapi-schema/enum-dict-member.json +++ b/tests/qapi-schema/enum-dict-member.json @@ -1,2 +1,2 @@ -# FIXME: we should reject any enum member that is not a string +# we reject any enum member that is not a string { 'enum': 'MyEnum', 'data': [ { 'value': 'str' } ] } diff --git a/tests/qapi-schema/enum-dict-member.out b/tests/qapi-schema/enum-dict-member.out index 8b293f85c5..e69de29bb2 100644 --- a/tests/qapi-schema/enum-dict-member.out +++ b/tests/qapi-schema/enum-dict-member.out @@ -1,3 +0,0 @@ -[OrderedDict([('enum', 'MyEnum'), ('data', [OrderedDict([('value', 'str')])])])] -[{'enum_name': 'MyEnum', 'enum_values': [OrderedDict([('value', 'str')])]}] -[] diff --git a/tests/qapi-schema/enum-max-member.err b/tests/qapi-schema/enum-max-member.err index e69de29bb2..f77837fb45 100644 --- a/tests/qapi-schema/enum-max-member.err +++ b/tests/qapi-schema/enum-max-member.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-max-member.json:3: Enum 'MyEnum' member 'max' clashes with '(automatic)' diff --git a/tests/qapi-schema/enum-max-member.exit b/tests/qapi-schema/enum-max-member.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/enum-max-member.exit +++ b/tests/qapi-schema/enum-max-member.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/enum-max-member.json b/tests/qapi-schema/enum-max-member.json index 1519541344..4bcda0bf07 100644 --- a/tests/qapi-schema/enum-max-member.json +++ b/tests/qapi-schema/enum-max-member.json @@ -1,3 +1,3 @@ -# FIXME: we should reject user-supplied 'max' for clashing with implicit enum end +# we reject user-supplied 'max' for clashing with implicit enum end # TODO: should we instead munge the implicit value to avoid the clash? { 'enum': 'MyEnum', 'data': [ 'max' ] } diff --git a/tests/qapi-schema/enum-max-member.out b/tests/qapi-schema/enum-max-member.out index c933044c69..e69de29bb2 100644 --- a/tests/qapi-schema/enum-max-member.out +++ b/tests/qapi-schema/enum-max-member.out @@ -1,3 +0,0 @@ -[OrderedDict([('enum', 'MyEnum'), ('data', ['max'])])] -[{'enum_name': 'MyEnum', 'enum_values': ['max']}] -[] diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err index 814ab2613d..b8ccae071b 100644 --- a/tests/qapi-schema/enum-missing-data.err +++ b/tests/qapi-schema/enum-missing-data.err @@ -1,6 +1 @@ -Traceback (most recent call last): - File "tests/qapi-schema/test-qapi.py", line 19, in - exprs = parse_schema(sys.argv[1]) - File "scripts/qapi.py", line 334, in parse_schema - add_enum(expr['enum'], expr['data']) -KeyError: 'data' +tests/qapi-schema/enum-missing-data.json:2: Enum 'MyEnum' requires an array for 'data' diff --git a/tests/qapi-schema/enum-missing-data.json b/tests/qapi-schema/enum-missing-data.json index 01f3f32e70..558fd35e93 100644 --- a/tests/qapi-schema/enum-missing-data.json +++ b/tests/qapi-schema/enum-missing-data.json @@ -1,2 +1,2 @@ -# FIXME: we should require that all QAPI enums have a data array +# we require that all QAPI enums have a data array { 'enum': 'MyEnum' } diff --git a/tests/qapi-schema/enum-wrong-data.err b/tests/qapi-schema/enum-wrong-data.err index e69de29bb2..11b43471cf 100644 --- a/tests/qapi-schema/enum-wrong-data.err +++ b/tests/qapi-schema/enum-wrong-data.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-wrong-data.json:2: Enum 'MyEnum' requires an array for 'data' diff --git a/tests/qapi-schema/enum-wrong-data.exit b/tests/qapi-schema/enum-wrong-data.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/enum-wrong-data.exit +++ b/tests/qapi-schema/enum-wrong-data.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/enum-wrong-data.json b/tests/qapi-schema/enum-wrong-data.json index 61d25ec427..7b3e255c14 100644 --- a/tests/qapi-schema/enum-wrong-data.json +++ b/tests/qapi-schema/enum-wrong-data.json @@ -1,2 +1,2 @@ -# FIXME: we should require that all qapi enums have an array for data +# we require that all qapi enums have an array for data { 'enum': 'MyEnum', 'data': { 'value': 'str' } } diff --git a/tests/qapi-schema/enum-wrong-data.out b/tests/qapi-schema/enum-wrong-data.out index 28d22116d0..e69de29bb2 100644 --- a/tests/qapi-schema/enum-wrong-data.out +++ b/tests/qapi-schema/enum-wrong-data.out @@ -1,3 +0,0 @@ -[OrderedDict([('enum', 'MyEnum'), ('data', OrderedDict([('value', 'str')]))])] -[{'enum_name': 'MyEnum', 'enum_values': OrderedDict([('value', 'str')])}] -[] From 3d0c48292633260269cb21551d9bab006b2f2781 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:05 -0600 Subject: [PATCH 08/40] qapi: Add some union tests Demonstrate that the qapi generator doesn't deal well with unions that aren't up to par. Later patches will update the expected reseults as the generator is made stricter. A few tests work as planned, but most show poor or missing error messages. Of particular note, qapi-code-gen.txt documents 'base' only for flat unions, but the tests here demonstrate that we currently allow a 'base' to a simple union, although it is exercised only in the testsuite. Later patches will remove this undocumented feature, to give us more flexibility in adding other future extensions to union types. For example, one possible extension is the idea of a type-safe simple enum, where added fields tie the discriminator to a user-defined enum type rather than creating an implicit enum from the names in 'data'. But adding such safety on top of a simple enum with a base type could look ambiguous with a flat enum; besides, the documentation also mentions how any simple union can be represented by an equivalent flat union. So it will be simpler to just outlaw support for something we aren't using. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/Makefile | 14 +++++++++++--- tests/qapi-schema/alternate-array.err | 0 tests/qapi-schema/alternate-array.exit | 1 + tests/qapi-schema/alternate-array.json | 8 ++++++++ tests/qapi-schema/alternate-array.out | 4 ++++ tests/qapi-schema/alternate-base.err | 0 tests/qapi-schema/alternate-base.exit | 1 + tests/qapi-schema/alternate-base.json | 7 +++++++ tests/qapi-schema/alternate-base.out | 4 ++++ tests/qapi-schema/alternate-clash.err | 0 tests/qapi-schema/alternate-clash.exit | 1 + tests/qapi-schema/alternate-clash.json | 4 ++++ tests/qapi-schema/alternate-clash.out | 3 +++ tests/qapi-schema/alternate-conflict-dict.err | 0 tests/qapi-schema/alternate-conflict-dict.exit | 1 + tests/qapi-schema/alternate-conflict-dict.json | 9 +++++++++ tests/qapi-schema/alternate-conflict-dict.out | 6 ++++++ tests/qapi-schema/alternate-conflict-string.err | 0 tests/qapi-schema/alternate-conflict-string.exit | 1 + tests/qapi-schema/alternate-conflict-string.json | 7 +++++++ tests/qapi-schema/alternate-conflict-string.out | 5 +++++ tests/qapi-schema/alternate-good.err | 0 tests/qapi-schema/alternate-good.exit | 1 + tests/qapi-schema/alternate-good.json | 10 ++++++++++ tests/qapi-schema/alternate-good.out | 6 ++++++ tests/qapi-schema/alternate-nested.err | 0 tests/qapi-schema/alternate-nested.exit | 1 + tests/qapi-schema/alternate-nested.json | 7 +++++++ tests/qapi-schema/alternate-nested.out | 5 +++++ tests/qapi-schema/alternate-unknown.err | 0 tests/qapi-schema/alternate-unknown.exit | 1 + tests/qapi-schema/alternate-unknown.json | 4 ++++ tests/qapi-schema/alternate-unknown.out | 3 +++ tests/qapi-schema/flat-union-bad-base.err | 1 + tests/qapi-schema/flat-union-bad-base.exit | 1 + tests/qapi-schema/flat-union-bad-base.json | 13 +++++++++++++ tests/qapi-schema/flat-union-bad-base.out | 0 .../qapi-schema/flat-union-bad-discriminator.err | 0 .../qapi-schema/flat-union-bad-discriminator.exit | 1 + .../qapi-schema/flat-union-bad-discriminator.json | 14 ++++++++++++++ .../qapi-schema/flat-union-bad-discriminator.out | 10 ++++++++++ tests/qapi-schema/flat-union-base-star.err | 1 + tests/qapi-schema/flat-union-base-star.exit | 1 + tests/qapi-schema/flat-union-base-star.json | 12 ++++++++++++ tests/qapi-schema/flat-union-base-star.out | 0 tests/qapi-schema/flat-union-base-union.err | 1 + tests/qapi-schema/flat-union-base-union.exit | 1 + tests/qapi-schema/flat-union-base-union.json | 15 +++++++++++++++ tests/qapi-schema/flat-union-base-union.out | 0 tests/qapi-schema/flat-union-branch-clash.err | 0 tests/qapi-schema/flat-union-branch-clash.exit | 1 + tests/qapi-schema/flat-union-branch-clash.json | 14 ++++++++++++++ tests/qapi-schema/flat-union-branch-clash.out | 9 +++++++++ tests/qapi-schema/flat-union-inline.err | 1 + tests/qapi-schema/flat-union-inline.exit | 1 + tests/qapi-schema/flat-union-inline.json | 11 +++++++++++ tests/qapi-schema/flat-union-inline.out | 0 tests/qapi-schema/flat-union-int-branch.err | 0 tests/qapi-schema/flat-union-int-branch.exit | 1 + tests/qapi-schema/flat-union-int-branch.json | 12 ++++++++++++ tests/qapi-schema/flat-union-int-branch.out | 7 +++++++ tests/qapi-schema/flat-union-no-base.err | 2 +- tests/qapi-schema/flat-union-no-base.json | 8 +++++--- .../flat-union-optional-discriminator.err | 0 .../flat-union-optional-discriminator.exit | 1 + .../flat-union-optional-discriminator.json | 10 ++++++++++ .../flat-union-optional-discriminator.out | 7 +++++++ tests/qapi-schema/union-bad-branch.err | 0 tests/qapi-schema/union-bad-branch.exit | 1 + tests/qapi-schema/union-bad-branch.json | 8 ++++++++ tests/qapi-schema/union-bad-branch.out | 6 ++++++ tests/qapi-schema/union-base-no-discriminator.err | 0 .../qapi-schema/union-base-no-discriminator.exit | 1 + .../qapi-schema/union-base-no-discriminator.json | 14 ++++++++++++++ tests/qapi-schema/union-base-no-discriminator.out | 8 ++++++++ tests/qapi-schema/union-invalid-base.err | 2 +- tests/qapi-schema/union-invalid-base.json | 4 +++- tests/qapi-schema/union-max.err | 0 tests/qapi-schema/union-max.exit | 1 + tests/qapi-schema/union-max.json | 3 +++ tests/qapi-schema/union-max.out | 3 +++ tests/qapi-schema/union-optional-branch.err | 0 tests/qapi-schema/union-optional-branch.exit | 1 + tests/qapi-schema/union-optional-branch.json | 2 ++ tests/qapi-schema/union-optional-branch.out | 3 +++ tests/qapi-schema/union-unknown.err | 0 tests/qapi-schema/union-unknown.exit | 1 + tests/qapi-schema/union-unknown.json | 3 +++ tests/qapi-schema/union-unknown.out | 3 +++ 89 files changed, 325 insertions(+), 9 deletions(-) create mode 100644 tests/qapi-schema/alternate-array.err create mode 100644 tests/qapi-schema/alternate-array.exit create mode 100644 tests/qapi-schema/alternate-array.json create mode 100644 tests/qapi-schema/alternate-array.out create mode 100644 tests/qapi-schema/alternate-base.err create mode 100644 tests/qapi-schema/alternate-base.exit create mode 100644 tests/qapi-schema/alternate-base.json create mode 100644 tests/qapi-schema/alternate-base.out create mode 100644 tests/qapi-schema/alternate-clash.err create mode 100644 tests/qapi-schema/alternate-clash.exit create mode 100644 tests/qapi-schema/alternate-clash.json create mode 100644 tests/qapi-schema/alternate-clash.out create mode 100644 tests/qapi-schema/alternate-conflict-dict.err create mode 100644 tests/qapi-schema/alternate-conflict-dict.exit create mode 100644 tests/qapi-schema/alternate-conflict-dict.json create mode 100644 tests/qapi-schema/alternate-conflict-dict.out create mode 100644 tests/qapi-schema/alternate-conflict-string.err create mode 100644 tests/qapi-schema/alternate-conflict-string.exit create mode 100644 tests/qapi-schema/alternate-conflict-string.json create mode 100644 tests/qapi-schema/alternate-conflict-string.out create mode 100644 tests/qapi-schema/alternate-good.err create mode 100644 tests/qapi-schema/alternate-good.exit create mode 100644 tests/qapi-schema/alternate-good.json create mode 100644 tests/qapi-schema/alternate-good.out create mode 100644 tests/qapi-schema/alternate-nested.err create mode 100644 tests/qapi-schema/alternate-nested.exit create mode 100644 tests/qapi-schema/alternate-nested.json create mode 100644 tests/qapi-schema/alternate-nested.out create mode 100644 tests/qapi-schema/alternate-unknown.err create mode 100644 tests/qapi-schema/alternate-unknown.exit create mode 100644 tests/qapi-schema/alternate-unknown.json create mode 100644 tests/qapi-schema/alternate-unknown.out create mode 100644 tests/qapi-schema/flat-union-bad-base.err create mode 100644 tests/qapi-schema/flat-union-bad-base.exit create mode 100644 tests/qapi-schema/flat-union-bad-base.json create mode 100644 tests/qapi-schema/flat-union-bad-base.out create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.err create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.exit create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.json create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.out create mode 100644 tests/qapi-schema/flat-union-base-star.err create mode 100644 tests/qapi-schema/flat-union-base-star.exit create mode 100644 tests/qapi-schema/flat-union-base-star.json create mode 100644 tests/qapi-schema/flat-union-base-star.out create mode 100644 tests/qapi-schema/flat-union-base-union.err create mode 100644 tests/qapi-schema/flat-union-base-union.exit create mode 100644 tests/qapi-schema/flat-union-base-union.json create mode 100644 tests/qapi-schema/flat-union-base-union.out create mode 100644 tests/qapi-schema/flat-union-branch-clash.err create mode 100644 tests/qapi-schema/flat-union-branch-clash.exit create mode 100644 tests/qapi-schema/flat-union-branch-clash.json create mode 100644 tests/qapi-schema/flat-union-branch-clash.out create mode 100644 tests/qapi-schema/flat-union-inline.err create mode 100644 tests/qapi-schema/flat-union-inline.exit create mode 100644 tests/qapi-schema/flat-union-inline.json create mode 100644 tests/qapi-schema/flat-union-inline.out create mode 100644 tests/qapi-schema/flat-union-int-branch.err create mode 100644 tests/qapi-schema/flat-union-int-branch.exit create mode 100644 tests/qapi-schema/flat-union-int-branch.json create mode 100644 tests/qapi-schema/flat-union-int-branch.out create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.err create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.exit create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.json create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.out create mode 100644 tests/qapi-schema/union-bad-branch.err create mode 100644 tests/qapi-schema/union-bad-branch.exit create mode 100644 tests/qapi-schema/union-bad-branch.json create mode 100644 tests/qapi-schema/union-bad-branch.out create mode 100644 tests/qapi-schema/union-base-no-discriminator.err create mode 100644 tests/qapi-schema/union-base-no-discriminator.exit create mode 100644 tests/qapi-schema/union-base-no-discriminator.json create mode 100644 tests/qapi-schema/union-base-no-discriminator.out create mode 100644 tests/qapi-schema/union-max.err create mode 100644 tests/qapi-schema/union-max.exit create mode 100644 tests/qapi-schema/union-max.json create mode 100644 tests/qapi-schema/union-max.out create mode 100644 tests/qapi-schema/union-optional-branch.err create mode 100644 tests/qapi-schema/union-optional-branch.exit create mode 100644 tests/qapi-schema/union-optional-branch.json create mode 100644 tests/qapi-schema/union-optional-branch.out create mode 100644 tests/qapi-schema/union-unknown.err create mode 100644 tests/qapi-schema/union-unknown.exit create mode 100644 tests/qapi-schema/union-unknown.json create mode 100644 tests/qapi-schema/union-unknown.out diff --git a/tests/Makefile b/tests/Makefile index 3865a611e7..3978c3d302 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -217,10 +217,18 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ qapi-schema-test.json quoted-structural-chars.json \ trailing-comma-list.json trailing-comma-object.json \ unclosed-list.json unclosed-object.json unclosed-string.json \ - duplicate-key.json union-invalid-base.json flat-union-no-base.json \ - flat-union-invalid-discriminator.json \ + duplicate-key.json union-invalid-base.json union-bad-branch.json \ + union-optional-branch.json union-unknown.json union-max.json \ + flat-union-optional-discriminator.json flat-union-no-base.json \ + flat-union-invalid-discriminator.json flat-union-inline.json \ flat-union-invalid-branch-key.json flat-union-reverse-define.json \ - flat-union-string-discriminator.json \ + flat-union-string-discriminator.json union-base-no-discriminator.json \ + flat-union-bad-discriminator.json flat-union-bad-base.json \ + flat-union-base-star.json flat-union-int-branch.json \ + flat-union-base-union.json flat-union-branch-clash.json \ + alternate-nested.json alternate-unknown.json alternate-clash.json \ + alternate-good.json alternate-base.json alternate-array.json \ + alternate-conflict-string.json alternate-conflict-dict.json \ include-simple.json include-relpath.json include-format-err.json \ include-non-file.json include-no-file.json include-before-err.json \ include-nested-err.json include-self-cycle.json include-cycle.json \ diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-array.exit b/tests/qapi-schema/alternate-array.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-array.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json new file mode 100644 index 0000000000..25224c6c52 --- /dev/null +++ b/tests/qapi-schema/alternate-array.json @@ -0,0 +1,8 @@ +# FIXME: we should not allow array branches in anonymous unions +# TODO: should we support this? +{ 'type': 'One', + 'data': { 'name': 'str' } } +{ 'union': 'MyUnion', + 'discriminator': {}, + 'data': { 'one': 'One', + 'two': [ 'int' ] } } diff --git a/tests/qapi-schema/alternate-array.out b/tests/qapi-schema/alternate-array.out new file mode 100644 index 0000000000..90dc22c460 --- /dev/null +++ b/tests/qapi-schema/alternate-array.out @@ -0,0 +1,4 @@ +[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', ['int'])]))])] +[{'enum_name': 'MyUnionKind', 'enum_values': None}] +[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))])] diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-base.exit b/tests/qapi-schema/alternate-base.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-base.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json new file mode 100644 index 0000000000..2d36db1835 --- /dev/null +++ b/tests/qapi-schema/alternate-base.json @@ -0,0 +1,7 @@ +# FIXME: we should reject anonymous union with base type +{ 'type': 'Base', + 'data': { 'string': 'str' } } +{ 'union': 'MyUnion', + 'base': 'Base', + 'discriminator': {}, + 'data': { 'number': 'int' } } diff --git a/tests/qapi-schema/alternate-base.out b/tests/qapi-schema/alternate-base.out new file mode 100644 index 0000000000..7fb31f53aa --- /dev/null +++ b/tests/qapi-schema/alternate-base.out @@ -0,0 +1,4 @@ +[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', OrderedDict()), ('data', OrderedDict([('number', 'int')]))])] +[{'enum_name': 'MyUnionKind', 'enum_values': None}] +[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])] diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-clash.exit b/tests/qapi-schema/alternate-clash.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-clash.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-clash.json b/tests/qapi-schema/alternate-clash.json new file mode 100644 index 0000000000..7e2ef23f0d --- /dev/null +++ b/tests/qapi-schema/alternate-clash.json @@ -0,0 +1,4 @@ +# FIXME: we should detect C enum collisions in an anonymous union +{ 'union': 'Union1', + 'discriminator': {}, + 'data': { 'one': 'str', 'ONE': 'int' } } diff --git a/tests/qapi-schema/alternate-clash.out b/tests/qapi-schema/alternate-clash.out new file mode 100644 index 0000000000..c6687fa98a --- /dev/null +++ b/tests/qapi-schema/alternate-clash.out @@ -0,0 +1,3 @@ +[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('ONE', 'int')]))])] +[{'enum_name': 'Union1Kind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-conflict-dict.exit b/tests/qapi-schema/alternate-conflict-dict.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-dict.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json new file mode 100644 index 0000000000..d2ed9de272 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-dict.json @@ -0,0 +1,9 @@ +# FIXME: we should reject anonymous unions with multiple object branches +{ 'type': 'One', + 'data': { 'name': 'str' } } +{ 'type': 'Two', + 'data': { 'value': 'int' } } +{ 'union': 'MyUnion', + 'discriminator': {}, + 'data': { 'one': 'One', + 'two': 'Two' } } diff --git a/tests/qapi-schema/alternate-conflict-dict.out b/tests/qapi-schema/alternate-conflict-dict.out new file mode 100644 index 0000000000..b9ac945f5a --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-dict.out @@ -0,0 +1,6 @@ +[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))]), + OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', 'Two')]))])] +[{'enum_name': 'MyUnionKind', 'enum_values': None}] +[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))])] diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-conflict-string.exit b/tests/qapi-schema/alternate-conflict-string.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-string.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json new file mode 100644 index 0000000000..35245a30e6 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-string.json @@ -0,0 +1,7 @@ +# FIXME: we should reject anonymous unions with multiple string-like branches +{ 'enum': 'Enum', + 'data': [ 'hello', 'world' ] } +{ 'union': 'MyUnion', + 'discriminator': {}, + 'data': { 'one': 'str', + 'two': 'Enum' } } diff --git a/tests/qapi-schema/alternate-conflict-string.out b/tests/qapi-schema/alternate-conflict-string.out new file mode 100644 index 0000000000..e7b39a2117 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-string.out @@ -0,0 +1,5 @@ +[OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]), + OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('two', 'Enum')]))])] +[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']}, + {'enum_name': 'MyUnionKind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/alternate-good.err b/tests/qapi-schema/alternate-good.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-good.exit b/tests/qapi-schema/alternate-good.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-good.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-good.json b/tests/qapi-schema/alternate-good.json new file mode 100644 index 0000000000..73d0993b39 --- /dev/null +++ b/tests/qapi-schema/alternate-good.json @@ -0,0 +1,10 @@ +# Working example of anonymous union +{ 'type': 'Data', + 'data': { '*number': 'int', '*name': 'str' } } +{ 'enum': 'Enum', + 'data': [ 'hello', 'world' ] } +{ 'union': 'MyUnion', + 'discriminator': {}, + 'data': { 'value': 'int', + 'string': 'Enum', + 'struct': 'Data' } } diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out new file mode 100644 index 0000000000..b5117d1ab2 --- /dev/null +++ b/tests/qapi-schema/alternate-good.out @@ -0,0 +1,6 @@ +[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]), + OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]), + OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])] +[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']}, + {'enum_name': 'MyUnionKind', 'enum_values': None}] +[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])] diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-nested.exit b/tests/qapi-schema/alternate-nested.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-nested.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json new file mode 100644 index 0000000000..d5812bf7ad --- /dev/null +++ b/tests/qapi-schema/alternate-nested.json @@ -0,0 +1,7 @@ +# FIXME: we should reject a nested anonymous union branch +{ 'union': 'Union1', + 'discriminator': {}, + 'data': { 'name': 'str', 'value': 'int' } } +{ 'union': 'Union2', + 'discriminator': {}, + 'data': { 'nested': 'Union1' } } diff --git a/tests/qapi-schema/alternate-nested.out b/tests/qapi-schema/alternate-nested.out new file mode 100644 index 0000000000..0137c1f984 --- /dev/null +++ b/tests/qapi-schema/alternate-nested.out @@ -0,0 +1,5 @@ +[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('name', 'str'), ('value', 'int')]))]), + OrderedDict([('union', 'Union2'), ('discriminator', OrderedDict()), ('data', OrderedDict([('nested', 'Union1')]))])] +[{'enum_name': 'Union1Kind', 'enum_values': None}, + {'enum_name': 'Union2Kind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-unknown.exit b/tests/qapi-schema/alternate-unknown.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/alternate-unknown.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json new file mode 100644 index 0000000000..0bab9c2c58 --- /dev/null +++ b/tests/qapi-schema/alternate-unknown.json @@ -0,0 +1,4 @@ +# FIXME: we should reject an anonymous union with unknown type in branch +{ 'union': 'Union', + 'discriminator': {}, + 'data': { 'unknown': 'MissingType' } } diff --git a/tests/qapi-schema/alternate-unknown.out b/tests/qapi-schema/alternate-unknown.out new file mode 100644 index 0000000000..0911cdc978 --- /dev/null +++ b/tests/qapi-schema/alternate-unknown.out @@ -0,0 +1,3 @@ +[OrderedDict([('union', 'Union'), ('discriminator', OrderedDict()), ('data', OrderedDict([('unknown', 'MissingType')]))])] +[{'enum_name': 'UnionKind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err new file mode 100644 index 0000000000..5962ff4f74 --- /dev/null +++ b/tests/qapi-schema/flat-union-bad-base.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-bad-base.json:9: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type diff --git a/tests/qapi-schema/flat-union-bad-base.exit b/tests/qapi-schema/flat-union-bad-base.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/flat-union-bad-base.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json new file mode 100644 index 0000000000..6c141320dc --- /dev/null +++ b/tests/qapi-schema/flat-union-bad-base.json @@ -0,0 +1,13 @@ +# FIXME: poor message: we require the base to be an existing complex type +# TODO: should we allow an anonymous inline base type? +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'type': 'TestTypeA', + 'data': { 'string': 'str' } } +{ 'type': 'TestTypeB', + 'data': { 'integer': 'int' } } +{ 'union': 'TestUnion', + 'base': { 'enum1': 'TestEnum', 'kind': 'str' }, + 'discriminator': 'enum1', + 'data': { 'value1': 'TestTypeA', + 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/flat-union-bad-base.out b/tests/qapi-schema/flat-union-bad-base.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-bad-discriminator.exit b/tests/qapi-schema/flat-union-bad-discriminator.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/flat-union-bad-discriminator.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json new file mode 100644 index 0000000000..1599a59454 --- /dev/null +++ b/tests/qapi-schema/flat-union-bad-discriminator.json @@ -0,0 +1,14 @@ +# FIXME: we should require the discriminator to be a string naming a base-type member +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'type': 'TestBase', + 'data': { 'enum1': 'TestEnum', 'kind': 'str' } } +{ 'type': 'TestTypeA', + 'data': { 'string': 'str' } } +{ 'type': 'TestTypeB', + 'data': { 'integer': 'int' } } +{ 'union': 'TestUnion', + 'base': 'TestBase', + 'discriminator': [], + 'data': { 'kind1': 'TestTypeA', + 'kind2': 'TestTypeB' } } diff --git a/tests/qapi-schema/flat-union-bad-discriminator.out b/tests/qapi-schema/flat-union-bad-discriminator.out new file mode 100644 index 0000000000..b6ce2171ba --- /dev/null +++ b/tests/qapi-schema/flat-union-bad-discriminator.out @@ -0,0 +1,10 @@ +[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), + OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]), + OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', []), ('data', OrderedDict([('kind1', 'TestTypeA'), ('kind2', 'TestTypeB')]))])] +[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}, + {'enum_name': 'TestUnionKind', 'enum_values': None}] +[OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]), + OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] diff --git a/tests/qapi-schema/flat-union-base-star.err b/tests/qapi-schema/flat-union-base-star.err new file mode 100644 index 0000000000..60e47efab5 --- /dev/null +++ b/tests/qapi-schema/flat-union-base-star.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-base-star.json:8: Base '**' is not a valid type diff --git a/tests/qapi-schema/flat-union-base-star.exit b/tests/qapi-schema/flat-union-base-star.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/flat-union-base-star.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/flat-union-base-star.json b/tests/qapi-schema/flat-union-base-star.json new file mode 100644 index 0000000000..994533a514 --- /dev/null +++ b/tests/qapi-schema/flat-union-base-star.json @@ -0,0 +1,12 @@ +# we require the base to be an existing complex type +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'type': 'TestTypeA', + 'data': { 'string': 'str' } } +{ 'type': 'TestTypeB', + 'data': { 'integer': 'int' } } +{ 'union': 'TestUnion', + 'base': '**', + 'discriminator': 'enum1', + 'data': { 'value1': 'TestTypeA', + 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/flat-union-base-star.out b/tests/qapi-schema/flat-union-base-star.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err new file mode 100644 index 0000000000..185bf51a72 --- /dev/null +++ b/tests/qapi-schema/flat-union-base-union.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid type diff --git a/tests/qapi-schema/flat-union-base-union.exit b/tests/qapi-schema/flat-union-base-union.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/flat-union-base-union.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json new file mode 100644 index 0000000000..838986c48a --- /dev/null +++ b/tests/qapi-schema/flat-union-base-union.json @@ -0,0 +1,15 @@ +# FIXME: the error message needs help: we require the base to be a struct +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'type': 'TestTypeA', + 'data': { 'string': 'str' } } +{ 'type': 'TestTypeB', + 'data': { 'integer': 'int' } } +{ 'union': 'UnionBase', + 'data': { 'kind1': 'TestTypeA', + 'kind2': 'TestTypeB' } } +{ 'union': 'TestUnion', + 'base': 'UnionBase', + 'discriminator': 'type', + 'data': { 'kind1': 'TestTypeA', + 'kind2': 'TestTypeB' } } diff --git a/tests/qapi-schema/flat-union-base-union.out b/tests/qapi-schema/flat-union-base-union.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-branch-clash.err b/tests/qapi-schema/flat-union-branch-clash.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-branch-clash.exit b/tests/qapi-schema/flat-union-branch-clash.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/flat-union-branch-clash.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json new file mode 100644 index 0000000000..4091477b04 --- /dev/null +++ b/tests/qapi-schema/flat-union-branch-clash.json @@ -0,0 +1,14 @@ +# FIXME: we should check for no duplicate keys between branches and base +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'type': 'Base', + 'data': { 'enum1': 'TestEnum', 'name': 'str' } } +{ 'type': 'Branch1', + 'data': { 'name': 'str' } } +{ 'type': 'Branch2', + 'data': { 'value': 'int' } } +{ 'union': 'TestUnion', + 'base': 'Base', + 'discriminator': 'enum1', + 'data': { 'value1': 'Branch1', + 'value2': 'Branch2' } } diff --git a/tests/qapi-schema/flat-union-branch-clash.out b/tests/qapi-schema/flat-union-branch-clash.out new file mode 100644 index 0000000000..5d541133b9 --- /dev/null +++ b/tests/qapi-schema/flat-union-branch-clash.out @@ -0,0 +1,9 @@ +[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), + OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), + OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]), + OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'Branch1'), ('value2', 'Branch2')]))])] +[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}] +[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), + OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])] diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err new file mode 100644 index 0000000000..51fbe54350 --- /dev/null +++ b/tests/qapi-schema/flat-union-inline.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-inline.json:7: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type diff --git a/tests/qapi-schema/flat-union-inline.exit b/tests/qapi-schema/flat-union-inline.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/flat-union-inline.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json new file mode 100644 index 0000000000..2bdffeb248 --- /dev/null +++ b/tests/qapi-schema/flat-union-inline.json @@ -0,0 +1,11 @@ +# FIXME: poor message: we require branches to be a complex type name +# TODO: should we allow anonymous inline types? +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'type': 'Base', + 'data': { 'enum1': 'TestEnum', 'kind': 'str' } } +{ 'union': 'TestUnion', + 'base': { 'enum1': 'TestEnum', 'kind': 'str' }, + 'discriminator': 'enum1', + 'data': { 'value1': { 'string': 'str' }, + 'value2': { 'integer': 'int' } } } diff --git a/tests/qapi-schema/flat-union-inline.out b/tests/qapi-schema/flat-union-inline.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-int-branch.err b/tests/qapi-schema/flat-union-int-branch.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-int-branch.exit b/tests/qapi-schema/flat-union-int-branch.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/flat-union-int-branch.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json new file mode 100644 index 0000000000..3543215436 --- /dev/null +++ b/tests/qapi-schema/flat-union-int-branch.json @@ -0,0 +1,12 @@ +# FIXME: we should require flat union branches to be a complex type +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'type': 'Base', + 'data': { 'enum1': 'TestEnum' } } +{ 'type': 'TestTypeB', + 'data': { 'integer': 'int' } } +{ 'union': 'TestUnion', + 'base': 'Base', + 'discriminator': 'enum1', + 'data': { 'value1': 'int', + 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/flat-union-int-branch.out b/tests/qapi-schema/flat-union-int-branch.out new file mode 100644 index 0000000000..cd40e6c198 --- /dev/null +++ b/tests/qapi-schema/flat-union-int-branch.out @@ -0,0 +1,7 @@ +[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), + OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), + OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'int'), ('value2', 'TestTypeB')]))])] +[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}] +[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), + OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err index a59749eb84..97323a0432 100644 --- a/tests/qapi-schema/flat-union-no-base.err +++ b/tests/qapi-schema/flat-union-no-base.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-no-base.json:7: Flat union 'TestUnion' must have a base field +tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a base field diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json index 50f267323b..08a02476a3 100644 --- a/tests/qapi-schema/flat-union-no-base.json +++ b/tests/qapi-schema/flat-union-no-base.json @@ -1,10 +1,12 @@ +# FIXME: flat unions should require a base +# TODO: simple unions should be able to use an enum discriminator { 'type': 'TestTypeA', 'data': { 'string': 'str' } } - { 'type': 'TestTypeB', 'data': { 'integer': 'int' } } - +{ 'enum': 'Enum', + 'data': [ 'value1', 'value2' ] } { 'union': 'TestUnion', - 'discriminator': 'enum1', + 'discriminator': 'Enum', 'data': { 'value1': 'TestTypeA', 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/flat-union-optional-discriminator.err b/tests/qapi-schema/flat-union-optional-discriminator.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-optional-discriminator.exit b/tests/qapi-schema/flat-union-optional-discriminator.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/flat-union-optional-discriminator.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json new file mode 100644 index 0000000000..ece0d31fb3 --- /dev/null +++ b/tests/qapi-schema/flat-union-optional-discriminator.json @@ -0,0 +1,10 @@ +# FIXME: we should require the discriminator to be non-optional +{ 'enum': 'Enum', 'data': [ 'one', 'two' ] } +{ 'type': 'Base', + 'data': { '*switch': 'Enum' } } +{ 'type': 'Branch', 'data': { 'name': 'str' } } +{ 'union': 'MyUnion', + 'base': 'Base', + 'discriminator': '*switch', + 'data': { 'one': 'Branch', + 'two': 'Branch' } } diff --git a/tests/qapi-schema/flat-union-optional-discriminator.out b/tests/qapi-schema/flat-union-optional-discriminator.out new file mode 100644 index 0000000000..bb7db00902 --- /dev/null +++ b/tests/qapi-schema/flat-union-optional-discriminator.out @@ -0,0 +1,7 @@ +[OrderedDict([('enum', 'Enum'), ('data', ['one', 'two'])]), + OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]), + OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', '*switch'), ('data', OrderedDict([('one', 'Branch'), ('two', 'Branch')]))])] +[{'enum_name': 'Enum', 'enum_values': ['one', 'two']}] +[OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]), + OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))])] diff --git a/tests/qapi-schema/union-bad-branch.err b/tests/qapi-schema/union-bad-branch.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/union-bad-branch.exit b/tests/qapi-schema/union-bad-branch.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/union-bad-branch.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/union-bad-branch.json b/tests/qapi-schema/union-bad-branch.json new file mode 100644 index 0000000000..11e46de565 --- /dev/null +++ b/tests/qapi-schema/union-bad-branch.json @@ -0,0 +1,8 @@ +# FIXME: we should reject normal unions where branches would collide in C +{ 'type': 'One', + 'data': { 'string': 'str' } } +{ 'type': 'Two', + 'data': { 'number': 'int' } } +{ 'union': 'MyUnion', + 'data': { 'one': 'One', + 'ONE': 'Two' } } diff --git a/tests/qapi-schema/union-bad-branch.out b/tests/qapi-schema/union-bad-branch.out new file mode 100644 index 0000000000..6baf01be79 --- /dev/null +++ b/tests/qapi-schema/union-bad-branch.out @@ -0,0 +1,6 @@ +[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))]), + OrderedDict([('union', 'MyUnion'), ('data', OrderedDict([('one', 'One'), ('ONE', 'Two')]))])] +[{'enum_name': 'MyUnionKind', 'enum_values': None}] +[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))])] diff --git a/tests/qapi-schema/union-base-no-discriminator.err b/tests/qapi-schema/union-base-no-discriminator.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/union-base-no-discriminator.exit b/tests/qapi-schema/union-base-no-discriminator.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/union-base-no-discriminator.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json new file mode 100644 index 0000000000..c8cba3ad7c --- /dev/null +++ b/tests/qapi-schema/union-base-no-discriminator.json @@ -0,0 +1,14 @@ +# FIXME: we should reject simple unions with a base +{ 'type': 'TestTypeA', + 'data': { 'string': 'str' } } + +{ 'type': 'TestTypeB', + 'data': { 'integer': 'int' } } + +{ 'type': 'Base', + 'data': { 'string': 'str' } } + +{ 'union': 'TestUnion', + 'base': 'Base', + 'data': { 'value1': 'TestTypeA', + 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/union-base-no-discriminator.out b/tests/qapi-schema/union-base-no-discriminator.out new file mode 100644 index 0000000000..505fd57dc0 --- /dev/null +++ b/tests/qapi-schema/union-base-no-discriminator.out @@ -0,0 +1,8 @@ +[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('data', OrderedDict([('value1', 'TestTypeA'), ('value2', 'TestTypeB')]))])] +[{'enum_name': 'TestUnionKind', 'enum_values': None}] +[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])] diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err index 938f96962b..3cc82c0701 100644 --- a/tests/qapi-schema/union-invalid-base.err +++ b/tests/qapi-schema/union-invalid-base.err @@ -1 +1 @@ -tests/qapi-schema/union-invalid-base.json:7: Base 'TestBaseWrong' is not a valid type +tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid type diff --git a/tests/qapi-schema/union-invalid-base.json b/tests/qapi-schema/union-invalid-base.json index 1fa4930010..bc5dc8d043 100644 --- a/tests/qapi-schema/union-invalid-base.json +++ b/tests/qapi-schema/union-invalid-base.json @@ -1,3 +1,4 @@ +# a union base type must be a struct { 'type': 'TestTypeA', 'data': { 'string': 'str' } } @@ -5,6 +6,7 @@ 'data': { 'integer': 'int' } } { 'union': 'TestUnion', - 'base': 'TestBaseWrong', + 'base': 'int', + 'discriminator': 'int', 'data': { 'value1': 'TestTypeA', 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/union-max.err b/tests/qapi-schema/union-max.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/union-max.exit b/tests/qapi-schema/union-max.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/union-max.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/union-max.json b/tests/qapi-schema/union-max.json new file mode 100644 index 0000000000..45648c474b --- /dev/null +++ b/tests/qapi-schema/union-max.json @@ -0,0 +1,3 @@ +# FIXME: we should reject 'max' branch in a union, for collision with C enum +{ 'union': 'Union', + 'data': { 'max': 'int' } } diff --git a/tests/qapi-schema/union-max.out b/tests/qapi-schema/union-max.out new file mode 100644 index 0000000000..2757d367f0 --- /dev/null +++ b/tests/qapi-schema/union-max.out @@ -0,0 +1,3 @@ +[OrderedDict([('union', 'Union'), ('data', OrderedDict([('max', 'int')]))])] +[{'enum_name': 'UnionKind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/union-optional-branch.err b/tests/qapi-schema/union-optional-branch.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/union-optional-branch.exit b/tests/qapi-schema/union-optional-branch.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/union-optional-branch.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/union-optional-branch.json b/tests/qapi-schema/union-optional-branch.json new file mode 100644 index 0000000000..c513db72d4 --- /dev/null +++ b/tests/qapi-schema/union-optional-branch.json @@ -0,0 +1,2 @@ +# FIXME: union branches cannot be optional +{ 'union': 'Union', 'data': { '*a': 'int', 'b': 'str' } } diff --git a/tests/qapi-schema/union-optional-branch.out b/tests/qapi-schema/union-optional-branch.out new file mode 100644 index 0000000000..b03b5d2b4f --- /dev/null +++ b/tests/qapi-schema/union-optional-branch.out @@ -0,0 +1,3 @@ +[OrderedDict([('union', 'Union'), ('data', OrderedDict([('*a', 'int'), ('b', 'str')]))])] +[{'enum_name': 'UnionKind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/union-unknown.err b/tests/qapi-schema/union-unknown.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/union-unknown.exit b/tests/qapi-schema/union-unknown.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/union-unknown.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/union-unknown.json b/tests/qapi-schema/union-unknown.json new file mode 100644 index 0000000000..258f1d3821 --- /dev/null +++ b/tests/qapi-schema/union-unknown.json @@ -0,0 +1,3 @@ +# FIXME: we should reject a union with unknown type in branch +{ 'union': 'Union', + 'data': { 'unknown': 'MissingType' } } diff --git a/tests/qapi-schema/union-unknown.out b/tests/qapi-schema/union-unknown.out new file mode 100644 index 0000000000..8223dcf2c0 --- /dev/null +++ b/tests/qapi-schema/union-unknown.out @@ -0,0 +1,3 @@ +[OrderedDict([('union', 'Union'), ('data', OrderedDict([('unknown', 'MissingType')]))])] +[{'enum_name': 'UnionKind', 'enum_values': None}] +[] From 805017b7791200f1b72deef17dc98fd272b941eb Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:06 -0600 Subject: [PATCH 09/40] qapi: Clean up test coverage of simple unions The tests of UserDefNativeListUnion serve to validate code generation of simple unions without a base type, except that it did not have full coverage in the strict test. The next commits will remove tests and support for simple unions with a base type, so there is no real loss at repurposing that test here as opposed to churn of adding a new test then deleting the old one. Fix some indentation and long lines while at it. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/test-qmp-input-strict.c | 57 +++++++++++++++--------------- tests/test-qmp-input-visitor.c | 61 +++++++++++++++++---------------- tests/test-qmp-output-visitor.c | 38 +++++++++++++------- 3 files changed, 86 insertions(+), 70 deletions(-) diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c index d5360c6a87..486848e952 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -1,7 +1,7 @@ /* * QMP Input Visitor unit-tests (strict mode). * - * Copyright (C) 2011-2012 Red Hat Inc. + * Copyright (C) 2011-2012, 2015 Red Hat Inc. * * Authors: * Luiz Capitulino @@ -141,18 +141,18 @@ static void test_validate_list(TestInputVisitorData *data, qapi_free_UserDefOneList(head); } -static void test_validate_union(TestInputVisitorData *data, - const void *unused) +static void test_validate_union_native_list(TestInputVisitorData *data, + const void *unused) { - UserDefUnion *tmp = NULL; + UserDefNativeListUnion *tmp = NULL; Visitor *v; Error *err = NULL; - v = validate_test_init(data, "{ 'type': 'b', 'integer': 41, 'data' : { 'integer': 42 } }"); + v = validate_test_init(data, "{ 'type': 'integer', 'data' : [ 1, 2 ] }"); - visit_type_UserDefUnion(v, &tmp, NULL, &err); + visit_type_UserDefNativeListUnion(v, &tmp, NULL, &err); g_assert(!err); - qapi_free_UserDefUnion(tmp); + qapi_free_UserDefNativeListUnion(tmp); } static void test_validate_union_flat(TestInputVisitorData *data, @@ -232,18 +232,19 @@ static void test_validate_fail_list(TestInputVisitorData *data, qapi_free_UserDefOneList(head); } -static void test_validate_fail_union(TestInputVisitorData *data, - const void *unused) +static void test_validate_fail_union_native_list(TestInputVisitorData *data, + const void *unused) { - UserDefUnion *tmp = NULL; + UserDefNativeListUnion *tmp = NULL; Error *err = NULL; Visitor *v; - v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 } }"); + v = validate_test_init(data, + "{ 'type': 'integer', 'data' : [ 'string' ] }"); - visit_type_UserDefUnion(v, &tmp, NULL, &err); + visit_type_UserDefNativeListUnion(v, &tmp, NULL, &err); g_assert(err); - qapi_free_UserDefUnion(tmp); + qapi_free_UserDefNativeListUnion(tmp); } static void test_validate_fail_union_flat(TestInputVisitorData *data, @@ -304,31 +305,31 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); validate_test_add("/visitor/input-strict/pass/struct", - &testdata, test_validate_struct); + &testdata, test_validate_struct); validate_test_add("/visitor/input-strict/pass/struct-nested", - &testdata, test_validate_struct_nested); + &testdata, test_validate_struct_nested); validate_test_add("/visitor/input-strict/pass/list", - &testdata, test_validate_list); - validate_test_add("/visitor/input-strict/pass/union", - &testdata, test_validate_union); + &testdata, test_validate_list); validate_test_add("/visitor/input-strict/pass/union-flat", - &testdata, test_validate_union_flat); + &testdata, test_validate_union_flat); validate_test_add("/visitor/input-strict/pass/union-anon", - &testdata, test_validate_union_anon); + &testdata, test_validate_union_anon); + validate_test_add("/visitor/input-strict/pass/union-native-list", + &testdata, test_validate_union_native_list); validate_test_add("/visitor/input-strict/fail/struct", - &testdata, test_validate_fail_struct); + &testdata, test_validate_fail_struct); validate_test_add("/visitor/input-strict/fail/struct-nested", - &testdata, test_validate_fail_struct_nested); + &testdata, test_validate_fail_struct_nested); validate_test_add("/visitor/input-strict/fail/list", - &testdata, test_validate_fail_list); - validate_test_add("/visitor/input-strict/fail/union", - &testdata, test_validate_fail_union); + &testdata, test_validate_fail_list); validate_test_add("/visitor/input-strict/fail/union-flat", - &testdata, test_validate_fail_union_flat); + &testdata, test_validate_fail_union_flat); validate_test_add("/visitor/input-strict/fail/union-flat-no-discriminator", - &testdata, test_validate_fail_union_flat_no_discrim); + &testdata, test_validate_fail_union_flat_no_discrim); validate_test_add("/visitor/input-strict/fail/union-anon", - &testdata, test_validate_fail_union_anon); + &testdata, test_validate_fail_union_anon); + validate_test_add("/visitor/input-strict/fail/union-native-list", + &testdata, test_validate_fail_union_native_list); g_test_run(); diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index 1c8e87295c..0039ff6f07 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -1,7 +1,7 @@ /* * QMP Input Visitor unit-tests. * - * Copyright (C) 2011 Red Hat Inc. + * Copyright (C) 2011, 2015 Red Hat Inc. * * Authors: * Luiz Capitulino @@ -670,55 +670,58 @@ int main(int argc, char **argv) input_visitor_test_add("/visitor/input/number", &in_visitor_data, test_visitor_in_number); input_visitor_test_add("/visitor/input/string", - &in_visitor_data, test_visitor_in_string); + &in_visitor_data, test_visitor_in_string); input_visitor_test_add("/visitor/input/enum", - &in_visitor_data, test_visitor_in_enum); + &in_visitor_data, test_visitor_in_enum); input_visitor_test_add("/visitor/input/struct", - &in_visitor_data, test_visitor_in_struct); + &in_visitor_data, test_visitor_in_struct); input_visitor_test_add("/visitor/input/struct-nested", - &in_visitor_data, test_visitor_in_struct_nested); + &in_visitor_data, test_visitor_in_struct_nested); input_visitor_test_add("/visitor/input/list", - &in_visitor_data, test_visitor_in_list); + &in_visitor_data, test_visitor_in_list); input_visitor_test_add("/visitor/input/union", &in_visitor_data, test_visitor_in_union); input_visitor_test_add("/visitor/input/union-flat", - &in_visitor_data, test_visitor_in_union_flat); + &in_visitor_data, test_visitor_in_union_flat); input_visitor_test_add("/visitor/input/union-anon", - &in_visitor_data, test_visitor_in_union_anon); + &in_visitor_data, test_visitor_in_union_anon); input_visitor_test_add("/visitor/input/errors", - &in_visitor_data, test_visitor_in_errors); + &in_visitor_data, test_visitor_in_errors); input_visitor_test_add("/visitor/input/native_list/int", - &in_visitor_data, - test_visitor_in_native_list_int); + &in_visitor_data, + test_visitor_in_native_list_int); input_visitor_test_add("/visitor/input/native_list/int8", - &in_visitor_data, - test_visitor_in_native_list_int8); + &in_visitor_data, + test_visitor_in_native_list_int8); input_visitor_test_add("/visitor/input/native_list/int16", - &in_visitor_data, - test_visitor_in_native_list_int16); + &in_visitor_data, + test_visitor_in_native_list_int16); input_visitor_test_add("/visitor/input/native_list/int32", - &in_visitor_data, - test_visitor_in_native_list_int32); + &in_visitor_data, + test_visitor_in_native_list_int32); input_visitor_test_add("/visitor/input/native_list/int64", - &in_visitor_data, - test_visitor_in_native_list_int64); + &in_visitor_data, + test_visitor_in_native_list_int64); input_visitor_test_add("/visitor/input/native_list/uint8", - &in_visitor_data, - test_visitor_in_native_list_uint8); + &in_visitor_data, + test_visitor_in_native_list_uint8); input_visitor_test_add("/visitor/input/native_list/uint16", - &in_visitor_data, - test_visitor_in_native_list_uint16); + &in_visitor_data, + test_visitor_in_native_list_uint16); input_visitor_test_add("/visitor/input/native_list/uint32", - &in_visitor_data, - test_visitor_in_native_list_uint32); + &in_visitor_data, + test_visitor_in_native_list_uint32); input_visitor_test_add("/visitor/input/native_list/uint64", - &in_visitor_data, test_visitor_in_native_list_uint64); + &in_visitor_data, + test_visitor_in_native_list_uint64); input_visitor_test_add("/visitor/input/native_list/bool", - &in_visitor_data, test_visitor_in_native_list_bool); + &in_visitor_data, test_visitor_in_native_list_bool); input_visitor_test_add("/visitor/input/native_list/str", - &in_visitor_data, test_visitor_in_native_list_string); + &in_visitor_data, + test_visitor_in_native_list_string); input_visitor_test_add("/visitor/input/native_list/number", - &in_visitor_data, test_visitor_in_native_list_number); + &in_visitor_data, + test_visitor_in_native_list_number); g_test_run(); diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c index 74020de5e7..e5bf40fabb 100644 --- a/tests/test-qmp-output-visitor.c +++ b/tests/test-qmp-output-visitor.c @@ -1,7 +1,7 @@ /* * QMP Output Visitor unit-tests. * - * Copyright (C) 2011 Red Hat Inc. + * Copyright (C) 2011, 2015 Red Hat Inc. * * Authors: * Luiz Capitulino @@ -871,29 +871,41 @@ int main(int argc, char **argv) output_visitor_test_add("/visitor/output/empty", &out_visitor_data, test_visitor_out_empty); output_visitor_test_add("/visitor/output/native_list/int", - &out_visitor_data, test_visitor_out_native_list_int); + &out_visitor_data, + test_visitor_out_native_list_int); output_visitor_test_add("/visitor/output/native_list/int8", - &out_visitor_data, test_visitor_out_native_list_int8); + &out_visitor_data, + test_visitor_out_native_list_int8); output_visitor_test_add("/visitor/output/native_list/int16", - &out_visitor_data, test_visitor_out_native_list_int16); + &out_visitor_data, + test_visitor_out_native_list_int16); output_visitor_test_add("/visitor/output/native_list/int32", - &out_visitor_data, test_visitor_out_native_list_int32); + &out_visitor_data, + test_visitor_out_native_list_int32); output_visitor_test_add("/visitor/output/native_list/int64", - &out_visitor_data, test_visitor_out_native_list_int64); + &out_visitor_data, + test_visitor_out_native_list_int64); output_visitor_test_add("/visitor/output/native_list/uint8", - &out_visitor_data, test_visitor_out_native_list_uint8); + &out_visitor_data, + test_visitor_out_native_list_uint8); output_visitor_test_add("/visitor/output/native_list/uint16", - &out_visitor_data, test_visitor_out_native_list_uint16); + &out_visitor_data, + test_visitor_out_native_list_uint16); output_visitor_test_add("/visitor/output/native_list/uint32", - &out_visitor_data, test_visitor_out_native_list_uint32); + &out_visitor_data, + test_visitor_out_native_list_uint32); output_visitor_test_add("/visitor/output/native_list/uint64", - &out_visitor_data, test_visitor_out_native_list_uint64); + &out_visitor_data, + test_visitor_out_native_list_uint64); output_visitor_test_add("/visitor/output/native_list/bool", - &out_visitor_data, test_visitor_out_native_list_bool); + &out_visitor_data, + test_visitor_out_native_list_bool); output_visitor_test_add("/visitor/output/native_list/string", - &out_visitor_data, test_visitor_out_native_list_str); + &out_visitor_data, + test_visitor_out_native_list_str); output_visitor_test_add("/visitor/output/native_list/number", - &out_visitor_data, test_visitor_out_native_list_number); + &out_visitor_data, + test_visitor_out_native_list_number); g_test_run(); From a8d4a2e4d7e1a0207699de47142c9bdbf2cc8675 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:07 -0600 Subject: [PATCH 10/40] qapi: Forbid base without discriminator in unions None of the existing QMP or QGA interfaces uses a union with a base type but no discriminator; it is easier to avoid this in the generator to save room for other future extensions more likely to be useful. An earlier commit added a union-base-no-discriminator test to ensure that we eventually give a decent error message; likewise, removing UserDefUnion outright is okay, because we moved all the tests we wish to keep into the tests of the simple union UserDefNativeListUnion in the previous commit. Now is the time to actually forbid simple union with base, and remove the last vestiges from the testsuite. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi-types.py | 7 ++-- scripts/qapi-visit.py | 11 +++--- scripts/qapi.py | 20 +++++------ tests/qapi-schema/qapi-schema-test.json | 4 --- tests/qapi-schema/qapi-schema-test.out | 2 -- .../union-base-no-discriminator.err | 1 + .../union-base-no-discriminator.exit | 2 +- .../union-base-no-discriminator.json | 2 +- .../union-base-no-discriminator.out | 8 ----- tests/test-qmp-input-visitor.c | 19 ---------- tests/test-qmp-output-visitor.c | 36 ------------------- 11 files changed, 21 insertions(+), 91 deletions(-) diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index e400b03479..f6fb930279 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -242,10 +242,9 @@ struct %(name)s ''') if base: - base_fields = find_struct(base)['data'] - if discriminator: - base_fields = base_fields.copy() - del base_fields[discriminator] + assert discriminator + base_fields = find_struct(base)['data'].copy() + del base_fields[discriminator] ret += generate_struct_fields(base_fields) else: assert not discriminator diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 41596bb95b..dbf0101cba 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -310,16 +310,15 @@ def generate_visit_union(expr): ret = "" disc_type = enum_define['enum_name'] else: - # There will always be a discriminator in the C switch code, by default it - # is an enum type generated silently as "'%sKind' % (name)" + # There will always be a discriminator in the C switch code, by default + # it is an enum type generated silently as "'%sKind' % (name)" ret = generate_visit_enum('%sKind' % name, members.keys()) disc_type = '%sKind' % (name) if base: - base_fields = find_struct(base)['data'] - if discriminator: - base_fields = base_fields.copy() - del base_fields[discriminator] + assert discriminator + base_fields = find_struct(base)['data'].copy() + del base_fields[discriminator] ret += generate_visit_struct_fields(name, "", "", base_fields) if discriminator: diff --git a/scripts/qapi.py b/scripts/qapi.py index 3ce8c3321b..438468e3aa 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -259,22 +259,22 @@ def check_union(expr, expr_info): discriminator = expr.get('discriminator') members = expr['data'] - # If the object has a member 'base', its value must name a complex type. - if base: + # If the object has a member 'base', its value must name a complex type, + # and there must be a discriminator. + if base is not None: + if discriminator is None: + raise QAPIExprError(expr_info, + "Union '%s' requires a discriminator to go " + "along with base" %name) base_fields = find_base_fields(base) if not base_fields: raise QAPIExprError(expr_info, "Base '%s' is not a valid type" % base) - # If the union object has no member 'discriminator', it's an - # ordinary union. - if not discriminator: - enum_define = None - - # Else if the value of member 'discriminator' is {}, it's an - # anonymous union. - elif discriminator == {}: + # If the union object has no member 'discriminator', it's a + # simple union. If 'discriminator' is {}, it is an anonymous union. + if not discriminator or discriminator == {}: enum_define = None # Else, it's a flat union. diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 84f0f07637..b134f3fa29 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -36,10 +36,6 @@ { 'type': 'UserDefC', 'data': { 'string1': 'str', 'string2': 'str' } } -{ 'union': 'UserDefUnion', - 'base': 'UserDefZero', - 'data': { 'a' : 'UserDefA', 'b' : 'UserDefB' } } - { 'type': 'UserDefUnionBase', 'data': { 'string': 'str', 'enum1': 'EnumOne' } } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 915a61bcd1..664ae7b4b3 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -7,7 +7,6 @@ OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), - OrderedDict([('union', 'UserDefUnion'), ('base', 'UserDefZero'), ('data', OrderedDict([('a', 'UserDefA'), ('b', 'UserDefB')]))]), OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]), OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]), @@ -24,7 +23,6 @@ OrderedDict([('event', 'EVENT_C'), ('data', OrderedDict([('*a', 'int'), ('*b', 'UserDefOne'), ('c', 'str')]))]), OrderedDict([('event', 'EVENT_D'), ('data', OrderedDict([('a', 'EventStructOne'), ('b', 'str'), ('*c', 'str'), ('*enum3', 'EnumOne')]))])] [{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']}, - {'enum_name': 'UserDefUnionKind', 'enum_values': None}, {'enum_name': 'UserDefAnonUnionKind', 'enum_values': None}, {'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None}] [OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), diff --git a/tests/qapi-schema/union-base-no-discriminator.err b/tests/qapi-schema/union-base-no-discriminator.err index e69de29bb2..fc8b79c459 100644 --- a/tests/qapi-schema/union-base-no-discriminator.err +++ b/tests/qapi-schema/union-base-no-discriminator.err @@ -0,0 +1 @@ +tests/qapi-schema/union-base-no-discriminator.json:11: Union 'TestUnion' requires a discriminator to go along with base diff --git a/tests/qapi-schema/union-base-no-discriminator.exit b/tests/qapi-schema/union-base-no-discriminator.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/union-base-no-discriminator.exit +++ b/tests/qapi-schema/union-base-no-discriminator.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json index c8cba3ad7c..052596c46e 100644 --- a/tests/qapi-schema/union-base-no-discriminator.json +++ b/tests/qapi-schema/union-base-no-discriminator.json @@ -1,4 +1,4 @@ -# FIXME: we should reject simple unions with a base +# we reject simple unions with a base (or flat unions without discriminator) { 'type': 'TestTypeA', 'data': { 'string': 'str' } } diff --git a/tests/qapi-schema/union-base-no-discriminator.out b/tests/qapi-schema/union-base-no-discriminator.out index 505fd57dc0..e69de29bb2 100644 --- a/tests/qapi-schema/union-base-no-discriminator.out +++ b/tests/qapi-schema/union-base-no-discriminator.out @@ -1,8 +0,0 @@ -[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('data', OrderedDict([('value1', 'TestTypeA'), ('value2', 'TestTypeB')]))])] -[{'enum_name': 'TestUnionKind', 'enum_values': None}] -[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])] diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index 0039ff6f07..cc33f6461d 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -293,23 +293,6 @@ static void test_visitor_in_list(TestInputVisitorData *data, qapi_free_UserDefOneList(head); } -static void test_visitor_in_union(TestInputVisitorData *data, - const void *unused) -{ - Visitor *v; - Error *err = NULL; - UserDefUnion *tmp; - - v = visitor_input_test_init(data, "{ 'type': 'b', 'integer': 41, 'data' : { 'integer': 42 } }"); - - visit_type_UserDefUnion(v, &tmp, NULL, &err); - g_assert(err == NULL); - g_assert_cmpint(tmp->kind, ==, USER_DEF_UNION_KIND_B); - g_assert_cmpint(tmp->integer, ==, 41); - g_assert_cmpint(tmp->b->integer, ==, 42); - qapi_free_UserDefUnion(tmp); -} - static void test_visitor_in_union_flat(TestInputVisitorData *data, const void *unused) { @@ -679,8 +662,6 @@ int main(int argc, char **argv) &in_visitor_data, test_visitor_in_struct_nested); input_visitor_test_add("/visitor/input/list", &in_visitor_data, test_visitor_in_list); - input_visitor_test_add("/visitor/input/union", - &in_visitor_data, test_visitor_in_union); input_visitor_test_add("/visitor/input/union-flat", &in_visitor_data, test_visitor_in_union_flat); input_visitor_test_add("/visitor/input/union-anon", diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c index e5bf40fabb..ebe6ea3373 100644 --- a/tests/test-qmp-output-visitor.c +++ b/tests/test-qmp-output-visitor.c @@ -422,40 +422,6 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data, qapi_free_UserDefNestedList(head); } -static void test_visitor_out_union(TestOutputVisitorData *data, - const void *unused) -{ - QObject *arg, *qvalue; - QDict *qdict, *value; - - Error *err = NULL; - - UserDefUnion *tmp = g_malloc0(sizeof(UserDefUnion)); - tmp->kind = USER_DEF_UNION_KIND_A; - tmp->integer = 41; - tmp->a = g_malloc0(sizeof(UserDefA)); - tmp->a->boolean = true; - - visit_type_UserDefUnion(data->ov, &tmp, NULL, &err); - g_assert(err == NULL); - arg = qmp_output_get_qobject(data->qov); - - g_assert(qobject_type(arg) == QTYPE_QDICT); - qdict = qobject_to_qdict(arg); - - g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "a"); - g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 41); - - qvalue = qdict_get(qdict, "data"); - g_assert(data != NULL); - g_assert(qobject_type(qvalue) == QTYPE_QDICT); - value = qobject_to_qdict(qvalue); - g_assert_cmpint(qdict_get_bool(value, "boolean"), ==, true); - - qapi_free_UserDefUnion(tmp); - QDECREF(qdict); -} - static void test_visitor_out_union_flat(TestOutputVisitorData *data, const void *unused) { @@ -862,8 +828,6 @@ int main(int argc, char **argv) &out_visitor_data, test_visitor_out_list); output_visitor_test_add("/visitor/output/list-qapi-free", &out_visitor_data, test_visitor_out_list_qapi_free); - output_visitor_test_add("/visitor/output/union", - &out_visitor_data, test_visitor_out_union); output_visitor_test_add("/visitor/output/union-flat", &out_visitor_data, test_visitor_out_union_flat); output_visitor_test_add("/visitor/output/union-anon", From 44bd1276a7dea747c41f250cb71ab65965343a7f Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:08 -0600 Subject: [PATCH 11/40] qapi: Tighten checking of unions Previous commits demonstrated that the generator had several flaws with less-than-perfect unions: - a simple union that listed the same branch twice (or two variant names that map to the same C enumerator, including the implicit MAX sentinel) ended up generating invalid C code - an anonymous union that listed two branches with the same qtype ended up generating invalid C code - the generator crashed on anonymous union attempts to use an array type - the generator was silently ignoring a base type for anonymous unions - the generator allowed unknown types or nested anonymous unions as a branch in an anonymous union Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi-types.py | 13 +-- scripts/qapi.py | 95 +++++++++++++++---- tests/qapi-schema/alternate-array.err | 1 + tests/qapi-schema/alternate-array.exit | 2 +- tests/qapi-schema/alternate-array.json | 2 +- tests/qapi-schema/alternate-array.out | 4 - tests/qapi-schema/alternate-base.err | 1 + tests/qapi-schema/alternate-base.exit | 2 +- tests/qapi-schema/alternate-base.json | 2 +- tests/qapi-schema/alternate-base.out | 4 - tests/qapi-schema/alternate-clash.err | 1 + tests/qapi-schema/alternate-clash.exit | 2 +- tests/qapi-schema/alternate-clash.json | 2 +- tests/qapi-schema/alternate-clash.out | 3 - tests/qapi-schema/alternate-conflict-dict.err | 1 + .../qapi-schema/alternate-conflict-dict.exit | 2 +- .../qapi-schema/alternate-conflict-dict.json | 2 +- tests/qapi-schema/alternate-conflict-dict.out | 6 -- .../qapi-schema/alternate-conflict-string.err | 1 + .../alternate-conflict-string.exit | 2 +- .../alternate-conflict-string.json | 2 +- .../qapi-schema/alternate-conflict-string.out | 5 - tests/qapi-schema/alternate-nested.err | 1 + tests/qapi-schema/alternate-nested.exit | 2 +- tests/qapi-schema/alternate-nested.json | 2 +- tests/qapi-schema/alternate-nested.out | 5 - tests/qapi-schema/alternate-unknown.err | 1 + tests/qapi-schema/alternate-unknown.exit | 2 +- tests/qapi-schema/alternate-unknown.json | 2 +- tests/qapi-schema/alternate-unknown.out | 3 - tests/qapi-schema/flat-union-bad-base.err | 2 +- tests/qapi-schema/flat-union-bad-base.json | 2 +- .../flat-union-bad-discriminator.err | 1 + .../flat-union-bad-discriminator.exit | 2 +- .../flat-union-bad-discriminator.json | 2 +- .../flat-union-bad-discriminator.out | 10 -- tests/qapi-schema/flat-union-inline.err | 2 +- tests/qapi-schema/flat-union-inline.json | 2 +- tests/qapi-schema/flat-union-no-base.err | 2 +- tests/qapi-schema/flat-union-no-base.json | 2 +- tests/qapi-schema/union-bad-branch.err | 1 + tests/qapi-schema/union-bad-branch.exit | 2 +- tests/qapi-schema/union-bad-branch.json | 2 +- tests/qapi-schema/union-bad-branch.out | 6 -- tests/qapi-schema/union-max.err | 1 + tests/qapi-schema/union-max.exit | 2 +- tests/qapi-schema/union-max.json | 2 +- tests/qapi-schema/union-max.out | 3 - 48 files changed, 113 insertions(+), 106 deletions(-) diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index f6fb930279..2390887f28 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -181,17 +181,8 @@ const int %(name)s_qtypes[QTYPE_MAX] = { name=name) for key in members: - qapi_type = members[key] - if builtin_types.has_key(qapi_type): - qtype = builtin_types[qapi_type] - elif find_struct(qapi_type): - qtype = "QTYPE_QDICT" - elif find_union(qapi_type): - qtype = "QTYPE_QDICT" - elif find_enum(qapi_type): - qtype = "QTYPE_QSTRING" - else: - assert False, "Invalid anonymous union member" + qtype = find_anonymous_member_qtype(members[key]) + assert qtype, "Invalid anonymous union member" ret += mcgen(''' [ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s, diff --git a/scripts/qapi.py b/scripts/qapi.py index 438468e3aa..5f0f699994 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -224,6 +224,23 @@ def find_base_fields(base): return None return base_struct_define['data'] +# Return the qtype of an anonymous union branch, or None on error. +def find_anonymous_member_qtype(qapi_type): + if builtin_types.has_key(qapi_type): + return builtin_types[qapi_type] + elif find_struct(qapi_type): + return "QTYPE_QDICT" + elif find_enum(qapi_type): + return "QTYPE_QSTRING" + else: + union = find_union(qapi_type) + if union: + discriminator = union.get('discriminator') + if discriminator == {}: + return None + return "QTYPE_QDICT" + return None + # Return the discriminator enum define if discriminator is specified as an # enum type, otherwise return None. def discriminator_find_enum_define(expr): @@ -258,6 +275,8 @@ def check_union(expr, expr_info): base = expr.get('base') discriminator = expr.get('discriminator') members = expr['data'] + values = { 'MAX': '(automatic)' } + types_seen = {} # If the object has a member 'base', its value must name a complex type, # and there must be a discriminator. @@ -266,26 +285,35 @@ def check_union(expr, expr_info): raise QAPIExprError(expr_info, "Union '%s' requires a discriminator to go " "along with base" %name) + + # If the union object has no member 'discriminator', it's a + # simple union. If 'discriminator' is {}, it is an anonymous union. + if discriminator is None or discriminator == {}: + enum_define = None + if base is not None: + raise QAPIExprError(expr_info, + "Union '%s' must not have a base" + % name) + + # Else, it's a flat union. + else: + # The object must have a string member 'base'. + if not isinstance(base, str): + raise QAPIExprError(expr_info, + "Flat union '%s' must have a string base field" + % name) base_fields = find_base_fields(base) if not base_fields: raise QAPIExprError(expr_info, "Base '%s' is not a valid type" % base) - # If the union object has no member 'discriminator', it's a - # simple union. If 'discriminator' is {}, it is an anonymous union. - if not discriminator or discriminator == {}: - enum_define = None - - # Else, it's a flat union. - else: - # The object must have a member 'base'. - if not base: - raise QAPIExprError(expr_info, - "Flat union '%s' must have a base field" - % name) # The value of member 'discriminator' must name a member of the # base type. + if not isinstance(discriminator, str): + raise QAPIExprError(expr_info, + "Flat union '%s' discriminator must be a string" + % name) discriminator_type = base_fields.get(discriminator) if not discriminator_type: raise QAPIExprError(expr_info, @@ -301,15 +329,42 @@ def check_union(expr, expr_info): # Check every branch for (key, value) in members.items(): - # If this named member's value names an enum type, then all members + # If the discriminator names an enum type, then all members # of 'data' must also be members of the enum type. - if enum_define and not key in enum_define['enum_values']: - raise QAPIExprError(expr_info, - "Discriminator value '%s' is not found in " - "enum '%s'" % - (key, enum_define["enum_name"])) - # Todo: add checking for values. Key is checked as above, value can be - # also checked here, but we need more functions to handle array case. + if enum_define: + if not key in enum_define['enum_values']: + raise QAPIExprError(expr_info, + "Discriminator value '%s' is not found in " + "enum '%s'" % + (key, enum_define["enum_name"])) + + # Otherwise, check for conflicts in the generated enum + else: + c_key = _generate_enum_string(key) + if c_key in values: + raise QAPIExprError(expr_info, + "Union '%s' member '%s' clashes with '%s'" + % (name, key, values[c_key])) + values[c_key] = key + + # Ensure anonymous unions have no type conflicts. + if discriminator == {}: + if isinstance(value, list): + raise QAPIExprError(expr_info, + "Anonymous union '%s' member '%s' must " + "not be array type" % (name, key)) + qtype = find_anonymous_member_qtype(value) + if not qtype: + raise QAPIExprError(expr_info, + "Anonymous union '%s' member '%s' has " + "invalid type '%s'" % (name, key, value)) + if qtype in types_seen: + raise QAPIExprError(expr_info, + "Anonymous union '%s' member '%s' can't " + "be distinguished from member '%s'" + % (name, key, types_seen[qtype])) + types_seen[qtype] = key + def check_enum(expr, expr_info): name = expr['enum'] diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err index e69de29bb2..8d6ccc7325 100644 --- a/tests/qapi-schema/alternate-array.err +++ b/tests/qapi-schema/alternate-array.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-array.json:5: Anonymous union 'MyUnion' member 'two' must not be array type diff --git a/tests/qapi-schema/alternate-array.exit b/tests/qapi-schema/alternate-array.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/alternate-array.exit +++ b/tests/qapi-schema/alternate-array.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json index 25224c6c52..0da1a641b3 100644 --- a/tests/qapi-schema/alternate-array.json +++ b/tests/qapi-schema/alternate-array.json @@ -1,4 +1,4 @@ -# FIXME: we should not allow array branches in anonymous unions +# we do not allow array branches in anonymous unions # TODO: should we support this? { 'type': 'One', 'data': { 'name': 'str' } } diff --git a/tests/qapi-schema/alternate-array.out b/tests/qapi-schema/alternate-array.out index 90dc22c460..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-array.out +++ b/tests/qapi-schema/alternate-array.out @@ -1,4 +0,0 @@ -[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', ['int'])]))])] -[{'enum_name': 'MyUnionKind', 'enum_values': None}] -[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))])] diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err index e69de29bb2..85595b28a6 100644 --- a/tests/qapi-schema/alternate-base.err +++ b/tests/qapi-schema/alternate-base.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-base.json:4: Union 'MyUnion' must not have a base diff --git a/tests/qapi-schema/alternate-base.exit b/tests/qapi-schema/alternate-base.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/alternate-base.exit +++ b/tests/qapi-schema/alternate-base.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json index 2d36db1835..dad7f02998 100644 --- a/tests/qapi-schema/alternate-base.json +++ b/tests/qapi-schema/alternate-base.json @@ -1,4 +1,4 @@ -# FIXME: we should reject anonymous union with base type +# we reject anonymous union with base type { 'type': 'Base', 'data': { 'string': 'str' } } { 'union': 'MyUnion', diff --git a/tests/qapi-schema/alternate-base.out b/tests/qapi-schema/alternate-base.out index 7fb31f53aa..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-base.out +++ b/tests/qapi-schema/alternate-base.out @@ -1,4 +0,0 @@ -[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', OrderedDict()), ('data', OrderedDict([('number', 'int')]))])] -[{'enum_name': 'MyUnionKind', 'enum_values': None}] -[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])] diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err index e69de29bb2..1130c12834 100644 --- a/tests/qapi-schema/alternate-clash.err +++ b/tests/qapi-schema/alternate-clash.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-clash.json:2: Union 'Union1' member 'ONE' clashes with 'one' diff --git a/tests/qapi-schema/alternate-clash.exit b/tests/qapi-schema/alternate-clash.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/alternate-clash.exit +++ b/tests/qapi-schema/alternate-clash.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/alternate-clash.json b/tests/qapi-schema/alternate-clash.json index 7e2ef23f0d..fa2d27ed5e 100644 --- a/tests/qapi-schema/alternate-clash.json +++ b/tests/qapi-schema/alternate-clash.json @@ -1,4 +1,4 @@ -# FIXME: we should detect C enum collisions in an anonymous union +# we detect C enum collisions in an anonymous union { 'union': 'Union1', 'discriminator': {}, 'data': { 'one': 'str', 'ONE': 'int' } } diff --git a/tests/qapi-schema/alternate-clash.out b/tests/qapi-schema/alternate-clash.out index c6687fa98a..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-clash.out +++ b/tests/qapi-schema/alternate-clash.out @@ -1,3 +0,0 @@ -[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('ONE', 'int')]))])] -[{'enum_name': 'Union1Kind', 'enum_values': None}] -[] diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err index e69de29bb2..2d4550c565 100644 --- a/tests/qapi-schema/alternate-conflict-dict.err +++ b/tests/qapi-schema/alternate-conflict-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-conflict-dict.json:6: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one' diff --git a/tests/qapi-schema/alternate-conflict-dict.exit b/tests/qapi-schema/alternate-conflict-dict.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/alternate-conflict-dict.exit +++ b/tests/qapi-schema/alternate-conflict-dict.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json index d2ed9de272..ded302edae 100644 --- a/tests/qapi-schema/alternate-conflict-dict.json +++ b/tests/qapi-schema/alternate-conflict-dict.json @@ -1,4 +1,4 @@ -# FIXME: we should reject anonymous unions with multiple object branches +# we reject anonymous unions with multiple object branches { 'type': 'One', 'data': { 'name': 'str' } } { 'type': 'Two', diff --git a/tests/qapi-schema/alternate-conflict-dict.out b/tests/qapi-schema/alternate-conflict-dict.out index b9ac945f5a..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-conflict-dict.out +++ b/tests/qapi-schema/alternate-conflict-dict.out @@ -1,6 +0,0 @@ -[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))]), - OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', 'Two')]))])] -[{'enum_name': 'MyUnionKind', 'enum_values': None}] -[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))])] diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err index e69de29bb2..271ddcdec8 100644 --- a/tests/qapi-schema/alternate-conflict-string.err +++ b/tests/qapi-schema/alternate-conflict-string.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-conflict-string.json:4: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one' diff --git a/tests/qapi-schema/alternate-conflict-string.exit b/tests/qapi-schema/alternate-conflict-string.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/alternate-conflict-string.exit +++ b/tests/qapi-schema/alternate-conflict-string.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json index 35245a30e6..3834a3ddfe 100644 --- a/tests/qapi-schema/alternate-conflict-string.json +++ b/tests/qapi-schema/alternate-conflict-string.json @@ -1,4 +1,4 @@ -# FIXME: we should reject anonymous unions with multiple string-like branches +# we reject anonymous unions with multiple string-like branches { 'enum': 'Enum', 'data': [ 'hello', 'world' ] } { 'union': 'MyUnion', diff --git a/tests/qapi-schema/alternate-conflict-string.out b/tests/qapi-schema/alternate-conflict-string.out index e7b39a2117..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-conflict-string.out +++ b/tests/qapi-schema/alternate-conflict-string.out @@ -1,5 +0,0 @@ -[OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]), - OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('two', 'Enum')]))])] -[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']}, - {'enum_name': 'MyUnionKind', 'enum_values': None}] -[] diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err index e69de29bb2..59df96e132 100644 --- a/tests/qapi-schema/alternate-nested.err +++ b/tests/qapi-schema/alternate-nested.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-nested.json:5: Anonymous union 'Union2' member 'nested' has invalid type 'Union1' diff --git a/tests/qapi-schema/alternate-nested.exit b/tests/qapi-schema/alternate-nested.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/alternate-nested.exit +++ b/tests/qapi-schema/alternate-nested.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json index d5812bf7ad..ed2b6b7055 100644 --- a/tests/qapi-schema/alternate-nested.json +++ b/tests/qapi-schema/alternate-nested.json @@ -1,4 +1,4 @@ -# FIXME: we should reject a nested anonymous union branch +# we reject a nested anonymous union branch { 'union': 'Union1', 'discriminator': {}, 'data': { 'name': 'str', 'value': 'int' } } diff --git a/tests/qapi-schema/alternate-nested.out b/tests/qapi-schema/alternate-nested.out index 0137c1f984..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-nested.out +++ b/tests/qapi-schema/alternate-nested.out @@ -1,5 +0,0 @@ -[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('name', 'str'), ('value', 'int')]))]), - OrderedDict([('union', 'Union2'), ('discriminator', OrderedDict()), ('data', OrderedDict([('nested', 'Union1')]))])] -[{'enum_name': 'Union1Kind', 'enum_values': None}, - {'enum_name': 'Union2Kind', 'enum_values': None}] -[] diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err index e69de29bb2..bf8e9aef64 100644 --- a/tests/qapi-schema/alternate-unknown.err +++ b/tests/qapi-schema/alternate-unknown.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-unknown.json:2: Anonymous union 'Union' member 'unknown' has invalid type 'MissingType' diff --git a/tests/qapi-schema/alternate-unknown.exit b/tests/qapi-schema/alternate-unknown.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/alternate-unknown.exit +++ b/tests/qapi-schema/alternate-unknown.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json index 0bab9c2c58..0c305c282c 100644 --- a/tests/qapi-schema/alternate-unknown.json +++ b/tests/qapi-schema/alternate-unknown.json @@ -1,4 +1,4 @@ -# FIXME: we should reject an anonymous union with unknown type in branch +# we reject an anonymous union with unknown type in branch { 'union': 'Union', 'discriminator': {}, 'data': { 'unknown': 'MissingType' } } diff --git a/tests/qapi-schema/alternate-unknown.out b/tests/qapi-schema/alternate-unknown.out index 0911cdc978..e69de29bb2 100644 --- a/tests/qapi-schema/alternate-unknown.out +++ b/tests/qapi-schema/alternate-unknown.out @@ -1,3 +0,0 @@ -[OrderedDict([('union', 'Union'), ('discriminator', OrderedDict()), ('data', OrderedDict([('unknown', 'MissingType')]))])] -[{'enum_name': 'UnionKind', 'enum_values': None}] -[] diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err index 5962ff4f74..f9c31b2bf5 100644 --- a/tests/qapi-schema/flat-union-bad-base.err +++ b/tests/qapi-schema/flat-union-bad-base.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-bad-base.json:9: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type +tests/qapi-schema/flat-union-bad-base.json:9: Flat union 'TestUnion' must have a string base field diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json index 6c141320dc..bb0f02d298 100644 --- a/tests/qapi-schema/flat-union-bad-base.json +++ b/tests/qapi-schema/flat-union-bad-base.json @@ -1,4 +1,4 @@ -# FIXME: poor message: we require the base to be an existing complex type +# we require the base to be an existing complex type # TODO: should we allow an anonymous inline base type? { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err index e69de29bb2..1661c52b03 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.err +++ b/tests/qapi-schema/flat-union-bad-discriminator.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-bad-discriminator.json:10: Flat union 'TestUnion' discriminator must be a string diff --git a/tests/qapi-schema/flat-union-bad-discriminator.exit b/tests/qapi-schema/flat-union-bad-discriminator.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.exit +++ b/tests/qapi-schema/flat-union-bad-discriminator.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json index 1599a59454..3ce43e8356 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.json +++ b/tests/qapi-schema/flat-union-bad-discriminator.json @@ -1,4 +1,4 @@ -# FIXME: we should require the discriminator to be a string naming a base-type member +# we require the discriminator to be a string naming a base-type member { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } { 'type': 'TestBase', diff --git a/tests/qapi-schema/flat-union-bad-discriminator.out b/tests/qapi-schema/flat-union-bad-discriminator.out index b6ce2171ba..e69de29bb2 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.out +++ b/tests/qapi-schema/flat-union-bad-discriminator.out @@ -1,10 +0,0 @@ -[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), - OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]), - OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', []), ('data', OrderedDict([('kind1', 'TestTypeA'), ('kind2', 'TestTypeB')]))])] -[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}, - {'enum_name': 'TestUnionKind', 'enum_values': None}] -[OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]), - OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err index 51fbe54350..ec586277b7 100644 --- a/tests/qapi-schema/flat-union-inline.err +++ b/tests/qapi-schema/flat-union-inline.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-inline.json:7: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type +tests/qapi-schema/flat-union-inline.json:7: Flat union 'TestUnion' must have a string base field diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json index 2bdffeb248..f3da1175f8 100644 --- a/tests/qapi-schema/flat-union-inline.json +++ b/tests/qapi-schema/flat-union-inline.json @@ -1,4 +1,4 @@ -# FIXME: poor message: we require branches to be a complex type name +# we require branches to be a complex type name # TODO: should we allow anonymous inline types? { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err index 97323a0432..bb3f708747 100644 --- a/tests/qapi-schema/flat-union-no-base.err +++ b/tests/qapi-schema/flat-union-no-base.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a base field +tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a string base field diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json index 08a02476a3..9547bb8988 100644 --- a/tests/qapi-schema/flat-union-no-base.json +++ b/tests/qapi-schema/flat-union-no-base.json @@ -1,4 +1,4 @@ -# FIXME: flat unions should require a base +# flat unions require a base # TODO: simple unions should be able to use an enum discriminator { 'type': 'TestTypeA', 'data': { 'string': 'str' } } diff --git a/tests/qapi-schema/union-bad-branch.err b/tests/qapi-schema/union-bad-branch.err index e69de29bb2..8822735561 100644 --- a/tests/qapi-schema/union-bad-branch.err +++ b/tests/qapi-schema/union-bad-branch.err @@ -0,0 +1 @@ +tests/qapi-schema/union-bad-branch.json:6: Union 'MyUnion' member 'ONE' clashes with 'one' diff --git a/tests/qapi-schema/union-bad-branch.exit b/tests/qapi-schema/union-bad-branch.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/union-bad-branch.exit +++ b/tests/qapi-schema/union-bad-branch.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/union-bad-branch.json b/tests/qapi-schema/union-bad-branch.json index 11e46de565..4303666bb5 100644 --- a/tests/qapi-schema/union-bad-branch.json +++ b/tests/qapi-schema/union-bad-branch.json @@ -1,4 +1,4 @@ -# FIXME: we should reject normal unions where branches would collide in C +# we reject normal unions where branches would collide in C { 'type': 'One', 'data': { 'string': 'str' } } { 'type': 'Two', diff --git a/tests/qapi-schema/union-bad-branch.out b/tests/qapi-schema/union-bad-branch.out index 6baf01be79..e69de29bb2 100644 --- a/tests/qapi-schema/union-bad-branch.out +++ b/tests/qapi-schema/union-bad-branch.out @@ -1,6 +0,0 @@ -[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))]), - OrderedDict([('union', 'MyUnion'), ('data', OrderedDict([('one', 'One'), ('ONE', 'Two')]))])] -[{'enum_name': 'MyUnionKind', 'enum_values': None}] -[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))])] diff --git a/tests/qapi-schema/union-max.err b/tests/qapi-schema/union-max.err index e69de29bb2..55ce4399d6 100644 --- a/tests/qapi-schema/union-max.err +++ b/tests/qapi-schema/union-max.err @@ -0,0 +1 @@ +tests/qapi-schema/union-max.json:2: Union 'Union' member 'max' clashes with '(automatic)' diff --git a/tests/qapi-schema/union-max.exit b/tests/qapi-schema/union-max.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/union-max.exit +++ b/tests/qapi-schema/union-max.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/union-max.json b/tests/qapi-schema/union-max.json index 45648c474b..d6ad986999 100644 --- a/tests/qapi-schema/union-max.json +++ b/tests/qapi-schema/union-max.json @@ -1,3 +1,3 @@ -# FIXME: we should reject 'max' branch in a union, for collision with C enum +# we reject 'max' branch in a union, for collision with C enum { 'union': 'Union', 'data': { 'max': 'int' } } diff --git a/tests/qapi-schema/union-max.out b/tests/qapi-schema/union-max.out index 2757d367f0..e69de29bb2 100644 --- a/tests/qapi-schema/union-max.out +++ b/tests/qapi-schema/union-max.out @@ -1,3 +0,0 @@ -[OrderedDict([('union', 'Union'), ('data', OrderedDict([('max', 'int')]))])] -[{'enum_name': 'UnionKind', 'enum_values': None}] -[] From 268a1c5eb10832c2e4476d3fe199ea547dabecb7 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:09 -0600 Subject: [PATCH 12/40] qapi: Prepare for catching more semantic parse errors This patch widens the scope of a try block (with the attending reindentation required by Python) in preparation for a future patch adding more instances of QAPIExprError inside the block. It's easier to separate indentation from semantic changes, so this patch has no real behavior change. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 5f0f699994..0c3459bfe2 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -399,6 +399,7 @@ def check_exprs(schema): check_event(expr, info) def parse_schema(input_file): + # First pass: read entire file into memory try: schema = QAPISchema(open(input_file, "r")) except (QAPISchemaError, QAPIExprError), e: @@ -407,24 +408,26 @@ def parse_schema(input_file): exprs = [] - for expr_elem in schema.exprs: - expr = expr_elem['expr'] - if expr.has_key('enum'): - add_enum(expr['enum'], expr.get('data')) - elif expr.has_key('union'): - add_union(expr) - elif expr.has_key('type'): - add_struct(expr) - exprs.append(expr) - - # Try again for hidden UnionKind enum - for expr_elem in schema.exprs: - expr = expr_elem['expr'] - if expr.has_key('union'): - if not discriminator_find_enum_define(expr): - add_enum('%sKind' % expr['union']) - try: + # Next pass: learn the types. + for expr_elem in schema.exprs: + expr = expr_elem['expr'] + if expr.has_key('enum'): + add_enum(expr['enum'], expr.get('data')) + elif expr.has_key('union'): + add_union(expr) + elif expr.has_key('type'): + add_struct(expr) + exprs.append(expr) + + # Try again for hidden UnionKind enum + for expr_elem in schema.exprs: + expr = expr_elem['expr'] + if expr.has_key('union'): + if not discriminator_find_enum_define(expr): + add_enum('%sKind' % expr['union']) + + # Final pass - validate that exprs make sense check_exprs(schema) except QAPIExprError, e: print >>sys.stderr, e From 811d04fd0cff1229480d3f5b2e349f646ab6e3c1 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:10 -0600 Subject: [PATCH 13/40] qapi: Segregate anonymous unions into alternates in generator Special-casing 'discriminator == {}' for handling anonymous unions is getting awkward; since this particular type is not always a dictionary on the wire, it is easier to treat it as a completely different class of type, "alternate", so that if a type is listed in the union_types array, we know it is not an anonymous union. This patch just further segregates union handling, to make sure that anonymous unions are not stored in union_types, and splitting up check_union() into separate functions. A future patch will change the qapi grammar, and having the segregation already in place will make it easier to deal with the distinct meta-type. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi-types.py | 6 +- scripts/qapi-visit.py | 4 +- scripts/qapi.py | 88 +++++++++++++++++---------- tests/qapi-schema/alternate-base.err | 2 +- tests/qapi-schema/alternate-clash.err | 2 +- 5 files changed, 62 insertions(+), 40 deletions(-) diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index 2390887f28..c9e0201d10 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -170,7 +170,7 @@ typedef enum %(name)s return lookup_decl + enum_decl -def generate_anon_union_qtypes(expr): +def generate_alternate_qtypes(expr): name = expr['union'] members = expr['data'] @@ -181,7 +181,7 @@ const int %(name)s_qtypes[QTYPE_MAX] = { name=name) for key in members: - qtype = find_anonymous_member_qtype(members[key]) + qtype = find_alternate_member_qtype(members[key]) assert qtype, "Invalid anonymous union member" ret += mcgen(''' @@ -408,7 +408,7 @@ for expr in exprs: fdef.write(generate_enum_lookup('%sKind' % expr['union'], expr['data'].keys())) if expr.get('discriminator') == {}: - fdef.write(generate_anon_union_qtypes(expr)) + fdef.write(generate_alternate_qtypes(expr)) else: continue fdecl.write(ret) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index dbf0101cba..6bd2b6bfab 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -237,7 +237,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s *obj, const char *name, Error **er ''', name=name) -def generate_visit_anon_union(name, members): +def generate_visit_alternate(name, members): ret = mcgen(''' void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp) @@ -302,7 +302,7 @@ def generate_visit_union(expr): if discriminator == {}: assert not base - return generate_visit_anon_union(name, members) + return generate_visit_alternate(name, members) enum_define = discriminator_find_enum_define(expr) if enum_define: diff --git a/scripts/qapi.py b/scripts/qapi.py index 0c3459bfe2..0b88325abd 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -224,21 +224,16 @@ def find_base_fields(base): return None return base_struct_define['data'] -# Return the qtype of an anonymous union branch, or None on error. -def find_anonymous_member_qtype(qapi_type): +# Return the qtype of an alternate branch, or None on error. +def find_alternate_member_qtype(qapi_type): if builtin_types.has_key(qapi_type): return builtin_types[qapi_type] elif find_struct(qapi_type): return "QTYPE_QDICT" elif find_enum(qapi_type): return "QTYPE_QSTRING" - else: - union = find_union(qapi_type) - if union: - discriminator = union.get('discriminator') - if discriminator == {}: - return None - return "QTYPE_QDICT" + elif find_union(qapi_type): + return "QTYPE_QDICT" return None # Return the discriminator enum define if discriminator is specified as an @@ -276,7 +271,6 @@ def check_union(expr, expr_info): discriminator = expr.get('discriminator') members = expr['data'] values = { 'MAX': '(automatic)' } - types_seen = {} # If the object has a member 'base', its value must name a complex type, # and there must be a discriminator. @@ -286,13 +280,15 @@ def check_union(expr, expr_info): "Union '%s' requires a discriminator to go " "along with base" %name) - # If the union object has no member 'discriminator', it's a - # simple union. If 'discriminator' is {}, it is an anonymous union. - if discriminator is None or discriminator == {}: + # Two types of unions, determined by discriminator. + assert discriminator != {} + + # With no discriminator it is a simple union. + if discriminator is None: enum_define = None if base is not None: raise QAPIExprError(expr_info, - "Union '%s' must not have a base" + "Simple union '%s' must not have a base" % name) # Else, it's a flat union. @@ -347,24 +343,46 @@ def check_union(expr, expr_info): % (name, key, values[c_key])) values[c_key] = key - # Ensure anonymous unions have no type conflicts. - if discriminator == {}: - if isinstance(value, list): - raise QAPIExprError(expr_info, - "Anonymous union '%s' member '%s' must " - "not be array type" % (name, key)) - qtype = find_anonymous_member_qtype(value) - if not qtype: - raise QAPIExprError(expr_info, - "Anonymous union '%s' member '%s' has " - "invalid type '%s'" % (name, key, value)) - if qtype in types_seen: - raise QAPIExprError(expr_info, - "Anonymous union '%s' member '%s' can't " - "be distinguished from member '%s'" - % (name, key, types_seen[qtype])) - types_seen[qtype] = key +def check_alternate(expr, expr_info): + name = expr['union'] + base = expr.get('base') + discriminator = expr.get('discriminator') + members = expr['data'] + values = { 'MAX': '(automatic)' } + types_seen = {} + assert discriminator == {} + if base is not None: + raise QAPIExprError(expr_info, + "Anonymous union '%s' must not have a base" + % name) + + # Check every branch + for (key, value) in members.items(): + # Check for conflicts in the generated enum + c_key = _generate_enum_string(key) + if c_key in values: + raise QAPIExprError(expr_info, + "Anonymous union '%s' member '%s' clashes " + "with '%s'" % (name, key, values[c_key])) + values[c_key] = key + + # Ensure alternates have no type conflicts. + if isinstance(value, list): + raise QAPIExprError(expr_info, + "Anonymous union '%s' member '%s' must " + "not be array type" % (name, key)) + qtype = find_alternate_member_qtype(value) + if not qtype: + raise QAPIExprError(expr_info, + "Anonymous union '%s' member '%s' has " + "invalid type '%s'" % (name, key, value)) + if qtype in types_seen: + raise QAPIExprError(expr_info, + "Anonymous union '%s' member '%s' can't " + "be distinguished from member '%s'" + % (name, key, types_seen[qtype])) + types_seen[qtype] = key def check_enum(expr, expr_info): name = expr['enum'] @@ -394,7 +412,10 @@ def check_exprs(schema): if expr.has_key('enum'): check_enum(expr, info) elif expr.has_key('union'): - check_union(expr, info) + if expr.get('discriminator') == {}: + check_alternate(expr, info) + else: + check_union(expr, info) elif expr.has_key('event'): check_event(expr, info) @@ -536,7 +557,8 @@ def find_struct(name): def add_union(definition): global union_types - union_types.append(definition) + if definition.get('discriminator') != {}: + union_types.append(definition) def find_union(name): global union_types diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err index 85595b28a6..a2486b8407 100644 --- a/tests/qapi-schema/alternate-base.err +++ b/tests/qapi-schema/alternate-base.err @@ -1 +1 @@ -tests/qapi-schema/alternate-base.json:4: Union 'MyUnion' must not have a base +tests/qapi-schema/alternate-base.json:4: Anonymous union 'MyUnion' must not have a base diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err index 1130c12834..8949f52b3e 100644 --- a/tests/qapi-schema/alternate-clash.err +++ b/tests/qapi-schema/alternate-clash.err @@ -1 +1 @@ -tests/qapi-schema/alternate-clash.json:2: Union 'Union1' member 'ONE' clashes with 'one' +tests/qapi-schema/alternate-clash.json:2: Anonymous union 'Union1' member 'ONE' clashes with 'one' From ab045267447d52e63a79c0e18f89ae4411f5420b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:11 -0600 Subject: [PATCH 14/40] qapi: Rename anonymous union type in test Reduce churn in the future patch that replaces anonymous unions with a new metatype 'alternate' by changing 'AnonUnion' to 'Alternate'. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/qapi-schema/qapi-schema-test.json | 2 +- tests/qapi-schema/qapi-schema-test.out | 4 ++-- tests/test-qmp-input-strict.c | 28 ++++++++++++------------- tests/test-qmp-input-visitor.c | 16 +++++++------- tests/test-qmp-output-visitor.c | 16 +++++++------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index b134f3fa29..e1d35e1248 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -53,7 +53,7 @@ 'discriminator': 'enum1', 'data': { 'value1' : 'UserDefC', 'value2' : 'UserDefB', 'value3' : 'UserDefA' } } -{ 'union': 'UserDefAnonUnion', +{ 'union': 'UserDefAlternate', 'discriminator': {}, 'data': { 'uda': 'UserDefA', 's': 'str', 'i': 'int' } } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 664ae7b4b3..b55ab8de10 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -10,7 +10,7 @@ OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]), OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]), - OrderedDict([('union', 'UserDefAnonUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), + OrderedDict([('union', 'UserDefAlternate'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('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'])]))]), OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]), OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]), @@ -23,7 +23,7 @@ OrderedDict([('event', 'EVENT_C'), ('data', OrderedDict([('*a', 'int'), ('*b', 'UserDefOne'), ('c', 'str')]))]), OrderedDict([('event', 'EVENT_D'), ('data', OrderedDict([('a', 'EventStructOne'), ('b', 'str'), ('*c', 'str'), ('*enum3', 'EnumOne')]))])] [{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']}, - {'enum_name': 'UserDefAnonUnionKind', 'enum_values': None}, + {'enum_name': 'UserDefAlternateKind', 'enum_values': None}, {'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None}] [OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), OrderedDict([('type', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c index 486848e952..a5f7bf3c3e 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -173,18 +173,18 @@ static void test_validate_union_flat(TestInputVisitorData *data, qapi_free_UserDefFlatUnion(tmp); } -static void test_validate_union_anon(TestInputVisitorData *data, - const void *unused) +static void test_validate_alternate(TestInputVisitorData *data, + const void *unused) { - UserDefAnonUnion *tmp = NULL; + UserDefAlternate *tmp = NULL; Visitor *v; Error *err = NULL; v = validate_test_init(data, "42"); - visit_type_UserDefAnonUnion(v, &tmp, NULL, &err); + visit_type_UserDefAlternate(v, &tmp, NULL, &err); g_assert(!err); - qapi_free_UserDefAnonUnion(tmp); + qapi_free_UserDefAlternate(tmp); } static void test_validate_fail_struct(TestInputVisitorData *data, @@ -276,18 +276,18 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data, qapi_free_UserDefFlatUnion2(tmp); } -static void test_validate_fail_union_anon(TestInputVisitorData *data, - const void *unused) +static void test_validate_fail_alternate(TestInputVisitorData *data, + const void *unused) { - UserDefAnonUnion *tmp = NULL; + UserDefAlternate *tmp = NULL; Visitor *v; Error *err = NULL; v = validate_test_init(data, "3.14"); - visit_type_UserDefAnonUnion(v, &tmp, NULL, &err); + visit_type_UserDefAlternate(v, &tmp, NULL, &err); g_assert(err); - qapi_free_UserDefAnonUnion(tmp); + qapi_free_UserDefAlternate(tmp); } static void validate_test_add(const char *testpath, @@ -312,8 +312,8 @@ int main(int argc, char **argv) &testdata, test_validate_list); validate_test_add("/visitor/input-strict/pass/union-flat", &testdata, test_validate_union_flat); - validate_test_add("/visitor/input-strict/pass/union-anon", - &testdata, test_validate_union_anon); + validate_test_add("/visitor/input-strict/pass/alternate", + &testdata, test_validate_alternate); validate_test_add("/visitor/input-strict/pass/union-native-list", &testdata, test_validate_union_native_list); validate_test_add("/visitor/input-strict/fail/struct", @@ -326,8 +326,8 @@ int main(int argc, char **argv) &testdata, test_validate_fail_union_flat); validate_test_add("/visitor/input-strict/fail/union-flat-no-discriminator", &testdata, test_validate_fail_union_flat_no_discrim); - validate_test_add("/visitor/input-strict/fail/union-anon", - &testdata, test_validate_fail_union_anon); + validate_test_add("/visitor/input-strict/fail/alternate", + &testdata, test_validate_fail_alternate); validate_test_add("/visitor/input-strict/fail/union-native-list", &testdata, test_validate_fail_union_native_list); diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index cc33f6461d..882359a34f 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -315,20 +315,20 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data, qapi_free_UserDefFlatUnion(tmp); } -static void test_visitor_in_union_anon(TestInputVisitorData *data, - const void *unused) +static void test_visitor_in_alternate(TestInputVisitorData *data, + const void *unused) { Visitor *v; Error *err = NULL; - UserDefAnonUnion *tmp; + UserDefAlternate *tmp; v = visitor_input_test_init(data, "42"); - visit_type_UserDefAnonUnion(v, &tmp, NULL, &err); + visit_type_UserDefAlternate(v, &tmp, NULL, &err); g_assert(err == NULL); - g_assert_cmpint(tmp->kind, ==, USER_DEF_ANON_UNION_KIND_I); + g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I); g_assert_cmpint(tmp->i, ==, 42); - qapi_free_UserDefAnonUnion(tmp); + qapi_free_UserDefAlternate(tmp); } static void test_native_list_integer_helper(TestInputVisitorData *data, @@ -664,8 +664,8 @@ int main(int argc, char **argv) &in_visitor_data, test_visitor_in_list); input_visitor_test_add("/visitor/input/union-flat", &in_visitor_data, test_visitor_in_union_flat); - input_visitor_test_add("/visitor/input/union-anon", - &in_visitor_data, test_visitor_in_union_anon); + input_visitor_test_add("/visitor/input/alternate", + &in_visitor_data, test_visitor_in_alternate); input_visitor_test_add("/visitor/input/errors", &in_visitor_data, test_visitor_in_errors); input_visitor_test_add("/visitor/input/native_list/int", diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c index ebe6ea3373..602bc12cbc 100644 --- a/tests/test-qmp-output-visitor.c +++ b/tests/test-qmp-output-visitor.c @@ -453,24 +453,24 @@ static void test_visitor_out_union_flat(TestOutputVisitorData *data, QDECREF(qdict); } -static void test_visitor_out_union_anon(TestOutputVisitorData *data, - const void *unused) +static void test_visitor_out_alternate(TestOutputVisitorData *data, + const void *unused) { QObject *arg; Error *err = NULL; - UserDefAnonUnion *tmp = g_malloc0(sizeof(UserDefAnonUnion)); - tmp->kind = USER_DEF_ANON_UNION_KIND_I; + UserDefAlternate *tmp = g_malloc0(sizeof(UserDefAlternate)); + tmp->kind = USER_DEF_ALTERNATE_KIND_I; tmp->i = 42; - visit_type_UserDefAnonUnion(data->ov, &tmp, NULL, &err); + visit_type_UserDefAlternate(data->ov, &tmp, NULL, &err); g_assert(err == NULL); arg = qmp_output_get_qobject(data->qov); g_assert(qobject_type(arg) == QTYPE_QINT); g_assert_cmpint(qint_get_int(qobject_to_qint(arg)), ==, 42); - qapi_free_UserDefAnonUnion(tmp); + qapi_free_UserDefAlternate(tmp); } static void test_visitor_out_empty(TestOutputVisitorData *data, @@ -830,8 +830,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-anon", - &out_visitor_data, test_visitor_out_union_anon); + output_visitor_test_add("/visitor/output/alternate", + &out_visitor_data, test_visitor_out_alternate); output_visitor_test_add("/visitor/output/empty", &out_visitor_data, test_visitor_out_empty); output_visitor_test_add("/visitor/output/native_list/int", From 7b1b98c420355ccea98d8bd55c9193ee6b7cef97 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:12 -0600 Subject: [PATCH 15/40] qapi: Document new 'alternate' meta-type The next patch will quit special-casing "'union':'Foo', 'discriminator':{}" and instead use "'alternate':'Foo'". Separating docs from implementation makes it easier to focus on wording without holding up code. In particular, making alternate a separate type makes for a nice type hierarchy: /-------- meta-type ------\ / | \ simple types alternate complex types | | | | built-in enum type(struct) union | \ / / \ numeric string simple flat A later patch will then clean up 'type' vs. 'struct' confusion. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/qapi-code-gen.txt | 57 ++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index 6404a2d734..588b1104af 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -85,11 +85,12 @@ the definition of complex structs that can have mutually recursive types, and allows for indefinite nesting of QMP that satisfies the schema. A type name should not be defined more than once. -There are six top-level expressions recognized by the parser: -'include', 'command', 'type', 'enum', 'union', and 'event'. There are -several built-in types, such as 'int' and 'str'; additionally, the -top-level expressions can define complex types, enumeration types, and -several flavors of union types. The 'command' and 'event' expressions +There are seven top-level expressions recognized by the parser: +'include', 'command', 'type', 'enum', 'union', 'alternate', and +'event'. There are several groups of types: simple types (a number of +built-in types, such as 'int' and 'str'; as well as enumerations), +complex types (structs and two flavors of unions), and alternate types +(a choice between other types). The 'command' and 'event' expressions can refer to existing types by name, or list an anonymous type as a dictionary. Listing a type name inside an array refers to a single-dimension array of that type; multi-dimension arrays are not @@ -261,14 +262,12 @@ open-coding the field to be type 'str'. Usage: { 'union': STRING, 'data': DICT } or: { 'union': STRING, 'data': DICT, 'base': COMPLEX-TYPE-NAME, 'discriminator': ENUM-MEMBER-OF-BASE } -or: { 'union': STRING, 'data': DICT, 'discriminator': {} } Union types are used to let the user choose between several different -variants for an object. There are three flavors: simple (no -discriminator or base), flat (both base and discriminator are -strings), and anonymous (discriminator is an empty dictionary). A -union type is defined using a data dictionary as explained in the -following paragraphs. +variants for an object. There are two flavors: simple (no +discriminator or base), flat (both discriminator and base). A union +type is defined using a data dictionary as explained in the following +paragraphs. A simple union type defines a mapping from automatic discriminator values to data types like in this example: @@ -350,20 +349,36 @@ is identical on the wire to: 'data': { 'one': 'Branch1', 'two': 'Branch2' } } -The final flavor of unions is an anonymous union. While the other two -union types are always passed as a JSON object in the wire format, an -anonymous union instead allows the direct use of different types in -its place. Anonymous unions are declared using an empty dictionary as -their discriminator. The discriminator values never appear on the -wire, they are only used in the generated C code. Anonymous unions -cannot have a base type. +=== Alternate types === - { 'union': 'BlockRef', - 'discriminator': {}, +Usage: { 'alternate': STRING, 'data': DICT } + +An alternate type is one that allows a choice between two or more JSON +data types (string, integer, number, or object, but currently not +array) on the wire. The definition is similar to a simple union type, +where each branch of the union names a QAPI type. For example: + + { 'alternate': 'BlockRef', 'data': { 'definition': 'BlockdevOptions', 'reference': 'str' } } -This example allows using both of the following example objects: +Just like for a simple union, an implicit C enum 'NameKind' is created +to enumerate the branches for the alternate 'Name'. + +Unlike a union, the discriminator string is never passed on the wire +for QMP. Instead, the value's JSON type serves as an implicit +discriminator, which in turn means that an alternate can only express +a choice between types represented differently in JSON. If a branch +is typed as the 'bool' built-in, the alternate accepts true and false; +if it is typed as any of the various numeric built-ins, it accepts a +JSON number; if it is typed as a 'str' built-in or named enum type, it +accepts a JSON string; and if it is typed as a complex type (struct or +union), it accepts a JSON object. Two different complex types, for +instance, aren't permitted, because both are represented as a JSON +object. + +The example alternate declaration above allows using both of the +following example objects: { "file": "my_existing_block_device_id" } { "file": { "driver": "file", From ab916faddd16f0165e9cc2551f90699be8efde53 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:13 -0600 Subject: [PATCH 16/40] qapi: Use 'alternate' to replace anonymous union Previous patches have led up to the point where I create the new meta-type "'alternate':'Foo'". See the previous patches for documentation; I intentionally split as much work into earlier patches to minimize the size of this patch, but a lot of it is churn due to testsuite fallout after updating to the new type. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- qapi/block-core.json | 6 ++-- scripts/qapi-types.py | 26 ++++++++++----- scripts/qapi-visit.py | 17 ++++++---- scripts/qapi.py | 32 ++++++++----------- tests/qapi-schema/alternate-array.err | 2 +- tests/qapi-schema/alternate-array.json | 5 ++- tests/qapi-schema/alternate-base.err | 2 +- tests/qapi-schema/alternate-base.json | 5 ++- tests/qapi-schema/alternate-clash.err | 2 +- tests/qapi-schema/alternate-clash.json | 5 ++- tests/qapi-schema/alternate-conflict-dict.err | 2 +- .../qapi-schema/alternate-conflict-dict.json | 5 ++- .../qapi-schema/alternate-conflict-string.err | 2 +- .../alternate-conflict-string.json | 5 ++- tests/qapi-schema/alternate-good.json | 5 ++- tests/qapi-schema/alternate-good.out | 4 +-- tests/qapi-schema/alternate-nested.err | 2 +- tests/qapi-schema/alternate-nested.json | 10 +++--- tests/qapi-schema/alternate-unknown.err | 2 +- tests/qapi-schema/alternate-unknown.json | 5 ++- .../flat-union-bad-discriminator.err | 2 +- .../flat-union-bad-discriminator.json | 3 +- tests/qapi-schema/qapi-schema-test.json | 3 +- tests/qapi-schema/qapi-schema-test.out | 2 +- 24 files changed, 77 insertions(+), 77 deletions(-) diff --git a/qapi/block-core.json b/qapi/block-core.json index 1c17224c77..3d20e61dee 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -1503,8 +1503,7 @@ # # Since: 2.2 ## -{ 'union': 'Qcow2OverlapChecks', - 'discriminator': {}, +{ 'alternate': 'Qcow2OverlapChecks', 'data': { 'flags': 'Qcow2OverlapCheckFlags', 'mode': 'Qcow2OverlapCheckMode' } } @@ -1795,8 +1794,7 @@ # # Since: 1.7 ## -{ 'union': 'BlockdevRef', - 'discriminator': {}, +{ 'alternate': 'BlockdevRef', 'data': { 'definition': 'BlockdevOptions', 'reference': 'str' } } diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index c9e0201d10..9c8d68cfb4 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -172,7 +172,7 @@ typedef enum %(name)s def generate_alternate_qtypes(expr): - name = expr['union'] + name = expr['alternate'] members = expr['data'] ret = mcgen(''' @@ -182,7 +182,7 @@ const int %(name)s_qtypes[QTYPE_MAX] = { for key in members: qtype = find_alternate_member_qtype(members[key]) - assert qtype, "Invalid anonymous union member" + assert qtype, "Invalid alternate member" ret += mcgen(''' [ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s, @@ -197,9 +197,9 @@ const int %(name)s_qtypes[QTYPE_MAX] = { return ret -def generate_union(expr): +def generate_union(expr, meta): - name = expr['union'] + name = expr[meta] typeinfo = expr['data'] base = expr.get('base') @@ -243,7 +243,7 @@ struct %(name)s ret += mcgen(''' }; ''') - if discriminator == {}: + if meta == 'alternate': ret += mcgen(''' extern const int %(name)s_qtypes[]; ''', @@ -407,8 +407,12 @@ for expr in exprs: ret += generate_enum('%sKind' % expr['union'], expr['data'].keys()) fdef.write(generate_enum_lookup('%sKind' % expr['union'], expr['data'].keys())) - if expr.get('discriminator') == {}: - fdef.write(generate_alternate_qtypes(expr)) + elif expr.has_key('alternate'): + ret += generate_fwd_struct(expr['alternate'], expr['data']) + "\n" + ret += generate_enum('%sKind' % expr['alternate'], expr['data'].keys()) + fdef.write(generate_enum_lookup('%sKind' % expr['alternate'], + expr['data'].keys())) + fdef.write(generate_alternate_qtypes(expr)) else: continue fdecl.write(ret) @@ -438,11 +442,17 @@ for expr in exprs: ret += generate_type_cleanup_decl(expr['type']) fdef.write(generate_type_cleanup(expr['type']) + "\n") elif expr.has_key('union'): - ret += generate_union(expr) + ret += generate_union(expr, 'union') ret += generate_type_cleanup_decl(expr['union'] + "List") fdef.write(generate_type_cleanup(expr['union'] + "List") + "\n") ret += generate_type_cleanup_decl(expr['union']) fdef.write(generate_type_cleanup(expr['union']) + "\n") + elif expr.has_key('alternate'): + ret += generate_union(expr, 'alternate') + ret += generate_type_cleanup_decl(expr['alternate'] + "List") + fdef.write(generate_type_cleanup(expr['alternate'] + "List") + "\n") + ret += generate_type_cleanup_decl(expr['alternate']) + fdef.write(generate_type_cleanup(expr['alternate']) + "\n") elif expr.has_key('enum'): ret += generate_type_cleanup_decl(expr['enum'] + "List") fdef.write(generate_type_cleanup(expr['enum'] + "List") + "\n") diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 6bd2b6bfab..9222671ff8 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -256,7 +256,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e ''', name=name) - # For anon union, always use the default enum type automatically generated + # For alternate, always use the default enum type automatically generated # as "'%sKind' % (name)" disc_type = '%sKind' % (name) @@ -264,7 +264,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e assert (members[key] in builtin_types.keys() or find_struct(members[key]) or find_union(members[key]) - or find_enum(members[key])), "Invalid anonymous union member" + or find_enum(members[key])), "Invalid alternate member" enum_full_value = generate_enum_full_value(disc_type, key) ret += mcgen(''' @@ -300,10 +300,6 @@ def generate_visit_union(expr): base = expr.get('base') discriminator = expr.get('discriminator') - if discriminator == {}: - assert not base - return generate_visit_alternate(name, members) - enum_define = discriminator_find_enum_define(expr) if enum_define: # Use the enum type as discriminator @@ -568,6 +564,15 @@ for expr in exprs: expr['data'].keys()) ret += generate_declaration(expr['union'], expr['data']) fdecl.write(ret) + elif expr.has_key('alternate'): + ret = generate_visit_alternate(expr['alternate'], expr['data']) + ret += generate_visit_list(expr['alternate'], expr['data']) + fdef.write(ret) + + ret = generate_decl_enum('%sKind' % expr['alternate'], + expr['data'].keys()) + ret += generate_declaration(expr['alternate'], expr['data']) + fdecl.write(ret) elif expr.has_key('enum'): ret = generate_visit_list(expr['enum'], expr['data']) ret += generate_visit_enum(expr['enum'], expr['data']) diff --git a/scripts/qapi.py b/scripts/qapi.py index 0b88325abd..05c38c5b00 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -281,7 +281,6 @@ def check_union(expr, expr_info): "along with base" %name) # Two types of unions, determined by discriminator. - assert discriminator != {} # With no discriminator it is a simple union. if discriminator is None: @@ -344,17 +343,14 @@ def check_union(expr, expr_info): values[c_key] = key def check_alternate(expr, expr_info): - name = expr['union'] - base = expr.get('base') - discriminator = expr.get('discriminator') + name = expr['alternate'] members = expr['data'] values = { 'MAX': '(automatic)' } types_seen = {} - assert discriminator == {} - if base is not None: + if expr.get('base') is not None: raise QAPIExprError(expr_info, - "Anonymous union '%s' must not have a base" + "Alternate '%s' must not have a base" % name) # Check every branch @@ -363,23 +359,23 @@ def check_alternate(expr, expr_info): c_key = _generate_enum_string(key) if c_key in values: raise QAPIExprError(expr_info, - "Anonymous union '%s' member '%s' clashes " - "with '%s'" % (name, key, values[c_key])) + "Alternate '%s' member '%s' clashes with '%s'" + % (name, key, values[c_key])) values[c_key] = key # Ensure alternates have no type conflicts. if isinstance(value, list): raise QAPIExprError(expr_info, - "Anonymous union '%s' member '%s' must " + "Alternate '%s' member '%s' must " "not be array type" % (name, key)) qtype = find_alternate_member_qtype(value) if not qtype: raise QAPIExprError(expr_info, - "Anonymous union '%s' member '%s' has " + "Alternate '%s' member '%s' has " "invalid type '%s'" % (name, key, value)) if qtype in types_seen: raise QAPIExprError(expr_info, - "Anonymous union '%s' member '%s' can't " + "Alternate '%s' member '%s' can't " "be distinguished from member '%s'" % (name, key, types_seen[qtype])) types_seen[qtype] = key @@ -412,10 +408,9 @@ def check_exprs(schema): if expr.has_key('enum'): check_enum(expr, info) elif expr.has_key('union'): - if expr.get('discriminator') == {}: - check_alternate(expr, info) - else: - check_union(expr, info) + check_union(expr, info) + elif expr.has_key('alternate'): + check_alternate(expr, info) elif expr.has_key('event'): check_event(expr, info) @@ -447,6 +442,8 @@ def parse_schema(input_file): if expr.has_key('union'): if not discriminator_find_enum_define(expr): add_enum('%sKind' % expr['union']) + elif expr.has_key('alternate'): + add_enum('%sKind' % expr['alternate']) # Final pass - validate that exprs make sense check_exprs(schema) @@ -557,8 +554,7 @@ def find_struct(name): def add_union(definition): global union_types - if definition.get('discriminator') != {}: - union_types.append(definition) + union_types.append(definition) def find_union(name): global union_types diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err index 8d6ccc7325..e2a5fc29bf 100644 --- a/tests/qapi-schema/alternate-array.err +++ b/tests/qapi-schema/alternate-array.err @@ -1 +1 @@ -tests/qapi-schema/alternate-array.json:5: Anonymous union 'MyUnion' member 'two' must not be array type +tests/qapi-schema/alternate-array.json:5: Alternate 'Alt' member 'two' must not be array type diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json index 0da1a641b3..fc0632f128 100644 --- a/tests/qapi-schema/alternate-array.json +++ b/tests/qapi-schema/alternate-array.json @@ -1,8 +1,7 @@ -# we do not allow array branches in anonymous unions +# we do not allow array branches in alternates # TODO: should we support this? { 'type': 'One', 'data': { 'name': 'str' } } -{ 'union': 'MyUnion', - 'discriminator': {}, +{ 'alternate': 'Alt', 'data': { 'one': 'One', 'two': [ 'int' ] } } diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err index a2486b8407..4a2566eed3 100644 --- a/tests/qapi-schema/alternate-base.err +++ b/tests/qapi-schema/alternate-base.err @@ -1 +1 @@ -tests/qapi-schema/alternate-base.json:4: Anonymous union 'MyUnion' must not have a base +tests/qapi-schema/alternate-base.json:4: Alternate 'Alt' must not have a base diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json index dad7f02998..66edc89640 100644 --- a/tests/qapi-schema/alternate-base.json +++ b/tests/qapi-schema/alternate-base.json @@ -1,7 +1,6 @@ -# we reject anonymous union with base type +# we reject alternate with base type { 'type': 'Base', 'data': { 'string': 'str' } } -{ 'union': 'MyUnion', +{ 'alternate': 'Alt', 'base': 'Base', - 'discriminator': {}, 'data': { 'number': 'int' } } diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err index 8949f52b3e..51bea3e272 100644 --- a/tests/qapi-schema/alternate-clash.err +++ b/tests/qapi-schema/alternate-clash.err @@ -1 +1 @@ -tests/qapi-schema/alternate-clash.json:2: Anonymous union 'Union1' member 'ONE' clashes with 'one' +tests/qapi-schema/alternate-clash.json:2: Alternate 'Alt1' member 'ONE' clashes with 'one' diff --git a/tests/qapi-schema/alternate-clash.json b/tests/qapi-schema/alternate-clash.json index fa2d27ed5e..39479353bb 100644 --- a/tests/qapi-schema/alternate-clash.json +++ b/tests/qapi-schema/alternate-clash.json @@ -1,4 +1,3 @@ -# we detect C enum collisions in an anonymous union -{ 'union': 'Union1', - 'discriminator': {}, +# we detect C enum collisions in an alternate +{ 'alternate': 'Alt1', 'data': { 'one': 'str', 'ONE': 'int' } } diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err index 2d4550c565..0f411f4faf 100644 --- a/tests/qapi-schema/alternate-conflict-dict.err +++ b/tests/qapi-schema/alternate-conflict-dict.err @@ -1 +1 @@ -tests/qapi-schema/alternate-conflict-dict.json:6: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one' +tests/qapi-schema/alternate-conflict-dict.json:6: Alternate 'Alt' member 'two' can't be distinguished from member 'one' diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json index ded302edae..fcb3e36cd9 100644 --- a/tests/qapi-schema/alternate-conflict-dict.json +++ b/tests/qapi-schema/alternate-conflict-dict.json @@ -1,9 +1,8 @@ -# we reject anonymous unions with multiple object branches +# we reject alternates with multiple object branches { 'type': 'One', 'data': { 'name': 'str' } } { 'type': 'Two', 'data': { 'value': 'int' } } -{ 'union': 'MyUnion', - 'discriminator': {}, +{ 'alternate': 'Alt', 'data': { 'one': 'One', 'two': 'Two' } } diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err index 271ddcdec8..fc523b0879 100644 --- a/tests/qapi-schema/alternate-conflict-string.err +++ b/tests/qapi-schema/alternate-conflict-string.err @@ -1 +1 @@ -tests/qapi-schema/alternate-conflict-string.json:4: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one' +tests/qapi-schema/alternate-conflict-string.json:4: Alternate 'Alt' member 'two' can't be distinguished from member 'one' diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json index 3834a3ddfe..72f04a820a 100644 --- a/tests/qapi-schema/alternate-conflict-string.json +++ b/tests/qapi-schema/alternate-conflict-string.json @@ -1,7 +1,6 @@ -# we reject anonymous unions with multiple string-like branches +# we reject alternates with multiple string-like branches { 'enum': 'Enum', 'data': [ 'hello', 'world' ] } -{ 'union': 'MyUnion', - 'discriminator': {}, +{ 'alternate': 'Alt', 'data': { 'one': 'str', 'two': 'Enum' } } diff --git a/tests/qapi-schema/alternate-good.json b/tests/qapi-schema/alternate-good.json index 73d0993b39..99d614f138 100644 --- a/tests/qapi-schema/alternate-good.json +++ b/tests/qapi-schema/alternate-good.json @@ -1,10 +1,9 @@ -# Working example of anonymous union +# Working example of alternate { 'type': 'Data', 'data': { '*number': 'int', '*name': 'str' } } { 'enum': 'Enum', 'data': [ 'hello', 'world' ] } -{ 'union': 'MyUnion', - 'discriminator': {}, +{ 'alternate': 'Alt', 'data': { 'value': 'int', 'string': 'Enum', 'struct': 'Data' } } diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out index b5117d1ab2..c3a6b7723c 100644 --- a/tests/qapi-schema/alternate-good.out +++ b/tests/qapi-schema/alternate-good.out @@ -1,6 +1,6 @@ [OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]), OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]), - OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])] + OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])] [{'enum_name': 'Enum', 'enum_values': ['hello', 'world']}, - {'enum_name': 'MyUnionKind', 'enum_values': None}] + {'enum_name': 'AltKind', 'enum_values': None}] [OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])] diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err index 59df96e132..00b05c601e 100644 --- a/tests/qapi-schema/alternate-nested.err +++ b/tests/qapi-schema/alternate-nested.err @@ -1 +1 @@ -tests/qapi-schema/alternate-nested.json:5: Anonymous union 'Union2' member 'nested' has invalid type 'Union1' +tests/qapi-schema/alternate-nested.json:4: Alternate 'Alt2' member 'nested' has invalid type 'Alt1' diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json index ed2b6b7055..c4233b9f33 100644 --- a/tests/qapi-schema/alternate-nested.json +++ b/tests/qapi-schema/alternate-nested.json @@ -1,7 +1,5 @@ -# we reject a nested anonymous union branch -{ 'union': 'Union1', - 'discriminator': {}, +# we reject a nested alternate branch +{ 'alternate': 'Alt1', 'data': { 'name': 'str', 'value': 'int' } } -{ 'union': 'Union2', - 'discriminator': {}, - 'data': { 'nested': 'Union1' } } +{ 'alternate': 'Alt2', + 'data': { 'nested': 'Alt1' } } diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err index bf8e9aef64..7af1b4c569 100644 --- a/tests/qapi-schema/alternate-unknown.err +++ b/tests/qapi-schema/alternate-unknown.err @@ -1 +1 @@ -tests/qapi-schema/alternate-unknown.json:2: Anonymous union 'Union' member 'unknown' has invalid type 'MissingType' +tests/qapi-schema/alternate-unknown.json:2: Alternate 'Alt' member 'unknown' has invalid type 'MissingType' diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json index 0c305c282c..ad5c103028 100644 --- a/tests/qapi-schema/alternate-unknown.json +++ b/tests/qapi-schema/alternate-unknown.json @@ -1,4 +1,3 @@ -# we reject an anonymous union with unknown type in branch -{ 'union': 'Union', - 'discriminator': {}, +# we reject an alternate with unknown type in branch +{ 'alternate': 'Alt', 'data': { 'unknown': 'MissingType' } } diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err index 1661c52b03..507e2bab4a 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.err +++ b/tests/qapi-schema/flat-union-bad-discriminator.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-bad-discriminator.json:10: Flat union 'TestUnion' discriminator must be a string +tests/qapi-schema/flat-union-bad-discriminator.json:11: Flat union 'TestUnion' discriminator must be a string diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json index 3ce43e8356..982f072555 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.json +++ b/tests/qapi-schema/flat-union-bad-discriminator.json @@ -1,4 +1,5 @@ # we require the discriminator to be a string naming a base-type member +# this tests the old syntax for anonymous unions before we added alternates { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } { 'type': 'TestBase', @@ -9,6 +10,6 @@ 'data': { 'integer': 'int' } } { 'union': 'TestUnion', 'base': 'TestBase', - 'discriminator': [], + 'discriminator': {}, 'data': { 'kind1': 'TestTypeA', 'kind2': 'TestTypeB' } } diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index e1d35e1248..dec8a7c108 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -53,8 +53,7 @@ 'discriminator': 'enum1', 'data': { 'value1' : 'UserDefC', 'value2' : 'UserDefB', 'value3' : 'UserDefA' } } -{ 'union': 'UserDefAlternate', - 'discriminator': {}, +{ 'alternate': 'UserDefAlternate', 'data': { 'uda': 'UserDefA', 's': 'str', 'i': 'int' } } # for testing native lists diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index b55ab8de10..313ecf3ad3 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -10,7 +10,7 @@ OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]), OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]), - OrderedDict([('union', 'UserDefAlternate'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), + OrderedDict([('alternate', 'UserDefAlternate'), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('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'])]))]), OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]), OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]), From 9050c65b71ac1d197330e6db221f63189e21bad5 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:14 -0600 Subject: [PATCH 17/40] qapi: Add some expr tests Demonstrate that the qapi generator doesn't deal well with expressions that aren't up to par. Later patches will improve the expected results as the generator is made stricter. Only a few of the the added tests actually behave sanely at rejecting obvious problems or demonstrating success. Note that in some cases, we reject bad QAPI merely because our pseudo-JSON parser does not yet know how to parse numbers. This series does not address that, but when a later series adds support for numeric defaults of integer fields, the testsuite will ensure that we don't lose the error (and hopefully that the error message quality is improved). Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/Makefile | 8 +++++--- tests/qapi-schema/bad-base.err | 0 tests/qapi-schema/bad-base.exit | 1 + tests/qapi-schema/bad-base.json | 3 +++ tests/qapi-schema/bad-base.out | 4 ++++ tests/qapi-schema/bad-ident.err | 0 tests/qapi-schema/bad-ident.exit | 1 + tests/qapi-schema/bad-ident.json | 2 ++ tests/qapi-schema/bad-ident.out | 3 +++ tests/qapi-schema/bad-type-bool.err | 1 + tests/qapi-schema/bad-type-bool.exit | 1 + tests/qapi-schema/bad-type-bool.json | 3 +++ tests/qapi-schema/bad-type-bool.out | 0 tests/qapi-schema/bad-type-dict.err | 0 tests/qapi-schema/bad-type-dict.exit | 1 + tests/qapi-schema/bad-type-dict.json | 2 ++ tests/qapi-schema/bad-type-dict.out | 3 +++ tests/qapi-schema/bad-type-int.err | 1 + tests/qapi-schema/bad-type-int.exit | 1 + tests/qapi-schema/bad-type-int.json | 3 +++ tests/qapi-schema/bad-type-int.out | 0 tests/qapi-schema/double-data.err | 1 + tests/qapi-schema/double-data.exit | 1 + tests/qapi-schema/double-data.json | 2 ++ tests/qapi-schema/double-data.out | 0 tests/qapi-schema/double-type.err | 0 tests/qapi-schema/double-type.exit | 1 + tests/qapi-schema/double-type.json | 2 ++ tests/qapi-schema/double-type.out | 3 +++ tests/qapi-schema/event-case.err | 0 tests/qapi-schema/event-case.exit | 1 + tests/qapi-schema/event-case.json | 3 +++ tests/qapi-schema/event-case.out | 3 +++ tests/qapi-schema/ident-with-escape.err | 0 tests/qapi-schema/ident-with-escape.exit | 1 + tests/qapi-schema/ident-with-escape.json | 4 ++++ tests/qapi-schema/ident-with-escape.out | 3 +++ tests/qapi-schema/missing-type.err | 0 tests/qapi-schema/missing-type.exit | 1 + tests/qapi-schema/missing-type.json | 2 ++ tests/qapi-schema/missing-type.out | 3 +++ tests/qapi-schema/unknown-expr-key.err | 0 tests/qapi-schema/unknown-expr-key.exit | 1 + tests/qapi-schema/unknown-expr-key.json | 2 ++ tests/qapi-schema/unknown-expr-key.out | 3 +++ 45 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 tests/qapi-schema/bad-base.err create mode 100644 tests/qapi-schema/bad-base.exit create mode 100644 tests/qapi-schema/bad-base.json create mode 100644 tests/qapi-schema/bad-base.out create mode 100644 tests/qapi-schema/bad-ident.err create mode 100644 tests/qapi-schema/bad-ident.exit create mode 100644 tests/qapi-schema/bad-ident.json create mode 100644 tests/qapi-schema/bad-ident.out create mode 100644 tests/qapi-schema/bad-type-bool.err create mode 100644 tests/qapi-schema/bad-type-bool.exit create mode 100644 tests/qapi-schema/bad-type-bool.json create mode 100644 tests/qapi-schema/bad-type-bool.out create mode 100644 tests/qapi-schema/bad-type-dict.err create mode 100644 tests/qapi-schema/bad-type-dict.exit create mode 100644 tests/qapi-schema/bad-type-dict.json create mode 100644 tests/qapi-schema/bad-type-dict.out create mode 100644 tests/qapi-schema/bad-type-int.err create mode 100644 tests/qapi-schema/bad-type-int.exit create mode 100644 tests/qapi-schema/bad-type-int.json create mode 100644 tests/qapi-schema/bad-type-int.out create mode 100644 tests/qapi-schema/double-data.err create mode 100644 tests/qapi-schema/double-data.exit create mode 100644 tests/qapi-schema/double-data.json create mode 100644 tests/qapi-schema/double-data.out create mode 100644 tests/qapi-schema/double-type.err create mode 100644 tests/qapi-schema/double-type.exit create mode 100644 tests/qapi-schema/double-type.json create mode 100644 tests/qapi-schema/double-type.out create mode 100644 tests/qapi-schema/event-case.err create mode 100644 tests/qapi-schema/event-case.exit create mode 100644 tests/qapi-schema/event-case.json create mode 100644 tests/qapi-schema/event-case.out create mode 100644 tests/qapi-schema/ident-with-escape.err create mode 100644 tests/qapi-schema/ident-with-escape.exit create mode 100644 tests/qapi-schema/ident-with-escape.json create mode 100644 tests/qapi-schema/ident-with-escape.out create mode 100644 tests/qapi-schema/missing-type.err create mode 100644 tests/qapi-schema/missing-type.exit create mode 100644 tests/qapi-schema/missing-type.json create mode 100644 tests/qapi-schema/missing-type.out create mode 100644 tests/qapi-schema/unknown-expr-key.err create mode 100644 tests/qapi-schema/unknown-expr-key.exit create mode 100644 tests/qapi-schema/unknown-expr-key.json create mode 100644 tests/qapi-schema/unknown-expr-key.out diff --git a/tests/Makefile b/tests/Makefile index 3978c3d302..835ec9cb4e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -210,8 +210,10 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ comments.json empty.json enum-empty.json enum-missing-data.json \ enum-wrong-data.json enum-int-member.json enum-dict-member.json \ enum-clash-member.json enum-max-member.json enum-union-clash.json \ - enum-bad-name.json \ - funny-char.json indented-expr.json \ + enum-bad-name.json funny-char.json indented-expr.json \ + missing-type.json bad-ident.json ident-with-escape.json \ + double-type.json bad-base.json bad-type-bool.json bad-type-int.json \ + bad-type-dict.json double-data.json unknown-expr-key.json \ missing-colon.json missing-comma-list.json \ missing-comma-object.json non-objects.json \ qapi-schema-test.json quoted-structural-chars.json \ @@ -232,7 +234,7 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ include-simple.json include-relpath.json include-format-err.json \ include-non-file.json include-no-file.json include-before-err.json \ include-nested-err.json include-self-cycle.json include-cycle.json \ - include-repetition.json event-nest-struct.json) + include-repetition.json event-nest-struct.json event-case.json) GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \ tests/test-qmp-commands.h tests/test-qapi-event.h diff --git a/tests/qapi-schema/bad-base.err b/tests/qapi-schema/bad-base.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/bad-base.exit b/tests/qapi-schema/bad-base.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/bad-base.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/bad-base.json b/tests/qapi-schema/bad-base.json new file mode 100644 index 0000000000..de964a0e80 --- /dev/null +++ b/tests/qapi-schema/bad-base.json @@ -0,0 +1,3 @@ +# FIXME: we should reject a base that is not a struct +{ 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } } +{ 'type': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } } diff --git a/tests/qapi-schema/bad-base.out b/tests/qapi-schema/bad-base.out new file mode 100644 index 0000000000..91d12fcf6d --- /dev/null +++ b/tests/qapi-schema/bad-base.out @@ -0,0 +1,4 @@ +[OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]), + OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])] +[{'enum_name': 'UnionKind', 'enum_values': None}] +[OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])] diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/bad-ident.exit b/tests/qapi-schema/bad-ident.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/bad-ident.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/bad-ident.json b/tests/qapi-schema/bad-ident.json new file mode 100644 index 0000000000..f139110e89 --- /dev/null +++ b/tests/qapi-schema/bad-ident.json @@ -0,0 +1,2 @@ +# FIXME: we should reject creating a type name with bad name +{ 'type': '*oops', 'data': { 'i': 'int' } } diff --git a/tests/qapi-schema/bad-ident.out b/tests/qapi-schema/bad-ident.out new file mode 100644 index 0000000000..165e34645d --- /dev/null +++ b/tests/qapi-schema/bad-ident.out @@ -0,0 +1,3 @@ +[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])] +[] +[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])] diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err new file mode 100644 index 0000000000..badb7c2847 --- /dev/null +++ b/tests/qapi-schema/bad-type-bool.err @@ -0,0 +1 @@ +tests/qapi-schema/bad-type-bool.json:3:11: Stray "t" diff --git a/tests/qapi-schema/bad-type-bool.exit b/tests/qapi-schema/bad-type-bool.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/bad-type-bool.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/bad-type-bool.json b/tests/qapi-schema/bad-type-bool.json new file mode 100644 index 0000000000..22d6369ef8 --- /dev/null +++ b/tests/qapi-schema/bad-type-bool.json @@ -0,0 +1,3 @@ +# we reject an expression with a metatype that is not a string +# FIXME: once the parser understands bool inputs, improve the error message +{ 'type': true, 'data': { } } diff --git a/tests/qapi-schema/bad-type-bool.out b/tests/qapi-schema/bad-type-bool.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/bad-type-dict.err b/tests/qapi-schema/bad-type-dict.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/bad-type-dict.exit b/tests/qapi-schema/bad-type-dict.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/bad-type-dict.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/bad-type-dict.json b/tests/qapi-schema/bad-type-dict.json new file mode 100644 index 0000000000..3c392a7cbd --- /dev/null +++ b/tests/qapi-schema/bad-type-dict.json @@ -0,0 +1,2 @@ +# FIXME: we should reject an expression with a metatype that is not a string +{ 'command': { } } diff --git a/tests/qapi-schema/bad-type-dict.out b/tests/qapi-schema/bad-type-dict.out new file mode 100644 index 0000000000..c62f1edb76 --- /dev/null +++ b/tests/qapi-schema/bad-type-dict.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', OrderedDict())])] +[] +[] diff --git a/tests/qapi-schema/bad-type-int.err b/tests/qapi-schema/bad-type-int.err new file mode 100644 index 0000000000..9808550007 --- /dev/null +++ b/tests/qapi-schema/bad-type-int.err @@ -0,0 +1 @@ +tests/qapi-schema/bad-type-int.json:3:11: Stray "1" diff --git a/tests/qapi-schema/bad-type-int.exit b/tests/qapi-schema/bad-type-int.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/bad-type-int.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/bad-type-int.json b/tests/qapi-schema/bad-type-int.json new file mode 100644 index 0000000000..398879df95 --- /dev/null +++ b/tests/qapi-schema/bad-type-int.json @@ -0,0 +1,3 @@ +# we reject an expression with a metatype that is not a string +# FIXME: once the parser understands integer inputs, improve the error message +{ 'type': 1, 'data': { } } diff --git a/tests/qapi-schema/bad-type-int.out b/tests/qapi-schema/bad-type-int.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/double-data.err b/tests/qapi-schema/double-data.err new file mode 100644 index 0000000000..6f1a67b907 --- /dev/null +++ b/tests/qapi-schema/double-data.err @@ -0,0 +1 @@ +tests/qapi-schema/double-data.json:2:39: Duplicate key "data" diff --git a/tests/qapi-schema/double-data.exit b/tests/qapi-schema/double-data.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/double-data.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/double-data.json b/tests/qapi-schema/double-data.json new file mode 100644 index 0000000000..a94b7dfe22 --- /dev/null +++ b/tests/qapi-schema/double-data.json @@ -0,0 +1,2 @@ +# we reject an expression with duplicate top-level keys +{ 'type': 'bar', 'data': { }, 'data': { 'string': 'str'} } diff --git a/tests/qapi-schema/double-data.out b/tests/qapi-schema/double-data.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/double-type.exit b/tests/qapi-schema/double-type.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/double-type.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/double-type.json b/tests/qapi-schema/double-type.json new file mode 100644 index 0000000000..6ca96b97c1 --- /dev/null +++ b/tests/qapi-schema/double-type.json @@ -0,0 +1,2 @@ +# FIXME: we should reject an expression with ambiguous metatype +{ 'command': 'foo', 'type': 'bar', 'data': { } } diff --git a/tests/qapi-schema/double-type.out b/tests/qapi-schema/double-type.out new file mode 100644 index 0000000000..3e244f5780 --- /dev/null +++ b/tests/qapi-schema/double-type.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])] +[] +[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])] diff --git a/tests/qapi-schema/event-case.err b/tests/qapi-schema/event-case.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/event-case.exit b/tests/qapi-schema/event-case.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/event-case.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/event-case.json b/tests/qapi-schema/event-case.json new file mode 100644 index 0000000000..3a92d8b610 --- /dev/null +++ b/tests/qapi-schema/event-case.json @@ -0,0 +1,3 @@ +# TODO: might be nice to enforce naming conventions; but until then this works +# even though events should usually be ALL_CAPS +{ 'event': 'oops' } diff --git a/tests/qapi-schema/event-case.out b/tests/qapi-schema/event-case.out new file mode 100644 index 0000000000..3764bc781d --- /dev/null +++ b/tests/qapi-schema/event-case.out @@ -0,0 +1,3 @@ +[OrderedDict([('event', 'oops')])] +[] +[] diff --git a/tests/qapi-schema/ident-with-escape.err b/tests/qapi-schema/ident-with-escape.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/ident-with-escape.exit b/tests/qapi-schema/ident-with-escape.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/ident-with-escape.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/ident-with-escape.json b/tests/qapi-schema/ident-with-escape.json new file mode 100644 index 0000000000..cfb205052a --- /dev/null +++ b/tests/qapi-schema/ident-with-escape.json @@ -0,0 +1,4 @@ +# FIXME: we should allow escape sequences in strings, if they map back to ASCII +# { 'command': 'fooA', 'data': { 'bar1': 'str' } } +{ 'c\u006fmmand': '\u0066\u006f\u006FA', + 'd\u0061ta': { '\u0062\u0061\u00721': '\u0073\u0074\u0072' } } diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out new file mode 100644 index 0000000000..a44623fa4a --- /dev/null +++ b/tests/qapi-schema/ident-with-escape.out @@ -0,0 +1,3 @@ +[OrderedDict([('cu006fmmand', 'u0066u006fu006FA'), ('du0061ta', OrderedDict([('u0062u0061u00721', 'u0073u0074u0072')]))])] +[] +[] diff --git a/tests/qapi-schema/missing-type.err b/tests/qapi-schema/missing-type.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/missing-type.exit b/tests/qapi-schema/missing-type.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/missing-type.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/missing-type.json b/tests/qapi-schema/missing-type.json new file mode 100644 index 0000000000..1696f5c139 --- /dev/null +++ b/tests/qapi-schema/missing-type.json @@ -0,0 +1,2 @@ +# FIXME: we should reject an expression with missing metatype +{ 'data': { } } diff --git a/tests/qapi-schema/missing-type.out b/tests/qapi-schema/missing-type.out new file mode 100644 index 0000000000..67fd4fa676 --- /dev/null +++ b/tests/qapi-schema/missing-type.out @@ -0,0 +1,3 @@ +[OrderedDict([('data', OrderedDict())])] +[] +[] diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/unknown-expr-key.exit b/tests/qapi-schema/unknown-expr-key.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/unknown-expr-key.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json new file mode 100644 index 0000000000..1e9282d8e1 --- /dev/null +++ b/tests/qapi-schema/unknown-expr-key.json @@ -0,0 +1,2 @@ +# FIXME: we should reject an expression with unknown top-level keys +{ 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } } diff --git a/tests/qapi-schema/unknown-expr-key.out b/tests/qapi-schema/unknown-expr-key.out new file mode 100644 index 0000000000..c93f020aeb --- /dev/null +++ b/tests/qapi-schema/unknown-expr-key.out @@ -0,0 +1,3 @@ +[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])] +[] +[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])] From 0545f6b8874c28d97369f2c83e5077e0461d4f12 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:15 -0600 Subject: [PATCH 18/40] qapi: Better error messages for bad expressions The previous commit demonstrated that the generator overlooked some fairly basic broken expressions: - missing metataype - metatype key has a non-string value - unknown key in relation to the metatype - conflicting metatype (this patch treats the second metatype as an unknown key of the first key visited, which is not necessarily the first key the user typed) Add check_keys to cover these situations, and update testcases to match. A couple other tests (enum-missing-data, indented-expr) had to change since the validation added here occurs so early. Conversely, changes to ident-with-escape results show that we still have problems where our handling of escape sequences differs from true JSON, which will matter down the road if we allow arbitrary default string values for optional parameters (but for now is not too bad, as we currently can avoid unicode escaping as we don't need to represent anything beyond C identifier material). While valid .json files won't trigger any of these cases, we might as well be nicer to developers that make a typo while trying to add new QAPI code. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 44 ++++++++++++++++++++---- tests/qapi-schema/alternate-base.err | 2 +- tests/qapi-schema/bad-type-dict.err | 1 + tests/qapi-schema/bad-type-dict.exit | 2 +- tests/qapi-schema/bad-type-dict.json | 2 +- tests/qapi-schema/bad-type-dict.out | 3 -- tests/qapi-schema/double-type.err | 1 + tests/qapi-schema/double-type.exit | 2 +- tests/qapi-schema/double-type.json | 2 +- tests/qapi-schema/double-type.out | 3 -- tests/qapi-schema/enum-missing-data.err | 2 +- tests/qapi-schema/ident-with-escape.err | 1 + tests/qapi-schema/ident-with-escape.exit | 2 +- tests/qapi-schema/ident-with-escape.out | 3 -- tests/qapi-schema/indented-expr.json | 4 +-- tests/qapi-schema/indented-expr.out | 2 +- tests/qapi-schema/missing-type.err | 1 + tests/qapi-schema/missing-type.exit | 2 +- tests/qapi-schema/missing-type.json | 2 +- tests/qapi-schema/missing-type.out | 3 -- tests/qapi-schema/unknown-expr-key.err | 1 + tests/qapi-schema/unknown-expr-key.exit | 2 +- tests/qapi-schema/unknown-expr-key.json | 2 +- tests/qapi-schema/unknown-expr-key.out | 3 -- 24 files changed, 56 insertions(+), 36 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 05c38c5b00..868f08b593 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -348,11 +348,6 @@ def check_alternate(expr, expr_info): values = { 'MAX': '(automatic)' } types_seen = {} - if expr.get('base') is not None: - raise QAPIExprError(expr_info, - "Alternate '%s' must not have a base" - % name) - # Check every branch for (key, value) in members.items(): # Check for conflicts in the generated enum @@ -414,6 +409,26 @@ def check_exprs(schema): elif expr.has_key('event'): check_event(expr, info) +def check_keys(expr_elem, meta, required, optional=[]): + expr = expr_elem['expr'] + info = expr_elem['info'] + name = expr[meta] + if not isinstance(name, str): + raise QAPIExprError(info, + "'%s' key must have a string value" % meta) + required = required + [ meta ] + for (key, value) in expr.items(): + if not key in required and not key in optional: + raise QAPIExprError(info, + "Unknown key '%s' in %s '%s'" + % (key, meta, name)) + for key in required: + if not expr.has_key(key): + raise QAPIExprError(info, + "Key '%s' is missing from %s '%s'" + % (key, meta, name)) + + def parse_schema(input_file): # First pass: read entire file into memory try: @@ -425,15 +440,30 @@ def parse_schema(input_file): exprs = [] try: - # Next pass: learn the types. + # Next pass: learn the types and check for valid expression keys. At + # this point, top-level 'include' has already been flattened. for expr_elem in schema.exprs: expr = expr_elem['expr'] if expr.has_key('enum'): - add_enum(expr['enum'], expr.get('data')) + check_keys(expr_elem, 'enum', ['data']) + add_enum(expr['enum'], expr['data']) elif expr.has_key('union'): + check_keys(expr_elem, 'union', ['data'], + ['base', 'discriminator']) add_union(expr) + elif expr.has_key('alternate'): + check_keys(expr_elem, 'alternate', ['data']) elif expr.has_key('type'): + check_keys(expr_elem, 'type', ['data'], ['base']) add_struct(expr) + elif expr.has_key('command'): + check_keys(expr_elem, 'command', [], + ['data', 'returns', 'gen', 'success-response']) + elif expr.has_key('event'): + check_keys(expr_elem, 'event', [], ['data']) + else: + raise QAPIExprError(expr_elem['info'], + "Expression is missing metatype") exprs.append(expr) # Try again for hidden UnionKind enum diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err index 4a2566eed3..30d8a34373 100644 --- a/tests/qapi-schema/alternate-base.err +++ b/tests/qapi-schema/alternate-base.err @@ -1 +1 @@ -tests/qapi-schema/alternate-base.json:4: Alternate 'Alt' must not have a base +tests/qapi-schema/alternate-base.json:4: Unknown key 'base' in alternate 'Alt' diff --git a/tests/qapi-schema/bad-type-dict.err b/tests/qapi-schema/bad-type-dict.err index e69de29bb2..0b2a2aeac4 100644 --- a/tests/qapi-schema/bad-type-dict.err +++ b/tests/qapi-schema/bad-type-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/bad-type-dict.json:2: 'command' key must have a string value diff --git a/tests/qapi-schema/bad-type-dict.exit b/tests/qapi-schema/bad-type-dict.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/bad-type-dict.exit +++ b/tests/qapi-schema/bad-type-dict.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/bad-type-dict.json b/tests/qapi-schema/bad-type-dict.json index 3c392a7cbd..2a91b241f8 100644 --- a/tests/qapi-schema/bad-type-dict.json +++ b/tests/qapi-schema/bad-type-dict.json @@ -1,2 +1,2 @@ -# FIXME: we should reject an expression with a metatype that is not a string +# we reject an expression with a metatype that is not a string { 'command': { } } diff --git a/tests/qapi-schema/bad-type-dict.out b/tests/qapi-schema/bad-type-dict.out index c62f1edb76..e69de29bb2 100644 --- a/tests/qapi-schema/bad-type-dict.out +++ b/tests/qapi-schema/bad-type-dict.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', OrderedDict())])] -[] -[] diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err index e69de29bb2..ceb6e46cfe 100644 --- a/tests/qapi-schema/double-type.err +++ b/tests/qapi-schema/double-type.err @@ -0,0 +1 @@ +tests/qapi-schema/double-type.json:2: Unknown key 'command' in type 'bar' diff --git a/tests/qapi-schema/double-type.exit b/tests/qapi-schema/double-type.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/double-type.exit +++ b/tests/qapi-schema/double-type.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/double-type.json b/tests/qapi-schema/double-type.json index 6ca96b97c1..471623a2e5 100644 --- a/tests/qapi-schema/double-type.json +++ b/tests/qapi-schema/double-type.json @@ -1,2 +1,2 @@ -# FIXME: we should reject an expression with ambiguous metatype +# we reject an expression with ambiguous metatype { 'command': 'foo', 'type': 'bar', 'data': { } } diff --git a/tests/qapi-schema/double-type.out b/tests/qapi-schema/double-type.out index 3e244f5780..e69de29bb2 100644 --- a/tests/qapi-schema/double-type.out +++ b/tests/qapi-schema/double-type.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])] -[] -[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])] diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err index b8ccae071b..ba4873ae69 100644 --- a/tests/qapi-schema/enum-missing-data.err +++ b/tests/qapi-schema/enum-missing-data.err @@ -1 +1 @@ -tests/qapi-schema/enum-missing-data.json:2: Enum 'MyEnum' requires an array for 'data' +tests/qapi-schema/enum-missing-data.json:2: Key 'data' is missing from enum 'MyEnum' diff --git a/tests/qapi-schema/ident-with-escape.err b/tests/qapi-schema/ident-with-escape.err index e69de29bb2..f7d1c55327 100644 --- a/tests/qapi-schema/ident-with-escape.err +++ b/tests/qapi-schema/ident-with-escape.err @@ -0,0 +1 @@ +tests/qapi-schema/ident-with-escape.json:3: Expression is missing metatype diff --git a/tests/qapi-schema/ident-with-escape.exit b/tests/qapi-schema/ident-with-escape.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/ident-with-escape.exit +++ b/tests/qapi-schema/ident-with-escape.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out index a44623fa4a..e69de29bb2 100644 --- a/tests/qapi-schema/ident-with-escape.out +++ b/tests/qapi-schema/ident-with-escape.out @@ -1,3 +0,0 @@ -[OrderedDict([('cu006fmmand', 'u0066u006fu006FA'), ('du0061ta', OrderedDict([('u0062u0061u00721', 'u0073u0074u0072')]))])] -[] -[] diff --git a/tests/qapi-schema/indented-expr.json b/tests/qapi-schema/indented-expr.json index d80af60564..7115d3131e 100644 --- a/tests/qapi-schema/indented-expr.json +++ b/tests/qapi-schema/indented-expr.json @@ -1,2 +1,2 @@ -{ 'id' : 'eins' } - { 'id' : 'zwei' } +{ 'command' : 'eins' } + { 'command' : 'zwei' } diff --git a/tests/qapi-schema/indented-expr.out b/tests/qapi-schema/indented-expr.out index 98af89aa1d..b5ce9151bc 100644 --- a/tests/qapi-schema/indented-expr.out +++ b/tests/qapi-schema/indented-expr.out @@ -1,3 +1,3 @@ -[OrderedDict([('id', 'eins')]), OrderedDict([('id', 'zwei')])] +[OrderedDict([('command', 'eins')]), OrderedDict([('command', 'zwei')])] [] [] diff --git a/tests/qapi-schema/missing-type.err b/tests/qapi-schema/missing-type.err index e69de29bb2..b3e7b14e42 100644 --- a/tests/qapi-schema/missing-type.err +++ b/tests/qapi-schema/missing-type.err @@ -0,0 +1 @@ +tests/qapi-schema/missing-type.json:2: Expression is missing metatype diff --git a/tests/qapi-schema/missing-type.exit b/tests/qapi-schema/missing-type.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/missing-type.exit +++ b/tests/qapi-schema/missing-type.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/missing-type.json b/tests/qapi-schema/missing-type.json index 1696f5c139..ff5349d3fe 100644 --- a/tests/qapi-schema/missing-type.json +++ b/tests/qapi-schema/missing-type.json @@ -1,2 +1,2 @@ -# FIXME: we should reject an expression with missing metatype +# we reject an expression with missing metatype { 'data': { } } diff --git a/tests/qapi-schema/missing-type.out b/tests/qapi-schema/missing-type.out index 67fd4fa676..e69de29bb2 100644 --- a/tests/qapi-schema/missing-type.out +++ b/tests/qapi-schema/missing-type.out @@ -1,3 +0,0 @@ -[OrderedDict([('data', OrderedDict())])] -[] -[] diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err index e69de29bb2..0a35bfdbd6 100644 --- a/tests/qapi-schema/unknown-expr-key.err +++ b/tests/qapi-schema/unknown-expr-key.err @@ -0,0 +1 @@ +tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in type 'bar' diff --git a/tests/qapi-schema/unknown-expr-key.exit b/tests/qapi-schema/unknown-expr-key.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/unknown-expr-key.exit +++ b/tests/qapi-schema/unknown-expr-key.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json index 1e9282d8e1..ba7bdf3ed6 100644 --- a/tests/qapi-schema/unknown-expr-key.json +++ b/tests/qapi-schema/unknown-expr-key.json @@ -1,2 +1,2 @@ -# FIXME: we should reject an expression with unknown top-level keys +# we reject an expression with unknown top-level keys { 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } } diff --git a/tests/qapi-schema/unknown-expr-key.out b/tests/qapi-schema/unknown-expr-key.out index c93f020aeb..e69de29bb2 100644 --- a/tests/qapi-schema/unknown-expr-key.out +++ b/tests/qapi-schema/unknown-expr-key.out @@ -1,3 +0,0 @@ -[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])] -[] -[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])] From cfdd5bcad515a8371af59dba9625e31a6f6f733e Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:16 -0600 Subject: [PATCH 19/40] qapi: Add tests of redefined expressions Demonstrate that the qapi generator doesn't deal very well with redefined expressions. At the parse level, they are silently accepted; and while the testsuite just stops at parsing, I've further tested that many of them cause generator crashes or invalid C code if they were appended to qapi-schema-test.json. A later patch will tighten things up and adjust the testsuite to match. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/Makefile | 2 ++ tests/qapi-schema/command-int.err | 0 tests/qapi-schema/command-int.exit | 1 + tests/qapi-schema/command-int.json | 3 +++ tests/qapi-schema/command-int.out | 3 +++ tests/qapi-schema/event-max.err | 0 tests/qapi-schema/event-max.exit | 1 + tests/qapi-schema/event-max.json | 2 ++ tests/qapi-schema/event-max.out | 3 +++ tests/qapi-schema/redefined-builtin.err | 0 tests/qapi-schema/redefined-builtin.exit | 1 + tests/qapi-schema/redefined-builtin.json | 2 ++ tests/qapi-schema/redefined-builtin.out | 3 +++ tests/qapi-schema/redefined-command.err | 0 tests/qapi-schema/redefined-command.exit | 1 + tests/qapi-schema/redefined-command.json | 3 +++ tests/qapi-schema/redefined-command.out | 4 ++++ tests/qapi-schema/redefined-event.err | 0 tests/qapi-schema/redefined-event.exit | 1 + tests/qapi-schema/redefined-event.json | 3 +++ tests/qapi-schema/redefined-event.out | 4 ++++ tests/qapi-schema/redefined-type.err | 0 tests/qapi-schema/redefined-type.exit | 1 + tests/qapi-schema/redefined-type.json | 3 +++ tests/qapi-schema/redefined-type.out | 4 ++++ 25 files changed, 45 insertions(+) create mode 100644 tests/qapi-schema/command-int.err create mode 100644 tests/qapi-schema/command-int.exit create mode 100644 tests/qapi-schema/command-int.json create mode 100644 tests/qapi-schema/command-int.out create mode 100644 tests/qapi-schema/event-max.err create mode 100644 tests/qapi-schema/event-max.exit create mode 100644 tests/qapi-schema/event-max.json create mode 100644 tests/qapi-schema/event-max.out create mode 100644 tests/qapi-schema/redefined-builtin.err create mode 100644 tests/qapi-schema/redefined-builtin.exit create mode 100644 tests/qapi-schema/redefined-builtin.json create mode 100644 tests/qapi-schema/redefined-builtin.out create mode 100644 tests/qapi-schema/redefined-command.err create mode 100644 tests/qapi-schema/redefined-command.exit create mode 100644 tests/qapi-schema/redefined-command.json create mode 100644 tests/qapi-schema/redefined-command.out create mode 100644 tests/qapi-schema/redefined-event.err create mode 100644 tests/qapi-schema/redefined-event.exit create mode 100644 tests/qapi-schema/redefined-event.json create mode 100644 tests/qapi-schema/redefined-event.out create mode 100644 tests/qapi-schema/redefined-type.err create mode 100644 tests/qapi-schema/redefined-type.exit create mode 100644 tests/qapi-schema/redefined-type.json create mode 100644 tests/qapi-schema/redefined-type.out diff --git a/tests/Makefile b/tests/Makefile index 835ec9cb4e..c27940c417 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -214,6 +214,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ missing-type.json bad-ident.json ident-with-escape.json \ double-type.json bad-base.json bad-type-bool.json bad-type-int.json \ bad-type-dict.json double-data.json unknown-expr-key.json \ + redefined-type.json redefined-command.json redefined-builtin.json \ + redefined-event.json command-int.json event-max.json \ missing-colon.json missing-comma-list.json \ missing-comma-object.json non-objects.json \ qapi-schema-test.json quoted-structural-chars.json \ diff --git a/tests/qapi-schema/command-int.err b/tests/qapi-schema/command-int.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/command-int.exit b/tests/qapi-schema/command-int.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/command-int.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/command-int.json b/tests/qapi-schema/command-int.json new file mode 100644 index 0000000000..fcbb643c44 --- /dev/null +++ b/tests/qapi-schema/command-int.json @@ -0,0 +1,3 @@ +# FIXME: we should reject collisions between commands and types +{ 'command': 'int', 'data': { 'character': 'str' }, + 'returns': { 'value': 'int' } } diff --git a/tests/qapi-schema/command-int.out b/tests/qapi-schema/command-int.out new file mode 100644 index 0000000000..d8e1854a04 --- /dev/null +++ b/tests/qapi-schema/command-int.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'int'), ('data', OrderedDict([('character', 'str')])), ('returns', OrderedDict([('value', 'int')]))])] +[] +[] diff --git a/tests/qapi-schema/event-max.err b/tests/qapi-schema/event-max.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/event-max.exit b/tests/qapi-schema/event-max.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/event-max.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/event-max.json b/tests/qapi-schema/event-max.json new file mode 100644 index 0000000000..997c61c511 --- /dev/null +++ b/tests/qapi-schema/event-max.json @@ -0,0 +1,2 @@ +# FIXME: an event named 'MAX' would conflict with implicit C enum +{ 'event': 'MAX' } diff --git a/tests/qapi-schema/event-max.out b/tests/qapi-schema/event-max.out new file mode 100644 index 0000000000..010c42b3a9 --- /dev/null +++ b/tests/qapi-schema/event-max.out @@ -0,0 +1,3 @@ +[OrderedDict([('event', 'MAX')])] +[] +[] diff --git a/tests/qapi-schema/redefined-builtin.err b/tests/qapi-schema/redefined-builtin.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/redefined-builtin.exit b/tests/qapi-schema/redefined-builtin.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/redefined-builtin.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json new file mode 100644 index 0000000000..a10050dc73 --- /dev/null +++ b/tests/qapi-schema/redefined-builtin.json @@ -0,0 +1,2 @@ +# FIXME: we should reject types that duplicate builtin names +{ 'type': 'size', 'data': { 'myint': 'size' } } diff --git a/tests/qapi-schema/redefined-builtin.out b/tests/qapi-schema/redefined-builtin.out new file mode 100644 index 0000000000..b0a9aea548 --- /dev/null +++ b/tests/qapi-schema/redefined-builtin.out @@ -0,0 +1,3 @@ +[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])] +[] +[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])] diff --git a/tests/qapi-schema/redefined-command.err b/tests/qapi-schema/redefined-command.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/redefined-command.exit b/tests/qapi-schema/redefined-command.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/redefined-command.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/redefined-command.json b/tests/qapi-schema/redefined-command.json new file mode 100644 index 0000000000..d8c9975e1c --- /dev/null +++ b/tests/qapi-schema/redefined-command.json @@ -0,0 +1,3 @@ +# FIXME: we should reject commands defined more than once +{ 'command': 'foo', 'data': { 'one': 'str' } } +{ 'command': 'foo', 'data': { '*two': 'str' } } diff --git a/tests/qapi-schema/redefined-command.out b/tests/qapi-schema/redefined-command.out new file mode 100644 index 0000000000..44ddba60d5 --- /dev/null +++ b/tests/qapi-schema/redefined-command.out @@ -0,0 +1,4 @@ +[OrderedDict([('command', 'foo'), ('data', OrderedDict([('one', 'str')]))]), + OrderedDict([('command', 'foo'), ('data', OrderedDict([('*two', 'str')]))])] +[] +[] diff --git a/tests/qapi-schema/redefined-event.err b/tests/qapi-schema/redefined-event.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/redefined-event.exit b/tests/qapi-schema/redefined-event.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/redefined-event.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/redefined-event.json b/tests/qapi-schema/redefined-event.json new file mode 100644 index 0000000000..152cce720f --- /dev/null +++ b/tests/qapi-schema/redefined-event.json @@ -0,0 +1,3 @@ +# FIXME: we should reject duplicate events +{ 'event': 'EVENT_A', 'data': { 'myint': 'int' } } +{ 'event': 'EVENT_A', 'data': { 'myint': 'int' } } diff --git a/tests/qapi-schema/redefined-event.out b/tests/qapi-schema/redefined-event.out new file mode 100644 index 0000000000..261b183fc9 --- /dev/null +++ b/tests/qapi-schema/redefined-event.out @@ -0,0 +1,4 @@ +[OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))]), + OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))])] +[] +[] diff --git a/tests/qapi-schema/redefined-type.err b/tests/qapi-schema/redefined-type.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/redefined-type.exit b/tests/qapi-schema/redefined-type.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/redefined-type.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json new file mode 100644 index 0000000000..7972194824 --- /dev/null +++ b/tests/qapi-schema/redefined-type.json @@ -0,0 +1,3 @@ +# FIXME: we should reject types defined more than once +{ 'type': 'foo', 'data': { 'one': 'str' } } +{ 'enum': 'foo', 'data': [ 'two' ] } diff --git a/tests/qapi-schema/redefined-type.out b/tests/qapi-schema/redefined-type.out new file mode 100644 index 0000000000..b509e5776c --- /dev/null +++ b/tests/qapi-schema/redefined-type.out @@ -0,0 +1,4 @@ +[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))]), + OrderedDict([('enum', 'foo'), ('data', ['two'])])] +[{'enum_name': 'foo', 'enum_values': ['two']}] +[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))])] From 4dc2e6906e1084fdd37bf67385c5dcd2c72ae22b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:17 -0600 Subject: [PATCH 20/40] qapi: Better error messages for duplicated expressions The previous commit demonstrated that the generator overlooked duplicate expressions: - a complex type or command reusing a built-in type name - redeclaration of a type name, whether by the same or different metatype - redeclaration of a command or event - collision of a type with implicit 'Kind' enum for a union - collision with an implicit MAX enum constant Since the c_type() function in the generator treats all names as being in the same namespace, this patch adds a global array to track all known names and their source, to prevent collisions before it can cause further problems. While valid .json files won't trigger any of these cases, we might as well be nicer to developers that make a typo while trying to add new QAPI code. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 63 ++++++++++++++++++------ tests/qapi-schema/command-int.err | 1 + tests/qapi-schema/command-int.exit | 2 +- tests/qapi-schema/command-int.json | 2 +- tests/qapi-schema/command-int.out | 3 -- tests/qapi-schema/enum-union-clash.err | 1 + tests/qapi-schema/enum-union-clash.exit | 2 +- tests/qapi-schema/enum-union-clash.json | 2 +- tests/qapi-schema/enum-union-clash.out | 5 -- tests/qapi-schema/event-max.err | 1 + tests/qapi-schema/event-max.exit | 2 +- tests/qapi-schema/event-max.json | 2 +- tests/qapi-schema/event-max.out | 3 -- tests/qapi-schema/redefined-builtin.err | 1 + tests/qapi-schema/redefined-builtin.exit | 2 +- tests/qapi-schema/redefined-builtin.json | 2 +- tests/qapi-schema/redefined-builtin.out | 3 -- tests/qapi-schema/redefined-command.err | 1 + tests/qapi-schema/redefined-command.exit | 2 +- tests/qapi-schema/redefined-command.json | 2 +- tests/qapi-schema/redefined-command.out | 4 -- tests/qapi-schema/redefined-event.err | 1 + tests/qapi-schema/redefined-event.exit | 2 +- tests/qapi-schema/redefined-event.json | 2 +- tests/qapi-schema/redefined-event.out | 4 -- tests/qapi-schema/redefined-type.err | 1 + tests/qapi-schema/redefined-type.exit | 2 +- tests/qapi-schema/redefined-type.json | 2 +- tests/qapi-schema/redefined-type.out | 4 -- 29 files changed, 70 insertions(+), 54 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 868f08b593..6a339d60b5 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -32,6 +32,12 @@ builtin_types = { 'size': 'QTYPE_QINT', } +enum_types = [] +struct_types = [] +union_types = [] +events = [] +all_names = {} + def error_path(parent): res = "" while parent: @@ -256,7 +262,14 @@ def discriminator_find_enum_define(expr): return find_enum(discriminator_type) def check_event(expr, expr_info): + global events + name = expr['event'] params = expr.get('data') + + if name.upper() == 'MAX': + raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created") + events.append(name) + if params: for argname, argentry, optional, structured in parse_args(params): if structured: @@ -430,6 +443,9 @@ def check_keys(expr_elem, meta, required, optional=[]): def parse_schema(input_file): + global all_names + exprs = [] + # First pass: read entire file into memory try: schema = QAPISchema(open(input_file, "r")) @@ -437,30 +453,34 @@ def parse_schema(input_file): print >>sys.stderr, e exit(1) - exprs = [] - try: # Next pass: learn the types and check for valid expression keys. At # this point, top-level 'include' has already been flattened. + for builtin in builtin_types.keys(): + all_names[builtin] = 'built-in' for expr_elem in schema.exprs: expr = expr_elem['expr'] + info = expr_elem['info'] if expr.has_key('enum'): check_keys(expr_elem, 'enum', ['data']) - add_enum(expr['enum'], expr['data']) + add_enum(expr['enum'], info, expr['data']) elif expr.has_key('union'): check_keys(expr_elem, 'union', ['data'], ['base', 'discriminator']) - add_union(expr) + add_union(expr, info) elif expr.has_key('alternate'): check_keys(expr_elem, 'alternate', ['data']) + add_name(expr['alternate'], info, 'alternate') elif expr.has_key('type'): check_keys(expr_elem, 'type', ['data'], ['base']) - add_struct(expr) + add_struct(expr, info) elif expr.has_key('command'): check_keys(expr_elem, 'command', [], ['data', 'returns', 'gen', 'success-response']) + add_name(expr['command'], info, 'command') elif expr.has_key('event'): check_keys(expr_elem, 'event', [], ['data']) + add_name(expr['event'], info, 'event') else: raise QAPIExprError(expr_elem['info'], "Expression is missing metatype") @@ -471,9 +491,11 @@ def parse_schema(input_file): expr = expr_elem['expr'] if expr.has_key('union'): if not discriminator_find_enum_define(expr): - add_enum('%sKind' % expr['union']) + add_enum('%sKind' % expr['union'], expr_elem['info'], + implicit=True) elif expr.has_key('alternate'): - add_enum('%sKind' % expr['alternate']) + add_enum('%sKind' % expr['alternate'], expr_elem['info'], + implicit=True) # Final pass - validate that exprs make sense check_exprs(schema) @@ -567,12 +589,22 @@ def type_name(name): return c_list_type(name[0]) return name -enum_types = [] -struct_types = [] -union_types = [] +def add_name(name, info, meta, implicit = False): + global all_names + if name in all_names: + raise QAPIExprError(info, + "%s '%s' is already defined" + % (all_names[name], name)) + if not implicit and name[-4:] == 'Kind': + raise QAPIExprError(info, + "%s '%s' should not end in 'Kind'" + % (meta, name)) + all_names[name] = meta -def add_struct(definition): +def add_struct(definition, info): global struct_types + name = definition['type'] + add_name(name, info, 'struct') struct_types.append(definition) def find_struct(name): @@ -582,8 +614,10 @@ def find_struct(name): return struct return None -def add_union(definition): +def add_union(definition, info): global union_types + name = definition['union'] + add_name(name, info, 'union') union_types.append(definition) def find_union(name): @@ -593,8 +627,9 @@ def find_union(name): return union return None -def add_enum(name, enum_values = None): +def add_enum(name, info, enum_values = None, implicit = False): global enum_types + add_name(name, info, 'enum', implicit) enum_types.append({"enum_name": name, "enum_values": enum_values}) def find_enum(name): @@ -636,7 +671,7 @@ def c_type(name, is_param=False): return name elif name == None or len(name) == 0: return 'void' - elif name == name.upper(): + elif name in events: return '%sEvent *%s' % (camel_case(name), eatspace) else: return '%s *%s' % (name, eatspace) diff --git a/tests/qapi-schema/command-int.err b/tests/qapi-schema/command-int.err index e69de29bb2..0f9300679b 100644 --- a/tests/qapi-schema/command-int.err +++ b/tests/qapi-schema/command-int.err @@ -0,0 +1 @@ +tests/qapi-schema/command-int.json:2: built-in 'int' is already defined diff --git a/tests/qapi-schema/command-int.exit b/tests/qapi-schema/command-int.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/command-int.exit +++ b/tests/qapi-schema/command-int.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/command-int.json b/tests/qapi-schema/command-int.json index fcbb643c44..c90d408abe 100644 --- a/tests/qapi-schema/command-int.json +++ b/tests/qapi-schema/command-int.json @@ -1,3 +1,3 @@ -# FIXME: we should reject collisions between commands and types +# we reject collisions between commands and types { 'command': 'int', 'data': { 'character': 'str' }, 'returns': { 'value': 'int' } } diff --git a/tests/qapi-schema/command-int.out b/tests/qapi-schema/command-int.out index d8e1854a04..e69de29bb2 100644 --- a/tests/qapi-schema/command-int.out +++ b/tests/qapi-schema/command-int.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'int'), ('data', OrderedDict([('character', 'str')])), ('returns', OrderedDict([('value', 'int')]))])] -[] -[] diff --git a/tests/qapi-schema/enum-union-clash.err b/tests/qapi-schema/enum-union-clash.err index e69de29bb2..c04e1a8064 100644 --- a/tests/qapi-schema/enum-union-clash.err +++ b/tests/qapi-schema/enum-union-clash.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-union-clash.json:2: enum 'UnionKind' should not end in 'Kind' diff --git a/tests/qapi-schema/enum-union-clash.exit b/tests/qapi-schema/enum-union-clash.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/enum-union-clash.exit +++ b/tests/qapi-schema/enum-union-clash.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/enum-union-clash.json b/tests/qapi-schema/enum-union-clash.json index 714ff6d556..593282b6cf 100644 --- a/tests/qapi-schema/enum-union-clash.json +++ b/tests/qapi-schema/enum-union-clash.json @@ -1,4 +1,4 @@ -# FIXME: we should reject types that would conflict with implicit union enum +# we reject types that would conflict with implicit union enum { 'enum': 'UnionKind', 'data': [ 'oops' ] } { 'union': 'Union', 'data': { 'a': 'int' } } diff --git a/tests/qapi-schema/enum-union-clash.out b/tests/qapi-schema/enum-union-clash.out index d45f5e8a69..e69de29bb2 100644 --- a/tests/qapi-schema/enum-union-clash.out +++ b/tests/qapi-schema/enum-union-clash.out @@ -1,5 +0,0 @@ -[OrderedDict([('enum', 'UnionKind'), ('data', ['oops'])]), - OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int')]))])] -[{'enum_name': 'UnionKind', 'enum_values': ['oops']}, - {'enum_name': 'UnionKind', 'enum_values': None}] -[] diff --git a/tests/qapi-schema/event-max.err b/tests/qapi-schema/event-max.err index e69de29bb2..c856534379 100644 --- a/tests/qapi-schema/event-max.err +++ b/tests/qapi-schema/event-max.err @@ -0,0 +1 @@ +tests/qapi-schema/event-max.json:2: Event name 'MAX' cannot be created diff --git a/tests/qapi-schema/event-max.exit b/tests/qapi-schema/event-max.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/event-max.exit +++ b/tests/qapi-schema/event-max.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/event-max.json b/tests/qapi-schema/event-max.json index 997c61c511..f3d7de2a30 100644 --- a/tests/qapi-schema/event-max.json +++ b/tests/qapi-schema/event-max.json @@ -1,2 +1,2 @@ -# FIXME: an event named 'MAX' would conflict with implicit C enum +# an event named 'MAX' would conflict with implicit C enum { 'event': 'MAX' } diff --git a/tests/qapi-schema/event-max.out b/tests/qapi-schema/event-max.out index 010c42b3a9..e69de29bb2 100644 --- a/tests/qapi-schema/event-max.out +++ b/tests/qapi-schema/event-max.out @@ -1,3 +0,0 @@ -[OrderedDict([('event', 'MAX')])] -[] -[] diff --git a/tests/qapi-schema/redefined-builtin.err b/tests/qapi-schema/redefined-builtin.err index e69de29bb2..b2757225c4 100644 --- a/tests/qapi-schema/redefined-builtin.err +++ b/tests/qapi-schema/redefined-builtin.err @@ -0,0 +1 @@ +tests/qapi-schema/redefined-builtin.json:2: built-in 'size' is already defined diff --git a/tests/qapi-schema/redefined-builtin.exit b/tests/qapi-schema/redefined-builtin.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/redefined-builtin.exit +++ b/tests/qapi-schema/redefined-builtin.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json index a10050dc73..df328ccc66 100644 --- a/tests/qapi-schema/redefined-builtin.json +++ b/tests/qapi-schema/redefined-builtin.json @@ -1,2 +1,2 @@ -# FIXME: we should reject types that duplicate builtin names +# we reject types that duplicate builtin names { 'type': 'size', 'data': { 'myint': 'size' } } diff --git a/tests/qapi-schema/redefined-builtin.out b/tests/qapi-schema/redefined-builtin.out index b0a9aea548..e69de29bb2 100644 --- a/tests/qapi-schema/redefined-builtin.out +++ b/tests/qapi-schema/redefined-builtin.out @@ -1,3 +0,0 @@ -[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])] -[] -[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])] diff --git a/tests/qapi-schema/redefined-command.err b/tests/qapi-schema/redefined-command.err index e69de29bb2..82ae256e63 100644 --- a/tests/qapi-schema/redefined-command.err +++ b/tests/qapi-schema/redefined-command.err @@ -0,0 +1 @@ +tests/qapi-schema/redefined-command.json:3: command 'foo' is already defined diff --git a/tests/qapi-schema/redefined-command.exit b/tests/qapi-schema/redefined-command.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/redefined-command.exit +++ b/tests/qapi-schema/redefined-command.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/redefined-command.json b/tests/qapi-schema/redefined-command.json index d8c9975e1c..247e401948 100644 --- a/tests/qapi-schema/redefined-command.json +++ b/tests/qapi-schema/redefined-command.json @@ -1,3 +1,3 @@ -# FIXME: we should reject commands defined more than once +# we reject commands defined more than once { 'command': 'foo', 'data': { 'one': 'str' } } { 'command': 'foo', 'data': { '*two': 'str' } } diff --git a/tests/qapi-schema/redefined-command.out b/tests/qapi-schema/redefined-command.out index 44ddba60d5..e69de29bb2 100644 --- a/tests/qapi-schema/redefined-command.out +++ b/tests/qapi-schema/redefined-command.out @@ -1,4 +0,0 @@ -[OrderedDict([('command', 'foo'), ('data', OrderedDict([('one', 'str')]))]), - OrderedDict([('command', 'foo'), ('data', OrderedDict([('*two', 'str')]))])] -[] -[] diff --git a/tests/qapi-schema/redefined-event.err b/tests/qapi-schema/redefined-event.err index e69de29bb2..35429cb481 100644 --- a/tests/qapi-schema/redefined-event.err +++ b/tests/qapi-schema/redefined-event.err @@ -0,0 +1 @@ +tests/qapi-schema/redefined-event.json:3: event 'EVENT_A' is already defined diff --git a/tests/qapi-schema/redefined-event.exit b/tests/qapi-schema/redefined-event.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/redefined-event.exit +++ b/tests/qapi-schema/redefined-event.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/redefined-event.json b/tests/qapi-schema/redefined-event.json index 152cce720f..7717e91c18 100644 --- a/tests/qapi-schema/redefined-event.json +++ b/tests/qapi-schema/redefined-event.json @@ -1,3 +1,3 @@ -# FIXME: we should reject duplicate events +# we reject duplicate events { 'event': 'EVENT_A', 'data': { 'myint': 'int' } } { 'event': 'EVENT_A', 'data': { 'myint': 'int' } } diff --git a/tests/qapi-schema/redefined-event.out b/tests/qapi-schema/redefined-event.out index 261b183fc9..e69de29bb2 100644 --- a/tests/qapi-schema/redefined-event.out +++ b/tests/qapi-schema/redefined-event.out @@ -1,4 +0,0 @@ -[OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))]), - OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))])] -[] -[] diff --git a/tests/qapi-schema/redefined-type.err b/tests/qapi-schema/redefined-type.err index e69de29bb2..06ea78c478 100644 --- a/tests/qapi-schema/redefined-type.err +++ b/tests/qapi-schema/redefined-type.err @@ -0,0 +1 @@ +tests/qapi-schema/redefined-type.json:3: struct 'foo' is already defined diff --git a/tests/qapi-schema/redefined-type.exit b/tests/qapi-schema/redefined-type.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/redefined-type.exit +++ b/tests/qapi-schema/redefined-type.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json index 7972194824..e6a5f24ca9 100644 --- a/tests/qapi-schema/redefined-type.json +++ b/tests/qapi-schema/redefined-type.json @@ -1,3 +1,3 @@ -# FIXME: we should reject types defined more than once +# we reject types defined more than once { 'type': 'foo', 'data': { 'one': 'str' } } { 'enum': 'foo', 'data': [ 'two' ] } diff --git a/tests/qapi-schema/redefined-type.out b/tests/qapi-schema/redefined-type.out index b509e5776c..e69de29bb2 100644 --- a/tests/qapi-schema/redefined-type.out +++ b/tests/qapi-schema/redefined-type.out @@ -1,4 +0,0 @@ -[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))]), - OrderedDict([('enum', 'foo'), ('data', ['two'])])] -[{'enum_name': 'foo', 'enum_values': ['two']}] -[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))])] From e53188ada516c814a729551be2448684d6d8ce08 Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Mon, 4 May 2015 09:05:18 -0600 Subject: [PATCH 21/40] qapi: Allow true, false and null in schema json In the near term, we will use it for a sensible-looking 'gen':false inside command declarations, instead of the current ugly 'gen':'no'. In the long term, it will allow conversion from shorthand with defaults mentioned only in side-band documentation: 'data':{'*flag':'bool', '*string':'str'} into an explicit default value documentation, as in: 'data':{'flag':{'type':'bool', 'optional':true, 'default':true}, 'string':{'type':'str', 'optional':true, 'default':null}} We still don't parse integer values (also necessary before we can allow explicit defaults), but that can come in a later series. Update the testsuite to match an improved error message. Signed-off-by: Fam Zheng Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 21 ++++++++++++++++++--- tests/qapi-schema/bad-type-bool.err | 2 +- tests/qapi-schema/bad-type-bool.json | 1 - 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 6a339d60b5..1dd91eed42 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -158,6 +158,20 @@ class QAPISchema: return else: string += ch + elif self.tok in "tfn": + val = self.src[self.cursor - 1:] + if val.startswith("true"): + self.val = True + self.cursor += 3 + return + elif val.startswith("false"): + self.val = False + self.cursor += 4 + return + elif val.startswith("null"): + self.val = None + self.cursor += 3 + return elif self.tok == '\n': if self.cursor == len(self.src): self.tok = None @@ -197,8 +211,9 @@ class QAPISchema: if self.tok == ']': self.accept() return expr - if not self.tok in [ '{', '[', "'" ]: - raise QAPISchemaError(self, 'Expected "{", "[", "]" or string') + if not self.tok in "{['tfn": + raise QAPISchemaError(self, 'Expected "{", "[", "]", string, ' + 'boolean or "null"') while True: expr.append(self.get_expr(True)) if self.tok == ']': @@ -217,7 +232,7 @@ class QAPISchema: elif self.tok == '[': self.accept() expr = self.get_values() - elif self.tok == "'": + elif self.tok in "'tfn": expr = self.val self.accept() else: diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err index badb7c2847..de6168c82d 100644 --- a/tests/qapi-schema/bad-type-bool.err +++ b/tests/qapi-schema/bad-type-bool.err @@ -1 +1 @@ -tests/qapi-schema/bad-type-bool.json:3:11: Stray "t" +tests/qapi-schema/bad-type-bool.json:2: 'type' key must have a string value diff --git a/tests/qapi-schema/bad-type-bool.json b/tests/qapi-schema/bad-type-bool.json index 22d6369ef8..e1e9fb0dde 100644 --- a/tests/qapi-schema/bad-type-bool.json +++ b/tests/qapi-schema/bad-type-bool.json @@ -1,3 +1,2 @@ # we reject an expression with a metatype that is not a string -# FIXME: once the parser understands bool inputs, improve the error message { 'type': true, 'data': { } } From d708cdbe8792a55f53e90c1c787e871d527e8d4b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:19 -0600 Subject: [PATCH 22/40] qapi: Unify type bypass and add tests For a few QMP commands, we are forced to pass an arbitrary type without tracking it properly in QAPI. Among the existing clients, this unnamed type was spelled 'dict', 'visitor', and '**'; this patch standardizes on '**', matching the documentation changes earlier in the series. Meanwhile, for the 'gen' key, we have been ignoring the value, although the schema consistently used "'no'" ('success-response' was hard-coded to checking for 'no'). But now that we can support a literal "false" in the schema, we might as well use that rather than ignoring the value or special-casing a random string. Note that these are one-way switches (use of 'gen':true is not the same as omitting 'gen'). Also, the use of '**' requires 'gen':false, but the use of 'gen':false does not mandate the use of '**'. There is no difference to the generated code. Add some tests on what we'd like to guarantee, although it will take later patches to clean up test results and actually enforce the use of a bool parameter. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- qapi-schema.json | 14 +++++++------- qga/qapi-schema.json | 8 ++++---- scripts/qapi-commands.py | 9 ++------- tests/Makefile | 1 + tests/qapi-schema/type-bypass-bad-gen.err | 0 tests/qapi-schema/type-bypass-bad-gen.exit | 1 + tests/qapi-schema/type-bypass-bad-gen.json | 2 ++ tests/qapi-schema/type-bypass-bad-gen.out | 3 +++ tests/qapi-schema/type-bypass-no-gen.err | 0 tests/qapi-schema/type-bypass-no-gen.exit | 1 + tests/qapi-schema/type-bypass-no-gen.json | 2 ++ tests/qapi-schema/type-bypass-no-gen.out | 3 +++ tests/qapi-schema/type-bypass.err | 0 tests/qapi-schema/type-bypass.exit | 1 + tests/qapi-schema/type-bypass.json | 2 ++ tests/qapi-schema/type-bypass.out | 3 +++ 16 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 tests/qapi-schema/type-bypass-bad-gen.err create mode 100644 tests/qapi-schema/type-bypass-bad-gen.exit create mode 100644 tests/qapi-schema/type-bypass-bad-gen.json create mode 100644 tests/qapi-schema/type-bypass-bad-gen.out create mode 100644 tests/qapi-schema/type-bypass-no-gen.err create mode 100644 tests/qapi-schema/type-bypass-no-gen.exit create mode 100644 tests/qapi-schema/type-bypass-no-gen.json create mode 100644 tests/qapi-schema/type-bypass-no-gen.out create mode 100644 tests/qapi-schema/type-bypass.err create mode 100644 tests/qapi-schema/type-bypass.exit create mode 100644 tests/qapi-schema/type-bypass.json create mode 100644 tests/qapi-schema/type-bypass.out diff --git a/qapi-schema.json b/qapi-schema.json index ac9594d66d..7f4cf86ee5 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1561,8 +1561,8 @@ ## { 'command': 'qom-get', 'data': { 'path': 'str', 'property': 'str' }, - 'returns': 'visitor', - 'gen': 'no' } + 'returns': '**', + 'gen': false } ## # @qom-set: @@ -1579,8 +1579,8 @@ # Since: 1.2 ## { 'command': 'qom-set', - 'data': { 'path': 'str', 'property': 'str', 'value': 'visitor' }, - 'gen': 'no' } + 'data': { 'path': 'str', 'property': 'str', 'value': '**' }, + 'gen': false } ## # @set_password: @@ -1943,7 +1943,7 @@ ## { 'command': 'netdev_add', 'data': {'type': 'str', 'id': 'str', '*props': '**'}, - 'gen': 'no' } + 'gen': false } ## # @netdev_del: @@ -1976,8 +1976,8 @@ # Since: 2.0 ## { 'command': 'object-add', - 'data': {'qom-type': 'str', 'id': 'str', '*props': 'dict'}, - 'gen': 'no' } + 'data': {'qom-type': 'str', 'id': 'str', '*props': '**'}, + 'gen': false } ## # @object-del: diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 5c4cd40a92..fecc4427d5 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -195,7 +195,7 @@ # Since: 0.15.0 ## { 'command': 'guest-shutdown', 'data': { '*mode': 'str' }, - 'success-response': 'no' } + 'success-response': false } ## # @guest-file-open: @@ -470,7 +470,7 @@ # # Since: 1.1 ## -{ 'command': 'guest-suspend-disk', 'success-response': 'no' } +{ 'command': 'guest-suspend-disk', 'success-response': false } ## # @guest-suspend-ram @@ -502,7 +502,7 @@ # # Since: 1.1 ## -{ 'command': 'guest-suspend-ram', 'success-response': 'no' } +{ 'command': 'guest-suspend-ram', 'success-response': false } ## # @guest-suspend-hybrid @@ -529,7 +529,7 @@ # # Since: 1.1 ## -{ 'command': 'guest-suspend-hybrid', 'success-response': 'no' } +{ 'command': 'guest-suspend-hybrid', 'success-response': false } ## # @GuestIpAddressType: diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py index 053ba85b5f..cb786825ee 100644 --- a/scripts/qapi-commands.py +++ b/scripts/qapi-commands.py @@ -2,7 +2,7 @@ # QAPI command marshaller generator # # Copyright IBM, Corp. 2011 -# Copyright (C) 2014 Red Hat, Inc. +# Copyright (C) 2014-2015 Red Hat, Inc. # # Authors: # Anthony Liguori @@ -293,17 +293,12 @@ out: return ret -def option_value_matches(opt, val, cmd): - if opt in cmd and cmd[opt] == val: - return True - return False - def gen_registry(commands): registry="" push_indent() for cmd in commands: options = 'QCO_NO_OPTIONS' - if option_value_matches('success-response', 'no', cmd): + if not cmd.get('success-response', True): options = 'QCO_NO_SUCCESS_RESP' registry += mcgen(''' diff --git a/tests/Makefile b/tests/Makefile index c27940c417..64e678502c 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -216,6 +216,7 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ bad-type-dict.json double-data.json unknown-expr-key.json \ redefined-type.json redefined-command.json redefined-builtin.json \ redefined-event.json command-int.json event-max.json \ + type-bypass.json type-bypass-no-gen.json type-bypass-bad-gen.json \ missing-colon.json missing-comma-list.json \ missing-comma-object.json non-objects.json \ qapi-schema-test.json quoted-structural-chars.json \ diff --git a/tests/qapi-schema/type-bypass-bad-gen.err b/tests/qapi-schema/type-bypass-bad-gen.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/type-bypass-bad-gen.exit b/tests/qapi-schema/type-bypass-bad-gen.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/type-bypass-bad-gen.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/type-bypass-bad-gen.json b/tests/qapi-schema/type-bypass-bad-gen.json new file mode 100644 index 0000000000..bb70bee085 --- /dev/null +++ b/tests/qapi-schema/type-bypass-bad-gen.json @@ -0,0 +1,2 @@ +# FIXME: 'gen' should only appear with value false +{ 'command': 'foo', 'gen': 'whatever' } diff --git a/tests/qapi-schema/type-bypass-bad-gen.out b/tests/qapi-schema/type-bypass-bad-gen.out new file mode 100644 index 0000000000..e678f2c18e --- /dev/null +++ b/tests/qapi-schema/type-bypass-bad-gen.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'foo'), ('gen', 'whatever')])] +[] +[] diff --git a/tests/qapi-schema/type-bypass-no-gen.err b/tests/qapi-schema/type-bypass-no-gen.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/type-bypass-no-gen.exit b/tests/qapi-schema/type-bypass-no-gen.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/type-bypass-no-gen.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/type-bypass-no-gen.json b/tests/qapi-schema/type-bypass-no-gen.json new file mode 100644 index 0000000000..af87c191ad --- /dev/null +++ b/tests/qapi-schema/type-bypass-no-gen.json @@ -0,0 +1,2 @@ +# FIXME: type bypass should only work with 'gen':false +{ 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**' } diff --git a/tests/qapi-schema/type-bypass-no-gen.out b/tests/qapi-schema/type-bypass-no-gen.out new file mode 100644 index 0000000000..8b2a9ac94a --- /dev/null +++ b/tests/qapi-schema/type-bypass-no-gen.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'unsafe'), ('data', OrderedDict([('arg', '**')])), ('returns', '**')])] +[] +[] diff --git a/tests/qapi-schema/type-bypass.err b/tests/qapi-schema/type-bypass.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/type-bypass.exit b/tests/qapi-schema/type-bypass.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/type-bypass.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/type-bypass.json b/tests/qapi-schema/type-bypass.json new file mode 100644 index 0000000000..48b2137833 --- /dev/null +++ b/tests/qapi-schema/type-bypass.json @@ -0,0 +1,2 @@ +# Use of 'gen':false allows bypassing type system +{ 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**', 'gen': false } diff --git a/tests/qapi-schema/type-bypass.out b/tests/qapi-schema/type-bypass.out new file mode 100644 index 0000000000..eaf20f8344 --- /dev/null +++ b/tests/qapi-schema/type-bypass.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'unsafe'), ('data', OrderedDict([('arg', '**')])), ('returns', '**'), ('gen', False)])] +[] +[] From 0d8b9fb5f296a96723d98a45a6a00bfd4e45e1b9 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:20 -0600 Subject: [PATCH 23/40] qapi: Add some type check tests Demonstrate that the qapi generator silently parses confusing types, which may cause other errors later on. Later patches will update the expected results as the generator is made stricter. Most of the new tests focus on blatant errors. But returns-whitelist is a case where we have historically allowed returning something other than a JSON object from particular commands; we have to keep that behavior to avoid breaking clients, but it would be nicer to avoid adding such commands in the future, because any return that is not an (array of) object cannot be easily extended if future qemu wants to return additional information. The QMP protocol already documents that clients should ignore unknown dictionary keys, but does not require clients to have to handle more than one type of JSON object. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/Makefile | 10 +++++++--- tests/qapi-schema/bad-data.err | 0 tests/qapi-schema/bad-data.exit | 1 + tests/qapi-schema/bad-data.json | 2 ++ tests/qapi-schema/bad-data.out | 3 +++ tests/qapi-schema/data-array-empty.err | 0 tests/qapi-schema/data-array-empty.exit | 1 + tests/qapi-schema/data-array-empty.json | 2 ++ tests/qapi-schema/data-array-empty.out | 3 +++ tests/qapi-schema/data-array-unknown.err | 0 tests/qapi-schema/data-array-unknown.exit | 1 + tests/qapi-schema/data-array-unknown.json | 2 ++ tests/qapi-schema/data-array-unknown.out | 3 +++ tests/qapi-schema/data-int.err | 0 tests/qapi-schema/data-int.exit | 1 + tests/qapi-schema/data-int.json | 2 ++ tests/qapi-schema/data-int.out | 3 +++ tests/qapi-schema/data-member-array-bad.err | 0 tests/qapi-schema/data-member-array-bad.exit | 1 + tests/qapi-schema/data-member-array-bad.json | 2 ++ tests/qapi-schema/data-member-array-bad.out | 3 +++ tests/qapi-schema/data-member-array.err | 0 tests/qapi-schema/data-member-array.exit | 1 + tests/qapi-schema/data-member-array.json | 4 ++++ tests/qapi-schema/data-member-array.out | 5 +++++ tests/qapi-schema/data-member-unknown.err | 0 tests/qapi-schema/data-member-unknown.exit | 1 + tests/qapi-schema/data-member-unknown.json | 2 ++ tests/qapi-schema/data-member-unknown.out | 3 +++ tests/qapi-schema/data-unknown.err | 0 tests/qapi-schema/data-unknown.exit | 1 + tests/qapi-schema/data-unknown.json | 2 ++ tests/qapi-schema/data-unknown.out | 3 +++ tests/qapi-schema/nested-struct-data.err | 0 tests/qapi-schema/nested-struct-data.exit | 1 + tests/qapi-schema/nested-struct-data.json | 4 ++++ tests/qapi-schema/nested-struct-data.out | 3 +++ tests/qapi-schema/nested-struct-returns.err | 0 tests/qapi-schema/nested-struct-returns.exit | 1 + tests/qapi-schema/nested-struct-returns.json | 3 +++ tests/qapi-schema/nested-struct-returns.out | 3 +++ tests/qapi-schema/returns-alternate.err | 0 tests/qapi-schema/returns-alternate.exit | 1 + tests/qapi-schema/returns-alternate.json | 3 +++ tests/qapi-schema/returns-alternate.out | 4 ++++ tests/qapi-schema/returns-array-bad.err | 0 tests/qapi-schema/returns-array-bad.exit | 1 + tests/qapi-schema/returns-array-bad.json | 2 ++ tests/qapi-schema/returns-array-bad.out | 3 +++ tests/qapi-schema/returns-int.err | 0 tests/qapi-schema/returns-int.exit | 1 + tests/qapi-schema/returns-int.json | 2 ++ tests/qapi-schema/returns-int.out | 3 +++ tests/qapi-schema/returns-unknown.err | 0 tests/qapi-schema/returns-unknown.exit | 1 + tests/qapi-schema/returns-unknown.json | 2 ++ tests/qapi-schema/returns-unknown.out | 3 +++ tests/qapi-schema/returns-whitelist.err | 0 tests/qapi-schema/returns-whitelist.exit | 1 + tests/qapi-schema/returns-whitelist.json | 11 +++++++++++ tests/qapi-schema/returns-whitelist.out | 7 +++++++ 61 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 tests/qapi-schema/bad-data.err create mode 100644 tests/qapi-schema/bad-data.exit create mode 100644 tests/qapi-schema/bad-data.json create mode 100644 tests/qapi-schema/bad-data.out create mode 100644 tests/qapi-schema/data-array-empty.err create mode 100644 tests/qapi-schema/data-array-empty.exit create mode 100644 tests/qapi-schema/data-array-empty.json create mode 100644 tests/qapi-schema/data-array-empty.out create mode 100644 tests/qapi-schema/data-array-unknown.err create mode 100644 tests/qapi-schema/data-array-unknown.exit create mode 100644 tests/qapi-schema/data-array-unknown.json create mode 100644 tests/qapi-schema/data-array-unknown.out create mode 100644 tests/qapi-schema/data-int.err create mode 100644 tests/qapi-schema/data-int.exit create mode 100644 tests/qapi-schema/data-int.json create mode 100644 tests/qapi-schema/data-int.out create mode 100644 tests/qapi-schema/data-member-array-bad.err create mode 100644 tests/qapi-schema/data-member-array-bad.exit create mode 100644 tests/qapi-schema/data-member-array-bad.json create mode 100644 tests/qapi-schema/data-member-array-bad.out create mode 100644 tests/qapi-schema/data-member-array.err create mode 100644 tests/qapi-schema/data-member-array.exit create mode 100644 tests/qapi-schema/data-member-array.json create mode 100644 tests/qapi-schema/data-member-array.out create mode 100644 tests/qapi-schema/data-member-unknown.err create mode 100644 tests/qapi-schema/data-member-unknown.exit create mode 100644 tests/qapi-schema/data-member-unknown.json create mode 100644 tests/qapi-schema/data-member-unknown.out create mode 100644 tests/qapi-schema/data-unknown.err create mode 100644 tests/qapi-schema/data-unknown.exit create mode 100644 tests/qapi-schema/data-unknown.json create mode 100644 tests/qapi-schema/data-unknown.out create mode 100644 tests/qapi-schema/nested-struct-data.err create mode 100644 tests/qapi-schema/nested-struct-data.exit create mode 100644 tests/qapi-schema/nested-struct-data.json create mode 100644 tests/qapi-schema/nested-struct-data.out create mode 100644 tests/qapi-schema/nested-struct-returns.err create mode 100644 tests/qapi-schema/nested-struct-returns.exit create mode 100644 tests/qapi-schema/nested-struct-returns.json create mode 100644 tests/qapi-schema/nested-struct-returns.out create mode 100644 tests/qapi-schema/returns-alternate.err create mode 100644 tests/qapi-schema/returns-alternate.exit create mode 100644 tests/qapi-schema/returns-alternate.json create mode 100644 tests/qapi-schema/returns-alternate.out create mode 100644 tests/qapi-schema/returns-array-bad.err create mode 100644 tests/qapi-schema/returns-array-bad.exit create mode 100644 tests/qapi-schema/returns-array-bad.json create mode 100644 tests/qapi-schema/returns-array-bad.out create mode 100644 tests/qapi-schema/returns-int.err create mode 100644 tests/qapi-schema/returns-int.exit create mode 100644 tests/qapi-schema/returns-int.json create mode 100644 tests/qapi-schema/returns-int.out create mode 100644 tests/qapi-schema/returns-unknown.err create mode 100644 tests/qapi-schema/returns-unknown.exit create mode 100644 tests/qapi-schema/returns-unknown.json create mode 100644 tests/qapi-schema/returns-unknown.out create mode 100644 tests/qapi-schema/returns-whitelist.err create mode 100644 tests/qapi-schema/returns-whitelist.exit create mode 100644 tests/qapi-schema/returns-whitelist.json create mode 100644 tests/qapi-schema/returns-whitelist.out diff --git a/tests/Makefile b/tests/Makefile index 64e678502c..e2a3bd349e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -215,10 +215,14 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ double-type.json bad-base.json bad-type-bool.json bad-type-int.json \ bad-type-dict.json double-data.json unknown-expr-key.json \ redefined-type.json redefined-command.json redefined-builtin.json \ - redefined-event.json command-int.json event-max.json \ + redefined-event.json command-int.json bad-data.json event-max.json \ type-bypass.json type-bypass-no-gen.json type-bypass-bad-gen.json \ - missing-colon.json missing-comma-list.json \ - missing-comma-object.json non-objects.json \ + data-array-empty.json data-array-unknown.json data-int.json \ + data-unknown.json data-member-unknown.json data-member-array.json \ + data-member-array-bad.json returns-array-bad.json returns-int.json \ + returns-unknown.json returns-alternate.json returns-whitelist.json \ + missing-colon.json missing-comma-list.json missing-comma-object.json \ + nested-struct-data.json nested-struct-returns.json non-objects.json \ qapi-schema-test.json quoted-structural-chars.json \ trailing-comma-list.json trailing-comma-object.json \ unclosed-list.json unclosed-object.json unclosed-string.json \ diff --git a/tests/qapi-schema/bad-data.err b/tests/qapi-schema/bad-data.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/bad-data.exit b/tests/qapi-schema/bad-data.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/bad-data.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/bad-data.json b/tests/qapi-schema/bad-data.json new file mode 100644 index 0000000000..ff1616dd96 --- /dev/null +++ b/tests/qapi-schema/bad-data.json @@ -0,0 +1,2 @@ +# FIXME: we should ensure 'data' is a dictionary for all but enums +{ 'command': 'oops', 'data': [ ] } diff --git a/tests/qapi-schema/bad-data.out b/tests/qapi-schema/bad-data.out new file mode 100644 index 0000000000..67802be93c --- /dev/null +++ b/tests/qapi-schema/bad-data.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('data', [])])] +[] +[] diff --git a/tests/qapi-schema/data-array-empty.err b/tests/qapi-schema/data-array-empty.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/data-array-empty.exit b/tests/qapi-schema/data-array-empty.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/data-array-empty.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/data-array-empty.json b/tests/qapi-schema/data-array-empty.json new file mode 100644 index 0000000000..edb543a75d --- /dev/null +++ b/tests/qapi-schema/data-array-empty.json @@ -0,0 +1,2 @@ +# FIXME: we should reject an array for data if it does not contain a known type +{ 'command': 'oops', 'data': { 'empty': [ ] } } diff --git a/tests/qapi-schema/data-array-empty.out b/tests/qapi-schema/data-array-empty.out new file mode 100644 index 0000000000..6f25c9e18a --- /dev/null +++ b/tests/qapi-schema/data-array-empty.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('data', OrderedDict([('empty', [])]))])] +[] +[] diff --git a/tests/qapi-schema/data-array-unknown.err b/tests/qapi-schema/data-array-unknown.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/data-array-unknown.exit b/tests/qapi-schema/data-array-unknown.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/data-array-unknown.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/data-array-unknown.json b/tests/qapi-schema/data-array-unknown.json new file mode 100644 index 0000000000..20cd3c0c30 --- /dev/null +++ b/tests/qapi-schema/data-array-unknown.json @@ -0,0 +1,2 @@ +# FIXME: we should reject an array for data if it does not contain a known type +{ 'command': 'oops', 'data': { 'array': [ 'NoSuchType' ] } } diff --git a/tests/qapi-schema/data-array-unknown.out b/tests/qapi-schema/data-array-unknown.out new file mode 100644 index 0000000000..4314ab5dfa --- /dev/null +++ b/tests/qapi-schema/data-array-unknown.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('data', OrderedDict([('array', ['NoSuchType'])]))])] +[] +[] diff --git a/tests/qapi-schema/data-int.err b/tests/qapi-schema/data-int.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/data-int.exit b/tests/qapi-schema/data-int.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/data-int.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/data-int.json b/tests/qapi-schema/data-int.json new file mode 100644 index 0000000000..37916e09a9 --- /dev/null +++ b/tests/qapi-schema/data-int.json @@ -0,0 +1,2 @@ +# FIXME: we should reject commands where data is not an array or complex type +{ 'command': 'oops', 'data': 'int' } diff --git a/tests/qapi-schema/data-int.out b/tests/qapi-schema/data-int.out new file mode 100644 index 0000000000..e589a4ff92 --- /dev/null +++ b/tests/qapi-schema/data-int.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('data', 'int')])] +[] +[] diff --git a/tests/qapi-schema/data-member-array-bad.err b/tests/qapi-schema/data-member-array-bad.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/data-member-array-bad.exit b/tests/qapi-schema/data-member-array-bad.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/data-member-array-bad.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/data-member-array-bad.json b/tests/qapi-schema/data-member-array-bad.json new file mode 100644 index 0000000000..c954af13ee --- /dev/null +++ b/tests/qapi-schema/data-member-array-bad.json @@ -0,0 +1,2 @@ +# FIXME: we should reject data if it does not contain a valid array type +{ 'command': 'oops', 'data': { 'member': [ { 'nested': 'str' } ] } } diff --git a/tests/qapi-schema/data-member-array-bad.out b/tests/qapi-schema/data-member-array-bad.out new file mode 100644 index 0000000000..0e00c4171c --- /dev/null +++ b/tests/qapi-schema/data-member-array-bad.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', [OrderedDict([('nested', 'str')])])]))])] +[] +[] diff --git a/tests/qapi-schema/data-member-array.err b/tests/qapi-schema/data-member-array.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/data-member-array.exit b/tests/qapi-schema/data-member-array.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/data-member-array.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/data-member-array.json b/tests/qapi-schema/data-member-array.json new file mode 100644 index 0000000000..7cce276979 --- /dev/null +++ b/tests/qapi-schema/data-member-array.json @@ -0,0 +1,4 @@ +# valid array members +{ 'enum': 'abc', 'data': [ 'a', 'b', 'c' ] } +{ 'type': 'def', 'data': { 'array': [ 'abc' ] } } +{ 'command': 'okay', 'data': { 'member1': [ 'int' ], 'member2': [ 'def' ] } } diff --git a/tests/qapi-schema/data-member-array.out b/tests/qapi-schema/data-member-array.out new file mode 100644 index 0000000000..82871204ed --- /dev/null +++ b/tests/qapi-schema/data-member-array.out @@ -0,0 +1,5 @@ +[OrderedDict([('enum', 'abc'), ('data', ['a', 'b', 'c'])]), + OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))]), + OrderedDict([('command', 'okay'), ('data', OrderedDict([('member1', ['int']), ('member2', ['def'])]))])] +[{'enum_name': 'abc', 'enum_values': ['a', 'b', 'c']}] +[OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))])] diff --git a/tests/qapi-schema/data-member-unknown.err b/tests/qapi-schema/data-member-unknown.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/data-member-unknown.exit b/tests/qapi-schema/data-member-unknown.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/data-member-unknown.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/data-member-unknown.json b/tests/qapi-schema/data-member-unknown.json new file mode 100644 index 0000000000..40e6252c1d --- /dev/null +++ b/tests/qapi-schema/data-member-unknown.json @@ -0,0 +1,2 @@ +# FIXME: we should reject data if it does not contain a known type +{ 'command': 'oops', 'data': { 'member': 'NoSuchType' } } diff --git a/tests/qapi-schema/data-member-unknown.out b/tests/qapi-schema/data-member-unknown.out new file mode 100644 index 0000000000..36252a5f73 --- /dev/null +++ b/tests/qapi-schema/data-member-unknown.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', 'NoSuchType')]))])] +[] +[] diff --git a/tests/qapi-schema/data-unknown.err b/tests/qapi-schema/data-unknown.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/data-unknown.exit b/tests/qapi-schema/data-unknown.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/data-unknown.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/data-unknown.json b/tests/qapi-schema/data-unknown.json new file mode 100644 index 0000000000..776bd342e5 --- /dev/null +++ b/tests/qapi-schema/data-unknown.json @@ -0,0 +1,2 @@ +# FIXME: we should reject data if it does not contain a known type +{ 'command': 'oops', 'data': 'NoSuchType' } diff --git a/tests/qapi-schema/data-unknown.out b/tests/qapi-schema/data-unknown.out new file mode 100644 index 0000000000..2c60726881 --- /dev/null +++ b/tests/qapi-schema/data-unknown.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('data', 'NoSuchType')])] +[] +[] diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/nested-struct-data.exit b/tests/qapi-schema/nested-struct-data.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/nested-struct-data.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/nested-struct-data.json b/tests/qapi-schema/nested-struct-data.json new file mode 100644 index 0000000000..0247c8c82c --- /dev/null +++ b/tests/qapi-schema/nested-struct-data.json @@ -0,0 +1,4 @@ +# FIXME: inline subtypes collide with our desired future use of defaults +{ 'command': 'foo', + 'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' }, + 'returns': {} } diff --git a/tests/qapi-schema/nested-struct-data.out b/tests/qapi-schema/nested-struct-data.out new file mode 100644 index 0000000000..999cbb8eef --- /dev/null +++ b/tests/qapi-schema/nested-struct-data.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'foo'), ('data', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')])), ('returns', OrderedDict())])] +[] +[] diff --git a/tests/qapi-schema/nested-struct-returns.err b/tests/qapi-schema/nested-struct-returns.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/nested-struct-returns.exit b/tests/qapi-schema/nested-struct-returns.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/nested-struct-returns.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/nested-struct-returns.json b/tests/qapi-schema/nested-struct-returns.json new file mode 100644 index 0000000000..5a46840b71 --- /dev/null +++ b/tests/qapi-schema/nested-struct-returns.json @@ -0,0 +1,3 @@ +# FIXME: inline subtypes collide with our desired future use of defaults +{ 'command': 'foo', + 'returns': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } } diff --git a/tests/qapi-schema/nested-struct-returns.out b/tests/qapi-schema/nested-struct-returns.out new file mode 100644 index 0000000000..c53d23b3a8 --- /dev/null +++ b/tests/qapi-schema/nested-struct-returns.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'foo'), ('returns', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')]))])] +[] +[] diff --git a/tests/qapi-schema/returns-alternate.err b/tests/qapi-schema/returns-alternate.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/returns-alternate.exit b/tests/qapi-schema/returns-alternate.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/returns-alternate.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/returns-alternate.json b/tests/qapi-schema/returns-alternate.json new file mode 100644 index 0000000000..b3b91fd3d4 --- /dev/null +++ b/tests/qapi-schema/returns-alternate.json @@ -0,0 +1,3 @@ +# FIXME: we should reject returns if it is an alternate type +{ 'alternate': 'Alt', 'data': { 'a': 'int', 'b': 'str' } } +{ 'command': 'oops', 'returns': 'Alt' } diff --git a/tests/qapi-schema/returns-alternate.out b/tests/qapi-schema/returns-alternate.out new file mode 100644 index 0000000000..8a03ed3adf --- /dev/null +++ b/tests/qapi-schema/returns-alternate.out @@ -0,0 +1,4 @@ +[OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]), + OrderedDict([('command', 'oops'), ('returns', 'Alt')])] +[{'enum_name': 'AltKind', 'enum_values': None}] +[] diff --git a/tests/qapi-schema/returns-array-bad.err b/tests/qapi-schema/returns-array-bad.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/returns-array-bad.exit b/tests/qapi-schema/returns-array-bad.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/returns-array-bad.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/returns-array-bad.json b/tests/qapi-schema/returns-array-bad.json new file mode 100644 index 0000000000..14882c1e45 --- /dev/null +++ b/tests/qapi-schema/returns-array-bad.json @@ -0,0 +1,2 @@ +# FIXME: we should reject an array return that is not a single type +{ 'command': 'oops', 'returns': [ 'str', 'str' ] } diff --git a/tests/qapi-schema/returns-array-bad.out b/tests/qapi-schema/returns-array-bad.out new file mode 100644 index 0000000000..eccad55219 --- /dev/null +++ b/tests/qapi-schema/returns-array-bad.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('returns', ['str', 'str'])])] +[] +[] diff --git a/tests/qapi-schema/returns-int.err b/tests/qapi-schema/returns-int.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/returns-int.exit b/tests/qapi-schema/returns-int.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/returns-int.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/returns-int.json b/tests/qapi-schema/returns-int.json new file mode 100644 index 0000000000..7888fb1b08 --- /dev/null +++ b/tests/qapi-schema/returns-int.json @@ -0,0 +1,2 @@ +# It is okay (although not extensible) to return a non-dictionary +{ 'command': 'okay', 'returns': 'int' } diff --git a/tests/qapi-schema/returns-int.out b/tests/qapi-schema/returns-int.out new file mode 100644 index 0000000000..36b00a9f3b --- /dev/null +++ b/tests/qapi-schema/returns-int.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'okay'), ('returns', 'int')])] +[] +[] diff --git a/tests/qapi-schema/returns-unknown.err b/tests/qapi-schema/returns-unknown.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/returns-unknown.exit b/tests/qapi-schema/returns-unknown.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/returns-unknown.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/returns-unknown.json b/tests/qapi-schema/returns-unknown.json new file mode 100644 index 0000000000..61f20ebb02 --- /dev/null +++ b/tests/qapi-schema/returns-unknown.json @@ -0,0 +1,2 @@ +# FIXME: we should reject returns if it does not contain a known type +{ 'command': 'oops', 'returns': 'NoSuchType' } diff --git a/tests/qapi-schema/returns-unknown.out b/tests/qapi-schema/returns-unknown.out new file mode 100644 index 0000000000..a482c837ce --- /dev/null +++ b/tests/qapi-schema/returns-unknown.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'oops'), ('returns', 'NoSuchType')])] +[] +[] diff --git a/tests/qapi-schema/returns-whitelist.err b/tests/qapi-schema/returns-whitelist.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/returns-whitelist.exit b/tests/qapi-schema/returns-whitelist.exit new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/tests/qapi-schema/returns-whitelist.exit @@ -0,0 +1 @@ +0 diff --git a/tests/qapi-schema/returns-whitelist.json b/tests/qapi-schema/returns-whitelist.json new file mode 100644 index 0000000000..8328563b4e --- /dev/null +++ b/tests/qapi-schema/returns-whitelist.json @@ -0,0 +1,11 @@ +# FIXME: we should enforce that 'returns' be a dict or array of dict unless whitelisted +{ 'command': 'human-monitor-command', + 'data': {'command-line': 'str', '*cpu-index': 'int'}, + 'returns': 'str' } +{ 'enum': 'TpmModel', 'data': [ 'tpm-tis' ] } +{ 'command': 'query-tpm-models', 'returns': ['TpmModel'] } +{ 'command': 'guest-get-time', + 'returns': 'int' } + +{ 'command': 'no-way-this-will-get-whitelisted', + 'returns': [ 'int' ] } diff --git a/tests/qapi-schema/returns-whitelist.out b/tests/qapi-schema/returns-whitelist.out new file mode 100644 index 0000000000..2adcd8ba10 --- /dev/null +++ b/tests/qapi-schema/returns-whitelist.out @@ -0,0 +1,7 @@ +[OrderedDict([('command', 'human-monitor-command'), ('data', OrderedDict([('command-line', 'str'), ('*cpu-index', 'int')])), ('returns', 'str')]), + OrderedDict([('enum', 'TpmModel'), ('data', ['tpm-tis'])]), + OrderedDict([('command', 'query-tpm-models'), ('returns', ['TpmModel'])]), + OrderedDict([('command', 'guest-get-time'), ('returns', 'int')]), + OrderedDict([('command', 'no-way-this-will-get-whitelisted'), ('returns', ['int'])])] +[{'enum_name': 'TpmModel', 'enum_values': ['tpm-tis']}] +[] From dd883c6f0547f02ae805d02852ff3691f6d08f85 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:21 -0600 Subject: [PATCH 24/40] qapi: More rigourous checking of types Now that we know every expression is valid with regards to its keys, we can add further tests that those keys refer to valid types. With this patch, all uses of a type (the 'data': of command, type, union, alternate, and event; the 'returns': of command; the 'base': of type and union) must resolve to an appropriate subset of metatypes declared by the current qapi parse; this includes recursing into each member of a data dictionary. Dealing with '**' and nested anonymous structs will be done in later patches. Update the testsuite to match improved output. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 96 ++++++++++++++++++-- tests/qapi-schema/alternate-array.err | 2 +- tests/qapi-schema/alternate-nested.err | 2 +- tests/qapi-schema/alternate-unknown.err | 2 +- tests/qapi-schema/bad-base.err | 1 + tests/qapi-schema/bad-base.exit | 2 +- tests/qapi-schema/bad-base.json | 2 +- tests/qapi-schema/bad-base.out | 4 - tests/qapi-schema/bad-data.err | 1 + tests/qapi-schema/bad-data.exit | 2 +- tests/qapi-schema/bad-data.json | 2 +- tests/qapi-schema/bad-data.out | 3 - tests/qapi-schema/data-array-empty.err | 1 + tests/qapi-schema/data-array-empty.exit | 2 +- tests/qapi-schema/data-array-empty.json | 2 +- tests/qapi-schema/data-array-empty.out | 3 - tests/qapi-schema/data-array-unknown.err | 1 + tests/qapi-schema/data-array-unknown.exit | 2 +- tests/qapi-schema/data-array-unknown.json | 2 +- tests/qapi-schema/data-array-unknown.out | 3 - tests/qapi-schema/data-int.err | 1 + tests/qapi-schema/data-int.exit | 2 +- tests/qapi-schema/data-int.json | 2 +- tests/qapi-schema/data-int.out | 3 - tests/qapi-schema/data-member-array-bad.err | 1 + tests/qapi-schema/data-member-array-bad.exit | 2 +- tests/qapi-schema/data-member-array-bad.json | 2 +- tests/qapi-schema/data-member-array-bad.out | 3 - tests/qapi-schema/data-member-unknown.err | 1 + tests/qapi-schema/data-member-unknown.exit | 2 +- tests/qapi-schema/data-member-unknown.json | 2 +- tests/qapi-schema/data-member-unknown.out | 3 - tests/qapi-schema/data-unknown.err | 1 + tests/qapi-schema/data-unknown.exit | 2 +- tests/qapi-schema/data-unknown.json | 2 +- tests/qapi-schema/data-unknown.out | 3 - tests/qapi-schema/flat-union-int-branch.err | 1 + tests/qapi-schema/flat-union-int-branch.exit | 2 +- tests/qapi-schema/flat-union-int-branch.json | 2 +- tests/qapi-schema/flat-union-int-branch.out | 7 -- tests/qapi-schema/returns-array-bad.err | 1 + tests/qapi-schema/returns-array-bad.exit | 2 +- tests/qapi-schema/returns-array-bad.json | 2 +- tests/qapi-schema/returns-array-bad.out | 3 - tests/qapi-schema/returns-unknown.err | 1 + tests/qapi-schema/returns-unknown.exit | 2 +- tests/qapi-schema/returns-unknown.json | 2 +- tests/qapi-schema/returns-unknown.out | 3 - tests/qapi-schema/union-unknown.err | 1 + tests/qapi-schema/union-unknown.exit | 2 +- tests/qapi-schema/union-unknown.json | 2 +- tests/qapi-schema/union-unknown.out | 3 - 52 files changed, 126 insertions(+), 77 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 1dd91eed42..3c33e4e46c 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -276,6 +276,64 @@ def discriminator_find_enum_define(expr): return find_enum(discriminator_type) +def check_type(expr_info, source, value, allow_array = False, + allow_dict = False, allow_metas = []): + global all_names + orig_value = value + + if value is None: + return + + if value == '**': + return + + # Check if array type for value is okay + if isinstance(value, list): + if not allow_array: + raise QAPIExprError(expr_info, + "%s cannot be an array" % source) + if len(value) != 1 or not isinstance(value[0], str): + raise QAPIExprError(expr_info, + "%s: array type must contain single type name" + % source) + value = value[0] + orig_value = "array of %s" %value + + # Check if type name for value is okay + if isinstance(value, str): + if not value in all_names: + raise QAPIExprError(expr_info, + "%s uses unknown type '%s'" + % (source, orig_value)) + if not all_names[value] in allow_metas: + raise QAPIExprError(expr_info, + "%s cannot use %s type '%s'" + % (source, all_names[value], orig_value)) + return + + # value is a dictionary, check that each member is okay + if not isinstance(value, OrderedDict): + raise QAPIExprError(expr_info, + "%s should be a dictionary" % source) + if not allow_dict: + raise QAPIExprError(expr_info, + "%s should be a type name" % source) + for (key, arg) in value.items(): + check_type(expr_info, "Member '%s' of %s" % (key, source), arg, + allow_array=True, allow_dict=True, + allow_metas=['built-in', 'union', 'alternate', 'struct', + 'enum']) + +def check_command(expr, expr_info): + name = expr['command'] + check_type(expr_info, "'data' for command '%s'" % name, + expr.get('data'), allow_dict=True, + allow_metas=['union', 'struct']) + check_type(expr_info, "'returns' for command '%s'" % name, + expr.get('returns'), allow_array=True, allow_dict=True, + allow_metas=['built-in', 'union', 'alternate', 'struct', + 'enum']) + def check_event(expr, expr_info): global events name = expr['event'] @@ -284,7 +342,9 @@ def check_event(expr, expr_info): if name.upper() == 'MAX': raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created") events.append(name) - + check_type(expr_info, "'data' for event '%s'" % name, + expr.get('data'), allow_dict=True, + allow_metas=['union', 'struct']) if params: for argname, argentry, optional, structured in parse_args(params): if structured: @@ -313,6 +373,7 @@ def check_union(expr, expr_info): # With no discriminator it is a simple union. if discriminator is None: enum_define = None + allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum'] if base is not None: raise QAPIExprError(expr_info, "Simple union '%s' must not have a base" @@ -344,6 +405,7 @@ def check_union(expr, expr_info): "type '%s'" % (discriminator, base)) enum_define = find_enum(discriminator_type) + allow_metas=['struct'] # Do not allow string discriminator if not enum_define: raise QAPIExprError(expr_info, @@ -352,6 +414,11 @@ def check_union(expr, expr_info): # Check every branch for (key, value) in members.items(): + # Each value must name a known type; furthermore, in flat unions, + # branches must be a struct + check_type(expr_info, "Member '%s' of union '%s'" % (key, name), + value, allow_array=True, allow_metas=allow_metas) + # If the discriminator names an enum type, then all members # of 'data' must also be members of the enum type. if enum_define: @@ -387,15 +454,11 @@ def check_alternate(expr, expr_info): values[c_key] = key # Ensure alternates have no type conflicts. - if isinstance(value, list): - raise QAPIExprError(expr_info, - "Alternate '%s' member '%s' must " - "not be array type" % (name, key)) + check_type(expr_info, "Member '%s' of alternate '%s'" % (key, name), + value, + allow_metas=['built-in', 'union', 'struct', 'enum']) qtype = find_alternate_member_qtype(value) - if not qtype: - raise QAPIExprError(expr_info, - "Alternate '%s' member '%s' has " - "invalid type '%s'" % (name, key, value)) + assert qtype if qtype in types_seen: raise QAPIExprError(expr_info, "Alternate '%s' member '%s' can't " @@ -423,6 +486,15 @@ def check_enum(expr, expr_info): % (name, member, values[key])) values[key] = member +def check_struct(expr, expr_info): + name = expr['type'] + members = expr['data'] + + check_type(expr_info, "'data' for type '%s'" % name, members, + allow_dict=True) + check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'), + allow_metas=['struct']) + def check_exprs(schema): for expr_elem in schema.exprs: expr = expr_elem['expr'] @@ -434,8 +506,14 @@ def check_exprs(schema): check_union(expr, info) elif expr.has_key('alternate'): check_alternate(expr, info) + elif expr.has_key('type'): + check_struct(expr, info) + elif expr.has_key('command'): + check_command(expr, info) elif expr.has_key('event'): check_event(expr, info) + else: + assert False, 'unexpected meta type' def check_keys(expr_elem, meta, required, optional=[]): expr = expr_elem['expr'] diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err index e2a5fc29bf..7b930c64ab 100644 --- a/tests/qapi-schema/alternate-array.err +++ b/tests/qapi-schema/alternate-array.err @@ -1 +1 @@ -tests/qapi-schema/alternate-array.json:5: Alternate 'Alt' member 'two' must not be array type +tests/qapi-schema/alternate-array.json:5: Member 'two' of alternate 'Alt' cannot be an array diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err index 00b05c601e..4d1187e60e 100644 --- a/tests/qapi-schema/alternate-nested.err +++ b/tests/qapi-schema/alternate-nested.err @@ -1 +1 @@ -tests/qapi-schema/alternate-nested.json:4: Alternate 'Alt2' member 'nested' has invalid type 'Alt1' +tests/qapi-schema/alternate-nested.json:4: Member 'nested' of alternate 'Alt2' cannot use alternate type 'Alt1' diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err index 7af1b4c569..dea45dc730 100644 --- a/tests/qapi-schema/alternate-unknown.err +++ b/tests/qapi-schema/alternate-unknown.err @@ -1 +1 @@ -tests/qapi-schema/alternate-unknown.json:2: Alternate 'Alt' member 'unknown' has invalid type 'MissingType' +tests/qapi-schema/alternate-unknown.json:2: Member 'unknown' of alternate 'Alt' uses unknown type 'MissingType' diff --git a/tests/qapi-schema/bad-base.err b/tests/qapi-schema/bad-base.err index e69de29bb2..f398bbb7d3 100644 --- a/tests/qapi-schema/bad-base.err +++ b/tests/qapi-schema/bad-base.err @@ -0,0 +1 @@ +tests/qapi-schema/bad-base.json:3: 'base' for type 'MyType' cannot use union type 'Union' diff --git a/tests/qapi-schema/bad-base.exit b/tests/qapi-schema/bad-base.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/bad-base.exit +++ b/tests/qapi-schema/bad-base.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/bad-base.json b/tests/qapi-schema/bad-base.json index de964a0e80..a6904706ad 100644 --- a/tests/qapi-schema/bad-base.json +++ b/tests/qapi-schema/bad-base.json @@ -1,3 +1,3 @@ -# FIXME: we should reject a base that is not a struct +# we reject a base that is not a struct { 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } } { 'type': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } } diff --git a/tests/qapi-schema/bad-base.out b/tests/qapi-schema/bad-base.out index 91d12fcf6d..e69de29bb2 100644 --- a/tests/qapi-schema/bad-base.out +++ b/tests/qapi-schema/bad-base.out @@ -1,4 +0,0 @@ -[OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]), - OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])] -[{'enum_name': 'UnionKind', 'enum_values': None}] -[OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])] diff --git a/tests/qapi-schema/bad-data.err b/tests/qapi-schema/bad-data.err index e69de29bb2..8523ac4f46 100644 --- a/tests/qapi-schema/bad-data.err +++ b/tests/qapi-schema/bad-data.err @@ -0,0 +1 @@ +tests/qapi-schema/bad-data.json:2: 'data' for command 'oops' cannot be an array diff --git a/tests/qapi-schema/bad-data.exit b/tests/qapi-schema/bad-data.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/bad-data.exit +++ b/tests/qapi-schema/bad-data.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/bad-data.json b/tests/qapi-schema/bad-data.json index ff1616dd96..832eeb76f4 100644 --- a/tests/qapi-schema/bad-data.json +++ b/tests/qapi-schema/bad-data.json @@ -1,2 +1,2 @@ -# FIXME: we should ensure 'data' is a dictionary for all but enums +# we ensure 'data' is a dictionary for all but enums { 'command': 'oops', 'data': [ ] } diff --git a/tests/qapi-schema/bad-data.out b/tests/qapi-schema/bad-data.out index 67802be93c..e69de29bb2 100644 --- a/tests/qapi-schema/bad-data.out +++ b/tests/qapi-schema/bad-data.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('data', [])])] -[] -[] diff --git a/tests/qapi-schema/data-array-empty.err b/tests/qapi-schema/data-array-empty.err index e69de29bb2..f713f14893 100644 --- a/tests/qapi-schema/data-array-empty.err +++ b/tests/qapi-schema/data-array-empty.err @@ -0,0 +1 @@ +tests/qapi-schema/data-array-empty.json:2: Member 'empty' of 'data' for command 'oops': array type must contain single type name diff --git a/tests/qapi-schema/data-array-empty.exit b/tests/qapi-schema/data-array-empty.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/data-array-empty.exit +++ b/tests/qapi-schema/data-array-empty.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/data-array-empty.json b/tests/qapi-schema/data-array-empty.json index edb543a75d..652dcfb24a 100644 --- a/tests/qapi-schema/data-array-empty.json +++ b/tests/qapi-schema/data-array-empty.json @@ -1,2 +1,2 @@ -# FIXME: we should reject an array for data if it does not contain a known type +# we reject an array for data if it does not contain a known type { 'command': 'oops', 'data': { 'empty': [ ] } } diff --git a/tests/qapi-schema/data-array-empty.out b/tests/qapi-schema/data-array-empty.out index 6f25c9e18a..e69de29bb2 100644 --- a/tests/qapi-schema/data-array-empty.out +++ b/tests/qapi-schema/data-array-empty.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('data', OrderedDict([('empty', [])]))])] -[] -[] diff --git a/tests/qapi-schema/data-array-unknown.err b/tests/qapi-schema/data-array-unknown.err index e69de29bb2..8b731bbcc8 100644 --- a/tests/qapi-schema/data-array-unknown.err +++ b/tests/qapi-schema/data-array-unknown.err @@ -0,0 +1 @@ +tests/qapi-schema/data-array-unknown.json:2: Member 'array' of 'data' for command 'oops' uses unknown type 'array of NoSuchType' diff --git a/tests/qapi-schema/data-array-unknown.exit b/tests/qapi-schema/data-array-unknown.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/data-array-unknown.exit +++ b/tests/qapi-schema/data-array-unknown.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/data-array-unknown.json b/tests/qapi-schema/data-array-unknown.json index 20cd3c0c30..6f3e883315 100644 --- a/tests/qapi-schema/data-array-unknown.json +++ b/tests/qapi-schema/data-array-unknown.json @@ -1,2 +1,2 @@ -# FIXME: we should reject an array for data if it does not contain a known type +# we reject an array for data if it does not contain a known type { 'command': 'oops', 'data': { 'array': [ 'NoSuchType' ] } } diff --git a/tests/qapi-schema/data-array-unknown.out b/tests/qapi-schema/data-array-unknown.out index 4314ab5dfa..e69de29bb2 100644 --- a/tests/qapi-schema/data-array-unknown.out +++ b/tests/qapi-schema/data-array-unknown.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('data', OrderedDict([('array', ['NoSuchType'])]))])] -[] -[] diff --git a/tests/qapi-schema/data-int.err b/tests/qapi-schema/data-int.err index e69de29bb2..1a9b077c06 100644 --- a/tests/qapi-schema/data-int.err +++ b/tests/qapi-schema/data-int.err @@ -0,0 +1 @@ +tests/qapi-schema/data-int.json:2: 'data' for command 'oops' cannot use built-in type 'int' diff --git a/tests/qapi-schema/data-int.exit b/tests/qapi-schema/data-int.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/data-int.exit +++ b/tests/qapi-schema/data-int.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/data-int.json b/tests/qapi-schema/data-int.json index 37916e09a9..a334d92e8c 100644 --- a/tests/qapi-schema/data-int.json +++ b/tests/qapi-schema/data-int.json @@ -1,2 +1,2 @@ -# FIXME: we should reject commands where data is not an array or complex type +# we reject commands where data is not an array or complex type { 'command': 'oops', 'data': 'int' } diff --git a/tests/qapi-schema/data-int.out b/tests/qapi-schema/data-int.out index e589a4ff92..e69de29bb2 100644 --- a/tests/qapi-schema/data-int.out +++ b/tests/qapi-schema/data-int.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('data', 'int')])] -[] -[] diff --git a/tests/qapi-schema/data-member-array-bad.err b/tests/qapi-schema/data-member-array-bad.err index e69de29bb2..2c072d5986 100644 --- a/tests/qapi-schema/data-member-array-bad.err +++ b/tests/qapi-schema/data-member-array-bad.err @@ -0,0 +1 @@ +tests/qapi-schema/data-member-array-bad.json:2: Member 'member' of 'data' for command 'oops': array type must contain single type name diff --git a/tests/qapi-schema/data-member-array-bad.exit b/tests/qapi-schema/data-member-array-bad.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/data-member-array-bad.exit +++ b/tests/qapi-schema/data-member-array-bad.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/data-member-array-bad.json b/tests/qapi-schema/data-member-array-bad.json index c954af13ee..b2ff144ec6 100644 --- a/tests/qapi-schema/data-member-array-bad.json +++ b/tests/qapi-schema/data-member-array-bad.json @@ -1,2 +1,2 @@ -# FIXME: we should reject data if it does not contain a valid array type +# we reject data if it does not contain a valid array type { 'command': 'oops', 'data': { 'member': [ { 'nested': 'str' } ] } } diff --git a/tests/qapi-schema/data-member-array-bad.out b/tests/qapi-schema/data-member-array-bad.out index 0e00c4171c..e69de29bb2 100644 --- a/tests/qapi-schema/data-member-array-bad.out +++ b/tests/qapi-schema/data-member-array-bad.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', [OrderedDict([('nested', 'str')])])]))])] -[] -[] diff --git a/tests/qapi-schema/data-member-unknown.err b/tests/qapi-schema/data-member-unknown.err index e69de29bb2..ab905db802 100644 --- a/tests/qapi-schema/data-member-unknown.err +++ b/tests/qapi-schema/data-member-unknown.err @@ -0,0 +1 @@ +tests/qapi-schema/data-member-unknown.json:2: Member 'member' of 'data' for command 'oops' uses unknown type 'NoSuchType' diff --git a/tests/qapi-schema/data-member-unknown.exit b/tests/qapi-schema/data-member-unknown.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/data-member-unknown.exit +++ b/tests/qapi-schema/data-member-unknown.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/data-member-unknown.json b/tests/qapi-schema/data-member-unknown.json index 40e6252c1d..342a41ec90 100644 --- a/tests/qapi-schema/data-member-unknown.json +++ b/tests/qapi-schema/data-member-unknown.json @@ -1,2 +1,2 @@ -# FIXME: we should reject data if it does not contain a known type +# we reject data if it does not contain a known type { 'command': 'oops', 'data': { 'member': 'NoSuchType' } } diff --git a/tests/qapi-schema/data-member-unknown.out b/tests/qapi-schema/data-member-unknown.out index 36252a5f73..e69de29bb2 100644 --- a/tests/qapi-schema/data-member-unknown.out +++ b/tests/qapi-schema/data-member-unknown.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', 'NoSuchType')]))])] -[] -[] diff --git a/tests/qapi-schema/data-unknown.err b/tests/qapi-schema/data-unknown.err index e69de29bb2..5b07277a95 100644 --- a/tests/qapi-schema/data-unknown.err +++ b/tests/qapi-schema/data-unknown.err @@ -0,0 +1 @@ +tests/qapi-schema/data-unknown.json:2: 'data' for command 'oops' uses unknown type 'NoSuchType' diff --git a/tests/qapi-schema/data-unknown.exit b/tests/qapi-schema/data-unknown.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/data-unknown.exit +++ b/tests/qapi-schema/data-unknown.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/data-unknown.json b/tests/qapi-schema/data-unknown.json index 776bd342e5..32aba43b3f 100644 --- a/tests/qapi-schema/data-unknown.json +++ b/tests/qapi-schema/data-unknown.json @@ -1,2 +1,2 @@ -# FIXME: we should reject data if it does not contain a known type +# we reject data if it does not contain a known type { 'command': 'oops', 'data': 'NoSuchType' } diff --git a/tests/qapi-schema/data-unknown.out b/tests/qapi-schema/data-unknown.out index 2c60726881..e69de29bb2 100644 --- a/tests/qapi-schema/data-unknown.out +++ b/tests/qapi-schema/data-unknown.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('data', 'NoSuchType')])] -[] -[] diff --git a/tests/qapi-schema/flat-union-int-branch.err b/tests/qapi-schema/flat-union-int-branch.err index e69de29bb2..faf01573b7 100644 --- a/tests/qapi-schema/flat-union-int-branch.err +++ b/tests/qapi-schema/flat-union-int-branch.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-int-branch.json:8: Member 'value1' of union 'TestUnion' cannot use built-in type 'int' diff --git a/tests/qapi-schema/flat-union-int-branch.exit b/tests/qapi-schema/flat-union-int-branch.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/flat-union-int-branch.exit +++ b/tests/qapi-schema/flat-union-int-branch.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json index 3543215436..d373131653 100644 --- a/tests/qapi-schema/flat-union-int-branch.json +++ b/tests/qapi-schema/flat-union-int-branch.json @@ -1,4 +1,4 @@ -# FIXME: we should require flat union branches to be a complex type +# we require flat union branches to be a complex type { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } { 'type': 'Base', diff --git a/tests/qapi-schema/flat-union-int-branch.out b/tests/qapi-schema/flat-union-int-branch.out index cd40e6c198..e69de29bb2 100644 --- a/tests/qapi-schema/flat-union-int-branch.out +++ b/tests/qapi-schema/flat-union-int-branch.out @@ -1,7 +0,0 @@ -[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), - OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'int'), ('value2', 'TestTypeB')]))])] -[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}] -[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] diff --git a/tests/qapi-schema/returns-array-bad.err b/tests/qapi-schema/returns-array-bad.err index e69de29bb2..138095ccde 100644 --- a/tests/qapi-schema/returns-array-bad.err +++ b/tests/qapi-schema/returns-array-bad.err @@ -0,0 +1 @@ +tests/qapi-schema/returns-array-bad.json:2: 'returns' for command 'oops': array type must contain single type name diff --git a/tests/qapi-schema/returns-array-bad.exit b/tests/qapi-schema/returns-array-bad.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/returns-array-bad.exit +++ b/tests/qapi-schema/returns-array-bad.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/returns-array-bad.json b/tests/qapi-schema/returns-array-bad.json index 14882c1e45..09b0b1f182 100644 --- a/tests/qapi-schema/returns-array-bad.json +++ b/tests/qapi-schema/returns-array-bad.json @@ -1,2 +1,2 @@ -# FIXME: we should reject an array return that is not a single type +# we reject an array return that is not a single type { 'command': 'oops', 'returns': [ 'str', 'str' ] } diff --git a/tests/qapi-schema/returns-array-bad.out b/tests/qapi-schema/returns-array-bad.out index eccad55219..e69de29bb2 100644 --- a/tests/qapi-schema/returns-array-bad.out +++ b/tests/qapi-schema/returns-array-bad.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('returns', ['str', 'str'])])] -[] -[] diff --git a/tests/qapi-schema/returns-unknown.err b/tests/qapi-schema/returns-unknown.err index e69de29bb2..1f43e3ac9f 100644 --- a/tests/qapi-schema/returns-unknown.err +++ b/tests/qapi-schema/returns-unknown.err @@ -0,0 +1 @@ +tests/qapi-schema/returns-unknown.json:2: 'returns' for command 'oops' uses unknown type 'NoSuchType' diff --git a/tests/qapi-schema/returns-unknown.exit b/tests/qapi-schema/returns-unknown.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/returns-unknown.exit +++ b/tests/qapi-schema/returns-unknown.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/returns-unknown.json b/tests/qapi-schema/returns-unknown.json index 61f20ebb02..25bd498bff 100644 --- a/tests/qapi-schema/returns-unknown.json +++ b/tests/qapi-schema/returns-unknown.json @@ -1,2 +1,2 @@ -# FIXME: we should reject returns if it does not contain a known type +# we reject returns if it does not contain a known type { 'command': 'oops', 'returns': 'NoSuchType' } diff --git a/tests/qapi-schema/returns-unknown.out b/tests/qapi-schema/returns-unknown.out index a482c837ce..e69de29bb2 100644 --- a/tests/qapi-schema/returns-unknown.out +++ b/tests/qapi-schema/returns-unknown.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'oops'), ('returns', 'NoSuchType')])] -[] -[] diff --git a/tests/qapi-schema/union-unknown.err b/tests/qapi-schema/union-unknown.err index e69de29bb2..54fe456f9c 100644 --- a/tests/qapi-schema/union-unknown.err +++ b/tests/qapi-schema/union-unknown.err @@ -0,0 +1 @@ +tests/qapi-schema/union-unknown.json:2: Member 'unknown' of union 'Union' uses unknown type 'MissingType' diff --git a/tests/qapi-schema/union-unknown.exit b/tests/qapi-schema/union-unknown.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/union-unknown.exit +++ b/tests/qapi-schema/union-unknown.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/union-unknown.json b/tests/qapi-schema/union-unknown.json index 258f1d3821..aa7e8143d8 100644 --- a/tests/qapi-schema/union-unknown.json +++ b/tests/qapi-schema/union-unknown.json @@ -1,3 +1,3 @@ -# FIXME: we should reject a union with unknown type in branch +# we reject a union with unknown type in branch { 'union': 'Union', 'data': { 'unknown': 'MissingType' } } diff --git a/tests/qapi-schema/union-unknown.out b/tests/qapi-schema/union-unknown.out index 8223dcf2c0..e69de29bb2 100644 --- a/tests/qapi-schema/union-unknown.out +++ b/tests/qapi-schema/union-unknown.out @@ -1,3 +0,0 @@ -[OrderedDict([('union', 'Union'), ('data', OrderedDict([('unknown', 'MissingType')]))])] -[{'enum_name': 'UnionKind', 'enum_values': None}] -[] From c9e0a798691d8c45747b082206e789c8f50523c9 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:22 -0600 Subject: [PATCH 25/40] qapi: Require valid names Previous commits demonstrated that the generator overlooked various bad naming situations: - types, commands, and events need a valid name - enum members must be valid names, when combined with prefix - union and alternate branches cannot be marked optional Valid upstream names match [a-zA-Z][a-zA-Z0-9_-]*; valid downstream names match __[a-zA-Z][a-zA-Z0-9._-]*. Enumerations match the weaker [a-zA-Z0-9._-]+ (in part thanks to QKeyCode picking an enum that starts with a digit, which we can't change now due to backwards compatibility). Rather than call out three separate regex, this patch just uses a broader combination that allows both upstream and downstream names, as well as a small hack that realizes that any enum name is merely a suffix to an already valid name prefix (that is, any enum name is valid if prepending _ fits the normal rules). We could reject new enumeration names beginning with a digit by whitelisting existing exceptions. We could also be stricter about the distinction between upstream names (no leading underscore, no use of dot) and downstream (mandatory leading double underscore), but it is probably not worth the bother. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 63 ++++++++++++++----- tests/qapi-schema/bad-ident.err | 1 + tests/qapi-schema/bad-ident.exit | 2 +- tests/qapi-schema/bad-ident.json | 2 +- tests/qapi-schema/bad-ident.out | 3 - tests/qapi-schema/enum-bad-name.err | 1 + tests/qapi-schema/enum-bad-name.exit | 2 +- tests/qapi-schema/enum-bad-name.json | 2 +- tests/qapi-schema/enum-bad-name.out | 3 - tests/qapi-schema/enum-dict-member.err | 2 +- .../flat-union-bad-discriminator.err | 2 +- .../flat-union-optional-discriminator.err | 1 + .../flat-union-optional-discriminator.exit | 2 +- .../flat-union-optional-discriminator.json | 2 +- .../flat-union-optional-discriminator.out | 7 --- tests/qapi-schema/union-optional-branch.err | 1 + tests/qapi-schema/union-optional-branch.exit | 2 +- tests/qapi-schema/union-optional-branch.json | 2 +- tests/qapi-schema/union-optional-branch.out | 3 - 19 files changed, 60 insertions(+), 43 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 3c33e4e46c..5bc32e311d 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -276,8 +276,31 @@ def discriminator_find_enum_define(expr): return find_enum(discriminator_type) +valid_name = re.compile('^[a-zA-Z_][a-zA-Z0-9_.-]*$') +def check_name(expr_info, source, name, allow_optional = False, + enum_member = False): + global valid_name + membername = name + + if not isinstance(name, str): + raise QAPIExprError(expr_info, + "%s requires a string name" % source) + if name.startswith('*'): + membername = name[1:] + if not allow_optional: + raise QAPIExprError(expr_info, + "%s does not allow optional name '%s'" + % (source, name)) + # Enum members can start with a digit, because the generated C + # code always prefixes it with the enum name + if enum_member: + membername = '_' + membername + if not valid_name.match(membername): + raise QAPIExprError(expr_info, + "%s uses invalid name '%s'" % (source, name)) + def check_type(expr_info, source, value, allow_array = False, - allow_dict = False, allow_metas = []): + allow_dict = False, allow_optional = False, allow_metas = []): global all_names orig_value = value @@ -319,18 +342,21 @@ def check_type(expr_info, source, value, allow_array = False, raise QAPIExprError(expr_info, "%s should be a type name" % source) for (key, arg) in value.items(): + check_name(expr_info, "Member of %s" % source, key, + allow_optional=allow_optional) check_type(expr_info, "Member '%s' of %s" % (key, source), arg, - allow_array=True, allow_dict=True, + allow_array=True, allow_dict=True, allow_optional=True, allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']) def check_command(expr, expr_info): name = expr['command'] check_type(expr_info, "'data' for command '%s'" % name, - expr.get('data'), allow_dict=True, + expr.get('data'), allow_dict=True, allow_optional=True, allow_metas=['union', 'struct']) check_type(expr_info, "'returns' for command '%s'" % name, expr.get('returns'), allow_array=True, allow_dict=True, + allow_optional=True, allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']) @@ -343,7 +369,7 @@ def check_event(expr, expr_info): raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created") events.append(name) check_type(expr_info, "'data' for event '%s'" % name, - expr.get('data'), allow_dict=True, + expr.get('data'), allow_dict=True, allow_optional=True, allow_metas=['union', 'struct']) if params: for argname, argentry, optional, structured in parse_args(params): @@ -392,12 +418,10 @@ def check_union(expr, expr_info): "Base '%s' is not a valid type" % base) - # The value of member 'discriminator' must name a member of the - # base type. - if not isinstance(discriminator, str): - raise QAPIExprError(expr_info, - "Flat union '%s' discriminator must be a string" - % name) + # The value of member 'discriminator' must name a non-optional + # member of the base type. + check_name(expr_info, "Discriminator of flat union '%s'" % name, + discriminator) discriminator_type = base_fields.get(discriminator) if not discriminator_type: raise QAPIExprError(expr_info, @@ -414,6 +438,8 @@ def check_union(expr, expr_info): # Check every branch for (key, value) in members.items(): + check_name(expr_info, "Member of union '%s'" % name, key) + # Each value must name a known type; furthermore, in flat unions, # branches must be a struct check_type(expr_info, "Member '%s' of union '%s'" % (key, name), @@ -445,6 +471,8 @@ def check_alternate(expr, expr_info): # Check every branch for (key, value) in members.items(): + check_name(expr_info, "Member of alternate '%s'" % name, key) + # Check for conflicts in the generated enum c_key = _generate_enum_string(key) if c_key in values: @@ -475,10 +503,8 @@ def check_enum(expr, expr_info): raise QAPIExprError(expr_info, "Enum '%s' requires an array for 'data'" % name) for member in members: - if not isinstance(member, str): - raise QAPIExprError(expr_info, - "Enum '%s' member '%s' is not a string" - % (name, member)) + check_name(expr_info, "Member of enum '%s'" %name, member, + enum_member=True) key = _generate_enum_string(member) if key in values: raise QAPIExprError(expr_info, @@ -491,7 +517,7 @@ def check_struct(expr, expr_info): members = expr['data'] check_type(expr_info, "'data' for type '%s'" % name, members, - allow_dict=True) + allow_dict=True, allow_optional=True) check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'), allow_metas=['struct']) @@ -682,8 +708,11 @@ def type_name(name): return c_list_type(name[0]) return name -def add_name(name, info, meta, implicit = False): +def add_name(name, info, meta, implicit = False, source = None): global all_names + if not source: + source = "'%s'" % meta + check_name(info, source, name) if name in all_names: raise QAPIExprError(info, "%s '%s' is already defined" @@ -697,7 +726,7 @@ def add_name(name, info, meta, implicit = False): def add_struct(definition, info): global struct_types name = definition['type'] - add_name(name, info, 'struct') + add_name(name, info, 'struct', source="'type'") struct_types.append(definition) def find_struct(name): diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err index e69de29bb2..42b490ce0b 100644 --- a/tests/qapi-schema/bad-ident.err +++ b/tests/qapi-schema/bad-ident.err @@ -0,0 +1 @@ +tests/qapi-schema/bad-ident.json:2: 'type' does not allow optional name '*oops' diff --git a/tests/qapi-schema/bad-ident.exit b/tests/qapi-schema/bad-ident.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/bad-ident.exit +++ b/tests/qapi-schema/bad-ident.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/bad-ident.json b/tests/qapi-schema/bad-ident.json index f139110e89..da949e8903 100644 --- a/tests/qapi-schema/bad-ident.json +++ b/tests/qapi-schema/bad-ident.json @@ -1,2 +1,2 @@ -# FIXME: we should reject creating a type name with bad name +# we reject creating a type name with bad name { 'type': '*oops', 'data': { 'i': 'int' } } diff --git a/tests/qapi-schema/bad-ident.out b/tests/qapi-schema/bad-ident.out index 165e34645d..e69de29bb2 100644 --- a/tests/qapi-schema/bad-ident.out +++ b/tests/qapi-schema/bad-ident.out @@ -1,3 +0,0 @@ -[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])] -[] -[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])] diff --git a/tests/qapi-schema/enum-bad-name.err b/tests/qapi-schema/enum-bad-name.err index e69de29bb2..9c3c1002b7 100644 --- a/tests/qapi-schema/enum-bad-name.err +++ b/tests/qapi-schema/enum-bad-name.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-bad-name.json:2: Member of enum 'MyEnum' uses invalid name 'not^possible' diff --git a/tests/qapi-schema/enum-bad-name.exit b/tests/qapi-schema/enum-bad-name.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/enum-bad-name.exit +++ b/tests/qapi-schema/enum-bad-name.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/enum-bad-name.json b/tests/qapi-schema/enum-bad-name.json index 0c32448a3e..8506562b31 100644 --- a/tests/qapi-schema/enum-bad-name.json +++ b/tests/qapi-schema/enum-bad-name.json @@ -1,2 +1,2 @@ -# FIXME: we should ensure all enum names can map to C +# we ensure all enum names can map to C { 'enum': 'MyEnum', 'data': [ 'not^possible' ] } diff --git a/tests/qapi-schema/enum-bad-name.out b/tests/qapi-schema/enum-bad-name.out index d24ea49a0f..e69de29bb2 100644 --- a/tests/qapi-schema/enum-bad-name.out +++ b/tests/qapi-schema/enum-bad-name.out @@ -1,3 +0,0 @@ -[OrderedDict([('enum', 'MyEnum'), ('data', ['not^possible'])])] -[{'enum_name': 'MyEnum', 'enum_values': ['not^possible']}] -[] diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err index 7e966a8aae..8ca146ea59 100644 --- a/tests/qapi-schema/enum-dict-member.err +++ b/tests/qapi-schema/enum-dict-member.err @@ -1 +1 @@ -tests/qapi-schema/enum-dict-member.json:2: Enum 'MyEnum' member 'OrderedDict([('value', 'str')])' is not a string +tests/qapi-schema/enum-dict-member.json:2: Member of enum 'MyEnum' requires a string name diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err index 507e2bab4a..c38cc8e4df 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.err +++ b/tests/qapi-schema/flat-union-bad-discriminator.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-bad-discriminator.json:11: Flat union 'TestUnion' discriminator must be a string +tests/qapi-schema/flat-union-bad-discriminator.json:11: Discriminator of flat union 'TestUnion' requires a string name diff --git a/tests/qapi-schema/flat-union-optional-discriminator.err b/tests/qapi-schema/flat-union-optional-discriminator.err index e69de29bb2..aaabedb3bd 100644 --- a/tests/qapi-schema/flat-union-optional-discriminator.err +++ b/tests/qapi-schema/flat-union-optional-discriminator.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-optional-discriminator.json:6: Discriminator of flat union 'MyUnion' does not allow optional name '*switch' diff --git a/tests/qapi-schema/flat-union-optional-discriminator.exit b/tests/qapi-schema/flat-union-optional-discriminator.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/flat-union-optional-discriminator.exit +++ b/tests/qapi-schema/flat-union-optional-discriminator.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json index ece0d31fb3..25ce0e6612 100644 --- a/tests/qapi-schema/flat-union-optional-discriminator.json +++ b/tests/qapi-schema/flat-union-optional-discriminator.json @@ -1,4 +1,4 @@ -# FIXME: we should require the discriminator to be non-optional +# we require the discriminator to be non-optional { 'enum': 'Enum', 'data': [ 'one', 'two' ] } { 'type': 'Base', 'data': { '*switch': 'Enum' } } diff --git a/tests/qapi-schema/flat-union-optional-discriminator.out b/tests/qapi-schema/flat-union-optional-discriminator.out index bb7db00902..e69de29bb2 100644 --- a/tests/qapi-schema/flat-union-optional-discriminator.out +++ b/tests/qapi-schema/flat-union-optional-discriminator.out @@ -1,7 +0,0 @@ -[OrderedDict([('enum', 'Enum'), ('data', ['one', 'two'])]), - OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]), - OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', '*switch'), ('data', OrderedDict([('one', 'Branch'), ('two', 'Branch')]))])] -[{'enum_name': 'Enum', 'enum_values': ['one', 'two']}] -[OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]), - OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))])] diff --git a/tests/qapi-schema/union-optional-branch.err b/tests/qapi-schema/union-optional-branch.err index e69de29bb2..3ada1334dc 100644 --- a/tests/qapi-schema/union-optional-branch.err +++ b/tests/qapi-schema/union-optional-branch.err @@ -0,0 +1 @@ +tests/qapi-schema/union-optional-branch.json:2: Member of union 'Union' does not allow optional name '*a' diff --git a/tests/qapi-schema/union-optional-branch.exit b/tests/qapi-schema/union-optional-branch.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/union-optional-branch.exit +++ b/tests/qapi-schema/union-optional-branch.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/union-optional-branch.json b/tests/qapi-schema/union-optional-branch.json index c513db72d4..591615fc68 100644 --- a/tests/qapi-schema/union-optional-branch.json +++ b/tests/qapi-schema/union-optional-branch.json @@ -1,2 +1,2 @@ -# FIXME: union branches cannot be optional +# union branches cannot be optional { 'union': 'Union', 'data': { '*a': 'int', 'b': 'str' } } diff --git a/tests/qapi-schema/union-optional-branch.out b/tests/qapi-schema/union-optional-branch.out index b03b5d2b4f..e69de29bb2 100644 --- a/tests/qapi-schema/union-optional-branch.out +++ b/tests/qapi-schema/union-optional-branch.out @@ -1,3 +0,0 @@ -[OrderedDict([('union', 'Union'), ('data', OrderedDict([('*a', 'int'), ('b', 'str')]))])] -[{'enum_name': 'UnionKind', 'enum_values': None}] -[] From 10d4d997f86cf2a4ce89145df5658952d5722e56 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:23 -0600 Subject: [PATCH 26/40] qapi: Whitelist commands that don't return dictionary ...or an array of dictionaries. Although we have to cater to existing commands, returning a non-dictionary means the command is not extensible (no new name/value pairs can be added if more information must be returned in parallel). By making the whitelist explicit, any new command that falls foul of this practice will have to be self-documenting, which will encourage developers to either justify the action or rework the design to use a dictionary after all. It's a little bit sloppy that we share a single whitelist among three clients (it's too permissive for each). If this is a problem, a future patch could tighten things by having the generator take the whitelist as an argument (as in scripts/qapi-commands.py --legacy-returns=...), or by having the generator output C code that requires explicit use of the whitelist (as in: #ifndef FROBNICATE_LEGACY_RETURN_OK # error Command 'frobnicate' should return a dictionary #endif then having the callers define appropriate macros). But until we need such fine-grained separation (if ever), this patch does the job just fine. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 31 +++++++++++++++++++++--- tests/qapi-schema/returns-alternate.err | 1 + tests/qapi-schema/returns-alternate.exit | 2 +- tests/qapi-schema/returns-alternate.json | 2 +- tests/qapi-schema/returns-alternate.out | 4 --- tests/qapi-schema/returns-int.json | 3 ++- tests/qapi-schema/returns-int.out | 2 +- tests/qapi-schema/returns-whitelist.err | 1 + tests/qapi-schema/returns-whitelist.exit | 2 +- tests/qapi-schema/returns-whitelist.json | 2 +- tests/qapi-schema/returns-whitelist.out | 7 ------ 11 files changed, 37 insertions(+), 20 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 5bc32e311d..2402d053e4 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -32,6 +32,30 @@ builtin_types = { 'size': 'QTYPE_QINT', } +# Whitelist of commands allowed to return a non-dictionary +returns_whitelist = [ + # From QMP: + 'human-monitor-command', + 'query-migrate-cache-size', + 'query-tpm-models', + 'query-tpm-types', + 'ringbuf-read', + + # From QGA: + 'guest-file-open', + 'guest-fsfreeze-freeze', + 'guest-fsfreeze-freeze-list', + 'guest-fsfreeze-status', + 'guest-fsfreeze-thaw', + 'guest-get-time', + 'guest-set-vcpus', + 'guest-sync', + 'guest-sync-delimited', + + # From qapi-schema-test: + 'user_def_cmd3', +] + enum_types = [] struct_types = [] union_types = [] @@ -354,11 +378,12 @@ def check_command(expr, expr_info): check_type(expr_info, "'data' for command '%s'" % name, expr.get('data'), allow_dict=True, allow_optional=True, allow_metas=['union', 'struct']) + returns_meta = ['union', 'struct'] + if name in returns_whitelist: + returns_meta += ['built-in', 'alternate', 'enum'] check_type(expr_info, "'returns' for command '%s'" % name, expr.get('returns'), allow_array=True, allow_dict=True, - allow_optional=True, - allow_metas=['built-in', 'union', 'alternate', 'struct', - 'enum']) + allow_optional=True, allow_metas=returns_meta) def check_event(expr, expr_info): global events diff --git a/tests/qapi-schema/returns-alternate.err b/tests/qapi-schema/returns-alternate.err index e69de29bb2..dfbb419cac 100644 --- a/tests/qapi-schema/returns-alternate.err +++ b/tests/qapi-schema/returns-alternate.err @@ -0,0 +1 @@ +tests/qapi-schema/returns-alternate.json:3: 'returns' for command 'oops' cannot use alternate type 'Alt' diff --git a/tests/qapi-schema/returns-alternate.exit b/tests/qapi-schema/returns-alternate.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/returns-alternate.exit +++ b/tests/qapi-schema/returns-alternate.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/returns-alternate.json b/tests/qapi-schema/returns-alternate.json index b3b91fd3d4..972390c06b 100644 --- a/tests/qapi-schema/returns-alternate.json +++ b/tests/qapi-schema/returns-alternate.json @@ -1,3 +1,3 @@ -# FIXME: we should reject returns if it is an alternate type +# we reject returns if it is an alternate type { 'alternate': 'Alt', 'data': { 'a': 'int', 'b': 'str' } } { 'command': 'oops', 'returns': 'Alt' } diff --git a/tests/qapi-schema/returns-alternate.out b/tests/qapi-schema/returns-alternate.out index 8a03ed3adf..e69de29bb2 100644 --- a/tests/qapi-schema/returns-alternate.out +++ b/tests/qapi-schema/returns-alternate.out @@ -1,4 +0,0 @@ -[OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]), - OrderedDict([('command', 'oops'), ('returns', 'Alt')])] -[{'enum_name': 'AltKind', 'enum_values': None}] -[] diff --git a/tests/qapi-schema/returns-int.json b/tests/qapi-schema/returns-int.json index 7888fb1b08..870ec6366b 100644 --- a/tests/qapi-schema/returns-int.json +++ b/tests/qapi-schema/returns-int.json @@ -1,2 +1,3 @@ # It is okay (although not extensible) to return a non-dictionary -{ 'command': 'okay', 'returns': 'int' } +# But to make it work, the name must be in a whitelist +{ 'command': 'guest-get-time', 'returns': 'int' } diff --git a/tests/qapi-schema/returns-int.out b/tests/qapi-schema/returns-int.out index 36b00a9f3b..70b3ac5e6f 100644 --- a/tests/qapi-schema/returns-int.out +++ b/tests/qapi-schema/returns-int.out @@ -1,3 +1,3 @@ -[OrderedDict([('command', 'okay'), ('returns', 'int')])] +[OrderedDict([('command', 'guest-get-time'), ('returns', 'int')])] [] [] diff --git a/tests/qapi-schema/returns-whitelist.err b/tests/qapi-schema/returns-whitelist.err index e69de29bb2..a41f019a52 100644 --- a/tests/qapi-schema/returns-whitelist.err +++ b/tests/qapi-schema/returns-whitelist.err @@ -0,0 +1 @@ +tests/qapi-schema/returns-whitelist.json:10: 'returns' for command 'no-way-this-will-get-whitelisted' cannot use built-in type 'array of int' diff --git a/tests/qapi-schema/returns-whitelist.exit b/tests/qapi-schema/returns-whitelist.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/returns-whitelist.exit +++ b/tests/qapi-schema/returns-whitelist.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/returns-whitelist.json b/tests/qapi-schema/returns-whitelist.json index 8328563b4e..e8b3cea396 100644 --- a/tests/qapi-schema/returns-whitelist.json +++ b/tests/qapi-schema/returns-whitelist.json @@ -1,4 +1,4 @@ -# FIXME: we should enforce that 'returns' be a dict or array of dict unless whitelisted +# we enforce that 'returns' be a dict or array of dict unless whitelisted { 'command': 'human-monitor-command', 'data': {'command-line': 'str', '*cpu-index': 'int'}, 'returns': 'str' } diff --git a/tests/qapi-schema/returns-whitelist.out b/tests/qapi-schema/returns-whitelist.out index 2adcd8ba10..e69de29bb2 100644 --- a/tests/qapi-schema/returns-whitelist.out +++ b/tests/qapi-schema/returns-whitelist.out @@ -1,7 +0,0 @@ -[OrderedDict([('command', 'human-monitor-command'), ('data', OrderedDict([('command-line', 'str'), ('*cpu-index', 'int')])), ('returns', 'str')]), - OrderedDict([('enum', 'TpmModel'), ('data', ['tpm-tis'])]), - OrderedDict([('command', 'query-tpm-models'), ('returns', ['TpmModel'])]), - OrderedDict([('command', 'guest-get-time'), ('returns', 'int')]), - OrderedDict([('command', 'no-way-this-will-get-whitelisted'), ('returns', ['int'])])] -[{'enum_name': 'TpmModel', 'enum_values': ['tpm-tis']}] -[] From 2cbf09925ad45401673a79ab77f67de2f04a826c Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:24 -0600 Subject: [PATCH 27/40] qapi: More rigorous checking for type safety bypass Now that we have a way to validate every type, we can also be stricter about enforcing that callers that want to bypass type safety in generated code. Prior to this patch, it didn't matter what value was associated with the key 'gen', but it looked odd that 'gen':'yes' could result in bypassing the generated code. These changes also enforce the changes made earlier in the series for documentation and consolidation of using '**' as the wildcard type, as well as 'gen':false as the canonical spelling for requesting type bypass. Note that 'gen':false is a one-way switch away from the default; we do not support 'gen':true (similar for 'success-response'). In practice, this doesn't matter. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 22 +++++++++++++++++----- tests/qapi-schema/type-bypass-bad-gen.err | 1 + tests/qapi-schema/type-bypass-bad-gen.exit | 2 +- tests/qapi-schema/type-bypass-bad-gen.json | 2 +- tests/qapi-schema/type-bypass-bad-gen.out | 3 --- tests/qapi-schema/type-bypass-no-gen.err | 1 + tests/qapi-schema/type-bypass-no-gen.exit | 2 +- tests/qapi-schema/type-bypass-no-gen.json | 2 +- tests/qapi-schema/type-bypass-no-gen.out | 3 --- 9 files changed, 23 insertions(+), 15 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index 2402d053e4..e391b5a649 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -324,14 +324,15 @@ def check_name(expr_info, source, name, allow_optional = False, "%s uses invalid name '%s'" % (source, name)) def check_type(expr_info, source, value, allow_array = False, - allow_dict = False, allow_optional = False, allow_metas = []): + allow_dict = False, allow_optional = False, + allow_star = False, allow_metas = []): global all_names orig_value = value if value is None: return - if value == '**': + if allow_star and value == '**': return # Check if array type for value is okay @@ -348,6 +349,10 @@ def check_type(expr_info, source, value, allow_array = False, # Check if type name for value is okay if isinstance(value, str): + if value == '**': + raise QAPIExprError(expr_info, + "%s uses '**' but did not request 'gen':false" + % source) if not value in all_names: raise QAPIExprError(expr_info, "%s uses unknown type '%s'" @@ -371,19 +376,22 @@ def check_type(expr_info, source, value, allow_array = False, check_type(expr_info, "Member '%s' of %s" % (key, source), arg, allow_array=True, allow_dict=True, allow_optional=True, allow_metas=['built-in', 'union', 'alternate', 'struct', - 'enum']) + 'enum'], allow_star=allow_star) def check_command(expr, expr_info): name = expr['command'] + allow_star = expr.has_key('gen') + check_type(expr_info, "'data' for command '%s'" % name, expr.get('data'), allow_dict=True, allow_optional=True, - allow_metas=['union', 'struct']) + allow_metas=['union', 'struct'], allow_star=allow_star) returns_meta = ['union', 'struct'] if name in returns_whitelist: returns_meta += ['built-in', 'alternate', 'enum'] check_type(expr_info, "'returns' for command '%s'" % name, expr.get('returns'), allow_array=True, allow_dict=True, - allow_optional=True, allow_metas=returns_meta) + allow_optional=True, allow_metas=returns_meta, + allow_star=allow_star) def check_event(expr, expr_info): global events @@ -579,6 +587,10 @@ def check_keys(expr_elem, meta, required, optional=[]): raise QAPIExprError(info, "Unknown key '%s' in %s '%s'" % (key, meta, name)) + if (key == 'gen' or key == 'success-response') and value != False: + raise QAPIExprError(info, + "'%s' of %s '%s' should only use false value" + % (key, meta, name)) for key in required: if not expr.has_key(key): raise QAPIExprError(info, diff --git a/tests/qapi-schema/type-bypass-bad-gen.err b/tests/qapi-schema/type-bypass-bad-gen.err index e69de29bb2..a83c3c655d 100644 --- a/tests/qapi-schema/type-bypass-bad-gen.err +++ b/tests/qapi-schema/type-bypass-bad-gen.err @@ -0,0 +1 @@ +tests/qapi-schema/type-bypass-bad-gen.json:2: 'gen' of command 'foo' should only use false value diff --git a/tests/qapi-schema/type-bypass-bad-gen.exit b/tests/qapi-schema/type-bypass-bad-gen.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/type-bypass-bad-gen.exit +++ b/tests/qapi-schema/type-bypass-bad-gen.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/type-bypass-bad-gen.json b/tests/qapi-schema/type-bypass-bad-gen.json index bb70bee085..e8dec34249 100644 --- a/tests/qapi-schema/type-bypass-bad-gen.json +++ b/tests/qapi-schema/type-bypass-bad-gen.json @@ -1,2 +1,2 @@ -# FIXME: 'gen' should only appear with value false +# 'gen' should only appear with value false { 'command': 'foo', 'gen': 'whatever' } diff --git a/tests/qapi-schema/type-bypass-bad-gen.out b/tests/qapi-schema/type-bypass-bad-gen.out index e678f2c18e..e69de29bb2 100644 --- a/tests/qapi-schema/type-bypass-bad-gen.out +++ b/tests/qapi-schema/type-bypass-bad-gen.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'foo'), ('gen', 'whatever')])] -[] -[] diff --git a/tests/qapi-schema/type-bypass-no-gen.err b/tests/qapi-schema/type-bypass-no-gen.err index e69de29bb2..20cef0a8a7 100644 --- a/tests/qapi-schema/type-bypass-no-gen.err +++ b/tests/qapi-schema/type-bypass-no-gen.err @@ -0,0 +1 @@ +tests/qapi-schema/type-bypass-no-gen.json:2: Member 'arg' of 'data' for command 'unsafe' uses '**' but did not request 'gen':false diff --git a/tests/qapi-schema/type-bypass-no-gen.exit b/tests/qapi-schema/type-bypass-no-gen.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/type-bypass-no-gen.exit +++ b/tests/qapi-schema/type-bypass-no-gen.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/type-bypass-no-gen.json b/tests/qapi-schema/type-bypass-no-gen.json index af87c191ad..4feae3719c 100644 --- a/tests/qapi-schema/type-bypass-no-gen.json +++ b/tests/qapi-schema/type-bypass-no-gen.json @@ -1,2 +1,2 @@ -# FIXME: type bypass should only work with 'gen':false +# type bypass only works with 'gen':false { 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**' } diff --git a/tests/qapi-schema/type-bypass-no-gen.out b/tests/qapi-schema/type-bypass-no-gen.out index 8b2a9ac94a..e69de29bb2 100644 --- a/tests/qapi-schema/type-bypass-no-gen.out +++ b/tests/qapi-schema/type-bypass-no-gen.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'unsafe'), ('data', OrderedDict([('arg', '**')])), ('returns', '**')])] -[] -[] From fd41dd4eae5f7ea92f10c04cb3f217727fcee91f Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:25 -0600 Subject: [PATCH 28/40] qapi: Prefer 'struct' over 'type' in generator Referring to "type" as both a meta-type (built-in, enum, union, alternate, or struct) and a specific type (the name that the schema uses for declaring structs) is confusing. The confusion is only made worse by the fact that the generator mostly already refers to struct even when dealing with expr['type']. This commit changes the generator to consistently refer to it as struct everywhere, plus a single back-compat tweak that allows accepting the existing .json files as-is, so that the meat of this change is separate from the mindless churn of that change. Fix the testsuite fallout for error messages that change, and in some cases, become more legible. Improve comments to better match our intentions where a struct (rather than any complex type) is required. Note that in some cases, an error message now refers to 'struct' while the schema still refers to 'type'; that will be cleaned up in the later commit to the schema. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi-types.py | 16 +++---- scripts/qapi-visit.py | 8 ++-- scripts/qapi.py | 46 ++++++++++++------- tests/qapi-schema/alternate-good.out | 4 +- tests/qapi-schema/bad-base.err | 2 +- tests/qapi-schema/bad-ident.err | 2 +- tests/qapi-schema/bad-type-bool.err | 2 +- tests/qapi-schema/data-member-array.out | 4 +- tests/qapi-schema/double-type.err | 2 +- tests/qapi-schema/flat-union-bad-base.json | 2 +- tests/qapi-schema/flat-union-base-star.err | 2 +- tests/qapi-schema/flat-union-base-star.json | 2 +- tests/qapi-schema/flat-union-base-union.err | 2 +- tests/qapi-schema/flat-union-base-union.json | 2 +- tests/qapi-schema/flat-union-branch-clash.out | 12 ++--- tests/qapi-schema/flat-union-inline.json | 2 +- tests/qapi-schema/flat-union-int-branch.json | 2 +- .../flat-union-invalid-discriminator.err | 2 +- .../qapi-schema/flat-union-reverse-define.out | 12 ++--- tests/qapi-schema/qapi-schema-test.out | 44 +++++++++--------- tests/qapi-schema/union-invalid-base.err | 2 +- tests/qapi-schema/unknown-expr-key.err | 2 +- 22 files changed, 93 insertions(+), 81 deletions(-) diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index 9c8d68cfb4..a429d9ec05 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -83,7 +83,7 @@ def generate_struct_fields(members): def generate_struct(expr): - structname = expr.get('type', "") + structname = expr.get('struct', "") fieldname = expr.get('field', "") members = expr['data'] base = expr.get('base') @@ -394,8 +394,8 @@ fdecl.write(guardend("QAPI_TYPES_BUILTIN_STRUCT_DECL")) for expr in exprs: ret = "\n" - if expr.has_key('type'): - ret += generate_fwd_struct(expr['type'], expr['data']) + if expr.has_key('struct'): + ret += generate_fwd_struct(expr['struct'], expr['data']) elif expr.has_key('enum'): ret += generate_enum(expr['enum'], expr['data']) + "\n" ret += generate_fwd_enum_struct(expr['enum'], expr['data']) @@ -435,12 +435,12 @@ if do_builtins: for expr in exprs: ret = "\n" - if expr.has_key('type'): + if expr.has_key('struct'): ret += generate_struct(expr) + "\n" - ret += generate_type_cleanup_decl(expr['type'] + "List") - fdef.write(generate_type_cleanup(expr['type'] + "List") + "\n") - ret += generate_type_cleanup_decl(expr['type']) - fdef.write(generate_type_cleanup(expr['type']) + "\n") + ret += generate_type_cleanup_decl(expr['struct'] + "List") + fdef.write(generate_type_cleanup(expr['struct'] + "List") + "\n") + ret += generate_type_cleanup_decl(expr['struct']) + fdef.write(generate_type_cleanup(expr['struct']) + "\n") elif expr.has_key('union'): ret += generate_union(expr, 'union') ret += generate_type_cleanup_decl(expr['union'] + "List") diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 9222671ff8..c739a95a87 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -178,7 +178,7 @@ def generate_visit_struct_body(field_prefix, name, members): def generate_visit_struct(expr): - name = expr['type'] + name = expr['struct'] members = expr['data'] base = expr.get('base') @@ -545,12 +545,12 @@ if do_builtins: fdef.write(generate_visit_list(typename, None)) for expr in exprs: - if expr.has_key('type'): + if expr.has_key('struct'): ret = generate_visit_struct(expr) - ret += generate_visit_list(expr['type'], expr['data']) + ret += generate_visit_list(expr['struct'], expr['data']) fdef.write(ret) - ret = generate_declaration(expr['type'], expr['data']) + ret = generate_declaration(expr['struct'], expr['data']) fdecl.write(ret) elif expr.has_key('union'): ret = generate_visit_union(expr) diff --git a/scripts/qapi.py b/scripts/qapi.py index e391b5a649..e50fec826b 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -419,7 +419,7 @@ def check_union(expr, expr_info): members = expr['data'] values = { 'MAX': '(automatic)' } - # If the object has a member 'base', its value must name a complex type, + # If the object has a member 'base', its value must name a struct, # and there must be a discriminator. if base is not None: if discriminator is None: @@ -448,18 +448,18 @@ def check_union(expr, expr_info): base_fields = find_base_fields(base) if not base_fields: raise QAPIExprError(expr_info, - "Base '%s' is not a valid type" + "Base '%s' is not a valid struct" % base) # The value of member 'discriminator' must name a non-optional - # member of the base type. + # member of the base struct. check_name(expr_info, "Discriminator of flat union '%s'" % name, discriminator) discriminator_type = base_fields.get(discriminator) if not discriminator_type: raise QAPIExprError(expr_info, "Discriminator '%s' is not a member of base " - "type '%s'" + "struct '%s'" % (discriminator, base)) enum_define = find_enum(discriminator_type) allow_metas=['struct'] @@ -546,12 +546,12 @@ def check_enum(expr, expr_info): values[key] = member def check_struct(expr, expr_info): - name = expr['type'] + name = expr['struct'] members = expr['data'] - check_type(expr_info, "'data' for type '%s'" % name, members, + check_type(expr_info, "'data' for struct '%s'" % name, members, allow_dict=True, allow_optional=True) - check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'), + check_type(expr_info, "'base' for struct '%s'" % name, expr.get('base'), allow_metas=['struct']) def check_exprs(schema): @@ -565,7 +565,7 @@ def check_exprs(schema): check_union(expr, info) elif expr.has_key('alternate'): check_alternate(expr, info) - elif expr.has_key('type'): + elif expr.has_key('struct'): check_struct(expr, info) elif expr.has_key('command'): check_command(expr, info) @@ -617,6 +617,20 @@ def parse_schema(input_file): for expr_elem in schema.exprs: expr = expr_elem['expr'] info = expr_elem['info'] + + # back-compat hack until all schemas have been converted; + # preserve the ordering of the original expression + if expr.has_key('type'): + seen_type = False + for (key, value) in expr.items(): + if key == 'type': + seen_type = True + del expr['type'] + expr['struct'] = value + elif seen_type: + del expr[key] + expr[key] = value + if expr.has_key('enum'): check_keys(expr_elem, 'enum', ['data']) add_enum(expr['enum'], info, expr['data']) @@ -627,8 +641,8 @@ def parse_schema(input_file): elif expr.has_key('alternate'): check_keys(expr_elem, 'alternate', ['data']) add_name(expr['alternate'], info, 'alternate') - elif expr.has_key('type'): - check_keys(expr_elem, 'type', ['data'], ['base']) + elif expr.has_key('struct'): + check_keys(expr_elem, 'struct', ['data'], ['base']) add_struct(expr, info) elif expr.has_key('command'): check_keys(expr_elem, 'command', [], @@ -745,11 +759,9 @@ def type_name(name): return c_list_type(name[0]) return name -def add_name(name, info, meta, implicit = False, source = None): +def add_name(name, info, meta, implicit = False): global all_names - if not source: - source = "'%s'" % meta - check_name(info, source, name) + check_name(info, "'%s'" % meta, name) if name in all_names: raise QAPIExprError(info, "%s '%s' is already defined" @@ -762,14 +774,14 @@ def add_name(name, info, meta, implicit = False, source = None): def add_struct(definition, info): global struct_types - name = definition['type'] - add_name(name, info, 'struct', source="'type'") + name = definition['struct'] + add_name(name, info, 'struct') struct_types.append(definition) def find_struct(name): global struct_types for struct in struct_types: - if struct['type'] == name: + if struct['struct'] == name: return struct return None diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out index c3a6b7723c..99848eefbb 100644 --- a/tests/qapi-schema/alternate-good.out +++ b/tests/qapi-schema/alternate-good.out @@ -1,6 +1,6 @@ -[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]), +[OrderedDict([('struct', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]), OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]), OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])] [{'enum_name': 'Enum', 'enum_values': ['hello', 'world']}, {'enum_name': 'AltKind', 'enum_values': None}] -[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])] +[OrderedDict([('struct', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])] diff --git a/tests/qapi-schema/bad-base.err b/tests/qapi-schema/bad-base.err index f398bbb7d3..154274bdd3 100644 --- a/tests/qapi-schema/bad-base.err +++ b/tests/qapi-schema/bad-base.err @@ -1 +1 @@ -tests/qapi-schema/bad-base.json:3: 'base' for type 'MyType' cannot use union type 'Union' +tests/qapi-schema/bad-base.json:3: 'base' for struct 'MyType' cannot use union type 'Union' diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err index 42b490ce0b..c4190602b5 100644 --- a/tests/qapi-schema/bad-ident.err +++ b/tests/qapi-schema/bad-ident.err @@ -1 +1 @@ -tests/qapi-schema/bad-ident.json:2: 'type' does not allow optional name '*oops' +tests/qapi-schema/bad-ident.json:2: 'struct' does not allow optional name '*oops' diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err index de6168c82d..62fd70baaf 100644 --- a/tests/qapi-schema/bad-type-bool.err +++ b/tests/qapi-schema/bad-type-bool.err @@ -1 +1 @@ -tests/qapi-schema/bad-type-bool.json:2: 'type' key must have a string value +tests/qapi-schema/bad-type-bool.json:2: 'struct' key must have a string value diff --git a/tests/qapi-schema/data-member-array.out b/tests/qapi-schema/data-member-array.out index 82871204ed..c39fa25484 100644 --- a/tests/qapi-schema/data-member-array.out +++ b/tests/qapi-schema/data-member-array.out @@ -1,5 +1,5 @@ [OrderedDict([('enum', 'abc'), ('data', ['a', 'b', 'c'])]), - OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))]), + OrderedDict([('struct', 'def'), ('data', OrderedDict([('array', ['abc'])]))]), OrderedDict([('command', 'okay'), ('data', OrderedDict([('member1', ['int']), ('member2', ['def'])]))])] [{'enum_name': 'abc', 'enum_values': ['a', 'b', 'c']}] -[OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))])] +[OrderedDict([('struct', 'def'), ('data', OrderedDict([('array', ['abc'])]))])] diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err index ceb6e46cfe..f9613c6d6b 100644 --- a/tests/qapi-schema/double-type.err +++ b/tests/qapi-schema/double-type.err @@ -1 +1 @@ -tests/qapi-schema/double-type.json:2: Unknown key 'command' in type 'bar' +tests/qapi-schema/double-type.json:2: Unknown key 'command' in struct 'bar' diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json index bb0f02d298..d41c80c3bd 100644 --- a/tests/qapi-schema/flat-union-bad-base.json +++ b/tests/qapi-schema/flat-union-bad-base.json @@ -1,4 +1,4 @@ -# we require the base to be an existing complex type +# we require the base to be an existing struct # TODO: should we allow an anonymous inline base type? { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } diff --git a/tests/qapi-schema/flat-union-base-star.err b/tests/qapi-schema/flat-union-base-star.err index 60e47efab5..b7748f08bf 100644 --- a/tests/qapi-schema/flat-union-base-star.err +++ b/tests/qapi-schema/flat-union-base-star.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-base-star.json:8: Base '**' is not a valid type +tests/qapi-schema/flat-union-base-star.json:8: Base '**' is not a valid struct diff --git a/tests/qapi-schema/flat-union-base-star.json b/tests/qapi-schema/flat-union-base-star.json index 994533a514..76724075b1 100644 --- a/tests/qapi-schema/flat-union-base-star.json +++ b/tests/qapi-schema/flat-union-base-star.json @@ -1,4 +1,4 @@ -# we require the base to be an existing complex type +# we require the base to be an existing struct { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } { 'type': 'TestTypeA', diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err index 185bf51a72..ede9859a39 100644 --- a/tests/qapi-schema/flat-union-base-union.err +++ b/tests/qapi-schema/flat-union-base-union.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid type +tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid struct diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json index 838986c48a..0ba6e28d3b 100644 --- a/tests/qapi-schema/flat-union-base-union.json +++ b/tests/qapi-schema/flat-union-base-union.json @@ -1,4 +1,4 @@ -# FIXME: the error message needs help: we require the base to be a struct +# we require the base to be a struct { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } { 'type': 'TestTypeA', diff --git a/tests/qapi-schema/flat-union-branch-clash.out b/tests/qapi-schema/flat-union-branch-clash.out index 5d541133b9..04c239565e 100644 --- a/tests/qapi-schema/flat-union-branch-clash.out +++ b/tests/qapi-schema/flat-union-branch-clash.out @@ -1,9 +1,9 @@ [OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), - OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), - OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]), + OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), + OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]), OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'Branch1'), ('value2', 'Branch2')]))])] [{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}] -[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), - OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])] +[OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), + OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), + OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])] diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json index f3da1175f8..65c15d0a1e 100644 --- a/tests/qapi-schema/flat-union-inline.json +++ b/tests/qapi-schema/flat-union-inline.json @@ -1,4 +1,4 @@ -# we require branches to be a complex type name +# we require branches to be a struct name # TODO: should we allow anonymous inline types? { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json index d373131653..ee93cf803a 100644 --- a/tests/qapi-schema/flat-union-int-branch.json +++ b/tests/qapi-schema/flat-union-int-branch.json @@ -1,4 +1,4 @@ -# we require flat union branches to be a complex type +# we require flat union branches to be a struct { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } { 'type': 'Base', diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.err b/tests/qapi-schema/flat-union-invalid-discriminator.err index 790b6759b8..5f4055614e 100644 --- a/tests/qapi-schema/flat-union-invalid-discriminator.err +++ b/tests/qapi-schema/flat-union-invalid-discriminator.err @@ -1 +1 @@ -tests/qapi-schema/flat-union-invalid-discriminator.json:13: Discriminator 'enum_wrong' is not a member of base type 'TestBase' +tests/qapi-schema/flat-union-invalid-discriminator.json:13: Discriminator 'enum_wrong' is not a member of base struct 'TestBase' diff --git a/tests/qapi-schema/flat-union-reverse-define.out b/tests/qapi-schema/flat-union-reverse-define.out index 03c952e28a..1ed7b8a519 100644 --- a/tests/qapi-schema/flat-union-reverse-define.out +++ b/tests/qapi-schema/flat-union-reverse-define.out @@ -1,9 +1,9 @@ [OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'TestTypeA'), ('value2', 'TestTypeB')]))]), - OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), + OrderedDict([('struct', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), - OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] + OrderedDict([('struct', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('struct', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] [{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}] -[OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), - OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), - OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] +[OrderedDict([('struct', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]), + OrderedDict([('struct', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]), + OrderedDict([('struct', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])] diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 313ecf3ad3..83ab1a5139 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -1,13 +1,13 @@ [OrderedDict([('enum', 'EnumOne'), ('data', ['value1', 'value2', 'value3'])]), - OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), - OrderedDict([('type', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('type', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('type', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), - OrderedDict([('type', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), - OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), - OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), - OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), + OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), + OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), + OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), + OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), + OrderedDict([('struct', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]), OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]), OrderedDict([('alternate', 'UserDefAlternate'), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]), @@ -16,8 +16,8 @@ OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]), OrderedDict([('command', 'user_def_cmd2'), ('data', OrderedDict([('ud1a', 'UserDefOne'), ('*ud1b', 'UserDefOne')])), ('returns', 'UserDefTwo')]), OrderedDict([('command', 'user_def_cmd3'), ('data', OrderedDict([('a', 'int'), ('*b', 'int')])), ('returns', 'int')]), - OrderedDict([('type', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]), - OrderedDict([('type', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))]), + OrderedDict([('struct', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]), + OrderedDict([('struct', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))]), OrderedDict([('event', 'EVENT_A')]), OrderedDict([('event', 'EVENT_B'), ('data', OrderedDict())]), OrderedDict([('event', 'EVENT_C'), ('data', OrderedDict([('*a', 'int'), ('*b', 'UserDefOne'), ('c', 'str')]))]), @@ -25,14 +25,14 @@ [{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']}, {'enum_name': 'UserDefAlternateKind', 'enum_values': None}, {'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None}] -[OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), - OrderedDict([('type', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('type', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('type', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), - OrderedDict([('type', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), - OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), - OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), - OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), - OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), - OrderedDict([('type', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]), - OrderedDict([('type', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))])] +[OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), + OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), + OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), + OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), + OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), + OrderedDict([('struct', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]), + OrderedDict([('struct', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]), + OrderedDict([('struct', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))])] diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err index 3cc82c0701..9f637963e8 100644 --- a/tests/qapi-schema/union-invalid-base.err +++ b/tests/qapi-schema/union-invalid-base.err @@ -1 +1 @@ -tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid type +tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid struct diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err index 0a35bfdbd6..12f5ed5b43 100644 --- a/tests/qapi-schema/unknown-expr-key.err +++ b/tests/qapi-schema/unknown-expr-key.err @@ -1 +1 @@ -tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in type 'bar' +tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in struct 'bar' From 3b2a8b85322f3677525a65c0b35deadf45fb704b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:26 -0600 Subject: [PATCH 29/40] qapi: Document 'struct' metatype Referring to "type" as both a meta-type (built-in, enum, union, alternate, or struct) and a specific type (the name that the schema uses for declaring structs) is confusing. Now that the generator accepts 'struct' as a synonym for 'type', update all documentation to use saner wording. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/qapi-code-gen.txt | 58 +++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index 588b1104af..a874a6d436 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -72,7 +72,7 @@ x.y.z)' comment. For example: # # Since: 0.14.0 ## - { 'type': 'BlockStats', + { 'struct': 'BlockStats', 'data': {'*device': 'str', 'stats': 'BlockDeviceStats', '*parent': 'BlockStats', '*backing': 'BlockStats'} } @@ -86,7 +86,7 @@ types, and allows for indefinite nesting of QMP that satisfies the schema. A type name should not be defined more than once. There are seven top-level expressions recognized by the parser: -'include', 'command', 'type', 'enum', 'union', 'alternate', and +'include', 'command', 'struct', 'enum', 'union', 'alternate', and 'event'. There are several groups of types: simple types (a number of built-in types, such as 'int' and 'str'; as well as enumerations), complex types (structs and two flavors of unions), and alternate types @@ -128,9 +128,9 @@ In the rest of this document, usage lines are given for each expression type, with literal strings written in lower case and placeholders written in capitals. If a literal string includes a prefix of '*', that key/value pair can be omitted from the expression. -For example, a usage statement that includes '*base':COMPLEX-TYPE-NAME +For example, a usage statement that includes '*base':STRUCT-NAME means that an expression has an optional key 'base', which if present -must have a value that forms a complex type name. +must have a value that forms a struct name. === Built-in Types === @@ -168,17 +168,17 @@ an outer file. The parser may be made stricter in the future to prevent incomplete include files. -=== Complex types === +=== Struct types === -Usage: { 'type': STRING, 'data': DICT, '*base': COMPLEX-TYPE-NAME } +Usage: { 'struct': STRING, 'data': DICT, '*base': STRUCT-NAME } -A complex type is a dictionary containing a single 'data' key whose +A struct is a dictionary containing a single 'data' key whose value is a dictionary. This corresponds to a struct in C or an Object in JSON. Each value of the 'data' dictionary must be the name of a type, or a one-element array containing a type name. An example of a -complex type is: +struct is: - { 'type': 'MyType', + { 'struct': 'MyType', 'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } } The use of '*' as a prefix to the name means the member is optional in @@ -211,13 +211,13 @@ A structure that is used in both input and output of various commands must consider the backwards compatibility constraints of both directions of use. -A complex type definition can specify another complex type as its base. +A struct definition can specify another struct as its base. In this case, the fields of the base type are included as top-level fields -of the new complex type's dictionary in the QMP wire format. An example +of the new struct's dictionary in the QMP wire format. An example definition is: - { 'type': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } } - { 'type': 'BlockdevOptionsGenericCOWFormat', + { 'struct': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } } + { 'struct': 'BlockdevOptionsGenericCOWFormat', 'base': 'BlockdevOptionsGenericFormat', 'data': { '*backing': 'str' } } @@ -252,7 +252,7 @@ converting between strings and enum values. Since the wire format always passes by name, it is acceptable to reorder or add new enumeration members in any location without breaking QMP clients; however, removing enum values would break compatibility. For any -complex type that has a field that will only contain a finite set of +struct that has a field that will only contain a finite set of string values, using an enum type for that field is better than open-coding the field to be type 'str'. @@ -260,7 +260,7 @@ open-coding the field to be type 'str'. === Union types === Usage: { 'union': STRING, 'data': DICT } -or: { 'union': STRING, 'data': DICT, 'base': COMPLEX-TYPE-NAME, +or: { 'union': STRING, 'data': DICT, 'base': STRUCT-NAME, 'discriminator': ENUM-MEMBER-OF-BASE } Union types are used to let the user choose between several different @@ -272,8 +272,8 @@ paragraphs. A simple union type defines a mapping from automatic discriminator values to data types like in this example: - { 'type': 'FileOptions', 'data': { 'filename': 'str' } } - { 'type': 'Qcow2Options', + { 'struct': 'FileOptions', 'data': { 'filename': 'str' } } + { 'struct': 'Qcow2Options', 'data': { 'backing-file': 'str', 'lazy-refcounts': 'bool' } } { 'union': 'BlockdevOptions', @@ -296,13 +296,13 @@ the union can be named 'max', as this would collide with the implicit enum. The value for each branch can be of any type. -A flat union definition specifies a complex type as its base, and +A flat union definition specifies a struct as its base, and avoids nesting on the wire. All branches of the union must be complex types, and the top-level fields of the union dictionary on the wire will be combination of fields from both the base type and the appropriate branch type (when merging two dictionaries, there must be no keys in common). The 'discriminator' field must be the name of an -enum-typed member of the base type. +enum-typed member of the base struct. The following example enhances the above simple union example by adding a common field 'readonly', renaming the discriminator to @@ -310,7 +310,7 @@ something more applicable, and reducing the number of {} required on the wire: { 'enum': 'BlockdevDriver', 'data': [ 'raw', 'qcow2' ] } - { 'type': 'BlockdevCommonOptions', + { 'struct': 'BlockdevCommonOptions', 'data': { 'driver': 'BlockdevDriver', 'readonly': 'bool' } } { 'union': 'BlockdevOptions', 'base': 'BlockdevCommonOptions', @@ -335,16 +335,16 @@ and then a union of structures for each branch of the struct. A simple union can always be re-written as a flat union where the base class has a single member named 'type', and where each branch of the -union has a complex type with a single member named 'data'. That is, +union has a struct with a single member named 'data'. That is, { 'union': 'Simple', 'data': { 'one': 'str', 'two': 'int' } } is identical on the wire to: { 'enum': 'Enum', 'data': ['one', 'two'] } - { 'type': 'Base', 'data': { 'type': 'Enum' } } - { 'type': 'Branch1', 'data': { 'data': 'str' } } - { 'type': 'Branch2', 'data': { 'data': 'int' } } + { 'struct': 'Base', 'data': { 'type': 'Enum' } } + { 'struct': 'Branch1', 'data': { 'data': 'str' } } + { 'struct': 'Branch2', 'data': { 'data': 'int' } } { 'union': 'Flat': 'base': 'Base', 'discriminator': 'type', 'data': { 'one': 'Branch1', 'two': 'Branch2' } } @@ -402,7 +402,7 @@ part of a QMP command. The 'data' member is optional and defaults to {} (an empty dictionary). If present, it must be the string name of a complex type, a one-element array containing the name of a complex type, or a dictionary that declares an anonymous type with the same -semantics as a 'type' expression, with one exception noted below when +semantics as a 'struct' expression, with one exception noted below when 'gen' is used. The 'returns' member describes what will appear in the "return" field @@ -411,7 +411,7 @@ optional from the command declaration; if absent, the "return" field will be an empty dictionary. If 'returns' is present, it must be the string name of a complex or built-in type, a one-element array containing the name of a complex or built-in type, or a dictionary -that declares an anonymous type with the same semantics as a 'type' +that declares an anonymous type with the same semantics as a 'struct' expression, with one exception noted below when 'gen' is used. Although it is permitted to have the 'returns' member name a built-in type or an array of built-in types, any command that does this cannot @@ -430,7 +430,7 @@ Some example commands: { 'command': 'my-first-command', 'data': { 'arg1': 'str', '*arg2': 'str' } } - { 'type': 'MyType', 'data': { '*value': 'str' } } + { 'struct': 'MyType', 'data': { '*value': 'str' } } { 'command': 'my-second-command', 'returns': [ 'MyType' ] } @@ -474,7 +474,7 @@ Events are defined with the keyword 'event'. It is not allowed to name an event 'MAX', since the generator also produces a C enumeration of all event names with a generated _MAX value at the end. When 'data' is also specified, additional info will be included in the -event, with similar semantics to a 'type' expression. Finally there +event, with similar semantics to a 'struct' expression. Finally there will be C API generated in qapi-event.h; when called by QEMU code, a message with timestamp will be emitted on the wire. @@ -505,7 +505,7 @@ case we want to accept/return a list of this type with a command), and a command which takes that type as a parameter and returns the same type: $ cat example-schema.json - { 'type': 'UserDefOne', + { 'struct': 'UserDefOne', 'data': { 'integer': 'int', 'string': 'str' } } { 'command': 'my-command', From 895a2a80e0e054f0d5d3715aa93d10d15e49f9f7 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:27 -0600 Subject: [PATCH 30/40] qapi: Use 'struct' instead of 'type' in schema Referring to "type" as both a meta-type (built-in, enum, union, alternate, or struct) and a specific type (the name that the schema uses for declaring structs) is confusing. Do the bulk of the conversion to "struct" in qapi schema, with a fairly mechanical: for f in `find -name '*.json'; do sed -i "s/'type'/'struct'/"; done followed by manually filtering out the places where we have a 'type' embedded in 'data'. Then tweak a couple of tests whose output changes slightly due to longer lines. I also verified that the generated files for QMP and QGA (such as qmp-commands.h) are the same before and after, as assurance that I didn't leave in any accidental member name changes. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- qapi-schema.json | 166 +++++++++--------- qapi/block-core.json | 66 +++---- qapi/block.json | 2 +- qapi/common.json | 4 +- qapi/trace.json | 2 +- qga/qapi-schema.json | 28 +-- tests/qapi-schema/alternate-array.json | 2 +- tests/qapi-schema/alternate-base.json | 2 +- .../qapi-schema/alternate-conflict-dict.json | 4 +- tests/qapi-schema/alternate-good.json | 2 +- tests/qapi-schema/bad-base.json | 2 +- tests/qapi-schema/bad-ident.json | 2 +- tests/qapi-schema/bad-type-bool.json | 2 +- tests/qapi-schema/bad-type-int.err | 2 +- tests/qapi-schema/bad-type-int.json | 2 +- tests/qapi-schema/data-member-array.json | 2 +- tests/qapi-schema/double-data.err | 2 +- tests/qapi-schema/double-data.json | 2 +- tests/qapi-schema/double-type.json | 2 +- tests/qapi-schema/flat-union-bad-base.json | 4 +- .../flat-union-bad-discriminator.json | 6 +- tests/qapi-schema/flat-union-base-star.json | 4 +- tests/qapi-schema/flat-union-base-union.json | 4 +- .../qapi-schema/flat-union-branch-clash.json | 6 +- tests/qapi-schema/flat-union-inline.json | 2 +- tests/qapi-schema/flat-union-int-branch.json | 4 +- .../flat-union-invalid-branch-key.json | 6 +- .../flat-union-invalid-discriminator.json | 6 +- tests/qapi-schema/flat-union-no-base.json | 4 +- .../flat-union-optional-discriminator.json | 4 +- .../flat-union-reverse-define.json | 6 +- .../flat-union-string-discriminator.json | 6 +- tests/qapi-schema/qapi-schema-test.json | 22 +-- tests/qapi-schema/redefined-builtin.json | 2 +- tests/qapi-schema/redefined-type.json | 2 +- tests/qapi-schema/union-bad-branch.json | 4 +- .../union-base-no-discriminator.json | 6 +- tests/qapi-schema/union-invalid-base.json | 4 +- tests/qapi-schema/unknown-expr-key.json | 2 +- 39 files changed, 200 insertions(+), 200 deletions(-) diff --git a/qapi-schema.json b/qapi-schema.json index 7f4cf86ee5..6a4e0dfd4f 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -71,7 +71,7 @@ # # Since 0.14.0 ## -{ 'type': 'NameInfo', 'data': {'*name': 'str'} } +{ 'struct': 'NameInfo', 'data': {'*name': 'str'} } ## # @query-name: @@ -95,7 +95,7 @@ # # Since: 0.14.0 ## -{ 'type': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} } +{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} } ## # @query-kvm: @@ -170,7 +170,7 @@ # # Notes: @singlestep is enabled through the GDB stub ## -{ 'type': 'StatusInfo', +{ 'struct': 'StatusInfo', 'data': {'running': 'bool', 'singlestep': 'bool', 'status': 'RunState'} } ## @@ -195,7 +195,7 @@ # # Notes: If no UUID was specified for the guest, a null UUID is returned. ## -{ 'type': 'UuidInfo', 'data': {'UUID': 'str'} } +{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} } ## # @query-uuid: @@ -226,7 +226,7 @@ # # Since: 0.14.0 ## -{ 'type': 'ChardevInfo', 'data': {'label': 'str', +{ 'struct': 'ChardevInfo', 'data': {'label': 'str', 'filename': 'str', 'frontend-open': 'bool'} } @@ -250,7 +250,7 @@ # # Since: 2.0 ## -{ 'type': 'ChardevBackendInfo', 'data': {'name': 'str'} } +{ 'struct': 'ChardevBackendInfo', 'data': {'name': 'str'} } ## # @query-chardev-backends: @@ -339,7 +339,7 @@ # # Since: 1.2.0 ## -{ 'type': 'EventInfo', 'data': {'name': 'str'} } +{ 'struct': 'EventInfo', 'data': {'name': 'str'} } ## # @query-events: @@ -380,7 +380,7 @@ # # Since: 0.14.0 ## -{ 'type': 'MigrationStats', +{ 'struct': 'MigrationStats', 'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' , 'duplicate': 'int', 'skipped': 'int', 'normal': 'int', 'normal-bytes': 'int', 'dirty-pages-rate' : 'int', @@ -405,7 +405,7 @@ # # Since: 1.2 ## -{ 'type': 'XBZRLECacheStats', +{ 'struct': 'XBZRLECacheStats', 'data': {'cache-size': 'int', 'bytes': 'int', 'pages': 'int', 'cache-miss': 'int', 'cache-miss-rate': 'number', 'overflow': 'int' } } @@ -476,7 +476,7 @@ # # Since: 0.14.0 ## -{ 'type': 'MigrationInfo', +{ 'struct': 'MigrationInfo', 'data': {'*status': 'MigrationStatus', '*ram': 'MigrationStats', '*disk': 'MigrationStats', '*xbzrle-cache': 'XBZRLECacheStats', @@ -534,7 +534,7 @@ # # Since: 1.2 ## -{ 'type': 'MigrationCapabilityStatus', +{ 'struct': 'MigrationCapabilityStatus', 'data': { 'capability' : 'MigrationCapability', 'state' : 'bool' } } ## @@ -575,7 +575,7 @@ # # Since: 0.14.0 ## -{ 'type': 'MouseInfo', +{ 'struct': 'MouseInfo', 'data': {'name': 'str', 'index': 'int', 'current': 'bool', 'absolute': 'bool'} } @@ -621,7 +621,7 @@ # Notes: @halted is a transient state that changes frequently. By the time the # data is sent to the client, the guest may no longer be halted. ## -{ 'type': 'CpuInfo', +{ 'struct': 'CpuInfo', 'data': {'CPU': 'int', 'current': 'bool', 'halted': 'bool', '*pc': 'int', '*nip': 'int', '*npc': 'int', '*PC': 'int', 'thread_id': 'int'} } @@ -647,7 +647,7 @@ # # Since: 2.0 ## -{ 'type': 'IOThreadInfo', +{ 'struct': 'IOThreadInfo', 'data': {'id': 'str', 'thread-id': 'int'} } ## @@ -700,7 +700,7 @@ # # Since: 2.1 ## -{ 'type': 'VncBasicInfo', +{ 'struct': 'VncBasicInfo', 'data': { 'host': 'str', 'service': 'str', 'family': 'NetworkAddressFamily', @@ -715,7 +715,7 @@ # # Since: 2.1 ## -{ 'type': 'VncServerInfo', +{ 'struct': 'VncServerInfo', 'base': 'VncBasicInfo', 'data': { '*auth': 'str' } } @@ -732,7 +732,7 @@ # # Since: 0.14.0 ## -{ 'type': 'VncClientInfo', +{ 'struct': 'VncClientInfo', 'base': 'VncBasicInfo', 'data': { '*x509_dname': 'str', '*sasl_username': 'str' } } @@ -772,7 +772,7 @@ # # Since: 0.14.0 ## -{ 'type': 'VncInfo', +{ 'struct': 'VncInfo', 'data': {'enabled': 'bool', '*host': 'str', '*family': 'NetworkAddressFamily', '*service': 'str', '*auth': 'str', '*clients': ['VncClientInfo']} } @@ -826,7 +826,7 @@ # # Since: 2.3 ## -{ 'type': 'VncInfo2', +{ 'struct': 'VncInfo2', 'data': { 'id' : 'str', 'server' : ['VncBasicInfo'], 'clients' : ['VncClientInfo'], @@ -869,7 +869,7 @@ # # Since: 2.1 ## -{ 'type': 'SpiceBasicInfo', +{ 'struct': 'SpiceBasicInfo', 'data': { 'host': 'str', 'port': 'str', 'family': 'NetworkAddressFamily' } } @@ -883,7 +883,7 @@ # # Since: 2.1 ## -{ 'type': 'SpiceServerInfo', +{ 'struct': 'SpiceServerInfo', 'base': 'SpiceBasicInfo', 'data': { '*auth': 'str' } } @@ -907,7 +907,7 @@ # # Since: 0.14.0 ## -{ 'type': 'SpiceChannel', +{ 'struct': 'SpiceChannel', 'base': 'SpiceBasicInfo', 'data': {'connection-id': 'int', 'channel-type': 'int', 'channel-id': 'int', 'tls': 'bool'} } @@ -965,7 +965,7 @@ # # Since: 0.14.0 ## -{ 'type': 'SpiceInfo', +{ 'struct': 'SpiceInfo', 'data': {'enabled': 'bool', 'migrated': 'bool', '*host': 'str', '*port': 'int', '*tls-port': 'int', '*auth': 'str', '*compiled-version': 'str', 'mouse-mode': 'SpiceQueryMouseMode', '*channels': ['SpiceChannel']} } @@ -991,7 +991,7 @@ # Since: 0.14.0 # ## -{ 'type': 'BalloonInfo', 'data': {'actual': 'int' } } +{ 'struct': 'BalloonInfo', 'data': {'actual': 'int' } } ## # @query-balloon: @@ -1018,7 +1018,7 @@ # # Since: 0.14.0 ## -{ 'type': 'PciMemoryRange', 'data': {'base': 'int', 'limit': 'int'} } +{ 'struct': 'PciMemoryRange', 'data': {'base': 'int', 'limit': 'int'} } ## # @PciMemoryRegion @@ -1036,7 +1036,7 @@ # # Since: 0.14.0 ## -{ 'type': 'PciMemoryRegion', +{ 'struct': 'PciMemoryRegion', 'data': {'bar': 'int', 'type': 'str', 'address': 'int', 'size': 'int', '*prefetch': 'bool', '*mem_type_64': 'bool' } } @@ -1065,7 +1065,7 @@ # # Since: 0.14.0 ## -{ 'type': 'PciBridgeInfo', +{ 'struct': 'PciBridgeInfo', 'data': {'bus': { 'number': 'int', 'secondary': 'int', 'subordinate': 'int', 'io_range': 'PciMemoryRange', 'memory_range': 'PciMemoryRange', @@ -1104,7 +1104,7 @@ # # Since: 0.14.0 ## -{ 'type': 'PciDeviceInfo', +{ 'struct': 'PciDeviceInfo', 'data': {'bus': 'int', 'slot': 'int', 'function': 'int', 'class_info': {'*desc': 'str', 'class': 'int'}, 'id': {'device': 'int', 'vendor': 'int'}, @@ -1122,7 +1122,7 @@ # # Since: 0.14.0 ## -{ 'type': 'PciInfo', 'data': {'bus': 'int', 'devices': ['PciDeviceInfo']} } +{ 'struct': 'PciInfo', 'data': {'bus': 'int', 'devices': ['PciDeviceInfo']} } ## # @query-pci: @@ -1341,7 +1341,7 @@ # # Since: 1.6 ### -{ 'type': 'Abort', +{ 'struct': 'Abort', 'data': { } } ## @@ -1506,7 +1506,7 @@ # # Since: 1.2 ## -{ 'type': 'ObjectPropertyInfo', +{ 'struct': 'ObjectPropertyInfo', 'data': { 'name': 'str', 'type': 'str' } } ## @@ -1691,7 +1691,7 @@ # # Notes: This command is experimental and may change syntax in future releases. ## -{ 'type': 'ObjectTypeInfo', +{ 'struct': 'ObjectTypeInfo', 'data': { 'name': 'str' } } ## @@ -1723,7 +1723,7 @@ # # Since: 1.2 ## -{ 'type': 'DevicePropertyInfo', +{ 'struct': 'DevicePropertyInfo', 'data': { 'name': 'str', 'type': 'str', '*description': 'str' } } ## @@ -1903,7 +1903,7 @@ # # Since: 2.0 ## -{ 'type': 'DumpGuestMemoryCapability', +{ 'struct': 'DumpGuestMemoryCapability', 'data': { 'formats': ['DumpGuestMemoryFormat'] } } @@ -2000,7 +2000,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevNoneOptions', +{ 'struct': 'NetdevNoneOptions', 'data': { } } ## @@ -2020,7 +2020,7 @@ # # Since 1.2 ## -{ 'type': 'NetLegacyNicOptions', +{ 'struct': 'NetLegacyNicOptions', 'data': { '*netdev': 'str', '*macaddr': 'str', @@ -2035,7 +2035,7 @@ # # Since 1.2 ## -{ 'type': 'String', +{ 'struct': 'String', 'data': { 'str': 'str' } } @@ -2078,7 +2078,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevUserOptions', +{ 'struct': 'NetdevUserOptions', 'data': { '*hostname': 'str', '*restrict': 'bool', @@ -2130,7 +2130,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevTapOptions', +{ 'struct': 'NetdevTapOptions', 'data': { '*ifname': 'str', '*fd': 'str', @@ -2166,7 +2166,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevSocketOptions', +{ 'struct': 'NetdevSocketOptions', 'data': { '*fd': 'str', '*listen': 'str', @@ -2214,7 +2214,7 @@ # # Since 2.1 ## -{ 'type': 'NetdevL2TPv3Options', +{ 'struct': 'NetdevL2TPv3Options', 'data': { 'src': 'str', 'dst': 'str', @@ -2246,7 +2246,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevVdeOptions', +{ 'struct': 'NetdevVdeOptions', 'data': { '*sock': 'str', '*port': 'uint16', @@ -2265,7 +2265,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevDumpOptions', +{ 'struct': 'NetdevDumpOptions', 'data': { '*len': 'size', '*file': 'str' } } @@ -2281,7 +2281,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevBridgeOptions', +{ 'struct': 'NetdevBridgeOptions', 'data': { '*br': 'str', '*helper': 'str' } } @@ -2295,7 +2295,7 @@ # # Since 1.2 ## -{ 'type': 'NetdevHubPortOptions', +{ 'struct': 'NetdevHubPortOptions', 'data': { 'hubid': 'int32' } } @@ -2315,7 +2315,7 @@ # # Since 2.0 ## -{ 'type': 'NetdevNetmapOptions', +{ 'struct': 'NetdevNetmapOptions', 'data': { 'ifname': 'str', '*devname': 'str' } } @@ -2331,7 +2331,7 @@ # # Since 2.1 ## -{ 'type': 'NetdevVhostUserOptions', +{ 'struct': 'NetdevVhostUserOptions', 'data': { 'chardev': 'str', '*vhostforce': 'bool' } } @@ -2376,7 +2376,7 @@ # # Since 1.2 ## -{ 'type': 'NetLegacy', +{ 'struct': 'NetLegacy', 'data': { '*vlan': 'int32', '*id': 'str', @@ -2394,7 +2394,7 @@ # # Since 1.2 ## -{ 'type': 'Netdev', +{ 'struct': 'Netdev', 'data': { 'id': 'str', 'opts': 'NetClientOptions' } } @@ -2418,7 +2418,7 @@ # # Since 1.3 ## -{ 'type': 'InetSocketAddress', +{ 'struct': 'InetSocketAddress', 'data': { 'host': 'str', 'port': 'str', @@ -2435,7 +2435,7 @@ # # Since 1.3 ## -{ 'type': 'UnixSocketAddress', +{ 'struct': 'UnixSocketAddress', 'data': { 'path': 'str' } } @@ -2500,7 +2500,7 @@ # # Since: 1.2.0 ## -{ 'type': 'MachineInfo', +{ 'struct': 'MachineInfo', 'data': { 'name': 'str', '*alias': 'str', '*is-default': 'bool', 'cpu-max': 'int' } } @@ -2524,7 +2524,7 @@ # # Since: 1.2.0 ## -{ 'type': 'CpuDefinitionInfo', +{ 'struct': 'CpuDefinitionInfo', 'data': { 'name': 'str' } } ## @@ -2549,7 +2549,7 @@ # # Since: 1.2.0 ## -{ 'type': 'AddfdInfo', 'data': {'fdset-id': 'int', 'fd': 'int'} } +{ 'struct': 'AddfdInfo', 'data': {'fdset-id': 'int', 'fd': 'int'} } ## # @add-fd: @@ -2605,7 +2605,7 @@ # # Since: 1.2.0 ## -{ 'type': 'FdsetFdInfo', +{ 'struct': 'FdsetFdInfo', 'data': {'fd': 'int', '*opaque': 'str'} } ## @@ -2619,7 +2619,7 @@ # # Since: 1.2.0 ## -{ 'type': 'FdsetInfo', +{ 'struct': 'FdsetInfo', 'data': {'fdset-id': 'int', 'fds': ['FdsetFdInfo']} } ## @@ -2645,7 +2645,7 @@ # # Since: 1.2.0 ## -{ 'type': 'TargetInfo', +{ 'struct': 'TargetInfo', 'data': { 'arch': 'str' } } ## @@ -2745,7 +2745,7 @@ # # Since: 1.4 ## -{ 'type': 'ChardevFile', 'data': { '*in' : 'str', +{ 'struct': 'ChardevFile', 'data': { '*in' : 'str', 'out' : 'str' } } ## @@ -2759,7 +2759,7 @@ # # Since: 1.4 ## -{ 'type': 'ChardevHostdev', 'data': { 'device' : 'str' } } +{ 'struct': 'ChardevHostdev', 'data': { 'device' : 'str' } } ## # @ChardevSocket: @@ -2781,7 +2781,7 @@ # # Since: 1.4 ## -{ 'type': 'ChardevSocket', 'data': { 'addr' : 'SocketAddress', +{ 'struct': 'ChardevSocket', 'data': { 'addr' : 'SocketAddress', '*server' : 'bool', '*wait' : 'bool', '*nodelay' : 'bool', @@ -2798,7 +2798,7 @@ # # Since: 1.5 ## -{ 'type': 'ChardevUdp', 'data': { 'remote' : 'SocketAddress', +{ 'struct': 'ChardevUdp', 'data': { 'remote' : 'SocketAddress', '*local' : 'SocketAddress' } } ## @@ -2810,7 +2810,7 @@ # # Since: 1.5 ## -{ 'type': 'ChardevMux', 'data': { 'chardev' : 'str' } } +{ 'struct': 'ChardevMux', 'data': { 'chardev' : 'str' } } ## # @ChardevStdio: @@ -2823,7 +2823,7 @@ # # Since: 1.5 ## -{ 'type': 'ChardevStdio', 'data': { '*signal' : 'bool' } } +{ 'struct': 'ChardevStdio', 'data': { '*signal' : 'bool' } } ## # @ChardevSpiceChannel: @@ -2834,7 +2834,7 @@ # # Since: 1.5 ## -{ 'type': 'ChardevSpiceChannel', 'data': { 'type' : 'str' } } +{ 'struct': 'ChardevSpiceChannel', 'data': { 'type' : 'str' } } ## # @ChardevSpicePort: @@ -2845,7 +2845,7 @@ # # Since: 1.5 ## -{ 'type': 'ChardevSpicePort', 'data': { 'fqdn' : 'str' } } +{ 'struct': 'ChardevSpicePort', 'data': { 'fqdn' : 'str' } } ## # @ChardevVC: @@ -2859,7 +2859,7 @@ # # Since: 1.5 ## -{ 'type': 'ChardevVC', 'data': { '*width' : 'int', +{ 'struct': 'ChardevVC', 'data': { '*width' : 'int', '*height' : 'int', '*cols' : 'int', '*rows' : 'int' } } @@ -2873,7 +2873,7 @@ # # Since: 1.5 ## -{ 'type': 'ChardevRingbuf', 'data': { '*size' : 'int' } } +{ 'struct': 'ChardevRingbuf', 'data': { '*size' : 'int' } } ## # @ChardevBackend: @@ -2882,7 +2882,7 @@ # # Since: 1.4 (testdev since 2.2) ## -{ 'type': 'ChardevDummy', 'data': { } } +{ 'struct': 'ChardevDummy', 'data': { } } { 'union': 'ChardevBackend', 'data': { 'file' : 'ChardevFile', 'serial' : 'ChardevHostdev', @@ -2915,7 +2915,7 @@ # # Since: 1.4 ## -{ 'type' : 'ChardevReturn', 'data': { '*pty' : 'str' } } +{ 'struct' : 'ChardevReturn', 'data': { '*pty' : 'str' } } ## # @chardev-add: @@ -3002,7 +3002,7 @@ # # Since: 1.5 ## -{ 'type': 'TPMPassthroughOptions', 'data': { '*path' : 'str', +{ 'struct': 'TPMPassthroughOptions', 'data': { '*path' : 'str', '*cancel-path' : 'str'} } ## @@ -3030,7 +3030,7 @@ # # Since: 1.5 ## -{ 'type': 'TPMInfo', +{ 'struct': 'TPMInfo', 'data': {'id': 'str', 'model': 'TpmModel', 'options': 'TpmTypeOptions' } } @@ -3092,7 +3092,7 @@ # # Since 1.5 ## -{ 'type': 'AcpiTableOptions', +{ 'struct': 'AcpiTableOptions', 'data': { '*sig': 'str', '*rev': 'uint8', @@ -3138,7 +3138,7 @@ # # Since 1.5 ## -{ 'type': 'CommandLineParameterInfo', +{ 'struct': 'CommandLineParameterInfo', 'data': { 'name': 'str', 'type': 'CommandLineParameterType', '*help': 'str', @@ -3155,7 +3155,7 @@ # # Since 1.5 ## -{ 'type': 'CommandLineOptionInfo', +{ 'struct': 'CommandLineOptionInfo', 'data': { 'option': 'str', 'parameters': ['CommandLineParameterInfo'] } } ## @@ -3199,7 +3199,7 @@ # # Since: 1.5 ## -{ 'type': 'X86CPUFeatureWordInfo', +{ 'struct': 'X86CPUFeatureWordInfo', 'data': { 'cpuid-input-eax': 'int', '*cpuid-input-ecx': 'int', 'cpuid-register': 'X86CPURegister32', @@ -3252,7 +3252,7 @@ # Since 1.6 ## -{ 'type': 'RxFilterInfo', +{ 'struct': 'RxFilterInfo', 'data': { 'name': 'str', 'promiscuous': 'bool', @@ -3314,7 +3314,7 @@ # # Since: 2.0 ## -{ 'type' : 'InputKeyEvent', +{ 'struct' : 'InputKeyEvent', 'data' : { 'key' : 'KeyValue', 'down' : 'bool' } } @@ -3328,7 +3328,7 @@ # # Since: 2.0 ## -{ 'type' : 'InputBtnEvent', +{ 'struct' : 'InputBtnEvent', 'data' : { 'button' : 'InputButton', 'down' : 'bool' } } @@ -3343,7 +3343,7 @@ # # Since: 2.0 ## -{ 'type' : 'InputMoveEvent', +{ 'struct' : 'InputMoveEvent', 'data' : { 'axis' : 'InputAxis', 'value' : 'int' } } @@ -3426,7 +3426,7 @@ # # Since: 2.1 ## -{ 'type': 'NumaNodeOptions', +{ 'struct': 'NumaNodeOptions', 'data': { '*nodeid': 'uint16', '*cpus': ['uint16'], @@ -3473,7 +3473,7 @@ # Since: 2.1 ## -{ 'type': 'Memdev', +{ 'struct': 'Memdev', 'data': { 'size': 'size', 'merge': 'bool', @@ -3516,7 +3516,7 @@ # # Since: 2.1 ## -{ 'type': 'PCDIMMDeviceInfo', +{ 'struct': 'PCDIMMDeviceInfo', 'data': { '*id': 'str', 'addr': 'int', 'size': 'int', @@ -3570,7 +3570,7 @@ # # Since: 2.1 ## -{ 'type': 'ACPIOSTInfo', +{ 'struct': 'ACPIOSTInfo', 'data' : { '*device': 'str', 'slot': 'str', 'slot-type': 'ACPISlotType', diff --git a/qapi/block-core.json b/qapi/block-core.json index 3d20e61dee..dcf7c04ffa 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -26,7 +26,7 @@ # ## -{ 'type': 'SnapshotInfo', +{ 'struct': 'SnapshotInfo', 'data': { 'id': 'str', 'name': 'str', 'vm-state-size': 'int', 'date-sec': 'int', 'date-nsec': 'int', 'vm-clock-sec': 'int', 'vm-clock-nsec': 'int' } } @@ -45,7 +45,7 @@ # # Since: 1.7 ## -{ 'type': 'ImageInfoSpecificQCow2', +{ 'struct': 'ImageInfoSpecificQCow2', 'data': { 'compat': 'str', '*lazy-refcounts': 'bool', @@ -66,7 +66,7 @@ # # Since: 1.7 ## -{ 'type': 'ImageInfoSpecificVmdk', +{ 'struct': 'ImageInfoSpecificVmdk', 'data': { 'create-type': 'str', 'cid': 'int', @@ -126,7 +126,7 @@ # ## -{ 'type': 'ImageInfo', +{ 'struct': 'ImageInfo', 'data': {'filename': 'str', 'format': 'str', '*dirty-flag': 'bool', '*actual-size': 'int', 'virtual-size': 'int', '*cluster-size': 'int', '*encrypted': 'bool', '*compressed': 'bool', @@ -178,7 +178,7 @@ # ## -{ 'type': 'ImageCheck', +{ 'struct': 'ImageCheck', 'data': {'filename': 'str', 'format': 'str', 'check-errors': 'int', '*image-end-offset': 'int', '*corruptions': 'int', '*leaks': 'int', '*corruptions-fixed': 'int', '*leaks-fixed': 'int', @@ -196,7 +196,7 @@ # # Since: 2.3 ## -{ 'type': 'BlockdevCacheInfo', +{ 'struct': 'BlockdevCacheInfo', 'data': { 'writeback': 'bool', 'direct': 'bool', 'no-flush': 'bool' } } @@ -267,7 +267,7 @@ # Since: 0.14.0 # ## -{ 'type': 'BlockDeviceInfo', +{ 'struct': 'BlockDeviceInfo', 'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str', '*backing_file': 'str', 'backing_file_depth': 'int', 'encrypted': 'bool', 'encryption_key_missing': 'bool', @@ -321,7 +321,7 @@ # # Since 1.7 ## -{ 'type': 'BlockDeviceMapEntry', +{ 'struct': 'BlockDeviceMapEntry', 'data': { 'start': 'int', 'length': 'int', 'depth': 'int', 'zero': 'bool', 'data': 'bool', '*offset': 'int' } } @@ -340,7 +340,7 @@ # # Since: 1.3 ## -{ 'type': 'BlockDirtyInfo', +{ 'struct': 'BlockDirtyInfo', 'data': {'*name': 'str', 'count': 'int', 'granularity': 'uint32', 'frozen': 'bool'} } @@ -375,7 +375,7 @@ # # Since: 0.14.0 ## -{ 'type': 'BlockInfo', +{ 'struct': 'BlockInfo', 'data': {'device': 'str', 'type': 'str', 'removable': 'bool', 'locked': 'bool', '*inserted': 'BlockDeviceInfo', '*tray_open': 'bool', '*io-status': 'BlockDeviceIoStatus', @@ -428,7 +428,7 @@ # # Since: 0.14.0 ## -{ 'type': 'BlockDeviceStats', +{ 'struct': 'BlockDeviceStats', 'data': {'rd_bytes': 'int', 'wr_bytes': 'int', 'rd_operations': 'int', 'wr_operations': 'int', 'flush_operations': 'int', 'flush_total_time_ns': 'int', 'wr_total_time_ns': 'int', @@ -454,7 +454,7 @@ # # Since: 0.14.0 ## -{ 'type': 'BlockStats', +{ 'struct': 'BlockStats', 'data': {'*device': 'str', '*node-name': 'str', 'stats': 'BlockDeviceStats', '*parent': 'BlockStats', @@ -567,7 +567,7 @@ # # Since: 1.1 ## -{ 'type': 'BlockJobInfo', +{ 'struct': 'BlockJobInfo', 'data': {'type': 'str', 'device': 'str', 'len': 'int', 'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int', 'io-status': 'BlockDeviceIoStatus', 'ready': 'bool'} } @@ -677,7 +677,7 @@ # @mode: #optional whether and how QEMU should create a new image, default is # 'absolute-paths'. ## -{ 'type': 'BlockdevSnapshot', +{ 'struct': 'BlockdevSnapshot', 'data': { '*device': 'str', '*node-name': 'str', 'snapshot-file': 'str', '*snapshot-node-name': 'str', '*format': 'str', '*mode': 'NewImageMode' } } @@ -721,7 +721,7 @@ # # Since: 1.6 ## -{ 'type': 'DriveBackup', +{ 'struct': 'DriveBackup', 'data': { 'device': 'str', 'target': 'str', '*format': 'str', 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode', '*speed': 'int', '*bitmap': 'str', @@ -756,7 +756,7 @@ # # Since: 2.3 ## -{ 'type': 'BlockdevBackup', +{ 'struct': 'BlockdevBackup', 'data': { 'device': 'str', 'target': 'str', 'sync': 'MirrorSyncMode', '*speed': 'int', @@ -977,7 +977,7 @@ # # Since 2.4 ## -{ 'type': 'BlockDirtyBitmap', +{ 'struct': 'BlockDirtyBitmap', 'data': { 'node': 'str', 'name': 'str' } } ## @@ -992,7 +992,7 @@ # # Since 2.4 ## -{ 'type': 'BlockDirtyBitmapAdd', +{ 'struct': 'BlockDirtyBitmapAdd', 'data': { 'node': 'str', 'name': 'str', '*granularity': 'uint32' } } ## @@ -1313,7 +1313,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevCacheOptions', +{ 'struct': 'BlockdevCacheOptions', 'data': { '*writeback': 'bool', '*direct': 'bool', '*no-flush': 'bool' } } @@ -1360,7 +1360,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevOptionsBase', +{ 'struct': 'BlockdevOptionsBase', 'data': { 'driver': 'BlockdevDriver', '*id': 'str', '*node-name': 'str', @@ -1382,7 +1382,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevOptionsFile', +{ 'struct': 'BlockdevOptionsFile', 'data': { 'filename': 'str' } } ## @@ -1397,7 +1397,7 @@ # # Since: 2.2 ## -{ 'type': 'BlockdevOptionsNull', +{ 'struct': 'BlockdevOptionsNull', 'data': { '*size': 'int', '*latency-ns': 'uint64' } } ## @@ -1413,7 +1413,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevOptionsVVFAT', +{ 'struct': 'BlockdevOptionsVVFAT', 'data': { 'dir': 'str', '*fat-type': 'int', '*floppy': 'bool', '*rw': 'bool' } } @@ -1427,7 +1427,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevOptionsGenericFormat', +{ 'struct': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'BlockdevRef' } } ## @@ -1443,7 +1443,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevOptionsGenericCOWFormat', +{ 'struct': 'BlockdevOptionsGenericCOWFormat', 'base': 'BlockdevOptionsGenericFormat', 'data': { '*backing': 'BlockdevRef' } } @@ -1479,7 +1479,7 @@ # # Since: 2.2 ## -{ 'type': 'Qcow2OverlapCheckFlags', +{ 'struct': 'Qcow2OverlapCheckFlags', 'data': { '*template': 'Qcow2OverlapCheckMode', '*main-header': 'bool', '*active-l1': 'bool', @@ -1540,7 +1540,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevOptionsQcow2', +{ 'struct': 'BlockdevOptionsQcow2', 'base': 'BlockdevOptionsGenericCOWFormat', 'data': { '*lazy-refcounts': 'bool', '*pass-discard-request': 'bool', @@ -1575,7 +1575,7 @@ # use the default value, 'archipelago'. # Since: 2.2 ## -{ 'type': 'BlockdevOptionsArchipelago', +{ 'struct': 'BlockdevOptionsArchipelago', 'data': { 'volume': 'str', '*mport': 'int', '*vport': 'int', @@ -1627,7 +1627,7 @@ # # Since: 2.0 ## -{ 'type': 'BlkdebugInjectErrorOptions', +{ 'struct': 'BlkdebugInjectErrorOptions', 'data': { 'event': 'BlkdebugEvent', '*state': 'int', '*errno': 'int', @@ -1650,7 +1650,7 @@ # # Since: 2.0 ## -{ 'type': 'BlkdebugSetStateOptions', +{ 'struct': 'BlkdebugSetStateOptions', 'data': { 'event': 'BlkdebugEvent', '*state': 'int', 'new_state': 'int' } } @@ -1672,7 +1672,7 @@ # # Since: 2.0 ## -{ 'type': 'BlockdevOptionsBlkdebug', +{ 'struct': 'BlockdevOptionsBlkdebug', 'data': { 'image': 'BlockdevRef', '*config': 'str', '*align': 'int', @@ -1690,7 +1690,7 @@ # # Since: 2.0 ## -{ 'type': 'BlockdevOptionsBlkverify', +{ 'struct': 'BlockdevOptionsBlkverify', 'data': { 'test': 'BlockdevRef', 'raw': 'BlockdevRef' } } @@ -1727,7 +1727,7 @@ # # Since: 2.0 ## -{ 'type': 'BlockdevOptionsQuorum', +{ 'struct': 'BlockdevOptionsQuorum', 'data': { '*blkverify': 'bool', 'children': [ 'BlockdevRef' ], 'vote-threshold': 'int', diff --git a/qapi/block.json b/qapi/block.json index e3134657b6..aad645c4a6 100644 --- a/qapi/block.json +++ b/qapi/block.json @@ -52,7 +52,7 @@ # # Since: 1.7 ## -{ 'type': 'BlockdevSnapshotInternal', +{ 'struct': 'BlockdevSnapshotInternal', 'data': { 'device': 'str', 'name': 'str' } } ## diff --git a/qapi/common.json b/qapi/common.json index 63ef3b4724..12431c691b 100644 --- a/qapi/common.json +++ b/qapi/common.json @@ -50,7 +50,7 @@ # # Since: 0.14.0 ## -{ 'type': 'VersionInfo', +{ 'struct': 'VersionInfo', 'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'}, 'package': 'str'} } @@ -74,7 +74,7 @@ # # Since: 0.14.0 ## -{ 'type': 'CommandInfo', 'data': {'name': 'str'} } +{ 'struct': 'CommandInfo', 'data': {'name': 'str'} } ## # @query-commands: diff --git a/qapi/trace.json b/qapi/trace.json index 06c613c213..01b0a52a7e 100644 --- a/qapi/trace.json +++ b/qapi/trace.json @@ -32,7 +32,7 @@ # # Since 2.2 ## -{ 'type': 'TraceEventInfo', +{ 'struct': 'TraceEventInfo', 'data': {'name': 'str', 'state': 'TraceEventState'} } ## diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index fecc4427d5..b446dc729d 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -150,7 +150,7 @@ # # Since 1.1.0 ## -{ 'type': 'GuestAgentCommandInfo', +{ 'struct': 'GuestAgentCommandInfo', 'data': { 'name': 'str', 'enabled': 'bool', 'success-response': 'bool' } } ## @@ -164,7 +164,7 @@ # # Since 0.15.0 ## -{ 'type': 'GuestAgentInfo', +{ 'struct': 'GuestAgentInfo', 'data': { 'version': 'str', 'supported_commands': ['GuestAgentCommandInfo'] } } ## @@ -242,7 +242,7 @@ # # Since: 0.15.0 ## -{ 'type': 'GuestFileRead', +{ 'struct': 'GuestFileRead', 'data': { 'count': 'int', 'buf-b64': 'str', 'eof': 'bool' } } ## @@ -274,7 +274,7 @@ # # Since: 0.15.0 ## -{ 'type': 'GuestFileWrite', +{ 'struct': 'GuestFileWrite', 'data': { 'count': 'int', 'eof': 'bool' } } ## @@ -309,7 +309,7 @@ # # Since: 0.15.0 ## -{ 'type': 'GuestFileSeek', +{ 'struct': 'GuestFileSeek', 'data': { 'position': 'int', 'eof': 'bool' } } ## @@ -556,7 +556,7 @@ # # Since: 1.1 ## -{ 'type': 'GuestIpAddress', +{ 'struct': 'GuestIpAddress', 'data': {'ip-address': 'str', 'ip-address-type': 'GuestIpAddressType', 'prefix': 'int'} } @@ -572,7 +572,7 @@ # # Since: 1.1 ## -{ 'type': 'GuestNetworkInterface', +{ 'struct': 'GuestNetworkInterface', 'data': {'name': 'str', '*hardware-address': 'str', '*ip-addresses': ['GuestIpAddress'] } } @@ -604,7 +604,7 @@ # # Since: 1.5 ## -{ 'type': 'GuestLogicalProcessor', +{ 'struct': 'GuestLogicalProcessor', 'data': {'logical-id': 'int', 'online': 'bool', '*can-offline': 'bool'} } @@ -694,7 +694,7 @@ # # Since: 2.2 ## -{ 'type': 'GuestPCIAddress', +{ 'struct': 'GuestPCIAddress', 'data': {'domain': 'int', 'bus': 'int', 'slot': 'int', 'function': 'int'} } @@ -709,7 +709,7 @@ # # Since: 2.2 ## -{ 'type': 'GuestDiskAddress', +{ 'struct': 'GuestDiskAddress', 'data': {'pci-controller': 'GuestPCIAddress', 'bus-type': 'GuestDiskBusType', 'bus': 'int', 'target': 'int', 'unit': 'int'} } @@ -725,7 +725,7 @@ # # Since: 2.2 ## -{ 'type': 'GuestFilesystemInfo', +{ 'struct': 'GuestFilesystemInfo', 'data': {'name': 'str', 'mountpoint': 'str', 'type': 'str', 'disk': ['GuestDiskAddress']} } @@ -782,7 +782,7 @@ # # Since: 2.3 ## -{ 'type': 'GuestMemoryBlock', +{ 'struct': 'GuestMemoryBlock', 'data': {'phys-index': 'uint64', 'online': 'bool', '*can-offline': 'bool'} } @@ -835,7 +835,7 @@ # # Since: 2.3 ## -{ 'type': 'GuestMemoryBlockResponse', +{ 'struct': 'GuestMemoryBlockResponse', 'data': { 'phys-index': 'uint64', 'response': 'GuestMemoryBlockResponseType', '*error-code': 'int' }} @@ -876,7 +876,7 @@ # # Since: 2.3 ## -{ 'type': 'GuestMemoryBlockInfo', +{ 'struct': 'GuestMemoryBlockInfo', 'data': {'size': 'uint64'} } ## diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json index fc0632f128..f241aac122 100644 --- a/tests/qapi-schema/alternate-array.json +++ b/tests/qapi-schema/alternate-array.json @@ -1,6 +1,6 @@ # we do not allow array branches in alternates # TODO: should we support this? -{ 'type': 'One', +{ 'struct': 'One', 'data': { 'name': 'str' } } { 'alternate': 'Alt', 'data': { 'one': 'One', diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json index 66edc89640..529430ecf2 100644 --- a/tests/qapi-schema/alternate-base.json +++ b/tests/qapi-schema/alternate-base.json @@ -1,5 +1,5 @@ # we reject alternate with base type -{ 'type': 'Base', +{ 'struct': 'Base', 'data': { 'string': 'str' } } { 'alternate': 'Alt', 'base': 'Base', diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json index fcb3e36cd9..d566cca816 100644 --- a/tests/qapi-schema/alternate-conflict-dict.json +++ b/tests/qapi-schema/alternate-conflict-dict.json @@ -1,7 +1,7 @@ # we reject alternates with multiple object branches -{ 'type': 'One', +{ 'struct': 'One', 'data': { 'name': 'str' } } -{ 'type': 'Two', +{ 'struct': 'Two', 'data': { 'value': 'int' } } { 'alternate': 'Alt', 'data': { 'one': 'One', diff --git a/tests/qapi-schema/alternate-good.json b/tests/qapi-schema/alternate-good.json index 99d614f138..33717704ce 100644 --- a/tests/qapi-schema/alternate-good.json +++ b/tests/qapi-schema/alternate-good.json @@ -1,5 +1,5 @@ # Working example of alternate -{ 'type': 'Data', +{ 'struct': 'Data', 'data': { '*number': 'int', '*name': 'str' } } { 'enum': 'Enum', 'data': [ 'hello', 'world' ] } diff --git a/tests/qapi-schema/bad-base.json b/tests/qapi-schema/bad-base.json index a6904706ad..a634331cdd 100644 --- a/tests/qapi-schema/bad-base.json +++ b/tests/qapi-schema/bad-base.json @@ -1,3 +1,3 @@ # we reject a base that is not a struct { 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } } -{ 'type': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } } +{ 'struct': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } } diff --git a/tests/qapi-schema/bad-ident.json b/tests/qapi-schema/bad-ident.json index da949e8903..763627ad23 100644 --- a/tests/qapi-schema/bad-ident.json +++ b/tests/qapi-schema/bad-ident.json @@ -1,2 +1,2 @@ # we reject creating a type name with bad name -{ 'type': '*oops', 'data': { 'i': 'int' } } +{ 'struct': '*oops', 'data': { 'i': 'int' } } diff --git a/tests/qapi-schema/bad-type-bool.json b/tests/qapi-schema/bad-type-bool.json index e1e9fb0dde..bde17b56c4 100644 --- a/tests/qapi-schema/bad-type-bool.json +++ b/tests/qapi-schema/bad-type-bool.json @@ -1,2 +1,2 @@ # we reject an expression with a metatype that is not a string -{ 'type': true, 'data': { } } +{ 'struct': true, 'data': { } } diff --git a/tests/qapi-schema/bad-type-int.err b/tests/qapi-schema/bad-type-int.err index 9808550007..da89895404 100644 --- a/tests/qapi-schema/bad-type-int.err +++ b/tests/qapi-schema/bad-type-int.err @@ -1 +1 @@ -tests/qapi-schema/bad-type-int.json:3:11: Stray "1" +tests/qapi-schema/bad-type-int.json:3:13: Stray "1" diff --git a/tests/qapi-schema/bad-type-int.json b/tests/qapi-schema/bad-type-int.json index 398879df95..56fc6f8126 100644 --- a/tests/qapi-schema/bad-type-int.json +++ b/tests/qapi-schema/bad-type-int.json @@ -1,3 +1,3 @@ # we reject an expression with a metatype that is not a string # FIXME: once the parser understands integer inputs, improve the error message -{ 'type': 1, 'data': { } } +{ 'struct': 1, 'data': { } } diff --git a/tests/qapi-schema/data-member-array.json b/tests/qapi-schema/data-member-array.json index 7cce276979..e6f7f5da13 100644 --- a/tests/qapi-schema/data-member-array.json +++ b/tests/qapi-schema/data-member-array.json @@ -1,4 +1,4 @@ # valid array members { 'enum': 'abc', 'data': [ 'a', 'b', 'c' ] } -{ 'type': 'def', 'data': { 'array': [ 'abc' ] } } +{ 'struct': 'def', 'data': { 'array': [ 'abc' ] } } { 'command': 'okay', 'data': { 'member1': [ 'int' ], 'member2': [ 'def' ] } } diff --git a/tests/qapi-schema/double-data.err b/tests/qapi-schema/double-data.err index 6f1a67b907..cc765c4ff2 100644 --- a/tests/qapi-schema/double-data.err +++ b/tests/qapi-schema/double-data.err @@ -1 +1 @@ -tests/qapi-schema/double-data.json:2:39: Duplicate key "data" +tests/qapi-schema/double-data.json:2:41: Duplicate key "data" diff --git a/tests/qapi-schema/double-data.json b/tests/qapi-schema/double-data.json index a94b7dfe22..e76b519538 100644 --- a/tests/qapi-schema/double-data.json +++ b/tests/qapi-schema/double-data.json @@ -1,2 +1,2 @@ # we reject an expression with duplicate top-level keys -{ 'type': 'bar', 'data': { }, 'data': { 'string': 'str'} } +{ 'struct': 'bar', 'data': { }, 'data': { 'string': 'str'} } diff --git a/tests/qapi-schema/double-type.json b/tests/qapi-schema/double-type.json index 471623a2e5..911fa7af50 100644 --- a/tests/qapi-schema/double-type.json +++ b/tests/qapi-schema/double-type.json @@ -1,2 +1,2 @@ # we reject an expression with ambiguous metatype -{ 'command': 'foo', 'type': 'bar', 'data': { } } +{ 'command': 'foo', 'struct': 'bar', 'data': { } } diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json index d41c80c3bd..e2e622bb6e 100644 --- a/tests/qapi-schema/flat-union-bad-base.json +++ b/tests/qapi-schema/flat-union-bad-base.json @@ -2,9 +2,9 @@ # TODO: should we allow an anonymous inline base type? { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', 'base': { 'enum1': 'TestEnum', 'kind': 'str' }, diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json index 982f072555..cd10b9d901 100644 --- a/tests/qapi-schema/flat-union-bad-discriminator.json +++ b/tests/qapi-schema/flat-union-bad-discriminator.json @@ -2,11 +2,11 @@ # this tests the old syntax for anonymous unions before we added alternates { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestBase', +{ 'struct': 'TestBase', 'data': { 'enum1': 'TestEnum', 'kind': 'str' } } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', 'base': 'TestBase', diff --git a/tests/qapi-schema/flat-union-base-star.json b/tests/qapi-schema/flat-union-base-star.json index 76724075b1..5099439a9d 100644 --- a/tests/qapi-schema/flat-union-base-star.json +++ b/tests/qapi-schema/flat-union-base-star.json @@ -1,9 +1,9 @@ # we require the base to be an existing struct { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', 'base': '**', diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json index 0ba6e28d3b..6a8ea687a9 100644 --- a/tests/qapi-schema/flat-union-base-union.json +++ b/tests/qapi-schema/flat-union-base-union.json @@ -1,9 +1,9 @@ # we require the base to be a struct { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'UnionBase', 'data': { 'kind1': 'TestTypeA', diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json index 4091477b04..8b0b807a03 100644 --- a/tests/qapi-schema/flat-union-branch-clash.json +++ b/tests/qapi-schema/flat-union-branch-clash.json @@ -1,11 +1,11 @@ # FIXME: we should check for no duplicate keys between branches and base { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'Base', +{ 'struct': 'Base', 'data': { 'enum1': 'TestEnum', 'name': 'str' } } -{ 'type': 'Branch1', +{ 'struct': 'Branch1', 'data': { 'name': 'str' } } -{ 'type': 'Branch2', +{ 'struct': 'Branch2', 'data': { 'value': 'int' } } { 'union': 'TestUnion', 'base': 'Base', diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json index 65c15d0a1e..6bfdd65811 100644 --- a/tests/qapi-schema/flat-union-inline.json +++ b/tests/qapi-schema/flat-union-inline.json @@ -2,7 +2,7 @@ # TODO: should we allow anonymous inline types? { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'Base', +{ 'struct': 'Base', 'data': { 'enum1': 'TestEnum', 'kind': 'str' } } { 'union': 'TestUnion', 'base': { 'enum1': 'TestEnum', 'kind': 'str' }, diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json index ee93cf803a..9370c349e8 100644 --- a/tests/qapi-schema/flat-union-int-branch.json +++ b/tests/qapi-schema/flat-union-int-branch.json @@ -1,9 +1,9 @@ # we require flat union branches to be a struct { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'Base', +{ 'struct': 'Base', 'data': { 'enum1': 'TestEnum' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', 'base': 'Base', diff --git a/tests/qapi-schema/flat-union-invalid-branch-key.json b/tests/qapi-schema/flat-union-invalid-branch-key.json index a6242823ed..95ff7746bf 100644 --- a/tests/qapi-schema/flat-union-invalid-branch-key.json +++ b/tests/qapi-schema/flat-union-invalid-branch-key.json @@ -1,13 +1,13 @@ { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestBase', +{ 'struct': 'TestBase', 'data': { 'enum1': 'TestEnum' } } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.json b/tests/qapi-schema/flat-union-invalid-discriminator.json index 887157e173..48b94c3a4d 100644 --- a/tests/qapi-schema/flat-union-invalid-discriminator.json +++ b/tests/qapi-schema/flat-union-invalid-discriminator.json @@ -1,13 +1,13 @@ { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestBase', +{ 'struct': 'TestBase', 'data': { 'enum1': 'TestEnum' } } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json index 9547bb8988..ffc4c6f0e6 100644 --- a/tests/qapi-schema/flat-union-no-base.json +++ b/tests/qapi-schema/flat-union-no-base.json @@ -1,8 +1,8 @@ # flat unions require a base # TODO: simple unions should be able to use an enum discriminator -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'enum': 'Enum', 'data': [ 'value1', 'value2' ] } diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json index 25ce0e6612..08a8f7ef8b 100644 --- a/tests/qapi-schema/flat-union-optional-discriminator.json +++ b/tests/qapi-schema/flat-union-optional-discriminator.json @@ -1,8 +1,8 @@ # we require the discriminator to be non-optional { 'enum': 'Enum', 'data': [ 'one', 'two' ] } -{ 'type': 'Base', +{ 'struct': 'Base', 'data': { '*switch': 'Enum' } } -{ 'type': 'Branch', 'data': { 'name': 'str' } } +{ 'struct': 'Branch', 'data': { 'name': 'str' } } { 'union': 'MyUnion', 'base': 'Base', 'discriminator': '*switch', diff --git a/tests/qapi-schema/flat-union-reverse-define.json b/tests/qapi-schema/flat-union-reverse-define.json index 9ea7e72201..648bbfe2b7 100644 --- a/tests/qapi-schema/flat-union-reverse-define.json +++ b/tests/qapi-schema/flat-union-reverse-define.json @@ -4,14 +4,14 @@ 'data': { 'value1': 'TestTypeA', 'value2': 'TestTypeB' } } -{ 'type': 'TestBase', +{ 'struct': 'TestBase', 'data': { 'enum1': 'TestEnum' } } { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } diff --git a/tests/qapi-schema/flat-union-string-discriminator.json b/tests/qapi-schema/flat-union-string-discriminator.json index e966aeb395..8af60333b6 100644 --- a/tests/qapi-schema/flat-union-string-discriminator.json +++ b/tests/qapi-schema/flat-union-string-discriminator.json @@ -1,13 +1,13 @@ { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } -{ 'type': 'TestBase', +{ 'struct': 'TestBase', 'data': { 'enum1': 'TestEnum', 'kind': 'str' } } -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index dec8a7c108..f10efe2e2f 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -3,40 +3,40 @@ # for testing enums { 'enum': 'EnumOne', 'data': [ 'value1', 'value2', 'value3' ] } -{ 'type': 'NestedEnumsOne', +{ 'struct': 'NestedEnumsOne', 'data': { 'enum1': 'EnumOne', '*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } } # for testing nested structs -{ 'type': 'UserDefZero', +{ 'struct': 'UserDefZero', 'data': { 'integer': 'int' } } -{ 'type': 'UserDefOne', +{ 'struct': 'UserDefOne', 'base': 'UserDefZero', 'data': { 'string': 'str', '*enum1': 'EnumOne' } } -{ 'type': 'UserDefTwo', +{ 'struct': 'UserDefTwo', 'data': { 'string': 'str', 'dict': { 'string': 'str', 'dict': { 'userdef': 'UserDefOne', 'string': 'str' }, '*dict2': { 'userdef': 'UserDefOne', 'string': 'str' } } } } -{ 'type': 'UserDefNested', +{ 'struct': 'UserDefNested', 'data': { 'string0': 'str', 'dict1': { 'string1': 'str', 'dict2': { 'userdef1': 'UserDefOne', 'string2': 'str' }, '*dict3': { 'userdef2': 'UserDefOne', 'string3': 'str' } } } } # for testing unions -{ 'type': 'UserDefA', +{ 'struct': 'UserDefA', 'data': { 'boolean': 'bool' } } -{ 'type': 'UserDefB', +{ 'struct': 'UserDefB', 'data': { 'integer': 'int' } } -{ 'type': 'UserDefC', +{ 'struct': 'UserDefC', 'data': { 'string1': 'str', 'string2': 'str' } } -{ 'type': 'UserDefUnionBase', +{ 'struct': 'UserDefUnionBase', 'data': { 'string': 'str', 'enum1': 'EnumOne' } } { 'union': 'UserDefFlatUnion', @@ -88,7 +88,7 @@ # # For simplicity, this example doesn't use [type=]discriminator nor optargs # specific to discriminator values. -{ 'type': 'UserDefOptions', +{ 'struct': 'UserDefOptions', 'data': { '*i64' : [ 'int' ], '*u64' : [ 'uint64' ], @@ -97,7 +97,7 @@ '*u64x': 'uint64' } } # testing event -{ 'type': 'EventStructOne', +{ 'struct': 'EventStructOne', 'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } } { 'event': 'EVENT_A' } diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json index df328ccc66..45b8a550ad 100644 --- a/tests/qapi-schema/redefined-builtin.json +++ b/tests/qapi-schema/redefined-builtin.json @@ -1,2 +1,2 @@ # we reject types that duplicate builtin names -{ 'type': 'size', 'data': { 'myint': 'size' } } +{ 'struct': 'size', 'data': { 'myint': 'size' } } diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json index e6a5f24ca9..a09e768bae 100644 --- a/tests/qapi-schema/redefined-type.json +++ b/tests/qapi-schema/redefined-type.json @@ -1,3 +1,3 @@ # we reject types defined more than once -{ 'type': 'foo', 'data': { 'one': 'str' } } +{ 'struct': 'foo', 'data': { 'one': 'str' } } { 'enum': 'foo', 'data': [ 'two' ] } diff --git a/tests/qapi-schema/union-bad-branch.json b/tests/qapi-schema/union-bad-branch.json index 4303666bb5..913aa38bc8 100644 --- a/tests/qapi-schema/union-bad-branch.json +++ b/tests/qapi-schema/union-bad-branch.json @@ -1,7 +1,7 @@ # we reject normal unions where branches would collide in C -{ 'type': 'One', +{ 'struct': 'One', 'data': { 'string': 'str' } } -{ 'type': 'Two', +{ 'struct': 'Two', 'data': { 'number': 'int' } } { 'union': 'MyUnion', 'data': { 'one': 'One', diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json index 052596c46e..1409cf5c9e 100644 --- a/tests/qapi-schema/union-base-no-discriminator.json +++ b/tests/qapi-schema/union-base-no-discriminator.json @@ -1,11 +1,11 @@ # we reject simple unions with a base (or flat unions without discriminator) -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } -{ 'type': 'Base', +{ 'struct': 'Base', 'data': { 'string': 'str' } } { 'union': 'TestUnion', diff --git a/tests/qapi-schema/union-invalid-base.json b/tests/qapi-schema/union-invalid-base.json index bc5dc8d043..92be39df69 100644 --- a/tests/qapi-schema/union-invalid-base.json +++ b/tests/qapi-schema/union-invalid-base.json @@ -1,8 +1,8 @@ # a union base type must be a struct -{ 'type': 'TestTypeA', +{ 'struct': 'TestTypeA', 'data': { 'string': 'str' } } -{ 'type': 'TestTypeB', +{ 'struct': 'TestTypeB', 'data': { 'integer': 'int' } } { 'union': 'TestUnion', diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json index ba7bdf3ed6..3b2be00cc4 100644 --- a/tests/qapi-schema/unknown-expr-key.json +++ b/tests/qapi-schema/unknown-expr-key.json @@ -1,2 +1,2 @@ # we reject an expression with unknown top-level keys -{ 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } } +{ 'struct': 'bar', 'data': { 'string': 'str'}, 'bogus': { } } From 3e391d355644b2bff7c9f187759aadb46c6e051f Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:28 -0600 Subject: [PATCH 31/40] qapi: Forbid 'type' in schema Referring to "type" as both a meta-type (built-in, enum, union, alternate, or struct) and a specific type (the name that the schema uses for declaring structs) is confusing. Finish up the conversion to using "struct" in qapi schema by removing the hack in the generator that allowed 'type'. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scripts/qapi.py b/scripts/qapi.py index e50fec826b..333f59ab5b 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -617,20 +617,6 @@ def parse_schema(input_file): for expr_elem in schema.exprs: expr = expr_elem['expr'] info = expr_elem['info'] - - # back-compat hack until all schemas have been converted; - # preserve the ordering of the original expression - if expr.has_key('type'): - seen_type = False - for (key, value) in expr.items(): - if key == 'type': - seen_type = True - del expr['type'] - expr['struct'] = value - elif seen_type: - del expr[key] - expr[key] = value - if expr.has_key('enum'): check_keys(expr_elem, 'enum', ['data']) add_enum(expr['enum'], info, expr['data']) From b6fcf32d9b851a83dedcb609091236b97cc4a985 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:29 -0600 Subject: [PATCH 32/40] qapi: Merge UserDefTwo and UserDefNested in tests In the testsuite, UserDefTwo and UserDefNested were identical structs other than the member names. Reduce code duplication by having just one type, and choose names that also favor reuse. This will also make it easier for a later patch to get rid of inline nested types in QAPI. When touching code related to allocations, convert g_malloc0(sizeof(Type)) to the more typesafe g_new0(Type, 1). Ensure that 'make check-qapi-schema check-unit' still passes. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/qapi-schema/qapi-schema-test.json | 10 +--- tests/qapi-schema/qapi-schema-test.out | 6 +- tests/test-qmp-commands.c | 34 ++++++------ tests/test-qmp-input-strict.c | 17 +++--- tests/test-qmp-input-visitor.c | 17 +++--- tests/test-qmp-output-visitor.c | 48 ++++++++-------- tests/test-visitor-serialization.c | 73 +++++++++++++------------ 7 files changed, 102 insertions(+), 103 deletions(-) diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index f10efe2e2f..a6be983100 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -15,16 +15,10 @@ 'data': { 'string': 'str', '*enum1': 'EnumOne' } } { 'struct': 'UserDefTwo', - 'data': { 'string': 'str', - 'dict': { 'string': 'str', - 'dict': { 'userdef': 'UserDefOne', 'string': 'str' }, - '*dict2': { 'userdef': 'UserDefOne', 'string': 'str' } } } } - -{ 'struct': 'UserDefNested', 'data': { 'string0': 'str', 'dict1': { 'string1': 'str', - 'dict2': { 'userdef1': 'UserDefOne', 'string2': 'str' }, - '*dict3': { 'userdef2': 'UserDefOne', 'string3': 'str' } } } } + 'dict2': { 'userdef': 'UserDefOne', 'string': 'str' }, + '*dict3': { 'userdef': 'UserDefOne', 'string': 'str' } } } } # for testing unions { 'struct': 'UserDefA', diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 83ab1a5139..48f8f0ee6e 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -2,8 +2,7 @@ OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), - OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), @@ -28,8 +27,7 @@ [OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), - OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c index 554e222b32..8b50eac43d 100644 --- a/tests/test-qmp-commands.c +++ b/tests/test-qmp-commands.c @@ -31,14 +31,14 @@ UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a, ud1d->base = g_new0(UserDefZero, 1); ud1d->base->integer = has_udb1 ? ud1b->base->integer : 0; - ret = g_malloc0(sizeof(UserDefTwo)); - ret->string = strdup("blah1"); - ret->dict.string = strdup("blah2"); - ret->dict.dict.userdef = ud1c; - ret->dict.dict.string = strdup("blah3"); - ret->dict.has_dict2 = true; - ret->dict.dict2.userdef = ud1d; - ret->dict.dict2.string = strdup("blah4"); + ret = g_new0(UserDefTwo, 1); + ret->string0 = strdup("blah1"); + ret->dict1.string1 = strdup("blah2"); + ret->dict1.dict2.userdef = ud1c; + ret->dict1.dict2.string = strdup("blah3"); + ret->dict1.has_dict3 = true; + ret->dict1.dict3.userdef = ud1d; + ret->dict1.dict3.string = strdup("blah4"); return ret; } @@ -120,15 +120,15 @@ static void test_dispatch_cmd_io(void) ret = qobject_to_qdict(test_qmp_dispatch(req)); - assert(!strcmp(qdict_get_str(ret, "string"), "blah1")); - ret_dict = qdict_get_qdict(ret, "dict"); - assert(!strcmp(qdict_get_str(ret_dict, "string"), "blah2")); - ret_dict_dict = qdict_get_qdict(ret_dict, "dict"); + assert(!strcmp(qdict_get_str(ret, "string0"), "blah1")); + ret_dict = qdict_get_qdict(ret, "dict1"); + assert(!strcmp(qdict_get_str(ret_dict, "string1"), "blah2")); + ret_dict_dict = qdict_get_qdict(ret_dict, "dict2"); ret_dict_dict_userdef = qdict_get_qdict(ret_dict_dict, "userdef"); assert(qdict_get_int(ret_dict_dict_userdef, "integer") == 42); assert(!strcmp(qdict_get_str(ret_dict_dict_userdef, "string"), "hello")); assert(!strcmp(qdict_get_str(ret_dict_dict, "string"), "blah3")); - ret_dict_dict2 = qdict_get_qdict(ret_dict, "dict2"); + ret_dict_dict2 = qdict_get_qdict(ret_dict, "dict3"); ret_dict_dict2_userdef = qdict_get_qdict(ret_dict_dict2, "userdef"); assert(qdict_get_int(ret_dict_dict2_userdef, "integer") == 422); assert(!strcmp(qdict_get_str(ret_dict_dict2_userdef, "string"), "hello2")); @@ -192,7 +192,7 @@ static void test_dealloc_partial(void) QmpInputVisitor *qiv; ud2_dict = qdict_new(); - qdict_put_obj(ud2_dict, "string", QOBJECT(qstring_from_str(text))); + qdict_put_obj(ud2_dict, "string0", QOBJECT(qstring_from_str(text))); qiv = qmp_input_visitor_new(QOBJECT(ud2_dict)); visit_type_UserDefTwo(qmp_input_get_visitor(qiv), &ud2, NULL, &err); @@ -202,9 +202,9 @@ static void test_dealloc_partial(void) /* verify partial success */ assert(ud2 != NULL); - assert(ud2->string != NULL); - assert(strcmp(ud2->string, text) == 0); - assert(ud2->dict.dict.userdef == NULL); + assert(ud2->string0 != NULL); + assert(strcmp(ud2->string0, text) == 0); + assert(ud2->dict1.dict2.userdef == NULL); /* confirm & release construction error */ assert(err != NULL); diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c index a5f7bf3c3e..68f855bdf3 100644 --- a/tests/test-qmp-input-strict.c +++ b/tests/test-qmp-input-strict.c @@ -116,15 +116,18 @@ static void test_validate_struct(TestInputVisitorData *data, static void test_validate_struct_nested(TestInputVisitorData *data, const void *unused) { - UserDefNested *udp = NULL; + UserDefTwo *udp = NULL; Error *err = NULL; Visitor *v; - v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}"); + v = validate_test_init(data, "{ 'string0': 'string0', " + "'dict1': { 'string1': 'string1', " + "'dict2': { 'userdef': { 'integer': 42, " + "'string': 'string' }, 'string': 'string2'}}}"); - visit_type_UserDefNested(v, &udp, NULL, &err); + visit_type_UserDefTwo(v, &udp, NULL, &err); g_assert(!err); - qapi_free_UserDefNested(udp); + qapi_free_UserDefTwo(udp); } static void test_validate_list(TestInputVisitorData *data, @@ -207,15 +210,15 @@ static void test_validate_fail_struct(TestInputVisitorData *data, static void test_validate_fail_struct_nested(TestInputVisitorData *data, const void *unused) { - UserDefNested *udp = NULL; + UserDefTwo *udp = NULL; Error *err = NULL; Visitor *v; v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string', 'extra': [42, 23, {'foo':'bar'}] }, 'string2': 'string2'}}}"); - visit_type_UserDefNested(v, &udp, NULL, &err); + visit_type_UserDefTwo(v, &udp, NULL, &err); g_assert(err); - qapi_free_UserDefNested(udp); + qapi_free_UserDefTwo(udp); } static void test_validate_fail_list(TestInputVisitorData *data, diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index 882359a34f..115fb22c7e 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -248,23 +248,26 @@ static void check_and_free_str(char *str, const char *cmp) static void test_visitor_in_struct_nested(TestInputVisitorData *data, const void *unused) { - UserDefNested *udp = NULL; + UserDefTwo *udp = NULL; Error *err = NULL; Visitor *v; - v = visitor_input_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}"); + v = visitor_input_test_init(data, "{ 'string0': 'string0', " + "'dict1': { 'string1': 'string1', " + "'dict2': { 'userdef': { 'integer': 42, " + "'string': 'string' }, 'string': 'string2'}}}"); - visit_type_UserDefNested(v, &udp, NULL, &err); + visit_type_UserDefTwo(v, &udp, NULL, &err); g_assert(!err); check_and_free_str(udp->string0, "string0"); check_and_free_str(udp->dict1.string1, "string1"); - g_assert_cmpint(udp->dict1.dict2.userdef1->base->integer, ==, 42); - check_and_free_str(udp->dict1.dict2.userdef1->string, "string"); - check_and_free_str(udp->dict1.dict2.string2, "string2"); + g_assert_cmpint(udp->dict1.dict2.userdef->base->integer, ==, 42); + check_and_free_str(udp->dict1.dict2.userdef->string, "string"); + check_and_free_str(udp->dict1.dict2.string, "string2"); g_assert(udp->dict1.has_dict3 == false); - g_free(udp->dict1.dict2.userdef1); + g_free(udp->dict1.dict2.userdef); g_free(udp); } diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c index 602bc12cbc..0584b4da88 100644 --- a/tests/test-qmp-output-visitor.c +++ b/tests/test-qmp-output-visitor.c @@ -234,7 +234,7 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data, { int64_t value = 42; Error *err = NULL; - UserDefNested *ud2; + UserDefTwo *ud2; QObject *obj; QDict *qdict, *dict1, *dict2, *dict3, *userdef; const char *string = "user def string"; @@ -245,20 +245,20 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data, ud2->string0 = g_strdup(strings[0]); ud2->dict1.string1 = g_strdup(strings[1]); - ud2->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne)); - ud2->dict1.dict2.userdef1->string = g_strdup(string); - ud2->dict1.dict2.userdef1->base = g_new0(UserDefZero, 1); - ud2->dict1.dict2.userdef1->base->integer = value; - ud2->dict1.dict2.string2 = g_strdup(strings[2]); + ud2->dict1.dict2.userdef = g_new0(UserDefOne, 1); + ud2->dict1.dict2.userdef->string = g_strdup(string); + ud2->dict1.dict2.userdef->base = g_new0(UserDefZero, 1); + ud2->dict1.dict2.userdef->base->integer = value; + ud2->dict1.dict2.string = g_strdup(strings[2]); ud2->dict1.has_dict3 = true; - ud2->dict1.dict3.userdef2 = g_malloc0(sizeof(UserDefOne)); - ud2->dict1.dict3.userdef2->string = g_strdup(string); - ud2->dict1.dict3.userdef2->base = g_new0(UserDefZero, 1); - ud2->dict1.dict3.userdef2->base->integer = value; - ud2->dict1.dict3.string3 = g_strdup(strings[3]); + ud2->dict1.dict3.userdef = g_new0(UserDefOne, 1); + ud2->dict1.dict3.userdef->string = g_strdup(string); + ud2->dict1.dict3.userdef->base = g_new0(UserDefZero, 1); + ud2->dict1.dict3.userdef->base->integer = value; + ud2->dict1.dict3.string = g_strdup(strings[3]); - visit_type_UserDefNested(data->ov, &ud2, "unused", &err); + visit_type_UserDefTwo(data->ov, &ud2, "unused", &err); g_assert(!err); obj = qmp_output_get_qobject(data->qov); @@ -275,22 +275,22 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data, dict2 = qdict_get_qdict(dict1, "dict2"); g_assert_cmpint(qdict_size(dict2), ==, 2); - g_assert_cmpstr(qdict_get_str(dict2, "string2"), ==, strings[2]); - userdef = qdict_get_qdict(dict2, "userdef1"); + g_assert_cmpstr(qdict_get_str(dict2, "string"), ==, strings[2]); + userdef = qdict_get_qdict(dict2, "userdef"); g_assert_cmpint(qdict_size(userdef), ==, 2); g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value); g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string); dict3 = qdict_get_qdict(dict1, "dict3"); g_assert_cmpint(qdict_size(dict3), ==, 2); - g_assert_cmpstr(qdict_get_str(dict3, "string3"), ==, strings[3]); - userdef = qdict_get_qdict(dict3, "userdef2"); + g_assert_cmpstr(qdict_get_str(dict3, "string"), ==, strings[3]); + userdef = qdict_get_qdict(dict3, "userdef"); g_assert_cmpint(qdict_size(userdef), ==, 2); g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value); g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string); QDECREF(qdict); - qapi_free_UserDefNested(ud2); + qapi_free_UserDefTwo(ud2); } static void test_visitor_out_struct_errors(TestOutputVisitorData *data, @@ -398,7 +398,7 @@ static void test_visitor_out_list(TestOutputVisitorData *data, static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data, const void *unused) { - UserDefNestedList *p, *head = NULL; + UserDefTwoList *p, *head = NULL; const char string[] = "foo bar"; int i, max_count = 1024; @@ -408,18 +408,18 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data, p->value->string0 = g_strdup(string); p->value->dict1.string1 = g_strdup(string); - p->value->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne)); - p->value->dict1.dict2.userdef1->string = g_strdup(string); - p->value->dict1.dict2.userdef1->base = g_new0(UserDefZero, 1); - p->value->dict1.dict2.userdef1->base->integer = 42; - p->value->dict1.dict2.string2 = g_strdup(string); + p->value->dict1.dict2.userdef = g_new0(UserDefOne, 1); + p->value->dict1.dict2.userdef->string = g_strdup(string); + p->value->dict1.dict2.userdef->base = g_new0(UserDefZero, 1); + p->value->dict1.dict2.userdef->base->integer = 42; + p->value->dict1.dict2.string = g_strdup(string); p->value->dict1.has_dict3 = false; p->next = head; head = p; } - qapi_free_UserDefNestedList(head); + qapi_free_UserDefTwoList(head); } static void test_visitor_out_union_flat(TestOutputVisitorData *data, diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c index 7ad1886397..1ccaaa9231 100644 --- a/tests/test-visitor-serialization.c +++ b/tests/test-visitor-serialization.c @@ -249,57 +249,57 @@ static void visit_struct(Visitor *v, void **native, Error **errp) visit_type_TestStruct(v, (TestStruct **)native, NULL, errp); } -static UserDefNested *nested_struct_create(void) +static UserDefTwo *nested_struct_create(void) { - UserDefNested *udnp = g_malloc0(sizeof(*udnp)); + UserDefTwo *udnp = g_malloc0(sizeof(*udnp)); udnp->string0 = strdup("test_string0"); udnp->dict1.string1 = strdup("test_string1"); - udnp->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne)); - udnp->dict1.dict2.userdef1->base = g_new0(UserDefZero, 1); - udnp->dict1.dict2.userdef1->base->integer = 42; - udnp->dict1.dict2.userdef1->string = strdup("test_string"); - udnp->dict1.dict2.string2 = strdup("test_string2"); + udnp->dict1.dict2.userdef = g_new0(UserDefOne, 1); + udnp->dict1.dict2.userdef->base = g_new0(UserDefZero, 1); + udnp->dict1.dict2.userdef->base->integer = 42; + udnp->dict1.dict2.userdef->string = strdup("test_string"); + udnp->dict1.dict2.string = strdup("test_string2"); udnp->dict1.has_dict3 = true; - udnp->dict1.dict3.userdef2 = g_malloc0(sizeof(UserDefOne)); - udnp->dict1.dict3.userdef2->base = g_new0(UserDefZero, 1); - udnp->dict1.dict3.userdef2->base->integer = 43; - udnp->dict1.dict3.userdef2->string = strdup("test_string"); - udnp->dict1.dict3.string3 = strdup("test_string3"); + udnp->dict1.dict3.userdef = g_new0(UserDefOne, 1); + udnp->dict1.dict3.userdef->base = g_new0(UserDefZero, 1); + udnp->dict1.dict3.userdef->base->integer = 43; + udnp->dict1.dict3.userdef->string = strdup("test_string"); + udnp->dict1.dict3.string = strdup("test_string3"); return udnp; } -static void nested_struct_compare(UserDefNested *udnp1, UserDefNested *udnp2) +static void nested_struct_compare(UserDefTwo *udnp1, UserDefTwo *udnp2) { g_assert(udnp1); g_assert(udnp2); g_assert_cmpstr(udnp1->string0, ==, udnp2->string0); g_assert_cmpstr(udnp1->dict1.string1, ==, udnp2->dict1.string1); - g_assert_cmpint(udnp1->dict1.dict2.userdef1->base->integer, ==, - udnp2->dict1.dict2.userdef1->base->integer); - g_assert_cmpstr(udnp1->dict1.dict2.userdef1->string, ==, - udnp2->dict1.dict2.userdef1->string); - g_assert_cmpstr(udnp1->dict1.dict2.string2, ==, udnp2->dict1.dict2.string2); + g_assert_cmpint(udnp1->dict1.dict2.userdef->base->integer, ==, + udnp2->dict1.dict2.userdef->base->integer); + g_assert_cmpstr(udnp1->dict1.dict2.userdef->string, ==, + udnp2->dict1.dict2.userdef->string); + g_assert_cmpstr(udnp1->dict1.dict2.string, ==, udnp2->dict1.dict2.string); g_assert(udnp1->dict1.has_dict3 == udnp2->dict1.has_dict3); - g_assert_cmpint(udnp1->dict1.dict3.userdef2->base->integer, ==, - udnp2->dict1.dict3.userdef2->base->integer); - g_assert_cmpstr(udnp1->dict1.dict3.userdef2->string, ==, - udnp2->dict1.dict3.userdef2->string); - g_assert_cmpstr(udnp1->dict1.dict3.string3, ==, udnp2->dict1.dict3.string3); + g_assert_cmpint(udnp1->dict1.dict3.userdef->base->integer, ==, + udnp2->dict1.dict3.userdef->base->integer); + g_assert_cmpstr(udnp1->dict1.dict3.userdef->string, ==, + udnp2->dict1.dict3.userdef->string); + g_assert_cmpstr(udnp1->dict1.dict3.string, ==, udnp2->dict1.dict3.string); } -static void nested_struct_cleanup(UserDefNested *udnp) +static void nested_struct_cleanup(UserDefTwo *udnp) { - qapi_free_UserDefNested(udnp); + qapi_free_UserDefTwo(udnp); } static void visit_nested_struct(Visitor *v, void **native, Error **errp) { - visit_type_UserDefNested(v, (UserDefNested **)native, NULL, errp); + visit_type_UserDefTwo(v, (UserDefTwo **)native, NULL, errp); } static void visit_nested_struct_list(Visitor *v, void **native, Error **errp) { - visit_type_UserDefNestedList(v, (UserDefNestedList **)native, NULL, errp); + visit_type_UserDefTwoList(v, (UserDefTwoList **)native, NULL, errp); } /* test cases */ @@ -715,13 +715,14 @@ static void test_nested_struct(gconstpointer opaque) { TestArgs *args = (TestArgs *) opaque; const SerializeOps *ops = args->ops; - UserDefNested *udnp = nested_struct_create(); - UserDefNested *udnp_copy = NULL; + UserDefTwo *udnp = nested_struct_create(); + UserDefTwo *udnp_copy = NULL; Error *err = NULL; void *serialize_data; - + ops->serialize(udnp, &serialize_data, visit_nested_struct, &err); - ops->deserialize((void **)&udnp_copy, serialize_data, visit_nested_struct, &err); + ops->deserialize((void **)&udnp_copy, serialize_data, visit_nested_struct, + &err); g_assert(err == NULL); nested_struct_compare(udnp, udnp_copy); @@ -737,18 +738,18 @@ static void test_nested_struct_list(gconstpointer opaque) { TestArgs *args = (TestArgs *) opaque; const SerializeOps *ops = args->ops; - UserDefNestedList *listp = NULL, *tmp, *tmp_copy, *listp_copy = NULL; + UserDefTwoList *listp = NULL, *tmp, *tmp_copy, *listp_copy = NULL; Error *err = NULL; void *serialize_data; int i = 0; for (i = 0; i < 8; i++) { - tmp = g_malloc0(sizeof(UserDefNestedList)); + tmp = g_new0(UserDefTwoList, 1); tmp->value = nested_struct_create(); tmp->next = listp; listp = tmp; } - + ops->serialize(listp, &serialize_data, visit_nested_struct_list, &err); ops->deserialize((void **)&listp_copy, serialize_data, visit_nested_struct_list, &err); @@ -764,8 +765,8 @@ static void test_nested_struct_list(gconstpointer opaque) listp_copy = listp_copy->next; } - qapi_free_UserDefNestedList(tmp); - qapi_free_UserDefNestedList(tmp_copy); + qapi_free_UserDefTwoList(tmp); + qapi_free_UserDefTwoList(tmp_copy); ops->cleanup(serialize_data); g_free(args); From 6446a592760155bb3e2e248d56bab97a34af0336 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:30 -0600 Subject: [PATCH 33/40] qapi: Drop tests for inline nested structs A future patch will be using a 'name':{dictionary} entry in the QAPI schema to specify a default value for an optional argument; but existing use of inline nested structs conflicts with that goal. More precisely, a definition in the QAPI schema associates a name with a set of properties: Example 1: { 'struct': 'Foo', 'data': { MEMBERS... } } associates the global name 'Foo' with properties (meta-type struct) and MEMBERS... Example 2: 'mumble': TYPE within MEMBERS... above associates 'mumble' with properties (type TYPE) and (optional false) within type Foo The syntax of example 1 is extensible; if we need another property, we add another name/value pair to the dictionary (such as 'base':TYPE). The syntax of example 2 is not extensible, because the right hand side can only be a type. We have used name encoding to add a property: "'*mumble': 'int'" associates 'mumble' with (type int) and (optional true). Nice, but doesn't scale. So the solution is to change our existing uses to be syntactic sugar to an extensible form: NAME: TYPE --> NAME: { 'type': TYPE, 'optional': false } *ONAME: TYPE --> ONAME: { 'type': TYPE, 'optional': true } This patch fixes the testsuite to avoid inline nested types, by breaking the nesting into explicit types; it means that the type is now boxed instead of unboxed in C code, but makes no difference on the wire (and if desired, a later patch could change the generator to not do so much boxing in C). When touching code to add new allocations, also convert existing allocations to consistently prefer typesafe g_new0 over g_malloc0 when a type name is involved. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- tests/qapi-schema/qapi-schema-test.json | 12 ++++-- tests/qapi-schema/qapi-schema-test.out | 8 +++- tests/test-qmp-commands.c | 17 ++++---- tests/test-qmp-input-visitor.c | 14 ++++--- tests/test-qmp-output-visitor.c | 44 +++++++++++--------- tests/test-visitor-serialization.c | 54 ++++++++++++++----------- 6 files changed, 88 insertions(+), 61 deletions(-) diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index a6be983100..8193dc13a9 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -14,11 +14,17 @@ 'base': 'UserDefZero', 'data': { 'string': 'str', '*enum1': 'EnumOne' } } +{ 'struct': 'UserDefTwoDictDict', + 'data': { 'userdef': 'UserDefOne', 'string': 'str' } } + +{ 'struct': 'UserDefTwoDict', + 'data': { 'string1': 'str', + 'dict2': 'UserDefTwoDictDict', + '*dict3': 'UserDefTwoDictDict' } } + { 'struct': 'UserDefTwo', 'data': { 'string0': 'str', - 'dict1': { 'string1': 'str', - 'dict2': { 'userdef': 'UserDefOne', 'string': 'str' }, - '*dict3': { 'userdef': 'UserDefOne', 'string': 'str' } } } } + 'dict1': 'UserDefTwoDict' } } # for testing unions { 'struct': 'UserDefA', diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 48f8f0ee6e..93c49635eb 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -2,7 +2,9 @@ OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefTwoDictDict'), ('data', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]), + OrderedDict([('struct', 'UserDefTwoDict'), ('data', OrderedDict([('string1', 'str'), ('dict2', 'UserDefTwoDictDict'), ('*dict3', 'UserDefTwoDictDict')]))]), + OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', 'UserDefTwoDict')]))]), OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), @@ -27,7 +29,9 @@ [OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]), OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]), - OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]), + OrderedDict([('struct', 'UserDefTwoDictDict'), ('data', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]), + OrderedDict([('struct', 'UserDefTwoDict'), ('data', OrderedDict([('string1', 'str'), ('dict2', 'UserDefTwoDictDict'), ('*dict3', 'UserDefTwoDictDict')]))]), + OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', 'UserDefTwoDict')]))]), OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]), OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]), OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]), diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c index 8b50eac43d..ad2e4030b2 100644 --- a/tests/test-qmp-commands.c +++ b/tests/test-qmp-commands.c @@ -33,12 +33,15 @@ UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a, ret = g_new0(UserDefTwo, 1); ret->string0 = strdup("blah1"); - ret->dict1.string1 = strdup("blah2"); - ret->dict1.dict2.userdef = ud1c; - ret->dict1.dict2.string = strdup("blah3"); - ret->dict1.has_dict3 = true; - ret->dict1.dict3.userdef = ud1d; - ret->dict1.dict3.string = strdup("blah4"); + ret->dict1 = g_new0(UserDefTwoDict, 1); + ret->dict1->string1 = strdup("blah2"); + ret->dict1->dict2 = g_new0(UserDefTwoDictDict, 1); + ret->dict1->dict2->userdef = ud1c; + ret->dict1->dict2->string = strdup("blah3"); + ret->dict1->dict3 = g_new0(UserDefTwoDictDict, 1); + ret->dict1->has_dict3 = true; + ret->dict1->dict3->userdef = ud1d; + ret->dict1->dict3->string = strdup("blah4"); return ret; } @@ -204,7 +207,7 @@ static void test_dealloc_partial(void) assert(ud2 != NULL); assert(ud2->string0 != NULL); assert(strcmp(ud2->string0, text) == 0); - assert(ud2->dict1.dict2.userdef == NULL); + assert(ud2->dict1 == NULL); /* confirm & release construction error */ assert(err != NULL); diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c index 115fb22c7e..b96195309b 100644 --- a/tests/test-qmp-input-visitor.c +++ b/tests/test-qmp-input-visitor.c @@ -261,13 +261,15 @@ static void test_visitor_in_struct_nested(TestInputVisitorData *data, g_assert(!err); check_and_free_str(udp->string0, "string0"); - check_and_free_str(udp->dict1.string1, "string1"); - g_assert_cmpint(udp->dict1.dict2.userdef->base->integer, ==, 42); - check_and_free_str(udp->dict1.dict2.userdef->string, "string"); - check_and_free_str(udp->dict1.dict2.string, "string2"); - g_assert(udp->dict1.has_dict3 == false); + check_and_free_str(udp->dict1->string1, "string1"); + g_assert_cmpint(udp->dict1->dict2->userdef->base->integer, ==, 42); + check_and_free_str(udp->dict1->dict2->userdef->string, "string"); + check_and_free_str(udp->dict1->dict2->string, "string2"); + g_assert(udp->dict1->has_dict3 == false); - g_free(udp->dict1.dict2.userdef); + g_free(udp->dict1->dict2->userdef); + g_free(udp->dict1->dict2); + g_free(udp->dict1); g_free(udp); } diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c index 0584b4da88..f8c9367e48 100644 --- a/tests/test-qmp-output-visitor.c +++ b/tests/test-qmp-output-visitor.c @@ -244,19 +244,23 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data, ud2 = g_malloc0(sizeof(*ud2)); ud2->string0 = g_strdup(strings[0]); - ud2->dict1.string1 = g_strdup(strings[1]); - ud2->dict1.dict2.userdef = g_new0(UserDefOne, 1); - ud2->dict1.dict2.userdef->string = g_strdup(string); - ud2->dict1.dict2.userdef->base = g_new0(UserDefZero, 1); - ud2->dict1.dict2.userdef->base->integer = value; - ud2->dict1.dict2.string = g_strdup(strings[2]); + ud2->dict1 = g_malloc0(sizeof(*ud2->dict1)); + ud2->dict1->string1 = g_strdup(strings[1]); - ud2->dict1.has_dict3 = true; - ud2->dict1.dict3.userdef = g_new0(UserDefOne, 1); - ud2->dict1.dict3.userdef->string = g_strdup(string); - ud2->dict1.dict3.userdef->base = g_new0(UserDefZero, 1); - ud2->dict1.dict3.userdef->base->integer = value; - ud2->dict1.dict3.string = g_strdup(strings[3]); + ud2->dict1->dict2 = g_malloc0(sizeof(*ud2->dict1->dict2)); + ud2->dict1->dict2->userdef = g_new0(UserDefOne, 1); + ud2->dict1->dict2->userdef->string = g_strdup(string); + ud2->dict1->dict2->userdef->base = g_new0(UserDefZero, 1); + ud2->dict1->dict2->userdef->base->integer = value; + ud2->dict1->dict2->string = g_strdup(strings[2]); + + ud2->dict1->dict3 = g_malloc0(sizeof(*ud2->dict1->dict3)); + ud2->dict1->has_dict3 = true; + ud2->dict1->dict3->userdef = g_new0(UserDefOne, 1); + ud2->dict1->dict3->userdef->string = g_strdup(string); + ud2->dict1->dict3->userdef->base = g_new0(UserDefZero, 1); + ud2->dict1->dict3->userdef->base->integer = value; + ud2->dict1->dict3->string = g_strdup(strings[3]); visit_type_UserDefTwo(data->ov, &ud2, "unused", &err); g_assert(!err); @@ -407,13 +411,15 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data, p->value = g_malloc0(sizeof(*p->value)); p->value->string0 = g_strdup(string); - p->value->dict1.string1 = g_strdup(string); - p->value->dict1.dict2.userdef = g_new0(UserDefOne, 1); - p->value->dict1.dict2.userdef->string = g_strdup(string); - p->value->dict1.dict2.userdef->base = g_new0(UserDefZero, 1); - p->value->dict1.dict2.userdef->base->integer = 42; - p->value->dict1.dict2.string = g_strdup(string); - p->value->dict1.has_dict3 = false; + p->value->dict1 = g_new0(UserDefTwoDict, 1); + p->value->dict1->string1 = g_strdup(string); + p->value->dict1->dict2 = g_new0(UserDefTwoDictDict, 1); + p->value->dict1->dict2->userdef = g_new0(UserDefOne, 1); + p->value->dict1->dict2->userdef->string = g_strdup(string); + p->value->dict1->dict2->userdef->base = g_new0(UserDefZero, 1); + p->value->dict1->dict2->userdef->base->integer = 42; + p->value->dict1->dict2->string = g_strdup(string); + p->value->dict1->has_dict3 = false; p->next = head; head = p; diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c index 1ccaaa9231..fa86cae88a 100644 --- a/tests/test-visitor-serialization.c +++ b/tests/test-visitor-serialization.c @@ -1,6 +1,7 @@ /* * Unit-tests for visitor-based serialization * + * Copyright (C) 2014-2015 Red Hat, Inc. * Copyright IBM, Corp. 2012 * * Authors: @@ -253,18 +254,21 @@ static UserDefTwo *nested_struct_create(void) { UserDefTwo *udnp = g_malloc0(sizeof(*udnp)); udnp->string0 = strdup("test_string0"); - udnp->dict1.string1 = strdup("test_string1"); - udnp->dict1.dict2.userdef = g_new0(UserDefOne, 1); - udnp->dict1.dict2.userdef->base = g_new0(UserDefZero, 1); - udnp->dict1.dict2.userdef->base->integer = 42; - udnp->dict1.dict2.userdef->string = strdup("test_string"); - udnp->dict1.dict2.string = strdup("test_string2"); - udnp->dict1.has_dict3 = true; - udnp->dict1.dict3.userdef = g_new0(UserDefOne, 1); - udnp->dict1.dict3.userdef->base = g_new0(UserDefZero, 1); - udnp->dict1.dict3.userdef->base->integer = 43; - udnp->dict1.dict3.userdef->string = strdup("test_string"); - udnp->dict1.dict3.string = strdup("test_string3"); + udnp->dict1 = g_malloc0(sizeof(*udnp->dict1)); + udnp->dict1->string1 = strdup("test_string1"); + udnp->dict1->dict2 = g_malloc0(sizeof(*udnp->dict1->dict2)); + udnp->dict1->dict2->userdef = g_new0(UserDefOne, 1); + udnp->dict1->dict2->userdef->base = g_new0(UserDefZero, 1); + udnp->dict1->dict2->userdef->base->integer = 42; + udnp->dict1->dict2->userdef->string = strdup("test_string"); + udnp->dict1->dict2->string = strdup("test_string2"); + udnp->dict1->dict3 = g_malloc0(sizeof(*udnp->dict1->dict3)); + udnp->dict1->has_dict3 = true; + udnp->dict1->dict3->userdef = g_new0(UserDefOne, 1); + udnp->dict1->dict3->userdef->base = g_new0(UserDefZero, 1); + udnp->dict1->dict3->userdef->base->integer = 43; + udnp->dict1->dict3->userdef->string = strdup("test_string"); + udnp->dict1->dict3->string = strdup("test_string3"); return udnp; } @@ -273,18 +277,20 @@ static void nested_struct_compare(UserDefTwo *udnp1, UserDefTwo *udnp2) g_assert(udnp1); g_assert(udnp2); g_assert_cmpstr(udnp1->string0, ==, udnp2->string0); - g_assert_cmpstr(udnp1->dict1.string1, ==, udnp2->dict1.string1); - g_assert_cmpint(udnp1->dict1.dict2.userdef->base->integer, ==, - udnp2->dict1.dict2.userdef->base->integer); - g_assert_cmpstr(udnp1->dict1.dict2.userdef->string, ==, - udnp2->dict1.dict2.userdef->string); - g_assert_cmpstr(udnp1->dict1.dict2.string, ==, udnp2->dict1.dict2.string); - g_assert(udnp1->dict1.has_dict3 == udnp2->dict1.has_dict3); - g_assert_cmpint(udnp1->dict1.dict3.userdef->base->integer, ==, - udnp2->dict1.dict3.userdef->base->integer); - g_assert_cmpstr(udnp1->dict1.dict3.userdef->string, ==, - udnp2->dict1.dict3.userdef->string); - g_assert_cmpstr(udnp1->dict1.dict3.string, ==, udnp2->dict1.dict3.string); + g_assert_cmpstr(udnp1->dict1->string1, ==, udnp2->dict1->string1); + g_assert_cmpint(udnp1->dict1->dict2->userdef->base->integer, ==, + udnp2->dict1->dict2->userdef->base->integer); + g_assert_cmpstr(udnp1->dict1->dict2->userdef->string, ==, + udnp2->dict1->dict2->userdef->string); + g_assert_cmpstr(udnp1->dict1->dict2->string, ==, + udnp2->dict1->dict2->string); + g_assert(udnp1->dict1->has_dict3 == udnp2->dict1->has_dict3); + g_assert_cmpint(udnp1->dict1->dict3->userdef->base->integer, ==, + udnp2->dict1->dict3->userdef->base->integer); + g_assert_cmpstr(udnp1->dict1->dict3->userdef->string, ==, + udnp2->dict1->dict3->userdef->string); + g_assert_cmpstr(udnp1->dict1->dict3->string, ==, + udnp2->dict1->dict3->string); } static void nested_struct_cleanup(UserDefTwo *udnp) From 4752cdbbf330ac7c593a6f337b97a79648f3f878 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:31 -0600 Subject: [PATCH 34/40] qapi: Drop inline nested struct in query-version A future patch will be using a 'name':{dictionary} entry in the QAPI schema to specify a default value for an optional argument (see previous commit message for more details why); but existing use of inline nested structs conflicts with that goal. This patch fixes one of only two commands relying on nested types, by breaking the nesting into an explicit type; it means that the type is now boxed instead of unboxed in C code, but the QMP wire format is unaffected by this change. Prefer the safer g_new0() while making the conversion. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- hmp.c | 2 +- qapi/common.json | 26 +++++++++++++++++++------- qmp.c | 9 +++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/hmp.c b/hmp.c index d85d913a79..97d9c2c05f 100644 --- a/hmp.c +++ b/hmp.c @@ -60,7 +60,7 @@ void hmp_info_version(Monitor *mon, const QDict *qdict) info = qmp_query_version(NULL); monitor_printf(mon, "%" PRId64 ".%" PRId64 ".%" PRId64 "%s\n", - info->qemu.major, info->qemu.minor, info->qemu.micro, + info->qemu->major, info->qemu->minor, info->qemu->micro, info->package); qapi_free_VersionInfo(info); diff --git a/qapi/common.json b/qapi/common.json index 12431c691b..bad56bf688 100644 --- a/qapi/common.json +++ b/qapi/common.json @@ -28,16 +28,29 @@ 'data': [ 'GenericError', 'CommandNotFound', 'DeviceEncrypted', 'DeviceNotActive', 'DeviceNotFound', 'KVMMissingCap' ] } +## +# @VersionTriple +# +# A three-part version number. +# +# @qemu.major: The major version number. +# +# @qemu.minor: The minor version number. +# +# @qemu.micro: The micro version number. +# +# Since: 2.4 +## +{ 'struct': 'VersionTriple', + 'data': {'major': 'int', 'minor': 'int', 'micro': 'int'} } + + ## # @VersionInfo: # # A description of QEMU's version. # -# @qemu.major: The major version of QEMU -# -# @qemu.minor: The minor version of QEMU -# -# @qemu.micro: The micro version of QEMU. By current convention, a micro +# @qemu: The version of QEMU. By current convention, a micro # version of 50 signifies a development branch. A micro version # greater than or equal to 90 signifies a release candidate for # the next minor version. A micro version of less than 50 @@ -51,8 +64,7 @@ # Since: 0.14.0 ## { 'struct': 'VersionInfo', - 'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'}, - 'package': 'str'} } + 'data': {'qemu': 'VersionTriple', 'package': 'str'} } ## # @query-version: diff --git a/qmp.c b/qmp.c index e6c70504df..3f5dfe3f51 100644 --- a/qmp.c +++ b/qmp.c @@ -45,15 +45,16 @@ NameInfo *qmp_query_name(Error **errp) VersionInfo *qmp_query_version(Error **errp) { - VersionInfo *info = g_malloc0(sizeof(*info)); + VersionInfo *info = g_new0(VersionInfo, 1); const char *version = QEMU_VERSION; char *tmp; - info->qemu.major = strtol(version, &tmp, 10); + info->qemu = g_new0(VersionTriple, 1); + info->qemu->major = strtol(version, &tmp, 10); tmp++; - info->qemu.minor = strtol(tmp, &tmp, 10); + info->qemu->minor = strtol(tmp, &tmp, 10); tmp++; - info->qemu.micro = strtol(tmp, &tmp, 10); + info->qemu->micro = strtol(tmp, &tmp, 10); info->package = g_strdup(QEMU_PKGVERSION); return info; From 9fa02cd194a131aca75ab646ece975b6835400e1 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:32 -0600 Subject: [PATCH 35/40] qapi: Drop inline nested structs in query-pci A future patch will be using a 'name':{dictionary} entry in the QAPI schema to specify a default value for an optional argument (see previous commit message for more details why); but existing use of inline nested structs conflicts with that goal. This patch fixes one of only two commands relying on nested types, by breaking the nesting into an explicit type; it means that the type is now boxed instead of unboxed in C code, but the QMP wire format is unaffected by this change. Prefer the safer g_new0() while making the conversion, and reduce some long lines. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- hmp.c | 26 +++++++------- hw/pci/pci.c | 42 ++++++++++++---------- qapi-schema.json | 90 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 98 insertions(+), 60 deletions(-) diff --git a/hmp.c b/hmp.c index 97d9c2c05f..3010d04c92 100644 --- a/hmp.c +++ b/hmp.c @@ -648,14 +648,14 @@ static void hmp_info_pci_device(Monitor *mon, const PciDeviceInfo *dev) dev->slot, dev->function); monitor_printf(mon, " "); - if (dev->class_info.has_desc) { - monitor_printf(mon, "%s", dev->class_info.desc); + if (dev->class_info->has_desc) { + monitor_printf(mon, "%s", dev->class_info->desc); } else { - monitor_printf(mon, "Class %04" PRId64, dev->class_info.q_class); + monitor_printf(mon, "Class %04" PRId64, dev->class_info->q_class); } monitor_printf(mon, ": PCI device %04" PRIx64 ":%04" PRIx64 "\n", - dev->id.vendor, dev->id.device); + dev->id->vendor, dev->id->device); if (dev->has_irq) { monitor_printf(mon, " IRQ %" PRId64 ".\n", dev->irq); @@ -663,25 +663,25 @@ static void hmp_info_pci_device(Monitor *mon, const PciDeviceInfo *dev) if (dev->has_pci_bridge) { monitor_printf(mon, " BUS %" PRId64 ".\n", - dev->pci_bridge->bus.number); + dev->pci_bridge->bus->number); monitor_printf(mon, " secondary bus %" PRId64 ".\n", - dev->pci_bridge->bus.secondary); + dev->pci_bridge->bus->secondary); monitor_printf(mon, " subordinate bus %" PRId64 ".\n", - dev->pci_bridge->bus.subordinate); + dev->pci_bridge->bus->subordinate); monitor_printf(mon, " IO range [0x%04"PRIx64", 0x%04"PRIx64"]\n", - dev->pci_bridge->bus.io_range->base, - dev->pci_bridge->bus.io_range->limit); + dev->pci_bridge->bus->io_range->base, + dev->pci_bridge->bus->io_range->limit); monitor_printf(mon, " memory range [0x%08"PRIx64", 0x%08"PRIx64"]\n", - dev->pci_bridge->bus.memory_range->base, - dev->pci_bridge->bus.memory_range->limit); + dev->pci_bridge->bus->memory_range->base, + dev->pci_bridge->bus->memory_range->limit); monitor_printf(mon, " prefetchable memory range " "[0x%08"PRIx64", 0x%08"PRIx64"]\n", - dev->pci_bridge->bus.prefetchable_range->base, - dev->pci_bridge->bus.prefetchable_range->limit); + dev->pci_bridge->bus->prefetchable_range->base, + dev->pci_bridge->bus->prefetchable_range->limit); } for (region = dev->regions; region; region = region->next) { diff --git a/hw/pci/pci.c b/hw/pci/pci.c index b3d5100112..f5c7a99717 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -1456,24 +1456,26 @@ static PciBridgeInfo *qmp_query_pci_bridge(PCIDevice *dev, PCIBus *bus, int bus_num) { PciBridgeInfo *info; + PciMemoryRange *range; - info = g_malloc0(sizeof(*info)); + info = g_new0(PciBridgeInfo, 1); - info->bus.number = dev->config[PCI_PRIMARY_BUS]; - info->bus.secondary = dev->config[PCI_SECONDARY_BUS]; - info->bus.subordinate = dev->config[PCI_SUBORDINATE_BUS]; + info->bus = g_new0(PciBusInfo, 1); + info->bus->number = dev->config[PCI_PRIMARY_BUS]; + info->bus->secondary = dev->config[PCI_SECONDARY_BUS]; + info->bus->subordinate = dev->config[PCI_SUBORDINATE_BUS]; - info->bus.io_range = g_malloc0(sizeof(*info->bus.io_range)); - info->bus.io_range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO); - info->bus.io_range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO); + range = info->bus->io_range = g_new0(PciMemoryRange, 1); + range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO); + range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO); - info->bus.memory_range = g_malloc0(sizeof(*info->bus.memory_range)); - info->bus.memory_range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY); - info->bus.memory_range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY); + range = info->bus->memory_range = g_new0(PciMemoryRange, 1); + range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY); + range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY); - info->bus.prefetchable_range = g_malloc0(sizeof(*info->bus.prefetchable_range)); - info->bus.prefetchable_range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH); - info->bus.prefetchable_range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH); + range = info->bus->prefetchable_range = g_new0(PciMemoryRange, 1); + range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH); + range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH); if (dev->config[PCI_SECONDARY_BUS] != 0) { PCIBus *child_bus = pci_find_bus_nr(bus, dev->config[PCI_SECONDARY_BUS]); @@ -1494,21 +1496,23 @@ static PciDeviceInfo *qmp_query_pci_device(PCIDevice *dev, PCIBus *bus, uint8_t type; int class; - info = g_malloc0(sizeof(*info)); + info = g_new0(PciDeviceInfo, 1); info->bus = bus_num; info->slot = PCI_SLOT(dev->devfn); info->function = PCI_FUNC(dev->devfn); + info->class_info = g_new0(PciDeviceClass, 1); class = pci_get_word(dev->config + PCI_CLASS_DEVICE); - info->class_info.q_class = class; + info->class_info->q_class = class; desc = get_class_desc(class); if (desc->desc) { - info->class_info.has_desc = true; - info->class_info.desc = g_strdup(desc->desc); + info->class_info->has_desc = true; + info->class_info->desc = g_strdup(desc->desc); } - info->id.vendor = pci_get_word(dev->config + PCI_VENDOR_ID); - info->id.device = pci_get_word(dev->config + PCI_DEVICE_ID); + info->id = g_new0(PciDeviceId, 1); + info->id->vendor = pci_get_word(dev->config + PCI_VENDOR_ID); + info->id->device = pci_get_word(dev->config + PCI_DEVICE_ID); info->regions = qmp_query_pci_regions(dev); info->qdev_id = g_strdup(dev->qdev.id ? dev->qdev.id : ""); diff --git a/qapi-schema.json b/qapi-schema.json index 6a4e0dfd4f..27ec9882db 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1040,37 +1040,76 @@ 'data': {'bar': 'int', 'type': 'str', 'address': 'int', 'size': 'int', '*prefetch': 'bool', '*mem_type_64': 'bool' } } +## +# @PciBusInfo: +# +# Information about a bus of a PCI Bridge device +# +# @number: primary bus interface number. This should be the number of the +# bus the device resides on. +# +# @secondary: secondary bus interface number. This is the number of the +# main bus for the bridge +# +# @subordinate: This is the highest number bus that resides below the +# bridge. +# +# @io_range: The PIO range for all devices on this bridge +# +# @memory_range: The MMIO range for all devices on this bridge +# +# @prefetchable_range: The range of prefetchable MMIO for all devices on +# this bridge +# +# Since: 2.4 +## +{ 'struct': 'PciBusInfo', + 'data': {'number': 'int', 'secondary': 'int', 'subordinate': 'int', + 'io_range': 'PciMemoryRange', + 'memory_range': 'PciMemoryRange', + 'prefetchable_range': 'PciMemoryRange' } } + ## # @PciBridgeInfo: # # Information about a PCI Bridge device # -# @bus.number: primary bus interface number. This should be the number of the -# bus the device resides on. -# -# @bus.secondary: secondary bus interface number. This is the number of the -# main bus for the bridge -# -# @bus.subordinate: This is the highest number bus that resides below the -# bridge. -# -# @bus.io_range: The PIO range for all devices on this bridge -# -# @bus.memory_range: The MMIO range for all devices on this bridge -# -# @bus.prefetchable_range: The range of prefetchable MMIO for all devices on -# this bridge +# @bus: information about the bus the device resides on # # @devices: a list of @PciDeviceInfo for each device on this bridge # # Since: 0.14.0 ## { 'struct': 'PciBridgeInfo', - 'data': {'bus': { 'number': 'int', 'secondary': 'int', 'subordinate': 'int', - 'io_range': 'PciMemoryRange', - 'memory_range': 'PciMemoryRange', - 'prefetchable_range': 'PciMemoryRange' }, - '*devices': ['PciDeviceInfo']} } + 'data': {'bus': 'PciBusInfo', '*devices': ['PciDeviceInfo']} } + +## +# @PciDeviceClass: +# +# Information about the Class of a PCI device +# +# @desc: #optional a string description of the device's class +# +# @class: the class code of the device +# +# Since: 2.4 +## +{ 'struct': 'PciDeviceClass', + 'data': {'*desc': 'str', 'class': 'int'} } + +## +# @PciDeviceId: +# +# Information about the Id of a PCI device +# +# @device: the PCI device id +# +# @vendor: the PCI vendor id +# +# Since: 2.4 +## +{ 'struct': 'PciDeviceId', + 'data': {'device': 'int', 'vendor': 'int'} } ## # @PciDeviceInfo: @@ -1083,13 +1122,9 @@ # # @function: the function of the slot used by the device # -# @class_info.desc: #optional a string description of the device's class +# @class_info: the class of the device # -# @class_info.class: the class code of the device -# -# @id.device: the PCI device id -# -# @id.vendor: the PCI vendor id +# @id: the PCI device id # # @irq: #optional if an IRQ is assigned to the device, the IRQ number # @@ -1106,8 +1141,7 @@ ## { 'struct': 'PciDeviceInfo', 'data': {'bus': 'int', 'slot': 'int', 'function': 'int', - 'class_info': {'*desc': 'str', 'class': 'int'}, - 'id': {'device': 'int', 'vendor': 'int'}, + 'class_info': 'PciDeviceClass', 'id': 'PciDeviceId', '*irq': 'int', 'qdev_id': 'str', '*pci_bridge': 'PciBridgeInfo', 'regions': ['PciMemoryRegion']} } From 6b5abc7df7ef9aadb3ff0eba6ccf4f1f0181e2e1 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:33 -0600 Subject: [PATCH 36/40] qapi: Drop support for inline nested types A future patch will be using a 'name':{dictionary} entry in the QAPI schema to specify a default value for an optional argument (see previous commit messages for more details why); but existing use of inline nested structs conflicts with that goal. Now that all commands have been changed to avoid inline nested structs, nuke support for them, and turn it into a hard error. Update the testsuite to reflect tighter parsing rules. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi-commands.py | 8 ++--- scripts/qapi-event.py | 4 +-- scripts/qapi-types.py | 9 ++--- scripts/qapi-visit.py | 37 +++----------------- scripts/qapi.py | 20 ++++------- tests/qapi-schema/event-nest-struct.err | 2 +- tests/qapi-schema/nested-struct-data.err | 1 + tests/qapi-schema/nested-struct-data.exit | 2 +- tests/qapi-schema/nested-struct-data.json | 2 +- tests/qapi-schema/nested-struct-data.out | 3 -- tests/qapi-schema/nested-struct-returns.err | 1 + tests/qapi-schema/nested-struct-returns.exit | 2 +- tests/qapi-schema/nested-struct-returns.json | 2 +- tests/qapi-schema/nested-struct-returns.out | 3 -- 14 files changed, 27 insertions(+), 69 deletions(-) diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py index cb786825ee..93e43f0e48 100644 --- a/scripts/qapi-commands.py +++ b/scripts/qapi-commands.py @@ -28,7 +28,7 @@ def type_visitor(name): def generate_command_decl(name, args, ret_type): arglist="" - for argname, argtype, optional, structured in parse_args(args): + for argname, argtype, optional in parse_args(args): argtype = c_type(argtype, is_param=True) if optional: arglist += "bool has_%s, " % c_var(argname) @@ -53,7 +53,7 @@ def gen_sync_call(name, args, ret_type, indent=0): retval="" if ret_type: retval = "retval = " - for argname, argtype, optional, structured in parse_args(args): + for argname, argtype, optional in parse_args(args): if optional: arglist += "has_%s, " % c_var(argname) arglist += "%s, " % (c_var(argname)) @@ -96,7 +96,7 @@ Visitor *v; def gen_visitor_input_vars_decl(args): ret = "" push_indent() - for argname, argtype, optional, structured in parse_args(args): + for argname, argtype, optional in parse_args(args): if optional: ret += mcgen(''' bool has_%(argname)s = false; @@ -139,7 +139,7 @@ v = qapi_dealloc_get_visitor(md); v = qmp_input_get_visitor(mi); ''') - for argname, argtype, optional, structured in parse_args(args): + for argname, argtype, optional in parse_args(args): if optional: ret += mcgen(''' visit_optional(v, &has_%(c_name)s, "%(name)s", %(errp)s); diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py index 601e3076ab..47dc041805 100644 --- a/scripts/qapi-event.py +++ b/scripts/qapi-event.py @@ -21,7 +21,7 @@ def _generate_event_api_name(event_name, params): l = len(api_name) if params: - for argname, argentry, optional, structured in parse_args(params): + for argname, argentry, optional in parse_args(params): if optional: api_name += "bool has_%s,\n" % c_var(argname) api_name += "".ljust(l) @@ -93,7 +93,7 @@ def generate_event_implement(api_name, event_name, params): """, event_name = event_name) - for argname, argentry, optional, structured in parse_args(params): + for argname, argentry, optional in parse_args(params): if optional: ret += mcgen(""" if (has_%(var)s) { diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py index a429d9ec05..2bf8145076 100644 --- a/scripts/qapi-types.py +++ b/scripts/qapi-types.py @@ -63,18 +63,13 @@ typedef struct %(name)sList def generate_struct_fields(members): ret = '' - for argname, argentry, optional, structured in parse_args(members): + for argname, argentry, optional in parse_args(members): if optional: ret += mcgen(''' bool has_%(c_name)s; ''', c_name=c_var(argname)) - if structured: - push_indent() - ret += generate_struct({ "field": argname, "data": argentry}) - pop_indent() - else: - ret += mcgen(''' + ret += mcgen(''' %(c_type)s %(c_name)s; ''', c_type=c_type(argentry), c_name=c_var(argname)) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index c739a95a87..6156162500 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -51,27 +51,6 @@ def generate_visit_struct_fields(name, field_prefix, fn_prefix, members, base = else: full_name = "%s_%s" % (name, fn_prefix) - for argname, argentry, optional, structured in parse_args(members): - if structured: - if not fn_prefix: - nested_fn_prefix = argname - else: - nested_fn_prefix = "%s_%s" % (fn_prefix, argname) - - nested_field_prefix = "%s%s." % (field_prefix, argname) - ret += generate_visit_struct_fields(name, nested_field_prefix, - nested_fn_prefix, argentry) - ret += mcgen(''' - -static void visit_type_%(full_name)s_field_%(c_name)s(Visitor *m, %(name)s **obj, Error **errp) -{ -''', - name=name, full_name=full_name, c_name=c_var(argname)) - ret += generate_visit_struct_body(full_name, argname, argentry) - ret += mcgen(''' -} -''') - if base: ret += generate_visit_implicit_struct(base) @@ -94,7 +73,7 @@ if (err) { c_prefix=c_var(field_prefix), type=type_name(base), c_name=c_var('base')) - for argname, argentry, optional, structured in parse_args(members): + for argname, argentry, optional in parse_args(members): if optional: ret += mcgen(''' visit_optional(m, &(*obj)->%(c_prefix)shas_%(c_name)s, "%(name)s", &err); @@ -104,18 +83,12 @@ if (!err && (*obj)->%(prefix)shas_%(c_name)s) { c_name=c_var(argname), name=argname) push_indent() - if structured: - ret += mcgen(''' -visit_type_%(full_name)s_field_%(c_name)s(m, obj, &err); -''', - full_name=full_name, c_name=c_var(argname)) - else: - ret += mcgen(''' + ret += mcgen(''' visit_type_%(type)s(m, &(*obj)->%(c_prefix)s%(c_name)s, "%(name)s", &err); ''', - c_prefix=c_var(field_prefix), prefix=field_prefix, - type=type_name(argentry), c_name=c_var(argname), - name=argname) + c_prefix=c_var(field_prefix), prefix=field_prefix, + type=type_name(argentry), c_name=c_var(argname), + name=argname) if optional: pop_indent() diff --git a/scripts/qapi.py b/scripts/qapi.py index 333f59ab5b..44898b082a 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -373,10 +373,12 @@ def check_type(expr_info, source, value, allow_array = False, for (key, arg) in value.items(): check_name(expr_info, "Member of %s" % source, key, allow_optional=allow_optional) + # Todo: allow dictionaries to represent default values of + # an optional argument. check_type(expr_info, "Member '%s' of %s" % (key, source), arg, - allow_array=True, allow_dict=True, allow_optional=True, + allow_array=True, allow_star=allow_star, allow_metas=['built-in', 'union', 'alternate', 'struct', - 'enum'], allow_star=allow_star) + 'enum']) def check_command(expr, expr_info): name = expr['command'] @@ -404,13 +406,6 @@ def check_event(expr, expr_info): check_type(expr_info, "'data' for event '%s'" % name, expr.get('data'), allow_dict=True, allow_optional=True, allow_metas=['union', 'struct']) - if params: - for argname, argentry, optional, structured in parse_args(params): - if structured: - raise QAPIExprError(expr_info, - "Nested structure define in event is not " - "supported, event '%s', argname '%s'" - % (expr['event'], argname)) def check_union(expr, expr_info): name = expr['union'] @@ -671,13 +666,12 @@ def parse_args(typeinfo): argname = member argentry = typeinfo[member] optional = False - structured = False if member.startswith('*'): argname = member[1:] optional = True - if isinstance(argentry, OrderedDict): - structured = True - yield (argname, argentry, optional, structured) + # Todo: allow argentry to be OrderedDict, for providing the + # value of an optional argument. + yield (argname, argentry, optional) def de_camel_case(name): new_name = '' diff --git a/tests/qapi-schema/event-nest-struct.err b/tests/qapi-schema/event-nest-struct.err index 91bde1c967..5a42701b8f 100644 --- a/tests/qapi-schema/event-nest-struct.err +++ b/tests/qapi-schema/event-nest-struct.err @@ -1 +1 @@ -tests/qapi-schema/event-nest-struct.json:1: Nested structure define in event is not supported, event 'EVENT_A', argname 'a' +tests/qapi-schema/event-nest-struct.json:1: Member 'a' of 'data' for event 'EVENT_A' should be a type name diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err index e69de29bb2..da767bade2 100644 --- a/tests/qapi-schema/nested-struct-data.err +++ b/tests/qapi-schema/nested-struct-data.err @@ -0,0 +1 @@ +tests/qapi-schema/nested-struct-data.json:2: Member 'a' of 'data' for command 'foo' should be a type name diff --git a/tests/qapi-schema/nested-struct-data.exit b/tests/qapi-schema/nested-struct-data.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/nested-struct-data.exit +++ b/tests/qapi-schema/nested-struct-data.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/nested-struct-data.json b/tests/qapi-schema/nested-struct-data.json index 0247c8c82c..3d52d2b398 100644 --- a/tests/qapi-schema/nested-struct-data.json +++ b/tests/qapi-schema/nested-struct-data.json @@ -1,4 +1,4 @@ -# FIXME: inline subtypes collide with our desired future use of defaults +# inline subtypes collide with our desired future use of defaults { 'command': 'foo', 'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' }, 'returns': {} } diff --git a/tests/qapi-schema/nested-struct-data.out b/tests/qapi-schema/nested-struct-data.out index 999cbb8eef..e69de29bb2 100644 --- a/tests/qapi-schema/nested-struct-data.out +++ b/tests/qapi-schema/nested-struct-data.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'foo'), ('data', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')])), ('returns', OrderedDict())])] -[] -[] diff --git a/tests/qapi-schema/nested-struct-returns.err b/tests/qapi-schema/nested-struct-returns.err index e69de29bb2..5238d075b7 100644 --- a/tests/qapi-schema/nested-struct-returns.err +++ b/tests/qapi-schema/nested-struct-returns.err @@ -0,0 +1 @@ +tests/qapi-schema/nested-struct-returns.json:2: Member 'a' of 'returns' for command 'foo' should be a type name diff --git a/tests/qapi-schema/nested-struct-returns.exit b/tests/qapi-schema/nested-struct-returns.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/nested-struct-returns.exit +++ b/tests/qapi-schema/nested-struct-returns.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/nested-struct-returns.json b/tests/qapi-schema/nested-struct-returns.json index 5a46840b71..d2cd047f0d 100644 --- a/tests/qapi-schema/nested-struct-returns.json +++ b/tests/qapi-schema/nested-struct-returns.json @@ -1,3 +1,3 @@ -# FIXME: inline subtypes collide with our desired future use of defaults +# inline subtypes collide with our desired future use of defaults { 'command': 'foo', 'returns': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } } diff --git a/tests/qapi-schema/nested-struct-returns.out b/tests/qapi-schema/nested-struct-returns.out index c53d23b3a8..e69de29bb2 100644 --- a/tests/qapi-schema/nested-struct-returns.out +++ b/tests/qapi-schema/nested-struct-returns.out @@ -1,3 +0,0 @@ -[OrderedDict([('command', 'foo'), ('returns', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')]))])] -[] -[] From a82b982e2bddf7cd7cb490f83643e952e17d4523 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:34 -0600 Subject: [PATCH 37/40] qapi: Drop dead visitor code related to nested structs Now that we no longer have nested structs to visit, the use of prefix strings is no longer required. Remove the code that is no longer reachable. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi-visit.py | 49 +++++++++++-------------------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py index 6156162500..0e67b336fc 100644 --- a/scripts/qapi-visit.py +++ b/scripts/qapi-visit.py @@ -43,50 +43,43 @@ static void visit_type_implicit_%(c_type)s(Visitor *m, %(c_type)s **obj, Error * ''', c_type=type_name(type)) -def generate_visit_struct_fields(name, field_prefix, fn_prefix, members, base = None): +def generate_visit_struct_fields(name, members, base = None): substructs = [] ret = '' - if not fn_prefix: - full_name = name - else: - full_name = "%s_%s" % (name, fn_prefix) if base: ret += generate_visit_implicit_struct(base) ret += mcgen(''' -static void visit_type_%(full_name)s_fields(Visitor *m, %(name)s **obj, Error **errp) +static void visit_type_%(name)s_fields(Visitor *m, %(name)s **obj, Error **errp) { Error *err = NULL; ''', - name=name, full_name=full_name) + name=name) push_indent() if base: ret += mcgen(''' -visit_type_implicit_%(type)s(m, &(*obj)->%(c_prefix)s%(c_name)s, &err); +visit_type_implicit_%(type)s(m, &(*obj)->%(c_name)s, &err); if (err) { goto out; } ''', - c_prefix=c_var(field_prefix), type=type_name(base), c_name=c_var('base')) for argname, argentry, optional in parse_args(members): if optional: ret += mcgen(''' -visit_optional(m, &(*obj)->%(c_prefix)shas_%(c_name)s, "%(name)s", &err); -if (!err && (*obj)->%(prefix)shas_%(c_name)s) { +visit_optional(m, &(*obj)->has_%(c_name)s, "%(name)s", &err); +if (!err && (*obj)->has_%(c_name)s) { ''', - c_prefix=c_var(field_prefix), prefix=field_prefix, c_name=c_var(argname), name=argname) push_indent() ret += mcgen(''' -visit_type_%(type)s(m, &(*obj)->%(c_prefix)s%(c_name)s, "%(name)s", &err); +visit_type_%(type)s(m, &(*obj)->%(c_name)s, "%(name)s", &err); ''', - c_prefix=c_var(field_prefix), prefix=field_prefix, type=type_name(argentry), c_name=c_var(argname), name=argname) @@ -114,29 +107,11 @@ out: return ret -def generate_visit_struct_body(field_prefix, name, members): +def generate_visit_struct_body(name, members): ret = mcgen(''' Error *err = NULL; -''') - - if not field_prefix: - full_name = name - else: - full_name = "%s_%s" % (field_prefix, name) - - if len(field_prefix): - ret += mcgen(''' - visit_start_struct(m, NULL, "", "%(name)s", 0, &err); -''', - name=name) - else: - ret += mcgen(''' visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err); -''', - name=name) - - ret += mcgen(''' if (!err) { if (*obj) { visit_type_%(name)s_fields(m, obj, errp); @@ -145,7 +120,7 @@ def generate_visit_struct_body(field_prefix, name, members): } error_propagate(errp, err); ''', - name=full_name) + name=name) return ret @@ -155,7 +130,7 @@ def generate_visit_struct(expr): members = expr['data'] base = expr.get('base') - ret = generate_visit_struct_fields(name, "", "", members, base) + ret = generate_visit_struct_fields(name, members, base) ret += mcgen(''' @@ -164,7 +139,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e ''', name=name) - ret += generate_visit_struct_body("", name, members) + ret += generate_visit_struct_body(name, members) ret += mcgen(''' } @@ -288,7 +263,7 @@ def generate_visit_union(expr): assert discriminator base_fields = find_struct(base)['data'].copy() del base_fields[discriminator] - ret += generate_visit_struct_fields(name, "", "", base_fields) + ret += generate_visit_struct_fields(name, base_fields) if discriminator: for key in members: From 363b4262a10a52f6d7ac1073bab5e6648da4051b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:35 -0600 Subject: [PATCH 38/40] qapi: Tweak doc references to QMP when QGA is also meant We have more than one qapi schema in use by more than one protocol. Add a new term 'Client JSON Protocol' for use throughout the document, to avoid confusion on whether something refers only to QMP and not QGA. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- docs/qapi-code-gen.txt | 157 +++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 76 deletions(-) diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt index a874a6d436..269a1f3d27 100644 --- a/docs/qapi-code-gen.txt +++ b/docs/qapi-code-gen.txt @@ -13,12 +13,14 @@ functionality to internal and external users. For external users/processes, this interface is made available by a JSON-based wire format for the QEMU Monitor Protocol (QMP) for controlling qemu, as well as the QEMU Guest Agent (QGA) for communicating with the guest. +The remainder of this document uses "Client JSON Protocol" when +referring to the wire contents of a QMP or QGA connection. -To map QMP and QGA interfaces to the native C QAPI implementations, a -JSON-based schema is used to define types and function signatures, and -a set of scripts is used to generate types, signatures, and -marshaling/dispatch code. This document will describe how the schemas, -scripts, and resulting code are used. +To map Client JSON Protocol interfaces to the native C QAPI +implementations, a JSON-based schema is used to define types and +function signatures, and a set of scripts is used to generate types, +signatures, and marshaling/dispatch code. This document will describe +how the schemas, scripts, and resulting code are used. == QMP/Guest agent schema == @@ -34,13 +36,13 @@ generated C structs and parameter lists). Ordering doesn't matter between top-level expressions or the keys within an expression, but does matter within dictionary values for 'data' and 'returns' members of a single expression. QAPI schema input is written using 'single -quotes' instead of JSON's "double quotes" (in contrast, QMP uses no -comments, and while input accepts 'single quotes' as an extension, -output is strict JSON using only "double quotes"). As in JSON, -trailing commas are not permitted in arrays or dictionaries. Input -must be ASCII (although QMP supports full Unicode strings, the QAPI -parser does not). At present, there is no place where a QAPI schema -requires the use of JSON numbers or null. +quotes' instead of JSON's "double quotes" (in contrast, Client JSON +Protocol uses no comments, and while input accepts 'single quotes' as +an extension, output is strict JSON using only "double quotes"). As +in JSON, trailing commas are not permitted in arrays or dictionaries. +Input must be ASCII (although QMP supports full Unicode strings, the +QAPI parser does not). At present, there is no place where a QAPI +schema requires the use of JSON numbers or null. Comments are allowed; anything between an unquoted # and the following newline is ignored. Although there is not yet a documentation @@ -82,8 +84,11 @@ that will use those types. Forward references are allowed: the parser scans in two passes, where the first pass learns all type names, and the second validates the schema and generates the code. This allows the definition of complex structs that can have mutually recursive -types, and allows for indefinite nesting of QMP that satisfies the -schema. A type name should not be defined more than once. +types, and allows for indefinite nesting of Client JSON Protocol that +satisfies the schema. A type name should not be defined more than +once. It is permissible for the schema to contain additional types +not used by any commands or events in the Client JSON Protocol, for +the side effect of generated C code used internally. There are seven top-level expressions recognized by the parser: 'include', 'command', 'struct', 'enum', 'union', 'alternate', and @@ -182,7 +187,7 @@ struct is: 'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } } The use of '*' as a prefix to the name means the member is optional in -the corresponding QMP usage. +the corresponding JSON protocol usage. The default initialization value of an optional argument should not be changed between versions of QEMU unless the new default maintains backward @@ -213,8 +218,8 @@ of use. A struct definition can specify another struct as its base. In this case, the fields of the base type are included as top-level fields -of the new struct's dictionary in the QMP wire format. An example -definition is: +of the new struct's dictionary in the Client JSON Protocol wire +format. An example definition is: { 'struct': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } } { 'struct': 'BlockdevOptionsGenericCOWFormat', @@ -242,19 +247,19 @@ useful. The list of strings should be lower case; if an enum name represents multiple words, use '-' between words. The string 'max' is not allowed as an enum value, and values should not be repeated. -The enumeration values are passed as strings over the QMP protocol, -but are encoded as C enum integral values in generated code. While -the C code starts numbering at 0, it is better to use explicit +The enumeration values are passed as strings over the Client JSON +Protocol, but are encoded as C enum integral values in generated code. +While the C code starts numbering at 0, it is better to use explicit comparisons to enum values than implicit comparisons to 0; the C code will also include a generated enum member ending in _MAX for tracking the size of the enum, useful when using common functions for converting between strings and enum values. Since the wire format always passes by name, it is acceptable to reorder or add new -enumeration members in any location without breaking QMP clients; -however, removing enum values would break compatibility. For any -struct that has a field that will only contain a finite set of -string values, using an enum type for that field is better than -open-coding the field to be type 'str'. +enumeration members in any location without breaking clients of Client +JSON Protocol; however, removing enum values would break +compatibility. For any struct that has a field that will only contain +a finite set of string values, using an enum type for that field is +better than open-coding the field to be type 'str'. === Union types === @@ -280,10 +285,10 @@ values to data types like in this example: 'data': { 'file': 'FileOptions', 'qcow2': 'Qcow2Options' } } -In the QMP wire format, a simple union is represented by a dictionary -that contains the 'type' field as a discriminator, and a 'data' field -that is of the specified data type corresponding to the discriminator -value, as in these examples: +In the Client JSON Protocol, a simple union is represented by a +dictionary that contains the 'type' field as a discriminator, and a +'data' field that is of the specified data type corresponding to the +discriminator value, as in these examples: { "type": "file", "data" : { "filename": "/some/place/my-image" } } { "type": "qcow2", "data" : { "backing-file": "/some/place/my-image", @@ -366,16 +371,16 @@ Just like for a simple union, an implicit C enum 'NameKind' is created to enumerate the branches for the alternate 'Name'. Unlike a union, the discriminator string is never passed on the wire -for QMP. Instead, the value's JSON type serves as an implicit -discriminator, which in turn means that an alternate can only express -a choice between types represented differently in JSON. If a branch -is typed as the 'bool' built-in, the alternate accepts true and false; -if it is typed as any of the various numeric built-ins, it accepts a -JSON number; if it is typed as a 'str' built-in or named enum type, it -accepts a JSON string; and if it is typed as a complex type (struct or -union), it accepts a JSON object. Two different complex types, for -instance, aren't permitted, because both are represented as a JSON -object. +for the Client JSON Protocol. Instead, the value's JSON type serves +as an implicit discriminator, which in turn means that an alternate +can only express a choice between types represented differently in +JSON. If a branch is typed as the 'bool' built-in, the alternate +accepts true and false; if it is typed as any of the various numeric +built-ins, it accepts a JSON number; if it is typed as a 'str' +built-in or named enum type, it accepts a JSON string; and if it is +typed as a complex type (struct or union), it accepts a JSON object. +Two different complex types, for instance, aren't permitted, because +both are represented as a JSON object. The example alternate declaration above allows using both of the following example objects: @@ -394,37 +399,37 @@ Usage: { 'command': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT, Commands are defined by using a dictionary containing several members, where three members are most common. The 'command' member is a -mandatory string, and determines the "execute" value passed in a QMP -command exchange. +mandatory string, and determines the "execute" value passed in a +Client JSON Protocol command exchange. The 'data' argument maps to the "arguments" dictionary passed in as -part of a QMP command. The 'data' member is optional and defaults to -{} (an empty dictionary). If present, it must be the string name of a -complex type, a one-element array containing the name of a complex -type, or a dictionary that declares an anonymous type with the same -semantics as a 'struct' expression, with one exception noted below when -'gen' is used. +part of a Client JSON Protocol command. The 'data' member is optional +and defaults to {} (an empty dictionary). If present, it must be the +string name of a complex type, a one-element array containing the name +of a complex type, or a dictionary that declares an anonymous type +with the same semantics as a 'struct' expression, with one exception +noted below when 'gen' is used. The 'returns' member describes what will appear in the "return" field -of a QMP reply on successful completion of a command. The member is -optional from the command declaration; if absent, the "return" field -will be an empty dictionary. If 'returns' is present, it must be the -string name of a complex or built-in type, a one-element array -containing the name of a complex or built-in type, or a dictionary -that declares an anonymous type with the same semantics as a 'struct' -expression, with one exception noted below when 'gen' is used. -Although it is permitted to have the 'returns' member name a built-in -type or an array of built-in types, any command that does this cannot -be extended to return additional information in the future; thus, new -commands should strongly consider returning a dictionary-based type or -an array of dictionaries, even if the dictionary only contains one -field at the present. +of a Client JSON Protocol reply on successful completion of a command. +The member is optional from the command declaration; if absent, the +"return" field will be an empty dictionary. If 'returns' is present, +it must be the string name of a complex or built-in type, a +one-element array containing the name of a complex or built-in type, +or a dictionary that declares an anonymous type with the same +semantics as a 'struct' expression, with one exception noted below +when 'gen' is used. Although it is permitted to have the 'returns' +member name a built-in type or an array of built-in types, any command +that does this cannot be extended to return additional information in +the future; thus, new commands should strongly consider returning a +dictionary-based type or an array of dictionaries, even if the +dictionary only contains one field at the present. -All commands use a dictionary to report failure, with no way to -specify that in QAPI. Where the error return is different than the -usual GenericError class in order to help the client react differently -to certain error conditions, it is worth documenting this in the -comments before the command declaration. +All commands in Client JSON Protocol use a dictionary to report +failure, with no way to specify that in QAPI. Where the error return +is different than the usual GenericError class in order to help the +client react differently to certain error conditions, it is worth +documenting this in the comments before the command declaration. Some example commands: @@ -434,7 +439,7 @@ Some example commands: { 'command': 'my-second-command', 'returns': [ 'MyType' ] } -which would validate this QMP transaction: +which would validate this Client JSON Protocol transaction: => { "execute": "my-first-command", "arguments": { "arg1": "hello" } } @@ -443,14 +448,14 @@ which would validate this QMP transaction: <= { "return": [ { "value": "one" }, { } ] } In rare cases, QAPI cannot express a type-safe representation of a -corresponding QMP command. In these cases, if the command expression -includes the key 'gen' with boolean value false, then the 'data' or -'returns' member that intends to bypass generated type-safety and do -its own manual validation should use an inline dictionary definition, -with a value of '**' rather than a valid type name for the keys that -the generated code will not validate. Please try to avoid adding new -commands that rely on this, and instead use type-safe unions. For an -example of bypass usage: +corresponding Client JSON Protocol command. In these cases, if the +command expression includes the key 'gen' with boolean value false, +then the 'data' or 'returns' member that intends to bypass generated +type-safety and do its own manual validation should use an inline +dictionary definition, with a value of '**' rather than a valid type +name for the keys that the generated code will not validate. Please +try to avoid adding new commands that rely on this, and instead use +type-safe unions. For an example of bypass usage: { 'command': 'netdev_add', 'data': {'type': 'str', 'id': 'str', '*props': '**'}, @@ -494,9 +499,9 @@ Resulting in this JSON object: 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 QMP/guest agent server, unmarshal the arguments into +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 QMP/guest agent response to be returned to the user. +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 From a7f5966b297330f6492020019544ae87c45d699b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:36 -0600 Subject: [PATCH 39/40] qapi: Support (subset of) \u escapes in strings The handling of \ inside QAPI strings was less than ideal, and really only worked JSON's \/, \\, \", and our extension of \' (an obvious extension, when you realize we use '' instead of "" for strings). For other things, like '\n', it resulted in a literal 'n' instead of a newline. Of course, at the moment, we really have no use for escaped characters, as QAPI has to map to C identifiers, and we currently support ASCII only for that. But down the road, we may add support for default values for string parameters to a command or struct; if that happens, it would be nice to correctly support all JSON escape sequences, such as \n or \uXXXX. This gets us closer, by supporting Unicode escapes in the ASCII range. Since JSON does not require \OCTAL or \xXX escapes, and our QMP implementation does not understand them either, I intentionally reject it here, but it would be an easy addition if we desired it. Likewise, intentionally refusing the NUL byte means we don't have to worry about C strings being shorter than the qapi input. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 36 +++++++++++++++++++- tests/Makefile | 2 ++ tests/qapi-schema/escape-outside-string.err | 1 + tests/qapi-schema/escape-outside-string.exit | 1 + tests/qapi-schema/escape-outside-string.json | 3 ++ tests/qapi-schema/escape-outside-string.out | 0 tests/qapi-schema/escape-too-big.err | 1 + tests/qapi-schema/escape-too-big.exit | 1 + tests/qapi-schema/escape-too-big.json | 3 ++ tests/qapi-schema/escape-too-big.out | 0 tests/qapi-schema/escape-too-short.err | 1 + tests/qapi-schema/escape-too-short.exit | 1 + tests/qapi-schema/escape-too-short.json | 3 ++ tests/qapi-schema/escape-too-short.out | 0 tests/qapi-schema/ident-with-escape.err | 1 - tests/qapi-schema/ident-with-escape.exit | 2 +- tests/qapi-schema/ident-with-escape.json | 2 +- tests/qapi-schema/ident-with-escape.out | 3 ++ tests/qapi-schema/unicode-str.err | 1 + tests/qapi-schema/unicode-str.exit | 1 + tests/qapi-schema/unicode-str.json | 2 ++ tests/qapi-schema/unicode-str.out | 0 tests/qapi-schema/unknown-escape.err | 1 + tests/qapi-schema/unknown-escape.exit | 1 + tests/qapi-schema/unknown-escape.json | 3 ++ tests/qapi-schema/unknown-escape.out | 0 26 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/qapi-schema/escape-outside-string.err create mode 100644 tests/qapi-schema/escape-outside-string.exit create mode 100644 tests/qapi-schema/escape-outside-string.json create mode 100644 tests/qapi-schema/escape-outside-string.out create mode 100644 tests/qapi-schema/escape-too-big.err create mode 100644 tests/qapi-schema/escape-too-big.exit create mode 100644 tests/qapi-schema/escape-too-big.json create mode 100644 tests/qapi-schema/escape-too-big.out create mode 100644 tests/qapi-schema/escape-too-short.err create mode 100644 tests/qapi-schema/escape-too-short.exit create mode 100644 tests/qapi-schema/escape-too-short.json create mode 100644 tests/qapi-schema/escape-too-short.out create mode 100644 tests/qapi-schema/unicode-str.err create mode 100644 tests/qapi-schema/unicode-str.exit create mode 100644 tests/qapi-schema/unicode-str.json create mode 100644 tests/qapi-schema/unicode-str.out create mode 100644 tests/qapi-schema/unknown-escape.err create mode 100644 tests/qapi-schema/unknown-escape.exit create mode 100644 tests/qapi-schema/unknown-escape.json create mode 100644 tests/qapi-schema/unknown-escape.out diff --git a/scripts/qapi.py b/scripts/qapi.py index 44898b082a..6a9aa24a6c 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -173,7 +173,41 @@ class QAPISchema: raise QAPISchemaError(self, 'Missing terminating "\'"') if esc: - string += ch + if ch == 'b': + string += '\b' + elif ch == 'f': + string += '\f' + elif ch == 'n': + string += '\n' + elif ch == 'r': + string += '\r' + elif ch == 't': + string += '\t' + elif ch == 'u': + value = 0 + for x in range(0, 4): + ch = self.src[self.cursor] + self.cursor += 1 + if ch not in "0123456789abcdefABCDEF": + raise QAPISchemaError(self, + '\\u escape needs 4 ' + 'hex digits') + value = (value << 4) + int(ch, 16) + # If Python 2 and 3 didn't disagree so much on + # how to handle Unicode, then we could allow + # Unicode string defaults. But most of QAPI is + # ASCII-only, so we aren't losing much for now. + if not value or value > 0x7f: + raise QAPISchemaError(self, + 'For now, \\u escape ' + 'only supports non-zero ' + 'values up to \\u007f') + string += chr(value) + elif ch in "\\/'\"": + string += ch + else: + raise QAPISchemaError(self, + "Unknown escape \\%s" %ch) esc = False elif ch == "\\": esc = True diff --git a/tests/Makefile b/tests/Makefile index e2a3bd349e..547a2499be 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -212,6 +212,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ enum-clash-member.json enum-max-member.json enum-union-clash.json \ enum-bad-name.json funny-char.json indented-expr.json \ missing-type.json bad-ident.json ident-with-escape.json \ + escape-outside-string.json unknown-escape.json \ + escape-too-short.json escape-too-big.json unicode-str.json \ double-type.json bad-base.json bad-type-bool.json bad-type-int.json \ bad-type-dict.json double-data.json unknown-expr-key.json \ redefined-type.json redefined-command.json redefined-builtin.json \ diff --git a/tests/qapi-schema/escape-outside-string.err b/tests/qapi-schema/escape-outside-string.err new file mode 100644 index 0000000000..b9b8837fd2 --- /dev/null +++ b/tests/qapi-schema/escape-outside-string.err @@ -0,0 +1 @@ +tests/qapi-schema/escape-outside-string.json:3:27: Stray "\" diff --git a/tests/qapi-schema/escape-outside-string.exit b/tests/qapi-schema/escape-outside-string.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/escape-outside-string.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/escape-outside-string.json b/tests/qapi-schema/escape-outside-string.json new file mode 100644 index 0000000000..482f79554b --- /dev/null +++ b/tests/qapi-schema/escape-outside-string.json @@ -0,0 +1,3 @@ +# escape sequences are permitted only inside strings +# { 'command': 'foo', 'data': {} } +{ 'command': 'foo', 'data'\u003a{} } diff --git a/tests/qapi-schema/escape-outside-string.out b/tests/qapi-schema/escape-outside-string.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/escape-too-big.err b/tests/qapi-schema/escape-too-big.err new file mode 100644 index 0000000000..d9aeb5dc38 --- /dev/null +++ b/tests/qapi-schema/escape-too-big.err @@ -0,0 +1 @@ +tests/qapi-schema/escape-too-big.json:3:14: For now, \u escape only supports non-zero values up to \u007f diff --git a/tests/qapi-schema/escape-too-big.exit b/tests/qapi-schema/escape-too-big.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/escape-too-big.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/escape-too-big.json b/tests/qapi-schema/escape-too-big.json new file mode 100644 index 0000000000..62bcecd557 --- /dev/null +++ b/tests/qapi-schema/escape-too-big.json @@ -0,0 +1,3 @@ +# we don't support full Unicode strings, yet +# { 'command': 'é' } +{ 'command': '\u00e9' } diff --git a/tests/qapi-schema/escape-too-big.out b/tests/qapi-schema/escape-too-big.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/escape-too-short.err b/tests/qapi-schema/escape-too-short.err new file mode 100644 index 0000000000..934de598ee --- /dev/null +++ b/tests/qapi-schema/escape-too-short.err @@ -0,0 +1 @@ +tests/qapi-schema/escape-too-short.json:3:14: \u escape needs 4 hex digits diff --git a/tests/qapi-schema/escape-too-short.exit b/tests/qapi-schema/escape-too-short.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/escape-too-short.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/escape-too-short.json b/tests/qapi-schema/escape-too-short.json new file mode 100644 index 0000000000..6cb1dec8f7 --- /dev/null +++ b/tests/qapi-schema/escape-too-short.json @@ -0,0 +1,3 @@ +# the \u escape requires 4 hex digits +# { 'command': 'a' } +{ 'command': '\u61' } diff --git a/tests/qapi-schema/escape-too-short.out b/tests/qapi-schema/escape-too-short.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/ident-with-escape.err b/tests/qapi-schema/ident-with-escape.err index f7d1c55327..e69de29bb2 100644 --- a/tests/qapi-schema/ident-with-escape.err +++ b/tests/qapi-schema/ident-with-escape.err @@ -1 +0,0 @@ -tests/qapi-schema/ident-with-escape.json:3: Expression is missing metatype diff --git a/tests/qapi-schema/ident-with-escape.exit b/tests/qapi-schema/ident-with-escape.exit index d00491fd7e..573541ac97 100644 --- a/tests/qapi-schema/ident-with-escape.exit +++ b/tests/qapi-schema/ident-with-escape.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/qapi-schema/ident-with-escape.json b/tests/qapi-schema/ident-with-escape.json index cfb205052a..56617501e7 100644 --- a/tests/qapi-schema/ident-with-escape.json +++ b/tests/qapi-schema/ident-with-escape.json @@ -1,4 +1,4 @@ -# FIXME: we should allow escape sequences in strings, if they map back to ASCII +# we allow escape sequences in strings, if they map back to ASCII # { 'command': 'fooA', 'data': { 'bar1': 'str' } } { 'c\u006fmmand': '\u0066\u006f\u006FA', 'd\u0061ta': { '\u0062\u0061\u00721': '\u0073\u0074\u0072' } } diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out index e69de29bb2..402843081b 100644 --- a/tests/qapi-schema/ident-with-escape.out +++ b/tests/qapi-schema/ident-with-escape.out @@ -0,0 +1,3 @@ +[OrderedDict([('command', 'fooA'), ('data', OrderedDict([('bar1', 'str')]))])] +[] +[] diff --git a/tests/qapi-schema/unicode-str.err b/tests/qapi-schema/unicode-str.err new file mode 100644 index 0000000000..f621cd6448 --- /dev/null +++ b/tests/qapi-schema/unicode-str.err @@ -0,0 +1 @@ +tests/qapi-schema/unicode-str.json:2: 'command' uses invalid name 'é' diff --git a/tests/qapi-schema/unicode-str.exit b/tests/qapi-schema/unicode-str.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/unicode-str.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/unicode-str.json b/tests/qapi-schema/unicode-str.json new file mode 100644 index 0000000000..5253a1b9f3 --- /dev/null +++ b/tests/qapi-schema/unicode-str.json @@ -0,0 +1,2 @@ +# we don't support full Unicode strings, yet +{ 'command': 'é' } diff --git a/tests/qapi-schema/unicode-str.out b/tests/qapi-schema/unicode-str.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/unknown-escape.err b/tests/qapi-schema/unknown-escape.err new file mode 100644 index 0000000000..000e30ddf3 --- /dev/null +++ b/tests/qapi-schema/unknown-escape.err @@ -0,0 +1 @@ +tests/qapi-schema/unknown-escape.json:3:21: Unknown escape \x diff --git a/tests/qapi-schema/unknown-escape.exit b/tests/qapi-schema/unknown-escape.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/unknown-escape.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/unknown-escape.json b/tests/qapi-schema/unknown-escape.json new file mode 100644 index 0000000000..8e6891e52a --- /dev/null +++ b/tests/qapi-schema/unknown-escape.json @@ -0,0 +1,3 @@ +# we only recognize JSON escape sequences, plus our \' extension (no \x) +# { 'command': 'foo', 'data': {} } +{ 'command': 'foo', 'dat\x61':{} } diff --git a/tests/qapi-schema/unknown-escape.out b/tests/qapi-schema/unknown-escape.out new file mode 100644 index 0000000000..e69de29bb2 From ff55d72eaf9628e7d58e7b067b361cdbf789c9f4 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 4 May 2015 09:05:37 -0600 Subject: [PATCH 40/40] qapi: Check for member name conflicts with a base class Our type inheritance for both 'struct' and for flat 'union' merges key/value pairs from the base class with those from the type in question. Although the C code currently boxes things so that there is a distinction between which member is referred to, the QMP wire format does not allow passing a key more than once in a single object. Besides, if we ever change the generated C code to not be quite so boxy, we'd want to avoid duplicate member names there, too. Fix a testsuite entry added in an earlier patch, as well as adding a couple more tests to ensure we have appropriate coverage. Ensure that collisions are detected, regardless of whether there is a difference in opinion on whether the member name is optional. Signed-off-by: Eric Blake Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi.py | 23 ++++++++++++++++++- tests/Makefile | 3 ++- tests/qapi-schema/flat-union-branch-clash.err | 1 + .../qapi-schema/flat-union-branch-clash.exit | 2 +- .../qapi-schema/flat-union-branch-clash.json | 4 ++-- tests/qapi-schema/flat-union-branch-clash.out | 9 -------- tests/qapi-schema/struct-base-clash-deep.err | 1 + tests/qapi-schema/struct-base-clash-deep.exit | 1 + tests/qapi-schema/struct-base-clash-deep.json | 9 ++++++++ tests/qapi-schema/struct-base-clash-deep.out | 0 tests/qapi-schema/struct-base-clash.err | 1 + tests/qapi-schema/struct-base-clash.exit | 1 + tests/qapi-schema/struct-base-clash.json | 6 +++++ tests/qapi-schema/struct-base-clash.out | 0 14 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 tests/qapi-schema/struct-base-clash-deep.err create mode 100644 tests/qapi-schema/struct-base-clash-deep.exit create mode 100644 tests/qapi-schema/struct-base-clash-deep.json create mode 100644 tests/qapi-schema/struct-base-clash-deep.out create mode 100644 tests/qapi-schema/struct-base-clash.err create mode 100644 tests/qapi-schema/struct-base-clash.exit create mode 100644 tests/qapi-schema/struct-base-clash.json create mode 100644 tests/qapi-schema/struct-base-clash.out diff --git a/scripts/qapi.py b/scripts/qapi.py index 6a9aa24a6c..166b74f644 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -414,6 +414,20 @@ def check_type(expr_info, source, value, allow_array = False, allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']) +def check_member_clash(expr_info, base_name, data, source = ""): + base = find_struct(base_name) + assert base + base_members = base['data'] + for key in data.keys(): + if key.startswith('*'): + key = key[1:] + if key in base_members or "*" + key in base_members: + raise QAPIExprError(expr_info, + "Member name '%s'%s clashes with base '%s'" + % (key, source, base_name)) + if base.get('base'): + check_member_clash(expr_info, base['base'], data, source) + def check_command(expr, expr_info): name = expr['command'] allow_star = expr.has_key('gen') @@ -503,9 +517,14 @@ def check_union(expr, expr_info): check_name(expr_info, "Member of union '%s'" % name, key) # Each value must name a known type; furthermore, in flat unions, - # branches must be a struct + # branches must be a struct with no overlapping member names check_type(expr_info, "Member '%s' of union '%s'" % (key, name), value, allow_array=True, allow_metas=allow_metas) + if base: + branch_struct = find_struct(value) + assert branch_struct + check_member_clash(expr_info, base, branch_struct['data'], + " of branch '%s'" % key) # If the discriminator names an enum type, then all members # of 'data' must also be members of the enum type. @@ -582,6 +601,8 @@ def check_struct(expr, expr_info): allow_dict=True, allow_optional=True) check_type(expr_info, "'base' for struct '%s'" % name, expr.get('base'), allow_metas=['struct']) + if expr.get('base'): + check_member_clash(expr_info, expr['base'], expr['data']) def check_exprs(schema): for expr_elem in schema.exprs: diff --git a/tests/Makefile b/tests/Makefile index 547a2499be..666aee2ac3 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -243,7 +243,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \ include-simple.json include-relpath.json include-format-err.json \ include-non-file.json include-no-file.json include-before-err.json \ include-nested-err.json include-self-cycle.json include-cycle.json \ - include-repetition.json event-nest-struct.json event-case.json) + include-repetition.json event-nest-struct.json event-case.json \ + 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 diff --git a/tests/qapi-schema/flat-union-branch-clash.err b/tests/qapi-schema/flat-union-branch-clash.err index e69de29bb2..f11276688c 100644 --- a/tests/qapi-schema/flat-union-branch-clash.err +++ b/tests/qapi-schema/flat-union-branch-clash.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-branch-clash.json:10: Member name 'name' of branch 'value1' clashes with base 'Base' diff --git a/tests/qapi-schema/flat-union-branch-clash.exit b/tests/qapi-schema/flat-union-branch-clash.exit index 573541ac97..d00491fd7e 100644 --- a/tests/qapi-schema/flat-union-branch-clash.exit +++ b/tests/qapi-schema/flat-union-branch-clash.exit @@ -1 +1 @@ -0 +1 diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json index 8b0b807a03..8fb054f004 100644 --- a/tests/qapi-schema/flat-union-branch-clash.json +++ b/tests/qapi-schema/flat-union-branch-clash.json @@ -1,8 +1,8 @@ -# FIXME: we should check for no duplicate keys between branches and base +# we check for no duplicate keys between branches and base { 'enum': 'TestEnum', 'data': [ 'value1', 'value2' ] } { 'struct': 'Base', - 'data': { 'enum1': 'TestEnum', 'name': 'str' } } + 'data': { 'enum1': 'TestEnum', '*name': 'str' } } { 'struct': 'Branch1', 'data': { 'name': 'str' } } { 'struct': 'Branch2', diff --git a/tests/qapi-schema/flat-union-branch-clash.out b/tests/qapi-schema/flat-union-branch-clash.out index 04c239565e..e69de29bb2 100644 --- a/tests/qapi-schema/flat-union-branch-clash.out +++ b/tests/qapi-schema/flat-union-branch-clash.out @@ -1,9 +0,0 @@ -[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]), - OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), - OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]), - OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'Branch1'), ('value2', 'Branch2')]))])] -[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}] -[OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]), - OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]), - OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])] diff --git a/tests/qapi-schema/struct-base-clash-deep.err b/tests/qapi-schema/struct-base-clash-deep.err new file mode 100644 index 0000000000..e3e9f8d289 --- /dev/null +++ b/tests/qapi-schema/struct-base-clash-deep.err @@ -0,0 +1 @@ +tests/qapi-schema/struct-base-clash-deep.json:7: Member name 'name' clashes with base 'Base' diff --git a/tests/qapi-schema/struct-base-clash-deep.exit b/tests/qapi-schema/struct-base-clash-deep.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/struct-base-clash-deep.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/struct-base-clash-deep.json b/tests/qapi-schema/struct-base-clash-deep.json new file mode 100644 index 0000000000..552fe94317 --- /dev/null +++ b/tests/qapi-schema/struct-base-clash-deep.json @@ -0,0 +1,9 @@ +# we check for no duplicate keys with indirect base +{ 'struct': 'Base', + 'data': { 'name': 'str' } } +{ 'struct': 'Mid', + 'base': 'Base', + 'data': { 'value': 'int' } } +{ 'struct': 'Sub', + 'base': 'Mid', + 'data': { '*name': 'str' } } diff --git a/tests/qapi-schema/struct-base-clash-deep.out b/tests/qapi-schema/struct-base-clash-deep.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/struct-base-clash.err b/tests/qapi-schema/struct-base-clash.err new file mode 100644 index 0000000000..3ac37fb26a --- /dev/null +++ b/tests/qapi-schema/struct-base-clash.err @@ -0,0 +1 @@ +tests/qapi-schema/struct-base-clash.json:4: Member name 'name' clashes with base 'Base' diff --git a/tests/qapi-schema/struct-base-clash.exit b/tests/qapi-schema/struct-base-clash.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/struct-base-clash.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/struct-base-clash.json b/tests/qapi-schema/struct-base-clash.json new file mode 100644 index 0000000000..f2afc9b6f6 --- /dev/null +++ b/tests/qapi-schema/struct-base-clash.json @@ -0,0 +1,6 @@ +# we check for no duplicate keys with base +{ 'struct': 'Base', + 'data': { 'name': 'str' } } +{ 'struct': 'Sub', + 'base': 'Base', + 'data': { 'name': 'str' } } diff --git a/tests/qapi-schema/struct-base-clash.out b/tests/qapi-schema/struct-base-clash.out new file mode 100644 index 0000000000..e69de29bb2