QAPI patches patches for 2025-07-16
-----BEGIN PGP SIGNATURE----- iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmh34AYSHGFybWJydUBy ZWRoYXQuY29tAAoJEDhwtADrkYZTiw4P/iFXG7aZvnMC1OwJ0bUKJIIWTwcxrio2 ELKvxHplktZe+KUHPoH+l+5j9ytSl8k6BKlMZgiqJv6pVVFUXlYmwPZ19Rtwi2o5 62J2Lq/f33JSFh/ROqgM88gey61nxtL8eK2+Y2CxBGL3rd39VF/qto9HzE48kITM iDPKeCzcikDg0QIlnj4afP1wNbJawFBW60JAugaEdHG5VBhjcPQTDI1bMaSLpUJu 8d+v3bHLTM8FqHnkIdxQWDTnMwI9SVfqovzoDbsVw4sa9Ptt8GdKnUHUbNRtyeNp 9zRu01ztMV9zuByAwsRm2ECdTwxsOVhVp4nFUxM24u2eTO6Pixe1sgogrtxcUVIf tQuorYqeu1AAEyqz2iYFxrxKgsqRkA/etQCzibg+1coKlmLi7UTra4F0LEUWUMxZ iXLGlnDZx9iwQq5iVHDb0zSzbBSlAamYK5HX4p8E7skJ7yaSwwZGgfQAr4JfJ7Yu 73k4nuRHcLYIoyv02KE8npHFE62I0hH5YvJ2G9vQ2GJtw0vbCcVn01G3PYCe/P6/ RXLSWAZmIyON7FAst1MZd2sF+0QG0beEt81bUrGbKEZY8qhXL4/mABmJvOp+eNJ6 23Z2bFNBkjCMxsJYQL8h9E9Zg/VAvxFJRoGClEPV+PfjCIy/wZPR8gUeYVPuDya4 63sn7GO5hu3c =+Yt5 -----END PGP SIGNATURE----- Merge tag 'pull-qapi-2025-07-16' of https://repo.or.cz/qemu/armbru into staging QAPI patches patches for 2025-07-16 # -----BEGIN PGP SIGNATURE----- # # iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmh34AYSHGFybWJydUBy # ZWRoYXQuY29tAAoJEDhwtADrkYZTiw4P/iFXG7aZvnMC1OwJ0bUKJIIWTwcxrio2 # ELKvxHplktZe+KUHPoH+l+5j9ytSl8k6BKlMZgiqJv6pVVFUXlYmwPZ19Rtwi2o5 # 62J2Lq/f33JSFh/ROqgM88gey61nxtL8eK2+Y2CxBGL3rd39VF/qto9HzE48kITM # iDPKeCzcikDg0QIlnj4afP1wNbJawFBW60JAugaEdHG5VBhjcPQTDI1bMaSLpUJu # 8d+v3bHLTM8FqHnkIdxQWDTnMwI9SVfqovzoDbsVw4sa9Ptt8GdKnUHUbNRtyeNp # 9zRu01ztMV9zuByAwsRm2ECdTwxsOVhVp4nFUxM24u2eTO6Pixe1sgogrtxcUVIf # tQuorYqeu1AAEyqz2iYFxrxKgsqRkA/etQCzibg+1coKlmLi7UTra4F0LEUWUMxZ # iXLGlnDZx9iwQq5iVHDb0zSzbBSlAamYK5HX4p8E7skJ7yaSwwZGgfQAr4JfJ7Yu # 73k4nuRHcLYIoyv02KE8npHFE62I0hH5YvJ2G9vQ2GJtw0vbCcVn01G3PYCe/P6/ # RXLSWAZmIyON7FAst1MZd2sF+0QG0beEt81bUrGbKEZY8qhXL4/mABmJvOp+eNJ6 # 23Z2bFNBkjCMxsJYQL8h9E9Zg/VAvxFJRoGClEPV+PfjCIy/wZPR8gUeYVPuDya4 # 63sn7GO5hu3c # =+Yt5 # -----END PGP SIGNATURE----- # gpg: Signature made Wed 16 Jul 2025 13:23:18 EDT # gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653 # gpg: issuer "armbru@redhat.com" # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full] # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full] # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * tag 'pull-qapi-2025-07-16' of https://repo.or.cz/qemu/armbru: python: fix editable installs for modern pip/setuptools MAINTAINERS: Add docs/requirements.txt docs: Bump sphinx to 6.2.1 tests/qtest/qom-test: unit test for qom-list-get python: use qom-list-get qom: qom-list-get Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
commit
3656e761bc
9 changed files with 305 additions and 27 deletions
|
|
@ -4428,6 +4428,7 @@ M: Peter Maydell <peter.maydell@linaro.org>
|
|||
S: Maintained
|
||||
F: docs/conf.py
|
||||
F: docs/*/conf.py
|
||||
F: docs/requirements.txt
|
||||
F: docs/sphinx/
|
||||
F: docs/_templates/
|
||||
F: docs/devel/docs.rst
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Used by readthedocs.io
|
||||
# Should be in sync with the "installed" key of pythondeps.toml
|
||||
|
||||
sphinx==5.3.0
|
||||
sphinx_rtd_theme==1.1.1
|
||||
sphinx==6.2.1
|
||||
sphinx_rtd_theme==1.2.2
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ $(QEMU_MINVENV_DIR) $(QEMU_MINVENV_DIR)/bin/activate: setup.cfg tests/minreqs.tx
|
|||
echo "INSTALL -r tests/minreqs.txt $(QEMU_MINVENV_DIR)";\
|
||||
$(PIP_INSTALL) -r tests/minreqs.txt 1>/dev/null; \
|
||||
echo "INSTALL -e qemu $(QEMU_MINVENV_DIR)"; \
|
||||
$(PIP_INSTALL) -e . 1>/dev/null; \
|
||||
PIP_CONFIG_SETTINGS="editable_mode=compat" $(PIP_INSTALL) -e . 1>/dev/null; \
|
||||
)
|
||||
@touch $(QEMU_MINVENV_DIR)
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ check-dev: dev-venv
|
|||
|
||||
.PHONY: develop
|
||||
develop:
|
||||
$(PIP_INSTALL) -e .[devel]
|
||||
PIP_CONFIG_SETTINGS="editable_mode=compat" $(PIP_INSTALL) -e .[devel]
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ QOM commands:
|
|||
##
|
||||
|
||||
import argparse
|
||||
|
||||
from qemu.qmp import ExecuteError
|
||||
from typing import List
|
||||
|
||||
from .qom_common import QOMCommand
|
||||
|
||||
|
|
@ -224,28 +223,34 @@ class QOMTree(QOMCommand):
|
|||
super().__init__(args)
|
||||
self.path = args.path
|
||||
|
||||
def _list_node(self, path: str) -> None:
|
||||
print(path)
|
||||
items = self.qom_list(path)
|
||||
for item in items:
|
||||
if item.child:
|
||||
continue
|
||||
try:
|
||||
rsp = self.qmp.cmd('qom-get', path=path,
|
||||
property=item.name)
|
||||
print(f" {item.name}: {rsp} ({item.type})")
|
||||
except ExecuteError as err:
|
||||
print(f" {item.name}: <EXCEPTION: {err!s}> ({item.type})")
|
||||
print('')
|
||||
for item in items:
|
||||
if not item.child:
|
||||
continue
|
||||
def _list_nodes(self, paths: List[str]) -> None:
|
||||
all_paths_props = self.qom_list_get(paths)
|
||||
i = 0
|
||||
|
||||
for props in all_paths_props:
|
||||
path = paths[i]
|
||||
i = i + 1
|
||||
print(path)
|
||||
if path == '/':
|
||||
path = ''
|
||||
self._list_node(f"{path}/{item.name}")
|
||||
newpaths = []
|
||||
|
||||
for item in props.properties:
|
||||
if item.child:
|
||||
newpaths += [f"{path}/{item.name}"]
|
||||
else:
|
||||
value = item.value
|
||||
if value is None:
|
||||
value = "<EXCEPTION: property could not be read>"
|
||||
print(f" {item.name}: {value} ({item.type})")
|
||||
|
||||
print('')
|
||||
|
||||
if newpaths:
|
||||
self._list_nodes(newpaths)
|
||||
|
||||
def run(self) -> int:
|
||||
self._list_node(self.path)
|
||||
self._list_nodes([self.path])
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,52 @@ class ObjectPropertyInfo:
|
|||
return self.type.startswith('link<')
|
||||
|
||||
|
||||
class ObjectPropertyValue:
|
||||
"""
|
||||
Represents a property return from e.g. qom-tree-get
|
||||
"""
|
||||
def __init__(self, name: str, type_: str, value: object):
|
||||
self.name = name
|
||||
self.type = type_
|
||||
self.value = value
|
||||
|
||||
@classmethod
|
||||
def make(cls, value: Dict[str, Any]) -> 'ObjectPropertyValue':
|
||||
"""
|
||||
Build an ObjectPropertyValue from a Dict with an unknown shape.
|
||||
"""
|
||||
assert value.keys() >= {'name', 'type'}
|
||||
assert value.keys() <= {'name', 'type', 'value'}
|
||||
return cls(value['name'], value['type'], value.get('value'))
|
||||
|
||||
@property
|
||||
def child(self) -> bool:
|
||||
"""Is this property a child property?"""
|
||||
return self.type.startswith('child<')
|
||||
|
||||
|
||||
class ObjectPropertiesValues:
|
||||
"""
|
||||
Represents the return type from e.g. qom-list-get
|
||||
"""
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, properties: List[ObjectPropertyValue]) -> None:
|
||||
self.properties = properties
|
||||
|
||||
@classmethod
|
||||
def make(cls, value: Dict[str, Any]) -> 'ObjectPropertiesValues':
|
||||
"""
|
||||
Build an ObjectPropertiesValues from a Dict with an unknown shape.
|
||||
"""
|
||||
assert value.keys() == {'properties'}
|
||||
props = [ObjectPropertyValue(item['name'],
|
||||
item['type'],
|
||||
item.get('value'))
|
||||
for item in value['properties']]
|
||||
return cls(props)
|
||||
|
||||
|
||||
CommandT = TypeVar('CommandT', bound='QOMCommand')
|
||||
|
||||
|
||||
|
|
@ -145,6 +191,15 @@ class QOMCommand:
|
|||
assert isinstance(rsp, list)
|
||||
return [ObjectPropertyInfo.make(x) for x in rsp]
|
||||
|
||||
def qom_list_get(self, paths: List[str]) -> List[ObjectPropertiesValues]:
|
||||
"""
|
||||
:return: a strongly typed list from the 'qom-list-get' command.
|
||||
"""
|
||||
rsp = self.qmp.cmd('qom-list-get', paths=paths)
|
||||
# qom-list-get returns List[ObjectPropertiesValues]
|
||||
assert isinstance(rsp, list)
|
||||
return [ObjectPropertiesValues.make(x) for x in rsp]
|
||||
|
||||
@classmethod
|
||||
def command_runner(
|
||||
cls: Type[CommandT],
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ pycotap = { accepted = ">=1.1.0", installed = "1.3.1" }
|
|||
|
||||
[docs]
|
||||
# Please keep the installed versions in sync with docs/requirements.txt
|
||||
sphinx = { accepted = ">=3.4.3", installed = "5.3.0", canary = "sphinx-build" }
|
||||
sphinx_rtd_theme = { accepted = ">=0.5", installed = "1.1.1" }
|
||||
sphinx = { accepted = ">=3.4.3", installed = "6.2.1", canary = "sphinx-build" }
|
||||
sphinx_rtd_theme = { accepted = ">=0.5", installed = "1.2.2" }
|
||||
|
||||
[testdeps]
|
||||
qemu.qmp = { accepted = ">=0.0.3", installed = "0.0.3" }
|
||||
|
|
|
|||
|
|
@ -47,6 +47,34 @@
|
|||
'*description': 'str',
|
||||
'*default-value': 'any' } }
|
||||
|
||||
##
|
||||
# @ObjectPropertyValue:
|
||||
#
|
||||
# @name: the name of the property.
|
||||
#
|
||||
# @type: the type of the property, as described in `ObjectPropertyInfo`.
|
||||
#
|
||||
# @value: the value of the property. Absent when the property cannot
|
||||
# be read.
|
||||
#
|
||||
# Since 10.1
|
||||
##
|
||||
{ 'struct': 'ObjectPropertyValue',
|
||||
'data': { 'name': 'str',
|
||||
'type': 'str',
|
||||
'*value': 'any' } }
|
||||
|
||||
##
|
||||
# @ObjectPropertiesValues:
|
||||
#
|
||||
# @properties: a list of properties.
|
||||
#
|
||||
# Since 10.1
|
||||
##
|
||||
{ 'struct': 'ObjectPropertiesValues',
|
||||
'data': { 'properties': [ 'ObjectPropertyValue' ] }}
|
||||
|
||||
|
||||
##
|
||||
# @qom-list:
|
||||
#
|
||||
|
|
@ -124,6 +152,28 @@
|
|||
'returns': 'any',
|
||||
'allow-preconfig': true }
|
||||
|
||||
##
|
||||
# @qom-list-get:
|
||||
#
|
||||
# List properties and their values for each object path in the input
|
||||
# list.
|
||||
#
|
||||
# @paths: The absolute or partial path for each object, as described
|
||||
# in `qom-get`.
|
||||
#
|
||||
# Errors:
|
||||
# - If any path is not valid or is ambiguous
|
||||
#
|
||||
# Returns: A list where each element is the result for the
|
||||
# corresponding element of @paths.
|
||||
#
|
||||
# Since 10.1
|
||||
##
|
||||
{ 'command': 'qom-list-get',
|
||||
'data': { 'paths': [ 'str' ] },
|
||||
'returns': [ 'ObjectPropertiesValues' ],
|
||||
'allow-preconfig': true }
|
||||
|
||||
##
|
||||
# @qom-set:
|
||||
#
|
||||
|
|
|
|||
|
|
@ -69,6 +69,59 @@ ObjectPropertyInfoList *qmp_qom_list(const char *path, Error **errp)
|
|||
return props;
|
||||
}
|
||||
|
||||
static void qom_list_add_property_value(Object *obj, ObjectProperty *prop,
|
||||
ObjectPropertyValueList **props)
|
||||
{
|
||||
ObjectPropertyValue *item = g_new0(ObjectPropertyValue, 1);
|
||||
|
||||
QAPI_LIST_PREPEND(*props, item);
|
||||
|
||||
item->name = g_strdup(prop->name);
|
||||
item->type = g_strdup(prop->type);
|
||||
item->value = object_property_get_qobject(obj, prop->name, NULL);
|
||||
}
|
||||
|
||||
static ObjectPropertyValueList *qom_get_property_value_list(const char *path,
|
||||
Error **errp)
|
||||
{
|
||||
Object *obj;
|
||||
ObjectProperty *prop;
|
||||
ObjectPropertyIterator iter;
|
||||
ObjectPropertyValueList *props = NULL;
|
||||
|
||||
obj = qom_resolve_path(path, errp);
|
||||
if (obj == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
object_property_iter_init(&iter, obj);
|
||||
while ((prop = object_property_iter_next(&iter))) {
|
||||
qom_list_add_property_value(obj, prop, &props);
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
ObjectPropertiesValuesList *qmp_qom_list_get(strList *paths, Error **errp)
|
||||
{
|
||||
ObjectPropertiesValuesList *head = NULL, **tail = &head;
|
||||
strList *path;
|
||||
|
||||
for (path = paths; path; path = path->next) {
|
||||
ObjectPropertiesValues *item = g_new0(ObjectPropertiesValues, 1);
|
||||
|
||||
QAPI_LIST_APPEND(tail, item);
|
||||
|
||||
item->properties = qom_get_property_value_list(path->value, errp);
|
||||
if (!item->properties) {
|
||||
qapi_free_ObjectPropertiesValuesList(head);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
void qmp_qom_set(const char *path, const char *property, QObject *value,
|
||||
Error **errp)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,11 +11,119 @@
|
|||
|
||||
#include "qobject/qdict.h"
|
||||
#include "qobject/qlist.h"
|
||||
#include "qobject/qstring.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "libqtest.h"
|
||||
|
||||
#define RAM_NAME "node0"
|
||||
#define RAM_SIZE 65536
|
||||
|
||||
static int verbosity_level;
|
||||
|
||||
/*
|
||||
* Verify that the /object/RAM_NAME 'size' property is RAM_SIZE.
|
||||
*/
|
||||
static void test_list_get_value(QTestState *qts)
|
||||
{
|
||||
QDict *args = qdict_new();
|
||||
g_autoptr(QDict) response = NULL;
|
||||
g_autoptr(QList) paths = qlist_new();
|
||||
QListEntry *entry, *prop_entry;
|
||||
const char *prop_name;
|
||||
QList *properties, *return_list;
|
||||
QDict *obj;
|
||||
|
||||
qlist_append_str(paths, "/objects/" RAM_NAME);
|
||||
qdict_put_obj(args, "paths", QOBJECT(qlist_copy(paths)));
|
||||
response = qtest_qmp(qts, "{ 'execute': 'qom-list-get',"
|
||||
" 'arguments': %p }", args);
|
||||
g_assert(response);
|
||||
g_assert(qdict_haskey(response, "return"));
|
||||
return_list = qobject_to(QList, qdict_get(response, "return"));
|
||||
|
||||
entry = QTAILQ_FIRST(&return_list->head);
|
||||
obj = qobject_to(QDict, qlist_entry_obj(entry));
|
||||
g_assert(qdict_haskey(obj, "properties"));
|
||||
properties = qobject_to(QList, qdict_get(obj, "properties"));
|
||||
|
||||
QLIST_FOREACH_ENTRY(properties, prop_entry) {
|
||||
QDict *prop = qobject_to(QDict, qlist_entry_obj(prop_entry));
|
||||
|
||||
g_assert(qdict_haskey(prop, "name"));
|
||||
g_assert(qdict_haskey(prop, "value"));
|
||||
|
||||
prop_name = qdict_get_str(prop, "name");
|
||||
if (!strcmp(prop_name, "type")) {
|
||||
g_assert_cmpstr(qdict_get_str(prop, "value"), ==,
|
||||
"memory-backend-ram");
|
||||
|
||||
} else if (!strcmp(prop_name, "size")) {
|
||||
g_assert_cmpint(qdict_get_int(prop, "value"), ==, RAM_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void test_list_get(QTestState *qts, QList *paths)
|
||||
{
|
||||
QListEntry *entry, *prop_entry, *path_entry;
|
||||
g_autoptr(QDict) response = NULL;
|
||||
QDict *args = qdict_new();
|
||||
QDict *prop;
|
||||
QList *return_list;
|
||||
|
||||
if (verbosity_level >= 2) {
|
||||
g_test_message("Obtaining properties for paths:");
|
||||
QLIST_FOREACH_ENTRY(paths, path_entry) {
|
||||
QString *qstr = qobject_to(QString, qlist_entry_obj(path_entry));
|
||||
g_test_message(" %s", qstring_get_str(qstr));
|
||||
}
|
||||
}
|
||||
|
||||
qdict_put_obj(args, "paths", QOBJECT(qlist_copy(paths)));
|
||||
response = qtest_qmp(qts, "{ 'execute': 'qom-list-get',"
|
||||
" 'arguments': %p }", args);
|
||||
g_assert(response);
|
||||
g_assert(qdict_haskey(response, "return"));
|
||||
return_list = qobject_to(QList, qdict_get(response, "return"));
|
||||
g_assert(!qlist_empty(return_list));
|
||||
|
||||
path_entry = QTAILQ_FIRST(&paths->head);
|
||||
QLIST_FOREACH_ENTRY(return_list, entry) {
|
||||
QDict *obj = qobject_to(QDict, qlist_entry_obj(entry));
|
||||
g_assert(qdict_haskey(obj, "properties"));
|
||||
QList *properties = qobject_to(QList, qdict_get(obj, "properties"));
|
||||
bool has_child = false;
|
||||
|
||||
QLIST_FOREACH_ENTRY(properties, prop_entry) {
|
||||
prop = qobject_to(QDict, qlist_entry_obj(prop_entry));
|
||||
g_assert(qdict_haskey(prop, "name"));
|
||||
g_assert(qdict_haskey(prop, "type"));
|
||||
has_child |= strstart(qdict_get_str(prop, "type"), "child<", NULL);
|
||||
}
|
||||
|
||||
if (has_child) {
|
||||
/* build a list of child paths */
|
||||
QString *qstr = qobject_to(QString, qlist_entry_obj(path_entry));
|
||||
const char *path = qstring_get_str(qstr);
|
||||
g_autoptr(QList) child_paths = qlist_new();
|
||||
|
||||
QLIST_FOREACH_ENTRY(properties, prop_entry) {
|
||||
prop = qobject_to(QDict, qlist_entry_obj(prop_entry));
|
||||
if (strstart(qdict_get_str(prop, "type"), "child<", NULL)) {
|
||||
g_autofree char *child_path = g_strdup_printf(
|
||||
"%s/%s", path, qdict_get_str(prop, "name"));
|
||||
qlist_append_str(child_paths, child_path);
|
||||
}
|
||||
}
|
||||
|
||||
/* fetch props for all children with one qom-list-get call */
|
||||
test_list_get(qts, child_paths);
|
||||
}
|
||||
|
||||
path_entry = QTAILQ_NEXT(path_entry, next);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_properties(QTestState *qts, const char *path, bool recurse)
|
||||
{
|
||||
char *child_path;
|
||||
|
|
@ -85,8 +193,10 @@ static void test_machine(gconstpointer data)
|
|||
const char *machine = data;
|
||||
QDict *response;
|
||||
QTestState *qts;
|
||||
g_autoptr(QList) paths = qlist_new();
|
||||
|
||||
qts = qtest_initf("-machine %s", machine);
|
||||
qts = qtest_initf("-machine %s -object memory-backend-ram,id=%s,size=%d",
|
||||
machine, RAM_NAME, RAM_SIZE);
|
||||
|
||||
if (g_test_slow()) {
|
||||
/* Make sure we can get the machine class properties: */
|
||||
|
|
@ -101,6 +211,10 @@ static void test_machine(gconstpointer data)
|
|||
|
||||
test_properties(qts, "/machine", true);
|
||||
|
||||
qlist_append_str(paths, "/machine");
|
||||
test_list_get(qts, paths);
|
||||
test_list_get_value(qts);
|
||||
|
||||
response = qtest_qmp(qts, "{ 'execute': 'quit' }");
|
||||
g_assert(qdict_haskey(response, "return"));
|
||||
qobject_unref(response);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue