52 changes: 42 additions & 10 deletions tests/qapi-schema/qapi-schema-test.json
Expand Up @@ -114,6 +114,38 @@
{ 'struct': 'UserDefC',
'data': { 'string1': 'str', 'string2': 'str' } }

# this tests that unions can contain other unions in their branches
{ 'enum': 'TestUnionEnum',
'data': [ 'value-a', 'value-b' ] }

{ 'enum': 'TestUnionEnumA',
'data': [ 'value-a1', 'value-a2' ] }

{ 'struct': 'TestUnionTypeA1',
'data': { 'integer': 'int',
'name': 'str'} }

{ 'struct': 'TestUnionTypeA2',
'data': { 'integer': 'int',
'size': 'int' } }

{ 'union': 'TestUnionTypeA',
'base': { 'type-a': 'TestUnionEnumA' },
'discriminator': 'type-a',
'data': { 'value-a1': 'TestUnionTypeA1',
'value-a2': 'TestUnionTypeA2' } }

{ 'struct': 'TestUnionTypeB',
'data': { 'integer': 'int',
'onoff': 'bool' } }

{ 'union': 'TestUnionInUnion',
'base': { 'type': 'TestUnionEnum' },
'discriminator': 'type',
'data': { 'value-a': 'TestUnionTypeA',
'value-b': 'TestUnionTypeB' } }


# for testing use of 'number' within alternates
{ 'alternate': 'AltEnumBool', 'data': { 'e': 'EnumOne', 'b': 'bool' } }
{ 'alternate': 'AltEnumNum', 'data': { 'e': 'EnumOne', 'n': 'number' } }
Expand Down Expand Up @@ -220,18 +252,19 @@

{ 'struct': 'TestIfStruct',
'data': { 'foo': 'int',
'bar': { 'type': 'int', 'if': 'TEST_IF_STRUCT_BAR'} },
'bar': { 'type': 'int', 'if': 'TEST_IF_STRUCT_MEMBER'},
'*baz': { 'type': 'str', 'if': 'TEST_IF_STRUCT_MEMBER'} },
'if': 'TEST_IF_STRUCT' }

{ 'enum': 'TestIfEnum',
'data': [ 'foo', { 'name' : 'bar', 'if': 'TEST_IF_ENUM_BAR' } ],
'if': 'TEST_IF_ENUM' }
'data': [ 'foo', { 'name' : 'bar', 'if': 'TEST_IF_ENUM_MEMBER' } ],
'if': 'TEST_IF_UNION' }

{ 'union': 'TestIfUnion',
'base': { 'type': 'TestIfEnum' },
'discriminator': 'type',
'data': { 'foo': 'TestStruct',
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ENUM_BAR'} },
'bar': { 'type': 'UserDefZero', 'if': 'TEST_IF_ENUM_MEMBER'} },
'if': { 'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT'] } }

