1 /*
2 * testutilsqemuschema.c: helper functions for QEMU QAPI schema testing
3 *
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.
8 *
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.
13 *
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/>.
17 */
18 #include <config.h>
19 #include "testutils.h"
20 #include "testutilsqemu.h"
21 #include "testutilsqemuschema.h"
22 #include "qemu/qemu_qapi.h"
23
24 struct testQEMUSchemaValidateCtxt {
25 GHashTable *schema;
26 virBuffer *debug;
27 bool allowDeprecated;
28 bool allowIncomplete; /* allow members not (yet) covered by the schema */
29 };
30
31
32 static int
testQEMUSchemaValidateDeprecated(virJSONValue * root,const char * name,struct testQEMUSchemaValidateCtxt * ctxt)33 testQEMUSchemaValidateDeprecated(virJSONValue *root,
34 const char *name,
35 struct testQEMUSchemaValidateCtxt *ctxt)
36 {
37 virJSONValue *features = virJSONValueObjectGetArray(root, "features");
38 size_t nfeatures;
39 size_t i;
40
41 if (!features)
42 return 0;
43
44 nfeatures = virJSONValueArraySize(features);
45
46 for (i = 0; i < nfeatures; i++) {
47 virJSONValue *cur = virJSONValueArrayGet(features, i);
48 const char *curstr;
49
50 if (!cur ||
51 !(curstr = virJSONValueGetString(cur))) {
52 virBufferAsprintf(ctxt->debug, "ERROR: features of '%s' are malformed", name);
53 return -2;
54 }
55
56 if (STREQ(curstr, "deprecated")) {
57 if (ctxt->allowDeprecated) {
58 virBufferAsprintf(ctxt->debug, "WARNING: '%s' is deprecated", name);
59 if (virTestGetVerbose())
60 g_fprintf(stderr, "\nWARNING: '%s' is deprecated\n", name);
61 return 0;
62 } else {
63 virBufferAsprintf(ctxt->debug, "ERROR: '%s' is deprecated", name);
64 return -1;
65 }
66 }
67 }
68
69 return 0;
70 }
71
72
73 static int
74 testQEMUSchemaValidateRecurse(virJSONValue *obj,
75 virJSONValue *root,
76 struct testQEMUSchemaValidateCtxt *ctxt);
77
78 static int
testQEMUSchemaValidateBuiltin(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)79 testQEMUSchemaValidateBuiltin(virJSONValue *obj,
80 virJSONValue *root,
81 struct testQEMUSchemaValidateCtxt *ctxt)
82 {
83 const char *t = virJSONValueObjectGetString(root, "json-type");
84 const char *s = NULL;
85 bool b = false;
86 int ret = -1;
87
88 if (STREQ_NULLABLE(t, "value")) {
89 s = "{any}";
90 ret = 0;
91 goto cleanup;
92 }
93
94 switch (virJSONValueGetType(obj)) {
95 case VIR_JSON_TYPE_STRING:
96 if (STRNEQ_NULLABLE(t, "string"))
97 goto cleanup;
98 s = virJSONValueGetString(obj);
99 break;
100
101 case VIR_JSON_TYPE_NUMBER:
102 if (STRNEQ_NULLABLE(t, "int") &&
103 STRNEQ_NULLABLE(t, "number"))
104 goto cleanup;
105 s = "{number}";
106 break;
107
108 case VIR_JSON_TYPE_BOOLEAN:
109 if (STRNEQ_NULLABLE(t, "boolean"))
110 goto cleanup;
111 virJSONValueGetBoolean(obj, &b);
112 if (b)
113 s = "true";
114 else
115 s = "false";
116 break;
117
118 case VIR_JSON_TYPE_NULL:
119 if (STRNEQ_NULLABLE(t, "null"))
120 goto cleanup;
121 break;
122
123 case VIR_JSON_TYPE_OBJECT:
124 case VIR_JSON_TYPE_ARRAY:
125 goto cleanup;
126 }
127
128 ret = 0;
129
130 cleanup:
131 if (ret == 0)
132 virBufferAsprintf(ctxt->debug, "'%s': OK", s);
133 else
134 virBufferAsprintf(ctxt->debug, "ERROR: expected type '%s', actual type %d",
135 t, virJSONValueGetType(obj));
136 return ret;
137 }
138
139 struct testQEMUSchemaValidateObjectMemberData {
140 virJSONValue *rootmembers;
141 struct testQEMUSchemaValidateCtxt *ctxt;
142 bool missingMandatory;
143 };
144
145
146 static virJSONValue *
testQEMUSchemaStealObjectMemberByName(const char * name,virJSONValue * members)147 testQEMUSchemaStealObjectMemberByName(const char *name,
148 virJSONValue *members)
149 {
150 virJSONValue *member;
151 virJSONValue *ret = NULL;
152 size_t i;
153
154 for (i = 0; i < virJSONValueArraySize(members); i++) {
155 member = virJSONValueArrayGet(members, i);
156
157 if (STREQ_NULLABLE(name, virJSONValueObjectGetString(member, "name"))) {
158 ret = virJSONValueArraySteal(members, i);
159 break;
160 }
161 }
162
163 return ret;
164 }
165
166
167 static int
testQEMUSchemaValidateObjectMember(const char * key,virJSONValue * value,void * opaque)168 testQEMUSchemaValidateObjectMember(const char *key,
169 virJSONValue *value,
170 void *opaque)
171 {
172 struct testQEMUSchemaValidateObjectMemberData *data = opaque;
173 g_autoptr(virJSONValue) keymember = NULL;
174 const char *keytype;
175 virJSONValue *keyschema = NULL;
176 int rc;
177
178 virBufferStrcat(data->ctxt->debug, key, ": ", NULL);
179
180 /* lookup 'member' entry for key */
181 if (!(keymember = testQEMUSchemaStealObjectMemberByName(key, data->rootmembers))) {
182 if (data->ctxt->allowIncomplete) {
183 virBufferAddLit(data->ctxt->debug, " schema missing - OK(waived)\n");
184 return 0;
185 }
186 virBufferAddLit(data->ctxt->debug, "ERROR: attribute not in schema\n");
187 return -1;
188 }
189
190 /* lookup schema entry for keytype */
191 if (!(keytype = virJSONValueObjectGetString(keymember, "type")) ||
192 !(keyschema = virHashLookup(data->ctxt->schema, keytype))) {
193 virBufferAsprintf(data->ctxt->debug, "ERROR: can't find schema for type '%s'\n",
194 NULLSTR(keytype));
195 return -2;
196 }
197
198 /* recurse */
199 rc = testQEMUSchemaValidateRecurse(value, keyschema, data->ctxt);
200
201 virBufferAddLit(data->ctxt->debug, "\n");
202 return rc;
203 }
204
205
206 static int
testQEMUSchemaValidateObjectMergeVariantMember(size_t pos G_GNUC_UNUSED,virJSONValue * item,void * opaque)207 testQEMUSchemaValidateObjectMergeVariantMember(size_t pos G_GNUC_UNUSED,
208 virJSONValue *item,
209 void *opaque)
210 {
211 virJSONValue *array = opaque;
212 g_autoptr(virJSONValue) copy = NULL;
213
214 if (!(copy = virJSONValueCopy(item)))
215 return -1;
216
217 if (virJSONValueArrayAppend(array, ©) < 0)
218 return -1;
219
220 return 1;
221 }
222
223
224 /**
225 * testQEMUSchemaValidateObjectMergeVariant:
226 *
227 * Merges schema of variant @variantname in @root into @root and removes the
228 * 'variants' array from @root.
229 */
230 static int
testQEMUSchemaValidateObjectMergeVariant(virJSONValue * root,const char * variantfield,const char * variantname,struct testQEMUSchemaValidateCtxt * ctxt)231 testQEMUSchemaValidateObjectMergeVariant(virJSONValue *root,
232 const char *variantfield,
233 const char *variantname,
234 struct testQEMUSchemaValidateCtxt *ctxt)
235 {
236 size_t i;
237 g_autoptr(virJSONValue) variants = NULL;
238 virJSONValue *variant;
239 virJSONValue *variantschema;
240 virJSONValue *variantschemamembers;
241 virJSONValue *rootmembers;
242 const char *varianttype = NULL;
243
244 if (!(variants = virJSONValueObjectStealArray(root, "variants"))) {
245 virBufferAddLit(ctxt->debug, "ERROR: missing 'variants' in schema\n");
246 return -2;
247 }
248
249 for (i = 0; i < virJSONValueArraySize(variants); i++) {
250 variant = virJSONValueArrayGet(variants, i);
251
252 if (STREQ_NULLABLE(variantname,
253 virJSONValueObjectGetString(variant, "case"))) {
254 varianttype = virJSONValueObjectGetString(variant, "type");
255 break;
256 }
257 }
258
259 if (!varianttype) {
260 virBufferAsprintf(ctxt->debug, "ERROR: variant '%s' for discriminator '%s' not found\n",
261 variantname, variantfield);
262 return -1;
263 }
264
265 if (!(variantschema = virHashLookup(ctxt->schema, varianttype)) ||
266 !(variantschemamembers = virJSONValueObjectGetArray(variantschema, "members"))) {
267 virBufferAsprintf(ctxt->debug,
268 "ERROR: missing schema or schema members for variant '%s'(%s)\n",
269 variantname, varianttype);
270 return -2;
271 }
272
273 rootmembers = virJSONValueObjectGetArray(root, "members");
274
275 if (virJSONValueArrayForeachSteal(variantschemamembers,
276 testQEMUSchemaValidateObjectMergeVariantMember,
277 rootmembers) < 0) {
278 return -2;
279 }
280
281 return 0;
282 }
283
284
285 static int
testQEMUSchemaValidateObjectMandatoryMember(size_t pos G_GNUC_UNUSED,virJSONValue * item,void * opaque G_GNUC_UNUSED)286 testQEMUSchemaValidateObjectMandatoryMember(size_t pos G_GNUC_UNUSED,
287 virJSONValue *item,
288 void *opaque G_GNUC_UNUSED)
289 {
290 struct testQEMUSchemaValidateObjectMemberData *data = opaque;
291
292 if (virJSONValueObjectHasKey(item, "default") != 1) {
293 virBufferAsprintf(data->ctxt->debug, "ERROR: missing mandatory attribute '%s'\n",
294 NULLSTR(virJSONValueObjectGetString(item, "name")));
295 data->missingMandatory = true;
296 }
297
298 return 1;
299 }
300
301
302 static int
testQEMUSchemaValidateObject(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)303 testQEMUSchemaValidateObject(virJSONValue *obj,
304 virJSONValue *root,
305 struct testQEMUSchemaValidateCtxt *ctxt)
306 {
307 struct testQEMUSchemaValidateObjectMemberData data = { NULL, ctxt, false };
308 g_autoptr(virJSONValue) localroot = NULL;
309 const char *variantfield;
310 const char *variantname;
311
312 if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
313 virBufferAddLit(ctxt->debug, "ERROR: not an object");
314 return -1;
315 }
316
317 virBufferAddLit(ctxt->debug, "{\n");
318 virBufferAdjustIndent(ctxt->debug, 3);
319
320 /* copy schema */
321 if (!(localroot = virJSONValueCopy(root)))
322 return -2;
323
324 /* remove variant */
325 if ((variantfield = virJSONValueObjectGetString(localroot, "tag"))) {
326 if (!(variantname = virJSONValueObjectGetString(obj, variantfield))) {
327 virBufferAsprintf(ctxt->debug, "ERROR: missing variant discriminator attribute '%s'\n",
328 variantfield);
329 return -1;
330 }
331
332 if (testQEMUSchemaValidateObjectMergeVariant(localroot, variantfield,
333 variantname, ctxt) < 0)
334 return -1;
335 }
336
337
338 /* validate members */
339 data.rootmembers = virJSONValueObjectGetArray(localroot, "members");
340 if (virJSONValueObjectForeachKeyValue(obj,
341 testQEMUSchemaValidateObjectMember,
342 &data) < 0)
343 return -1;
344
345 /* check missing mandatory values */
346 if (virJSONValueArrayForeachSteal(data.rootmembers,
347 testQEMUSchemaValidateObjectMandatoryMember,
348 &data) < 0) {
349 return -2;
350 }
351
352 if (data.missingMandatory)
353 return -1;
354
355 virBufferAdjustIndent(ctxt->debug, -3);
356 virBufferAddLit(ctxt->debug, "} OK");
357 return 0;
358 }
359
360
361 static int
testQEMUSchemaValidateEnum(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)362 testQEMUSchemaValidateEnum(virJSONValue *obj,
363 virJSONValue *root,
364 struct testQEMUSchemaValidateCtxt *ctxt)
365 {
366 const char *objstr;
367 virJSONValue *values = NULL;
368 virJSONValue *members = NULL;
369 size_t i;
370
371 if (virJSONValueGetType(obj) != VIR_JSON_TYPE_STRING) {
372 virBufferAddLit(ctxt->debug, "ERROR: not a string");
373 return -1;
374 }
375
376 objstr = virJSONValueGetString(obj);
377
378 /* qemu-6.2 added a "members" array superseding "values" */
379 if ((members = virJSONValueObjectGetArray(root, "members"))) {
380 for (i = 0; i < virJSONValueArraySize(members); i++) {
381 virJSONValue *member = virJSONValueArrayGet(members, i);
382
383 if (STREQ_NULLABLE(objstr, virJSONValueObjectGetString(member, "name"))) {
384 int rc;
385
386 /* the new 'members' array allows us to check deprecations */
387 if ((rc = testQEMUSchemaValidateDeprecated(member, objstr, ctxt)) < 0)
388 return rc;
389
390 virBufferAsprintf(ctxt->debug, "'%s' OK", NULLSTR(objstr));
391 return 0;
392 }
393 }
394
395 virBufferAsprintf(ctxt->debug, "ERROR: enum value '%s' is not in schema",
396 NULLSTR(objstr));
397 return -1;
398 }
399
400 if ((values = virJSONValueObjectGetArray(root, "values"))) {
401 for (i = 0; i < virJSONValueArraySize(values); i++) {
402 virJSONValue *value = virJSONValueArrayGet(values, i);
403
404 if (STREQ_NULLABLE(objstr, virJSONValueGetString(value))) {
405 virBufferAsprintf(ctxt->debug, "'%s' OK", NULLSTR(objstr));
406 return 0;
407 }
408 }
409
410 virBufferAsprintf(ctxt->debug, "ERROR: enum value '%s' is not in schema",
411 NULLSTR(objstr));
412 return -1;
413 }
414
415 virBufferAsprintf(ctxt->debug, "ERROR: missing enum values in schema '%s'",
416 NULLSTR(virJSONValueObjectGetString(root, "name")));
417 return -2;
418 }
419
420
421 static int
testQEMUSchemaValidateArray(virJSONValue * objs,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)422 testQEMUSchemaValidateArray(virJSONValue *objs,
423 virJSONValue *root,
424 struct testQEMUSchemaValidateCtxt *ctxt)
425 {
426 const char *elemtypename = virJSONValueObjectGetString(root, "element-type");
427 virJSONValue *elementschema;
428 virJSONValue *obj;
429 size_t i;
430
431 if (virJSONValueGetType(objs) != VIR_JSON_TYPE_ARRAY) {
432 virBufferAddLit(ctxt->debug, "ERROR: not an array\n");
433 return -1;
434 }
435
436 if (!elemtypename ||
437 !(elementschema = virHashLookup(ctxt->schema, elemtypename))) {
438 virBufferAsprintf(ctxt->debug, "ERROR: missing schema for array element type '%s'",
439 NULLSTR(elemtypename));
440 return -2;
441 }
442
443 virBufferAddLit(ctxt->debug, "[\n");
444 virBufferAdjustIndent(ctxt->debug, 3);
445
446 for (i = 0; i < virJSONValueArraySize(objs); i++) {
447 obj = virJSONValueArrayGet(objs, i);
448
449 if (testQEMUSchemaValidateRecurse(obj, elementschema, ctxt) < 0)
450 return -1;
451 virBufferAddLit(ctxt->debug, ",\n");
452 }
453 virBufferAddLit(ctxt->debug, "] OK");
454 virBufferAdjustIndent(ctxt->debug, -3);
455
456 return 0;
457 }
458
459 static int
testQEMUSchemaValidateAlternate(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)460 testQEMUSchemaValidateAlternate(virJSONValue *obj,
461 virJSONValue *root,
462 struct testQEMUSchemaValidateCtxt *ctxt)
463 {
464 virJSONValue *members;
465 virJSONValue *member;
466 size_t i;
467 size_t n;
468 const char *membertype;
469 virJSONValue *memberschema;
470 int indent;
471 int rc;
472
473 if (!(members = virJSONValueObjectGetArray(root, "members"))) {
474 virBufferAddLit(ctxt->debug, "ERROR: missing 'members' for alternate schema");
475 return -2;
476 }
477
478 virBufferAddLit(ctxt->debug, "(\n");
479 virBufferAdjustIndent(ctxt->debug, 3);
480 indent = virBufferGetIndent(ctxt->debug);
481
482 n = virJSONValueArraySize(members);
483 for (i = 0; i < n; i++) {
484 membertype = NULL;
485
486 /* P != NP */
487 virBufferAsprintf(ctxt->debug, "(alternate %zu/%zu)\n", i + 1, n);
488 virBufferAdjustIndent(ctxt->debug, 3);
489
490 if (!(member = virJSONValueArrayGet(members, i)) ||
491 !(membertype = virJSONValueObjectGetString(member, "type")) ||
492 !(memberschema = virHashLookup(ctxt->schema, membertype))) {
493 virBufferAsprintf(ctxt->debug, "ERROR: missing schema for alternate type '%s'",
494 NULLSTR(membertype));
495 return -2;
496 }
497
498 rc = testQEMUSchemaValidateRecurse(obj, memberschema, ctxt);
499
500 virBufferAddLit(ctxt->debug, "\n");
501 virBufferSetIndent(ctxt->debug, indent);
502 virBufferAsprintf(ctxt->debug, "(/alternate %zu/%zu)\n", i + 1, n);
503
504 if (rc == 0) {
505 virBufferAdjustIndent(ctxt->debug, -3);
506 virBufferAddLit(ctxt->debug, ") OK");
507 return 0;
508 }
509 }
510
511 virBufferAddLit(ctxt->debug, "ERROR: no alternate type was matched");
512 return -1;
513 }
514
515
516 static int
testQEMUSchemaValidateRecurse(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)517 testQEMUSchemaValidateRecurse(virJSONValue *obj,
518 virJSONValue *root,
519 struct testQEMUSchemaValidateCtxt *ctxt)
520 {
521 const char *n = virJSONValueObjectGetString(root, "name");
522 const char *t = virJSONValueObjectGetString(root, "meta-type");
523 int rc;
524
525 if ((rc = testQEMUSchemaValidateDeprecated(root, n, ctxt)) < 0)
526 return rc;
527
528 if (STREQ_NULLABLE(t, "builtin")) {
529 return testQEMUSchemaValidateBuiltin(obj, root, ctxt);
530 } else if (STREQ_NULLABLE(t, "object")) {
531 return testQEMUSchemaValidateObject(obj, root, ctxt);
532 } else if (STREQ_NULLABLE(t, "enum")) {
533 return testQEMUSchemaValidateEnum(obj, root, ctxt);
534 } else if (STREQ_NULLABLE(t, "array")) {
535 return testQEMUSchemaValidateArray(obj, root, ctxt);
536 } else if (STREQ_NULLABLE(t, "alternate")) {
537 return testQEMUSchemaValidateAlternate(obj, root, ctxt);
538 }
539
540 virBufferAsprintf(ctxt->debug,
541 "qapi schema meta-type '%s' of type '%s' not handled\n",
542 NULLSTR(t), NULLSTR(n));
543 return -2;
544 }
545
546
547 /**
548 * testQEMUSchemaValidate:
549 * @obj: object to validate
550 * @root: schema entry to start from
551 * @schema: hash table containing schema entries
552 * @debug: a virBuffer which will be filled with debug information if provided
553 *
554 * Validates whether @obj conforms to the QAPI schema passed in via @schema,
555 * starting from the node @root. Returns 0, if @obj matches @schema, -1 if it
556 * does not and -2 if there is a problem with the schema or with internals.
557 *
558 * @debug is filled with information regarding the validation process
559 */
560 int
testQEMUSchemaValidate(virJSONValue * obj,virJSONValue * root,GHashTable * schema,bool allowDeprecated,virBuffer * debug)561 testQEMUSchemaValidate(virJSONValue *obj,
562 virJSONValue *root,
563 GHashTable *schema,
564 bool allowDeprecated,
565 virBuffer *debug)
566 {
567 struct testQEMUSchemaValidateCtxt ctxt = { .schema = schema,
568 .debug = debug,
569 .allowDeprecated = allowDeprecated };
570
571 return testQEMUSchemaValidateRecurse(obj, root, &ctxt);
572 }
573
574
575 /**
576 * testQEMUSchemaValidateCommand:
577 * @command: command to validate
578 * @arguments: arguments of @command to validate
579 * @schema: hash table containing schema entries
580 * @allowDeprecated: don't fails schema validation if @command or one of @arguments
581 * is deprecated
582 * @allowRemoved: skip validation fully if @command was not found
583 * @allowIncomplete: don't fail validation if members not covered by schema are present
584 * (for waiving commands with incomplete schema)
585 * @debug: a virBuffer which will be filled with debug information if provided
586 *
587 * Validates whether @command and its @arguments conform to the QAPI schema
588 * passed in via @schema. Returns 0, if the command and args match @schema,
589 * -1 if it does not and -2 if there is a problem with the schema or with
590 * internals.
591 *
592 * @allowRemoved should generally be used only if it's certain that there's a
593 * replacement of @command in place.
594 *
595 * @debug is filled with information regarding the validation process
596 */
597 int
testQEMUSchemaValidateCommand(const char * command,virJSONValue * arguments,GHashTable * schema,bool allowDeprecated,bool allowRemoved,bool allowIncomplete,virBuffer * debug)598 testQEMUSchemaValidateCommand(const char *command,
599 virJSONValue *arguments,
600 GHashTable *schema,
601 bool allowDeprecated,
602 bool allowRemoved,
603 bool allowIncomplete,
604 virBuffer *debug)
605 {
606 struct testQEMUSchemaValidateCtxt ctxt = { .schema = schema,
607 .debug = debug,
608 .allowDeprecated = allowDeprecated,
609 .allowIncomplete = allowIncomplete };
610 g_autofree char *schemapatharguments = g_strdup_printf("%s/arg-type", command);
611 g_autoptr(virJSONValue) emptyargs = NULL;
612 virJSONValue *schemarootcommand;
613 virJSONValue *schemarootarguments;
614 int rc;
615
616 if (virQEMUQAPISchemaPathGet(command, schema, &schemarootcommand) < 0 ||
617 !schemarootcommand) {
618 if (allowRemoved)
619 return 0;
620
621 virBufferAsprintf(debug, "ERROR: command '%s' not found in the schema", command);
622 return -1;
623 }
624
625 if ((rc = testQEMUSchemaValidateDeprecated(schemarootcommand, command, &ctxt)) < 0)
626 return rc;
627
628 if (!arguments)
629 arguments = emptyargs = virJSONValueNewObject();
630
631 if (virQEMUQAPISchemaPathGet(schemapatharguments, schema, &schemarootarguments) < 0 ||
632 !schemarootarguments) {
633 virBufferAsprintf(debug, "ERROR: failed to look up 'arg-type' of '%s'", command);
634 return -1;
635 }
636
637 return testQEMUSchemaValidateRecurse(arguments, schemarootarguments, &ctxt);
638 }
639
640
641 /**
642 * testQEMUSchemaEntryMatchTemplate:
643 *
644 * @schemaentry: a JSON object representing a 'object' node in the QAPI schema
645 * ...: a NULL terminated list of strings representing the template of properties
646 * which the QMP object needs to have.
647 *
648 * The strings have following format:
649 *
650 * "type:name"
651 * "?type:name"
652 *
653 * "type" corresponds to the 'type' property of the member to check (str, bool, any ...)
654 * "name" corresponds to the name of the member to check
655 *
656 * If the query string starts with an '?' and member 'name' may be missing.
657 *
658 * This function matches that @schemaentry has all expected members and the
659 * members have expected types. @schemaentry also must not have any unknown
660 * members.
661 */
662 int
testQEMUSchemaEntryMatchTemplate(virJSONValue * schemaentry,...)663 testQEMUSchemaEntryMatchTemplate(virJSONValue *schemaentry,
664 ...)
665 {
666 g_autoptr(virJSONValue) members = NULL;
667 va_list ap;
668 const char *next;
669 int ret = -1;
670
671 if (STRNEQ_NULLABLE(virJSONValueObjectGetString(schemaentry, "meta-type"), "object")) {
672 VIR_TEST_VERBOSE("schemaentry is not an object");
673 return -1;
674 }
675
676 if (!(members = virJSONValueCopy(virJSONValueObjectGetArray(schemaentry, "members")))) {
677 VIR_TEST_VERBOSE("failed to copy 'members'");
678 return -1;
679 }
680
681 va_start(ap, schemaentry);
682
683 /* pass 1 */
684
685 while ((next = va_arg(ap, const char *))) {
686 char modifier = *next;
687 g_autofree char *type = NULL;
688 char *name;
689 size_t i;
690 bool found = false;
691 bool optional = false;
692
693 if (!g_ascii_isalpha(modifier))
694 next++;
695
696 if (modifier == '?')
697 optional = true;
698
699 type = g_strdup(next);
700
701 if ((name = strchr(type, ':'))) {
702 *(name++) = '\0';
703 } else {
704 VIR_TEST_VERBOSE("malformed template string '%s'", next);
705 goto cleanup;
706 }
707
708 for (i = 0; i < virJSONValueArraySize(members); i++) {
709 virJSONValue *member = virJSONValueArrayGet(members, i);
710 const char *membername = virJSONValueObjectGetString(member, "name");
711 const char *membertype = virJSONValueObjectGetString(member, "type");
712
713 if (STRNEQ_NULLABLE(name, membername))
714 continue;
715
716 if (STRNEQ_NULLABLE(membertype, type)) {
717 VIR_TEST_VERBOSE("member '%s' is of unexpected type '%s' (expected '%s')",
718 NULLSTR(membername), NULLSTR(membertype), type);
719 goto cleanup;
720 }
721
722 found = true;
723 break;
724 }
725
726 if (found) {
727 virJSONValueFree(virJSONValueArraySteal(members, i));
728 } else {
729 if (!optional) {
730 VIR_TEST_VERBOSE("mandatory member '%s' not found", name);
731 goto cleanup;
732 }
733 }
734 }
735
736 /* pass 2 - check any unexpected members */
737 if (virJSONValueArraySize(members) > 0) {
738 size_t i;
739
740 for (i = 0; i < virJSONValueArraySize(members); i++) {
741 VIR_TEST_VERBOSE("unexpected member '%s'",
742 NULLSTR(virJSONValueObjectGetString(virJSONValueArrayGet(members, i), "name")));
743 }
744
745 goto cleanup;
746 }
747
748 ret = 0;
749
750 cleanup:
751 va_end(ap);
752 return ret;
753 }
754
755
756 static virJSONValue *
testQEMUSchemaLoadReplies(const char * filename)757 testQEMUSchemaLoadReplies(const char *filename)
758 {
759 g_autofree char *caps = NULL;
760 char *schemaReply;
761 char *end;
762 g_autoptr(virJSONValue) reply = NULL;
763 virJSONValue *schema = NULL;
764
765 if (virTestLoadFile(filename, &caps) < 0)
766 return NULL;
767
768 if (!(schemaReply = strstr(caps, "\"execute\": \"query-qmp-schema\"")) ||
769 !(schemaReply = strstr(schemaReply, "\n\n")) ||
770 !(end = strstr(schemaReply + 2, "\n\n"))) {
771 VIR_TEST_VERBOSE("failed to find reply to 'query-qmp-schema' in '%s'",
772 filename);
773 return NULL;
774 }
775
776 schemaReply += 2;
777 *end = '\0';
778
779 if (!(reply = virJSONValueFromString(schemaReply))) {
780 VIR_TEST_VERBOSE("failed to parse 'query-qmp-schema' reply from '%s'",
781 filename);
782 return NULL;
783 }
784
785 if (!(schema = virJSONValueObjectStealArray(reply, "return"))) {
786 VIR_TEST_VERBOSE("missing qapi schema data in reply in '%s'",
787 filename);
788 return NULL;
789 }
790
791 return schema;
792 }
793
794
795 /**
796 * testQEMUSchemaGetLatest:
797 *
798 * Returns the schema data as the qemu monitor would reply from the latest
799 * replies file used for qemucapabilitiestest for the x86_64 architecture.
800 */
801 virJSONValue *
testQEMUSchemaGetLatest(const char * arch)802 testQEMUSchemaGetLatest(const char *arch)
803 {
804 g_autofree char *capsLatestFile = NULL;
805
806 if (!(capsLatestFile = testQemuGetLatestCapsForArch(arch, "replies"))) {
807 VIR_TEST_VERBOSE("failed to find latest caps replies");
808 return NULL;
809 }
810
811 VIR_TEST_DEBUG("replies file: '%s'", capsLatestFile);
812
813 return testQEMUSchemaLoadReplies(capsLatestFile);
814 }
815
816
817 GHashTable *
testQEMUSchemaLoadLatest(const char * arch)818 testQEMUSchemaLoadLatest(const char *arch)
819 {
820 virJSONValue *schema;
821
822 if (!(schema = testQEMUSchemaGetLatest(arch)))
823 return NULL;
824
825 return virQEMUQAPISchemaConvert(schema);
826 }
827
828
829 GHashTable *
testQEMUSchemaLoad(const char * filename)830 testQEMUSchemaLoad(const char *filename)
831 {
832 virJSONValue *schema;
833
834 if (!(schema = testQEMUSchemaLoadReplies(filename)))
835 return NULL;
836
837 return virQEMUQAPISchemaConvert(schema);
838 }
839