* Move unit and bench tests into separate directories

* Clean-up and improve gitlab-ci jobs
 * Drop the non-working "check-speed" makefile target
 * Minor documentation updates
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCAAvFiEEJ7iIR+7gJQEY8+q5LtnXdP5wLbUFAmBLonURHHRodXRoQHJl
 ZGhhdC5jb20ACgkQLtnXdP5wLbVsZg/9HoRP+nUoz9y7d2/GpXP01zj5xtatMadW
 RFUAlCqJapWyDXlzXVlKmvjIQeBa1uvLv6eBbAr9E748Aurmwrpw+TZ0rvIgbZrG
 fNYwixBjWcf8BtxoqRki/zdjsBV9Lym7H0G/QxYxOfve8IdHntE5iVnI0hCpaNge
 E2SLAXlFsKvC7MbLtm7+KRyIyA4PAshqJWgH+T2mdOcAA6SudLU4SidyPQJo02LX
 8U2NP2zB/5VldFGtc3TuFmoWhCSctfa3ntmNTBUBfJAEoB7hUTdKH3naZ52Qd2X9
 nAEWJAh7yiXnClz8Ant/QywvM+OjxmxVWz2Kc/1jJYqLQVGB1e8JBWH6pJxboDJb
 8z+G5SYAw5woOE+ya57doPyDbi9EHn5O6K5FIjhn/rc5YJp6SA9ilG89E3vrhr/m
 RMTCvm2nrPxenXXSovKi1t5SB6X8uyNKOuAXgEef8T28c03ZSZbs6u/YTDjPHSkp
 lU9PGxr+Ft2on+GzSXoC1HBzLyesiTsjF7leE4aB9jOGgnqCMiJsALyx/XWq5x1m
 wtpp7TcObB7p9DZ2Kf4601CoVaVO2ESjor9T81JLDtqVrlD0VHhuFGsFvOlo/M2k
 JePmg93CoX+ptP47Pyou8fnjkwCI4raM55s1SsYvVqdg2Rs1Btj6CwIqc2QEjn7N
 KIXZ7HG4i80=
 =YxBC
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/thuth-gitlab/tags/pull-request-2021-03-12' into staging

* Move unit and bench tests into separate directories
* Clean-up and improve gitlab-ci jobs
* Drop the non-working "check-speed" makefile target
* Minor documentation updates

# gpg: Signature made Fri 12 Mar 2021 17:18:45 GMT
# gpg:                using RSA key 27B88847EEE0250118F3EAB92ED9D774FE702DB5
# gpg:                issuer "thuth@redhat.com"
# gpg: Good signature from "Thomas Huth <th.huth@gmx.de>" [full]
# gpg:                 aka "Thomas Huth <thuth@redhat.com>" [full]
# gpg:                 aka "Thomas Huth <huth@tuxfamily.org>" [full]
# gpg:                 aka "Thomas Huth <th.huth@posteo.de>" [unknown]
# Primary key fingerprint: 27B8 8847 EEE0 2501 18F3  EAB9 2ED9 D774 FE70 2DB5

* remotes/thuth-gitlab/tags/pull-request-2021-03-12:
  README: Add Documentation blurb
  MAINTAINERS: Merge the Gitlab-CI section into the generic CI section
  tests: remove "make check-speed" in favor of "make bench"
  gitlab-ci.yml: Merge check-crypto-old jobs into the build-crypto-old jobs
  gitlab-ci.yml: Merge one of the coroutine jobs with the tcg-disabled job
  gitlab-ci.yml: Add some missing dependencies to the jobs
  gitlab-ci.yml: Move build-tools-and-docs-debian to a better place
  tests: Move benchmarks into a separate folder
  tests: Move unit tests into a separate directory

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2021-03-14 15:13:53 +00:00
commit 757acb9a82
116 changed files with 311 additions and 338 deletions

View file

@ -0,0 +1,712 @@
/*
* Unit-tests for Block layer QDict extras
*
* Copyright (c) 2013-2018 Red Hat, Inc.
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "block/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qnum.h"
#include "qapi/error.h"
static void qdict_defaults_test(void)
{
QDict *dict, *copy;
dict = qdict_new();
copy = qdict_new();
qdict_set_default_str(dict, "foo", "abc");
qdict_set_default_str(dict, "foo", "def");
g_assert_cmpstr(qdict_get_str(dict, "foo"), ==, "abc");
qdict_set_default_str(dict, "bar", "ghi");
qdict_copy_default(copy, dict, "foo");
g_assert_cmpstr(qdict_get_str(copy, "foo"), ==, "abc");
qdict_set_default_str(copy, "bar", "xyz");
qdict_copy_default(copy, dict, "bar");
g_assert_cmpstr(qdict_get_str(copy, "bar"), ==, "xyz");
qobject_unref(copy);
qobject_unref(dict);
}
static void qdict_flatten_test(void)
{
QList *e_1 = qlist_new();
QList *e = qlist_new();
QDict *e_1_2 = qdict_new();
QDict *f = qdict_new();
QList *y = qlist_new();
QDict *z = qdict_new();
QDict *root = qdict_new();
/*
* Test the flattening of
*
* {
* "e": [
* 42,
* [
* 23,
* 66,
* {
* "a": 0,
* "b": 1
* }
* ]
* ],
* "f": {
* "c": 2,
* "d": 3,
* },
* "g": 4,
* "y": [{}],
* "z": {"a": []}
* }
*
* to
*
* {
* "e.0": 42,
* "e.1.0": 23,
* "e.1.1": 66,
* "e.1.2.a": 0,
* "e.1.2.b": 1,
* "f.c": 2,
* "f.d": 3,
* "g": 4,
* "y.0": {},
* "z.a": []
* }
*/
qdict_put_int(e_1_2, "a", 0);
qdict_put_int(e_1_2, "b", 1);
qlist_append_int(e_1, 23);
qlist_append_int(e_1, 66);
qlist_append(e_1, e_1_2);
qlist_append_int(e, 42);
qlist_append(e, e_1);
qdict_put_int(f, "c", 2);
qdict_put_int(f, "d", 3);
qlist_append(y, qdict_new());
qdict_put(z, "a", qlist_new());
qdict_put(root, "e", e);
qdict_put(root, "f", f);
qdict_put_int(root, "g", 4);
qdict_put(root, "y", y);
qdict_put(root, "z", z);
qdict_flatten(root);
g_assert(qdict_get_int(root, "e.0") == 42);
g_assert(qdict_get_int(root, "e.1.0") == 23);
g_assert(qdict_get_int(root, "e.1.1") == 66);
g_assert(qdict_get_int(root, "e.1.2.a") == 0);
g_assert(qdict_get_int(root, "e.1.2.b") == 1);
g_assert(qdict_get_int(root, "f.c") == 2);
g_assert(qdict_get_int(root, "f.d") == 3);
g_assert(qdict_get_int(root, "g") == 4);
g_assert(!qdict_size(qdict_get_qdict(root, "y.0")));
g_assert(qlist_empty(qdict_get_qlist(root, "z.a")));
g_assert(qdict_size(root) == 10);
qobject_unref(root);
}
static void qdict_clone_flatten_test(void)
{
QDict *dict1 = qdict_new();
QDict *dict2 = qdict_new();
QDict *cloned_dict1;
/*
* Test that we can clone and flatten
* { "a": { "b": 42 } }
* without modifying the clone.
*/
qdict_put_int(dict2, "b", 42);
qdict_put(dict1, "a", dict2);
cloned_dict1 = qdict_clone_shallow(dict1);
qdict_flatten(dict1);
g_assert(qdict_size(dict1) == 1);
g_assert(qdict_get_int(dict1, "a.b") == 42);
g_assert(qdict_size(cloned_dict1) == 1);
g_assert(qdict_get_qdict(cloned_dict1, "a") == dict2);
g_assert(qdict_size(dict2) == 1);
g_assert(qdict_get_int(dict2, "b") == 42);
qobject_unref(dict1);
qobject_unref(cloned_dict1);
}
static void qdict_array_split_test(void)
{
QDict *test_dict = qdict_new();
QDict *dict1, *dict2;
QNum *int1;
QList *test_list;
/*
* Test the split of
*
* {
* "1.x": 0,
* "4.y": 1,
* "0.a": 42,
* "o.o": 7,
* "0.b": 23,
* "2": 66
* }
*
* to
*
* [
* {
* "a": 42,
* "b": 23
* },
* {
* "x": 0
* },
* 66
* ]
*
* and
*
* {
* "4.y": 1,
* "o.o": 7
* }
*
* (remaining in the old QDict)
*
* This example is given in the comment of qdict_array_split().
*/
qdict_put_int(test_dict, "1.x", 0);
qdict_put_int(test_dict, "4.y", 1);
qdict_put_int(test_dict, "0.a", 42);
qdict_put_int(test_dict, "o.o", 7);
qdict_put_int(test_dict, "0.b", 23);
qdict_put_int(test_dict, "2", 66);
qdict_array_split(test_dict, &test_list);
dict1 = qobject_to(QDict, qlist_pop(test_list));
dict2 = qobject_to(QDict, qlist_pop(test_list));
int1 = qobject_to(QNum, qlist_pop(test_list));
g_assert(dict1);
g_assert(dict2);
g_assert(int1);
g_assert(qlist_empty(test_list));
qobject_unref(test_list);
g_assert(qdict_get_int(dict1, "a") == 42);
g_assert(qdict_get_int(dict1, "b") == 23);
g_assert(qdict_size(dict1) == 2);
qobject_unref(dict1);
g_assert(qdict_get_int(dict2, "x") == 0);
g_assert(qdict_size(dict2) == 1);
qobject_unref(dict2);
g_assert_cmpint(qnum_get_int(int1), ==, 66);
qobject_unref(int1);
g_assert(qdict_get_int(test_dict, "4.y") == 1);
g_assert(qdict_get_int(test_dict, "o.o") == 7);
g_assert(qdict_size(test_dict) == 2);
qobject_unref(test_dict);
/*
* Test the split of
*
* {
* "0": 42,
* "1": 23,
* "1.x": 84
* }
*
* to
*
* [
* 42
* ]
*
* and
*
* {
* "1": 23,
* "1.x": 84
* }
*
* That is, test whether splitting stops if there is both an entry with key
* of "%u" and other entries with keys prefixed "%u." for the same index.
*/
test_dict = qdict_new();
qdict_put_int(test_dict, "0", 42);
qdict_put_int(test_dict, "1", 23);
qdict_put_int(test_dict, "1.x", 84);
qdict_array_split(test_dict, &test_list);
int1 = qobject_to(QNum, qlist_pop(test_list));
g_assert(int1);
g_assert(qlist_empty(test_list));
qobject_unref(test_list);
g_assert_cmpint(qnum_get_int(int1), ==, 42);
qobject_unref(int1);
g_assert(qdict_get_int(test_dict, "1") == 23);
g_assert(qdict_get_int(test_dict, "1.x") == 84);
g_assert(qdict_size(test_dict) == 2);
qobject_unref(test_dict);
}
static void qdict_array_entries_test(void)
{
QDict *dict = qdict_new();
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 0);
qdict_put_int(dict, "bar", 0);
qdict_put_int(dict, "baz.0", 0);
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 0);
qdict_put_int(dict, "foo.1", 0);
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, -EINVAL);
qdict_put_int(dict, "foo.0", 0);
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 2);
qdict_put_int(dict, "foo.bar", 0);
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, -EINVAL);
qdict_del(dict, "foo.bar");
qdict_put_int(dict, "foo.2.a", 0);
qdict_put_int(dict, "foo.2.b", 0);
qdict_put_int(dict, "foo.2.c", 0);
g_assert_cmpint(qdict_array_entries(dict, "foo."), ==, 3);
g_assert_cmpint(qdict_array_entries(dict, ""), ==, -EINVAL);
qobject_unref(dict);
dict = qdict_new();
qdict_put_int(dict, "1", 0);
g_assert_cmpint(qdict_array_entries(dict, ""), ==, -EINVAL);
qdict_put_int(dict, "0", 0);
g_assert_cmpint(qdict_array_entries(dict, ""), ==, 2);
qdict_put_int(dict, "bar", 0);
g_assert_cmpint(qdict_array_entries(dict, ""), ==, -EINVAL);
qdict_del(dict, "bar");
qdict_put_int(dict, "2.a", 0);
qdict_put_int(dict, "2.b", 0);
qdict_put_int(dict, "2.c", 0);
g_assert_cmpint(qdict_array_entries(dict, ""), ==, 3);
qobject_unref(dict);
}
static void qdict_join_test(void)
{
QDict *dict1, *dict2;
bool overwrite = false;
int i;
dict1 = qdict_new();
dict2 = qdict_new();
/* Test everything once without overwrite and once with */
do {
/* Test empty dicts */
qdict_join(dict1, dict2, overwrite);
g_assert(qdict_size(dict1) == 0);
g_assert(qdict_size(dict2) == 0);
/* First iteration: Test movement */
/* Second iteration: Test empty source and non-empty destination */
qdict_put_int(dict2, "foo", 42);
for (i = 0; i < 2; i++) {
qdict_join(dict1, dict2, overwrite);
g_assert(qdict_size(dict1) == 1);
g_assert(qdict_size(dict2) == 0);
g_assert(qdict_get_int(dict1, "foo") == 42);
}
/* Test non-empty source and destination without conflict */
qdict_put_int(dict2, "bar", 23);
qdict_join(dict1, dict2, overwrite);
g_assert(qdict_size(dict1) == 2);
g_assert(qdict_size(dict2) == 0);
g_assert(qdict_get_int(dict1, "foo") == 42);
g_assert(qdict_get_int(dict1, "bar") == 23);
/* Test conflict */
qdict_put_int(dict2, "foo", 84);
qdict_join(dict1, dict2, overwrite);
g_assert(qdict_size(dict1) == 2);
g_assert(qdict_size(dict2) == !overwrite);
g_assert(qdict_get_int(dict1, "foo") == (overwrite ? 84 : 42));
g_assert(qdict_get_int(dict1, "bar") == 23);
if (!overwrite) {
g_assert(qdict_get_int(dict2, "foo") == 84);
}
/* Check the references */
g_assert(qdict_get(dict1, "foo")->base.refcnt == 1);
g_assert(qdict_get(dict1, "bar")->base.refcnt == 1);
if (!overwrite) {
g_assert(qdict_get(dict2, "foo")->base.refcnt == 1);
}
/* Clean up */
qdict_del(dict1, "foo");
qdict_del(dict1, "bar");
if (!overwrite) {
qdict_del(dict2, "foo");
}
} while (overwrite ^= true);
qobject_unref(dict1);
qobject_unref(dict2);
}
static void qdict_crumple_test_recursive(void)
{
QDict *src, *dst, *rule, *vnc, *acl, *listen;
QDict *empty, *empty_dict, *empty_list_0;
QList *rules, *empty_list, *empty_dict_a;
src = qdict_new();
qdict_put_str(src, "vnc.listen.addr", "127.0.0.1");
qdict_put_str(src, "vnc.listen.port", "5901");
qdict_put_str(src, "vnc.acl.rules.0.match", "fred");
qdict_put_str(src, "vnc.acl.rules.0.policy", "allow");
qdict_put_str(src, "vnc.acl.rules.1.match", "bob");
qdict_put_str(src, "vnc.acl.rules.1.policy", "deny");
qdict_put_str(src, "vnc.acl.default", "deny");
qdict_put_str(src, "vnc.acl..name", "acl0");
qdict_put_str(src, "vnc.acl.rule..name", "acl0");
qdict_put(src, "empty.dict.a", qlist_new());
qdict_put(src, "empty.list.0", qdict_new());
dst = qobject_to(QDict, qdict_crumple(src, &error_abort));
g_assert(dst);
g_assert_cmpint(qdict_size(dst), ==, 2);
vnc = qdict_get_qdict(dst, "vnc");
g_assert(vnc);
g_assert_cmpint(qdict_size(vnc), ==, 3);
listen = qdict_get_qdict(vnc, "listen");
g_assert(listen);
g_assert_cmpint(qdict_size(listen), ==, 2);
g_assert_cmpstr("127.0.0.1", ==, qdict_get_str(listen, "addr"));
g_assert_cmpstr("5901", ==, qdict_get_str(listen, "port"));
acl = qdict_get_qdict(vnc, "acl");
g_assert(acl);
g_assert_cmpint(qdict_size(acl), ==, 3);
rules = qdict_get_qlist(acl, "rules");
g_assert(rules);
g_assert_cmpint(qlist_size(rules), ==, 2);
rule = qobject_to(QDict, qlist_pop(rules));
g_assert(rule);
g_assert_cmpint(qdict_size(rule), ==, 2);
g_assert_cmpstr("fred", ==, qdict_get_str(rule, "match"));
g_assert_cmpstr("allow", ==, qdict_get_str(rule, "policy"));
qobject_unref(rule);
rule = qobject_to(QDict, qlist_pop(rules));
g_assert(rule);
g_assert_cmpint(qdict_size(rule), ==, 2);
g_assert_cmpstr("bob", ==, qdict_get_str(rule, "match"));
g_assert_cmpstr("deny", ==, qdict_get_str(rule, "policy"));
qobject_unref(rule);
/* With recursive crumpling, we should see all names unescaped */
g_assert_cmpstr("acl0", ==, qdict_get_str(vnc, "acl.name"));
g_assert_cmpstr("acl0", ==, qdict_get_str(acl, "rule.name"));
empty = qdict_get_qdict(dst, "empty");
g_assert(empty);
g_assert_cmpint(qdict_size(empty), ==, 2);
empty_dict = qdict_get_qdict(empty, "dict");
g_assert(empty_dict);
g_assert_cmpint(qdict_size(empty_dict), ==, 1);
empty_dict_a = qdict_get_qlist(empty_dict, "a");
g_assert(empty_dict_a && qlist_empty(empty_dict_a));
empty_list = qdict_get_qlist(empty, "list");
g_assert(empty_list);
g_assert_cmpint(qlist_size(empty_list), ==, 1);
empty_list_0 = qobject_to(QDict, qlist_pop(empty_list));
g_assert(empty_list_0);
g_assert_cmpint(qdict_size(empty_list_0), ==, 0);
qobject_unref(empty_list_0);
qobject_unref(src);
qobject_unref(dst);
}
static void qdict_crumple_test_empty(void)
{
QDict *src, *dst;
src = qdict_new();
dst = qobject_to(QDict, qdict_crumple(src, &error_abort));
g_assert_cmpint(qdict_size(dst), ==, 0);
qobject_unref(src);
qobject_unref(dst);
}
static int qdict_count_entries(QDict *dict)
{
const QDictEntry *e;
int count = 0;
for (e = qdict_first(dict); e; e = qdict_next(dict, e)) {
count++;
}
return count;
}
static void qdict_rename_keys_test(void)
{
QDict *dict = qdict_new();
QDict *copy;
QDictRenames *renames;
Error *local_err = NULL;
qdict_put_str(dict, "abc", "foo");
qdict_put_str(dict, "abcdef", "bar");
qdict_put_int(dict, "number", 42);
qdict_put_bool(dict, "flag", true);
qdict_put_null(dict, "nothing");
/* Empty rename list */
renames = (QDictRenames[]) {
{ NULL, "this can be anything" }
};
copy = qdict_clone_shallow(dict);
qdict_rename_keys(copy, renames, &error_abort);
g_assert_cmpstr(qdict_get_str(copy, "abc"), ==, "foo");
g_assert_cmpstr(qdict_get_str(copy, "abcdef"), ==, "bar");
g_assert_cmpint(qdict_get_int(copy, "number"), ==, 42);
g_assert_cmpint(qdict_get_bool(copy, "flag"), ==, true);
g_assert(qobject_type(qdict_get(copy, "nothing")) == QTYPE_QNULL);
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
qobject_unref(copy);
/* Simple rename of all entries */
renames = (QDictRenames[]) {
{ "abc", "str1" },
{ "abcdef", "str2" },
{ "number", "int" },
{ "flag", "bool" },
{ "nothing", "null" },
{ NULL , NULL }
};
copy = qdict_clone_shallow(dict);
qdict_rename_keys(copy, renames, &error_abort);
g_assert(!qdict_haskey(copy, "abc"));
g_assert(!qdict_haskey(copy, "abcdef"));
g_assert(!qdict_haskey(copy, "number"));
g_assert(!qdict_haskey(copy, "flag"));
g_assert(!qdict_haskey(copy, "nothing"));
g_assert_cmpstr(qdict_get_str(copy, "str1"), ==, "foo");
g_assert_cmpstr(qdict_get_str(copy, "str2"), ==, "bar");
g_assert_cmpint(qdict_get_int(copy, "int"), ==, 42);
g_assert_cmpint(qdict_get_bool(copy, "bool"), ==, true);
g_assert(qobject_type(qdict_get(copy, "null")) == QTYPE_QNULL);
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
qobject_unref(copy);
/* Renames are processed top to bottom */
renames = (QDictRenames[]) {
{ "abc", "tmp" },
{ "abcdef", "abc" },
{ "number", "abcdef" },
{ "flag", "number" },
{ "nothing", "flag" },
{ "tmp", "nothing" },
{ NULL , NULL }
};
copy = qdict_clone_shallow(dict);
qdict_rename_keys(copy, renames, &error_abort);
g_assert_cmpstr(qdict_get_str(copy, "nothing"), ==, "foo");
g_assert_cmpstr(qdict_get_str(copy, "abc"), ==, "bar");
g_assert_cmpint(qdict_get_int(copy, "abcdef"), ==, 42);
g_assert_cmpint(qdict_get_bool(copy, "number"), ==, true);
g_assert(qobject_type(qdict_get(copy, "flag")) == QTYPE_QNULL);
g_assert(!qdict_haskey(copy, "tmp"));
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
qobject_unref(copy);
/* Conflicting rename */
renames = (QDictRenames[]) {
{ "abcdef", "abc" },
{ NULL , NULL }
};
copy = qdict_clone_shallow(dict);
qdict_rename_keys(copy, renames, &local_err);
error_free_or_abort(&local_err);
g_assert_cmpstr(qdict_get_str(copy, "abc"), ==, "foo");
g_assert_cmpstr(qdict_get_str(copy, "abcdef"), ==, "bar");
g_assert_cmpint(qdict_get_int(copy, "number"), ==, 42);
g_assert_cmpint(qdict_get_bool(copy, "flag"), ==, true);
g_assert(qobject_type(qdict_get(copy, "nothing")) == QTYPE_QNULL);
g_assert_cmpint(qdict_count_entries(copy), ==, 5);
qobject_unref(copy);
/* Renames in an empty dict */
renames = (QDictRenames[]) {
{ "abcdef", "abc" },
{ NULL , NULL }
};
qobject_unref(dict);
dict = qdict_new();
qdict_rename_keys(dict, renames, &error_abort);
g_assert(qdict_first(dict) == NULL);
qobject_unref(dict);
}
static void qdict_crumple_test_bad_inputs(void)
{
QDict *src, *nested;
Error *error = NULL;
src = qdict_new();
/* rule.0 can't be both a string and a dict */
qdict_put_str(src, "rule.0", "fred");
qdict_put_str(src, "rule.0.policy", "allow");
g_assert(qdict_crumple(src, &error) == NULL);
error_free_or_abort(&error);
qobject_unref(src);
src = qdict_new();
/* rule can't be both a list and a dict */
qdict_put_str(src, "rule.0", "fred");
qdict_put_str(src, "rule.a", "allow");
g_assert(qdict_crumple(src, &error) == NULL);
error_free_or_abort(&error);
qobject_unref(src);
src = qdict_new();
/* The input should be flat, ie no dicts or lists */
nested = qdict_new();
qdict_put(nested, "x", qdict_new());
qdict_put(src, "rule.a", nested);
qdict_put_str(src, "rule.b", "allow");
g_assert(qdict_crumple(src, &error) == NULL);
error_free_or_abort(&error);
qobject_unref(src);
src = qdict_new();
/* List indexes must not have gaps */
qdict_put_str(src, "rule.0", "deny");
qdict_put_str(src, "rule.3", "allow");
g_assert(qdict_crumple(src, &error) == NULL);
error_free_or_abort(&error);
qobject_unref(src);
src = qdict_new();
/* List indexes must be in %zu format */
qdict_put_str(src, "rule.0", "deny");
qdict_put_str(src, "rule.+1", "allow");
g_assert(qdict_crumple(src, &error) == NULL);
error_free_or_abort(&error);
qobject_unref(src);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/public/defaults", qdict_defaults_test);
g_test_add_func("/public/flatten", qdict_flatten_test);
g_test_add_func("/public/clone_flatten", qdict_clone_flatten_test);
g_test_add_func("/public/array_split", qdict_array_split_test);
g_test_add_func("/public/array_entries", qdict_array_entries_test);
g_test_add_func("/public/join", qdict_join_test);
g_test_add_func("/public/crumple/recursive",
qdict_crumple_test_recursive);
g_test_add_func("/public/crumple/empty",
qdict_crumple_test_empty);
g_test_add_func("/public/crumple/bad_inputs",
qdict_crumple_test_bad_inputs);
g_test_add_func("/public/rename_keys", qdict_rename_keys_test);
return g_test_run();
}

379
tests/unit/check-qdict.c Normal file
View file

@ -0,0 +1,379 @@
/*
* QDict unit-tests.
*
* Copyright (C) 2009 Red Hat Inc.
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qstring.h"
/*
* Public Interface test-cases
*
* (with some violations to access 'private' data)
*/
static void qdict_new_test(void)
{
QDict *qdict;
qdict = qdict_new();
g_assert(qdict != NULL);
g_assert(qdict_size(qdict) == 0);
g_assert(qdict->base.refcnt == 1);
g_assert(qobject_type(QOBJECT(qdict)) == QTYPE_QDICT);
qobject_unref(qdict);
}
static void qdict_put_obj_test(void)
{
QNum *qn;
QDict *qdict;
QDictEntry *ent;
const int num = 42;
qdict = qdict_new();
// key "" will have tdb hash 12345
qdict_put_int(qdict, "", num);
g_assert(qdict_size(qdict) == 1);
ent = QLIST_FIRST(&qdict->table[12345 % QDICT_BUCKET_MAX]);
qn = qobject_to(QNum, ent->value);
g_assert_cmpint(qnum_get_int(qn), ==, num);
qobject_unref(qdict);
}
static void qdict_destroy_simple_test(void)
{
QDict *qdict;
qdict = qdict_new();
qdict_put_int(qdict, "num", 0);
qdict_put_str(qdict, "str", "foo");
qobject_unref(qdict);
}
static void qdict_get_test(void)
{
QNum *qn;
QObject *obj;
const int value = -42;
const char *key = "test";
QDict *tests_dict = qdict_new();
qdict_put_int(tests_dict, key, value);
obj = qdict_get(tests_dict, key);
g_assert(obj != NULL);
qn = qobject_to(QNum, obj);
g_assert_cmpint(qnum_get_int(qn), ==, value);
qobject_unref(tests_dict);
}
static void qdict_get_int_test(void)
{
int ret;
const int value = 100;
const char *key = "int";
QDict *tests_dict = qdict_new();
qdict_put_int(tests_dict, key, value);
ret = qdict_get_int(tests_dict, key);
g_assert(ret == value);
qobject_unref(tests_dict);
}
static void qdict_get_try_int_test(void)
{
int ret;
const int value = 100;
const char *key = "int";
QDict *tests_dict = qdict_new();
qdict_put_int(tests_dict, key, value);
qdict_put_str(tests_dict, "string", "test");
ret = qdict_get_try_int(tests_dict, key, 0);
g_assert(ret == value);
ret = qdict_get_try_int(tests_dict, "missing", -42);
g_assert_cmpuint(ret, ==, -42);
ret = qdict_get_try_int(tests_dict, "string", -42);
g_assert_cmpuint(ret, ==, -42);
qobject_unref(tests_dict);
}
static void qdict_get_str_test(void)
{
const char *p;
const char *key = "key";
const char *str = "string";
QDict *tests_dict = qdict_new();
qdict_put_str(tests_dict, key, str);
p = qdict_get_str(tests_dict, key);
g_assert(p != NULL);
g_assert(strcmp(p, str) == 0);
qobject_unref(tests_dict);
}
static void qdict_get_try_str_test(void)
{
const char *p;
const char *key = "key";
const char *str = "string";
QDict *tests_dict = qdict_new();
qdict_put_str(tests_dict, key, str);
p = qdict_get_try_str(tests_dict, key);
g_assert(p != NULL);
g_assert(strcmp(p, str) == 0);
qobject_unref(tests_dict);
}
static void qdict_haskey_not_test(void)
{
QDict *tests_dict = qdict_new();
g_assert(qdict_haskey(tests_dict, "test") == 0);
qobject_unref(tests_dict);
}
static void qdict_haskey_test(void)
{
const char *key = "test";
QDict *tests_dict = qdict_new();
qdict_put_int(tests_dict, key, 0);
g_assert(qdict_haskey(tests_dict, key) == 1);
qobject_unref(tests_dict);
}
static void qdict_del_test(void)
{
const char *key = "key test";
QDict *tests_dict = qdict_new();
qdict_put_str(tests_dict, key, "foo");
g_assert(qdict_size(tests_dict) == 1);
qdict_del(tests_dict, key);
g_assert(qdict_size(tests_dict) == 0);
g_assert(qdict_haskey(tests_dict, key) == 0);
qobject_unref(tests_dict);
}
static void qobject_to_qdict_test(void)
{
QDict *tests_dict = qdict_new();
g_assert(qobject_to(QDict, QOBJECT(tests_dict)) == tests_dict);
qobject_unref(tests_dict);
}
static void qdict_iterapi_test(void)
{
int count;
const QDictEntry *ent;
QDict *tests_dict = qdict_new();
g_assert(qdict_first(tests_dict) == NULL);
qdict_put_int(tests_dict, "key1", 1);
qdict_put_int(tests_dict, "key2", 2);
qdict_put_int(tests_dict, "key3", 3);
count = 0;
for (ent = qdict_first(tests_dict); ent; ent = qdict_next(tests_dict, ent)){
g_assert(qdict_haskey(tests_dict, qdict_entry_key(ent)) == 1);
count++;
}
g_assert(count == qdict_size(tests_dict));
/* Do it again to test restarting */
count = 0;
for (ent = qdict_first(tests_dict); ent; ent = qdict_next(tests_dict, ent)){
g_assert(qdict_haskey(tests_dict, qdict_entry_key(ent)) == 1);
count++;
}
g_assert(count == qdict_size(tests_dict));
qobject_unref(tests_dict);
}
/*
* Errors test-cases
*/
static void qdict_put_exists_test(void)
{
int value;
const char *key = "exists";
QDict *tests_dict = qdict_new();
qdict_put_int(tests_dict, key, 1);
qdict_put_int(tests_dict, key, 2);
value = qdict_get_int(tests_dict, key);
g_assert(value == 2);
g_assert(qdict_size(tests_dict) == 1);
qobject_unref(tests_dict);
}
static void qdict_get_not_exists_test(void)
{
QDict *tests_dict = qdict_new();
g_assert(qdict_get(tests_dict, "foo") == NULL);
qobject_unref(tests_dict);
}
/*
* Stress test-case
*
* This is a lot big for a unit-test, but there is no other place
* to have it.
*/
static void remove_dots(char *string)
{
char *p = strchr(string, ':');
if (p)
*p = '\0';
}
static QString *read_line(FILE *file, char *key)
{
char value[128];
if (fscanf(file, "%127s%127s", key, value) == EOF) {
return NULL;
}
remove_dots(key);
return qstring_from_str(value);
}
#define reset_file(file) fseek(file, 0L, SEEK_SET)
static void qdict_stress_test(void)
{
size_t lines;
char key[128];
FILE *test_file;
QDict *qdict;
QString *value;
const char *test_file_path = "tests/data/qobject/qdict.txt";
test_file = fopen(test_file_path, "r");
g_assert(test_file != NULL);
// Create the dict
qdict = qdict_new();
g_assert(qdict != NULL);
// Add everything from the test file
for (lines = 0;; lines++) {
value = read_line(test_file, key);
if (!value)
break;
qdict_put(qdict, key, value);
}
g_assert(qdict_size(qdict) == lines);
// Check if everything is really in there
reset_file(test_file);
for (;;) {
const char *str1, *str2;
value = read_line(test_file, key);
if (!value)
break;
str1 = qstring_get_str(value);
str2 = qdict_get_str(qdict, key);
g_assert(str2 != NULL);
g_assert(strcmp(str1, str2) == 0);
qobject_unref(value);
}
// Delete everything
reset_file(test_file);
for (;;) {
value = read_line(test_file, key);
if (!value)
break;
qdict_del(qdict, key);
qobject_unref(value);
g_assert(qdict_haskey(qdict, key) == 0);
}
fclose(test_file);
g_assert(qdict_size(qdict) == 0);
qobject_unref(qdict);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/public/new", qdict_new_test);
g_test_add_func("/public/put_obj", qdict_put_obj_test);
g_test_add_func("/public/destroy_simple", qdict_destroy_simple_test);
/* Continue, but now with fixtures */
g_test_add_func("/public/get", qdict_get_test);
g_test_add_func("/public/get_int", qdict_get_int_test);
g_test_add_func("/public/get_try_int", qdict_get_try_int_test);
g_test_add_func("/public/get_str", qdict_get_str_test);
g_test_add_func("/public/get_try_str", qdict_get_try_str_test);
g_test_add_func("/public/haskey_not", qdict_haskey_not_test);
g_test_add_func("/public/haskey", qdict_haskey_test);
g_test_add_func("/public/del", qdict_del_test);
g_test_add_func("/public/to_qdict", qobject_to_qdict_test);
g_test_add_func("/public/iterapi", qdict_iterapi_test);
g_test_add_func("/errors/put_exists", qdict_put_exists_test);
g_test_add_func("/errors/get_not_exists", qdict_get_not_exists_test);
/* The Big one */
if (g_test_slow()) {
g_test_add_func("/stress/test", qdict_stress_test);
}
return g_test_run();
}

1518
tests/unit/check-qjson.c Normal file

File diff suppressed because it is too large Load diff

103
tests/unit/check-qlist.c Normal file
View file

@ -0,0 +1,103 @@
/*
* QList unit-tests.
*
* Copyright (C) 2009 Red Hat Inc.
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qlist.h"
/*
* Public Interface test-cases
*
* (with some violations to access 'private' data)
*/
static void qlist_new_test(void)
{
QList *qlist;
qlist = qlist_new();
g_assert(qlist != NULL);
g_assert(qlist->base.refcnt == 1);
g_assert(qobject_type(QOBJECT(qlist)) == QTYPE_QLIST);
qobject_unref(qlist);
}
static void qlist_append_test(void)
{
QNum *qi;
QList *qlist;
QListEntry *entry;
qi = qnum_from_int(42);
qlist = qlist_new();
qlist_append(qlist, qi);
entry = QTAILQ_FIRST(&qlist->head);
g_assert(entry != NULL);
g_assert(entry->value == QOBJECT(qi));
qobject_unref(qlist);
}
static void qobject_to_qlist_test(void)
{
QList *qlist;
qlist = qlist_new();
g_assert(qobject_to(QList, QOBJECT(qlist)) == qlist);
qobject_unref(qlist);
}
static void qlist_iter_test(void)
{
const int iter_max = 42;
int i;
QList *qlist;
QListEntry *entry;
QNum *qi;
int64_t val;
qlist = qlist_new();
for (i = 0; i < iter_max; i++)
qlist_append_int(qlist, i);
i = 0;
QLIST_FOREACH_ENTRY(qlist, entry) {
qi = qobject_to(QNum, qlist_entry_obj(entry));
g_assert(qi != NULL);
g_assert(qnum_get_try_int(qi, &val));
g_assert_cmpint(val, ==, i);
i++;
}
g_assert(i == iter_max);
qobject_unref(qlist);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/public/new", qlist_new_test);
g_test_add_func("/public/append", qlist_append_test);
g_test_add_func("/public/to_qlist", qobject_to_qlist_test);
g_test_add_func("/public/iter", qlist_iter_test);
return g_test_run();
}

101
tests/unit/check-qlit.c Normal file
View file

@ -0,0 +1,101 @@
/*
* QLit unit-tests.
*
* Copyright (C) 2017 Red Hat Inc.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qbool.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qlit.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qstring.h"
static QLitObject qlit = QLIT_QDICT(((QLitDictEntry[]) {
{ "foo", QLIT_QNUM(42) },
{ "bar", QLIT_QSTR("hello world") },
{ "baz", QLIT_QNULL },
{ "bee", QLIT_QLIST(((QLitObject[]) {
QLIT_QNUM(43),
QLIT_QNUM(44),
QLIT_QBOOL(true),
{ },
}))},
{ },
}));
static QLitObject qlit_foo = QLIT_QDICT(((QLitDictEntry[]) {
{ "foo", QLIT_QNUM(42) },
{ },
}));
static QObject *make_qobject(void)
{
QDict *qdict = qdict_new();
QList *list = qlist_new();
qdict_put_int(qdict, "foo", 42);
qdict_put_str(qdict, "bar", "hello world");
qdict_put_null(qdict, "baz");
qlist_append_int(list, 43);
qlist_append_int(list, 44);
qlist_append_bool(list, true);
qdict_put(qdict, "bee", list);
return QOBJECT(qdict);
}
static void qlit_equal_qobject_test(void)
{
QObject *qobj = make_qobject();
g_assert(qlit_equal_qobject(&qlit, qobj));
g_assert(!qlit_equal_qobject(&qlit_foo, qobj));
qdict_put(qobject_to(QDict, qobj), "bee", qlist_new());
g_assert(!qlit_equal_qobject(&qlit, qobj));
qobject_unref(qobj);
}
static void qobject_from_qlit_test(void)
{
QObject *obj, *qobj = qobject_from_qlit(&qlit);
QDict *qdict;
QList *bee;
qdict = qobject_to(QDict, qobj);
g_assert_cmpint(qdict_get_int(qdict, "foo"), ==, 42);
g_assert_cmpstr(qdict_get_str(qdict, "bar"), ==, "hello world");
g_assert(qobject_type(qdict_get(qdict, "baz")) == QTYPE_QNULL);
bee = qdict_get_qlist(qdict, "bee");
obj = qlist_pop(bee);
g_assert_cmpint(qnum_get_int(qobject_to(QNum, obj)), ==, 43);
qobject_unref(obj);
obj = qlist_pop(bee);
g_assert_cmpint(qnum_get_int(qobject_to(QNum, obj)), ==, 44);
qobject_unref(obj);
obj = qlist_pop(bee);
g_assert(qbool_get_bool(qobject_to(QBool, obj)));
qobject_unref(obj);
qobject_unref(qobj);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qlit/equal_qobject", qlit_equal_qobject_test);
g_test_add_func("/qlit/qobject_from_qlit", qobject_from_qlit_test);
return g_test_run();
}

78
tests/unit/check-qnull.c Normal file
View file

@ -0,0 +1,78 @@
/*
* QNull unit-tests.
*
* Copyright (C) 2016 Red Hat Inc.
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qnull.h"
#include "qemu-common.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
#include "qapi/error.h"
/*
* Public Interface test-cases
*
* (with some violations to access 'private' data)
*/
static void qnull_ref_test(void)
{
QObject *obj;
g_assert(qnull_.base.refcnt == 1);
obj = QOBJECT(qnull());
g_assert(obj);
g_assert(obj == QOBJECT(&qnull_));
g_assert(qnull_.base.refcnt == 2);
g_assert(qobject_type(obj) == QTYPE_QNULL);
qobject_unref(obj);
g_assert(qnull_.base.refcnt == 1);
}
static void qnull_visit_test(void)
{
QObject *obj;
Visitor *v;
QNull *null;
/*
* Most tests of interactions between QObject and visitors are in
* test-qmp-*-visitor; but these tests live here because they
* depend on layering violations to check qnull_ refcnt.
*/
g_assert(qnull_.base.refcnt == 1);
obj = QOBJECT(qnull());
v = qobject_input_visitor_new(obj);
qobject_unref(obj);
visit_type_null(v, NULL, &null, &error_abort);
g_assert(obj == QOBJECT(&qnull_));
qobject_unref(null);
visit_free(v);
null = NULL;
v = qobject_output_visitor_new(&obj);
visit_type_null(v, NULL, &null, &error_abort);
visit_complete(v, &obj);
g_assert(obj == QOBJECT(&qnull_));
qobject_unref(null);
qobject_unref(obj);
visit_free(v);
g_assert(qnull_.base.refcnt == 1);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/public/qnull_ref", qnull_ref_test);
g_test_add_func("/public/qnull_visit", qnull_visit_test);
return g_test_run();
}

175
tests/unit/check-qnum.c Normal file
View file

@ -0,0 +1,175 @@
/*
* QNum unit-tests.
*
* Copyright (C) 2009 Red Hat Inc.
* Copyright IBM, Corp. 2009
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
* Anthony Liguori <aliguori@us.ibm.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qnum.h"
#include "qemu-common.h"
/*
* Public Interface test-cases
*
* (with some violations to access 'private' data)
*/
static void qnum_from_int_test(void)
{
QNum *qn;
const int value = -42;
qn = qnum_from_int(value);
g_assert(qn != NULL);
g_assert_cmpint(qn->kind, ==, QNUM_I64);
g_assert_cmpint(qn->u.i64, ==, value);
g_assert_cmpint(qn->base.refcnt, ==, 1);
g_assert_cmpint(qobject_type(QOBJECT(qn)), ==, QTYPE_QNUM);
qobject_unref(qn);
}
static void qnum_from_uint_test(void)
{
QNum *qn;
const uint64_t value = UINT64_MAX;
qn = qnum_from_uint(value);
g_assert(qn != NULL);
g_assert_cmpint(qn->kind, ==, QNUM_U64);
g_assert(qn->u.u64 == value);
g_assert(qn->base.refcnt == 1);
g_assert(qobject_type(QOBJECT(qn)) == QTYPE_QNUM);
qobject_unref(qn);
}
static void qnum_from_double_test(void)
{
QNum *qn;
const double value = -42.23423;
qn = qnum_from_double(value);
g_assert(qn != NULL);
g_assert_cmpint(qn->kind, ==, QNUM_DOUBLE);
g_assert_cmpfloat(qn->u.dbl, ==, value);
g_assert_cmpint(qn->base.refcnt, ==, 1);
g_assert_cmpint(qobject_type(QOBJECT(qn)), ==, QTYPE_QNUM);
qobject_unref(qn);
}
static void qnum_from_int64_test(void)
{
QNum *qn;
const int64_t value = 0x1234567890abcdefLL;
qn = qnum_from_int(value);
g_assert_cmpint((int64_t) qn->u.i64, ==, value);
qobject_unref(qn);
}
static void qnum_get_int_test(void)
{
QNum *qn;
const int value = 123456;
qn = qnum_from_int(value);
g_assert_cmpint(qnum_get_int(qn), ==, value);
qobject_unref(qn);
}
static void qnum_get_uint_test(void)
{
QNum *qn;
const int value = 123456;
uint64_t val;
int64_t ival;
qn = qnum_from_uint(value);
g_assert(qnum_get_try_uint(qn, &val));
g_assert_cmpuint(val, ==, value);
qobject_unref(qn);
qn = qnum_from_int(value);
g_assert(qnum_get_try_uint(qn, &val));
g_assert_cmpuint(val, ==, value);
qobject_unref(qn);
/* invalid cases */
qn = qnum_from_int(-1);
g_assert(!qnum_get_try_uint(qn, &val));
qobject_unref(qn);
qn = qnum_from_uint(-1ULL);
g_assert(!qnum_get_try_int(qn, &ival));
qobject_unref(qn);
qn = qnum_from_double(0.42);
g_assert(!qnum_get_try_uint(qn, &val));
qobject_unref(qn);
}
static void qobject_to_qnum_test(void)
{
QNum *qn;
qn = qnum_from_int(0);
g_assert(qobject_to(QNum, QOBJECT(qn)) == qn);
qobject_unref(qn);
qn = qnum_from_double(0);
g_assert(qobject_to(QNum, QOBJECT(qn)) == qn);
qobject_unref(qn);
}
static void qnum_to_string_test(void)
{
QNum *qn;
char *tmp;
qn = qnum_from_int(123456);
tmp = qnum_to_string(qn);
g_assert_cmpstr(tmp, ==, "123456");
g_free(tmp);
qobject_unref(qn);
qn = qnum_from_double(0.42);
tmp = qnum_to_string(qn);
g_assert_cmpstr(tmp, ==, "0.41999999999999998");
g_free(tmp);
qobject_unref(qn);
qn = qnum_from_double(2.718281828459045);
tmp = qnum_to_string(qn);
g_assert_cmpstr(tmp, ==, "2.7182818284590451");
g_free(tmp);
qobject_unref(qn);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qnum/from_int", qnum_from_int_test);
g_test_add_func("/qnum/from_uint", qnum_from_uint_test);
g_test_add_func("/qnum/from_double", qnum_from_double_test);
g_test_add_func("/qnum/from_int64", qnum_from_int64_test);
g_test_add_func("/qnum/get_int", qnum_get_int_test);
g_test_add_func("/qnum/get_uint", qnum_get_uint_test);
g_test_add_func("/qnum/to_qnum", qobject_to_qnum_test);
g_test_add_func("/qnum/to_string", qnum_to_string_test);
return g_test_run();
}

334
tests/unit/check-qobject.c Normal file
View file

@ -0,0 +1,334 @@
/*
* Generic QObject unit-tests.
*
* Copyright (C) 2017 Red Hat Inc.
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "block/qdict.h"
#include "qapi/error.h"
#include "qapi/qmp/qbool.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qnull.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qstring.h"
#include "qemu-common.h"
#include <math.h>
/* Marks the end of the test_equality() argument list.
* We cannot use NULL there because that is a valid argument. */
static QObject test_equality_end_of_arguments;
/**
* Test whether all variadic QObject *arguments are equal (@expected
* is true) or whether they are all not equal (@expected is false).
* Every QObject is tested to be equal to itself (to test
* reflexivity), all tests are done both ways (to test symmetry), and
* transitivity is not assumed but checked (each object is compared to
* every other one).
*
* Note that qobject_is_equal() is not really an equivalence relation,
* so this function may not be used for all objects (reflexivity is
* not guaranteed, e.g. in the case of a QNum containing NaN).
*
* The @_ argument is required because a boolean may not be the last
* argument before a variadic argument list (C11 7.16.1.4 para. 4).
*/
static void do_test_equality(bool expected, int _, ...)
{
va_list ap_count, ap_extract;
QObject **args;
int arg_count = 0;
int i, j;
va_start(ap_count, _);
va_copy(ap_extract, ap_count);
while (va_arg(ap_count, QObject *) != &test_equality_end_of_arguments) {
arg_count++;
}
va_end(ap_count);
args = g_new(QObject *, arg_count);
for (i = 0; i < arg_count; i++) {
args[i] = va_arg(ap_extract, QObject *);
}
va_end(ap_extract);
for (i = 0; i < arg_count; i++) {
g_assert(qobject_is_equal(args[i], args[i]) == true);
for (j = i + 1; j < arg_count; j++) {
g_assert(qobject_is_equal(args[i], args[j]) == expected);
}
}
g_free(args);
}
#define check_equal(...) \
do_test_equality(true, 0, __VA_ARGS__, &test_equality_end_of_arguments)
#define check_unequal(...) \
do_test_equality(false, 0, __VA_ARGS__, &test_equality_end_of_arguments)
static void do_free_all(int _, ...)
{
va_list ap;
QObject *obj;
va_start(ap, _);
while ((obj = va_arg(ap, QObject *)) != NULL) {
qobject_unref(obj);
}
va_end(ap);
}
#define free_all(...) \
do_free_all(0, __VA_ARGS__, NULL)
static void qobject_is_equal_null_test(void)
{
check_unequal(qnull(), NULL);
}
static void qobject_is_equal_num_test(void)
{
QNum *u0, *i0, *d0, *dnan, *um42, *im42, *dm42;
u0 = qnum_from_uint(0u);
i0 = qnum_from_int(0);
d0 = qnum_from_double(0.0);
dnan = qnum_from_double(NAN);
um42 = qnum_from_uint((uint64_t)-42);
im42 = qnum_from_int(-42);
dm42 = qnum_from_double(-42.0);
/* Integers representing a mathematically equal number should
* compare equal */
check_equal(u0, i0);
/* Doubles, however, are always unequal to integers */
check_unequal(u0, d0);
check_unequal(i0, d0);
/* Do not assume any object is equal to itself -- note however
* that NaN cannot occur in a JSON object anyway. */
g_assert(qobject_is_equal(QOBJECT(dnan), QOBJECT(dnan)) == false);
/* No unsigned overflow */
check_unequal(um42, im42);
check_unequal(um42, dm42);
check_unequal(im42, dm42);
free_all(u0, i0, d0, dnan, um42, im42, dm42);
}
static void qobject_is_equal_bool_test(void)
{
QBool *btrue_0, *btrue_1, *bfalse_0, *bfalse_1;
btrue_0 = qbool_from_bool(true);
btrue_1 = qbool_from_bool(true);
bfalse_0 = qbool_from_bool(false);
bfalse_1 = qbool_from_bool(false);
check_equal(btrue_0, btrue_1);
check_equal(bfalse_0, bfalse_1);
check_unequal(btrue_0, bfalse_0);
free_all(btrue_0, btrue_1, bfalse_0, bfalse_1);
}
static void qobject_is_equal_string_test(void)
{
QString *str_base, *str_whitespace_0, *str_whitespace_1, *str_whitespace_2;
QString *str_whitespace_3, *str_case, *str_built;
str_base = qstring_from_str("foo");
str_whitespace_0 = qstring_from_str(" foo");
str_whitespace_1 = qstring_from_str("foo ");
str_whitespace_2 = qstring_from_str("foo\b");
str_whitespace_3 = qstring_from_str("fooo\b");
str_case = qstring_from_str("Foo");
/* Should yield "foo" */
str_built = qstring_from_substr("buffoon", 3, 6);
check_unequal(str_base, str_whitespace_0, str_whitespace_1,
str_whitespace_2, str_whitespace_3, str_case);
check_equal(str_base, str_built);
free_all(str_base, str_whitespace_0, str_whitespace_1, str_whitespace_2,
str_whitespace_3, str_case, str_built);
}
static void qobject_is_equal_list_test(void)
{
QList *list_0, *list_1, *list_cloned;
QList *list_reordered, *list_longer, *list_shorter;
list_0 = qlist_new();
list_1 = qlist_new();
list_reordered = qlist_new();
list_longer = qlist_new();
list_shorter = qlist_new();
qlist_append_int(list_0, 1);
qlist_append_int(list_0, 2);
qlist_append_int(list_0, 3);
qlist_append_int(list_1, 1);
qlist_append_int(list_1, 2);
qlist_append_int(list_1, 3);
qlist_append_int(list_reordered, 1);
qlist_append_int(list_reordered, 3);
qlist_append_int(list_reordered, 2);
qlist_append_int(list_longer, 1);
qlist_append_int(list_longer, 2);
qlist_append_int(list_longer, 3);
qlist_append_null(list_longer);
qlist_append_int(list_shorter, 1);
qlist_append_int(list_shorter, 2);
list_cloned = qlist_copy(list_0);
check_equal(list_0, list_1, list_cloned);
check_unequal(list_0, list_reordered, list_longer, list_shorter);
/* With a NaN in it, the list should no longer compare equal to
* itself */
qlist_append(list_0, qnum_from_double(NAN));
g_assert(qobject_is_equal(QOBJECT(list_0), QOBJECT(list_0)) == false);
free_all(list_0, list_1, list_cloned, list_reordered, list_longer,
list_shorter);
}
static void qobject_is_equal_dict_test(void)
{
QDict *dict_0, *dict_1, *dict_cloned;
QDict *dict_different_key, *dict_different_value, *dict_different_null_key;
QDict *dict_longer, *dict_shorter, *dict_nested;
QDict *dict_crumpled;
dict_0 = qdict_new();
dict_1 = qdict_new();
dict_different_key = qdict_new();
dict_different_value = qdict_new();
dict_different_null_key = qdict_new();
dict_longer = qdict_new();
dict_shorter = qdict_new();
dict_nested = qdict_new();
qdict_put_int(dict_0, "f.o", 1);
qdict_put_int(dict_0, "bar", 2);
qdict_put_int(dict_0, "baz", 3);
qdict_put_null(dict_0, "null");
qdict_put_int(dict_1, "f.o", 1);
qdict_put_int(dict_1, "bar", 2);
qdict_put_int(dict_1, "baz", 3);
qdict_put_null(dict_1, "null");
qdict_put_int(dict_different_key, "F.o", 1);
qdict_put_int(dict_different_key, "bar", 2);
qdict_put_int(dict_different_key, "baz", 3);
qdict_put_null(dict_different_key, "null");
qdict_put_int(dict_different_value, "f.o", 42);
qdict_put_int(dict_different_value, "bar", 2);
qdict_put_int(dict_different_value, "baz", 3);
qdict_put_null(dict_different_value, "null");
qdict_put_int(dict_different_null_key, "f.o", 1);
qdict_put_int(dict_different_null_key, "bar", 2);
qdict_put_int(dict_different_null_key, "baz", 3);
qdict_put_null(dict_different_null_key, "none");
qdict_put_int(dict_longer, "f.o", 1);
qdict_put_int(dict_longer, "bar", 2);
qdict_put_int(dict_longer, "baz", 3);
qdict_put_int(dict_longer, "xyz", 4);
qdict_put_null(dict_longer, "null");
qdict_put_int(dict_shorter, "f.o", 1);
qdict_put_int(dict_shorter, "bar", 2);
qdict_put_int(dict_shorter, "baz", 3);
qdict_put(dict_nested, "f", qdict_new());
qdict_put_int(qdict_get_qdict(dict_nested, "f"), "o", 1);
qdict_put_int(dict_nested, "bar", 2);
qdict_put_int(dict_nested, "baz", 3);
qdict_put_null(dict_nested, "null");
dict_cloned = qdict_clone_shallow(dict_0);
check_equal(dict_0, dict_1, dict_cloned);
check_unequal(dict_0, dict_different_key, dict_different_value,
dict_different_null_key, dict_longer, dict_shorter,
dict_nested);
dict_crumpled = qobject_to(QDict, qdict_crumple(dict_1, &error_abort));
check_equal(dict_crumpled, dict_nested);
qdict_flatten(dict_nested);
check_equal(dict_0, dict_nested);
/* Containing an NaN value will make this dict compare unequal to
* itself */
qdict_put(dict_0, "NaN", qnum_from_double(NAN));
g_assert(qobject_is_equal(QOBJECT(dict_0), QOBJECT(dict_0)) == false);
free_all(dict_0, dict_1, dict_cloned, dict_different_key,
dict_different_value, dict_different_null_key, dict_longer,
dict_shorter, dict_nested, dict_crumpled);
}
static void qobject_is_equal_conversion_test(void)
{
QNum *u0, *i0, *d0;
QString *s0, *s_empty;
QBool *bfalse;
u0 = qnum_from_uint(0u);
i0 = qnum_from_int(0);
d0 = qnum_from_double(0.0);
s0 = qstring_from_str("0");
s_empty = qstring_new();
bfalse = qbool_from_bool(false);
/* No automatic type conversion */
check_unequal(u0, s0, s_empty, bfalse, qnull(), NULL);
check_unequal(i0, s0, s_empty, bfalse, qnull(), NULL);
check_unequal(d0, s0, s_empty, bfalse, qnull(), NULL);
free_all(u0, i0, d0, s0, s_empty, bfalse);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/public/qobject_is_equal_null",
qobject_is_equal_null_test);
g_test_add_func("/public/qobject_is_equal_num", qobject_is_equal_num_test);
g_test_add_func("/public/qobject_is_equal_bool",
qobject_is_equal_bool_test);
g_test_add_func("/public/qobject_is_equal_string",
qobject_is_equal_string_test);
g_test_add_func("/public/qobject_is_equal_list",
qobject_is_equal_list_test);
g_test_add_func("/public/qobject_is_equal_dict",
qobject_is_equal_dict_test);
g_test_add_func("/public/qobject_is_equal_conversion",
qobject_is_equal_conversion_test);
return g_test_run();
}

View file

@ -0,0 +1,103 @@
/*
* QOM interface test.
*
* Copyright (C) 2013 Red Hat Inc.
*
* Authors:
* Igor Mammedov <imammedo@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qom/object.h"
#include "qemu/module.h"
#define TYPE_TEST_IF "test-interface"
typedef struct TestIfClass TestIfClass;
DECLARE_CLASS_CHECKERS(TestIfClass, TEST_IF,
TYPE_TEST_IF)
#define TEST_IF(obj) \
INTERFACE_CHECK(TestIf, (obj), TYPE_TEST_IF)
typedef struct TestIf TestIf;
struct TestIfClass {
InterfaceClass parent_class;
uint32_t test;
};
static const TypeInfo test_if_info = {
.name = TYPE_TEST_IF,
.parent = TYPE_INTERFACE,
.class_size = sizeof(TestIfClass),
};
#define PATTERN 0xFAFBFCFD
static void test_class_init(ObjectClass *oc, void *data)
{
TestIfClass *tc = TEST_IF_CLASS(oc);
g_assert(tc);
tc->test = PATTERN;
}
#define TYPE_DIRECT_IMPL "direct-impl"
static const TypeInfo direct_impl_info = {
.name = TYPE_DIRECT_IMPL,
.parent = TYPE_OBJECT,
.class_init = test_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_TEST_IF },
{ }
}
};
#define TYPE_INTERMEDIATE_IMPL "intermediate-impl"
static const TypeInfo intermediate_impl_info = {
.name = TYPE_INTERMEDIATE_IMPL,
.parent = TYPE_DIRECT_IMPL,
};
static void test_interface_impl(const char *type)
{
Object *obj = object_new(type);
TestIf *iobj = TEST_IF(obj);
TestIfClass *ioc = TEST_IF_GET_CLASS(iobj);
g_assert(iobj);
g_assert(ioc->test == PATTERN);
object_unref(obj);
}
static void interface_direct_test(void)
{
test_interface_impl(TYPE_DIRECT_IMPL);
}
static void interface_intermediate_test(void)
{
test_interface_impl(TYPE_INTERMEDIATE_IMPL);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
type_register_static(&test_if_info);
type_register_static(&direct_impl_info);
type_register_static(&intermediate_impl_info);
g_test_add_func("/qom/interface/direct_impl", interface_direct_test);
g_test_add_func("/qom/interface/intermediate_impl",
interface_intermediate_test);
return g_test_run();
}

View file

@ -0,0 +1,638 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qom/object.h"
#include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/config-file.h"
#include "qom/object_interfaces.h"
#define TYPE_DUMMY "qemu-dummy"
typedef struct DummyObject DummyObject;
typedef struct DummyObjectClass DummyObjectClass;
DECLARE_INSTANCE_CHECKER(DummyObject, DUMMY_OBJECT,
TYPE_DUMMY)
typedef enum DummyAnimal DummyAnimal;
enum DummyAnimal {
DUMMY_FROG,
DUMMY_ALLIGATOR,
DUMMY_PLATYPUS,
DUMMY_LAST,
};
const QEnumLookup dummy_animal_map = {
.array = (const char *const[]) {
[DUMMY_FROG] = "frog",
[DUMMY_ALLIGATOR] = "alligator",
[DUMMY_PLATYPUS] = "platypus",
},
.size = DUMMY_LAST
};
struct DummyObject {
Object parent_obj;
bool bv;
DummyAnimal av;
char *sv;
};
struct DummyObjectClass {
ObjectClass parent_class;
};
static void dummy_set_bv(Object *obj,
bool value,
Error **errp)
{
DummyObject *dobj = DUMMY_OBJECT(obj);
dobj->bv = value;
}
static bool dummy_get_bv(Object *obj,
Error **errp)
{
DummyObject *dobj = DUMMY_OBJECT(obj);
return dobj->bv;
}
static void dummy_set_av(Object *obj,
int value,
Error **errp)
{
DummyObject *dobj = DUMMY_OBJECT(obj);
dobj->av = value;
}
static int dummy_get_av(Object *obj,
Error **errp)
{
DummyObject *dobj = DUMMY_OBJECT(obj);
return dobj->av;
}
static void dummy_set_sv(Object *obj,
const char *value,
Error **errp)
{
DummyObject *dobj = DUMMY_OBJECT(obj);
g_free(dobj->sv);
dobj->sv = g_strdup(value);
}
static char *dummy_get_sv(Object *obj,
Error **errp)
{
DummyObject *dobj = DUMMY_OBJECT(obj);
return g_strdup(dobj->sv);
}
static void dummy_init(Object *obj)
{
object_property_add_bool(obj, "bv",
dummy_get_bv,
dummy_set_bv);
}
static void dummy_class_init(ObjectClass *cls, void *data)
{
object_class_property_add_str(cls, "sv",
dummy_get_sv,
dummy_set_sv);
object_class_property_add_enum(cls, "av",
"DummyAnimal",
&dummy_animal_map,
dummy_get_av,
dummy_set_av);
}
static void dummy_finalize(Object *obj)
{
DummyObject *dobj = DUMMY_OBJECT(obj);
g_free(dobj->sv);
}
static const TypeInfo dummy_info = {
.name = TYPE_DUMMY,
.parent = TYPE_OBJECT,
.instance_size = sizeof(DummyObject),
.instance_init = dummy_init,
.instance_finalize = dummy_finalize,
.class_size = sizeof(DummyObjectClass),
.class_init = dummy_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_USER_CREATABLE },
{ }
}
};
/*
* The following 3 object classes are used to
* simulate the kind of relationships seen in
* qdev, which result in complex object
* property destruction ordering.
*
* DummyDev has a 'bus' child to a DummyBus
* DummyBus has a 'backend' child to a DummyBackend
* DummyDev has a 'backend' link to DummyBackend
*
* When DummyDev is finalized, it unparents the
* DummyBackend, which unparents the DummyDev
* which deletes the 'backend' link from DummyDev
* to DummyBackend. This illustrates that the
* object_property_del_all() method needs to
* cope with the list of properties being changed
* while it iterates over them.
*/
typedef struct DummyDev DummyDev;
typedef struct DummyDevClass DummyDevClass;
typedef struct DummyBus DummyBus;
typedef struct DummyBusClass DummyBusClass;
typedef struct DummyBackend DummyBackend;
typedef struct DummyBackendClass DummyBackendClass;
#define TYPE_DUMMY_DEV "qemu-dummy-dev"
#define TYPE_DUMMY_BUS "qemu-dummy-bus"
#define TYPE_DUMMY_BACKEND "qemu-dummy-backend"
DECLARE_INSTANCE_CHECKER(DummyDev, DUMMY_DEV,
TYPE_DUMMY_DEV)
DECLARE_INSTANCE_CHECKER(DummyBus, DUMMY_BUS,
TYPE_DUMMY_BUS)
DECLARE_INSTANCE_CHECKER(DummyBackend, DUMMY_BACKEND,
TYPE_DUMMY_BACKEND)
struct DummyDev {
Object parent_obj;
DummyBus *bus;
};
struct DummyDevClass {
ObjectClass parent_class;
};
struct DummyBus {
Object parent_obj;
DummyBackend *backend;
};
struct DummyBusClass {
ObjectClass parent_class;
};
struct DummyBackend {
Object parent_obj;
};
struct DummyBackendClass {
ObjectClass parent_class;
};
static void dummy_dev_finalize(Object *obj)
{
DummyDev *dev = DUMMY_DEV(obj);
object_unref(OBJECT(dev->bus));
}
static void dummy_dev_init(Object *obj)
{
DummyDev *dev = DUMMY_DEV(obj);
DummyBus *bus = DUMMY_BUS(object_new(TYPE_DUMMY_BUS));
DummyBackend *backend = DUMMY_BACKEND(object_new(TYPE_DUMMY_BACKEND));
object_property_add_child(obj, "bus", OBJECT(bus));
dev->bus = bus;
object_property_add_child(OBJECT(bus), "backend", OBJECT(backend));
bus->backend = backend;
object_property_add_link(obj, "backend", TYPE_DUMMY_BACKEND,
(Object **)&bus->backend, NULL, 0);
}
static void dummy_dev_unparent(Object *obj)
{
DummyDev *dev = DUMMY_DEV(obj);
object_unparent(OBJECT(dev->bus));
}
static void dummy_dev_class_init(ObjectClass *klass, void *opaque)
{
klass->unparent = dummy_dev_unparent;
}
static void dummy_bus_finalize(Object *obj)
{
DummyBus *bus = DUMMY_BUS(obj);
object_unref(OBJECT(bus->backend));
}
static void dummy_bus_init(Object *obj)
{
}
static void dummy_bus_unparent(Object *obj)
{
DummyBus *bus = DUMMY_BUS(obj);
object_property_del(obj->parent, "backend");
object_unparent(OBJECT(bus->backend));
}
static void dummy_bus_class_init(ObjectClass *klass, void *opaque)
{
klass->unparent = dummy_bus_unparent;
}
static void dummy_backend_init(Object *obj)
{
}
static const TypeInfo dummy_dev_info = {
.name = TYPE_DUMMY_DEV,
.parent = TYPE_OBJECT,
.instance_size = sizeof(DummyDev),
.instance_init = dummy_dev_init,
.instance_finalize = dummy_dev_finalize,
.class_size = sizeof(DummyDevClass),
.class_init = dummy_dev_class_init,
};
static const TypeInfo dummy_bus_info = {
.name = TYPE_DUMMY_BUS,
.parent = TYPE_OBJECT,
.instance_size = sizeof(DummyBus),
.instance_init = dummy_bus_init,
.instance_finalize = dummy_bus_finalize,
.class_size = sizeof(DummyBusClass),
.class_init = dummy_bus_class_init,
};
static const TypeInfo dummy_backend_info = {
.name = TYPE_DUMMY_BACKEND,
.parent = TYPE_OBJECT,
.instance_size = sizeof(DummyBackend),
.instance_init = dummy_backend_init,
.class_size = sizeof(DummyBackendClass),
};
static QemuOptsList qemu_object_opts = {
.name = "object",
.implied_opt_name = "qom-type",
.head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head),
.desc = {
{ }
},
};
static void test_dummy_createv(void)
{
Error *err = NULL;
Object *parent = object_get_objects_root();
DummyObject *dobj = DUMMY_OBJECT(
object_new_with_props(TYPE_DUMMY,
parent,
"dummy0",
&err,
"bv", "yes",
"sv", "Hiss hiss hiss",
"av", "platypus",
NULL));
g_assert(err == NULL);
g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss");
g_assert(dobj->bv == true);
g_assert(dobj->av == DUMMY_PLATYPUS);
g_assert(object_resolve_path_component(parent, "dummy0")
== OBJECT(dobj));
object_unparent(OBJECT(dobj));
}
static Object *new_helper(Error **errp,
Object *parent,
...)
{
va_list vargs;
Object *obj;
va_start(vargs, parent);
obj = object_new_with_propv(TYPE_DUMMY,
parent,
"dummy0",
errp,
vargs);
va_end(vargs);
return obj;
}
static void test_dummy_createlist(void)
{
Error *err = NULL;
Object *parent = object_get_objects_root();
DummyObject *dobj = DUMMY_OBJECT(
new_helper(&err,
parent,
"bv", "yes",
"sv", "Hiss hiss hiss",
"av", "platypus",
NULL));
g_assert(err == NULL);
g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss");
g_assert(dobj->bv == true);
g_assert(dobj->av == DUMMY_PLATYPUS);
g_assert(object_resolve_path_component(parent, "dummy0")
== OBJECT(dobj));
object_unparent(OBJECT(dobj));
}
static void test_dummy_createcmdl(void)
{
QemuOpts *opts;
DummyObject *dobj;
Error *err = NULL;
const char *params = TYPE_DUMMY \
",id=dev0," \
"bv=yes,sv=Hiss hiss hiss,av=platypus";
qemu_add_opts(&qemu_object_opts);
opts = qemu_opts_parse(&qemu_object_opts, params, true, &err);
g_assert(err == NULL);
g_assert(opts);
dobj = DUMMY_OBJECT(user_creatable_add_opts(opts, &err));
g_assert(err == NULL);
g_assert(dobj);
g_assert_cmpstr(dobj->sv, ==, "Hiss hiss hiss");
g_assert(dobj->bv == true);
g_assert(dobj->av == DUMMY_PLATYPUS);
user_creatable_del("dev0", &error_abort);
object_unref(OBJECT(dobj));
/*
* cmdline-parsing via qemu_opts_parse() results in a QemuOpts entry
* corresponding to the Object's ID to be added to the QemuOptsList
* for objects. To avoid having this entry conflict with future
* Objects using the same ID (which can happen in cases where
* qemu_opts_parse() is used to parse the object params, such as
* with hmp_object_add() at the time of this comment), we need to
* check for this in user_creatable_del() and remove the QemuOpts if
* it is present.
*
* The below check ensures this works as expected.
*/
g_assert_null(qemu_opts_find(&qemu_object_opts, "dev0"));
}
static void test_dummy_badenum(void)
{
Error *err = NULL;
Object *parent = object_get_objects_root();
Object *dobj =
object_new_with_props(TYPE_DUMMY,
parent,
"dummy0",
&err,
"bv", "yes",
"sv", "Hiss hiss hiss",
"av", "yeti",
NULL);
g_assert(dobj == NULL);
g_assert(err != NULL);
g_assert_cmpstr(error_get_pretty(err), ==,
"Invalid parameter 'yeti'");
g_assert(object_resolve_path_component(parent, "dummy0")
== NULL);
error_free(err);
}
static void test_dummy_getenum(void)
{
Error *err = NULL;
int val;
Object *parent = object_get_objects_root();
DummyObject *dobj = DUMMY_OBJECT(
object_new_with_props(TYPE_DUMMY,
parent,
"dummy0",
&err,
"av", "platypus",
NULL));
g_assert(err == NULL);
g_assert(dobj->av == DUMMY_PLATYPUS);
val = object_property_get_enum(OBJECT(dobj),
"av",
"DummyAnimal",
&error_abort);
g_assert(val == DUMMY_PLATYPUS);
/* A bad enum type name */
val = object_property_get_enum(OBJECT(dobj),
"av",
"BadAnimal",
&err);
g_assert(val == -1);
error_free_or_abort(&err);
/* A non-enum property name */
val = object_property_get_enum(OBJECT(dobj),
"iv",
"DummyAnimal",
&err);
g_assert(val == -1);
error_free_or_abort(&err);
object_unparent(OBJECT(dobj));
}
static void test_dummy_prop_iterator(ObjectPropertyIterator *iter,
const char *expected[], int n)
{
ObjectProperty *prop;
int i;
while ((prop = object_property_iter_next(iter))) {
for (i = 0; i < n; i++) {
if (!g_strcmp0(prop->name, expected[i])) {
break;
}
}
g_assert(i < n);
expected[i] = NULL;
}
for (i = 0; i < n; i++) {
g_assert(!expected[i]);
}
}
static void test_dummy_iterator(void)
{
const char *expected[] = {
"type", /* inherited from TYPE_OBJECT */
"sv", "av", /* class properties */
"bv"}; /* instance property */
Object *parent = object_get_objects_root();
DummyObject *dobj = DUMMY_OBJECT(
object_new_with_props(TYPE_DUMMY,
parent,
"dummy0",
&error_abort,
"bv", "yes",
"sv", "Hiss hiss hiss",
"av", "platypus",
NULL));
ObjectPropertyIterator iter;
object_property_iter_init(&iter, OBJECT(dobj));
test_dummy_prop_iterator(&iter, expected, ARRAY_SIZE(expected));
object_unparent(OBJECT(dobj));
}
static void test_dummy_class_iterator(void)
{
const char *expected[] = { "type", "av", "sv" };
ObjectPropertyIterator iter;
ObjectClass *klass = object_class_by_name(TYPE_DUMMY);
object_class_property_iter_init(&iter, klass);
test_dummy_prop_iterator(&iter, expected, ARRAY_SIZE(expected));
}
static void test_dummy_delchild(void)
{
Object *parent = object_get_objects_root();
DummyDev *dev = DUMMY_DEV(
object_new_with_props(TYPE_DUMMY_DEV,
parent,
"dev0",
&error_abort,
NULL));
object_unparent(OBJECT(dev));
}
static void test_qom_partial_path(void)
{
Object *root = object_get_objects_root();
Object *cont1 = container_get(root, "/cont1");
Object *obj1 = object_new(TYPE_DUMMY);
Object *obj2a = object_new(TYPE_DUMMY);
Object *obj2b = object_new(TYPE_DUMMY);
bool ambiguous;
/* Objects created:
* /cont1
* /cont1/obj1
* /cont1/obj2 (obj2a)
* /obj2 (obj2b)
*/
object_property_add_child(cont1, "obj1", obj1);
object_unref(obj1);
object_property_add_child(cont1, "obj2", obj2a);
object_unref(obj2a);
object_property_add_child(root, "obj2", obj2b);
object_unref(obj2b);
ambiguous = false;
g_assert(!object_resolve_path_type("", TYPE_DUMMY, &ambiguous));
g_assert(ambiguous);
g_assert(!object_resolve_path_type("", TYPE_DUMMY, NULL));
ambiguous = false;
g_assert(!object_resolve_path("obj2", &ambiguous));
g_assert(ambiguous);
g_assert(!object_resolve_path("obj2", NULL));
ambiguous = false;
g_assert(object_resolve_path("obj1", &ambiguous) == obj1);
g_assert(!ambiguous);
g_assert(object_resolve_path("obj1", NULL) == obj1);
object_unparent(obj2b);
object_unparent(cont1);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
type_register_static(&dummy_info);
type_register_static(&dummy_dev_info);
type_register_static(&dummy_bus_info);
type_register_static(&dummy_backend_info);
g_test_add_func("/qom/proplist/createlist", test_dummy_createlist);
g_test_add_func("/qom/proplist/createv", test_dummy_createv);
g_test_add_func("/qom/proplist/createcmdline", test_dummy_createcmdl);
g_test_add_func("/qom/proplist/badenum", test_dummy_badenum);
g_test_add_func("/qom/proplist/getenum", test_dummy_getenum);
g_test_add_func("/qom/proplist/iterator", test_dummy_iterator);
g_test_add_func("/qom/proplist/class_iterator", test_dummy_class_iterator);
g_test_add_func("/qom/proplist/delchild", test_dummy_delchild);
g_test_add_func("/qom/resolve/partial", test_qom_partial_path);
return g_test_run();
}

View file

@ -0,0 +1,82 @@
/*
* QString unit-tests.
*
* Copyright (C) 2009 Red Hat Inc.
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qstring.h"
#include "qemu-common.h"
/*
* Public Interface test-cases
*
* (with some violations to access 'private' data)
*/
static void qstring_from_str_test(void)
{
QString *qstring;
const char *str = "QEMU";
qstring = qstring_from_str(str);
g_assert(qstring != NULL);
g_assert(qstring->base.refcnt == 1);
g_assert(strcmp(str, qstring->string) == 0);
g_assert(qobject_type(QOBJECT(qstring)) == QTYPE_QSTRING);
qobject_unref(qstring);
}
static void qstring_get_str_test(void)
{
QString *qstring;
const char *ret_str;
const char *str = "QEMU/KVM";
qstring = qstring_from_str(str);
ret_str = qstring_get_str(qstring);
g_assert(strcmp(ret_str, str) == 0);
qobject_unref(qstring);
}
static void qstring_from_substr_test(void)
{
QString *qs;
qs = qstring_from_substr("virtualization", 3, 10);
g_assert(qs != NULL);
g_assert(strcmp(qstring_get_str(qs), "tualiza") == 0);
qobject_unref(qs);
}
static void qobject_to_qstring_test(void)
{
QString *qstring;
qstring = qstring_from_str("foo");
g_assert(qobject_to(QString, QOBJECT(qstring)) == qstring);
qobject_unref(qstring);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/public/from_str", qstring_from_str_test);
g_test_add_func("/public/get_str", qstring_get_str_test);
g_test_add_func("/public/from_substr", qstring_from_substr_test);
g_test_add_func("/public/to_qstring", qobject_to_qstring_test);
return g_test_run();
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2015-2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Richard W.M. Jones <rjones@redhat.com>
*/
#include "qemu/osdep.h"
/* Include this first because it defines QCRYPTO_HAVE_TLS_TEST_SUPPORT */
#include "crypto-tls-x509-helpers.h"
#include "crypto-tls-psk-helpers.h"
#include "qemu/sockets.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
void test_tls_psk_init(const char *pskfile)
{
FILE *fp;
fp = fopen(pskfile, "w");
if (fp == NULL) {
g_critical("Failed to create pskfile %s", pskfile);
abort();
}
/* Don't do this in real applications! Use psktool. */
fprintf(fp, "qemu:009d5638c40fde0c\n");
fclose(fp);
}
void test_tls_psk_cleanup(const char *pskfile)
{
unlink(pskfile);
}
#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */

View file

@ -0,0 +1,33 @@
/*
* Copyright (C) 2015-2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Richard W.M. Jones <rjones@redhat.com>
*/
#ifndef TESTS_CRYPTO_TLS_PSK_HELPERS_H
#define TESTS_CRYPTO_TLS_PSK_HELPERS_H
#include <gnutls/gnutls.h>
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
void test_tls_psk_init(const char *keyfile);
void test_tls_psk_cleanup(const char *keyfile);
#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */
#endif

View file

@ -0,0 +1,508 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include "qemu/osdep.h"
#include "crypto-tls-x509-helpers.h"
#include "crypto/init.h"
#include "qemu/sockets.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
/*
* This stores some static data that is needed when
* encoding extensions in the x509 certs
*/
asn1_node pkix_asn1;
/*
* To avoid consuming random entropy to generate keys,
* here's one we prepared earlier :-)
*/
gnutls_x509_privkey_t privkey;
# define PRIVATE_KEY \
"-----BEGIN RSA PRIVATE KEY-----\n" \
"MIIG5AIBAAKCAYEAyjWyLSNm5PZvYUKUcDWGqbLX10b2ood+YaFjWSnJrqx/q3qh\n" \
"rVGBJglD25AJENJsmZF3zPP1oMhfIxsXu63Hdkb6Rdlc2RUoUP34x9VC1izH25mR\n" \
"6c8DPDp1d6IraZ/llDMI1HsBFz0qGWtvOHgm815XG4PAr/N8rDsuqfv/cJ01KlnO\n" \
"0OdO5QRXCJf9g/dYd41MPu7wOXk9FqjQlmRoP59HgtJ+zUpE4z+Keruw9cMT9VJj\n" \
"0oT+pQ9ysenqeZ3gbT224T1khrEhT5kifhtFLNyDssRchUUWH0hiqoOO1vgb+850\n" \
"W6/1VdxvuPam48py4diSPi1Vip8NITCOBaX9FIpVp4Ruw4rTPVMNMjq9Cpx/DwMP\n" \
"9MbfXfnaVaZaMrmq67/zPhl0eVbUrecH2hQ3ZB9oIF4GkNskzlWF5+yPy6zqk304\n" \
"AKaiFR6jRyh3YfHo2XFqV8x/hxdsIEXOtEUGhSIcpynsW+ckUCartzu7xbhXjd4b\n" \
"kxJT89+riPFYij09AgMBAAECggGBAKyFkaZXXROeejrmHlV6JZGlp+fhgM38gkRz\n" \
"+Jp7P7rLLAY3E7gXIPQ91WqAAmwazFNdvHPd9USfkCQYmnAi/VoZhrCPmlsQZRxt\n" \
"A5QjjOnEvSPMa6SrXZxGWDCg6R8uMCb4P+FhrPWR1thnRDZOtRTQ+crc50p3mHgt\n" \
"6ktXWIJRbqnag8zSfQqCYGtRmhe8sfsWT+Yl4El4+jjaAVU/B364u7+PLmaiphGp\n" \
"BdJfTsTwEpgtGkPj+osDmhzXcZkfq3V+fz5JLkemsCiQKmn4VJRpg8c3ZmE8NPNt\n" \
"gRtGWZ4W3WKDvhotT65WpQx4+6R8Duux/blNPBmH1Upmwd7kj7GYFBArbCjgd9PT\n" \
"xgfCSUZpgOZHHkcgSB+022a8XncXna7WYYij28SLtwImFyu0nNtqECFQHH5u+k6C\n" \
"LRYBSN+3t3At8dQuk01NVrJBndmjmXRfxpqUtTdeaNgVpdUYRY98s30G68NYGSra\n" \
"aEvhhRSghkcLNetkobpY9pUgeqW/tQKBwQDZHHK9nDMt/zk1TxtILeUSitPXcv1/\n" \
"8ufXqO0miHdH23XuXhIEA6Ef26RRVGDGgpjkveDJK/1w5feJ4H/ni4Vclil/cm38\n" \
"OwRqjjd7ElHJX6JQbsxEx/gNTk5/QW1iAL9TXUalgepsSXYT6AJ0/CJv0jmJSJ36\n" \
"YoKMOM8uqzb2KhN6i+RlJRi5iY53kUhWTJq5ArWvNhUzQNSYODI4bNxlsKSBL2Ik\n" \
"LZ5QKHuaEjQet0IlPlfIb4PzMm8CHa/urOcCgcEA7m3zW/lL5bIFoKPjWig5Lbn1\n" \
"aHfrG2ngqzWtgWtfZqMH8OkZc1Mdhhmvd46titjiLjeI+UP/uHXR0068PnrNngzl\n" \
"tTgwlakzu+bWzqhBm1F+3/341st/FEk07r0P/3/PhezVjwfO8c8Exj7pLxH4wrH0\n" \
"ROHgDbClmlJRu6OO78wk1+Vapf5DWa8YfA+q+fdvr7KvgGyytheKMT/b/dsqOq7y\n" \
"qZPjmaJKWAvV3RWG8lWHFSdHx2IAHMHfGr17Y/w7AoHBALzwZeYebeekiVucGSjq\n" \
"T8SgLhT7zCIx+JMUPjVfYzaUhP/Iu7Lkma6IzWm9nW6Drpy5pUpMzwUWDCLfzU9q\n" \
"eseFIl337kEn9wLn+t5OpgAyCqYmlftxbqvdrrBN9uvnrJjWvqk/8wsDrw9JxAGc\n" \
"fjeD4nBXUqvYWLXApoR9mZoGKedmoH9pFig4zlO9ig8YITnKYuQ0k6SD0b8agJHc\n" \
"Ir0YSUDnRGgpjvFBGbeOCe+FGbohk/EpItJc3IAh5740lwKBwAdXd2DjokSmYKn7\n" \
"oeqKxofz6+yVlLW5YuOiuX78sWlVp87xPolgi84vSEnkKM/Xsc8+goc6YstpRVa+\n" \
"W+mImoA9YW1dF5HkLeWhTAf9AlgoAEIhbeIfTgBv6KNZSv7RDrDPBBxtXx/vAfSg\n" \
"x0ldwk0scZsVYXLKd67yzfV7KdGUdaX4N/xYgfZm/9gCG3+q8NN2KxVHQ5F71BOE\n" \
"JeABOaGo9WvnU+DNMIDZjHJMUWVw4MHz/a/UArDf/2CxaPVBNQKBwASg6j4ohSTk\n" \
"J7aE6RQ3OBmmDDpixcoCJt9u9SjHVYMlbs5CEJGVSczk0SG3y8P1lOWNDSRnMksZ\n" \
"xWnHdP/ogcuYMuvK7UACNAF0zNddtzOhzcpNmejFj+WCHYY/UmPr2/Kf6t7Cxk2K\n" \
"3cZ4tqWsiTmBT8Bknmah7L5DrhS+ZBJliDeFAA8fZHdMH0Xjr4UBp9kF90EMTdW1\n" \
"Xr5uz7ZrMsYpYQI7mmyqV9SSjUg4iBXwVSoag1iDJ1K8Qg/L7Semgg==\n" \
"-----END RSA PRIVATE KEY-----\n"
/*
* This loads the private key we defined earlier
*/
static gnutls_x509_privkey_t test_tls_load_key(void)
{
gnutls_x509_privkey_t key;
const gnutls_datum_t data = { (unsigned char *)PRIVATE_KEY,
strlen(PRIVATE_KEY) };
int err;
err = gnutls_x509_privkey_init(&key);
if (err < 0) {
g_critical("Failed to init key %s", gnutls_strerror(err));
abort();
}
err = gnutls_x509_privkey_import(key, &data,
GNUTLS_X509_FMT_PEM);
if (err < 0) {
if (err != GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR &&
err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
g_critical("Failed to import key %s", gnutls_strerror(err));
abort();
}
err = gnutls_x509_privkey_import_pkcs8(
key, &data, GNUTLS_X509_FMT_PEM, NULL, 0);
if (err < 0) {
g_critical("Failed to import PKCS8 key %s", gnutls_strerror(err));
abort();
}
}
return key;
}
void test_tls_init(const char *keyfile)
{
qcrypto_init(&error_abort);
if (asn1_array2tree(pkix_asn1_tab, &pkix_asn1, NULL) != ASN1_SUCCESS) {
abort();
}
privkey = test_tls_load_key();
if (!g_file_set_contents(keyfile, PRIVATE_KEY, -1, NULL)) {
abort();
}
}
void test_tls_cleanup(const char *keyfile)
{
asn1_delete_structure(&pkix_asn1);
unlink(keyfile);
}
/*
* Turns an ASN1 object into a DER encoded byte array
*/
static void test_tls_der_encode(asn1_node src,
const char *src_name,
gnutls_datum_t *res)
{
int size;
char *data = NULL;
size = 0;
asn1_der_coding(src, src_name, NULL, &size, NULL);
data = g_new0(char, size);
asn1_der_coding(src, src_name, data, &size, NULL);
res->data = (unsigned char *)data;
res->size = size;
}
static void
test_tls_get_ipaddr(const char *addrstr,
char **data,
int *datalen)
{
struct addrinfo *res;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
g_assert(getaddrinfo(addrstr, NULL, &hints, &res) == 0);
*datalen = res->ai_addrlen;
*data = g_new(char, *datalen);
memcpy(*data, res->ai_addr, *datalen);
freeaddrinfo(res);
}
/*
* This is a fairly lame x509 certificate generator.
*
* Do not copy/use this code for generating real certificates
* since it leaves out many things that you would want in
* certificates for real world usage.
*
* This is good enough only for doing tests of the QEMU
* TLS certificate code
*/
void
test_tls_generate_cert(QCryptoTLSTestCertReq *req,
gnutls_x509_crt_t ca)
{
gnutls_x509_crt_t crt;
int err;
static char buffer[1024 * 1024];
size_t size = sizeof(buffer);
char serial[5] = { 1, 2, 3, 4, 0 };
gnutls_datum_t der;
time_t start = time(NULL) + (60 * 60 * req->start_offset);
time_t expire = time(NULL) + (60 * 60 * (req->expire_offset
? req->expire_offset : 24));
/*
* Prepare our new certificate object
*/
err = gnutls_x509_crt_init(&crt);
if (err < 0) {
g_critical("Failed to initialize certificate %s", gnutls_strerror(err));
abort();
}
err = gnutls_x509_crt_set_key(crt, privkey);
if (err < 0) {
g_critical("Failed to set certificate key %s", gnutls_strerror(err));
abort();
}
/*
* A v3 certificate is required in order to be able
* set any of the basic constraints, key purpose and
* key usage data
*/
gnutls_x509_crt_set_version(crt, 3);
if (req->country) {
err = gnutls_x509_crt_set_dn_by_oid(
crt, GNUTLS_OID_X520_COUNTRY_NAME, 0,
req->country, strlen(req->country));
if (err < 0) {
g_critical("Failed to set certificate country name %s",
gnutls_strerror(err));
abort();
}
}
if (req->cn) {
err = gnutls_x509_crt_set_dn_by_oid(
crt, GNUTLS_OID_X520_COMMON_NAME, 0,
req->cn, strlen(req->cn));
if (err < 0) {
g_critical("Failed to set certificate common name %s",
gnutls_strerror(err));
abort();
}
}
/*
* Setup the subject altnames, which are used
* for hostname checks in live sessions
*/
if (req->altname1) {
err = gnutls_x509_crt_set_subject_alt_name(
crt, GNUTLS_SAN_DNSNAME,
req->altname1,
strlen(req->altname1),
GNUTLS_FSAN_APPEND);
if (err < 0) {
g_critical("Failed to set certificate alt name %s",
gnutls_strerror(err));
abort();
}
}
if (req->altname2) {
err = gnutls_x509_crt_set_subject_alt_name(
crt, GNUTLS_SAN_DNSNAME,
req->altname2,
strlen(req->altname2),
GNUTLS_FSAN_APPEND);
if (err < 0) {
g_critical("Failed to set certificate %s alt name",
gnutls_strerror(err));
abort();
}
}
/*
* IP address need to be put into the cert in their
* raw byte form, not strings, hence this is a little
* more complicated
*/
if (req->ipaddr1) {
char *data;
int len;
test_tls_get_ipaddr(req->ipaddr1, &data, &len);
err = gnutls_x509_crt_set_subject_alt_name(
crt, GNUTLS_SAN_IPADDRESS,
data, len, GNUTLS_FSAN_APPEND);
if (err < 0) {
g_critical("Failed to set certificate alt name %s",
gnutls_strerror(err));
abort();
}
g_free(data);
}
if (req->ipaddr2) {
char *data;
int len;
test_tls_get_ipaddr(req->ipaddr2, &data, &len);
err = gnutls_x509_crt_set_subject_alt_name(
crt, GNUTLS_SAN_IPADDRESS,
data, len, GNUTLS_FSAN_APPEND);
if (err < 0) {
g_critical("Failed to set certificate alt name %s",
gnutls_strerror(err));
abort();
}
g_free(data);
}
/*
* Basic constraints are used to decide if the cert
* is for a CA or not. We can't use the convenient
* gnutls API for setting this, since it hardcodes
* the 'critical' field which we want control over
*/
if (req->basicConstraintsEnable) {
asn1_node ext = NULL;
asn1_create_element(pkix_asn1, "PKIX1.BasicConstraints", &ext);
asn1_write_value(ext, "cA",
req->basicConstraintsIsCA ? "TRUE" : "FALSE", 1);
asn1_write_value(ext, "pathLenConstraint", NULL, 0);
test_tls_der_encode(ext, "", &der);
err = gnutls_x509_crt_set_extension_by_oid(
crt, "2.5.29.19",
der.data, der.size,
req->basicConstraintsCritical);
if (err < 0) {
g_critical("Failed to set certificate basic constraints %s",
gnutls_strerror(err));
g_free(der.data);
abort();
}
asn1_delete_structure(&ext);
g_free(der.data);
}
/*
* Next up the key usage extension. Again we can't
* use the gnutls API since it hardcodes the extension
* to be 'critical'
*/
if (req->keyUsageEnable) {
asn1_node ext = NULL;
char str[2];
str[0] = req->keyUsageValue & 0xff;
str[1] = (req->keyUsageValue >> 8) & 0xff;
asn1_create_element(pkix_asn1, "PKIX1.KeyUsage", &ext);
asn1_write_value(ext, "", str, 9);
test_tls_der_encode(ext, "", &der);
err = gnutls_x509_crt_set_extension_by_oid(
crt, "2.5.29.15",
der.data, der.size,
req->keyUsageCritical);
if (err < 0) {
g_critical("Failed to set certificate key usage %s",
gnutls_strerror(err));
g_free(der.data);
abort();
}
asn1_delete_structure(&ext);
g_free(der.data);
}
/*
* Finally the key purpose extension. This time
* gnutls has the opposite problem, always hardcoding
* it to be non-critical. So once again we have to
* set this the hard way building up ASN1 data ourselves
*/
if (req->keyPurposeEnable) {
asn1_node ext = NULL;
asn1_create_element(pkix_asn1, "PKIX1.ExtKeyUsageSyntax", &ext);
if (req->keyPurposeOID1) {
asn1_write_value(ext, "", "NEW", 1);
asn1_write_value(ext, "?LAST", req->keyPurposeOID1, 1);
}
if (req->keyPurposeOID2) {
asn1_write_value(ext, "", "NEW", 1);
asn1_write_value(ext, "?LAST", req->keyPurposeOID2, 1);
}
test_tls_der_encode(ext, "", &der);
err = gnutls_x509_crt_set_extension_by_oid(
crt, "2.5.29.37",
der.data, der.size,
req->keyPurposeCritical);
if (err < 0) {
g_critical("Failed to set certificate key purpose %s",
gnutls_strerror(err));
g_free(der.data);
abort();
}
asn1_delete_structure(&ext);
g_free(der.data);
}
/*
* Any old serial number will do, so lets pick 5
*/
err = gnutls_x509_crt_set_serial(crt, serial, 5);
if (err < 0) {
g_critical("Failed to set certificate serial %s",
gnutls_strerror(err));
abort();
}
err = gnutls_x509_crt_set_activation_time(crt, start);
if (err < 0) {
g_critical("Failed to set certificate activation %s",
gnutls_strerror(err));
abort();
}
err = gnutls_x509_crt_set_expiration_time(crt, expire);
if (err < 0) {
g_critical("Failed to set certificate expiration %s",
gnutls_strerror(err));
abort();
}
/*
* If no 'ca' is set then we are self signing
* the cert. This is done for the root CA certs
*/
err = gnutls_x509_crt_sign2(crt, ca ? ca : crt, privkey,
GNUTLS_DIG_SHA256, 0);
if (err < 0) {
g_critical("Failed to sign certificate %s",
gnutls_strerror(err));
abort();
}
/*
* Finally write the new cert out to disk
*/
err = gnutls_x509_crt_export(
crt, GNUTLS_X509_FMT_PEM, buffer, &size);
if (err < 0) {
g_critical("Failed to export certificate %s: %d",
gnutls_strerror(err), err);
abort();
}
if (!g_file_set_contents(req->filename, buffer, -1, NULL)) {
g_critical("Failed to write certificate %s",
req->filename);
abort();
}
req->crt = crt;
}
void test_tls_write_cert_chain(const char *filename,
gnutls_x509_crt_t *certs,
size_t ncerts)
{
size_t i;
size_t capacity = 1024, offset = 0;
char *buffer = g_new0(char, capacity);
int err;
for (i = 0; i < ncerts; i++) {
size_t len = capacity - offset;
retry:
err = gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
buffer + offset, &len);
if (err < 0) {
if (err == GNUTLS_E_SHORT_MEMORY_BUFFER) {
buffer = g_renew(char, buffer, offset + len);
capacity = offset + len;
goto retry;
}
g_critical("Failed to export certificate chain %s: %d",
gnutls_strerror(err), err);
abort();
}
offset += len;
}
if (!g_file_set_contents(filename, buffer, offset, NULL)) {
abort();
}
g_free(buffer);
}
void test_tls_discard_cert(QCryptoTLSTestCertReq *req)
{
if (!req->crt) {
return;
}
gnutls_x509_crt_deinit(req->crt);
req->crt = NULL;
if (getenv("QEMU_TEST_DEBUG_CERTS") == NULL) {
unlink(req->filename);
}
}
#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */

View file

@ -0,0 +1,132 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#ifndef TESTS_CRYPTO_TLS_X509_HELPERS_H
#define TESTS_CRYPTO_TLS_X509_HELPERS_H
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#if !(defined WIN32) && \
defined(CONFIG_TASN1)
# define QCRYPTO_HAVE_TLS_TEST_SUPPORT
#endif
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
# include <libtasn1.h>
/*
* This contains parameter about how to generate
* certificates.
*/
typedef struct QCryptoTLSTestCertReq QCryptoTLSTestCertReq;
struct QCryptoTLSTestCertReq {
gnutls_x509_crt_t crt;
const char *filename;
/* Identifying information */
const char *country;
const char *cn;
const char *altname1;
const char *altname2;
const char *ipaddr1;
const char *ipaddr2;
/* Basic constraints */
bool basicConstraintsEnable;
bool basicConstraintsCritical;
bool basicConstraintsIsCA;
/* Key usage */
bool keyUsageEnable;
bool keyUsageCritical;
int keyUsageValue;
/* Key purpose (aka Extended key usage) */
bool keyPurposeEnable;
bool keyPurposeCritical;
const char *keyPurposeOID1;
const char *keyPurposeOID2;
/* zero for current time, or non-zero for hours from now */
int start_offset;
/* zero for 24 hours from now, or non-zero for hours from now */
int expire_offset;
};
void test_tls_generate_cert(QCryptoTLSTestCertReq *req,
gnutls_x509_crt_t ca);
void test_tls_write_cert_chain(const char *filename,
gnutls_x509_crt_t *certs,
size_t ncerts);
void test_tls_discard_cert(QCryptoTLSTestCertReq *req);
void test_tls_init(const char *keyfile);
void test_tls_cleanup(const char *keyfile);
# define TLS_CERT_REQ(varname, cavarname, \
country, commonname, \
altname1, altname2, \
ipaddr1, ipaddr2, \
basicconsenable, basicconscritical, basicconsca, \
keyusageenable, keyusagecritical, keyusagevalue, \
keypurposeenable, keypurposecritical, \
keypurposeoid1, keypurposeoid2, \
startoffset, endoffset) \
static QCryptoTLSTestCertReq varname = { \
NULL, WORKDIR #varname "-ctx.pem", \
country, commonname, altname1, altname2, \
ipaddr1, ipaddr2, \
basicconsenable, basicconscritical, basicconsca, \
keyusageenable, keyusagecritical, keyusagevalue, \
keypurposeenable, keypurposecritical, \
keypurposeoid1, keypurposeoid2, \
startoffset, endoffset \
}; \
test_tls_generate_cert(&varname, cavarname.crt)
# define TLS_ROOT_REQ(varname, \
country, commonname, \
altname1, altname2, \
ipaddr1, ipaddr2, \
basicconsenable, basicconscritical, basicconsca, \
keyusageenable, keyusagecritical, keyusagevalue, \
keypurposeenable, keypurposecritical, \
keypurposeoid1, keypurposeoid2, \
startoffset, endoffset) \
static QCryptoTLSTestCertReq varname = { \
NULL, WORKDIR #varname "-ctx.pem", \
country, commonname, altname1, altname2, \
ipaddr1, ipaddr2, \
basicconsenable, basicconscritical, basicconsca, \
keyusageenable, keyusagecritical, keyusagevalue, \
keypurposeenable, keypurposecritical, \
keypurposeoid1, keypurposeoid2, \
startoffset, endoffset \
}; \
test_tls_generate_cert(&varname, NULL)
extern const asn1_static_node pkix_asn1_tab[];
#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */
#endif

View file

@ -0,0 +1,163 @@
/*
* QEMU I/O channel test helpers
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "io-channel-helpers.h"
#include "qemu/iov.h"
struct QIOChannelTest {
QIOChannel *src;
QIOChannel *dst;
bool blocking;
size_t len;
size_t niov;
char *input;
struct iovec *inputv;
char *output;
struct iovec *outputv;
Error *writeerr;
Error *readerr;
};
/* This thread sends all data using iovecs */
static gpointer test_io_thread_writer(gpointer opaque)
{
QIOChannelTest *data = opaque;
qio_channel_set_blocking(data->src, data->blocking, NULL);
qio_channel_writev_all(data->src,
data->inputv,
data->niov,
&data->writeerr);
return NULL;
}
/* This thread receives all data using iovecs */
static gpointer test_io_thread_reader(gpointer opaque)
{
QIOChannelTest *data = opaque;
qio_channel_set_blocking(data->dst, data->blocking, NULL);
qio_channel_readv_all(data->dst,
data->outputv,
data->niov,
&data->readerr);
return NULL;
}
QIOChannelTest *qio_channel_test_new(void)
{
QIOChannelTest *data = g_new0(QIOChannelTest, 1);
size_t i;
size_t offset;
/* We'll send 1 MB of data */
#define CHUNK_COUNT 250
#define CHUNK_LEN 4194
data->len = CHUNK_COUNT * CHUNK_LEN;
data->input = g_new0(char, data->len);
data->output = g_new0(gchar, data->len);
/* Fill input with a pattern */
for (i = 0; i < data->len; i += CHUNK_LEN) {
memset(data->input + i, (i / CHUNK_LEN), CHUNK_LEN);
}
/* We'll split the data across a bunch of IO vecs */
data->niov = CHUNK_COUNT;
data->inputv = g_new0(struct iovec, data->niov);
data->outputv = g_new0(struct iovec, data->niov);
for (i = 0, offset = 0; i < data->niov; i++, offset += CHUNK_LEN) {
data->inputv[i].iov_base = data->input + offset;
data->outputv[i].iov_base = data->output + offset;
data->inputv[i].iov_len = CHUNK_LEN;
data->outputv[i].iov_len = CHUNK_LEN;
}
return data;
}
void qio_channel_test_run_threads(QIOChannelTest *test,
bool blocking,
QIOChannel *src,
QIOChannel *dst)
{
GThread *reader, *writer;
test->src = src;
test->dst = dst;
test->blocking = blocking;
reader = g_thread_new("reader",
test_io_thread_reader,
test);
writer = g_thread_new("writer",
test_io_thread_writer,
test);
g_thread_join(reader);
g_thread_join(writer);
test->dst = test->src = NULL;
}
void qio_channel_test_run_writer(QIOChannelTest *test,
QIOChannel *src)
{
test->src = src;
test_io_thread_writer(test);
test->src = NULL;
}
void qio_channel_test_run_reader(QIOChannelTest *test,
QIOChannel *dst)
{
test->dst = dst;
test_io_thread_reader(test);
test->dst = NULL;
}
void qio_channel_test_validate(QIOChannelTest *test)
{
g_assert(test->readerr == NULL);
g_assert(test->writeerr == NULL);
g_assert_cmpint(memcmp(test->input,
test->output,
test->len), ==, 0);
g_free(test->inputv);
g_free(test->outputv);
g_free(test->input);
g_free(test->output);
g_free(test);
}

View file

@ -0,0 +1,41 @@
/*
* QEMU I/O channel test helpers
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TEST_IO_CHANNEL_HELPERS_H
#define TEST_IO_CHANNEL_HELPERS_H
#include "io/channel.h"
typedef struct QIOChannelTest QIOChannelTest;
QIOChannelTest *qio_channel_test_new(void);
void qio_channel_test_run_threads(QIOChannelTest *test,
bool blocking,
QIOChannel *src,
QIOChannel *dst);
void qio_channel_test_run_writer(QIOChannelTest *test,
QIOChannel *src);
void qio_channel_test_run_reader(QIOChannelTest *test,
QIOChannel *dst);
void qio_channel_test_validate(QIOChannelTest *test);
#endif /* TEST_IO_CHANNEL_HELPERS_H */

127
tests/unit/iothread.c Normal file
View file

@ -0,0 +1,127 @@
/*
* Event loop thread implementation for unit tests
*
* Copyright Red Hat Inc., 2013, 2016
*
* Authors:
* Stefan Hajnoczi <stefanha@redhat.com>
* Paolo Bonzini <pbonzini@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "block/aio.h"
#include "qemu/main-loop.h"
#include "qemu/rcu.h"
#include "iothread.h"
struct IOThread {
AioContext *ctx;
GMainContext *worker_context;
GMainLoop *main_loop;
QemuThread thread;
QemuMutex init_done_lock;
QemuCond init_done_cond; /* is thread initialization done? */
bool stopping;
};
static __thread IOThread *my_iothread;
AioContext *qemu_get_current_aio_context(void)
{
return my_iothread ? my_iothread->ctx : qemu_get_aio_context();
}
static void iothread_init_gcontext(IOThread *iothread)
{
GSource *source;
iothread->worker_context = g_main_context_new();
source = aio_get_g_source(iothread_get_aio_context(iothread));
g_source_attach(source, iothread->worker_context);
g_source_unref(source);
iothread->main_loop = g_main_loop_new(iothread->worker_context, TRUE);
}
static void *iothread_run(void *opaque)
{
IOThread *iothread = opaque;
rcu_register_thread();
my_iothread = iothread;
qemu_mutex_lock(&iothread->init_done_lock);
iothread->ctx = aio_context_new(&error_abort);
/*
* We must connect the ctx to a GMainContext, because in older versions
* of glib the g_source_ref()/unref() functions are not threadsafe
* on sources without a context.
*/
iothread_init_gcontext(iothread);
/*
* g_main_context_push_thread_default() must be called before anything
* in this new thread uses glib.
*/
g_main_context_push_thread_default(iothread->worker_context);
qemu_cond_signal(&iothread->init_done_cond);
qemu_mutex_unlock(&iothread->init_done_lock);
while (!qatomic_read(&iothread->stopping)) {
aio_poll(iothread->ctx, true);
}
g_main_context_pop_thread_default(iothread->worker_context);
rcu_unregister_thread();
return NULL;
}
static void iothread_stop_bh(void *opaque)
{
IOThread *iothread = opaque;
iothread->stopping = true;
}
void iothread_join(IOThread *iothread)
{
aio_bh_schedule_oneshot(iothread->ctx, iothread_stop_bh, iothread);
qemu_thread_join(&iothread->thread);
g_main_context_unref(iothread->worker_context);
g_main_loop_unref(iothread->main_loop);
qemu_cond_destroy(&iothread->init_done_cond);
qemu_mutex_destroy(&iothread->init_done_lock);
aio_context_unref(iothread->ctx);
g_free(iothread);
}
IOThread *iothread_new(void)
{
IOThread *iothread = g_new0(IOThread, 1);
qemu_mutex_init(&iothread->init_done_lock);
qemu_cond_init(&iothread->init_done_cond);
qemu_thread_create(&iothread->thread, NULL, iothread_run,
iothread, QEMU_THREAD_JOINABLE);
/* Wait for initialization to complete */
qemu_mutex_lock(&iothread->init_done_lock);
while (iothread->ctx == NULL) {
qemu_cond_wait(&iothread->init_done_cond,
&iothread->init_done_lock);
}
qemu_mutex_unlock(&iothread->init_done_lock);
return iothread;
}
AioContext *iothread_get_aio_context(IOThread *iothread)
{
return iothread->ctx;
}

25
tests/unit/iothread.h Normal file
View file

@ -0,0 +1,25 @@
/*
* Event loop thread implementation for unit tests
*
* Copyright Red Hat Inc., 2013, 2016
*
* Authors:
* Stefan Hajnoczi <stefanha@redhat.com>
* Paolo Bonzini <pbonzini@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#ifndef TEST_IOTHREAD_H
#define TEST_IOTHREAD_H
#include "block/aio.h"
#include "qemu/thread.h"
typedef struct IOThread IOThread;
IOThread *iothread_new(void);
void iothread_join(IOThread *iothread);
AioContext *iothread_get_aio_context(IOThread *iothread);
#endif

184
tests/unit/meson.build Normal file
View file

@ -0,0 +1,184 @@
testblock = declare_dependency(dependencies: [block], sources: 'iothread.c')
tests = {
'check-block-qdict': [],
'check-qdict': [],
'check-qnum': [],
'check-qstring': [],
'check-qlist': [],
'check-qnull': [],
'check-qobject': [],
'check-qjson': [],
'check-qlit': [],
'test-qobject-output-visitor': [testqapi],
'test-clone-visitor': [testqapi],
'test-qobject-input-visitor': [testqapi],
'test-string-input-visitor': [testqapi],
'test-string-output-visitor': [testqapi],
'test-opts-visitor': [testqapi],
'test-visitor-serialization': [testqapi],
'test-bitmap': [],
# all code tested by test-x86-cpuid is inside topology.h
'test-x86-cpuid': [],
'test-cutils': [],
'test-shift128': [],
'test-mul64': [],
# all code tested by test-int128 is inside int128.h
'test-int128': [],
'rcutorture': [],
'test-rcu-list': [],
'test-rcu-simpleq': [],
'test-rcu-tailq': [],
'test-rcu-slist': [],
'test-qdist': [],
'test-qht': [],
'test-bitops': [],
'test-bitcnt': [],
'test-qgraph': ['../qtest/libqos/qgraph.c'],
'check-qom-interface': [qom],
'check-qom-proplist': [qom],
'test-qemu-opts': [],
'test-keyval': [testqapi],
'test-logging': [],
'test-uuid': [],
'ptimer-test': ['ptimer-test-stubs.c', meson.source_root() / 'hw/core/ptimer.c'],
'test-qapi-util': [],
}
if have_system or have_tools
tests += {
'test-qmp-event': [testqapi],
}
endif
if have_block
tests += {
'test-coroutine': [testblock],
'test-aio': [testblock],
'test-aio-multithread': [testblock],
'test-throttle': [testblock],
'test-thread-pool': [testblock],
'test-hbitmap': [testblock],
'test-bdrv-drain': [testblock],
'test-bdrv-graph-mod': [testblock],
'test-blockjob': [testblock],
'test-blockjob-txn': [testblock],
'test-block-backend': [testblock],
'test-block-iothread': [testblock],
'test-write-threshold': [testblock],
'test-crypto-hash': [crypto],
'test-crypto-hmac': [crypto],
'test-crypto-cipher': [crypto],
'test-crypto-secret': [crypto, keyutils],
'test-authz-simple': [authz],
'test-authz-list': [authz],
'test-authz-listfile': [authz],
'test-io-task': [testblock],
'test-io-channel-socket': ['socket-helpers.c', 'io-channel-helpers.c', io],
'test-io-channel-file': ['io-channel-helpers.c', io],
'test-io-channel-command': ['io-channel-helpers.c', io],
'test-io-channel-buffer': ['io-channel-helpers.c', io],
'test-crypto-ivgen': [io],
'test-crypto-afsplit': [io],
'test-crypto-block': [io],
}
if 'CONFIG_GNUTLS' in config_host and \
'CONFIG_TASN1' in config_host and \
'CONFIG_POSIX' in config_host
tests += {
'test-crypto-tlscredsx509': ['crypto-tls-x509-helpers.c', 'pkix_asn1_tab.c',
tasn1, crypto, gnutls],
'test-crypto-tlssession': ['crypto-tls-x509-helpers.c', 'pkix_asn1_tab.c', 'crypto-tls-psk-helpers.c',
tasn1, crypto, gnutls],
'test-io-channel-tls': ['io-channel-helpers.c', 'crypto-tls-x509-helpers.c', 'pkix_asn1_tab.c',
tasn1, io, crypto, gnutls]}
endif
if 'CONFIG_AUTH_PAM' in config_host
tests += {'test-authz-pam': [authz]}
endif
if 'CONFIG_QEMU_PRIVATE_XTS' in config_host
tests += {'test-crypto-xts': [crypto, io]}
endif
if 'CONFIG_POSIX' in config_host
tests += {'test-image-locking': [testblock]}
endif
if 'CONFIG_REPLICATION' in config_host
tests += {'test-replication': [testblock]}
endif
if 'CONFIG_NETTLE' in config_host or 'CONFIG_GCRYPT' in config_host
tests += {'test-crypto-pbkdf': [io]}
endif
if 'CONFIG_EPOLL_CREATE1' in config_host
tests += {'test-fdmon-epoll': [testblock]}
endif
endif
if have_system
tests += {
'test-iov': [],
'test-qmp-cmds': [testqapi],
'test-xbzrle': [migration],
'test-timed-average': [],
'test-util-sockets': ['socket-helpers.c'],
'test-base64': [],
'test-bufferiszero': [],
'test-vmstate': [migration, io]
}
if 'CONFIG_INOTIFY1' in config_host
tests += {'test-util-filemonitor': []}
endif
# Some tests: test-char, test-qdev-global-props, and test-qga,
# are not runnable under TSan due to a known issue.
# https://github.com/google/sanitizers/issues/1116
if 'CONFIG_TSAN' not in config_host
if 'CONFIG_POSIX' in config_host
tests += {
'test-char': ['socket-helpers.c', qom, io, chardev]
}
endif
tests += {
'test-qdev-global-props': [qom, hwcore, testqapi]
}
endif
endif
if 'CONFIG_TSAN' not in config_host and \
'CONFIG_GUEST_AGENT' in config_host and \
'CONFIG_LINUX' in config_host
tests += {'test-qga': ['../qtest/libqtest.c']}
test_deps += {'test-qga': qga}
endif
test_env = environment()
test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
slow_tests = {
'test-crypto-tlscredsx509': 45,
'test-crypto-tlssession': 45
}
foreach test_name, extra: tests
src = [test_name + '.c']
deps = [qemuutil]
if extra.length() > 0
# use a sourceset to quickly separate sources and deps
test_ss = ss.source_set()
test_ss.add(extra)
src += test_ss.all_sources()
deps += test_ss.all_dependencies()
endif
exe = executable(test_name, src, genh, dependencies: deps)
test(test_name, exe,
depends: test_deps.get(test_name, []),
env: test_env,
args: ['--tap', '-k'],
protocol: 'tap',
timeout: slow_tests.get(test_name, 30),
priority: slow_tests.get(test_name, 30),
suite: ['unit'])
endforeach

1108
tests/unit/pkix_asn1_tab.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
/*
* Stubs for the ptimer-test
*
* Copyright (c) 2016 Dmitry Osipenko <digetx@gmail.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "sysemu/replay.h"
#include "migration/vmstate.h"
#include "sysemu/cpu-timers.h"
#include "ptimer-test.h"
const VMStateInfo vmstate_info_uint8;
const VMStateInfo vmstate_info_uint32;
const VMStateInfo vmstate_info_uint64;
const VMStateInfo vmstate_info_int64;
const VMStateInfo vmstate_info_timer;
struct QEMUBH {
QEMUBHFunc *cb;
void *opaque;
};
QEMUTimerListGroup main_loop_tlg;
int64_t ptimer_test_time_ns;
/* under qtest_enabled(), will not artificially limit period - see hw/core/ptimer.c. */
int use_icount;
bool qtest_allowed;
void timer_init_full(QEMUTimer *ts,
QEMUTimerListGroup *timer_list_group, QEMUClockType type,
int scale, int attributes,
QEMUTimerCB *cb, void *opaque)
{
if (!timer_list_group) {
timer_list_group = &main_loop_tlg;
}
ts->timer_list = timer_list_group->tl[type];
ts->cb = cb;
ts->opaque = opaque;
ts->scale = scale;
ts->attributes = attributes;
ts->expire_time = -1;
}
void timer_mod(QEMUTimer *ts, int64_t expire_time)
{
QEMUTimerList *timer_list = ts->timer_list;
QEMUTimer *t = &timer_list->active_timers;
while (t->next != NULL) {
if (t->next == ts) {
break;
}
t = t->next;
}
ts->expire_time = MAX(expire_time * ts->scale, 0);
ts->next = NULL;
t->next = ts;
}
void timer_del(QEMUTimer *ts)
{
QEMUTimerList *timer_list = ts->timer_list;
QEMUTimer *t = &timer_list->active_timers;
while (t->next != NULL) {
if (t->next == ts) {
t->next = ts->next;
return;
}
t = t->next;
}
}
int64_t qemu_clock_get_ns(QEMUClockType type)
{
return ptimer_test_time_ns;
}
int64_t qemu_clock_deadline_ns_all(QEMUClockType type, int attr_mask)
{
QEMUTimerList *timer_list = main_loop_tlg.tl[QEMU_CLOCK_VIRTUAL];
QEMUTimer *t = timer_list->active_timers.next;
int64_t deadline = -1;
while (t != NULL) {
if (deadline == -1) {
deadline = t->expire_time;
} else {
deadline = MIN(deadline, t->expire_time);
}
t = t->next;
}
return deadline;
}
QEMUBH *qemu_bh_new(QEMUBHFunc *cb, void *opaque)
{
QEMUBH *bh = g_new(QEMUBH, 1);
bh->cb = cb;
bh->opaque = opaque;
return bh;
}
void qemu_bh_delete(QEMUBH *bh)
{
g_free(bh);
}

892
tests/unit/ptimer-test.c Normal file
View file

@ -0,0 +1,892 @@
/*
* QTest testcase for the ptimer
*
* Copyright (c) 2016 Dmitry Osipenko <digetx@gmail.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include <glib/gprintf.h>
#include "qemu/main-loop.h"
#include "hw/ptimer.h"
#include "ptimer-test.h"
static bool triggered;
static void ptimer_trigger(void *opaque)
{
triggered = true;
}
static void ptimer_test_expire_qemu_timers(int64_t expire_time,
QEMUClockType type)
{
QEMUTimerList *timer_list = main_loop_tlg.tl[type];
QEMUTimer *t = timer_list->active_timers.next;
while (t != NULL) {
if (t->expire_time == expire_time) {
timer_del(t);
if (t->cb != NULL) {
t->cb(t->opaque);
}
}
t = t->next;
}
}
static void ptimer_test_set_qemu_time_ns(int64_t ns)
{
ptimer_test_time_ns = ns;
}
static void qemu_clock_step(uint64_t ns)
{
int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
QEMU_TIMER_ATTR_ALL);
int64_t advanced_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns;
while (deadline != -1 && deadline <= advanced_time) {
ptimer_test_set_qemu_time_ns(deadline);
ptimer_test_expire_qemu_timers(deadline, QEMU_CLOCK_VIRTUAL);
deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL,
QEMU_TIMER_ATTR_ALL);
}
ptimer_test_set_qemu_time_ns(advanced_time);
}
static void check_set_count(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 1000);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 1000);
g_assert_false(triggered);
ptimer_free(ptimer);
}
static void check_set_limit(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_limit(ptimer, 1000, 0);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_cmpuint(ptimer_get_limit(ptimer), ==, 1000);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_limit(ptimer, 2000, 1);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 2000);
g_assert_cmpuint(ptimer_get_limit(ptimer), ==, 2000);
g_assert_false(triggered);
ptimer_free(ptimer);
}
static void check_oneshot(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool no_round_down = (*policy & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 2000000);
ptimer_set_count(ptimer, 10);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 2 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 8 : 7);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_stop(ptimer);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 8 : 7);
g_assert_false(triggered);
qemu_clock_step(2000000 * 11);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 8 : 7);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 7 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 1 : 0);
if (no_round_down) {
g_assert_false(triggered);
} else {
g_assert_true(triggered);
triggered = false;
}
qemu_clock_step(2000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
if (no_round_down) {
g_assert_true(triggered);
triggered = false;
} else {
g_assert_false(triggered);
}
qemu_clock_step(4000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 10);
ptimer_transaction_commit(ptimer);
qemu_clock_step(20000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 10);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_limit(ptimer, 9, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(20000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 9);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 8 : 7);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 20);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 19 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 1 : 0);
g_assert_false(triggered);
qemu_clock_step(2000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_true(triggered);
ptimer_transaction_begin(ptimer);
ptimer_stop(ptimer);
ptimer_transaction_commit(ptimer);
triggered = false;
qemu_clock_step(2000000 * 12 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_false(triggered);
ptimer_free(ptimer);
}
static void check_periodic(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool wrap_policy = (*policy & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD);
bool no_immediate_trigger = (*policy & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER);
bool no_immediate_reload = (*policy & PTIMER_POLICY_NO_IMMEDIATE_RELOAD);
bool no_round_down = (*policy & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
bool trig_only_on_dec = (*policy & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 2000000);
ptimer_set_limit(ptimer, 10, 1);
ptimer_run(ptimer, 0);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 10);
g_assert_false(triggered);
qemu_clock_step(1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 10 : 9);
g_assert_false(triggered);
qemu_clock_step(2000000 * 10 - 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, wrap_policy ? 0 : 10);
g_assert_true(triggered);
qemu_clock_step(1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
wrap_policy ? 0 : (no_round_down ? 10 : 9));
g_assert_true(triggered);
triggered = false;
qemu_clock_step(2000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 9 : 8) + (wrap_policy ? 1 : 0));
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 20);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 20);
g_assert_false(triggered);
qemu_clock_step(1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 20 : 19);
g_assert_false(triggered);
qemu_clock_step(2000000 * 11 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 9 : 8);
g_assert_false(triggered);
qemu_clock_step(2000000 * 10);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 9 : 8) + (wrap_policy ? 1 : 0));
g_assert_true(triggered);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 3);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 3);
g_assert_false(triggered);
qemu_clock_step(1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 3 : 2);
g_assert_false(triggered);
qemu_clock_step(2000000 * 4);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 9 : 8) + (wrap_policy ? 1 : 0));
g_assert_true(triggered);
ptimer_transaction_begin(ptimer);
ptimer_stop(ptimer);
ptimer_transaction_commit(ptimer);
triggered = false;
qemu_clock_step(2000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 9 : 8) + (wrap_policy ? 1 : 0));
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 3);
ptimer_run(ptimer, 0);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 3 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
wrap_policy ? 0 : (no_round_down ? 10 : 9));
g_assert_true(triggered);
triggered = false;
qemu_clock_step(2000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 9 : 8) + (wrap_policy ? 1 : 0));
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 0);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
no_immediate_reload ? 0 : 10);
if (no_immediate_trigger || trig_only_on_dec) {
g_assert_false(triggered);
} else {
g_assert_true(triggered);
}
triggered = false;
qemu_clock_step(1);
if (no_immediate_reload) {
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_false(triggered);
qemu_clock_step(2000000);
if (no_immediate_trigger) {
g_assert_true(triggered);
} else {
g_assert_false(triggered);
}
triggered = false;
}
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 10 : 9);
g_assert_false(triggered);
qemu_clock_step(2000000 * 12);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 8 : 7) + (wrap_policy ? 1 : 0));
g_assert_true(triggered);
ptimer_transaction_begin(ptimer);
ptimer_stop(ptimer);
ptimer_transaction_commit(ptimer);
triggered = false;
qemu_clock_step(2000000 * 10);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 8 : 7) + (wrap_policy ? 1 : 0));
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_run(ptimer, 0);
ptimer_transaction_commit(ptimer);
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 0);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 8 : 7) + (wrap_policy ? 1 : 0));
g_assert_false(triggered);
ptimer_free(ptimer);
}
static void check_on_the_fly_mode_change(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool wrap_policy = (*policy & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD);
bool no_round_down = (*policy & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 2000000);
ptimer_set_limit(ptimer, 10, 1);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 9 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 1 : 0);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_run(ptimer, 0);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 1 : 0);
g_assert_false(triggered);
qemu_clock_step(2000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
wrap_policy ? 0 : (no_round_down ? 10 : 9));
g_assert_true(triggered);
triggered = false;
qemu_clock_step(2000000 * 9);
ptimer_transaction_begin(ptimer);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
(no_round_down ? 1 : 0) + (wrap_policy ? 1 : 0));
g_assert_false(triggered);
qemu_clock_step(2000000 * 3);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_true(triggered);
ptimer_free(ptimer);
}
static void check_on_the_fly_period_change(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool no_round_down = (*policy & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 2000000);
ptimer_set_limit(ptimer, 8, 1);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 4 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 4 : 3);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 4000000);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 4 : 3);
qemu_clock_step(4000000 * 2 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 2 : 0);
g_assert_false(triggered);
qemu_clock_step(4000000 * 2);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_true(triggered);
ptimer_free(ptimer);
}
static void check_on_the_fly_freq_change(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool no_round_down = (*policy & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_freq(ptimer, 500);
ptimer_set_limit(ptimer, 8, 1);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 4 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 4 : 3);
g_assert_false(triggered);
ptimer_transaction_begin(ptimer);
ptimer_set_freq(ptimer, 250);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 4 : 3);
qemu_clock_step(2000000 * 4 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 2 : 0);
g_assert_false(triggered);
qemu_clock_step(2000000 * 4);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_true(triggered);
ptimer_free(ptimer);
}
static void check_run_with_period_0(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 99);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
qemu_clock_step(10 * NANOSECONDS_PER_SECOND);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 99);
g_assert_false(triggered);
ptimer_free(ptimer);
}
static void check_run_with_delta_0(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool wrap_policy = (*policy & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD);
bool no_immediate_trigger = (*policy & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER);
bool no_immediate_reload = (*policy & PTIMER_POLICY_NO_IMMEDIATE_RELOAD);
bool no_round_down = (*policy & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
bool trig_only_on_dec = (*policy & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 2000000);
ptimer_set_limit(ptimer, 99, 0);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
no_immediate_reload ? 0 : 99);
if (no_immediate_trigger || trig_only_on_dec) {
g_assert_false(triggered);
} else {
g_assert_true(triggered);
}
triggered = false;
if (no_immediate_trigger || no_immediate_reload) {
qemu_clock_step(2000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
no_immediate_reload ? 0 : (no_round_down ? 98 : 97));
if (no_immediate_trigger && no_immediate_reload) {
g_assert_true(triggered);
triggered = false;
} else {
g_assert_false(triggered);
}
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 99);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
}
qemu_clock_step(2000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 98 : 97);
g_assert_false(triggered);
qemu_clock_step(2000000 * 97);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 1 : 0);
g_assert_false(triggered);
qemu_clock_step(2000000 * 2);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_true(triggered);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 0);
ptimer_run(ptimer, 0);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
no_immediate_reload ? 0 : 99);
if (no_immediate_trigger || trig_only_on_dec) {
g_assert_false(triggered);
} else {
g_assert_true(triggered);
}
triggered = false;
qemu_clock_step(1);
if (no_immediate_reload) {
qemu_clock_step(2000000);
}
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 99 : 98);
if (no_immediate_reload && no_immediate_trigger) {
g_assert_true(triggered);
} else {
g_assert_false(triggered);
}
triggered = false;
qemu_clock_step(2000000);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, no_round_down ? 98 : 97);
g_assert_false(triggered);
qemu_clock_step(2000000 * 98);
g_assert_cmpuint(ptimer_get_count(ptimer), ==,
wrap_policy ? 0 : (no_round_down ? 99 : 98));
g_assert_true(triggered);
ptimer_transaction_begin(ptimer);
ptimer_stop(ptimer);
ptimer_transaction_commit(ptimer);
ptimer_free(ptimer);
}
static void check_periodic_with_load_0(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool continuous_trigger = (*policy & PTIMER_POLICY_CONTINUOUS_TRIGGER);
bool no_immediate_trigger = (*policy & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER);
bool trig_only_on_dec = (*policy & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 2000000);
ptimer_run(ptimer, 0);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
if (no_immediate_trigger || trig_only_on_dec) {
g_assert_false(triggered);
} else {
g_assert_true(triggered);
}
triggered = false;
qemu_clock_step(2000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
if (continuous_trigger || no_immediate_trigger) {
g_assert_true(triggered);
} else {
g_assert_false(triggered);
}
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_count(ptimer, 10);
ptimer_run(ptimer, 0);
ptimer_transaction_commit(ptimer);
qemu_clock_step(2000000 * 10 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
g_assert_true(triggered);
triggered = false;
qemu_clock_step(2000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
if (continuous_trigger) {
g_assert_true(triggered);
} else {
g_assert_false(triggered);
}
ptimer_transaction_begin(ptimer);
ptimer_stop(ptimer);
ptimer_transaction_commit(ptimer);
ptimer_free(ptimer);
}
static void check_oneshot_with_load_0(gconstpointer arg)
{
const uint8_t *policy = arg;
ptimer_state *ptimer = ptimer_init(ptimer_trigger, NULL, *policy);
bool no_immediate_trigger = (*policy & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER);
bool trig_only_on_dec = (*policy & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT);
triggered = false;
ptimer_transaction_begin(ptimer);
ptimer_set_period(ptimer, 2000000);
ptimer_run(ptimer, 1);
ptimer_transaction_commit(ptimer);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
if (no_immediate_trigger || trig_only_on_dec) {
g_assert_false(triggered);
} else {
g_assert_true(triggered);
}
triggered = false;
qemu_clock_step(2000000 + 1);
g_assert_cmpuint(ptimer_get_count(ptimer), ==, 0);
if (no_immediate_trigger) {
g_assert_true(triggered);
} else {
g_assert_false(triggered);
}
ptimer_free(ptimer);
}
static void add_ptimer_tests(uint8_t policy)
{
char policy_name[256] = "";
char *tmp;
if (policy == PTIMER_POLICY_DEFAULT) {
g_sprintf(policy_name, "default");
}
if (policy & PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD) {
g_strlcat(policy_name, "wrap_after_one_period,", 256);
}
if (policy & PTIMER_POLICY_CONTINUOUS_TRIGGER) {
g_strlcat(policy_name, "continuous_trigger,", 256);
}
if (policy & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER) {
g_strlcat(policy_name, "no_immediate_trigger,", 256);
}
if (policy & PTIMER_POLICY_NO_IMMEDIATE_RELOAD) {
g_strlcat(policy_name, "no_immediate_reload,", 256);
}
if (policy & PTIMER_POLICY_NO_COUNTER_ROUND_DOWN) {
g_strlcat(policy_name, "no_counter_rounddown,", 256);
}
if (policy & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT) {
g_strlcat(policy_name, "trigger_only_on_decrement,", 256);
}
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/set_count policy=%s", policy_name),
g_memdup(&policy, 1), check_set_count, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/set_limit policy=%s", policy_name),
g_memdup(&policy, 1), check_set_limit, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/oneshot policy=%s", policy_name),
g_memdup(&policy, 1), check_oneshot, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/periodic policy=%s", policy_name),
g_memdup(&policy, 1), check_periodic, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/on_the_fly_mode_change policy=%s",
policy_name),
g_memdup(&policy, 1), check_on_the_fly_mode_change, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/on_the_fly_period_change policy=%s",
policy_name),
g_memdup(&policy, 1), check_on_the_fly_period_change, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/on_the_fly_freq_change policy=%s",
policy_name),
g_memdup(&policy, 1), check_on_the_fly_freq_change, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/run_with_period_0 policy=%s",
policy_name),
g_memdup(&policy, 1), check_run_with_period_0, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/run_with_delta_0 policy=%s",
policy_name),
g_memdup(&policy, 1), check_run_with_delta_0, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/periodic_with_load_0 policy=%s",
policy_name),
g_memdup(&policy, 1), check_periodic_with_load_0, g_free);
g_free(tmp);
g_test_add_data_func_full(
tmp = g_strdup_printf("/ptimer/oneshot_with_load_0 policy=%s",
policy_name),
g_memdup(&policy, 1), check_oneshot_with_load_0, g_free);
g_free(tmp);
}
static void add_all_ptimer_policies_comb_tests(void)
{
int last_policy = PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT;
int policy = PTIMER_POLICY_DEFAULT;
for (; policy < (last_policy << 1); policy++) {
if ((policy & PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT) &&
(policy & PTIMER_POLICY_NO_IMMEDIATE_TRIGGER)) {
/* Incompatible policy flag settings -- don't try to test them */
continue;
}
add_ptimer_tests(policy);
}
}
int main(int argc, char **argv)
{
int i;
g_test_init(&argc, &argv, NULL);
for (i = 0; i < QEMU_CLOCK_MAX; i++) {
main_loop_tlg.tl[i] = g_new0(QEMUTimerList, 1);
}
add_all_ptimer_policies_comb_tests();
qtest_allowed = true;
return g_test_run();
}

22
tests/unit/ptimer-test.h Normal file
View file

@ -0,0 +1,22 @@
/*
* QTest testcase for the ptimer
*
* Copyright (c) 2016 Dmitry Osipenko <digetx@gmail.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/
#ifndef PTIMER_TEST_H
#define PTIMER_TEST_H
extern bool qtest_allowed;
extern int64_t ptimer_test_time_ns;
struct QEMUTimerList {
QEMUTimer active_timers;
};
#endif

483
tests/unit/rcutorture.c Normal file
View file

@ -0,0 +1,483 @@
/*
* rcutorture.c: simple user-level performance/stress test of RCU.
*
* Usage:
* ./rcu <nreaders> rperf [ <seconds> ]
* Run a read-side performance test with the specified
* number of readers for <seconds> seconds.
* ./rcu <nupdaters> uperf [ <seconds> ]
* Run an update-side performance test with the specified
* number of updaters and specified duration.
* ./rcu <nreaders> perf [ <seconds> ]
* Run a combined read/update performance test with the specified
* number of readers and one updater and specified duration.
*
* The above tests produce output as follows:
*
* n_reads: 46008000 n_updates: 146026 nreaders: 2 nupdaters: 1 duration: 1
* ns/read: 43.4707 ns/update: 6848.1
*
* The first line lists the total number of RCU reads and updates executed
* during the test, the number of reader threads, the number of updater
* threads, and the duration of the test in seconds. The second line
* lists the average duration of each type of operation in nanoseconds,
* or "nan" if the corresponding type of operation was not performed.
*
* ./rcu <nreaders> stress [ <seconds> ]
* Run a stress test with the specified number of readers and
* one updater.
*
* This test produces output as follows:
*
* n_reads: 114633217 n_updates: 3903415 n_mberror: 0
* rcu_stress_count: 114618391 14826 0 0 0 0 0 0 0 0 0
*
* The first line lists the number of RCU read and update operations
* executed, followed by the number of memory-ordering violations
* (which will be zero in a correct RCU implementation). The second
* line lists the number of readers observing progressively more stale
* data. A correct RCU implementation will have all but the first two
* numbers non-zero.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (c) 2008 Paul E. McKenney, IBM Corporation.
*/
/*
* Test variables.
*/
#include "qemu/osdep.h"
#include "qemu/atomic.h"
#include "qemu/rcu.h"
#include "qemu/thread.h"
int nthreadsrunning;
#define GOFLAG_INIT 0
#define GOFLAG_RUN 1
#define GOFLAG_STOP 2
static volatile int goflag = GOFLAG_INIT;
#define RCU_READ_RUN 1000
#define NR_THREADS 100
static QemuThread threads[NR_THREADS];
static struct rcu_reader_data *data[NR_THREADS];
static int n_threads;
/*
* Statistical counts
*
* These are the sum of local counters at the end of a run.
* Updates are protected by a mutex.
*/
static QemuMutex counts_mutex;
long long n_reads = 0LL;
long n_updates = 0L;
static void create_thread(void *(*func)(void *))
{
if (n_threads >= NR_THREADS) {
fprintf(stderr, "Thread limit of %d exceeded!\n", NR_THREADS);
exit(-1);
}
qemu_thread_create(&threads[n_threads], "test", func, &data[n_threads],
QEMU_THREAD_JOINABLE);
n_threads++;
}
static void wait_all_threads(void)
{
int i;
for (i = 0; i < n_threads; i++) {
qemu_thread_join(&threads[i]);
}
n_threads = 0;
}
/*
* Performance test.
*/
static void *rcu_read_perf_test(void *arg)
{
int i;
long long n_reads_local = 0;
rcu_register_thread();
*(struct rcu_reader_data **)arg = &rcu_reader;
qatomic_inc(&nthreadsrunning);
while (goflag == GOFLAG_INIT) {
g_usleep(1000);
}
while (goflag == GOFLAG_RUN) {
for (i = 0; i < RCU_READ_RUN; i++) {
rcu_read_lock();
rcu_read_unlock();
}
n_reads_local += RCU_READ_RUN;
}
qemu_mutex_lock(&counts_mutex);
n_reads += n_reads_local;
qemu_mutex_unlock(&counts_mutex);
rcu_unregister_thread();
return NULL;
}
static void *rcu_update_perf_test(void *arg)
{
long long n_updates_local = 0;
rcu_register_thread();
*(struct rcu_reader_data **)arg = &rcu_reader;
qatomic_inc(&nthreadsrunning);
while (goflag == GOFLAG_INIT) {
g_usleep(1000);
}
while (goflag == GOFLAG_RUN) {
synchronize_rcu();
n_updates_local++;
}
qemu_mutex_lock(&counts_mutex);
n_updates += n_updates_local;
qemu_mutex_unlock(&counts_mutex);
rcu_unregister_thread();
return NULL;
}
static void perftestinit(void)
{
nthreadsrunning = 0;
}
static void perftestrun(int nthreads, int duration, int nreaders, int nupdaters)
{
while (qatomic_read(&nthreadsrunning) < nthreads) {
g_usleep(1000);
}
goflag = GOFLAG_RUN;
g_usleep(duration * G_USEC_PER_SEC);
goflag = GOFLAG_STOP;
wait_all_threads();
printf("n_reads: %lld n_updates: %ld nreaders: %d nupdaters: %d duration: %d\n",
n_reads, n_updates, nreaders, nupdaters, duration);
printf("ns/read: %g ns/update: %g\n",
((duration * 1000*1000*1000.*(double)nreaders) /
(double)n_reads),
((duration * 1000*1000*1000.*(double)nupdaters) /
(double)n_updates));
exit(0);
}
static void perftest(int nreaders, int duration)
{
int i;
perftestinit();
for (i = 0; i < nreaders; i++) {
create_thread(rcu_read_perf_test);
}
create_thread(rcu_update_perf_test);
perftestrun(i + 1, duration, nreaders, 1);
}
static void rperftest(int nreaders, int duration)
{
int i;
perftestinit();
for (i = 0; i < nreaders; i++) {
create_thread(rcu_read_perf_test);
}
perftestrun(i, duration, nreaders, 0);
}
static void uperftest(int nupdaters, int duration)
{
int i;
perftestinit();
for (i = 0; i < nupdaters; i++) {
create_thread(rcu_update_perf_test);
}
perftestrun(i, duration, 0, nupdaters);
}
/*
* Stress test.
*/
#define RCU_STRESS_PIPE_LEN 10
struct rcu_stress {
int age; /* how many update cycles while not rcu_stress_current */
int mbtest;
};
struct rcu_stress rcu_stress_array[RCU_STRESS_PIPE_LEN] = { { 0 } };
struct rcu_stress *rcu_stress_current;
int n_mberror;
/* Updates protected by counts_mutex */
long long rcu_stress_count[RCU_STRESS_PIPE_LEN + 1];
static void *rcu_read_stress_test(void *arg)
{
int i;
struct rcu_stress *p;
int pc;
long long n_reads_local = 0;
long long rcu_stress_local[RCU_STRESS_PIPE_LEN + 1] = { 0 };
volatile int garbage = 0;
rcu_register_thread();
*(struct rcu_reader_data **)arg = &rcu_reader;
while (goflag == GOFLAG_INIT) {
g_usleep(1000);
}
while (goflag == GOFLAG_RUN) {
rcu_read_lock();
p = qatomic_rcu_read(&rcu_stress_current);
if (qatomic_read(&p->mbtest) == 0) {
n_mberror++;
}
rcu_read_lock();
for (i = 0; i < 100; i++) {
garbage++;
}
rcu_read_unlock();
pc = qatomic_read(&p->age);
rcu_read_unlock();
if ((pc > RCU_STRESS_PIPE_LEN) || (pc < 0)) {
pc = RCU_STRESS_PIPE_LEN;
}
rcu_stress_local[pc]++;
n_reads_local++;
}
qemu_mutex_lock(&counts_mutex);
n_reads += n_reads_local;
for (i = 0; i <= RCU_STRESS_PIPE_LEN; i++) {
rcu_stress_count[i] += rcu_stress_local[i];
}
qemu_mutex_unlock(&counts_mutex);
rcu_unregister_thread();
return NULL;
}
/*
* Stress Test Updater
*
* The updater cycles around updating rcu_stress_current to point at
* one of the rcu_stress_array_entries and resets it's age. It
* then increments the age of all the other entries. The age
* will be read under an rcu_read_lock() and distribution of values
* calculated. The final result gives an indication of how many
* previously current rcu_stress entries are in flight until the RCU
* cycle complete.
*/
static void *rcu_update_stress_test(void *arg)
{
int i, rcu_stress_idx = 0;
struct rcu_stress *cp = qatomic_read(&rcu_stress_current);
rcu_register_thread();
*(struct rcu_reader_data **)arg = &rcu_reader;
while (goflag == GOFLAG_INIT) {
g_usleep(1000);
}
while (goflag == GOFLAG_RUN) {
struct rcu_stress *p;
rcu_stress_idx++;
if (rcu_stress_idx >= RCU_STRESS_PIPE_LEN) {
rcu_stress_idx = 0;
}
p = &rcu_stress_array[rcu_stress_idx];
/* catching up with ourselves would be a bug */
assert(p != cp);
qatomic_set(&p->mbtest, 0);
smp_mb();
qatomic_set(&p->age, 0);
qatomic_set(&p->mbtest, 1);
qatomic_rcu_set(&rcu_stress_current, p);
cp = p;
/*
* New RCU structure is now live, update pipe counts on old
* ones.
*/
for (i = 0; i < RCU_STRESS_PIPE_LEN; i++) {
if (i != rcu_stress_idx) {
qatomic_set(&rcu_stress_array[i].age,
rcu_stress_array[i].age + 1);
}
}
synchronize_rcu();
n_updates++;
}
rcu_unregister_thread();
return NULL;
}
static void *rcu_fake_update_stress_test(void *arg)
{
rcu_register_thread();
*(struct rcu_reader_data **)arg = &rcu_reader;
while (goflag == GOFLAG_INIT) {
g_usleep(1000);
}
while (goflag == GOFLAG_RUN) {
synchronize_rcu();
g_usleep(1000);
}
rcu_unregister_thread();
return NULL;
}
static void stresstest(int nreaders, int duration)
{
int i;
rcu_stress_current = &rcu_stress_array[0];
rcu_stress_current->age = 0;
rcu_stress_current->mbtest = 1;
for (i = 0; i < nreaders; i++) {
create_thread(rcu_read_stress_test);
}
create_thread(rcu_update_stress_test);
for (i = 0; i < 5; i++) {
create_thread(rcu_fake_update_stress_test);
}
goflag = GOFLAG_RUN;
g_usleep(duration * G_USEC_PER_SEC);
goflag = GOFLAG_STOP;
wait_all_threads();
printf("n_reads: %lld n_updates: %ld n_mberror: %d\n",
n_reads, n_updates, n_mberror);
printf("rcu_stress_count:");
for (i = 0; i <= RCU_STRESS_PIPE_LEN; i++) {
printf(" %lld", rcu_stress_count[i]);
}
printf("\n");
exit(0);
}
/* GTest interface */
static void gtest_stress(int nreaders, int duration)
{
int i;
rcu_stress_current = &rcu_stress_array[0];
rcu_stress_current->age = 0;
rcu_stress_current->mbtest = 1;
for (i = 0; i < nreaders; i++) {
create_thread(rcu_read_stress_test);
}
create_thread(rcu_update_stress_test);
for (i = 0; i < 5; i++) {
create_thread(rcu_fake_update_stress_test);
}
goflag = GOFLAG_RUN;
g_usleep(duration * G_USEC_PER_SEC);
goflag = GOFLAG_STOP;
wait_all_threads();
g_assert_cmpint(n_mberror, ==, 0);
for (i = 2; i <= RCU_STRESS_PIPE_LEN; i++) {
g_assert_cmpint(rcu_stress_count[i], ==, 0);
}
}
static void gtest_stress_1_1(void)
{
gtest_stress(1, 1);
}
static void gtest_stress_10_1(void)
{
gtest_stress(10, 1);
}
static void gtest_stress_1_5(void)
{
gtest_stress(1, 5);
}
static void gtest_stress_10_5(void)
{
gtest_stress(10, 5);
}
/*
* Mainprogram.
*/
static void usage(int argc, char *argv[])
{
fprintf(stderr, "Usage: %s [nreaders [ [r|u]perf | stress [duration]]\n",
argv[0]);
exit(-1);
}
int main(int argc, char *argv[])
{
int nreaders = 1;
int duration = 1;
qemu_mutex_init(&counts_mutex);
if (argc >= 2 && argv[1][0] == '-') {
g_test_init(&argc, &argv, NULL);
if (g_test_quick()) {
g_test_add_func("/rcu/torture/1reader", gtest_stress_1_1);
g_test_add_func("/rcu/torture/10readers", gtest_stress_10_1);
} else {
g_test_add_func("/rcu/torture/1reader", gtest_stress_1_5);
g_test_add_func("/rcu/torture/10readers", gtest_stress_10_5);
}
return g_test_run();
}
if (argc >= 2) {
nreaders = strtoul(argv[1], NULL, 0);
}
if (argc > 3) {
duration = strtoul(argv[3], NULL, 0);
}
if (argc < 3 || strcmp(argv[2], "stress") == 0) {
stresstest(nreaders, duration);
} else if (strcmp(argv[2], "rperf") == 0) {
rperftest(nreaders, duration);
} else if (strcmp(argv[2], "uperf") == 0) {
uperftest(nreaders, duration);
} else if (strcmp(argv[2], "perf") == 0) {
perftest(nreaders, duration);
}
usage(argc, argv);
return 0;
}

157
tests/unit/socket-helpers.c Normal file
View file

@ -0,0 +1,157 @@
/*
* Helper functions for tests using sockets
*
* Copyright 2015-2018 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qemu/sockets.h"
#include "socket-helpers.h"
#ifndef AI_ADDRCONFIG
# define AI_ADDRCONFIG 0
#endif
#ifndef EAI_ADDRFAMILY
# define EAI_ADDRFAMILY 0
#endif
/*
* @hostname: a DNS name or numeric IP address
*
* Check whether it is possible to bind & connect to ports
* on the DNS name or IP address @hostname. If an IP address
* is used, it must not be a wildcard address.
*
* Returns 0 on success, -1 on error with errno set
*/
static int socket_can_bind_connect(const char *hostname, int family)
{
int lfd = -1, cfd = -1, afd = -1;
struct addrinfo ai, *res = NULL;
struct sockaddr_storage ss;
socklen_t sslen = sizeof(ss);
int soerr;
socklen_t soerrlen = sizeof(soerr);
bool check_soerr = false;
int rc;
int ret = -1;
memset(&ai, 0, sizeof(ai));
ai.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
ai.ai_family = family;
ai.ai_socktype = SOCK_STREAM;
/* lookup */
rc = getaddrinfo(hostname, NULL, &ai, &res);
if (rc != 0) {
if (rc == EAI_ADDRFAMILY || rc == EAI_FAMILY || rc == EAI_NONAME) {
errno = EADDRNOTAVAIL;
} else {
errno = EINVAL;
}
goto cleanup;
}
lfd = qemu_socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (lfd < 0) {
goto cleanup;
}
cfd = qemu_socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (cfd < 0) {
goto cleanup;
}
if (bind(lfd, res->ai_addr, res->ai_addrlen) < 0) {
goto cleanup;
}
if (listen(lfd, 1) < 0) {
goto cleanup;
}
if (getsockname(lfd, (struct sockaddr *)&ss, &sslen) < 0) {
goto cleanup;
}
qemu_set_nonblock(cfd);
if (connect(cfd, (struct sockaddr *)&ss, sslen) < 0) {
if (errno == EINPROGRESS) {
check_soerr = true;
} else {
goto cleanup;
}
}
sslen = sizeof(ss);
afd = accept(lfd, (struct sockaddr *)&ss, &sslen);
if (afd < 0) {
goto cleanup;
}
if (check_soerr) {
if (qemu_getsockopt(cfd, SOL_SOCKET, SO_ERROR, &soerr, &soerrlen) < 0) {
goto cleanup;
}
if (soerr) {
errno = soerr;
goto cleanup;
}
}
ret = 0;
cleanup:
if (afd != -1) {
close(afd);
}
if (cfd != -1) {
close(cfd);
}
if (lfd != -1) {
close(lfd);
}
if (res) {
freeaddrinfo(res);
}
return ret;
}
int socket_check_protocol_support(bool *has_ipv4, bool *has_ipv6)
{
*has_ipv4 = *has_ipv6 = false;
if (socket_can_bind_connect("127.0.0.1", PF_INET) < 0) {
if (errno != EADDRNOTAVAIL) {
return -1;
}
} else {
*has_ipv4 = true;
}
if (socket_can_bind_connect("::1", PF_INET6) < 0) {
if (errno != EADDRNOTAVAIL) {
return -1;
}
} else {
*has_ipv6 = true;
}
return 0;
}

View file

@ -0,0 +1,35 @@
/*
* Helper functions for tests using sockets
*
* Copyright 2015-2018 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TESTS_SOCKET_HELPERS_H
#define TESTS_SOCKET_HELPERS_H
/*
* @has_ipv4: set to true on return if IPv4 is available
* @has_ipv6: set to true on return if IPv6 is available
*
* Check whether IPv4 and/or IPv6 are available for use.
* On success, @has_ipv4 and @has_ipv6 will be set to
* indicate whether the respective protocols are available.
*
* Returns 0 on success, -1 on fatal error
*/
int socket_check_protocol_support(bool *has_ipv4, bool *has_ipv6);
#endif

View file

@ -0,0 +1,460 @@
/*
* AioContext multithreading tests
*
* Copyright Red Hat, Inc. 2016
*
* Authors:
* Paolo Bonzini <pbonzini@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "block/aio.h"
#include "qemu/coroutine.h"
#include "qemu/thread.h"
#include "qemu/error-report.h"
#include "iothread.h"
/* AioContext management */
#define NUM_CONTEXTS 5
static IOThread *threads[NUM_CONTEXTS];
static AioContext *ctx[NUM_CONTEXTS];
static __thread int id = -1;
static QemuEvent done_event;
/* Run a function synchronously on a remote iothread. */
typedef struct CtxRunData {
QEMUBHFunc *cb;
void *arg;
} CtxRunData;
static void ctx_run_bh_cb(void *opaque)
{
CtxRunData *data = opaque;
data->cb(data->arg);
qemu_event_set(&done_event);
}
static void ctx_run(int i, QEMUBHFunc *cb, void *opaque)
{
CtxRunData data = {
.cb = cb,
.arg = opaque
};
qemu_event_reset(&done_event);
aio_bh_schedule_oneshot(ctx[i], ctx_run_bh_cb, &data);
qemu_event_wait(&done_event);
}
/* Starting the iothreads. */
static void set_id_cb(void *opaque)
{
int *i = opaque;
id = *i;
}
static void create_aio_contexts(void)
{
int i;
for (i = 0; i < NUM_CONTEXTS; i++) {
threads[i] = iothread_new();
ctx[i] = iothread_get_aio_context(threads[i]);
}
qemu_event_init(&done_event, false);
for (i = 0; i < NUM_CONTEXTS; i++) {
ctx_run(i, set_id_cb, &i);
}
}
/* Stopping the iothreads. */
static void join_aio_contexts(void)
{
int i;
for (i = 0; i < NUM_CONTEXTS; i++) {
aio_context_ref(ctx[i]);
}
for (i = 0; i < NUM_CONTEXTS; i++) {
iothread_join(threads[i]);
}
for (i = 0; i < NUM_CONTEXTS; i++) {
aio_context_unref(ctx[i]);
}
qemu_event_destroy(&done_event);
}
/* Basic test for the stuff above. */
static void test_lifecycle(void)
{
create_aio_contexts();
join_aio_contexts();
}
/* aio_co_schedule test. */
static Coroutine *to_schedule[NUM_CONTEXTS];
static bool now_stopping;
static int count_retry;
static int count_here;
static int count_other;
static bool schedule_next(int n)
{
Coroutine *co;
co = qatomic_xchg(&to_schedule[n], NULL);
if (!co) {
qatomic_inc(&count_retry);
return false;
}
if (n == id) {
qatomic_inc(&count_here);
} else {
qatomic_inc(&count_other);
}
aio_co_schedule(ctx[n], co);
return true;
}
static void finish_cb(void *opaque)
{
schedule_next(id);
}
static coroutine_fn void test_multi_co_schedule_entry(void *opaque)
{
g_assert(to_schedule[id] == NULL);
while (!qatomic_mb_read(&now_stopping)) {
int n;
n = g_test_rand_int_range(0, NUM_CONTEXTS);
schedule_next(n);
qatomic_mb_set(&to_schedule[id], qemu_coroutine_self());
qemu_coroutine_yield();
g_assert(to_schedule[id] == NULL);
}
}
static void test_multi_co_schedule(int seconds)
{
int i;
count_here = count_other = count_retry = 0;
now_stopping = false;
create_aio_contexts();
for (i = 0; i < NUM_CONTEXTS; i++) {
Coroutine *co1 = qemu_coroutine_create(test_multi_co_schedule_entry, NULL);
aio_co_schedule(ctx[i], co1);
}
g_usleep(seconds * 1000000);
qatomic_mb_set(&now_stopping, true);
for (i = 0; i < NUM_CONTEXTS; i++) {
ctx_run(i, finish_cb, NULL);
to_schedule[i] = NULL;
}
join_aio_contexts();
g_test_message("scheduled %d, queued %d, retry %d, total %d",
count_other, count_here, count_retry,
count_here + count_other + count_retry);
}
static void test_multi_co_schedule_1(void)
{
test_multi_co_schedule(1);
}
static void test_multi_co_schedule_10(void)
{
test_multi_co_schedule(10);
}
/* CoMutex thread-safety. */
static uint32_t atomic_counter;
static uint32_t running;
static uint32_t counter;
static CoMutex comutex;
static void coroutine_fn test_multi_co_mutex_entry(void *opaque)
{
while (!qatomic_mb_read(&now_stopping)) {
qemu_co_mutex_lock(&comutex);
counter++;
qemu_co_mutex_unlock(&comutex);
/* Increase atomic_counter *after* releasing the mutex. Otherwise
* there is a chance (it happens about 1 in 3 runs) that the iothread
* exits before the coroutine is woken up, causing a spurious
* assertion failure.
*/
qatomic_inc(&atomic_counter);
}
qatomic_dec(&running);
}
static void test_multi_co_mutex(int threads, int seconds)
{
int i;
qemu_co_mutex_init(&comutex);
counter = 0;
atomic_counter = 0;
now_stopping = false;
create_aio_contexts();
assert(threads <= NUM_CONTEXTS);
running = threads;
for (i = 0; i < threads; i++) {
Coroutine *co1 = qemu_coroutine_create(test_multi_co_mutex_entry, NULL);
aio_co_schedule(ctx[i], co1);
}
g_usleep(seconds * 1000000);
qatomic_mb_set(&now_stopping, true);
while (running > 0) {
g_usleep(100000);
}
join_aio_contexts();
g_test_message("%d iterations/second", counter / seconds);
g_assert_cmpint(counter, ==, atomic_counter);
}
/* Testing with NUM_CONTEXTS threads focuses on the queue. The mutex however
* is too contended (and the threads spend too much time in aio_poll)
* to actually stress the handoff protocol.
*/
static void test_multi_co_mutex_1(void)
{
test_multi_co_mutex(NUM_CONTEXTS, 1);
}
static void test_multi_co_mutex_10(void)
{
test_multi_co_mutex(NUM_CONTEXTS, 10);
}
/* Testing with fewer threads stresses the handoff protocol too. Still, the
* case where the locker _can_ pick up a handoff is very rare, happening
* about 10 times in 1 million, so increase the runtime a bit compared to
* other "quick" testcases that only run for 1 second.
*/
static void test_multi_co_mutex_2_3(void)
{
test_multi_co_mutex(2, 3);
}
static void test_multi_co_mutex_2_30(void)
{
test_multi_co_mutex(2, 30);
}
/* Same test with fair mutexes, for performance comparison. */
#ifdef CONFIG_LINUX
#include "qemu/futex.h"
/* The nodes for the mutex reside in this structure (on which we try to avoid
* false sharing). The head of the mutex is in the "mutex_head" variable.
*/
static struct {
int next, locked;
int padding[14];
} nodes[NUM_CONTEXTS] __attribute__((__aligned__(64)));
static int mutex_head = -1;
static void mcs_mutex_lock(void)
{
int prev;
nodes[id].next = -1;
nodes[id].locked = 1;
prev = qatomic_xchg(&mutex_head, id);
if (prev != -1) {
qatomic_set(&nodes[prev].next, id);
qemu_futex_wait(&nodes[id].locked, 1);
}
}
static void mcs_mutex_unlock(void)
{
int next;
if (qatomic_read(&nodes[id].next) == -1) {
if (qatomic_read(&mutex_head) == id &&
qatomic_cmpxchg(&mutex_head, id, -1) == id) {
/* Last item in the list, exit. */
return;
}
while (qatomic_read(&nodes[id].next) == -1) {
/* mcs_mutex_lock did the xchg, but has not updated
* nodes[prev].next yet.
*/
}
}
/* Wake up the next in line. */
next = qatomic_read(&nodes[id].next);
nodes[next].locked = 0;
qemu_futex_wake(&nodes[next].locked, 1);
}
static void test_multi_fair_mutex_entry(void *opaque)
{
while (!qatomic_mb_read(&now_stopping)) {
mcs_mutex_lock();
counter++;
mcs_mutex_unlock();
qatomic_inc(&atomic_counter);
}
qatomic_dec(&running);
}
static void test_multi_fair_mutex(int threads, int seconds)
{
int i;
assert(mutex_head == -1);
counter = 0;
atomic_counter = 0;
now_stopping = false;
create_aio_contexts();
assert(threads <= NUM_CONTEXTS);
running = threads;
for (i = 0; i < threads; i++) {
Coroutine *co1 = qemu_coroutine_create(test_multi_fair_mutex_entry, NULL);
aio_co_schedule(ctx[i], co1);
}
g_usleep(seconds * 1000000);
qatomic_mb_set(&now_stopping, true);
while (running > 0) {
g_usleep(100000);
}
join_aio_contexts();
g_test_message("%d iterations/second", counter / seconds);
g_assert_cmpint(counter, ==, atomic_counter);
}
static void test_multi_fair_mutex_1(void)
{
test_multi_fair_mutex(NUM_CONTEXTS, 1);
}
static void test_multi_fair_mutex_10(void)
{
test_multi_fair_mutex(NUM_CONTEXTS, 10);
}
#endif
/* Same test with pthread mutexes, for performance comparison and
* portability. */
static QemuMutex mutex;
static void test_multi_mutex_entry(void *opaque)
{
while (!qatomic_mb_read(&now_stopping)) {
qemu_mutex_lock(&mutex);
counter++;
qemu_mutex_unlock(&mutex);
qatomic_inc(&atomic_counter);
}
qatomic_dec(&running);
}
static void test_multi_mutex(int threads, int seconds)
{
int i;
qemu_mutex_init(&mutex);
counter = 0;
atomic_counter = 0;
now_stopping = false;
create_aio_contexts();
assert(threads <= NUM_CONTEXTS);
running = threads;
for (i = 0; i < threads; i++) {
Coroutine *co1 = qemu_coroutine_create(test_multi_mutex_entry, NULL);
aio_co_schedule(ctx[i], co1);
}
g_usleep(seconds * 1000000);
qatomic_mb_set(&now_stopping, true);
while (running > 0) {
g_usleep(100000);
}
join_aio_contexts();
g_test_message("%d iterations/second", counter / seconds);
g_assert_cmpint(counter, ==, atomic_counter);
}
static void test_multi_mutex_1(void)
{
test_multi_mutex(NUM_CONTEXTS, 1);
}
static void test_multi_mutex_10(void)
{
test_multi_mutex(NUM_CONTEXTS, 10);
}
/* End of tests. */
int main(int argc, char **argv)
{
init_clocks(NULL);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/aio/multi/lifecycle", test_lifecycle);
if (g_test_quick()) {
g_test_add_func("/aio/multi/schedule", test_multi_co_schedule_1);
g_test_add_func("/aio/multi/mutex/contended", test_multi_co_mutex_1);
g_test_add_func("/aio/multi/mutex/handoff", test_multi_co_mutex_2_3);
#ifdef CONFIG_LINUX
g_test_add_func("/aio/multi/mutex/mcs", test_multi_fair_mutex_1);
#endif
g_test_add_func("/aio/multi/mutex/pthread", test_multi_mutex_1);
} else {
g_test_add_func("/aio/multi/schedule", test_multi_co_schedule_10);
g_test_add_func("/aio/multi/mutex/contended", test_multi_co_mutex_10);
g_test_add_func("/aio/multi/mutex/handoff", test_multi_co_mutex_2_30);
#ifdef CONFIG_LINUX
g_test_add_func("/aio/multi/mutex/mcs", test_multi_fair_mutex_10);
#endif
g_test_add_func("/aio/multi/mutex/pthread", test_multi_mutex_10);
}
return g_test_run();
}

921
tests/unit/test-aio.c Normal file
View file

@ -0,0 +1,921 @@
/*
* AioContext tests
*
* Copyright Red Hat, Inc. 2012
*
* Authors:
* Paolo Bonzini <pbonzini@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "block/aio.h"
#include "qapi/error.h"
#include "qemu/timer.h"
#include "qemu/sockets.h"
#include "qemu/error-report.h"
#include "qemu/coroutine.h"
#include "qemu/main-loop.h"
static AioContext *ctx;
typedef struct {
EventNotifier e;
int n;
int active;
bool auto_set;
} EventNotifierTestData;
/* Wait until event notifier becomes inactive */
static void wait_until_inactive(EventNotifierTestData *data)
{
while (data->active > 0) {
aio_poll(ctx, true);
}
}
/* Simple callbacks for testing. */
typedef struct {
QEMUBH *bh;
int n;
int max;
} BHTestData;
typedef struct {
QEMUTimer timer;
QEMUClockType clock_type;
int n;
int max;
int64_t ns;
AioContext *ctx;
} TimerTestData;
static void bh_test_cb(void *opaque)
{
BHTestData *data = opaque;
if (++data->n < data->max) {
qemu_bh_schedule(data->bh);
}
}
static void timer_test_cb(void *opaque)
{
TimerTestData *data = opaque;
if (++data->n < data->max) {
timer_mod(&data->timer,
qemu_clock_get_ns(data->clock_type) + data->ns);
}
}
static void dummy_io_handler_read(EventNotifier *e)
{
}
static void bh_delete_cb(void *opaque)
{
BHTestData *data = opaque;
if (++data->n < data->max) {
qemu_bh_schedule(data->bh);
} else {
qemu_bh_delete(data->bh);
data->bh = NULL;
}
}
static void event_ready_cb(EventNotifier *e)
{
EventNotifierTestData *data = container_of(e, EventNotifierTestData, e);
g_assert(event_notifier_test_and_clear(e));
data->n++;
if (data->active > 0) {
data->active--;
}
if (data->auto_set && data->active) {
event_notifier_set(e);
}
}
/* Tests using aio_*. */
typedef struct {
QemuMutex start_lock;
EventNotifier notifier;
bool thread_acquired;
} AcquireTestData;
static void *test_acquire_thread(void *opaque)
{
AcquireTestData *data = opaque;
/* Wait for other thread to let us start */
qemu_mutex_lock(&data->start_lock);
qemu_mutex_unlock(&data->start_lock);
/* event_notifier_set might be called either before or after
* the main thread's call to poll(). The test case's outcome
* should be the same in either case.
*/
event_notifier_set(&data->notifier);
aio_context_acquire(ctx);
aio_context_release(ctx);
data->thread_acquired = true; /* success, we got here */
return NULL;
}
static void set_event_notifier(AioContext *ctx, EventNotifier *notifier,
EventNotifierHandler *handler)
{
aio_set_event_notifier(ctx, notifier, false, handler, NULL);
}
static void dummy_notifier_read(EventNotifier *n)
{
event_notifier_test_and_clear(n);
}
static void test_acquire(void)
{
QemuThread thread;
AcquireTestData data;
/* Dummy event notifier ensures aio_poll() will block */
event_notifier_init(&data.notifier, false);
set_event_notifier(ctx, &data.notifier, dummy_notifier_read);
g_assert(!aio_poll(ctx, false)); /* consume aio_notify() */
qemu_mutex_init(&data.start_lock);
qemu_mutex_lock(&data.start_lock);
data.thread_acquired = false;
qemu_thread_create(&thread, "test_acquire_thread",
test_acquire_thread,
&data, QEMU_THREAD_JOINABLE);
/* Block in aio_poll(), let other thread kick us and acquire context */
aio_context_acquire(ctx);
qemu_mutex_unlock(&data.start_lock); /* let the thread run */
g_assert(aio_poll(ctx, true));
g_assert(!data.thread_acquired);
aio_context_release(ctx);
qemu_thread_join(&thread);
set_event_notifier(ctx, &data.notifier, NULL);
event_notifier_cleanup(&data.notifier);
g_assert(data.thread_acquired);
}
static void test_bh_schedule(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(aio_poll(ctx, true));
g_assert_cmpint(data.n, ==, 1);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
qemu_bh_delete(data.bh);
}
static void test_bh_schedule10(void)
{
BHTestData data = { .n = 0, .max = 10 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
g_assert(aio_poll(ctx, true));
g_assert_cmpint(data.n, ==, 2);
while (data.n < 10) {
aio_poll(ctx, true);
}
g_assert_cmpint(data.n, ==, 10);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 10);
qemu_bh_delete(data.bh);
}
static void test_bh_cancel(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
qemu_bh_cancel(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
qemu_bh_delete(data.bh);
}
static void test_bh_delete(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
qemu_bh_delete(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
}
static void test_bh_delete_from_cb(void)
{
BHTestData data1 = { .n = 0, .max = 1 };
data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
qemu_bh_schedule(data1.bh);
g_assert_cmpint(data1.n, ==, 0);
while (data1.n < data1.max) {
aio_poll(ctx, true);
}
g_assert_cmpint(data1.n, ==, data1.max);
g_assert(data1.bh == NULL);
g_assert(!aio_poll(ctx, false));
}
static void test_bh_delete_from_cb_many(void)
{
BHTestData data1 = { .n = 0, .max = 1 };
BHTestData data2 = { .n = 0, .max = 3 };
BHTestData data3 = { .n = 0, .max = 2 };
BHTestData data4 = { .n = 0, .max = 4 };
data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
data2.bh = aio_bh_new(ctx, bh_delete_cb, &data2);
data3.bh = aio_bh_new(ctx, bh_delete_cb, &data3);
data4.bh = aio_bh_new(ctx, bh_delete_cb, &data4);
qemu_bh_schedule(data1.bh);
qemu_bh_schedule(data2.bh);
qemu_bh_schedule(data3.bh);
qemu_bh_schedule(data4.bh);
g_assert_cmpint(data1.n, ==, 0);
g_assert_cmpint(data2.n, ==, 0);
g_assert_cmpint(data3.n, ==, 0);
g_assert_cmpint(data4.n, ==, 0);
g_assert(aio_poll(ctx, false));
g_assert_cmpint(data1.n, ==, 1);
g_assert_cmpint(data2.n, ==, 1);
g_assert_cmpint(data3.n, ==, 1);
g_assert_cmpint(data4.n, ==, 1);
g_assert(data1.bh == NULL);
while (data1.n < data1.max ||
data2.n < data2.max ||
data3.n < data3.max ||
data4.n < data4.max) {
aio_poll(ctx, true);
}
g_assert_cmpint(data1.n, ==, data1.max);
g_assert_cmpint(data2.n, ==, data2.max);
g_assert_cmpint(data3.n, ==, data3.max);
g_assert_cmpint(data4.n, ==, data4.max);
g_assert(data1.bh == NULL);
g_assert(data2.bh == NULL);
g_assert(data3.bh == NULL);
g_assert(data4.bh == NULL);
}
static void test_bh_flush(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(aio_poll(ctx, true));
g_assert_cmpint(data.n, ==, 1);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
qemu_bh_delete(data.bh);
}
static void test_set_event_notifier(void)
{
EventNotifierTestData data = { .n = 0, .active = 0 };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
set_event_notifier(ctx, &data.e, NULL);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
event_notifier_cleanup(&data.e);
}
static void test_wait_event_notifier(void)
{
EventNotifierTestData data = { .n = 0, .active = 1 };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
while (aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
g_assert_cmpint(data.active, ==, 1);
event_notifier_set(&data.e);
g_assert(aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
g_assert_cmpint(data.active, ==, 0);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
g_assert_cmpint(data.active, ==, 0);
set_event_notifier(ctx, &data.e, NULL);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
event_notifier_cleanup(&data.e);
}
static void test_flush_event_notifier(void)
{
EventNotifierTestData data = { .n = 0, .active = 10, .auto_set = true };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
while (aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
g_assert_cmpint(data.active, ==, 10);
event_notifier_set(&data.e);
g_assert(aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
g_assert_cmpint(data.active, ==, 9);
g_assert(aio_poll(ctx, false));
wait_until_inactive(&data);
g_assert_cmpint(data.n, ==, 10);
g_assert_cmpint(data.active, ==, 0);
g_assert(!aio_poll(ctx, false));
set_event_notifier(ctx, &data.e, NULL);
g_assert(!aio_poll(ctx, false));
event_notifier_cleanup(&data.e);
}
static void test_aio_external_client(void)
{
int i, j;
for (i = 1; i < 3; i++) {
EventNotifierTestData data = { .n = 0, .active = 10, .auto_set = true };
event_notifier_init(&data.e, false);
aio_set_event_notifier(ctx, &data.e, true, event_ready_cb, NULL);
event_notifier_set(&data.e);
for (j = 0; j < i; j++) {
aio_disable_external(ctx);
}
for (j = 0; j < i; j++) {
assert(!aio_poll(ctx, false));
assert(event_notifier_test_and_clear(&data.e));
event_notifier_set(&data.e);
aio_enable_external(ctx);
}
assert(aio_poll(ctx, false));
set_event_notifier(ctx, &data.e, NULL);
event_notifier_cleanup(&data.e);
}
}
static void test_wait_event_notifier_noflush(void)
{
EventNotifierTestData data = { .n = 0 };
EventNotifierTestData dummy = { .n = 0, .active = 1 };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
/* Until there is an active descriptor, aio_poll may or may not call
* event_ready_cb. Still, it must not block. */
event_notifier_set(&data.e);
g_assert(aio_poll(ctx, true));
data.n = 0;
/* An active event notifier forces aio_poll to look at EventNotifiers. */
event_notifier_init(&dummy.e, false);
set_event_notifier(ctx, &dummy.e, event_ready_cb);
event_notifier_set(&data.e);
g_assert(aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
event_notifier_set(&data.e);
g_assert(aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 2);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 2);
event_notifier_set(&dummy.e);
wait_until_inactive(&dummy);
g_assert_cmpint(data.n, ==, 2);
g_assert_cmpint(dummy.n, ==, 1);
g_assert_cmpint(dummy.active, ==, 0);
set_event_notifier(ctx, &dummy.e, NULL);
event_notifier_cleanup(&dummy.e);
set_event_notifier(ctx, &data.e, NULL);
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 2);
event_notifier_cleanup(&data.e);
}
static void test_timer_schedule(void)
{
TimerTestData data = { .n = 0, .ctx = ctx, .ns = SCALE_MS * 750LL,
.max = 2,
.clock_type = QEMU_CLOCK_REALTIME };
EventNotifier e;
/* aio_poll will not block to wait for timers to complete unless it has
* an fd to wait on. Fixing this breaks other tests. So create a dummy one.
*/
event_notifier_init(&e, false);
set_event_notifier(ctx, &e, dummy_io_handler_read);
aio_poll(ctx, false);
aio_timer_init(ctx, &data.timer, data.clock_type,
SCALE_NS, timer_test_cb, &data);
timer_mod(&data.timer,
qemu_clock_get_ns(data.clock_type) +
data.ns);
g_assert_cmpint(data.n, ==, 0);
/* timer_mod may well cause an event notifer to have gone off,
* so clear that
*/
do {} while (aio_poll(ctx, false));
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 0);
g_usleep(1 * G_USEC_PER_SEC);
g_assert_cmpint(data.n, ==, 0);
g_assert(aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
/* timer_mod called by our callback */
do {} while (aio_poll(ctx, false));
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 1);
g_assert(aio_poll(ctx, true));
g_assert_cmpint(data.n, ==, 2);
/* As max is now 2, an event notifier should not have gone off */
g_assert(!aio_poll(ctx, false));
g_assert_cmpint(data.n, ==, 2);
set_event_notifier(ctx, &e, NULL);
event_notifier_cleanup(&e);
timer_del(&data.timer);
}
/* Now the same tests, using the context as a GSource. They are
* very similar to the ones above, with g_main_context_iteration
* replacing aio_poll. However:
* - sometimes both the AioContext and the glib main loop wake
* themselves up. Hence, some "g_assert(!aio_poll(ctx, false));"
* are replaced by "while (g_main_context_iteration(NULL, false));".
* - there is no exact replacement for a blocking wait.
* "while (g_main_context_iteration(NULL, true)" seems to work,
* but it is not documented _why_ it works. For these tests a
* non-blocking loop like "while (g_main_context_iteration(NULL, false)"
* works well, and that's what I am using.
*/
static void test_source_flush(void)
{
g_assert(!g_main_context_iteration(NULL, false));
aio_notify(ctx);
while (g_main_context_iteration(NULL, false));
g_assert(!g_main_context_iteration(NULL, false));
}
static void test_source_bh_schedule(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(g_main_context_iteration(NULL, true));
g_assert_cmpint(data.n, ==, 1);
g_assert(!g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
qemu_bh_delete(data.bh);
}
static void test_source_bh_schedule10(void)
{
BHTestData data = { .n = 0, .max = 10 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
g_assert(g_main_context_iteration(NULL, true));
g_assert_cmpint(data.n, ==, 2);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 10);
g_assert(!g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 10);
qemu_bh_delete(data.bh);
}
static void test_source_bh_cancel(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
qemu_bh_cancel(data.bh);
g_assert_cmpint(data.n, ==, 0);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 0);
qemu_bh_delete(data.bh);
}
static void test_source_bh_delete(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
qemu_bh_delete(data.bh);
g_assert_cmpint(data.n, ==, 0);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 0);
}
static void test_source_bh_delete_from_cb(void)
{
BHTestData data1 = { .n = 0, .max = 1 };
data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
qemu_bh_schedule(data1.bh);
g_assert_cmpint(data1.n, ==, 0);
g_main_context_iteration(NULL, true);
g_assert_cmpint(data1.n, ==, data1.max);
g_assert(data1.bh == NULL);
assert(g_main_context_iteration(NULL, false));
assert(!g_main_context_iteration(NULL, false));
}
static void test_source_bh_delete_from_cb_many(void)
{
BHTestData data1 = { .n = 0, .max = 1 };
BHTestData data2 = { .n = 0, .max = 3 };
BHTestData data3 = { .n = 0, .max = 2 };
BHTestData data4 = { .n = 0, .max = 4 };
data1.bh = aio_bh_new(ctx, bh_delete_cb, &data1);
data2.bh = aio_bh_new(ctx, bh_delete_cb, &data2);
data3.bh = aio_bh_new(ctx, bh_delete_cb, &data3);
data4.bh = aio_bh_new(ctx, bh_delete_cb, &data4);
qemu_bh_schedule(data1.bh);
qemu_bh_schedule(data2.bh);
qemu_bh_schedule(data3.bh);
qemu_bh_schedule(data4.bh);
g_assert_cmpint(data1.n, ==, 0);
g_assert_cmpint(data2.n, ==, 0);
g_assert_cmpint(data3.n, ==, 0);
g_assert_cmpint(data4.n, ==, 0);
g_assert(g_main_context_iteration(NULL, false));
g_assert_cmpint(data1.n, ==, 1);
g_assert_cmpint(data2.n, ==, 1);
g_assert_cmpint(data3.n, ==, 1);
g_assert_cmpint(data4.n, ==, 1);
g_assert(data1.bh == NULL);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data1.n, ==, data1.max);
g_assert_cmpint(data2.n, ==, data2.max);
g_assert_cmpint(data3.n, ==, data3.max);
g_assert_cmpint(data4.n, ==, data4.max);
g_assert(data1.bh == NULL);
g_assert(data2.bh == NULL);
g_assert(data3.bh == NULL);
g_assert(data4.bh == NULL);
}
static void test_source_bh_flush(void)
{
BHTestData data = { .n = 0 };
data.bh = aio_bh_new(ctx, bh_test_cb, &data);
qemu_bh_schedule(data.bh);
g_assert_cmpint(data.n, ==, 0);
g_assert(g_main_context_iteration(NULL, true));
g_assert_cmpint(data.n, ==, 1);
g_assert(!g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
qemu_bh_delete(data.bh);
}
static void test_source_set_event_notifier(void)
{
EventNotifierTestData data = { .n = 0, .active = 0 };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 0);
set_event_notifier(ctx, &data.e, NULL);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 0);
event_notifier_cleanup(&data.e);
}
static void test_source_wait_event_notifier(void)
{
EventNotifierTestData data = { .n = 0, .active = 1 };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 0);
g_assert_cmpint(data.active, ==, 1);
event_notifier_set(&data.e);
g_assert(g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
g_assert_cmpint(data.active, ==, 0);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
g_assert_cmpint(data.active, ==, 0);
set_event_notifier(ctx, &data.e, NULL);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
event_notifier_cleanup(&data.e);
}
static void test_source_flush_event_notifier(void)
{
EventNotifierTestData data = { .n = 0, .active = 10, .auto_set = true };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 0);
g_assert_cmpint(data.active, ==, 10);
event_notifier_set(&data.e);
g_assert(g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
g_assert_cmpint(data.active, ==, 9);
g_assert(g_main_context_iteration(NULL, false));
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 10);
g_assert_cmpint(data.active, ==, 0);
g_assert(!g_main_context_iteration(NULL, false));
set_event_notifier(ctx, &data.e, NULL);
while (g_main_context_iteration(NULL, false));
event_notifier_cleanup(&data.e);
}
static void test_source_wait_event_notifier_noflush(void)
{
EventNotifierTestData data = { .n = 0 };
EventNotifierTestData dummy = { .n = 0, .active = 1 };
event_notifier_init(&data.e, false);
set_event_notifier(ctx, &data.e, event_ready_cb);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 0);
/* Until there is an active descriptor, glib may or may not call
* event_ready_cb. Still, it must not block. */
event_notifier_set(&data.e);
g_main_context_iteration(NULL, true);
data.n = 0;
/* An active event notifier forces aio_poll to look at EventNotifiers. */
event_notifier_init(&dummy.e, false);
set_event_notifier(ctx, &dummy.e, event_ready_cb);
event_notifier_set(&data.e);
g_assert(g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
g_assert(!g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 1);
event_notifier_set(&data.e);
g_assert(g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 2);
g_assert(!g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 2);
event_notifier_set(&dummy.e);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 2);
g_assert_cmpint(dummy.n, ==, 1);
g_assert_cmpint(dummy.active, ==, 0);
set_event_notifier(ctx, &dummy.e, NULL);
event_notifier_cleanup(&dummy.e);
set_event_notifier(ctx, &data.e, NULL);
while (g_main_context_iteration(NULL, false));
g_assert_cmpint(data.n, ==, 2);
event_notifier_cleanup(&data.e);
}
static void test_source_timer_schedule(void)
{
TimerTestData data = { .n = 0, .ctx = ctx, .ns = SCALE_MS * 750LL,
.max = 2,
.clock_type = QEMU_CLOCK_REALTIME };
EventNotifier e;
int64_t expiry;
/* aio_poll will not block to wait for timers to complete unless it has
* an fd to wait on. Fixing this breaks other tests. So create a dummy one.
*/
event_notifier_init(&e, false);
set_event_notifier(ctx, &e, dummy_io_handler_read);
do {} while (g_main_context_iteration(NULL, false));
aio_timer_init(ctx, &data.timer, data.clock_type,
SCALE_NS, timer_test_cb, &data);
expiry = qemu_clock_get_ns(data.clock_type) +
data.ns;
timer_mod(&data.timer, expiry);
g_assert_cmpint(data.n, ==, 0);
g_usleep(1 * G_USEC_PER_SEC);
g_assert_cmpint(data.n, ==, 0);
g_assert(g_main_context_iteration(NULL, true));
g_assert_cmpint(data.n, ==, 1);
expiry += data.ns;
while (data.n < 2) {
g_main_context_iteration(NULL, true);
}
g_assert_cmpint(data.n, ==, 2);
g_assert(qemu_clock_get_ns(data.clock_type) > expiry);
set_event_notifier(ctx, &e, NULL);
event_notifier_cleanup(&e);
timer_del(&data.timer);
}
/*
* Check that aio_co_enter() can chain many times
*
* Two coroutines should be able to invoke each other via aio_co_enter() many
* times without hitting a limit like stack exhaustion. In other words, the
* calls should be chained instead of nested.
*/
typedef struct {
Coroutine *other;
unsigned i;
unsigned max;
} ChainData;
static void coroutine_fn chain(void *opaque)
{
ChainData *data = opaque;
for (data->i = 0; data->i < data->max; data->i++) {
/* Queue up the other coroutine... */
aio_co_enter(ctx, data->other);
/* ...and give control to it */
qemu_coroutine_yield();
}
}
static void test_queue_chaining(void)
{
/* This number of iterations hit stack exhaustion in the past: */
ChainData data_a = { .max = 25000 };
ChainData data_b = { .max = 25000 };
data_b.other = qemu_coroutine_create(chain, &data_a);
data_a.other = qemu_coroutine_create(chain, &data_b);
qemu_coroutine_enter(data_b.other);
g_assert_cmpint(data_a.i, ==, data_a.max);
g_assert_cmpint(data_b.i, ==, data_b.max - 1);
/* Allow the second coroutine to terminate */
qemu_coroutine_enter(data_a.other);
g_assert_cmpint(data_b.i, ==, data_b.max);
}
/* End of tests. */
int main(int argc, char **argv)
{
qemu_init_main_loop(&error_fatal);
ctx = qemu_get_aio_context();
while (g_main_context_iteration(NULL, false));
g_test_init(&argc, &argv, NULL);
g_test_add_func("/aio/acquire", test_acquire);
g_test_add_func("/aio/bh/schedule", test_bh_schedule);
g_test_add_func("/aio/bh/schedule10", test_bh_schedule10);
g_test_add_func("/aio/bh/cancel", test_bh_cancel);
g_test_add_func("/aio/bh/delete", test_bh_delete);
g_test_add_func("/aio/bh/callback-delete/one", test_bh_delete_from_cb);
g_test_add_func("/aio/bh/callback-delete/many", test_bh_delete_from_cb_many);
g_test_add_func("/aio/bh/flush", test_bh_flush);
g_test_add_func("/aio/event/add-remove", test_set_event_notifier);
g_test_add_func("/aio/event/wait", test_wait_event_notifier);
g_test_add_func("/aio/event/wait/no-flush-cb", test_wait_event_notifier_noflush);
g_test_add_func("/aio/event/flush", test_flush_event_notifier);
g_test_add_func("/aio/external-client", test_aio_external_client);
g_test_add_func("/aio/timer/schedule", test_timer_schedule);
g_test_add_func("/aio/coroutine/queue-chaining", test_queue_chaining);
g_test_add_func("/aio-gsource/flush", test_source_flush);
g_test_add_func("/aio-gsource/bh/schedule", test_source_bh_schedule);
g_test_add_func("/aio-gsource/bh/schedule10", test_source_bh_schedule10);
g_test_add_func("/aio-gsource/bh/cancel", test_source_bh_cancel);
g_test_add_func("/aio-gsource/bh/delete", test_source_bh_delete);
g_test_add_func("/aio-gsource/bh/callback-delete/one", test_source_bh_delete_from_cb);
g_test_add_func("/aio-gsource/bh/callback-delete/many", test_source_bh_delete_from_cb_many);
g_test_add_func("/aio-gsource/bh/flush", test_source_bh_flush);
g_test_add_func("/aio-gsource/event/add-remove", test_source_set_event_notifier);
g_test_add_func("/aio-gsource/event/wait", test_source_wait_event_notifier);
g_test_add_func("/aio-gsource/event/wait/no-flush-cb", test_source_wait_event_notifier_noflush);
g_test_add_func("/aio-gsource/event/flush", test_source_flush_event_notifier);
g_test_add_func("/aio-gsource/timer/schedule", test_source_timer_schedule);
return g_test_run();
}

View file

@ -0,0 +1,160 @@
/*
* QEMU list file authorization object tests
*
* Copyright (c) 2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "authz/list.h"
#include "qemu/module.h"
static void test_authz_default_deny(void)
{
QAuthZList *auth = qauthz_list_new("auth0",
QAUTHZ_LIST_POLICY_DENY,
&error_abort);
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_default_allow(void)
{
QAuthZList *auth = qauthz_list_new("auth0",
QAUTHZ_LIST_POLICY_ALLOW,
&error_abort);
g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_explicit_deny(void)
{
QAuthZList *auth = qauthz_list_new("auth0",
QAUTHZ_LIST_POLICY_ALLOW,
&error_abort);
qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_DENY,
QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_explicit_allow(void)
{
QAuthZList *auth = qauthz_list_new("auth0",
QAUTHZ_LIST_POLICY_DENY,
&error_abort);
qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_complex(void)
{
QAuthZList *auth = qauthz_list_new("auth0",
QAUTHZ_LIST_POLICY_DENY,
&error_abort);
qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
qauthz_list_append_rule(auth, "bob", QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
qauthz_list_append_rule(auth, "dan", QAUTHZ_LIST_POLICY_DENY,
QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
qauthz_list_append_rule(auth, "dan*", QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_GLOB, &error_abort);
g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_add_remove(void)
{
QAuthZList *auth = qauthz_list_new("auth0",
QAUTHZ_LIST_POLICY_ALLOW,
&error_abort);
g_assert_cmpint(qauthz_list_append_rule(auth, "fred",
QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_EXACT,
&error_abort),
==, 0);
g_assert_cmpint(qauthz_list_append_rule(auth, "bob",
QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_EXACT,
&error_abort),
==, 1);
g_assert_cmpint(qauthz_list_append_rule(auth, "dan",
QAUTHZ_LIST_POLICY_DENY,
QAUTHZ_LIST_FORMAT_EXACT,
&error_abort),
==, 2);
g_assert_cmpint(qauthz_list_append_rule(auth, "frank",
QAUTHZ_LIST_POLICY_DENY,
QAUTHZ_LIST_FORMAT_EXACT,
&error_abort),
==, 3);
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
g_assert_cmpint(qauthz_list_delete_rule(auth, "dan"),
==, 2);
g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
g_assert_cmpint(qauthz_list_insert_rule(auth, "dan",
QAUTHZ_LIST_POLICY_DENY,
QAUTHZ_LIST_FORMAT_EXACT,
2,
&error_abort),
==, 2);
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
object_unparent(OBJECT(auth));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
g_test_add_func("/auth/list/complex", test_authz_complex);
g_test_add_func("/auth/list/add-remove", test_authz_add_remove);
return g_test_run();
}

View file

@ -0,0 +1,196 @@
/*
* QEMU list authorization object tests
*
* Copyright (c) 2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "authz/listfile.h"
static char *workdir;
static gchar *qemu_authz_listfile_test_save(const gchar *name,
const gchar *cfg)
{
gchar *path = g_strdup_printf("%s/default-deny.cfg", workdir);
GError *gerr = NULL;
if (!g_file_set_contents(path, cfg, -1, &gerr)) {
g_printerr("Unable to save config %s: %s\n",
path, gerr->message);
g_error_free(gerr);
g_free(path);
rmdir(workdir);
abort();
}
return path;
}
static void test_authz_default_deny(void)
{
gchar *file = qemu_authz_listfile_test_save(
"default-deny.cfg",
"{ \"policy\": \"deny\" }");
Error *local_err = NULL;
QAuthZListFile *auth = qauthz_list_file_new("auth0",
file, false,
&local_err);
unlink(file);
g_free(file);
g_assert(local_err == NULL);
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_default_allow(void)
{
gchar *file = qemu_authz_listfile_test_save(
"default-allow.cfg",
"{ \"policy\": \"allow\" }");
Error *local_err = NULL;
QAuthZListFile *auth = qauthz_list_file_new("auth0",
file, false,
&local_err);
unlink(file);
g_free(file);
g_assert(local_err == NULL);
g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_explicit_deny(void)
{
gchar *file = qemu_authz_listfile_test_save(
"explicit-deny.cfg",
"{ \"rules\": [ "
" { \"match\": \"fred\","
" \"policy\": \"deny\","
" \"format\": \"exact\" } ],"
" \"policy\": \"allow\" }");
Error *local_err = NULL;
QAuthZListFile *auth = qauthz_list_file_new("auth0",
file, false,
&local_err);
unlink(file);
g_free(file);
g_assert(local_err == NULL);
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_explicit_allow(void)
{
gchar *file = qemu_authz_listfile_test_save(
"explicit-allow.cfg",
"{ \"rules\": [ "
" { \"match\": \"fred\","
" \"policy\": \"allow\","
" \"format\": \"exact\" } ],"
" \"policy\": \"deny\" }");
Error *local_err = NULL;
QAuthZListFile *auth = qauthz_list_file_new("auth0",
file, false,
&local_err);
unlink(file);
g_free(file);
g_assert(local_err == NULL);
g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_complex(void)
{
gchar *file = qemu_authz_listfile_test_save(
"complex.cfg",
"{ \"rules\": [ "
" { \"match\": \"fred\","
" \"policy\": \"allow\","
" \"format\": \"exact\" },"
" { \"match\": \"bob\","
" \"policy\": \"allow\","
" \"format\": \"exact\" },"
" { \"match\": \"dan\","
" \"policy\": \"deny\","
" \"format\": \"exact\" },"
" { \"match\": \"dan*\","
" \"policy\": \"allow\","
" \"format\": \"glob\" } ],"
" \"policy\": \"deny\" }");
Error *local_err = NULL;
QAuthZListFile *auth = qauthz_list_file_new("auth0",
file, false,
&local_err);
unlink(file);
g_free(file);
g_assert(local_err == NULL);
g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
object_unparent(OBJECT(auth));
}
int main(int argc, char **argv)
{
int ret;
GError *gerr = NULL;
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
workdir = g_dir_make_tmp("qemu-test-authz-listfile-XXXXXX",
&gerr);
if (!workdir) {
g_printerr("Unable to create temporary dir: %s\n",
gerr->message);
g_error_free(gerr);
abort();
}
g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
g_test_add_func("/auth/list/complex", test_authz_complex);
ret = g_test_run();
rmdir(workdir);
g_free(workdir);
return ret;
}

133
tests/unit/test-authz-pam.c Normal file
View file

@ -0,0 +1,133 @@
/*
* QEMU PAM authorization object tests
*
* Copyright (c) 2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "authz/pamacct.h"
#include <security/pam_appl.h>
static bool failauth;
/*
* These three functions are exported by libpam.so.
*
* By defining them again here, our impls are resolved
* by the linker instead of those in libpam.so
*
* The test suite is thus isolated from the host system
* PAM setup, so we can do predictable test scenarios
*/
int
pam_start(const char *service_name, const char *user,
const struct pam_conv *pam_conversation,
pam_handle_t **pamh)
{
failauth = true;
if (!g_str_equal(service_name, "qemu-vnc")) {
return PAM_AUTH_ERR;
}
if (g_str_equal(user, "fred")) {
failauth = false;
}
*pamh = (pam_handle_t *)0xbadeaffe;
return PAM_SUCCESS;
}
int
pam_acct_mgmt(pam_handle_t *pamh, int flags)
{
if (failauth) {
return PAM_AUTH_ERR;
}
return PAM_SUCCESS;
}
int
pam_end(pam_handle_t *pamh, int status)
{
return PAM_SUCCESS;
}
static void test_authz_unknown_service(void)
{
Error *local_err = NULL;
QAuthZPAM *auth = qauthz_pam_new("auth0",
"qemu-does-not-exist",
&error_abort);
g_assert_nonnull(auth);
g_assert_false(qauthz_is_allowed(QAUTHZ(auth), "fred", &local_err));
error_free_or_abort(&local_err);
object_unparent(OBJECT(auth));
}
static void test_authz_good_user(void)
{
QAuthZPAM *auth = qauthz_pam_new("auth0",
"qemu-vnc",
&error_abort);
g_assert_nonnull(auth);
g_assert_true(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
object_unparent(OBJECT(auth));
}
static void test_authz_bad_user(void)
{
Error *local_err = NULL;
QAuthZPAM *auth = qauthz_pam_new("auth0",
"qemu-vnc",
&error_abort);
g_assert_nonnull(auth);
g_assert_false(qauthz_is_allowed(QAUTHZ(auth), "bob", &local_err));
error_free_or_abort(&local_err);
object_unparent(OBJECT(auth));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
g_test_add_func("/auth/pam/unknown-service", test_authz_unknown_service);
g_test_add_func("/auth/pam/good-user", test_authz_good_user);
g_test_add_func("/auth/pam/bad-user", test_authz_bad_user);
return g_test_run();
}

View file

@ -0,0 +1,51 @@
/*
* QEMU simple authorization object testing
*
* Copyright (c) 2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "authz/simple.h"
static void test_authz_simple(void)
{
QAuthZSimple *authz = qauthz_simple_new("authz0",
"cthulu",
&error_abort);
g_assert(!qauthz_is_allowed(QAUTHZ(authz), "cthul", &error_abort));
g_assert(qauthz_is_allowed(QAUTHZ(authz), "cthulu", &error_abort));
g_assert(!qauthz_is_allowed(QAUTHZ(authz), "cthuluu", &error_abort));
g_assert(!qauthz_is_allowed(QAUTHZ(authz), "fred", &error_abort));
object_unparent(OBJECT(authz));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
g_test_add_func("/authz/simple", test_authz_simple);
return g_test_run();
}

108
tests/unit/test-base64.c Normal file
View file

@ -0,0 +1,108 @@
/*
* QEMU base64 helper test
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/base64.h"
static void test_base64_good(void)
{
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW\n"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
const char expect[] = "Because we focused on the snake, "
"we missed the scorpion.";
size_t len;
uint8_t *actual = qbase64_decode(input,
-1,
&len,
&error_abort);
g_assert(actual != NULL);
g_assert_cmpint(len, ==, strlen(expect));
g_assert_cmpstr((char *)actual, ==, expect);
g_free(actual);
}
static void test_base64_bad(const char *input,
size_t input_len)
{
size_t len;
Error *err = NULL;
uint8_t *actual = qbase64_decode(input,
input_len,
&len,
&err);
error_free_or_abort(&err);
g_assert(actual == NULL);
g_assert_cmpint(len, ==, 0);
}
static void test_base64_embedded_nul(void)
{
/* We put a NUL character in the middle of the base64
* text which is invalid data, given the expected length */
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW\0"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
test_base64_bad(input, G_N_ELEMENTS(input) - 1);
}
static void test_base64_not_nul_terminated(void)
{
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW\n"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
/* Using '-2' to make us drop the trailing NUL, thus
* creating an invalid base64 sequence for decoding */
test_base64_bad(input, G_N_ELEMENTS(input) - 2);
}
static void test_base64_invalid_chars(void)
{
/* We put a single quote character in the middle
* of the base64 text which is invalid data */
const char input[] =
"QmVjYXVzZSB3ZSBmb2N1c2VkIG9uIHRoZSBzbmFrZSwgd2UgbW'"
"lzc2VkIHRoZSBzY29ycGlvbi4=";
test_base64_bad(input, strlen(input));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/util/base64/good", test_base64_good);
g_test_add_func("/util/base64/embedded-nul", test_base64_embedded_nul);
g_test_add_func("/util/base64/not-nul-terminated",
test_base64_not_nul_terminated);
g_test_add_func("/util/base64/invalid-chars", test_base64_invalid_chars);
return g_test_run();
}

2230
tests/unit/test-bdrv-drain.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,200 @@
/*
* Block node graph modifications tests
*
* Copyright (c) 2019 Virtuozzo International GmbH. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/main-loop.h"
#include "block/block_int.h"
#include "sysemu/block-backend.h"
static BlockDriver bdrv_pass_through = {
.format_name = "pass-through",
.bdrv_child_perm = bdrv_default_perms,
};
static void no_perm_default_perms(BlockDriverState *bs, BdrvChild *c,
BdrvChildRole role,
BlockReopenQueue *reopen_queue,
uint64_t perm, uint64_t shared,
uint64_t *nperm, uint64_t *nshared)
{
*nperm = 0;
*nshared = BLK_PERM_ALL;
}
static BlockDriver bdrv_no_perm = {
.format_name = "no-perm",
.bdrv_child_perm = no_perm_default_perms,
};
static BlockDriverState *no_perm_node(const char *name)
{
return bdrv_new_open_driver(&bdrv_no_perm, name, BDRV_O_RDWR, &error_abort);
}
static BlockDriverState *pass_through_node(const char *name)
{
return bdrv_new_open_driver(&bdrv_pass_through, name,
BDRV_O_RDWR, &error_abort);
}
/*
* test_update_perm_tree
*
* When checking node for a possibility to update permissions, it's subtree
* should be correctly checked too. New permissions for each node should be
* calculated and checked in context of permissions of other nodes. If we
* check new permissions of the node only in context of old permissions of
* its neighbors, we can finish up with wrong permission graph.
*
* This test firstly create the following graph:
* +--------+
* | root |
* +--------+
* |
* | perm: write, read
* | shared: except write
* v
* +-------------------+ +----------------+
* | passtrough filter |---------->| null-co node |
* +-------------------+ +----------------+
*
*
* and then, tries to append filter under node. Expected behavior: fail.
* Otherwise we'll get the following picture, with two BdrvChild'ren, having
* write permission to one node, without actually sharing it.
*
* +--------+
* | root |
* +--------+
* |
* | perm: write, read
* | shared: except write
* v
* +-------------------+
* | passtrough filter |
* +-------------------+
* | |
* perm: write, read | | perm: write, read
* shared: except write | | shared: except write
* v v
* +----------------+
* | null co node |
* +----------------+
*/
static void test_update_perm_tree(void)
{
int ret;
BlockBackend *root = blk_new(qemu_get_aio_context(),
BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ,
BLK_PERM_ALL & ~BLK_PERM_WRITE);
BlockDriverState *bs = no_perm_node("node");
BlockDriverState *filter = pass_through_node("filter");
blk_insert_bs(root, bs, &error_abort);
bdrv_attach_child(filter, bs, "child", &child_of_bds,
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, &error_abort);
ret = bdrv_append(filter, bs, NULL);
g_assert_cmpint(ret, <, 0);
blk_unref(root);
}
/*
* test_should_update_child
*
* Test that bdrv_replace_node, and concretely should_update_child
* do the right thing, i.e. not creating loops on the graph.
*
* The test does the following:
* 1. initial graph:
*
* +------+ +--------+
* | root | | filter |
* +------+ +--------+
* | |
* root| target|
* v v
* +------+ +--------+
* | node |<---------| target |
* +------+ backing +--------+
*
* 2. Append @filter above @node. If should_update_child works correctly,
* it understands, that backing child of @target should not be updated,
* as it will create a loop on node graph. Resulting picture should
* be the left one, not the right:
*
* +------+ +------+
* | root | | root |
* +------+ +------+
* | |
* root| root|
* v v
* +--------+ target +--------+ target
* | filter |--------------+ | filter |--------------+
* +--------+ | +--------+ |
* | | | ^ v
* backing| | backing| | +--------+
* v v | +-----------| target |
* +------+ +--------+ v backing +--------+
* | node |<---------| target | +------+
* +------+ backing +--------+ | node |
* +------+
*
* (good picture) (bad picture)
*
*/
static void test_should_update_child(void)
{
BlockBackend *root = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
BlockDriverState *bs = no_perm_node("node");
BlockDriverState *filter = no_perm_node("filter");
BlockDriverState *target = no_perm_node("target");
blk_insert_bs(root, bs, &error_abort);
bdrv_set_backing_hd(target, bs, &error_abort);
g_assert(target->backing->bs == bs);
bdrv_attach_child(filter, target, "target", &child_of_bds,
BDRV_CHILD_DATA, &error_abort);
bdrv_append(filter, bs, &error_abort);
g_assert(target->backing->bs == bs);
bdrv_unref(bs);
blk_unref(root);
}
int main(int argc, char *argv[])
{
bdrv_init();
qemu_init_main_loop(&error_abort);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/bdrv-graph-mod/update-perm-tree", test_update_perm_tree);
g_test_add_func("/bdrv-graph-mod/should-update-child",
test_should_update_child);
return g_test_run();
}

140
tests/unit/test-bitcnt.c Normal file
View file

@ -0,0 +1,140 @@
/*
* Test bit count routines
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/host-utils.h"
struct bitcnt_test_data {
/* value to count */
union {
uint8_t w8;
uint16_t w16;
uint32_t w32;
uint64_t w64;
} value;
/* expected result */
int popct;
};
struct bitcnt_test_data eight_bit_data[] = {
{ { .w8 = 0x00 }, .popct=0 },
{ { .w8 = 0x01 }, .popct=1 },
{ { .w8 = 0x03 }, .popct=2 },
{ { .w8 = 0x04 }, .popct=1 },
{ { .w8 = 0x0f }, .popct=4 },
{ { .w8 = 0x3f }, .popct=6 },
{ { .w8 = 0x40 }, .popct=1 },
{ { .w8 = 0xf0 }, .popct=4 },
{ { .w8 = 0x7f }, .popct=7 },
{ { .w8 = 0x80 }, .popct=1 },
{ { .w8 = 0xf1 }, .popct=5 },
{ { .w8 = 0xfe }, .popct=7 },
{ { .w8 = 0xff }, .popct=8 },
};
static void test_ctpop8(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(eight_bit_data); i++) {
struct bitcnt_test_data *d = &eight_bit_data[i];
g_assert(ctpop8(d->value.w8)==d->popct);
}
}
struct bitcnt_test_data sixteen_bit_data[] = {
{ { .w16 = 0x0000 }, .popct=0 },
{ { .w16 = 0x0001 }, .popct=1 },
{ { .w16 = 0x0003 }, .popct=2 },
{ { .w16 = 0x000f }, .popct=4 },
{ { .w16 = 0x003f }, .popct=6 },
{ { .w16 = 0x00f0 }, .popct=4 },
{ { .w16 = 0x0f0f }, .popct=8 },
{ { .w16 = 0x1f1f }, .popct=10 },
{ { .w16 = 0x4000 }, .popct=1 },
{ { .w16 = 0x4001 }, .popct=2 },
{ { .w16 = 0x7000 }, .popct=3 },
{ { .w16 = 0x7fff }, .popct=15 },
};
static void test_ctpop16(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(sixteen_bit_data); i++) {
struct bitcnt_test_data *d = &sixteen_bit_data[i];
g_assert(ctpop16(d->value.w16)==d->popct);
}
}
struct bitcnt_test_data thirtytwo_bit_data[] = {
{ { .w32 = 0x00000000 }, .popct=0 },
{ { .w32 = 0x00000001 }, .popct=1 },
{ { .w32 = 0x0000000f }, .popct=4 },
{ { .w32 = 0x00000f0f }, .popct=8 },
{ { .w32 = 0x00001f1f }, .popct=10 },
{ { .w32 = 0x00004001 }, .popct=2 },
{ { .w32 = 0x00007000 }, .popct=3 },
{ { .w32 = 0x00007fff }, .popct=15 },
{ { .w32 = 0x55555555 }, .popct=16 },
{ { .w32 = 0xaaaaaaaa }, .popct=16 },
{ { .w32 = 0xff000000 }, .popct=8 },
{ { .w32 = 0xc0c0c0c0 }, .popct=8 },
{ { .w32 = 0x0ffffff0 }, .popct=24 },
{ { .w32 = 0x80000000 }, .popct=1 },
{ { .w32 = 0xffffffff }, .popct=32 },
};
static void test_ctpop32(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(thirtytwo_bit_data); i++) {
struct bitcnt_test_data *d = &thirtytwo_bit_data[i];
g_assert(ctpop32(d->value.w32)==d->popct);
}
}
struct bitcnt_test_data sixtyfour_bit_data[] = {
{ { .w64 = 0x0000000000000000ULL }, .popct=0 },
{ { .w64 = 0x0000000000000001ULL }, .popct=1 },
{ { .w64 = 0x000000000000000fULL }, .popct=4 },
{ { .w64 = 0x0000000000000f0fULL }, .popct=8 },
{ { .w64 = 0x0000000000001f1fULL }, .popct=10 },
{ { .w64 = 0x0000000000004001ULL }, .popct=2 },
{ { .w64 = 0x0000000000007000ULL }, .popct=3 },
{ { .w64 = 0x0000000000007fffULL }, .popct=15 },
{ { .w64 = 0x0000005500555555ULL }, .popct=16 },
{ { .w64 = 0x00aa0000aaaa00aaULL }, .popct=16 },
{ { .w64 = 0x000f000000f00000ULL }, .popct=8 },
{ { .w64 = 0x0c0c0000c0c0c0c0ULL }, .popct=12 },
{ { .w64 = 0xf00f00f0f0f0f000ULL }, .popct=24 },
{ { .w64 = 0x8000000000000000ULL }, .popct=1 },
{ { .w64 = 0xf0f0f0f0f0f0f0f0ULL }, .popct=32 },
{ { .w64 = 0xffffffffffffffffULL }, .popct=64 },
};
static void test_ctpop64(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(sixtyfour_bit_data); i++) {
struct bitcnt_test_data *d = &sixtyfour_bit_data[i];
g_assert(ctpop64(d->value.w64)==d->popct);
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/bitcnt/ctpop8", test_ctpop8);
g_test_add_func("/bitcnt/ctpop16", test_ctpop16);
g_test_add_func("/bitcnt/ctpop32", test_ctpop32);
g_test_add_func("/bitcnt/ctpop64", test_ctpop64);
return g_test_run();
}

138
tests/unit/test-bitmap.c Normal file
View file

@ -0,0 +1,138 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Bitmap.c unit-tests.
*
* Copyright (C) 2019, Red Hat, Inc.
*
* Author: Peter Xu <peterx@redhat.com>
*/
#include "qemu/osdep.h"
#include "qemu/bitmap.h"
#define BMAP_SIZE 1024
static void check_bitmap_copy_with_offset(void)
{
unsigned long *bmap1, *bmap2, *bmap3, total;
bmap1 = bitmap_new(BMAP_SIZE);
bmap2 = bitmap_new(BMAP_SIZE);
bmap3 = bitmap_new(BMAP_SIZE);
bmap1[0] = g_test_rand_int();
bmap1[1] = g_test_rand_int();
bmap1[2] = g_test_rand_int();
bmap1[3] = g_test_rand_int();
total = BITS_PER_LONG * 4;
/* Shift 115 bits into bmap2 */
bitmap_copy_with_dst_offset(bmap2, bmap1, 115, total);
/* Shift another 85 bits into bmap3 */
bitmap_copy_with_dst_offset(bmap3, bmap2, 85, total + 115);
/* Shift back 200 bits back */
bitmap_copy_with_src_offset(bmap2, bmap3, 200, total);
g_assert_cmpmem(bmap1, total / BITS_PER_LONG,
bmap2, total / BITS_PER_LONG);
bitmap_clear(bmap1, 0, BMAP_SIZE);
/* Set bits in bmap1 are 100-245 */
bitmap_set(bmap1, 100, 145);
/* Set bits in bmap2 are 60-205 */
bitmap_copy_with_src_offset(bmap2, bmap1, 40, 250);
g_assert_cmpint(find_first_bit(bmap2, 60), ==, 60);
g_assert_cmpint(find_next_zero_bit(bmap2, 205, 60), ==, 205);
g_assert(test_bit(205, bmap2) == 0);
/* Set bits in bmap3 are 135-280 */
bitmap_copy_with_dst_offset(bmap3, bmap1, 35, 250);
g_assert_cmpint(find_first_bit(bmap3, 135), ==, 135);
g_assert_cmpint(find_next_zero_bit(bmap3, 280, 135), ==, 280);
g_assert(test_bit(280, bmap3) == 0);
g_free(bmap1);
g_free(bmap2);
g_free(bmap3);
}
typedef void (*bmap_set_func)(unsigned long *map, long i, long len);
static void bitmap_set_case(bmap_set_func set_func)
{
unsigned long *bmap;
int offset;
bmap = bitmap_new(BMAP_SIZE);
/* Set one bit at offset in second word */
for (offset = 0; offset <= BITS_PER_LONG; offset++) {
bitmap_clear(bmap, 0, BMAP_SIZE);
set_func(bmap, BITS_PER_LONG + offset, 1);
g_assert_cmpint(find_first_bit(bmap, 2 * BITS_PER_LONG),
==, BITS_PER_LONG + offset);
g_assert_cmpint(find_next_zero_bit(bmap,
3 * BITS_PER_LONG,
BITS_PER_LONG + offset),
==, BITS_PER_LONG + offset + 1);
}
/* Both Aligned, set bits [BITS_PER_LONG, 3*BITS_PER_LONG] */
set_func(bmap, BITS_PER_LONG, 2 * BITS_PER_LONG);
g_assert_cmpuint(bmap[1], ==, -1ul);
g_assert_cmpuint(bmap[2], ==, -1ul);
g_assert_cmpint(find_first_bit(bmap, BITS_PER_LONG), ==, BITS_PER_LONG);
g_assert_cmpint(find_next_zero_bit(bmap, 3 * BITS_PER_LONG, BITS_PER_LONG),
==, 3 * BITS_PER_LONG);
for (offset = 0; offset <= BITS_PER_LONG; offset++) {
bitmap_clear(bmap, 0, BMAP_SIZE);
/* End Aligned, set bits [BITS_PER_LONG - offset, 3*BITS_PER_LONG] */
set_func(bmap, BITS_PER_LONG - offset, 2 * BITS_PER_LONG + offset);
g_assert_cmpuint(bmap[1], ==, -1ul);
g_assert_cmpuint(bmap[2], ==, -1ul);
g_assert_cmpint(find_first_bit(bmap, BITS_PER_LONG),
==, BITS_PER_LONG - offset);
g_assert_cmpint(find_next_zero_bit(bmap,
3 * BITS_PER_LONG,
BITS_PER_LONG - offset),
==, 3 * BITS_PER_LONG);
}
for (offset = 0; offset <= BITS_PER_LONG; offset++) {
bitmap_clear(bmap, 0, BMAP_SIZE);
/* Start Aligned, set bits [BITS_PER_LONG, 3*BITS_PER_LONG + offset] */
set_func(bmap, BITS_PER_LONG, 2 * BITS_PER_LONG + offset);
g_assert_cmpuint(bmap[1], ==, -1ul);
g_assert_cmpuint(bmap[2], ==, -1ul);
g_assert_cmpint(find_first_bit(bmap, BITS_PER_LONG),
==, BITS_PER_LONG);
g_assert_cmpint(find_next_zero_bit(bmap,
3 * BITS_PER_LONG + offset,
BITS_PER_LONG),
==, 3 * BITS_PER_LONG + offset);
}
g_free(bmap);
}
static void check_bitmap_set(void)
{
bitmap_set_case(bitmap_set);
bitmap_set_case(bitmap_set_atomic);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/bitmap/bitmap_copy_with_offset",
check_bitmap_copy_with_offset);
g_test_add_func("/bitmap/bitmap_set",
check_bitmap_set);
g_test_run();
return 0;
}

146
tests/unit/test-bitops.c Normal file
View file

@ -0,0 +1,146 @@
/*
* Test bitops routines
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/bitops.h"
typedef struct {
uint32_t value;
int start;
int length;
int32_t result;
} S32Test;
typedef struct {
uint64_t value;
int start;
int length;
int64_t result;
} S64Test;
static const S32Test test_s32_data[] = {
{ 0x38463983, 4, 4, -8 },
{ 0x38463983, 12, 8, 0x63 },
{ 0x38463983, 0, 32, 0x38463983 },
};
static const S64Test test_s64_data[] = {
{ 0x8459826734967223ULL, 60, 4, -8 },
{ 0x8459826734967223ULL, 0, 64, 0x8459826734967223LL },
};
static void test_sextract32(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_s32_data); i++) {
const S32Test *test = &test_s32_data[i];
int32_t r = sextract32(test->value, test->start, test->length);
g_assert_cmpint(r, ==, test->result);
}
}
static void test_sextract64(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_s32_data); i++) {
const S32Test *test = &test_s32_data[i];
int64_t r = sextract64(test->value, test->start, test->length);
g_assert_cmpint(r, ==, test->result);
}
for (i = 0; i < ARRAY_SIZE(test_s64_data); i++) {
const S64Test *test = &test_s64_data[i];
int64_t r = sextract64(test->value, test->start, test->length);
g_assert_cmpint(r, ==, test->result);
}
}
typedef struct {
uint32_t unshuffled;
uint32_t shuffled;
} Shuffle32Test;
typedef struct {
uint64_t unshuffled;
uint64_t shuffled;
} Shuffle64Test;
static const Shuffle32Test test_shuffle32_data[] = {
{ 0x0000FFFF, 0x55555555 },
{ 0x000081C5, 0x40015011 },
};
static const Shuffle64Test test_shuffle64_data[] = {
{ 0x00000000FFFFFFFFULL, 0x5555555555555555ULL },
{ 0x00000000493AB02CULL, 0x1041054445000450ULL },
};
static void test_half_shuffle32(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_shuffle32_data); i++) {
const Shuffle32Test *test = &test_shuffle32_data[i];
uint32_t r = half_shuffle32(test->unshuffled);
g_assert_cmpint(r, ==, test->shuffled);
}
}
static void test_half_shuffle64(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_shuffle64_data); i++) {
const Shuffle64Test *test = &test_shuffle64_data[i];
uint64_t r = half_shuffle64(test->unshuffled);
g_assert_cmpint(r, ==, test->shuffled);
}
}
static void test_half_unshuffle32(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_shuffle32_data); i++) {
const Shuffle32Test *test = &test_shuffle32_data[i];
uint32_t r = half_unshuffle32(test->shuffled);
g_assert_cmpint(r, ==, test->unshuffled);
}
}
static void test_half_unshuffle64(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_shuffle64_data); i++) {
const Shuffle64Test *test = &test_shuffle64_data[i];
uint64_t r = half_unshuffle64(test->shuffled);
g_assert_cmpint(r, ==, test->unshuffled);
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/bitops/sextract32", test_sextract32);
g_test_add_func("/bitops/sextract64", test_sextract64);
g_test_add_func("/bitops/half_shuffle32", test_half_shuffle32);
g_test_add_func("/bitops/half_shuffle64", test_half_shuffle64);
g_test_add_func("/bitops/half_unshuffle32", test_half_unshuffle32);
g_test_add_func("/bitops/half_unshuffle64", test_half_unshuffle64);
return g_test_run();
}

View file

@ -0,0 +1,85 @@
/*
* BlockBackend tests
*
* Copyright (c) 2017 Kevin Wolf <kwolf@redhat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "block/block.h"
#include "sysemu/block-backend.h"
#include "qapi/error.h"
#include "qemu/main-loop.h"
static void test_drain_aio_error_flush_cb(void *opaque, int ret)
{
bool *completed = opaque;
g_assert(ret == -ENOMEDIUM);
*completed = true;
}
static void test_drain_aio_error(void)
{
BlockBackend *blk = blk_new(qemu_get_aio_context(),
BLK_PERM_ALL, BLK_PERM_ALL);
BlockAIOCB *acb;
bool completed = false;
acb = blk_aio_flush(blk, test_drain_aio_error_flush_cb, &completed);
g_assert(acb != NULL);
g_assert(completed == false);
blk_drain(blk);
g_assert(completed == true);
blk_unref(blk);
}
static void test_drain_all_aio_error(void)
{
BlockBackend *blk = blk_new(qemu_get_aio_context(),
BLK_PERM_ALL, BLK_PERM_ALL);
BlockAIOCB *acb;
bool completed = false;
acb = blk_aio_flush(blk, test_drain_aio_error_flush_cb, &completed);
g_assert(acb != NULL);
g_assert(completed == false);
blk_drain_all();
g_assert(completed == true);
blk_unref(blk);
}
int main(int argc, char **argv)
{
bdrv_init();
qemu_init_main_loop(&error_abort);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/block-backend/drain_aio_error", test_drain_aio_error);
g_test_add_func("/block-backend/drain_all_aio_error",
test_drain_all_aio_error);
return g_test_run();
}

View file

@ -0,0 +1,774 @@
/*
* Block tests for iothreads
*
* Copyright (c) 2018 Kevin Wolf <kwolf@redhat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "block/block.h"
#include "block/blockjob_int.h"
#include "sysemu/block-backend.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qemu/main-loop.h"
#include "iothread.h"
static int coroutine_fn bdrv_test_co_prwv(BlockDriverState *bs,
uint64_t offset, uint64_t bytes,
QEMUIOVector *qiov, int flags)
{
return 0;
}
static int coroutine_fn bdrv_test_co_pdiscard(BlockDriverState *bs,
int64_t offset, int bytes)
{
return 0;
}
static int coroutine_fn
bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
PreallocMode prealloc, BdrvRequestFlags flags,
Error **errp)
{
return 0;
}
static int coroutine_fn bdrv_test_co_block_status(BlockDriverState *bs,
bool want_zero,
int64_t offset, int64_t count,
int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
*pnum = count;
return 0;
}
static BlockDriver bdrv_test = {
.format_name = "test",
.instance_size = 1,
.bdrv_co_preadv = bdrv_test_co_prwv,
.bdrv_co_pwritev = bdrv_test_co_prwv,
.bdrv_co_pdiscard = bdrv_test_co_pdiscard,
.bdrv_co_truncate = bdrv_test_co_truncate,
.bdrv_co_block_status = bdrv_test_co_block_status,
};
static void test_sync_op_pread(BdrvChild *c)
{
uint8_t buf[512];
int ret;
/* Success */
ret = bdrv_pread(c, 0, buf, sizeof(buf));
g_assert_cmpint(ret, ==, 512);
/* Early error: Negative offset */
ret = bdrv_pread(c, -2, buf, sizeof(buf));
g_assert_cmpint(ret, ==, -EIO);
}
static void test_sync_op_pwrite(BdrvChild *c)
{
uint8_t buf[512];
int ret;
/* Success */
ret = bdrv_pwrite(c, 0, buf, sizeof(buf));
g_assert_cmpint(ret, ==, 512);
/* Early error: Negative offset */
ret = bdrv_pwrite(c, -2, buf, sizeof(buf));
g_assert_cmpint(ret, ==, -EIO);
}
static void test_sync_op_blk_pread(BlockBackend *blk)
{
uint8_t buf[512];
int ret;
/* Success */
ret = blk_pread(blk, 0, buf, sizeof(buf));
g_assert_cmpint(ret, ==, 512);
/* Early error: Negative offset */
ret = blk_pread(blk, -2, buf, sizeof(buf));
g_assert_cmpint(ret, ==, -EIO);
}
static void test_sync_op_blk_pwrite(BlockBackend *blk)
{
uint8_t buf[512];
int ret;
/* Success */
ret = blk_pwrite(blk, 0, buf, sizeof(buf), 0);
g_assert_cmpint(ret, ==, 512);
/* Early error: Negative offset */
ret = blk_pwrite(blk, -2, buf, sizeof(buf), 0);
g_assert_cmpint(ret, ==, -EIO);
}
static void test_sync_op_load_vmstate(BdrvChild *c)
{
uint8_t buf[512];
int ret;
/* Error: Driver does not support snapshots */
ret = bdrv_load_vmstate(c->bs, buf, 0, sizeof(buf));
g_assert_cmpint(ret, ==, -ENOTSUP);
}
static void test_sync_op_save_vmstate(BdrvChild *c)
{
uint8_t buf[512];
int ret;
/* Error: Driver does not support snapshots */
ret = bdrv_save_vmstate(c->bs, buf, 0, sizeof(buf));
g_assert_cmpint(ret, ==, -ENOTSUP);
}
static void test_sync_op_pdiscard(BdrvChild *c)
{
int ret;
/* Normal success path */
c->bs->open_flags |= BDRV_O_UNMAP;
ret = bdrv_pdiscard(c, 0, 512);
g_assert_cmpint(ret, ==, 0);
/* Early success: UNMAP not supported */
c->bs->open_flags &= ~BDRV_O_UNMAP;
ret = bdrv_pdiscard(c, 0, 512);
g_assert_cmpint(ret, ==, 0);
/* Early error: Negative offset */
ret = bdrv_pdiscard(c, -2, 512);
g_assert_cmpint(ret, ==, -EIO);
}
static void test_sync_op_blk_pdiscard(BlockBackend *blk)
{
int ret;
/* Early success: UNMAP not supported */
ret = blk_pdiscard(blk, 0, 512);
g_assert_cmpint(ret, ==, 0);
/* Early error: Negative offset */
ret = blk_pdiscard(blk, -2, 512);
g_assert_cmpint(ret, ==, -EIO);
}
static void test_sync_op_truncate(BdrvChild *c)
{
int ret;
/* Normal success path */
ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, 0, NULL);
g_assert_cmpint(ret, ==, 0);
/* Early error: Negative offset */
ret = bdrv_truncate(c, -2, false, PREALLOC_MODE_OFF, 0, NULL);
g_assert_cmpint(ret, ==, -EINVAL);
/* Error: Read-only image */
c->bs->read_only = true;
c->bs->open_flags &= ~BDRV_O_RDWR;
ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, 0, NULL);
g_assert_cmpint(ret, ==, -EACCES);
c->bs->read_only = false;
c->bs->open_flags |= BDRV_O_RDWR;
}
static void test_sync_op_block_status(BdrvChild *c)
{
int ret;
int64_t n;
/* Normal success path */
ret = bdrv_is_allocated(c->bs, 0, 65536, &n);
g_assert_cmpint(ret, ==, 0);
/* Early success: No driver support */
bdrv_test.bdrv_co_block_status = NULL;
ret = bdrv_is_allocated(c->bs, 0, 65536, &n);
g_assert_cmpint(ret, ==, 1);
/* Early success: bytes = 0 */
ret = bdrv_is_allocated(c->bs, 0, 0, &n);
g_assert_cmpint(ret, ==, 0);
/* Early success: Offset > image size*/
ret = bdrv_is_allocated(c->bs, 0x1000000, 0x1000000, &n);
g_assert_cmpint(ret, ==, 0);
}
static void test_sync_op_flush(BdrvChild *c)
{
int ret;
/* Normal success path */
ret = bdrv_flush(c->bs);
g_assert_cmpint(ret, ==, 0);
/* Early success: Read-only image */
c->bs->read_only = true;
c->bs->open_flags &= ~BDRV_O_RDWR;
ret = bdrv_flush(c->bs);
g_assert_cmpint(ret, ==, 0);
c->bs->read_only = false;
c->bs->open_flags |= BDRV_O_RDWR;
}
static void test_sync_op_blk_flush(BlockBackend *blk)
{
BlockDriverState *bs = blk_bs(blk);
int ret;
/* Normal success path */
ret = blk_flush(blk);
g_assert_cmpint(ret, ==, 0);
/* Early success: Read-only image */
bs->read_only = true;
bs->open_flags &= ~BDRV_O_RDWR;
ret = blk_flush(blk);
g_assert_cmpint(ret, ==, 0);
bs->read_only = false;
bs->open_flags |= BDRV_O_RDWR;
}
static void test_sync_op_check(BdrvChild *c)
{
BdrvCheckResult result;
int ret;
/* Error: Driver does not implement check */
ret = bdrv_check(c->bs, &result, 0);
g_assert_cmpint(ret, ==, -ENOTSUP);
}
static void test_sync_op_invalidate_cache(BdrvChild *c)
{
/* Early success: Image is not inactive */
bdrv_invalidate_cache(c->bs, NULL);
}
typedef struct SyncOpTest {
const char *name;
void (*fn)(BdrvChild *c);
void (*blkfn)(BlockBackend *blk);
} SyncOpTest;
const SyncOpTest sync_op_tests[] = {
{
.name = "/sync-op/pread",
.fn = test_sync_op_pread,
.blkfn = test_sync_op_blk_pread,
}, {
.name = "/sync-op/pwrite",
.fn = test_sync_op_pwrite,
.blkfn = test_sync_op_blk_pwrite,
}, {
.name = "/sync-op/load_vmstate",
.fn = test_sync_op_load_vmstate,
}, {
.name = "/sync-op/save_vmstate",
.fn = test_sync_op_save_vmstate,
}, {
.name = "/sync-op/pdiscard",
.fn = test_sync_op_pdiscard,
.blkfn = test_sync_op_blk_pdiscard,
}, {
.name = "/sync-op/truncate",
.fn = test_sync_op_truncate,
}, {
.name = "/sync-op/block_status",
.fn = test_sync_op_block_status,
}, {
.name = "/sync-op/flush",
.fn = test_sync_op_flush,
.blkfn = test_sync_op_blk_flush,
}, {
.name = "/sync-op/check",
.fn = test_sync_op_check,
}, {
.name = "/sync-op/invalidate_cache",
.fn = test_sync_op_invalidate_cache,
},
};
/* Test synchronous operations that run in a different iothread, so we have to
* poll for the coroutine there to return. */
static void test_sync_op(const void *opaque)
{
const SyncOpTest *t = opaque;
IOThread *iothread = iothread_new();
AioContext *ctx = iothread_get_aio_context(iothread);
BlockBackend *blk;
BlockDriverState *bs;
BdrvChild *c;
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL);
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
bs->total_sectors = 65536 / BDRV_SECTOR_SIZE;
blk_insert_bs(blk, bs, &error_abort);
c = QLIST_FIRST(&bs->parents);
blk_set_aio_context(blk, ctx, &error_abort);
aio_context_acquire(ctx);
t->fn(c);
if (t->blkfn) {
t->blkfn(blk);
}
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
aio_context_release(ctx);
bdrv_unref(bs);
blk_unref(blk);
}
typedef struct TestBlockJob {
BlockJob common;
bool should_complete;
int n;
} TestBlockJob;
static int test_job_prepare(Job *job)
{
g_assert(qemu_get_current_aio_context() == qemu_get_aio_context());
return 0;
}
static int coroutine_fn test_job_run(Job *job, Error **errp)
{
TestBlockJob *s = container_of(job, TestBlockJob, common.job);
job_transition_to_ready(&s->common.job);
while (!s->should_complete) {
s->n++;
g_assert(qemu_get_current_aio_context() == job->aio_context);
/* Avoid job_sleep_ns() because it marks the job as !busy. We want to
* emulate some actual activity (probably some I/O) here so that the
* drain involved in AioContext switches has to wait for this activity
* to stop. */
qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 1000000);
job_pause_point(&s->common.job);
}
g_assert(qemu_get_current_aio_context() == job->aio_context);
return 0;
}
static void test_job_complete(Job *job, Error **errp)
{
TestBlockJob *s = container_of(job, TestBlockJob, common.job);
s->should_complete = true;
}
BlockJobDriver test_job_driver = {
.job_driver = {
.instance_size = sizeof(TestBlockJob),
.free = block_job_free,
.user_resume = block_job_user_resume,
.run = test_job_run,
.complete = test_job_complete,
.prepare = test_job_prepare,
},
};
static void test_attach_blockjob(void)
{
IOThread *iothread = iothread_new();
AioContext *ctx = iothread_get_aio_context(iothread);
BlockBackend *blk;
BlockDriverState *bs;
TestBlockJob *tjob;
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL);
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
blk_insert_bs(blk, bs, &error_abort);
tjob = block_job_create("job0", &test_job_driver, NULL, bs,
0, BLK_PERM_ALL,
0, 0, NULL, NULL, &error_abort);
job_start(&tjob->common.job);
while (tjob->n == 0) {
aio_poll(qemu_get_aio_context(), false);
}
blk_set_aio_context(blk, ctx, &error_abort);
tjob->n = 0;
while (tjob->n == 0) {
aio_poll(qemu_get_aio_context(), false);
}
aio_context_acquire(ctx);
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
aio_context_release(ctx);
tjob->n = 0;
while (tjob->n == 0) {
aio_poll(qemu_get_aio_context(), false);
}
blk_set_aio_context(blk, ctx, &error_abort);
tjob->n = 0;
while (tjob->n == 0) {
aio_poll(qemu_get_aio_context(), false);
}
aio_context_acquire(ctx);
job_complete_sync(&tjob->common.job, &error_abort);
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
aio_context_release(ctx);
bdrv_unref(bs);
blk_unref(blk);
}
/*
* Test that changing the AioContext for one node in a tree (here through blk)
* changes all other nodes as well:
*
* blk
* |
* | bs_verify [blkverify]
* | / \
* | / \
* bs_a [bdrv_test] bs_b [bdrv_test]
*
*/
static void test_propagate_basic(void)
{
IOThread *iothread = iothread_new();
AioContext *ctx = iothread_get_aio_context(iothread);
AioContext *main_ctx;
BlockBackend *blk;
BlockDriverState *bs_a, *bs_b, *bs_verify;
QDict *options;
/*
* Create bs_a and its BlockBackend. We cannot take the RESIZE
* permission because blkverify will not share it on the test
* image.
*/
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL & ~BLK_PERM_RESIZE,
BLK_PERM_ALL);
bs_a = bdrv_new_open_driver(&bdrv_test, "bs_a", BDRV_O_RDWR, &error_abort);
blk_insert_bs(blk, bs_a, &error_abort);
/* Create bs_b */
bs_b = bdrv_new_open_driver(&bdrv_test, "bs_b", BDRV_O_RDWR, &error_abort);
/* Create blkverify filter that references both bs_a and bs_b */
options = qdict_new();
qdict_put_str(options, "driver", "blkverify");
qdict_put_str(options, "test", "bs_a");
qdict_put_str(options, "raw", "bs_b");
bs_verify = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
/* Switch the AioContext */
blk_set_aio_context(blk, ctx, &error_abort);
g_assert(blk_get_aio_context(blk) == ctx);
g_assert(bdrv_get_aio_context(bs_a) == ctx);
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
g_assert(bdrv_get_aio_context(bs_b) == ctx);
/* Switch the AioContext back */
main_ctx = qemu_get_aio_context();
aio_context_acquire(ctx);
blk_set_aio_context(blk, main_ctx, &error_abort);
aio_context_release(ctx);
g_assert(blk_get_aio_context(blk) == main_ctx);
g_assert(bdrv_get_aio_context(bs_a) == main_ctx);
g_assert(bdrv_get_aio_context(bs_verify) == main_ctx);
g_assert(bdrv_get_aio_context(bs_b) == main_ctx);
bdrv_unref(bs_verify);
bdrv_unref(bs_b);
bdrv_unref(bs_a);
blk_unref(blk);
}
/*
* Test that diamonds in the graph don't lead to endless recursion:
*
* blk
* |
* bs_verify [blkverify]
* / \
* / \
* bs_b [raw] bs_c[raw]
* \ /
* \ /
* bs_a [bdrv_test]
*/
static void test_propagate_diamond(void)
{
IOThread *iothread = iothread_new();
AioContext *ctx = iothread_get_aio_context(iothread);
AioContext *main_ctx;
BlockBackend *blk;
BlockDriverState *bs_a, *bs_b, *bs_c, *bs_verify;
QDict *options;
/* Create bs_a */
bs_a = bdrv_new_open_driver(&bdrv_test, "bs_a", BDRV_O_RDWR, &error_abort);
/* Create bs_b and bc_c */
options = qdict_new();
qdict_put_str(options, "driver", "raw");
qdict_put_str(options, "file", "bs_a");
qdict_put_str(options, "node-name", "bs_b");
bs_b = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
options = qdict_new();
qdict_put_str(options, "driver", "raw");
qdict_put_str(options, "file", "bs_a");
qdict_put_str(options, "node-name", "bs_c");
bs_c = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
/* Create blkverify filter that references both bs_b and bs_c */
options = qdict_new();
qdict_put_str(options, "driver", "blkverify");
qdict_put_str(options, "test", "bs_b");
qdict_put_str(options, "raw", "bs_c");
bs_verify = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
/*
* Do not take the RESIZE permission: This would require the same
* from bs_c and thus from bs_a; however, blkverify will not share
* it on bs_b, and thus it will not be available for bs_a.
*/
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL & ~BLK_PERM_RESIZE,
BLK_PERM_ALL);
blk_insert_bs(blk, bs_verify, &error_abort);
/* Switch the AioContext */
blk_set_aio_context(blk, ctx, &error_abort);
g_assert(blk_get_aio_context(blk) == ctx);
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
g_assert(bdrv_get_aio_context(bs_a) == ctx);
g_assert(bdrv_get_aio_context(bs_b) == ctx);
g_assert(bdrv_get_aio_context(bs_c) == ctx);
/* Switch the AioContext back */
main_ctx = qemu_get_aio_context();
aio_context_acquire(ctx);
blk_set_aio_context(blk, main_ctx, &error_abort);
aio_context_release(ctx);
g_assert(blk_get_aio_context(blk) == main_ctx);
g_assert(bdrv_get_aio_context(bs_verify) == main_ctx);
g_assert(bdrv_get_aio_context(bs_a) == main_ctx);
g_assert(bdrv_get_aio_context(bs_b) == main_ctx);
g_assert(bdrv_get_aio_context(bs_c) == main_ctx);
blk_unref(blk);
bdrv_unref(bs_verify);
bdrv_unref(bs_c);
bdrv_unref(bs_b);
bdrv_unref(bs_a);
}
static void test_propagate_mirror(void)
{
IOThread *iothread = iothread_new();
AioContext *ctx = iothread_get_aio_context(iothread);
AioContext *main_ctx = qemu_get_aio_context();
BlockDriverState *src, *target, *filter;
BlockBackend *blk;
Job *job;
Error *local_err = NULL;
/* Create src and target*/
src = bdrv_new_open_driver(&bdrv_test, "src", BDRV_O_RDWR, &error_abort);
target = bdrv_new_open_driver(&bdrv_test, "target", BDRV_O_RDWR,
&error_abort);
/* Start a mirror job */
mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0,
MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false,
BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
false, "filter_node", MIRROR_COPY_MODE_BACKGROUND,
&error_abort);
job = job_get("job0");
filter = bdrv_find_node("filter_node");
/* Change the AioContext of src */
bdrv_try_set_aio_context(src, ctx, &error_abort);
g_assert(bdrv_get_aio_context(src) == ctx);
g_assert(bdrv_get_aio_context(target) == ctx);
g_assert(bdrv_get_aio_context(filter) == ctx);
g_assert(job->aio_context == ctx);
/* Change the AioContext of target */
aio_context_acquire(ctx);
bdrv_try_set_aio_context(target, main_ctx, &error_abort);
aio_context_release(ctx);
g_assert(bdrv_get_aio_context(src) == main_ctx);
g_assert(bdrv_get_aio_context(target) == main_ctx);
g_assert(bdrv_get_aio_context(filter) == main_ctx);
/* With a BlockBackend on src, changing target must fail */
blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
blk_insert_bs(blk, src, &error_abort);
bdrv_try_set_aio_context(target, ctx, &local_err);
error_free_or_abort(&local_err);
g_assert(blk_get_aio_context(blk) == main_ctx);
g_assert(bdrv_get_aio_context(src) == main_ctx);
g_assert(bdrv_get_aio_context(target) == main_ctx);
g_assert(bdrv_get_aio_context(filter) == main_ctx);
/* ...unless we explicitly allow it */
aio_context_acquire(ctx);
blk_set_allow_aio_context_change(blk, true);
bdrv_try_set_aio_context(target, ctx, &error_abort);
aio_context_release(ctx);
g_assert(blk_get_aio_context(blk) == ctx);
g_assert(bdrv_get_aio_context(src) == ctx);
g_assert(bdrv_get_aio_context(target) == ctx);
g_assert(bdrv_get_aio_context(filter) == ctx);
job_cancel_sync_all();
aio_context_acquire(ctx);
blk_set_aio_context(blk, main_ctx, &error_abort);
bdrv_try_set_aio_context(target, main_ctx, &error_abort);
aio_context_release(ctx);
blk_unref(blk);
bdrv_unref(src);
bdrv_unref(target);
}
static void test_attach_second_node(void)
{
IOThread *iothread = iothread_new();
AioContext *ctx = iothread_get_aio_context(iothread);
AioContext *main_ctx = qemu_get_aio_context();
BlockBackend *blk;
BlockDriverState *bs, *filter;
QDict *options;
blk = blk_new(ctx, BLK_PERM_ALL, BLK_PERM_ALL);
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
blk_insert_bs(blk, bs, &error_abort);
options = qdict_new();
qdict_put_str(options, "driver", "raw");
qdict_put_str(options, "file", "base");
filter = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
g_assert(blk_get_aio_context(blk) == ctx);
g_assert(bdrv_get_aio_context(bs) == ctx);
g_assert(bdrv_get_aio_context(filter) == ctx);
aio_context_acquire(ctx);
blk_set_aio_context(blk, main_ctx, &error_abort);
aio_context_release(ctx);
g_assert(blk_get_aio_context(blk) == main_ctx);
g_assert(bdrv_get_aio_context(bs) == main_ctx);
g_assert(bdrv_get_aio_context(filter) == main_ctx);
bdrv_unref(filter);
bdrv_unref(bs);
blk_unref(blk);
}
static void test_attach_preserve_blk_ctx(void)
{
IOThread *iothread = iothread_new();
AioContext *ctx = iothread_get_aio_context(iothread);
BlockBackend *blk;
BlockDriverState *bs;
blk = blk_new(ctx, BLK_PERM_ALL, BLK_PERM_ALL);
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
bs->total_sectors = 65536 / BDRV_SECTOR_SIZE;
/* Add node to BlockBackend that has an iothread context assigned */
blk_insert_bs(blk, bs, &error_abort);
g_assert(blk_get_aio_context(blk) == ctx);
g_assert(bdrv_get_aio_context(bs) == ctx);
/* Remove the node again */
aio_context_acquire(ctx);
blk_remove_bs(blk);
aio_context_release(ctx);
g_assert(blk_get_aio_context(blk) == ctx);
g_assert(bdrv_get_aio_context(bs) == qemu_get_aio_context());
/* Re-attach the node */
blk_insert_bs(blk, bs, &error_abort);
g_assert(blk_get_aio_context(blk) == ctx);
g_assert(bdrv_get_aio_context(bs) == ctx);
aio_context_acquire(ctx);
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
aio_context_release(ctx);
bdrv_unref(bs);
blk_unref(blk);
}
int main(int argc, char **argv)
{
int i;
bdrv_init();
qemu_init_main_loop(&error_abort);
g_test_init(&argc, &argv, NULL);
for (i = 0; i < ARRAY_SIZE(sync_op_tests); i++) {
const SyncOpTest *t = &sync_op_tests[i];
g_test_add_data_func(t->name, t, test_sync_op);
}
g_test_add_func("/attach/blockjob", test_attach_blockjob);
g_test_add_func("/attach/second_node", test_attach_second_node);
g_test_add_func("/attach/preserve_blk_ctx", test_attach_preserve_blk_ctx);
g_test_add_func("/propagate/basic", test_propagate_basic);
g_test_add_func("/propagate/diamond", test_propagate_diamond);
g_test_add_func("/propagate/mirror", test_propagate_mirror);
return g_test_run();
}

View file

@ -0,0 +1,262 @@
/*
* Blockjob transactions tests
*
* Copyright Red Hat, Inc. 2015
*
* Authors:
* Stefan Hajnoczi <stefanha@redhat.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/main-loop.h"
#include "block/blockjob_int.h"
#include "sysemu/block-backend.h"
#include "qapi/qmp/qdict.h"
typedef struct {
BlockJob common;
unsigned int iterations;
bool use_timer;
int rc;
int *result;
} TestBlockJob;
static void test_block_job_clean(Job *job)
{
BlockJob *bjob = container_of(job, BlockJob, job);
BlockDriverState *bs = blk_bs(bjob->blk);
bdrv_unref(bs);
}
static int coroutine_fn test_block_job_run(Job *job, Error **errp)
{
TestBlockJob *s = container_of(job, TestBlockJob, common.job);
while (s->iterations--) {
if (s->use_timer) {
job_sleep_ns(job, 0);
} else {
job_yield(job);
}
if (job_is_cancelled(job)) {
break;
}
}
return s->rc;
}
typedef struct {
TestBlockJob *job;
int *result;
} TestBlockJobCBData;
static void test_block_job_cb(void *opaque, int ret)
{
TestBlockJobCBData *data = opaque;
if (!ret && job_is_cancelled(&data->job->common.job)) {
ret = -ECANCELED;
}
*data->result = ret;
g_free(data);
}
static const BlockJobDriver test_block_job_driver = {
.job_driver = {
.instance_size = sizeof(TestBlockJob),
.free = block_job_free,
.user_resume = block_job_user_resume,
.run = test_block_job_run,
.clean = test_block_job_clean,
},
};
/* Create a block job that completes with a given return code after a given
* number of event loop iterations. The return code is stored in the given
* result pointer.
*
* The event loop iterations can either be handled automatically with a 0 delay
* timer, or they can be stepped manually by entering the coroutine.
*/
static BlockJob *test_block_job_start(unsigned int iterations,
bool use_timer,
int rc, int *result, JobTxn *txn)
{
BlockDriverState *bs;
TestBlockJob *s;
TestBlockJobCBData *data;
static unsigned counter;
char job_id[24];
data = g_new0(TestBlockJobCBData, 1);
QDict *opt = qdict_new();
qdict_put_str(opt, "file.read-zeroes", "on");
bs = bdrv_open("null-co://", NULL, opt, 0, &error_abort);
g_assert_nonnull(bs);
snprintf(job_id, sizeof(job_id), "job%u", counter++);
s = block_job_create(job_id, &test_block_job_driver, txn, bs,
0, BLK_PERM_ALL, 0, JOB_DEFAULT,
test_block_job_cb, data, &error_abort);
s->iterations = iterations;
s->use_timer = use_timer;
s->rc = rc;
s->result = result;
data->job = s;
data->result = result;
return &s->common;
}
static void test_single_job(int expected)
{
BlockJob *job;
JobTxn *txn;
int result = -EINPROGRESS;
txn = job_txn_new();
job = test_block_job_start(1, true, expected, &result, txn);
job_start(&job->job);
if (expected == -ECANCELED) {
job_cancel(&job->job, false);
}
while (result == -EINPROGRESS) {
aio_poll(qemu_get_aio_context(), true);
}
g_assert_cmpint(result, ==, expected);
job_txn_unref(txn);
}
static void test_single_job_success(void)
{
test_single_job(0);
}
static void test_single_job_failure(void)
{
test_single_job(-EIO);
}
static void test_single_job_cancel(void)
{
test_single_job(-ECANCELED);
}
static void test_pair_jobs(int expected1, int expected2)
{
BlockJob *job1;
BlockJob *job2;
JobTxn *txn;
int result1 = -EINPROGRESS;
int result2 = -EINPROGRESS;
txn = job_txn_new();
job1 = test_block_job_start(1, true, expected1, &result1, txn);
job2 = test_block_job_start(2, true, expected2, &result2, txn);
job_start(&job1->job);
job_start(&job2->job);
/* Release our reference now to trigger as many nice
* use-after-free bugs as possible.
*/
job_txn_unref(txn);
if (expected1 == -ECANCELED) {
job_cancel(&job1->job, false);
}
if (expected2 == -ECANCELED) {
job_cancel(&job2->job, false);
}
while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) {
aio_poll(qemu_get_aio_context(), true);
}
/* Failure or cancellation of one job cancels the other job */
if (expected1 != 0) {
expected2 = -ECANCELED;
} else if (expected2 != 0) {
expected1 = -ECANCELED;
}
g_assert_cmpint(result1, ==, expected1);
g_assert_cmpint(result2, ==, expected2);
}
static void test_pair_jobs_success(void)
{
test_pair_jobs(0, 0);
}
static void test_pair_jobs_failure(void)
{
/* Test both orderings. The two jobs run for a different number of
* iterations so the code path is different depending on which job fails
* first.
*/
test_pair_jobs(-EIO, 0);
test_pair_jobs(0, -EIO);
}
static void test_pair_jobs_cancel(void)
{
test_pair_jobs(-ECANCELED, 0);
test_pair_jobs(0, -ECANCELED);
}
static void test_pair_jobs_fail_cancel_race(void)
{
BlockJob *job1;
BlockJob *job2;
JobTxn *txn;
int result1 = -EINPROGRESS;
int result2 = -EINPROGRESS;
txn = job_txn_new();
job1 = test_block_job_start(1, true, -ECANCELED, &result1, txn);
job2 = test_block_job_start(2, false, 0, &result2, txn);
job_start(&job1->job);
job_start(&job2->job);
job_cancel(&job1->job, false);
/* Now make job2 finish before the main loop kicks jobs. This simulates
* the race between a pending kick and another job completing.
*/
job_enter(&job2->job);
job_enter(&job2->job);
while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) {
aio_poll(qemu_get_aio_context(), true);
}
g_assert_cmpint(result1, ==, -ECANCELED);
g_assert_cmpint(result2, ==, -ECANCELED);
job_txn_unref(txn);
}
int main(int argc, char **argv)
{
qemu_init_main_loop(&error_abort);
bdrv_init();
g_test_init(&argc, &argv, NULL);
g_test_add_func("/single/success", test_single_job_success);
g_test_add_func("/single/failure", test_single_job_failure);
g_test_add_func("/single/cancel", test_single_job_cancel);
g_test_add_func("/pair/success", test_pair_jobs_success);
g_test_add_func("/pair/failure", test_pair_jobs_failure);
g_test_add_func("/pair/cancel", test_pair_jobs_cancel);
g_test_add_func("/pair/fail-cancel-race", test_pair_jobs_fail_cancel_race);
return g_test_run();
}

393
tests/unit/test-blockjob.c Normal file
View file

@ -0,0 +1,393 @@
/*
* Blockjob tests
*
* Copyright Igalia, S.L. 2016
*
* Authors:
* Alberto Garcia <berto@igalia.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/main-loop.h"
#include "block/blockjob_int.h"
#include "sysemu/block-backend.h"
#include "qapi/qmp/qdict.h"
static const BlockJobDriver test_block_job_driver = {
.job_driver = {
.instance_size = sizeof(BlockJob),
.free = block_job_free,
.user_resume = block_job_user_resume,
},
};
static void block_job_cb(void *opaque, int ret)
{
}
static BlockJob *mk_job(BlockBackend *blk, const char *id,
const BlockJobDriver *drv, bool should_succeed,
int flags)
{
BlockJob *job;
Error *err = NULL;
job = block_job_create(id, drv, NULL, blk_bs(blk),
0, BLK_PERM_ALL, 0, flags, block_job_cb,
NULL, &err);
if (should_succeed) {
g_assert_null(err);
g_assert_nonnull(job);
if (id) {
g_assert_cmpstr(job->job.id, ==, id);
} else {
g_assert_cmpstr(job->job.id, ==, blk_name(blk));
}
} else {
error_free_or_abort(&err);
g_assert_null(job);
}
return job;
}
static BlockJob *do_test_id(BlockBackend *blk, const char *id,
bool should_succeed)
{
return mk_job(blk, id, &test_block_job_driver,
should_succeed, JOB_DEFAULT);
}
/* This creates a BlockBackend (optionally with a name) with a
* BlockDriverState inserted. */
static BlockBackend *create_blk(const char *name)
{
/* No I/O is performed on this device */
BlockBackend *blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
BlockDriverState *bs;
QDict *opt = qdict_new();
qdict_put_str(opt, "file.read-zeroes", "on");
bs = bdrv_open("null-co://", NULL, opt, 0, &error_abort);
g_assert_nonnull(bs);
blk_insert_bs(blk, bs, &error_abort);
bdrv_unref(bs);
if (name) {
Error *err = NULL;
monitor_add_blk(blk, name, &err);
g_assert_null(err);
}
return blk;
}
/* This destroys the backend */
static void destroy_blk(BlockBackend *blk)
{
if (blk_name(blk)[0] != '\0') {
monitor_remove_blk(blk);
}
blk_remove_bs(blk);
blk_unref(blk);
}
static void test_job_ids(void)
{
BlockBackend *blk[3];
BlockJob *job[3];
blk[0] = create_blk(NULL);
blk[1] = create_blk("drive1");
blk[2] = create_blk("drive2");
/* No job ID provided and the block backend has no name */
job[0] = do_test_id(blk[0], NULL, false);
/* These are all invalid job IDs */
job[0] = do_test_id(blk[0], "0id", false);
job[0] = do_test_id(blk[0], "", false);
job[0] = do_test_id(blk[0], " ", false);
job[0] = do_test_id(blk[0], "123", false);
job[0] = do_test_id(blk[0], "_id", false);
job[0] = do_test_id(blk[0], "-id", false);
job[0] = do_test_id(blk[0], ".id", false);
job[0] = do_test_id(blk[0], "#id", false);
/* This one is valid */
job[0] = do_test_id(blk[0], "id0", true);
/* We can have two jobs in the same BDS */
job[1] = do_test_id(blk[0], "id1", true);
job_early_fail(&job[1]->job);
/* Duplicate job IDs are not allowed */
job[1] = do_test_id(blk[1], "id0", false);
/* But once job[0] finishes we can reuse its ID */
job_early_fail(&job[0]->job);
job[1] = do_test_id(blk[1], "id0", true);
/* No job ID specified, defaults to the backend name ('drive1') */
job_early_fail(&job[1]->job);
job[1] = do_test_id(blk[1], NULL, true);
/* Duplicate job ID */
job[2] = do_test_id(blk[2], "drive1", false);
/* The ID of job[2] would default to 'drive2' but it is already in use */
job[0] = do_test_id(blk[0], "drive2", true);
job[2] = do_test_id(blk[2], NULL, false);
/* This one is valid */
job[2] = do_test_id(blk[2], "id_2", true);
job_early_fail(&job[0]->job);
job_early_fail(&job[1]->job);
job_early_fail(&job[2]->job);
destroy_blk(blk[0]);
destroy_blk(blk[1]);
destroy_blk(blk[2]);
}
typedef struct CancelJob {
BlockJob common;
BlockBackend *blk;
bool should_converge;
bool should_complete;
} CancelJob;
static void cancel_job_complete(Job *job, Error **errp)
{
CancelJob *s = container_of(job, CancelJob, common.job);
s->should_complete = true;
}
static int coroutine_fn cancel_job_run(Job *job, Error **errp)
{
CancelJob *s = container_of(job, CancelJob, common.job);
while (!s->should_complete) {
if (job_is_cancelled(&s->common.job)) {
return 0;
}
if (!job_is_ready(&s->common.job) && s->should_converge) {
job_transition_to_ready(&s->common.job);
}
job_sleep_ns(&s->common.job, 100000);
}
return 0;
}
static const BlockJobDriver test_cancel_driver = {
.job_driver = {
.instance_size = sizeof(CancelJob),
.free = block_job_free,
.user_resume = block_job_user_resume,
.run = cancel_job_run,
.complete = cancel_job_complete,
},
};
static CancelJob *create_common(Job **pjob)
{
BlockBackend *blk;
Job *job;
BlockJob *bjob;
CancelJob *s;
blk = create_blk(NULL);
bjob = mk_job(blk, "Steve", &test_cancel_driver, true,
JOB_MANUAL_FINALIZE | JOB_MANUAL_DISMISS);
job = &bjob->job;
job_ref(job);
assert(job->status == JOB_STATUS_CREATED);
s = container_of(bjob, CancelJob, common);
s->blk = blk;
*pjob = job;
return s;
}
static void cancel_common(CancelJob *s)
{
BlockJob *job = &s->common;
BlockBackend *blk = s->blk;
JobStatus sts = job->job.status;
AioContext *ctx;
ctx = job->job.aio_context;
aio_context_acquire(ctx);
job_cancel_sync(&job->job);
if (sts != JOB_STATUS_CREATED && sts != JOB_STATUS_CONCLUDED) {
Job *dummy = &job->job;
job_dismiss(&dummy, &error_abort);
}
assert(job->job.status == JOB_STATUS_NULL);
job_unref(&job->job);
destroy_blk(blk);
aio_context_release(ctx);
}
static void test_cancel_created(void)
{
Job *job;
CancelJob *s;
s = create_common(&job);
cancel_common(s);
}
static void test_cancel_running(void)
{
Job *job;
CancelJob *s;
s = create_common(&job);
job_start(job);
assert(job->status == JOB_STATUS_RUNNING);
cancel_common(s);
}
static void test_cancel_paused(void)
{
Job *job;
CancelJob *s;
s = create_common(&job);
job_start(job);
assert(job->status == JOB_STATUS_RUNNING);
job_user_pause(job, &error_abort);
job_enter(job);
assert(job->status == JOB_STATUS_PAUSED);
cancel_common(s);
}
static void test_cancel_ready(void)
{
Job *job;
CancelJob *s;
s = create_common(&job);
job_start(job);
assert(job->status == JOB_STATUS_RUNNING);
s->should_converge = true;
job_enter(job);
assert(job->status == JOB_STATUS_READY);
cancel_common(s);
}
static void test_cancel_standby(void)
{
Job *job;
CancelJob *s;
s = create_common(&job);
job_start(job);
assert(job->status == JOB_STATUS_RUNNING);
s->should_converge = true;
job_enter(job);
assert(job->status == JOB_STATUS_READY);
job_user_pause(job, &error_abort);
job_enter(job);
assert(job->status == JOB_STATUS_STANDBY);
cancel_common(s);
}
static void test_cancel_pending(void)
{
Job *job;
CancelJob *s;
s = create_common(&job);
job_start(job);
assert(job->status == JOB_STATUS_RUNNING);
s->should_converge = true;
job_enter(job);
assert(job->status == JOB_STATUS_READY);
job_complete(job, &error_abort);
job_enter(job);
while (!job->deferred_to_main_loop) {
aio_poll(qemu_get_aio_context(), true);
}
assert(job->status == JOB_STATUS_READY);
aio_poll(qemu_get_aio_context(), true);
assert(job->status == JOB_STATUS_PENDING);
cancel_common(s);
}
static void test_cancel_concluded(void)
{
Job *job;
CancelJob *s;
s = create_common(&job);
job_start(job);
assert(job->status == JOB_STATUS_RUNNING);
s->should_converge = true;
job_enter(job);
assert(job->status == JOB_STATUS_READY);
job_complete(job, &error_abort);
job_enter(job);
while (!job->deferred_to_main_loop) {
aio_poll(qemu_get_aio_context(), true);
}
assert(job->status == JOB_STATUS_READY);
aio_poll(qemu_get_aio_context(), true);
assert(job->status == JOB_STATUS_PENDING);
aio_context_acquire(job->aio_context);
job_finalize(job, &error_abort);
aio_context_release(job->aio_context);
assert(job->status == JOB_STATUS_CONCLUDED);
cancel_common(s);
}
int main(int argc, char **argv)
{
qemu_init_main_loop(&error_abort);
bdrv_init();
g_test_init(&argc, &argv, NULL);
g_test_add_func("/blockjob/ids", test_job_ids);
g_test_add_func("/blockjob/cancel/created", test_cancel_created);
g_test_add_func("/blockjob/cancel/running", test_cancel_running);
g_test_add_func("/blockjob/cancel/paused", test_cancel_paused);
g_test_add_func("/blockjob/cancel/ready", test_cancel_ready);
g_test_add_func("/blockjob/cancel/standby", test_cancel_standby);
g_test_add_func("/blockjob/cancel/pending", test_cancel_pending);
g_test_add_func("/blockjob/cancel/concluded", test_cancel_concluded);
return g_test_run();
}

View file

@ -0,0 +1,78 @@
/*
* QEMU buffer_is_zero test
*
* Copyright (c) 2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qemu/cutils.h"
static char buffer[8 * 1024 * 1024];
static void test_1(void)
{
size_t s, a, o;
/* Basic positive test. */
g_assert(buffer_is_zero(buffer, sizeof(buffer)));
/* Basic negative test. */
buffer[sizeof(buffer) - 1] = 1;
g_assert(!buffer_is_zero(buffer, sizeof(buffer)));
buffer[sizeof(buffer) - 1] = 0;
/* Positive tests for size and alignment. */
for (a = 1; a <= 64; a++) {
for (s = 1; s < 1024; s++) {
buffer[a - 1] = 1;
buffer[a + s] = 1;
g_assert(buffer_is_zero(buffer + a, s));
buffer[a - 1] = 0;
buffer[a + s] = 0;
}
}
/* Negative tests for size, alignment, and the offset of the marker. */
for (a = 1; a <= 64; a++) {
for (s = 1; s < 1024; s++) {
for (o = 0; o < s; ++o) {
buffer[a + o] = 1;
g_assert(!buffer_is_zero(buffer + a, s));
buffer[a + o] = 0;
}
}
}
}
static void test_2(void)
{
if (g_test_perf()) {
test_1();
} else {
do {
test_1();
} while (test_buffer_is_zero_next_accel());
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/cutils/bufferiszero", test_2);
return g_test_run();
}

1560
tests/unit/test-char.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,199 @@
/*
* QAPI Clone Visitor unit-tests.
*
* Copyright (C) 2016 Red Hat Inc.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/clone-visitor.h"
#include "test-qapi-visit.h"
static void test_clone_struct(void)
{
UserDefOne *src, *dst;
src = g_new0(UserDefOne, 1);
src->integer = 42;
src->string = g_strdup("Hello");
src->has_enum1 = false;
src->enum1 = ENUM_ONE_VALUE2;
dst = QAPI_CLONE(UserDefOne, src);
g_assert(dst);
g_assert_cmpint(dst->integer, ==, 42);
g_assert(dst->string != src->string);
g_assert_cmpstr(dst->string, ==, "Hello");
g_assert_cmpint(dst->has_enum1, ==, false);
/* Our implementation does this, but it is not required:
g_assert_cmpint(dst->enum1, ==, ENUM_ONE_VALUE2);
*/
qapi_free_UserDefOne(src);
qapi_free_UserDefOne(dst);
}
static void test_clone_alternate(void)
{
AltEnumBool *b_src, *s_src, *b_dst, *s_dst;
b_src = g_new0(AltEnumBool, 1);
b_src->type = QTYPE_QBOOL;
b_src->u.b = true;
s_src = g_new0(AltEnumBool, 1);
s_src->type = QTYPE_QSTRING;
s_src->u.e = ENUM_ONE_VALUE1;
b_dst = QAPI_CLONE(AltEnumBool, b_src);
g_assert(b_dst);
g_assert_cmpint(b_dst->type, ==, b_src->type);
g_assert_cmpint(b_dst->u.b, ==, b_src->u.b);
s_dst = QAPI_CLONE(AltEnumBool, s_src);
g_assert(s_dst);
g_assert_cmpint(s_dst->type, ==, s_src->type);
g_assert_cmpint(s_dst->u.e, ==, s_src->u.e);
qapi_free_AltEnumBool(b_src);
qapi_free_AltEnumBool(s_src);
qapi_free_AltEnumBool(b_dst);
qapi_free_AltEnumBool(s_dst);
}
static void test_clone_list_union(void)
{
uint8List *src = NULL, *dst;
uint8List *tmp = NULL;
int i;
/* Build list in reverse */
for (i = 10; i; i--) {
QAPI_LIST_PREPEND(src, i);
}
dst = QAPI_CLONE(uint8List, src);
for (tmp = dst, i = 1; i <= 10; i++) {
g_assert(tmp);
g_assert_cmpint(tmp->value, ==, i);
tmp = tmp->next;
}
g_assert(!tmp);
qapi_free_uint8List(src);
qapi_free_uint8List(dst);
}
static void test_clone_empty(void)
{
Empty2 *src, *dst;
src = g_new0(Empty2, 1);
dst = QAPI_CLONE(Empty2, src);
g_assert(dst);
qapi_free_Empty2(src);
qapi_free_Empty2(dst);
}
static void test_clone_complex1(void)
{
UserDefListUnion *src, *dst;
src = g_new0(UserDefListUnion, 1);
src->type = USER_DEF_LIST_UNION_KIND_STRING;
dst = QAPI_CLONE(UserDefListUnion, src);
g_assert(dst);
g_assert_cmpint(dst->type, ==, src->type);
g_assert(!dst->u.string.data);
qapi_free_UserDefListUnion(src);
qapi_free_UserDefListUnion(dst);
}
static void test_clone_complex2(void)
{
WrapAlternate *src, *dst;
src = g_new0(WrapAlternate, 1);
src->alt = g_new(UserDefAlternate, 1);
src->alt->type = QTYPE_QDICT;
src->alt->u.udfu.integer = 42;
/* Clone intentionally converts NULL into "" for strings */
src->alt->u.udfu.string = NULL;
src->alt->u.udfu.enum1 = ENUM_ONE_VALUE3;
src->alt->u.udfu.u.value3.intb = 99;
src->alt->u.udfu.u.value3.has_a_b = true;
src->alt->u.udfu.u.value3.a_b = true;
dst = QAPI_CLONE(WrapAlternate, src);
g_assert(dst);
g_assert(dst->alt);
g_assert_cmpint(dst->alt->type, ==, QTYPE_QDICT);
g_assert_cmpint(dst->alt->u.udfu.integer, ==, 42);
g_assert_cmpstr(dst->alt->u.udfu.string, ==, "");
g_assert_cmpint(dst->alt->u.udfu.enum1, ==, ENUM_ONE_VALUE3);
g_assert_cmpint(dst->alt->u.udfu.u.value3.intb, ==, 99);
g_assert_cmpint(dst->alt->u.udfu.u.value3.has_a_b, ==, true);
g_assert_cmpint(dst->alt->u.udfu.u.value3.a_b, ==, true);
qapi_free_WrapAlternate(src);
qapi_free_WrapAlternate(dst);
}
static void test_clone_complex3(void)
{
__org_qemu_x_Struct2 *src, *dst;
__org_qemu_x_Union1List *tmp;
src = g_new0(__org_qemu_x_Struct2, 1);
tmp = src->array = g_new0(__org_qemu_x_Union1List, 1);
tmp->value = g_new0(__org_qemu_x_Union1, 1);
tmp->value->type = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH;
tmp->value->u.__org_qemu_x_branch.data = g_strdup("one");
tmp = tmp->next = g_new0(__org_qemu_x_Union1List, 1);
tmp->value = g_new0(__org_qemu_x_Union1, 1);
tmp->value->type = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH;
tmp->value->u.__org_qemu_x_branch.data = g_strdup("two");
tmp = tmp->next = g_new0(__org_qemu_x_Union1List, 1);
tmp->value = g_new0(__org_qemu_x_Union1, 1);
tmp->value->type = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH;
tmp->value->u.__org_qemu_x_branch.data = g_strdup("three");
dst = QAPI_CLONE(__org_qemu_x_Struct2, src);
g_assert(dst);
tmp = dst->array;
g_assert(tmp);
g_assert(tmp->value);
g_assert_cmpstr(tmp->value->u.__org_qemu_x_branch.data, ==, "one");
tmp = tmp->next;
g_assert(tmp);
g_assert(tmp->value);
g_assert_cmpstr(tmp->value->u.__org_qemu_x_branch.data, ==, "two");
tmp = tmp->next;
g_assert(tmp);
g_assert(tmp->value);
g_assert_cmpstr(tmp->value->u.__org_qemu_x_branch.data, ==, "three");
tmp = tmp->next;
g_assert(!tmp);
qapi_free___org_qemu_x_Struct2(src);
qapi_free___org_qemu_x_Struct2(dst);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/visitor/clone/struct", test_clone_struct);
g_test_add_func("/visitor/clone/alternate", test_clone_alternate);
g_test_add_func("/visitor/clone/list_union", test_clone_list_union);
g_test_add_func("/visitor/clone/empty", test_clone_empty);
g_test_add_func("/visitor/clone/complex1", test_clone_complex1);
g_test_add_func("/visitor/clone/complex2", test_clone_complex2);
g_test_add_func("/visitor/clone/complex3", test_clone_complex3);
return g_test_run();
}

512
tests/unit/test-coroutine.c Normal file
View file

@ -0,0 +1,512 @@
/*
* Coroutine tests
*
* Copyright IBM, Corp. 2011
*
* Authors:
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/coroutine.h"
#include "qemu/coroutine_int.h"
#include "qemu/lockable.h"
/*
* Check that qemu_in_coroutine() works
*/
static void coroutine_fn verify_in_coroutine(void *opaque)
{
g_assert(qemu_in_coroutine());
}
static void test_in_coroutine(void)
{
Coroutine *coroutine;
g_assert(!qemu_in_coroutine());
coroutine = qemu_coroutine_create(verify_in_coroutine, NULL);
qemu_coroutine_enter(coroutine);
}
/*
* Check that qemu_coroutine_self() works
*/
static void coroutine_fn verify_self(void *opaque)
{
Coroutine **p_co = opaque;
g_assert(qemu_coroutine_self() == *p_co);
}
static void test_self(void)
{
Coroutine *coroutine;
coroutine = qemu_coroutine_create(verify_self, &coroutine);
qemu_coroutine_enter(coroutine);
}
/*
* Check that qemu_coroutine_entered() works
*/
static void coroutine_fn verify_entered_step_2(void *opaque)
{
Coroutine *caller = (Coroutine *)opaque;
g_assert(qemu_coroutine_entered(caller));
g_assert(qemu_coroutine_entered(qemu_coroutine_self()));
qemu_coroutine_yield();
/* Once more to check it still works after yielding */
g_assert(qemu_coroutine_entered(caller));
g_assert(qemu_coroutine_entered(qemu_coroutine_self()));
}
static void coroutine_fn verify_entered_step_1(void *opaque)
{
Coroutine *self = qemu_coroutine_self();
Coroutine *coroutine;
g_assert(qemu_coroutine_entered(self));
coroutine = qemu_coroutine_create(verify_entered_step_2, self);
g_assert(!qemu_coroutine_entered(coroutine));
qemu_coroutine_enter(coroutine);
g_assert(!qemu_coroutine_entered(coroutine));
qemu_coroutine_enter(coroutine);
}
static void test_entered(void)
{
Coroutine *coroutine;
coroutine = qemu_coroutine_create(verify_entered_step_1, NULL);
g_assert(!qemu_coroutine_entered(coroutine));
qemu_coroutine_enter(coroutine);
}
/*
* Check that coroutines may nest multiple levels
*/
typedef struct {
unsigned int n_enter; /* num coroutines entered */
unsigned int n_return; /* num coroutines returned */
unsigned int max; /* maximum level of nesting */
} NestData;
static void coroutine_fn nest(void *opaque)
{
NestData *nd = opaque;
nd->n_enter++;
if (nd->n_enter < nd->max) {
Coroutine *child;
child = qemu_coroutine_create(nest, nd);
qemu_coroutine_enter(child);
}
nd->n_return++;
}
static void test_nesting(void)
{
Coroutine *root;
NestData nd = {
.n_enter = 0,
.n_return = 0,
.max = 128,
};
root = qemu_coroutine_create(nest, &nd);
qemu_coroutine_enter(root);
/* Must enter and return from max nesting level */
g_assert_cmpint(nd.n_enter, ==, nd.max);
g_assert_cmpint(nd.n_return, ==, nd.max);
}
/*
* Check that yield/enter transfer control correctly
*/
static void coroutine_fn yield_5_times(void *opaque)
{
bool *done = opaque;
int i;
for (i = 0; i < 5; i++) {
qemu_coroutine_yield();
}
*done = true;
}
static void test_yield(void)
{
Coroutine *coroutine;
bool done = false;
int i = -1; /* one extra time to return from coroutine */
coroutine = qemu_coroutine_create(yield_5_times, &done);
while (!done) {
qemu_coroutine_enter(coroutine);
i++;
}
g_assert_cmpint(i, ==, 5); /* coroutine must yield 5 times */
}
static void coroutine_fn c2_fn(void *opaque)
{
qemu_coroutine_yield();
}
static void coroutine_fn c1_fn(void *opaque)
{
Coroutine *c2 = opaque;
qemu_coroutine_enter(c2);
}
static void test_no_dangling_access(void)
{
Coroutine *c1;
Coroutine *c2;
Coroutine tmp;
c2 = qemu_coroutine_create(c2_fn, NULL);
c1 = qemu_coroutine_create(c1_fn, c2);
qemu_coroutine_enter(c1);
/* c1 shouldn't be used any more now; make sure we segfault if it is */
tmp = *c1;
memset(c1, 0xff, sizeof(Coroutine));
qemu_coroutine_enter(c2);
/* Must restore the coroutine now to avoid corrupted pool */
*c1 = tmp;
}
static bool locked;
static int done;
static void coroutine_fn mutex_fn(void *opaque)
{
CoMutex *m = opaque;
qemu_co_mutex_lock(m);
assert(!locked);
locked = true;
qemu_coroutine_yield();
locked = false;
qemu_co_mutex_unlock(m);
done++;
}
static void coroutine_fn lockable_fn(void *opaque)
{
QemuLockable *x = opaque;
qemu_lockable_lock(x);
assert(!locked);
locked = true;
qemu_coroutine_yield();
locked = false;
qemu_lockable_unlock(x);
done++;
}
static void do_test_co_mutex(CoroutineEntry *entry, void *opaque)
{
Coroutine *c1 = qemu_coroutine_create(entry, opaque);
Coroutine *c2 = qemu_coroutine_create(entry, opaque);
done = 0;
qemu_coroutine_enter(c1);
g_assert(locked);
qemu_coroutine_enter(c2);
/* Unlock queues c2. It is then started automatically when c1 yields or
* terminates.
*/
qemu_coroutine_enter(c1);
g_assert_cmpint(done, ==, 1);
g_assert(locked);
qemu_coroutine_enter(c2);
g_assert_cmpint(done, ==, 2);
g_assert(!locked);
}
static void test_co_mutex(void)
{
CoMutex m;
qemu_co_mutex_init(&m);
do_test_co_mutex(mutex_fn, &m);
}
static void test_co_mutex_lockable(void)
{
CoMutex m;
CoMutex *null_pointer = NULL;
qemu_co_mutex_init(&m);
do_test_co_mutex(lockable_fn, QEMU_MAKE_LOCKABLE(&m));
g_assert(QEMU_MAKE_LOCKABLE(null_pointer) == NULL);
}
/*
* Check that creation, enter, and return work
*/
static void coroutine_fn set_and_exit(void *opaque)
{
bool *done = opaque;
*done = true;
}
static void test_lifecycle(void)
{
Coroutine *coroutine;
bool done = false;
/* Create, enter, and return from coroutine */
coroutine = qemu_coroutine_create(set_and_exit, &done);
qemu_coroutine_enter(coroutine);
g_assert(done); /* expect done to be true (first time) */
/* Repeat to check that no state affects this test */
done = false;
coroutine = qemu_coroutine_create(set_and_exit, &done);
qemu_coroutine_enter(coroutine);
g_assert(done); /* expect done to be true (second time) */
}
#define RECORD_SIZE 10 /* Leave some room for expansion */
struct coroutine_position {
int func;
int state;
};
static struct coroutine_position records[RECORD_SIZE];
static unsigned record_pos;
static void record_push(int func, int state)
{
struct coroutine_position *cp = &records[record_pos++];
g_assert_cmpint(record_pos, <, RECORD_SIZE);
cp->func = func;
cp->state = state;
}
static void coroutine_fn co_order_test(void *opaque)
{
record_push(2, 1);
g_assert(qemu_in_coroutine());
qemu_coroutine_yield();
record_push(2, 2);
g_assert(qemu_in_coroutine());
}
static void do_order_test(void)
{
Coroutine *co;
co = qemu_coroutine_create(co_order_test, NULL);
record_push(1, 1);
qemu_coroutine_enter(co);
record_push(1, 2);
g_assert(!qemu_in_coroutine());
qemu_coroutine_enter(co);
record_push(1, 3);
g_assert(!qemu_in_coroutine());
}
static void test_order(void)
{
int i;
const struct coroutine_position expected_pos[] = {
{1, 1,}, {2, 1}, {1, 2}, {2, 2}, {1, 3}
};
do_order_test();
g_assert_cmpint(record_pos, ==, 5);
for (i = 0; i < record_pos; i++) {
g_assert_cmpint(records[i].func , ==, expected_pos[i].func );
g_assert_cmpint(records[i].state, ==, expected_pos[i].state);
}
}
/*
* Lifecycle benchmark
*/
static void coroutine_fn empty_coroutine(void *opaque)
{
/* Do nothing */
}
static void perf_lifecycle(void)
{
Coroutine *coroutine;
unsigned int i, max;
double duration;
max = 1000000;
g_test_timer_start();
for (i = 0; i < max; i++) {
coroutine = qemu_coroutine_create(empty_coroutine, NULL);
qemu_coroutine_enter(coroutine);
}
duration = g_test_timer_elapsed();
g_test_message("Lifecycle %u iterations: %f s", max, duration);
}
static void perf_nesting(void)
{
unsigned int i, maxcycles, maxnesting;
double duration;
maxcycles = 10000;
maxnesting = 1000;
Coroutine *root;
g_test_timer_start();
for (i = 0; i < maxcycles; i++) {
NestData nd = {
.n_enter = 0,
.n_return = 0,
.max = maxnesting,
};
root = qemu_coroutine_create(nest, &nd);
qemu_coroutine_enter(root);
}
duration = g_test_timer_elapsed();
g_test_message("Nesting %u iterations of %u depth each: %f s",
maxcycles, maxnesting, duration);
}
/*
* Yield benchmark
*/
static void coroutine_fn yield_loop(void *opaque)
{
unsigned int *counter = opaque;
while ((*counter) > 0) {
(*counter)--;
qemu_coroutine_yield();
}
}
static void perf_yield(void)
{
unsigned int i, maxcycles;
double duration;
maxcycles = 100000000;
i = maxcycles;
Coroutine *coroutine = qemu_coroutine_create(yield_loop, &i);
g_test_timer_start();
while (i > 0) {
qemu_coroutine_enter(coroutine);
}
duration = g_test_timer_elapsed();
g_test_message("Yield %u iterations: %f s", maxcycles, duration);
}
static __attribute__((noinline)) void dummy(unsigned *i)
{
(*i)--;
}
static void perf_baseline(void)
{
unsigned int i, maxcycles;
double duration;
maxcycles = 100000000;
i = maxcycles;
g_test_timer_start();
while (i > 0) {
dummy(&i);
}
duration = g_test_timer_elapsed();
g_test_message("Function call %u iterations: %f s", maxcycles, duration);
}
static __attribute__((noinline)) void perf_cost_func(void *opaque)
{
qemu_coroutine_yield();
}
static void perf_cost(void)
{
const unsigned long maxcycles = 40000000;
unsigned long i = 0;
double duration;
unsigned long ops;
Coroutine *co;
g_test_timer_start();
while (i++ < maxcycles) {
co = qemu_coroutine_create(perf_cost_func, &i);
qemu_coroutine_enter(co);
qemu_coroutine_enter(co);
}
duration = g_test_timer_elapsed();
ops = (long)(maxcycles / (duration * 1000));
g_test_message("Run operation %lu iterations %f s, %luK operations/s, "
"%luns per coroutine",
maxcycles,
duration, ops,
(unsigned long)(1000000000.0 * duration / maxcycles));
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
/* This test assumes there is a freelist and marks freed coroutine memory
* with a sentinel value. If there is no freelist this would legitimately
* crash, so skip it.
*/
if (CONFIG_COROUTINE_POOL) {
g_test_add_func("/basic/no-dangling-access", test_no_dangling_access);
}
g_test_add_func("/basic/lifecycle", test_lifecycle);
g_test_add_func("/basic/yield", test_yield);
g_test_add_func("/basic/nesting", test_nesting);
g_test_add_func("/basic/self", test_self);
g_test_add_func("/basic/entered", test_entered);
g_test_add_func("/basic/in_coroutine", test_in_coroutine);
g_test_add_func("/basic/order", test_order);
g_test_add_func("/locking/co-mutex", test_co_mutex);
g_test_add_func("/locking/co-mutex/lockable", test_co_mutex_lockable);
if (g_test_perf()) {
g_test_add_func("/perf/lifecycle", perf_lifecycle);
g_test_add_func("/perf/nesting", perf_nesting);
g_test_add_func("/perf/yield", perf_yield);
g_test_add_func("/perf/function-call", perf_baseline);
g_test_add_func("/perf/cost", perf_cost);
}
return g_test_run();
}

View file

@ -0,0 +1,194 @@
/*
* QEMU Crypto anti-forensic splitter
*
* Copyright (c) 2015-2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "crypto/init.h"
#include "crypto/afsplit.h"
typedef struct QCryptoAFSplitTestData QCryptoAFSplitTestData;
struct QCryptoAFSplitTestData {
const char *path;
QCryptoHashAlgorithm hash;
uint32_t stripes;
size_t blocklen;
const uint8_t *key;
const uint8_t *splitkey;
};
static QCryptoAFSplitTestData test_data[] = {
{
.path = "/crypto/afsplit/sha256/5",
.hash = QCRYPTO_HASH_ALG_SHA256,
.stripes = 5,
.blocklen = 32,
.key = (const uint8_t *)
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7"
"\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
.splitkey = (const uint8_t *)
"\xfd\xd2\x73\xb1\x7d\x99\x93\x34"
"\x70\xde\xfa\x07\xc5\xac\x58\xd2"
"\x30\x67\x2f\x1a\x35\x43\x60\x7d"
"\x77\x02\xdb\x62\x3c\xcb\x2c\x33"
"\x48\x08\xb6\xf1\x7c\xa3\x20\xa0"
"\xad\x2d\x4c\xf3\xcd\x18\x6f\x53"
"\xf9\xe8\xe7\x59\x27\x3c\xa9\x54"
"\x61\x87\xb3\xaf\xf6\xf7\x7e\x64"
"\x86\xaa\x89\x7f\x1f\x9f\xdb\x86"
"\xf4\xa2\x16\xff\xa3\x4f\x8c\xa1"
"\x59\xc4\x23\x34\x28\xc4\x77\x71"
"\x83\xd4\xcd\x8e\x89\x1b\xc7\xc5"
"\xae\x4d\xa9\xcd\xc9\x72\x85\x70"
"\x13\x68\x52\x83\xfc\xb8\x11\x72"
"\xba\x3d\xc6\x4a\x28\xfa\xe2\x86"
"\x7b\x27\xab\x58\xe1\xa4\xca\xf6"
"\x9e\xbc\xfe\x0c\x92\x79\xb3\xec"
"\x1c\x5f\x79\x3b\x0d\x1e\xaa\x1a"
"\x77\x0f\x70\x19\x4b\xc8\x80\xee"
"\x27\x7c\x6e\x4a\x91\x96\x5c\xf4"
},
{
.path = "/crypto/afsplit/sha256/5000",
.hash = QCRYPTO_HASH_ALG_SHA256,
.stripes = 5000,
.blocklen = 16,
.key = (const uint8_t *)
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
},
{
.path = "/crypto/afsplit/sha1/1000",
.hash = QCRYPTO_HASH_ALG_SHA1,
.stripes = 1000,
.blocklen = 32,
.key = (const uint8_t *)
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7"
"\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
},
{
.path = "/crypto/afsplit/sha256/big",
.hash = QCRYPTO_HASH_ALG_SHA256,
.stripes = 1000,
.blocklen = 64,
.key = (const uint8_t *)
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
},
};
static inline char hex(int i)
{
if (i < 10) {
return '0' + i;
}
return 'a' + (i - 10);
}
static char *hex_string(const uint8_t *bytes,
size_t len)
{
char *hexstr = g_new0(char, len * 2 + 1);
size_t i;
for (i = 0; i < len; i++) {
hexstr[i * 2] = hex((bytes[i] >> 4) & 0xf);
hexstr[i * 2 + 1] = hex(bytes[i] & 0xf);
}
hexstr[len * 2] = '\0';
return hexstr;
}
static void test_afsplit(const void *opaque)
{
const QCryptoAFSplitTestData *data = opaque;
size_t splitlen = data->blocklen * data->stripes;
uint8_t *splitkey = g_new0(uint8_t, splitlen);
uint8_t *key = g_new0(uint8_t, data->blocklen);
gchar *expect, *actual;
/* First time we round-trip the key */
qcrypto_afsplit_encode(data->hash,
data->blocklen, data->stripes,
data->key, splitkey,
&error_abort);
qcrypto_afsplit_decode(data->hash,
data->blocklen, data->stripes,
splitkey, key,
&error_abort);
expect = hex_string(data->key, data->blocklen);
actual = hex_string(key, data->blocklen);
g_assert_cmpstr(actual, ==, expect);
g_free(actual);
g_free(expect);
/* Second time we merely try decoding a previous split */
if (data->splitkey) {
memset(key, 0, data->blocklen);
qcrypto_afsplit_decode(data->hash,
data->blocklen, data->stripes,
data->splitkey, key,
&error_abort);
expect = hex_string(data->key, data->blocklen);
actual = hex_string(key, data->blocklen);
g_assert_cmpstr(actual, ==, expect);
g_free(actual);
g_free(expect);
}
g_free(key);
g_free(splitkey);
}
int main(int argc, char **argv)
{
size_t i;
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
if (!qcrypto_hash_supports(test_data[i].hash)) {
continue;
}
g_test_add_data_func(test_data[i].path, &test_data[i], test_afsplit);
}
return g_test_run();
}

View file

@ -0,0 +1,368 @@
/*
* QEMU Crypto block encryption
*
* Copyright (c) 2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "crypto/init.h"
#include "crypto/block.h"
#include "qemu/buffer.h"
#include "qemu/module.h"
#include "crypto/secret.h"
#ifndef _WIN32
#include <sys/resource.h>
#endif
#if (defined(_WIN32) || defined RUSAGE_THREAD) && \
(defined(CONFIG_NETTLE) || defined(CONFIG_GCRYPT))
#define TEST_LUKS
#else
#undef TEST_LUKS
#endif
static QCryptoBlockCreateOptions qcow_create_opts = {
.format = Q_CRYPTO_BLOCK_FORMAT_QCOW,
.u.qcow = {
.has_key_secret = true,
.key_secret = (char *)"sec0",
},
};
static QCryptoBlockOpenOptions qcow_open_opts = {
.format = Q_CRYPTO_BLOCK_FORMAT_QCOW,
.u.qcow = {
.has_key_secret = true,
.key_secret = (char *)"sec0",
},
};
#ifdef TEST_LUKS
static QCryptoBlockOpenOptions luks_open_opts = {
.format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
.u.luks = {
.has_key_secret = true,
.key_secret = (char *)"sec0",
},
};
/* Creation with all default values */
static QCryptoBlockCreateOptions luks_create_opts_default = {
.format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
.u.luks = {
.has_key_secret = true,
.key_secret = (char *)"sec0",
},
};
/* ...and with explicit values */
static QCryptoBlockCreateOptions luks_create_opts_aes256_cbc_plain64 = {
.format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
.u.luks = {
.has_key_secret = true,
.key_secret = (char *)"sec0",
.has_cipher_alg = true,
.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
.has_cipher_mode = true,
.cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
.has_ivgen_alg = true,
.ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
},
};
static QCryptoBlockCreateOptions luks_create_opts_aes256_cbc_essiv = {
.format = Q_CRYPTO_BLOCK_FORMAT_LUKS,
.u.luks = {
.has_key_secret = true,
.key_secret = (char *)"sec0",
.has_cipher_alg = true,
.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
.has_cipher_mode = true,
.cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
.has_ivgen_alg = true,
.ivgen_alg = QCRYPTO_IVGEN_ALG_ESSIV,
.has_ivgen_hash_alg = true,
.ivgen_hash_alg = QCRYPTO_HASH_ALG_SHA256,
.has_hash_alg = true,
.hash_alg = QCRYPTO_HASH_ALG_SHA1,
},
};
#endif /* TEST_LUKS */
static struct QCryptoBlockTestData {
const char *path;
QCryptoBlockCreateOptions *create_opts;
QCryptoBlockOpenOptions *open_opts;
bool expect_header;
QCryptoCipherAlgorithm cipher_alg;
QCryptoCipherMode cipher_mode;
QCryptoHashAlgorithm hash_alg;
QCryptoIVGenAlgorithm ivgen_alg;
QCryptoHashAlgorithm ivgen_hash;
bool slow;
} test_data[] = {
{
.path = "/crypto/block/qcow",
.create_opts = &qcow_create_opts,
.open_opts = &qcow_open_opts,
.expect_header = false,
.cipher_alg = QCRYPTO_CIPHER_ALG_AES_128,
.cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
.ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
},
#ifdef TEST_LUKS
{
.path = "/crypto/block/luks/default",
.create_opts = &luks_create_opts_default,
.open_opts = &luks_open_opts,
.expect_header = true,
.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
.cipher_mode = QCRYPTO_CIPHER_MODE_XTS,
.hash_alg = QCRYPTO_HASH_ALG_SHA256,
.ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
.slow = true,
},
{
.path = "/crypto/block/luks/aes-256-cbc-plain64",
.create_opts = &luks_create_opts_aes256_cbc_plain64,
.open_opts = &luks_open_opts,
.expect_header = true,
.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
.cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
.hash_alg = QCRYPTO_HASH_ALG_SHA256,
.ivgen_alg = QCRYPTO_IVGEN_ALG_PLAIN64,
.slow = true,
},
{
.path = "/crypto/block/luks/aes-256-cbc-essiv",
.create_opts = &luks_create_opts_aes256_cbc_essiv,
.open_opts = &luks_open_opts,
.expect_header = true,
.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256,
.cipher_mode = QCRYPTO_CIPHER_MODE_CBC,
.hash_alg = QCRYPTO_HASH_ALG_SHA1,
.ivgen_alg = QCRYPTO_IVGEN_ALG_ESSIV,
.ivgen_hash = QCRYPTO_HASH_ALG_SHA256,
.slow = true,
},
#endif
};
static ssize_t test_block_read_func(QCryptoBlock *block,
size_t offset,
uint8_t *buf,
size_t buflen,
void *opaque,
Error **errp)
{
Buffer *header = opaque;
g_assert_cmpint(offset + buflen, <=, header->capacity);
memcpy(buf, header->buffer + offset, buflen);
return buflen;
}
static ssize_t test_block_init_func(QCryptoBlock *block,
size_t headerlen,
void *opaque,
Error **errp)
{
Buffer *header = opaque;
g_assert_cmpint(header->capacity, ==, 0);
buffer_reserve(header, headerlen);
return headerlen;
}
static ssize_t test_block_write_func(QCryptoBlock *block,
size_t offset,
const uint8_t *buf,
size_t buflen,
void *opaque,
Error **errp)
{
Buffer *header = opaque;
g_assert_cmpint(buflen + offset, <=, header->capacity);
memcpy(header->buffer + offset, buf, buflen);
header->offset = offset + buflen;
return buflen;
}
static Object *test_block_secret(void)
{
return object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "123456",
NULL);
}
static void test_block_assert_setup(const struct QCryptoBlockTestData *data,
QCryptoBlock *blk)
{
QCryptoIVGen *ivgen;
QCryptoCipher *cipher;
ivgen = qcrypto_block_get_ivgen(blk);
cipher = qcrypto_block_get_cipher(blk);
g_assert(ivgen);
g_assert(cipher);
g_assert_cmpint(data->cipher_alg, ==, cipher->alg);
g_assert_cmpint(data->cipher_mode, ==, cipher->mode);
g_assert_cmpint(data->hash_alg, ==,
qcrypto_block_get_kdf_hash(blk));
g_assert_cmpint(data->ivgen_alg, ==,
qcrypto_ivgen_get_algorithm(ivgen));
g_assert_cmpint(data->ivgen_hash, ==,
qcrypto_ivgen_get_hash(ivgen));
}
static void test_block(gconstpointer opaque)
{
const struct QCryptoBlockTestData *data = opaque;
QCryptoBlock *blk;
Buffer header;
Object *sec = test_block_secret();
memset(&header, 0, sizeof(header));
buffer_init(&header, "header");
blk = qcrypto_block_create(data->create_opts, NULL,
test_block_init_func,
test_block_write_func,
&header,
&error_abort);
g_assert(blk);
if (data->expect_header) {
g_assert_cmpint(header.capacity, >, 0);
} else {
g_assert_cmpint(header.capacity, ==, 0);
}
test_block_assert_setup(data, blk);
qcrypto_block_free(blk);
object_unparent(sec);
/* Ensure we can't open without the secret */
blk = qcrypto_block_open(data->open_opts, NULL,
test_block_read_func,
&header,
0,
1,
NULL);
g_assert(blk == NULL);
/* Ensure we can't open without the secret, unless NO_IO */
blk = qcrypto_block_open(data->open_opts, NULL,
test_block_read_func,
&header,
QCRYPTO_BLOCK_OPEN_NO_IO,
1,
&error_abort);
g_assert(qcrypto_block_get_cipher(blk) == NULL);
g_assert(qcrypto_block_get_ivgen(blk) == NULL);
qcrypto_block_free(blk);
/* Now open for real with secret */
sec = test_block_secret();
blk = qcrypto_block_open(data->open_opts, NULL,
test_block_read_func,
&header,
0,
1,
&error_abort);
g_assert(blk);
test_block_assert_setup(data, blk);
qcrypto_block_free(blk);
object_unparent(sec);
buffer_free(&header);
}
int main(int argc, char **argv)
{
gsize i;
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
if (test_data[i].open_opts->format == Q_CRYPTO_BLOCK_FORMAT_LUKS &&
!qcrypto_hash_supports(test_data[i].hash_alg)) {
continue;
}
if (!test_data[i].slow ||
g_test_slow()) {
g_test_add_data_func(test_data[i].path, &test_data[i], test_block);
}
}
return g_test_run();
}

View file

@ -0,0 +1,801 @@
/*
* QEMU Crypto cipher algorithms
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "crypto/init.h"
#include "crypto/cipher.h"
#include "qapi/error.h"
typedef struct QCryptoCipherTestData QCryptoCipherTestData;
struct QCryptoCipherTestData {
const char *path;
QCryptoCipherAlgorithm alg;
QCryptoCipherMode mode;
const char *key;
const char *plaintext;
const char *ciphertext;
const char *iv;
};
/* AES test data comes from appendix F of:
*
* http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
*/
static QCryptoCipherTestData test_data[] = {
{
/* NIST F.1.1 ECB-AES128.Encrypt */
.path = "/crypto/cipher/aes-ecb-128",
.alg = QCRYPTO_CIPHER_ALG_AES_128,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "2b7e151628aed2a6abf7158809cf4f3c",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"3ad77bb40d7a3660a89ecaf32466ef97"
"f5d3d58503b9699de785895a96fdbaaf"
"43b1cd7f598ece23881b00e3ed030688"
"7b0c785e27e8ad3f8223207104725dd4"
},
{
/* NIST F.1.3 ECB-AES192.Encrypt */
.path = "/crypto/cipher/aes-ecb-192",
.alg = QCRYPTO_CIPHER_ALG_AES_192,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"bd334f1d6e45f25ff712a214571fa5cc"
"974104846d0ad3ad7734ecb3ecee4eef"
"ef7afd2270e2e60adce0ba2face6444e"
"9a4b41ba738d6c72fb16691603c18e0e"
},
{
/* NIST F.1.5 ECB-AES256.Encrypt */
.path = "/crypto/cipher/aes-ecb-256",
.alg = QCRYPTO_CIPHER_ALG_AES_256,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key =
"603deb1015ca71be2b73aef0857d7781"
"1f352c073b6108d72d9810a30914dff4",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"f3eed1bdb5d2a03c064b5a7e3db181f8"
"591ccb10d410ed26dc5ba74a31362870"
"b6ed21b99ca6f4f9f153e7b1beafed1d"
"23304b7a39f9f3ff067d8d8f9e24ecc7",
},
{
/* NIST F.2.1 CBC-AES128.Encrypt */
.path = "/crypto/cipher/aes-cbc-128",
.alg = QCRYPTO_CIPHER_ALG_AES_128,
.mode = QCRYPTO_CIPHER_MODE_CBC,
.key = "2b7e151628aed2a6abf7158809cf4f3c",
.iv = "000102030405060708090a0b0c0d0e0f",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"7649abac8119b246cee98e9b12e9197d"
"5086cb9b507219ee95db113a917678b2"
"73bed6b8e3c1743b7116e69e22229516"
"3ff1caa1681fac09120eca307586e1a7",
},
{
/* NIST F.2.3 CBC-AES128.Encrypt */
.path = "/crypto/cipher/aes-cbc-192",
.alg = QCRYPTO_CIPHER_ALG_AES_192,
.mode = QCRYPTO_CIPHER_MODE_CBC,
.key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
.iv = "000102030405060708090a0b0c0d0e0f",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"4f021db243bc633d7178183a9fa071e8"
"b4d9ada9ad7dedf4e5e738763f69145a"
"571b242012fb7ae07fa9baac3df102e0"
"08b0e27988598881d920a9e64f5615cd",
},
{
/* NIST F.2.5 CBC-AES128.Encrypt */
.path = "/crypto/cipher/aes-cbc-256",
.alg = QCRYPTO_CIPHER_ALG_AES_256,
.mode = QCRYPTO_CIPHER_MODE_CBC,
.key =
"603deb1015ca71be2b73aef0857d7781"
"1f352c073b6108d72d9810a30914dff4",
.iv = "000102030405060708090a0b0c0d0e0f",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"f58c4c04d6e5f1ba779eabfb5f7bfbd6"
"9cfc4e967edb808d679f777bc6702c7d"
"39f23369a9d9bacfa530e26304231461"
"b2eb05e2c39be9fcda6c19078c6a9d1b",
},
{
.path = "/crypto/cipher/des-rfb-ecb-56",
.alg = QCRYPTO_CIPHER_ALG_DES_RFB,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "0123456789abcdef",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"8f346aaf64eaf24040720d80648c52e7"
"aefc616be53ab1a3d301e69d91e01838"
"ffd29f1bb5596ad94ea2d8e6196b7f09"
"30d8ed0bf2773af36dd82a6280c20926",
},
#if defined(CONFIG_NETTLE) || defined(CONFIG_GCRYPT)
{
/* Borrowed from linux-kernel crypto/testmgr.h */
.path = "/crypto/cipher/3des-cbc",
.alg = QCRYPTO_CIPHER_ALG_3DES,
.mode = QCRYPTO_CIPHER_MODE_CBC,
.key =
"e9c0ff2e760b6424444d995a12d640c0"
"eac284e81495dbe8",
.iv =
"7d3388930f93b242",
.plaintext =
"6f54206f614d796e5320636565727374"
"54206f6f4d206e612079655372637465"
"20736f54206f614d796e532063656572"
"737454206f6f4d206e61207965537263"
"746520736f54206f614d796e53206365"
"6572737454206f6f4d206e6120796553"
"7263746520736f54206f614d796e5320"
"63656572737454206f6f4d206e610a79",
.ciphertext =
"0e2db6973c5633f4671721c76e8ad549"
"74b34905c51cd0ed12565c5396b6007d"
"9048fcf58d2939cc8ad5351836234ed7"
"76d1da0c9467bb048bf2036ca8cfb6ea"
"226447aa8f7513bf9fc2c3f0c956c57a"
"71632e897b1e12cae25fafd8a4f8c97a"
"d6f92131624445a6d6bc5ad32d5443cc"
"9ddea570e942458a6bfab19113b0d919",
},
{
/* Borrowed from linux-kernel crypto/testmgr.h */
.path = "/crypto/cipher/3des-ecb",
.alg = QCRYPTO_CIPHER_ALG_3DES,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key =
"0123456789abcdef5555555555555555"
"fedcba9876543210",
.plaintext =
"736f6d6564617461",
.ciphertext =
"18d748e563620572",
},
{
/* Borrowed from linux-kernel crypto/testmgr.h */
.path = "/crypto/cipher/3des-ctr",
.alg = QCRYPTO_CIPHER_ALG_3DES,
.mode = QCRYPTO_CIPHER_MODE_CTR,
.key =
"9cd6f39cb95a67005a67002dceeb2dce"
"ebb45172b451721f",
.iv =
"ffffffffffffffff",
.plaintext =
"05ec77fb42d559208b128669f05bcf56"
"39ad349f66ea7dc448d3ba0db118e34a"
"fe41285c278e11856cf75ec2553ca00b"
"9265e970db4fd6b900b41fe649fd442f"
"533a8d149863ca5dc1a833a70e9178ec"
"77de42d5bc078b12e54cf05b22563980"
"6b9f66c950c4af36ba0d947fe34add41"
"28b31a8e11f843f75e21553c876e9265"
"cc57dba235b900eb72e649d0442fb619"
"8d14ff46ca5d24a8339a6d9178c377de"
"a108bc07ee71e54cd75b22b51c806bf2"
"45c9503baf369960947fc64adda40fb3"
"1aed74f8432a5e218813876ef158cc57"
"3ea2359c67eb72c549d0bb02b619e04b"
"ff46295d248f169a6df45fc3aa3da108"
"937aee71d84cd7be01b51ce74ef2452c"
"503b82159960cb52c6a930a40f9679ed"
"74df432abd048813fa4df15823573e81"
"689c67ce51c5ac37bb02957ce04bd246"
"29b01b8f16f940f45f26aa3d846f937a"
"cd54d8a30abe01e873e74ed1452cb71e"
"8215fc47cb5225a9309b629679c074df"
"a609bd04ef76fa4dd458238a1d8168f3"
"5ace5138ac379e61957cc74bd2a50cb0"
"1be275f9402b5f268910846ff659cd54"
"3fa30a9d64e873da4ed1b803b71ee148"
"fc472e52258c179b62f55cc0ab32a609"
"907bef76d94dd4bf068a1de44ff35a2d"
"5138836a9e61c853c7ae31a50c977ee2"
"75dc402bb2058910fb42f65920543f86"
"699d64cf56daad34b803ea7de148d347",
.ciphertext =
"07c20820721f49ef19cd6f3253052215"
"a2852bdb85d2d8b9dd0d1b45cb6911d4"
"eabeb2455d0caebea0c127ac659f537e"
"afc21bb5b86d360c25c0f86d0b2901da"
"1378dc89121243faf612ef8d87627883"
"e2be41204c6d351bd10c30cfe2de2b03"
"bf4573d4e55995d1b39b276297bdde7f"
"a4d23980aa5023f074883da86a18793b"
"c4966c8d2240926ed6ad2a1fde63c0e7"
"07f72df7b5f3f0cc017c2a9bc210caaa"
"fd2b3fc5f3f6fc9b45db53e45bf3c97b"
"8e52ffc802b8ac9da10039da3d2d0e01"
"097d8d5ebe53b9b08ee7e2966ab278ea"
"de238ba5fa5ce3dabf8e316a55d16ab2"
"b5466fa5f0eeba1f9f98b0664fd03fa9"
"df5f58c4f4ff755c403a097e6e1c97d4"
"cce7e771cf0b150871fa0797cde6ca1d"
"14280ccf99137af1ebfafa9207de1da1"
"d33669fe514d9f2e83374f1f4830ed04"
"4da4ef3aca76f41c418f6337782f86a6"
"ef417ed2af88ab675271c38ef8269372"
"aad60ee70b46b13ab408a9a8a0cf200c"
"52bc8b0556b2bc319b74b92929969a50"
"dc45dc1aeb0c64d4d3057e5955c3f490"
"c2abf89b8adacea1c3f4ad77dd44c8ac"
"a3f1c9d2195cb0caa234c1f76cfdac65"
"32dc48c4f2006b77f17d76acc031632a"
"a53a62c891b10365cb43d106dfc367bc"
"dce0cd35ce4965a0527ba70d07a91bb0"
"407772c2ea0e3a7846b991b6e73d5142"
"fd51b0c62c6313785ceefccfc4700034",
},
#endif
{
/* RFC 2144, Appendix B.1 */
.path = "/crypto/cipher/cast5-128",
.alg = QCRYPTO_CIPHER_ALG_CAST5_128,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "0123456712345678234567893456789A",
.plaintext = "0123456789abcdef",
.ciphertext = "238b4fe5847e44b2",
},
{
/* libgcrypt serpent.c */
.path = "/crypto/cipher/serpent-128",
.alg = QCRYPTO_CIPHER_ALG_SERPENT_128,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "00000000000000000000000000000000",
.plaintext = "d29d576fcea3a3a7ed9099f29273d78e",
.ciphertext = "b2288b968ae8b08648d1ce9606fd992d",
},
{
/* libgcrypt serpent.c */
.path = "/crypto/cipher/serpent-192",
.alg = QCRYPTO_CIPHER_ALG_SERPENT_192,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "00000000000000000000000000000000"
"0000000000000000",
.plaintext = "d29d576fceaba3a7ed9899f2927bd78e",
.ciphertext = "130e353e1037c22405e8faefb2c3c3e9",
},
{
/* libgcrypt serpent.c */
.path = "/crypto/cipher/serpent-256a",
.alg = QCRYPTO_CIPHER_ALG_SERPENT_256,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "00000000000000000000000000000000"
"00000000000000000000000000000000",
.plaintext = "d095576fcea3e3a7ed98d9f29073d78e",
.ciphertext = "b90ee5862de69168f2bdd5125b45472b",
},
{
/* libgcrypt serpent.c */
.path = "/crypto/cipher/serpent-256b",
.alg = QCRYPTO_CIPHER_ALG_SERPENT_256,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "00000000000000000000000000000000"
"00000000000000000000000000000000",
.plaintext = "00000000010000000200000003000000",
.ciphertext = "2061a42782bd52ec691ec383b03ba77c",
},
{
/* Twofish paper "Known Answer Test" */
.path = "/crypto/cipher/twofish-128",
.alg = QCRYPTO_CIPHER_ALG_TWOFISH_128,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "d491db16e7b1c39e86cb086b789f5419",
.plaintext = "019f9809de1711858faac3a3ba20fbc3",
.ciphertext = "6363977de839486297e661c6c9d668eb",
},
{
/* Twofish paper "Known Answer Test", I=3 */
.path = "/crypto/cipher/twofish-192",
.alg = QCRYPTO_CIPHER_ALG_TWOFISH_192,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "88b2b2706b105e36b446bb6d731a1e88"
"efa71f788965bd44",
.plaintext = "39da69d6ba4997d585b6dc073ca341b2",
.ciphertext = "182b02d81497ea45f9daacdc29193a65",
},
{
/* Twofish paper "Known Answer Test", I=4 */
.path = "/crypto/cipher/twofish-256",
.alg = QCRYPTO_CIPHER_ALG_TWOFISH_256,
.mode = QCRYPTO_CIPHER_MODE_ECB,
.key = "d43bb7556ea32e46f2a282b7d45b4e0d"
"57ff739d4dc92c1bd7fc01700cc8216f",
.plaintext = "90afe91bb288544f2c32dc239b2635e6",
.ciphertext = "6cb4561c40bf0a9705931cb6d408e7fa",
},
{
/* #1 32 byte key, 32 byte PTX */
.path = "/crypto/cipher/aes-xts-128-1",
.alg = QCRYPTO_CIPHER_ALG_AES_128,
.mode = QCRYPTO_CIPHER_MODE_XTS,
.key =
"00000000000000000000000000000000"
"00000000000000000000000000000000",
.iv =
"00000000000000000000000000000000",
.plaintext =
"00000000000000000000000000000000"
"00000000000000000000000000000000",
.ciphertext =
"917cf69ebd68b2ec9b9fe9a3eadda692"
"cd43d2f59598ed858c02c2652fbf922e",
},
{
/* #2, 32 byte key, 32 byte PTX */
.path = "/crypto/cipher/aes-xts-128-2",
.alg = QCRYPTO_CIPHER_ALG_AES_128,
.mode = QCRYPTO_CIPHER_MODE_XTS,
.key =
"11111111111111111111111111111111"
"22222222222222222222222222222222",
.iv =
"33333333330000000000000000000000",
.plaintext =
"44444444444444444444444444444444"
"44444444444444444444444444444444",
.ciphertext =
"c454185e6a16936e39334038acef838b"
"fb186fff7480adc4289382ecd6d394f0",
},
{
/* #5 from xts.7, 32 byte key, 32 byte PTX */
.path = "/crypto/cipher/aes-xts-128-3",
.alg = QCRYPTO_CIPHER_ALG_AES_128,
.mode = QCRYPTO_CIPHER_MODE_XTS,
.key =
"fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0"
"bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0",
.iv =
"9a785634120000000000000000000000",
.plaintext =
"44444444444444444444444444444444"
"44444444444444444444444444444444",
.ciphertext =
"b01f86f8edc1863706fa8a4253e34f28"
"af319de38334870f4dd1f94cbe9832f1",
},
{
/* #4, 32 byte key, 512 byte PTX */
.path = "/crypto/cipher/aes-xts-128-4",
.alg = QCRYPTO_CIPHER_ALG_AES_128,
.mode = QCRYPTO_CIPHER_MODE_XTS,
.key =
"27182818284590452353602874713526"
"31415926535897932384626433832795",
.iv =
"00000000000000000000000000000000",
.plaintext =
"000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
"202122232425262728292a2b2c2d2e2f"
"303132333435363738393a3b3c3d3e3f"
"404142434445464748494a4b4c4d4e4f"
"505152535455565758595a5b5c5d5e5f"
"606162636465666768696a6b6c6d6e6f"
"707172737475767778797a7b7c7d7e7f"
"808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9f"
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
"000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
"202122232425262728292a2b2c2d2e2f"
"303132333435363738393a3b3c3d3e3f"
"404142434445464748494a4b4c4d4e4f"
"505152535455565758595a5b5c5d5e5f"
"606162636465666768696a6b6c6d6e6f"
"707172737475767778797a7b7c7d7e7f"
"808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9f"
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
.ciphertext =
"27a7479befa1d476489f308cd4cfa6e2"
"a96e4bbe3208ff25287dd3819616e89c"
"c78cf7f5e543445f8333d8fa7f560000"
"05279fa5d8b5e4ad40e736ddb4d35412"
"328063fd2aab53e5ea1e0a9f332500a5"
"df9487d07a5c92cc512c8866c7e860ce"
"93fdf166a24912b422976146ae20ce84"
"6bb7dc9ba94a767aaef20c0d61ad0265"
"5ea92dc4c4e41a8952c651d33174be51"
"a10c421110e6d81588ede82103a252d8"
"a750e8768defffed9122810aaeb99f91"
"72af82b604dc4b8e51bcb08235a6f434"
"1332e4ca60482a4ba1a03b3e65008fc5"
"da76b70bf1690db4eae29c5f1badd03c"
"5ccf2a55d705ddcd86d449511ceb7ec3"
"0bf12b1fa35b913f9f747a8afd1b130e"
"94bff94effd01a91735ca1726acd0b19"
"7c4e5b03393697e126826fb6bbde8ecc"
"1e08298516e2c9ed03ff3c1b7860f6de"
"76d4cecd94c8119855ef5297ca67e9f3"
"e7ff72b1e99785ca0a7e7720c5b36dc6"
"d72cac9574c8cbbc2f801e23e56fd344"
"b07f22154beba0f08ce8891e643ed995"
"c94d9a69c9f1b5f499027a78572aeebd"
"74d20cc39881c213ee770b1010e4bea7"
"18846977ae119f7a023ab58cca0ad752"
"afe656bb3c17256a9f6e9bf19fdd5a38"
"fc82bbe872c5539edb609ef4f79c203e"
"bb140f2e583cb2ad15b4aa5b655016a8"
"449277dbd477ef2c8d6c017db738b18d"
"eb4a427d1923ce3ff262735779a418f2"
"0a282df920147beabe421ee5319d0568",
},
{
/* Bad config - cast5-128 has 8 byte block size
* which is incompatible with XTS
*/
.path = "/crypto/cipher/cast5-xts-128",
.alg = QCRYPTO_CIPHER_ALG_CAST5_128,
.mode = QCRYPTO_CIPHER_MODE_XTS,
.key =
"27182818284590452353602874713526"
"31415926535897932384626433832795",
},
{
/* NIST F.5.1 CTR-AES128.Encrypt */
.path = "/crypto/cipher/aes-ctr-128",
.alg = QCRYPTO_CIPHER_ALG_AES_128,
.mode = QCRYPTO_CIPHER_MODE_CTR,
.key = "2b7e151628aed2a6abf7158809cf4f3c",
.iv = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"874d6191b620e3261bef6864990db6ce"
"9806f66b7970fdff8617187bb9fffdff"
"5ae4df3edbd5d35e5b4f09020db03eab"
"1e031dda2fbe03d1792170a0f3009cee",
},
{
/* NIST F.5.3 CTR-AES192.Encrypt */
.path = "/crypto/cipher/aes-ctr-192",
.alg = QCRYPTO_CIPHER_ALG_AES_192,
.mode = QCRYPTO_CIPHER_MODE_CTR,
.key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
.iv = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"1abc932417521ca24f2b0459fe7e6e0b"
"090339ec0aa6faefd5ccc2c6f4ce8e94"
"1e36b26bd1ebc670d1bd1d665620abf7"
"4f78a7f6d29809585a97daec58c6b050",
},
{
/* NIST F.5.5 CTR-AES256.Encrypt */
.path = "/crypto/cipher/aes-ctr-256",
.alg = QCRYPTO_CIPHER_ALG_AES_256,
.mode = QCRYPTO_CIPHER_MODE_CTR,
.key = "603deb1015ca71be2b73aef0857d7781"
"1f352c073b6108d72d9810a30914dff4",
.iv = "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
.plaintext =
"6bc1bee22e409f96e93d7e117393172a"
"ae2d8a571e03ac9c9eb76fac45af8e51"
"30c81c46a35ce411e5fbc1191a0a52ef"
"f69f2445df4f9b17ad2b417be66c3710",
.ciphertext =
"601ec313775789a5b7a7f504bbf3d228"
"f443e3ca4d62b59aca84e990cacaf5c5"
"2b0930daa23de94ce87017ba2d84988d"
"dfc9c58db67aada613c2dd08457941a6",
}
};
static inline int unhex(char c)
{
if (c >= 'a' && c <= 'f') {
return 10 + (c - 'a');
}
if (c >= 'A' && c <= 'F') {
return 10 + (c - 'A');
}
return c - '0';
}
static inline char hex(int i)
{
if (i < 10) {
return '0' + i;
}
return 'a' + (i - 10);
}
static size_t unhex_string(const char *hexstr,
uint8_t **data)
{
size_t len;
size_t i;
if (!hexstr) {
*data = NULL;
return 0;
}
len = strlen(hexstr);
*data = g_new0(uint8_t, len / 2);
for (i = 0; i < len; i += 2) {
(*data)[i/2] = (unhex(hexstr[i]) << 4) | unhex(hexstr[i+1]);
}
return len / 2;
}
static char *hex_string(const uint8_t *bytes,
size_t len)
{
char *hexstr = g_new0(char, len * 2 + 1);
size_t i;
for (i = 0; i < len; i++) {
hexstr[i*2] = hex((bytes[i] >> 4) & 0xf);
hexstr[i*2+1] = hex(bytes[i] & 0xf);
}
hexstr[len*2] = '\0';
return hexstr;
}
static void test_cipher(const void *opaque)
{
const QCryptoCipherTestData *data = opaque;
QCryptoCipher *cipher;
uint8_t *key, *iv = NULL, *ciphertext = NULL,
*plaintext = NULL, *outtext = NULL;
size_t nkey, niv = 0, nciphertext = 0, nplaintext = 0;
char *outtexthex = NULL;
size_t ivsize, keysize, blocksize;
Error *err = NULL;
nkey = unhex_string(data->key, &key);
if (data->iv) {
niv = unhex_string(data->iv, &iv);
}
if (data->ciphertext) {
nciphertext = unhex_string(data->ciphertext, &ciphertext);
}
if (data->plaintext) {
nplaintext = unhex_string(data->plaintext, &plaintext);
}
g_assert(nciphertext == nplaintext);
outtext = g_new0(uint8_t, nciphertext);
cipher = qcrypto_cipher_new(
data->alg, data->mode,
key, nkey,
&err);
if (data->plaintext) {
g_assert(err == NULL);
g_assert(cipher != NULL);
} else {
error_free_or_abort(&err);
g_assert(cipher == NULL);
goto cleanup;
}
keysize = qcrypto_cipher_get_key_len(data->alg);
blocksize = qcrypto_cipher_get_block_len(data->alg);
ivsize = qcrypto_cipher_get_iv_len(data->alg, data->mode);
if (data->mode == QCRYPTO_CIPHER_MODE_XTS) {
g_assert_cmpint(keysize * 2, ==, nkey);
} else {
g_assert_cmpint(keysize, ==, nkey);
}
g_assert_cmpint(ivsize, ==, niv);
if (niv) {
g_assert_cmpint(blocksize, ==, niv);
}
if (iv) {
g_assert(qcrypto_cipher_setiv(cipher,
iv, niv,
&error_abort) == 0);
}
g_assert(qcrypto_cipher_encrypt(cipher,
plaintext,
outtext,
nplaintext,
&error_abort) == 0);
outtexthex = hex_string(outtext, nciphertext);
g_assert_cmpstr(outtexthex, ==, data->ciphertext);
g_free(outtexthex);
if (iv) {
g_assert(qcrypto_cipher_setiv(cipher,
iv, niv,
&error_abort) == 0);
}
g_assert(qcrypto_cipher_decrypt(cipher,
ciphertext,
outtext,
nplaintext,
&error_abort) == 0);
outtexthex = hex_string(outtext, nplaintext);
g_assert_cmpstr(outtexthex, ==, data->plaintext);
cleanup:
g_free(outtext);
g_free(outtexthex);
g_free(key);
g_free(iv);
g_free(ciphertext);
g_free(plaintext);
qcrypto_cipher_free(cipher);
}
static void test_cipher_null_iv(void)
{
QCryptoCipher *cipher;
uint8_t key[32] = { 0 };
uint8_t plaintext[32] = { 0 };
uint8_t ciphertext[32] = { 0 };
cipher = qcrypto_cipher_new(
QCRYPTO_CIPHER_ALG_AES_256,
QCRYPTO_CIPHER_MODE_CBC,
key, sizeof(key),
&error_abort);
g_assert(cipher != NULL);
/* Don't call qcrypto_cipher_setiv */
qcrypto_cipher_encrypt(cipher,
plaintext,
ciphertext,
sizeof(plaintext),
&error_abort);
qcrypto_cipher_free(cipher);
}
static void test_cipher_short_plaintext(void)
{
Error *err = NULL;
QCryptoCipher *cipher;
uint8_t key[32] = { 0 };
uint8_t plaintext1[20] = { 0 };
uint8_t ciphertext1[20] = { 0 };
uint8_t plaintext2[40] = { 0 };
uint8_t ciphertext2[40] = { 0 };
int ret;
cipher = qcrypto_cipher_new(
QCRYPTO_CIPHER_ALG_AES_256,
QCRYPTO_CIPHER_MODE_CBC,
key, sizeof(key),
&error_abort);
g_assert(cipher != NULL);
/* Should report an error as plaintext is shorter
* than block size
*/
ret = qcrypto_cipher_encrypt(cipher,
plaintext1,
ciphertext1,
sizeof(plaintext1),
&err);
g_assert(ret == -1);
error_free_or_abort(&err);
/* Should report an error as plaintext is larger than
* block size, but not a multiple of block size
*/
ret = qcrypto_cipher_encrypt(cipher,
plaintext2,
ciphertext2,
sizeof(plaintext2),
&err);
g_assert(ret == -1);
error_free_or_abort(&err);
qcrypto_cipher_free(cipher);
}
int main(int argc, char **argv)
{
size_t i;
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
if (qcrypto_cipher_supports(test_data[i].alg, test_data[i].mode)) {
g_test_add_data_func(test_data[i].path, &test_data[i], test_cipher);
}
}
g_test_add_func("/crypto/cipher/null-iv",
test_cipher_null_iv);
g_test_add_func("/crypto/cipher/short-plaintext",
test_cipher_short_plaintext);
return g_test_run();
}

View file

@ -0,0 +1,255 @@
/*
* QEMU Crypto hash algorithms
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "crypto/init.h"
#include "crypto/hash.h"
#define INPUT_TEXT "Hiss hisss Hissss hiss Hiss hisss Hiss hiss"
#define INPUT_TEXT1 "Hiss hisss "
#define INPUT_TEXT2 "Hissss hiss "
#define INPUT_TEXT3 "Hiss hisss Hiss hiss"
#define OUTPUT_MD5 "628d206371563035ab8ef62f492bdec9"
#define OUTPUT_SHA1 "b2e74f26758a3a421e509cee045244b78753cc02"
#define OUTPUT_SHA224 "e2f7415aad33ef79f6516b0986d7175f" \
"9ca3389a85bf6cfed078737b"
#define OUTPUT_SHA256 "bc757abb0436586f392b437e5dd24096" \
"f7f224de6b74d4d86e2abc6121b160d0"
#define OUTPUT_SHA384 "887ce52efb4f46700376356583b7e279" \
"4f612bd024e4495087ddb946c448c69d" \
"56dbf7152a94a5e63a80f3ba9f0eed78"
#define OUTPUT_SHA512 "3a90d79638235ec6c4c11bebd84d83c0" \
"549bc1e84edc4b6ec7086487641256cb" \
"63b54e4cb2d2032b393994aa263c0dbb" \
"e00a9f2fe9ef6037352232a1eec55ee7"
#define OUTPUT_RIPEMD160 "f3d658fad3fdfb2b52c9369cf0d441249ddfa8a0"
#define OUTPUT_MD5_B64 "Yo0gY3FWMDWrjvYvSSveyQ=="
#define OUTPUT_SHA1_B64 "sudPJnWKOkIeUJzuBFJEt4dTzAI="
#define OUTPUT_SHA224_B64 "4vdBWq0z73n2UWsJhtcXX5yjOJqFv2z+0Hhzew=="
#define OUTPUT_SHA256_B64 "vHV6uwQ2WG85K0N+XdJAlvfyJN5rdNTYbiq8YSGxYNA="
#define OUTPUT_SHA384_B64 "iHzlLvtPRnADdjVlg7fieU9hK9Ak5ElQh925RsRI" \
"xp1W2/cVKpSl5jqA87qfDu14"
#define OUTPUT_SHA512_B64 "OpDXljgjXsbEwRvr2E2DwFSbwehO3Etuxwhkh2QS" \
"VstjtU5MstIDKzk5lKomPA274AqfL+nvYDc1IjKh" \
"7sVe5w=="
#define OUTPUT_RIPEMD160_B64 "89ZY+tP9+ytSyTac8NRBJJ3fqKA="
static const char *expected_outputs[] = {
[QCRYPTO_HASH_ALG_MD5] = OUTPUT_MD5,
[QCRYPTO_HASH_ALG_SHA1] = OUTPUT_SHA1,
[QCRYPTO_HASH_ALG_SHA224] = OUTPUT_SHA224,
[QCRYPTO_HASH_ALG_SHA256] = OUTPUT_SHA256,
[QCRYPTO_HASH_ALG_SHA384] = OUTPUT_SHA384,
[QCRYPTO_HASH_ALG_SHA512] = OUTPUT_SHA512,
[QCRYPTO_HASH_ALG_RIPEMD160] = OUTPUT_RIPEMD160,
};
static const char *expected_outputs_b64[] = {
[QCRYPTO_HASH_ALG_MD5] = OUTPUT_MD5_B64,
[QCRYPTO_HASH_ALG_SHA1] = OUTPUT_SHA1_B64,
[QCRYPTO_HASH_ALG_SHA224] = OUTPUT_SHA224_B64,
[QCRYPTO_HASH_ALG_SHA256] = OUTPUT_SHA256_B64,
[QCRYPTO_HASH_ALG_SHA384] = OUTPUT_SHA384_B64,
[QCRYPTO_HASH_ALG_SHA512] = OUTPUT_SHA512_B64,
[QCRYPTO_HASH_ALG_RIPEMD160] = OUTPUT_RIPEMD160_B64,
};
static const int expected_lens[] = {
[QCRYPTO_HASH_ALG_MD5] = 16,
[QCRYPTO_HASH_ALG_SHA1] = 20,
[QCRYPTO_HASH_ALG_SHA224] = 28,
[QCRYPTO_HASH_ALG_SHA256] = 32,
[QCRYPTO_HASH_ALG_SHA384] = 48,
[QCRYPTO_HASH_ALG_SHA512] = 64,
[QCRYPTO_HASH_ALG_RIPEMD160] = 20,
};
static const char hex[] = "0123456789abcdef";
/* Test with dynamic allocation */
static void test_hash_alloc(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
uint8_t *result = NULL;
size_t resultlen = 0;
int ret;
size_t j;
if (!qcrypto_hash_supports(i)) {
continue;
}
ret = qcrypto_hash_bytes(i,
INPUT_TEXT,
strlen(INPUT_TEXT),
&result,
&resultlen,
NULL);
g_assert(ret == 0);
g_assert(resultlen == expected_lens[i]);
for (j = 0; j < resultlen; j++) {
g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]);
g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]);
}
g_free(result);
}
}
/* Test with caller preallocating */
static void test_hash_prealloc(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
uint8_t *result;
size_t resultlen;
int ret;
size_t j;
if (!qcrypto_hash_supports(i)) {
continue;
}
resultlen = expected_lens[i];
result = g_new0(uint8_t, resultlen);
ret = qcrypto_hash_bytes(i,
INPUT_TEXT,
strlen(INPUT_TEXT),
&result,
&resultlen,
NULL);
g_assert(ret == 0);
g_assert(resultlen == expected_lens[i]);
for (j = 0; j < resultlen; j++) {
g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]);
g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]);
}
g_free(result);
}
}
/* Test with dynamic allocation */
static void test_hash_iov(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
struct iovec iov[3] = {
{ .iov_base = (char *)INPUT_TEXT1, .iov_len = strlen(INPUT_TEXT1) },
{ .iov_base = (char *)INPUT_TEXT2, .iov_len = strlen(INPUT_TEXT2) },
{ .iov_base = (char *)INPUT_TEXT3, .iov_len = strlen(INPUT_TEXT3) },
};
uint8_t *result = NULL;
size_t resultlen = 0;
int ret;
size_t j;
if (!qcrypto_hash_supports(i)) {
continue;
}
ret = qcrypto_hash_bytesv(i,
iov, 3,
&result,
&resultlen,
NULL);
g_assert(ret == 0);
g_assert(resultlen == expected_lens[i]);
for (j = 0; j < resultlen; j++) {
g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]);
g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]);
}
g_free(result);
}
}
/* Test with printable hashing */
static void test_hash_digest(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
int ret;
char *digest;
size_t digestsize;
if (!qcrypto_hash_supports(i)) {
continue;
}
digestsize = qcrypto_hash_digest_len(i);
g_assert_cmpint(digestsize * 2, ==, strlen(expected_outputs[i]));
ret = qcrypto_hash_digest(i,
INPUT_TEXT,
strlen(INPUT_TEXT),
&digest,
NULL);
g_assert(ret == 0);
g_assert_cmpstr(digest, ==, expected_outputs[i]);
g_free(digest);
}
}
/* Test with base64 encoding */
static void test_hash_base64(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
int ret;
char *digest;
if (!qcrypto_hash_supports(i)) {
continue;
}
ret = qcrypto_hash_base64(i,
INPUT_TEXT,
strlen(INPUT_TEXT),
&digest,
NULL);
g_assert(ret == 0);
g_assert_cmpstr(digest, ==, expected_outputs_b64[i]);
g_free(digest);
}
}
int main(int argc, char **argv)
{
g_assert(qcrypto_init(NULL) == 0);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/crypto/hash/iov", test_hash_iov);
g_test_add_func("/crypto/hash/alloc", test_hash_alloc);
g_test_add_func("/crypto/hash/prealloc", test_hash_prealloc);
g_test_add_func("/crypto/hash/digest", test_hash_digest);
g_test_add_func("/crypto/hash/base64", test_hash_base64);
return g_test_run();
}

View file

@ -0,0 +1,266 @@
/*
* QEMU Crypto hmac algorithms tests
*
* Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
*
* Authors:
* Longpeng(Mike) <longpeng2@huawei.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or
* (at your option) any later version. See the COPYING file in the
* top-level directory.
*
*/
#include "qemu/osdep.h"
#include "crypto/init.h"
#include "crypto/hmac.h"
#define INPUT_TEXT1 "ABCDEFGHIJKLMNOPQRSTUVWXY"
#define INPUT_TEXT2 "Zabcdefghijklmnopqrstuvwx"
#define INPUT_TEXT3 "yz0123456789"
#define INPUT_TEXT INPUT_TEXT1 \
INPUT_TEXT2 \
INPUT_TEXT3
#define KEY "monkey monkey monkey monkey"
typedef struct QCryptoHmacTestData QCryptoHmacTestData;
struct QCryptoHmacTestData {
QCryptoHashAlgorithm alg;
const char *hex_digest;
};
static QCryptoHmacTestData test_data[] = {
{
.alg = QCRYPTO_HASH_ALG_MD5,
.hex_digest =
"ede9cb83679ba82d88fbeae865b3f8fc",
},
{
.alg = QCRYPTO_HASH_ALG_SHA1,
.hex_digest =
"c7b5a631e3aac975c4ededfcd346e469"
"dbc5f2d1",
},
{
.alg = QCRYPTO_HASH_ALG_SHA224,
.hex_digest =
"5f768179dbb29ca722875d0f461a2e2f"
"597d0210340a84df1a8e9c63",
},
{
.alg = QCRYPTO_HASH_ALG_SHA256,
.hex_digest =
"3798f363c57afa6edaffe39016ca7bad"
"efd1e670afb0e3987194307dec3197db",
},
{
.alg = QCRYPTO_HASH_ALG_SHA384,
.hex_digest =
"d218680a6032d33dccd9882d6a6a7164"
"64f26623be257a9b2919b185294f4a49"
"9e54b190bfd6bc5cedd2cd05c7e65e82",
},
{
.alg = QCRYPTO_HASH_ALG_SHA512,
.hex_digest =
"835a4f5b3750b4c1fccfa88da2f746a4"
"900160c9f18964309bb736c13b59491b"
"8e32d37b724cc5aebb0f554c6338a3b5"
"94c4ba26862b2dadb59b7ede1d08d53e",
},
{
.alg = QCRYPTO_HASH_ALG_RIPEMD160,
.hex_digest =
"94964ed4c1155b62b668c241d67279e5"
"8a711676",
},
};
static const char hex[] = "0123456789abcdef";
static void test_hmac_alloc(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
QCryptoHmacTestData *data = &test_data[i];
QCryptoHmac *hmac = NULL;
uint8_t *result = NULL;
size_t resultlen = 0;
Error *err = NULL;
const char *exp_output = NULL;
int ret;
size_t j;
if (!qcrypto_hmac_supports(data->alg)) {
return;
}
exp_output = data->hex_digest;
hmac = qcrypto_hmac_new(data->alg, (const uint8_t *)KEY,
strlen(KEY), &err);
g_assert(err == NULL);
g_assert(hmac != NULL);
ret = qcrypto_hmac_bytes(hmac, (const char *)INPUT_TEXT,
strlen(INPUT_TEXT), &result,
&resultlen, &err);
g_assert(err == NULL);
g_assert(ret == 0);
for (j = 0; j < resultlen; j++) {
g_assert(exp_output[j * 2] == hex[(result[j] >> 4) & 0xf]);
g_assert(exp_output[j * 2 + 1] == hex[result[j] & 0xf]);
}
qcrypto_hmac_free(hmac);
g_free(result);
}
}
static void test_hmac_prealloc(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
QCryptoHmacTestData *data = &test_data[i];
QCryptoHmac *hmac = NULL;
uint8_t *result = NULL;
size_t resultlen = 0;
Error *err = NULL;
const char *exp_output = NULL;
int ret;
size_t j;
if (!qcrypto_hmac_supports(data->alg)) {
return;
}
exp_output = data->hex_digest;
resultlen = strlen(exp_output) / 2;
result = g_new0(uint8_t, resultlen);
hmac = qcrypto_hmac_new(data->alg, (const uint8_t *)KEY,
strlen(KEY), &err);
g_assert(err == NULL);
g_assert(hmac != NULL);
ret = qcrypto_hmac_bytes(hmac, (const char *)INPUT_TEXT,
strlen(INPUT_TEXT), &result,
&resultlen, &err);
g_assert(err == NULL);
g_assert(ret == 0);
exp_output = data->hex_digest;
for (j = 0; j < resultlen; j++) {
g_assert(exp_output[j * 2] == hex[(result[j] >> 4) & 0xf]);
g_assert(exp_output[j * 2 + 1] == hex[result[j] & 0xf]);
}
qcrypto_hmac_free(hmac);
g_free(result);
}
}
static void test_hmac_iov(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
QCryptoHmacTestData *data = &test_data[i];
QCryptoHmac *hmac = NULL;
uint8_t *result = NULL;
size_t resultlen = 0;
Error *err = NULL;
const char *exp_output = NULL;
int ret;
size_t j;
struct iovec iov[3] = {
{ .iov_base = (char *)INPUT_TEXT1, .iov_len = strlen(INPUT_TEXT1) },
{ .iov_base = (char *)INPUT_TEXT2, .iov_len = strlen(INPUT_TEXT2) },
{ .iov_base = (char *)INPUT_TEXT3, .iov_len = strlen(INPUT_TEXT3) },
};
if (!qcrypto_hmac_supports(data->alg)) {
return;
}
exp_output = data->hex_digest;
hmac = qcrypto_hmac_new(data->alg, (const uint8_t *)KEY,
strlen(KEY), &err);
g_assert(err == NULL);
g_assert(hmac != NULL);
ret = qcrypto_hmac_bytesv(hmac, iov, 3, &result,
&resultlen, &err);
g_assert(err == NULL);
g_assert(ret == 0);
for (j = 0; j < resultlen; j++) {
g_assert(exp_output[j * 2] == hex[(result[j] >> 4) & 0xf]);
g_assert(exp_output[j * 2 + 1] == hex[result[j] & 0xf]);
}
qcrypto_hmac_free(hmac);
g_free(result);
}
}
static void test_hmac_digest(void)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
QCryptoHmacTestData *data = &test_data[i];
QCryptoHmac *hmac = NULL;
uint8_t *result = NULL;
Error *err = NULL;
const char *exp_output = NULL;
int ret;
if (!qcrypto_hmac_supports(data->alg)) {
return;
}
exp_output = data->hex_digest;
hmac = qcrypto_hmac_new(data->alg, (const uint8_t *)KEY,
strlen(KEY), &err);
g_assert(err == NULL);
g_assert(hmac != NULL);
ret = qcrypto_hmac_digest(hmac, (const char *)INPUT_TEXT,
strlen(INPUT_TEXT), (char **)&result,
&err);
g_assert(err == NULL);
g_assert(ret == 0);
g_assert_cmpstr((const char *)result, ==, exp_output);
qcrypto_hmac_free(hmac);
g_free(result);
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
g_test_add_func("/crypto/hmac/iov", test_hmac_iov);
g_test_add_func("/crypto/hmac/alloc", test_hmac_alloc);
g_test_add_func("/crypto/hmac/prealloc", test_hmac_prealloc);
g_test_add_func("/crypto/hmac/digest", test_hmac_digest);
return g_test_run();
}

View file

@ -0,0 +1,174 @@
/*
* QEMU Crypto IV generator algorithms
*
* Copyright (c) 2015-2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "crypto/ivgen.h"
struct QCryptoIVGenTestData {
const char *path;
uint64_t sector;
QCryptoIVGenAlgorithm ivalg;
QCryptoHashAlgorithm hashalg;
QCryptoCipherAlgorithm cipheralg;
const uint8_t *key;
size_t nkey;
const uint8_t *iv;
size_t niv;
} test_data[] = {
/* Small */
{
"/crypto/ivgen/plain/1",
.sector = 0x1,
.ivalg = QCRYPTO_IVGEN_ALG_PLAIN,
.iv = (const uint8_t *)"\x01\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00",
.niv = 16,
},
/* Big ! */
{
"/crypto/ivgen/plain/1f2e3d4c",
.sector = 0x1f2e3d4cULL,
.ivalg = QCRYPTO_IVGEN_ALG_PLAIN,
.iv = (const uint8_t *)"\x4c\x3d\x2e\x1f\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00",
.niv = 16,
},
/* Truncation */
{
"/crypto/ivgen/plain/1f2e3d4c5b6a7988",
.sector = 0x1f2e3d4c5b6a7988ULL,
.ivalg = QCRYPTO_IVGEN_ALG_PLAIN,
.iv = (const uint8_t *)"\x88\x79\x6a\x5b\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00",
.niv = 16,
},
/* Small */
{
"/crypto/ivgen/plain64/1",
.sector = 0x1,
.ivalg = QCRYPTO_IVGEN_ALG_PLAIN64,
.iv = (const uint8_t *)"\x01\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00",
.niv = 16,
},
/* Big ! */
{
"/crypto/ivgen/plain64/1f2e3d4c",
.sector = 0x1f2e3d4cULL,
.ivalg = QCRYPTO_IVGEN_ALG_PLAIN64,
.iv = (const uint8_t *)"\x4c\x3d\x2e\x1f\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00",
.niv = 16,
},
/* No Truncation */
{
"/crypto/ivgen/plain64/1f2e3d4c5b6a7988",
.sector = 0x1f2e3d4c5b6a7988ULL,
.ivalg = QCRYPTO_IVGEN_ALG_PLAIN64,
.iv = (const uint8_t *)"\x88\x79\x6a\x5b\x4c\x3d\x2e\x1f"
"\x00\x00\x00\x00\x00\x00\x00\x00",
.niv = 16,
},
/* Small */
{
"/crypto/ivgen/essiv/1",
.sector = 0x1,
.ivalg = QCRYPTO_IVGEN_ALG_ESSIV,
.cipheralg = QCRYPTO_CIPHER_ALG_AES_128,
.hashalg = QCRYPTO_HASH_ALG_SHA256,
.key = (const uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
.nkey = 16,
.iv = (const uint8_t *)"\xd4\x83\x71\xb2\xa1\x94\x53\x88"
"\x1c\x7a\x2d\06\x2d\x0b\x65\x46",
.niv = 16,
},
/* Big ! */
{
"/crypto/ivgen/essiv/1f2e3d4c",
.sector = 0x1f2e3d4cULL,
.ivalg = QCRYPTO_IVGEN_ALG_ESSIV,
.cipheralg = QCRYPTO_CIPHER_ALG_AES_128,
.hashalg = QCRYPTO_HASH_ALG_SHA256,
.key = (const uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
.nkey = 16,
.iv = (const uint8_t *)"\x5d\x36\x09\x5d\xc6\x9e\x5e\xe9"
"\xe3\x02\x8d\xd8\x7a\x3d\xe7\x8f",
.niv = 16,
},
/* No Truncation */
{
"/crypto/ivgen/essiv/1f2e3d4c5b6a7988",
.sector = 0x1f2e3d4c5b6a7988ULL,
.ivalg = QCRYPTO_IVGEN_ALG_ESSIV,
.cipheralg = QCRYPTO_CIPHER_ALG_AES_128,
.hashalg = QCRYPTO_HASH_ALG_SHA256,
.key = (const uint8_t *)"\x00\x01\x02\x03\x04\x05\x06\x07"
"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
.nkey = 16,
.iv = (const uint8_t *)"\x58\xbb\x81\x94\x51\x83\x23\x23"
"\x7a\x08\x93\xa9\xdc\xd2\xd9\xab",
.niv = 16,
},
};
static void test_ivgen(const void *opaque)
{
const struct QCryptoIVGenTestData *data = opaque;
uint8_t *iv = g_new0(uint8_t, data->niv);
QCryptoIVGen *ivgen = qcrypto_ivgen_new(
data->ivalg,
data->cipheralg,
data->hashalg,
data->key,
data->nkey,
&error_abort);
qcrypto_ivgen_calculate(ivgen,
data->sector,
iv,
data->niv,
&error_abort);
g_assert(memcmp(iv, data->iv, data->niv) == 0);
qcrypto_ivgen_free(ivgen);
g_free(iv);
}
int main(int argc, char **argv)
{
size_t i;
g_test_init(&argc, &argv, NULL);
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
if (test_data[i].ivalg == QCRYPTO_IVGEN_ALG_ESSIV &&
!qcrypto_hash_supports(test_data[i].hashalg)) {
continue;
}
g_test_add_data_func(test_data[i].path,
&(test_data[i]),
test_ivgen);
}
return g_test_run();
}

View file

@ -0,0 +1,446 @@
/*
* QEMU Crypto cipher algorithms
*
* Copyright (c) 2015-2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "crypto/init.h"
#ifndef _WIN32
#include <sys/resource.h>
#endif
#if ((defined(CONFIG_NETTLE) || defined(CONFIG_GCRYPT)) && \
(defined(_WIN32) || defined(RUSAGE_THREAD)))
#include "crypto/pbkdf.h"
typedef struct QCryptoPbkdfTestData QCryptoPbkdfTestData;
struct QCryptoPbkdfTestData {
const char *path;
QCryptoHashAlgorithm hash;
unsigned int iterations;
const char *key;
size_t nkey;
const char *salt;
size_t nsalt;
const char *out;
size_t nout;
bool slow;
};
/* This test data comes from cryptsetup package
*
* $SRC/lib/crypto_backend/pbkdf2_generic.c
*
* under LGPLv2.1+ license
*/
static QCryptoPbkdfTestData test_data[] = {
/* RFC 3962 test data */
{
.path = "/crypto/pbkdf/rfc3962/sha1/iter1",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 1,
.key = "password",
.nkey = 8,
.salt = "ATHENA.MIT.EDUraeburn",
.nsalt = 21,
.out = "\xcd\xed\xb5\x28\x1b\xb2\xf8\x01"
"\x56\x5a\x11\x22\xb2\x56\x35\x15"
"\x0a\xd1\xf7\xa0\x4b\xb9\xf3\xa3"
"\x33\xec\xc0\xe2\xe1\xf7\x08\x37",
.nout = 32
},
{
.path = "/crypto/pbkdf/rfc3962/sha1/iter2",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 2,
.key = "password",
.nkey = 8,
.salt = "ATHENA.MIT.EDUraeburn",
.nsalt = 21,
.out = "\x01\xdb\xee\x7f\x4a\x9e\x24\x3e"
"\x98\x8b\x62\xc7\x3c\xda\x93\x5d"
"\xa0\x53\x78\xb9\x32\x44\xec\x8f"
"\x48\xa9\x9e\x61\xad\x79\x9d\x86",
.nout = 32
},
{
.path = "/crypto/pbkdf/rfc3962/sha1/iter1200a",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 1200,
.key = "password",
.nkey = 8,
.salt = "ATHENA.MIT.EDUraeburn",
.nsalt = 21,
.out = "\x5c\x08\xeb\x61\xfd\xf7\x1e\x4e"
"\x4e\xc3\xcf\x6b\xa1\xf5\x51\x2b"
"\xa7\xe5\x2d\xdb\xc5\xe5\x14\x2f"
"\x70\x8a\x31\xe2\xe6\x2b\x1e\x13",
.nout = 32
},
{
.path = "/crypto/pbkdf/rfc3962/sha1/iter5",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 5,
.key = "password",
.nkey = 8,
.salt = "\0224VxxV4\022", /* "\x1234567878563412 */
.nsalt = 8,
.out = "\xd1\xda\xa7\x86\x15\xf2\x87\xe6"
"\xa1\xc8\xb1\x20\xd7\x06\x2a\x49"
"\x3f\x98\xd2\x03\xe6\xbe\x49\xa6"
"\xad\xf4\xfa\x57\x4b\x6e\x64\xee",
.nout = 32
},
{
.path = "/crypto/pbkdf/rfc3962/sha1/iter1200b",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 64,
.salt = "pass phrase equals block size",
.nsalt = 29,
.out = "\x13\x9c\x30\xc0\x96\x6b\xc3\x2b"
"\xa5\x5f\xdb\xf2\x12\x53\x0a\xc9"
"\xc5\xec\x59\xf1\xa4\x52\xf5\xcc"
"\x9a\xd9\x40\xfe\xa0\x59\x8e\xd1",
.nout = 32
},
{
.path = "/crypto/pbkdf/rfc3962/sha1/iter1200c",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 65,
.salt = "pass phrase exceeds block size",
.nsalt = 30,
.out = "\x9c\xca\xd6\xd4\x68\x77\x0c\xd5"
"\x1b\x10\xe6\xa6\x87\x21\xbe\x61"
"\x1a\x8b\x4d\x28\x26\x01\xdb\x3b"
"\x36\xbe\x92\x46\x91\x5e\xc8\x2a",
.nout = 32
},
{
.path = "/crypto/pbkdf/rfc3962/sha1/iter50",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 50,
.key = "\360\235\204\236", /* g-clef ("\xf09d849e) */
.nkey = 4,
.salt = "EXAMPLE.COMpianist",
.nsalt = 18,
.out = "\x6b\x9c\xf2\x6d\x45\x45\x5a\x43"
"\xa5\xb8\xbb\x27\x6a\x40\x3b\x39"
"\xe7\xfe\x37\xa0\xc4\x1e\x02\xc2"
"\x81\xff\x30\x69\xe1\xe9\x4f\x52",
.nout = 32
},
/* RFC-6070 test data */
{
.path = "/crypto/pbkdf/rfc6070/sha1/iter1",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 1,
.key = "password",
.nkey = 8,
.salt = "salt",
.nsalt = 4,
.out = "\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9"
"\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6",
.nout = 20
},
{
.path = "/crypto/pbkdf/rfc6070/sha1/iter2",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 2,
.key = "password",
.nkey = 8,
.salt = "salt",
.nsalt = 4,
.out = "\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e"
"\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57",
.nout = 20
},
{
.path = "/crypto/pbkdf/rfc6070/sha1/iter4096",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 4096,
.key = "password",
.nkey = 8,
.salt = "salt",
.nsalt = 4,
.out = "\x4b\x00\x79\x01\xb7\x65\x48\x9a\xbe\xad"
"\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1",
.nout = 20
},
{
.path = "/crypto/pbkdf/rfc6070/sha1/iter16777216",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 16777216,
.key = "password",
.nkey = 8,
.salt = "salt",
.nsalt = 4,
.out = "\xee\xfe\x3d\x61\xcd\x4d\xa4\xe4\xe9\x94"
"\x5b\x3d\x6b\xa2\x15\x8c\x26\x34\xe9\x84",
.nout = 20,
.slow = true,
},
{
.path = "/crypto/pbkdf/rfc6070/sha1/iter4096a",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 4096,
.key = "passwordPASSWORDpassword",
.nkey = 24,
.salt = "saltSALTsaltSALTsaltSALTsaltSALTsalt",
.nsalt = 36,
.out = "\x3d\x2e\xec\x4f\xe4\x1c\x84\x9b\x80\xc8"
"\xd8\x36\x62\xc0\xe4\x4a\x8b\x29\x1a\x96"
"\x4c\xf2\xf0\x70\x38",
.nout = 25
},
{
.path = "/crypto/pbkdf/rfc6070/sha1/iter4096b",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 4096,
.key = "pass\0word",
.nkey = 9,
.salt = "sa\0lt",
.nsalt = 5,
.out = "\x56\xfa\x6a\xa7\x55\x48\x09\x9d\xcc\x37"
"\xd7\xf0\x34\x25\xe0\xc3",
.nout = 16
},
/* non-RFC misc test data */
#ifdef CONFIG_NETTLE
{
/* empty password test.
* Broken with libgcrypt <= 1.5.0, hence CONFIG_NETTLE */
.path = "/crypto/pbkdf/nonrfc/sha1/iter2",
.hash = QCRYPTO_HASH_ALG_SHA1,
.iterations = 2,
.key = "",
.nkey = 0,
.salt = "salt",
.nsalt = 4,
.out = "\x13\x3a\x4c\xe8\x37\xb4\xd2\x52\x1e\xe2"
"\xbf\x03\xe1\x1c\x71\xca\x79\x4e\x07\x97",
.nout = 20
},
#endif
{
/* Password exceeds block size test */
.path = "/crypto/pbkdf/nonrfc/sha256/iter1200",
.hash = QCRYPTO_HASH_ALG_SHA256,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 65,
.salt = "pass phrase exceeds block size",
.nsalt = 30,
.out = "\x22\x34\x4b\xc4\xb6\xe3\x26\x75"
"\xa8\x09\x0f\x3e\xa8\x0b\xe0\x1d"
"\x5f\x95\x12\x6a\x2c\xdd\xc3\xfa"
"\xcc\x4a\x5e\x6d\xca\x04\xec\x58",
.nout = 32
},
{
.path = "/crypto/pbkdf/nonrfc/sha512/iter1200",
.hash = QCRYPTO_HASH_ALG_SHA512,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 129,
.salt = "pass phrase exceeds block size",
.nsalt = 30,
.out = "\x0f\xb2\xed\x2c\x0e\x6e\xfb\x7d"
"\x7d\x8e\xdd\x58\x01\xb4\x59\x72"
"\x99\x92\x16\x30\x5e\xa4\x36\x8d"
"\x76\x14\x80\xf3\xe3\x7a\x22\xb9",
.nout = 32
},
{
.path = "/crypto/pbkdf/nonrfc/sha224/iter1200",
.hash = QCRYPTO_HASH_ALG_SHA224,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 129,
.salt = "pass phrase exceeds block size",
.nsalt = 30,
.out = "\x13\x3b\x88\x0c\x0e\x52\xa2\x41"
"\x49\x33\x35\xa6\xc3\x83\xae\x23"
"\xf6\x77\x43\x9e\x5b\x30\x92\x3e"
"\x4a\x3a\xaa\x24\x69\x3c\xed\x20",
.nout = 32
},
{
.path = "/crypto/pbkdf/nonrfc/sha384/iter1200",
.hash = QCRYPTO_HASH_ALG_SHA384,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 129,
.salt = "pass phrase exceeds block size",
.nsalt = 30,
.out = "\xfe\xe3\xe1\x84\xc9\x25\x3e\x10"
"\x47\xc8\x7d\x53\xc6\xa5\xe3\x77"
"\x29\x41\x76\xbd\x4b\xe3\x9b\xac"
"\x05\x6c\x11\xdd\x17\xc5\x93\x80",
.nout = 32
},
{
.path = "/crypto/pbkdf/nonrfc/ripemd160/iter1200",
.hash = QCRYPTO_HASH_ALG_RIPEMD160,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 129,
.salt = "pass phrase exceeds block size",
.nsalt = 30,
.out = "\xd6\xcb\xd8\xa7\xdb\x0c\xa2\x2a"
"\x23\x5e\x47\xaf\xdb\xda\xa8\xef"
"\xe4\x01\x0d\x6f\xb5\x33\xc8\xbd"
"\xce\xbf\x91\x14\x8b\x5c\x48\x41",
.nout = 32
},
#if 0
{
.path = "/crypto/pbkdf/nonrfc/whirlpool/iter1200",
.hash = QCRYPTO_HASH_ALG_WHIRLPOOL,
.iterations = 1200,
.key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
.nkey = 65,
.salt = "pass phrase exceeds block size",
.nsalt = 30,
.out = "\x9c\x1c\x74\xf5\x88\x26\xe7\x6a"
"\x53\x58\xf4\x0c\x39\xe7\x80\x89"
"\x07\xc0\x31\x19\x9a\x50\xa2\x48"
"\xf1\xd9\xfe\x78\x64\xe5\x84\x50",
.nout = 32
}
#endif
};
static inline char hex(int i)
{
if (i < 10) {
return '0' + i;
}
return 'a' + (i - 10);
}
static char *hex_string(const uint8_t *bytes,
size_t len)
{
char *hexstr = g_new0(char, len * 2 + 1);
size_t i;
for (i = 0; i < len; i++) {
hexstr[i * 2] = hex((bytes[i] >> 4) & 0xf);
hexstr[i * 2 + 1] = hex(bytes[i] & 0xf);
}
hexstr[len * 2] = '\0';
return hexstr;
}
static void test_pbkdf(const void *opaque)
{
const QCryptoPbkdfTestData *data = opaque;
size_t nout = data->nout;
uint8_t *out = g_new0(uint8_t, nout);
gchar *expect, *actual;
qcrypto_pbkdf2(data->hash,
(uint8_t *)data->key, data->nkey,
(uint8_t *)data->salt, data->nsalt,
data->iterations,
(uint8_t *)out, nout,
&error_abort);
expect = hex_string((const uint8_t *)data->out, data->nout);
actual = hex_string(out, nout);
g_assert_cmpstr(actual, ==, expect);
g_free(actual);
g_free(expect);
g_free(out);
}
static void test_pbkdf_timing(void)
{
uint8_t key[32];
uint8_t salt[32];
int iters;
memset(key, 0x5d, sizeof(key));
memset(salt, 0x7c, sizeof(salt));
iters = qcrypto_pbkdf2_count_iters(QCRYPTO_HASH_ALG_SHA256,
key, sizeof(key),
salt, sizeof(salt),
32,
&error_abort);
g_assert(iters >= (1 << 15));
}
int main(int argc, char **argv)
{
size_t i;
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
if (!test_data[i].slow ||
g_test_slow()) {
g_test_add_data_func(test_data[i].path, &test_data[i], test_pbkdf);
}
}
if (g_test_slow()) {
g_test_add_func("/crypt0/pbkdf/timing", test_pbkdf_timing);
}
return g_test_run();
}
#else
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
return g_test_run();
}
#endif

View file

@ -0,0 +1,614 @@
/*
* QEMU Crypto secret handling
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "crypto/init.h"
#include "crypto/secret.h"
#include "qapi/error.h"
#include "qemu/module.h"
#ifdef CONFIG_KEYUTILS
#include "crypto/secret_keyring.h"
#include <keyutils.h>
#endif
static void test_secret_direct(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "123456",
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "123456");
object_unparent(sec);
g_free(pw);
}
static void test_secret_indirect_good(void)
{
Object *sec;
char *fname = NULL;
int fd = g_file_open_tmp("qemu-test-crypto-secret-XXXXXX",
&fname,
NULL);
g_assert(fd >= 0);
g_assert_nonnull(fname);
g_assert(write(fd, "123456", 6) == 6);
sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"file", fname,
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "123456");
object_unparent(sec);
g_free(pw);
close(fd);
unlink(fname);
g_free(fname);
}
static void test_secret_indirect_badfile(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
NULL,
"file", "does-not-exist",
NULL);
g_assert(sec == NULL);
}
static void test_secret_indirect_emptyfile(void)
{
Object *sec;
char *fname = NULL;
int fd = g_file_open_tmp("qemu-test-crypto-secretXXXXXX",
&fname,
NULL);
g_assert(fd >= 0);
g_assert_nonnull(fname);
sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"file", fname,
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "");
object_unparent(sec);
g_free(pw);
close(fd);
unlink(fname);
g_free(fname);
}
#ifdef CONFIG_KEYUTILS
#define DESCRIPTION "qemu_test_secret"
#define PAYLOAD "Test Payload"
static void test_secret_keyring_good(void)
{
char key_str[16];
Object *sec;
int32_t key = add_key("user", DESCRIPTION, PAYLOAD,
strlen(PAYLOAD), KEY_SPEC_PROCESS_KEYRING);
g_assert(key >= 0);
snprintf(key_str, sizeof(key_str), "0x%08x", key);
sec = object_new_with_props(
TYPE_QCRYPTO_SECRET_KEYRING,
object_get_objects_root(),
"sec0",
&error_abort,
"serial", key_str,
NULL);
assert(0 <= keyctl_unlink(key, KEY_SPEC_PROCESS_KEYRING));
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, PAYLOAD);
object_unparent(sec);
g_free(pw);
}
static void test_secret_keyring_revoked_key(void)
{
char key_str[16];
Object *sec;
int32_t key = add_key("user", DESCRIPTION, PAYLOAD,
strlen(PAYLOAD), KEY_SPEC_PROCESS_KEYRING);
g_assert(key >= 0);
g_assert_false(keyctl_revoke(key));
snprintf(key_str, sizeof(key_str), "0x%08x", key);
sec = object_new_with_props(
TYPE_QCRYPTO_SECRET_KEYRING,
object_get_objects_root(),
"sec0",
NULL,
"serial", key_str,
NULL);
g_assert(errno == EKEYREVOKED);
g_assert(sec == NULL);
keyctl_unlink(key, KEY_SPEC_PROCESS_KEYRING);
}
static void test_secret_keyring_expired_key(void)
{
char key_str[16];
Object *sec;
int32_t key = add_key("user", DESCRIPTION, PAYLOAD,
strlen(PAYLOAD), KEY_SPEC_PROCESS_KEYRING);
g_assert(key >= 0);
g_assert_false(keyctl_set_timeout(key, 1));
sleep(1);
snprintf(key_str, sizeof(key_str), "0x%08x", key);
sec = object_new_with_props(
TYPE_QCRYPTO_SECRET_KEYRING,
object_get_objects_root(),
"sec0",
NULL,
"serial", key_str,
NULL);
g_assert(errno == EKEYEXPIRED);
g_assert(sec == NULL);
keyctl_unlink(key, KEY_SPEC_PROCESS_KEYRING);
}
static void test_secret_keyring_bad_serial_key(void)
{
Object *sec;
sec = object_new_with_props(
TYPE_QCRYPTO_SECRET_KEYRING,
object_get_objects_root(),
"sec0",
NULL,
"serial", "1",
NULL);
g_assert(errno == ENOKEY);
g_assert(sec == NULL);
}
/*
* TODO
* test_secret_keyring_bad_key_access_right() is not working yet.
* We don't know yet if this due a bug in the Linux kernel or
* whether it's normal syscall behavior.
* We've requested information from kernel maintainers.
* See: <https://www.spinics.net/lists/keyrings/index.html>
* Thread: 'security/keys: remove possessor verify after key permission check'
*/
static void test_secret_keyring_bad_key_access_right(void)
{
char key_str[16];
Object *sec;
g_test_skip("TODO: Need responce from Linux kernel maintainers");
return;
int32_t key = add_key("user", DESCRIPTION, PAYLOAD,
strlen(PAYLOAD), KEY_SPEC_PROCESS_KEYRING);
g_assert(key >= 0);
g_assert_false(keyctl_setperm(key, KEY_POS_ALL & (~KEY_POS_READ)));
snprintf(key_str, sizeof(key_str), "0x%08x", key);
sec = object_new_with_props(
TYPE_QCRYPTO_SECRET_KEYRING,
object_get_objects_root(),
"sec0",
NULL,
"serial", key_str,
NULL);
g_assert(errno == EACCES);
g_assert(sec == NULL);
keyctl_unlink(key, KEY_SPEC_PROCESS_KEYRING);
}
#endif /* CONFIG_KEYUTILS */
static void test_secret_noconv_base64_good(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "MTIzNDU2",
"format", "base64",
NULL);
char *pw = qcrypto_secret_lookup_as_base64("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "MTIzNDU2");
object_unparent(sec);
g_free(pw);
}
static void test_secret_noconv_base64_bad(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
NULL,
"data", "MTI$NDU2",
"format", "base64",
NULL);
g_assert(sec == NULL);
}
static void test_secret_noconv_utf8(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "123456",
"format", "raw",
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "123456");
object_unparent(sec);
g_free(pw);
}
static void test_secret_conv_base64_utf8valid(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "MTIzNDU2",
"format", "base64",
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "123456");
object_unparent(sec);
g_free(pw);
}
static void test_secret_conv_base64_utf8invalid(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "f0VMRgIBAQAAAA==",
"format", "base64",
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
NULL);
g_assert(pw == NULL);
object_unparent(sec);
}
static void test_secret_conv_utf8_base64(void)
{
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "123456",
NULL);
char *pw = qcrypto_secret_lookup_as_base64("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "MTIzNDU2");
object_unparent(sec);
g_free(pw);
}
static void test_secret_crypt_raw(void)
{
Object *master = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"master",
&error_abort,
"data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
"format", "base64",
NULL);
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data",
"\xCC\xBF\xF7\x09\x46\x19\x0B\x52\x2A\x3A\xB4\x6B\xCD\x7A\xB0\xB0",
"format", "raw",
"keyid", "master",
"iv", "0I7Gw/TKuA+Old2W2apQ3g==",
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "123456");
object_unparent(sec);
object_unparent(master);
g_free(pw);
}
static void test_secret_crypt_base64(void)
{
Object *master = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"master",
&error_abort,
"data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
"format", "base64",
NULL);
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
&error_abort,
"data", "zL/3CUYZC1IqOrRrzXqwsA==",
"format", "base64",
"keyid", "master",
"iv", "0I7Gw/TKuA+Old2W2apQ3g==",
NULL);
char *pw = qcrypto_secret_lookup_as_utf8("sec0",
&error_abort);
g_assert_cmpstr(pw, ==, "123456");
object_unparent(sec);
object_unparent(master);
g_free(pw);
}
static void test_secret_crypt_short_key(void)
{
Object *master = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"master",
&error_abort,
"data", "9miloPQCzGy+TL6aonfzVc",
"format", "base64",
NULL);
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
NULL,
"data", "zL/3CUYZC1IqOrRrzXqwsA==",
"format", "raw",
"keyid", "master",
"iv", "0I7Gw/TKuA+Old2W2apQ3g==",
NULL);
g_assert(sec == NULL);
object_unparent(master);
}
static void test_secret_crypt_short_iv(void)
{
Object *master = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"master",
&error_abort,
"data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
"format", "base64",
NULL);
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
NULL,
"data", "zL/3CUYZC1IqOrRrzXqwsA==",
"format", "raw",
"keyid", "master",
"iv", "0I7Gw/TKuA+Old2W2a",
NULL);
g_assert(sec == NULL);
object_unparent(master);
}
static void test_secret_crypt_missing_iv(void)
{
Object *master = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"master",
&error_abort,
"data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
"format", "base64",
NULL);
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
NULL,
"data", "zL/3CUYZC1IqOrRrzXqwsA==",
"format", "raw",
"keyid", "master",
NULL);
g_assert(sec == NULL);
object_unparent(master);
}
static void test_secret_crypt_bad_iv(void)
{
Object *master = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"master",
&error_abort,
"data", "9miloPQCzGy+TL6aonfzVcptibCmCIhKzrnlfwiWivk=",
"format", "base64",
NULL);
Object *sec = object_new_with_props(
TYPE_QCRYPTO_SECRET,
object_get_objects_root(),
"sec0",
NULL,
"data", "zL/3CUYZC1IqOrRrzXqwsA==",
"format", "raw",
"keyid", "master",
"iv", "0I7Gw/TK$$uA+Old2W2a",
NULL);
g_assert(sec == NULL);
object_unparent(master);
}
int main(int argc, char **argv)
{
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
g_test_add_func("/crypto/secret/direct",
test_secret_direct);
g_test_add_func("/crypto/secret/indirect/good",
test_secret_indirect_good);
g_test_add_func("/crypto/secret/indirect/badfile",
test_secret_indirect_badfile);
g_test_add_func("/crypto/secret/indirect/emptyfile",
test_secret_indirect_emptyfile);
#ifdef CONFIG_KEYUTILS
g_test_add_func("/crypto/secret/keyring/good",
test_secret_keyring_good);
g_test_add_func("/crypto/secret/keyring/revoked_key",
test_secret_keyring_revoked_key);
g_test_add_func("/crypto/secret/keyring/expired_key",
test_secret_keyring_expired_key);
g_test_add_func("/crypto/secret/keyring/bad_serial_key",
test_secret_keyring_bad_serial_key);
g_test_add_func("/crypto/secret/keyring/bad_key_access_right",
test_secret_keyring_bad_key_access_right);
#endif /* CONFIG_KEYUTILS */
g_test_add_func("/crypto/secret/noconv/base64/good",
test_secret_noconv_base64_good);
g_test_add_func("/crypto/secret/noconv/base64/bad",
test_secret_noconv_base64_bad);
g_test_add_func("/crypto/secret/noconv/utf8",
test_secret_noconv_utf8);
g_test_add_func("/crypto/secret/conv/base64/utf8valid",
test_secret_conv_base64_utf8valid);
g_test_add_func("/crypto/secret/conv/base64/utf8invalid",
test_secret_conv_base64_utf8invalid);
g_test_add_func("/crypto/secret/conv/utf8/base64",
test_secret_conv_utf8_base64);
g_test_add_func("/crypto/secret/crypt/raw",
test_secret_crypt_raw);
g_test_add_func("/crypto/secret/crypt/base64",
test_secret_crypt_base64);
g_test_add_func("/crypto/secret/crypt/shortkey",
test_secret_crypt_short_key);
g_test_add_func("/crypto/secret/crypt/shortiv",
test_secret_crypt_short_iv);
g_test_add_func("/crypto/secret/crypt/missingiv",
test_secret_crypt_missing_iv);
g_test_add_func("/crypto/secret/crypt/badiv",
test_secret_crypt_bad_iv);
return g_test_run();
}

View file

@ -0,0 +1,718 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include "qemu/osdep.h"
#include "crypto-tls-x509-helpers.h"
#include "crypto/tlscredsx509.h"
#include "qapi/error.h"
#include "qemu/module.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
#define WORKDIR "tests/test-crypto-tlscredsx509-work/"
#define KEYFILE WORKDIR "key-ctx.pem"
struct QCryptoTLSCredsTestData {
bool isServer;
const char *cacrt;
const char *crt;
bool expectFail;
};
static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
const char *certdir,
Error **errp)
{
Object *parent = object_get_objects_root();
Object *creds = object_new_with_props(
TYPE_QCRYPTO_TLS_CREDS_X509,
parent,
"testtlscreds",
errp,
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"server" : "client"),
"dir", certdir,
"verify-peer", "yes",
"sanity-check", "yes",
NULL);
if (!creds) {
return NULL;
}
return QCRYPTO_TLS_CREDS(creds);
}
/*
* This tests sanity checking of our own certificates
*
* The code being tested is used when TLS creds are created,
* and aim to ensure QMEU has been configured with sane
* certificates. This allows us to give much much much
* clearer error messages to the admin when they misconfigure
* things.
*/
static void test_tls_creds(const void *opaque)
{
struct QCryptoTLSCredsTestData *data =
(struct QCryptoTLSCredsTestData *)opaque;
QCryptoTLSCreds *creds;
#define CERT_DIR "tests/test-crypto-tlscredsx509-certs/"
mkdir(CERT_DIR, 0700);
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
if (data->isServer) {
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
} else {
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
}
if (access(data->cacrt, R_OK) == 0) {
g_assert(link(data->cacrt,
CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
}
if (data->isServer) {
if (access(data->crt, R_OK) == 0) {
g_assert(link(data->crt,
CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
}
g_assert(link(KEYFILE,
CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
} else {
if (access(data->crt, R_OK) == 0) {
g_assert(link(data->crt,
CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
}
g_assert(link(KEYFILE,
CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
}
creds = test_tls_creds_create(
(data->isServer ?
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER :
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT),
CERT_DIR,
data->expectFail ? NULL : &error_abort);
if (data->expectFail) {
g_assert(creds == NULL);
} else {
g_assert(creds != NULL);
}
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
if (data->isServer) {
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
} else {
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
}
rmdir(CERT_DIR);
if (creds) {
object_unparent(OBJECT(creds));
}
}
int main(int argc, char **argv)
{
int ret;
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
g_setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
mkdir(WORKDIR, 0700);
test_tls_init(KEYFILE);
# define TLS_TEST_REG(name, isServer, caCrt, crt, expectFail) \
struct QCryptoTLSCredsTestData name = { \
isServer, caCrt, crt, expectFail \
}; \
g_test_add_data_func("/qcrypto/tlscredsx509/" # name, \
&name, test_tls_creds); \
/* A perfect CA, perfect client & perfect server */
/* Basic:CA:critical */
TLS_ROOT_REQ(cacertreq,
"UK", "qemu CA", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercertreq, cacertreq,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_CERT_REQ(clientcertreq, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
TLS_TEST_REG(perfectserver, true,
cacertreq.filename, servercertreq.filename, false);
TLS_TEST_REG(perfectclient, false,
cacertreq.filename, clientcertreq.filename, false);
/* Some other CAs which are good */
/* Basic:CA:critical */
TLS_ROOT_REQ(cacert1req,
"UK", "qemu CA 1", NULL, NULL, NULL, NULL,
true, true, true,
false, false, 0,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercert1req, cacert1req,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* Basic:CA:not-critical */
TLS_ROOT_REQ(cacert2req,
"UK", "qemu CA 2", NULL, NULL, NULL, NULL,
true, false, true,
false, false, 0,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercert2req, cacert2req,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* Key usage:cert-sign:critical */
TLS_ROOT_REQ(cacert3req,
"UK", "qemu CA 3", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercert3req, cacert3req,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_TEST_REG(goodca1, true,
cacert1req.filename, servercert1req.filename, false);
TLS_TEST_REG(goodca2, true,
cacert2req.filename, servercert2req.filename, false);
TLS_TEST_REG(goodca3, true,
cacert3req.filename, servercert3req.filename, false);
/* Now some bad certs */
/* Key usage:dig-sig:not-critical */
TLS_ROOT_REQ(cacert4req,
"UK", "qemu CA 4", NULL, NULL, NULL, NULL,
true, true, true,
true, false, GNUTLS_KEY_DIGITAL_SIGNATURE,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercert4req, cacert4req,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* no-basic */
TLS_ROOT_REQ(cacert5req,
"UK", "qemu CA 5", NULL, NULL, NULL, NULL,
false, false, false,
false, false, 0,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercert5req, cacert5req,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* Key usage:dig-sig:critical */
TLS_ROOT_REQ(cacert6req,
"UK", "qemu CA 6", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_DIGITAL_SIGNATURE,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercert6req, cacert6req,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_TEST_REG(badca1, true, cacert4req.filename, servercert4req.filename,
true);
TLS_TEST_REG(badca2, true,
cacert5req.filename, servercert5req.filename, true);
TLS_TEST_REG(badca3, true,
cacert6req.filename, servercert6req.filename, true);
/* Various good servers */
/* no usage or purpose */
TLS_CERT_REQ(servercert7req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
false, false, NULL, NULL,
0, 0);
/* usage:cert-sign+dig-sig+encipher:critical */
TLS_CERT_REQ(servercert8req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT |
GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
/* usage:cert-sign:not-critical */
TLS_CERT_REQ(servercert9req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, false, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
/* purpose:server:critical */
TLS_CERT_REQ(servercert10req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* purpose:server:not-critical */
TLS_CERT_REQ(servercert11req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, false, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* purpose:client+server:critical */
TLS_CERT_REQ(servercert12req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, true,
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
0, 0);
/* purpose:client+server:not-critical */
TLS_CERT_REQ(servercert13req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, false,
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
0, 0);
TLS_TEST_REG(goodserver1, true,
cacertreq.filename, servercert7req.filename, false);
TLS_TEST_REG(goodserver2, true,
cacertreq.filename, servercert8req.filename, false);
TLS_TEST_REG(goodserver3, true,
cacertreq.filename, servercert9req.filename, false);
TLS_TEST_REG(goodserver4, true,
cacertreq.filename, servercert10req.filename, false);
TLS_TEST_REG(goodserver5, true,
cacertreq.filename, servercert11req.filename, false);
TLS_TEST_REG(goodserver6, true,
cacertreq.filename, servercert12req.filename, false);
TLS_TEST_REG(goodserver7, true,
cacertreq.filename, servercert13req.filename, false);
/* Bad servers */
/* usage:cert-sign:critical */
TLS_CERT_REQ(servercert14req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
/* purpose:client:critical */
TLS_CERT_REQ(servercert15req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
/* usage: none:critical */
TLS_CERT_REQ(servercert16req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true, 0,
false, false, NULL, NULL,
0, 0);
TLS_TEST_REG(badserver1, true,
cacertreq.filename, servercert14req.filename, true);
TLS_TEST_REG(badserver2, true,
cacertreq.filename, servercert15req.filename, true);
TLS_TEST_REG(badserver3, true,
cacertreq.filename, servercert16req.filename, true);
/* Various good clients */
/* no usage or purpose */
TLS_CERT_REQ(clientcert1req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
false, false, NULL, NULL,
0, 0);
/* usage:cert-sign+dig-sig+encipher:critical */
TLS_CERT_REQ(clientcert2req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT |
GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
/* usage:cert-sign:not-critical */
TLS_CERT_REQ(clientcert3req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, false, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
/* purpose:client:critical */
TLS_CERT_REQ(clientcert4req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
/* purpose:client:not-critical */
TLS_CERT_REQ(clientcert5req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, false, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
/* purpose:client+client:critical */
TLS_CERT_REQ(clientcert6req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, true,
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
0, 0);
/* purpose:client+client:not-critical */
TLS_CERT_REQ(clientcert7req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, false,
GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
0, 0);
TLS_TEST_REG(goodclient1, false,
cacertreq.filename, clientcert1req.filename, false);
TLS_TEST_REG(goodclient2, false,
cacertreq.filename, clientcert2req.filename, false);
TLS_TEST_REG(goodclient3, false,
cacertreq.filename, clientcert3req.filename, false);
TLS_TEST_REG(goodclient4, false,
cacertreq.filename, clientcert4req.filename, false);
TLS_TEST_REG(goodclient5, false,
cacertreq.filename, clientcert5req.filename, false);
TLS_TEST_REG(goodclient6, false,
cacertreq.filename, clientcert6req.filename, false);
TLS_TEST_REG(goodclient7, false,
cacertreq.filename, clientcert7req.filename, false);
/* Bad clients */
/* usage:cert-sign:critical */
TLS_CERT_REQ(clientcert8req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
/* purpose:client:critical */
TLS_CERT_REQ(clientcert9req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
false, false, 0,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* usage: none:critical */
TLS_CERT_REQ(clientcert10req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true, 0,
false, false, NULL, NULL,
0, 0);
TLS_TEST_REG(badclient1, false,
cacertreq.filename, clientcert8req.filename, true);
TLS_TEST_REG(badclient2, false,
cacertreq.filename, clientcert9req.filename, true);
TLS_TEST_REG(badclient3, false,
cacertreq.filename, clientcert10req.filename, true);
/* Expired stuff */
TLS_ROOT_REQ(cacertexpreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, -1);
TLS_CERT_REQ(servercertexpreq, cacertexpreq,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_CERT_REQ(servercertexp1req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, -1);
TLS_CERT_REQ(clientcertexp1req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, -1);
TLS_TEST_REG(expired1, true,
cacertexpreq.filename, servercertexpreq.filename, true);
TLS_TEST_REG(expired2, true,
cacertreq.filename, servercertexp1req.filename, true);
TLS_TEST_REG(expired3, false,
cacertreq.filename, clientcertexp1req.filename, true);
/* Not activated stuff */
TLS_ROOT_REQ(cacertnewreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
1, 2);
TLS_CERT_REQ(servercertnewreq, cacertnewreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_CERT_REQ(servercertnew1req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
1, 2);
TLS_CERT_REQ(clientcertnew1req, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
1, 2);
TLS_TEST_REG(inactive1, true,
cacertnewreq.filename, servercertnewreq.filename, true);
TLS_TEST_REG(inactive2, true,
cacertreq.filename, servercertnew1req.filename, true);
TLS_TEST_REG(inactive3, false,
cacertreq.filename, clientcertnew1req.filename, true);
TLS_ROOT_REQ(cacertrootreq,
"UK", "qemu root", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
"UK", "qemu level 1a", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
"UK", "qemu level 1b", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
"UK", "qemu level 2a", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
"UK", "qemu client level 2b", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
gnutls_x509_crt_t certchain[] = {
cacertrootreq.crt,
cacertlevel1areq.crt,
cacertlevel1breq.crt,
cacertlevel2areq.crt,
};
test_tls_write_cert_chain(WORKDIR "cacertchain-ctx.pem",
certchain,
G_N_ELEMENTS(certchain));
TLS_TEST_REG(chain1, true,
WORKDIR "cacertchain-ctx.pem",
servercertlevel3areq.filename, false);
TLS_TEST_REG(chain2, false,
WORKDIR "cacertchain-ctx.pem",
clientcertlevel2breq.filename, false);
/* Some missing certs - first two are fatal, the last
* is ok
*/
TLS_TEST_REG(missingca, true,
"cacertdoesnotexist.pem",
servercert1req.filename, true);
TLS_TEST_REG(missingserver, true,
cacert1req.filename,
"servercertdoesnotexist.pem", true);
TLS_TEST_REG(missingclient, false,
cacert1req.filename,
"clientcertdoesnotexist.pem", false);
ret = g_test_run();
test_tls_discard_cert(&cacertreq);
test_tls_discard_cert(&cacert1req);
test_tls_discard_cert(&cacert2req);
test_tls_discard_cert(&cacert3req);
test_tls_discard_cert(&cacert4req);
test_tls_discard_cert(&cacert5req);
test_tls_discard_cert(&cacert6req);
test_tls_discard_cert(&servercertreq);
test_tls_discard_cert(&servercert1req);
test_tls_discard_cert(&servercert2req);
test_tls_discard_cert(&servercert3req);
test_tls_discard_cert(&servercert4req);
test_tls_discard_cert(&servercert5req);
test_tls_discard_cert(&servercert6req);
test_tls_discard_cert(&servercert7req);
test_tls_discard_cert(&servercert8req);
test_tls_discard_cert(&servercert9req);
test_tls_discard_cert(&servercert10req);
test_tls_discard_cert(&servercert11req);
test_tls_discard_cert(&servercert12req);
test_tls_discard_cert(&servercert13req);
test_tls_discard_cert(&servercert14req);
test_tls_discard_cert(&servercert15req);
test_tls_discard_cert(&servercert16req);
test_tls_discard_cert(&clientcertreq);
test_tls_discard_cert(&clientcert1req);
test_tls_discard_cert(&clientcert2req);
test_tls_discard_cert(&clientcert3req);
test_tls_discard_cert(&clientcert4req);
test_tls_discard_cert(&clientcert5req);
test_tls_discard_cert(&clientcert6req);
test_tls_discard_cert(&clientcert7req);
test_tls_discard_cert(&clientcert8req);
test_tls_discard_cert(&clientcert9req);
test_tls_discard_cert(&clientcert10req);
test_tls_discard_cert(&cacertexpreq);
test_tls_discard_cert(&servercertexpreq);
test_tls_discard_cert(&servercertexp1req);
test_tls_discard_cert(&clientcertexp1req);
test_tls_discard_cert(&cacertnewreq);
test_tls_discard_cert(&servercertnewreq);
test_tls_discard_cert(&servercertnew1req);
test_tls_discard_cert(&clientcertnew1req);
test_tls_discard_cert(&cacertrootreq);
test_tls_discard_cert(&cacertlevel1areq);
test_tls_discard_cert(&cacertlevel1breq);
test_tls_discard_cert(&cacertlevel2areq);
test_tls_discard_cert(&servercertlevel3areq);
test_tls_discard_cert(&clientcertlevel2breq);
unlink(WORKDIR "cacertchain-ctx.pem");
test_tls_cleanup(KEYFILE);
rmdir(WORKDIR);
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
int
main(void)
{
return EXIT_SUCCESS;
}
#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */

View file

@ -0,0 +1,660 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include "qemu/osdep.h"
#include "crypto-tls-x509-helpers.h"
#include "crypto-tls-psk-helpers.h"
#include "crypto/tlscredsx509.h"
#include "crypto/tlscredspsk.h"
#include "crypto/tlssession.h"
#include "qom/object_interfaces.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "qemu/sockets.h"
#include "authz/list.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
#define WORKDIR "tests/test-crypto-tlssession-work/"
#define PSKFILE WORKDIR "keys.psk"
#define KEYFILE WORKDIR "key-ctx.pem"
static ssize_t testWrite(const char *buf, size_t len, void *opaque)
{
int *fd = opaque;
return write(*fd, buf, len);
}
static ssize_t testRead(char *buf, size_t len, void *opaque)
{
int *fd = opaque;
return read(*fd, buf, len);
}
static QCryptoTLSCreds *test_tls_creds_psk_create(
QCryptoTLSCredsEndpoint endpoint,
const char *dir)
{
Object *parent = object_get_objects_root();
Object *creds = object_new_with_props(
TYPE_QCRYPTO_TLS_CREDS_PSK,
parent,
(endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"testtlscredsserver" : "testtlscredsclient"),
&error_abort,
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"server" : "client"),
"dir", dir,
"priority", "NORMAL",
NULL
);
return QCRYPTO_TLS_CREDS(creds);
}
static void test_crypto_tls_session_psk(void)
{
QCryptoTLSCreds *clientCreds;
QCryptoTLSCreds *serverCreds;
QCryptoTLSSession *clientSess = NULL;
QCryptoTLSSession *serverSess = NULL;
int channel[2];
bool clientShake = false;
bool serverShake = false;
int ret;
/* We'll use this for our fake client-server connection */
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, channel);
g_assert(ret == 0);
/*
* We have an evil loop to do the handshake in a single
* thread, so we need these non-blocking to avoid deadlock
* of ourselves
*/
qemu_set_nonblock(channel[0]);
qemu_set_nonblock(channel[1]);
clientCreds = test_tls_creds_psk_create(
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
WORKDIR);
g_assert(clientCreds != NULL);
serverCreds = test_tls_creds_psk_create(
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
WORKDIR);
g_assert(serverCreds != NULL);
/* Now the real part of the test, setup the sessions */
clientSess = qcrypto_tls_session_new(
clientCreds, NULL, NULL,
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT, &error_abort);
g_assert(clientSess != NULL);
serverSess = qcrypto_tls_session_new(
serverCreds, NULL, NULL,
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, &error_abort);
g_assert(serverSess != NULL);
/* For handshake to work, we need to set the I/O callbacks
* to read/write over the socketpair
*/
qcrypto_tls_session_set_callbacks(serverSess,
testWrite, testRead,
&channel[0]);
qcrypto_tls_session_set_callbacks(clientSess,
testWrite, testRead,
&channel[1]);
/*
* Finally we loop around & around doing handshake on each
* session until we get an error, or the handshake completes.
* This relies on the socketpair being nonblocking to avoid
* deadlocking ourselves upon handshake
*/
do {
int rv;
if (!serverShake) {
rv = qcrypto_tls_session_handshake(serverSess,
&error_abort);
g_assert(rv >= 0);
if (qcrypto_tls_session_get_handshake_status(serverSess) ==
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
serverShake = true;
}
}
if (!clientShake) {
rv = qcrypto_tls_session_handshake(clientSess,
&error_abort);
g_assert(rv >= 0);
if (qcrypto_tls_session_get_handshake_status(clientSess) ==
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
clientShake = true;
}
}
} while (!clientShake || !serverShake);
/* Finally make sure the server & client validation is successful. */
g_assert(qcrypto_tls_session_check_credentials(serverSess,
&error_abort) == 0);
g_assert(qcrypto_tls_session_check_credentials(clientSess,
&error_abort) == 0);
object_unparent(OBJECT(serverCreds));
object_unparent(OBJECT(clientCreds));
qcrypto_tls_session_free(serverSess);
qcrypto_tls_session_free(clientSess);
close(channel[0]);
close(channel[1]);
}
struct QCryptoTLSSessionTestData {
const char *servercacrt;
const char *clientcacrt;
const char *servercrt;
const char *clientcrt;
bool expectServerFail;
bool expectClientFail;
const char *hostname;
const char *const *wildcards;
};
static QCryptoTLSCreds *test_tls_creds_x509_create(
QCryptoTLSCredsEndpoint endpoint,
const char *certdir)
{
Object *parent = object_get_objects_root();
Object *creds = object_new_with_props(
TYPE_QCRYPTO_TLS_CREDS_X509,
parent,
(endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"testtlscredsserver" : "testtlscredsclient"),
&error_abort,
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"server" : "client"),
"dir", certdir,
"verify-peer", "yes",
"priority", "NORMAL",
/* We skip initial sanity checks here because we
* want to make sure that problems are being
* detected at the TLS session validation stage,
* and the test-crypto-tlscreds test already
* validate the sanity check code.
*/
"sanity-check", "no",
NULL
);
return QCRYPTO_TLS_CREDS(creds);
}
/*
* This tests validation checking of peer certificates
*
* This is replicating the checks that are done for an
* active TLS session after handshake completes. To
* simulate that we create our TLS contexts, skipping
* sanity checks. We then get a socketpair, and
* initiate a TLS session across them. Finally do
* do actual cert validation tests
*/
static void test_crypto_tls_session_x509(const void *opaque)
{
struct QCryptoTLSSessionTestData *data =
(struct QCryptoTLSSessionTestData *)opaque;
QCryptoTLSCreds *clientCreds;
QCryptoTLSCreds *serverCreds;
QCryptoTLSSession *clientSess = NULL;
QCryptoTLSSession *serverSess = NULL;
QAuthZList *auth;
const char * const *wildcards;
int channel[2];
bool clientShake = false;
bool serverShake = false;
int ret;
/* We'll use this for our fake client-server connection */
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, channel);
g_assert(ret == 0);
/*
* We have an evil loop to do the handshake in a single
* thread, so we need these non-blocking to avoid deadlock
* of ourselves
*/
qemu_set_nonblock(channel[0]);
qemu_set_nonblock(channel[1]);
#define CLIENT_CERT_DIR "tests/test-crypto-tlssession-client/"
#define SERVER_CERT_DIR "tests/test-crypto-tlssession-server/"
mkdir(CLIENT_CERT_DIR, 0700);
mkdir(SERVER_CERT_DIR, 0700);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
g_assert(link(data->servercacrt,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
g_assert(link(data->servercrt,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
g_assert(link(KEYFILE,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
g_assert(link(data->clientcacrt,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
g_assert(link(data->clientcrt,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
g_assert(link(KEYFILE,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
clientCreds = test_tls_creds_x509_create(
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
CLIENT_CERT_DIR);
g_assert(clientCreds != NULL);
serverCreds = test_tls_creds_x509_create(
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
SERVER_CERT_DIR);
g_assert(serverCreds != NULL);
auth = qauthz_list_new("tlssessionacl",
QAUTHZ_LIST_POLICY_DENY,
&error_abort);
wildcards = data->wildcards;
while (wildcards && *wildcards) {
qauthz_list_append_rule(auth, *wildcards,
QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_GLOB,
&error_abort);
wildcards++;
}
/* Now the real part of the test, setup the sessions */
clientSess = qcrypto_tls_session_new(
clientCreds, data->hostname, NULL,
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT, &error_abort);
g_assert(clientSess != NULL);
serverSess = qcrypto_tls_session_new(
serverCreds, NULL,
data->wildcards ? "tlssessionacl" : NULL,
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, &error_abort);
g_assert(serverSess != NULL);
/* For handshake to work, we need to set the I/O callbacks
* to read/write over the socketpair
*/
qcrypto_tls_session_set_callbacks(serverSess,
testWrite, testRead,
&channel[0]);
qcrypto_tls_session_set_callbacks(clientSess,
testWrite, testRead,
&channel[1]);
/*
* Finally we loop around & around doing handshake on each
* session until we get an error, or the handshake completes.
* This relies on the socketpair being nonblocking to avoid
* deadlocking ourselves upon handshake
*/
do {
int rv;
if (!serverShake) {
rv = qcrypto_tls_session_handshake(serverSess,
&error_abort);
g_assert(rv >= 0);
if (qcrypto_tls_session_get_handshake_status(serverSess) ==
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
serverShake = true;
}
}
if (!clientShake) {
rv = qcrypto_tls_session_handshake(clientSess,
&error_abort);
g_assert(rv >= 0);
if (qcrypto_tls_session_get_handshake_status(clientSess) ==
QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
clientShake = true;
}
}
} while (!clientShake || !serverShake);
/* Finally make sure the server validation does what
* we were expecting
*/
if (qcrypto_tls_session_check_credentials(
serverSess, data->expectServerFail ? NULL : &error_abort) < 0) {
g_assert(data->expectServerFail);
} else {
g_assert(!data->expectServerFail);
}
/*
* And the same for the client validation check
*/
if (qcrypto_tls_session_check_credentials(
clientSess, data->expectClientFail ? NULL : &error_abort) < 0) {
g_assert(data->expectClientFail);
} else {
g_assert(!data->expectClientFail);
}
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
rmdir(CLIENT_CERT_DIR);
rmdir(SERVER_CERT_DIR);
object_unparent(OBJECT(serverCreds));
object_unparent(OBJECT(clientCreds));
object_unparent(OBJECT(auth));
qcrypto_tls_session_free(serverSess);
qcrypto_tls_session_free(clientSess);
close(channel[0]);
close(channel[1]);
}
int main(int argc, char **argv)
{
int ret;
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
g_setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
mkdir(WORKDIR, 0700);
test_tls_init(KEYFILE);
test_tls_psk_init(PSKFILE);
/* Simple initial test using Pre-Shared Keys. */
g_test_add_func("/qcrypto/tlssession/psk",
test_crypto_tls_session_psk);
/* More complex tests using X.509 certificates. */
# define TEST_SESS_REG(name, caCrt, \
serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards) \
struct QCryptoTLSSessionTestData name = { \
caCrt, caCrt, serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards \
}; \
g_test_add_data_func("/qcrypto/tlssession/" # name, \
&name, test_crypto_tls_session_x509); \
# define TEST_SESS_REG_EXT(name, serverCaCrt, clientCaCrt, \
serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards) \
struct QCryptoTLSSessionTestData name = { \
serverCaCrt, clientCaCrt, serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards \
}; \
g_test_add_data_func("/qcrypto/tlssession/" # name, \
&name, test_crypto_tls_session_x509); \
/* A perfect CA, perfect client & perfect server */
/* Basic:CA:critical */
TLS_ROOT_REQ(cacertreq,
"UK", "qemu CA", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_ROOT_REQ(altcacertreq,
"UK", "qemu CA 1", NULL, NULL, NULL, NULL,
true, true, true,
false, false, 0,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercertreq, cacertreq,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_CERT_REQ(clientcertreq, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
TLS_CERT_REQ(clientcertaltreq, altcacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
TEST_SESS_REG(basicca, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", NULL);
TEST_SESS_REG_EXT(differentca, cacertreq.filename,
altcacertreq.filename, servercertreq.filename,
clientcertaltreq.filename, true, true, "qemu.org", NULL);
/* When an altname is set, the CN is ignored, so it must be duplicated
* as an altname for it to match */
TLS_CERT_REQ(servercertalt1req, cacertreq,
"UK", "qemu.org", "www.qemu.org", "qemu.org",
"192.168.122.1", "fec0::dead:beaf",
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
/* This intentionally doesn't replicate */
TLS_CERT_REQ(servercertalt2req, cacertreq,
"UK", "qemu.org", "www.qemu.org", "wiki.qemu.org",
"192.168.122.1", "fec0::dead:beaf",
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TEST_SESS_REG(altname1, cacertreq.filename,
servercertalt1req.filename, clientcertreq.filename,
false, false, "qemu.org", NULL);
TEST_SESS_REG(altname2, cacertreq.filename,
servercertalt1req.filename, clientcertreq.filename,
false, false, "www.qemu.org", NULL);
TEST_SESS_REG(altname3, cacertreq.filename,
servercertalt1req.filename, clientcertreq.filename,
false, true, "wiki.qemu.org", NULL);
TEST_SESS_REG(altname4, cacertreq.filename,
servercertalt2req.filename, clientcertreq.filename,
false, true, "qemu.org", NULL);
TEST_SESS_REG(altname5, cacertreq.filename,
servercertalt2req.filename, clientcertreq.filename,
false, false, "www.qemu.org", NULL);
TEST_SESS_REG(altname6, cacertreq.filename,
servercertalt2req.filename, clientcertreq.filename,
false, false, "wiki.qemu.org", NULL);
const char *const wildcards1[] = {
"C=UK,CN=dogfood",
NULL,
};
const char *const wildcards2[] = {
"C=UK,CN=qemu",
NULL,
};
const char *const wildcards3[] = {
"C=UK,CN=dogfood",
"C=UK,CN=qemu",
NULL,
};
const char *const wildcards4[] = {
"C=UK,CN=qemustuff",
NULL,
};
const char *const wildcards5[] = {
"C=UK,CN=qemu*",
NULL,
};
const char *const wildcards6[] = {
"C=UK,CN=*emu*",
NULL,
};
TEST_SESS_REG(wildcard1, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
true, false, "qemu.org", wildcards1);
TEST_SESS_REG(wildcard2, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards2);
TEST_SESS_REG(wildcard3, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards3);
TEST_SESS_REG(wildcard4, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
true, false, "qemu.org", wildcards4);
TEST_SESS_REG(wildcard5, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards5);
TEST_SESS_REG(wildcard6, cacertreq.filename,
servercertreq.filename, clientcertreq.filename,
false, false, "qemu.org", wildcards6);
TLS_ROOT_REQ(cacertrootreq,
"UK", "qemu root", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
"UK", "qemu level 1a", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
"UK", "qemu level 1b", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
"UK", "qemu level 2a", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
"UK", "qemu client level 2b", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
gnutls_x509_crt_t certchain[] = {
cacertrootreq.crt,
cacertlevel1areq.crt,
cacertlevel1breq.crt,
cacertlevel2areq.crt,
};
test_tls_write_cert_chain(WORKDIR "cacertchain-sess.pem",
certchain,
G_N_ELEMENTS(certchain));
TEST_SESS_REG(cachain, WORKDIR "cacertchain-sess.pem",
servercertlevel3areq.filename, clientcertlevel2breq.filename,
false, false, "qemu.org", NULL);
ret = g_test_run();
test_tls_discard_cert(&clientcertreq);
test_tls_discard_cert(&clientcertaltreq);
test_tls_discard_cert(&servercertreq);
test_tls_discard_cert(&servercertalt1req);
test_tls_discard_cert(&servercertalt2req);
test_tls_discard_cert(&cacertreq);
test_tls_discard_cert(&altcacertreq);
test_tls_discard_cert(&cacertrootreq);
test_tls_discard_cert(&cacertlevel1areq);
test_tls_discard_cert(&cacertlevel1breq);
test_tls_discard_cert(&cacertlevel2areq);
test_tls_discard_cert(&servercertlevel3areq);
test_tls_discard_cert(&clientcertlevel2breq);
unlink(WORKDIR "cacertchain-sess.pem");
test_tls_psk_cleanup(PSKFILE);
test_tls_cleanup(KEYFILE);
rmdir(WORKDIR);
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
int
main(void)
{
return EXIT_SUCCESS;
}
#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */

View file

@ -0,0 +1,529 @@
/*
* QEMU Crypto XTS cipher mode
*
* Copyright (c) 2015-2018 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* This code is originally derived from public domain / WTFPL code in
* LibTomCrypt crytographic library http://libtom.org. The XTS code
* was donated by Elliptic Semiconductor Inc (www.ellipticsemi.com)
* to the LibTom Projects
*
*/
#include "qemu/osdep.h"
#include "crypto/init.h"
#include "crypto/xts.h"
#include "crypto/aes.h"
typedef struct {
const char *path;
int keylen;
unsigned char key1[32];
unsigned char key2[32];
uint64_t seqnum;
unsigned long PTLEN;
unsigned char PTX[512], CTX[512];
} QCryptoXTSTestData;
static const QCryptoXTSTestData test_data[] = {
/* #1 32 byte key, 32 byte PTX */
{
"/crypto/xts/t-1-key-32-ptx-32",
32,
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
0,
32,
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
{ 0x91, 0x7c, 0xf6, 0x9e, 0xbd, 0x68, 0xb2, 0xec,
0x9b, 0x9f, 0xe9, 0xa3, 0xea, 0xdd, 0xa6, 0x92,
0xcd, 0x43, 0xd2, 0xf5, 0x95, 0x98, 0xed, 0x85,
0x8c, 0x02, 0xc2, 0x65, 0x2f, 0xbf, 0x92, 0x2e },
},
/* #2, 32 byte key, 32 byte PTX */
{
"/crypto/xts/t-2-key-32-ptx-32",
32,
{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 },
{ 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22 },
0x3333333333LL,
32,
{ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 },
{ 0xc4, 0x54, 0x18, 0x5e, 0x6a, 0x16, 0x93, 0x6e,
0x39, 0x33, 0x40, 0x38, 0xac, 0xef, 0x83, 0x8b,
0xfb, 0x18, 0x6f, 0xff, 0x74, 0x80, 0xad, 0xc4,
0x28, 0x93, 0x82, 0xec, 0xd6, 0xd3, 0x94, 0xf0 },
},
/* #5 from xts.7, 32 byte key, 32 byte PTX */
{
"/crypto/xts/t-5-key-32-ptx-32",
32,
{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
{ 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
0x123456789aLL,
32,
{ 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44 },
{ 0xb0, 0x1f, 0x86, 0xf8, 0xed, 0xc1, 0x86, 0x37,
0x06, 0xfa, 0x8a, 0x42, 0x53, 0xe3, 0x4f, 0x28,
0xaf, 0x31, 0x9d, 0xe3, 0x83, 0x34, 0x87, 0x0f,
0x4d, 0xd1, 0xf9, 0x4c, 0xbe, 0x98, 0x32, 0xf1 },
},
/* #4, 32 byte key, 512 byte PTX */
{
"/crypto/xts/t-4-key-32-ptx-512",
32,
{ 0x27, 0x18, 0x28, 0x18, 0x28, 0x45, 0x90, 0x45,
0x23, 0x53, 0x60, 0x28, 0x74, 0x71, 0x35, 0x26 },
{ 0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x97, 0x93,
0x23, 0x84, 0x62, 0x64, 0x33, 0x83, 0x27, 0x95 },
0,
512,
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
},
{
0x27, 0xa7, 0x47, 0x9b, 0xef, 0xa1, 0xd4, 0x76,
0x48, 0x9f, 0x30, 0x8c, 0xd4, 0xcf, 0xa6, 0xe2,
0xa9, 0x6e, 0x4b, 0xbe, 0x32, 0x08, 0xff, 0x25,
0x28, 0x7d, 0xd3, 0x81, 0x96, 0x16, 0xe8, 0x9c,
0xc7, 0x8c, 0xf7, 0xf5, 0xe5, 0x43, 0x44, 0x5f,
0x83, 0x33, 0xd8, 0xfa, 0x7f, 0x56, 0x00, 0x00,
0x05, 0x27, 0x9f, 0xa5, 0xd8, 0xb5, 0xe4, 0xad,
0x40, 0xe7, 0x36, 0xdd, 0xb4, 0xd3, 0x54, 0x12,
0x32, 0x80, 0x63, 0xfd, 0x2a, 0xab, 0x53, 0xe5,
0xea, 0x1e, 0x0a, 0x9f, 0x33, 0x25, 0x00, 0xa5,
0xdf, 0x94, 0x87, 0xd0, 0x7a, 0x5c, 0x92, 0xcc,
0x51, 0x2c, 0x88, 0x66, 0xc7, 0xe8, 0x60, 0xce,
0x93, 0xfd, 0xf1, 0x66, 0xa2, 0x49, 0x12, 0xb4,
0x22, 0x97, 0x61, 0x46, 0xae, 0x20, 0xce, 0x84,
0x6b, 0xb7, 0xdc, 0x9b, 0xa9, 0x4a, 0x76, 0x7a,
0xae, 0xf2, 0x0c, 0x0d, 0x61, 0xad, 0x02, 0x65,
0x5e, 0xa9, 0x2d, 0xc4, 0xc4, 0xe4, 0x1a, 0x89,
0x52, 0xc6, 0x51, 0xd3, 0x31, 0x74, 0xbe, 0x51,
0xa1, 0x0c, 0x42, 0x11, 0x10, 0xe6, 0xd8, 0x15,
0x88, 0xed, 0xe8, 0x21, 0x03, 0xa2, 0x52, 0xd8,
0xa7, 0x50, 0xe8, 0x76, 0x8d, 0xef, 0xff, 0xed,
0x91, 0x22, 0x81, 0x0a, 0xae, 0xb9, 0x9f, 0x91,
0x72, 0xaf, 0x82, 0xb6, 0x04, 0xdc, 0x4b, 0x8e,
0x51, 0xbc, 0xb0, 0x82, 0x35, 0xa6, 0xf4, 0x34,
0x13, 0x32, 0xe4, 0xca, 0x60, 0x48, 0x2a, 0x4b,
0xa1, 0xa0, 0x3b, 0x3e, 0x65, 0x00, 0x8f, 0xc5,
0xda, 0x76, 0xb7, 0x0b, 0xf1, 0x69, 0x0d, 0xb4,
0xea, 0xe2, 0x9c, 0x5f, 0x1b, 0xad, 0xd0, 0x3c,
0x5c, 0xcf, 0x2a, 0x55, 0xd7, 0x05, 0xdd, 0xcd,
0x86, 0xd4, 0x49, 0x51, 0x1c, 0xeb, 0x7e, 0xc3,
0x0b, 0xf1, 0x2b, 0x1f, 0xa3, 0x5b, 0x91, 0x3f,
0x9f, 0x74, 0x7a, 0x8a, 0xfd, 0x1b, 0x13, 0x0e,
0x94, 0xbf, 0xf9, 0x4e, 0xff, 0xd0, 0x1a, 0x91,
0x73, 0x5c, 0xa1, 0x72, 0x6a, 0xcd, 0x0b, 0x19,
0x7c, 0x4e, 0x5b, 0x03, 0x39, 0x36, 0x97, 0xe1,
0x26, 0x82, 0x6f, 0xb6, 0xbb, 0xde, 0x8e, 0xcc,
0x1e, 0x08, 0x29, 0x85, 0x16, 0xe2, 0xc9, 0xed,
0x03, 0xff, 0x3c, 0x1b, 0x78, 0x60, 0xf6, 0xde,
0x76, 0xd4, 0xce, 0xcd, 0x94, 0xc8, 0x11, 0x98,
0x55, 0xef, 0x52, 0x97, 0xca, 0x67, 0xe9, 0xf3,
0xe7, 0xff, 0x72, 0xb1, 0xe9, 0x97, 0x85, 0xca,
0x0a, 0x7e, 0x77, 0x20, 0xc5, 0xb3, 0x6d, 0xc6,
0xd7, 0x2c, 0xac, 0x95, 0x74, 0xc8, 0xcb, 0xbc,
0x2f, 0x80, 0x1e, 0x23, 0xe5, 0x6f, 0xd3, 0x44,
0xb0, 0x7f, 0x22, 0x15, 0x4b, 0xeb, 0xa0, 0xf0,
0x8c, 0xe8, 0x89, 0x1e, 0x64, 0x3e, 0xd9, 0x95,
0xc9, 0x4d, 0x9a, 0x69, 0xc9, 0xf1, 0xb5, 0xf4,
0x99, 0x02, 0x7a, 0x78, 0x57, 0x2a, 0xee, 0xbd,
0x74, 0xd2, 0x0c, 0xc3, 0x98, 0x81, 0xc2, 0x13,
0xee, 0x77, 0x0b, 0x10, 0x10, 0xe4, 0xbe, 0xa7,
0x18, 0x84, 0x69, 0x77, 0xae, 0x11, 0x9f, 0x7a,
0x02, 0x3a, 0xb5, 0x8c, 0xca, 0x0a, 0xd7, 0x52,
0xaf, 0xe6, 0x56, 0xbb, 0x3c, 0x17, 0x25, 0x6a,
0x9f, 0x6e, 0x9b, 0xf1, 0x9f, 0xdd, 0x5a, 0x38,
0xfc, 0x82, 0xbb, 0xe8, 0x72, 0xc5, 0x53, 0x9e,
0xdb, 0x60, 0x9e, 0xf4, 0xf7, 0x9c, 0x20, 0x3e,
0xbb, 0x14, 0x0f, 0x2e, 0x58, 0x3c, 0xb2, 0xad,
0x15, 0xb4, 0xaa, 0x5b, 0x65, 0x50, 0x16, 0xa8,
0x44, 0x92, 0x77, 0xdb, 0xd4, 0x77, 0xef, 0x2c,
0x8d, 0x6c, 0x01, 0x7d, 0xb7, 0x38, 0xb1, 0x8d,
0xeb, 0x4a, 0x42, 0x7d, 0x19, 0x23, 0xce, 0x3f,
0xf2, 0x62, 0x73, 0x57, 0x79, 0xa4, 0x18, 0xf2,
0x0a, 0x28, 0x2d, 0xf9, 0x20, 0x14, 0x7b, 0xea,
0xbe, 0x42, 0x1e, 0xe5, 0x31, 0x9d, 0x05, 0x68,
}
},
/* #7, 32 byte key, 17 byte PTX */
{
"/crypto/xts/t-7-key-32-ptx-17",
32,
{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
{ 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
0x123456789aLL,
17,
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 },
{ 0x6c, 0x16, 0x25, 0xdb, 0x46, 0x71, 0x52, 0x2d,
0x3d, 0x75, 0x99, 0x60, 0x1d, 0xe7, 0xca, 0x09, 0xed },
},
/* #15, 32 byte key, 25 byte PTX */
{
"/crypto/xts/t-15-key-32-ptx-25",
32,
{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
{ 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
0x123456789aLL,
25,
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 },
{ 0x8f, 0x4d, 0xcb, 0xad, 0x55, 0x55, 0x8d, 0x7b,
0x4e, 0x01, 0xd9, 0x37, 0x9c, 0xd4, 0xea, 0x22,
0xed, 0xbf, 0x9d, 0xac, 0xe4, 0x5d, 0x6f, 0x6a, 0x73 },
},
/* #21, 32 byte key, 31 byte PTX */
{
"/crypto/xts/t-21-key-32-ptx-31",
32,
{ 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8,
0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0 },
{ 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8,
0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0 },
0x123456789aLL,
31,
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e },
{ 0xd0, 0x5b, 0xc0, 0x90, 0xa8, 0xe0, 0x4f, 0x1b,
0x3d, 0x3e, 0xcd, 0xd5, 0xba, 0xec, 0x0f, 0xd4,
0xed, 0xbf, 0x9d, 0xac, 0xe4, 0x5d, 0x6f, 0x6a,
0x73, 0x06, 0xe6, 0x4b, 0xe5, 0xdd, 0x82 },
},
};
#define STORE64L(x, y) \
do { \
(y)[7] = (unsigned char)(((x) >> 56) & 255); \
(y)[6] = (unsigned char)(((x) >> 48) & 255); \
(y)[5] = (unsigned char)(((x) >> 40) & 255); \
(y)[4] = (unsigned char)(((x) >> 32) & 255); \
(y)[3] = (unsigned char)(((x) >> 24) & 255); \
(y)[2] = (unsigned char)(((x) >> 16) & 255); \
(y)[1] = (unsigned char)(((x) >> 8) & 255); \
(y)[0] = (unsigned char)((x) & 255); \
} while (0)
struct TestAES {
AES_KEY enc;
AES_KEY dec;
};
static void test_xts_aes_encrypt(const void *ctx,
size_t length,
uint8_t *dst,
const uint8_t *src)
{
const struct TestAES *aesctx = ctx;
AES_encrypt(src, dst, &aesctx->enc);
}
static void test_xts_aes_decrypt(const void *ctx,
size_t length,
uint8_t *dst,
const uint8_t *src)
{
const struct TestAES *aesctx = ctx;
AES_decrypt(src, dst, &aesctx->dec);
}
static void test_xts(const void *opaque)
{
const QCryptoXTSTestData *data = opaque;
uint8_t out[512], Torg[16], T[16];
uint64_t seq;
struct TestAES aesdata;
struct TestAES aestweak;
AES_set_encrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.enc);
AES_set_decrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.dec);
AES_set_encrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.enc);
AES_set_decrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.dec);
seq = data->seqnum;
STORE64L(seq, Torg);
memset(Torg + 8, 0, 8);
memcpy(T, Torg, sizeof(T));
xts_encrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, data->PTLEN, out, data->PTX);
g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
memcpy(T, Torg, sizeof(T));
xts_decrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, data->PTLEN, out, data->CTX);
g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
}
static void test_xts_split(const void *opaque)
{
const QCryptoXTSTestData *data = opaque;
uint8_t out[512], Torg[16], T[16];
uint64_t seq;
unsigned long len = data->PTLEN / 2;
struct TestAES aesdata;
struct TestAES aestweak;
AES_set_encrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.enc);
AES_set_decrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.dec);
AES_set_encrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.enc);
AES_set_decrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.dec);
seq = data->seqnum;
STORE64L(seq, Torg);
memset(Torg + 8, 0, 8);
memcpy(T, Torg, sizeof(T));
xts_encrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, len, out, data->PTX);
xts_encrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, len, &out[len], &data->PTX[len]);
g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
memcpy(T, Torg, sizeof(T));
xts_decrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, len, out, data->CTX);
xts_decrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, len, &out[len], &data->CTX[len]);
g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
}
static void test_xts_unaligned(const void *opaque)
{
#define BAD_ALIGN 3
const QCryptoXTSTestData *data = opaque;
uint8_t in[512 + BAD_ALIGN], out[512 + BAD_ALIGN];
uint8_t Torg[16], T[16 + BAD_ALIGN];
uint64_t seq;
struct TestAES aesdata;
struct TestAES aestweak;
AES_set_encrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.enc);
AES_set_decrypt_key(data->key1, data->keylen / 2 * 8, &aesdata.dec);
AES_set_encrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.enc);
AES_set_decrypt_key(data->key2, data->keylen / 2 * 8, &aestweak.dec);
seq = data->seqnum;
STORE64L(seq, Torg);
memset(Torg + 8, 0, 8);
/* IV not aligned */
memcpy(T + BAD_ALIGN, Torg, 16);
memcpy(in, data->PTX, data->PTLEN);
xts_encrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T + BAD_ALIGN, data->PTLEN, out, in);
g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
/* plain text not aligned */
memcpy(T, Torg, 16);
memcpy(in + BAD_ALIGN, data->PTX, data->PTLEN);
xts_encrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, data->PTLEN, out, in + BAD_ALIGN);
g_assert(memcmp(out, data->CTX, data->PTLEN) == 0);
/* cipher text not aligned */
memcpy(T, Torg, 16);
memcpy(in, data->PTX, data->PTLEN);
xts_encrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, data->PTLEN, out + BAD_ALIGN, in);
g_assert(memcmp(out + BAD_ALIGN, data->CTX, data->PTLEN) == 0);
/* IV not aligned */
memcpy(T + BAD_ALIGN, Torg, 16);
memcpy(in, data->CTX, data->PTLEN);
xts_decrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T + BAD_ALIGN, data->PTLEN, out, in);
g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
/* cipher text not aligned */
memcpy(T, Torg, 16);
memcpy(in + BAD_ALIGN, data->CTX, data->PTLEN);
xts_decrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, data->PTLEN, out, in + BAD_ALIGN);
g_assert(memcmp(out, data->PTX, data->PTLEN) == 0);
/* plain text not aligned */
memcpy(T, Torg, 16);
memcpy(in, data->CTX, data->PTLEN);
xts_decrypt(&aesdata, &aestweak,
test_xts_aes_encrypt,
test_xts_aes_decrypt,
T, data->PTLEN, out + BAD_ALIGN, in);
g_assert(memcmp(out + BAD_ALIGN, data->PTX, data->PTLEN) == 0);
}
int main(int argc, char **argv)
{
size_t i;
g_test_init(&argc, &argv, NULL);
g_assert(qcrypto_init(NULL) == 0);
for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
gchar *path = g_strdup_printf("%s/basic", test_data[i].path);
g_test_add_data_func(path, &test_data[i], test_xts);
g_free(path);
/* skip the cases where the length is smaller than 2*blocklen
* or the length is not a multiple of 32
*/
if ((test_data[i].PTLEN >= 32) && !(test_data[i].PTLEN % 32)) {
path = g_strdup_printf("%s/split", test_data[i].path);
g_test_add_data_func(path, &test_data[i], test_xts_split);
g_free(path);
}
path = g_strdup_printf("%s/unaligned", test_data[i].path);
g_test_add_data_func(path, &test_data[i], test_xts_unaligned);
g_free(path);
}
return g_test_run();
}

2574
tests/unit/test-cutils.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* fdmon-epoll tests
*
* Copyright (c) 2020 Red Hat, Inc.
*/
#include "qemu/osdep.h"
#include "block/aio.h"
#include "qapi/error.h"
#include "qemu/main-loop.h"
static AioContext *ctx;
static void dummy_fd_handler(EventNotifier *notifier)
{
event_notifier_test_and_clear(notifier);
}
static void add_event_notifiers(EventNotifier *notifiers, size_t n)
{
for (size_t i = 0; i < n; i++) {
event_notifier_init(&notifiers[i], false);
aio_set_event_notifier(ctx, &notifiers[i], false,
dummy_fd_handler, NULL);
}
}
static void remove_event_notifiers(EventNotifier *notifiers, size_t n)
{
for (size_t i = 0; i < n; i++) {
aio_set_event_notifier(ctx, &notifiers[i], false, NULL, NULL);
event_notifier_cleanup(&notifiers[i]);
}
}
/* Check that fd handlers work when external clients are disabled */
static void test_external_disabled(void)
{
EventNotifier notifiers[100];
/* fdmon-epoll is only enabled when many fd handlers are registered */
add_event_notifiers(notifiers, G_N_ELEMENTS(notifiers));
event_notifier_set(&notifiers[0]);
assert(aio_poll(ctx, true));
aio_disable_external(ctx);
event_notifier_set(&notifiers[0]);
assert(aio_poll(ctx, true));
aio_enable_external(ctx);
remove_event_notifiers(notifiers, G_N_ELEMENTS(notifiers));
}
int main(int argc, char **argv)
{
/*
* This code relies on the fact that fdmon-io_uring disables itself when
* the glib main loop is in use. The main loop uses fdmon-poll and upgrades
* to fdmon-epoll when the number of fds exceeds a threshold.
*/
qemu_init_main_loop(&error_fatal);
ctx = qemu_get_aio_context();
while (g_main_context_iteration(NULL, false)) {
/* Do nothing */
}
g_test_init(&argc, &argv, NULL);
g_test_add_func("/fdmon-epoll/external-disabled", test_external_disabled);
return g_test_run();
}

1119
tests/unit/test-hbitmap.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
/*
* Image locking tests
*
* Copyright (c) 2018 Red Hat Inc.
*
* Author: Fam Zheng <famz@redhat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "block/block.h"
#include "sysemu/block-backend.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qemu/main-loop.h"
static BlockBackend *open_image(const char *path,
uint64_t perm, uint64_t shared_perm,
Error **errp)
{
Error *local_err = NULL;
BlockBackend *blk;
QDict *options = qdict_new();
qdict_put_str(options, "driver", "raw");
blk = blk_new_open(path, NULL, options, BDRV_O_RDWR, &local_err);
if (blk) {
g_assert_null(local_err);
if (blk_set_perm(blk, perm, shared_perm, errp)) {
blk_unref(blk);
blk = NULL;
}
} else {
error_propagate(errp, local_err);
}
return blk;
}
static void check_locked_bytes(int fd, uint64_t perm_locks,
uint64_t shared_perm_locks)
{
int i;
if (!perm_locks && !shared_perm_locks) {
g_assert(!qemu_lock_fd_test(fd, 0, 0, true));
return;
}
for (i = 0; (1ULL << i) <= BLK_PERM_ALL; i++) {
uint64_t bit = (1ULL << i);
bool perm_expected = !!(bit & perm_locks);
bool shared_perm_expected = !!(bit & shared_perm_locks);
g_assert_cmpint(perm_expected, ==,
!!qemu_lock_fd_test(fd, 100 + i, 1, true));
g_assert_cmpint(shared_perm_expected, ==,
!!qemu_lock_fd_test(fd, 200 + i, 1, true));
}
}
static void test_image_locking_basic(void)
{
BlockBackend *blk1, *blk2, *blk3;
char img_path[] = "/tmp/qtest.XXXXXX";
uint64_t perm, shared_perm;
int fd = mkstemp(img_path);
assert(fd >= 0);
perm = BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ;
shared_perm = BLK_PERM_ALL;
blk1 = open_image(img_path, perm, shared_perm, &error_abort);
g_assert(blk1);
check_locked_bytes(fd, perm, ~shared_perm);
/* compatible perm between blk1 and blk2 */
blk2 = open_image(img_path, perm | BLK_PERM_RESIZE, shared_perm, NULL);
g_assert(blk2);
check_locked_bytes(fd, perm | BLK_PERM_RESIZE, ~shared_perm);
/* incompatible perm with already open blk1 and blk2 */
blk3 = open_image(img_path, perm, BLK_PERM_WRITE_UNCHANGED, NULL);
g_assert_null(blk3);
blk_unref(blk2);
/* Check that extra bytes in blk2 are correctly unlocked */
check_locked_bytes(fd, perm, ~shared_perm);
blk_unref(blk1);
/* Image is unused, no lock there */
check_locked_bytes(fd, 0, 0);
blk3 = open_image(img_path, perm, BLK_PERM_WRITE_UNCHANGED, &error_abort);
g_assert(blk3);
blk_unref(blk3);
close(fd);
unlink(img_path);
}
static void test_set_perm_abort(void)
{
BlockBackend *blk1, *blk2;
char img_path[] = "/tmp/qtest.XXXXXX";
uint64_t perm, shared_perm;
int r;
int fd = mkstemp(img_path);
assert(fd >= 0);
perm = BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ;
shared_perm = BLK_PERM_ALL;
blk1 = open_image(img_path, perm, shared_perm, &error_abort);
g_assert(blk1);
blk2 = open_image(img_path, perm, shared_perm, &error_abort);
g_assert(blk2);
check_locked_bytes(fd, perm, ~shared_perm);
/* A failed blk_set_perm mustn't change perm status (locked bytes) */
r = blk_set_perm(blk2, perm | BLK_PERM_RESIZE, BLK_PERM_WRITE_UNCHANGED,
NULL);
g_assert_cmpint(r, !=, 0);
check_locked_bytes(fd, perm, ~shared_perm);
blk_unref(blk1);
blk_unref(blk2);
}
int main(int argc, char **argv)
{
bdrv_init();
qemu_init_main_loop(&error_abort);
g_test_init(&argc, &argv, NULL);
if (qemu_has_ofd_lock()) {
g_test_add_func("/image-locking/basic", test_image_locking_basic);
g_test_add_func("/image-locking/set-perm-abort", test_set_perm_abort);
}
return g_test_run();
}

223
tests/unit/test-int128.c Normal file
View file

@ -0,0 +1,223 @@
/*
* Test Int128 arithmetic
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/int128.h"
/* clang doesn't support __noclone__ but it does have a mechanism for
* telling us this. We assume that if we don't have __has_attribute()
* then this is GCC and that GCC always supports __noclone__.
*/
#if defined(__has_attribute)
#if !__has_attribute(__noclone__)
#define ATTRIBUTE_NOCLONE
#endif
#endif
#ifndef ATTRIBUTE_NOCLONE
#define ATTRIBUTE_NOCLONE __attribute__((__noclone__))
#endif
static uint32_t tests[8] = {
0x00000000, 0x00000001, 0x7FFFFFFE, 0x7FFFFFFF,
0x80000000, 0x80000001, 0xFFFFFFFE, 0xFFFFFFFF,
};
#define LOW 3ULL
#define HIGH (1ULL << 63)
#define MIDDLE (-1ULL & ~LOW & ~HIGH)
static uint64_t expand16(unsigned x)
{
return (x & LOW) | ((x & 4) ? MIDDLE : 0) | (x & 0x8000 ? HIGH : 0);
}
static Int128 expand(uint32_t x)
{
uint64_t l, h;
l = expand16(x & 65535);
h = expand16(x >> 16);
return (Int128) int128_make128(l, h);
};
static void test_and(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
Int128 a = expand(tests[i]);
Int128 b = expand(tests[j]);
Int128 r = expand(tests[i] & tests[j]);
Int128 s = int128_and(a, b);
g_assert_cmpuint(int128_getlo(r), ==, int128_getlo(s));
g_assert_cmpuint(int128_gethi(r), ==, int128_gethi(s));
}
}
}
static void test_add(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
Int128 a = expand(tests[i]);
Int128 b = expand(tests[j]);
Int128 r = expand(tests[i] + tests[j]);
Int128 s = int128_add(a, b);
g_assert_cmpuint(int128_getlo(r), ==, int128_getlo(s));
g_assert_cmpuint(int128_gethi(r), ==, int128_gethi(s));
}
}
}
static void test_sub(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
Int128 a = expand(tests[i]);
Int128 b = expand(tests[j]);
Int128 r = expand(tests[i] - tests[j]);
Int128 s = int128_sub(a, b);
g_assert_cmpuint(int128_getlo(r), ==, int128_getlo(s));
g_assert_cmpuint(int128_gethi(r), ==, int128_gethi(s));
}
}
}
static void test_neg(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
Int128 a = expand(tests[i]);
Int128 r = expand(-tests[i]);
Int128 s = int128_neg(a);
g_assert_cmpuint(int128_getlo(r), ==, int128_getlo(s));
g_assert_cmpuint(int128_gethi(r), ==, int128_gethi(s));
}
}
static void test_nz(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
Int128 a = expand(tests[i]);
g_assert_cmpuint(int128_nz(a), ==, tests[i] != 0);
}
}
}
static void test_le(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
/* Signed comparison */
int32_t a = (int32_t) tests[i];
int32_t b = (int32_t) tests[j];
g_assert_cmpuint(int128_le(expand(a), expand(b)), ==, a <= b);
}
}
}
static void test_lt(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
/* Signed comparison */
int32_t a = (int32_t) tests[i];
int32_t b = (int32_t) tests[j];
g_assert_cmpuint(int128_lt(expand(a), expand(b)), ==, a < b);
}
}
}
static void test_ge(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
/* Signed comparison */
int32_t a = (int32_t) tests[i];
int32_t b = (int32_t) tests[j];
g_assert_cmpuint(int128_ge(expand(a), expand(b)), ==, a >= b);
}
}
}
static void test_gt(void)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(tests); ++i) {
for (j = 0; j < ARRAY_SIZE(tests); ++j) {
/* Signed comparison */
int32_t a = (int32_t) tests[i];
int32_t b = (int32_t) tests[j];
g_assert_cmpuint(int128_gt(expand(a), expand(b)), ==, a > b);
}
}
}
/* Make sure to test undefined behavior at runtime! */
static void __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
test_rshift_one(uint32_t x, int n, uint64_t h, uint64_t l)
{
Int128 a = expand(x);
Int128 r = int128_rshift(a, n);
g_assert_cmpuint(int128_getlo(r), ==, l);
g_assert_cmpuint(int128_gethi(r), ==, h);
}
static void test_rshift(void)
{
test_rshift_one(0x00010000U, 64, 0x0000000000000000ULL, 0x0000000000000001ULL);
test_rshift_one(0x80010000U, 64, 0xFFFFFFFFFFFFFFFFULL, 0x8000000000000001ULL);
test_rshift_one(0x7FFE0000U, 64, 0x0000000000000000ULL, 0x7FFFFFFFFFFFFFFEULL);
test_rshift_one(0xFFFE0000U, 64, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFFEULL);
test_rshift_one(0x00010000U, 60, 0x0000000000000000ULL, 0x0000000000000010ULL);
test_rshift_one(0x80010000U, 60, 0xFFFFFFFFFFFFFFF8ULL, 0x0000000000000010ULL);
test_rshift_one(0x00018000U, 60, 0x0000000000000000ULL, 0x0000000000000018ULL);
test_rshift_one(0x80018000U, 60, 0xFFFFFFFFFFFFFFF8ULL, 0x0000000000000018ULL);
test_rshift_one(0x7FFE0000U, 60, 0x0000000000000007ULL, 0xFFFFFFFFFFFFFFE0ULL);
test_rshift_one(0xFFFE0000U, 60, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFE0ULL);
test_rshift_one(0x7FFE8000U, 60, 0x0000000000000007ULL, 0xFFFFFFFFFFFFFFE8ULL);
test_rshift_one(0xFFFE8000U, 60, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFE8ULL);
test_rshift_one(0x00018000U, 0, 0x0000000000000001ULL, 0x8000000000000000ULL);
test_rshift_one(0x80018000U, 0, 0x8000000000000001ULL, 0x8000000000000000ULL);
test_rshift_one(0x7FFE0000U, 0, 0x7FFFFFFFFFFFFFFEULL, 0x0000000000000000ULL);
test_rshift_one(0xFFFE0000U, 0, 0xFFFFFFFFFFFFFFFEULL, 0x0000000000000000ULL);
test_rshift_one(0x7FFE8000U, 0, 0x7FFFFFFFFFFFFFFEULL, 0x8000000000000000ULL);
test_rshift_one(0xFFFE8000U, 0, 0xFFFFFFFFFFFFFFFEULL, 0x8000000000000000ULL);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/int128/int128_and", test_and);
g_test_add_func("/int128/int128_add", test_add);
g_test_add_func("/int128/int128_sub", test_sub);
g_test_add_func("/int128/int128_neg", test_neg);
g_test_add_func("/int128/int128_nz", test_nz);
g_test_add_func("/int128/int128_le", test_le);
g_test_add_func("/int128/int128_lt", test_lt);
g_test_add_func("/int128/int128_ge", test_ge);
g_test_add_func("/int128/int128_gt", test_gt);
g_test_add_func("/int128/int128_rshift", test_rshift);
return g_test_run();
}

View file

@ -0,0 +1,52 @@
/*
* QEMU I/O channel buffer test
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "io/channel-buffer.h"
#include "qemu/module.h"
#include "io-channel-helpers.h"
static void test_io_channel_buf(void)
{
QIOChannelBuffer *buf;
QIOChannelTest *test;
buf = qio_channel_buffer_new(0);
test = qio_channel_test_new();
qio_channel_test_run_writer(test, QIO_CHANNEL(buf));
buf->offset = 0;
qio_channel_test_run_reader(test, QIO_CHANNEL(buf));
qio_channel_test_validate(test);
object_unref(OBJECT(buf));
}
int main(int argc, char **argv)
{
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/io/channel/buf", test_io_channel_buf);
return g_test_run();
}

View file

@ -0,0 +1,130 @@
/*
* QEMU I/O channel command test
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "io/channel-command.h"
#include "io-channel-helpers.h"
#include "qapi/error.h"
#include "qemu/module.h"
#ifndef WIN32
static void test_io_channel_command_fifo(bool async)
{
#define TEST_FIFO "tests/test-io-channel-command.fifo"
QIOChannel *src, *dst;
QIOChannelTest *test;
const char *srcfifo = "PIPE:" TEST_FIFO ",wronly";
const char *dstfifo = "PIPE:" TEST_FIFO ",rdonly";
const char *srcargv[] = {
"/bin/socat", "-", srcfifo, NULL,
};
const char *dstargv[] = {
"/bin/socat", dstfifo, "-", NULL,
};
unlink(TEST_FIFO);
if (access("/bin/socat", X_OK) < 0) {
return; /* Pretend success if socat is not present */
}
if (mkfifo(TEST_FIFO, 0600) < 0) {
abort();
}
src = QIO_CHANNEL(qio_channel_command_new_spawn(srcargv,
O_WRONLY,
&error_abort));
dst = QIO_CHANNEL(qio_channel_command_new_spawn(dstargv,
O_RDONLY,
&error_abort));
test = qio_channel_test_new();
qio_channel_test_run_threads(test, async, src, dst);
qio_channel_test_validate(test);
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
unlink(TEST_FIFO);
}
static void test_io_channel_command_fifo_async(void)
{
test_io_channel_command_fifo(true);
}
static void test_io_channel_command_fifo_sync(void)
{
test_io_channel_command_fifo(false);
}
static void test_io_channel_command_echo(bool async)
{
QIOChannel *ioc;
QIOChannelTest *test;
const char *socatargv[] = {
"/bin/socat", "-", "-", NULL,
};
if (access("/bin/socat", X_OK) < 0) {
return; /* Pretend success if socat is not present */
}
ioc = QIO_CHANNEL(qio_channel_command_new_spawn(socatargv,
O_RDWR,
&error_abort));
test = qio_channel_test_new();
qio_channel_test_run_threads(test, async, ioc, ioc);
qio_channel_test_validate(test);
object_unref(OBJECT(ioc));
}
static void test_io_channel_command_echo_async(void)
{
test_io_channel_command_echo(true);
}
static void test_io_channel_command_echo_sync(void)
{
test_io_channel_command_echo(false);
}
#endif
int main(int argc, char **argv)
{
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
#ifndef WIN32
g_test_add_func("/io/channel/command/fifo/sync",
test_io_channel_command_fifo_sync);
g_test_add_func("/io/channel/command/fifo/async",
test_io_channel_command_fifo_async);
g_test_add_func("/io/channel/command/echo/sync",
test_io_channel_command_echo_sync);
g_test_add_func("/io/channel/command/echo/async",
test_io_channel_command_echo_async);
#endif
return g_test_run();
}

View file

@ -0,0 +1,155 @@
/*
* QEMU I/O channel file test
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "io/channel-file.h"
#include "io/channel-util.h"
#include "io-channel-helpers.h"
#include "qapi/error.h"
#include "qemu/module.h"
#define TEST_FILE "tests/test-io-channel-file.txt"
#define TEST_MASK 0600
/*
* On Windows the stat() function in the C library checks only
* the FAT-style READONLY attribute and does not look at the ACL at all.
*/
#ifdef _WIN32
#define TEST_MASK_EXPECT 0700
#else
#define TEST_MASK_EXPECT 0777
#endif
static void test_io_channel_file_helper(int flags)
{
QIOChannel *src, *dst;
QIOChannelTest *test;
struct stat st;
mode_t mask;
int ret;
unlink(TEST_FILE);
src = QIO_CHANNEL(qio_channel_file_new_path(
TEST_FILE,
flags, TEST_MASK,
&error_abort));
dst = QIO_CHANNEL(qio_channel_file_new_path(
TEST_FILE,
O_RDONLY | O_BINARY, 0,
&error_abort));
test = qio_channel_test_new();
qio_channel_test_run_writer(test, src);
qio_channel_test_run_reader(test, dst);
qio_channel_test_validate(test);
/* Check that the requested mode took effect. */
mask = umask(0);
umask(mask);
ret = stat(TEST_FILE, &st);
g_assert_cmpint(ret, >, -1);
g_assert_cmpuint(TEST_MASK & ~mask, ==, st.st_mode & TEST_MASK_EXPECT);
unlink(TEST_FILE);
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
}
static void test_io_channel_file(void)
{
test_io_channel_file_helper(O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
}
static void test_io_channel_file_rdwr(void)
{
test_io_channel_file_helper(O_RDWR | O_CREAT | O_TRUNC | O_BINARY);
}
static void test_io_channel_fd(void)
{
QIOChannel *ioc;
int fd = -1;
fd = open(TEST_FILE, O_CREAT | O_TRUNC | O_WRONLY, 0600);
g_assert_cmpint(fd, >, -1);
ioc = qio_channel_new_fd(fd, &error_abort);
g_assert_cmpstr(object_get_typename(OBJECT(ioc)),
==,
TYPE_QIO_CHANNEL_FILE);
unlink(TEST_FILE);
object_unref(OBJECT(ioc));
}
#ifndef _WIN32
static void test_io_channel_pipe(bool async)
{
QIOChannel *src, *dst;
QIOChannelTest *test;
int fd[2];
if (pipe(fd) < 0) {
perror("pipe");
abort();
}
src = QIO_CHANNEL(qio_channel_file_new_fd(fd[1]));
dst = QIO_CHANNEL(qio_channel_file_new_fd(fd[0]));
test = qio_channel_test_new();
qio_channel_test_run_threads(test, async, src, dst);
qio_channel_test_validate(test);
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
}
static void test_io_channel_pipe_async(void)
{
test_io_channel_pipe(true);
}
static void test_io_channel_pipe_sync(void)
{
test_io_channel_pipe(false);
}
#endif /* ! _WIN32 */
int main(int argc, char **argv)
{
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/io/channel/file", test_io_channel_file);
g_test_add_func("/io/channel/file/rdwr", test_io_channel_file_rdwr);
g_test_add_func("/io/channel/file/fd", test_io_channel_fd);
#ifndef _WIN32
g_test_add_func("/io/channel/pipe/sync", test_io_channel_pipe_sync);
g_test_add_func("/io/channel/pipe/async", test_io_channel_pipe_async);
#endif
return g_test_run();
}

View file

@ -0,0 +1,603 @@
/*
* QEMU I/O channel sockets test
*
* Copyright (c) 2015-2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "io/channel-socket.h"
#include "io/channel-util.h"
#include "io-channel-helpers.h"
#include "socket-helpers.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "qemu/main-loop.h"
static void test_io_channel_set_socket_bufs(QIOChannel *src,
QIOChannel *dst)
{
int buflen = 64 * 1024;
/*
* Make the socket buffers small so that we see
* the effects of partial reads/writes
*/
setsockopt(((QIOChannelSocket *)src)->fd,
SOL_SOCKET, SO_SNDBUF,
(char *)&buflen,
sizeof(buflen));
setsockopt(((QIOChannelSocket *)dst)->fd,
SOL_SOCKET, SO_SNDBUF,
(char *)&buflen,
sizeof(buflen));
}
static void test_io_channel_setup_sync(SocketAddress *listen_addr,
SocketAddress *connect_addr,
QIOChannel **srv,
QIOChannel **src,
QIOChannel **dst)
{
QIOChannelSocket *lioc;
lioc = qio_channel_socket_new();
qio_channel_socket_listen_sync(lioc, listen_addr, 1, &error_abort);
if (listen_addr->type == SOCKET_ADDRESS_TYPE_INET) {
SocketAddress *laddr = qio_channel_socket_get_local_address(
lioc, &error_abort);
g_free(connect_addr->u.inet.port);
connect_addr->u.inet.port = g_strdup(laddr->u.inet.port);
qapi_free_SocketAddress(laddr);
}
*src = QIO_CHANNEL(qio_channel_socket_new());
qio_channel_socket_connect_sync(
QIO_CHANNEL_SOCKET(*src), connect_addr, &error_abort);
qio_channel_set_delay(*src, false);
qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN);
*dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
g_assert(*dst);
test_io_channel_set_socket_bufs(*src, *dst);
*srv = QIO_CHANNEL(lioc);
}
struct TestIOChannelData {
bool err;
GMainLoop *loop;
};
static void test_io_channel_complete(QIOTask *task,
gpointer opaque)
{
struct TestIOChannelData *data = opaque;
data->err = qio_task_propagate_error(task, NULL);
g_main_loop_quit(data->loop);
}
static void test_io_channel_setup_async(SocketAddress *listen_addr,
SocketAddress *connect_addr,
QIOChannel **srv,
QIOChannel **src,
QIOChannel **dst)
{
QIOChannelSocket *lioc;
struct TestIOChannelData data;
data.loop = g_main_loop_new(g_main_context_default(),
TRUE);
lioc = qio_channel_socket_new();
qio_channel_socket_listen_async(
lioc, listen_addr, 1,
test_io_channel_complete, &data, NULL, NULL);
g_main_loop_run(data.loop);
g_main_context_iteration(g_main_context_default(), FALSE);
g_assert(!data.err);
if (listen_addr->type == SOCKET_ADDRESS_TYPE_INET) {
SocketAddress *laddr = qio_channel_socket_get_local_address(
lioc, &error_abort);
g_free(connect_addr->u.inet.port);
connect_addr->u.inet.port = g_strdup(laddr->u.inet.port);
qapi_free_SocketAddress(laddr);
}
*src = QIO_CHANNEL(qio_channel_socket_new());
qio_channel_socket_connect_async(
QIO_CHANNEL_SOCKET(*src), connect_addr,
test_io_channel_complete, &data, NULL, NULL);
g_main_loop_run(data.loop);
g_main_context_iteration(g_main_context_default(), FALSE);
g_assert(!data.err);
qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN);
*dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
g_assert(*dst);
qio_channel_set_delay(*src, false);
test_io_channel_set_socket_bufs(*src, *dst);
*srv = QIO_CHANNEL(lioc);
g_main_loop_unref(data.loop);
}
static void test_io_channel_socket_path_exists(SocketAddress *addr,
bool expectExists)
{
if (addr->type != SOCKET_ADDRESS_TYPE_UNIX) {
return;
}
g_assert(g_file_test(addr->u.q_unix.path,
G_FILE_TEST_EXISTS) == expectExists);
}
static void test_io_channel(bool async,
SocketAddress *listen_addr,
SocketAddress *connect_addr,
bool passFD)
{
QIOChannel *src, *dst, *srv;
QIOChannelTest *test;
if (async) {
test_io_channel_setup_async(listen_addr, connect_addr,
&srv, &src, &dst);
g_assert(!passFD ||
qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(!passFD ||
qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_SHUTDOWN));
g_assert(qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_SHUTDOWN));
test_io_channel_socket_path_exists(listen_addr, true);
test = qio_channel_test_new();
qio_channel_test_run_threads(test, true, src, dst);
qio_channel_test_validate(test);
test_io_channel_socket_path_exists(listen_addr, true);
/* unref without close, to ensure finalize() cleans up */
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
test_io_channel_socket_path_exists(listen_addr, true);
object_unref(OBJECT(srv));
test_io_channel_socket_path_exists(listen_addr, false);
test_io_channel_setup_async(listen_addr, connect_addr,
&srv, &src, &dst);
g_assert(!passFD ||
qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(!passFD ||
qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_SHUTDOWN));
g_assert(qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_SHUTDOWN));
test = qio_channel_test_new();
qio_channel_test_run_threads(test, false, src, dst);
qio_channel_test_validate(test);
/* close before unref, to ensure finalize copes with already closed */
qio_channel_close(src, &error_abort);
qio_channel_close(dst, &error_abort);
test_io_channel_socket_path_exists(listen_addr, true);
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
test_io_channel_socket_path_exists(listen_addr, true);
qio_channel_close(srv, &error_abort);
test_io_channel_socket_path_exists(listen_addr, false);
object_unref(OBJECT(srv));
test_io_channel_socket_path_exists(listen_addr, false);
} else {
test_io_channel_setup_sync(listen_addr, connect_addr,
&srv, &src, &dst);
g_assert(!passFD ||
qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(!passFD ||
qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_SHUTDOWN));
g_assert(qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_SHUTDOWN));
test_io_channel_socket_path_exists(listen_addr, true);
test = qio_channel_test_new();
qio_channel_test_run_threads(test, true, src, dst);
qio_channel_test_validate(test);
test_io_channel_socket_path_exists(listen_addr, true);
/* unref without close, to ensure finalize() cleans up */
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
test_io_channel_socket_path_exists(listen_addr, true);
object_unref(OBJECT(srv));
test_io_channel_socket_path_exists(listen_addr, false);
test_io_channel_setup_sync(listen_addr, connect_addr,
&srv, &src, &dst);
g_assert(!passFD ||
qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(!passFD ||
qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_SHUTDOWN));
g_assert(qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_SHUTDOWN));
test = qio_channel_test_new();
qio_channel_test_run_threads(test, false, src, dst);
qio_channel_test_validate(test);
test_io_channel_socket_path_exists(listen_addr, true);
/* close before unref, to ensure finalize copes with already closed */
qio_channel_close(src, &error_abort);
qio_channel_close(dst, &error_abort);
test_io_channel_socket_path_exists(listen_addr, true);
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
test_io_channel_socket_path_exists(listen_addr, true);
qio_channel_close(srv, &error_abort);
test_io_channel_socket_path_exists(listen_addr, false);
object_unref(OBJECT(srv));
test_io_channel_socket_path_exists(listen_addr, false);
}
}
static void test_io_channel_ipv4(bool async)
{
SocketAddress *listen_addr = g_new0(SocketAddress, 1);
SocketAddress *connect_addr = g_new0(SocketAddress, 1);
listen_addr->type = SOCKET_ADDRESS_TYPE_INET;
listen_addr->u.inet = (InetSocketAddress) {
.host = g_strdup("127.0.0.1"),
.port = NULL, /* Auto-select */
};
connect_addr->type = SOCKET_ADDRESS_TYPE_INET;
connect_addr->u.inet = (InetSocketAddress) {
.host = g_strdup("127.0.0.1"),
.port = NULL, /* Filled in later */
};
test_io_channel(async, listen_addr, connect_addr, false);
qapi_free_SocketAddress(listen_addr);
qapi_free_SocketAddress(connect_addr);
}
static void test_io_channel_ipv4_sync(void)
{
return test_io_channel_ipv4(false);
}
static void test_io_channel_ipv4_async(void)
{
return test_io_channel_ipv4(true);
}
static void test_io_channel_ipv6(bool async)
{
SocketAddress *listen_addr = g_new0(SocketAddress, 1);
SocketAddress *connect_addr = g_new0(SocketAddress, 1);
listen_addr->type = SOCKET_ADDRESS_TYPE_INET;
listen_addr->u.inet = (InetSocketAddress) {
.host = g_strdup("::1"),
.port = NULL, /* Auto-select */
};
connect_addr->type = SOCKET_ADDRESS_TYPE_INET;
connect_addr->u.inet = (InetSocketAddress) {
.host = g_strdup("::1"),
.port = NULL, /* Filled in later */
};
test_io_channel(async, listen_addr, connect_addr, false);
qapi_free_SocketAddress(listen_addr);
qapi_free_SocketAddress(connect_addr);
}
static void test_io_channel_ipv6_sync(void)
{
return test_io_channel_ipv6(false);
}
static void test_io_channel_ipv6_async(void)
{
return test_io_channel_ipv6(true);
}
#ifndef _WIN32
static void test_io_channel_unix(bool async)
{
SocketAddress *listen_addr = g_new0(SocketAddress, 1);
SocketAddress *connect_addr = g_new0(SocketAddress, 1);
#define TEST_SOCKET "test-io-channel-socket.sock"
listen_addr->type = SOCKET_ADDRESS_TYPE_UNIX;
listen_addr->u.q_unix.path = g_strdup(TEST_SOCKET);
connect_addr->type = SOCKET_ADDRESS_TYPE_UNIX;
connect_addr->u.q_unix.path = g_strdup(TEST_SOCKET);
test_io_channel(async, listen_addr, connect_addr, true);
qapi_free_SocketAddress(listen_addr);
qapi_free_SocketAddress(connect_addr);
}
static void test_io_channel_unix_sync(void)
{
return test_io_channel_unix(false);
}
static void test_io_channel_unix_async(void)
{
return test_io_channel_unix(true);
}
static void test_io_channel_unix_fd_pass(void)
{
SocketAddress *listen_addr = g_new0(SocketAddress, 1);
SocketAddress *connect_addr = g_new0(SocketAddress, 1);
QIOChannel *src, *dst, *srv;
int testfd;
int fdsend[3];
int *fdrecv = NULL;
size_t nfdrecv = 0;
size_t i;
char bufsend[12], bufrecv[12];
struct iovec iosend[1], iorecv[1];
#define TEST_SOCKET "test-io-channel-socket.sock"
#define TEST_FILE "test-io-channel-socket.txt"
testfd = open(TEST_FILE, O_RDWR|O_TRUNC|O_CREAT, 0700);
g_assert(testfd != -1);
fdsend[0] = testfd;
fdsend[1] = testfd;
fdsend[2] = testfd;
listen_addr->type = SOCKET_ADDRESS_TYPE_UNIX;
listen_addr->u.q_unix.path = g_strdup(TEST_SOCKET);
connect_addr->type = SOCKET_ADDRESS_TYPE_UNIX;
connect_addr->u.q_unix.path = g_strdup(TEST_SOCKET);
test_io_channel_setup_sync(listen_addr, connect_addr, &srv, &src, &dst);
memcpy(bufsend, "Hello World", G_N_ELEMENTS(bufsend));
iosend[0].iov_base = bufsend;
iosend[0].iov_len = G_N_ELEMENTS(bufsend);
iorecv[0].iov_base = bufrecv;
iorecv[0].iov_len = G_N_ELEMENTS(bufrecv);
g_assert(qio_channel_has_feature(src, QIO_CHANNEL_FEATURE_FD_PASS));
g_assert(qio_channel_has_feature(dst, QIO_CHANNEL_FEATURE_FD_PASS));
qio_channel_writev_full(src,
iosend,
G_N_ELEMENTS(iosend),
fdsend,
G_N_ELEMENTS(fdsend),
&error_abort);
qio_channel_readv_full(dst,
iorecv,
G_N_ELEMENTS(iorecv),
&fdrecv,
&nfdrecv,
&error_abort);
g_assert(nfdrecv == G_N_ELEMENTS(fdsend));
/* Each recvd FD should be different from sent FD */
for (i = 0; i < nfdrecv; i++) {
g_assert_cmpint(fdrecv[i], !=, testfd);
}
/* Each recvd FD should be different from each other */
g_assert_cmpint(fdrecv[0], !=, fdrecv[1]);
g_assert_cmpint(fdrecv[0], !=, fdrecv[2]);
g_assert_cmpint(fdrecv[1], !=, fdrecv[2]);
/* Check the I/O buf we sent at the same time matches */
g_assert(memcmp(bufsend, bufrecv, G_N_ELEMENTS(bufsend)) == 0);
/* Write some data into the FD we received */
g_assert(write(fdrecv[0], bufsend, G_N_ELEMENTS(bufsend)) ==
G_N_ELEMENTS(bufsend));
/* Read data from the original FD and make sure it matches */
memset(bufrecv, 0, G_N_ELEMENTS(bufrecv));
g_assert(lseek(testfd, 0, SEEK_SET) == 0);
g_assert(read(testfd, bufrecv, G_N_ELEMENTS(bufrecv)) ==
G_N_ELEMENTS(bufrecv));
g_assert(memcmp(bufsend, bufrecv, G_N_ELEMENTS(bufsend)) == 0);
object_unref(OBJECT(src));
object_unref(OBJECT(dst));
object_unref(OBJECT(srv));
qapi_free_SocketAddress(listen_addr);
qapi_free_SocketAddress(connect_addr);
unlink(TEST_SOCKET);
unlink(TEST_FILE);
close(testfd);
for (i = 0; i < nfdrecv; i++) {
close(fdrecv[i]);
}
g_free(fdrecv);
}
static void test_io_channel_unix_listen_cleanup(void)
{
QIOChannelSocket *ioc;
struct sockaddr_un un;
int sock;
#define TEST_SOCKET "test-io-channel-socket.sock"
ioc = qio_channel_socket_new();
/* Manually bind ioc without calling the qio api to avoid setting
* the LISTEN feature */
sock = qemu_socket(PF_UNIX, SOCK_STREAM, 0);
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
snprintf(un.sun_path, sizeof(un.sun_path), "%s", TEST_SOCKET);
unlink(TEST_SOCKET);
bind(sock, (struct sockaddr *)&un, sizeof(un));
ioc->fd = sock;
ioc->localAddrLen = sizeof(ioc->localAddr);
getsockname(sock, (struct sockaddr *)&ioc->localAddr,
&ioc->localAddrLen);
g_assert(g_file_test(TEST_SOCKET, G_FILE_TEST_EXISTS));
object_unref(OBJECT(ioc));
g_assert(g_file_test(TEST_SOCKET, G_FILE_TEST_EXISTS));
unlink(TEST_SOCKET);
}
#endif /* _WIN32 */
static void test_io_channel_ipv4_fd(void)
{
QIOChannel *ioc;
int fd = -1;
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_addr = {
.s_addr = htonl(INADDR_LOOPBACK),
}
/* Leave port unset for auto-assign */
};
socklen_t salen = sizeof(sa);
fd = socket(AF_INET, SOCK_STREAM, 0);
g_assert_cmpint(fd, >, -1);
g_assert_cmpint(bind(fd, (struct sockaddr *)&sa, salen), ==, 0);
ioc = qio_channel_new_fd(fd, &error_abort);
g_assert_cmpstr(object_get_typename(OBJECT(ioc)),
==,
TYPE_QIO_CHANNEL_SOCKET);
object_unref(OBJECT(ioc));
}
int main(int argc, char **argv)
{
bool has_ipv4, has_ipv6;
module_call_init(MODULE_INIT_QOM);
qemu_init_main_loop(&error_abort);
socket_init();
g_test_init(&argc, &argv, NULL);
/* We're creating actual IPv4/6 sockets, so we should
* check if the host running tests actually supports
* each protocol to avoid breaking tests on machines
* with either IPv4 or IPv6 disabled.
*/
if (socket_check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
g_printerr("socket_check_protocol_support() failed\n");
goto end;
}
if (has_ipv4) {
g_test_add_func("/io/channel/socket/ipv4-sync",
test_io_channel_ipv4_sync);
g_test_add_func("/io/channel/socket/ipv4-async",
test_io_channel_ipv4_async);
g_test_add_func("/io/channel/socket/ipv4-fd",
test_io_channel_ipv4_fd);
}
if (has_ipv6) {
g_test_add_func("/io/channel/socket/ipv6-sync",
test_io_channel_ipv6_sync);
g_test_add_func("/io/channel/socket/ipv6-async",
test_io_channel_ipv6_async);
}
#ifndef _WIN32
g_test_add_func("/io/channel/socket/unix-sync",
test_io_channel_unix_sync);
g_test_add_func("/io/channel/socket/unix-async",
test_io_channel_unix_async);
g_test_add_func("/io/channel/socket/unix-fd-pass",
test_io_channel_unix_fd_pass);
g_test_add_func("/io/channel/socket/unix-listen-cleanup",
test_io_channel_unix_listen_cleanup);
#endif /* _WIN32 */
end:
return g_test_run();
}

View file

@ -0,0 +1,346 @@
/*
* QEMU I/O channel TLS test
*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include "qemu/osdep.h"
#include "crypto-tls-x509-helpers.h"
#include "io/channel-tls.h"
#include "io/channel-socket.h"
#include "io-channel-helpers.h"
#include "crypto/init.h"
#include "crypto/tlscredsx509.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "authz/list.h"
#include "qom/object_interfaces.h"
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
#define WORKDIR "tests/test-io-channel-tls-work/"
#define KEYFILE WORKDIR "key-ctx.pem"
struct QIOChannelTLSTestData {
const char *servercacrt;
const char *clientcacrt;
const char *servercrt;
const char *clientcrt;
bool expectServerFail;
bool expectClientFail;
const char *hostname;
const char *const *wildcards;
};
struct QIOChannelTLSHandshakeData {
bool finished;
bool failed;
};
static void test_tls_handshake_done(QIOTask *task,
gpointer opaque)
{
struct QIOChannelTLSHandshakeData *data = opaque;
data->finished = true;
data->failed = qio_task_propagate_error(task, NULL);
}
static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
const char *certdir)
{
Object *parent = object_get_objects_root();
Object *creds = object_new_with_props(
TYPE_QCRYPTO_TLS_CREDS_X509,
parent,
(endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"testtlscredsserver" : "testtlscredsclient"),
&error_abort,
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
"server" : "client"),
"dir", certdir,
"verify-peer", "yes",
"priority", "NORMAL",
/* We skip initial sanity checks here because we
* want to make sure that problems are being
* detected at the TLS session validation stage,
* and the test-crypto-tlscreds test already
* validate the sanity check code.
*/
"sanity-check", "no",
NULL
);
return QCRYPTO_TLS_CREDS(creds);
}
/*
* This tests validation checking of peer certificates
*
* This is replicating the checks that are done for an
* active TLS session after handshake completes. To
* simulate that we create our TLS contexts, skipping
* sanity checks. When then get a socketpair, and
* initiate a TLS session across them. Finally do
* do actual cert validation tests
*/
static void test_io_channel_tls(const void *opaque)
{
struct QIOChannelTLSTestData *data =
(struct QIOChannelTLSTestData *)opaque;
QCryptoTLSCreds *clientCreds;
QCryptoTLSCreds *serverCreds;
QIOChannelTLS *clientChanTLS;
QIOChannelTLS *serverChanTLS;
QIOChannelSocket *clientChanSock;
QIOChannelSocket *serverChanSock;
QAuthZList *auth;
const char * const *wildcards;
int channel[2];
struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
struct QIOChannelTLSHandshakeData serverHandshake = { false, false };
QIOChannelTest *test;
GMainContext *mainloop;
/* We'll use this for our fake client-server connection */
g_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0);
#define CLIENT_CERT_DIR "tests/test-io-channel-tls-client/"
#define SERVER_CERT_DIR "tests/test-io-channel-tls-server/"
mkdir(CLIENT_CERT_DIR, 0700);
mkdir(SERVER_CERT_DIR, 0700);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
g_assert(link(data->servercacrt,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
g_assert(link(data->servercrt,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
g_assert(link(KEYFILE,
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
g_assert(link(data->clientcacrt,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
g_assert(link(data->clientcrt,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
g_assert(link(KEYFILE,
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
clientCreds = test_tls_creds_create(
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
CLIENT_CERT_DIR);
g_assert(clientCreds != NULL);
serverCreds = test_tls_creds_create(
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
SERVER_CERT_DIR);
g_assert(serverCreds != NULL);
auth = qauthz_list_new("channeltlsacl",
QAUTHZ_LIST_POLICY_DENY,
&error_abort);
wildcards = data->wildcards;
while (wildcards && *wildcards) {
qauthz_list_append_rule(auth, *wildcards,
QAUTHZ_LIST_POLICY_ALLOW,
QAUTHZ_LIST_FORMAT_GLOB,
&error_abort);
wildcards++;
}
clientChanSock = qio_channel_socket_new_fd(
channel[0], &error_abort);
g_assert(clientChanSock != NULL);
serverChanSock = qio_channel_socket_new_fd(
channel[1], &error_abort);
g_assert(serverChanSock != NULL);
/*
* We have an evil loop to do the handshake in a single
* thread, so we need these non-blocking to avoid deadlock
* of ourselves
*/
qio_channel_set_blocking(QIO_CHANNEL(clientChanSock), false, NULL);
qio_channel_set_blocking(QIO_CHANNEL(serverChanSock), false, NULL);
/* Now the real part of the test, setup the sessions */
clientChanTLS = qio_channel_tls_new_client(
QIO_CHANNEL(clientChanSock), clientCreds,
data->hostname, &error_abort);
g_assert(clientChanTLS != NULL);
serverChanTLS = qio_channel_tls_new_server(
QIO_CHANNEL(serverChanSock), serverCreds,
"channeltlsacl", &error_abort);
g_assert(serverChanTLS != NULL);
qio_channel_tls_handshake(clientChanTLS,
test_tls_handshake_done,
&clientHandshake,
NULL,
NULL);
qio_channel_tls_handshake(serverChanTLS,
test_tls_handshake_done,
&serverHandshake,
NULL,
NULL);
/*
* Finally we loop around & around doing handshake on each
* session until we get an error, or the handshake completes.
* This relies on the socketpair being nonblocking to avoid
* deadlocking ourselves upon handshake
*/
mainloop = g_main_context_default();
do {
g_main_context_iteration(mainloop, TRUE);
} while (!clientHandshake.finished ||
!serverHandshake.finished);
g_assert(clientHandshake.failed == data->expectClientFail);
g_assert(serverHandshake.failed == data->expectServerFail);
test = qio_channel_test_new();
qio_channel_test_run_threads(test, false,
QIO_CHANNEL(clientChanTLS),
QIO_CHANNEL(serverChanTLS));
qio_channel_test_validate(test);
test = qio_channel_test_new();
qio_channel_test_run_threads(test, true,
QIO_CHANNEL(clientChanTLS),
QIO_CHANNEL(serverChanTLS));
qio_channel_test_validate(test);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
rmdir(CLIENT_CERT_DIR);
rmdir(SERVER_CERT_DIR);
object_unparent(OBJECT(serverCreds));
object_unparent(OBJECT(clientCreds));
object_unref(OBJECT(serverChanTLS));
object_unref(OBJECT(clientChanTLS));
object_unref(OBJECT(serverChanSock));
object_unref(OBJECT(clientChanSock));
object_unparent(OBJECT(auth));
close(channel[0]);
close(channel[1]);
}
int main(int argc, char **argv)
{
int ret;
g_assert(qcrypto_init(NULL) == 0);
module_call_init(MODULE_INIT_QOM);
g_test_init(&argc, &argv, NULL);
g_setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
mkdir(WORKDIR, 0700);
test_tls_init(KEYFILE);
# define TEST_CHANNEL(name, caCrt, \
serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards) \
struct QIOChannelTLSTestData name = { \
caCrt, caCrt, serverCrt, clientCrt, \
expectServerFail, expectClientFail, \
hostname, wildcards \
}; \
g_test_add_data_func("/qio/channel/tls/" # name, \
&name, test_io_channel_tls);
/* A perfect CA, perfect client & perfect server */
/* Basic:CA:critical */
TLS_ROOT_REQ(cacertreq,
"UK", "qemu CA", NULL, NULL, NULL, NULL,
true, true, true,
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
false, false, NULL, NULL,
0, 0);
TLS_CERT_REQ(servercertreq, cacertreq,
"UK", "qemu.org", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
0, 0);
TLS_CERT_REQ(clientcertreq, cacertreq,
"UK", "qemu", NULL, NULL, NULL, NULL,
true, true, false,
true, true,
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
0, 0);
const char *const wildcards[] = {
"C=UK,CN=qemu*",
NULL,
};
TEST_CHANNEL(basic, cacertreq.filename, servercertreq.filename,
clientcertreq.filename, false, false,
"qemu.org", wildcards);
ret = g_test_run();
test_tls_discard_cert(&clientcertreq);
test_tls_discard_cert(&servercertreq);
test_tls_discard_cert(&cacertreq);
test_tls_cleanup(KEYFILE);
rmdir(WORKDIR);
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
int
main(void)
{
return EXIT_SUCCESS;
}
#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */

268
tests/unit/test-io-task.c Normal file
View file

@ -0,0 +1,268 @@
/*
* QEMU I/O task tests
*
* Copyright (c) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qom/object.h"
#include "io/task.h"
#include "qapi/error.h"
#include "qemu/module.h"
#define TYPE_DUMMY "qemu:dummy"
typedef struct DummyObject DummyObject;
typedef struct DummyObjectClass DummyObjectClass;
struct DummyObject {
Object parent;
};
struct DummyObjectClass {
ObjectClass parent;
};
static const TypeInfo dummy_info = {
.parent = TYPE_OBJECT,
.name = TYPE_DUMMY,
.instance_size = sizeof(DummyObject),
.class_size = sizeof(DummyObjectClass),
};
struct TestTaskData {
Object *source;
Error *err;
bool freed;
};
static void task_callback(QIOTask *task,
gpointer opaque)
{
struct TestTaskData *data = opaque;
data->source = qio_task_get_source(task);
qio_task_propagate_error(task, &data->err);
}
static void test_task_complete(void)
{
QIOTask *task;
Object *obj = object_new(TYPE_DUMMY);
Object *src;
struct TestTaskData data = { NULL, NULL, false };
task = qio_task_new(obj, task_callback, &data, NULL);
src = qio_task_get_source(task);
qio_task_complete(task);
g_assert(obj == src);
object_unref(obj);
g_assert(data.source == obj);
g_assert(data.err == NULL);
g_assert(data.freed == false);
}
static void task_data_free(gpointer opaque)
{
struct TestTaskData *data = opaque;
data->freed = true;
}
static void test_task_data_free(void)
{
QIOTask *task;
Object *obj = object_new(TYPE_DUMMY);
struct TestTaskData data = { NULL, NULL, false };
task = qio_task_new(obj, task_callback, &data, task_data_free);
qio_task_complete(task);
object_unref(obj);
g_assert(data.source == obj);
g_assert(data.err == NULL);
g_assert(data.freed == true);
}
static void test_task_failure(void)
{
QIOTask *task;
Object *obj = object_new(TYPE_DUMMY);
struct TestTaskData data = { NULL, NULL, false };
Error *err = NULL;
task = qio_task_new(obj, task_callback, &data, NULL);
error_setg(&err, "Some error");
qio_task_set_error(task, err);
qio_task_complete(task);
object_unref(obj);
g_assert(data.source == obj);
g_assert(data.err == err);
g_assert(data.freed == false);
error_free(data.err);
}
struct TestThreadWorkerData {
Object *source;
Error *err;
bool fail;
GThread *worker;
GThread *complete;
GMainLoop *loop;
};
static void test_task_thread_worker(QIOTask *task,
gpointer opaque)
{
struct TestThreadWorkerData *data = opaque;
data->worker = g_thread_self();
if (data->fail) {
Error *err = NULL;
error_setg(&err, "Testing fail");
qio_task_set_error(task, err);
}
}
static void test_task_thread_callback(QIOTask *task,
gpointer opaque)
{
struct TestThreadWorkerData *data = opaque;
data->source = qio_task_get_source(task);
qio_task_propagate_error(task, &data->err);
data->complete = g_thread_self();
g_main_loop_quit(data->loop);
}
static void test_task_thread_complete(void)
{
QIOTask *task;
Object *obj = object_new(TYPE_DUMMY);
struct TestThreadWorkerData data = { 0 };
GThread *self;
data.loop = g_main_loop_new(g_main_context_default(),
TRUE);
task = qio_task_new(obj,
test_task_thread_callback,
&data,
NULL);
qio_task_run_in_thread(task,
test_task_thread_worker,
&data,
NULL,
NULL);
g_main_loop_run(data.loop);
g_main_loop_unref(data.loop);
object_unref(obj);
g_assert(data.source == obj);
g_assert(data.err == NULL);
self = g_thread_self();
/* Make sure the test_task_thread_worker actually got
* run in a different thread */
g_assert(data.worker != self);
/* And that the test_task_thread_callback got rnu in
* the main loop thread (ie this one) */
g_assert(data.complete == self);
}
static void test_task_thread_failure(void)
{
QIOTask *task;
Object *obj = object_new(TYPE_DUMMY);
struct TestThreadWorkerData data = { 0 };
GThread *self;
data.loop = g_main_loop_new(g_main_context_default(),
TRUE);
data.fail = true;
task = qio_task_new(obj,
test_task_thread_callback,
&data,
NULL);
qio_task_run_in_thread(task,
test_task_thread_worker,
&data,
NULL,
NULL);
g_main_loop_run(data.loop);
g_main_loop_unref(data.loop);
object_unref(obj);
g_assert(data.source == obj);
error_free_or_abort(&data.err);
self = g_thread_self();
/* Make sure the test_task_thread_worker actually got
* run in a different thread */
g_assert(data.worker != self);
/* And that the test_task_thread_callback got rnu in
* the main loop thread (ie this one) */
g_assert(data.complete == self);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
type_register_static(&dummy_info);
g_test_add_func("/crypto/task/complete", test_task_complete);
g_test_add_func("/crypto/task/datafree", test_task_data_free);
g_test_add_func("/crypto/task/failure", test_task_failure);
g_test_add_func("/crypto/task/thread_complete", test_task_thread_complete);
g_test_add_func("/crypto/task/thread_failure", test_task_thread_failure);
return g_test_run();
}

581
tests/unit/test-iov.c Normal file
View file

@ -0,0 +1,581 @@
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qemu/iov.h"
#include "qemu/sockets.h"
/* create a randomly-sized iovec with random vectors */
static void iov_random(struct iovec **iovp, unsigned *iov_cntp)
{
unsigned niov = g_test_rand_int_range(3,8);
struct iovec *iov = g_malloc(niov * sizeof(*iov));
unsigned i;
for (i = 0; i < niov; ++i) {
iov[i].iov_len = g_test_rand_int_range(5,20);
iov[i].iov_base = g_malloc(iov[i].iov_len);
}
*iovp = iov;
*iov_cntp = niov;
}
static void iov_free(struct iovec *iov, unsigned niov)
{
unsigned i;
for (i = 0; i < niov; ++i) {
g_free(iov[i].iov_base);
}
g_free(iov);
}
static bool iov_equals(const struct iovec *a, const struct iovec *b,
unsigned niov)
{
return memcmp(a, b, sizeof(a[0]) * niov) == 0;
}
static void test_iov_bytes(struct iovec *iov, unsigned niov,
size_t offset, size_t bytes)
{
unsigned i;
size_t j, o;
unsigned char *b;
o = 0;
/* we walk over all elements, */
for (i = 0; i < niov; ++i) {
b = iov[i].iov_base;
/* over each char of each element, */
for (j = 0; j < iov[i].iov_len; ++j) {
/* counting each of them and
* verifying that the ones within [offset,offset+bytes)
* range are equal to the position number (o) */
if (o >= offset && o < offset + bytes) {
g_assert(b[j] == (o & 255));
} else {
g_assert(b[j] == 0xff);
}
++o;
}
}
}
static void test_to_from_buf_1(void)
{
unsigned niov;
struct iovec *iov;
size_t sz;
unsigned char *ibuf, *obuf;
unsigned i, j, n;
iov_random(&iov, &niov);
sz = iov_size(iov, niov);
ibuf = g_malloc(sz + 8) + 4;
memcpy(ibuf-4, "aaaa", 4); memcpy(ibuf + sz, "bbbb", 4);
obuf = g_malloc(sz + 8) + 4;
memcpy(obuf-4, "xxxx", 4); memcpy(obuf + sz, "yyyy", 4);
/* fill in ibuf with 0123456... */
for (i = 0; i < sz; ++i) {
ibuf[i] = i & 255;
}
for (i = 0; i <= sz; ++i) {
/* Test from/to buf for offset(i) in [0..sz] up to the end of buffer.
* For last iteration with offset == sz, the procedure should
* skip whole vector and process exactly 0 bytes */
/* first set bytes [i..sz) to some "random" value */
n = iov_memset(iov, niov, 0, 0xff, sz);
g_assert(n == sz);
/* next copy bytes [i..sz) from ibuf to iovec */
n = iov_from_buf(iov, niov, i, ibuf + i, sz - i);
g_assert(n == sz - i);
/* clear part of obuf */
memset(obuf + i, 0, sz - i);
/* and set this part of obuf to values from iovec */
n = iov_to_buf(iov, niov, i, obuf + i, sz - i);
g_assert(n == sz - i);
/* now compare resulting buffers */
g_assert(memcmp(ibuf, obuf, sz) == 0);
/* test just one char */
n = iov_to_buf(iov, niov, i, obuf + i, 1);
g_assert(n == (i < sz));
if (n) {
g_assert(obuf[i] == (i & 255));
}
for (j = i; j <= sz; ++j) {
/* now test num of bytes cap up to byte no. j,
* with j in [i..sz]. */
/* clear iovec */
n = iov_memset(iov, niov, 0, 0xff, sz);
g_assert(n == sz);
/* copy bytes [i..j) from ibuf to iovec */
n = iov_from_buf(iov, niov, i, ibuf + i, j - i);
g_assert(n == j - i);
/* clear part of obuf */
memset(obuf + i, 0, j - i);
/* copy bytes [i..j) from iovec to obuf */
n = iov_to_buf(iov, niov, i, obuf + i, j - i);
g_assert(n == j - i);
/* verify result */
g_assert(memcmp(ibuf, obuf, sz) == 0);
/* now actually check if the iovec contains the right data */
test_iov_bytes(iov, niov, i, j - i);
}
}
g_assert(!memcmp(ibuf-4, "aaaa", 4) && !memcmp(ibuf+sz, "bbbb", 4));
g_free(ibuf-4);
g_assert(!memcmp(obuf-4, "xxxx", 4) && !memcmp(obuf+sz, "yyyy", 4));
g_free(obuf-4);
iov_free(iov, niov);
}
static void test_to_from_buf(void)
{
int x;
for (x = 0; x < 4; ++x) {
test_to_from_buf_1();
}
}
static void test_io(void)
{
#ifndef _WIN32
/* socketpair(PF_UNIX) which does not exist on windows */
int sv[2];
int r;
unsigned i, j, k, s, t;
fd_set fds;
unsigned niov;
struct iovec *iov, *siov;
unsigned char *buf;
size_t sz;
iov_random(&iov, &niov);
sz = iov_size(iov, niov);
buf = g_malloc(sz);
for (i = 0; i < sz; ++i) {
buf[i] = i & 255;
}
iov_from_buf(iov, niov, 0, buf, sz);
siov = g_memdup(iov, sizeof(*iov) * niov);
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) < 0) {
perror("socketpair");
exit(1);
}
FD_ZERO(&fds);
t = 0;
if (fork() == 0) {
/* writer */
close(sv[0]);
FD_SET(sv[1], &fds);
fcntl(sv[1], F_SETFL, O_RDWR|O_NONBLOCK);
r = g_test_rand_int_range(sz / 2, sz);
setsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &r, sizeof(r));
for (i = 0; i <= sz; ++i) {
for (j = i; j <= sz; ++j) {
k = i;
do {
s = g_test_rand_int_range(0, j - k + 1);
r = iov_send(sv[1], iov, niov, k, s);
g_assert(memcmp(iov, siov, sizeof(*iov)*niov) == 0);
if (r >= 0) {
k += r;
t += r;
usleep(g_test_rand_int_range(0, 30));
} else if (errno == EAGAIN) {
select(sv[1]+1, NULL, &fds, NULL, NULL);
continue;
} else {
perror("send");
exit(1);
}
} while(k < j);
}
}
iov_free(iov, niov);
g_free(buf);
g_free(siov);
exit(0);
} else {
/* reader & verifier */
close(sv[1]);
FD_SET(sv[0], &fds);
fcntl(sv[0], F_SETFL, O_RDWR|O_NONBLOCK);
r = g_test_rand_int_range(sz / 2, sz);
setsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &r, sizeof(r));
usleep(500000);
for (i = 0; i <= sz; ++i) {
for (j = i; j <= sz; ++j) {
k = i;
iov_memset(iov, niov, 0, 0xff, sz);
do {
s = g_test_rand_int_range(0, j - k + 1);
r = iov_recv(sv[0], iov, niov, k, s);
g_assert(memcmp(iov, siov, sizeof(*iov)*niov) == 0);
if (r > 0) {
k += r;
t += r;
} else if (!r) {
if (s) {
break;
}
} else if (errno == EAGAIN) {
select(sv[0]+1, &fds, NULL, NULL, NULL);
continue;
} else {
perror("recv");
exit(1);
}
} while(k < j);
test_iov_bytes(iov, niov, i, j - i);
}
}
iov_free(iov, niov);
g_free(buf);
g_free(siov);
}
#endif
}
static void test_discard_front(void)
{
struct iovec *iov;
struct iovec *iov_tmp;
unsigned int iov_cnt;
unsigned int iov_cnt_tmp;
void *old_base;
size_t size;
size_t ret;
/* Discard zero bytes */
iov_random(&iov, &iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
ret = iov_discard_front(&iov_tmp, &iov_cnt_tmp, 0);
g_assert(ret == 0);
g_assert(iov_tmp == iov);
g_assert(iov_cnt_tmp == iov_cnt);
iov_free(iov, iov_cnt);
/* Discard more bytes than vector size */
iov_random(&iov, &iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
ret = iov_discard_front(&iov_tmp, &iov_cnt_tmp, size + 1);
g_assert(ret == size);
g_assert(iov_cnt_tmp == 0);
iov_free(iov, iov_cnt);
/* Discard entire vector */
iov_random(&iov, &iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
ret = iov_discard_front(&iov_tmp, &iov_cnt_tmp, size);
g_assert(ret == size);
g_assert(iov_cnt_tmp == 0);
iov_free(iov, iov_cnt);
/* Discard within first element */
iov_random(&iov, &iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
old_base = iov->iov_base;
size = g_test_rand_int_range(1, iov->iov_len);
ret = iov_discard_front(&iov_tmp, &iov_cnt_tmp, size);
g_assert(ret == size);
g_assert(iov_tmp == iov);
g_assert(iov_cnt_tmp == iov_cnt);
g_assert(iov_tmp->iov_base == old_base + size);
iov_tmp->iov_base = old_base; /* undo before g_free() */
iov_free(iov, iov_cnt);
/* Discard entire first element */
iov_random(&iov, &iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
ret = iov_discard_front(&iov_tmp, &iov_cnt_tmp, iov->iov_len);
g_assert(ret == iov->iov_len);
g_assert(iov_tmp == iov + 1);
g_assert(iov_cnt_tmp == iov_cnt - 1);
iov_free(iov, iov_cnt);
/* Discard within second element */
iov_random(&iov, &iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
old_base = iov[1].iov_base;
size = iov->iov_len + g_test_rand_int_range(1, iov[1].iov_len);
ret = iov_discard_front(&iov_tmp, &iov_cnt_tmp, size);
g_assert(ret == size);
g_assert(iov_tmp == iov + 1);
g_assert(iov_cnt_tmp == iov_cnt - 1);
g_assert(iov_tmp->iov_base == old_base + (size - iov->iov_len));
iov_tmp->iov_base = old_base; /* undo before g_free() */
iov_free(iov, iov_cnt);
}
static void test_discard_front_undo(void)
{
IOVDiscardUndo undo;
struct iovec *iov;
struct iovec *iov_tmp;
struct iovec *iov_orig;
unsigned int iov_cnt;
unsigned int iov_cnt_tmp;
size_t size;
/* Discard zero bytes */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
iov_discard_front_undoable(&iov_tmp, &iov_cnt_tmp, 0, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard more bytes than vector size */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
iov_discard_front_undoable(&iov_tmp, &iov_cnt_tmp, size + 1, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard entire vector */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
iov_discard_front_undoable(&iov_tmp, &iov_cnt_tmp, size, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard within first element */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
size = g_test_rand_int_range(1, iov->iov_len);
iov_discard_front_undoable(&iov_tmp, &iov_cnt_tmp, size, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard entire first element */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
iov_discard_front_undoable(&iov_tmp, &iov_cnt_tmp, iov->iov_len, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard within second element */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_tmp = iov;
iov_cnt_tmp = iov_cnt;
size = iov->iov_len + g_test_rand_int_range(1, iov[1].iov_len);
iov_discard_front_undoable(&iov_tmp, &iov_cnt_tmp, size, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
}
static void test_discard_back(void)
{
struct iovec *iov;
unsigned int iov_cnt;
unsigned int iov_cnt_tmp;
void *old_base;
size_t size;
size_t ret;
/* Discard zero bytes */
iov_random(&iov, &iov_cnt);
iov_cnt_tmp = iov_cnt;
ret = iov_discard_back(iov, &iov_cnt_tmp, 0);
g_assert(ret == 0);
g_assert(iov_cnt_tmp == iov_cnt);
iov_free(iov, iov_cnt);
/* Discard more bytes than vector size */
iov_random(&iov, &iov_cnt);
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
ret = iov_discard_back(iov, &iov_cnt_tmp, size + 1);
g_assert(ret == size);
g_assert(iov_cnt_tmp == 0);
iov_free(iov, iov_cnt);
/* Discard entire vector */
iov_random(&iov, &iov_cnt);
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
ret = iov_discard_back(iov, &iov_cnt_tmp, size);
g_assert(ret == size);
g_assert(iov_cnt_tmp == 0);
iov_free(iov, iov_cnt);
/* Discard within last element */
iov_random(&iov, &iov_cnt);
iov_cnt_tmp = iov_cnt;
old_base = iov[iov_cnt - 1].iov_base;
size = g_test_rand_int_range(1, iov[iov_cnt - 1].iov_len);
ret = iov_discard_back(iov, &iov_cnt_tmp, size);
g_assert(ret == size);
g_assert(iov_cnt_tmp == iov_cnt);
g_assert(iov[iov_cnt - 1].iov_base == old_base);
iov_free(iov, iov_cnt);
/* Discard entire last element */
iov_random(&iov, &iov_cnt);
iov_cnt_tmp = iov_cnt;
old_base = iov[iov_cnt - 1].iov_base;
size = iov[iov_cnt - 1].iov_len;
ret = iov_discard_back(iov, &iov_cnt_tmp, size);
g_assert(ret == size);
g_assert(iov_cnt_tmp == iov_cnt - 1);
iov_free(iov, iov_cnt);
/* Discard within second-to-last element */
iov_random(&iov, &iov_cnt);
iov_cnt_tmp = iov_cnt;
old_base = iov[iov_cnt - 2].iov_base;
size = iov[iov_cnt - 1].iov_len +
g_test_rand_int_range(1, iov[iov_cnt - 2].iov_len);
ret = iov_discard_back(iov, &iov_cnt_tmp, size);
g_assert(ret == size);
g_assert(iov_cnt_tmp == iov_cnt - 1);
g_assert(iov[iov_cnt - 2].iov_base == old_base);
iov_free(iov, iov_cnt);
}
static void test_discard_back_undo(void)
{
IOVDiscardUndo undo;
struct iovec *iov;
struct iovec *iov_orig;
unsigned int iov_cnt;
unsigned int iov_cnt_tmp;
size_t size;
/* Discard zero bytes */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_cnt_tmp = iov_cnt;
iov_discard_back_undoable(iov, &iov_cnt_tmp, 0, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard more bytes than vector size */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
iov_discard_back_undoable(iov, &iov_cnt_tmp, size + 1, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard entire vector */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_cnt_tmp = iov_cnt;
size = iov_size(iov, iov_cnt);
iov_discard_back_undoable(iov, &iov_cnt_tmp, size, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard within last element */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_cnt_tmp = iov_cnt;
size = g_test_rand_int_range(1, iov[iov_cnt - 1].iov_len);
iov_discard_back_undoable(iov, &iov_cnt_tmp, size, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard entire last element */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_cnt_tmp = iov_cnt;
size = iov[iov_cnt - 1].iov_len;
iov_discard_back_undoable(iov, &iov_cnt_tmp, size, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
/* Discard within second-to-last element */
iov_random(&iov, &iov_cnt);
iov_orig = g_memdup(iov, sizeof(iov[0]) * iov_cnt);
iov_cnt_tmp = iov_cnt;
size = iov[iov_cnt - 1].iov_len +
g_test_rand_int_range(1, iov[iov_cnt - 2].iov_len);
iov_discard_back_undoable(iov, &iov_cnt_tmp, size, &undo);
iov_discard_undo(&undo);
assert(iov_equals(iov, iov_orig, iov_cnt));
g_free(iov_orig);
iov_free(iov, iov_cnt);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_rand_int();
g_test_add_func("/basic/iov/from-to-buf", test_to_from_buf);
g_test_add_func("/basic/iov/io", test_io);
g_test_add_func("/basic/iov/discard-front", test_discard_front);
g_test_add_func("/basic/iov/discard-back", test_discard_back);
g_test_add_func("/basic/iov/discard-front-undo", test_discard_front_undo);
g_test_add_func("/basic/iov/discard-back-undo", test_discard_back_undo);
return g_test_run();
}

765
tests/unit/test-keyval.c Normal file
View file

@ -0,0 +1,765 @@
/*
* Unit tests for parsing of KEY=VALUE,... strings
*
* Copyright (C) 2017 Red Hat Inc.
*
* Authors:
* Markus Armbruster <armbru@redhat.com>,
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qobject-input-visitor.h"
#include "test-qapi-visit.h"
#include "qemu/cutils.h"
#include "qemu/option.h"
static void test_keyval_parse(void)
{
Error *err = NULL;
QDict *qdict, *sub_qdict;
char long_key[129];
char *params;
bool help;
/* Nothing */
qdict = keyval_parse("", NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 0);
qobject_unref(qdict);
/* Empty key (qemu_opts_parse() accepts this) */
qdict = keyval_parse("=val", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Empty key fragment */
qdict = keyval_parse(".", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
qdict = keyval_parse("key.", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Invalid non-empty key (qemu_opts_parse() doesn't care) */
qdict = keyval_parse("7up=val", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Overlong key */
memset(long_key, 'a', 127);
long_key[127] = 'z';
long_key[128] = 0;
params = g_strdup_printf("k.%s=v", long_key);
qdict = keyval_parse(params + 2, NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Overlong key fragment */
qdict = keyval_parse(params, NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
g_free(params);
/* Long key (qemu_opts_parse() accepts and truncates silently) */
params = g_strdup_printf("k.%s=v", long_key + 1);
qdict = keyval_parse(params + 2, NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(qdict, long_key + 1), ==, "v");
qobject_unref(qdict);
/* Long key fragment */
qdict = keyval_parse(params, NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
sub_qdict = qdict_get_qdict(qdict, "k");
g_assert(sub_qdict);
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(sub_qdict, long_key + 1), ==, "v");
qobject_unref(qdict);
g_free(params);
/* Crap after valid key */
qdict = keyval_parse("key[0]=val", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Multiple keys, last one wins */
qdict = keyval_parse("a=1,b=2,,x,a=3", NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 2);
g_assert_cmpstr(qdict_get_try_str(qdict, "a"), ==, "3");
g_assert_cmpstr(qdict_get_try_str(qdict, "b"), ==, "2,x");
qobject_unref(qdict);
/* Even when it doesn't in qemu_opts_parse() */
qdict = keyval_parse("id=foo,id=bar", NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "bar");
qobject_unref(qdict);
/* Dotted keys */
qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 2);
sub_qdict = qdict_get_qdict(qdict, "a");
g_assert(sub_qdict);
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
sub_qdict = qdict_get_qdict(sub_qdict, "b");
g_assert(sub_qdict);
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(sub_qdict, "c"), ==, "2");
g_assert_cmpstr(qdict_get_try_str(qdict, "d"), ==, "3");
qobject_unref(qdict);
/* Inconsistent dotted keys */
qdict = keyval_parse("a.b=1,a=2", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
qdict = keyval_parse("a.b=1,a.b.c=2", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Trailing comma is ignored */
qdict = keyval_parse("x=y,", NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, "y");
qobject_unref(qdict);
/* Except when it isn't */
qdict = keyval_parse(",", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Value containing ,id= not misinterpreted as qemu_opts_parse() does */
qdict = keyval_parse("x=,,id=bar", NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, ",id=bar");
qobject_unref(qdict);
/* Anti-social ID is left to caller (qemu_opts_parse() rejects it) */
qdict = keyval_parse("id=666", NULL, NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "666");
qobject_unref(qdict);
/* Implied value not supported (unlike qemu_opts_parse()) */
qdict = keyval_parse("an,noaus,noaus=", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Implied value, key "no" (qemu_opts_parse(): negated empty key) */
qdict = keyval_parse("no", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Implied key */
qdict = keyval_parse("an,aus=off,noaus=", "implied", NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 3);
g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "an");
g_assert_cmpstr(qdict_get_try_str(qdict, "aus"), ==, "off");
g_assert_cmpstr(qdict_get_try_str(qdict, "noaus"), ==, "");
qobject_unref(qdict);
/* Implied dotted key */
qdict = keyval_parse("val", "eins.zwei", NULL, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
sub_qdict = qdict_get_qdict(qdict, "eins");
g_assert(sub_qdict);
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(sub_qdict, "zwei"), ==, "val");
qobject_unref(qdict);
/* Implied key with empty value (qemu_opts_parse() accepts this) */
qdict = keyval_parse(",", "implied", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Likewise (qemu_opts_parse(): implied key with comma value) */
qdict = keyval_parse(",,,a=1", "implied", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Implied key's value can't have comma (qemu_opts_parse(): it can) */
qdict = keyval_parse("val,,ue", "implied", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Empty key is not an implied key */
qdict = keyval_parse("=val", "implied", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* "help" by itself, without implied key */
qdict = keyval_parse("help", NULL, &help, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 0);
g_assert(help);
qobject_unref(qdict);
/* "help" by itself, with implied key */
qdict = keyval_parse("help", "implied", &help, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 0);
g_assert(help);
qobject_unref(qdict);
/* "help" when no help is available, without implied key */
qdict = keyval_parse("help", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* "help" when no help is available, with implied key */
qdict = keyval_parse("help", "implied", NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Key "help" */
qdict = keyval_parse("help=on", NULL, &help, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(qdict, "help"), ==, "on");
g_assert(!help);
qobject_unref(qdict);
/* "help" followed by crap, without implied key */
qdict = keyval_parse("help.abc", NULL, &help, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* "help" followed by crap, with implied key */
qdict = keyval_parse("help.abc", "implied", &help, &err);
g_assert_cmpuint(qdict_size(qdict), ==, 1);
g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "help.abc");
g_assert(!help);
qobject_unref(qdict);
/* "help" with other stuff, without implied key */
qdict = keyval_parse("number=42,help,foo=bar", NULL, &help, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 2);
g_assert_cmpstr(qdict_get_try_str(qdict, "number"), ==, "42");
g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
g_assert(help);
qobject_unref(qdict);
/* "help" with other stuff, with implied key */
qdict = keyval_parse("val,help,foo=bar", "implied", &help, &error_abort);
g_assert_cmpuint(qdict_size(qdict), ==, 2);
g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "val");
g_assert_cmpstr(qdict_get_try_str(qdict, "foo"), ==, "bar");
g_assert(help);
qobject_unref(qdict);
}
static void check_list012(QList *qlist)
{
static const char *expected[] = { "null", "eins", "zwei" };
int i;
QString *qstr;
g_assert(qlist);
for (i = 0; i < ARRAY_SIZE(expected); i++) {
qstr = qobject_to(QString, qlist_pop(qlist));
g_assert(qstr);
g_assert_cmpstr(qstring_get_str(qstr), ==, expected[i]);
qobject_unref(qstr);
}
g_assert(qlist_empty(qlist));
}
static void test_keyval_parse_list(void)
{
Error *err = NULL;
QDict *qdict, *sub_qdict;
/* Root can't be a list */
qdict = keyval_parse("0=1", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* List elements need not be in order */
qdict = keyval_parse("list.0=null,list.2=zwei,list.1=eins", NULL, NULL,
&error_abort);
g_assert_cmpint(qdict_size(qdict), ==, 1);
check_list012(qdict_get_qlist(qdict, "list"));
qobject_unref(qdict);
/* Multiple indexes, last one wins */
qdict = keyval_parse("list.1=goner,list.0=null,list.01=eins,list.2=zwei",
NULL, NULL, &error_abort);
g_assert_cmpint(qdict_size(qdict), ==, 1);
check_list012(qdict_get_qlist(qdict, "list"));
qobject_unref(qdict);
/* List at deeper nesting */
qdict = keyval_parse("a.list.1=eins,a.list.00=null,a.list.2=zwei", NULL,
NULL, &error_abort);
g_assert_cmpint(qdict_size(qdict), ==, 1);
sub_qdict = qdict_get_qdict(qdict, "a");
g_assert_cmpint(qdict_size(sub_qdict), ==, 1);
check_list012(qdict_get_qlist(sub_qdict, "list"));
qobject_unref(qdict);
/* Inconsistent dotted keys: both list and dictionary */
qdict = keyval_parse("a.b.c=1,a.b.0=2", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
qdict = keyval_parse("a.0.c=1,a.b.c=2", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
/* Missing list indexes */
qdict = keyval_parse("list.1=lonely", NULL, NULL, &err);
error_free_or_abort(&err);
g_assert(!qdict);
qdict = keyval_parse("list.0=null,list.2=eins,list.02=zwei", NULL, NULL,
&err);
error_free_or_abort(&err);
g_assert(!qdict);
}
static void test_keyval_visit_bool(void)
{
Error *err = NULL;
Visitor *v;
QDict *qdict;
bool b;
qdict = keyval_parse("bool1=on,bool2=off", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_bool(v, "bool1", &b, &error_abort);
g_assert(b);
visit_type_bool(v, "bool2", &b, &error_abort);
g_assert(!b);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
qdict = keyval_parse("bool1=offer", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_bool(v, "bool1", &b, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
}
static void test_keyval_visit_number(void)
{
Error *err = NULL;
Visitor *v;
QDict *qdict;
uint64_t u;
/* Lower limit zero */
qdict = keyval_parse("number1=0", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, "number1", &u, &error_abort);
g_assert_cmpuint(u, ==, 0);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Upper limit 2^64-1 */
qdict = keyval_parse("number1=18446744073709551615,number2=-1", NULL,
NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, "number1", &u, &error_abort);
g_assert_cmphex(u, ==, UINT64_MAX);
visit_type_uint64(v, "number2", &u, &error_abort);
g_assert_cmphex(u, ==, UINT64_MAX);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Above upper limit */
qdict = keyval_parse("number1=18446744073709551616", NULL, NULL,
&error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, "number1", &u, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
/* Below lower limit */
qdict = keyval_parse("number1=-18446744073709551616", NULL, NULL,
&error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, "number1", &u, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
/* Hex and octal */
qdict = keyval_parse("number1=0x2a,number2=052", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, "number1", &u, &error_abort);
g_assert_cmpuint(u, ==, 42);
visit_type_uint64(v, "number2", &u, &error_abort);
g_assert_cmpuint(u, ==, 42);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Trailing crap */
qdict = keyval_parse("number1=3.14,number2=08", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, "number1", &u, &err);
error_free_or_abort(&err);
visit_type_uint64(v, "number2", &u, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
}
static void test_keyval_visit_size(void)
{
Error *err = NULL;
Visitor *v;
QDict *qdict;
uint64_t sz;
/* Lower limit zero */
qdict = keyval_parse("sz1=0", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &error_abort);
g_assert_cmpuint(sz, ==, 0);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Note: full 64 bits of precision */
/* Around double limit of precision: 2^53-1, 2^53, 2^53+1 */
qdict = keyval_parse("sz1=9007199254740991,"
"sz2=9007199254740992,"
"sz3=9007199254740993",
NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0x1fffffffffffff);
visit_type_size(v, "sz2", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0x20000000000000);
visit_type_size(v, "sz3", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0x20000000000001);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Close to signed integer limit 2^63 */
qdict = keyval_parse("sz1=9223372036854775807," /* 7fffffffffffffff */
"sz2=9223372036854775808," /* 8000000000000000 */
"sz3=9223372036854775809", /* 8000000000000001 */
NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0x7fffffffffffffff);
visit_type_size(v, "sz2", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0x8000000000000000);
visit_type_size(v, "sz3", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0x8000000000000001);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */
qdict = keyval_parse("sz1=18446744073709549568," /* fffffffffffff800 */
"sz2=18446744073709550591", /* fffffffffffffbff */
NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
visit_type_size(v, "sz2", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0xfffffffffffffbff);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Actual limit 2^64-1*/
qdict = keyval_parse("sz1=18446744073709551615", /* ffffffffffffffff */
NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &error_abort);
g_assert_cmphex(sz, ==, 0xffffffffffffffff);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Beyond limits */
qdict = keyval_parse("sz1=-1,"
"sz2=18446744073709551616", /* 2^64 */
NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &err);
error_free_or_abort(&err);
visit_type_size(v, "sz2", &sz, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
/* Suffixes */
qdict = keyval_parse("sz1=8b,sz2=1.5k,sz3=2M,sz4=0.1G,sz5=16777215T",
NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &error_abort);
g_assert_cmpuint(sz, ==, 8);
visit_type_size(v, "sz2", &sz, &error_abort);
g_assert_cmpuint(sz, ==, 1536);
visit_type_size(v, "sz3", &sz, &error_abort);
g_assert_cmphex(sz, ==, 2 * MiB);
visit_type_size(v, "sz4", &sz, &error_abort);
g_assert_cmphex(sz, ==, GiB / 10);
visit_type_size(v, "sz5", &sz, &error_abort);
g_assert_cmphex(sz, ==, 16777215ULL * TiB);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
/* Beyond limit with suffix */
qdict = keyval_parse("sz1=16777216T", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
/* Trailing crap */
qdict = keyval_parse("sz1=0Z,sz2=16Gi", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_size(v, "sz1", &sz, &err);
error_free_or_abort(&err);
visit_type_size(v, "sz2", &sz, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
}
static void test_keyval_visit_dict(void)
{
Error *err = NULL;
Visitor *v;
QDict *qdict;
int64_t i;
qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_start_struct(v, "a", NULL, 0, &error_abort);
visit_start_struct(v, "b", NULL, 0, &error_abort);
visit_type_int(v, "c", &i, &error_abort);
g_assert_cmpint(i, ==, 2);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_type_int(v, "d", &i, &error_abort);
g_assert_cmpint(i, ==, 3);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
qdict = keyval_parse("a.b=", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_start_struct(v, "a", NULL, 0, &error_abort);
visit_type_int(v, "c", &i, &err); /* a.c missing */
error_free_or_abort(&err);
visit_check_struct(v, &err);
error_free_or_abort(&err); /* a.b unexpected */
visit_end_struct(v, NULL);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
}
static void test_keyval_visit_list(void)
{
Error *err = NULL;
Visitor *v;
QDict *qdict;
char *s;
qdict = keyval_parse("a.0=,a.1=I,a.2.0=II", NULL, NULL, &error_abort);
/* TODO empty list */
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_start_list(v, "a", NULL, 0, &error_abort);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "");
g_free(s);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "I");
g_free(s);
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "II");
g_free(s);
visit_check_list(v, &error_abort);
visit_end_list(v, NULL);
visit_check_list(v, &error_abort);
visit_end_list(v, NULL);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
qdict = keyval_parse("a.0=,b.0.0=head", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_start_list(v, "a", NULL, 0, &error_abort);
visit_check_list(v, &err); /* a[0] unexpected */
error_free_or_abort(&err);
visit_end_list(v, NULL);
visit_start_list(v, "b", NULL, 0, &error_abort);
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_str(v, NULL, &s, &error_abort);
g_assert_cmpstr(s, ==, "head");
g_free(s);
visit_type_str(v, NULL, &s, &err); /* b[0][1] missing */
error_free_or_abort(&err);
visit_end_list(v, NULL);
visit_end_list(v, NULL);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
}
static void test_keyval_visit_optional(void)
{
Visitor *v;
QDict *qdict;
bool present;
int64_t i;
qdict = keyval_parse("a.b=1", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_optional(v, "b", &present);
g_assert(!present); /* b missing */
visit_optional(v, "a", &present);
g_assert(present); /* a present */
visit_start_struct(v, "a", NULL, 0, &error_abort);
visit_optional(v, "b", &present);
g_assert(present); /* a.b present */
visit_type_int(v, "b", &i, &error_abort);
g_assert_cmpint(i, ==, 1);
visit_optional(v, "a", &present);
g_assert(!present); /* a.a missing */
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
}
static void test_keyval_visit_alternate(void)
{
Error *err = NULL;
Visitor *v;
QDict *qdict;
AltStrObj *aso;
AltNumEnum *ane;
AltEnumBool *aeb;
/*
* Can't do scalar alternate variants other than string. You get
* the string variant if there is one, else an error.
* TODO make it work for unambiguous cases like AltEnumBool below
*/
qdict = keyval_parse("a=1,b=2,c=on", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_AltStrObj(v, "a", &aso, &error_abort);
g_assert_cmpint(aso->type, ==, QTYPE_QSTRING);
g_assert_cmpstr(aso->u.s, ==, "1");
qapi_free_AltStrObj(aso);
visit_type_AltNumEnum(v, "b", &ane, &err);
error_free_or_abort(&err);
visit_type_AltEnumBool(v, "c", &aeb, &err);
error_free_or_abort(&err);
visit_end_struct(v, NULL);
visit_free(v);
}
static void test_keyval_visit_any(void)
{
Visitor *v;
QDict *qdict;
QObject *any;
QList *qlist;
QString *qstr;
qdict = keyval_parse("a.0=null,a.1=1", NULL, NULL, &error_abort);
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
qobject_unref(qdict);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
visit_type_any(v, "a", &any, &error_abort);
qlist = qobject_to(QList, any);
g_assert(qlist);
qstr = qobject_to(QString, qlist_pop(qlist));
g_assert_cmpstr(qstring_get_str(qstr), ==, "null");
qobject_unref(qstr);
qstr = qobject_to(QString, qlist_pop(qlist));
g_assert_cmpstr(qstring_get_str(qstr), ==, "1");
g_assert(qlist_empty(qlist));
qobject_unref(qstr);
qobject_unref(any);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
visit_free(v);
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/keyval/keyval_parse", test_keyval_parse);
g_test_add_func("/keyval/keyval_parse/list", test_keyval_parse_list);
g_test_add_func("/keyval/visit/bool", test_keyval_visit_bool);
g_test_add_func("/keyval/visit/number", test_keyval_visit_number);
g_test_add_func("/keyval/visit/size", test_keyval_visit_size);
g_test_add_func("/keyval/visit/dict", test_keyval_visit_dict);
g_test_add_func("/keyval/visit/list", test_keyval_visit_list);
g_test_add_func("/keyval/visit/optional", test_keyval_visit_optional);
g_test_add_func("/keyval/visit/alternate", test_keyval_visit_alternate);
g_test_add_func("/keyval/visit/any", test_keyval_visit_any);
g_test_run();
return 0;
}

218
tests/unit/test-logging.c Normal file
View file

@ -0,0 +1,218 @@
/*
* logging unit-tests
*
* Copyright (C) 2016 Linaro Ltd.
*
* Author: Alex Bennée <alex.bennee@linaro.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include <glib/gstdio.h>
#include "qemu-common.h"
#include "qapi/error.h"
#include "qemu/log.h"
static void test_parse_range(void)
{
Error *err = NULL;
qemu_set_dfilter_ranges("0x1000+0x100", &error_abort);
g_assert_false(qemu_log_in_addr_range(0xfff));
g_assert(qemu_log_in_addr_range(0x1000));
g_assert(qemu_log_in_addr_range(0x1001));
g_assert(qemu_log_in_addr_range(0x10ff));
g_assert_false(qemu_log_in_addr_range(0x1100));
qemu_set_dfilter_ranges("0x1000-0x100", &error_abort);
g_assert_false(qemu_log_in_addr_range(0x1001));
g_assert(qemu_log_in_addr_range(0x1000));
g_assert(qemu_log_in_addr_range(0x0f01));
g_assert_false(qemu_log_in_addr_range(0x0f00));
qemu_set_dfilter_ranges("0x1000..0x1100", &error_abort);
g_assert_false(qemu_log_in_addr_range(0xfff));
g_assert(qemu_log_in_addr_range(0x1000));
g_assert(qemu_log_in_addr_range(0x1100));
g_assert_false(qemu_log_in_addr_range(0x1101));
qemu_set_dfilter_ranges("0x1000..0x1000", &error_abort);
g_assert_false(qemu_log_in_addr_range(0xfff));
g_assert(qemu_log_in_addr_range(0x1000));
g_assert_false(qemu_log_in_addr_range(0x1001));
qemu_set_dfilter_ranges("0x1000+0x100,0x2100-0x100,0x3000..0x3100",
&error_abort);
g_assert(qemu_log_in_addr_range(0x1050));
g_assert(qemu_log_in_addr_range(0x2050));
g_assert(qemu_log_in_addr_range(0x3050));
qemu_set_dfilter_ranges("0xffffffffffffffff-1", &error_abort);
g_assert(qemu_log_in_addr_range(UINT64_MAX));
g_assert_false(qemu_log_in_addr_range(UINT64_MAX - 1));
qemu_set_dfilter_ranges("0..0xffffffffffffffff", &error_abort);
g_assert(qemu_log_in_addr_range(0));
g_assert(qemu_log_in_addr_range(UINT64_MAX));
qemu_set_dfilter_ranges("2..1", &err);
error_free_or_abort(&err);
qemu_set_dfilter_ranges("0x1000+onehundred", &err);
error_free_or_abort(&err);
qemu_set_dfilter_ranges("0x1000+0", &err);
error_free_or_abort(&err);
}
static void set_log_path_tmp(char const *dir, char const *tpl, Error **errp)
{
gchar *file_path = g_build_filename(dir, tpl, NULL);
qemu_set_log_filename(file_path, errp);
g_free(file_path);
}
static void test_parse_path(gconstpointer data)
{
gchar const *tmp_path = data;
Error *err = NULL;
set_log_path_tmp(tmp_path, "qemu.log", &error_abort);
set_log_path_tmp(tmp_path, "qemu-%d.log", &error_abort);
set_log_path_tmp(tmp_path, "qemu.log.%d", &error_abort);
set_log_path_tmp(tmp_path, "qemu-%d%d.log", &err);
error_free_or_abort(&err);
}
static void test_logfile_write(gconstpointer data)
{
QemuLogFile *logfile;
QemuLogFile *logfile2;
gchar const *dir = data;
g_autofree gchar *file_path = NULL;
g_autofree gchar *file_path1 = NULL;
FILE *orig_fd;
/*
* Before starting test, set log flags, to ensure the file gets
* opened below with the call to qemu_set_log_filename().
* In cases where a logging backend other than log is used,
* this is needed.
*/
qemu_set_log(CPU_LOG_TB_OUT_ASM);
file_path = g_build_filename(dir, "qemu_test_log_write0.log", NULL);
file_path1 = g_build_filename(dir, "qemu_test_log_write1.log", NULL);
/*
* Test that even if an open file handle is changed,
* our handle remains valid due to RCU.
*/
qemu_set_log_filename(file_path, &error_abort);
rcu_read_lock();
logfile = qatomic_rcu_read(&qemu_logfile);
orig_fd = logfile->fd;
g_assert(logfile && logfile->fd);
fprintf(logfile->fd, "%s 1st write to file\n", __func__);
fflush(logfile->fd);
/* Change the logfile and ensure that the handle is still valid. */
qemu_set_log_filename(file_path1, &error_abort);
logfile2 = qatomic_rcu_read(&qemu_logfile);
g_assert(logfile->fd == orig_fd);
g_assert(logfile2->fd != logfile->fd);
fprintf(logfile->fd, "%s 2nd write to file\n", __func__);
fflush(logfile->fd);
rcu_read_unlock();
}
static void test_logfile_lock(gconstpointer data)
{
FILE *logfile;
gchar const *dir = data;
g_autofree gchar *file_path = NULL;
file_path = g_build_filename(dir, "qemu_test_logfile_lock0.log", NULL);
/*
* Test the use of the logfile lock, such
* that even if an open file handle is closed,
* our handle remains valid for use due to RCU.
*/
qemu_set_log_filename(file_path, &error_abort);
logfile = qemu_log_lock();
g_assert(logfile);
fprintf(logfile, "%s 1st write to file\n", __func__);
fflush(logfile);
/*
* Initiate a close file and make sure our handle remains
* valid since we still have the logfile lock.
*/
qemu_log_close();
fprintf(logfile, "%s 2nd write to file\n", __func__);
fflush(logfile);
qemu_log_unlock(logfile);
}
/* Remove a directory and all its entries (non-recursive). */
static void rmdir_full(gchar const *root)
{
GDir *root_gdir = g_dir_open(root, 0, NULL);
gchar const *entry_name;
g_assert_nonnull(root_gdir);
while ((entry_name = g_dir_read_name(root_gdir)) != NULL) {
gchar *entry_path = g_build_filename(root, entry_name, NULL);
g_assert(g_remove(entry_path) == 0);
g_free(entry_path);
}
g_dir_close(root_gdir);
g_assert(g_rmdir(root) == 0);
}
int main(int argc, char **argv)
{
g_autofree gchar *tmp_path = g_dir_make_tmp("qemu-test-logging.XXXXXX", NULL);
int rc;
g_test_init(&argc, &argv, NULL);
g_assert_nonnull(tmp_path);
g_test_add_func("/logging/parse_range", test_parse_range);
g_test_add_data_func("/logging/parse_path", tmp_path, test_parse_path);
g_test_add_data_func("/logging/logfile_write_path",
tmp_path, test_logfile_write);
g_test_add_data_func("/logging/logfile_lock_path",
tmp_path, test_logfile_lock);
rc = g_test_run();
qemu_log_close();
drain_call_rcu();
rmdir_full(tmp_path);
return rc;
}

68
tests/unit/test-mul64.c Normal file
View file

@ -0,0 +1,68 @@
/*
* Test 64x64 -> 128 multiply subroutines
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/host-utils.h"
typedef struct {
uint64_t a, b;
uint64_t rh, rl;
} Test;
static const Test test_u_data[] = {
{ 1, 1, 0, 1 },
{ 10000, 10000, 0, 100000000 },
{ 0xffffffffffffffffULL, 2, 1, 0xfffffffffffffffeULL },
{ 0xffffffffffffffffULL, 0xffffffffffffffffULL,
0xfffffffffffffffeULL, 0x0000000000000001ULL },
{ 0x1122334455667788ull, 0x8877665544332211ull,
0x092228fb777ae38full, 0x0a3e963337c60008ull },
};
static const Test test_s_data[] = {
{ 1, 1, 0, 1 },
{ 1, -1, -1, -1 },
{ -10, -10, 0, 100 },
{ 10000, 10000, 0, 100000000 },
{ -1, 2, -1, -2 },
{ 0x1122334455667788ULL, 0x1122334455667788ULL,
0x01258f60bbc2975cULL, 0x1eace4a3c82fb840ULL },
};
static void test_u(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_u_data); ++i) {
uint64_t rl, rh;
mulu64(&rl, &rh, test_u_data[i].a, test_u_data[i].b);
g_assert_cmpuint(rl, ==, test_u_data[i].rl);
g_assert_cmpuint(rh, ==, test_u_data[i].rh);
}
}
static void test_s(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_s_data); ++i) {
uint64_t rl, rh;
muls64(&rl, &rh, test_s_data[i].a, test_s_data[i].b);
g_assert_cmpuint(rl, ==, test_s_data[i].rl);
g_assert_cmpint(rh, ==, test_s_data[i].rh);
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/host-utils/mulu64", test_u);
g_test_add_func("/host-utils/muls64", test_s);
return g_test_run();
}

View file

@ -0,0 +1,371 @@
/*
* Options Visitor unit-tests.
*
* Copyright (C) 2013 Red Hat, Inc.
*
* Authors:
* Laszlo Ersek <lersek@redhat.com> (based on test-string-output-visitor)
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/config-file.h" /* qemu_add_opts() */
#include "qemu/option.h" /* qemu_opts_parse() */
#include "qapi/error.h"
#include "qapi/opts-visitor.h" /* opts_visitor_new() */
#include "test-qapi-visit.h" /* visit_type_UserDefOptions() */
static QemuOptsList userdef_opts = {
.name = "userdef",
.head = QTAILQ_HEAD_INITIALIZER(userdef_opts.head),
.desc = { { 0 } } /* validated with OptsVisitor */
};
/* fixture (= glib test case context) and test case manipulation */
typedef struct OptsVisitorFixture {
UserDefOptions *userdef;
Error *err;
} OptsVisitorFixture;
static void
setup_fixture(OptsVisitorFixture *f, gconstpointer test_data)
{
const char *opts_string = test_data;
QemuOpts *opts;
Visitor *v;
opts = qemu_opts_parse(qemu_find_opts("userdef"), opts_string, false,
NULL);
g_assert(opts != NULL);
v = opts_visitor_new(opts);
visit_type_UserDefOptions(v, NULL, &f->userdef, &f->err);
visit_free(v);
qemu_opts_del(opts);
}
static void
teardown_fixture(OptsVisitorFixture *f, gconstpointer test_data)
{
qapi_free_UserDefOptions(f->userdef);
error_free(f->err);
}
static void
add_test(const char *testpath,
void (*test_func)(OptsVisitorFixture *f, gconstpointer test_data),
gconstpointer test_data)
{
g_test_add(testpath, OptsVisitorFixture, test_data, setup_fixture,
test_func, teardown_fixture);
}
/* test output evaluation */
static void
expect_ok(OptsVisitorFixture *f, gconstpointer test_data)
{
g_assert(f->err == NULL);
g_assert(f->userdef != NULL);
}
static void
expect_fail(OptsVisitorFixture *f, gconstpointer test_data)
{
g_assert(f->err != NULL);
/* The error message is printed when this test utility is invoked directly
* (ie. without gtester) and the --verbose flag is passed:
*
* tests/test-opts-visitor --verbose
*/
g_test_message("'%s': %s", (const char *)test_data,
error_get_pretty(f->err));
}
static void
test_value(OptsVisitorFixture *f, gconstpointer test_data)
{
uint64_t magic, bitval;
intList *i64;
uint64List *u64;
uint16List *u16;
expect_ok(f, test_data);
magic = 0;
for (i64 = f->userdef->i64; i64 != NULL; i64 = i64->next) {
g_assert(-16 <= i64->value && i64->value < 64-16);
bitval = 1ull << (i64->value + 16);
g_assert((magic & bitval) == 0);
magic |= bitval;
}
g_assert(magic == 0xDEADBEEF);
magic = 0;
for (u64 = f->userdef->u64; u64 != NULL; u64 = u64->next) {
g_assert(u64->value < 64);
bitval = 1ull << u64->value;
g_assert((magic & bitval) == 0);
magic |= bitval;
}
g_assert(magic == 0xBADC0FFEE0DDF00DULL);
magic = 0;
for (u16 = f->userdef->u16; u16 != NULL; u16 = u16->next) {
g_assert(u16->value < 64);
bitval = 1ull << u16->value;
g_assert((magic & bitval) == 0);
magic |= bitval;
}
g_assert(magic == 0xD15EA5E);
}
static void
expect_i64_min(OptsVisitorFixture *f, gconstpointer test_data)
{
expect_ok(f, test_data);
g_assert(f->userdef->has_i64);
g_assert(f->userdef->i64->next == NULL);
g_assert(f->userdef->i64->value == INT64_MIN);
}
static void
expect_i64_max(OptsVisitorFixture *f, gconstpointer test_data)
{
expect_ok(f, test_data);
g_assert(f->userdef->has_i64);
g_assert(f->userdef->i64->next == NULL);
g_assert(f->userdef->i64->value == INT64_MAX);
}
static void
expect_zero(OptsVisitorFixture *f, gconstpointer test_data)
{
expect_ok(f, test_data);
g_assert(f->userdef->has_u64);
g_assert(f->userdef->u64->next == NULL);
g_assert(f->userdef->u64->value == 0);
}
static void
expect_u64_max(OptsVisitorFixture *f, gconstpointer test_data)
{
expect_ok(f, test_data);
g_assert(f->userdef->has_u64);
g_assert(f->userdef->u64->next == NULL);
g_assert(f->userdef->u64->value == UINT64_MAX);
}
/* test cases */
static void
test_opts_range_unvisited(void)
{
Error *err = NULL;
intList *list = NULL;
intList *tail;
QemuOpts *opts;
Visitor *v;
opts = qemu_opts_parse(qemu_find_opts("userdef"), "ilist=0-2", false,
&error_abort);
v = opts_visitor_new(opts);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
/* Would be simpler if the visitor genuinely supported virtual walks */
visit_start_list(v, "ilist", (GenericList **)&list, sizeof(*list),
&error_abort);
tail = list;
visit_type_int(v, NULL, &tail->value, &error_abort);
g_assert_cmpint(tail->value, ==, 0);
tail = (intList *)visit_next_list(v, (GenericList *)tail, sizeof(*list));
g_assert(tail);
visit_type_int(v, NULL, &tail->value, &error_abort);
g_assert_cmpint(tail->value, ==, 1);
tail = (intList *)visit_next_list(v, (GenericList *)tail, sizeof(*list));
g_assert(tail);
visit_check_list(v, &error_abort); /* unvisited tail ignored until... */
visit_end_list(v, (void **)&list);
visit_check_struct(v, &err); /* ...here */
error_free_or_abort(&err);
visit_end_struct(v, NULL);
qapi_free_intList(list);
visit_free(v);
qemu_opts_del(opts);
}
static void
test_opts_range_beyond(void)
{
Error *err = NULL;
intList *list = NULL;
intList *tail;
QemuOpts *opts;
Visitor *v;
int64_t val;
opts = qemu_opts_parse(qemu_find_opts("userdef"), "ilist=0", false,
&error_abort);
v = opts_visitor_new(opts);
visit_start_struct(v, NULL, NULL, 0, &error_abort);
/* Would be simpler if the visitor genuinely supported virtual walks */
visit_start_list(v, "ilist", (GenericList **)&list, sizeof(*list),
&error_abort);
tail = list;
visit_type_int(v, NULL, &tail->value, &error_abort);
g_assert_cmpint(tail->value, ==, 0);
tail = (intList *)visit_next_list(v, (GenericList *)tail, sizeof(*tail));
g_assert(!tail);
visit_type_int(v, NULL, &val, &err);
error_free_or_abort(&err);
visit_end_list(v, (void **)&list);
visit_check_struct(v, &error_abort);
visit_end_struct(v, NULL);
qapi_free_intList(list);
visit_free(v);
qemu_opts_del(opts);
}
static void
test_opts_dict_unvisited(void)
{
Error *err = NULL;
QemuOpts *opts;
Visitor *v;
UserDefOptions *userdef;
opts = qemu_opts_parse(qemu_find_opts("userdef"), "i64x=0,bogus=1", false,
&error_abort);
v = opts_visitor_new(opts);
visit_type_UserDefOptions(v, NULL, &userdef, &err);
error_free_or_abort(&err);
visit_free(v);
qemu_opts_del(opts);
g_assert(!userdef);
}
int
main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
qemu_add_opts(&userdef_opts);
/* Three hexadecimal magic numbers, "dead beef", "bad coffee, odd food" and
* "disease", from
* <http://en.wikipedia.org/wiki/Magic_number_%28programming%29>, were
* converted to binary and dissected into bit ranges. Each magic number is
* going to be recomposed using the lists called "i64", "u64" and "u16",
* respectively.
*
* (Note that these types pertain to the individual bit shift counts, not
* the magic numbers themselves; the intent is to exercise opts_type_int()
* and opts_type_uint64().)
*
* The "i64" shift counts have been decreased by 16 (decimal) in order to
* test negative values as well. Finally, the full list of QemuOpt elements
* has been permuted with "shuf".
*
* Both "i64" and "u64" have some (distinct) single-element ranges
* represented as both "a" and "a-a". "u16" is a special case of "i64" (see
* visit_type_uint16()), so it wouldn't add a separate test in this regard.
*/
add_test("/visitor/opts/flatten/value", &test_value,
"i64=-1-0,u64=12-16,u64=2-3,i64=-11--9,u64=57,u16=9,i64=5-5,"
"u16=1-4,u16=20,u64=63-63,i64=-16--13,u64=50-52,i64=14-15,u16=11,"
"i64=7,u16=18,i64=2-3,u16=6,u64=54-55,u64=0,u64=18-20,u64=33-43,"
"i64=9-12,u16=26-27,u64=59-61,u16=13-16,u64=29-31,u64=22-23,"
"u16=24,i64=-7--3");
add_test("/visitor/opts/i64/val1/errno", &expect_fail,
"i64=0x8000000000000000");
add_test("/visitor/opts/i64/val1/empty", &expect_fail, "i64=");
add_test("/visitor/opts/i64/val1/trailing", &expect_fail, "i64=5z");
add_test("/visitor/opts/i64/nonlist", &expect_fail, "i64x=5-6");
add_test("/visitor/opts/i64/val2/errno", &expect_fail,
"i64=0x7fffffffffffffff-0x8000000000000000");
add_test("/visitor/opts/i64/val2/empty", &expect_fail, "i64=5-");
add_test("/visitor/opts/i64/val2/trailing", &expect_fail, "i64=5-6z");
add_test("/visitor/opts/i64/range/empty", &expect_fail, "i64=6-5");
add_test("/visitor/opts/i64/range/minval", &expect_i64_min,
"i64=-0x8000000000000000--0x8000000000000000");
add_test("/visitor/opts/i64/range/maxval", &expect_i64_max,
"i64=0x7fffffffffffffff-0x7fffffffffffffff");
add_test("/visitor/opts/u64/val1/errno", &expect_fail, "u64=-1");
add_test("/visitor/opts/u64/val1/empty", &expect_fail, "u64=");
add_test("/visitor/opts/u64/val1/trailing", &expect_fail, "u64=5z");
add_test("/visitor/opts/u64/nonlist", &expect_fail, "u64x=5-6");
add_test("/visitor/opts/u64/val2/errno", &expect_fail,
"u64=0xffffffffffffffff-0x10000000000000000");
add_test("/visitor/opts/u64/val2/empty", &expect_fail, "u64=5-");
add_test("/visitor/opts/u64/val2/trailing", &expect_fail, "u64=5-6z");
add_test("/visitor/opts/u64/range/empty", &expect_fail, "u64=6-5");
add_test("/visitor/opts/u64/range/minval", &expect_zero, "u64=0-0");
add_test("/visitor/opts/u64/range/maxval", &expect_u64_max,
"u64=0xffffffffffffffff-0xffffffffffffffff");
/* Test maximum range sizes. The macro value is open-coded here
* *intentionally*; the test case must use concrete values by design. If
* OPTS_VISITOR_RANGE_MAX is changed, the following values need to be
* recalculated as well. The assert and this comment should help with it.
*/
g_assert(OPTS_VISITOR_RANGE_MAX == 65536);
/* The unsigned case is simple, a u64-u64 difference can always be
* represented as a u64.
*/
add_test("/visitor/opts/u64/range/max", &expect_ok, "u64=0-65535");
add_test("/visitor/opts/u64/range/2big", &expect_fail, "u64=0-65536");
/* The same cannot be said about an i64-i64 difference. */
add_test("/visitor/opts/i64/range/max/pos/a", &expect_ok,
"i64=0x7fffffffffff0000-0x7fffffffffffffff");
add_test("/visitor/opts/i64/range/max/pos/b", &expect_ok,
"i64=0x7ffffffffffeffff-0x7ffffffffffffffe");
add_test("/visitor/opts/i64/range/2big/pos", &expect_fail,
"i64=0x7ffffffffffeffff-0x7fffffffffffffff");
add_test("/visitor/opts/i64/range/max/neg/a", &expect_ok,
"i64=-0x8000000000000000--0x7fffffffffff0001");
add_test("/visitor/opts/i64/range/max/neg/b", &expect_ok,
"i64=-0x7fffffffffffffff--0x7fffffffffff0000");
add_test("/visitor/opts/i64/range/2big/neg", &expect_fail,
"i64=-0x8000000000000000--0x7fffffffffff0000");
add_test("/visitor/opts/i64/range/2big/full", &expect_fail,
"i64=-0x8000000000000000-0x7fffffffffffffff");
g_test_add_func("/visitor/opts/range/unvisited",
test_opts_range_unvisited);
g_test_add_func("/visitor/opts/range/beyond",
test_opts_range_beyond);
g_test_add_func("/visitor/opts/dict/unvisited", test_opts_dict_unvisited);
g_test_run();
return 0;
}

View file

@ -0,0 +1,78 @@
/*
* Unit tests for QAPI utility functions
*
* Copyright (C) 2017 Red Hat Inc.
*
* Authors:
* Markus Armbruster <armbru@redhat.com>,
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
static void test_qapi_enum_parse(void)
{
Error *err = NULL;
int ret;
ret = qapi_enum_parse(&QType_lookup, NULL, QTYPE_NONE, &error_abort);
g_assert_cmpint(ret, ==, QTYPE_NONE);
ret = qapi_enum_parse(&QType_lookup, "junk", -1, NULL);
g_assert_cmpint(ret, ==, -1);
ret = qapi_enum_parse(&QType_lookup, "junk", -1, &err);
error_free_or_abort(&err);
ret = qapi_enum_parse(&QType_lookup, "none", -1, &error_abort);
g_assert_cmpint(ret, ==, QTYPE_NONE);
ret = qapi_enum_parse(&QType_lookup, QType_str(QTYPE__MAX - 1),
QTYPE__MAX - 1, &error_abort);
g_assert_cmpint(ret, ==, QTYPE__MAX - 1);
}
static void test_parse_qapi_name(void)
{
int ret;
/* Must start with a letter */
ret = parse_qapi_name("a", true);
g_assert(ret == 1);
ret = parse_qapi_name("a$", false);
g_assert(ret == 1);
ret = parse_qapi_name("", false);
g_assert(ret == -1);
ret = parse_qapi_name("1", false);
g_assert(ret == -1);
/* Only letters, digits, hyphen, underscore */
ret = parse_qapi_name("A-Za-z0-9_", true);
g_assert(ret == 10);
ret = parse_qapi_name("A-Za-z0-9_$", false);
g_assert(ret == 10);
ret = parse_qapi_name("A-Za-z0-9_$", true);
g_assert(ret == -1);
/* __RFQDN_ */
ret = parse_qapi_name("__com.redhat_supports", true);
g_assert(ret == 21);
ret = parse_qapi_name("_com.example_", false);
g_assert(ret == -1);
ret = parse_qapi_name("__com.example", false);
g_assert(ret == -1);
ret = parse_qapi_name("__com.example_", false);
g_assert(ret == -1);
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qapi/util/qapi_enum_parse", test_qapi_enum_parse);
g_test_add_func("/qapi/util/parse_qapi_name", test_parse_qapi_name);
g_test_run();
return 0;
}

View file

@ -0,0 +1,319 @@
/*
* Test code for qdev global-properties handling
*
* Copyright (c) 2012 Red Hat Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "hw/qdev-properties.h"
#include "qom/object.h"
#include "qapi/error.h"
#include "qapi/visitor.h"
#define TYPE_STATIC_PROPS "static_prop_type"
typedef struct MyType MyType;
DECLARE_INSTANCE_CHECKER(MyType, STATIC_TYPE,
TYPE_STATIC_PROPS)
#define TYPE_SUBCLASS "static_prop_subtype"
#define PROP_DEFAULT 100
struct MyType {
DeviceState parent_obj;
uint32_t prop1;
uint32_t prop2;
};
static Property static_props[] = {
DEFINE_PROP_UINT32("prop1", MyType, prop1, PROP_DEFAULT),
DEFINE_PROP_UINT32("prop2", MyType, prop2, PROP_DEFAULT),
DEFINE_PROP_END_OF_LIST()
};
static void static_prop_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = NULL;
device_class_set_props(dc, static_props);
}
static const TypeInfo static_prop_type = {
.name = TYPE_STATIC_PROPS,
.parent = TYPE_DEVICE,
.instance_size = sizeof(MyType),
.class_init = static_prop_class_init,
};
static const TypeInfo subclass_type = {
.name = TYPE_SUBCLASS,
.parent = TYPE_STATIC_PROPS,
};
/* Test simple static property setting to default value */
static void test_static_prop_subprocess(void)
{
MyType *mt;
mt = STATIC_TYPE(object_new(TYPE_STATIC_PROPS));
qdev_realize(DEVICE(mt), NULL, &error_fatal);
g_assert_cmpuint(mt->prop1, ==, PROP_DEFAULT);
}
static void test_static_prop(void)
{
g_test_trap_subprocess("/qdev/properties/static/default/subprocess", 0, 0);
g_test_trap_assert_passed();
g_test_trap_assert_stderr("");
g_test_trap_assert_stdout("");
}
static void register_global_properties(GlobalProperty *props)
{
int i;
for (i = 0; props[i].driver != NULL; i++) {
qdev_prop_register_global(props + i);
}
}
/* Test setting of static property using global properties */
static void test_static_globalprop_subprocess(void)
{
MyType *mt;
static GlobalProperty props[] = {
{ TYPE_STATIC_PROPS, "prop1", "200" },
{}
};
register_global_properties(props);
mt = STATIC_TYPE(object_new(TYPE_STATIC_PROPS));
qdev_realize(DEVICE(mt), NULL, &error_fatal);
g_assert_cmpuint(mt->prop1, ==, 200);
g_assert_cmpuint(mt->prop2, ==, PROP_DEFAULT);
}
static void test_static_globalprop(void)
{
g_test_trap_subprocess("/qdev/properties/static/global/subprocess", 0, 0);
g_test_trap_assert_passed();
g_test_trap_assert_stderr("");
g_test_trap_assert_stdout("");
}
#define TYPE_DYNAMIC_PROPS "dynamic-prop-type"
DECLARE_INSTANCE_CHECKER(MyType, DYNAMIC_TYPE,
TYPE_DYNAMIC_PROPS)
#define TYPE_UNUSED_HOTPLUG "hotplug-type"
#define TYPE_UNUSED_NOHOTPLUG "nohotplug-type"
static void prop1_accessor(Object *obj, Visitor *v, const char *name,
void *opaque, Error **errp)
{
MyType *mt = DYNAMIC_TYPE(obj);
visit_type_uint32(v, name, &mt->prop1, errp);
}
static void prop2_accessor(Object *obj, Visitor *v, const char *name,
void *opaque, Error **errp)
{
MyType *mt = DYNAMIC_TYPE(obj);
visit_type_uint32(v, name, &mt->prop2, errp);
}
static void dynamic_instance_init(Object *obj)
{
object_property_add(obj, "prop1", "uint32", prop1_accessor, prop1_accessor,
NULL, NULL);
object_property_add(obj, "prop2", "uint32", prop2_accessor, prop2_accessor,
NULL, NULL);
}
static void dynamic_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = NULL;
}
static const TypeInfo dynamic_prop_type = {
.name = TYPE_DYNAMIC_PROPS,
.parent = TYPE_DEVICE,
.instance_size = sizeof(MyType),
.instance_init = dynamic_instance_init,
.class_init = dynamic_class_init,
};
static void hotplug_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = NULL;
dc->hotpluggable = true;
}
static const TypeInfo hotplug_type = {
.name = TYPE_UNUSED_HOTPLUG,
.parent = TYPE_DEVICE,
.instance_size = sizeof(MyType),
.instance_init = dynamic_instance_init,
.class_init = hotplug_class_init,
};
static void nohotplug_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = NULL;
dc->hotpluggable = false;
}
static const TypeInfo nohotplug_type = {
.name = TYPE_UNUSED_NOHOTPLUG,
.parent = TYPE_DEVICE,
.instance_size = sizeof(MyType),
.instance_init = dynamic_instance_init,
.class_init = nohotplug_class_init,
};
#define TYPE_NONDEVICE "nondevice-type"
static const TypeInfo nondevice_type = {
.name = TYPE_NONDEVICE,
.parent = TYPE_OBJECT,
};
/* Test setting of dynamic properties using global properties */
static void test_dynamic_globalprop_subprocess(void)
{
MyType *mt;
static GlobalProperty props[] = {
{ TYPE_DYNAMIC_PROPS, "prop1", "101", },
{ TYPE_DYNAMIC_PROPS, "prop2", "102", },
{ TYPE_DYNAMIC_PROPS"-bad", "prop3", "103", },
{ TYPE_UNUSED_HOTPLUG, "prop4", "104", },
{ TYPE_UNUSED_NOHOTPLUG, "prop5", "105", },
{ TYPE_NONDEVICE, "prop6", "106", },
{}
};
int global_error;
register_global_properties(props);
mt = DYNAMIC_TYPE(object_new(TYPE_DYNAMIC_PROPS));
qdev_realize(DEVICE(mt), NULL, &error_fatal);
g_assert_cmpuint(mt->prop1, ==, 101);
g_assert_cmpuint(mt->prop2, ==, 102);
global_error = qdev_prop_check_globals();
g_assert_cmpuint(global_error, ==, 1);
g_assert(props[0].used);
g_assert(props[1].used);
g_assert(!props[2].used);
g_assert(!props[3].used);
g_assert(!props[4].used);
g_assert(!props[5].used);
}
static void test_dynamic_globalprop(void)
{
g_test_trap_subprocess("/qdev/properties/dynamic/global/subprocess", 0, 0);
g_test_trap_assert_passed();
g_test_trap_assert_stderr_unmatched("*prop1*");
g_test_trap_assert_stderr_unmatched("*prop2*");
g_test_trap_assert_stderr(
"*warning: global dynamic-prop-type-bad.prop3 has invalid class name*");
g_test_trap_assert_stderr_unmatched("*prop4*");
g_test_trap_assert_stderr(
"*warning: global nohotplug-type.prop5=105 not used*");
g_test_trap_assert_stderr(
"*warning: global nondevice-type.prop6 has invalid class name*");
g_test_trap_assert_stdout("");
}
/* Test if global props affecting subclasses are applied in the right order */
static void test_subclass_global_props(void)
{
MyType *mt;
/* Global properties must be applied in the order they were registered */
static GlobalProperty props[] = {
{ TYPE_STATIC_PROPS, "prop1", "101" },
{ TYPE_SUBCLASS, "prop1", "102" },
{ TYPE_SUBCLASS, "prop2", "103" },
{ TYPE_STATIC_PROPS, "prop2", "104" },
{}
};
register_global_properties(props);
mt = STATIC_TYPE(object_new(TYPE_SUBCLASS));
qdev_realize(DEVICE(mt), NULL, &error_fatal);
g_assert_cmpuint(mt->prop1, ==, 102);
g_assert_cmpuint(mt->prop2, ==, 104);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
module_call_init(MODULE_INIT_QOM);
type_register_static(&static_prop_type);
type_register_static(&subclass_type);
type_register_static(&dynamic_prop_type);
type_register_static(&hotplug_type);
type_register_static(&nohotplug_type);
type_register_static(&nondevice_type);
g_test_add_func("/qdev/properties/static/default/subprocess",
test_static_prop_subprocess);
g_test_add_func("/qdev/properties/static/default",
test_static_prop);
g_test_add_func("/qdev/properties/static/global/subprocess",
test_static_globalprop_subprocess);
g_test_add_func("/qdev/properties/static/global",
test_static_globalprop);
g_test_add_func("/qdev/properties/dynamic/global/subprocess",
test_dynamic_globalprop_subprocess);
g_test_add_func("/qdev/properties/dynamic/global",
test_dynamic_globalprop);
g_test_add_func("/qdev/properties/global/subclass",
test_subclass_global_props);
g_test_run();
return 0;
}

389
tests/unit/test-qdist.c Normal file
View file

@ -0,0 +1,389 @@
/*
* Copyright (C) 2016, Emilio G. Cota <cota@braap.org>
*
* License: GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/qdist.h"
#include <math.h>
struct entry_desc {
double x;
unsigned long count;
/* 0 prints a space, 1-8 prints from qdist_blocks[] */
int fill_code;
};
/* See: https://en.wikipedia.org/wiki/Block_Elements */
static const gunichar qdist_blocks[] = {
0x2581,
0x2582,
0x2583,
0x2584,
0x2585,
0x2586,
0x2587,
0x2588
};
#define QDIST_NR_BLOCK_CODES ARRAY_SIZE(qdist_blocks)
static char *pr_hist(const struct entry_desc *darr, size_t n)
{
GString *s = g_string_new("");
size_t i;
for (i = 0; i < n; i++) {
int fill = darr[i].fill_code;
if (fill) {
assert(fill <= QDIST_NR_BLOCK_CODES);
g_string_append_unichar(s, qdist_blocks[fill - 1]);
} else {
g_string_append_c(s, ' ');
}
}
return g_string_free(s, FALSE);
}
static void
histogram_check(const struct qdist *dist, const struct entry_desc *darr,
size_t n, size_t n_bins)
{
char *pr = qdist_pr_plain(dist, n_bins);
char *str = pr_hist(darr, n);
g_assert_cmpstr(pr, ==, str);
g_free(pr);
g_free(str);
}
static void histogram_check_single_full(const struct qdist *dist, size_t n_bins)
{
struct entry_desc desc = { .fill_code = 8 };
histogram_check(dist, &desc, 1, n_bins);
}
static void
entries_check(const struct qdist *dist, const struct entry_desc *darr, size_t n)
{
size_t i;
for (i = 0; i < n; i++) {
struct qdist_entry *e = &dist->entries[i];
g_assert_cmpuint(e->count, ==, darr[i].count);
}
}
static void
entries_insert(struct qdist *dist, const struct entry_desc *darr, size_t n)
{
size_t i;
for (i = 0; i < n; i++) {
qdist_add(dist, darr[i].x, darr[i].count);
}
}
static void do_test_bin(const struct entry_desc *a, size_t n_a,
const struct entry_desc *b, size_t n_b)
{
struct qdist qda;
struct qdist qdb;
qdist_init(&qda);
entries_insert(&qda, a, n_a);
qdist_inc(&qda, a[0].x);
qdist_add(&qda, a[0].x, -1);
g_assert_cmpuint(qdist_unique_entries(&qda), ==, n_a);
g_assert_cmpfloat(qdist_xmin(&qda), ==, a[0].x);
g_assert_cmpfloat(qdist_xmax(&qda), ==, a[n_a - 1].x);
histogram_check(&qda, a, n_a, 0);
histogram_check(&qda, a, n_a, n_a);
qdist_bin__internal(&qdb, &qda, n_b);
g_assert_cmpuint(qdb.n, ==, n_b);
entries_check(&qdb, b, n_b);
g_assert_cmpuint(qdist_sample_count(&qda), ==, qdist_sample_count(&qdb));
/*
* No histogram_check() for $qdb, since we'd rebin it and that is a bug.
* Instead, regenerate it from $qda.
*/
histogram_check(&qda, b, n_b, n_b);
qdist_destroy(&qdb);
qdist_destroy(&qda);
}
static void do_test_pr(uint32_t opt)
{
static const struct entry_desc desc[] = {
[0] = { 1, 900, 8 },
[1] = { 2, 1, 1 },
[2] = { 3, 2, 1 }
};
static const char border[] = "|";
const char *llabel = NULL;
const char *rlabel = NULL;
struct qdist dist;
GString *s;
char *str;
char *pr;
size_t n;
n = ARRAY_SIZE(desc);
qdist_init(&dist);
entries_insert(&dist, desc, n);
histogram_check(&dist, desc, n, 0);
s = g_string_new("");
if (opt & QDIST_PR_LABELS) {
unsigned int lopts = opt & (QDIST_PR_NODECIMAL |
QDIST_PR_PERCENT |
QDIST_PR_100X |
QDIST_PR_NOBINRANGE);
if (lopts == 0) {
llabel = "[1.0,1.7)";
rlabel = "[2.3,3.0]";
} else if (lopts == QDIST_PR_NODECIMAL) {
llabel = "[1,2)";
rlabel = "[2,3]";
} else if (lopts == (QDIST_PR_PERCENT | QDIST_PR_NODECIMAL)) {
llabel = "[1,2)%";
rlabel = "[2,3]%";
} else if (lopts == QDIST_PR_100X) {
llabel = "[100.0,166.7)";
rlabel = "[233.3,300.0]";
} else if (lopts == (QDIST_PR_NOBINRANGE | QDIST_PR_NODECIMAL)) {
llabel = "1";
rlabel = "3";
} else {
g_assert_cmpstr("BUG", ==, "This is not meant to be exhaustive");
}
}
if (llabel) {
g_string_append(s, llabel);
}
if (opt & QDIST_PR_BORDER) {
g_string_append(s, border);
}
str = pr_hist(desc, n);
g_string_append(s, str);
g_free(str);
if (opt & QDIST_PR_BORDER) {
g_string_append(s, border);
}
if (rlabel) {
g_string_append(s, rlabel);
}
str = g_string_free(s, FALSE);
pr = qdist_pr(&dist, n, opt);
g_assert_cmpstr(pr, ==, str);
g_free(pr);
g_free(str);
qdist_destroy(&dist);
}
static inline void do_test_pr_label(uint32_t opt)
{
opt |= QDIST_PR_LABELS;
do_test_pr(opt);
}
static void test_pr(void)
{
do_test_pr(0);
do_test_pr(QDIST_PR_BORDER);
/* 100X should be ignored because we're not setting LABELS */
do_test_pr(QDIST_PR_100X);
do_test_pr_label(0);
do_test_pr_label(QDIST_PR_NODECIMAL);
do_test_pr_label(QDIST_PR_PERCENT | QDIST_PR_NODECIMAL);
do_test_pr_label(QDIST_PR_100X);
do_test_pr_label(QDIST_PR_NOBINRANGE | QDIST_PR_NODECIMAL);
}
static void test_bin_shrink(void)
{
static const struct entry_desc a[] = {
[0] = { 0.0, 42922, 7 },
[1] = { 0.25, 47834, 8 },
[2] = { 0.50, 26628, 0 },
[3] = { 0.625, 597, 4 },
[4] = { 0.75, 10298, 1 },
[5] = { 0.875, 22, 2 },
[6] = { 1.0, 2771, 1 }
};
static const struct entry_desc b[] = {
[0] = { 0.0, 42922, 7 },
[1] = { 0.25, 47834, 8 },
[2] = { 0.50, 27225, 3 },
[3] = { 0.75, 13091, 1 }
};
return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
}
static void test_bin_expand(void)
{
static const struct entry_desc a[] = {
[0] = { 0.0, 11713, 5 },
[1] = { 0.25, 20294, 0 },
[2] = { 0.50, 17266, 8 },
[3] = { 0.625, 1506, 0 },
[4] = { 0.75, 10355, 6 },
[5] = { 0.833, 2, 1 },
[6] = { 0.875, 99, 4 },
[7] = { 1.0, 4301, 2 }
};
static const struct entry_desc b[] = {
[0] = { 0.0, 11713, 5 },
[1] = { 0.0, 0, 0 },
[2] = { 0.0, 20294, 8 },
[3] = { 0.0, 0, 0 },
[4] = { 0.0, 0, 0 },
[5] = { 0.0, 17266, 6 },
[6] = { 0.0, 1506, 1 },
[7] = { 0.0, 10355, 4 },
[8] = { 0.0, 101, 1 },
[9] = { 0.0, 4301, 2 }
};
return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
}
static void test_bin_precision(void)
{
static const struct entry_desc a[] = {
[0] = { 0, 213549, 8 },
[1] = { 1, 70, 1 },
};
static const struct entry_desc b[] = {
[0] = { 0, 213549, 8 },
[1] = { 0, 70, 1 },
};
return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
}
static void test_bin_simple(void)
{
static const struct entry_desc a[] = {
[0] = { 10, 101, 8 },
[1] = { 11, 0, 0 },
[2] = { 12, 2, 1 }
};
static const struct entry_desc b[] = {
[0] = { 0, 101, 8 },
[1] = { 0, 0, 0 },
[2] = { 0, 0, 0 },
[3] = { 0, 0, 0 },
[4] = { 0, 2, 1 }
};
return do_test_bin(a, ARRAY_SIZE(a), b, ARRAY_SIZE(b));
}
static void test_single_full(void)
{
struct qdist dist;
qdist_init(&dist);
qdist_add(&dist, 3, 102);
g_assert_cmpfloat(qdist_avg(&dist), ==, 3);
g_assert_cmpfloat(qdist_xmin(&dist), ==, 3);
g_assert_cmpfloat(qdist_xmax(&dist), ==, 3);
histogram_check_single_full(&dist, 0);
histogram_check_single_full(&dist, 1);
histogram_check_single_full(&dist, 10);
qdist_destroy(&dist);
}
static void test_single_empty(void)
{
struct qdist dist;
char *pr;
qdist_init(&dist);
qdist_add(&dist, 3, 0);
g_assert_cmpuint(qdist_sample_count(&dist), ==, 0);
g_assert(isnan(qdist_avg(&dist)));
g_assert_cmpfloat(qdist_xmin(&dist), ==, 3);
g_assert_cmpfloat(qdist_xmax(&dist), ==, 3);
pr = qdist_pr_plain(&dist, 0);
g_assert_cmpstr(pr, ==, " ");
g_free(pr);
pr = qdist_pr_plain(&dist, 1);
g_assert_cmpstr(pr, ==, " ");
g_free(pr);
pr = qdist_pr_plain(&dist, 2);
g_assert_cmpstr(pr, ==, " ");
g_free(pr);
qdist_destroy(&dist);
}
static void test_none(void)
{
struct qdist dist;
char *pr;
qdist_init(&dist);
g_assert(isnan(qdist_avg(&dist)));
g_assert(isnan(qdist_xmin(&dist)));
g_assert(isnan(qdist_xmax(&dist)));
pr = qdist_pr_plain(&dist, 0);
g_assert_cmpstr(pr, ==, "(empty)");
g_free(pr);
pr = qdist_pr_plain(&dist, 2);
g_assert_cmpstr(pr, ==, "(empty)");
g_free(pr);
pr = qdist_pr(&dist, 0, QDIST_PR_BORDER);
g_assert_cmpstr(pr, ==, "(empty)");
g_free(pr);
qdist_destroy(&dist);
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qdist/none", test_none);
g_test_add_func("/qdist/single/empty", test_single_empty);
g_test_add_func("/qdist/single/full", test_single_full);
g_test_add_func("/qdist/binning/simple", test_bin_simple);
g_test_add_func("/qdist/binning/precision", test_bin_precision);
g_test_add_func("/qdist/binning/expand", test_bin_expand);
g_test_add_func("/qdist/binning/shrink", test_bin_shrink);
g_test_add_func("/qdist/pr", test_pr);
return g_test_run();
}

1057
tests/unit/test-qemu-opts.c Normal file

File diff suppressed because it is too large Load diff

1019
tests/unit/test-qga.c Normal file

File diff suppressed because it is too large Load diff

434
tests/unit/test-qgraph.c Normal file
View file

@ -0,0 +1,434 @@
/*
* libqos driver framework
*
* Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>
*/
#include "qemu/osdep.h"
#include "../qtest/libqos/qgraph.h"
#include "../qtest/libqos/qgraph_internal.h"
#define MACHINE_PC "x86_64/pc"
#define MACHINE_RASPI2 "arm/raspi2"
#define I440FX "i440FX-pcihost"
#define PCIBUS_PC "pcibus-pc"
#define SDHCI "sdhci"
#define PCIBUS "pci-bus"
#define SDHCI_PCI "sdhci-pci"
#define SDHCI_MM "generic-sdhci"
#define REGISTER_TEST "register-test"
int npath;
static void *machinefunct(QTestState *qts)
{
return NULL;
}
static void *driverfunct(void *obj, QGuestAllocator *machine, void *arg)
{
return NULL;
}
static void testfunct(void *obj, void *arg, QGuestAllocator *alloc)
{
return;
}
static void check_interface(const char *interface)
{
g_assert_cmpint(qos_graph_has_machine(interface), ==, FALSE);
g_assert_nonnull(qos_graph_get_node(interface));
g_assert_cmpint(qos_graph_has_node(interface), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(interface), ==, QNODE_INTERFACE);
qos_graph_node_set_availability(interface, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(interface), ==, TRUE);
}
static void check_machine(const char *machine)
{
qos_node_create_machine(machine, machinefunct);
g_assert_nonnull(qos_graph_get_machine(machine));
g_assert_cmpint(qos_graph_has_machine(machine), ==, TRUE);
g_assert_nonnull(qos_graph_get_node(machine));
g_assert_cmpint(qos_graph_get_node_availability(machine), ==, FALSE);
qos_graph_node_set_availability(machine, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(machine), ==, TRUE);
g_assert_cmpint(qos_graph_has_node(machine), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(machine), ==, QNODE_MACHINE);
}
static void check_contains(const char *machine, const char *driver)
{
QOSGraphEdge *edge;
qos_node_contains(machine, driver, NULL);
edge = qos_graph_get_edge(machine, driver);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==, QEDGE_CONTAINS);
g_assert_cmpint(qos_graph_has_edge(machine, driver), ==, TRUE);
}
static void check_produces(const char *machine, const char *interface)
{
QOSGraphEdge *edge;
qos_node_produces(machine, interface);
check_interface(interface);
edge = qos_graph_get_edge(machine, interface);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==,
QEDGE_PRODUCES);
g_assert_cmpint(qos_graph_has_edge(machine, interface), ==, TRUE);
}
static void check_consumes(const char *driver, const char *interface)
{
QOSGraphEdge *edge;
qos_node_consumes(driver, interface, NULL);
check_interface(interface);
edge = qos_graph_get_edge(interface, driver);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==, QEDGE_CONSUMED_BY);
g_assert_cmpint(qos_graph_has_edge(interface, driver), ==, TRUE);
}
static void check_driver(const char *driver)
{
qos_node_create_driver(driver, driverfunct);
g_assert_cmpint(qos_graph_has_machine(driver), ==, FALSE);
g_assert_nonnull(qos_graph_get_node(driver));
g_assert_cmpint(qos_graph_has_node(driver), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(driver), ==, QNODE_DRIVER);
g_assert_cmpint(qos_graph_get_node_availability(driver), ==, FALSE);
qos_graph_node_set_availability(driver, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(driver), ==, TRUE);
}
static void check_test(const char *test, const char *interface)
{
QOSGraphEdge *edge;
char *full_name = g_strdup_printf("%s-tests/%s", interface, test);
qos_add_test(test, interface, testfunct, NULL);
g_assert_cmpint(qos_graph_has_machine(test), ==, FALSE);
g_assert_cmpint(qos_graph_has_machine(full_name), ==, FALSE);
g_assert_nonnull(qos_graph_get_node(full_name));
g_assert_cmpint(qos_graph_has_node(full_name), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_type(full_name), ==, QNODE_TEST);
edge = qos_graph_get_edge(interface, full_name);
g_assert_nonnull(edge);
g_assert_cmpint(qos_graph_edge_get_type(edge), ==,
QEDGE_CONSUMED_BY);
g_assert_cmpint(qos_graph_has_edge(interface, full_name), ==, TRUE);
g_assert_cmpint(qos_graph_get_node_availability(full_name), ==, TRUE);
qos_graph_node_set_availability(full_name, FALSE);
g_assert_cmpint(qos_graph_get_node_availability(full_name), ==, FALSE);
g_free(full_name);
}
static void count_each_test(QOSGraphNode *path, int len)
{
npath++;
}
static void check_leaf_discovered(int n)
{
npath = 0;
qos_graph_foreach_test_path(count_each_test);
g_assert_cmpint(n, ==, npath);
}
/* G_Test functions */
static void init_nop(void)
{
qos_graph_init();
qos_graph_destroy();
}
static void test_machine(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
qos_graph_destroy();
}
static void test_contains(void)
{
qos_graph_init();
check_contains(MACHINE_PC, I440FX);
g_assert_null(qos_graph_get_machine(MACHINE_PC));
g_assert_null(qos_graph_get_machine(I440FX));
g_assert_null(qos_graph_get_node(MACHINE_PC));
g_assert_null(qos_graph_get_node(I440FX));
qos_graph_destroy();
}
static void test_multiple_contains(void)
{
qos_graph_init();
check_contains(MACHINE_PC, I440FX);
check_contains(MACHINE_PC, PCIBUS_PC);
qos_graph_destroy();
}
static void test_produces(void)
{
qos_graph_init();
check_produces(MACHINE_PC, I440FX);
g_assert_null(qos_graph_get_machine(MACHINE_PC));
g_assert_null(qos_graph_get_machine(I440FX));
g_assert_null(qos_graph_get_node(MACHINE_PC));
g_assert_nonnull(qos_graph_get_node(I440FX));
qos_graph_destroy();
}
static void test_multiple_produces(void)
{
qos_graph_init();
check_produces(MACHINE_PC, I440FX);
check_produces(MACHINE_PC, PCIBUS_PC);
qos_graph_destroy();
}
static void test_consumes(void)
{
qos_graph_init();
check_consumes(I440FX, SDHCI);
g_assert_null(qos_graph_get_machine(I440FX));
g_assert_null(qos_graph_get_machine(SDHCI));
g_assert_null(qos_graph_get_node(I440FX));
g_assert_nonnull(qos_graph_get_node(SDHCI));
qos_graph_destroy();
}
static void test_multiple_consumes(void)
{
qos_graph_init();
check_consumes(I440FX, SDHCI);
check_consumes(PCIBUS_PC, SDHCI);
qos_graph_destroy();
}
static void test_driver(void)
{
qos_graph_init();
check_driver(I440FX);
qos_graph_destroy();
}
static void test_test(void)
{
qos_graph_init();
check_test(REGISTER_TEST, SDHCI);
qos_graph_destroy();
}
static void test_machine_contains_driver(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_driver(I440FX);
check_contains(MACHINE_PC, I440FX);
qos_graph_destroy();
}
static void test_driver_contains_driver(void)
{
qos_graph_init();
check_driver(PCIBUS_PC);
check_driver(I440FX);
check_contains(PCIBUS_PC, I440FX);
qos_graph_destroy();
}
static void test_machine_produces_interface(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_produces(MACHINE_PC, SDHCI);
qos_graph_destroy();
}
static void test_driver_produces_interface(void)
{
qos_graph_init();
check_driver(I440FX);
check_produces(I440FX, SDHCI);
qos_graph_destroy();
}
static void test_machine_consumes_interface(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_consumes(MACHINE_PC, SDHCI);
qos_graph_destroy();
}
static void test_driver_consumes_interface(void)
{
qos_graph_init();
check_driver(I440FX);
check_consumes(I440FX, SDHCI);
qos_graph_destroy();
}
static void test_test_consumes_interface(void)
{
qos_graph_init();
check_test(REGISTER_TEST, SDHCI);
qos_graph_destroy();
}
static void test_full_sample(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_contains(MACHINE_PC, I440FX);
check_driver(I440FX);
check_driver(PCIBUS_PC);
check_contains(I440FX, PCIBUS_PC);
check_produces(PCIBUS_PC, PCIBUS);
check_driver(SDHCI_PCI);
qos_node_consumes(SDHCI_PCI, PCIBUS, NULL);
check_produces(SDHCI_PCI, SDHCI);
check_driver(SDHCI_MM);
check_produces(SDHCI_MM, SDHCI);
qos_add_test(REGISTER_TEST, SDHCI, testfunct, NULL);
check_leaf_discovered(1);
qos_print_graph();
qos_graph_destroy();
}
static void test_full_sample_raspi(void)
{
qos_graph_init();
check_machine(MACHINE_PC);
check_contains(MACHINE_PC, I440FX);
check_driver(I440FX);
check_driver(PCIBUS_PC);
check_contains(I440FX, PCIBUS_PC);
check_produces(PCIBUS_PC, PCIBUS);
check_driver(SDHCI_PCI);
qos_node_consumes(SDHCI_PCI, PCIBUS, NULL);
check_produces(SDHCI_PCI, SDHCI);
check_machine(MACHINE_RASPI2);
check_contains(MACHINE_RASPI2, SDHCI_MM);
check_driver(SDHCI_MM);
check_produces(SDHCI_MM, SDHCI);
qos_add_test(REGISTER_TEST, SDHCI, testfunct, NULL);
qos_print_graph();
check_leaf_discovered(2);
qos_graph_destroy();
}
static void test_cycle(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_driver("B");
check_driver("C");
check_driver("D");
check_contains(MACHINE_RASPI2, "B");
check_contains("B", "C");
check_contains("C", "D");
check_contains("D", MACHINE_RASPI2);
check_leaf_discovered(0);
qos_print_graph();
qos_graph_destroy();
}
static void test_two_test_same_interface(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_produces(MACHINE_RASPI2, "B");
qos_add_test("C", "B", testfunct, NULL);
qos_add_test("D", "B", testfunct, NULL);
check_contains(MACHINE_RASPI2, "B");
check_leaf_discovered(4);
qos_print_graph();
qos_graph_destroy();
}
static void test_test_in_path(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_produces(MACHINE_RASPI2, "B");
qos_add_test("C", "B", testfunct, NULL);
check_driver("D");
check_consumes("D", "B");
check_produces("D", "E");
qos_add_test("F", "E", testfunct, NULL);
check_leaf_discovered(2);
qos_print_graph();
qos_graph_destroy();
}
static void test_double_edge(void)
{
qos_graph_init();
check_machine(MACHINE_RASPI2);
check_produces("B", "C");
qos_node_consumes("C", "B", NULL);
qos_add_test("D", "C", testfunct, NULL);
check_contains(MACHINE_RASPI2, "B");
qos_print_graph();
qos_graph_destroy();
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qgraph/init_nop", init_nop);
g_test_add_func("/qgraph/test_machine", test_machine);
g_test_add_func("/qgraph/test_contains", test_contains);
g_test_add_func("/qgraph/test_multiple_contains", test_multiple_contains);
g_test_add_func("/qgraph/test_produces", test_produces);
g_test_add_func("/qgraph/test_multiple_produces", test_multiple_produces);
g_test_add_func("/qgraph/test_consumes", test_consumes);
g_test_add_func("/qgraph/test_multiple_consumes",
test_multiple_consumes);
g_test_add_func("/qgraph/test_driver", test_driver);
g_test_add_func("/qgraph/test_test", test_test);
g_test_add_func("/qgraph/test_machine_contains_driver",
test_machine_contains_driver);
g_test_add_func("/qgraph/test_driver_contains_driver",
test_driver_contains_driver);
g_test_add_func("/qgraph/test_machine_produces_interface",
test_machine_produces_interface);
g_test_add_func("/qgraph/test_driver_produces_interface",
test_driver_produces_interface);
g_test_add_func("/qgraph/test_machine_consumes_interface",
test_machine_consumes_interface);
g_test_add_func("/qgraph/test_driver_consumes_interface",
test_driver_consumes_interface);
g_test_add_func("/qgraph/test_test_consumes_interface",
test_test_consumes_interface);
g_test_add_func("/qgraph/test_full_sample", test_full_sample);
g_test_add_func("/qgraph/test_full_sample_raspi", test_full_sample_raspi);
g_test_add_func("/qgraph/test_cycle", test_cycle);
g_test_add_func("/qgraph/test_two_test_same_interface",
test_two_test_same_interface);
g_test_add_func("/qgraph/test_test_in_path", test_test_in_path);
g_test_add_func("/qgraph/test_double_edge", test_double_edge);
g_test_run();
return 0;
}

260
tests/unit/test-qht.c Normal file
View file

@ -0,0 +1,260 @@
/*
* Copyright (C) 2016, Emilio G. Cota <cota@braap.org>
*
* License: GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/qht.h"
#include "qemu/rcu.h"
#define N 5000
static struct qht ht;
static int32_t arr[N * 2];
static bool is_equal(const void *ap, const void *bp)
{
const int32_t *a = ap;
const int32_t *b = bp;
return *a == *b;
}
static void insert(int a, int b)
{
int i;
for (i = a; i < b; i++) {
uint32_t hash;
void *existing;
bool inserted;
arr[i] = i;
hash = i;
inserted = qht_insert(&ht, &arr[i], hash, NULL);
g_assert_true(inserted);
inserted = qht_insert(&ht, &arr[i], hash, &existing);
g_assert_false(inserted);
g_assert_true(existing == &arr[i]);
}
}
static void do_rm(int init, int end, bool exist)
{
int i;
for (i = init; i < end; i++) {
uint32_t hash;
hash = arr[i];
if (exist) {
g_assert_true(qht_remove(&ht, &arr[i], hash));
} else {
g_assert_false(qht_remove(&ht, &arr[i], hash));
}
}
}
static void rm(int init, int end)
{
do_rm(init, end, true);
}
static void rm_nonexist(int init, int end)
{
do_rm(init, end, false);
}
static void check(int a, int b, bool expected)
{
struct qht_stats stats;
int i;
rcu_read_lock();
for (i = a; i < b; i++) {
void *p;
uint32_t hash;
int32_t val;
val = i;
hash = i;
/* test both lookup variants; results should be the same */
if (i % 2) {
p = qht_lookup(&ht, &val, hash);
} else {
p = qht_lookup_custom(&ht, &val, hash, is_equal);
}
g_assert_true(!!p == expected);
}
rcu_read_unlock();
qht_statistics_init(&ht, &stats);
if (stats.used_head_buckets) {
g_assert_cmpfloat(qdist_avg(&stats.chain), >=, 1.0);
}
g_assert_cmpuint(stats.head_buckets, >, 0);
qht_statistics_destroy(&stats);
}
static void count_func(void *p, uint32_t hash, void *userp)
{
unsigned int *curr = userp;
(*curr)++;
}
static void check_n(size_t expected)
{
struct qht_stats stats;
qht_statistics_init(&ht, &stats);
g_assert_cmpuint(stats.entries, ==, expected);
qht_statistics_destroy(&stats);
}
static void iter_check(unsigned int count)
{
unsigned int curr = 0;
qht_iter(&ht, count_func, &curr);
g_assert_cmpuint(curr, ==, count);
}
static void sum_func(void *p, uint32_t hash, void *userp)
{
uint32_t *sum = userp;
uint32_t a = *(uint32_t *)p;
*sum += a;
}
static void iter_sum_check(unsigned int expected)
{
unsigned int sum = 0;
qht_iter(&ht, sum_func, &sum);
g_assert_cmpuint(sum, ==, expected);
}
static bool rm_mod_func(void *p, uint32_t hash, void *userp)
{
uint32_t a = *(uint32_t *)p;
unsigned int mod = *(unsigned int *)userp;
return a % mod == 0;
}
static void iter_rm_mod(unsigned int mod)
{
qht_iter_remove(&ht, rm_mod_func, &mod);
}
static void iter_rm_mod_check(unsigned int mod)
{
unsigned int expected = 0;
unsigned int i;
for (i = 0; i < N; i++) {
if (i % mod == 0) {
continue;
}
expected += i;
}
iter_sum_check(expected);
}
static void qht_do_test(unsigned int mode, size_t init_entries)
{
/* under KVM we might fetch stats from an uninitialized qht */
check_n(0);
qht_init(&ht, is_equal, 0, mode);
rm_nonexist(0, 4);
/*
* Test that we successfully delete the last element in a bucket.
* This is a hard-to-reach code path when resizing is on, but without
* resizing we can easily hit it if init_entries <= 1.
* Given that the number of elements per bucket can be 4 or 6 depending on
* the host's pointer size, test the removal of the 4th and 6th elements.
*/
insert(0, 4);
rm_nonexist(5, 6);
rm(3, 4);
check_n(3);
insert(3, 6);
rm(5, 6);
check_n(5);
rm_nonexist(7, 8);
iter_rm_mod(1);
if (!(mode & QHT_MODE_AUTO_RESIZE)) {
qht_resize(&ht, init_entries * 4 + 4);
}
check_n(0);
rm_nonexist(0, 10);
insert(0, N);
check(0, N, true);
check_n(N);
check(-N, -1, false);
iter_check(N);
rm(101, 102);
check_n(N - 1);
insert(N, N * 2);
check_n(N + N - 1);
rm(N, N * 2);
check_n(N - 1);
insert(101, 102);
check_n(N);
rm(10, 200);
check_n(N - 190);
insert(150, 200);
check_n(N - 190 + 50);
insert(10, 150);
check_n(N);
qht_reset(&ht);
insert(0, N);
rm_nonexist(N, N + 32);
iter_rm_mod(10);
iter_rm_mod_check(10);
check_n(N * 9 / 10);
qht_reset_size(&ht, 0);
check_n(0);
check(0, N, false);
qht_destroy(&ht);
}
static void qht_test(unsigned int mode)
{
qht_do_test(mode, 0);
qht_do_test(mode, 1);
qht_do_test(mode, 2);
qht_do_test(mode, 8);
qht_do_test(mode, 16);
qht_do_test(mode, 8192);
qht_do_test(mode, 16384);
}
static void test_default(void)
{
qht_test(0);
}
static void test_resize(void)
{
qht_test(QHT_MODE_AUTO_RESIZE);
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qht/mode/default", test_default);
g_test_add_func("/qht/mode/resize", test_resize);
return g_test_run();
}

359
tests/unit/test-qmp-cmds.c Normal file
View file

@ -0,0 +1,359 @@
#include "qemu/osdep.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qstring.h"
#include "qapi/error.h"
#include "qapi/qobject-input-visitor.h"
#include "tests/test-qapi-types.h"
#include "tests/test-qapi-visit.h"
#include "test-qapi-commands.h"
#include "test-qapi-init-commands.h"
static QmpCommandList qmp_commands;
#if defined(TEST_IF_STRUCT) && defined(TEST_IF_CMD)
UserDefThree *qmp_TestIfCmd(TestIfStruct *foo, Error **errp)
{
return NULL;
}
#endif
UserDefThree *qmp_TestCmdReturnDefThree(Error **errp)
{
return NULL;
}
void qmp_user_def_cmd(Error **errp)
{
}
void qmp_test_flags_command(Error **errp)
{
}
void qmp_cmd_success_response(Error **errp)
{
}
void qmp_coroutine_cmd(Error **errp)
{
}
Empty2 *qmp_user_def_cmd0(Error **errp)
{
return g_new0(Empty2, 1);
}
void qmp_user_def_cmd1(UserDefOne * ud1, Error **errp)
{
}
void qmp_test_features0(FeatureStruct0 *fs0, FeatureStruct1 *fs1,
FeatureStruct2 *fs2, FeatureStruct3 *fs3,
FeatureStruct4 *fs4, CondFeatureStruct1 *cfs1,
CondFeatureStruct2 *cfs2, CondFeatureStruct3 *cfs3,
Error **errp)
{
}
void qmp_test_command_features1(Error **errp)
{
}
void qmp_test_command_features3(Error **errp)
{
}
void qmp_test_command_cond_features1(Error **errp)
{
}
void qmp_test_command_cond_features2(Error **errp)
{
}
void qmp_test_command_cond_features3(Error **errp)
{
}
UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a,
bool has_udb1, UserDefOne *ud1b,
Error **errp)
{
UserDefTwo *ret;
UserDefOne *ud1c = g_malloc0(sizeof(UserDefOne));
UserDefOne *ud1d = g_malloc0(sizeof(UserDefOne));
ud1c->string = strdup(ud1a->string);
ud1c->integer = ud1a->integer;
ud1d->string = strdup(has_udb1 ? ud1b->string : "blah0");
ud1d->integer = has_udb1 ? ud1b->integer : 0;
ret = g_new0(UserDefTwo, 1);
ret->string0 = strdup("blah1");
ret->dict1 = g_new0(UserDefTwoDict, 1);
ret->dict1->string1 = strdup("blah2");
ret->dict1->dict2 = g_new0(UserDefTwoDictDict, 1);
ret->dict1->dict2->userdef = ud1c;
ret->dict1->dict2->string = strdup("blah3");
ret->dict1->dict3 = g_new0(UserDefTwoDictDict, 1);
ret->dict1->has_dict3 = true;
ret->dict1->dict3->userdef = ud1d;
ret->dict1->dict3->string = strdup("blah4");
return ret;
}
int64_t qmp_guest_get_time(int64_t a, bool has_b, int64_t b, Error **errp)
{
return a + (has_b ? b : 0);
}
QObject *qmp_guest_sync(QObject *arg, Error **errp)
{
return arg;
}
void qmp_boxed_struct(UserDefZero *arg, Error **errp)
{
}
void qmp_boxed_union(UserDefListUnion *arg, Error **errp)
{
}
void qmp_boxed_empty(Empty1 *arg, Error **errp)
{
}
__org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
__org_qemu_x_StructList *b,
__org_qemu_x_Union2 *c,
__org_qemu_x_Alt *d,
Error **errp)
{
__org_qemu_x_Union1 *ret = g_new0(__org_qemu_x_Union1, 1);
ret->type = ORG_QEMU_X_UNION1_KIND___ORG_QEMU_X_BRANCH;
ret->u.__org_qemu_x_branch.data = strdup("blah1");
/* Also test that 'wchar-t' was munged to 'q_wchar_t' */
if (b && b->value && !b->value->has_q_wchar_t) {
b->value->q_wchar_t = 1;
}
return ret;
}
static QObject *do_qmp_dispatch(bool allow_oob, const char *template, ...)
{
va_list ap;
QDict *req, *resp;
QObject *ret;
va_start(ap, template);
req = qdict_from_vjsonf_nofail(template, ap);
va_end(ap);
resp = qmp_dispatch(&qmp_commands, QOBJECT(req), allow_oob, NULL);
g_assert(resp);
ret = qdict_get(resp, "return");
g_assert(ret);
g_assert(qdict_size(resp) == 1);
qobject_ref(ret);
qobject_unref(resp);
qobject_unref(req);
return ret;
}
static void do_qmp_dispatch_error(bool allow_oob, ErrorClass cls,
const char *template, ...)
{
va_list ap;
QDict *req, *resp;
QDict *error;
va_start(ap, template);
req = qdict_from_vjsonf_nofail(template, ap);
va_end(ap);
resp = qmp_dispatch(&qmp_commands, QOBJECT(req), allow_oob, NULL);
g_assert(resp);
error = qdict_get_qdict(resp, "error");
g_assert(error);
g_assert_cmpstr(qdict_get_try_str(error, "class"),
==, QapiErrorClass_str(cls));
g_assert(qdict_get_try_str(error, "desc"));
g_assert(qdict_size(error) == 2);
g_assert(qdict_size(resp) == 1);
qobject_unref(resp);
qobject_unref(req);
}
/* test commands with no input and no return value */
static void test_dispatch_cmd(void)
{
QDict *ret;
ret = qobject_to(QDict,
do_qmp_dispatch(false,
"{ 'execute': 'user_def_cmd' }"));
assert(ret && qdict_size(ret) == 0);
qobject_unref(ret);
}
static void test_dispatch_cmd_oob(void)
{
QDict *ret;
ret = qobject_to(QDict,
do_qmp_dispatch(true,
"{ 'exec-oob': 'test-flags-command' }"));
assert(ret && qdict_size(ret) == 0);
qobject_unref(ret);
}
/* test commands that return an error due to invalid parameters */
static void test_dispatch_cmd_failure(void)
{
/* missing arguments */
do_qmp_dispatch_error(false, ERROR_CLASS_GENERIC_ERROR,
"{ 'execute': 'user_def_cmd2' }");
/* extra arguments */
do_qmp_dispatch_error(false, ERROR_CLASS_GENERIC_ERROR,
"{ 'execute': 'user_def_cmd',"
" 'arguments': { 'a': 66 } }");
}
static void test_dispatch_cmd_success_response(void)
{
QDict *req = qdict_new();
QDict *resp;
qdict_put_str(req, "execute", "cmd-success-response");
resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false, NULL);
g_assert_null(resp);
qobject_unref(req);
}
/* test commands that involve both input parameters and return values */
static void test_dispatch_cmd_io(void)
{
QDict *ret, *ret_dict, *ret_dict_dict, *ret_dict_dict_userdef;
QDict *ret_dict_dict2, *ret_dict_dict2_userdef;
QNum *ret3;
int64_t val;
ret = qobject_to(QDict, do_qmp_dispatch(false,
"{ 'execute': 'user_def_cmd2', 'arguments': {"
" 'ud1a': { 'integer': 42, 'string': 'hello' },"
" 'ud1b': { 'integer': 422, 'string': 'hello2' } } }"));
assert(!strcmp(qdict_get_str(ret, "string0"), "blah1"));
ret_dict = qdict_get_qdict(ret, "dict1");
assert(!strcmp(qdict_get_str(ret_dict, "string1"), "blah2"));
ret_dict_dict = qdict_get_qdict(ret_dict, "dict2");
ret_dict_dict_userdef = qdict_get_qdict(ret_dict_dict, "userdef");
assert(qdict_get_int(ret_dict_dict_userdef, "integer") == 42);
assert(!strcmp(qdict_get_str(ret_dict_dict_userdef, "string"), "hello"));
assert(!strcmp(qdict_get_str(ret_dict_dict, "string"), "blah3"));
ret_dict_dict2 = qdict_get_qdict(ret_dict, "dict3");
ret_dict_dict2_userdef = qdict_get_qdict(ret_dict_dict2, "userdef");
assert(qdict_get_int(ret_dict_dict2_userdef, "integer") == 422);
assert(!strcmp(qdict_get_str(ret_dict_dict2_userdef, "string"), "hello2"));
assert(!strcmp(qdict_get_str(ret_dict_dict2, "string"), "blah4"));
qobject_unref(ret);
ret3 = qobject_to(QNum, do_qmp_dispatch(false,
"{ 'execute': 'guest-get-time', 'arguments': { 'a': 66 } }"));
g_assert(qnum_get_try_int(ret3, &val));
g_assert_cmpint(val, ==, 66);
qobject_unref(ret3);
}
/* test generated dealloc functions for generated types */
static void test_dealloc_types(void)
{
UserDefOne *ud1test, *ud1a, *ud1b;
UserDefOneList *ud1list;
ud1test = g_malloc0(sizeof(UserDefOne));
ud1test->integer = 42;
ud1test->string = g_strdup("hi there 42");
qapi_free_UserDefOne(ud1test);
ud1a = g_malloc0(sizeof(UserDefOne));
ud1a->integer = 43;
ud1a->string = g_strdup("hi there 43");
ud1b = g_malloc0(sizeof(UserDefOne));
ud1b->integer = 44;
ud1b->string = g_strdup("hi there 44");
ud1list = g_malloc0(sizeof(UserDefOneList));
ud1list->value = ud1a;
ud1list->next = g_malloc0(sizeof(UserDefOneList));
ud1list->next->value = ud1b;
qapi_free_UserDefOneList(ud1list);
}
/* test generated deallocation on an object whose construction was prematurely
* terminated due to an error */
static void test_dealloc_partial(void)
{
static const char text[] = "don't leak me";
UserDefTwo *ud2 = NULL;
Error *err = NULL;
/* create partial object */
{
QDict *ud2_dict;
Visitor *v;
ud2_dict = qdict_new();
qdict_put_str(ud2_dict, "string0", text);
v = qobject_input_visitor_new(QOBJECT(ud2_dict));
visit_type_UserDefTwo(v, NULL, &ud2, &err);
visit_free(v);
qobject_unref(ud2_dict);
}
/* verify that visit_type_XXX() cleans up properly on error */
error_free_or_abort(&err);
assert(!ud2);
/* Manually create a partial object, leaving ud2->dict1 at NULL */
ud2 = g_new0(UserDefTwo, 1);
ud2->string0 = g_strdup(text);
/* tear down partial object */
qapi_free_UserDefTwo(ud2);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qmp/dispatch_cmd", test_dispatch_cmd);
g_test_add_func("/qmp/dispatch_cmd_oob", test_dispatch_cmd_oob);
g_test_add_func("/qmp/dispatch_cmd_failure", test_dispatch_cmd_failure);
g_test_add_func("/qmp/dispatch_cmd_io", test_dispatch_cmd_io);
g_test_add_func("/qmp/dispatch_cmd_success_response",
test_dispatch_cmd_success_response);
g_test_add_func("/qmp/dealloc_types", test_dealloc_types);
g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
test_qmp_init_marshal(&qmp_commands);
g_test_run();
return 0;
}

154
tests/unit/test-qmp-event.c Normal file
View file

@ -0,0 +1,154 @@
/*
* qapi event unit-tests.
*
* Copyright (c) 2014 Wenchao Xia
*
* Authors:
* Wenchao Xia <wenchaoqemu@gmail.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/error.h"
#include "qapi/qmp/qbool.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qmp-event.h"
#include "test-qapi-events.h"
#include "test-qapi-emit-events.h"
typedef struct TestEventData {
QDict *expect;
bool emitted;
} TestEventData;
TestEventData *test_event_data;
static GMutex test_event_lock;
void test_qapi_event_emit(test_QAPIEvent event, QDict *d)
{
QDict *t;
int64_t s, ms;
/* Verify that we have timestamp, then remove it to compare other fields */
t = qdict_get_qdict(d, "timestamp");
g_assert(t);
s = qdict_get_try_int(t, "seconds", -2);
ms = qdict_get_try_int(t, "microseconds", -2);
if (s == -1) {
g_assert(ms == -1);
} else {
g_assert(s >= 0);
g_assert(ms >= 0 && ms <= 999999);
}
g_assert(qdict_size(t) == 2);
qdict_del(d, "timestamp");
g_assert(qobject_is_equal(QOBJECT(d), QOBJECT(test_event_data->expect)));
test_event_data->emitted = true;
}
static void event_prepare(TestEventData *data,
const void *unused)
{
/* Global variable test_event_data was used to pass the expectation, so
test cases can't be executed at same time. */
g_mutex_lock(&test_event_lock);
test_event_data = data;
}
static void event_teardown(TestEventData *data,
const void *unused)
{
test_event_data = NULL;
g_mutex_unlock(&test_event_lock);
}
static void event_test_add(const char *testpath,
void (*test_func)(TestEventData *data,
const void *user_data))
{
g_test_add(testpath, TestEventData, NULL, event_prepare, test_func,
event_teardown);
}
/* Test cases */
static void test_event_a(TestEventData *data,
const void *unused)
{
data->expect = qdict_from_jsonf_nofail("{ 'event': 'EVENT_A' }");
qapi_event_send_event_a();
g_assert(data->emitted);
qobject_unref(data->expect);
}
static void test_event_b(TestEventData *data,
const void *unused)
{
data->expect = qdict_from_jsonf_nofail("{ 'event': 'EVENT_B' }");
qapi_event_send_event_b();
g_assert(data->emitted);
qobject_unref(data->expect);
}
static void test_event_c(TestEventData *data,
const void *unused)
{
UserDefOne b = { .integer = 2, .string = (char *)"test1" };
data->expect = qdict_from_jsonf_nofail(
"{ 'event': 'EVENT_C', 'data': {"
" 'a': 1, 'b': { 'integer': 2, 'string': 'test1' }, 'c': 'test2' } }");
qapi_event_send_event_c(true, 1, true, &b, "test2");
g_assert(data->emitted);
qobject_unref(data->expect);
}
/* Complex type */
static void test_event_d(TestEventData *data,
const void *unused)
{
UserDefOne struct1 = {
.integer = 2, .string = (char *)"test1",
.has_enum1 = true, .enum1 = ENUM_ONE_VALUE1,
};
EventStructOne a = {
.struct1 = &struct1,
.string = (char *)"test2",
.has_enum2 = true,
.enum2 = ENUM_ONE_VALUE2,
};
data->expect = qdict_from_jsonf_nofail(
"{ 'event': 'EVENT_D', 'data': {"
" 'a': {"
" 'struct1': { 'integer': 2, 'string': 'test1', 'enum1': 'value1' },"
" 'string': 'test2', 'enum2': 'value2' },"
" 'b': 'test3', 'enum3': 'value3' } }");
qapi_event_send_event_d(&a, "test3", false, NULL, true, ENUM_ONE_VALUE3);
g_assert(data->emitted);
qobject_unref(data->expect);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
event_test_add("/event/event_a", test_event_a);
event_test_add("/event/event_b", test_event_b);
event_test_add("/event/event_c", test_event_c);
event_test_add("/event/event_d", test_event_d);
g_test_run();
return 0;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,807 @@
/*
* QObject Output Visitor unit-tests.
*
* Copyright (C) 2011-2016 Red Hat Inc.
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/error.h"
#include "qapi/qobject-output-visitor.h"
#include "test-qapi-visit.h"
#include "qapi/qmp/qbool.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qmp/qnull.h"
#include "qapi/qmp/qnum.h"
#include "qapi/qmp/qstring.h"
typedef struct TestOutputVisitorData {
Visitor *ov;
QObject *obj;
} TestOutputVisitorData;
static void visitor_output_setup(TestOutputVisitorData *data,
const void *unused)
{
data->ov = qobject_output_visitor_new(&data->obj);
g_assert(data->ov);
}
static void visitor_output_teardown(TestOutputVisitorData *data,
const void *unused)
{
visit_free(data->ov);
data->ov = NULL;
qobject_unref(data->obj);
data->obj = NULL;
}
static QObject *visitor_get(TestOutputVisitorData *data)
{
visit_complete(data->ov, &data->obj);
g_assert(data->obj);
return data->obj;
}
static void visitor_reset(TestOutputVisitorData *data)
{
visitor_output_teardown(data, NULL);
visitor_output_setup(data, NULL);
}
static void test_visitor_out_int(TestOutputVisitorData *data,
const void *unused)
{
int64_t value = -42;
int64_t val;
QNum *qnum;
visit_type_int(data->ov, NULL, &value, &error_abort);
qnum = qobject_to(QNum, visitor_get(data));
g_assert(qnum);
g_assert(qnum_get_try_int(qnum, &val));
g_assert_cmpint(val, ==, value);
}
static void test_visitor_out_bool(TestOutputVisitorData *data,
const void *unused)
{
bool value = true;
QBool *qbool;
visit_type_bool(data->ov, NULL, &value, &error_abort);
qbool = qobject_to(QBool, visitor_get(data));
g_assert(qbool);
g_assert(qbool_get_bool(qbool) == value);
}
static void test_visitor_out_number(TestOutputVisitorData *data,
const void *unused)
{
double value = 3.14;
QNum *qnum;
visit_type_number(data->ov, NULL, &value, &error_abort);
qnum = qobject_to(QNum, visitor_get(data));
g_assert(qnum);
g_assert(qnum_get_double(qnum) == value);
}
static void test_visitor_out_string(TestOutputVisitorData *data,
const void *unused)
{
char *string = (char *) "Q E M U";
QString *qstr;
visit_type_str(data->ov, NULL, &string, &error_abort);
qstr = qobject_to(QString, visitor_get(data));
g_assert(qstr);
g_assert_cmpstr(qstring_get_str(qstr), ==, string);
}
static void test_visitor_out_no_string(TestOutputVisitorData *data,
const void *unused)
{
char *string = NULL;
QString *qstr;
/* A null string should return "" */
visit_type_str(data->ov, NULL, &string, &error_abort);
qstr = qobject_to(QString, visitor_get(data));
g_assert(qstr);
g_assert_cmpstr(qstring_get_str(qstr), ==, "");
}
static void test_visitor_out_enum(TestOutputVisitorData *data,
const void *unused)
{
EnumOne i;
QString *qstr;
for (i = 0; i < ENUM_ONE__MAX; i++) {
visit_type_EnumOne(data->ov, "unused", &i, &error_abort);
qstr = qobject_to(QString, visitor_get(data));
g_assert(qstr);
g_assert_cmpstr(qstring_get_str(qstr), ==, EnumOne_str(i));
visitor_reset(data);
}
}
static void test_visitor_out_struct(TestOutputVisitorData *data,
const void *unused)
{
TestStruct test_struct = { .integer = 42,
.boolean = false,
.string = (char *) "foo"};
TestStruct *p = &test_struct;
QDict *qdict;
visit_type_TestStruct(data->ov, NULL, &p, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpint(qdict_size(qdict), ==, 3);
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 42);
g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, false);
g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, "foo");
}
static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
const void *unused)
{
int64_t value = 42;
UserDefTwo *ud2;
QDict *qdict, *dict1, *dict2, *dict3, *userdef;
const char *string = "user def string";
const char *strings[] = { "forty two", "forty three", "forty four",
"forty five" };
ud2 = g_malloc0(sizeof(*ud2));
ud2->string0 = g_strdup(strings[0]);
ud2->dict1 = g_malloc0(sizeof(*ud2->dict1));
ud2->dict1->string1 = g_strdup(strings[1]);
ud2->dict1->dict2 = g_malloc0(sizeof(*ud2->dict1->dict2));
ud2->dict1->dict2->userdef = g_new0(UserDefOne, 1);
ud2->dict1->dict2->userdef->string = g_strdup(string);
ud2->dict1->dict2->userdef->integer = value;
ud2->dict1->dict2->string = g_strdup(strings[2]);
ud2->dict1->dict3 = g_malloc0(sizeof(*ud2->dict1->dict3));
ud2->dict1->has_dict3 = true;
ud2->dict1->dict3->userdef = g_new0(UserDefOne, 1);
ud2->dict1->dict3->userdef->string = g_strdup(string);
ud2->dict1->dict3->userdef->integer = value;
ud2->dict1->dict3->string = g_strdup(strings[3]);
visit_type_UserDefTwo(data->ov, "unused", &ud2, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpint(qdict_size(qdict), ==, 2);
g_assert_cmpstr(qdict_get_str(qdict, "string0"), ==, strings[0]);
dict1 = qdict_get_qdict(qdict, "dict1");
g_assert_cmpint(qdict_size(dict1), ==, 3);
g_assert_cmpstr(qdict_get_str(dict1, "string1"), ==, strings[1]);
dict2 = qdict_get_qdict(dict1, "dict2");
g_assert_cmpint(qdict_size(dict2), ==, 2);
g_assert_cmpstr(qdict_get_str(dict2, "string"), ==, strings[2]);
userdef = qdict_get_qdict(dict2, "userdef");
g_assert_cmpint(qdict_size(userdef), ==, 2);
g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value);
g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string);
dict3 = qdict_get_qdict(dict1, "dict3");
g_assert_cmpint(qdict_size(dict3), ==, 2);
g_assert_cmpstr(qdict_get_str(dict3, "string"), ==, strings[3]);
userdef = qdict_get_qdict(dict3, "userdef");
g_assert_cmpint(qdict_size(userdef), ==, 2);
g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value);
g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string);
qapi_free_UserDefTwo(ud2);
}
static void test_visitor_out_list(TestOutputVisitorData *data,
const void *unused)
{
const char *value_str = "list value";
TestStruct *value;
TestStructList *head = NULL;
const int max_items = 10;
bool value_bool = true;
int value_int = 10;
QListEntry *entry;
QList *qlist;
int i;
/* Build the list in reverse order... */
for (i = 0; i < max_items; i++) {
value = g_malloc0(sizeof(*value));
value->integer = value_int + (max_items - i - 1);
value->boolean = value_bool;
value->string = g_strdup(value_str);
QAPI_LIST_PREPEND(head, value);
}
visit_type_TestStructList(data->ov, NULL, &head, &error_abort);
qlist = qobject_to(QList, visitor_get(data));
g_assert(qlist);
g_assert(!qlist_empty(qlist));
/* ...and ensure that the visitor sees it in order */
i = 0;
QLIST_FOREACH_ENTRY(qlist, entry) {
QDict *qdict;
qdict = qobject_to(QDict, entry->value);
g_assert(qdict);
g_assert_cmpint(qdict_size(qdict), ==, 3);
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, value_int + i);
g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, value_bool);
g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, value_str);
i++;
}
g_assert_cmpint(i, ==, max_items);
qapi_free_TestStructList(head);
}
static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data,
const void *unused)
{
UserDefTwo *value;
UserDefTwoList *head = NULL;
const char string[] = "foo bar";
int i, max_count = 1024;
for (i = 0; i < max_count; i++) {
value = g_malloc0(sizeof(*value));
value->string0 = g_strdup(string);
value->dict1 = g_new0(UserDefTwoDict, 1);
value->dict1->string1 = g_strdup(string);
value->dict1->dict2 = g_new0(UserDefTwoDictDict, 1);
value->dict1->dict2->userdef = g_new0(UserDefOne, 1);
value->dict1->dict2->userdef->string = g_strdup(string);
value->dict1->dict2->userdef->integer = 42;
value->dict1->dict2->string = g_strdup(string);
value->dict1->has_dict3 = false;
QAPI_LIST_PREPEND(head, value);
}
qapi_free_UserDefTwoList(head);
}
static void test_visitor_out_any(TestOutputVisitorData *data,
const void *unused)
{
QObject *qobj;
QNum *qnum;
QBool *qbool;
QString *qstring;
QDict *qdict;
int64_t val;
qobj = QOBJECT(qnum_from_int(-42));
visit_type_any(data->ov, NULL, &qobj, &error_abort);
qnum = qobject_to(QNum, visitor_get(data));
g_assert(qnum);
g_assert(qnum_get_try_int(qnum, &val));
g_assert_cmpint(val, ==, -42);
qobject_unref(qobj);
visitor_reset(data);
qdict = qdict_new();
qdict_put_int(qdict, "integer", -42);
qdict_put_bool(qdict, "boolean", true);
qdict_put_str(qdict, "string", "foo");
qobj = QOBJECT(qdict);
visit_type_any(data->ov, NULL, &qobj, &error_abort);
qobject_unref(qobj);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
qnum = qobject_to(QNum, qdict_get(qdict, "integer"));
g_assert(qnum);
g_assert(qnum_get_try_int(qnum, &val));
g_assert_cmpint(val, ==, -42);
qbool = qobject_to(QBool, qdict_get(qdict, "boolean"));
g_assert(qbool);
g_assert(qbool_get_bool(qbool) == true);
qstring = qobject_to(QString, qdict_get(qdict, "string"));
g_assert(qstring);
g_assert_cmpstr(qstring_get_str(qstring), ==, "foo");
}
static void test_visitor_out_union_flat(TestOutputVisitorData *data,
const void *unused)
{
QDict *qdict;
UserDefFlatUnion *tmp = g_malloc0(sizeof(UserDefFlatUnion));
tmp->enum1 = ENUM_ONE_VALUE1;
tmp->string = g_strdup("str");
tmp->integer = 41;
tmp->u.value1.boolean = true;
visit_type_UserDefFlatUnion(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpstr(qdict_get_str(qdict, "enum1"), ==, "value1");
g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, "str");
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 41);
g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, true);
qapi_free_UserDefFlatUnion(tmp);
}
static void test_visitor_out_alternate(TestOutputVisitorData *data,
const void *unused)
{
UserDefAlternate *tmp;
QNum *qnum;
QString *qstr;
QDict *qdict;
int64_t val;
tmp = g_new0(UserDefAlternate, 1);
tmp->type = QTYPE_QNUM;
tmp->u.i = 42;
visit_type_UserDefAlternate(data->ov, NULL, &tmp, &error_abort);
qnum = qobject_to(QNum, visitor_get(data));
g_assert(qnum);
g_assert(qnum_get_try_int(qnum, &val));
g_assert_cmpint(val, ==, 42);
qapi_free_UserDefAlternate(tmp);
visitor_reset(data);
tmp = g_new0(UserDefAlternate, 1);
tmp->type = QTYPE_QSTRING;
tmp->u.e = ENUM_ONE_VALUE1;
visit_type_UserDefAlternate(data->ov, NULL, &tmp, &error_abort);
qstr = qobject_to(QString, visitor_get(data));
g_assert(qstr);
g_assert_cmpstr(qstring_get_str(qstr), ==, "value1");
qapi_free_UserDefAlternate(tmp);
visitor_reset(data);
tmp = g_new0(UserDefAlternate, 1);
tmp->type = QTYPE_QNULL;
tmp->u.n = qnull();
visit_type_UserDefAlternate(data->ov, NULL, &tmp, &error_abort);
g_assert_cmpint(qobject_type(visitor_get(data)), ==, QTYPE_QNULL);
qapi_free_UserDefAlternate(tmp);
visitor_reset(data);
tmp = g_new0(UserDefAlternate, 1);
tmp->type = QTYPE_QDICT;
tmp->u.udfu.integer = 1;
tmp->u.udfu.string = g_strdup("str");
tmp->u.udfu.enum1 = ENUM_ONE_VALUE1;
tmp->u.udfu.u.value1.boolean = true;
visit_type_UserDefAlternate(data->ov, NULL, &tmp, &error_abort);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpint(qdict_size(qdict), ==, 4);
g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 1);
g_assert_cmpstr(qdict_get_str(qdict, "string"), ==, "str");
g_assert_cmpstr(qdict_get_str(qdict, "enum1"), ==, "value1");
g_assert_cmpint(qdict_get_bool(qdict, "boolean"), ==, true);
qapi_free_UserDefAlternate(tmp);
}
static void test_visitor_out_null(TestOutputVisitorData *data,
const void *unused)
{
QNull *null = NULL;
QDict *qdict;
QObject *nil;
visit_start_struct(data->ov, NULL, NULL, 0, &error_abort);
visit_type_null(data->ov, "a", &null, &error_abort);
visit_check_struct(data->ov, &error_abort);
visit_end_struct(data->ov, NULL);
qdict = qobject_to(QDict, visitor_get(data));
g_assert(qdict);
g_assert_cmpint(qdict_size(qdict), ==, 1);
nil = qdict_get(qdict, "a");
g_assert(nil);
g_assert(qobject_type(nil) == QTYPE_QNULL);
}
static void init_list_union(UserDefListUnion *cvalue)
{
int i;
switch (cvalue->type) {
case USER_DEF_LIST_UNION_KIND_INTEGER: {
intList **tail = &cvalue->u.integer.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_S8: {
int8List **tail = &cvalue->u.s8.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_S16: {
int16List **tail = &cvalue->u.s16.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_S32: {
int32List **tail = &cvalue->u.s32.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_S64: {
int64List **tail = &cvalue->u.s64.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_U8: {
uint8List **tail = &cvalue->u.u8.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_U16: {
uint16List **tail = &cvalue->u.u16.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_U32: {
uint32List **tail = &cvalue->u.u32.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_U64: {
uint64List **tail = &cvalue->u.u64.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, i);
}
break;
}
case USER_DEF_LIST_UNION_KIND_BOOLEAN: {
boolList **tail = &cvalue->u.boolean.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, QEMU_IS_ALIGNED(i, 3));
}
break;
}
case USER_DEF_LIST_UNION_KIND_STRING: {
strList **tail = &cvalue->u.string.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, g_strdup_printf("%d", i));
}
break;
}
case USER_DEF_LIST_UNION_KIND_NUMBER: {
numberList **tail = &cvalue->u.number.data;
for (i = 0; i < 32; i++) {
QAPI_LIST_APPEND(tail, (double)i / 3);
}
break;
}
default:
g_assert_not_reached();
}
}
static void check_list_union(QObject *qobj,
UserDefListUnionKind kind)
{
QDict *qdict;
QList *qlist;
int i;
qdict = qobject_to(QDict, qobj);
g_assert(qdict);
g_assert(qdict_haskey(qdict, "data"));
qlist = qlist_copy(qobject_to(QList, qdict_get(qdict, "data")));
switch (kind) {
case USER_DEF_LIST_UNION_KIND_U8:
case USER_DEF_LIST_UNION_KIND_U16:
case USER_DEF_LIST_UNION_KIND_U32:
case USER_DEF_LIST_UNION_KIND_U64:
for (i = 0; i < 32; i++) {
QObject *tmp;
QNum *qvalue;
uint64_t val;
tmp = qlist_peek(qlist);
g_assert(tmp);
qvalue = qobject_to(QNum, tmp);
g_assert(qnum_get_try_uint(qvalue, &val));
g_assert_cmpint(val, ==, i);
qobject_unref(qlist_pop(qlist));
}
break;
case USER_DEF_LIST_UNION_KIND_S8:
case USER_DEF_LIST_UNION_KIND_S16:
case USER_DEF_LIST_UNION_KIND_S32:
case USER_DEF_LIST_UNION_KIND_S64:
/*
* All integer elements in JSON arrays get stored into QNums
* when we convert to QObjects, so we can check them all in
* the same fashion, so simply fall through here.
*/
case USER_DEF_LIST_UNION_KIND_INTEGER:
for (i = 0; i < 32; i++) {
QObject *tmp;
QNum *qvalue;
int64_t val;
tmp = qlist_peek(qlist);
g_assert(tmp);
qvalue = qobject_to(QNum, tmp);
g_assert(qnum_get_try_int(qvalue, &val));
g_assert_cmpint(val, ==, i);
qobject_unref(qlist_pop(qlist));
}
break;
case USER_DEF_LIST_UNION_KIND_BOOLEAN:
for (i = 0; i < 32; i++) {
QObject *tmp;
QBool *qvalue;
tmp = qlist_peek(qlist);
g_assert(tmp);
qvalue = qobject_to(QBool, tmp);
g_assert_cmpint(qbool_get_bool(qvalue), ==, i % 3 == 0);
qobject_unref(qlist_pop(qlist));
}
break;
case USER_DEF_LIST_UNION_KIND_STRING:
for (i = 0; i < 32; i++) {
QObject *tmp;
QString *qvalue;
gchar str[8];
tmp = qlist_peek(qlist);
g_assert(tmp);
qvalue = qobject_to(QString, tmp);
sprintf(str, "%d", i);
g_assert_cmpstr(qstring_get_str(qvalue), ==, str);
qobject_unref(qlist_pop(qlist));
}
break;
case USER_DEF_LIST_UNION_KIND_NUMBER:
for (i = 0; i < 32; i++) {
QObject *tmp;
QNum *qvalue;
GString *double_expected = g_string_new("");
GString *double_actual = g_string_new("");
tmp = qlist_peek(qlist);
g_assert(tmp);
qvalue = qobject_to(QNum, tmp);
g_string_printf(double_expected, "%.6f", (double)i / 3);
g_string_printf(double_actual, "%.6f", qnum_get_double(qvalue));
g_assert_cmpstr(double_actual->str, ==, double_expected->str);
qobject_unref(qlist_pop(qlist));
g_string_free(double_expected, true);
g_string_free(double_actual, true);
}
break;
default:
g_assert_not_reached();
}
qobject_unref(qlist);
}
static void test_list_union(TestOutputVisitorData *data,
const void *unused,
UserDefListUnionKind kind)
{
UserDefListUnion *cvalue = g_new0(UserDefListUnion, 1);
QObject *obj;
cvalue->type = kind;
init_list_union(cvalue);
visit_type_UserDefListUnion(data->ov, NULL, &cvalue, &error_abort);
obj = visitor_get(data);
check_list_union(obj, cvalue->type);
qapi_free_UserDefListUnion(cvalue);
}
static void test_visitor_out_list_union_int(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_INTEGER);
}
static void test_visitor_out_list_union_int8(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_S8);
}
static void test_visitor_out_list_union_int16(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_S16);
}
static void test_visitor_out_list_union_int32(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_S32);
}
static void test_visitor_out_list_union_int64(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_S64);
}
static void test_visitor_out_list_union_uint8(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_U8);
}
static void test_visitor_out_list_union_uint16(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_U16);
}
static void test_visitor_out_list_union_uint32(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_U32);
}
static void test_visitor_out_list_union_uint64(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_U64);
}
static void test_visitor_out_list_union_bool(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_BOOLEAN);
}
static void test_visitor_out_list_union_str(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_STRING);
}
static void test_visitor_out_list_union_number(TestOutputVisitorData *data,
const void *unused)
{
test_list_union(data, unused, USER_DEF_LIST_UNION_KIND_NUMBER);
}
static void output_visitor_test_add(const char *testpath,
TestOutputVisitorData *data,
void (*test_func)(TestOutputVisitorData *data, const void *user_data))
{
g_test_add(testpath, TestOutputVisitorData, data, visitor_output_setup,
test_func, visitor_output_teardown);
}
int main(int argc, char **argv)
{
TestOutputVisitorData out_visitor_data;
g_test_init(&argc, &argv, NULL);
output_visitor_test_add("/visitor/output/int",
&out_visitor_data, test_visitor_out_int);
output_visitor_test_add("/visitor/output/bool",
&out_visitor_data, test_visitor_out_bool);
output_visitor_test_add("/visitor/output/number",
&out_visitor_data, test_visitor_out_number);
output_visitor_test_add("/visitor/output/string",
&out_visitor_data, test_visitor_out_string);
output_visitor_test_add("/visitor/output/no-string",
&out_visitor_data, test_visitor_out_no_string);
output_visitor_test_add("/visitor/output/enum",
&out_visitor_data, test_visitor_out_enum);
output_visitor_test_add("/visitor/output/struct",
&out_visitor_data, test_visitor_out_struct);
output_visitor_test_add("/visitor/output/struct-nested",
&out_visitor_data, test_visitor_out_struct_nested);
output_visitor_test_add("/visitor/output/list",
&out_visitor_data, test_visitor_out_list);
output_visitor_test_add("/visitor/output/any",
&out_visitor_data, test_visitor_out_any);
output_visitor_test_add("/visitor/output/list-qapi-free",
&out_visitor_data, test_visitor_out_list_qapi_free);
output_visitor_test_add("/visitor/output/union-flat",
&out_visitor_data, test_visitor_out_union_flat);
output_visitor_test_add("/visitor/output/alternate",
&out_visitor_data, test_visitor_out_alternate);
output_visitor_test_add("/visitor/output/null",
&out_visitor_data, test_visitor_out_null);
output_visitor_test_add("/visitor/output/list_union/int",
&out_visitor_data,
test_visitor_out_list_union_int);
output_visitor_test_add("/visitor/output/list_union/int8",
&out_visitor_data,
test_visitor_out_list_union_int8);
output_visitor_test_add("/visitor/output/list_union/int16",
&out_visitor_data,
test_visitor_out_list_union_int16);
output_visitor_test_add("/visitor/output/list_union/int32",
&out_visitor_data,
test_visitor_out_list_union_int32);
output_visitor_test_add("/visitor/output/list_union/int64",
&out_visitor_data,
test_visitor_out_list_union_int64);
output_visitor_test_add("/visitor/output/list_union/uint8",
&out_visitor_data,
test_visitor_out_list_union_uint8);
output_visitor_test_add("/visitor/output/list_union/uint16",
&out_visitor_data,
test_visitor_out_list_union_uint16);
output_visitor_test_add("/visitor/output/list_union/uint32",
&out_visitor_data,
test_visitor_out_list_union_uint32);
output_visitor_test_add("/visitor/output/list_union/uint64",
&out_visitor_data,
test_visitor_out_list_union_uint64);
output_visitor_test_add("/visitor/output/list_union/bool",
&out_visitor_data,
test_visitor_out_list_union_bool);
output_visitor_test_add("/visitor/output/list_union/string",
&out_visitor_data,
test_visitor_out_list_union_str);
output_visitor_test_add("/visitor/output/list_union/number",
&out_visitor_data,
test_visitor_out_list_union_number);
g_test_run();
return 0;
}

381
tests/unit/test-rcu-list.c Normal file
View file

@ -0,0 +1,381 @@
/*
* rcuq_test.c
*
* usage: rcuq_test <readers> <duration>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Copyright (c) 2013 Mike D. Day, IBM Corporation.
*/
#include "qemu/osdep.h"
#include "qemu/atomic.h"
#include "qemu/rcu.h"
#include "qemu/thread.h"
#include "qemu/rcu_queue.h"
/*
* Test variables.
*/
static QemuMutex counts_mutex;
static long long n_reads = 0LL;
static long long n_updates = 0LL;
static int64_t n_reclaims;
static int64_t n_nodes_removed;
static long long n_nodes = 0LL;
static int g_test_in_charge = 0;
static int nthreadsrunning;
#define GOFLAG_INIT 0
#define GOFLAG_RUN 1
#define GOFLAG_STOP 2
static int goflag = GOFLAG_INIT;
#define RCU_READ_RUN 1000
#define RCU_UPDATE_RUN 10
#define NR_THREADS 100
#define RCU_Q_LEN 100
static QemuThread threads[NR_THREADS];
static struct rcu_reader_data *data[NR_THREADS];
static int n_threads;
static int select_random_el(int max)
{
return (rand() % max);
}
static void create_thread(void *(*func)(void *))
{
if (n_threads >= NR_THREADS) {
fprintf(stderr, "Thread limit of %d exceeded!\n", NR_THREADS);
exit(-1);
}
qemu_thread_create(&threads[n_threads], "test", func, &data[n_threads],
QEMU_THREAD_JOINABLE);
n_threads++;
}
static void wait_all_threads(void)
{
int i;
for (i = 0; i < n_threads; i++) {
qemu_thread_join(&threads[i]);
}
n_threads = 0;
}
#ifndef TEST_LIST_TYPE
#define TEST_LIST_TYPE 1
#endif
struct list_element {
#if TEST_LIST_TYPE == 1
QLIST_ENTRY(list_element) entry;
#elif TEST_LIST_TYPE == 2
QSIMPLEQ_ENTRY(list_element) entry;
#elif TEST_LIST_TYPE == 3
QTAILQ_ENTRY(list_element) entry;
#elif TEST_LIST_TYPE == 4
QSLIST_ENTRY(list_element) entry;
#else
#error Invalid TEST_LIST_TYPE
#endif
struct rcu_head rcu;
};
static void reclaim_list_el(struct rcu_head *prcu)
{
struct list_element *el = container_of(prcu, struct list_element, rcu);
g_free(el);
/* Accessed only from call_rcu thread. */
qatomic_set_i64(&n_reclaims, n_reclaims + 1);
}
#if TEST_LIST_TYPE == 1
static QLIST_HEAD(, list_element) Q_list_head;
#define TEST_NAME "qlist"
#define TEST_LIST_REMOVE_RCU QLIST_REMOVE_RCU
#define TEST_LIST_INSERT_AFTER_RCU QLIST_INSERT_AFTER_RCU
#define TEST_LIST_INSERT_HEAD_RCU QLIST_INSERT_HEAD_RCU
#define TEST_LIST_FOREACH_RCU QLIST_FOREACH_RCU
#define TEST_LIST_FOREACH_SAFE_RCU QLIST_FOREACH_SAFE_RCU
#elif TEST_LIST_TYPE == 2
static QSIMPLEQ_HEAD(, list_element) Q_list_head =
QSIMPLEQ_HEAD_INITIALIZER(Q_list_head);
#define TEST_NAME "qsimpleq"
#define TEST_LIST_REMOVE_RCU(el, f) \
QSIMPLEQ_REMOVE_RCU(&Q_list_head, el, list_element, f)
#define TEST_LIST_INSERT_AFTER_RCU(list_el, el, f) \
QSIMPLEQ_INSERT_AFTER_RCU(&Q_list_head, list_el, el, f)
#define TEST_LIST_INSERT_HEAD_RCU QSIMPLEQ_INSERT_HEAD_RCU
#define TEST_LIST_FOREACH_RCU QSIMPLEQ_FOREACH_RCU
#define TEST_LIST_FOREACH_SAFE_RCU QSIMPLEQ_FOREACH_SAFE_RCU
#elif TEST_LIST_TYPE == 3
static QTAILQ_HEAD(, list_element) Q_list_head;
#define TEST_NAME "qtailq"
#define TEST_LIST_REMOVE_RCU(el, f) QTAILQ_REMOVE_RCU(&Q_list_head, el, f)
#define TEST_LIST_INSERT_AFTER_RCU(list_el, el, f) \
QTAILQ_INSERT_AFTER_RCU(&Q_list_head, list_el, el, f)
#define TEST_LIST_INSERT_HEAD_RCU QTAILQ_INSERT_HEAD_RCU
#define TEST_LIST_FOREACH_RCU QTAILQ_FOREACH_RCU
#define TEST_LIST_FOREACH_SAFE_RCU QTAILQ_FOREACH_SAFE_RCU
#elif TEST_LIST_TYPE == 4
static QSLIST_HEAD(, list_element) Q_list_head;
#define TEST_NAME "qslist"
#define TEST_LIST_REMOVE_RCU(el, f) \
QSLIST_REMOVE_RCU(&Q_list_head, el, list_element, f)
#define TEST_LIST_INSERT_AFTER_RCU(list_el, el, f) \
QSLIST_INSERT_AFTER_RCU(&Q_list_head, list_el, el, f)
#define TEST_LIST_INSERT_HEAD_RCU QSLIST_INSERT_HEAD_RCU
#define TEST_LIST_FOREACH_RCU QSLIST_FOREACH_RCU
#define TEST_LIST_FOREACH_SAFE_RCU QSLIST_FOREACH_SAFE_RCU
#else
#error Invalid TEST_LIST_TYPE
#endif
static void *rcu_q_reader(void *arg)
{
long long n_reads_local = 0;
struct list_element *el;
rcu_register_thread();
*(struct rcu_reader_data **)arg = &rcu_reader;
qatomic_inc(&nthreadsrunning);
while (qatomic_read(&goflag) == GOFLAG_INIT) {
g_usleep(1000);
}
while (qatomic_read(&goflag) == GOFLAG_RUN) {
rcu_read_lock();
TEST_LIST_FOREACH_RCU(el, &Q_list_head, entry) {
n_reads_local++;
if (qatomic_read(&goflag) == GOFLAG_STOP) {
break;
}
}
rcu_read_unlock();
g_usleep(100);
}
qemu_mutex_lock(&counts_mutex);
n_reads += n_reads_local;
qemu_mutex_unlock(&counts_mutex);
rcu_unregister_thread();
return NULL;
}
static void *rcu_q_updater(void *arg)
{
int j, target_el;
long long n_nodes_local = 0;
long long n_updates_local = 0;
long long n_removed_local = 0;
struct list_element *el, *prev_el;
*(struct rcu_reader_data **)arg = &rcu_reader;
qatomic_inc(&nthreadsrunning);
while (qatomic_read(&goflag) == GOFLAG_INIT) {
g_usleep(1000);
}
while (qatomic_read(&goflag) == GOFLAG_RUN) {
target_el = select_random_el(RCU_Q_LEN);
j = 0;
/* FOREACH_RCU could work here but let's use both macros */
TEST_LIST_FOREACH_SAFE_RCU(prev_el, &Q_list_head, entry, el) {
j++;
if (target_el == j) {
TEST_LIST_REMOVE_RCU(prev_el, entry);
/* may be more than one updater in the future */
call_rcu1(&prev_el->rcu, reclaim_list_el);
n_removed_local++;
break;
}
}
if (qatomic_read(&goflag) == GOFLAG_STOP) {
break;
}
target_el = select_random_el(RCU_Q_LEN);
j = 0;
TEST_LIST_FOREACH_RCU(el, &Q_list_head, entry) {
j++;
if (target_el == j) {
struct list_element *new_el = g_new(struct list_element, 1);
n_nodes_local++;
TEST_LIST_INSERT_AFTER_RCU(el, new_el, entry);
break;
}
}
n_updates_local += 2;
synchronize_rcu();
}
synchronize_rcu();
qemu_mutex_lock(&counts_mutex);
n_nodes += n_nodes_local;
n_updates += n_updates_local;
qatomic_set_i64(&n_nodes_removed, n_nodes_removed + n_removed_local);
qemu_mutex_unlock(&counts_mutex);
return NULL;
}
static void rcu_qtest_init(void)
{
struct list_element *new_el;
int i;
nthreadsrunning = 0;
srand(time(0));
for (i = 0; i < RCU_Q_LEN; i++) {
new_el = g_new(struct list_element, 1);
TEST_LIST_INSERT_HEAD_RCU(&Q_list_head, new_el, entry);
}
qemu_mutex_lock(&counts_mutex);
n_nodes += RCU_Q_LEN;
qemu_mutex_unlock(&counts_mutex);
}
static void rcu_qtest_run(int duration, int nreaders)
{
int nthreads = nreaders + 1;
while (qatomic_read(&nthreadsrunning) < nthreads) {
g_usleep(1000);
}
qatomic_set(&goflag, GOFLAG_RUN);
sleep(duration);
qatomic_set(&goflag, GOFLAG_STOP);
wait_all_threads();
}
static void rcu_qtest(const char *test, int duration, int nreaders)
{
int i;
long long n_removed_local = 0;
struct list_element *el, *prev_el;
rcu_qtest_init();
for (i = 0; i < nreaders; i++) {
create_thread(rcu_q_reader);
}
create_thread(rcu_q_updater);
rcu_qtest_run(duration, nreaders);
TEST_LIST_FOREACH_SAFE_RCU(prev_el, &Q_list_head, entry, el) {
TEST_LIST_REMOVE_RCU(prev_el, entry);
call_rcu1(&prev_el->rcu, reclaim_list_el);
n_removed_local++;
}
qemu_mutex_lock(&counts_mutex);
qatomic_set_i64(&n_nodes_removed, n_nodes_removed + n_removed_local);
qemu_mutex_unlock(&counts_mutex);
synchronize_rcu();
while (qatomic_read_i64(&n_nodes_removed) >
qatomic_read_i64(&n_reclaims)) {
g_usleep(100);
synchronize_rcu();
}
if (g_test_in_charge) {
g_assert_cmpint(qatomic_read_i64(&n_nodes_removed), ==,
qatomic_read_i64(&n_reclaims));
} else {
printf("%s: %d readers; 1 updater; nodes read: " \
"%lld, nodes removed: %"PRIi64"; nodes reclaimed: %"PRIi64"\n",
test, nthreadsrunning - 1, n_reads,
qatomic_read_i64(&n_nodes_removed),
qatomic_read_i64(&n_reclaims));
exit(0);
}
}
static void usage(int argc, char *argv[])
{
fprintf(stderr, "Usage: %s duration nreaders\n", argv[0]);
exit(-1);
}
static int gtest_seconds;
static void gtest_rcuq_one(void)
{
rcu_qtest("rcuqtest", gtest_seconds / 4, 1);
}
static void gtest_rcuq_few(void)
{
rcu_qtest("rcuqtest", gtest_seconds / 4, 5);
}
static void gtest_rcuq_many(void)
{
rcu_qtest("rcuqtest", gtest_seconds / 2, 20);
}
int main(int argc, char *argv[])
{
int duration = 0, readers = 0;
qemu_mutex_init(&counts_mutex);
if (argc >= 2) {
if (argv[1][0] == '-') {
g_test_init(&argc, &argv, NULL);
if (g_test_quick()) {
gtest_seconds = 4;
} else {
gtest_seconds = 20;
}
g_test_add_func("/rcu/"TEST_NAME"/single-threaded", gtest_rcuq_one);
g_test_add_func("/rcu/"TEST_NAME"/short-few", gtest_rcuq_few);
g_test_add_func("/rcu/"TEST_NAME"/long-many", gtest_rcuq_many);
g_test_in_charge = 1;
return g_test_run();
}
duration = strtoul(argv[1], NULL, 0);
}
if (argc >= 3) {
readers = strtoul(argv[2], NULL, 0);
}
if (duration && readers) {
rcu_qtest(argv[0], duration, readers);
return 0;
}
usage(argc, argv);
return -1;
}

View file

@ -0,0 +1,2 @@
#define TEST_LIST_TYPE 2
#include "test-rcu-list.c"

View file

@ -0,0 +1,2 @@
#define TEST_LIST_TYPE 4
#include "test-rcu-list.c"

View file

@ -0,0 +1,2 @@
#define TEST_LIST_TYPE 3
#include "test-rcu-list.c"

View file

@ -0,0 +1,623 @@
/*
* Block replication tests
*
* Copyright (c) 2016 FUJITSU LIMITED
* Author: Changlong Xie <xiecl.fnst@cn.fujitsu.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or
* later. See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qemu/option.h"
#include "qemu/main-loop.h"
#include "replication.h"
#include "block/block_int.h"
#include "block/qdict.h"
#include "sysemu/block-backend.h"
#define IMG_SIZE (64 * 1024 * 1024)
/* primary */
#define P_ID "primary-id"
static char *p_local_disk;
/* secondary */
#define S_ID "secondary-id"
#define S_LOCAL_DISK_ID "secondary-local-disk-id"
static char *s_local_disk;
static char *s_active_disk;
static char *s_hidden_disk;
/* FIXME: steal from blockdev.c */
QemuOptsList qemu_drive_opts = {
.name = "drive",
.head = QTAILQ_HEAD_INITIALIZER(qemu_drive_opts.head),
.desc = {
{ /* end of list */ }
},
};
#define NOT_DONE 0x7fffffff
static void blk_rw_done(void *opaque, int ret)
{
*(int *)opaque = ret;
}
static void test_blk_read(BlockBackend *blk, long pattern,
int64_t pattern_offset, int64_t pattern_count,
int64_t offset, int64_t count,
bool expect_failed)
{
void *pattern_buf = NULL;
QEMUIOVector qiov;
void *cmp_buf = NULL;
int async_ret = NOT_DONE;
if (pattern) {
cmp_buf = g_malloc(pattern_count);
memset(cmp_buf, pattern, pattern_count);
}
pattern_buf = g_malloc(count);
if (pattern) {
memset(pattern_buf, pattern, count);
} else {
memset(pattern_buf, 0x00, count);
}
qemu_iovec_init(&qiov, 1);
qemu_iovec_add(&qiov, pattern_buf, count);
blk_aio_preadv(blk, offset, &qiov, 0, blk_rw_done, &async_ret);
while (async_ret == NOT_DONE) {
main_loop_wait(false);
}
if (expect_failed) {
g_assert(async_ret != 0);
} else {
g_assert(async_ret == 0);
if (pattern) {
g_assert(memcmp(pattern_buf + pattern_offset,
cmp_buf, pattern_count) <= 0);
}
}
g_free(pattern_buf);
g_free(cmp_buf);
qemu_iovec_destroy(&qiov);
}
static void test_blk_write(BlockBackend *blk, long pattern, int64_t offset,
int64_t count, bool expect_failed)
{
void *pattern_buf = NULL;
QEMUIOVector qiov;
int async_ret = NOT_DONE;
pattern_buf = g_malloc(count);
if (pattern) {
memset(pattern_buf, pattern, count);
} else {
memset(pattern_buf, 0x00, count);
}
qemu_iovec_init(&qiov, 1);
qemu_iovec_add(&qiov, pattern_buf, count);
blk_aio_pwritev(blk, offset, &qiov, 0, blk_rw_done, &async_ret);
while (async_ret == NOT_DONE) {
main_loop_wait(false);
}
if (expect_failed) {
g_assert(async_ret != 0);
} else {
g_assert(async_ret == 0);
}
g_free(pattern_buf);
qemu_iovec_destroy(&qiov);
}
/*
* Create a uniquely-named empty temporary file.
*/
static void make_temp(char *template)
{
int fd;
fd = mkstemp(template);
g_assert(fd >= 0);
close(fd);
}
static void prepare_imgs(void)
{
make_temp(p_local_disk);
make_temp(s_local_disk);
make_temp(s_active_disk);
make_temp(s_hidden_disk);
/* Primary */
bdrv_img_create(p_local_disk, "qcow2", NULL, NULL, NULL, IMG_SIZE,
BDRV_O_RDWR, true, &error_abort);
/* Secondary */
bdrv_img_create(s_local_disk, "qcow2", NULL, NULL, NULL, IMG_SIZE,
BDRV_O_RDWR, true, &error_abort);
bdrv_img_create(s_active_disk, "qcow2", NULL, NULL, NULL, IMG_SIZE,
BDRV_O_RDWR, true, &error_abort);
bdrv_img_create(s_hidden_disk, "qcow2", NULL, NULL, NULL, IMG_SIZE,
BDRV_O_RDWR, true, &error_abort);
}
static void cleanup_imgs(void)
{
/* Primary */
unlink(p_local_disk);
/* Secondary */
unlink(s_local_disk);
unlink(s_active_disk);
unlink(s_hidden_disk);
}
static BlockBackend *start_primary(void)
{
BlockBackend *blk;
QemuOpts *opts;
QDict *qdict;
char *cmdline;
cmdline = g_strdup_printf("driver=replication,mode=primary,node-name=xxx,"
"file.driver=qcow2,file.file.filename=%s,"
"file.file.locking=off"
, p_local_disk);
opts = qemu_opts_parse_noisily(&qemu_drive_opts, cmdline, false);
g_free(cmdline);
qdict = qemu_opts_to_qdict(opts, NULL);
qdict_set_default_str(qdict, BDRV_OPT_CACHE_DIRECT, "off");
qdict_set_default_str(qdict, BDRV_OPT_CACHE_NO_FLUSH, "off");
blk = blk_new_open(NULL, NULL, qdict, BDRV_O_RDWR, &error_abort);
g_assert(blk);
monitor_add_blk(blk, P_ID, &error_abort);
qemu_opts_del(opts);
return blk;
}
static void teardown_primary(void)
{
BlockBackend *blk;
AioContext *ctx;
/* remove P_ID */
blk = blk_by_name(P_ID);
assert(blk);
ctx = blk_get_aio_context(blk);
aio_context_acquire(ctx);
monitor_remove_blk(blk);
blk_unref(blk);
aio_context_release(ctx);
}
static void test_primary_read(void)
{
BlockBackend *blk;
blk = start_primary();
/* read from 0 to IMG_SIZE */
test_blk_read(blk, 0, 0, IMG_SIZE, 0, IMG_SIZE, true);
teardown_primary();
}
static void test_primary_write(void)
{
BlockBackend *blk;
blk = start_primary();
/* write from 0 to IMG_SIZE */
test_blk_write(blk, 0, 0, IMG_SIZE, true);
teardown_primary();
}
static void test_primary_start(void)
{
BlockBackend *blk = NULL;
blk = start_primary();
replication_start_all(REPLICATION_MODE_PRIMARY, &error_abort);
/* read from 0 to IMG_SIZE */
test_blk_read(blk, 0, 0, IMG_SIZE, 0, IMG_SIZE, true);
/* write 0x22 from 0 to IMG_SIZE */
test_blk_write(blk, 0x22, 0, IMG_SIZE, false);
teardown_primary();
}
static void test_primary_stop(void)
{
bool failover = true;
start_primary();
replication_start_all(REPLICATION_MODE_PRIMARY, &error_abort);
replication_stop_all(failover, &error_abort);
teardown_primary();
}
static void test_primary_do_checkpoint(void)
{
start_primary();
replication_start_all(REPLICATION_MODE_PRIMARY, &error_abort);
replication_do_checkpoint_all(&error_abort);
teardown_primary();
}
static void test_primary_get_error_all(void)
{
start_primary();
replication_start_all(REPLICATION_MODE_PRIMARY, &error_abort);
replication_get_error_all(&error_abort);
teardown_primary();
}
static BlockBackend *start_secondary(void)
{
QemuOpts *opts;
QDict *qdict;
BlockBackend *blk;
char *cmdline;
/* add s_local_disk and forge S_LOCAL_DISK_ID */
cmdline = g_strdup_printf("file.filename=%s,driver=qcow2,"
"file.locking=off",
s_local_disk);
opts = qemu_opts_parse_noisily(&qemu_drive_opts, cmdline, false);
g_free(cmdline);
qdict = qemu_opts_to_qdict(opts, NULL);
qdict_set_default_str(qdict, BDRV_OPT_CACHE_DIRECT, "off");
qdict_set_default_str(qdict, BDRV_OPT_CACHE_NO_FLUSH, "off");
blk = blk_new_open(NULL, NULL, qdict, BDRV_O_RDWR, &error_abort);
assert(blk);
monitor_add_blk(blk, S_LOCAL_DISK_ID, &error_abort);
/* format s_local_disk with pattern "0x11" */
test_blk_write(blk, 0x11, 0, IMG_SIZE, false);
qemu_opts_del(opts);
/* add S_(ACTIVE/HIDDEN)_DISK and forge S_ID */
cmdline = g_strdup_printf("driver=replication,mode=secondary,top-id=%s,"
"file.driver=qcow2,file.file.filename=%s,"
"file.file.locking=off,"
"file.backing.driver=qcow2,"
"file.backing.file.filename=%s,"
"file.backing.file.locking=off,"
"file.backing.backing=%s"
, S_ID, s_active_disk, s_hidden_disk
, S_LOCAL_DISK_ID);
opts = qemu_opts_parse_noisily(&qemu_drive_opts, cmdline, false);
g_free(cmdline);
qdict = qemu_opts_to_qdict(opts, NULL);
qdict_set_default_str(qdict, BDRV_OPT_CACHE_DIRECT, "off");
qdict_set_default_str(qdict, BDRV_OPT_CACHE_NO_FLUSH, "off");
blk = blk_new_open(NULL, NULL, qdict, BDRV_O_RDWR, &error_abort);
assert(blk);
monitor_add_blk(blk, S_ID, &error_abort);
qemu_opts_del(opts);
return blk;
}
static void teardown_secondary(void)
{
/* only need to destroy two BBs */
BlockBackend *blk;
AioContext *ctx;
/* remove S_LOCAL_DISK_ID */
blk = blk_by_name(S_LOCAL_DISK_ID);
assert(blk);
ctx = blk_get_aio_context(blk);
aio_context_acquire(ctx);
monitor_remove_blk(blk);
blk_unref(blk);
aio_context_release(ctx);
/* remove S_ID */
blk = blk_by_name(S_ID);
assert(blk);
ctx = blk_get_aio_context(blk);
aio_context_acquire(ctx);
monitor_remove_blk(blk);
blk_unref(blk);
aio_context_release(ctx);
}
static void test_secondary_read(void)
{
BlockBackend *blk;
blk = start_secondary();
/* read from 0 to IMG_SIZE */
test_blk_read(blk, 0, 0, IMG_SIZE, 0, IMG_SIZE, true);
teardown_secondary();
}
static void test_secondary_write(void)
{
BlockBackend *blk;
blk = start_secondary();
/* write from 0 to IMG_SIZE */
test_blk_write(blk, 0, 0, IMG_SIZE, true);
teardown_secondary();
}
#ifndef _WIN32
static void test_secondary_start(void)
{
BlockBackend *top_blk, *local_blk;
bool failover = true;
top_blk = start_secondary();
replication_start_all(REPLICATION_MODE_SECONDARY, &error_abort);
/* read from s_local_disk (0, IMG_SIZE) */
test_blk_read(top_blk, 0x11, 0, IMG_SIZE, 0, IMG_SIZE, false);
/* write 0x22 to s_local_disk (IMG_SIZE / 2, IMG_SIZE) */
local_blk = blk_by_name(S_LOCAL_DISK_ID);
test_blk_write(local_blk, 0x22, IMG_SIZE / 2, IMG_SIZE / 2, false);
/* replication will backup s_local_disk to s_hidden_disk */
test_blk_read(top_blk, 0x11, IMG_SIZE / 2,
IMG_SIZE / 2, 0, IMG_SIZE, false);
/* write 0x33 to s_active_disk (0, IMG_SIZE / 2) */
test_blk_write(top_blk, 0x33, 0, IMG_SIZE / 2, false);
/* read from s_active_disk (0, IMG_SIZE/2) */
test_blk_read(top_blk, 0x33, 0, IMG_SIZE / 2,
0, IMG_SIZE / 2, false);
/* unblock top_bs */
replication_stop_all(failover, &error_abort);
teardown_secondary();
}
static void test_secondary_stop(void)
{
BlockBackend *top_blk, *local_blk;
bool failover = true;
top_blk = start_secondary();
replication_start_all(REPLICATION_MODE_SECONDARY, &error_abort);
/* write 0x22 to s_local_disk (IMG_SIZE / 2, IMG_SIZE) */
local_blk = blk_by_name(S_LOCAL_DISK_ID);
test_blk_write(local_blk, 0x22, IMG_SIZE / 2, IMG_SIZE / 2, false);
/* replication will backup s_local_disk to s_hidden_disk */
test_blk_read(top_blk, 0x11, IMG_SIZE / 2,
IMG_SIZE / 2, 0, IMG_SIZE, false);
/* write 0x33 to s_active_disk (0, IMG_SIZE / 2) */
test_blk_write(top_blk, 0x33, 0, IMG_SIZE / 2, false);
/* do active commit */
replication_stop_all(failover, &error_abort);
/* read from s_local_disk (0, IMG_SIZE / 2) */
test_blk_read(top_blk, 0x33, 0, IMG_SIZE / 2,
0, IMG_SIZE / 2, false);
/* read from s_local_disk (IMG_SIZE / 2, IMG_SIZE) */
test_blk_read(top_blk, 0x22, IMG_SIZE / 2,
IMG_SIZE / 2, 0, IMG_SIZE, false);
teardown_secondary();
}
static void test_secondary_continuous_replication(void)
{
BlockBackend *top_blk, *local_blk;
top_blk = start_secondary();
replication_start_all(REPLICATION_MODE_SECONDARY, &error_abort);
/* write 0x22 to s_local_disk (IMG_SIZE / 2, IMG_SIZE) */
local_blk = blk_by_name(S_LOCAL_DISK_ID);
test_blk_write(local_blk, 0x22, IMG_SIZE / 2, IMG_SIZE / 2, false);
/* replication will backup s_local_disk to s_hidden_disk */
test_blk_read(top_blk, 0x11, IMG_SIZE / 2,
IMG_SIZE / 2, 0, IMG_SIZE, false);
/* write 0x33 to s_active_disk (0, IMG_SIZE / 2) */
test_blk_write(top_blk, 0x33, 0, IMG_SIZE / 2, false);
/* do failover (active commit) */
replication_stop_all(true, &error_abort);
/* it should ignore all requests from now on */
/* start after failover */
replication_start_all(REPLICATION_MODE_PRIMARY, &error_abort);
/* checkpoint */
replication_do_checkpoint_all(&error_abort);
/* stop */
replication_stop_all(true, &error_abort);
/* read from s_local_disk (0, IMG_SIZE / 2) */
test_blk_read(top_blk, 0x33, 0, IMG_SIZE / 2,
0, IMG_SIZE / 2, false);
/* read from s_local_disk (IMG_SIZE / 2, IMG_SIZE) */
test_blk_read(top_blk, 0x22, IMG_SIZE / 2,
IMG_SIZE / 2, 0, IMG_SIZE, false);
teardown_secondary();
}
static void test_secondary_do_checkpoint(void)
{
BlockBackend *top_blk, *local_blk;
bool failover = true;
top_blk = start_secondary();
replication_start_all(REPLICATION_MODE_SECONDARY, &error_abort);
/* write 0x22 to s_local_disk (IMG_SIZE / 2, IMG_SIZE) */
local_blk = blk_by_name(S_LOCAL_DISK_ID);
test_blk_write(local_blk, 0x22, IMG_SIZE / 2,
IMG_SIZE / 2, false);
/* replication will backup s_local_disk to s_hidden_disk */
test_blk_read(top_blk, 0x11, IMG_SIZE / 2,
IMG_SIZE / 2, 0, IMG_SIZE, false);
replication_do_checkpoint_all(&error_abort);
/* after checkpoint, read pattern 0x22 from s_local_disk */
test_blk_read(top_blk, 0x22, IMG_SIZE / 2,
IMG_SIZE / 2, 0, IMG_SIZE, false);
/* unblock top_bs */
replication_stop_all(failover, &error_abort);
teardown_secondary();
}
static void test_secondary_get_error_all(void)
{
bool failover = true;
start_secondary();
replication_start_all(REPLICATION_MODE_SECONDARY, &error_abort);
replication_get_error_all(&error_abort);
/* unblock top_bs */
replication_stop_all(failover, &error_abort);
teardown_secondary();
}
#endif
static void sigabrt_handler(int signo)
{
cleanup_imgs();
}
static void setup_sigabrt_handler(void)
{
#ifdef _WIN32
signal(SIGABRT, sigabrt_handler);
#else
struct sigaction sigact;
sigact = (struct sigaction) {
.sa_handler = sigabrt_handler,
.sa_flags = SA_RESETHAND,
};
sigemptyset(&sigact.sa_mask);
sigaction(SIGABRT, &sigact, NULL);
#endif
}
int main(int argc, char **argv)
{
int ret;
const char *tmpdir = g_get_tmp_dir();
p_local_disk = g_strdup_printf("%s/p_local_disk.XXXXXX", tmpdir);
s_local_disk = g_strdup_printf("%s/s_local_disk.XXXXXX", tmpdir);
s_active_disk = g_strdup_printf("%s/s_active_disk.XXXXXX", tmpdir);
s_hidden_disk = g_strdup_printf("%s/s_hidden_disk.XXXXXX", tmpdir);
qemu_init_main_loop(&error_fatal);
bdrv_init();
g_test_init(&argc, &argv, NULL);
setup_sigabrt_handler();
prepare_imgs();
/* Primary */
g_test_add_func("/replication/primary/read", test_primary_read);
g_test_add_func("/replication/primary/write", test_primary_write);
g_test_add_func("/replication/primary/start", test_primary_start);
g_test_add_func("/replication/primary/stop", test_primary_stop);
g_test_add_func("/replication/primary/do_checkpoint",
test_primary_do_checkpoint);
g_test_add_func("/replication/primary/get_error_all",
test_primary_get_error_all);
/* Secondary */
g_test_add_func("/replication/secondary/read", test_secondary_read);
g_test_add_func("/replication/secondary/write", test_secondary_write);
#ifndef _WIN32
g_test_add_func("/replication/secondary/start", test_secondary_start);
g_test_add_func("/replication/secondary/stop", test_secondary_stop);
g_test_add_func("/replication/secondary/continuous_replication",
test_secondary_continuous_replication);
g_test_add_func("/replication/secondary/do_checkpoint",
test_secondary_do_checkpoint);
g_test_add_func("/replication/secondary/get_error_all",
test_secondary_get_error_all);
#endif
ret = g_test_run();
cleanup_imgs();
g_free(p_local_disk);
g_free(s_local_disk);
g_free(s_active_disk);
g_free(s_hidden_disk);
return ret;
}

139
tests/unit/test-shift128.c Normal file
View file

@ -0,0 +1,139 @@
/*
* Test unsigned left and right shift
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/host-utils.h"
typedef struct {
uint64_t low;
uint64_t high;
uint64_t rlow;
uint64_t rhigh;
int32_t shift;
bool overflow;
} test_data;
static const test_data test_ltable[] = {
{ 0x4C7ULL, 0x0ULL, 0x00000000000004C7ULL,
0x0000000000000000ULL, 0, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000002ULL,
0x0000000000000000ULL, 1, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000004ULL,
0x0000000000000000ULL, 2, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000010ULL,
0x0000000000000000ULL, 4, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000100ULL,
0x0000000000000000ULL, 8, false },
{ 0x001ULL, 0x0ULL, 0x0000000000010000ULL,
0x0000000000000000ULL, 16, false },
{ 0x001ULL, 0x0ULL, 0x0000000080000000ULL,
0x0000000000000000ULL, 31, false },
{ 0x001ULL, 0x0ULL, 0x0000200000000000ULL,
0x0000000000000000ULL, 45, false },
{ 0x001ULL, 0x0ULL, 0x1000000000000000ULL,
0x0000000000000000ULL, 60, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x0000000000000001ULL, 64, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x0000000000010000ULL, 80, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x8000000000000000ULL, 127, false },
{ 0x000ULL, 0x1ULL, 0x0000000000000000ULL,
0x0000000000000000ULL, 64, true },
{ 0x008ULL, 0x0ULL, 0x0000000000000000ULL,
0x0000000000000008ULL, 64, false },
{ 0x008ULL, 0x0ULL, 0x0000000000000000ULL,
0x8000000000000000ULL, 124, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x4000000000000000ULL, 126, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x8000000000000000ULL, 127, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000001ULL,
0x0000000000000000ULL, 128, false },
{ 0x000ULL, 0x0ULL, 0x0000000000000000ULL,
0x0000000000000000ULL, 200, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x0000000000000100ULL, 200, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x8000000000000000ULL, -1, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x8000000000000000ULL, INT32_MAX, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x4000000000000000ULL, -2, false },
{ 0x001ULL, 0x0ULL, 0x0000000000000000ULL,
0x4000000000000000ULL, INT32_MAX - 1, false },
{ 0x8888888888888888ULL, 0x9999999999999999ULL,
0x8000000000000000ULL, 0x9888888888888888ULL, 60, true },
{ 0x8888888888888888ULL, 0x9999999999999999ULL,
0x0000000000000000ULL, 0x8888888888888888ULL, 64, true },
};
static const test_data test_rtable[] = {
{ 0x00000000000004C7ULL, 0x0ULL, 0x00000000000004C7ULL, 0x0ULL, 0, false },
{ 0x0800000000000000ULL, 0x0ULL, 0x0400000000000000ULL, 0x0ULL, 1, false },
{ 0x0800000000000000ULL, 0x0ULL, 0x0200000000000000ULL, 0x0ULL, 2, false },
{ 0x0800000000000000ULL, 0x0ULL, 0x0008000000000000ULL, 0x0ULL, 8, false },
{ 0x0800000000000000ULL, 0x0ULL, 0x0000080000000000ULL, 0x0ULL, 16, false },
{ 0x0800000000000000ULL, 0x0ULL, 0x0000000008000000ULL, 0x0ULL, 32, false },
{ 0x8000000000000000ULL, 0x0ULL, 0x0000000000000001ULL, 0x0ULL, 63, false },
{ 0x8000000000000000ULL, 0x0ULL, 0x0000000000000000ULL, 0x0ULL, 64, false },
{ 0x0000000000000000ULL, 0x8000000000000000ULL,
0x0000000000000000ULL, 0x8000000000000000ULL, 128, false },
{ 0x0000000000000000ULL, 0x8000000000000000ULL,
0x0080000000000000ULL, 0x0000000000000000ULL, 200, false },
{ 0x0000000000000000ULL, 0x0000000000000000ULL,
0x0000000000000000ULL, 0x0000000000000000ULL, 200, false },
{ 0x0000000000000000ULL, 0x8000000000000000ULL,
0x0000000000000000ULL, 0x0000000000000080ULL, -200, false },
{ 0x8000000000000000ULL, 0x8000000000000000ULL,
0x0000000080000000ULL, 0x0000000080000000ULL, 32, false },
{ 0x0800000000000000ULL, 0x0800000000000000ULL,
0x0800000000000000ULL, 0x0000000000000000ULL, 64, false },
{ 0x0800000000000000ULL, 0x0800000000000000ULL,
0x0008000000000000ULL, 0x0000000000000000ULL, 72, false },
{ 0x8000000000000000ULL, 0x8000000000000000ULL,
0x0000000000000001ULL, 0x0000000000000000ULL, 127, false },
{ 0x0000000000000000ULL, 0x8000000000000000ULL,
0x0000000000000001ULL, 0x0000000000000000ULL, -1, false },
{ 0x0000000000000000ULL, 0x8000000000000000ULL,
0x0000000000000002ULL, 0x0000000000000000ULL, -2, false },
};
static void test_lshift(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_ltable); ++i) {
bool overflow = false;
test_data tmp = test_ltable[i];
ulshift(&tmp.low, &tmp.high, tmp.shift, &overflow);
g_assert_cmpuint(tmp.low, ==, tmp.rlow);
g_assert_cmpuint(tmp.high, ==, tmp.rhigh);
g_assert_cmpuint(tmp.overflow, ==, overflow);
}
}
static void test_rshift(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_rtable); ++i) {
test_data tmp = test_rtable[i];
urshift(&tmp.low, &tmp.high, tmp.shift);
g_assert_cmpuint(tmp.low, ==, tmp.rlow);
g_assert_cmpuint(tmp.high, ==, tmp.rhigh);
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/host-utils/test_lshift", test_lshift);
g_test_add_func("/host-utils/test_rshift", test_rshift);
return g_test_run();
}

View file

@ -0,0 +1,501 @@
/*
* String Input Visitor unit-tests.
*
* Copyright (C) 2012 Red Hat Inc.
*
* Authors:
* Paolo Bonzini <pbonzini@redhat.com> (based on test-qobject-input-visitor)
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/error.h"
#include "qapi/string-input-visitor.h"
#include "test-qapi-visit.h"
typedef struct TestInputVisitorData {
Visitor *v;
} TestInputVisitorData;
static void visitor_input_teardown(TestInputVisitorData *data,
const void *unused)
{
if (data->v) {
visit_free(data->v);
data->v = NULL;
}
}
/* This is provided instead of a test setup function so that the JSON
string used by the tests are kept in the test functions (and not
int main()) */
static
Visitor *visitor_input_test_init(TestInputVisitorData *data,
const char *string)
{
visitor_input_teardown(data, NULL);
data->v = string_input_visitor_new(string);
g_assert(data->v);
return data->v;
}
static void test_visitor_in_int(TestInputVisitorData *data,
const void *unused)
{
int64_t res = 0, value = -42;
Error *err = NULL;
Visitor *v;
v = visitor_input_test_init(data, "-42");
visit_type_int(v, NULL, &res, &error_abort);
g_assert_cmpint(res, ==, value);
v = visitor_input_test_init(data, "not an int");
visit_type_int(v, NULL, &res, &err);
error_free_or_abort(&err);
v = visitor_input_test_init(data, "");
visit_type_int(v, NULL, &res, &err);
error_free_or_abort(&err);
}
static void check_ilist(Visitor *v, int64_t *expected, size_t n)
{
int64List *res = NULL;
int64List *tail;
int i;
visit_type_int64List(v, NULL, &res, &error_abort);
tail = res;
for (i = 0; i < n; i++) {
g_assert(tail);
g_assert_cmpint(tail->value, ==, expected[i]);
tail = tail->next;
}
g_assert(!tail);
qapi_free_int64List(res);
}
static void check_ulist(Visitor *v, uint64_t *expected, size_t n)
{
uint64List *res = NULL;
uint64List *tail;
int i;
visit_type_uint64List(v, NULL, &res, &error_abort);
tail = res;
for (i = 0; i < n; i++) {
g_assert(tail);
g_assert_cmpuint(tail->value, ==, expected[i]);
tail = tail->next;
}
g_assert(!tail);
qapi_free_uint64List(res);
}
static void test_visitor_in_intList(TestInputVisitorData *data,
const void *unused)
{
int64_t expect1[] = { 1, 2, 0, 2, 3, 4, 20, 5, 6, 7,
8, 9, 1, 2, 3, 4, 5, 6, 7, 8 };
int64_t expect2[] = { 32767, -32768, -32767 };
int64_t expect3[] = { INT64_MIN, INT64_MAX };
int64_t expect4[] = { 1 };
int64_t expect5[] = { INT64_MAX - 2, INT64_MAX - 1, INT64_MAX };
Error *err = NULL;
int64List *res = NULL;
Visitor *v;
int64_t val;
/* Valid lists */
v = visitor_input_test_init(data, "1,2,0,2-4,20,5-9,1-8");
check_ilist(v, expect1, ARRAY_SIZE(expect1));
v = visitor_input_test_init(data, "32767,-32768--32767");
check_ilist(v, expect2, ARRAY_SIZE(expect2));
v = visitor_input_test_init(data,
"-9223372036854775808,9223372036854775807");
check_ilist(v, expect3, ARRAY_SIZE(expect3));
v = visitor_input_test_init(data, "1-1");
check_ilist(v, expect4, ARRAY_SIZE(expect4));
v = visitor_input_test_init(data,
"9223372036854775805-9223372036854775807");
check_ilist(v, expect5, ARRAY_SIZE(expect5));
/* Value too large */
v = visitor_input_test_init(data, "9223372036854775808");
visit_type_int64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Value too small */
v = visitor_input_test_init(data, "-9223372036854775809");
visit_type_int64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Range not ascending */
v = visitor_input_test_init(data, "3-1");
visit_type_int64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
v = visitor_input_test_init(data, "9223372036854775807-0");
visit_type_int64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Range too big (65536 is the limit against DOS attacks) */
v = visitor_input_test_init(data, "0-65536");
visit_type_int64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Empty list */
v = visitor_input_test_init(data, "");
visit_type_int64List(v, NULL, &res, &error_abort);
g_assert(!res);
/* Not a list */
v = visitor_input_test_init(data, "not an int list");
visit_type_int64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Unvisited list tail */
v = visitor_input_test_init(data, "0,2-3");
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_int64(v, NULL, &val, &error_abort);
g_assert_cmpint(val, ==, 0);
visit_type_int64(v, NULL, &val, &error_abort);
g_assert_cmpint(val, ==, 2);
visit_check_list(v, &err);
error_free_or_abort(&err);
visit_end_list(v, NULL);
/* Visit beyond end of list */
v = visitor_input_test_init(data, "0");
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_int64(v, NULL, &val, &err);
g_assert_cmpint(val, ==, 0);
visit_type_int64(v, NULL, &val, &err);
error_free_or_abort(&err);
visit_check_list(v, &error_abort);
visit_end_list(v, NULL);
}
static void test_visitor_in_uintList(TestInputVisitorData *data,
const void *unused)
{
uint64_t expect1[] = { 1, 2, 0, 2, 3, 4, 20, 5, 6, 7,
8, 9, 1, 2, 3, 4, 5, 6, 7, 8 };
uint64_t expect2[] = { 32767, -32768, -32767 };
uint64_t expect3[] = { INT64_MIN, INT64_MAX };
uint64_t expect4[] = { 1 };
uint64_t expect5[] = { UINT64_MAX };
uint64_t expect6[] = { UINT64_MAX - 2, UINT64_MAX - 1, UINT64_MAX };
Error *err = NULL;
uint64List *res = NULL;
Visitor *v;
uint64_t val;
/* Valid lists */
v = visitor_input_test_init(data, "1,2,0,2-4,20,5-9,1-8");
check_ulist(v, expect1, ARRAY_SIZE(expect1));
v = visitor_input_test_init(data, "32767,-32768--32767");
check_ulist(v, expect2, ARRAY_SIZE(expect2));
v = visitor_input_test_init(data,
"-9223372036854775808,9223372036854775807");
check_ulist(v, expect3, ARRAY_SIZE(expect3));
v = visitor_input_test_init(data, "1-1");
check_ulist(v, expect4, ARRAY_SIZE(expect4));
v = visitor_input_test_init(data, "18446744073709551615");
check_ulist(v, expect5, ARRAY_SIZE(expect5));
v = visitor_input_test_init(data,
"18446744073709551613-18446744073709551615");
check_ulist(v, expect6, ARRAY_SIZE(expect6));
/* Value too large */
v = visitor_input_test_init(data, "18446744073709551616");
visit_type_uint64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Value too small */
v = visitor_input_test_init(data, "-18446744073709551616");
visit_type_uint64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Range not ascending */
v = visitor_input_test_init(data, "3-1");
visit_type_uint64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
v = visitor_input_test_init(data, "18446744073709551615-0");
visit_type_uint64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Range too big (65536 is the limit against DOS attacks) */
v = visitor_input_test_init(data, "0-65536");
visit_type_uint64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Empty list */
v = visitor_input_test_init(data, "");
visit_type_uint64List(v, NULL, &res, &error_abort);
g_assert(!res);
/* Not a list */
v = visitor_input_test_init(data, "not an uint list");
visit_type_uint64List(v, NULL, &res, &err);
error_free_or_abort(&err);
g_assert(!res);
/* Unvisited list tail */
v = visitor_input_test_init(data, "0,2-3");
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, NULL, &val, &error_abort);
g_assert_cmpuint(val, ==, 0);
visit_type_uint64(v, NULL, &val, &error_abort);
g_assert_cmpuint(val, ==, 2);
visit_check_list(v, &err);
error_free_or_abort(&err);
visit_end_list(v, NULL);
/* Visit beyond end of list */
v = visitor_input_test_init(data, "0");
visit_start_list(v, NULL, NULL, 0, &error_abort);
visit_type_uint64(v, NULL, &val, &err);
g_assert_cmpuint(val, ==, 0);
visit_type_uint64(v, NULL, &val, &err);
error_free_or_abort(&err);
visit_check_list(v, &error_abort);
visit_end_list(v, NULL);
}
static void test_visitor_in_bool(TestInputVisitorData *data,
const void *unused)
{
bool res = false;
Visitor *v;
v = visitor_input_test_init(data, "true");
visit_type_bool(v, NULL, &res, &error_abort);
g_assert_cmpint(res, ==, true);
v = visitor_input_test_init(data, "yes");
visit_type_bool(v, NULL, &res, &error_abort);
g_assert_cmpint(res, ==, true);
v = visitor_input_test_init(data, "on");
visit_type_bool(v, NULL, &res, &error_abort);
g_assert_cmpint(res, ==, true);
v = visitor_input_test_init(data, "false");
visit_type_bool(v, NULL, &res, &error_abort);
g_assert_cmpint(res, ==, false);
v = visitor_input_test_init(data, "no");
visit_type_bool(v, NULL, &res, &error_abort);
g_assert_cmpint(res, ==, false);
v = visitor_input_test_init(data, "off");
visit_type_bool(v, NULL, &res, &error_abort);
g_assert_cmpint(res, ==, false);
}
static void test_visitor_in_number(TestInputVisitorData *data,
const void *unused)
{
double res = 0, value = 3.14;
Error *err = NULL;
Visitor *v;
v = visitor_input_test_init(data, "3.14");
visit_type_number(v, NULL, &res, &error_abort);
g_assert_cmpfloat(res, ==, value);
/* NaN and infinity has to be rejected */
v = visitor_input_test_init(data, "NaN");
visit_type_number(v, NULL, &res, &err);
error_free_or_abort(&err);
v = visitor_input_test_init(data, "inf");
visit_type_number(v, NULL, &res, &err);
error_free_or_abort(&err);
}
static void test_visitor_in_string(TestInputVisitorData *data,
const void *unused)
{
char *res = NULL, *value = (char *) "Q E M U";
Visitor *v;
v = visitor_input_test_init(data, value);
visit_type_str(v, NULL, &res, &error_abort);
g_assert_cmpstr(res, ==, value);
g_free(res);
}
static void test_visitor_in_enum(TestInputVisitorData *data,
const void *unused)
{
Visitor *v;
EnumOne i;
for (i = 0; i < ENUM_ONE__MAX; i++) {
EnumOne res = -1;
v = visitor_input_test_init(data, EnumOne_str(i));
visit_type_EnumOne(v, NULL, &res, &error_abort);
g_assert_cmpint(i, ==, res);
}
}
/* Try to crash the visitors */
static void test_visitor_in_fuzz(TestInputVisitorData *data,
const void *unused)
{
int64_t ires;
intList *ilres;
bool bres;
double nres;
char *sres;
EnumOne eres;
Visitor *v;
unsigned int i;
char buf[10000];
for (i = 0; i < 100; i++) {
unsigned int j, k;
j = g_test_rand_int_range(0, sizeof(buf) - 1);
buf[j] = '\0';
for (k = 0; k != j; k++) {
buf[k] = (char)g_test_rand_int_range(0, 256);
}
v = visitor_input_test_init(data, buf);
visit_type_int(v, NULL, &ires, NULL);
v = visitor_input_test_init(data, buf);
visit_type_intList(v, NULL, &ilres, NULL);
qapi_free_intList(ilres);
v = visitor_input_test_init(data, buf);
visit_type_bool(v, NULL, &bres, NULL);
v = visitor_input_test_init(data, buf);
visit_type_number(v, NULL, &nres, NULL);
v = visitor_input_test_init(data, buf);
sres = NULL;
visit_type_str(v, NULL, &sres, NULL);
g_free(sres);
v = visitor_input_test_init(data, buf);
visit_type_EnumOne(v, NULL, &eres, NULL);
}
}
static void input_visitor_test_add(const char *testpath,
TestInputVisitorData *data,
void (*test_func)(TestInputVisitorData *data, const void *user_data))
{
g_test_add(testpath, TestInputVisitorData, data, NULL, test_func,
visitor_input_teardown);
}
int main(int argc, char **argv)
{
TestInputVisitorData in_visitor_data;
g_test_init(&argc, &argv, NULL);
input_visitor_test_add("/string-visitor/input/int",
&in_visitor_data, test_visitor_in_int);
input_visitor_test_add("/string-visitor/input/intList",
&in_visitor_data, test_visitor_in_intList);
input_visitor_test_add("/string-visitor/input/uintList",
&in_visitor_data, test_visitor_in_uintList);
input_visitor_test_add("/string-visitor/input/bool",
&in_visitor_data, test_visitor_in_bool);
input_visitor_test_add("/string-visitor/input/number",
&in_visitor_data, test_visitor_in_number);
input_visitor_test_add("/string-visitor/input/string",
&in_visitor_data, test_visitor_in_string);
input_visitor_test_add("/string-visitor/input/enum",
&in_visitor_data, test_visitor_in_enum);
input_visitor_test_add("/string-visitor/input/fuzz",
&in_visitor_data, test_visitor_in_fuzz);
g_test_run();
return 0;
}

View file

@ -0,0 +1,248 @@
/*
* String Output Visitor unit-tests.
*
* Copyright (C) 2012 Red Hat Inc.
*
* Authors:
* Paolo Bonzini <pbonzini@redhat.com> (based on test-qobject-output-visitor)
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/error.h"
#include "qapi/string-output-visitor.h"
#include "test-qapi-visit.h"
typedef struct TestOutputVisitorData {
Visitor *ov;
char *str;
bool human;
} TestOutputVisitorData;
static void visitor_output_setup_internal(TestOutputVisitorData *data,
bool human)
{
data->human = human;
data->ov = string_output_visitor_new(human, &data->str);
g_assert(data->ov);
}
static void visitor_output_setup(TestOutputVisitorData *data,
const void *unused)
{
return visitor_output_setup_internal(data, false);
}
static void visitor_output_setup_human(TestOutputVisitorData *data,
const void *unused)
{
return visitor_output_setup_internal(data, true);
}
static void visitor_output_teardown(TestOutputVisitorData *data,
const void *unused)
{
visit_free(data->ov);
data->ov = NULL;
g_free(data->str);
data->str = NULL;
}
static char *visitor_get(TestOutputVisitorData *data)
{
visit_complete(data->ov, &data->str);
g_assert(data->str);
return data->str;
}
static void visitor_reset(TestOutputVisitorData *data)
{
bool human = data->human;
visitor_output_teardown(data, NULL);
visitor_output_setup_internal(data, human);
}
static void test_visitor_out_int(TestOutputVisitorData *data,
const void *unused)
{
int64_t value = 42;
char *str;
visit_type_int(data->ov, NULL, &value, &error_abort);
str = visitor_get(data);
if (data->human) {
g_assert_cmpstr(str, ==, "42 (0x2a)");
} else {
g_assert_cmpstr(str, ==, "42");
}
}
static void test_visitor_out_intList(TestOutputVisitorData *data,
const void *unused)
{
int64_t value[] = {0, 1, 9, 10, 16, 15, 14,
3, 4, 5, 6, 11, 12, 13, 21, 22, INT64_MAX - 1, INT64_MAX};
intList *list = NULL, **tail = &list;
int i;
Error *err = NULL;
char *str;
for (i = 0; i < ARRAY_SIZE(value); i++) {
QAPI_LIST_APPEND(tail, value[i]);
}
visit_type_intList(data->ov, NULL, &list, &err);
g_assert(err == NULL);
str = visitor_get(data);
if (data->human) {
g_assert_cmpstr(str, ==,
"0-1,3-6,9-16,21-22,9223372036854775806-9223372036854775807 "
"(0x0-0x1,0x3-0x6,0x9-0x10,0x15-0x16,"
"0x7ffffffffffffffe-0x7fffffffffffffff)");
} else {
g_assert_cmpstr(str, ==,
"0-1,3-6,9-16,21-22,9223372036854775806-9223372036854775807");
}
qapi_free_intList(list);
}
static void test_visitor_out_bool(TestOutputVisitorData *data,
const void *unused)
{
bool value = true;
char *str;
visit_type_bool(data->ov, NULL, &value, &error_abort);
str = visitor_get(data);
g_assert_cmpstr(str, ==, "true");
}
static void test_visitor_out_number(TestOutputVisitorData *data,
const void *unused)
{
double value = 3.1415926535897932;
char *str;
visit_type_number(data->ov, NULL, &value, &error_abort);
str = visitor_get(data);
g_assert_cmpstr(str, ==, "3.1415926535897931");
}
static void test_visitor_out_string(TestOutputVisitorData *data,
const void *unused)
{
char *string = (char *) "Q E M U";
const char *string_human = "\"Q E M U\"";
char *str;
visit_type_str(data->ov, NULL, &string, &error_abort);
str = visitor_get(data);
if (data->human) {
g_assert_cmpstr(str, ==, string_human);
} else {
g_assert_cmpstr(str, ==, string);
}
}
static void test_visitor_out_no_string(TestOutputVisitorData *data,
const void *unused)
{
char *string = NULL;
char *str;
/* A null string should return "" */
visit_type_str(data->ov, NULL, &string, &error_abort);
str = visitor_get(data);
if (data->human) {
g_assert_cmpstr(str, ==, "<null>");
} else {
g_assert_cmpstr(str, ==, "");
}
}
static void test_visitor_out_enum(TestOutputVisitorData *data,
const void *unused)
{
char *str;
EnumOne i;
for (i = 0; i < ENUM_ONE__MAX; i++) {
visit_type_EnumOne(data->ov, "unused", &i, &error_abort);
str = visitor_get(data);
if (data->human) {
char *str_human = g_strdup_printf("\"%s\"", EnumOne_str(i));
g_assert_cmpstr(str, ==, str_human);
g_free(str_human);
} else {
g_assert_cmpstr(str, ==, EnumOne_str(i));
}
visitor_reset(data);
}
}
static void
output_visitor_test_add(const char *testpath,
TestOutputVisitorData *data,
void (*test_func)(TestOutputVisitorData *data,
const void *user_data),
bool human)
{
g_test_add(testpath, TestOutputVisitorData, data,
human ? visitor_output_setup_human : visitor_output_setup,
test_func, visitor_output_teardown);
}
int main(int argc, char **argv)
{
TestOutputVisitorData out_visitor_data;
g_test_init(&argc, &argv, NULL);
output_visitor_test_add("/string-visitor/output/int",
&out_visitor_data, test_visitor_out_int, false);
output_visitor_test_add("/string-visitor/output/int-human",
&out_visitor_data, test_visitor_out_int, true);
output_visitor_test_add("/string-visitor/output/bool",
&out_visitor_data, test_visitor_out_bool, false);
output_visitor_test_add("/string-visitor/output/bool-human",
&out_visitor_data, test_visitor_out_bool, true);
output_visitor_test_add("/string-visitor/output/number",
&out_visitor_data, test_visitor_out_number, false);
output_visitor_test_add("/string-visitor/output/number-human",
&out_visitor_data, test_visitor_out_number, true);
output_visitor_test_add("/string-visitor/output/string",
&out_visitor_data, test_visitor_out_string, false);
output_visitor_test_add("/string-visitor/output/string-human",
&out_visitor_data, test_visitor_out_string, true);
output_visitor_test_add("/string-visitor/output/no-string",
&out_visitor_data, test_visitor_out_no_string,
false);
output_visitor_test_add("/string-visitor/output/no-string-human",
&out_visitor_data, test_visitor_out_no_string,
true);
output_visitor_test_add("/string-visitor/output/enum",
&out_visitor_data, test_visitor_out_enum, false);
output_visitor_test_add("/string-visitor/output/enum-human",
&out_visitor_data, test_visitor_out_enum, true);
output_visitor_test_add("/string-visitor/output/intList",
&out_visitor_data, test_visitor_out_intList, false);
output_visitor_test_add("/string-visitor/output/intList-human",
&out_visitor_data, test_visitor_out_intList, true);
g_test_run();
return 0;
}

View file

@ -0,0 +1,250 @@
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "block/aio.h"
#include "block/thread-pool.h"
#include "block/block.h"
#include "qapi/error.h"
#include "qemu/timer.h"
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
static AioContext *ctx;
static ThreadPool *pool;
static int active;
typedef struct {
BlockAIOCB *aiocb;
int n;
int ret;
} WorkerTestData;
static int worker_cb(void *opaque)
{
WorkerTestData *data = opaque;
return qatomic_fetch_inc(&data->n);
}
static int long_cb(void *opaque)
{
WorkerTestData *data = opaque;
if (qatomic_cmpxchg(&data->n, 0, 1) == 0) {
g_usleep(2000000);
qatomic_or(&data->n, 2);
}
return 0;
}
static void done_cb(void *opaque, int ret)
{
WorkerTestData *data = opaque;
g_assert(data->ret == -EINPROGRESS || data->ret == -ECANCELED);
data->ret = ret;
data->aiocb = NULL;
/* Callbacks are serialized, so no need to use atomic ops. */
active--;
}
static void test_submit(void)
{
WorkerTestData data = { .n = 0 };
thread_pool_submit(pool, worker_cb, &data);
while (data.n == 0) {
aio_poll(ctx, true);
}
g_assert_cmpint(data.n, ==, 1);
}
static void test_submit_aio(void)
{
WorkerTestData data = { .n = 0, .ret = -EINPROGRESS };
data.aiocb = thread_pool_submit_aio(pool, worker_cb, &data,
done_cb, &data);
/* The callbacks are not called until after the first wait. */
active = 1;
g_assert_cmpint(data.ret, ==, -EINPROGRESS);
while (data.ret == -EINPROGRESS) {
aio_poll(ctx, true);
}
g_assert_cmpint(active, ==, 0);
g_assert_cmpint(data.n, ==, 1);
g_assert_cmpint(data.ret, ==, 0);
}
static void co_test_cb(void *opaque)
{
WorkerTestData *data = opaque;
active = 1;
data->n = 0;
data->ret = -EINPROGRESS;
thread_pool_submit_co(pool, worker_cb, data);
/* The test continues in test_submit_co, after qemu_coroutine_enter... */
g_assert_cmpint(data->n, ==, 1);
data->ret = 0;
active--;
/* The test continues in test_submit_co, after aio_poll... */
}
static void test_submit_co(void)
{
WorkerTestData data;
Coroutine *co = qemu_coroutine_create(co_test_cb, &data);
qemu_coroutine_enter(co);
/* Back here once the worker has started. */
g_assert_cmpint(active, ==, 1);
g_assert_cmpint(data.ret, ==, -EINPROGRESS);
/* aio_poll will execute the rest of the coroutine. */
while (data.ret == -EINPROGRESS) {
aio_poll(ctx, true);
}
/* Back here after the coroutine has finished. */
g_assert_cmpint(active, ==, 0);
g_assert_cmpint(data.ret, ==, 0);
}
static void test_submit_many(void)
{
WorkerTestData data[100];
int i;
/* Start more work items than there will be threads. */
for (i = 0; i < 100; i++) {
data[i].n = 0;
data[i].ret = -EINPROGRESS;
thread_pool_submit_aio(pool, worker_cb, &data[i], done_cb, &data[i]);
}
active = 100;
while (active > 0) {
aio_poll(ctx, true);
}
for (i = 0; i < 100; i++) {
g_assert_cmpint(data[i].n, ==, 1);
g_assert_cmpint(data[i].ret, ==, 0);
}
}
static void do_test_cancel(bool sync)
{
WorkerTestData data[100];
int num_canceled;
int i;
/* Start more work items than there will be threads, to ensure
* the pool is full.
*/
test_submit_many();
/* Start long running jobs, to ensure we can cancel some. */
for (i = 0; i < 100; i++) {
data[i].n = 0;
data[i].ret = -EINPROGRESS;
data[i].aiocb = thread_pool_submit_aio(pool, long_cb, &data[i],
done_cb, &data[i]);
}
/* Starting the threads may be left to a bottom half. Let it
* run, but do not waste too much time...
*/
active = 100;
aio_notify(ctx);
aio_poll(ctx, false);
/* Wait some time for the threads to start, with some sanity
* testing on the behavior of the scheduler...
*/
g_assert_cmpint(active, ==, 100);
g_usleep(1000000);
g_assert_cmpint(active, >, 50);
/* Cancel the jobs that haven't been started yet. */
num_canceled = 0;
for (i = 0; i < 100; i++) {
if (qatomic_cmpxchg(&data[i].n, 0, 4) == 0) {
data[i].ret = -ECANCELED;
if (sync) {
bdrv_aio_cancel(data[i].aiocb);
} else {
bdrv_aio_cancel_async(data[i].aiocb);
}
num_canceled++;
}
}
g_assert_cmpint(active, >, 0);
g_assert_cmpint(num_canceled, <, 100);
for (i = 0; i < 100; i++) {
if (data[i].aiocb && qatomic_read(&data[i].n) < 4) {
if (sync) {
/* Canceling the others will be a blocking operation. */
bdrv_aio_cancel(data[i].aiocb);
} else {
bdrv_aio_cancel_async(data[i].aiocb);
}
}
}
/* Finish execution and execute any remaining callbacks. */
while (active > 0) {
aio_poll(ctx, true);
}
g_assert_cmpint(active, ==, 0);
for (i = 0; i < 100; i++) {
g_assert(data[i].aiocb == NULL);
switch (data[i].n) {
case 0:
fprintf(stderr, "Callback not canceled but never started?\n");
abort();
case 3:
/* Couldn't be canceled asynchronously, must have completed. */
g_assert_cmpint(data[i].ret, ==, 0);
break;
case 4:
/* Could be canceled asynchronously, never started. */
g_assert_cmpint(data[i].ret, ==, -ECANCELED);
break;
default:
fprintf(stderr, "Callback aborted while running?\n");
abort();
}
}
}
static void test_cancel(void)
{
do_test_cancel(true);
}
static void test_cancel_async(void)
{
do_test_cancel(false);
}
int main(int argc, char **argv)
{
qemu_init_main_loop(&error_abort);
ctx = qemu_get_current_aio_context();
pool = aio_get_thread_pool(ctx);
g_test_init(&argc, &argv, NULL);
g_test_add_func("/thread-pool/submit", test_submit);
g_test_add_func("/thread-pool/submit-aio", test_submit_aio);
g_test_add_func("/thread-pool/submit-co", test_submit_co);
g_test_add_func("/thread-pool/submit-many", test_submit_many);
g_test_add_func("/thread-pool/cancel", test_cancel);
g_test_add_func("/thread-pool/cancel-async", test_cancel_async);
return g_test_run();
}

770
tests/unit/test-throttle.c Normal file
View file

@ -0,0 +1,770 @@
/*
* Throttle infrastructure tests
*
* Copyright Nodalink, EURL. 2013-2014
* Copyright Igalia, S.L. 2015
*
* Authors:
* Benoît Canet <benoit.canet@nodalink.com>
* Alberto Garcia <berto@igalia.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include <math.h>
#include "block/aio.h"
#include "qapi/error.h"
#include "qemu/throttle.h"
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "block/throttle-groups.h"
#include "sysemu/block-backend.h"
static AioContext *ctx;
static LeakyBucket bkt;
static ThrottleConfig cfg;
static ThrottleGroupMember tgm;
static ThrottleState ts;
static ThrottleTimers *tt;
/* useful function */
static bool double_cmp(double x, double y)
{
return fabsl(x - y) < 1e-6;
}
/* tests for single bucket operations */
static void test_leak_bucket(void)
{
throttle_config_init(&cfg);
bkt = cfg.buckets[THROTTLE_BPS_TOTAL];
/* set initial value */
bkt.avg = 150;
bkt.max = 15;
bkt.level = 1.5;
/* leak an op work of time */
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 150);
g_assert(bkt.avg == 150);
g_assert(bkt.max == 15);
g_assert(double_cmp(bkt.level, 0.5));
/* leak again emptying the bucket */
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 150);
g_assert(bkt.avg == 150);
g_assert(bkt.max == 15);
g_assert(double_cmp(bkt.level, 0));
/* check that the bucket level won't go lower */
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 150);
g_assert(bkt.avg == 150);
g_assert(bkt.max == 15);
g_assert(double_cmp(bkt.level, 0));
/* check that burst_level leaks correctly */
bkt.burst_level = 6;
bkt.max = 250;
bkt.burst_length = 2; /* otherwise burst_level will not leak */
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
g_assert(double_cmp(bkt.burst_level, 3.5));
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
g_assert(double_cmp(bkt.burst_level, 1));
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
g_assert(double_cmp(bkt.burst_level, 0));
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 100);
g_assert(double_cmp(bkt.burst_level, 0));
}
static void test_compute_wait(void)
{
unsigned i;
int64_t wait;
int64_t result;
throttle_config_init(&cfg);
bkt = cfg.buckets[THROTTLE_BPS_TOTAL];
/* no operation limit set */
bkt.avg = 0;
bkt.max = 15;
bkt.level = 1.5;
wait = throttle_compute_wait(&bkt);
g_assert(!wait);
/* zero delta */
bkt.avg = 150;
bkt.max = 15;
bkt.level = 15;
wait = throttle_compute_wait(&bkt);
g_assert(!wait);
/* below zero delta */
bkt.avg = 150;
bkt.max = 15;
bkt.level = 9;
wait = throttle_compute_wait(&bkt);
g_assert(!wait);
/* half an operation above max */
bkt.avg = 150;
bkt.max = 15;
bkt.level = 15.5;
wait = throttle_compute_wait(&bkt);
/* time required to do half an operation */
result = (int64_t) NANOSECONDS_PER_SECOND / 150 / 2;
g_assert(wait == result);
/* Perform I/O for 2.2 seconds at a rate of bkt.max */
bkt.burst_length = 2;
bkt.level = 0;
bkt.avg = 10;
bkt.max = 200;
for (i = 0; i < 22; i++) {
double units = bkt.max / 10;
bkt.level += units;
bkt.burst_level += units;
throttle_leak_bucket(&bkt, NANOSECONDS_PER_SECOND / 10);
wait = throttle_compute_wait(&bkt);
g_assert(double_cmp(bkt.burst_level, 0));
g_assert(double_cmp(bkt.level, (i + 1) * (bkt.max - bkt.avg) / 10));
/* We can do bursts for the 2 seconds we have configured in
* burst_length. We have 100 extra miliseconds of burst
* because bkt.level has been leaking during this time.
* After that, we have to wait. */
result = i < 21 ? 0 : 1.8 * NANOSECONDS_PER_SECOND;
g_assert(wait == result);
}
}
/* functions to test ThrottleState initialization/destroy methods */
static void read_timer_cb(void *opaque)
{
}
static void write_timer_cb(void *opaque)
{
}
static void test_init(void)
{
int i;
tt = &tgm.throttle_timers;
/* fill the structures with crap */
memset(&ts, 1, sizeof(ts));
memset(tt, 1, sizeof(*tt));
/* init structures */
throttle_init(&ts);
throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
read_timer_cb, write_timer_cb, &ts);
/* check initialized fields */
g_assert(tt->clock_type == QEMU_CLOCK_VIRTUAL);
g_assert(tt->timers[0]);
g_assert(tt->timers[1]);
/* check other fields where cleared */
g_assert(!ts.previous_leak);
g_assert(!ts.cfg.op_size);
for (i = 0; i < BUCKETS_COUNT; i++) {
g_assert(!ts.cfg.buckets[i].avg);
g_assert(!ts.cfg.buckets[i].max);
g_assert(!ts.cfg.buckets[i].level);
}
throttle_timers_destroy(tt);
}
static void test_destroy(void)
{
int i;
throttle_init(&ts);
throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
read_timer_cb, write_timer_cb, &ts);
throttle_timers_destroy(tt);
for (i = 0; i < 2; i++) {
g_assert(!tt->timers[i]);
}
}
/* function to test throttle_config and throttle_get_config */
static void test_config_functions(void)
{
int i;
ThrottleConfig orig_cfg, final_cfg;
orig_cfg.buckets[THROTTLE_BPS_TOTAL].avg = 153;
orig_cfg.buckets[THROTTLE_BPS_READ].avg = 56;
orig_cfg.buckets[THROTTLE_BPS_WRITE].avg = 1;
orig_cfg.buckets[THROTTLE_OPS_TOTAL].avg = 150;
orig_cfg.buckets[THROTTLE_OPS_READ].avg = 69;
orig_cfg.buckets[THROTTLE_OPS_WRITE].avg = 23;
orig_cfg.buckets[THROTTLE_BPS_TOTAL].max = 0;
orig_cfg.buckets[THROTTLE_BPS_READ].max = 56;
orig_cfg.buckets[THROTTLE_BPS_WRITE].max = 120;
orig_cfg.buckets[THROTTLE_OPS_TOTAL].max = 150;
orig_cfg.buckets[THROTTLE_OPS_READ].max = 400;
orig_cfg.buckets[THROTTLE_OPS_WRITE].max = 500;
orig_cfg.buckets[THROTTLE_BPS_TOTAL].level = 45;
orig_cfg.buckets[THROTTLE_BPS_READ].level = 65;
orig_cfg.buckets[THROTTLE_BPS_WRITE].level = 23;
orig_cfg.buckets[THROTTLE_OPS_TOTAL].level = 1;
orig_cfg.buckets[THROTTLE_OPS_READ].level = 90;
orig_cfg.buckets[THROTTLE_OPS_WRITE].level = 75;
orig_cfg.op_size = 1;
throttle_init(&ts);
throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
read_timer_cb, write_timer_cb, &ts);
/* structure reset by throttle_init previous_leak should be null */
g_assert(!ts.previous_leak);
throttle_config(&ts, QEMU_CLOCK_VIRTUAL, &orig_cfg);
/* has previous leak been initialized by throttle_config ? */
g_assert(ts.previous_leak);
/* get back the fixed configuration */
throttle_get_config(&ts, &final_cfg);
throttle_timers_destroy(tt);
g_assert(final_cfg.buckets[THROTTLE_BPS_TOTAL].avg == 153);
g_assert(final_cfg.buckets[THROTTLE_BPS_READ].avg == 56);
g_assert(final_cfg.buckets[THROTTLE_BPS_WRITE].avg == 1);
g_assert(final_cfg.buckets[THROTTLE_OPS_TOTAL].avg == 150);
g_assert(final_cfg.buckets[THROTTLE_OPS_READ].avg == 69);
g_assert(final_cfg.buckets[THROTTLE_OPS_WRITE].avg == 23);
g_assert(final_cfg.buckets[THROTTLE_BPS_TOTAL].max == 0);
g_assert(final_cfg.buckets[THROTTLE_BPS_READ].max == 56);
g_assert(final_cfg.buckets[THROTTLE_BPS_WRITE].max == 120);
g_assert(final_cfg.buckets[THROTTLE_OPS_TOTAL].max == 150);
g_assert(final_cfg.buckets[THROTTLE_OPS_READ].max == 400);
g_assert(final_cfg.buckets[THROTTLE_OPS_WRITE].max == 500);
g_assert(final_cfg.op_size == 1);
/* check bucket have been cleared */
for (i = 0; i < BUCKETS_COUNT; i++) {
g_assert(!final_cfg.buckets[i].level);
}
}
/* functions to test is throttle is enabled by a config */
static void set_cfg_value(bool is_max, int index, int value)
{
if (is_max) {
cfg.buckets[index].max = value;
/* If max is set, avg should never be 0 */
cfg.buckets[index].avg = MAX(cfg.buckets[index].avg, 1);
} else {
cfg.buckets[index].avg = value;
}
}
static void test_enabled(void)
{
int i;
throttle_config_init(&cfg);
g_assert(!throttle_enabled(&cfg));
for (i = 0; i < BUCKETS_COUNT; i++) {
throttle_config_init(&cfg);
set_cfg_value(false, i, 150);
g_assert(throttle_is_valid(&cfg, NULL));
g_assert(throttle_enabled(&cfg));
}
for (i = 0; i < BUCKETS_COUNT; i++) {
throttle_config_init(&cfg);
set_cfg_value(false, i, -150);
g_assert(!throttle_is_valid(&cfg, NULL));
}
}
/* tests functions for throttle_conflicting */
static void test_conflicts_for_one_set(bool is_max,
int total,
int read,
int write)
{
throttle_config_init(&cfg);
g_assert(throttle_is_valid(&cfg, NULL));
set_cfg_value(is_max, total, 1);
set_cfg_value(is_max, read, 1);
g_assert(!throttle_is_valid(&cfg, NULL));
throttle_config_init(&cfg);
set_cfg_value(is_max, total, 1);
set_cfg_value(is_max, write, 1);
g_assert(!throttle_is_valid(&cfg, NULL));
throttle_config_init(&cfg);
set_cfg_value(is_max, total, 1);
set_cfg_value(is_max, read, 1);
set_cfg_value(is_max, write, 1);
g_assert(!throttle_is_valid(&cfg, NULL));
throttle_config_init(&cfg);
set_cfg_value(is_max, total, 1);
g_assert(throttle_is_valid(&cfg, NULL));
throttle_config_init(&cfg);
set_cfg_value(is_max, read, 1);
set_cfg_value(is_max, write, 1);
g_assert(throttle_is_valid(&cfg, NULL));
}
static void test_conflicting_config(void)
{
/* bps average conflicts */
test_conflicts_for_one_set(false,
THROTTLE_BPS_TOTAL,
THROTTLE_BPS_READ,
THROTTLE_BPS_WRITE);
/* ops average conflicts */
test_conflicts_for_one_set(false,
THROTTLE_OPS_TOTAL,
THROTTLE_OPS_READ,
THROTTLE_OPS_WRITE);
/* bps average conflicts */
test_conflicts_for_one_set(true,
THROTTLE_BPS_TOTAL,
THROTTLE_BPS_READ,
THROTTLE_BPS_WRITE);
/* ops average conflicts */
test_conflicts_for_one_set(true,
THROTTLE_OPS_TOTAL,
THROTTLE_OPS_READ,
THROTTLE_OPS_WRITE);
}
/* functions to test the throttle_is_valid function */
static void test_is_valid_for_value(int value, bool should_be_valid)
{
int is_max, index;
for (is_max = 0; is_max < 2; is_max++) {
for (index = 0; index < BUCKETS_COUNT; index++) {
throttle_config_init(&cfg);
set_cfg_value(is_max, index, value);
g_assert(throttle_is_valid(&cfg, NULL) == should_be_valid);
}
}
}
static void test_is_valid(void)
{
/* negative number are invalid */
test_is_valid_for_value(-1, false);
/* zero are valids */
test_is_valid_for_value(0, true);
/* positives numers are valids */
test_is_valid_for_value(1, true);
}
static void test_ranges(void)
{
int i;
for (i = 0; i < BUCKETS_COUNT; i++) {
LeakyBucket *b = &cfg.buckets[i];
throttle_config_init(&cfg);
/* avg = 0 means throttling is disabled, but the config is valid */
b->avg = 0;
g_assert(throttle_is_valid(&cfg, NULL));
g_assert(!throttle_enabled(&cfg));
/* These are valid configurations (values <= THROTTLE_VALUE_MAX) */
b->avg = 1;
g_assert(throttle_is_valid(&cfg, NULL));
b->avg = THROTTLE_VALUE_MAX;
g_assert(throttle_is_valid(&cfg, NULL));
b->avg = THROTTLE_VALUE_MAX;
b->max = THROTTLE_VALUE_MAX;
g_assert(throttle_is_valid(&cfg, NULL));
/* Values over THROTTLE_VALUE_MAX are not allowed */
b->avg = THROTTLE_VALUE_MAX + 1;
g_assert(!throttle_is_valid(&cfg, NULL));
b->avg = THROTTLE_VALUE_MAX;
b->max = THROTTLE_VALUE_MAX + 1;
g_assert(!throttle_is_valid(&cfg, NULL));
/* burst_length must be between 1 and THROTTLE_VALUE_MAX */
b->avg = 1;
b->max = 1;
b->burst_length = 0;
g_assert(!throttle_is_valid(&cfg, NULL));
b->avg = 1;
b->max = 1;
b->burst_length = 1;
g_assert(throttle_is_valid(&cfg, NULL));
b->avg = 1;
b->max = 1;
b->burst_length = THROTTLE_VALUE_MAX;
g_assert(throttle_is_valid(&cfg, NULL));
b->avg = 1;
b->max = 1;
b->burst_length = THROTTLE_VALUE_MAX + 1;
g_assert(!throttle_is_valid(&cfg, NULL));
/* burst_length * max cannot exceed THROTTLE_VALUE_MAX */
b->avg = 1;
b->max = 2;
b->burst_length = THROTTLE_VALUE_MAX / 2;
g_assert(throttle_is_valid(&cfg, NULL));
b->avg = 1;
b->max = 3;
b->burst_length = THROTTLE_VALUE_MAX / 2;
g_assert(!throttle_is_valid(&cfg, NULL));
b->avg = 1;
b->max = THROTTLE_VALUE_MAX;
b->burst_length = 1;
g_assert(throttle_is_valid(&cfg, NULL));
b->avg = 1;
b->max = THROTTLE_VALUE_MAX;
b->burst_length = 2;
g_assert(!throttle_is_valid(&cfg, NULL));
}
}
static void test_max_is_missing_limit(void)
{
int i;
for (i = 0; i < BUCKETS_COUNT; i++) {
throttle_config_init(&cfg);
cfg.buckets[i].max = 100;
cfg.buckets[i].avg = 0;
g_assert(!throttle_is_valid(&cfg, NULL));
cfg.buckets[i].max = 0;
cfg.buckets[i].avg = 0;
g_assert(throttle_is_valid(&cfg, NULL));
cfg.buckets[i].max = 0;
cfg.buckets[i].avg = 100;
g_assert(throttle_is_valid(&cfg, NULL));
cfg.buckets[i].max = 30;
cfg.buckets[i].avg = 100;
g_assert(!throttle_is_valid(&cfg, NULL));
cfg.buckets[i].max = 100;
cfg.buckets[i].avg = 100;
g_assert(throttle_is_valid(&cfg, NULL));
}
}
static void test_iops_size_is_missing_limit(void)
{
/* A total/read/write iops limit is required */
throttle_config_init(&cfg);
cfg.op_size = 4096;
g_assert(!throttle_is_valid(&cfg, NULL));
}
static void test_have_timer(void)
{
/* zero structures */
memset(&ts, 0, sizeof(ts));
memset(tt, 0, sizeof(*tt));
/* no timer set should return false */
g_assert(!throttle_timers_are_initialized(tt));
/* init structures */
throttle_init(&ts);
throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
read_timer_cb, write_timer_cb, &ts);
/* timer set by init should return true */
g_assert(throttle_timers_are_initialized(tt));
throttle_timers_destroy(tt);
}
static void test_detach_attach(void)
{
/* zero structures */
memset(&ts, 0, sizeof(ts));
memset(tt, 0, sizeof(*tt));
/* init the structure */
throttle_init(&ts);
throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
read_timer_cb, write_timer_cb, &ts);
/* timer set by init should return true */
g_assert(throttle_timers_are_initialized(tt));
/* timer should no longer exist after detaching */
throttle_timers_detach_aio_context(tt);
g_assert(!throttle_timers_are_initialized(tt));
/* timer should exist again after attaching */
throttle_timers_attach_aio_context(tt, ctx);
g_assert(throttle_timers_are_initialized(tt));
throttle_timers_destroy(tt);
}
static bool do_test_accounting(bool is_ops, /* are we testing bps or ops */
int size, /* size of the operation to do */
double avg, /* io limit */
uint64_t op_size, /* ideal size of an io */
double total_result,
double read_result,
double write_result)
{
BucketType to_test[2][3] = { { THROTTLE_BPS_TOTAL,
THROTTLE_BPS_READ,
THROTTLE_BPS_WRITE, },
{ THROTTLE_OPS_TOTAL,
THROTTLE_OPS_READ,
THROTTLE_OPS_WRITE, } };
ThrottleConfig cfg;
BucketType index;
int i;
throttle_config_init(&cfg);
for (i = 0; i < 3; i++) {
BucketType index = to_test[is_ops][i];
cfg.buckets[index].avg = avg;
}
cfg.op_size = op_size;
throttle_init(&ts);
throttle_timers_init(tt, ctx, QEMU_CLOCK_VIRTUAL,
read_timer_cb, write_timer_cb, &ts);
throttle_config(&ts, QEMU_CLOCK_VIRTUAL, &cfg);
/* account a read */
throttle_account(&ts, false, size);
/* account a write */
throttle_account(&ts, true, size);
/* check total result */
index = to_test[is_ops][0];
if (!double_cmp(ts.cfg.buckets[index].level, total_result)) {
return false;
}
/* check read result */
index = to_test[is_ops][1];
if (!double_cmp(ts.cfg.buckets[index].level, read_result)) {
return false;
}
/* check write result */
index = to_test[is_ops][2];
if (!double_cmp(ts.cfg.buckets[index].level, write_result)) {
return false;
}
throttle_timers_destroy(tt);
return true;
}
static void test_accounting(void)
{
/* tests for bps */
/* op of size 1 */
g_assert(do_test_accounting(false,
1 * 512,
150,
0,
1024,
512,
512));
/* op of size 2 */
g_assert(do_test_accounting(false,
2 * 512,
150,
0,
2048,
1024,
1024));
/* op of size 2 and orthogonal parameter change */
g_assert(do_test_accounting(false,
2 * 512,
150,
17,
2048,
1024,
1024));
/* tests for ops */
/* op of size 1 */
g_assert(do_test_accounting(true,
1 * 512,
150,
0,
2,
1,
1));
/* op of size 2 */
g_assert(do_test_accounting(true,
2 * 512,
150,
0,
2,
1,
1));
/* jumbo op accounting fragmentation : size 64 with op size of 13 units */
g_assert(do_test_accounting(true,
64 * 512,
150,
13 * 512,
(64.0 * 2) / 13,
(64.0 / 13),
(64.0 / 13)));
/* same with orthogonal parameters changes */
g_assert(do_test_accounting(true,
64 * 512,
300,
13 * 512,
(64.0 * 2) / 13,
(64.0 / 13),
(64.0 / 13)));
}
static void test_groups(void)
{
ThrottleConfig cfg1, cfg2;
BlockBackend *blk1, *blk2, *blk3;
BlockBackendPublic *blkp1, *blkp2, *blkp3;
ThrottleGroupMember *tgm1, *tgm2, *tgm3;
/* No actual I/O is performed on these devices */
blk1 = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
blk2 = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
blk3 = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
blkp1 = blk_get_public(blk1);
blkp2 = blk_get_public(blk2);
blkp3 = blk_get_public(blk3);
tgm1 = &blkp1->throttle_group_member;
tgm2 = &blkp2->throttle_group_member;
tgm3 = &blkp3->throttle_group_member;
g_assert(tgm1->throttle_state == NULL);
g_assert(tgm2->throttle_state == NULL);
g_assert(tgm3->throttle_state == NULL);
throttle_group_register_tgm(tgm1, "bar", blk_get_aio_context(blk1));
throttle_group_register_tgm(tgm2, "foo", blk_get_aio_context(blk2));
throttle_group_register_tgm(tgm3, "bar", blk_get_aio_context(blk3));
g_assert(tgm1->throttle_state != NULL);
g_assert(tgm2->throttle_state != NULL);
g_assert(tgm3->throttle_state != NULL);
g_assert(!strcmp(throttle_group_get_name(tgm1), "bar"));
g_assert(!strcmp(throttle_group_get_name(tgm2), "foo"));
g_assert(tgm1->throttle_state == tgm3->throttle_state);
/* Setting the config of a group member affects the whole group */
throttle_config_init(&cfg1);
cfg1.buckets[THROTTLE_BPS_READ].avg = 500000;
cfg1.buckets[THROTTLE_BPS_WRITE].avg = 285000;
cfg1.buckets[THROTTLE_OPS_READ].avg = 20000;
cfg1.buckets[THROTTLE_OPS_WRITE].avg = 12000;
throttle_group_config(tgm1, &cfg1);
throttle_group_get_config(tgm1, &cfg1);
throttle_group_get_config(tgm3, &cfg2);
g_assert(!memcmp(&cfg1, &cfg2, sizeof(cfg1)));
cfg2.buckets[THROTTLE_BPS_READ].avg = 4547;
cfg2.buckets[THROTTLE_BPS_WRITE].avg = 1349;
cfg2.buckets[THROTTLE_OPS_READ].avg = 123;
cfg2.buckets[THROTTLE_OPS_WRITE].avg = 86;
throttle_group_config(tgm3, &cfg1);
throttle_group_get_config(tgm1, &cfg1);
throttle_group_get_config(tgm3, &cfg2);
g_assert(!memcmp(&cfg1, &cfg2, sizeof(cfg1)));
throttle_group_unregister_tgm(tgm1);
throttle_group_unregister_tgm(tgm2);
throttle_group_unregister_tgm(tgm3);
g_assert(tgm1->throttle_state == NULL);
g_assert(tgm2->throttle_state == NULL);
g_assert(tgm3->throttle_state == NULL);
}
int main(int argc, char **argv)
{
qemu_init_main_loop(&error_fatal);
ctx = qemu_get_aio_context();
bdrv_init();
module_call_init(MODULE_INIT_QOM);
do {} while (g_main_context_iteration(NULL, false));
/* tests in the same order as the header function declarations */
g_test_init(&argc, &argv, NULL);
g_test_add_func("/throttle/leak_bucket", test_leak_bucket);
g_test_add_func("/throttle/compute_wait", test_compute_wait);
g_test_add_func("/throttle/init", test_init);
g_test_add_func("/throttle/destroy", test_destroy);
g_test_add_func("/throttle/have_timer", test_have_timer);
g_test_add_func("/throttle/detach_attach", test_detach_attach);
g_test_add_func("/throttle/config/enabled", test_enabled);
g_test_add_func("/throttle/config/conflicting", test_conflicting_config);
g_test_add_func("/throttle/config/is_valid", test_is_valid);
g_test_add_func("/throttle/config/ranges", test_ranges);
g_test_add_func("/throttle/config/max", test_max_is_missing_limit);
g_test_add_func("/throttle/config/iops_size",
test_iops_size_is_missing_limit);
g_test_add_func("/throttle/config_functions", test_config_functions);
g_test_add_func("/throttle/accounting", test_accounting);
g_test_add_func("/throttle/groups", test_groups);
return g_test_run();
}

View file

@ -0,0 +1,89 @@
/*
* Timed average computation tests
*
* Copyright Nodalink, EURL. 2014
*
* Authors:
* Benoît Canet <benoit.canet@nodalink.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "sysemu/cpu-timers.h"
#include "qemu/timed-average.h"
/* This is the clock for QEMU_CLOCK_VIRTUAL */
static int64_t my_clock_value;
int64_t cpu_get_clock(void)
{
return my_clock_value;
}
static void account(TimedAverage *ta)
{
timed_average_account(ta, 1);
timed_average_account(ta, 5);
timed_average_account(ta, 2);
timed_average_account(ta, 4);
timed_average_account(ta, 3);
}
static void test_average(void)
{
TimedAverage ta;
uint64_t result;
int i;
/* we will compute some average on a period of 1 second */
timed_average_init(&ta, QEMU_CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND);
result = timed_average_min(&ta);
g_assert(result == 0);
result = timed_average_avg(&ta);
g_assert(result == 0);
result = timed_average_max(&ta);
g_assert(result == 0);
for (i = 0; i < 100; i++) {
account(&ta);
result = timed_average_min(&ta);
g_assert(result == 1);
result = timed_average_avg(&ta);
g_assert(result == 3);
result = timed_average_max(&ta);
g_assert(result == 5);
my_clock_value += NANOSECONDS_PER_SECOND / 10;
}
my_clock_value += NANOSECONDS_PER_SECOND * 100;
result = timed_average_min(&ta);
g_assert(result == 0);
result = timed_average_avg(&ta);
g_assert(result == 0);
result = timed_average_max(&ta);
g_assert(result == 0);
for (i = 0; i < 100; i++) {
account(&ta);
result = timed_average_min(&ta);
g_assert(result == 1);
result = timed_average_avg(&ta);
g_assert(result == 3);
result = timed_average_max(&ta);
g_assert(result == 5);
my_clock_value += NANOSECONDS_PER_SECOND / 10;
}
}
int main(int argc, char **argv)
{
/* tests in the same order as the header function declarations */
g_test_init(&argc, &argv, NULL);
g_test_add_func("/timed-average/average", test_average);
return g_test_run();
}

View file

@ -0,0 +1,720 @@
/*
* Tests for util/filemonitor-*.c
*
* Copyright 2018 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "qapi/error.h"
#include "qemu/filemonitor.h"
#include <glib/gstdio.h>
#include <utime.h>
enum {
QFILE_MONITOR_TEST_OP_ADD_WATCH,
QFILE_MONITOR_TEST_OP_DEL_WATCH,
QFILE_MONITOR_TEST_OP_EVENT,
QFILE_MONITOR_TEST_OP_CREATE,
QFILE_MONITOR_TEST_OP_APPEND,
QFILE_MONITOR_TEST_OP_TRUNC,
QFILE_MONITOR_TEST_OP_RENAME,
QFILE_MONITOR_TEST_OP_TOUCH,
QFILE_MONITOR_TEST_OP_UNLINK,
QFILE_MONITOR_TEST_OP_MKDIR,
QFILE_MONITOR_TEST_OP_RMDIR,
};
typedef struct {
int type;
const char *filesrc;
const char *filedst;
int64_t *watchid;
int eventid;
/*
* Only valid with OP_EVENT - this event might be
* swapped with the next OP_EVENT
*/
bool swapnext;
} QFileMonitorTestOp;
typedef struct {
int64_t id;
QFileMonitorEvent event;
char *filename;
} QFileMonitorTestRecord;
typedef struct {
QemuMutex lock;
GList *records;
} QFileMonitorTestData;
static QemuMutex evlock;
static bool evstopping;
static bool evrunning;
static bool debug;
/*
* Main function for a background thread that is
* running the event loop during the test
*/
static void *
qemu_file_monitor_test_event_loop(void *opaque G_GNUC_UNUSED)
{
qemu_mutex_lock(&evlock);
while (!evstopping) {
qemu_mutex_unlock(&evlock);
main_loop_wait(true);
qemu_mutex_lock(&evlock);
}
evrunning = false;
qemu_mutex_unlock(&evlock);
return NULL;
}
/*
* File monitor event handler which simply maintains
* an ordered list of all events that it receives
*/
static void
qemu_file_monitor_test_handler(int64_t id,
QFileMonitorEvent event,
const char *filename,
void *opaque)
{
QFileMonitorTestData *data = opaque;
QFileMonitorTestRecord *rec = g_new0(QFileMonitorTestRecord, 1);
if (debug) {
g_printerr("Queue event id %" PRIx64 " event %d file %s\n",
id, event, filename);
}
rec->id = id;
rec->event = event;
rec->filename = g_strdup(filename);
qemu_mutex_lock(&data->lock);
data->records = g_list_append(data->records, rec);
qemu_mutex_unlock(&data->lock);
}
static void
qemu_file_monitor_test_record_free(QFileMonitorTestRecord *rec)
{
g_free(rec->filename);
g_free(rec);
}
/*
* Get the next event record that has been received by
* the file monitor event handler. Since events are
* emitted in the background thread running the event
* loop, we can't assume there is a record available
* immediately. Thus we will sleep for upto 5 seconds
* to wait for the event to be queued for us.
*/
static QFileMonitorTestRecord *
qemu_file_monitor_test_next_record(QFileMonitorTestData *data,
QFileMonitorTestRecord *pushback)
{
GTimer *timer = g_timer_new();
QFileMonitorTestRecord *record = NULL;
GList *tmp;
qemu_mutex_lock(&data->lock);
while (!data->records && g_timer_elapsed(timer, NULL) < 5) {
qemu_mutex_unlock(&data->lock);
usleep(10 * 1000);
qemu_mutex_lock(&data->lock);
}
if (data->records) {
record = data->records->data;
if (pushback) {
data->records->data = pushback;
} else {
tmp = data->records;
data->records = g_list_remove_link(data->records, tmp);
g_list_free(tmp);
}
} else if (pushback) {
qemu_file_monitor_test_record_free(pushback);
}
qemu_mutex_unlock(&data->lock);
g_timer_destroy(timer);
return record;
}
/*
* Check whether the event record we retrieved matches
* data we were expecting to see for the event
*/
static bool
qemu_file_monitor_test_expect(QFileMonitorTestData *data,
int64_t id,
QFileMonitorEvent event,
const char *filename,
bool swapnext)
{
QFileMonitorTestRecord *rec;
bool ret = false;
rec = qemu_file_monitor_test_next_record(data, NULL);
retry:
if (!rec) {
g_printerr("Missing event watch id %" PRIx64 " event %d file %s\n",
id, event, filename);
return false;
}
if (id != rec->id) {
if (swapnext) {
rec = qemu_file_monitor_test_next_record(data, rec);
swapnext = false;
goto retry;
}
g_printerr("Expected watch id %" PRIx64 " but got %" PRIx64 "\n",
id, rec->id);
goto cleanup;
}
if (event != rec->event) {
g_printerr("Expected event %d but got %d\n", event, rec->event);
goto cleanup;
}
if (!g_str_equal(filename, rec->filename)) {
g_printerr("Expected filename %s but got %s\n",
filename, rec->filename);
goto cleanup;
}
ret = true;
cleanup:
qemu_file_monitor_test_record_free(rec);
return ret;
}
static void
test_file_monitor_events(void)
{
int64_t watch0 = 0;
int64_t watch1 = 0;
int64_t watch2 = 0;
int64_t watch3 = 0;
int64_t watch4 = 0;
int64_t watch5 = 0;
QFileMonitorTestOp ops[] = {
{ .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
.filesrc = NULL, .watchid = &watch0 },
{ .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
.filesrc = "one.txt", .watchid = &watch1 },
{ .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
.filesrc = "two.txt", .watchid = &watch2 },
{ .type = QFILE_MONITOR_TEST_OP_CREATE,
.filesrc = "one.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch1,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_CREATE,
.filesrc = "two.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch2,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_CREATE,
.filesrc = "three.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "three.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_UNLINK,
.filesrc = "three.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "three.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_RENAME,
.filesrc = "one.txt", .filedst = "two.txt" },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch1,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch2,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_APPEND,
.filesrc = "two.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_MODIFIED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch2,
.eventid = QFILE_MONITOR_EVENT_MODIFIED },
{ .type = QFILE_MONITOR_TEST_OP_TOUCH,
.filesrc = "two.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch2,
.eventid = QFILE_MONITOR_EVENT_ATTRIBUTES },
{ .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
.filesrc = "one.txt", .watchid = &watch1 },
{ .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
.filesrc = "one.txt", .watchid = &watch3 },
{ .type = QFILE_MONITOR_TEST_OP_CREATE,
.filesrc = "one.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch3,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
.filesrc = "one.txt", .watchid = &watch3 },
{ .type = QFILE_MONITOR_TEST_OP_UNLINK,
.filesrc = "one.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_MKDIR,
.filesrc = "fish", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "fish", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
.filesrc = "fish/", .watchid = &watch4 },
{ .type = QFILE_MONITOR_TEST_OP_ADD_WATCH,
.filesrc = "fish/one.txt", .watchid = &watch5 },
{ .type = QFILE_MONITOR_TEST_OP_CREATE,
.filesrc = "fish/one.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch4,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch5,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
.filesrc = "fish/one.txt", .watchid = &watch5 },
{ .type = QFILE_MONITOR_TEST_OP_RENAME,
.filesrc = "fish/one.txt", .filedst = "two.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "one.txt", .watchid = &watch4,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch2,
.eventid = QFILE_MONITOR_EVENT_CREATED },
{ .type = QFILE_MONITOR_TEST_OP_RMDIR,
.filesrc = "fish", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "", .watchid = &watch4,
.eventid = QFILE_MONITOR_EVENT_IGNORED,
.swapnext = true },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "fish", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
.filesrc = "fish", .watchid = &watch4 },
{ .type = QFILE_MONITOR_TEST_OP_UNLINK,
.filesrc = "two.txt", },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch0,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_EVENT,
.filesrc = "two.txt", .watchid = &watch2,
.eventid = QFILE_MONITOR_EVENT_DELETED },
{ .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
.filesrc = "two.txt", .watchid = &watch2 },
{ .type = QFILE_MONITOR_TEST_OP_DEL_WATCH,
.filesrc = NULL, .watchid = &watch0 },
};
Error *local_err = NULL;
GError *gerr = NULL;
QFileMonitor *mon = qemu_file_monitor_new(&local_err);
QemuThread th;
GTimer *timer;
gchar *dir = NULL;
int err = -1;
gsize i;
char *pathsrc = NULL;
char *pathdst = NULL;
QFileMonitorTestData data;
GHashTable *ids = g_hash_table_new(g_int64_hash, g_int64_equal);
char *travis_arch;
qemu_mutex_init(&data.lock);
data.records = NULL;
/*
* This test does not work on Travis LXD containers since some
* syscalls are blocked in that environment.
*/
travis_arch = getenv("TRAVIS_ARCH");
if (travis_arch && !g_str_equal(travis_arch, "x86_64")) {
g_test_skip("Test does not work on non-x86 Travis containers.");
return;
}
/*
* The file monitor needs the main loop running in
* order to receive events from inotify. We must
* thus spawn a background thread to run an event
* loop impl, while this thread triggers the
* actual file operations we're testing
*/
evrunning = 1;
evstopping = 0;
qemu_thread_create(&th, "event-loop",
qemu_file_monitor_test_event_loop, NULL,
QEMU_THREAD_JOINABLE);
if (local_err) {
g_printerr("File monitoring not available: %s",
error_get_pretty(local_err));
error_free(local_err);
return;
}
dir = g_dir_make_tmp("test-util-filemonitor-XXXXXX",
&gerr);
if (!dir) {
g_printerr("Unable to create tmp dir %s",
gerr->message);
g_error_free(gerr);
abort();
}
/*
* Run through the operation sequence validating events
* as we go
*/
for (i = 0; i < G_N_ELEMENTS(ops); i++) {
const QFileMonitorTestOp *op = &(ops[i]);
int fd;
struct utimbuf ubuf;
char *watchdir;
const char *watchfile;
pathsrc = g_strdup_printf("%s/%s", dir, op->filesrc);
if (op->filedst) {
pathdst = g_strdup_printf("%s/%s", dir, op->filedst);
}
switch (op->type) {
case QFILE_MONITOR_TEST_OP_ADD_WATCH:
if (debug) {
g_printerr("Add watch %s %s\n",
dir, op->filesrc);
}
if (op->filesrc && strchr(op->filesrc, '/')) {
watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
watchfile = strrchr(watchdir, '/');
*(char *)watchfile = '\0';
watchfile++;
if (*watchfile == '\0') {
watchfile = NULL;
}
} else {
watchdir = g_strdup(dir);
watchfile = op->filesrc;
}
*op->watchid =
qemu_file_monitor_add_watch(mon,
watchdir,
watchfile,
qemu_file_monitor_test_handler,
&data,
&local_err);
g_free(watchdir);
if (*op->watchid < 0) {
g_printerr("Unable to add watch %s",
error_get_pretty(local_err));
error_free(local_err);
goto cleanup;
}
if (debug) {
g_printerr("Watch ID %" PRIx64 "\n", *op->watchid);
}
if (g_hash_table_contains(ids, op->watchid)) {
g_printerr("Watch ID %" PRIx64 "already exists", *op->watchid);
goto cleanup;
}
g_hash_table_add(ids, op->watchid);
break;
case QFILE_MONITOR_TEST_OP_DEL_WATCH:
if (debug) {
g_printerr("Del watch %s %" PRIx64 "\n", dir, *op->watchid);
}
if (op->filesrc && strchr(op->filesrc, '/')) {
watchdir = g_strdup_printf("%s/%s", dir, op->filesrc);
watchfile = strrchr(watchdir, '/');
*(char *)watchfile = '\0';
} else {
watchdir = g_strdup(dir);
}
g_hash_table_remove(ids, op->watchid);
qemu_file_monitor_remove_watch(mon,
watchdir,
*op->watchid);
g_free(watchdir);
break;
case QFILE_MONITOR_TEST_OP_EVENT:
if (debug) {
g_printerr("Event id=%" PRIx64 " event=%d file=%s\n",
*op->watchid, op->eventid, op->filesrc);
}
if (!qemu_file_monitor_test_expect(&data, *op->watchid,
op->eventid, op->filesrc,
op->swapnext))
goto cleanup;
break;
case QFILE_MONITOR_TEST_OP_CREATE:
if (debug) {
g_printerr("Create %s\n", pathsrc);
}
fd = open(pathsrc, O_WRONLY | O_CREAT, 0700);
if (fd < 0) {
g_printerr("Unable to create %s: %s",
pathsrc, strerror(errno));
goto cleanup;
}
close(fd);
break;
case QFILE_MONITOR_TEST_OP_APPEND:
if (debug) {
g_printerr("Append %s\n", pathsrc);
}
fd = open(pathsrc, O_WRONLY | O_APPEND, 0700);
if (fd < 0) {
g_printerr("Unable to open %s: %s",
pathsrc, strerror(errno));
goto cleanup;
}
if (write(fd, "Hello World", 10) != 10) {
g_printerr("Unable to write %s: %s",
pathsrc, strerror(errno));
close(fd);
goto cleanup;
}
close(fd);
break;
case QFILE_MONITOR_TEST_OP_TRUNC:
if (debug) {
g_printerr("Truncate %s\n", pathsrc);
}
if (truncate(pathsrc, 4) < 0) {
g_printerr("Unable to truncate %s: %s",
pathsrc, strerror(errno));
goto cleanup;
}
break;
case QFILE_MONITOR_TEST_OP_RENAME:
if (debug) {
g_printerr("Rename %s -> %s\n", pathsrc, pathdst);
}
if (rename(pathsrc, pathdst) < 0) {
g_printerr("Unable to rename %s to %s: %s",
pathsrc, pathdst, strerror(errno));
goto cleanup;
}
break;
case QFILE_MONITOR_TEST_OP_UNLINK:
if (debug) {
g_printerr("Unlink %s\n", pathsrc);
}
if (unlink(pathsrc) < 0) {
g_printerr("Unable to unlink %s: %s",
pathsrc, strerror(errno));
goto cleanup;
}
break;
case QFILE_MONITOR_TEST_OP_TOUCH:
if (debug) {
g_printerr("Touch %s\n", pathsrc);
}
ubuf.actime = 1024;
ubuf.modtime = 1025;
if (utime(pathsrc, &ubuf) < 0) {
g_printerr("Unable to touch %s: %s",
pathsrc, strerror(errno));
goto cleanup;
}
break;
case QFILE_MONITOR_TEST_OP_MKDIR:
if (debug) {
g_printerr("Mkdir %s\n", pathsrc);
}
if (g_mkdir_with_parents(pathsrc, 0700) < 0) {
g_printerr("Unable to mkdir %s: %s",
pathsrc, strerror(errno));
goto cleanup;
}
break;
case QFILE_MONITOR_TEST_OP_RMDIR:
if (debug) {
g_printerr("Rmdir %s\n", pathsrc);
}
if (rmdir(pathsrc) < 0) {
g_printerr("Unable to rmdir %s: %s",
pathsrc, strerror(errno));
goto cleanup;
}
break;
default:
g_assert_not_reached();
}
g_free(pathsrc);
g_free(pathdst);
pathsrc = pathdst = NULL;
}
g_assert_cmpint(g_hash_table_size(ids), ==, 0);
err = 0;
cleanup:
g_free(pathsrc);
g_free(pathdst);
qemu_mutex_lock(&evlock);
evstopping = 1;
timer = g_timer_new();
while (evrunning && g_timer_elapsed(timer, NULL) < 5) {
qemu_mutex_unlock(&evlock);
usleep(10 * 1000);
qemu_mutex_lock(&evlock);
}
qemu_mutex_unlock(&evlock);
if (g_timer_elapsed(timer, NULL) >= 5) {
g_printerr("Event loop failed to quit after 5 seconds\n");
}
g_timer_destroy(timer);
qemu_file_monitor_free(mon);
g_list_foreach(data.records,
(GFunc)qemu_file_monitor_test_record_free, NULL);
g_list_free(data.records);
qemu_mutex_destroy(&data.lock);
if (dir) {
for (i = 0; i < G_N_ELEMENTS(ops); i++) {
const QFileMonitorTestOp *op = &(ops[i]);
char *path = g_strdup_printf("%s/%s",
dir, op->filesrc);
if (op->type == QFILE_MONITOR_TEST_OP_MKDIR) {
rmdir(path);
g_free(path);
} else {
unlink(path);
g_free(path);
if (op->filedst) {
path = g_strdup_printf("%s/%s",
dir, op->filedst);
unlink(path);
g_free(path);
}
}
}
if (rmdir(dir) < 0) {
g_printerr("Failed to remove %s: %s\n",
dir, strerror(errno));
abort();
}
}
g_hash_table_unref(ids);
g_free(dir);
g_assert(err == 0);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
qemu_init_main_loop(&error_abort);
qemu_mutex_init(&evlock);
debug = getenv("FILEMONITOR_DEBUG") != NULL;
g_test_add_func("/util/filemonitor", test_file_monitor_events);
return g_test_run();
}

View file

@ -0,0 +1,379 @@
/*
* Tests for util/qemu-sockets.c
*
* Copyright 2018 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qemu/sockets.h"
#include "qapi/error.h"
#include "socket-helpers.h"
#include "monitor/monitor.h"
static void test_fd_is_socket_bad(void)
{
char *tmp = g_strdup("qemu-test-util-sockets-XXXXXX");
int fd = mkstemp(tmp);
if (fd != 0) {
unlink(tmp);
}
g_free(tmp);
g_assert(fd >= 0);
g_assert(!fd_is_socket(fd));
close(fd);
}
static void test_fd_is_socket_good(void)
{
int fd = qemu_socket(PF_INET, SOCK_STREAM, 0);
g_assert(fd >= 0);
g_assert(fd_is_socket(fd));
close(fd);
}
static int mon_fd = -1;
static const char *mon_fdname;
__thread Monitor *cur_mon;
int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp)
{
g_assert(cur_mon);
g_assert(mon == cur_mon);
if (mon_fd == -1 || !g_str_equal(mon_fdname, fdname)) {
error_setg(errp, "No fd named %s", fdname);
return -1;
}
return dup(mon_fd);
}
/*
* Syms of stubs in libqemuutil.a are discarded at .o file
* granularity. To replace monitor_get_fd() and monitor_cur(), we
* must ensure that we also replace any other symbol that is used in
* the binary and would be taken from the same stub object file,
* otherwise we get duplicate syms at link time.
*/
Monitor *monitor_cur(void) { return cur_mon; }
int monitor_vprintf(Monitor *mon, const char *fmt, va_list ap) { abort(); }
#ifndef _WIN32
static void test_socket_fd_pass_name_good(void)
{
SocketAddress addr;
int fd;
cur_mon = g_malloc(1); /* Fake a monitor */
mon_fdname = "myfd";
mon_fd = qemu_socket(AF_INET, SOCK_STREAM, 0);
g_assert_cmpint(mon_fd, >, STDERR_FILENO);
addr.type = SOCKET_ADDRESS_TYPE_FD;
addr.u.fd.str = g_strdup(mon_fdname);
fd = socket_connect(&addr, &error_abort);
g_assert_cmpint(fd, !=, -1);
g_assert_cmpint(fd, !=, mon_fd);
close(fd);
fd = socket_listen(&addr, 1, &error_abort);
g_assert_cmpint(fd, !=, -1);
g_assert_cmpint(fd, !=, mon_fd);
close(fd);
g_free(addr.u.fd.str);
mon_fdname = NULL;
close(mon_fd);
mon_fd = -1;
g_free(cur_mon);
cur_mon = NULL;
}
static void test_socket_fd_pass_name_bad(void)
{
SocketAddress addr;
Error *err = NULL;
int fd;
cur_mon = g_malloc(1); /* Fake a monitor */
mon_fdname = "myfd";
mon_fd = dup(STDOUT_FILENO);
g_assert_cmpint(mon_fd, >, STDERR_FILENO);
addr.type = SOCKET_ADDRESS_TYPE_FD;
addr.u.fd.str = g_strdup(mon_fdname);
fd = socket_connect(&addr, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
fd = socket_listen(&addr, 1, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
g_free(addr.u.fd.str);
mon_fdname = NULL;
close(mon_fd);
mon_fd = -1;
g_free(cur_mon);
cur_mon = NULL;
}
static void test_socket_fd_pass_name_nomon(void)
{
SocketAddress addr;
Error *err = NULL;
int fd;
g_assert(cur_mon == NULL);
addr.type = SOCKET_ADDRESS_TYPE_FD;
addr.u.fd.str = g_strdup("myfd");
fd = socket_connect(&addr, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
fd = socket_listen(&addr, 1, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
g_free(addr.u.fd.str);
}
static void test_socket_fd_pass_num_good(void)
{
SocketAddress addr;
int fd, sfd;
g_assert(cur_mon == NULL);
sfd = qemu_socket(AF_INET, SOCK_STREAM, 0);
g_assert_cmpint(sfd, >, STDERR_FILENO);
addr.type = SOCKET_ADDRESS_TYPE_FD;
addr.u.fd.str = g_strdup_printf("%d", sfd);
fd = socket_connect(&addr, &error_abort);
g_assert_cmpint(fd, ==, sfd);
fd = socket_listen(&addr, 1, &error_abort);
g_assert_cmpint(fd, ==, sfd);
g_free(addr.u.fd.str);
close(sfd);
}
static void test_socket_fd_pass_num_bad(void)
{
SocketAddress addr;
Error *err = NULL;
int fd, sfd;
g_assert(cur_mon == NULL);
sfd = dup(STDOUT_FILENO);
addr.type = SOCKET_ADDRESS_TYPE_FD;
addr.u.fd.str = g_strdup_printf("%d", sfd);
fd = socket_connect(&addr, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
fd = socket_listen(&addr, 1, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
g_free(addr.u.fd.str);
close(sfd);
}
static void test_socket_fd_pass_num_nocli(void)
{
SocketAddress addr;
Error *err = NULL;
int fd;
cur_mon = g_malloc(1); /* Fake a monitor */
addr.type = SOCKET_ADDRESS_TYPE_FD;
addr.u.fd.str = g_strdup_printf("%d", STDOUT_FILENO);
fd = socket_connect(&addr, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
fd = socket_listen(&addr, 1, &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
g_free(addr.u.fd.str);
}
#endif
#ifdef CONFIG_LINUX
#define ABSTRACT_SOCKET_VARIANTS 3
typedef struct {
SocketAddress *server, *client[ABSTRACT_SOCKET_VARIANTS];
bool expect_connect[ABSTRACT_SOCKET_VARIANTS];
} abstract_socket_matrix_row;
static gpointer unix_client_thread_func(gpointer user_data)
{
abstract_socket_matrix_row *row = user_data;
Error *err = NULL;
int i, fd;
for (i = 0; i < ABSTRACT_SOCKET_VARIANTS; i++) {
if (row->expect_connect[i]) {
fd = socket_connect(row->client[i], &error_abort);
g_assert_cmpint(fd, >=, 0);
} else {
fd = socket_connect(row->client[i], &err);
g_assert_cmpint(fd, ==, -1);
error_free_or_abort(&err);
}
close(fd);
}
return NULL;
}
static void test_socket_unix_abstract_row(abstract_socket_matrix_row *test)
{
int fd, connfd, i;
GThread *cli;
struct sockaddr_un un;
socklen_t len = sizeof(un);
/* Last one must connect, or else accept() below hangs */
assert(test->expect_connect[ABSTRACT_SOCKET_VARIANTS - 1]);
fd = socket_listen(test->server, 1, &error_abort);
g_assert_cmpint(fd, >=, 0);
g_assert(fd_is_socket(fd));
cli = g_thread_new("abstract_unix_client",
unix_client_thread_func,
test);
for (i = 0; i < ABSTRACT_SOCKET_VARIANTS; i++) {
if (test->expect_connect[i]) {
connfd = accept(fd, (struct sockaddr *)&un, &len);
g_assert_cmpint(connfd, !=, -1);
close(connfd);
}
}
close(fd);
g_thread_join(cli);
}
static void test_socket_unix_abstract(void)
{
SocketAddress addr, addr_tight, addr_padded;
abstract_socket_matrix_row matrix[ABSTRACT_SOCKET_VARIANTS] = {
{ &addr,
{ &addr_tight, &addr_padded, &addr },
{ true, false, true } },
{ &addr_tight,
{ &addr_padded, &addr, &addr_tight },
{ false, true, true } },
{ &addr_padded,
{ &addr, &addr_tight, &addr_padded },
{ false, false, true } }
};
int i;
addr.type = SOCKET_ADDRESS_TYPE_UNIX;
addr.u.q_unix.path = g_strdup_printf("unix-%d-%u",
getpid(), g_random_int());
addr.u.q_unix.has_abstract = true;
addr.u.q_unix.abstract = true;
addr.u.q_unix.has_tight = false;
addr.u.q_unix.tight = false;
addr_tight = addr;
addr_tight.u.q_unix.has_tight = true;
addr_tight.u.q_unix.tight = true;
addr_padded = addr;
addr_padded.u.q_unix.has_tight = true;
addr_padded.u.q_unix.tight = false;
for (i = 0; i < ABSTRACT_SOCKET_VARIANTS; i++) {
test_socket_unix_abstract_row(&matrix[i]);
}
g_free(addr.u.q_unix.path);
}
#endif /* CONFIG_LINUX */
int main(int argc, char **argv)
{
bool has_ipv4, has_ipv6;
qemu_init_main_loop(&error_abort);
socket_init();
g_test_init(&argc, &argv, NULL);
/* We're creating actual IPv4/6 sockets, so we should
* check if the host running tests actually supports
* each protocol to avoid breaking tests on machines
* with either IPv4 or IPv6 disabled.
*/
if (socket_check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
g_printerr("socket_check_protocol_support() failed\n");
goto end;
}
if (has_ipv4) {
g_test_add_func("/util/socket/is-socket/bad",
test_fd_is_socket_bad);
g_test_add_func("/util/socket/is-socket/good",
test_fd_is_socket_good);
#ifndef _WIN32
g_test_add_func("/socket/fd-pass/name/good",
test_socket_fd_pass_name_good);
g_test_add_func("/socket/fd-pass/name/bad",
test_socket_fd_pass_name_bad);
g_test_add_func("/socket/fd-pass/name/nomon",
test_socket_fd_pass_name_nomon);
g_test_add_func("/socket/fd-pass/num/good",
test_socket_fd_pass_num_good);
g_test_add_func("/socket/fd-pass/num/bad",
test_socket_fd_pass_num_bad);
g_test_add_func("/socket/fd-pass/num/nocli",
test_socket_fd_pass_num_nocli);
#endif
}
#ifdef CONFIG_LINUX
g_test_add_func("/util/socket/unix-abstract",
test_socket_unix_abstract);
#endif
end:
return g_test_run();
}

184
tests/unit/test-uuid.c Normal file
View file

@ -0,0 +1,184 @@
/*
* QEMU UUID Library
*
* Copyright (c) 2016 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "qemu/uuid.h"
struct {
const char *uuidstr;
QemuUUID uuid;
bool uuidstr_is_valid;
bool check_unparse;
} uuid_test_data[] = {
{ /* Normal */
"586ece27-7f09-41e0-9e74-e901317e9d42",
{ { {
0x58, 0x6e, 0xce, 0x27, 0x7f, 0x09, 0x41, 0xe0,
0x9e, 0x74, 0xe9, 0x01, 0x31, 0x7e, 0x9d, 0x42,
} } },
true, true,
}, { /* NULL */
"00000000-0000-0000-0000-000000000000",
{ },
true, true,
}, { /* Upper case */
"0CC6C752-3961-4028-A286-C05CC616D396",
{ { {
0x0c, 0xc6, 0xc7, 0x52, 0x39, 0x61, 0x40, 0x28,
0xa2, 0x86, 0xc0, 0x5c, 0xc6, 0x16, 0xd3, 0x96,
} } },
true, false,
}, { /* Mixed case */
"0CC6C752-3961-4028-a286-c05cc616D396",
{ { {
0x0c, 0xc6, 0xc7, 0x52, 0x39, 0x61, 0x40, 0x28,
0xa2, 0x86, 0xc0, 0x5c, 0xc6, 0x16, 0xd3, 0x96,
} } },
true, false,
}, { /* Empty */
""
}, { /* Too short */
"abc",
}, { /* Non-hex */
"abcdefgh-0000-0000-0000-000000000000",
}, { /* No '-' */
"0cc6c75239614028a286c05cc616d396",
}, { /* '-' in wrong position */
"0cc6c-7523961-4028-a286-c05cc616d396",
}, { /* Double '-' */
"0cc6c752--3961-4028-a286-c05cc616d396",
}, { /* Too long */
"0000000000000000000000000000000000000000000000",
}, { /* Invalid char in the beginning */
")cc6c752-3961-4028-a286-c05cc616d396",
}, { /* Invalid char in the beginning, in extra */
")0cc6c752-3961-4028-a286-c05cc616d396",
}, { /* Invalid char in the middle */
"0cc6c752-39*1-4028-a286-c05cc616d396",
}, { /* Invalid char in the middle, in extra */
"0cc6c752-39*61-4028-a286-c05cc616d396",
}, { /* Invalid char in the end */
"0cc6c752-3961-4028-a286-c05cc616d39&",
}, { /* Invalid char in the end, in extra */
"0cc6c752-3961-4028-a286-c05cc616d396&",
}, { /* Short end and trailing space */
"0cc6c752-3961-4028-a286-c05cc616d39 ",
}, { /* Leading space and short end */
" 0cc6c752-3961-4028-a286-c05cc616d39",
},
};
static inline bool uuid_is_valid(QemuUUID *uuid)
{
return qemu_uuid_is_null(uuid) ||
((uuid->data[6] & 0xf0) == 0x40 && (uuid->data[8] & 0xc0) == 0x80);
}
static void test_uuid_generate(void)
{
QemuUUID uuid_not_null = { { {
0x58, 0x6e, 0xce, 0x27, 0x7f, 0x09, 0x41, 0xe0,
0x9e, 0x74, 0xe9, 0x01, 0x31, 0x7e, 0x9d, 0x42
} } };
QemuUUID uuid;
int i;
for (i = 0; i < 100; ++i) {
qemu_uuid_generate(&uuid);
g_assert(uuid_is_valid(&uuid));
g_assert_false(qemu_uuid_is_null(&uuid));
g_assert_false(qemu_uuid_is_equal(&uuid_not_null, &uuid));
}
}
static void test_uuid_is_null(void)
{
QemuUUID uuid_null = { };
QemuUUID uuid_not_null = { { {
0x58, 0x6e, 0xce, 0x27, 0x7f, 0x09, 0x41, 0xe0,
0x9e, 0x74, 0xe9, 0x01, 0x31, 0x7e, 0x9d, 0x42
} } };
QemuUUID uuid_not_null_2 = { { { 1 } } };
g_assert(qemu_uuid_is_null(&uuid_null));
g_assert_false(qemu_uuid_is_null(&uuid_not_null));
g_assert_false(qemu_uuid_is_null(&uuid_not_null_2));
}
static void test_uuid_parse(void)
{
int i, r;
for (i = 0; i < ARRAY_SIZE(uuid_test_data); i++) {
QemuUUID uuid;
bool is_valid = uuid_test_data[i].uuidstr_is_valid;
r = qemu_uuid_parse(uuid_test_data[i].uuidstr, &uuid);
g_assert_cmpint(!r, ==, is_valid);
if (is_valid) {
g_assert_cmpint(is_valid, ==, uuid_is_valid(&uuid));
g_assert_cmpmem(&uuid_test_data[i].uuid, sizeof(uuid),
&uuid, sizeof(uuid));
}
}
}
static void test_uuid_unparse(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(uuid_test_data); i++) {
char out[37];
if (!uuid_test_data[i].check_unparse) {
continue;
}
qemu_uuid_unparse(&uuid_test_data[i].uuid, out);
g_assert_cmpstr(uuid_test_data[i].uuidstr, ==, out);
}
}
static void test_uuid_unparse_strdup(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(uuid_test_data); i++) {
char *out;
if (!uuid_test_data[i].check_unparse) {
continue;
}
out = qemu_uuid_unparse_strdup(&uuid_test_data[i].uuid);
g_assert_cmpstr(uuid_test_data[i].uuidstr, ==, out);
g_free(out);
}
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/uuid/is_null", test_uuid_is_null);
g_test_add_func("/uuid/generate", test_uuid_generate);
g_test_add_func("/uuid/parse", test_uuid_parse);
g_test_add_func("/uuid/unparse", test_uuid_unparse);
g_test_add_func("/uuid/unparse_strdup", test_uuid_unparse_strdup);
return g_test_run();
}

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more