{ 'command': 'test-if-union-cmd',
Expand All @@ -240,25 +273,24 @@

{ 'alternate': 'TestIfAlternate',
'data': { 'foo': 'int',
'bar': { 'type': 'TestStruct', 'if': 'TEST_IF_ALT_BAR'} },
'bar': { 'type': 'TestStruct', 'if': 'TEST_IF_ALT_MEMBER'} },
'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }

{ 'command': 'test-if-alternate-cmd',
'data': { 'alt-cmd-arg': 'TestIfAlternate' },
'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }

{ 'command': 'test-if-cmd',
'data': {
'foo': 'TestIfStruct',
'bar': { 'type': 'TestIfEnum', 'if': 'TEST_IF_CMD_BAR' } },
'boxed': true,
'data': 'TestIfStruct',
'returns': 'UserDefThree',
'if': { 'all': ['TEST_IF_CMD', 'TEST_IF_STRUCT'] } }

{ 'command': 'test-cmd-return-def-three', 'returns': 'UserDefThree' }

{ 'event': 'TEST_IF_EVENT',
'data': { 'foo': 'TestIfStruct',
'bar': { 'type': ['TestIfEnum'], 'if': 'TEST_IF_EVT_BAR' } },
'boxed': true,
'data': 'TestIfStruct',
'if': { 'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT'] } }

{ 'event': 'TEST_IF_EVENT2', 'data': {},
Expand Down
61 changes: 40 additions & 21 deletions tests/qapi-schema/qapi-schema-test.out
Expand Up @@ -105,6 +105,35 @@ alternate UserDefAlternate
object UserDefC
member string1: str optional=False
member string2: str optional=False
enum TestUnionEnum
member value-a
member value-b
enum TestUnionEnumA
member value-a1
member value-a2
object TestUnionTypeA1
member integer: int optional=False
member name: str optional=False
object TestUnionTypeA2
member integer: int optional=False
member size: int optional=False
object q_obj_TestUnionTypeA-base
member type-a: TestUnionEnumA optional=False
object TestUnionTypeA
base q_obj_TestUnionTypeA-base
tag type-a
case value-a1: TestUnionTypeA1
case value-a2: TestUnionTypeA2
object TestUnionTypeB
member integer: int optional=False
member onoff: bool optional=False
object q_obj_TestUnionInUnion-base
member type: TestUnionEnum optional=False
object TestUnionInUnion
base q_obj_TestUnionInUnion-base
tag type
case value-a: TestUnionTypeA
case value-b: TestUnionTypeB
alternate AltEnumBool
tag type
case e: EnumOne
Expand Down Expand Up @@ -246,13 +275,15 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> None
object TestIfStruct
member foo: int optional=False
member bar: int optional=False
if TEST_IF_STRUCT_BAR
if TEST_IF_STRUCT_MEMBER
member baz: str optional=True
if TEST_IF_STRUCT_MEMBER
if TEST_IF_STRUCT
enum TestIfEnum
member foo
member bar
if TEST_IF_ENUM_BAR
if TEST_IF_ENUM
if TEST_IF_ENUM_MEMBER
if TEST_IF_UNION
object q_obj_TestIfUnion-base
member type: TestIfEnum optional=False
if {'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT']}
Expand All @@ -261,7 +292,7 @@ object TestIfUnion
tag type
case foo: TestStruct
case bar: UserDefZero
if TEST_IF_ENUM_BAR
if TEST_IF_ENUM_MEMBER
if {'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT']}
object q_obj_test-if-union-cmd-arg
member union-cmd-arg: TestIfUnion optional=False
Expand All @@ -273,33 +304,21 @@ alternate TestIfAlternate
tag type
case foo: int
case bar: TestStruct
if TEST_IF_ALT_BAR
if TEST_IF_ALT_MEMBER
if {'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT']}
object q_obj_test-if-alternate-cmd-arg
member alt-cmd-arg: TestIfAlternate optional=False
if {'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT']}
command test-if-alternate-cmd q_obj_test-if-alternate-cmd-arg -> None
gen=True success_response=True boxed=False oob=False preconfig=False
if {'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT']}
object q_obj_test-if-cmd-arg
member foo: TestIfStruct optional=False
member bar: TestIfEnum optional=False
if TEST_IF_CMD_BAR
if {'all': ['TEST_IF_CMD', 'TEST_IF_STRUCT']}
command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
gen=True success_response=True boxed=False oob=False preconfig=False
command test-if-cmd TestIfStruct -> UserDefThree
gen=True success_response=True boxed=True oob=False preconfig=False
if {'all': ['TEST_IF_CMD', 'TEST_IF_STRUCT']}
command test-cmd-return-def-three None -> UserDefThree
gen=True success_response=True boxed=False oob=False preconfig=False
array TestIfEnumList TestIfEnum
if TEST_IF_ENUM
object q_obj_TEST_IF_EVENT-arg
member foo: TestIfStruct optional=False
member bar: TestIfEnumList optional=False
if TEST_IF_EVT_BAR
if {'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT']}
event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
boxed=False
event TEST_IF_EVENT TestIfStruct
boxed=True
if {'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT']}
event TEST_IF_EVENT2 None
boxed=False
Expand Down
2 changes: 1 addition & 1 deletion tests/qapi-schema/returns-dict.err
@@ -1,2 +1,2 @@
returns-dict.json: In command 'oops':
returns-dict.json:2: 'returns' should be a type name
returns-dict.json:2: 'returns' should be a type name or array
2 changes: 2 additions & 0 deletions tests/qapi-schema/struct-data-typename.err
@@ -0,0 +1,2 @@
struct-data-typename.json: In struct 'Stru2':
struct-data-typename.json:2: 'data' should be an object or type name
2 changes: 2 additions & 0 deletions tests/qapi-schema/struct-data-typename.json
@@ -0,0 +1,2 @@
{ 'struct': 'Stru1', 'data': {} }
{ 'struct': 'Stru2', 'data': 'Stru1' }
Empty file.
2 changes: 1 addition & 1 deletion tests/qapi-schema/struct-member-invalid.err
@@ -1,2 +1,2 @@
struct-member-invalid.json: In struct 'Foo':
struct-member-invalid.json:1: 'data' member 'a' should be a type name
struct-member-invalid.json:1: 'data' member 'a' should be a type name or array
1 change: 1 addition & 0 deletions tests/qapi-schema/test-qapi.py
Expand Up @@ -206,6 +206,7 @@ def main(argv):
parser.add_argument('-d', '--dir', action='store', default='',
help="directory containing tests")
parser.add_argument('-u', '--update', action='store_true',
default='QAPI_TEST_UPDATE' in os.environ,
help="update expected test results")
parser.add_argument('tests', nargs='*', metavar='TEST', action='store')
args = parser.parse_args()
Expand Down
2 changes: 1 addition & 1 deletion tests/qapi-schema/union-array-branch.err
@@ -1,2 +1,2 @@
union-array-branch.json: In union 'TestUnion':
union-array-branch.json:8: 'data' member 'value1' cannot be an array
union-array-branch.json:8: 'data' member 'value1' should be a type name
2 changes: 1 addition & 1 deletion tests/qapi-schema/union-invalid-discriminator.err
@@ -1,2 +1,2 @@
union-invalid-discriminator.json: In union 'TestUnion':
union-invalid-discriminator.json:10: discriminator 'enum_wrong' is not a member of 'base'
union-invalid-discriminator.json:10: discriminator 'type_tag' is not a member of 'base'
4 changes: 2 additions & 2 deletions tests/qapi-schema/union-invalid-discriminator.json
Expand Up @@ -8,7 +8,7 @@
'data': { 'integer': 'int' } }

