]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-dict: Add unit tests
authorAki Tuomi <aki.tuomi@dovecot.fi>
Mon, 16 Jan 2017 13:31:10 +0000 (15:31 +0200)
committerGitLab <gitlab@git.dovecot.net>
Tue, 24 Jan 2017 12:42:38 +0000 (14:42 +0200)
src/lib-dict/Makefile.am
src/lib-dict/dict.conf [new file with mode: 0644]
src/lib-dict/test-dict-sql.c [new file with mode: 0644]

index eeeb9df2cce8406686e8f7fd1a658c5e5b2967bb..7ff21adbcb581e0334c23a0050338df7f6cdce82 100644 (file)
@@ -42,6 +42,8 @@ noinst_HEADERS = \
 pkginc_libdir=$(pkgincludedir)
 pkginc_lib_HEADERS = $(headers)
 
+EXTRA_DIST = dict.conf
+
 dict-drivers-register.c: Makefile $(top_builddir)/config.h
        rm -f $@
        echo '/* this file automatically generated by Makefile */' >$@
@@ -80,6 +82,11 @@ test_programs = \
 
 noinst_PROGRAMS = $(test_programs) test-dict-client
 
+# test-dict-sql must be compiled after the whole core
+# has been compiled, because it depends on things that
+# are build after lib-dict.
+check_PROGRAMS = test-dict-sql
+
 test_libs = \
        ../lib-test/libtest.la \
        ../lib/liblib.la
@@ -92,8 +99,24 @@ test_dict_client_SOURCES = test-dict-client.c
 test_dict_client_LDADD = $(noinst_LTLIBRARIES) ../lib/liblib.la
 test_dict_client_DEPENDENCIES = $(noinst_LTLIBRARIES) $(test_libs)
 
+test_dict_sql_CFLAGS = $(AM_CPPFLAGS)
+test_dict_sql_SOURCES = \
+       test-dict-sql.c
+test_dict_sql_LDADD = \
+       $(noinst_LTLIBRARIES) \
+       ../lib-sql/libdriver_test.la \
+       ../lib-sql/libsql.la \
+       ../lib-settings/libsettings.la \
+       $(test_libs)
+test_dict_sql_DEPENDENCIES = \
+       $(noinst_LTLIBRARIES) \
+       ../lib-sql/libdriver_test.la \
+       ../lib-sql/libsql.la \
+       ../lib-settings/libsettings.la \
+       $(test_libs)
+
 check: check-am check-test
 check-test: all-am
-       for bin in $(test_programs); do \
+       for bin in $(test_programs) $(check_PROGRAMS); do \
          if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
        done
