Skip to content

Commit

Permalink
qapi: Test for various name collisions
Browse files Browse the repository at this point in the history
Expose some weaknesses in the generator: we don't always forbid
the generation of structs that contain multiple members that map
to the same C or QMP name.  This has already been marked FIXME in
qapi.py in commit d90675f, but having more tests will make sure
future patches produce desired behavior; and updating existing
patches to better document things doesn't hurt, either.  Some of
these collisions are already caught in the old-style parser
checks, but ultimately we want all collisions to be caught in the
new-style QAPISchema*.check() methods.

This patch focuses on C struct members, and does not consider
collisions between commands and events (affecting C function
names), or even collisions between generated C type names with
user type names (for things like automatic FOOList struct
representing array types or FOOKind for an implicit enum).

There are two types of struct collisions we want to catch:
 1) Collision between two keys in a JSON object. qapi.py prevents
    that within a single struct (see test duplicate-key), but it is
    possible to have collisions between a type's members and its
    base type's members (existing tests struct-base-clash,
    struct-base-clash-deep), and its flat union variant members
    (renamed test flat-union-clash-member).
 2) Collision between two members of the C struct that is generated
    for a given QAPI type:
    a) Multiple QAPI names map to the same C name (new test
       args-name-clash)
    b) A QAPI name maps to a C name that is used for another purpose
       (new tests flat-union-clash-branch, struct-base-clash-base,
       union-clash-data). We already fixed some such cases in commit
       0f61af3 and 1e6c161, but more remain.
    c) Two C names generated for other purposes clash
       (updated test alternate-clash, new test union-clash-branches,
       union-clash-type, flat-union-clash-type)

Ultimately, if we need to have a flat union where a tag value
clashes with a base member name, we could change the generator to
name the union (using 'foo.u.value' rather than 'foo.value') or
otherwise munge the C name corresponding to tag values.  But
unless such a need arises, it will probably be easier to just
forbid these collisions.

Some of these negative tests will be deleted later, and positive
tests added to qapi-schema-test.json in their place, when the
generator code is reworked to avoid particular code generation
collisions in class 2).

[Note that viewing this patch with git rename detection enabled
may see some confusion due to renaming some tests while adding
others, but where the content is similar enough that git picks
the wrong pre- and post-patch files to associate]

Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <1443565276-4535-6-git-send-email-eblake@redhat.com>
[Improve commit message and comments a bit, drop an unrelated test]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
  • Loading branch information