{ 'union': 'TestUnion',
'base': { 'enum1': 'TestEnum' },
'discriminator': 'enum_wrong',
'base': { 'type-tag': 'TestEnum' },
'discriminator': 'type_tag',
'data': { 'value1': 'TestTypeA',
'value2': 'TestTypeB' } }
2 changes: 2 additions & 0 deletions tests/qapi-schema/union-invalid-union-subfield.err
@@ -0,0 +1,2 @@
union-invalid-union-subfield.json: In union 'TestUnion':
union-invalid-union-subfield.json:25: member 'teeth' of type 'TestTypeFish' collides with base member 'teeth'
30 changes: 30 additions & 0 deletions tests/qapi-schema/union-invalid-union-subfield.json
@@ -0,0 +1,30 @@
# Clash between common member and union variant's variant member
# Base's member 'teeth' clashes with TestTypeFish's

{ 'enum': 'TestEnum',
'data': [ 'animals', 'plants' ] }

{ 'enum': 'TestAnimals',
'data': [ 'fish', 'birds'] }

{ 'struct': 'TestTypeFish',
'data': { 'scales': 'int', 'teeth': 'int' } }

{ 'struct': 'TestTypeBirds',
'data': { 'feathers': 'int' } }

{ 'union': 'TestTypeAnimals',
'base': { 'atype': 'TestAnimals' },
'discriminator': 'atype',
'data': { 'fish': 'TestTypeFish',
'birds': 'TestTypeBirds' } }

