diff --git a/.travis.yml b/.travis.yml index 7e20794..727d112 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: c -sudo: false compiler: - gcc - clang @@ -62,9 +61,9 @@ script: - mkdir build/ && cd build/ - cmake .. ${EXTRA_CMAKE_OPTS} - make VERBOSE=1 - - ctest -V + - SKIPS_AS_FAILS=1 ctest -V -matrix: +jobs: include: # Windows MinGW cross compile on Linux - os: linux @@ -90,8 +89,12 @@ matrix: - eval "${MATRIX_EVAL}" install: - choco install ninja + - choco install redis-64 -y + before_script: + - redis-server --service-install --service-name Redis --port 6379 + - redis-server --service-start script: - mkdir build && cd build - cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&' cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release '&&' ninja -v - - ctest -V + - ./hiredis-test.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 564c138..48e5920 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,17 +33,10 @@ SET(hiredis_sources sockcompat.c alloc.c) -IF(WIN32) - SET(hiredis_sources - ${hiredis_sources} - hiredis.def - ) -ENDIF() - ADD_LIBRARY(hiredis SHARED ${hiredis_sources}) SET_TARGET_PROPERTIES(hiredis - PROPERTIES + PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE VERSION "${HIREDIS_SONAME}") IF(WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) @@ -87,7 +80,7 @@ IF(ENABLE_SSL) DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) ENDIF() -IF(NOT (DISABLE_TESTS OR (WIN32 OR MINGW))) +IF(NOT DISABLE_TESTS) ENABLE_TESTING() ADD_EXECUTABLE(hiredis-test test.c) TARGET_LINK_LIBRARIES(hiredis-test hiredis) diff --git a/hiredis.def b/hiredis.def deleted file mode 100644 index 7e7dbb4..0000000 --- a/hiredis.def +++ /dev/null @@ -1,38 +0,0 @@ -EXPORTS - redisAppendCommand - redisAppendCommandArgv - redisAppendFormattedCommand - redisBufferRead - redisBufferWrite - redisCommand - redisCommand - redisCommandArgv - redisConnect - redisConnectBindNonBlock - redisConnectBindNonBlockWithReuse - redisConnectFd - redisConnectNonBlock - redisConnectUnix - redisConnectUnixNonBlock - redisConnectUnixWithTimeout - redisConnectWithOptions - redisConnectWithTimeout - redisEnableKeepAlive - redisFormatCommand - redisFormatCommandArgv - redisFormatSdsCommandArgv - redisFree - redisFreeCommand - redisFreeKeepFd - redisFreeSdsCommand - redisGetReply - redisGetReplyFromReader - redisReaderCreate - redisReconnect - redisSetTimeout - redisvAppendCommand - redisvCommand - redisvFormatCommand - freeReplyObject - hi_calloc - hi_malloc diff --git a/net.c b/net.c index c928b33..bdb8943 100644 --- a/net.c +++ b/net.c @@ -316,11 +316,7 @@ int redisCheckSocketError(redisContext *c) { int redisContextSetTimeout(redisContext *c, const struct timeval tv) { const void *to_ptr = &tv; size_t to_sz = sizeof(tv); -#ifdef _WIN32 - DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; - to_ptr = &timeout_msec; - to_sz = sizeof(timeout_msec); -#endif + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); return REDIS_ERR; diff --git a/test.c b/test.c index 9c0de6d..c9e23fe 100644 --- a/test.c +++ b/test.c @@ -1,13 +1,13 @@ #include "fmacros.h" +#include "sockcompat.h" #include #include #include +#ifndef _WIN32 #include -#include #include -#include +#endif #include -#include #include #include #include @@ -17,6 +17,7 @@ #include "hiredis_ssl.h" #endif #include "net.h" +#include "win32.h" enum connection_type { CONN_TCP, @@ -48,14 +49,21 @@ struct config { }; /* The following lines make up our testing "framework" :) */ -static int tests = 0, fails = 0; +static int tests = 0, fails = 0, skips = 0; #define test(_s) { printf("#%02d ", ++tests); printf(_s); } #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} +#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; } static long long usec(void) { +#ifndef _MSC_VER struct timeval tv; gettimeofday(&tv,NULL); return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; +#else + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10; +#endif } /* The assert() calls below have side effects, so we need assert() @@ -491,19 +499,19 @@ static void test_blocking_connection_errors(void) { (strcmp(c->errstr, "Name or service not known") == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || strcmp(c->errstr, "Name does not resolve") == 0 || - strcmp(c->errstr, - "nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "Temporary failure in name resolution") == 0 || - strcmp(c->errstr, - "hostname nor servname provided, or not known") == 0 || - strcmp(c->errstr, "no address associated with name") == 0)); + strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 || + strcmp(c->errstr, "no address associated with name") == 0 || + strcmp(c->errstr, "No such host is known. ") == 0)); redisFree(c); } else { printf("Skipping NXDOMAIN test. Found evil ISP!\n"); freeaddrinfo(ai_tmp); } +#ifndef _WIN32 test("Returns error when the port is not open: "); c = redisConnect((char*)"localhost", 1); test_cond(c->err == REDIS_ERR_IO && @@ -514,6 +522,7 @@ static void test_blocking_connection_errors(void) { c = redisConnectUnix((char*)"/tmp/idontexist.sock"); test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ redisFree(c); +#endif } static void test_blocking_connection(struct config config) { @@ -599,11 +608,28 @@ static void test_blocking_connection(struct config config) { disconnect(c, 0); } +/* Send DEBUG SLEEP 0 to detect if we have this command */ +static int detect_debug_sleep(redisContext *c) { + int detected; + redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n"); + + if (reply == NULL || c->err) { + const char *cause = c->err ? c->errstr : "(none)"; + fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause); + exit(-1); + } + + detected = reply->type == REDIS_REPLY_STATUS; + freeReplyObject(reply); + + return detected; +} + static void test_blocking_connection_timeouts(struct config config) { redisContext *c; redisReply *reply; ssize_t s; - const char *cmd = "DEBUG SLEEP 3\r\n"; + const char *sleep_cmd = "DEBUG SLEEP 3\r\n"; struct timeval tv; c = do_connect(config); @@ -620,14 +646,24 @@ static void test_blocking_connection_timeouts(struct config config) { c = do_connect(config); test("Does not return a reply when the command times out: "); - redisAppendFormattedCommand(c, cmd, strlen(cmd)); - s = c->funcs->write(c); - tv.tv_sec = 0; - tv.tv_usec = 10000; - redisSetTimeout(c, tv); - reply = redisCommand(c, "GET foo"); - test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); - freeReplyObject(reply); + if (detect_debug_sleep(c)) { + redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd)); + s = c->funcs->write(c); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); +#ifndef _WIN32 + test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && + strcmp(c->errstr, "Resource temporarily unavailable") == 0); +#else + test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT && + strcmp(c->errstr, "recv timeout") == 0); +#endif + freeReplyObject(reply); + } else { + test_skipped(); + } test("Reconnect properly reconnects after a timeout: "); do_reconnect(c, config); @@ -679,6 +715,7 @@ static void test_blocking_io_errors(struct config config) { test_cond(reply == NULL); } +#ifndef _WIN32 /* On 2.0, QUIT will cause the connection to be closed immediately and * the read(2) for the reply on QUIT will set the error to EOF. * On >2.0, QUIT will return with OK and another read(2) needed to be @@ -686,14 +723,19 @@ static void test_blocking_io_errors(struct config config) { * conditions, the error will be set to EOF. */ assert(c->err == REDIS_ERR_EOF && strcmp(c->errstr,"Server closed the connection") == 0); +#endif redisFree(c); c = do_connect(config); test("Returns I/O error on socket timeout: "); struct timeval tv = { 0, 1000 }; assert(redisSetTimeout(c,tv) == REDIS_OK); - test_cond(redisGetReply(c,&_reply) == REDIS_ERR && - c->err == REDIS_ERR_IO && errno == EAGAIN); + int respcode = redisGetReply(c,&_reply); +#ifndef _WIN32 + test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN); +#else + test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT); +#endif redisFree(c); } @@ -921,9 +963,8 @@ int main(int argc, char **argv) { }; int throughput = 1; int test_inherit_fd = 1; - - /* Ignore broken pipe signal (for I/O error tests). */ - signal(SIGPIPE, SIG_IGN); + int skips_as_fails = 0; + int test_unix_socket; /* Parse command line options. */ argv++; argc--; @@ -941,6 +982,8 @@ int main(int argc, char **argv) { throughput = 0; } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { test_inherit_fd = 0; + } else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) { + skips_as_fails = 1; #ifdef HIREDIS_TEST_SSL } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) { argv++; argc--; @@ -965,6 +1008,16 @@ int main(int argc, char **argv) { argv++; argc--; } +#ifndef _WIN32 + /* Ignore broken pipe signal (for I/O error tests). */ + signal(SIGPIPE, SIG_IGN); + + test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0; +#else + /* Unix sockets don't exist in Windows */ + test_unix_socket = 0; +#endif + test_format_commands(); test_reply_reader(); test_blocking_connection_errors(); @@ -979,12 +1032,17 @@ int main(int argc, char **argv) { test_append_formatted_commands(cfg); if (throughput) test_throughput(cfg); - printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path); - cfg.type = CONN_UNIX; - test_blocking_connection(cfg); - test_blocking_connection_timeouts(cfg); - test_blocking_io_errors(cfg); - if (throughput) test_throughput(cfg); + printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path); + if (test_unix_socket) { + printf("\n"); + cfg.type = CONN_UNIX; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + } else { + test_skipped(); + } #ifdef HIREDIS_TEST_SSL if (cfg.ssl.port && cfg.ssl.host) { @@ -1001,17 +1059,24 @@ int main(int argc, char **argv) { #endif if (test_inherit_fd) { - printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); - cfg.type = CONN_FD; - test_blocking_connection(cfg); + printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); + if (test_unix_socket) { + printf("\n"); + cfg.type = CONN_FD; + test_blocking_connection(cfg); + } else { + test_skipped(); + } } - - if (fails) { + if (fails || (skips_as_fails && skips)) { printf("*** %d TESTS FAILED ***\n", fails); + if (skips) { + printf("*** %d TESTS SKIPPED ***\n", skips); + } return 1; } - printf("ALL TESTS PASSED\n"); + printf("ALL TESTS PASSED (%d skipped)\n", skips); return 0; } diff --git a/test.sh b/test.sh index 2cab9e6..c72bcb0 100755 --- a/test.sh +++ b/test.sh @@ -4,7 +4,9 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server} REDIS_PORT=${REDIS_PORT:-56379} REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} TEST_SSL=${TEST_SSL:-0} +SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0} SSL_TEST_ARGS= +SKIPS_ARG= tmpdir=$(mktemp -d) PID_FILE=${tmpdir}/hiredis-test-redis.pid @@ -67,4 +69,10 @@ fi cat ${tmpdir}/redis.conf ${REDIS_SERVER} ${tmpdir}/redis.conf -${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} +# Wait until we detect the unix socket +while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done + +# Treat skips as failures if directed +[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails" + +${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}