ebblake authored and Markus Armbruster committed Oct 12, 2015
1 parent 437db25 commit d220fbc
Show file tree
Hide file tree
Showing 46 changed files with 182 additions and 15 deletions.
9 changes: 8 additions & 1 deletion tests/Makefile
Expand Up @@ -241,6 +241,7 @@ qapi-schema += args-invalid.json
qapi-schema += args-member-array-bad.json
qapi-schema += args-member-array.json
qapi-schema += args-member-unknown.json
qapi-schema += args-name-clash.json
qapi-schema += args-union.json
qapi-schema += args-unknown.json
qapi-schema += bad-base.json
Expand Down Expand Up @@ -276,7 +277,9 @@ qapi-schema += flat-union-bad-base.json
qapi-schema += flat-union-bad-discriminator.json
qapi-schema += flat-union-base-any.json
qapi-schema += flat-union-base-union.json
qapi-schema += flat-union-branch-clash.json
qapi-schema += flat-union-clash-branch.json
qapi-schema += flat-union-clash-member.json
qapi-schema += flat-union-clash-type.json
qapi-schema += flat-union-inline.json
qapi-schema += flat-union-int-branch.json
qapi-schema += flat-union-invalid-branch-key.json
Expand Down Expand Up @@ -318,6 +321,7 @@ qapi-schema += returns-dict.json
qapi-schema += returns-int.json
qapi-schema += returns-unknown.json
qapi-schema += returns-whitelist.json
qapi-schema += struct-base-clash-base.json
qapi-schema += struct-base-clash-deep.json
qapi-schema += struct-base-clash.json
qapi-schema += struct-data-invalid.json
Expand All @@ -331,6 +335,9 @@ qapi-schema += unclosed-string.json
qapi-schema += unicode-str.json
qapi-schema += union-bad-branch.json
qapi-schema += union-base-no-discriminator.json
qapi-schema += union-clash-branches.json
qapi-schema += union-clash-data.json
qapi-schema += union-clash-type.json
qapi-schema += union-invalid-base.json
qapi-schema += union-max.json
qapi-schema += union-optional-branch.json
Expand Down
2 changes: 1 addition & 1 deletion tests/qapi-schema/alternate-clash.err
@@ -1 +1 @@
tests/qapi-schema/alternate-clash.json:2: Alternate 'Alt1' member 'ONE' clashes with 'one'
tests/qapi-schema/alternate-clash.json:7: Alternate 'Alt1' member 'a_b' clashes with 'a-b'
9 changes: 7 additions & 2 deletions tests/qapi-schema/alternate-clash.json
@@ -1,3 +1,8 @@
# we detect C enum collisions in an alternate
# Alternate branch name collision
# Reject an alternate that would result in a collision in generated C
# names (this would try to generate two enum values 'ALT1_KIND_A_B').
# TODO: In the future, if alternates are simplified to not generate
# the implicit Alt1Kind enum, we would still have a collision with the
# resulting C union trying to have two members named 'a_b'.
{ 'alternate': 'Alt1',
'data': { 'one': 'str', 'ONE': 'int' } }
'data': { 'a-b': 'str', 'a_b': 'int' } }
File renamed without changes.
1 change: 1 addition & 0 deletions tests/qapi-schema/args-name-clash.exit
@@ -0,0 +1 @@
0
5 changes: 5 additions & 0 deletions tests/qapi-schema/args-name-clash.json
@@ -0,0 +1,5 @@
# C member name collision
# FIXME - This parses, but fails to compile, because the C struct is given
# two 'a_b' members. Either reject this at parse time, or munge the C names
# to avoid the collision.
{ 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
6 changes: 6 additions & 0 deletions tests/qapi-schema/args-name-clash.out
@@ -0,0 +1,6 @@
object :empty
object :obj-oops-arg
member a-b: str optional=False
member a_b: str optional=False
command oops :obj-oops-arg -> None
gen=True success_response=True
2 changes: 1 addition & 1 deletion tests/qapi-schema/duplicate-key.err
@@ -1 +1 @@
tests/qapi-schema/duplicate-key.json:2:10: Duplicate key "key"
tests/qapi-schema/duplicate-key.json:3:10: Duplicate key "key"
1 change: 1 addition & 0 deletions tests/qapi-schema/duplicate-key.json
@@ -1,2 +1,3 @@
# QAPI cannot include the same key more than once in any {}
{ 'key': 'value',
'key': 'value' }
2 changes: 1 addition & 1 deletion 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 struct
tests/qapi-schema/flat-union-base-union.json:14: Base 'UnionBase' is not a valid struct
5 changes: 4 additions & 1 deletion tests/qapi-schema/flat-union-base-union.json
@@ -1,4 +1,7 @@
# we require the base to be a struct
# For now, we require the base to be a struct without variants
# TODO: It would be possible to allow a union as a base, as long as all
# permutations of QMP names exposed by base do not clash with any QMP
# member names added by local variants.
{ 'enum': 'TestEnum',
'data': [ 'value1', 'value2' ] }
{ 'struct': 'TestTypeA',
Expand Down
1 change: 0 additions & 1 deletion tests/qapi-schema/flat-union-branch-clash.err

This file was deleted.

Empty file.
1 change: 1 addition & 0 deletions tests/qapi-schema/flat-union-clash-branch.exit
@@ -0,0 +1 @@
0
18 changes: 18 additions & 0 deletions tests/qapi-schema/flat-union-clash-branch.json
@@ -0,0 +1,18 @@
# Flat union branch name collision
# FIXME: this parses, but then fails to compile due to a duplicate 'c_d'
# (one from the base member, the other from the branch name). We should
# either reject the collision at parse time, or munge the generated branch
# name to allow this to compile.
{ 'enum': 'TestEnum',
'data': [ 'base', 'c-d' ] }
{ 'struct': 'Base',
'data': { 'enum1': 'TestEnum', '*c_d': 'str' } }
{ 'struct': 'Branch1',
'data': { 'string': 'str' } }
{ 'struct': 'Branch2',
'data': { 'value': 'int' } }
{ 'union': 'TestUnion',
'base': 'Base',
'discriminator': 'enum1',
'data': { 'base': 'Branch1',
'c-d': 'Branch2' } }
14 changes: 14 additions & 0 deletions tests/qapi-schema/flat-union-clash-branch.out
@@ -0,0 +1,14 @@
object :empty
object Base
member enum1: TestEnum optional=False
member c_d: str optional=True
object Branch1
member string: str optional=False
object Branch2
member value: int optional=False
enum TestEnum ['base', 'c-d']
object TestUnion
base Base
tag enum1
case base: Branch1
case c-d: Branch2
1 change: 1 addition & 0 deletions tests/qapi-schema/flat-union-clash-member.err
@@ -0,0 +1 @@
tests/qapi-schema/flat-union-clash-member.json:11: Member name 'name' of branch 'value1' clashes with base 'Base'
@@ -1,4 +1,5 @@
# we check for no duplicate keys between branches and base
# We check for no duplicate keys between branch members and base
# base's member 'name' clashes with Branch1's
{ 'enum': 'TestEnum',
'data': [ 'value1', 'value2' ] }
{ 'struct': 'Base',
Expand Down
Empty file.
16 changes: 16 additions & 0 deletions tests/qapi-schema/flat-union-clash-type.err
@@ -0,0 +1,16 @@
Traceback (most recent call last):
File "tests/qapi-schema/test-qapi.py", line 55, in <module>
schema = QAPISchema(sys.argv[1])
File "scripts/qapi.py", line 1116, in __init__
self.check()
File "scripts/qapi.py", line 1299, in check
ent.check(self)
File "scripts/qapi.py", line 962, in check
self.variants.check(schema, members, seen)
File "scripts/qapi.py", line 1024, in check
v.check(schema, self.tag_member.type, vseen)
File "scripts/qapi.py", line 1032, in check
QAPISchemaObjectTypeMember.check(self, schema, [], seen)
File "scripts/qapi.py", line 994, in check
assert self.name not in seen
AssertionError
1 change: 1 addition & 0 deletions tests/qapi-schema/flat-union-clash-type.exit
@@ -0,0 +1 @@
1
16 changes: 16 additions & 0 deletions tests/qapi-schema/flat-union-clash-type.json
@@ -0,0 +1,16 @@
# Flat union branch 'type'
# FIXME: this triggers an assertion failure. But even with that fixed,
# we would have a clash in generated C, between the member 'type'
# inherited from 'Base' and the branch name 'type' within the
# union. We should either reject this, or munge the generated C to let
# it compile.
{ 'enum': 'TestEnum',
'data': [ 'type' ] }
{ 'struct': 'Base',
'data': { 'type': 'TestEnum' } }
{ 'struct': 'Branch1',
'data': { 'string': 'str' } }
{ 'union': 'TestUnion',
'base': 'Base',
'discriminator': 'type',
'data': { 'type': 'Branch1' } }
Empty file.
7 changes: 5 additions & 2 deletions tests/qapi-schema/qapi-schema-test.json
Expand Up @@ -32,11 +32,14 @@
'dict1': 'UserDefTwoDict' } }

# for testing unions
# Among other things, test that a name collision between branches does
# not cause any problems (since only one branch can be in use at a time),
# by intentionally using two branches that both have a C member 'a_b'
{ 'struct': 'UserDefA',
'data': { 'boolean': 'bool' } }
'data': { 'boolean': 'bool', '*a_b': 'int' } }