{ 'struct': 'TestTypePlants',
'data': { 'integer': 'int' } }

{ 'union': 'TestUnion',
'base': { 'type': 'TestEnum',
'teeth': 'int' },
'discriminator': 'type',
'data': { 'animals': 'TestTypeAnimals',
'plants': 'TestTypePlants' } }
Empty file.
2 changes: 2 additions & 0 deletions tests/qapi-schema/union-invalid-union-subtype.err
@@ -0,0 +1,2 @@
union-invalid-union-subtype.json: In union 'TestUnion':
union-invalid-union-subtype.json:25: base member 'type' of type 'TestTypeA' collides with base member 'type'
29 changes: 29 additions & 0 deletions tests/qapi-schema/union-invalid-union-subtype.json
@@ -0,0 +1,29 @@
# Clash between common member and union variant's common member
# Base's member 'type' clashes with TestTypeA's

{ 'enum': 'TestEnum',
'data': [ 'value-a', 'value-b' ] }

{ 'enum': 'TestEnumA',
'data': [ 'value-a1', 'value-a2' ] }

{ 'struct': 'TestTypeA1',
'data': { 'integer': 'int' } }

{ 'struct': 'TestTypeA2',
'data': { 'integer': 'int' } }

{ 'union': 'TestTypeA',
'base': { 'type': 'TestEnumA' },
'discriminator': 'type',
'data': { 'value-a1': 'TestTypeA1',
'value-a2': 'TestTypeA2' } }

{ 'struct': 'TestTypeB',
'data': { 'integer': 'int' } }

{ 'union': 'TestUnion',
'base': { 'type': 'TestEnum' },
'discriminator': 'type',
'data': { 'value-a': 'TestTypeA',
'value-b': 'TestTypeB' } }
Empty file.
47 changes: 47 additions & 0 deletions tests/unit/test-qobject-input-visitor.c
Expand Up @@ -706,6 +706,51 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data,
g_assert(&base->enum1 == &tmp->enum1);
}

static void test_visitor_in_union_in_union(TestInputVisitorData *data,
const void *unused)
{
Visitor *v;
g_autoptr(TestUnionInUnion) tmp = NULL;

v = visitor_input_test_init(data,
"{ 'type': 'value-a', "
" 'type-a': 'value-a1', "
" 'integer': 2, "
" 'name': 'fish' }");

visit_type_TestUnionInUnion(v, NULL, &tmp, &error_abort);
g_assert_cmpint(tmp->type, ==, TEST_UNION_ENUM_VALUE_A);
g_assert_cmpint(tmp->u.value_a.type_a, ==, TEST_UNION_ENUMA_VALUE_A1);
g_assert_cmpint(tmp->u.value_a.u.value_a1.integer, ==, 2);
g_assert_cmpint(strcmp(tmp->u.value_a.u.value_a1.name, "fish"), ==, 0);

qapi_free_TestUnionInUnion(tmp);

v = visitor_input_test_init(data,
"{ 'type': 'value-a', "
" 'type-a': 'value-a2', "
" 'integer': 1729, "
" 'size': 87539319 }");

visit_type_TestUnionInUnion(v, NULL, &tmp, &error_abort);
g_assert_cmpint(tmp->type, ==, TEST_UNION_ENUM_VALUE_A);
g_assert_cmpint(tmp->u.value_a.type_a, ==, TEST_UNION_ENUMA_VALUE_A2);
g_assert_cmpint(tmp->u.value_a.u.value_a2.integer, ==, 1729);
g_assert_cmpint(tmp->u.value_a.u.value_a2.size, ==, 87539319);

qapi_free_TestUnionInUnion(tmp);

v = visitor_input_test_init(data,
"{ 'type': 'value-b', "
" 'integer': 1729, "
" 'onoff': true }");

visit_type_TestUnionInUnion(v, NULL, &tmp, &error_abort);
g_assert_cmpint(tmp->type, ==, TEST_UNION_ENUM_VALUE_B);
g_assert_cmpint(tmp->u.value_b.integer, ==, 1729);
g_assert_cmpint(tmp->u.value_b.onoff, ==, true);
}

