From: Aki Tuomi Date: Mon, 16 Jan 2017 13:30:14 +0000 (+0200) Subject: lib-sql: Add test driver X-Git-Tag: 2.3.0.rc1~2265 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7b1ce34143ee2c47bf4d2bff73f9cd6e763fadc0;p=thirdparty%2Fdovecot%2Fcore.git lib-sql: Add test driver Mock SQL driver --- diff --git a/src/lib-sql/Makefile.am b/src/lib-sql/Makefile.am index a6186138d9..4e81960d2f 100644 --- a/src/lib-sql/Makefile.am +++ b/src/lib-sql/Makefile.am @@ -1,4 +1,4 @@ -noinst_LTLIBRARIES = libsql.la +noinst_LTLIBRARIES = libsql.la libdriver_test.la SQL_DRIVER_PLUGINS = @@ -90,6 +90,13 @@ sql_libs = \ $(CASSANDRA_LIBS) endif +libdriver_test_la_LDFLAGS = -avoid-version +libdriver_test_la_CPPFLAGS = $(AM_CPPFLAGS) \ + -I$(top_srcdir)/src/lib-test +libdriver_test_la_SOURCES = driver-test.c + +noinst_HEADERS = driver-test.h + pkglib_LTLIBRARIES = libdovecot-sql.la libdovecot_sql_la_SOURCES = libdovecot_sql_la_LIBADD = libsql.la $(deplibs) $(sql_libs) diff --git a/src/lib-sql/driver-test.c b/src/lib-sql/driver-test.c new file mode 100644 index 0000000000..02b917d1fe --- /dev/null +++ b/src/lib-sql/driver-test.c @@ -0,0 +1,510 @@ +#include "lib.h" +#include "test-lib.h" +#include "str.h" +#include "buffer.h" +#include "sql-api-private.h" +#include "driver-test.h" +#include "array.h" +#include "hex-binary.h" + +struct test_sql_db { + struct sql_db api; + + pool_t pool; + ARRAY(struct test_driver_result) expected; + const char *error; + bool failed:1; +}; + +struct test_sql_result { + struct sql_result api; + struct test_driver_result *result; + const char *error; +}; + +static struct sql_db *driver_test_mysql_init(const char *connect_string); +static struct sql_db *driver_test_cassandra_init(const char *connect_string); +static struct sql_db *driver_test_sqlite_init(const char *connect_string); +static void driver_test_deinit(struct sql_db *_db); +static int driver_test_connect(struct sql_db *_db); +static void driver_test_disconnect(struct sql_db *_db); +static const char * +driver_test_mysql_escape_string(struct sql_db *_db, const char *string); +static const char * +driver_test_escape_string(struct sql_db *_db, const char *string); +static void driver_test_exec(struct sql_db *_db, const char *query); +static void driver_test_query(struct sql_db *_db, const char *query, + sql_query_callback_t *callback, void *context); +static struct sql_result * +driver_test_query_s(struct sql_db *_db, const char *query); +static struct sql_transaction_context * +driver_test_transaction_begin(struct sql_db *_db); +static void driver_test_transaction_commit(struct sql_transaction_context *ctx, + sql_commit_callback_t *callback, + void *context); +static int +driver_test_transaction_commit_s(struct sql_transaction_context *ctx, + const char **error_r); +static void +driver_test_transaction_rollback(struct sql_transaction_context *ctx); +static void +driver_test_update(struct sql_transaction_context *ctx, const char *query, + unsigned int *affected_rows); +static const char * +driver_test_mysql_escape_blob(struct sql_db *_db, const unsigned char *data, + size_t size); +static const char * +driver_test_escape_blob(struct sql_db *_db, const unsigned char *data, + size_t size); + +static void driver_test_result_free(struct sql_result *result); +static int driver_test_result_next_row(struct sql_result *result); + +static unsigned int +driver_test_result_get_fields_count(struct sql_result *result); +static const char * +driver_test_result_get_field_name(struct sql_result *result, unsigned int idx); +static int +driver_test_result_find_field(struct sql_result *result, const char *field_name); + +static const char * +driver_test_result_get_field_value(struct sql_result *result, unsigned int idx); +static const unsigned char * +driver_test_result_get_field_value_binary(struct sql_result *result, + unsigned int idx, size_t *size_r); +static const char * +driver_test_result_find_field_value(struct sql_result *result, + const char *field_name); +static const char *const * +driver_test_result_get_values(struct sql_result *result); + +const char *driver_test_result_get_error(struct sql_result *result); + + +const struct sql_db driver_test_mysql_db = { + .name = "mysql", + + .v = { + driver_test_mysql_init, + driver_test_deinit, + driver_test_connect, + driver_test_disconnect, + driver_test_mysql_escape_string, + driver_test_exec, + driver_test_query, + driver_test_query_s, + + driver_test_transaction_begin, + driver_test_transaction_commit, + driver_test_transaction_commit_s, + driver_test_transaction_rollback, + driver_test_update, + + driver_test_mysql_escape_blob + } +}; + +const struct sql_db driver_test_cassandra_db = { + .name = "cassandra", + + .v = { + driver_test_cassandra_init, + driver_test_deinit, + driver_test_connect, + driver_test_disconnect, + driver_test_escape_string, + driver_test_exec, + driver_test_query, + driver_test_query_s, + + driver_test_transaction_begin, + driver_test_transaction_commit, + driver_test_transaction_commit_s, + driver_test_transaction_rollback, + driver_test_update, + + driver_test_escape_blob + } +}; + +const struct sql_db driver_test_sqlite_db = { + .name = "sqlite", + + .v = { + driver_test_sqlite_init, + driver_test_deinit, + driver_test_connect, + driver_test_disconnect, + driver_test_escape_string, + driver_test_exec, + driver_test_query, + driver_test_query_s, + + driver_test_transaction_begin, + driver_test_transaction_commit, + driver_test_transaction_commit_s, + driver_test_transaction_rollback, + driver_test_update, + + driver_test_escape_blob + } +}; + + +const struct sql_result driver_test_result = { + .v = { + driver_test_result_free, + driver_test_result_next_row, + driver_test_result_get_fields_count, + driver_test_result_get_field_name, + driver_test_result_find_field, + driver_test_result_get_field_value, + driver_test_result_get_field_value_binary, + driver_test_result_find_field_value, + driver_test_result_get_values, + driver_test_result_get_error + } +}; + +void sql_driver_test_register(void) +{ + sql_driver_register(&driver_test_mysql_db); + sql_driver_register(&driver_test_cassandra_db); + sql_driver_register(&driver_test_sqlite_db); +} + +void sql_driver_test_unregister(void) +{ + sql_driver_unregister(&driver_test_mysql_db); + sql_driver_unregister(&driver_test_cassandra_db); + sql_driver_unregister(&driver_test_sqlite_db); +} + +static struct sql_db *driver_test_init(const struct sql_db *driver, + const char *connect_string ATTR_UNUSED) +{ + pool_t pool = pool_alloconly_create(MEMPOOL_GROWING" test sql driver", 2048); + struct test_sql_db *ret = p_new(pool, struct test_sql_db, 1); + ret->pool = pool; + ret->api = *driver; + p_array_init(&ret->expected, pool, 8); + return &ret->api; +} + +static struct sql_db *driver_test_mysql_init(const char *connect_string) +{ + return driver_test_init(&driver_test_mysql_db, connect_string); +} + +static struct sql_db *driver_test_cassandra_init(const char *connect_string) +{ + return driver_test_init(&driver_test_cassandra_db, connect_string); +} + +static struct sql_db *driver_test_sqlite_init(const char *connect_string) +{ + return driver_test_init(&driver_test_sqlite_db, connect_string); +} + +static void driver_test_deinit(struct sql_db *_db ATTR_UNUSED) +{ + struct test_sql_db *db = (struct test_sql_db*)_db; + array_free(&_db->module_contexts); + pool_unref(&db->pool); +} + +static int driver_test_connect(struct sql_db *_db ATTR_UNUSED) +{ + /* nix */ + return 0; +} + +static void driver_test_disconnect(struct sql_db *_db ATTR_UNUSED) +{ } + +static const char * +driver_test_mysql_escape_string(struct sql_db *_db ATTR_UNUSED, + const char *string) +{ + string_t *esc = t_str_new(strlen(string)); + for(const char *ptr = string; *ptr != '\0'; ptr++) { + if (*ptr == '\n' || *ptr == '\r' || *ptr == '\\' || + *ptr == '\'' || *ptr == '\"' || *ptr == '\x1a') + str_append_c(esc, '\\'); + str_append_c(esc, *ptr); + } + return str_c(esc); +} + +static const char * +driver_test_escape_string(struct sql_db *_db ATTR_UNUSED, const char *string) +{ + return string; +} + +static void driver_test_exec(struct sql_db *_db, const char *query) +{ + struct test_sql_db *db = (struct test_sql_db*)_db; + struct test_driver_result *result = + array_idx_modifiable(&db->expected, 0); + i_assert(result->cur < result->nqueries); + +/* i_debug("DUMMY EXECUTE: %s", query); + i_debug("DUMMY EXPECT : %s", result->queries[result->cur]); */ + + test_assert(strcmp(result->queries[result->cur], query)==0); + + if (strcmp(result->queries[result->cur], query) != 0) { + db->error = "Invalid query"; + db->failed = TRUE; + } + + result->cur++; +} + +static void +driver_test_query(struct sql_db *_db, const char *query, + sql_query_callback_t *callback, void *context) +{ + struct sql_result *result = driver_test_query_s(_db, query); + if (callback != NULL) + callback(result, context); +} + +static struct sql_result * +driver_test_query_s(struct sql_db *_db, const char *query) +{ + struct test_sql_db *db = (struct test_sql_db*)_db; + struct test_driver_result *result = + array_idx_modifiable(&db->expected, 0); + struct test_sql_result *res = i_new(struct test_sql_result, 1); + + driver_test_exec(_db, query); + + if (db->failed) { + res->api.failed = TRUE; + } + + res->api.v = driver_test_result.v; + res->api.db = _db; + if (result->result != NULL) { + res->result = i_new(struct test_driver_result, 1); + memcpy(res->result, result, sizeof(*result)); + } + res->api.refcount = 1; + + /* drop it from array if it's used up */ + if (result->cur == result->nqueries) + array_delete(&db->expected, 0, 1); + + return &res->api; +} + +static struct sql_transaction_context * +driver_test_transaction_begin(struct sql_db *_db) +{ + struct sql_transaction_context *ctx = + i_new(struct sql_transaction_context, 1); + ctx->db = _db; + return ctx; +} + +static void +driver_test_transaction_commit(struct sql_transaction_context *ctx, + sql_commit_callback_t *callback, void *context) +{ + struct sql_commit_result res; + res.error_type = driver_test_transaction_commit_s(ctx, &res.error); + callback(&res, context); +} + +static int +driver_test_transaction_commit_s(struct sql_transaction_context *ctx, + const char **error_r) +{ + struct test_sql_db *db = (struct test_sql_db*)ctx->db; + int ret = 0; + + if (db->error != NULL) { + *error_r = db->error; + ret = -1; + } + i_free(ctx); + db->error = NULL; + db->failed = FALSE; + + return ret; +} + +static void +driver_test_transaction_rollback(struct sql_transaction_context *ctx) +{ + struct test_sql_db *db = (struct test_sql_db*)ctx->db; + i_free(ctx); + db->error = NULL; + db->failed = FALSE; +} + +static void +driver_test_update(struct sql_transaction_context *ctx, const char *query, + unsigned int *affected_rows) +{ + struct test_sql_db *db= (struct test_sql_db*)ctx->db; + struct test_driver_result *result = + array_idx_modifiable(&db->expected, 0); + driver_test_exec(ctx->db, query); + + if (affected_rows != NULL) + *affected_rows = result->affected_rows; + + /* drop it from array if it's used up */ + if (result->cur == result->nqueries) + array_delete(&db->expected, 0, 1); +} + +static const char * +driver_test_mysql_escape_blob(struct sql_db *_db ATTR_UNUSED, + const unsigned char *data, size_t size) +{ + return t_strdup_printf("X'%s'", binary_to_hex(data,size)); +} + +static const char * +driver_test_escape_blob(struct sql_db *_db ATTR_UNUSED, + const unsigned char *data, size_t size) +{ + return t_strdup_printf("X'%s'", binary_to_hex(data,size)); +} + +static void driver_test_result_free(struct sql_result *result) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + if (tsr->result != NULL) + i_free(tsr->result); + i_free(result); +} + +static int driver_test_result_next_row(struct sql_result *result) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + struct test_driver_result *r = tsr->result; + + if (r == NULL) return 0; + + struct test_driver_result_set *rs = + &(r->result[r->cur-1]); + if (rs->cur <= rs->rows) { + rs->cur++; + } + + return rs->cur <= rs->rows ? 1 : 0; +} + +static unsigned int +driver_test_result_get_fields_count(struct sql_result *result) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + struct test_driver_result *r = tsr->result; + struct test_driver_result_set *rs = + &(r->result[r->cur-1]); + return rs->cols; +} + +static const char * +driver_test_result_get_field_name(struct sql_result *result, unsigned int idx) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + struct test_driver_result *r = tsr->result; + struct test_driver_result_set *rs = + &(r->result[r->cur-1]); + i_assert(idx < rs->cols); + return rs->col_names[idx]; +} + +static int +driver_test_result_find_field(struct sql_result *result, const char *field_name) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + struct test_driver_result *r = tsr->result; + struct test_driver_result_set *rs = + &(r->result[r->cur-1]); + for(size_t i = 0; i < rs->cols; i++) { + if (strcmp(field_name, rs->col_names[i])==0) + return i; + } + return -1; +} + +static const char * +driver_test_result_get_field_value(struct sql_result *result, unsigned int idx) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + struct test_driver_result *r = tsr->result; + struct test_driver_result_set *rs = + &(r->result[r->cur-1]); + + i_assert(idx < rs->cols); + i_assert(rs->cur <= rs->rows); + + return rs->row_data[rs->cur-1][idx]; +} +static const unsigned char * +driver_test_result_get_field_value_binary(struct sql_result *result, + unsigned int idx, size_t *size_r) +{ + buffer_t *buf = buffer_create_dynamic(pool_datastack_create(), 64); + const char *value = driver_test_result_get_field_value(result, idx); + /* expect it hex encoded */ + if (hex_to_binary(value, buf) < 0) { + *size_r = 0; + return NULL; + } + *size_r = buf->used; + return buf->data; +} +static const char * +driver_test_result_find_field_value(struct sql_result *result, + const char *field_name) +{ + int idx = driver_test_result_find_field(result, field_name); + if (idx < 0) return NULL; + return driver_test_result_get_field_value(result, idx); +} +static const char *const * +driver_test_result_get_values(struct sql_result *result) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + struct test_driver_result *r = tsr->result; + struct test_driver_result_set *rs = + &(r->result[r->cur-1]); + i_assert(rs->cur <= rs->rows); + return rs->row_data[rs->cur-1]; +} + +const char *driver_test_result_get_error(struct sql_result *result) +{ + struct test_sql_result *tsr = + (struct test_sql_result *)result; + return tsr->error; +} + + +void sql_driver_test_add_expected_result(struct sql_db *_db, + const struct test_driver_result *result) +{ + struct test_sql_db *db = (struct test_sql_db*)_db; + array_append(&db->expected, result, 1); +} + +void sql_driver_test_clear_expected_results(struct sql_db *_db) +{ + struct test_sql_db *db = (struct test_sql_db*)_db; + array_clear(&db->expected); +} diff --git a/src/lib-sql/driver-test.h b/src/lib-sql/driver-test.h new file mode 100644 index 0000000000..49915ad7ad --- /dev/null +++ b/src/lib-sql/driver-test.h @@ -0,0 +1,28 @@ +#ifndef DRIVER_TEST_H +#define DRIVER_TEST_H 1 + +struct test_driver_result_set { + size_t rows, cols, cur; + const char *const *col_names; + const char ***row_data; +}; + +struct test_driver_result { + /* expected queries */ + size_t nqueries; + size_t cur; + unsigned int affected_rows; + const char *const *queries; + + /* test result, rows and columns */ + struct test_driver_result_set *result; +}; + +void sql_driver_test_register(void); +void sql_driver_test_unregister(void); + +void sql_driver_test_add_expected_result(struct sql_db *_db, + const struct test_driver_result *result); +void sql_driver_test_clear_expected_results(struct sql_db *_db); + +#endif