]>
Commit | Line | Data |
---|---|---|
1b76e838 HR |
1 | /* |
2 | * Generic QObject unit-tests. | |
3 | * | |
4 | * Copyright (C) 2017 Red Hat Inc. | |
5 | * | |
6 | * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. | |
7 | * See the COPYING.LIB file in the top-level directory. | |
8 | */ | |
1b76e838 | 9 | |
47e6b297 | 10 | #include "qemu/osdep.h" |
6b673957 | 11 | #include "qapi/qmp/qbool.h" |
452fcdbc | 12 | #include "qapi/qmp/qdict.h" |
47e6b297 | 13 | #include "qapi/qmp/qlist.h" |
15280c36 MA |
14 | #include "qapi/qmp/qnull.h" |
15 | #include "qapi/qmp/qnum.h" | |
6b673957 | 16 | #include "qapi/qmp/qstring.h" |
1b76e838 HR |
17 | #include "qemu-common.h" |
18 | ||
19 | #include <math.h> | |
20 | ||
21 | /* Marks the end of the test_equality() argument list. | |
22 | * We cannot use NULL there because that is a valid argument. */ | |
23 | static QObject test_equality_end_of_arguments; | |
24 | ||
25 | /** | |
26 | * Test whether all variadic QObject *arguments are equal (@expected | |
27 | * is true) or whether they are all not equal (@expected is false). | |
28 | * Every QObject is tested to be equal to itself (to test | |
29 | * reflexivity), all tests are done both ways (to test symmetry), and | |
30 | * transitivity is not assumed but checked (each object is compared to | |
31 | * every other one). | |
32 | * | |
33 | * Note that qobject_is_equal() is not really an equivalence relation, | |
34 | * so this function may not be used for all objects (reflexivity is | |
35 | * not guaranteed, e.g. in the case of a QNum containing NaN). | |
36 | * | |
37 | * The @_ argument is required because a boolean may not be the last | |
38 | * argument before a variadic argument list (C11 7.16.1.4 para. 4). | |
39 | */ | |
40 | static void do_test_equality(bool expected, int _, ...) | |
41 | { | |
42 | va_list ap_count, ap_extract; | |
43 | QObject **args; | |
44 | int arg_count = 0; | |
45 | int i, j; | |
46 | ||
47 | va_start(ap_count, _); | |
48 | va_copy(ap_extract, ap_count); | |
49 | while (va_arg(ap_count, QObject *) != &test_equality_end_of_arguments) { | |
50 | arg_count++; | |
51 | } | |
52 | va_end(ap_count); | |
53 | ||
54 | args = g_new(QObject *, arg_count); | |
55 | for (i = 0; i < arg_count; i++) { | |
56 | args[i] = va_arg(ap_extract, QObject *); | |
57 | } | |
58 | va_end(ap_extract); | |
59 | ||
60 | for (i = 0; i < arg_count; i++) { | |
61 | g_assert(qobject_is_equal(args[i], args[i]) == true); | |
62 | ||
63 | for (j = i + 1; j < arg_count; j++) { | |
64 | g_assert(qobject_is_equal(args[i], args[j]) == expected); | |
65 | } | |
66 | } | |
87c258cd MAL |
67 | |
68 | g_free(args); | |
1b76e838 HR |
69 | } |
70 | ||
71 | #define check_equal(...) \ | |
72 | do_test_equality(true, 0, __VA_ARGS__, &test_equality_end_of_arguments) | |
73 | #define check_unequal(...) \ | |
74 | do_test_equality(false, 0, __VA_ARGS__, &test_equality_end_of_arguments) | |
75 | ||
76 | static void do_free_all(int _, ...) | |
77 | { | |
78 | va_list ap; | |
79 | QObject *obj; | |
80 | ||
81 | va_start(ap, _); | |
82 | while ((obj = va_arg(ap, QObject *)) != NULL) { | |
cb3e7f08 | 83 | qobject_unref(obj); |
1b76e838 HR |
84 | } |
85 | va_end(ap); | |
86 | } | |
87 | ||
88 | #define free_all(...) \ | |
89 | do_free_all(0, __VA_ARGS__, NULL) | |
90 | ||
91 | static void qobject_is_equal_null_test(void) | |
92 | { | |
93 | check_unequal(qnull(), NULL); | |
94 | } | |
95 | ||
96 | static void qobject_is_equal_num_test(void) | |
97 | { | |
98 | QNum *u0, *i0, *d0, *dnan, *um42, *im42, *dm42; | |
99 | ||
100 | u0 = qnum_from_uint(0u); | |
101 | i0 = qnum_from_int(0); | |
102 | d0 = qnum_from_double(0.0); | |
103 | dnan = qnum_from_double(NAN); | |
104 | um42 = qnum_from_uint((uint64_t)-42); | |
105 | im42 = qnum_from_int(-42); | |
106 | dm42 = qnum_from_double(-42.0); | |
107 | ||
108 | /* Integers representing a mathematically equal number should | |
109 | * compare equal */ | |
110 | check_equal(u0, i0); | |
111 | /* Doubles, however, are always unequal to integers */ | |
112 | check_unequal(u0, d0); | |
113 | check_unequal(i0, d0); | |
114 | ||
115 | /* Do not assume any object is equal to itself -- note however | |
116 | * that NaN cannot occur in a JSON object anyway. */ | |
117 | g_assert(qobject_is_equal(QOBJECT(dnan), QOBJECT(dnan)) == false); | |
118 | ||
119 | /* No unsigned overflow */ | |
120 | check_unequal(um42, im42); | |
121 | check_unequal(um42, dm42); | |
122 | check_unequal(im42, dm42); | |
123 | ||
124 | free_all(u0, i0, d0, dnan, um42, im42, dm42); | |
125 | } | |
126 | ||
127 | static void qobject_is_equal_bool_test(void) | |
128 | { | |
129 | QBool *btrue_0, *btrue_1, *bfalse_0, *bfalse_1; | |
130 | ||
131 | btrue_0 = qbool_from_bool(true); | |
132 | btrue_1 = qbool_from_bool(true); | |
133 | bfalse_0 = qbool_from_bool(false); | |
134 | bfalse_1 = qbool_from_bool(false); | |
135 | ||
136 | check_equal(btrue_0, btrue_1); | |
137 | check_equal(bfalse_0, bfalse_1); | |
138 | check_unequal(btrue_0, bfalse_0); | |
139 | ||
140 | free_all(btrue_0, btrue_1, bfalse_0, bfalse_1); | |
141 | } | |
142 | ||
143 | static void qobject_is_equal_string_test(void) | |
144 | { | |
145 | QString *str_base, *str_whitespace_0, *str_whitespace_1, *str_whitespace_2; | |
146 | QString *str_whitespace_3, *str_case, *str_built; | |
147 | ||
148 | str_base = qstring_from_str("foo"); | |
149 | str_whitespace_0 = qstring_from_str(" foo"); | |
150 | str_whitespace_1 = qstring_from_str("foo "); | |
151 | str_whitespace_2 = qstring_from_str("foo\b"); | |
152 | str_whitespace_3 = qstring_from_str("fooo\b"); | |
153 | str_case = qstring_from_str("Foo"); | |
154 | ||
155 | /* Should yield "foo" */ | |
156 | str_built = qstring_from_substr("form", 0, 1); | |
157 | qstring_append_chr(str_built, 'o'); | |
158 | ||
159 | check_unequal(str_base, str_whitespace_0, str_whitespace_1, | |
160 | str_whitespace_2, str_whitespace_3, str_case); | |
161 | ||
162 | check_equal(str_base, str_built); | |
163 | ||
164 | free_all(str_base, str_whitespace_0, str_whitespace_1, str_whitespace_2, | |
165 | str_whitespace_3, str_case, str_built); | |
166 | } | |
167 | ||
168 | static void qobject_is_equal_list_test(void) | |
169 | { | |
170 | QList *list_0, *list_1, *list_cloned; | |
171 | QList *list_reordered, *list_longer, *list_shorter; | |
172 | ||
173 | list_0 = qlist_new(); | |
174 | list_1 = qlist_new(); | |
175 | list_reordered = qlist_new(); | |
176 | list_longer = qlist_new(); | |
177 | list_shorter = qlist_new(); | |
178 | ||
179 | qlist_append_int(list_0, 1); | |
180 | qlist_append_int(list_0, 2); | |
181 | qlist_append_int(list_0, 3); | |
182 | ||
183 | qlist_append_int(list_1, 1); | |
184 | qlist_append_int(list_1, 2); | |
185 | qlist_append_int(list_1, 3); | |
186 | ||
187 | qlist_append_int(list_reordered, 1); | |
188 | qlist_append_int(list_reordered, 3); | |
189 | qlist_append_int(list_reordered, 2); | |
190 | ||
191 | qlist_append_int(list_longer, 1); | |
192 | qlist_append_int(list_longer, 2); | |
193 | qlist_append_int(list_longer, 3); | |
194 | qlist_append_null(list_longer); | |
195 | ||
196 | qlist_append_int(list_shorter, 1); | |
197 | qlist_append_int(list_shorter, 2); | |
198 | ||
199 | list_cloned = qlist_copy(list_0); | |
200 | ||
201 | check_equal(list_0, list_1, list_cloned); | |
202 | check_unequal(list_0, list_reordered, list_longer, list_shorter); | |
203 | ||
204 | /* With a NaN in it, the list should no longer compare equal to | |
205 | * itself */ | |
206 | qlist_append(list_0, qnum_from_double(NAN)); | |
207 | g_assert(qobject_is_equal(QOBJECT(list_0), QOBJECT(list_0)) == false); | |
208 | ||
209 | free_all(list_0, list_1, list_cloned, list_reordered, list_longer, | |
210 | list_shorter); | |
211 | } | |
212 | ||
213 | static void qobject_is_equal_dict_test(void) | |
214 | { | |
215 | Error *local_err = NULL; | |
216 | QDict *dict_0, *dict_1, *dict_cloned; | |
217 | QDict *dict_different_key, *dict_different_value, *dict_different_null_key; | |
218 | QDict *dict_longer, *dict_shorter, *dict_nested; | |
219 | QDict *dict_crumpled; | |
220 | ||
221 | dict_0 = qdict_new(); | |
222 | dict_1 = qdict_new(); | |
223 | dict_different_key = qdict_new(); | |
224 | dict_different_value = qdict_new(); | |
225 | dict_different_null_key = qdict_new(); | |
226 | dict_longer = qdict_new(); | |
227 | dict_shorter = qdict_new(); | |
228 | dict_nested = qdict_new(); | |
229 | ||
230 | qdict_put_int(dict_0, "f.o", 1); | |
231 | qdict_put_int(dict_0, "bar", 2); | |
232 | qdict_put_int(dict_0, "baz", 3); | |
233 | qdict_put_null(dict_0, "null"); | |
234 | ||
235 | qdict_put_int(dict_1, "f.o", 1); | |
236 | qdict_put_int(dict_1, "bar", 2); | |
237 | qdict_put_int(dict_1, "baz", 3); | |
238 | qdict_put_null(dict_1, "null"); | |
239 | ||
240 | qdict_put_int(dict_different_key, "F.o", 1); | |
241 | qdict_put_int(dict_different_key, "bar", 2); | |
242 | qdict_put_int(dict_different_key, "baz", 3); | |
243 | qdict_put_null(dict_different_key, "null"); | |
244 | ||
245 | qdict_put_int(dict_different_value, "f.o", 42); | |
246 | qdict_put_int(dict_different_value, "bar", 2); | |
247 | qdict_put_int(dict_different_value, "baz", 3); | |
248 | qdict_put_null(dict_different_value, "null"); | |
249 | ||
250 | qdict_put_int(dict_different_null_key, "f.o", 1); | |
251 | qdict_put_int(dict_different_null_key, "bar", 2); | |
252 | qdict_put_int(dict_different_null_key, "baz", 3); | |
253 | qdict_put_null(dict_different_null_key, "none"); | |
254 | ||
255 | qdict_put_int(dict_longer, "f.o", 1); | |
256 | qdict_put_int(dict_longer, "bar", 2); | |
257 | qdict_put_int(dict_longer, "baz", 3); | |
258 | qdict_put_int(dict_longer, "xyz", 4); | |
259 | qdict_put_null(dict_longer, "null"); | |
260 | ||
261 | qdict_put_int(dict_shorter, "f.o", 1); | |
262 | qdict_put_int(dict_shorter, "bar", 2); | |
263 | qdict_put_int(dict_shorter, "baz", 3); | |
264 | ||
265 | qdict_put(dict_nested, "f", qdict_new()); | |
266 | qdict_put_int(qdict_get_qdict(dict_nested, "f"), "o", 1); | |
267 | qdict_put_int(dict_nested, "bar", 2); | |
268 | qdict_put_int(dict_nested, "baz", 3); | |
269 | qdict_put_null(dict_nested, "null"); | |
270 | ||
271 | dict_cloned = qdict_clone_shallow(dict_0); | |
272 | ||
273 | check_equal(dict_0, dict_1, dict_cloned); | |
274 | check_unequal(dict_0, dict_different_key, dict_different_value, | |
275 | dict_different_null_key, dict_longer, dict_shorter, | |
276 | dict_nested); | |
277 | ||
7dc847eb | 278 | dict_crumpled = qobject_to(QDict, qdict_crumple(dict_1, &local_err)); |
1b76e838 HR |
279 | g_assert(!local_err); |
280 | check_equal(dict_crumpled, dict_nested); | |
281 | ||
282 | qdict_flatten(dict_nested); | |
283 | check_equal(dict_0, dict_nested); | |
284 | ||
285 | /* Containing an NaN value will make this dict compare unequal to | |
286 | * itself */ | |
287 | qdict_put(dict_0, "NaN", qnum_from_double(NAN)); | |
288 | g_assert(qobject_is_equal(QOBJECT(dict_0), QOBJECT(dict_0)) == false); | |
289 | ||
290 | free_all(dict_0, dict_1, dict_cloned, dict_different_key, | |
291 | dict_different_value, dict_different_null_key, dict_longer, | |
292 | dict_shorter, dict_nested, dict_crumpled); | |
293 | } | |
294 | ||
295 | static void qobject_is_equal_conversion_test(void) | |
296 | { | |
297 | QNum *u0, *i0, *d0; | |
298 | QString *s0, *s_empty; | |
299 | QBool *bfalse; | |
300 | ||
301 | u0 = qnum_from_uint(0u); | |
302 | i0 = qnum_from_int(0); | |
303 | d0 = qnum_from_double(0.0); | |
304 | s0 = qstring_from_str("0"); | |
305 | s_empty = qstring_new(); | |
306 | bfalse = qbool_from_bool(false); | |
307 | ||
308 | /* No automatic type conversion */ | |
309 | check_unequal(u0, s0, s_empty, bfalse, qnull(), NULL); | |
310 | check_unequal(i0, s0, s_empty, bfalse, qnull(), NULL); | |
311 | check_unequal(d0, s0, s_empty, bfalse, qnull(), NULL); | |
312 | ||
313 | free_all(u0, i0, d0, s0, s_empty, bfalse); | |
314 | } | |
315 | ||
316 | int main(int argc, char **argv) | |
317 | { | |
318 | g_test_init(&argc, &argv, NULL); | |
319 | ||
320 | g_test_add_func("/public/qobject_is_equal_null", | |
321 | qobject_is_equal_null_test); | |
322 | g_test_add_func("/public/qobject_is_equal_num", qobject_is_equal_num_test); | |
323 | g_test_add_func("/public/qobject_is_equal_bool", | |
324 | qobject_is_equal_bool_test); | |
325 | g_test_add_func("/public/qobject_is_equal_string", | |
326 | qobject_is_equal_string_test); | |
327 | g_test_add_func("/public/qobject_is_equal_list", | |
328 | qobject_is_equal_list_test); | |
329 | g_test_add_func("/public/qobject_is_equal_dict", | |
330 | qobject_is_equal_dict_test); | |
331 | g_test_add_func("/public/qobject_is_equal_conversion", | |
332 | qobject_is_equal_conversion_test); | |
333 | ||
334 | return g_test_run(); | |
335 | } |