2 * testutilsqemuschema.c: helper functions for QEMU QAPI schema testing
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see
16 * <http://www.gnu.org/licenses/>.
19 #include "testutils.h"
20 #include "testutilsqemu.h"
21 #include "testutilsqemuschema.h"
22 #include "qemu/qemu_qapi.h"
25 testQEMUSchemaValidateRecurse(virJSONValuePtr obj
,
27 virHashTablePtr schema
,
31 testQEMUSchemaValidateBuiltin(virJSONValuePtr obj
,
35 const char *t
= virJSONValueObjectGetString(root
, "json-type");
40 if (STREQ_NULLABLE(t
, "value")) {
46 switch (virJSONValueGetType(obj
)) {
47 case VIR_JSON_TYPE_STRING
:
48 if (STRNEQ_NULLABLE(t
, "string"))
50 s
= virJSONValueGetString(obj
);
53 case VIR_JSON_TYPE_NUMBER
:
54 if (STRNEQ_NULLABLE(t
, "int") &&
55 STRNEQ_NULLABLE(t
, "number"))
60 case VIR_JSON_TYPE_BOOLEAN
:
61 if (STRNEQ_NULLABLE(t
, "boolean"))
63 virJSONValueGetBoolean(obj
, &b
);
70 case VIR_JSON_TYPE_NULL
:
71 if (STRNEQ_NULLABLE(t
, "null"))
75 case VIR_JSON_TYPE_OBJECT
:
76 case VIR_JSON_TYPE_ARRAY
:
84 virBufferAsprintf(debug
, "'%s': OK", s
);
86 virBufferAsprintf(debug
, "ERROR: expected type '%s', actual type %d",
87 t
, virJSONValueGetType(obj
));
91 struct testQEMUSchemaValidateObjectMemberData
{
92 virJSONValuePtr rootmembers
;
93 virHashTablePtr schema
;
95 bool missingMandatory
;
99 static virJSONValuePtr
100 testQEMUSchemaStealObjectMemberByName(const char *name
,
101 virJSONValuePtr members
)
103 virJSONValuePtr member
;
104 virJSONValuePtr ret
= NULL
;
107 for (i
= 0; i
< virJSONValueArraySize(members
); i
++) {
108 member
= virJSONValueArrayGet(members
, i
);
110 if (STREQ_NULLABLE(name
, virJSONValueObjectGetString(member
, "name"))) {
111 ret
= virJSONValueArraySteal(members
, i
);
121 testQEMUSchemaValidateObjectMember(const char *key
,
122 virJSONValuePtr value
,
125 struct testQEMUSchemaValidateObjectMemberData
*data
= opaque
;
126 virJSONValuePtr keymember
= NULL
;
128 virJSONValuePtr keyschema
= NULL
;
131 virBufferStrcat(data
->debug
, key
, ": ", NULL
);
133 /* lookup 'member' entry for key */
134 if (!(keymember
= testQEMUSchemaStealObjectMemberByName(key
, data
->rootmembers
))) {
135 virBufferAddLit(data
->debug
, "ERROR: attribute not in schema");
139 /* lookup schema entry for keytype */
140 if (!(keytype
= virJSONValueObjectGetString(keymember
, "type")) ||
141 !(keyschema
= virHashLookup(data
->schema
, keytype
))) {
142 virBufferAsprintf(data
->debug
, "ERROR: can't find schema for type '%s'",
149 ret
= testQEMUSchemaValidateRecurse(value
, keyschema
, data
->schema
,
153 virBufferAddLit(data
->debug
, "\n");
154 virJSONValueFree(keymember
);
160 testQEMUSchemaValidateObjectMergeVariantMember(size_t pos ATTRIBUTE_UNUSED
,
161 virJSONValuePtr item
,
164 virJSONValuePtr array
= opaque
;
165 virJSONValuePtr copy
;
167 if (!(copy
= virJSONValueCopy(item
)))
170 if (virJSONValueArrayAppend(array
, copy
) < 0)
178 * testQEMUSchemaValidateObjectMergeVariant:
180 * Merges schema of variant @variantname in @root into @root and removes the
181 * 'variants' array from @root.
184 testQEMUSchemaValidateObjectMergeVariant(virJSONValuePtr root
,
185 const char *variantfield
,
186 const char *variantname
,
187 virHashTablePtr schema
,
191 virJSONValuePtr variants
= NULL
;
192 virJSONValuePtr variant
;
193 virJSONValuePtr variantschema
;
194 virJSONValuePtr variantschemamembers
;
195 virJSONValuePtr rootmembers
;
196 const char *varianttype
= NULL
;
199 if (!(variants
= virJSONValueObjectStealArray(root
, "variants"))) {
200 virBufferAddLit(debug
, "ERROR: missing 'variants' in schema\n");
204 for (i
= 0; i
< virJSONValueArraySize(variants
); i
++) {
205 variant
= virJSONValueArrayGet(variants
, i
);
207 if (STREQ_NULLABLE(variantname
,
208 virJSONValueObjectGetString(variant
, "case"))) {
209 varianttype
= virJSONValueObjectGetString(variant
, "type");
215 virBufferAsprintf(debug
, "ERROR: variant '%s' for discriminator '%s' not found\n",
216 variantname
, variantfield
);
221 if (!(variantschema
= virHashLookup(schema
, varianttype
)) ||
222 !(variantschemamembers
= virJSONValueObjectGetArray(variantschema
, "members"))) {
223 virBufferAsprintf(debug
,
224 "ERROR: missing schema or schema members for variant '%s'(%s)\n",
225 variantname
, varianttype
);
230 rootmembers
= virJSONValueObjectGetArray(root
, "members");
232 if (virJSONValueArrayForeachSteal(variantschemamembers
,
233 testQEMUSchemaValidateObjectMergeVariantMember
,
242 virJSONValueFree(variants
);
248 testQEMUSchemaValidateObjectMandatoryMember(size_t pos ATTRIBUTE_UNUSED
,
249 virJSONValuePtr item
,
250 void *opaque ATTRIBUTE_UNUSED
)
252 struct testQEMUSchemaValidateObjectMemberData
*data
= opaque
;
254 if (virJSONValueObjectHasKey(item
, "default") != 1) {
255 virBufferAsprintf(data
->debug
, "ERROR: missing mandatory attribute '%s'\n",
256 NULLSTR(virJSONValueObjectGetString(item
, "name")));
257 data
->missingMandatory
= true;
265 testQEMUSchemaValidateObject(virJSONValuePtr obj
,
266 virJSONValuePtr root
,
267 virHashTablePtr schema
,
270 struct testQEMUSchemaValidateObjectMemberData data
= { NULL
, schema
,
272 virJSONValuePtr localroot
= NULL
;
273 const char *variantfield
;
274 const char *variantname
;
277 if (virJSONValueGetType(obj
) != VIR_JSON_TYPE_OBJECT
) {
278 virBufferAddLit(debug
, "ERROR: not an object");
282 virBufferAddLit(debug
, "{\n");
283 virBufferAdjustIndent(debug
, 3);
286 if (!(localroot
= virJSONValueCopy(root
))) {
292 if ((variantfield
= virJSONValueObjectGetString(localroot
, "tag"))) {
293 if (!(variantname
= virJSONValueObjectGetString(obj
, variantfield
))) {
294 virBufferAsprintf(debug
, "ERROR: missing variant discriminator attribute '%s'\n",
299 if (testQEMUSchemaValidateObjectMergeVariant(localroot
, variantfield
,
306 /* validate members */
307 data
.rootmembers
= virJSONValueObjectGetArray(localroot
, "members");
308 if (virJSONValueObjectForeachKeyValue(obj
,
309 testQEMUSchemaValidateObjectMember
,
313 /* check missing mandatory values */
314 if (virJSONValueArrayForeachSteal(data
.rootmembers
,
315 testQEMUSchemaValidateObjectMandatoryMember
,
321 if (data
.missingMandatory
)
324 virBufferAdjustIndent(debug
, -3);
325 virBufferAddLit(debug
, "} OK");
329 virJSONValueFree(localroot
);
335 testQEMUSchemaValidateEnum(virJSONValuePtr obj
,
336 virJSONValuePtr root
,
340 virJSONValuePtr values
= NULL
;
341 virJSONValuePtr value
;
344 if (virJSONValueGetType(obj
) != VIR_JSON_TYPE_STRING
) {
345 virBufferAddLit(debug
, "ERROR: not a string");
349 objstr
= virJSONValueGetString(obj
);
351 if (!(values
= virJSONValueObjectGetArray(root
, "values"))) {
352 virBufferAsprintf(debug
, "ERROR: missing enum values in schema '%s'",
353 NULLSTR(virJSONValueObjectGetString(root
, "name")));
357 for (i
= 0; i
< virJSONValueArraySize(values
); i
++) {
358 value
= virJSONValueArrayGet(values
, i
);
360 if (STREQ_NULLABLE(objstr
, virJSONValueGetString(value
))) {
361 virBufferAsprintf(debug
, "'%s' OK", NULLSTR(objstr
));
366 virBufferAsprintf(debug
, "ERROR: enum value '%s' is not in schema",
373 testQEMUSchemaValidateArray(virJSONValuePtr objs
,
374 virJSONValuePtr root
,
375 virHashTablePtr schema
,
378 const char *elemtypename
= virJSONValueObjectGetString(root
, "element-type");
379 virJSONValuePtr elementschema
;
383 if (virJSONValueGetType(objs
) != VIR_JSON_TYPE_ARRAY
) {
384 virBufferAddLit(debug
, "ERROR: not an array\n");
389 !(elementschema
= virHashLookup(schema
, elemtypename
))) {
390 virBufferAsprintf(debug
, "ERROR: missing schema for array element type '%s'",
391 NULLSTR(elemtypename
));
395 virBufferAddLit(debug
, "[\n");
396 virBufferAdjustIndent(debug
, 3);
398 for (i
= 0; i
< virJSONValueArraySize(objs
); i
++) {
399 obj
= virJSONValueArrayGet(objs
, i
);
401 if (testQEMUSchemaValidateRecurse(obj
, elementschema
, schema
, debug
) < 0)
403 virBufferAddLit(debug
, ",\n");
405 virBufferAddLit(debug
, "] OK");
406 virBufferAdjustIndent(debug
, -3);
412 testQEMUSchemaValidateAlternate(virJSONValuePtr obj
,
413 virJSONValuePtr root
,
414 virHashTablePtr schema
,
417 virJSONValuePtr members
;
418 virJSONValuePtr member
;
421 const char *membertype
;
422 virJSONValuePtr memberschema
;
426 if (!(members
= virJSONValueObjectGetArray(root
, "members"))) {
427 virBufferAddLit(debug
, "ERROR: missing 'members' for alternate schema");
431 virBufferAddLit(debug
, "(\n");
432 virBufferAdjustIndent(debug
, 3);
433 indent
= virBufferGetIndent(debug
, false);
435 n
= virJSONValueArraySize(members
);
436 for (i
= 0; i
< n
; i
++) {
440 virBufferAsprintf(debug
, "(alternate %zu/%zu)\n", i
+ 1, n
);
441 virBufferAdjustIndent(debug
, 3);
443 if (!(member
= virJSONValueArrayGet(members
, i
)) ||
444 !(membertype
= virJSONValueObjectGetString(member
, "type")) ||
445 !(memberschema
= virHashLookup(schema
, membertype
))) {
446 virBufferAsprintf(debug
, "ERROR: missing schema for alternate type '%s'",
447 NULLSTR(membertype
));
451 rc
= testQEMUSchemaValidateRecurse(obj
, memberschema
, schema
, debug
);
453 virBufferAddLit(debug
, "\n");
454 virBufferSetIndent(debug
, indent
);
455 virBufferAsprintf(debug
, "(/alternate %zu/%zu)\n", i
+ 1, n
);
458 virBufferAdjustIndent(debug
, -3);
459 virBufferAddLit(debug
, ") OK");
464 virBufferAddLit(debug
, "ERROR: no alternate type was matched");
470 testQEMUSchemaValidateRecurse(virJSONValuePtr obj
,
471 virJSONValuePtr root
,
472 virHashTablePtr schema
,
475 const char *n
= virJSONValueObjectGetString(root
, "name");
476 const char *t
= virJSONValueObjectGetString(root
, "meta-type");
478 if (STREQ_NULLABLE(t
, "builtin")) {
479 return testQEMUSchemaValidateBuiltin(obj
, root
, debug
);
480 } else if (STREQ_NULLABLE(t
, "object")) {
481 return testQEMUSchemaValidateObject(obj
, root
, schema
, debug
);
482 } else if (STREQ_NULLABLE(t
, "enum")) {
483 return testQEMUSchemaValidateEnum(obj
, root
, debug
);
484 } else if (STREQ_NULLABLE(t
, "array")) {
485 return testQEMUSchemaValidateArray(obj
, root
, schema
, debug
);
486 } else if (STREQ_NULLABLE(t
, "alternate")) {
487 return testQEMUSchemaValidateAlternate(obj
, root
, schema
, debug
);
490 virBufferAsprintf(debug
,
491 "qapi schema meta-type '%s' of type '%s' not handled\n",
492 NULLSTR(t
), NULLSTR(n
));
498 * testQEMUSchemaValidate:
499 * @obj: object to validate
500 * @root: schema entry to start from
501 * @schema: hash table containing schema entries
502 * @debug: a virBuffer which will be filled with debug information if provided
504 * Validates whether @obj conforms to the QAPI schema passed in via @schema,
505 * starting from the node @root. Returns 0, if @obj matches @schema, -1 if it
506 * does not and -2 if there is a problem with the schema or with internals.
508 * @debug is filled with information regarding the validation process
511 testQEMUSchemaValidate(virJSONValuePtr obj
,
512 virJSONValuePtr root
,
513 virHashTablePtr schema
,
516 return testQEMUSchemaValidateRecurse(obj
, root
, schema
, debug
);
521 * testQEMUSchemaGetLatest:
523 * Returns the schema data as the qemu monitor would reply from the latest
524 * replies file used for qemucapabilitiestest for the x86_64 architecture.
527 testQEMUSchemaGetLatest(void)
529 char *capsLatestFile
= NULL
;
530 char *capsLatest
= NULL
;
533 virJSONValuePtr reply
= NULL
;
534 virJSONValuePtr schema
= NULL
;
536 if (!(capsLatestFile
= testQemuGetLatestCapsForArch("x86_64", "replies"))) {
537 VIR_TEST_VERBOSE("failed to find latest caps replies\n");
541 VIR_TEST_DEBUG("replies file: '%s'\n", capsLatestFile
);
543 if (virTestLoadFile(capsLatestFile
, &capsLatest
) < 0)
546 if (!(schemaReply
= strstr(capsLatest
, "\"execute\": \"query-qmp-schema\"")) ||
547 !(schemaReply
= strstr(schemaReply
, "\n\n")) ||
548 !(end
= strstr(schemaReply
+ 2, "\n\n"))) {
549 VIR_TEST_VERBOSE("failed to find reply to 'query-qmp-schema' in '%s'\n",
557 if (!(reply
= virJSONValueFromString(schemaReply
))) {
558 VIR_TEST_VERBOSE("failed to parse 'query-qmp-schema' reply from '%s'\n",
563 if (!(schema
= virJSONValueObjectStealArray(reply
, "return"))) {
564 VIR_TEST_VERBOSE("missing qapi schema data in reply in '%s'\n",
570 VIR_FREE(capsLatestFile
);
571 VIR_FREE(capsLatest
);
572 virJSONValueFree(reply
);
578 testQEMUSchemaLoad(void)
580 virJSONValuePtr schema
;
582 if (!(schema
= testQEMUSchemaGetLatest()))
585 return virQEMUQAPISchemaConvert(schema
);