{ 'struct': 'UserDefB',
'data': { 'intb': 'int' } }
'data': { 'intb': 'int', '*a-b': 'bool' } }

{ 'union': 'UserDefFlatUnion',
'base': 'UserDefUnionBase', # intentional forward reference
Expand Down
2 changes: 2 additions & 0 deletions tests/qapi-schema/qapi-schema-test.out
Expand Up @@ -71,13 +71,15 @@ enum QEnumTwo ['value1', 'value2']
prefix QENUM_TWO
object UserDefA
member boolean: bool optional=False
member a_b: int optional=True
alternate UserDefAlternate
case uda: UserDefA
case s: str
case i: int
enum UserDefAlternateKind ['uda', 's', 'i']
object UserDefB
member intb: int optional=False
member a-b: bool optional=True
object UserDefC
member string1: str optional=False
member string2: str optional=False
Expand Down
Empty file.
1 change: 1 addition & 0 deletions tests/qapi-schema/struct-base-clash-base.exit
@@ -0,0 +1 @@
0
9 changes: 9 additions & 0 deletions tests/qapi-schema/struct-base-clash-base.json
@@ -0,0 +1,9 @@
# Struct member 'base'
# FIXME: this parses, but then fails to compile due to a duplicate 'base'
# (one explicit in QMP, the other used to box the base class members).
# We should either reject the collision at parse time, or change the
# generated struct to allow this to compile.
{ 'struct': 'Base', 'data': {} }
{ 'struct': 'Sub',
'base': 'Base',
'data': { 'base': 'str' } }
5 changes: 5 additions & 0 deletions tests/qapi-schema/struct-base-clash-base.out
@@ -0,0 +1,5 @@
object :empty
object Base
object Sub
base Base
member base: str optional=False
2 changes: 1 addition & 1 deletion tests/qapi-schema/struct-base-clash-deep.err
@@ -1 +1 @@
tests/qapi-schema/struct-base-clash-deep.json:7: Member name 'name' clashes with base 'Base'
tests/qapi-schema/struct-base-clash-deep.json:10: Member name 'name' clashes with base 'Base'
5 changes: 4 additions & 1 deletion tests/qapi-schema/struct-base-clash-deep.json
@@ -1,4 +1,7 @@
# we check for no duplicate keys with indirect base
# Reject attempts to duplicate QMP members
# Here, 'name' would have to appear twice on the wire, locally and
# indirectly for the grandparent base; the collision doesn't care that
# one instance is optional.
{ 'struct': 'Base',
'data': { 'name': 'str' } }
{ 'struct': 'Mid',
Expand Down
2 changes: 1 addition & 1 deletion tests/qapi-schema/struct-base-clash.err
@@ -1 +1 @@
tests/qapi-schema/struct-base-clash.json:4: Member name 'name' clashes with base 'Base'
tests/qapi-schema/struct-base-clash.json:5: Member name 'name' clashes with base 'Base'
3 changes: 2 additions & 1 deletion tests/qapi-schema/struct-base-clash.json
@@ -1,4 +1,5 @@
# we check for no duplicate keys with base
# Reject attempts to duplicate QMP members
# Here, 'name' would have to appear twice on the wire, locally and for base.
{ 'struct': 'Base',
'data': { 'name': 'str' } }
{ 'struct': 'Sub',
Expand Down
1 change: 1 addition & 0 deletions tests/qapi-schema/union-clash-branches.err
@@ -0,0 +1 @@
tests/qapi-schema/union-clash-branches.json:4: Union 'TestUnion' member 'a_b' clashes with 'a-b'
1 change: 1 addition & 0 deletions tests/qapi-schema/union-clash-branches.exit
@@ -0,0 +1 @@
1
5 changes: 5 additions & 0 deletions tests/qapi-schema/union-clash-branches.json
@@ -0,0 +1,5 @@
# Union branch name collision
# Reject a union that would result in a collision in generated C names (this
# would try to generate two enum values 'TEST_UNION_KIND_A_B').
{ 'union': 'TestUnion',
'data': { 'a-b': 'int', 'a_b': 'str' } }
Empty file.
Empty file.
1 change: 1 addition & 0 deletions tests/qapi-schema/union-clash-data.exit
@@ -0,0 +1 @@
0
7 changes: 7 additions & 0 deletions tests/qapi-schema/union-clash-data.json
@@ -0,0 +1,7 @@
# Union branch 'data'
# FIXME: this parses, but then fails to compile due to a duplicate 'data'
# (one from the branch name, another as a filler to avoid an empty union).
# we should either detect the collision at parse time, or change the
# generated struct to allow this to compile.
{ 'union': 'TestUnion',
'data': { 'data': 'int' } }
6 changes: 6 additions & 0 deletions tests/qapi-schema/union-clash-data.out
@@ -0,0 +1,6 @@
object :empty
object :obj-int-wrapper
member data: int optional=False
object TestUnion
case data: :obj-int-wrapper
enum TestUnionKind ['data']
16 changes: 16 additions & 0 deletions tests/qapi-schema/union-clash-type.err
@@ -0,0 +1,16 @@
Traceback (most recent call last):
File "tests/qapi-schema/test-qapi.py", line 55, in <module>
schema = QAPISchema(sys.argv[1])
File "scripts/qapi.py", line 1116, in __init__
self.check()
File "scripts/qapi.py", line 1299, in check
ent.check(self)
File "scripts/qapi.py", line 962, in check
self.variants.check(schema, members, seen)
File "scripts/qapi.py", line 1024, in check
v.check(schema, self.tag_member.type, vseen)
File "scripts/qapi.py", line 1032, in check
QAPISchemaObjectTypeMember.check(self, schema, [], seen)
File "scripts/qapi.py", line 994, in check
assert self.name not in seen
AssertionError
1 change: 1 addition & 0 deletions tests/qapi-schema/union-clash-type.exit
@@ -0,0 +1 @@
1
10 changes: 10 additions & 0 deletions tests/qapi-schema/union-clash-type.json
@@ -0,0 +1,10 @@
# Union branch 'type'
# FIXME: this triggers an assertion failure. But even with that fixed,
# we would have a clash in generated C, between the simple union's
# implicit tag member 'kind' and the branch name 'kind' within the
# union. We should either reject this, or munge the generated C to let
# it compile.
# TODO: Even when the generated C is switched to use 'type' rather than
# 'kind', to match the QMP spelling, the collision should still be detected.
{ 'union': 'TestUnion',
'data': { 'kind': 'int', 'type': 'str' } }
Empty file.

0 comments on commit d220fbc

Please sign in to comment.