From e1e7247500417d0c3f4046d5871c06a625fd556c Mon Sep 17 00:00:00 2001 From: Vadim Yanitskiy Date: Tue, 9 Apr 2019 16:55:44 +0700 Subject: [PATCH] Introduce initial unit test for db_sms_* API Since OsmoMSC has built-in SMSC, it needs to store the messages somewhere. Currently we use libdbi and SQLite3 back-end for that. For a long time, the db_sms_* API remained uncovered by unit tests. This change aims to fix that, and does cover the following calls: - db_sms_store(), - db_sms_get(), - db_sms_get_next_unsent(), - db_sms_mark_delivered(), - db_sms_delete_sent_message_by_id(), - db_sms_delete_by_msisdn(), - db_sms_delete_oldest_expired_message(). Due to performance reasons, the test database is initialized in RAM using the magic filename ':memory:'. This is a feature of SQLite3 (and not libdbi), see: https://www.sqlite.org/inmemorydb.html Of course, this unit test helped to discover some problems: 1) Storing an SMS with empty TP-User-Data (TP-UDL=0) causes buffer overruns in both db_sms_store() and db_sms_get(). 2) TP-User-Data-Length is always being interpreted in octets, regardless of DCS (Data Coding Scheme). This results in storing garbage in the database if the default 7-bit encoding is used. Fortunately, the 'user_data' buffer in structure 'gsm_sms' is large emough, so we don't experience buffer overruns. 3) db_sms_delete_oldest_expired_message() doesn't work as expected. Instead of removing the *oldest* expired message, it tries to remove the *newest* one. The current test expectations do reflect these problems. All of them will be fixed in the follow-up patches. Change-Id: Id94ad35b6f78f839137db2e17010fbf9b40111a3 --- configure.ac | 1 + src/libmsc/db.c | 5 +- tests/Makefile.am | 1 + tests/db_sms/Makefile.am | 51 +++ tests/db_sms/db_sms_test.c | 580 +++++++++++++++++++++++++++++++++++ tests/db_sms/db_sms_test.err | 70 +++++ tests/db_sms/db_sms_test.ok | 0 tests/testsuite.at | 7 + 8 files changed, 714 insertions(+), 1 deletion(-) create mode 100644 tests/db_sms/Makefile.am create mode 100644 tests/db_sms/db_sms_test.c create mode 100644 tests/db_sms/db_sms_test.err create mode 100644 tests/db_sms/db_sms_test.ok diff --git a/configure.ac b/configure.ac index 36ff99e3c..a2dfa5be3 100644 --- a/configure.ac +++ b/configure.ac @@ -251,6 +251,7 @@ AC_OUTPUT( tests/Makefile tests/atlocal tests/smpp/Makefile + tests/db_sms/Makefile tests/sms_queue/Makefile tests/msc_vlr/Makefile doc/Makefile diff --git a/src/libmsc/db.c b/src/libmsc/db.c index c2d833939..b564697f9 100644 --- a/src/libmsc/db.c +++ b/src/libmsc/db.c @@ -772,7 +772,10 @@ static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result resul daddr = dbi_result_get_string(result, "dest_addr"); if (daddr) OSMO_STRLCPY_ARRAY(sms->dst.addr, daddr); - sms->receiver = vlr_subscr_find_by_msisdn(net->vlr, sms->dst.addr, VSUB_USE_SMS_RECEIVER); + + if (net != NULL) /* db_sms_test passes NULL, so we need to be tolerant */ + sms->receiver = vlr_subscr_find_by_msisdn(net->vlr, sms->dst.addr, + VSUB_USE_SMS_RECEIVER); sms->src.npi = dbi_result_get_ulonglong(result, "src_npi"); sms->src.ton = dbi_result_get_ulonglong(result, "src_ton"); diff --git a/tests/Makefile.am b/tests/Makefile.am index ee4f47a0c..1fad55288 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,7 @@ SUBDIRS = \ sms_queue \ msc_vlr \ + db_sms \ $(NULL) if BUILD_SMPP diff --git a/tests/db_sms/Makefile.am b/tests/db_sms/Makefile.am new file mode 100644 index 000000000..4e850679d --- /dev/null +++ b/tests/db_sms/Makefile.am @@ -0,0 +1,51 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBASN1C_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOSIGTRAN_CFLAGS) \ + $(LIBOSMORANAP_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBSMPP34_CFLAGS) \ + $(LIBOSMOMGCPCLIENT_CFLAGS) \ + $(LIBOSMOGSUPCLIENT_CFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + db_sms_test.ok \ + db_sms_test.err \ + $(NULL) + +noinst_PROGRAMS = \ + db_sms_test \ + $(NULL) + +db_sms_test_SOURCES = \ + db_sms_test.c \ + $(srcdir)/../stubs.c \ + $(NULL) + +db_sms_test_LDADD = \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libvlr/libvlr.a \ + $(LIBSMPP34_LIBS) \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOSIGTRAN_LIBS) \ + $(LIBOSMORANAP_LIBS) \ + $(LIBASN1C_LIBS) \ + $(LIBOSMOMGCPCLIENT_LIBS) \ + $(LIBOSMOGSUPCLIENT_LIBS) \ + $(LIBRARY_GSM) \ + -ldbi \ + $(NULL) diff --git a/tests/db_sms/db_sms_test.c b/tests/db_sms/db_sms_test.c new file mode 100644 index 000000000..93aed2b23 --- /dev/null +++ b/tests/db_sms/db_sms_test.c @@ -0,0 +1,580 @@ +/* + * Test the storage API of the internal SMS Centre. + * + * (C) 2019 by Vadim Yanitskiy + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +/* Talloc context of this unit test */ +static void *talloc_ctx = NULL; + +static const struct sms_tp_ud { + /* Data Coding Scheme */ + uint8_t dcs; + /* TP User-Data-Length (depends on DCS) */ + uint8_t length; + /* Static TP User-Data filler (0 means disabled) */ + uint8_t filler_byte; + /* TP User-Data */ + uint8_t data[GSM340_UDL_OCT_MAX]; + /* Decoded text (for 7-bit default alphabet only) */ + char dec_text[GSM340_UDL_SPT_MAX + 1]; +} sms_tp_ud_set[] = { + { + .dcs = 0x00, /* Default GSM 7-bit alphabet */ + .length = 9, /* in septets */ + .dec_text = "Mahlzeit!", + .data = { + 0xcd, 0x30, 0x9a, 0xad, 0x2f, 0xa7, 0xe9, 0x21, + }, + }, + { + .dcs = 0x08, /* UCS-2 (16-bit) / UTF-16 */ + .length = 120, /* in octets */ + .data = { + 0x04, 0x23, 0x04, 0x32, 0x04, 0x30, 0x04, 0x36, + 0x04, 0x30, 0x04, 0x35, 0x04, 0x3c, 0x04, 0x4b, + 0x04, 0x39, 0x00, 0x20, 0x04, 0x3a, 0x04, 0x3b, + 0x04, 0x38, 0x04, 0x35, 0x04, 0x3d, 0x04, 0x42, + 0x00, 0x21, 0x00, 0x20, 0x04, 0x1d, 0x04, 0x30, + 0x04, 0x41, 0x04, 0x42, 0x04, 0x40, 0x04, 0x3e, + 0x04, 0x39, 0x04, 0x3a, 0x04, 0x38, 0x00, 0x20, + 0x00, 0x49, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, + 0x00, 0x72, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x74, + 0x00, 0x20, 0x04, 0x38, 0x00, 0x20, 0x00, 0x4d, + 0x00, 0x4d, 0x00, 0x53, 0x00, 0x20, 0x04, 0x31, + 0x04, 0x43, 0x04, 0x34, 0x04, 0x43, 0x04, 0x42, + 0x00, 0x20, 0x04, 0x34, 0x04, 0x3e, 0x04, 0x41, + 0x04, 0x42, 0x04, 0x30, 0x04, 0x32, 0x04, 0x3b, + 0x04, 0x35, 0x04, 0x3d, 0x04, 0x4b, 0x00, 0x2e, + }, + }, + { + .dcs = 0x04, /* 8-bit data */ + .length = 12, /* in octets */ + .data = { + /* User-Data-Header */ + 0x1e, /* Buffer-overflow! (should be 0x05) */ + /* Concatenated SM, 8-bit reference number */ + 0x00, 0x03, 0x5a, 0x05, 0x01, + + /* Dummy payload... */ + 0x05, 0x04, 0x0b, 0x84, 0x0b, 0x84, + }, + }, + { + .dcs = 0x00, /* Default GSM 7-bit alphabet */ + .length = 160, /* maximum, in septets */ + .filler_byte = 0x41, + }, + { + .dcs = 0x04, /* 8-bit data */ + .length = 140, /* maximum, in octets */ + .filler_byte = 0x42, + }, + { + .dcs = 0x00, /* Default GSM 7-bit alphabet */ + .length = 200, /* invalid, buffer overflow */ + .filler_byte = 0x41, + }, + { + .dcs = 0x04, /* 8-bit data */ + .length = 0xff, /* invalid, buffer overflow */ + .filler_byte = 0x42, + }, +}; + +#define SMS_ADDR(addr) \ + { 0x00, 0x00, addr } + +static struct sms_test { + /* Human-readable name of particular test message */ + const char *name; + /* Whether we expect db_sms_store() to fail */ + bool exp_db_sms_store_fail; + /* Whether we expect db_sms_get() to fail */ + bool exp_db_sms_get_fail; + /* SM TP-User-Data from sms_tp_ud_set[] */ + const struct sms_tp_ud *ud; + /* The message itself */ + struct gsm_sms sms; +} sms_test_set[] = { + { + .name = "Regular MO SMS", + .sms = { + .msg_ref = 0xde, + .src = SMS_ADDR("123456"), + .dst = SMS_ADDR("654321"), + .validity_minutes = 10, + .protocol_id = 0x00, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[0], + }, + { + .name = "Regular MT SMS", + .sms = { + .msg_ref = 0xbe, + .src = SMS_ADDR("654321"), + .dst = SMS_ADDR("123456"), + .validity_minutes = 180, + .protocol_id = 0x00, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[1], + }, + { + .name = "Complete TP-UD (160 septets, 7-bit encoding)", + .sms = { + .msg_ref = 0xee, + .src = SMS_ADDR("266753837248772"), + .dst = SMS_ADDR("266753837248378"), + .validity_minutes = 360, + .protocol_id = 0x00, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[3], + }, + { + .name = "Complete TP-UD (140 octets, 8-bit encoding)", + .sms = { + .msg_ref = 0xee, + .src = SMS_ADDR("266753838248772"), + .dst = SMS_ADDR("266753838248378"), + .validity_minutes = 360, + .protocol_id = 0xaa, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[4], + }, + { + .name = "TP-UD buffer overflow (UDH-Length > UD-Length)", + .sms = { + .msg_ref = 0x88, + .src = SMS_ADDR("834568373569772"), + .dst = SMS_ADDR("834568373569378"), + .validity_minutes = 200, + .protocol_id = 0xbb, + .ud_hdr_ind = 0x01, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[2], + }, + { + .name = "Truncated TP-UD (200 septets, 7-bit encoding)", + .sms = { + .msg_ref = 0xee, + .src = { 0x01, 0x00, "8786228337248772" }, + .dst = { 0x00, 0x01, "8786228337248378" }, + .validity_minutes = 360, + .protocol_id = 0xcc, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[5], + }, + { + .name = "Truncated TP-UD (255 octets, 8-bit encoding)", + .sms = { + .msg_ref = 0xee, + .src = { 0x01, 0x01, "8786228338248772" }, + .dst = { 0xaa, 0xff, "8786228338248378" }, + .validity_minutes = 360, + .protocol_id = 0xbb, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[6], + }, + { + .name = "Same MSISDN #1", + .sms = { + .msg_ref = 0x11, + .src = SMS_ADDR("72631"), + .dst = SMS_ADDR("72632"), + .validity_minutes = 10, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[0], + }, + { + .name = "Same MSISDN #2", + .sms = { + .msg_ref = 0x12, + .src = SMS_ADDR("72632"), + .dst = SMS_ADDR("72631"), + .validity_minutes = 10, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[0], + }, + { + .name = "Expired SMS", + .sms = { + .msg_ref = 0xde, + .src = SMS_ADDR("3974733772"), + .dst = SMS_ADDR("3974733378"), + .validity_minutes = 0, + /* SM TP-User-Data is taken from sms_tp_ud_set[] */ + }, + .ud = &sms_tp_ud_set[0], + }, +#if 0 + /* FIXME: there is a bug that causes ASAN / Valgrind to complain */ + { + .name = "Empty TP-UD", + .sms = { + .msg_ref = 0x38, + .src = SMS_ADDR("3678983772"), + .dst = SMS_ADDR("3678983378"), + .validity_minutes = 450, + .is_report = true, + .reply_path_req = 0x01, + .status_rep_req = 0x01, + .protocol_id = 0x55, + .data_coding_scheme = 0x08, + .ud_hdr_ind = 0x00, + .user_data_len = 0x00, + /* No TP-User-Data */ + }, + .ud = NULL, + }, +#endif +}; + +static void prepare_sms_test_set(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) { + struct sms_test *test = &sms_test_set[i]; + const struct sms_tp_ud *ud = test->ud; + + /* ID auto-increment */ + test->sms.id = i + 1; + + if (ud == NULL) + continue; + + test->sms.data_coding_scheme = ud->dcs; + test->sms.user_data_len = ud->length; + + if (ud->filler_byte) { + memset(test->sms.user_data, ud->filler_byte, + sizeof(test->sms.user_data)); + } else { + memcpy(test->sms.user_data, ud->data, sizeof(ud->data)); + if (ud->dec_text[0] != '\0') + strcpy(test->sms.text, ud->dec_text); + } + } +} + +static void test_db_sms_store(void) +{ + int rc, i; + + LOGP(DDB, LOGL_INFO, "Testing db_sms_store()...\n"); + + /* Store test SMS messages */ + for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) { + struct sms_test *test = &sms_test_set[i]; + + LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name); + + rc = db_sms_store(&test->sms); + if (!test->exp_db_sms_store_fail && rc == 0) + LOGPC(DDB, LOGL_INFO, "success, as expected\n"); + else if (test->exp_db_sms_store_fail && rc != 0) + LOGPC(DDB, LOGL_INFO, "failure, as expected\n"); + else + LOGPC(DDB, LOGL_ERROR, "unexpected rc=%d\n", rc); + } +} + +static int verify_sms(const struct sms_test *test, const struct gsm_sms *sms) +{ + int rc; + + LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name); + +#define MATCH_SMS_ADDR(ADDR) \ + if (strcmp(sms->ADDR.addr, test->sms.ADDR.addr) \ + || sms->ADDR.npi != test->sms.ADDR.npi \ + || sms->ADDR.ton != test->sms.ADDR.ton) { \ + LOGPC(DDB, LOGL_ERROR, #ADDR " address mismatch\n"); \ + return -EINVAL; \ + } + + MATCH_SMS_ADDR(src); + MATCH_SMS_ADDR(dst); + +#define MATCH_SMS_PARAM(PARAM, FMT) \ + if (sms->PARAM != test->sms.PARAM) { \ + LOGPC(DDB, LOGL_ERROR, \ + #PARAM " mismatch: E%" FMT " vs A%" FMT "\n", \ + test->sms.PARAM, sms->PARAM); \ + return -EINVAL; \ + } + + MATCH_SMS_PARAM(id, "llu"); + MATCH_SMS_PARAM(validity_minutes, "lu"); + MATCH_SMS_PARAM(is_report, "i"); + MATCH_SMS_PARAM(reply_path_req, PRIu8); + MATCH_SMS_PARAM(status_rep_req, PRIu8); + MATCH_SMS_PARAM(ud_hdr_ind, PRIu8); + MATCH_SMS_PARAM(protocol_id, PRIu8); + MATCH_SMS_PARAM(data_coding_scheme, PRIu8); + MATCH_SMS_PARAM(msg_ref, PRIu8); + MATCH_SMS_PARAM(user_data_len, PRIu8); + + /* Compare TP-User-Data */ + rc = memcmp(sms->user_data, test->sms.user_data, + sizeof(sms->user_data)); + if (rc) { + LOGPC(DDB, LOGL_ERROR, "TP-User-Data mismatch (diff=%d/%zu)\n", + rc, sizeof(sms->user_data)); + return -EINVAL; + } + + /* Compare decoded text */ + rc = strncmp(sms->text, test->sms.text, sizeof(sms->text)); + if (rc) { + LOGPC(DDB, LOGL_ERROR, "TP-User-Data (text) mismatch (diff=%d/%zu)\n", + rc, sizeof(sms->text)); + return -EINVAL; + } + + LOGPC(DDB, LOGL_NOTICE, "match\n"); + return 0; +} + +static void test_db_sms_get(void) +{ + struct gsm_sms *sms; + int i; + + LOGP(DDB, LOGL_INFO, "Testing db_sms_get()...\n"); + + /* Retrieve stored SMS messages */ + for (i = 0; i < ARRAY_SIZE(sms_test_set); i++) { + const struct sms_test *test = &sms_test_set[i]; + + LOGP(DDB, LOGL_NOTICE, "%s('%s'): ", __func__, test->name); + + sms = db_sms_get(NULL, test->sms.id); + if (!test->exp_db_sms_get_fail && sms != NULL) + LOGPC(DDB, LOGL_INFO, "success, as expected\n"); + else if (test->exp_db_sms_get_fail && sms == NULL) + LOGPC(DDB, LOGL_INFO, "failure, as expected\n"); + else + LOGPC(DDB, LOGL_ERROR, "unexpected result\n"); + + if (sms) { + verify_sms(test, sms); + talloc_free(sms); + } + } +} + +static void test_db_sms_delivery(void) +{ + struct gsm_sms *sms1, *sms2; + struct gsm_sms *sms; + int rc; + + LOGP(DDB, LOGL_INFO, "Testing db_sms_get_next_unsent() " + "and db_sms_mark_delivered()...\n"); + + /* Retrieve both #1 and #2 */ + sms1 = db_sms_get_next_unsent(NULL, 1, 0); + LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(#1): %s\n", + sms1 ? "found" : "not found"); + if (sms1 != NULL) + verify_sms(&sms_test_set[0], sms1); + + sms2 = db_sms_get_next_unsent(NULL, 2, 0); + LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(#2): %s\n", + sms2 ? "found" : "not found"); + if (sms2 != NULL) + verify_sms(&sms_test_set[1], sms2); + + /* Mark both #1 and #2 and delivered, release memory */ + if (sms1) { + LOGP(DDB, LOGL_DEBUG, "Marking #%llu as delivered: ", sms1->id); + rc = db_sms_mark_delivered(sms1); + LOGPC(DDB, LOGL_DEBUG, "rc=%d\n", rc); + talloc_free(sms1); + } + + if (sms2) { + LOGP(DDB, LOGL_DEBUG, "Marking #%llu as delivered: ", sms2->id); + rc = db_sms_mark_delivered(sms2); + LOGPC(DDB, LOGL_DEBUG, "rc=%d\n", rc); + talloc_free(sms2); + } + + /* Expect #3 as the next undelivered */ + sms = db_sms_get_next_unsent(NULL, 1, 0); + LOGP(DDB, LOGL_NOTICE, "db_sms_get_next_unsent(starting from #1): %s\n", + sms ? "found" : "not found"); + if (sms) { + verify_sms(&sms_test_set[2], sms); + talloc_free(sms); + } +} + +static void test_db_sms_delete(void) +{ + int rc; + + LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_sent_message_by_id()...\n"); + + /* Delete #1, which is marked as sent */ + LOGP(DDB, LOGL_NOTICE, "db_sms_delete_sent_message_by_id(#1, sent): "); + rc = db_sms_delete_sent_message_by_id(1); + LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc); + /* Don't expect to retrieve this message anymore */ + sms_test_set[0].exp_db_sms_get_fail = true; + + /* Try to delete #3, which is not marked as sent */ + LOGP(DDB, LOGL_NOTICE, "db_sms_delete_sent_message_by_id(#3, not sent): "); + rc = db_sms_delete_sent_message_by_id(3); + LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc); + /* Do expect to retrieve this message anyway */ + sms_test_set[2].exp_db_sms_get_fail = false; + + LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_by_msisdn()...\n"); + + LOGP(DDB, LOGL_NOTICE, "db_sms_delete_by_msisdn('72631'): "); + rc = db_sms_delete_by_msisdn("72631"); + LOGPC(DDB, LOGL_NOTICE, "rc=%d\n", rc); + + /* Don't expect both #8 and #9 anymore */ + sms_test_set[7].exp_db_sms_get_fail = true; + sms_test_set[8].exp_db_sms_get_fail = true; + + LOGP(DDB, LOGL_INFO, "Testing db_sms_delete_oldest_expired_message()...\n"); + + LOGP(DDB, LOGL_NOTICE, "db_sms_delete_oldest_expired_message()\n"); + db_sms_delete_oldest_expired_message(); + + /* Don't expect #10 anymore */ + sms_test_set[9].exp_db_sms_get_fail = true; + + /* We need to make sure that we removed exactly what we expected to remove */ + LOGP(DDB, LOGL_INFO, "Expectations updated, retrieving all messages again\n"); + test_db_sms_get(); +} + +static struct log_info_cat db_sms_test_categories[] = { + [DDB] = { + .name = "DDB", + .description = "Database Layer", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +}; + +static struct log_info info = { + .cat = db_sms_test_categories, + .num_cat = ARRAY_SIZE(db_sms_test_categories), +}; + +int main(int argc, char **argv) +{ + void *logging_ctx; + int rc; + + /* Track the use of talloc NULL memory contexts */ + talloc_enable_null_tracking(); + + talloc_ctx = talloc_named_const(NULL, 0, "db_sms_test"); + logging_ctx = talloc_named_const(talloc_ctx, 0, "logging"); + osmo_init_logging2(logging_ctx, &info); + + OSMO_ASSERT(osmo_stderr_target); + log_set_use_color(osmo_stderr_target, 0); + log_set_print_timestamp(osmo_stderr_target, 0); + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_print_level(osmo_stderr_target, 1); + +#if 0 + /* Having the database stored in a regular file may be useful + * for debugging, but this comes at the price of performance. */ + FILE *dbf = fopen("db_sms_test.db", "wb"); + OSMO_ASSERT(dbf != NULL); + fclose(dbf); +#endif + + /* Init a volatile database in RAM */ + LOGP(DDB, LOGL_DEBUG, "Init a new database\n"); + + /* HACK: db_init() prints libdbi version using LOGL_NOTICE, so + * the test output is not deterministic. Let's suppress this + * message by increasing the log level to LOGL_ERROR. */ + log_parse_category_mask(osmo_stderr_target, "DDB,7"); + rc = db_init(":memory:"); + OSMO_ASSERT(rc == 0); + + /* HACK: relax log level back to LOGL_DEBUG (see note above) */ + log_parse_category_mask(osmo_stderr_target, "DDB,1"); + + /* Prepare some tables */ + rc = db_prepare(); + OSMO_ASSERT(rc == 0); + LOGP(DDB, LOGL_DEBUG, "Init complete\n"); + + /* Prepare the test set */ + prepare_sms_test_set(); + + test_db_sms_store(); + test_db_sms_get(); + + test_db_sms_delivery(); + test_db_sms_delete(); + + /* Close the database */ + db_fini(); + + /* Deinit logging */ + log_fini(); + + /* Check for memory leaks */ + rc = talloc_total_blocks(talloc_ctx); + OSMO_ASSERT(rc == 2); /* db_sms_test + logging */ + talloc_free(talloc_ctx); + + talloc_report_full(NULL, stderr); + talloc_disable_null_tracking(); + + return 0; +} diff --git a/tests/db_sms/db_sms_test.err b/tests/db_sms/db_sms_test.err new file mode 100644 index 000000000..73dbd8ef0 --- /dev/null +++ b/tests/db_sms/db_sms_test.err @@ -0,0 +1,70 @@ +DDB DEBUG Init a new database +DDB DEBUG Init complete +DDB INFO Testing db_sms_store()... +DDB NOTICE test_db_sms_store('Regular MO SMS'): success, as expected +DDB NOTICE test_db_sms_store('Regular MT SMS'): success, as expected +DDB NOTICE test_db_sms_store('Complete TP-UD (160 septets, 7-bit encoding)'): success, as expected +DDB NOTICE test_db_sms_store('Complete TP-UD (140 octets, 8-bit encoding)'): success, as expected +DDB NOTICE test_db_sms_store('TP-UD buffer overflow (UDH-Length > UD-Length)'): success, as expected +DDB NOTICE test_db_sms_store('Truncated TP-UD (200 septets, 7-bit encoding)'): success, as expected +DDB NOTICE test_db_sms_store('Truncated TP-UD (255 octets, 8-bit encoding)'): success, as expected +DDB NOTICE test_db_sms_store('Same MSISDN #1'): success, as expected +DDB NOTICE test_db_sms_store('Same MSISDN #2'): success, as expected +DDB NOTICE test_db_sms_store('Expired SMS'): success, as expected +DDB INFO Testing db_sms_get()... +DDB NOTICE test_db_sms_get('Regular MO SMS'): success, as expected +DDB NOTICE verify_sms('Regular MO SMS'): match +DDB NOTICE test_db_sms_get('Regular MT SMS'): success, as expected +DDB NOTICE verify_sms('Regular MT SMS'): match +DDB NOTICE test_db_sms_get('Complete TP-UD (160 septets, 7-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Complete TP-UD (160 septets, 7-bit encoding)'): TP-User-Data mismatch (diff=-65/256) +DDB NOTICE test_db_sms_get('Complete TP-UD (140 octets, 8-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Complete TP-UD (140 octets, 8-bit encoding)'): TP-User-Data mismatch (diff=-66/256) +DDB NOTICE test_db_sms_get('TP-UD buffer overflow (UDH-Length > UD-Length)'): success, as expected +DDB NOTICE verify_sms('TP-UD buffer overflow (UDH-Length > UD-Length)'): match +DDB NOTICE test_db_sms_get('Truncated TP-UD (200 septets, 7-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Truncated TP-UD (200 septets, 7-bit encoding)'): TP-User-Data mismatch (diff=-65/256) +DDB NOTICE test_db_sms_get('Truncated TP-UD (255 octets, 8-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Truncated TP-UD (255 octets, 8-bit encoding)'): TP-User-Data mismatch (diff=-16896/256) +DDB NOTICE test_db_sms_get('Same MSISDN #1'): success, as expected +DDB NOTICE verify_sms('Same MSISDN #1'): match +DDB NOTICE test_db_sms_get('Same MSISDN #2'): success, as expected +DDB NOTICE verify_sms('Same MSISDN #2'): match +DDB NOTICE test_db_sms_get('Expired SMS'): success, as expected +DDB NOTICE verify_sms('Expired SMS'): match +DDB INFO Testing db_sms_get_next_unsent() and db_sms_mark_delivered()... +DDB NOTICE db_sms_get_next_unsent(#1): found +DDB NOTICE verify_sms('Regular MO SMS'): match +DDB NOTICE db_sms_get_next_unsent(#2): found +DDB NOTICE verify_sms('Regular MT SMS'): match +DDB DEBUG Marking #1 as delivered: rc=0 +DDB DEBUG Marking #2 as delivered: rc=0 +DDB NOTICE db_sms_get_next_unsent(starting from #1): found +DDB NOTICE verify_sms('Complete TP-UD (160 septets, 7-bit encoding)'): TP-User-Data mismatch (diff=-65/256) +DDB INFO Testing db_sms_delete_sent_message_by_id()... +DDB NOTICE db_sms_delete_sent_message_by_id(#1, sent): rc=0 +DDB NOTICE db_sms_delete_sent_message_by_id(#3, not sent): rc=0 +DDB INFO Testing db_sms_delete_by_msisdn()... +DDB NOTICE db_sms_delete_by_msisdn('72631'): rc=0 +DDB INFO Testing db_sms_delete_oldest_expired_message()... +DDB NOTICE db_sms_delete_oldest_expired_message() +DDB INFO Expectations updated, retrieving all messages again +DDB INFO Testing db_sms_get()... +DDB NOTICE test_db_sms_get('Regular MO SMS'): failure, as expected +DDB NOTICE test_db_sms_get('Regular MT SMS'): success, as expected +DDB NOTICE verify_sms('Regular MT SMS'): match +DDB NOTICE test_db_sms_get('Complete TP-UD (160 septets, 7-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Complete TP-UD (160 septets, 7-bit encoding)'): TP-User-Data mismatch (diff=-65/256) +DDB NOTICE test_db_sms_get('Complete TP-UD (140 octets, 8-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Complete TP-UD (140 octets, 8-bit encoding)'): TP-User-Data mismatch (diff=-66/256) +DDB NOTICE test_db_sms_get('TP-UD buffer overflow (UDH-Length > UD-Length)'): success, as expected +DDB NOTICE verify_sms('TP-UD buffer overflow (UDH-Length > UD-Length)'): match +DDB NOTICE test_db_sms_get('Truncated TP-UD (200 septets, 7-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Truncated TP-UD (200 septets, 7-bit encoding)'): TP-User-Data mismatch (diff=-65/256) +DDB NOTICE test_db_sms_get('Truncated TP-UD (255 octets, 8-bit encoding)'): success, as expected +DDB NOTICE verify_sms('Truncated TP-UD (255 octets, 8-bit encoding)'): TP-User-Data mismatch (diff=-16896/256) +DDB NOTICE test_db_sms_get('Same MSISDN #1'): failure, as expected +DDB NOTICE test_db_sms_get('Same MSISDN #2'): failure, as expected +DDB NOTICE test_db_sms_get('Expired SMS'): unexpected result +DDB NOTICE verify_sms('Expired SMS'): match +full talloc report on 'null_context' (total 0 bytes in 1 blocks) diff --git a/tests/db_sms/db_sms_test.ok b/tests/db_sms/db_sms_test.ok new file mode 100644 index 000000000..e69de29bb diff --git a/tests/testsuite.at b/tests/testsuite.at index f27b60c48..cd01bf1c6 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -16,6 +16,13 @@ cat $abs_srcdir/sms_queue/sms_queue_test.err > experr AT_CHECK([$abs_top_builddir/tests/sms_queue/sms_queue_test], [], [expout], [experr]) AT_CLEANUP +AT_SETUP([db_sms_test]) +AT_KEYWORDS([db_sms_test]) +cat $abs_srcdir/db_sms/db_sms_test.ok > expout +cat $abs_srcdir/db_sms/db_sms_test.err > experr +AT_CHECK([$abs_top_builddir/tests/db_sms/db_sms_test], [], [expout], [experr]) +AT_CLEANUP + AT_SETUP([msc_vlr_test_no_authen]) AT_KEYWORDS([msc_vlr_test_no_authen]) cat $abs_srcdir/msc_vlr/msc_vlr_test_no_authen.ok > expout