| 1 | /* |
| 2 | * QTest testcase for QOM |
| 3 | * |
| 4 | * Copyright (c) 2013 SUSE LINUX Products GmbH |
| 5 | * |
| 6 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 7 | * See the COPYING file in the top-level directory. |
| 8 | */ |
| 9 | |
| 10 | #include "qemu/osdep.h" |
| 11 | |
| 12 | #include "qobject/qdict.h" |
| 13 | #include "qobject/qlist.h" |
| 14 | #include "qobject/qstring.h" |
| 15 | #include "qemu/cutils.h" |
| 16 | #include "libqtest.h" |
| 17 | |
| 18 | #define RAM_NAME "node0" |
| 19 | #define RAM_SIZE 65536 |
| 20 | |
| 21 | static int verbosity_level; |
| 22 | |
| 23 | /* |
| 24 | * Verify that the /object/RAM_NAME 'size' property is RAM_SIZE. |
| 25 | */ |
| 26 | static void test_list_get_value(QTestState *qts) |
| 27 | { |
| 28 | QDict *args = qdict_new(); |
| 29 | g_autoptr(QDict) response = NULL; |
| 30 | g_autoptr(QList) paths = qlist_new(); |
| 31 | QListEntry *entry, *prop_entry; |
| 32 | const char *prop_name; |
| 33 | QList *properties, *return_list; |
| 34 | QDict *obj; |
| 35 | |
| 36 | qlist_append_str(paths, "/objects/" RAM_NAME); |
| 37 | qdict_put_obj(args, "paths", QOBJECT(qlist_copy(paths))); |
| 38 | response = qtest_qmp(qts, "{ 'execute': 'qom-list-get'," |
| 39 | " 'arguments': %p }", args); |
| 40 | g_assert(response); |
| 41 | g_assert(qdict_haskey(response, "return")); |
| 42 | return_list = qobject_to(QList, qdict_get(response, "return")); |
| 43 | |
| 44 | entry = QTAILQ_FIRST(&return_list->head); |
| 45 | obj = qobject_to(QDict, qlist_entry_obj(entry)); |
| 46 | g_assert(qdict_haskey(obj, "properties")); |
| 47 | properties = qobject_to(QList, qdict_get(obj, "properties")); |
| 48 | |
| 49 | QLIST_FOREACH_ENTRY(properties, prop_entry) { |
| 50 | QDict *prop = qobject_to(QDict, qlist_entry_obj(prop_entry)); |
| 51 | |
| 52 | g_assert(qdict_haskey(prop, "name")); |
| 53 | g_assert(qdict_haskey(prop, "value")); |
| 54 | |
| 55 | prop_name = qdict_get_str(prop, "name"); |
| 56 | if (!strcmp(prop_name, "type")) { |
| 57 | g_assert_cmpstr(qdict_get_str(prop, "value"), ==, |
| 58 | "memory-backend-ram"); |
| 59 | |
| 60 | } else if (!strcmp(prop_name, "size")) { |
| 61 | g_assert_cmpint(qdict_get_int(prop, "value"), ==, RAM_SIZE); |
| 62 | } |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | static void test_list_get(QTestState *qts, QList *paths) |
| 67 | { |
| 68 | QListEntry *entry, *prop_entry, *path_entry; |
| 69 | g_autoptr(QDict) response = NULL; |
| 70 | QDict *args = qdict_new(); |
| 71 | QDict *prop; |
| 72 | QList *return_list; |
| 73 | |
| 74 | if (verbosity_level >= 2) { |
| 75 | g_test_message("Obtaining properties for paths:"); |
| 76 | QLIST_FOREACH_ENTRY(paths, path_entry) { |
| 77 | QString *qstr = qobject_to(QString, qlist_entry_obj(path_entry)); |
| 78 | g_test_message(" %s", qstring_get_str(qstr)); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | qdict_put_obj(args, "paths", QOBJECT(qlist_copy(paths))); |
| 83 | response = qtest_qmp(qts, "{ 'execute': 'qom-list-get'," |
| 84 | " 'arguments': %p }", args); |
| 85 | g_assert(response); |
| 86 | g_assert(qdict_haskey(response, "return")); |
| 87 | return_list = qobject_to(QList, qdict_get(response, "return")); |
| 88 | g_assert(!qlist_empty(return_list)); |
| 89 | |
| 90 | path_entry = QTAILQ_FIRST(&paths->head); |
| 91 | QLIST_FOREACH_ENTRY(return_list, entry) { |
| 92 | QDict *obj = qobject_to(QDict, qlist_entry_obj(entry)); |
| 93 | g_assert(qdict_haskey(obj, "properties")); |
| 94 | QList *properties = qobject_to(QList, qdict_get(obj, "properties")); |
| 95 | bool has_child = false; |
| 96 | |
| 97 | QLIST_FOREACH_ENTRY(properties, prop_entry) { |
| 98 | prop = qobject_to(QDict, qlist_entry_obj(prop_entry)); |
| 99 | g_assert(qdict_haskey(prop, "name")); |
| 100 | g_assert(qdict_haskey(prop, "type")); |
| 101 | has_child |= strstart(qdict_get_str(prop, "type"), "child<", NULL); |
| 102 | } |
| 103 | |
| 104 | if (has_child) { |
| 105 | /* build a list of child paths */ |
| 106 | QString *qstr = qobject_to(QString, qlist_entry_obj(path_entry)); |
| 107 | const char *path = qstring_get_str(qstr); |
| 108 | g_autoptr(QList) child_paths = qlist_new(); |
| 109 | |
| 110 | QLIST_FOREACH_ENTRY(properties, prop_entry) { |
| 111 | prop = qobject_to(QDict, qlist_entry_obj(prop_entry)); |
| 112 | if (strstart(qdict_get_str(prop, "type"), "child<", NULL)) { |
| 113 | g_autofree char *child_path = g_strdup_printf( |
| 114 | "%s/%s", path, qdict_get_str(prop, "name")); |
| 115 | qlist_append_str(child_paths, child_path); |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | /* fetch props for all children with one qom-list-get call */ |
| 120 | test_list_get(qts, child_paths); |
| 121 | } |
| 122 | |
| 123 | path_entry = QTAILQ_NEXT(path_entry, next); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | static void test_properties(QTestState *qts, const char *path, bool recurse) |
| 128 | { |
| 129 | char *child_path; |
| 130 | QDict *response, *tuple, *tmp; |
| 131 | QList *list; |
| 132 | QListEntry *entry; |
| 133 | GSList *children = NULL, *links = NULL; |
| 134 | |
| 135 | if (verbosity_level >= 2) { |
| 136 | g_test_message("Obtaining properties of %s", path); |
| 137 | } |
| 138 | response = qtest_qmp(qts, "{ 'execute': 'qom-list'," |
| 139 | " 'arguments': { 'path': %s } }", path); |
| 140 | g_assert(response); |
| 141 | |
| 142 | if (!recurse) { |
| 143 | qobject_unref(response); |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | g_assert(qdict_haskey(response, "return")); |
| 148 | list = qobject_to(QList, qdict_get(response, "return")); |
| 149 | QLIST_FOREACH_ENTRY(list, entry) { |
| 150 | tuple = qobject_to(QDict, qlist_entry_obj(entry)); |
| 151 | bool is_child = strstart(qdict_get_str(tuple, "type"), "child<", NULL); |
| 152 | bool is_link = strstart(qdict_get_str(tuple, "type"), "link<", NULL); |
| 153 | |
| 154 | if (is_child || is_link) { |
| 155 | child_path = g_strdup_printf("%s/%s", |
| 156 | path, qdict_get_str(tuple, "name")); |
| 157 | if (is_child) { |
| 158 | children = g_slist_prepend(children, child_path); |
| 159 | } else { |
| 160 | links = g_slist_prepend(links, child_path); |
| 161 | } |
| 162 | } else { |
| 163 | const char *prop = qdict_get_str(tuple, "name"); |
| 164 | if (verbosity_level >= 3) { |
| 165 | g_test_message("-> %s", prop); |
| 166 | } |
| 167 | tmp = qtest_qmp(qts, |
| 168 | "{ 'execute': 'qom-get'," |
| 169 | " 'arguments': { 'path': %s, 'property': %s } }", |
| 170 | path, prop); |
| 171 | /* qom-get may fail but should not, e.g., segfault. */ |
| 172 | g_assert(tmp); |
| 173 | qobject_unref(tmp); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | while (links) { |
| 178 | test_properties(qts, links->data, false); |
| 179 | g_free(links->data); |
| 180 | links = g_slist_delete_link(links, links); |
| 181 | } |
| 182 | while (children) { |
| 183 | test_properties(qts, children->data, g_test_slow()); |
| 184 | g_free(children->data); |
| 185 | children = g_slist_delete_link(children, children); |
| 186 | } |
| 187 | |
| 188 | qobject_unref(response); |
| 189 | } |
| 190 | |
| 191 | static void test_machine(gconstpointer data) |
| 192 | { |
| 193 | const char *machine = data; |
| 194 | QDict *response; |
| 195 | QTestState *qts; |
| 196 | g_autoptr(QList) paths = qlist_new(); |
| 197 | |
| 198 | qts = qtest_initf("-machine %s -object memory-backend-ram,id=%s,size=%d", |
| 199 | machine, RAM_NAME, RAM_SIZE); |
| 200 | |
| 201 | if (g_test_slow()) { |
| 202 | /* Make sure we can get the machine class properties: */ |
| 203 | g_autofree char *qom_machine = g_strdup_printf("%s-machine", machine); |
| 204 | |
| 205 | response = qtest_qmp(qts, "{ 'execute': 'qom-list-properties'," |
| 206 | " 'arguments': { 'typename': %s } }", |
| 207 | qom_machine); |
| 208 | g_assert(response); |
| 209 | qobject_unref(response); |
| 210 | } |
| 211 | |
| 212 | test_properties(qts, "/machine", true); |
| 213 | |
| 214 | qlist_append_str(paths, "/"); |
| 215 | test_list_get(qts, paths); |
| 216 | test_list_get_value(qts); |
| 217 | |
| 218 | qtest_quit(qts); |
| 219 | } |
| 220 | |
| 221 | static void add_machine_test_case(const char *mname) |
| 222 | { |
| 223 | char *path; |
| 224 | |
| 225 | path = g_strdup_printf("qom/%s", mname); |
| 226 | qtest_add_data_func(path, mname, test_machine); |
| 227 | g_free(path); |
| 228 | } |
| 229 | |
| 230 | static void test_qom_qtests(void) |
| 231 | { |
| 232 | QTestState *qts; |
| 233 | |
| 234 | qts = qtest_initf("-machine none"); |
| 235 | |
| 236 | qtest_qom_tests(qts); |
| 237 | |
| 238 | qtest_quit(qts); |
| 239 | } |
| 240 | |
| 241 | int main(int argc, char **argv) |
| 242 | { |
| 243 | char *v_env = getenv("V"); |
| 244 | |
| 245 | if (v_env) { |
| 246 | verbosity_level = atoi(v_env); |
| 247 | } |
| 248 | |
| 249 | g_test_init(&argc, &argv, NULL); |
| 250 | |
| 251 | qtest_cb_for_every_machine(add_machine_test_case, g_test_quick()); |
| 252 | qtest_add_func("qom/qom-qtests", test_qom_qtests); |
| 253 | |
| 254 | return g_test_run(); |
| 255 | } |