From f1bac0e8becc22f505069ad52dae904eae5e004d Mon Sep 17 00:00:00 2001 From: oehhar Date: Thu, 5 Jun 2014 19:09:51 +0000 Subject: Robust async connect tests by temporarely switching off auto continuation. Ticket [13d3af3ad5] --- tests/socket.test | 15 ++++++++++-- unix/tclUnixSock.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++- win/tclWinSock.c | 38 ++++++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/tests/socket.test b/tests/socket.test index 0c9320a..b006cb4 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -67,10 +67,19 @@ namespace import -force ::tcltest::* testConstraint thread [expr {0 == [catch {package require Thread 2.7-}]}] testConstraint exec [llength [info commands exec]] + # Produce a random port number in the Dynamic/Private range # from 49152 through 65535. proc randport {} { expr {int(rand()*16383+49152)} } +# Check if socket_test option is available +testConstraint sockettest [expr {![catch { + set h [socket -async localhost [randport]] + fconfigure $h -unsupported1 1 + close $h + }]}] + + # Test the latency of tcp connections over the loopback interface. Some OSes # (e.g. NetBSD) seem to use the Nagle algorithm and delayed ACKs, so it takes # up to 200ms for a packet sent to localhost to arrive. We're measuring this @@ -2148,7 +2157,7 @@ test socket-14.10.1 {pending [socket -async] and nonblocking [puts], server is I close $sock } -result {{} ok} test socket-14.11.0 {pending [socket -async] and nonblocking [puts], no listener, no flush} \ - -constraints {socket} \ + -constraints {socket } \ -body { set sock [socket -async localhost [randport]] fconfigure $sock -blocking 0 @@ -2161,12 +2170,14 @@ test socket-14.11.0 {pending [socket -async] and nonblocking [puts], no listener unset x } -result {socket is not connected} -returnCodes 1 test socket-14.11.1 {pending [socket -async] and nonblocking [puts], no listener, flush} \ - -constraints {socket} \ + -constraints {socket sockettest} \ -body { set sock [socket -async localhost [randport]] + fconfigure $sock -unsupported1 1 fconfigure $sock -blocking 0 puts $sock ok flush $sock + fconfigure $sock -unsupported1 0 fileevent $sock writable {set x 1} vwait x close $sock diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index a9323c4..cf5d7b9 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -89,6 +89,9 @@ struct TcpState { * flag indicates that reentry is * still pending */ #define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ +#define TCP_ASYNC_TEST_MODE (1<<6) /* Async testing activated + * Do not automatically continue connection + * process */ /* * The following defines the maximum length of the listen queue. This is the @@ -125,6 +128,9 @@ static int TcpClose2Proc(ClientData instanceData, Tcl_Interp *interp, int flags); static int TcpGetHandleProc(ClientData instanceData, int direction, ClientData *handlePtr); +static int TcpSetOptionProc(ClientData instanceData, + Tcl_Interp *interp, const char *optionName, + const char *value); static int TcpGetOptionProc(ClientData instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); @@ -147,7 +153,7 @@ static const Tcl_ChannelType tcpChannelType = { TcpInputProc, /* Input proc. */ TcpOutputProc, /* Output proc. */ NULL, /* Seek proc. */ - NULL, /* Set option proc. */ + TcpSetOptionProc, /* Set option proc. */ TcpGetOptionProc, /* Get option proc. */ TcpWatchProc, /* Initialize notifier. */ TcpGetHandleProc, /* Get OS handles out of channel. */ @@ -439,6 +445,21 @@ WaitForConnect( if (!(statePtr->flags & TCP_ASYNC_PENDING)) { return 0; } + + /* + * In socket test mode do not continue with the connect + * Exceptions are: + * - Call by recv/send and blocking socket + * (errorCodePtr != NULL && ! flags & TCP_NONBLOCKING) + */ + + if ( (statePtr->flags & TCP_ASYNC_TEST_MODE) + && !(errorCodePtr != NULL && !(statePtr->flags & TCP_NONBLOCKING))) { + if (errorCodePtr != NULL) { + *errorCodePtr = EWOULDBLOCK; + } + return -1; + } if (errorCodePtr == NULL || (statePtr->flags & TCP_NONBLOCKING)) { timeout = 0; @@ -748,6 +769,50 @@ TcpHostPortList( /* *---------------------------------------------------------------------- * + * TcpSetOptionProc -- + * + * Sets Tcp channel specific options. + * + * Results: + * None, unless an error happens. + * + * Side effects: + * Changes attributes of the socket at the system level. + * + *---------------------------------------------------------------------- + */ + +static int +TcpSetOptionProc( + ClientData instanceData, /* Socket state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL. */ + const char *optionName, /* Name of the option to set. */ + const char *value) /* New value for option. */ +{ + TcpState *statePtr = instanceData; + + /* + * Set socket test int value + */ + if (!strcmp(optionName, "-unsupported1")) { + int intValue; + if (Tcl_GetInt(interp, value, &intValue) != TCL_OK) { + return TCL_ERROR; + } + if (intValue & 1) { + SET_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); + } else { + CLEAR_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); + } + return TCL_OK; + } + + return Tcl_BadChannelOption(interp, optionName, ""); +} + +/* + *---------------------------------------------------------------------- + * * TcpGetOptionProc -- * * Computes an option value for a TCP socket based channel, or a list of diff --git a/win/tclWinSock.c b/win/tclWinSock.c index f343f82..2703309 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -191,6 +191,9 @@ struct TcpState { * flag indicates that reentry is * still pending */ #define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ +#define TCP_ASYNC_TEST_MODE (1<<6) /* Async testing activated + * Do not automatically continue connection + * process */ /* * The following structure is what is added to the Tcl event queue when a @@ -602,6 +605,20 @@ WaitForConnect( } /* + * In socket test mode do not continue with the connect + * Exceptions are: + * - Call by recv/send and blocking socket + * (errorCodePtr != NULL && ! flags & TCP_NONBLOCKING) + * - Call by the event queue (errorCodePtr == NULL) + */ + + if ( (statePtr->flags & TCP_ASYNC_TEST_MODE) + && errorCodePtr != NULL && (statePtr->flags & TCP_NONBLOCKING)) { + *errorCodePtr = EWOULDBLOCK; + return -1; + } + + /* * Be sure to disable event servicing so we are truly modal. */ @@ -1123,6 +1140,7 @@ TcpSetOptionProc( const char *optionName, /* Name of the option to set. */ const char *value) /* New value for option. */ { + TcpState *statePtr = instanceData; #ifdef TCL_FEATURE_KEEPALIVE_NAGLE TcpState *statePtr = instanceData; SOCKET sock; @@ -1142,6 +1160,22 @@ TcpSetOptionProc( return TCL_ERROR; } + /* + * Set socket test int value + */ + if (!strcmp(optionName, "-unsupported1")) { + int intValue; + if (Tcl_GetInt(interp, value, &intValue) != TCL_OK) { + return TCL_ERROR; + } + if (intValue & 1) { + SET_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); + } else { + CLEAR_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); + } + return TCL_OK; + } + #ifdef TCL_FEATURE_KEEPALIVE_NAGLE #error "TCL_FEATURE_KEEPALIVE_NAGLE not reviewed for whether to treat statePtr->sockets as single fd or list" sock = statePtr->sockets->fd; @@ -1254,7 +1288,9 @@ TcpGetOptionProc( * Go one step in async connect * If any error is thrown save it as backround error to report eventually below */ - WaitForConnect(statePtr, NULL); + if (! (statePtr->flags & TCP_ASYNC_TEST_MODE) ) { + WaitForConnect(statePtr, NULL); + } sock = statePtr->sockets->fd; if (optionName != NULL) { -- cgit v0.12 From 57fd7d58a12e28ba76f2bafdf441d53fabf47cb0 Mon Sep 17 00:00:00 2001 From: oehhar Date: Thu, 17 Jul 2014 09:53:36 +0000 Subject: Replaced option "-unsupported1" by test command "testsocket debugflags" (thanks Donal, Donald). --- generic/tclTest.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/socket.test | 15 ++++++---- unix/tclUnixSock.c | 67 +++++++++--------------------------------- win/tclWinSock.c | 31 +++++++------------- 4 files changed, 118 insertions(+), 80 deletions(-) diff --git a/generic/tclTest.c b/generic/tclTest.c index a27c95a..80a2a37 100644 --- a/generic/tclTest.c +++ b/generic/tclTest.c @@ -75,6 +75,18 @@ typedef struct TestAsyncHandler { /* Next is list of handlers. */ } TestAsyncHandler; +/* + * Start of the socket driver state structure to acces field testFlags + */ + +typedef struct TcpState TcpState; + +struct TcpState { + Tcl_Channel channel; /* Channel associated with this socket. */ + int testFlags; /* bit field for tests. Is set by testsocket + * test procedure */ +}; + TCL_DECLARE_MUTEX(asyncTestMutex) static TestAsyncHandler *firstHandler = NULL; @@ -362,6 +374,8 @@ static int TestChannelCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char **argv); static int TestChannelEventCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char **argv); +static int TestSocketCmd(ClientData clientData, + Tcl_Interp *interp, int argc, const char **argv); static int TestFilesystemObjCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); @@ -666,6 +680,8 @@ Tcltest_Init( TestNumUtfCharsCmd, NULL, NULL); Tcl_CreateCommand(interp, "testsetplatform", TestsetplatformCmd, NULL, NULL); + Tcl_CreateCommand(interp, "testsocket", TestSocketCmd, + NULL, NULL); Tcl_CreateCommand(interp, "teststaticpkg", TeststaticpkgCmd, NULL, NULL); Tcl_CreateCommand(interp, "testtranslatefilename", @@ -5970,6 +5986,75 @@ TestChannelEventCmd( /* *---------------------------------------------------------------------- * + * TestSocketCmd -- + * + * Implements the Tcl "testsocket" debugging command and its + * subcommands. This is part of the testing environment. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + + /* ARGSUSED */ +static int +TestSocketCmd( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter for result. */ + int argc, /* Count of additional args. */ + const char **argv) /* Additional arg strings. */ +{ + const char *cmdName; /* Sub command. */ + size_t len; /* Length of subcommand string. */ + + if (argc < 2) { + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " subcommand ?additional args..?\"", NULL); + return TCL_ERROR; + } + cmdName = argv[1]; + len = strlen(cmdName); + + if ((cmdName[0] == 't') && (strncmp(cmdName, "testflags", len) == 0)) { + Tcl_Channel hChannel; + int modePtr; + TcpState *statePtr; + /* Set test value in the socket driver + */ + /* Check for argument "channel name" + */ + if (argc < 4) { + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " testflags channel flags\"", NULL); + return TCL_ERROR; + } + hChannel = Tcl_GetChannel(interp, argv[2], &modePtr); + if ( NULL == hChannel ) { + Tcl_AppendResult(interp, "unknown channel:", argv[2], NULL); + return TCL_ERROR; + } + statePtr = (TcpState *)Tcl_GetChannelInstanceData(hChannel); + if ( NULL == statePtr) { + Tcl_AppendResult(interp, "No channel instance data:", argv[2], + NULL); + return TCL_ERROR; + } + statePtr->testFlags = atoi(argv[3]); + return TCL_OK; + } + + Tcl_AppendResult(interp, "bad option \"", cmdName, "\": should be " + "testflags", NULL); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * * TestWrongNumArgsObjCmd -- * * Test the Tcl_WrongNumArgs function. diff --git a/tests/socket.test b/tests/socket.test index b006cb4..839e9d2 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -72,10 +72,10 @@ testConstraint exec [llength [info commands exec]] # from 49152 through 65535. proc randport {} { expr {int(rand()*16383+49152)} } -# Check if socket_test option is available -testConstraint sockettest [expr {![catch { +# Check if testsocket testflags is available +testConstraint testsocket_testflags [expr {![catch { set h [socket -async localhost [randport]] - fconfigure $h -unsupported1 1 + testsocket testflags $h 0 close $h }]}] @@ -2170,14 +2170,17 @@ test socket-14.11.0 {pending [socket -async] and nonblocking [puts], no listener unset x } -result {socket is not connected} -returnCodes 1 test socket-14.11.1 {pending [socket -async] and nonblocking [puts], no listener, flush} \ - -constraints {socket sockettest} \ + -constraints {socket testsocket_testflags} \ -body { set sock [socket -async localhost [randport]] - fconfigure $sock -unsupported1 1 + # Set the socket in async test mode. + # The async connect will not be continued on the following fconfigure + # and puts/flush. Thus, the connect will fail after them. + testsocket testflags $sock 1 fconfigure $sock -blocking 0 puts $sock ok flush $sock - fconfigure $sock -unsupported1 0 + testsocket testflags $sock 0 fileevent $sock writable {set x 1} vwait x close $sock diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index cf5d7b9..fdd4287 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -52,6 +52,8 @@ typedef struct TcpFdList { struct TcpState { Tcl_Channel channel; /* Channel associated with this file. */ + int testFlags; /* bit field for tests. Is set by testsocket + * test procedure */ TcpFdList fds; /* The file descriptors of the sockets. */ int flags; /* ORed combination of the bitfields defined * below. */ @@ -89,7 +91,13 @@ struct TcpState { * flag indicates that reentry is * still pending */ #define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ -#define TCP_ASYNC_TEST_MODE (1<<6) /* Async testing activated + +/* + * These bits may be ORed together into the "testFlags" field of a TcpState + * structure. + */ + +#define TCP_ASYNC_TEST_MODE (1<<0) /* Async testing activated * Do not automatically continue connection * process */ @@ -128,9 +136,6 @@ static int TcpClose2Proc(ClientData instanceData, Tcl_Interp *interp, int flags); static int TcpGetHandleProc(ClientData instanceData, int direction, ClientData *handlePtr); -static int TcpSetOptionProc(ClientData instanceData, - Tcl_Interp *interp, const char *optionName, - const char *value); static int TcpGetOptionProc(ClientData instanceData, Tcl_Interp *interp, const char *optionName, Tcl_DString *dsPtr); @@ -153,7 +158,7 @@ static const Tcl_ChannelType tcpChannelType = { TcpInputProc, /* Input proc. */ TcpOutputProc, /* Output proc. */ NULL, /* Seek proc. */ - TcpSetOptionProc, /* Set option proc. */ + NULL, /* Set option proc. */ TcpGetOptionProc, /* Get option proc. */ TcpWatchProc, /* Initialize notifier. */ TcpGetHandleProc, /* Get OS handles out of channel. */ @@ -453,11 +458,9 @@ WaitForConnect( * (errorCodePtr != NULL && ! flags & TCP_NONBLOCKING) */ - if ( (statePtr->flags & TCP_ASYNC_TEST_MODE) - && !(errorCodePtr != NULL && !(statePtr->flags & TCP_NONBLOCKING))) { - if (errorCodePtr != NULL) { - *errorCodePtr = EWOULDBLOCK; - } + if ( (statePtr->testFlags & TCP_ASYNC_TEST_MODE) + && ! (errorCodePtr != NULL && ! (statePtr->flags & TCP_NONBLOCKING))) { + *errorCodePtr = EWOULDBLOCK; return -1; } @@ -769,50 +772,6 @@ TcpHostPortList( /* *---------------------------------------------------------------------- * - * TcpSetOptionProc -- - * - * Sets Tcp channel specific options. - * - * Results: - * None, unless an error happens. - * - * Side effects: - * Changes attributes of the socket at the system level. - * - *---------------------------------------------------------------------- - */ - -static int -TcpSetOptionProc( - ClientData instanceData, /* Socket state. */ - Tcl_Interp *interp, /* For error reporting - can be NULL. */ - const char *optionName, /* Name of the option to set. */ - const char *value) /* New value for option. */ -{ - TcpState *statePtr = instanceData; - - /* - * Set socket test int value - */ - if (!strcmp(optionName, "-unsupported1")) { - int intValue; - if (Tcl_GetInt(interp, value, &intValue) != TCL_OK) { - return TCL_ERROR; - } - if (intValue & 1) { - SET_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); - } else { - CLEAR_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); - } - return TCL_OK; - } - - return Tcl_BadChannelOption(interp, optionName, ""); -} - -/* - *---------------------------------------------------------------------- - * * TcpGetOptionProc -- * * Computes an option value for a TCP socket based channel, or a list of diff --git a/win/tclWinSock.c b/win/tclWinSock.c index 2703309..d6c8e3a 100644 --- a/win/tclWinSock.c +++ b/win/tclWinSock.c @@ -133,6 +133,8 @@ typedef struct TcpFdList { struct TcpState { Tcl_Channel channel; /* Channel associated with this socket. */ + int testFlags; /* bit field for tests. Is set by testsocket + * test procedure */ struct TcpFdList *sockets; /* Windows SOCKET handle. */ int flags; /* Bit field comprised of the flags described * below. */ @@ -191,7 +193,13 @@ struct TcpState { * flag indicates that reentry is * still pending */ #define TCP_ASYNC_FAILED (1<<5) /* An async connect finally failed */ -#define TCP_ASYNC_TEST_MODE (1<<6) /* Async testing activated + +/* + * These bits may be ORed together into the "testFlags" field of a TcpState + * structure. + */ + +#define TCP_ASYNC_TEST_MODE (1<<0) /* Async testing activated * Do not automatically continue connection * process */ @@ -612,7 +620,7 @@ WaitForConnect( * - Call by the event queue (errorCodePtr == NULL) */ - if ( (statePtr->flags & TCP_ASYNC_TEST_MODE) + if ( (statePtr->testFlags & TCP_ASYNC_TEST_MODE) && errorCodePtr != NULL && (statePtr->flags & TCP_NONBLOCKING)) { *errorCodePtr = EWOULDBLOCK; return -1; @@ -1140,7 +1148,6 @@ TcpSetOptionProc( const char *optionName, /* Name of the option to set. */ const char *value) /* New value for option. */ { - TcpState *statePtr = instanceData; #ifdef TCL_FEATURE_KEEPALIVE_NAGLE TcpState *statePtr = instanceData; SOCKET sock; @@ -1160,22 +1167,6 @@ TcpSetOptionProc( return TCL_ERROR; } - /* - * Set socket test int value - */ - if (!strcmp(optionName, "-unsupported1")) { - int intValue; - if (Tcl_GetInt(interp, value, &intValue) != TCL_OK) { - return TCL_ERROR; - } - if (intValue & 1) { - SET_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); - } else { - CLEAR_BITS(statePtr->flags,TCP_ASYNC_TEST_MODE); - } - return TCL_OK; - } - #ifdef TCL_FEATURE_KEEPALIVE_NAGLE #error "TCL_FEATURE_KEEPALIVE_NAGLE not reviewed for whether to treat statePtr->sockets as single fd or list" sock = statePtr->sockets->fd; @@ -1288,7 +1279,7 @@ TcpGetOptionProc( * Go one step in async connect * If any error is thrown save it as backround error to report eventually below */ - if (! (statePtr->flags & TCP_ASYNC_TEST_MODE) ) { + if (! (statePtr->testFlags & TCP_ASYNC_TEST_MODE) ) { WaitForConnect(statePtr, NULL); } -- cgit v0.12 From 1990c76e8d05fcf48cccabb5d2f1a49c3c99ecd1 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 18 Jul 2014 10:00:52 +0000 Subject: Make sure the "sockettest" command is available even when running socket.test individually. --- tests/socket.test | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/socket.test b/tests/socket.test index 8ffd86b..29f1015 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -60,8 +60,13 @@ # listening at port 2048. If all fails, a message is printed and the tests # using the remote server are not performed. -package require tcltest 2 -namespace import -force ::tcltest::* +if {[lsearch [namespace children] ::tcltest] == -1} { + package require tcltest + namespace import -force ::tcltest::* +} + +::tcltest::loadTestedCommands +catch [list package require -exact Tcltest [info patchlevel]] # Some tests require the Thread package or exec command testConstraint thread [expr {0 == [catch {package require Thread 2.7-}]}] -- cgit v0.12