diff --git a/configure.ac b/configure.ac index 2ff58266..72d736b3 100644 --- a/configure.ac +++ b/configure.ac @@ -125,7 +125,7 @@ AC_CHECK_DECLS([__CYGWIN__]) AC_SEARCH_LIBS(accept, network socket) # Checks for library functions. -AC_CHECK_FUNCS([accept4 gai_strerror getaddrinfo gettimeofday select socket strerror strlcpy]) +AC_CHECK_FUNCS([accept4 gai_strerror getaddrinfo gettimeofday select socket strerror strlcpy setvbuf]) # Required for MinGW with GCC v4.8.1 on Win7 AC_DEFINE(WINVER, 0x0501, _) diff --git a/src/modbus.c b/src/modbus.c index af3e9781..2517efd7 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -427,6 +427,8 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) wsa_err = WSAGetLastError(); // no equivalent to ETIMEDOUT when select fails on Windows + // but we still need it to forget the invalid answer + modbus_flush(ctx); if (wsa_err == WSAENETDOWN || wsa_err == WSAENOTSOCK) { modbus_close(ctx); modbus_connect(ctx); diff --git a/tests/unit-test-client.c b/tests/unit-test-client.c index b3ac76fa..527940c9 100644 --- a/tests/unit-test-client.c +++ b/tests/unit-test-client.c @@ -98,6 +98,16 @@ int main(int argc, char *argv[]) memset(last_test_title, 0, sizeof(last_test_title)); +#ifdef _WIN32 +# ifdef HAVE_SETVBUF + // auto-flush so logs are comprehensible when the + // same console collects output of server and client + // https://stackoverflow.com/a/214292/4715872 + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); +# endif +#endif + if (argc > 1) { if (strcmp(argv[1], "tcp") == 0) { use_backend = TCP; @@ -288,6 +298,10 @@ int main(int argc, char *argv[]) : UT_INPUT_REGISTERS_NB; memset(tab_rp_registers, 0, nb_points * sizeof(uint16_t)); + /* Wait remaining bytes before flushing */ + usleep(1000000); + modbus_flush(ctx); + TEST_TITLE("4/5 modbus_write_and_read_registers"); /* Write registers to zero from tab_rp_registers and store read registers into tab_rp_registers. So the read registers must set to 0, except the @@ -721,13 +735,46 @@ int main(int argc, char *argv[]) /* Wait remaining bytes before flushing */ usleep(11 * 5000); modbus_flush(ctx); + /* ...and then some, just in case (e.g. kernel delays, clock jitter, multi-CPU...) */ + usleep(1000000); + modbus_flush(ctx); /* Timeout of 7ms between bytes */ - TEST_TITLE("2/2 Adapted byte timeout (7ms > 5ms)"); + TEST_TITLE("2/2-A Adapted byte timeout (7ms > 5ms)"); modbus_set_byte_timeout(ctx, 0, 7000); rc = modbus_read_registers( ctx, UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS, 1, tab_rp_registers); - ASSERT_TRUE(rc == 1, "FAILED (rc: %d != 1)", rc); + if (rc == 1) { + ASSERT_TRUE(rc == 1, "FAILED (rc: %d != 1)", rc); + } else { + /* Timeout of 20ms between bytes, allow for 2*16+1 + * Windows sleep seems to be at least 15ms always. + */ + usleep(1000000); + modbus_flush(ctx); + TEST_TITLE("2/2-B Adapted byte timeout (33ms > 20ms)"); + modbus_set_byte_timeout(ctx, 0, 33000); + rc = modbus_read_registers( + ctx, UT_REGISTERS_ADDRESS_BYTE_SLEEP_20_MS, 1, tab_rp_registers); + + if (rc == 1) { + ASSERT_TRUE(rc == 1, "FAILED (rc: %d != 1)", rc); + } else { + /* For some reason, FreeBSD 12 and OpenBSD 6.5 also + * tended to fail with 7ms and even 33ms variants + * as "gmake check", but passed in + * gmake -j 8 && ( ./tests/unit-test-server|cat & sleep 1 ; ./tests/unit-test-client|cat ) + * An even longer timeout seems to satisfy all of them. + */ + usleep(1000000); + modbus_flush(ctx); + TEST_TITLE("2/2-C Adapted byte timeout (66ms > 20ms)"); + modbus_set_byte_timeout(ctx, 0, 66000); + rc = modbus_read_registers( + ctx, UT_REGISTERS_ADDRESS_BYTE_SLEEP_20_MS, 1, tab_rp_registers); + ASSERT_TRUE(rc == 1, "FAILED (rc: %d != 1)", rc); + } + } } /* Restore original byte timeout */ diff --git a/tests/unit-test-server.c b/tests/unit-test-server.c index ea4455f1..1aab0d43 100644 --- a/tests/unit-test-server.c +++ b/tests/unit-test-server.c @@ -44,6 +44,16 @@ int main(int argc, char *argv[]) int header_length; char *ip_or_device = NULL; +#ifdef _WIN32 +# ifdef HAVE_SETVBUF + // auto-flush so logs are comprehensible when the + // same console collects output of server and client + // https://stackoverflow.com/a/214292/4715872 + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); +# endif +#endif + if (argc > 1) { if (strcmp(argv[1], "tcp") == 0) { use_backend = TCP; @@ -169,6 +179,9 @@ int main(int argc, char *argv[]) for (;;) { do { +#ifdef _WIN32 + fflush(stdout); +#endif rc = modbus_receive(ctx, query); /* Filtered queries return 0 */ } while (rc == 0); @@ -208,7 +221,7 @@ int main(int argc, char *argv[]) } else if (address == UT_REGISTERS_ADDRESS_SLEEP_500_MS) { printf("Sleep 0.5 s before replying\n"); usleep(500000); - } else if (address == UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS) { + } else if (address == UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS || address == UT_REGISTERS_ADDRESS_BYTE_SLEEP_20_MS) { /* Test low level only available in TCP mode */ /* Catch the reply and send reply byte a byte */ uint8_t req[] = "\x00\x1C\x00\x00\x00\x05\xFF\x03\x02\x00\x00"; @@ -223,7 +236,11 @@ int main(int argc, char *argv[]) req[1] = query[1]; for (i = 0; i < req_length; i++) { printf("(%.2X)", req[i]); - usleep(5000); + if (address == UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS) { + usleep(5000); + } else if (address == UT_REGISTERS_ADDRESS_BYTE_SLEEP_20_MS) { + usleep(20000); + } rc = send(w_s, (const char *) (req + i), 1, MSG_NOSIGNAL); if (rc == -1) { break; @@ -250,6 +267,9 @@ int main(int argc, char *argv[]) } } +#ifdef _WIN32 + fflush(stdout); +#endif rc = modbus_reply(ctx, query, rc, mb_mapping); if (rc == -1) { break; diff --git a/tests/unit-test.h.in b/tests/unit-test.h.in index 3d83967a..d6427c59 100644 --- a/tests/unit-test.h.in +++ b/tests/unit-test.h.in @@ -54,8 +54,11 @@ const uint16_t UT_REGISTERS_ADDRESS_SPECIAL = 0x170; const uint16_t UT_REGISTERS_ADDRESS_INVALID_TID_OR_SLAVE = 0x171; /* The server will wait for 1 second before replying to test timeout */ const uint16_t UT_REGISTERS_ADDRESS_SLEEP_500_MS = 0x172; -/* The server will wait for 5 ms before sending each byte */ +/* The server will wait for 5 ms before sending each byte + * WARNING: this may be too short for WIN32 */ const uint16_t UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS = 0x173; +/* The server will wait for 20 ms before sending each byte */ +const uint16_t UT_REGISTERS_ADDRESS_BYTE_SLEEP_20_MS = 0x174; /* If the following value is used, a bad response is sent. It's better to test with a lower value than