diff --git a/src/lib-dict/dict.conf b/src/lib-dict/dict.conf
new file mode 100644 (file)
index 0000000..059bddc
--- /dev/null
@@ -0,0 +1,41 @@
+connect = host=localhost
+
+map {
+  pattern = shared/dictmap/$key1/$key2
+  table = table
+  value_field = value
+  value_type = string
+
+  fields {
+    a = $key1
+    b = $key2
+  }
+}
+
+map {
+  pattern = shared/counters/$class/$name
+  table = counters
+  value_field = value
+  value_type = uint
+
+  fields {
+    class = $class
+    name = $name
+  }
+}
+
+map {
+  pattern = priv/quota/bytes
+  table = quota
+  username_field = username
+  value_field = bytes
+  value_type = uint
+}
+
+map {
+  pattern = priv/quota/count
+  table = quota
+  username_field = username
+  value_field = count
+  value_type = uint
+}
diff --git a/src/lib-dict/test-dict-sql.c b/src/lib-dict/test-dict-sql.c
new file mode 100644 (file)
index 0000000..8380ff5
--- /dev/null
@@ -0,0 +1,275 @@
+#include "lib.h"
+#include "test-lib.h"
+#include "sql-api.h"
+#include "dict.h"
+#include "dict-private.h"
+#include "dict-sql.h"
+#include "dict-sql-private.h"
+#include "driver-test.h"
+
+static void test_setup(struct dict **dict_r)
+{
+       const char *error = NULL;
+       struct dict_settings set = {
+               .username = "testuser",
+               .base_dir = "."
+       };
+       struct dict *dict = NULL;
+
+       if (dict_init("mysql:dict.conf", &set, &dict, &error) < 0)
+               i_fatal("cannot initialize dict: %s", error);
+
+       *dict_r = dict;
+}
+
+static void test_teardown(struct dict **_dict)
+{
+       struct dict *dict = *_dict;
+       *_dict = NULL;
+       if (dict != NULL) {
+               dict_deinit(&dict);
+       }
+}
+
+static void test_set_expected(struct dict *_dict,
+                             const struct test_driver_result *result)
+{
+       struct sql_dict *dict =
+               (struct sql_dict *)_dict;
+
+       sql_driver_test_add_expected_result(dict->db, result);
+}
+
+static void test_lookup_one(void)
+{
+       const char *value = NULL, *error = NULL;
+       struct test_driver_result_set rset = {
+               .rows = 1,
+               .cols = 1,
+               .col_names = (const char *[]){"value", NULL},
+               .row_data = (const char **[]){(const char*[]){"one", NULL}},
+       };
+       struct test_driver_result res = {
+               .nqueries = 1,
+               .queries = (const char *[]){"SELECT value FROM table WHERE a = 'hello' AND b = 'world'", NULL},
+               .result = &rset,
+       };
+       struct dict *dict;
+       pool_t pool = pool_datastack_create();
+
+       test_begin("dict lookup one");
+       test_setup(&dict);
+
+       test_set_expected(dict, &res);
+
+       test_assert(dict_lookup(dict, pool, "shared/dictmap/hello/world", &value, &error) == 1);
+       test_assert(value != NULL && strcmp(value, "one") == 0);
+        if (error != NULL)
+                i_error("dict_lookup failed: %s", error);
+       test_teardown(&dict);
+       test_end();
+}
+
+static void test_atomic_inc(void)
+{
+       const char *error;
+       struct test_driver_result res = {
+               .nqueries = 2,
+               .queries = (const char *[]){
+                       "UPDATE counters SET value=value+128 WHERE class = 'global' AND name = 'counter'",
+                       "UPDATE quota SET bytes=bytes+128,count=count+1 WHERE username = 'testuser'",
+                       NULL},
+               .result = NULL,
+       };
+       struct dict *dict;
+
+       test_begin("dict atomic inc");
+       test_setup(&dict);
+
+       test_set_expected(dict, &res);
+
+       struct dict_transaction_context *ctx = dict_transaction_begin(dict);
+       dict_atomic_inc(ctx, "shared/counters/global/counter", 128);
+       test_assert(dict_transaction_commit(&ctx, &error) == 0);
+        if (error != NULL)
+                i_error("dict_transaction_commit failed: %s", error);
+       error = NULL;
+       ctx = dict_transaction_begin(dict);
+       dict_atomic_inc(ctx, "priv/quota/bytes", 128);
+       dict_atomic_inc(ctx, "priv/quota/count", 1);
+       test_assert(dict_transaction_commit(&ctx, &error) == 0);
+        if (error != NULL)
+                i_error("dict_transaction_commit failed: %s", error);
+       test_teardown(&dict);
+       test_end();
+}
+
+static void test_set(void)
+{
+       const char *error;
+       struct test_driver_result res = {
+               .affected_rows = 1,
+               .nqueries = 2,
+               .queries = (const char *[]){
+                       "INSERT INTO counters (value,class,name) VALUES (128,'global','counter') ON DUPLICATE KEY UPDATE value=128",
+                       "INSERT INTO quota (bytes,count,username) VALUES (128,1,'testuser') ON DUPLICATE KEY UPDATE bytes=128,count=1",
+                       NULL},
+               .result = NULL,
+       };
+       struct dict *dict;
+
+       test_begin("dict set");
+       test_setup(&dict);
+
+       test_set_expected(dict, &res);
+
+       struct dict_transaction_context *ctx = dict_transaction_begin(dict);
+       dict_set(ctx, "shared/counters/global/counter", "128");
+       test_assert(dict_transaction_commit(&ctx, &error) == 1);
+        if (error != NULL)
+                i_error("dict_transaction_commit failed: %s", error);
+       error = NULL;
+       ctx = dict_transaction_begin(dict);
+       dict_set(ctx, "priv/quota/bytes", "128");
+       dict_set(ctx, "priv/quota/count", "1");
+       test_assert(dict_transaction_commit(&ctx, &error) == 1);
+        if (error != NULL)
+                i_error("dict_transaction_commit failed: %s", error);
+       test_teardown(&dict);
+       test_end();
+}
+
+static void test_unset(void)
+{
+       const char *error;
+       struct test_driver_result res = {
+               .affected_rows = 1,
+               .nqueries = 3,
+               .queries = (const char *[]){
+                       "DELETE FROM counters WHERE class = 'global' AND name = 'counter'",
+                       "DELETE FROM quota WHERE username = 'testuser'",
+                       "DELETE FROM quota WHERE username = 'testuser'",
+                       NULL},
+               .result = NULL,
+       };
+       struct dict *dict;
+
+       test_begin("dict unset");
+       test_setup(&dict);
+
+       test_set_expected(dict, &res);
+
+       struct dict_transaction_context *ctx = dict_transaction_begin(dict);
+       dict_unset(ctx, "shared/counters/global/counter");
+       test_assert(dict_transaction_commit(&ctx, &error) == 1);
+       if (error != NULL)
+                i_error("dict_transaction_commit failed: %s", error);
+       error = NULL;
+       ctx = dict_transaction_begin(dict);
+       dict_unset(ctx, "priv/quota/bytes");
+       dict_unset(ctx, "priv/quota/count");
+       test_assert(dict_transaction_commit(&ctx, &error) == 1);
+        if (error != NULL)
+                i_error("dict_transaction_commit failed: %s", error);
+       test_teardown(&dict);
+       test_end();
+}
+
+static void test_iterate(void)
+{
+       const char *key = NULL, *value = NULL, *error;
+       struct test_driver_result_set rset = {
+               .rows = 5,
+               .cols = 2,
+               .col_names = (const char *[]){"value", "name", NULL},
+               .row_data = (const char **[]){
+                       (const char*[]){"one", "counter", NULL},
+                       (const char*[]){"two", "counter", NULL},
+                       (const char*[]){"three", "counter", NULL},
+                       (const char*[]){"four", "counter", NULL},
+                       (const char*[]){"five", "counter", NULL},
+               },
+       };
+       struct test_driver_result res = {
+               .nqueries = 1,
+               .queries = (const char *[]){
+                       "SELECT value,name FROM counters WHERE class = 'global' AND name = 'counter'",
+                       NULL},
+               .result = &rset,
+       };
+       struct dict *dict;
+
+       test_begin("dict iterate");
+       test_setup(&dict);
+
+       test_set_expected(dict, &res);
+
+       struct dict_iterate_context *iter =
+               dict_iterate_init(dict, "shared/counters/global/counter",
+                                 DICT_ITERATE_FLAG_EXACT_KEY);
+
+       size_t idx = 0;
+       while(dict_iterate(iter, &key, &value)) {
+               i_assert(idx < rset.rows);
+               test_assert_idx(strcmp(key, "shared/counters/global/counter") == 0 &&
+                               strcmp(value, rset.row_data[idx][0]) == 0, idx);
+               idx++;
+       }
+
+       test_assert(idx == rset.rows);
+       test_assert(dict_iterate_deinit(&iter, &error) == 0);
+        if (error != NULL)
+                i_error("dict_iterate_deinit failed: %s", error);
+       error = NULL;
+
+       res.queries = (const char*[]){
+               "SELECT value,name FROM counters WHERE class = 'global' AND name LIKE '%' AND name NOT LIKE '%/%'",
+               NULL
+       };
+
+       res.cur = 0;
+       res.result->cur = 0;
+
+       test_set_expected(dict, &res);
+
+       iter = dict_iterate_init(dict, "shared/counters/global/", 0);
+
+       idx = 0;
+
+       while(dict_iterate(iter, &key, &value)) {
+               i_assert(idx < rset.rows);
+               test_assert_idx(strcmp(key, "shared/counters/global/counter") == 0 &&
+                               strcmp(value, rset.row_data[idx][0]) == 0, idx);
+               idx++;
+       }
+
+       test_assert(idx == rset.rows);
+       test_assert(dict_iterate_deinit(&iter, &error) == 0);
+       if (error != NULL)
+               i_error("dict_iterate_deinit failed: %s", error);
+       test_teardown(&dict);
+       test_end();
+}
+
+int main(void) {
+       sql_drivers_init();
+       sql_driver_test_register();
+       dict_sql_register();
+
+       static void (*const test_functions[])(void) = {
+               test_lookup_one,
+               test_atomic_inc,
+               test_set,
+               test_unset,
+               test_iterate,
+               NULL
+       };
+
+       int ret = test_run(test_functions);
+
+       dict_sql_unregister();
+       sql_driver_test_unregister();
+       sql_drivers_deinit();
+
+       return ret;
+}