Compare commits

...

9 Commits

Author SHA1 Message Date
1ac02fdc0e more tests disabled 2019-05-13 12:37:27 -07:00
687956358d disable IXWebSocketPingTimeoutTest 2019-05-13 12:26:40 -07:00
1a42c92325 server code / add dedicated thread to close/join terminated connection threads 2019-05-13 12:20:03 -07:00
6bb00b6788 close with params 2019-05-13 09:33:14 -07:00
12f6cd878d save timepoints after connect and not in contructor, adjusted tests (#72)
* save timepoints after connect and not in contructor, adjusted tests

* move call into setReadyState

* more time to detect client close in test
2019-05-13 09:08:46 -07:00
9aacebbbaf fix for Windows (#69)
* fix for Windows

* fix condition

* make condition only on Windows
2019-05-12 22:21:56 -07:00
701c3745c2 Fix run.py (#71)
* fix run.py

* run.py: fix Windows support

* fix test listing
2019-05-12 18:37:22 -07:00
156288b17b all derived class use final keyword 2019-05-12 11:43:21 -07:00
ed0e23e8a5 bump version to 2.0.0 2019-05-11 14:22:41 -07:00
22 changed files with 584 additions and 115 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
build
*.pyc

View File

@ -1 +1 @@
1.5.3
2.0.0

View File

@ -1 +1 @@
docker/Dockerfile.fedora
docker/Dockerfile.ubuntu_xenial

View File

@ -13,7 +13,7 @@
namespace ix
{
class SelectInterruptEventFd : public SelectInterrupt {
class SelectInterruptEventFd final : public SelectInterrupt {
public:
SelectInterruptEventFd();
virtual ~SelectInterruptEventFd();

View File

@ -14,7 +14,7 @@
namespace ix
{
class SelectInterruptPipe : public SelectInterrupt {
class SelectInterruptPipe final : public SelectInterrupt {
public:
SelectInterruptPipe();
virtual ~SelectInterruptPipe();

View File

@ -73,7 +73,7 @@ namespace ix
struct timeval timeout;
timeout.tv_sec = timeoutMs / 1000;
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
timeout.tv_usec = 1000 * (timeoutMs % 1000);
// Compute the highest fd.
int sockfd = _sockfd;

View File

@ -16,7 +16,7 @@
namespace ix
{
class SocketAppleSSL : public Socket
class SocketAppleSSL final : public Socket
{
public:
SocketAppleSSL(int fd = -1);

View File

@ -19,7 +19,7 @@
namespace ix
{
class SocketOpenSSL : public Socket
class SocketOpenSSL final : public Socket
{
public:
SocketOpenSSL(int fd = -1);

View File

@ -10,7 +10,7 @@
namespace ix
{
class SocketSChannel : public Socket
class SocketSChannel final : public Socket
{
public:
SocketSChannel();

View File

@ -30,7 +30,9 @@ namespace ix
_host(host),
_backlog(backlog),
_maxConnections(maxConnections),
_serverFd(-1),
_stop(false),
_stopGc(false),
_connectionStateFactory(&ConnectionState::createConnectionState)
{
@ -124,9 +126,15 @@ namespace ix
void SocketServer::start()
{
if (_thread.joinable()) return; // we've already been started
if (!_thread.joinable())
{
_thread = std::thread(&SocketServer::run, this);
}
_thread = std::thread(&SocketServer::run, this);
if (!_gcThread.joinable())
{
_gcThread = std::thread(&SocketServer::runGC, this);
}
}
void SocketServer::wait()
@ -142,21 +150,21 @@ namespace ix
void SocketServer::stop()
{
while (true)
// Stop accepting connections, and close the 'accept' thread
if (_thread.joinable())
{
if (closeTerminatedThreads()) break;
// wait 10ms and try again later.
// we could have a timeout, but if we exit of here
// we leaked threads, it is quite bad.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
_stop = true;
_thread.join();
_stop = false;
}
if (!_thread.joinable()) return; // nothing to do
_stop = true;
_thread.join();
_stop = false;
// Join all threads and make sure that all connections are terminated
if (_gcThread.joinable())
{
_stopGc = true;
_gcThread.join();
_stopGc = false;
}
_conditionVariable.notify_one();
Socket::closeSocket(_serverFd);
@ -175,7 +183,7 @@ namespace ix
// field becomes true, and we can use that to know that we can join that thread
// and remove it from our _connectionsThreads data structure (a list).
//
bool SocketServer::closeTerminatedThreads()
void SocketServer::closeTerminatedThreads()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
auto it = _connectionsThreads.begin();
@ -195,8 +203,6 @@ namespace ix
if (thread.joinable()) thread.join();
it = _connectionsThreads.erase(it);
}
return _connectionsThreads.empty();
}
void SocketServer::run()
@ -208,12 +214,6 @@ namespace ix
{
if (_stop) return;
// Garbage collection to shutdown/join threads for closed connections.
// We could run this in its own thread, so that we dont need to accept
// a new connection to close a thread.
// We could also use a condition variable to be notify when we need to do this
closeTerminatedThreads();
// Use select to check whether a new connection is in progress
fd_set rfds;
struct timeval timeout;
@ -290,5 +290,30 @@ namespace ix
connectionState)));
}
}
size_t SocketServer::getConnectionsThreadsCount()
{
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
return _connectionsThreads.size();
}
void SocketServer::runGC()
{
for (;;)
{
// Garbage collection to shutdown/join threads for closed connections.
closeTerminatedThreads();
// We quit this thread if all connections are closed and we received
// a stop request by setting _stopGc to true.
if (_stopGc && getConnectionsThreadsCount() == 0)
{
break;
}
// Sleep a little bit then keep cleaning up
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}

View File

@ -74,6 +74,12 @@ namespace ix
// background thread to wait for incoming connections
std::atomic<bool> _stop;
std::thread _thread;
void run();
// background thread to cleanup (join) terminated threads
std::atomic<bool> _stopGc;
std::thread _gcThread;
void runGC();
// the list of (connectionState, threads) for each connections
ConnectionThreads _connectionsThreads;
@ -87,13 +93,12 @@ namespace ix
// the factory to create ConnectionState objects
ConnectionStateFactory _connectionStateFactory;
// Methods
void run();
virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) = 0;
virtual size_t getConnectedClientsCount() = 0;
// Returns true if all connection threads are joined
bool closeTerminatedThreads();
void closeTerminatedThreads();
size_t getConnectionsThreadsCount();
};
}

View File

@ -212,9 +212,10 @@ namespace ix
return getReadyState() == ReadyState::Closing;
}
void WebSocket::close()
void WebSocket::close(uint16_t code,
const std::string& reason)
{
_ws.close();
_ws.close(code, reason);
}
void WebSocket::checkConnection(bool firstConnectionAttempt)
@ -223,7 +224,6 @@ namespace ix
uint32_t retries = 0;
millis duration;
ix::WebSocketInitResult status;
// Try to connect perpertually
while (true)
@ -249,7 +249,7 @@ namespace ix
}
// Try to connect synchronously
status = connect(_handshakeTimeoutSecs);
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
if (!status.success)
{

View File

@ -112,7 +112,11 @@ namespace ix
WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text);
void close();
// A close frame can provide a code and a reason
// FIXME: use constants
void close(uint16_t code = 1000,
const std::string& reason = "Normal closure");
void setOnMessageCallback(const OnMessageCallback& callback);
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);

View File

@ -23,7 +23,7 @@ namespace ix
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
std::shared_ptr<ConnectionState>)>;
class WebSocketServer : public SocketServer {
class WebSocketServer final : public SocketServer {
public:
WebSocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,

View File

@ -134,11 +134,6 @@ namespace ix
{
_pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
}
if (_pingIntervalOrTimeoutGCDSecs > 0)
{
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
}
}
// Client
@ -225,6 +220,10 @@ namespace ix
_closeWireSize = 0;
_closeRemote = false;
}
else if (readyState == ReadyState::OPEN)
{
initTimePointsAndGCDAfterConnect();
}
_readyState = readyState;
}
@ -234,6 +233,23 @@ namespace ix
_onCloseCallback = onCloseCallback;
}
void WebSocketTransport::initTimePointsAndGCDAfterConnect()
{
{
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
{
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
}
if (_pingIntervalOrTimeoutGCDSecs > 0)
{
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
}
}
// Only consider send PING time points for that computation.
bool WebSocketTransport::pingIntervalExceeded()
{
@ -303,6 +319,10 @@ namespace ix
}
}
#ifdef _WIN32
if (lastingTimeoutDelayInMs <= 0) lastingTimeoutDelayInMs = 20;
#endif
// poll the socket
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);

View File

@ -220,6 +220,8 @@ namespace ix
// after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion
bool closingDelayExceeded();
void initTimePointsAndGCDAfterConnect();
void sendCloseFrame(uint16_t code, const std::string& reason);
void closeSocketAndSwitchToClosedState(uint16_t code,

7
test/.gitignore vendored
View File

@ -1,9 +1,10 @@
CMakeCache.txt
package-lock.json
CMakeFiles
ixwebsocket_unittest
cmake_install.cmake
CMakeFiles
ixwebsocket_unittest
cmake_install.cmake
node_modules
ixwebsocket
Makefile
build
ixwebsocket_unittest.xml

View File

@ -5,12 +5,11 @@
cmake_minimum_required (VERSION 3.4.1)
project (ixwebsocket_unittest)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
find_package(Sanitizers)
set (CMAKE_CXX_STANDARD 14)
if (NOT WIN32)
if (UNIX)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
find_package(Sanitizers)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
option(USE_TLS "Add TLS support" ON)
@ -39,18 +38,24 @@ set (SOURCES
IXWebSocketPingTest.cpp
IXWebSocketTestConnectionDisconnection.cpp
IXUrlParserTest.cpp
IXWebSocketServerTest.cpp
IXWebSocketPingTest.cpp
)
# Some unittest don't work on windows yet
if (NOT WIN32)
if (UNIX)
list(APPEND SOURCES
IXWebSocketPingTimeoutTest.cpp
# IXWebSocketPingTimeoutTest.cpp # This test isn't reliable # (multiple platforms), disabling in master
# IXWebSocketCloseTest.cpp #
cmd_websocket_chat.cpp
)
endif()
add_executable(ixwebsocket_unittest ${SOURCES})
add_sanitizers(ixwebsocket_unittest)
if (UNIX)
add_sanitizers(ixwebsocket_unittest)
endif()
if (APPLE AND USE_TLS)
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")

View File

@ -0,0 +1,407 @@
/*
* IXWebSocketCloseTest.cpp
* Author: Alexandre Konieczny
* Copyright (c) 2019 Machine Zone. All rights reserved.
*/
#include <iostream>
#include <sstream>
#include <queue>
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXTest.h"
#include "catch.hpp"
using namespace ix;
namespace
{
class WebSocketClient
{
public:
WebSocketClient(int port);
void subscribe(const std::string& channel);
void start();
void stop();
void stop(uint16_t code, const std::string& reason);
bool isReady() const;
void sendMessage(const std::string& text);
uint16_t getCloseCode();
const std::string& getCloseReason();
bool getCloseRemote();
private:
ix::WebSocket _webSocket;
int _port;
mutable std::mutex _mutexCloseData;
uint16_t _closeCode;
std::string _closeReason;
bool _closeRemote;
};
WebSocketClient::WebSocketClient(int port)
: _port(port)
, _closeCode(0)
, _closeReason(std::string(""))
, _closeRemote(false)
{
;
}
bool WebSocketClient::isReady() const
{
return _webSocket.getReadyState() == ix::ReadyState::Open;
}
uint16_t WebSocketClient::getCloseCode()
{
std::lock_guard<std::mutex> lck(_mutexCloseData);
return _closeCode;
}
const std::string& WebSocketClient::getCloseReason()
{
std::lock_guard<std::mutex> lck(_mutexCloseData);
return _closeReason;
}
bool WebSocketClient::getCloseRemote()
{
std::lock_guard<std::mutex> lck(_mutexCloseData);
return _closeRemote;
}
void WebSocketClient::stop()
{
_webSocket.stop();
}
void WebSocketClient::stop(uint16_t code, const std::string& reason)
{
_webSocket.close(code, reason);
_webSocket.stop();
}
void WebSocketClient::start()
{
std::string url;
{
std::stringstream ss;
ss << "ws://localhost:"
<< _port
<< "/";
url = ss.str();
}
_webSocket.setUrl(url);
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
_webSocket.setOnMessageCallback(
[this](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
std::stringstream ss;
if (messageType == ix::WebSocketMessageType::Open)
{
log("client connected");
_webSocket.disableAutomaticReconnection();
}
else if (messageType == ix::WebSocketMessageType::Close)
{
log("client disconnected");
std::lock_guard<std::mutex> lck(_mutexCloseData);
_closeCode = closeInfo.code;
_closeReason = std::string(closeInfo.reason);
_closeRemote = closeInfo.remote;
_webSocket.disableAutomaticReconnection();
}
else if (messageType == ix::WebSocketMessageType::Error)
{
ss << "Error ! " << error.reason;
log(ss.str());
_webSocket.disableAutomaticReconnection();
}
else if (messageType == ix::WebSocketMessageType::Pong)
{
ss << "Received pong message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Ping)
{
ss << "Received ping message " << str;
log(ss.str());
}
else if (messageType == ix::WebSocketMessageType::Message)
{
ss << "Received message " << str;
log(ss.str());
}
else
{
ss << "Invalid ix::WebSocketMessageType";
log(ss.str());
}
});
_webSocket.start();
}
void WebSocketClient::sendMessage(const std::string& text)
{
_webSocket.send(text);
}
bool startServer(ix::WebSocketServer& server,
uint16_t& receivedCloseCode,
std::string& receivedCloseReason,
bool& receivedCloseRemote,
std::mutex& mutexWrite)
{
// A dev/null server
server.setOnConnectionCallback(
[&server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](std::shared_ptr<ix::WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
{
if (messageType == ix::WebSocketMessageType::Open)
{
Logger() << "New server connection";
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Headers:";
for (auto it : openInfo.headers)
{
Logger() << it.first << ": " << it.second;
}
}
else if (messageType == ix::WebSocketMessageType::Close)
{
log("Server closed connection");
//Logger() << closeInfo.code;
//Logger() << closeInfo.reason;
//Logger() << closeInfo.remote;
std::lock_guard<std::mutex> lck(mutexWrite);
receivedCloseCode = closeInfo.code;
receivedCloseReason = std::string(closeInfo.reason);
receivedCloseRemote = closeInfo.remote;
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
log(res.second);
return false;
}
server.start();
return true;
}
}
TEST_CASE("Websocket_client_close_default", "[close]")
{
SECTION("Make sure that close code and reason was used and sent to server.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
uint16_t serverReceivedCloseCode(0);
bool serverReceivedCloseRemote(false);
std::string serverReceivedCloseReason("");
std::mutex mutexWrite;
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
std::string session = ix::generateSessionId();
WebSocketClient webSocketClient(port);
webSocketClient.start();
// Wait for all chat instance to be ready
while (true)
{
if (webSocketClient.isReady()) break;
ix::msleep(10);
}
REQUIRE(server.getClients().size() == 1);
ix::msleep(100);
webSocketClient.stop();
ix::msleep(200);
// ensure client close is the same as values given
REQUIRE(webSocketClient.getCloseCode() == 1000);
REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
REQUIRE(webSocketClient.getCloseRemote() == false);
{
std::lock_guard<std::mutex> lck(mutexWrite);
// Here we read the code/reason received by the server, and ensure that remote is true
REQUIRE(serverReceivedCloseCode == 1000);
REQUIRE(serverReceivedCloseReason == "Normal closure");
REQUIRE(serverReceivedCloseRemote == true);
}
// Give us 1000ms for the server to notice that clients went away
ix::msleep(1000);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_client_close_params_given", "[close]")
{
SECTION("Make sure that close code and reason was used and sent to server.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
uint16_t serverReceivedCloseCode(0);
bool serverReceivedCloseRemote(false);
std::string serverReceivedCloseReason("");
std::mutex mutexWrite;
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
std::string session = ix::generateSessionId();
WebSocketClient webSocketClient(port);
webSocketClient.start();
// Wait for all chat instance to be ready
while (true)
{
if (webSocketClient.isReady()) break;
ix::msleep(10);
}
REQUIRE(server.getClients().size() == 1);
ix::msleep(100);
webSocketClient.stop(4000, "My reason");
ix::msleep(500);
// ensure client close is the same as values given
REQUIRE(webSocketClient.getCloseCode() == 4000);
REQUIRE(webSocketClient.getCloseReason() == "My reason");
REQUIRE(webSocketClient.getCloseRemote() == false);
{
std::lock_guard<std::mutex> lck(mutexWrite);
// Here we read the code/reason received by the server, and ensure that remote is true
REQUIRE(serverReceivedCloseCode == 4000);
REQUIRE(serverReceivedCloseReason == "My reason");
REQUIRE(serverReceivedCloseRemote == true);
}
// Give us 1000ms for the server to notice that clients went away
ix::msleep(1000);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}
TEST_CASE("Websocket_server_close", "[close]")
{
SECTION("Make sure that close code and reason was read from server.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = getFreePort();
ix::WebSocketServer server(port);
uint16_t serverReceivedCloseCode(0);
bool serverReceivedCloseRemote(false);
std::string serverReceivedCloseReason("");
std::mutex mutexWrite;
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
std::string session = ix::generateSessionId();
WebSocketClient webSocketClient(port);
webSocketClient.start();
// Wait for all chat instance to be ready
while (true)
{
if (webSocketClient.isReady()) break;
ix::msleep(10);
}
REQUIRE(server.getClients().size() == 1);
ix::msleep(200);
server.stop();
ix::msleep(500);
// ensure client close is the same as values given
REQUIRE(webSocketClient.getCloseCode() == 1000);
REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
REQUIRE(webSocketClient.getCloseRemote() == true);
{
std::lock_guard<std::mutex> lck(mutexWrite);
// Here we read the code/reason received by the server, and ensure that remote is true
REQUIRE(serverReceivedCloseCode == 1000);
REQUIRE(serverReceivedCloseReason == "Normal closure");
REQUIRE(serverReceivedCloseRemote == false);
}
// Give us 1000ms for the server to notice that clients went away
ix::msleep(1000);
REQUIRE(server.getClients().size() == 0);
ix::reportWebSocketTraffic();
}
}

View File

@ -413,13 +413,13 @@ TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod
REQUIRE(server.getClients().size() == 1);
ix::msleep(1850);
ix::msleep(1900);
webSocketClient.stop();
// Here we test ping interval
// -> expected ping messages == 1 as 1850 seconds, 1 ping sent every second
// -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 1);
// Give us 500ms for the server to notice that clients went away
@ -460,7 +460,7 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
webSocketClient.sendMessage("hello world");
ix::msleep(900);
webSocketClient.sendMessage("hello world");
ix::msleep(900);
ix::msleep(1100);
webSocketClient.stop();
@ -469,7 +469,7 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
// Here we test ping interval
// client has sent data, but ping should have been sent no matter what
// -> expected ping messages == 2 as 900+900+900 = 2700 seconds, 1 ping sent every second
// -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second
REQUIRE(serverReceivedPingMessages == 2);
// Give us 500ms for the server to notice that clients went away

View File

@ -350,7 +350,7 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
REQUIRE(server.getClients().size() == 1);
ix::msleep(2700);
ix::msleep(2900);
// Here we test ping timeout, no timeout yet
REQUIRE(serverReceivedPingMessages == 0);
@ -359,7 +359,7 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
REQUIRE(webSocketClient.isClosed() == false);
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
ix::msleep(400);
ix::msleep(200);
// Here we test ping timeout, timeout
REQUIRE(serverReceivedPingMessages == 0);
@ -410,7 +410,7 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]")
REQUIRE(serverReceivedPingMessages == 1);
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
ix::msleep(1000);
ix::msleep(1100);
// Here we test ping timeout, timeout
REQUIRE(serverReceivedPingMessages == 1);

View File

@ -28,9 +28,9 @@ try:
except ImportError:
hasClick = False
DEFAULT_EXE = 'ixwebsocket_unittest'
BUILD_TYPE = 'Debug'
XML_OUTPUT_FILE = 'ixwebsocket_unittest.xml'
TEST_EXE_PATH = None
class Command(object):
"""Run system commands with timeout
@ -65,7 +65,7 @@ class Command(object):
return True, self.process.returncode
def runCommand(cmd, assertOnFailure=True, timeout=None):
def runCommand(cmd, abortOnFailure=True, timeout=None):
'''Small wrapper to run a command and make sure it succeed'''
if timeout is None:
@ -73,16 +73,13 @@ def runCommand(cmd, assertOnFailure=True, timeout=None):
print('\nRunning', cmd)
command = Command(cmd)
timedout, ret = command.run(timeout)
succeed, ret = command.run(timeout)
if timedout:
print('Unittest timed out')
msg = 'cmd {} failed with error code {}'.format(cmd, ret)
if ret != 0:
if not succeed or ret != 0:
msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret)
print(msg)
if assertOnFailure:
assert False
if abortOnFailure:
sys.exit(-1)
def runCMake(sanitizer, buildDir):
@ -91,12 +88,6 @@ def runCMake(sanitizer, buildDir):
(remove build sub-folder).
'''
# CMake installed via Self Service ends up here.
cmake_executable = '/Applications/CMake.app/Contents/bin/cmake'
if not os.path.exists(cmake_executable):
cmake_executable = 'cmake'
sanitizersFlags = {
'asan': '-DSANITIZE_ADDRESS=On',
'ubsan': '-DSANITIZE_UNDEFINED=On',
@ -110,19 +101,22 @@ def runCMake(sanitizer, buildDir):
if not os.path.exists(cmakeExecutable):
cmakeExecutable = 'cmake'
generator = '"Unix Makefiles"'
if platform.system() == 'Windows':
generator = '"NMake Makefiles"'
#generator = '"NMake Makefiles"'
generator = '"Visual Studio 16 2019"'
else:
generator = '"Unix Makefiles"'
fmt = '''
{cmakeExecutable} -H. \
CMAKE_BUILD_TYPE = BUILD_TYPE
fmt = '{cmakeExecutable} -H. \
{sanitizerFlag} \
-B{buildDir} \
-DCMAKE_BUILD_TYPE=Debug \
-B"{buildDir}" \
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
-DUSE_TLS=1 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-G{generator}
'''
-G{generator}'
cmakeCmd = fmt.format(**locals())
runCommand(cmakeCmd)
@ -133,10 +127,10 @@ def runTest(args, buildDir, xmlOutput, testRunName):
if args is None:
args = ''
fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"'
testCommand = fmt.format(**locals())
testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args)
runCommand(testCommand,
assertOnFailure=False)
abortOnFailure=False)
def validateTestSuite(xmlOutput):
@ -296,8 +290,7 @@ def executeJobs(jobs):
def computeAllTestNames(buildDir):
'''Compute all test case names, by executing the unittest in a custom mode'''
executable = os.path.join(buildDir, DEFAULT_EXE)
cmd = '"{}" --list-test-names-only'.format(executable)
cmd = '"{}" --list-test-names-only'.format(TEST_EXE_PATH)
names = os.popen(cmd).read().splitlines()
names.sort() # Sort test names for execution determinism
return names
@ -344,7 +337,7 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime):
})
systemOut = ET.Element('system-out')
systemOut.text = result['output'].decode('utf-8')
systemOut.text = result['output'].decode('utf-8', 'ignore')
testCase.append(systemOut)
if not result['success']:
@ -365,16 +358,19 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
runCMake(sanitizer, buildDir)
# build with make
makeCmd = 'make'
jobs = '-j8'
#makeCmd = 'cmake --build '
#jobs = '-j8'
if platform.system() == 'Windows':
makeCmd = 'nmake'
#if platform.system() == 'Windows':
# makeCmd = 'nmake'
# nmake does not have a -j option
jobs = ''
# jobs = ''
runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs))
#runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs))
# build with cmake
runCommand('cmake --build ' + buildDir)
if buildOnly:
return
@ -409,12 +405,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
continue
# testName can contains spaces, so we enclose them in double quotes
executable = os.path.join(buildDir, DEFAULT_EXE)
if platform.system() == 'Windows':
executable += '.exe'
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, executable, testName, outputPath)
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, TEST_EXE_PATH, testName, outputPath)
jobs.append({
'name': testName,
@ -454,8 +445,6 @@ def main():
if not os.path.exists(buildDir):
os.makedirs(buildDir)
defaultOutput = DEFAULT_EXE + '.xml'
parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
sanitizers = ['tsan', 'asan', 'ubsan', 'none']
@ -481,14 +470,29 @@ def main():
# Default sanitizer is tsan
sanitizer = args.sanitizer
if args.sanitizer is None:
if args.no_sanitizer:
sanitizer = 'none'
elif args.sanitizer is None:
sanitizer = 'tsan'
# Sanitizers display lots of strange errors on Linux on CI,
# which looks like false positives
if platform.system() != 'Darwin':
sanitizer = 'none'
defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer)
xmlOutput = args.output or defaultOutput
xmlOutput = args.output or XML_OUTPUT_FILE
testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName
global TEST_EXE_PATH
if platform.system() == 'Windows':
TEST_EXE_PATH = os.path.join(buildDir, BUILD_TYPE, 'ixwebsocket_unittest.exe')
else:
TEST_EXE_PATH = os.path.join(buildDir, 'ixwebsocket_unittest')
if args.list:
# catch2 exit with a different error code when requesting the list of files
try:
@ -505,11 +509,6 @@ def main():
print('LLDB is only supported on Apple at this point')
args.lldb = False
# Sanitizers display lots of strange errors on Linux on CI,
# which looks like false positives
if platform.system() != 'Darwin':
sanitizer = 'none'
return run(args.test, buildDir, sanitizer, xmlOutput,
testRunName, args.build_only, args.lldb)