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