Compare commits
9 Commits
v2.0.0
...
feature/se
Author | SHA1 | Date | |
---|---|---|---|
1ac02fdc0e | |||
687956358d | |||
1a42c92325 | |||
6bb00b6788 | |||
12f6cd878d | |||
9aacebbbaf | |||
701c3745c2 | |||
156288b17b | |||
ed0e23e8a5 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
*.pyc
|
||||||
|
@ -1 +1 @@
|
|||||||
1.5.3
|
2.0.0
|
||||||
|
@ -1 +1 @@
|
|||||||
docker/Dockerfile.fedora
|
docker/Dockerfile.ubuntu_xenial
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SelectInterruptEventFd : public SelectInterrupt {
|
class SelectInterruptEventFd final : public SelectInterrupt {
|
||||||
public:
|
public:
|
||||||
SelectInterruptEventFd();
|
SelectInterruptEventFd();
|
||||||
virtual ~SelectInterruptEventFd();
|
virtual ~SelectInterruptEventFd();
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SelectInterruptPipe : public SelectInterrupt {
|
class SelectInterruptPipe final : public SelectInterrupt {
|
||||||
public:
|
public:
|
||||||
SelectInterruptPipe();
|
SelectInterruptPipe();
|
||||||
virtual ~SelectInterruptPipe();
|
virtual ~SelectInterruptPipe();
|
||||||
|
@ -73,7 +73,7 @@ namespace ix
|
|||||||
|
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
timeout.tv_sec = timeoutMs / 1000;
|
timeout.tv_sec = timeoutMs / 1000;
|
||||||
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
|
timeout.tv_usec = 1000 * (timeoutMs % 1000);
|
||||||
|
|
||||||
// Compute the highest fd.
|
// Compute the highest fd.
|
||||||
int sockfd = _sockfd;
|
int sockfd = _sockfd;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketAppleSSL : public Socket
|
class SocketAppleSSL final : public Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SocketAppleSSL(int fd = -1);
|
SocketAppleSSL(int fd = -1);
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketOpenSSL : public Socket
|
class SocketOpenSSL final : public Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SocketOpenSSL(int fd = -1);
|
SocketOpenSSL(int fd = -1);
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketSChannel : public Socket
|
class SocketSChannel final : public Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SocketSChannel();
|
SocketSChannel();
|
||||||
|
@ -30,7 +30,9 @@ namespace ix
|
|||||||
_host(host),
|
_host(host),
|
||||||
_backlog(backlog),
|
_backlog(backlog),
|
||||||
_maxConnections(maxConnections),
|
_maxConnections(maxConnections),
|
||||||
|
_serverFd(-1),
|
||||||
_stop(false),
|
_stop(false),
|
||||||
|
_stopGc(false),
|
||||||
_connectionStateFactory(&ConnectionState::createConnectionState)
|
_connectionStateFactory(&ConnectionState::createConnectionState)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -124,9 +126,15 @@ namespace ix
|
|||||||
|
|
||||||
void SocketServer::start()
|
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()
|
void SocketServer::wait()
|
||||||
@ -142,21 +150,21 @@ namespace ix
|
|||||||
|
|
||||||
void SocketServer::stop()
|
void SocketServer::stop()
|
||||||
{
|
{
|
||||||
while (true)
|
// Stop accepting connections, and close the 'accept' thread
|
||||||
|
if (_thread.joinable())
|
||||||
{
|
{
|
||||||
if (closeTerminatedThreads()) break;
|
_stop = true;
|
||||||
|
_thread.join();
|
||||||
// wait 10ms and try again later.
|
_stop = false;
|
||||||
// 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_thread.joinable()) return; // nothing to do
|
// Join all threads and make sure that all connections are terminated
|
||||||
|
if (_gcThread.joinable())
|
||||||
_stop = true;
|
{
|
||||||
_thread.join();
|
_stopGc = true;
|
||||||
_stop = false;
|
_gcThread.join();
|
||||||
|
_stopGc = false;
|
||||||
|
}
|
||||||
|
|
||||||
_conditionVariable.notify_one();
|
_conditionVariable.notify_one();
|
||||||
Socket::closeSocket(_serverFd);
|
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
|
// 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).
|
// and remove it from our _connectionsThreads data structure (a list).
|
||||||
//
|
//
|
||||||
bool SocketServer::closeTerminatedThreads()
|
void SocketServer::closeTerminatedThreads()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||||
auto it = _connectionsThreads.begin();
|
auto it = _connectionsThreads.begin();
|
||||||
@ -195,8 +203,6 @@ namespace ix
|
|||||||
if (thread.joinable()) thread.join();
|
if (thread.joinable()) thread.join();
|
||||||
it = _connectionsThreads.erase(it);
|
it = _connectionsThreads.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _connectionsThreads.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SocketServer::run()
|
void SocketServer::run()
|
||||||
@ -208,12 +214,6 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (_stop) return;
|
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
|
// Use select to check whether a new connection is in progress
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
@ -290,5 +290,30 @@ namespace ix
|
|||||||
connectionState)));
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,12 @@ namespace ix
|
|||||||
// background thread to wait for incoming connections
|
// background thread to wait for incoming connections
|
||||||
std::atomic<bool> _stop;
|
std::atomic<bool> _stop;
|
||||||
std::thread _thread;
|
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
|
// the list of (connectionState, threads) for each connections
|
||||||
ConnectionThreads _connectionsThreads;
|
ConnectionThreads _connectionsThreads;
|
||||||
@ -87,13 +93,12 @@ namespace ix
|
|||||||
// the factory to create ConnectionState objects
|
// the factory to create ConnectionState objects
|
||||||
ConnectionStateFactory _connectionStateFactory;
|
ConnectionStateFactory _connectionStateFactory;
|
||||||
|
|
||||||
// Methods
|
|
||||||
void run();
|
|
||||||
virtual void handleConnection(int fd,
|
virtual void handleConnection(int fd,
|
||||||
std::shared_ptr<ConnectionState> connectionState) = 0;
|
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||||
virtual size_t getConnectedClientsCount() = 0;
|
virtual size_t getConnectedClientsCount() = 0;
|
||||||
|
|
||||||
// Returns true if all connection threads are joined
|
// Returns true if all connection threads are joined
|
||||||
bool closeTerminatedThreads();
|
void closeTerminatedThreads();
|
||||||
|
size_t getConnectionsThreadsCount();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -212,9 +212,10 @@ namespace ix
|
|||||||
return getReadyState() == ReadyState::Closing;
|
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)
|
void WebSocket::checkConnection(bool firstConnectionAttempt)
|
||||||
@ -223,7 +224,6 @@ namespace ix
|
|||||||
|
|
||||||
uint32_t retries = 0;
|
uint32_t retries = 0;
|
||||||
millis duration;
|
millis duration;
|
||||||
ix::WebSocketInitResult status;
|
|
||||||
|
|
||||||
// Try to connect perpertually
|
// Try to connect perpertually
|
||||||
while (true)
|
while (true)
|
||||||
@ -249,7 +249,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to connect synchronously
|
// Try to connect synchronously
|
||||||
status = connect(_handshakeTimeoutSecs);
|
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
|
||||||
|
|
||||||
if (!status.success)
|
if (!status.success)
|
||||||
{
|
{
|
||||||
|
@ -112,7 +112,11 @@ namespace ix
|
|||||||
WebSocketSendInfo sendText(const std::string& text,
|
WebSocketSendInfo sendText(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
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);
|
void setOnMessageCallback(const OnMessageCallback& callback);
|
||||||
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
||||||
|
@ -23,7 +23,7 @@ namespace ix
|
|||||||
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
|
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
|
||||||
std::shared_ptr<ConnectionState>)>;
|
std::shared_ptr<ConnectionState>)>;
|
||||||
|
|
||||||
class WebSocketServer : public SocketServer {
|
class WebSocketServer final : public SocketServer {
|
||||||
public:
|
public:
|
||||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
|
@ -134,11 +134,6 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
|
_pingIntervalOrTimeoutGCDSecs = pingIntervalSecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pingIntervalOrTimeoutGCDSecs > 0)
|
|
||||||
{
|
|
||||||
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client
|
// Client
|
||||||
@ -225,6 +220,10 @@ namespace ix
|
|||||||
_closeWireSize = 0;
|
_closeWireSize = 0;
|
||||||
_closeRemote = false;
|
_closeRemote = false;
|
||||||
}
|
}
|
||||||
|
else if (readyState == ReadyState::OPEN)
|
||||||
|
{
|
||||||
|
initTimePointsAndGCDAfterConnect();
|
||||||
|
}
|
||||||
|
|
||||||
_readyState = readyState;
|
_readyState = readyState;
|
||||||
}
|
}
|
||||||
@ -234,6 +233,23 @@ namespace ix
|
|||||||
_onCloseCallback = onCloseCallback;
|
_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.
|
// Only consider send PING time points for that computation.
|
||||||
bool WebSocketTransport::pingIntervalExceeded()
|
bool WebSocketTransport::pingIntervalExceeded()
|
||||||
{
|
{
|
||||||
@ -303,6 +319,10 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (lastingTimeoutDelayInMs <= 0) lastingTimeoutDelayInMs = 20;
|
||||||
|
#endif
|
||||||
|
|
||||||
// poll the socket
|
// poll the socket
|
||||||
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
|
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
|
||||||
|
|
||||||
|
@ -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
|
// after calling close(), if no CLOSE frame answer is received back from the remote, we should close the connexion
|
||||||
bool closingDelayExceeded();
|
bool closingDelayExceeded();
|
||||||
|
|
||||||
|
void initTimePointsAndGCDAfterConnect();
|
||||||
|
|
||||||
void sendCloseFrame(uint16_t code, const std::string& reason);
|
void sendCloseFrame(uint16_t code, const std::string& reason);
|
||||||
|
|
||||||
void closeSocketAndSwitchToClosedState(uint16_t code,
|
void closeSocketAndSwitchToClosedState(uint16_t code,
|
||||||
|
7
test/.gitignore
vendored
7
test/.gitignore
vendored
@ -1,9 +1,10 @@
|
|||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
package-lock.json
|
package-lock.json
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
ixwebsocket_unittest
|
ixwebsocket_unittest
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
node_modules
|
node_modules
|
||||||
ixwebsocket
|
ixwebsocket
|
||||||
Makefile
|
Makefile
|
||||||
build
|
build
|
||||||
|
ixwebsocket_unittest.xml
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
cmake_minimum_required (VERSION 3.4.1)
|
cmake_minimum_required (VERSION 3.4.1)
|
||||||
project (ixwebsocket_unittest)
|
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)
|
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_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
option(USE_TLS "Add TLS support" ON)
|
option(USE_TLS "Add TLS support" ON)
|
||||||
@ -39,18 +38,24 @@ set (SOURCES
|
|||||||
IXWebSocketPingTest.cpp
|
IXWebSocketPingTest.cpp
|
||||||
IXWebSocketTestConnectionDisconnection.cpp
|
IXWebSocketTestConnectionDisconnection.cpp
|
||||||
IXUrlParserTest.cpp
|
IXUrlParserTest.cpp
|
||||||
|
IXWebSocketServerTest.cpp
|
||||||
|
IXWebSocketPingTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
if (NOT WIN32)
|
if (UNIX)
|
||||||
list(APPEND SOURCES
|
list(APPEND SOURCES
|
||||||
IXWebSocketPingTimeoutTest.cpp
|
# IXWebSocketPingTimeoutTest.cpp # This test isn't reliable # (multiple platforms), disabling in master
|
||||||
|
# IXWebSocketCloseTest.cpp #
|
||||||
cmd_websocket_chat.cpp
|
cmd_websocket_chat.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(ixwebsocket_unittest ${SOURCES})
|
add_executable(ixwebsocket_unittest ${SOURCES})
|
||||||
add_sanitizers(ixwebsocket_unittest)
|
|
||||||
|
if (UNIX)
|
||||||
|
add_sanitizers(ixwebsocket_unittest)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
if (APPLE AND USE_TLS)
|
||||||
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
|
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
|
||||||
|
407
test/IXWebSocketCloseTest.cpp
Normal file
407
test/IXWebSocketCloseTest.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -413,13 +413,13 @@ TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod
|
|||||||
|
|
||||||
REQUIRE(server.getClients().size() == 1);
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
ix::msleep(1850);
|
ix::msleep(1900);
|
||||||
|
|
||||||
webSocketClient.stop();
|
webSocketClient.stop();
|
||||||
|
|
||||||
|
|
||||||
// Here we test ping interval
|
// 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);
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// 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");
|
webSocketClient.sendMessage("hello world");
|
||||||
ix::msleep(900);
|
ix::msleep(900);
|
||||||
webSocketClient.sendMessage("hello world");
|
webSocketClient.sendMessage("hello world");
|
||||||
ix::msleep(900);
|
ix::msleep(1100);
|
||||||
|
|
||||||
webSocketClient.stop();
|
webSocketClient.stop();
|
||||||
|
|
||||||
@ -469,7 +469,7 @@ TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
|||||||
|
|
||||||
// Here we test ping interval
|
// Here we test ping interval
|
||||||
// client has sent data, but ping should have been sent no matter what
|
// 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);
|
REQUIRE(serverReceivedPingMessages == 2);
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 500ms for the server to notice that clients went away
|
||||||
|
@ -350,7 +350,7 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
|
|||||||
|
|
||||||
REQUIRE(server.getClients().size() == 1);
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
ix::msleep(2700);
|
ix::msleep(2900);
|
||||||
|
|
||||||
// Here we test ping timeout, no timeout yet
|
// Here we test ping timeout, no timeout yet
|
||||||
REQUIRE(serverReceivedPingMessages == 0);
|
REQUIRE(serverReceivedPingMessages == 0);
|
||||||
@ -359,7 +359,7 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
|
|||||||
REQUIRE(webSocketClient.isClosed() == false);
|
REQUIRE(webSocketClient.isClosed() == false);
|
||||||
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
|
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
|
||||||
|
|
||||||
ix::msleep(400);
|
ix::msleep(200);
|
||||||
|
|
||||||
// Here we test ping timeout, timeout
|
// Here we test ping timeout, timeout
|
||||||
REQUIRE(serverReceivedPingMessages == 0);
|
REQUIRE(serverReceivedPingMessages == 0);
|
||||||
@ -410,7 +410,7 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]")
|
|||||||
REQUIRE(serverReceivedPingMessages == 1);
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
||||||
|
|
||||||
ix::msleep(1000);
|
ix::msleep(1100);
|
||||||
|
|
||||||
// Here we test ping timeout, timeout
|
// Here we test ping timeout, timeout
|
||||||
REQUIRE(serverReceivedPingMessages == 1);
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
|
105
test/run.py
105
test/run.py
@ -28,9 +28,9 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
hasClick = False
|
hasClick = False
|
||||||
|
|
||||||
|
BUILD_TYPE = 'Debug'
|
||||||
DEFAULT_EXE = 'ixwebsocket_unittest'
|
XML_OUTPUT_FILE = 'ixwebsocket_unittest.xml'
|
||||||
|
TEST_EXE_PATH = None
|
||||||
|
|
||||||
class Command(object):
|
class Command(object):
|
||||||
"""Run system commands with timeout
|
"""Run system commands with timeout
|
||||||
@ -65,7 +65,7 @@ class Command(object):
|
|||||||
return True, self.process.returncode
|
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'''
|
'''Small wrapper to run a command and make sure it succeed'''
|
||||||
|
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
@ -73,16 +73,13 @@ def runCommand(cmd, assertOnFailure=True, timeout=None):
|
|||||||
|
|
||||||
print('\nRunning', cmd)
|
print('\nRunning', cmd)
|
||||||
command = Command(cmd)
|
command = Command(cmd)
|
||||||
timedout, ret = command.run(timeout)
|
succeed, ret = command.run(timeout)
|
||||||
|
|
||||||
if timedout:
|
if not succeed or ret != 0:
|
||||||
print('Unittest timed out')
|
msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret)
|
||||||
|
|
||||||
msg = 'cmd {} failed with error code {}'.format(cmd, ret)
|
|
||||||
if ret != 0:
|
|
||||||
print(msg)
|
print(msg)
|
||||||
if assertOnFailure:
|
if abortOnFailure:
|
||||||
assert False
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
def runCMake(sanitizer, buildDir):
|
def runCMake(sanitizer, buildDir):
|
||||||
@ -91,12 +88,6 @@ def runCMake(sanitizer, buildDir):
|
|||||||
(remove build sub-folder).
|
(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 = {
|
sanitizersFlags = {
|
||||||
'asan': '-DSANITIZE_ADDRESS=On',
|
'asan': '-DSANITIZE_ADDRESS=On',
|
||||||
'ubsan': '-DSANITIZE_UNDEFINED=On',
|
'ubsan': '-DSANITIZE_UNDEFINED=On',
|
||||||
@ -110,19 +101,22 @@ def runCMake(sanitizer, buildDir):
|
|||||||
if not os.path.exists(cmakeExecutable):
|
if not os.path.exists(cmakeExecutable):
|
||||||
cmakeExecutable = 'cmake'
|
cmakeExecutable = 'cmake'
|
||||||
|
|
||||||
generator = '"Unix Makefiles"'
|
|
||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
generator = '"NMake Makefiles"'
|
#generator = '"NMake Makefiles"'
|
||||||
|
generator = '"Visual Studio 16 2019"'
|
||||||
|
else:
|
||||||
|
generator = '"Unix Makefiles"'
|
||||||
|
|
||||||
fmt = '''
|
CMAKE_BUILD_TYPE = BUILD_TYPE
|
||||||
{cmakeExecutable} -H. \
|
|
||||||
|
fmt = '{cmakeExecutable} -H. \
|
||||||
{sanitizerFlag} \
|
{sanitizerFlag} \
|
||||||
-B{buildDir} \
|
-B"{buildDir}" \
|
||||||
-DCMAKE_BUILD_TYPE=Debug \
|
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
|
||||||
-DUSE_TLS=1 \
|
-DUSE_TLS=1 \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
-G{generator}
|
-G{generator}'
|
||||||
'''
|
|
||||||
cmakeCmd = fmt.format(**locals())
|
cmakeCmd = fmt.format(**locals())
|
||||||
runCommand(cmakeCmd)
|
runCommand(cmakeCmd)
|
||||||
|
|
||||||
@ -133,10 +127,10 @@ def runTest(args, buildDir, xmlOutput, testRunName):
|
|||||||
if args is None:
|
if args is None:
|
||||||
args = ''
|
args = ''
|
||||||
|
|
||||||
fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"'
|
testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args)
|
||||||
testCommand = fmt.format(**locals())
|
|
||||||
runCommand(testCommand,
|
runCommand(testCommand,
|
||||||
assertOnFailure=False)
|
abortOnFailure=False)
|
||||||
|
|
||||||
|
|
||||||
def validateTestSuite(xmlOutput):
|
def validateTestSuite(xmlOutput):
|
||||||
@ -296,8 +290,7 @@ def executeJobs(jobs):
|
|||||||
def computeAllTestNames(buildDir):
|
def computeAllTestNames(buildDir):
|
||||||
'''Compute all test case names, by executing the unittest in a custom mode'''
|
'''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(TEST_EXE_PATH)
|
||||||
cmd = '"{}" --list-test-names-only'.format(executable)
|
|
||||||
names = os.popen(cmd).read().splitlines()
|
names = os.popen(cmd).read().splitlines()
|
||||||
names.sort() # Sort test names for execution determinism
|
names.sort() # Sort test names for execution determinism
|
||||||
return names
|
return names
|
||||||
@ -344,7 +337,7 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime):
|
|||||||
})
|
})
|
||||||
|
|
||||||
systemOut = ET.Element('system-out')
|
systemOut = ET.Element('system-out')
|
||||||
systemOut.text = result['output'].decode('utf-8')
|
systemOut.text = result['output'].decode('utf-8', 'ignore')
|
||||||
testCase.append(systemOut)
|
testCase.append(systemOut)
|
||||||
|
|
||||||
if not result['success']:
|
if not result['success']:
|
||||||
@ -365,16 +358,19 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
|
|||||||
runCMake(sanitizer, buildDir)
|
runCMake(sanitizer, buildDir)
|
||||||
|
|
||||||
# build with make
|
# build with make
|
||||||
makeCmd = 'make'
|
#makeCmd = 'cmake --build '
|
||||||
jobs = '-j8'
|
#jobs = '-j8'
|
||||||
|
|
||||||
if platform.system() == 'Windows':
|
#if platform.system() == 'Windows':
|
||||||
makeCmd = 'nmake'
|
# makeCmd = 'nmake'
|
||||||
|
|
||||||
# nmake does not have a -j option
|
# 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:
|
if buildOnly:
|
||||||
return
|
return
|
||||||
@ -409,12 +405,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# testName can contains spaces, so we enclose them in double quotes
|
# testName can contains spaces, so we enclose them in double quotes
|
||||||
executable = os.path.join(buildDir, DEFAULT_EXE)
|
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, TEST_EXE_PATH, testName, outputPath)
|
||||||
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
executable += '.exe'
|
|
||||||
|
|
||||||
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, executable, testName, outputPath)
|
|
||||||
|
|
||||||
jobs.append({
|
jobs.append({
|
||||||
'name': testName,
|
'name': testName,
|
||||||
@ -454,8 +445,6 @@ def main():
|
|||||||
if not os.path.exists(buildDir):
|
if not os.path.exists(buildDir):
|
||||||
os.makedirs(buildDir)
|
os.makedirs(buildDir)
|
||||||
|
|
||||||
defaultOutput = DEFAULT_EXE + '.xml'
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
|
parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
|
||||||
|
|
||||||
sanitizers = ['tsan', 'asan', 'ubsan', 'none']
|
sanitizers = ['tsan', 'asan', 'ubsan', 'none']
|
||||||
@ -481,14 +470,29 @@ def main():
|
|||||||
|
|
||||||
# Default sanitizer is tsan
|
# Default sanitizer is tsan
|
||||||
sanitizer = args.sanitizer
|
sanitizer = args.sanitizer
|
||||||
if args.sanitizer is None:
|
|
||||||
|
if args.no_sanitizer:
|
||||||
|
sanitizer = 'none'
|
||||||
|
elif args.sanitizer is None:
|
||||||
sanitizer = 'tsan'
|
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)
|
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
|
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:
|
if args.list:
|
||||||
# catch2 exit with a different error code when requesting the list of files
|
# catch2 exit with a different error code when requesting the list of files
|
||||||
try:
|
try:
|
||||||
@ -505,11 +509,6 @@ def main():
|
|||||||
print('LLDB is only supported on Apple at this point')
|
print('LLDB is only supported on Apple at this point')
|
||||||
args.lldb = False
|
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,
|
return run(args.test, buildDir, sanitizer, xmlOutput,
|
||||||
testRunName, args.build_only, args.lldb)
|
testRunName, args.build_only, args.lldb)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user