diff --git a/.travis.yml b/.travis.yml index a1a17f61..b8699ec1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,8 @@ before_script: - sudo ldconfig # installing sphinx, needed for documentation - sudo apt-get install python-sphinx +# installing check, needed for unit tests + - sudo apt-get install check script: - mkdir build && cd build diff --git a/CMakeLists.txt b/CMakeLists.txt index f56cd67a..a5e1196b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,3 +53,4 @@ add_subdirectory(core) add_subdirectory(testing) add_subdirectory(other) add_subdirectory(docs) +add_subdirectory(auto_tests) diff --git a/INSTALL.md b/INSTALL.md index 8c7147fa..1ee1c66f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -16,14 +16,14 @@ Build dependencies: ```bash -apt-get install build-essential libtool autotools-dev automake libconfig-dev ncurses-dev cmake checkinstall +apt-get install build-essential libtool autotools-dev automake libconfig-dev ncurses-dev cmake checkinstall check ``` On Fedora: ```bash yum groupinstall "Development Tools" -yum install libtool autoconf automake libconfig-devel ncurses-devel cmake +yum install libtool autoconf automake libconfig-devel ncurses-devel cmake check ``` Note that `libconfig-dev` should be >= 1.4. @@ -91,7 +91,7 @@ There are no binaries/executables going to /bin/ or /usr/bin/ now. Everything is ####Homebrew: ``` -brew install libtool automake autoconf libconfig libsodium cmake +brew install libtool automake autoconf libconfig libsodium cmake check cmake . make ``` @@ -106,6 +106,7 @@ Grab the following packages: * http://www.cmake.org/ * https://github.com/jedisct1/libsodium * http://www.hyperrealm.com/libconfig/ + * http://check.sourceforge.net/ Uncompress and install them all. Make sure to follow the README as the instructions change, but they all follow the same pattern below: @@ -135,6 +136,7 @@ http://caiustheory.com/install-gcc-421-apple-build-56663-with-xcode-42 You should install: - [MinGW](http://sourceforge.net/projects/mingw/)'s C compiler - [CMake](http://www.cmake.org/cmake/resources/software.html) + - [check] (http://check.sourceforge.net/) You have to [modify your PATH environment variable](http://www.computerhope.com/issues/ch000549.htm) so that it contains MinGW's bin folder path. With default settings, the bin folder is located at `C:\MinGW\bin`, which means that you would have to append `;C:\MinGW\bin` to the PATH variable. diff --git a/auto_tests/CMakeLists.txt b/auto_tests/CMakeLists.txt new file mode 100644 index 00000000..237dae1b --- /dev/null +++ b/auto_tests/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 2.6.0) +cmake_policy(SET CMP0011 NEW) + +include_directories(${CHECK_INCLUDE_DIRS}) + +find_package(Check REQUIRED) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/messenger_test.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/friends_test.cmake) diff --git a/auto_tests/cmake/friends_test.cmake b/auto_tests/cmake/friends_test.cmake new file mode 100644 index 00000000..5c2d0fc6 --- /dev/null +++ b/auto_tests/cmake/friends_test.cmake @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.6.0) +project(friends_test C) +set(exe_name friends_test) + +add_executable(${exe_name} + friends_test.c) + +linkCoreLibraries(${exe_name}) +add_dependencies(${exe_name} Check) +target_link_libraries(${exe_name} check) diff --git a/auto_tests/cmake/messenger_test.cmake b/auto_tests/cmake/messenger_test.cmake new file mode 100644 index 00000000..084586bb --- /dev/null +++ b/auto_tests/cmake/messenger_test.cmake @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.6.0) +project(messenger_test C) +set(exe_name messenger_test) + +add_executable(${exe_name} + messenger_test.c) + +linkCoreLibraries(${exe_name}) +add_dependencies(${exe_name} Check) +target_link_libraries(${exe_name} check) diff --git a/auto_tests/friends_test.c b/auto_tests/friends_test.c new file mode 100755 index 00000000..4f777ab5 --- /dev/null +++ b/auto_tests/friends_test.c @@ -0,0 +1,217 @@ +/* Unit testing for friend requests, statuses, and messages. + * Purpose: Check that messaging functions actually do what + * they're supposed to by setting up two local clients. + * + * Design: (Subject to change.) + * 1. Parent sends a friend request, and waits for a response. + * It it doesn't get one, it kills the child. + * 2. Child gets friend request, accepts, then waits for a status change. + * 3. The parent waits on a status change, killing the child if it takes + * too long. + * 4. The child gets the status change, then sends a message. After that, + * it returns. If if doesn't get the status change, it just loops forever. + * 5. After getting the status change, the parent waits for a message, on getting + * one, it waits on the child to return, then returns 0. + * + * Note about "waiting": + * Wait time is decided by WAIT_COUNT and WAIT_TIME. c_sleep(WAIT_TIME) WAIT_COUNT + * times. This is used both to ensure that we don't loop forever on a broken build, + * and that we don't get too slow with messaging. The current time is 15 seconds. */ + +#include "../core/friend_requests.h" +#include "../core/Messenger.h" +#include +#include +#include +#include +#include + +#define WAIT_COUNT 30 +#define WAIT_TIME 500 + +/* first step, second step */ +#define FIRST_FLAG 0x1 +#define SECOND_FLAG 0x2 + +/* ensure that we sleep in milliseconds */ +#ifdef WIN32 +#define c_sleep(x) Sleep(x) +#else +#define c_sleep(x) usleep(1000*x) +#endif + +uint8_t *parent_id = NULL; +uint8_t *child_id = NULL; + +pid_t child_pid = 0; +int request_flags = 0; + +void do_tox(void) +{ + static int dht_on = 0; + + if(!dht_on && DHT_isconnected()) { + dht_on = 1; + } else if(dht_on && !DHT_isconnected()) { + dht_on = 0; + } + + doMessenger(); +} + +void parent_confirm_message(int num, uint8_t *data, uint16_t length) +{ + puts("OK"); + request_flags |= SECOND_FLAG; +} + +void parent_confirm_status(int num, USERSTATUS_KIND status, uint8_t *data, uint16_t length) +{ + puts("OK"); + request_flags |= FIRST_FLAG; +} + +int parent_friend_request(void) +{ + char *message = "Watson, come here, I need you."; + int len = strlen(message); + int i = 0; + + fputs("Sending child request.", stdout); + fflush(stdout); + + m_addfriend(child_id, (uint8_t *)message, len); + + /* wait on the status change */ + for(i = 0; i < WAIT_COUNT; i++) { + do_tox(); + if(request_flags & FIRST_FLAG) + break; + fputs(".", stdout); + fflush(stdout); + c_sleep(WAIT_TIME); + } + + if(!(request_flags & FIRST_FLAG)) { + fputs("\nfriends_test: The child took to long to respond!\n" + "Friend requests may be broken, failing build!\n", stderr); + kill(child_pid, SIGKILL); + return -1; + } + + return 0; +} + +void child_got_request(uint8_t *public_key, uint8_t *data, uint16_t length) +{ + fputs("OK\nsending status to parent", stdout); + fflush(stdout); + m_addfriend_norequest(public_key); + request_flags |= FIRST_FLAG; +} + +void child_got_statuschange(int friend_num, USERSTATUS_KIND status, uint8_t *string, uint16_t length) +{ + request_flags |= SECOND_FLAG; +} + +int parent_wait_for_message(void) +{ + int i = 0; + + fputs("Parent waiting for message.", stdout); + fflush(stdout); + + for(i = 0; i < WAIT_COUNT; i++) { + do_tox(); + if(request_flags & SECOND_FLAG) + break; + fputs(".", stdout); + fflush(stdout); + c_sleep(WAIT_TIME); + } + + if(!(request_flags & SECOND_FLAG)) { + fputs("\nParent hasn't recieved the message yet!\n" + "Messaging may be broken, failing the build!\n", stderr); + kill(child_pid, SIGKILL); + return -1; + } + + return 0; +} + +void cleanup(void) +{ + munmap(parent_id, crypto_box_PUBLICKEYBYTES); + munmap(child_id, crypto_box_PUBLICKEYBYTES); + puts("============= END TEST ============="); +} + +int main(int argc, char *argv[]) +{ + puts("=========== FRIENDS_TEST ==========="); + + /* set up the global memory */ + parent_id = mmap(NULL, crypto_box_PUBLICKEYBYTES, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + child_id = mmap(NULL, crypto_box_PUBLICKEYBYTES, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + fputs("friends_test: Starting test...\n", stdout); + if((child_pid = fork()) == 0) { + /* child */ + int i = 0; + char *message = "Y-yes Mr. Watson?"; + + initMessenger(); + Messenger_save(child_id); + msync(child_id, crypto_box_PUBLICKEYBYTES, MS_SYNC); + + m_callback_friendrequest(child_got_request); + m_callback_userstatus(child_got_statuschange); + + /* wait on the friend request */ + while(!(request_flags & FIRST_FLAG)) + do_tox(); + + /* wait for the status change */ + while(!(request_flags & SECOND_FLAG)) + do_tox(); + + for(i = 0; i < 6; i++) { + /* send the message six times, just to be sure */ + m_sendmessage(0, (uint8_t *)message, strlen(message)); + do_tox(); + } + + return 0; + } + + /* parent */ + if(atexit(cleanup) != 0) { + fputs("friends_test: atexit() failed!\nFailing build...\n", stderr); + kill(child_pid, SIGKILL); + return -1; + } + + msync(parent_id, crypto_box_PUBLICKEYBYTES, MS_SYNC); + m_callback_userstatus(parent_confirm_status); + m_callback_friendmessage(parent_confirm_message); + + /* hacky way to give the child time to set up */ + c_sleep(50); + + initMessenger(); + Messenger_save(parent_id); + + if(parent_friend_request() == -1) + return -1; + + if(parent_wait_for_message() == -1) + return -1; + + wait(NULL); + fputs("friends_test: Build passed!\n", stdout); + return 0; +} diff --git a/auto_tests/messenger_test.c b/auto_tests/messenger_test.c new file mode 100644 index 00000000..deed498f --- /dev/null +++ b/auto_tests/messenger_test.c @@ -0,0 +1,279 @@ +/* unit tests for /core/Messenger.c + * Design: + * Just call every non-static function in Messenger.c, checking that + * they return as they should with check calls. "Bad" calls of the type + * function(bad_data, good_length) are _not_ checked for, this type + * of call is the fault of the client code. + * + * Note: + * None of the functions here test things that rely on the network, i.e. + * checking that status changes are recieved, messages can be sent, etc. + * All of that is done in a separate test, with two local clients running. */ + +#include "../core/Messenger.h" +#include +#include +#include +#include + +#define REALLY_BIG_NUMBER ((1) << (sizeof(uint16_t) * 7)) +#define STRINGS_EQUAL(X, Y) (strcmp(X, Y) == 0) + +char *friend_id_str = "1145e295b0fbdc9330d5d74ec204a8bf23c315106040b4035d0d358d07ee3f7d"; + +/* in case we need more than one ID for a test */ +char *good_id_a_str = "DB9B569D14850ED8364C3744CAC2C8FF78985D213E980C7C508D0E91E8E45441"; +char *good_id_b_str = "d3f14b6d384d8f5f2a66cff637e69f28f539c5de61bc29744785291fa4ef4d64"; + +char *bad_id_str = "9B569D14ff637e69f2"; + +unsigned char *friend_id = NULL; +unsigned char *good_id_a = NULL; +unsigned char *good_id_b = NULL; +unsigned char *bad_id = NULL; + +int friend_id_num = 0; + +unsigned char * hex_string_to_bin(char hex_string[]) +{ + size_t len = strlen(hex_string); + unsigned char *val = calloc(1, len); + char *pos = hex_string; + int i = 0; + for(i = 0; i < len; ++i, pos+=2) + sscanf(pos,"%2hhx",&val[i]); + return val; +} + +START_TEST(test_m_sendmesage) +{ + char *message = "h-hi :3"; + int good_len = strlen(message); + int bad_len = MAX_DATA_SIZE; + + + ck_assert(m_sendmessage(-1, (uint8_t *)message, good_len) == 0); + ck_assert(m_sendmessage(REALLY_BIG_NUMBER, (uint8_t *)message, good_len) == 0); + ck_assert(m_sendmessage(17, (uint8_t *)message, good_len) == 0); + ck_assert(m_sendmessage(friend_id_num, (uint8_t *)message, bad_len) == 0); +} +END_TEST + +START_TEST(test_m_get_userstatus_size) +{ + int rc = 0; + ck_assert_msg((m_get_userstatus_size(-1) == -1), + "m_get_userstatus_size did NOT catch an argument of -1"); + ck_assert_msg((m_get_userstatus_size(REALLY_BIG_NUMBER) == -1), + "m_get_userstatus_size did NOT catch the following argument: %d\n", + REALLY_BIG_NUMBER); + rc = m_get_userstatus_size(friend_id_num); + + /* this WILL error if the original m_addfriend_norequest() failed */ + ck_assert_msg((rc > 0 && rc <= MAX_USERSTATUS_LENGTH), + "m_get_userstatus_size is returning out of range values!\n" + "(this can be caused by the error of m_addfriend_norequest" + " in the beginning of the suite)\n"); +} +END_TEST + +START_TEST(test_m_set_userstatus) +{ + char *status = "online!"; + uint16_t good_length = strlen(status); + uint16_t bad_length = REALLY_BIG_NUMBER; + + if(m_set_userstatus(USERSTATUS_KIND_ONLINE, + (uint8_t *)status, bad_length) != -1) + ck_abort_msg("m_set_userstatus did NOT catch the following length: %d\n", + REALLY_BIG_NUMBER); + + if((m_set_userstatus(USERSTATUS_KIND_RETAIN, + (uint8_t *)status, good_length)) != 0) + ck_abort_msg("m_set_userstatus did NOT return 0 on the following length: %d\n" + "MAX_USERSTATUS_LENGTH: %d\n", good_length, MAX_USERSTATUS_LENGTH); +} +END_TEST + +START_TEST(test_m_friendstatus) +{ + ck_assert_msg((m_friendstatus(-1) == NOFRIEND), + "m_friendstatus did NOT catch an argument of -1.\n"); + ck_assert_msg((m_friendstatus(REALLY_BIG_NUMBER) == NOFRIEND), + "m_friendstatus did NOT catch an argument of %d.\n", + REALLY_BIG_NUMBER); +} +END_TEST + +START_TEST(test_m_delfriend) +{ + ck_assert_msg((m_delfriend(-1) == -1), + "m_delfriend did NOT catch an argument of -1\n"); + ck_assert_msg((m_delfriend(REALLY_BIG_NUMBER) == -1), + "m_delfriend did NOT catch the following number: %d\n", + REALLY_BIG_NUMBER); +} +END_TEST + +START_TEST(test_m_addfriend) +{ + char *good_data = "test"; + char *bad_data = ""; + + int good_len = strlen(good_data); + int bad_len = strlen(bad_data); + int really_bad_len = (MAX_DATA_SIZE - crypto_box_PUBLICKEYBYTES + - crypto_box_NONCEBYTES - crypto_box_BOXZEROBYTES + + crypto_box_ZEROBYTES + 100); + + if(m_addfriend((uint8_t *)friend_id, (uint8_t *)good_data, really_bad_len) != FAERR_TOOLONG) + ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", really_bad_len); + + /* this will error if the original m_addfriend_norequest() failed */ + if(m_addfriend((uint8_t *)friend_id, (uint8_t *)good_data, good_len) != FAERR_ALREADYSENT) + ck_abort_msg("m_addfriend did NOT catch adding a friend we already have.\n" + "(this can be caused by the error of m_addfriend_norequest in" + " the beginning of the suite)\n"); + + if(m_addfriend((uint8_t *)good_id_b, (uint8_t *)bad_data, bad_len) != FAERR_NOMESSAGE) + ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", bad_len); + + /* this should REALLY error */ + if(m_addfriend((uint8_t *)bad_id, (uint8_t *)good_data, good_len) >= 0) + ck_abort_msg("The following ID passed through " + "m_addfriend without an error:\n'%s'\n", bad_id_str); +} +END_TEST + +START_TEST(test_setname) +{ + char *good_name = "consensualCorn"; + int good_length = strlen(good_name); + int bad_length = REALLY_BIG_NUMBER; + + if(setname((uint8_t *)good_name, bad_length) != -1) + ck_abort_msg("setname() did NOT error on %d as a length argument!\n", + bad_length); + if(setname((uint8_t *)good_name, good_length) != 0) + ck_abort_msg("setname() did NOT return 0 on good arguments!\n"); +} +END_TEST + +START_TEST(test_getself_name) +{ + char *nickname = "testGallop"; + int len = strlen(nickname); + char nick_check[len]; + + setname((uint8_t *)nickname, len); + getself_name((uint8_t *)nick_check); + + ck_assert_msg((!STRINGS_EQUAL(nickname, nick_check)), + "getself_name failed to return the known name!\n" + "known name: %s\nreturned: %s\n", nickname, nick_check); +} +END_TEST + +/* this test is excluded for now, due to lack of a way + * to set a friend's status for now. + * ideas: + * if we have access to the friends list, we could + * just add a status manually ourselves. */ +/* +START_TEST(test_m_copy_userstatus) +{ + assert(m_copy_userstatus(-1, buf, MAX_USERSTATUS_LENGTH) == -1); + assert(m_copy_userstatus(REALLY_BIG_NUMBER, buf, MAX_USERSTATUS_LENGTH) == -1); + m_copy_userstatus(friend_id_num, buf, MAX_USERSTATUS_LENGTH + 6); + + assert(STRINGS_EQUAL(name_buf, friend_id_status)); +} +END_TEST +*/ + +/* this test is excluded for now, due to lack of a way + * to set a friend's nickname for now. + * ideas: + * if we have access to the friends list, we could + * just add a name manually ourselves. */ +/* +START_TEST(test_getname) +{ + uint8_t name_buf[MAX_NAME_LENGTH]; + + assert(getname(-1, name_buf) == -1); + assert(getname(REALLY_BIG_NUMBER, name_buf) == -1); + + getname(friend_id_num, name_buf); + assert(name_buf[MAX_NAME_LENGTH] == '\0'); // something like this +} +END_TEST +*/ + +Suite *messenger_suite(void) +{ + Suite *s = suite_create("Messenger"); + + TCase *userstatus_size = tcase_create("userstatus_size"); + TCase *set_userstatus = tcase_create("set_userstatus"); + TCase *send_message = tcase_create("send_message"); + TCase *friendstatus = tcase_create("friendstatus"); + TCase *getself_name = tcase_create("getself_name"); + TCase *delfriend = tcase_create("delfriend"); + TCase *addfriend = tcase_create("addfriend"); + TCase *setname = tcase_create("setname"); + + tcase_add_test(userstatus_size, test_m_get_userstatus_size); + tcase_add_test(set_userstatus, test_m_set_userstatus); + tcase_add_test(friendstatus, test_m_friendstatus); + tcase_add_test(getself_name, test_getself_name); + tcase_add_test(send_message, test_m_sendmesage); + tcase_add_test(delfriend, test_m_delfriend); + tcase_add_test(addfriend, test_m_addfriend); + tcase_add_test(setname, test_setname); + + suite_add_tcase(s, userstatus_size); + suite_add_tcase(s, set_userstatus); + suite_add_tcase(s, friendstatus); + suite_add_tcase(s, send_message); + suite_add_tcase(s, getself_name); + suite_add_tcase(s, delfriend); + suite_add_tcase(s, addfriend); + suite_add_tcase(s, setname); + + return s; +} + +int main(int argc, char *argv[]) +{ + Suite *messenger = messenger_suite(); + SRunner *test_runner = srunner_create(messenger); + int number_failed = 0; + + friend_id = hex_string_to_bin(friend_id_str); + good_id_a = hex_string_to_bin(good_id_a_str); + good_id_b = hex_string_to_bin(good_id_b_str); + bad_id = hex_string_to_bin(bad_id_str); + + /* setup a default friend and friendnum */ + if(m_addfriend_norequest((uint8_t *)friend_id) < 0) + fputs("m_addfriend_norequest() failed on a valid ID!\n" + "this was CRITICAL to the test, and the build WILL fail.\n" + "the tests will continue now...\n\n", stderr); + + if((friend_id_num = getfriend_id((uint8_t *)friend_id)) < 0) + fputs("getfriend_id() failed on a valid ID!\n" + "this was CRITICAL to the test, and the build WILL fail.\n" + "the tests will continue now...\n\n", stderr); + + srunner_run_all(test_runner, CK_NORMAL); + number_failed = srunner_ntests_failed(test_runner); + + srunner_free(test_runner); + free(friend_id); + free(good_id_a); + free(good_id_b); + free(bad_id); + + return number_failed; +} diff --git a/auto_tests/run_tests b/auto_tests/run_tests new file mode 100755 index 00000000..4448b2e2 --- /dev/null +++ b/auto_tests/run_tests @@ -0,0 +1,5 @@ +#!/bin/bash +# run_tests - run the current tests for tox core +set -e + +./messenger_test && ./friends_test diff --git a/cmake/FindCheck.cmake b/cmake/FindCheck.cmake new file mode 100644 index 00000000..e9e7f4d1 --- /dev/null +++ b/cmake/FindCheck.cmake @@ -0,0 +1,46 @@ +# - Try to find the CHECK libraries +# Once done this will define +# +# CHECK_FOUND - system has check +# CHECK_INCLUDE_DIR - the check include directory +# CHECK_LIBRARIES - check library +# +# Copyright (c) 2007 Daniel Gollub +# Copyright (c) 2007 Bjoern Ricks +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +INCLUDE( FindPkgConfig ) + +# Take care about check.pc settings +PKG_SEARCH_MODULE( CHECK check ) + +# Look for CHECK include dir and libraries +IF( NOT CHECK_FOUND ) + + FIND_PATH( CHECK_INCLUDE_DIR check.h ) + + FIND_LIBRARY( CHECK_LIBRARIES NAMES check ) + + IF ( CHECK_INCLUDE_DIR AND CHECK_LIBRARIES ) + SET( CHECK_FOUND 1 ) + IF ( NOT Check_FIND_QUIETLY ) + MESSAGE ( STATUS "Found CHECK: ${CHECK_LIBRARIES}" ) + ENDIF ( NOT Check_FIND_QUIETLY ) + ELSE ( CHECK_INCLUDE_DIR AND CHECK_LIBRARIES ) + IF ( Check_FIND_REQUIRED ) + MESSAGE( FATAL_ERROR "Could NOT find CHECK" ) + ELSE ( Check_FIND_REQUIRED ) + IF ( NOT Check_FIND_QUIETLY ) + MESSAGE( STATUS "Could NOT find CHECK" ) + ENDIF ( NOT Check_FIND_QUIETLY ) + ENDIF ( Check_FIND_REQUIRED ) + ENDIF ( CHECK_INCLUDE_DIR AND CHECK_LIBRARIES ) +ENDIF( NOT CHECK_FOUND ) + +# Hide advanced variables from CMake GUIs +MARK_AS_ADVANCED( CHECK_INCLUDE_DIR CHECK_LIBRARIES ) +