static void test_visitor_in_alternate(TestInputVisitorData *data,
const void *unused)
{
Expand Down Expand Up @@ -1216,6 +1261,8 @@ int main(int argc, char **argv)
NULL, test_visitor_in_null);
input_visitor_test_add("/visitor/input/union-flat",
NULL, test_visitor_in_union_flat);
input_visitor_test_add("/visitor/input/union-in-union",
NULL, test_visitor_in_union_in_union);
input_visitor_test_add("/visitor/input/alternate",
NULL, test_visitor_in_alternate);
input_visitor_test_add("/visitor/input/errors",
Expand Down
58 changes: 58 additions & 0 deletions tests/unit/test-qobject-output-visitor.c
Expand Up @@ -352,6 +352,62 @@ static void test_visitor_out_union_flat(TestOutputVisitorData *data,
qapi_free_UserDefFlatUnion(tmp);
}

static void test_visitor_out_union_in_union(TestOutputVisitorData *data,
const void *unused)
{
QDict *qdict;

TestUnionInUnion *tmp = g_new0(TestUnionInUnion, 1);
tmp->type = TEST_UNION_ENUM_VALUE_A;
tmp->u.value_a.type_a = TEST_UNION_ENUMA_VALUE_A1;
tmp->u.value_a.u.value_a1.integer = 42;
tmp->u.value_a.u.value_a1.name = g_strdup("fish");

visit_type_TestUnionInUnion(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "value-a");
g_assert_cmpstr(qdict_get_str(qdict, "type-a"), ==, "value-a1");
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 42);
g_assert_cmpstr(qdict_get_str(qdict, "name"), ==, "fish");

qapi_free_TestUnionInUnion(tmp);


visitor_reset(data);
tmp = g_new0(TestUnionInUnion, 1);
tmp->type = TEST_UNION_ENUM_VALUE_A;
tmp->u.value_a.type_a = TEST_UNION_ENUMA_VALUE_A2;
tmp->u.value_a.u.value_a2.integer = 1729;
tmp->u.value_a.u.value_a2.size = 87539319;

visit_type_TestUnionInUnion(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "value-a");
g_assert_cmpstr(qdict_get_str(qdict, "type-a"), ==, "value-a2");
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 1729);
g_assert_cmpint(qdict_get_int(qdict, "size"), ==, 87539319);

qapi_free_TestUnionInUnion(tmp);


visitor_reset(data);
tmp = g_new0(TestUnionInUnion, 1);
tmp->type = TEST_UNION_ENUM_VALUE_B;
tmp->u.value_b.integer = 1729;
tmp->u.value_b.onoff = true;

visit_type_TestUnionInUnion(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "value-b");
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 1729);
g_assert_cmpint(qdict_get_bool(qdict, "onoff"), ==, true);

qapi_free_TestUnionInUnion(tmp);
}

static void test_visitor_out_alternate(TestOutputVisitorData *data,
const void *unused)
{
Expand Down Expand Up @@ -586,6 +642,8 @@ int main(int argc, char **argv)
&out_visitor_data, test_visitor_out_list_qapi_free);
output_visitor_test_add("/visitor/output/union-flat",
&out_visitor_data, test_visitor_out_union_flat);
output_visitor_test_add("/visitor/output/union-in-union",
&out_visitor_data, test_visitor_out_union_in_union);
output_visitor_test_add("/visitor/output/alternate",
&out_visitor_data, test_visitor_out_alternate);
output_visitor_test_add("/visitor/output/null",
Expand Down