]>
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" |
d8da9e71 | 11 | #include "qapi/error.h" |
6b673957 | 12 | #include "qapi/qmp/qbool.h" |
452fcdbc | 13 | #include "qapi/qmp/qdict.h" |
47e6b297 | 14 | #include "qapi/qmp/qlist.h" |
15280c36 MA |
15 | #include "qapi/qmp/qnull.h" |
16 | #include "qapi/qmp/qnum.h" | |
6b673957 | 17 | #include "qapi/qmp/qstring.h" |
1b76e838 HR |
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 | ||
1b76e838 HR |
76 | static void qobject_is_equal_null_test(void) |
77 | { | |
78 | check_unequal(qnull(), NULL); | |
79 | } | |
80 | ||
81 | static void qobject_is_equal_num_test(void) | |
82 | { | |
b307e505 MAL |
83 | g_autoptr(QNum) u0 = qnum_from_uint(0u); |
84 | g_autoptr(QNum) i0 = qnum_from_int(0); | |
85 | g_autoptr(QNum) d0 = qnum_from_double(0.0); | |
86 | g_autoptr(QNum) dnan = qnum_from_double(NAN); | |
87 | g_autoptr(QNum) um42 = qnum_from_uint((uint64_t)-42); | |
88 | g_autoptr(QNum) im42 = qnum_from_int(-42); | |
89 | g_autoptr(QNum) dm42 = qnum_from_double(-42.0); | |
1b76e838 | 90 | |
1b76e838 HR |
91 | |
92 | /* Integers representing a mathematically equal number should | |
93 | * compare equal */ | |
94 | check_equal(u0, i0); | |
95 | /* Doubles, however, are always unequal to integers */ | |
96 | check_unequal(u0, d0); | |
97 | check_unequal(i0, d0); | |
98 | ||
99 | /* Do not assume any object is equal to itself -- note however | |
100 | * that NaN cannot occur in a JSON object anyway. */ | |
101 | g_assert(qobject_is_equal(QOBJECT(dnan), QOBJECT(dnan)) == false); | |
102 | ||
103 | /* No unsigned overflow */ | |
104 | check_unequal(um42, im42); | |
105 | check_unequal(um42, dm42); | |
106 | check_unequal(im42, dm42); | |
1b76e838 HR |
107 | } |
108 | ||
109 | static void qobject_is_equal_bool_test(void) | |
110 | { | |
b307e505 MAL |
111 | g_autoptr(QBool) btrue_0 = qbool_from_bool(true); |
112 | g_autoptr(QBool) btrue_1 = qbool_from_bool(true); | |
113 | g_autoptr(QBool) bfalse_0 = qbool_from_bool(false); | |
114 | g_autoptr(QBool) bfalse_1 = qbool_from_bool(false); | |
1b76e838 HR |
115 | |
116 | check_equal(btrue_0, btrue_1); | |
117 | check_equal(bfalse_0, bfalse_1); | |
118 | check_unequal(btrue_0, bfalse_0); | |
1b76e838 HR |
119 | } |
120 | ||
121 | static void qobject_is_equal_string_test(void) | |
122 | { | |
b307e505 MAL |
123 | g_autoptr(QString) str_base = qstring_from_str("foo"); |
124 | g_autoptr(QString) str_whitespace_0 = qstring_from_str(" foo"); | |
125 | g_autoptr(QString) str_whitespace_1 = qstring_from_str("foo "); | |
126 | g_autoptr(QString) str_whitespace_2 = qstring_from_str("foo\b"); | |
127 | g_autoptr(QString) str_whitespace_3 = qstring_from_str("fooo\b"); | |
128 | g_autoptr(QString) str_case = qstring_from_str("Foo"); | |
1b76e838 | 129 | /* Should yield "foo" */ |
b307e505 | 130 | g_autoptr(QString) str_built = qstring_from_substr("buffoon", 3, 6); |
1b76e838 HR |
131 | |
132 | check_unequal(str_base, str_whitespace_0, str_whitespace_1, | |
133 | str_whitespace_2, str_whitespace_3, str_case); | |
134 | ||
135 | check_equal(str_base, str_built); | |
1b76e838 HR |
136 | } |
137 | ||
138 | static void qobject_is_equal_list_test(void) | |
139 | { | |
b307e505 MAL |
140 | g_autoptr(QList) list_0 = qlist_new(); |
141 | g_autoptr(QList) list_1 = qlist_new(); | |
142 | g_autoptr(QList) list_reordered = qlist_new(); | |
143 | g_autoptr(QList) list_longer = qlist_new(); | |
144 | g_autoptr(QList) list_shorter = qlist_new(); | |
145 | g_autoptr(QList) list_cloned = NULL; | |
1b76e838 HR |
146 | |
147 | qlist_append_int(list_0, 1); | |
148 | qlist_append_int(list_0, 2); | |
149 | qlist_append_int(list_0, 3); | |
150 | ||
151 | qlist_append_int(list_1, 1); | |
152 | qlist_append_int(list_1, 2); | |
153 | qlist_append_int(list_1, 3); | |
154 | ||
155 | qlist_append_int(list_reordered, 1); | |
156 | qlist_append_int(list_reordered, 3); | |
157 | qlist_append_int(list_reordered, 2); | |
158 | ||
159 | qlist_append_int(list_longer, 1); | |
160 | qlist_append_int(list_longer, 2); | |
161 | qlist_append_int(list_longer, 3); | |
162 | qlist_append_null(list_longer); | |
163 | ||
164 | qlist_append_int(list_shorter, 1); | |
165 | qlist_append_int(list_shorter, 2); | |
166 | ||
167 | list_cloned = qlist_copy(list_0); | |
168 | ||
169 | check_equal(list_0, list_1, list_cloned); | |
170 | check_unequal(list_0, list_reordered, list_longer, list_shorter); | |
171 | ||
172 | /* With a NaN in it, the list should no longer compare equal to | |
173 | * itself */ | |
174 | qlist_append(list_0, qnum_from_double(NAN)); | |
175 | g_assert(qobject_is_equal(QOBJECT(list_0), QOBJECT(list_0)) == false); | |
1b76e838 HR |
176 | } |
177 | ||
178 | static void qobject_is_equal_dict_test(void) | |
179 | { | |
b307e505 | 180 | g_autoptr(QDict) dict_cloned = NULL; |
b307e505 MAL |
181 | g_autoptr(QDict) dict_0 = qdict_new(); |
182 | g_autoptr(QDict) dict_1 = qdict_new(); | |
183 | g_autoptr(QDict) dict_different_key = qdict_new(); | |
184 | g_autoptr(QDict) dict_different_value = qdict_new(); | |
185 | g_autoptr(QDict) dict_different_null_key = qdict_new(); | |
186 | g_autoptr(QDict) dict_longer = qdict_new(); | |
187 | g_autoptr(QDict) dict_shorter = qdict_new(); | |
188 | g_autoptr(QDict) dict_nested = qdict_new(); | |
1b76e838 HR |
189 | |
190 | qdict_put_int(dict_0, "f.o", 1); | |
191 | qdict_put_int(dict_0, "bar", 2); | |
192 | qdict_put_int(dict_0, "baz", 3); | |
193 | qdict_put_null(dict_0, "null"); | |
194 | ||
195 | qdict_put_int(dict_1, "f.o", 1); | |
196 | qdict_put_int(dict_1, "bar", 2); | |
197 | qdict_put_int(dict_1, "baz", 3); | |
198 | qdict_put_null(dict_1, "null"); | |
199 | ||
200 | qdict_put_int(dict_different_key, "F.o", 1); | |
201 | qdict_put_int(dict_different_key, "bar", 2); | |
202 | qdict_put_int(dict_different_key, "baz", 3); | |
203 | qdict_put_null(dict_different_key, "null"); | |
204 | ||
205 | qdict_put_int(dict_different_value, "f.o", 42); | |
206 | qdict_put_int(dict_different_value, "bar", 2); | |
207 | qdict_put_int(dict_different_value, "baz", 3); | |
208 | qdict_put_null(dict_different_value, "null"); | |
209 | ||
210 | qdict_put_int(dict_different_null_key, "f.o", 1); | |
211 | qdict_put_int(dict_different_null_key, "bar", 2); | |
212 | qdict_put_int(dict_different_null_key, "baz", 3); | |
213 | qdict_put_null(dict_different_null_key, "none"); | |
214 | ||
215 | qdict_put_int(dict_longer, "f.o", 1); | |
216 | qdict_put_int(dict_longer, "bar", 2); | |
217 | qdict_put_int(dict_longer, "baz", 3); | |
218 | qdict_put_int(dict_longer, "xyz", 4); | |
219 | qdict_put_null(dict_longer, "null"); | |
220 | ||
221 | qdict_put_int(dict_shorter, "f.o", 1); | |
222 | qdict_put_int(dict_shorter, "bar", 2); | |
223 | qdict_put_int(dict_shorter, "baz", 3); | |
224 | ||
225 | qdict_put(dict_nested, "f", qdict_new()); | |
226 | qdict_put_int(qdict_get_qdict(dict_nested, "f"), "o", 1); | |
227 | qdict_put_int(dict_nested, "bar", 2); | |
228 | qdict_put_int(dict_nested, "baz", 3); | |
229 | qdict_put_null(dict_nested, "null"); | |
230 | ||
231 | dict_cloned = qdict_clone_shallow(dict_0); | |
232 | ||
233 | check_equal(dict_0, dict_1, dict_cloned); | |
234 | check_unequal(dict_0, dict_different_key, dict_different_value, | |
235 | dict_different_null_key, dict_longer, dict_shorter, | |
236 | dict_nested); | |
237 | ||
1b76e838 HR |
238 | /* Containing an NaN value will make this dict compare unequal to |
239 | * itself */ | |
240 | qdict_put(dict_0, "NaN", qnum_from_double(NAN)); | |
241 | g_assert(qobject_is_equal(QOBJECT(dict_0), QOBJECT(dict_0)) == false); | |
1b76e838 HR |
242 | } |
243 | ||
244 | static void qobject_is_equal_conversion_test(void) | |
245 | { | |
b307e505 MAL |
246 | g_autoptr(QNum) u0 = qnum_from_uint(0u); |
247 | g_autoptr(QNum) i0 = qnum_from_int(0); | |
248 | g_autoptr(QNum) d0 = qnum_from_double(0.0); | |
249 | g_autoptr(QString) s0 = qstring_from_str("0"); | |
250 | g_autoptr(QString) s_empty = qstring_new(); | |
251 | g_autoptr(QBool) bfalse = qbool_from_bool(false); | |
1b76e838 HR |
252 | |
253 | /* No automatic type conversion */ | |
254 | check_unequal(u0, s0, s_empty, bfalse, qnull(), NULL); | |
255 | check_unequal(i0, s0, s_empty, bfalse, qnull(), NULL); | |
256 | check_unequal(d0, s0, s_empty, bfalse, qnull(), NULL); | |
1b76e838 HR |
257 | } |
258 | ||
259 | int main(int argc, char **argv) | |
260 | { | |
261 | g_test_init(&argc, &argv, NULL); | |
262 | ||
263 | g_test_add_func("/public/qobject_is_equal_null", | |
264 | qobject_is_equal_null_test); | |
265 | g_test_add_func("/public/qobject_is_equal_num", qobject_is_equal_num_test); | |
266 | g_test_add_func("/public/qobject_is_equal_bool", | |
267 | qobject_is_equal_bool_test); | |
268 | g_test_add_func("/public/qobject_is_equal_string", | |
269 | qobject_is_equal_string_test); | |
270 | g_test_add_func("/public/qobject_is_equal_list", | |
271 | qobject_is_equal_list_test); | |
272 | g_test_add_func("/public/qobject_is_equal_dict", | |
273 | qobject_is_equal_dict_test); | |
274 | g_test_add_func("/public/qobject_is_equal_conversion", | |
275 | qobject_is_equal_conversion_test); | |
276 | ||
277 | return g_test_run(); | |
278 | } |