Skip to content

Commit

Permalink
lib-dict: Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cmouse authored and GitLab committed Jan 24, 2017
1 parent 7b1ce34 commit ef6a5a1
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 1 deletion.
25 changes: 24 additions & 1 deletion src/lib-dict/Makefile.am
Expand Up @@ -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 */' >$@
Expand Down Expand Up @@ -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
Expand All @@ -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
41 changes: 41 additions & 0 deletions src/lib-dict/dict.conf
@@ -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
}
275 changes: 275 additions & 0 deletions src/lib-dict/test-dict-sql.c
@@ -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;
}

0 comments on commit ef6a5a1

Please sign in to comment.