mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-23 06:05:27 +00:00
98b6087e83
Add a function which will allow to test whether a JSON object conforms to the QAPI schema. This greatly helps when developing formatters for new JSON objects and will help make sure that the code will not break in cases which have unit tests but were actually not function-tested (mostly various disk access protocols). Signed-off-by: Peter Krempa <pkrempa@redhat.com>
537 lines
16 KiB
C
537 lines
16 KiB
C
/*
|
|
* testutilsqemuschema.c: helper functions for QEMU QAPI schema testing
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <config.h>
|
|
#include "testutils.h"
|
|
#include "testutilsqemuschema.h"
|
|
#include "qemu/qemu_qapi.h"
|
|
|
|
static int
|
|
testQEMUSchemaValidateRecurse(virJSONValuePtr obj,
|
|
virJSONValuePtr root,
|
|
virHashTablePtr schema,
|
|
virBufferPtr debug);
|
|
|
|
static int
|
|
testQEMUSchemaValidateArrayBuiltin(virJSONValuePtr obj,
|
|
virJSONValuePtr root,
|
|
virBufferPtr debug)
|
|
{
|
|
const char *t = virJSONValueObjectGetString(root, "json-type");
|
|
const char *s = NULL;
|
|
bool b = false;
|
|
int ret = -1;
|
|
|
|
if (STREQ_NULLABLE(t, "value")) {
|
|
s = "{any}";
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
switch (virJSONValueGetType(obj)) {
|
|
case VIR_JSON_TYPE_STRING:
|
|
if (STRNEQ_NULLABLE(t, "string"))
|
|
goto cleanup;
|
|
s = virJSONValueGetString(obj);
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_NUMBER:
|
|
if (STRNEQ_NULLABLE(t, "int") &&
|
|
STRNEQ_NULLABLE(t, "number"))
|
|
goto cleanup;
|
|
s = "{number}";
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_BOOLEAN:
|
|
if (STRNEQ_NULLABLE(t, "boolean"))
|
|
goto cleanup;
|
|
virJSONValueGetBoolean(obj, &b);
|
|
if (b)
|
|
s = "true";
|
|
else
|
|
s = "false";
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_NULL:
|
|
if (STRNEQ_NULLABLE(t, "null"))
|
|
goto cleanup;
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_OBJECT:
|
|
case VIR_JSON_TYPE_ARRAY:
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (ret == 0)
|
|
virBufferAsprintf(debug, "'%s': OK", s);
|
|
else
|
|
virBufferAsprintf(debug, "ERROR: expected type '%s', actual type %d",
|
|
t, virJSONValueGetType(obj));
|
|
return ret;
|
|
}
|
|
|
|
struct testQEMUSchemaValidateObjectMemberData {
|
|
virJSONValuePtr rootmembers;
|
|
virHashTablePtr schema;
|
|
virBufferPtr debug;
|
|
bool missingMandatory;
|
|
};
|
|
|
|
|
|
static virJSONValuePtr
|
|
testQEMUSchemaStealObjectMemberByName(const char *name,
|
|
virJSONValuePtr members)
|
|
{
|
|
virJSONValuePtr member;
|
|
virJSONValuePtr ret = NULL;
|
|
size_t n;
|
|
size_t i;
|
|
|
|
n = virJSONValueArraySize(members);
|
|
for (i = 0; i < n; i++) {
|
|
member = virJSONValueArrayGet(members, i);
|
|
|
|
if (STREQ_NULLABLE(name, virJSONValueObjectGetString(member, "name"))) {
|
|
ret = virJSONValueArraySteal(members, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
testQEMUSchemaValidateObjectMember(const char *key,
|
|
virJSONValuePtr value,
|
|
void *opaque)
|
|
{
|
|
struct testQEMUSchemaValidateObjectMemberData *data = opaque;
|
|
virJSONValuePtr keymember = NULL;
|
|
const char *keytype;
|
|
virJSONValuePtr keyschema = NULL;
|
|
int ret = -1;
|
|
|
|
virBufferStrcat(data->debug, key, ": ", NULL);
|
|
|
|
/* lookup 'member' entry for key */
|
|
if (!(keymember = testQEMUSchemaStealObjectMemberByName(key, data->rootmembers))) {
|
|
virBufferAddLit(data->debug, "ERROR: attribute not in schema");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* lookup schema entry for keytype */
|
|
if (!(keytype = virJSONValueObjectGetString(keymember, "type")) ||
|
|
!(keyschema = virHashLookup(data->schema, keytype))) {
|
|
virBufferAsprintf(data->debug, "ERROR: can't find schema for type '%s'",
|
|
NULLSTR(keytype));
|
|
ret = -2;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* recurse */
|
|
ret = testQEMUSchemaValidateRecurse(value, keyschema, data->schema,
|
|
data->debug);
|
|
|
|
cleanup:
|
|
virBufferAddLit(data->debug, "\n");
|
|
virJSONValueFree(keymember);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
testQEMUSchemaValidateObjectMergeVariantMember(size_t pos ATTRIBUTE_UNUSED,
|
|
virJSONValuePtr item,
|
|
void *opaque)
|
|
{
|
|
virJSONValuePtr array = opaque;
|
|
virJSONValuePtr copy;
|
|
|
|
if (!(copy = virJSONValueCopy(item)))
|
|
return -1;
|
|
|
|
if (virJSONValueArrayAppend(array, copy) < 0)
|
|
return -1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* testQEMUSchemaValidateObjectMergeVariant:
|
|
*
|
|
* Merges schema of variant @variantname in @root into @root and removes the
|
|
* 'variants' array from @root.
|
|
*/
|
|
static int
|
|
testQEMUSchemaValidateObjectMergeVariant(virJSONValuePtr root,
|
|
const char *variantfield,
|
|
const char *variantname,
|
|
virHashTablePtr schema,
|
|
virBufferPtr debug)
|
|
{
|
|
size_t n;
|
|
size_t i;
|
|
virJSONValuePtr variants = NULL;
|
|
virJSONValuePtr variant;
|
|
virJSONValuePtr variantschema;
|
|
virJSONValuePtr variantschemamembers;
|
|
virJSONValuePtr rootmembers;
|
|
const char *varianttype = NULL;
|
|
int ret = -1;
|
|
|
|
if (!(variants = virJSONValueObjectStealArray(root, "variants"))) {
|
|
virBufferAddLit(debug, "ERROR: missing 'variants' in schema\n");
|
|
return -2;
|
|
}
|
|
|
|
n = virJSONValueArraySize(variants);
|
|
for (i = 0; i < n; i++) {
|
|
variant = virJSONValueArrayGet(variants, i);
|
|
|
|
if (STREQ_NULLABLE(variantname,
|
|
virJSONValueObjectGetString(variant, "case"))) {
|
|
varianttype = virJSONValueObjectGetString(variant, "type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!varianttype) {
|
|
virBufferAsprintf(debug, "ERROR: variant '%s' for discriminator '%s' not found\n",
|
|
variantname, variantfield);
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (!(variantschema = virHashLookup(schema, varianttype)) ||
|
|
!(variantschemamembers = virJSONValueObjectGetArray(variantschema, "members"))) {
|
|
virBufferAsprintf(debug,
|
|
"ERROR: missing schema or schema members for variant '%s'(%s)\n",
|
|
variantname, varianttype);
|
|
ret = -2;
|
|
goto cleanup;
|
|
}
|
|
|
|
rootmembers = virJSONValueObjectGetArray(root, "members");
|
|
|
|
if (virJSONValueArrayForeachSteal(variantschemamembers,
|
|
testQEMUSchemaValidateObjectMergeVariantMember,
|
|
rootmembers) < 0) {
|
|
ret = -2;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virJSONValueFree(variants);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
testQEMUSchemaValidateObjectMandatoryMember(size_t pos ATTRIBUTE_UNUSED,
|
|
virJSONValuePtr item,
|
|
void *opaque ATTRIBUTE_UNUSED)
|
|
{
|
|
struct testQEMUSchemaValidateObjectMemberData *data = opaque;
|
|
|
|
if (virJSONValueObjectHasKey(item, "default") != 1) {
|
|
virBufferAsprintf(data->debug, "ERROR: missing mandatory attribute '%s'\n",
|
|
NULLSTR(virJSONValueObjectGetString(item, "name")));
|
|
data->missingMandatory = true;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
testQEMUSchemaValidateObject(virJSONValuePtr obj,
|
|
virJSONValuePtr root,
|
|
virHashTablePtr schema,
|
|
virBufferPtr debug)
|
|
{
|
|
struct testQEMUSchemaValidateObjectMemberData data = { NULL, schema,
|
|
debug, false };
|
|
virJSONValuePtr localroot = NULL;
|
|
const char *variantfield;
|
|
const char *variantname;
|
|
int ret = -1;
|
|
|
|
if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
|
|
virBufferAddLit(debug, "ERROR: not an object");
|
|
return -1;
|
|
}
|
|
|
|
virBufferAddLit(debug, "{\n");
|
|
virBufferAdjustIndent(debug, 3);
|
|
|
|
/* copy schema */
|
|
if (!(localroot = virJSONValueCopy(root))) {
|
|
ret = -2;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* remove variant */
|
|
if ((variantfield = virJSONValueObjectGetString(localroot, "tag"))) {
|
|
if (!(variantname = virJSONValueObjectGetString(obj, variantfield))) {
|
|
virBufferAsprintf(debug, "ERROR: missing variant discriminator attribute '%s'\n",
|
|
variantfield);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (testQEMUSchemaValidateObjectMergeVariant(localroot, variantfield,
|
|
variantname,
|
|
schema, debug) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
/* validate members */
|
|
data.rootmembers = virJSONValueObjectGetArray(localroot, "members");
|
|
if (virJSONValueObjectForeachKeyValue(obj,
|
|
testQEMUSchemaValidateObjectMember,
|
|
&data) < 0)
|
|
goto cleanup;
|
|
|
|
/* check missing mandatory values */
|
|
if (virJSONValueArrayForeachSteal(data.rootmembers,
|
|
testQEMUSchemaValidateObjectMandatoryMember,
|
|
&data) < 0) {
|
|
ret = -2;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (data.missingMandatory)
|
|
goto cleanup;
|
|
|
|
virBufferAdjustIndent(debug, -3);
|
|
virBufferAddLit(debug, "} OK");
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virJSONValueFree(localroot);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
testQEMUSchemaValidateEnum(virJSONValuePtr obj,
|
|
virJSONValuePtr root,
|
|
virBufferPtr debug)
|
|
{
|
|
const char *objstr;
|
|
virJSONValuePtr values = NULL;
|
|
virJSONValuePtr value;
|
|
size_t n;
|
|
size_t i;
|
|
|
|
if (virJSONValueGetType(obj) != VIR_JSON_TYPE_STRING) {
|
|
virBufferAddLit(debug, "ERROR: not a string");
|
|
return -1;
|
|
}
|
|
|
|
objstr = virJSONValueGetString(obj);
|
|
|
|
if (!(values = virJSONValueObjectGetArray(root, "values"))) {
|
|
virBufferAsprintf(debug, "ERROR: missing enum values in schema '%s'",
|
|
NULLSTR(virJSONValueObjectGetString(root, "name")));
|
|
return -2;
|
|
}
|
|
|
|
n = virJSONValueArraySize(values);
|
|
for (i = 0; i < n; i++) {
|
|
value = virJSONValueArrayGet(values, i);
|
|
|
|
if (STREQ_NULLABLE(objstr, virJSONValueGetString(value))) {
|
|
virBufferAsprintf(debug, "'%s' OK", NULLSTR(objstr));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
virBufferAsprintf(debug, "ERROR: enum value '%s' is not in schema",
|
|
NULLSTR(objstr));
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
testQEMUSchemaValidateArray(virJSONValuePtr objs,
|
|
virJSONValuePtr root,
|
|
virHashTablePtr schema,
|
|
virBufferPtr debug)
|
|
{
|
|
const char *elemtypename = virJSONValueObjectGetString(root, "element-type");
|
|
virJSONValuePtr elementschema;
|
|
virJSONValuePtr obj;
|
|
size_t n;
|
|
size_t i;
|
|
|
|
if (virJSONValueGetType(objs) != VIR_JSON_TYPE_ARRAY) {
|
|
virBufferAddLit(debug, "ERROR: not an array\n");
|
|
return -1;
|
|
}
|
|
|
|
if (!elemtypename ||
|
|
!(elementschema = virHashLookup(schema, elemtypename))) {
|
|
virBufferAsprintf(debug, "ERROR: missing schema for array element type '%s'",
|
|
NULLSTR(elemtypename));
|
|
return -2;
|
|
}
|
|
|
|
virBufferAddLit(debug, "[\n");
|
|
virBufferAdjustIndent(debug, 3);
|
|
|
|
n = virJSONValueArraySize(objs);
|
|
for (i = 0; i < n; i++) {
|
|
obj = virJSONValueArrayGet(objs, i);
|
|
|
|
if (testQEMUSchemaValidateRecurse(obj, elementschema, schema, debug) < 0)
|
|
return -1;
|
|
virBufferAddLit(debug, ",\n");
|
|
}
|
|
virBufferAddLit(debug, "] OK");
|
|
virBufferAdjustIndent(debug, -3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
testQEMUSchemaValidateAlternate(virJSONValuePtr obj,
|
|
virJSONValuePtr root,
|
|
virHashTablePtr schema,
|
|
virBufferPtr debug)
|
|
{
|
|
virJSONValuePtr members;
|
|
virJSONValuePtr member;
|
|
size_t n;
|
|
size_t i;
|
|
const char *membertype;
|
|
virJSONValuePtr memberschema;
|
|
int indent;
|
|
int rc;
|
|
|
|
if (!(members = virJSONValueObjectGetArray(root, "members"))) {
|
|
virBufferAddLit(debug, "ERROR: missing 'members' for alternate schema");
|
|
return -2;
|
|
}
|
|
|
|
virBufferAddLit(debug, "(\n");
|
|
virBufferAdjustIndent(debug, 3);
|
|
indent = virBufferGetIndent(debug, false);
|
|
|
|
n = virJSONValueArraySize(members);
|
|
for (i = 0; i < n; i++) {
|
|
membertype = NULL;
|
|
|
|
/* P != NP */
|
|
virBufferAsprintf(debug, "(alternate %zu/%zu)\n", i + 1, n);
|
|
virBufferAdjustIndent(debug, 3);
|
|
|
|
if (!(member = virJSONValueArrayGet(members, i)) ||
|
|
!(membertype = virJSONValueObjectGetString(member, "type")) ||
|
|
!(memberschema = virHashLookup(schema, membertype))) {
|
|
virBufferAsprintf(debug, "ERROR: missing schema for alternate type '%s'",
|
|
NULLSTR(membertype));
|
|
return -2;
|
|
}
|
|
|
|
rc = testQEMUSchemaValidateRecurse(obj, memberschema, schema, debug);
|
|
|
|
virBufferAddLit(debug, "\n");
|
|
virBufferSetIndent(debug, indent);
|
|
virBufferAsprintf(debug, "(/alternate %zu/%zu)\n", i + 1, n);
|
|
|
|
if (rc == 0) {
|
|
virBufferAdjustIndent(debug, -3);
|
|
virBufferAddLit(debug, ") OK");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
virBufferAddLit(debug, "ERROR: no alternate type was matched");
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
testQEMUSchemaValidateRecurse(virJSONValuePtr obj,
|
|
virJSONValuePtr root,
|
|
virHashTablePtr schema,
|
|
virBufferPtr debug)
|
|
{
|
|
const char *n = virJSONValueObjectGetString(root, "name");
|
|
const char *t = virJSONValueObjectGetString(root, "meta-type");
|
|
|
|
if (STREQ_NULLABLE(t, "builtin")) {
|
|
return testQEMUSchemaValidateArrayBuiltin(obj, root, debug);
|
|
} else if (STREQ_NULLABLE(t, "object")) {
|
|
return testQEMUSchemaValidateObject(obj, root, schema, debug);
|
|
} else if (STREQ_NULLABLE(t, "enum")) {
|
|
return testQEMUSchemaValidateEnum(obj, root, debug);
|
|
} else if (STREQ_NULLABLE(t, "array")) {
|
|
return testQEMUSchemaValidateArray(obj, root, schema, debug);
|
|
} else if (STREQ_NULLABLE(t, "alternate")) {
|
|
return testQEMUSchemaValidateAlternate(obj, root, schema, debug);
|
|
}
|
|
|
|
virBufferAsprintf(debug,
|
|
"qapi schema meta-type '%s' of type '%s' not handled\n",
|
|
NULLSTR(t), NULLSTR(n));
|
|
return -2;
|
|
}
|
|
|
|
|
|
/**
|
|
* testQEMUSchemaValidate:
|
|
* @obj: object to validate
|
|
* @root: schema entry to start from
|
|
* @schema: hash table containing schema entries
|
|
* @debug: a virBuffer which will be filled with debug information if provided
|
|
*
|
|
* Validates whether @obj conforms to the QAPI schema passed in via @schema,
|
|
* starting from the node @root. Returns 0, if @obj matches @schema, -1 if it
|
|
* does not and -2 if there is a problem with the schema or with internals.
|
|
*
|
|
* @debug is filled with information regarding the validation process
|
|
*/
|
|
int
|
|
testQEMUSchemaValidate(virJSONValuePtr obj,
|
|
virJSONValuePtr root,
|
|
virHashTablePtr schema,
|
|
virBufferPtr debug)
|
|
{
|
|
return testQEMUSchemaValidateRecurse(obj, root, schema, debug);
|
|
}
|
|
|
|
|
|
virHashTablePtr
|
|
testQEMUSchemaLoad(void)
|
|
{
|
|
virJSONValuePtr schemajson;
|
|
|
|
if (!(schemajson = virTestLoadFileJSON("qemuqapischema.json", NULL)))
|
|
return NULL;
|
|
|
|
return virQEMUQAPISchemaConvert(schemajson);
|
|
}
|